使用lua 怎样获取web前端页面数据展示传来的密码

MQ可以使用如Apache ActiveMQ;
Worker/动态服务可以通过如Java技术实现;
RPC可以选择如alibaba Dubbo;
KV持久化存储可以选择SSDB(如果使用SSD盘则可以选择SSDB+RocksDB引擎)或者ARDB(LMDB引擎版);
缓存使用Redis;
SSDB/Redis分片使用如Twemproxy,这样不管使用Java还是Nginx+Lua,它们都不关心分片逻辑;
前端模板拼装使用Nginx+Lua;
数据集群数据存储的机器可以采用RAID技术或者主从模式防止单点故障;
因为数据变更不频繁,可以考虑SSD替代机械硬盘。
1、首先我们监听商品数据变更消息;
2、接收到消息后,数据聚合Worker通过RPC调用相关系统获取所有要展示的数据,此处获取数据的来源可能非常多而且响应速度完全受制于这些系统,可能耗时几百毫秒甚至上秒的时间;
3、将数据聚合为JSON串存储到相关数据集群;
4、前端Nginx通过Lua获取相关集群的数据进行展示;商品页需要获取基本信息+其他信息进行模板拼装,即拼装模板仅需要两次调用(另外因为其他信息数据量少且对一致性要求不高,因此我们完全可以缓存到Nginx本地全局内存,这样可以减少远程调用提高性能);当页面滚动到商品介绍页面时异步调用商品介绍服务获取数据;
5、如果从聚合的SSDB集群/Redis中获取不到相关数据;则回源到动态服务通过RPC调用相关系统获取所有要展示的数据返回(此处可以做限流处理,因为如果大量请求过来的话可能导致服务雪崩,需要采取保护措施),此处的逻辑和数据聚合Worker完全一样;然后发送MQ通知数据变更,这样下次访问时就可以从聚合的SSDB集群/Redis中获取数据了。
基本流程如上所述,主要分为Worker、动态服务、数据存储和前端展示;因为系统非常复杂,只介绍动态服务和前端展示、数据存储架构;Worker部分不做实现。
项目部署目录结构
/usr/chapter7
ssdb_basic_7770.conf
ssdb_basic_7771.conf
ssdb_basic_7772.conf
ssdb_basic_7773.conf
ssdb_desc_8880.conf
ssdb_desc_8881.conf
ssdb_desc_8882.conf
ssdb_desc_8883.conf
redis_other_6660.conf
redis_other_6661.conf
nginx_chapter7.conf
nutcracker.yml
nutcracker.init
header.html
footer.html
common.lua
数据存储实现
整体架构为主从模式,写数据到主集群,读数据从从集群读取数据,这样当一个集群不足以支撑流量时可以使用更多的集群来支撑更多的访问量;集群分片使用Twemproxy实现。
商品基本信息SSDB集群配置
vim /usr/chapter7/ssdb_basic_7770.conf
work_dir = /usr/data/ssdb_7770
pidfile = /usr/data/ssdb_7770.pid
ip: 0.0.0.0
port: 7770
allow: 127.0.0.1
allow: 192.168
replication:
binlog: yes
sync_speed: -1
level: error
output: /usr/data/ssdb_7770.log
cache_size: 500
block_size: 32
write_buffer_size: 64
compaction_speed: 1000
compression: yes
vim /usr/chapter7/ssdb_basic_7771.conf
work_dir = /usr/data/ssdb_7771
pidfile = /usr/data/ssdb_7771.pid
ip: 0.0.0.0
port: 7771
allow: 127.0.0.1
allow: 192.168
replication:
binlog: yes
sync_speed: -1
level: error
output: /usr/data/ssdb_7771.log
cache_size: 500
block_size: 32
write_buffer_size: 64
compaction_speed: 1000
compression: yes
vim /usr/chapter7/ssdb_basic_7773.conf
work_dir = /usr/data/ssdb_7773
pidfile = /usr/data/ssdb_7773.pid
ip: 0.0.0.0
port: 7773
allow: 127.0.0.1
allow: 192.168
replication:
binlog: yes
sync_speed: -1
type: sync
ip: 127.0.0.1
port: 7771
level: error
output: /usr/data/ssdb_7773.log
cache_size: 500
block_size: 32
write_buffer_size: 64
compaction_speed: 1000
compression: yes
配置文件使用Tab而不是空格做缩排,(复制到配置文件后请把空格替换为Tab)。主从关系:7770(主)–&7772(从),7771(主)—&7773(从);配置文件如何配置请参考。
创建工作目录
mkdir -p /usr/data/ssdb_7770
mkdir -p /usr/data/ssdb_7771
mkdir -p /usr/data/ssdb_7772
mkdir -p /usr/data/ssdb_7773
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/chapter7/ssdb_basic_7770.conf &
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/chapter7/ssdb_basic_7771.conf &
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/chapter7/ssdb_basic_7772.conf &
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/chapter7/ssdb_basic_7773.conf &
通过ps -aux | grep ssdb命令看是否启动了,tail -f nohup.out查看错误信息。
商品介绍SSDB集群配置
vim /usr/chapter7/ssdb_desc_8880.conf
work_dir = /usr/data/ssdb_8880
pidfile = /usr/data/ssdb8880.pid
ip: 0.0.0.0
port: 8880
allow: 127.0.0.1
allow: 192.168
replication:
binlog: yes
sync_speed: -1
level: error
output: /usr/data/ssdb_8880.log
cache_size: 500
block_size: 32
write_buffer_size: 64
compaction_speed: 1000
compression: yes
vim /usr/chapter7/ssdb_desc_8881.conf
work_dir = /usr/data/ssdb_8881
pidfile = /usr/data/ssdb8881.pid
ip: 0.0.0.0
port: 8881
allow: 127.0.0.1
allow: 192.168
level: error
output: /usr/data/ssdb_8881.log
cache_size: 500
block_size: 32
write_buffer_size: 64
compaction_speed: 1000
compression: yes
创建工作目录
mkdir -p /usr/data/ssdb_888{0,1,2,3}
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/chapter7/ssdb_desc_8880.conf &
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/chapter7/ssdb_desc_8881.conf &
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/chapter7/ssdb_desc_8882.conf &
nohup /usr/servers/ssdb-1.8.0/ssdb-server
/usr/chapter7/ssdb_desc_8883.conf &
其他信息Redis配置
pidfile "/var/run/redis_6660.pid"
#设置内存大小,根据实际情况设置,此处测试仅设置20mb
maxmemory 20mb
#内存不足时,所有KEY按照LRU算法删除
maxmemory-policy allkeys-lru
#Redis的过期算法不是精确的而是通过采样来算的,默认采样为3个,此处我们改成10
maxmemory-samples 10
#不进行RDB持久化
#不进行AOF持久化
appendonly no
如上配置放到配置文件最末尾即可;此处内存不足时的驱逐算法为所有KEY按照LRU进行删除(实际是内存基本上不会遇到满的情况);主从关系:6660(主)–&6661(从)和6660(主)–&6662(从)。
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/chapter7/redis_6660.conf &
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/chapter7/redis_6661.conf &
nohup /usr/servers/redis-2.8.19/src/redis-server /usr/chapter7/redis_6662.conf &
测试时在主SSDB/Redis中写入数据,然后从从SSDB/Redis能读取到数据即表示配置主从成功。
测试商品基本信息SSDB集群
root@kaitao:/usr/chapter7
127.0.0.1:7770& set i 1
127.0.0.1:7770&
root@kaitao:/usr/chapter7
127.0.0.1:7772& get i
Twemproxy配置
vim /usr/chapter7/nutcracker.yml
: 127.0.0.1:1111
127.0.0.1:7770:1
127.0.0.1:7771:1
: 127.0.0.1:1112
127.0.0.1:7772:1
127.0.0.1:7773:1
: 127.0.0.1:1113
127.0.0.1:8880:1
127.0.0.1:8881:1
: 127.0.0.1:1114
127.0.0.1:8882:1
127.0.0.1:8883:1
: 127.0.0.1:1115
127.0.0.1:6660:1
: 127.0.0.1:1116
127.0.0.1:6661:1
127.0.0.1:6662:1
1、因为我们使用了主从,所以需要给server起一个名字如server1、server2;否则分片算法默认根据ip:port:weight,这样就会主从数据的分片算法不一致;
2、其他信息Redis因为每个Redis是对等的,因此分片算法可以使用random;
3、我们使用了hash_tag,可以保证相同的tag在一个分片上(本例配置了但没有用到该特性)。
复制第六章的nutcracker.init,帮把配置文件改为usr/chapter7/nutcracker.yml。然后通过/usr/chapter7/nutcracker.init start启动Twemproxy。
测试主从集群是否工作正常:
root@kaitao:/usr/chapter7
127.0.0.1:1111& set i 1
127.0.0.1:1111&
root@kaitao:/usr/chapter7
127.0.0.1:1112& get i
127.0.0.1:1112&
root@kaitao:/usr/chapter7
127.0.0.1:1113& set i 1
127.0.0.1:1113&
root@kaitao:/usr/chapter7
127.0.0.1:1114& get i
127.0.0.1:1114&
root@kaitao:/usr/chapter7
127.0.0.1:1115& set i 1
127.0.0.1:1115&
root@kaitao:/usr/chapter7
127.0.0.1:1116& get i
动态服务实现
因为真实数据是从多个子系统获取,很难模拟这么多子系统交互,所以此处我们使用假数据来进行实现。
我们使用Maven搭建Web项目,Maven知识请自行学习。
本文将最小化依赖,即仅依赖我们需要的servlet、jackson、guava、jedis。
&javax.servlet&
&javax.servlet-api&
&provided&
&com.google.guava&
&redis.clients&
&com.fasterxml.jackson.core&
&jackson-core&
&com.fasterxml.jackson.core&
&jackson-databind&
guava是类似于apache commons的一个基础类库,用于简化一些重复操作,可以参考。
com.github.zhangkaitao.chapter7.servlet.ProductServiceServlet
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String type = req.getParameter("type");
String content = null;
if("basic".equals(type)) {
content = getBasicInfo(req.getParameter("skuId"));
} else if("desc".equals(type)) {
content = getDescInfo(req.getParameter("skuId"));
} else if("other".equals(type)) {
content = getOtherInfo(req.getParameter("ps3Id"), req.getParameter("brandId"));
} catch (Exception e) {
e.printStackTrace();
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
if(content != null) {
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(content);
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
根据请求参数type来决定调用哪个服务获取数据。
基本信息服务
private String getBasicInfo(String skuId) throws Exception {
Map&String, Object& map = new HashMap&String, Object&();
map.put("skuId", skuId);
map.put("name", "苹果(Apple)iPhone 6 (A1586) 16GB 金色 移动联通电信4G手机");
map.put("ps1Id", 9987);
map.put("ps2Id", 653);
map.put("ps3Id", 655);
map.put("brandId", 14026);
map.put("imgs", getImgs(skuId));
map.put("date", " 22:29:09");
map.put("weight", "400");
map.put("colorSize", getColorSize(skuId));
map.put("expands", getExpands(skuId));
map.put("propCodes", getPropCodes(skuId));
map.put("date", System.currentTimeMillis());
String content = objectMapper.writeValueAsString(map);
asyncSetToRedis(basicInfoJedisPool, "p:" + skuId + ":", content);
return objectMapper.writeValueAsString(map);
private List&String& getImgs(String skuId) {
return Lists.newArrayList(
"jfs/t277/193//98N19d42ce3.jpg",
"jfs/t352/148//b8cd7f/542d079bN3ea45c98.jpg",
"jfs/t274/315//cb380/542de6.jpg",
"jfs/t337/181//25/542d079aNf184ce18.jpg"
private List&Map&String, Object&& getColorSize(String skuId) {
return Lists.newArrayList(
makeColorSize(1217499, "金色", "公开版(16GB ROM)"),
makeColorSize(1217500, "深空灰", "公开版(16GB ROM)"),
makeColorSize(1217501, "银色", "公开版(16GB ROM)"),
makeColorSize(1217508, "金色", "公开版(64GB ROM)"),
makeColorSize(1217509, "深空灰", "公开版(64GB ROM)"),
makeColorSize(1217509, "银色", "公开版(64GB ROM)"),
makeColorSize(1217493, "金色", "移动4G版 (16GB)"),
makeColorSize(1217494, "深空灰", "移动4G版 (16GB)"),
makeColorSize(1217495, "银色", "移动4G版 (16GB)"),
makeColorSize(1217503, "金色", "移动4G版 (64GB)"),
makeColorSize(1217503, "金色", "移动4G版 (64GB)"),
makeColorSize(1217504, "深空灰", "移动4G版 (64GB)"),
makeColorSize(1217505, "银色", "移动4G版 (64GB)")
private Map&String, Object& makeColorSize(long skuId, String color, String size) {
Map&String, Object& cs1 = Maps.newHashMap();
cs1.put("SkuId", skuId);
cs1.put("Color", color);
cs1.put("Size", size);
return cs1;
private List&List&?&& getExpands(String skuId) {
return Lists.newArrayList(
(List&?&)Lists.newArrayList("热点", Lists.newArrayList("超薄7mm以下", "支持NFC")),
(List&?&)Lists.newArrayList("系统", "苹果(IOS)"),
(List&?&)Lists.newArrayList("系统", "苹果(IOS)"),
(List&?&)Lists.newArrayList("购买方式", "非合约机")
private Map&String, List&List&String&&& getPropCodes(String skuId) {
Map&String, List&List&String&&& map = Maps.newHashMap();
map.put("主体", Lists.&List&String&&newArrayList(
Lists.&String&newArrayList("品牌", "苹果(Apple)"),
Lists.&String&newArrayList("型号", "iPhone 6 A1586"),
Lists.&String&newArrayList("颜色", "金色"),
Lists.&String&newArrayList("上市年份", "2014年")
map.put("存储", Lists.&List&String&&newArrayList(
Lists.&String&newArrayList("机身内存", "16GB ROM"),
Lists.&String&newArrayList("储存卡类型", "不支持")
map.put("显示", Lists.&List&String&&newArrayList(
Lists.&String&newArrayList("屏幕尺寸", "4.7英寸"),
Lists.&String&newArrayList("触摸屏", "Retina HD"),
Lists.&String&newArrayList("分辨率", "1334 x 750")
return map;
private ObjectMapper objectMapper = new ObjectMapper();
private JedisPool basicInfoJedisPool = createJedisPool("127.0.0.1", 1111);
private JedisPool descInfoJedisPool = createJedisPool("127.0.0.1", 1113);
private JedisPool otherInfoJedisPool = createJedisPool("127.0.0.1", 1115);
private JedisPool createJedisPool(String host, int port) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100);
return new JedisPool(poolConfig, host, port);
private ExecutorService executorService = Executors.newFixedThreadPool(10);
private void asyncSetToRedis(final JedisPool jedisPool, final String key, final String content) {
executorService.submit(new Runnable() {
public void run() {
Jedis jedis = null;
jedis = jedisPool.getResource();
jedis.set(key, content);
} catch (Exception e) {
e.printStackTrace();
jedisPool.returnBrokenResource(jedis);
} finally {
jedisPool.returnResource(jedis);
本例使用Jackson进行JSON的序列化;Jedis进行Redis的操作;使用线程池做异步更新(实际应用中可以使用MQ做实现)。
vim /usr/chapter7/nginx_chapter7.conf
upstream backend {
server 127.0.0.1:8080 max_fails=5 fail_timeout=10s weight=1;
check interval=3000 rise=1 fall=2 timeout=5000 type=tcp default_down=false;
keepalive 100;
server_name
location ~ /backend/(.*) {
keepalive_timeout
keepalive_requests
proxy_http_version 1.1;
proxy_set_header Connection "";
rewrite /backend(/.*) $1 break;
proxy_pass_request_headers off;
proxy_next_upstream error
proxy_pass http://backend;
实际生产环境要把#internal打开,表示只有本nginx能访问。
vim /usr/servers/nginx/conf/nginx.conf
include /usr/chapter7/nginx_chapter7.
#为了方便测试,注释掉example.conf
include /usr/chapter6/nginx_chapter6.
lua_package_path "/usr/chapter7/lualib/?.;";
lua_package_cpath "/usr/chapter7/lualib/?.;";
lua模块从/usr/chapter7目录加载,因为我们要写自己的模块使用。
/usr/servers/nginx/sbin/nginx -s reload
前端展示实现
我们分为三部分实现:基础组件、商品介绍、前端展示部分。
首先我们进行基础组件的实现,商品介绍和前端展示部分都需要读取Redis和Http服务,因此我们可以抽取公共部分出来复用。
vim /usr/chapter7/lualib/item/common.lua
local redis = require("resty.redis")
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local function close_redis(red)
if not red then
local pool_max_idle_time = 10000
local pool_size = 100
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx_log(ngx_ERR, "set redis keepalive error : ", err)
local function read_redis(ip, port, keys)
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect(ip, port)
if not ok then
ngx_log(ngx_ERR, "connect to redis error : ", err)
return close_redis(red)
local resp = nil
resp, err = red:get(keys[1])
resp, err = red:mget(keys)
if not resp then
ngx_log(ngx_ERR, "get redis content error : ", err)
return close_redis(red)
if resp == ngx.null then
resp = nil
close_redis(red)
return resp
local function read_http(args)
local resp = ngx.location.capture("/backend/info", {
method = ngx.HTTP_GET,
args = args
if not resp then
ngx_log(ngx_ERR, "request error")
if resp.status ~= 200 then
ngx_log(ngx_ERR, "request error, status :", resp.status)
return resp.body
local _M = {
read_redis = read_redis,
read_http = read_http
整个逻辑和第六章类似;只是read_redis根据参数keys个数支持get和mget。 比如read_redis(ip, port, {“key1”})则调用get而read_redis(ip, port, {“key1”, “key2”})则调用mget。
vim /usr/chapter7/desc.lua
local common = require("mon")
local read_redis = common.read_redis
local read_http = common.read_http
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_exit = ngx.exit
local ngx_print = ngx.print
local ngx_re_match = ngx.re.match
local ngx_var = ngx.var
local descKey = "d:" .. skuId .. ":"
local descInfoStr = read_redis("127.0.0.1", 1114, {descKey})
if not descInfoStr then
ngx_log(ngx_ERR, "redis not found desc info, back to http, skuId : ", skuId)
descInfoStr = read_http({type="desc", skuId = skuId})
if not descInfoStr then
ngx_log(ngx_ERR, "http not found basic info, skuId : ", skuId)
return ngx_exit(404)
ngx_print("showdesc(")
ngx_print(descInfoStr)
ngx_print(")")
ngx_print(")")
通过复用逻辑后整体代码简化了许多;此处读取商品介绍从集群;另外前端展示使用JSONP技术展示商品介绍。
vim /usr/chapter7/nginx_chapter7.conf
location ~^/desc/(\d+)$ {
if ($host != "") {
return 403;
default_type application/x-
charset utf-8;
set $skuId $1;
content_by_lua_file /usr/chapter7/desc.
vim /usr/chapter7/item.lua
local common = require("mon")
local item = require("item")
local read_redis = common.read_redis
local read_http = common.read_http
local cjson = require("cjson")
local cjson_decode = cjson.decode
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_exit = ngx.exit
local ngx_print = ngx.print
local ngx_var = ngx.var
local skuId = ngx_var.skuId
local basicInfoKey = "p:" .. skuId .. ":"
local basicInfoStr = read_redis("127.0.0.1", 1112, {basicInfoKey})
if not basicInfoStr then
ngx_log(ngx_ERR, "redis not found basic info, back to http, skuId : ", skuId)
basicInfoStr = read_http({type="basic", skuId = skuId})
if not basicInfoStr then
ngx_log(ngx_ERR, "http not found basic info, skuId : ", skuId)
return ngx_exit(404)
local basicInfo = cjson_decode(basicInfoStr)
local ps3Id = basicInfo["ps3Id"]
local brandId = basicInfo["brandId"]
local breadcrumbKey = "s:" .. ps3Id .. ":"
local brandKey = "b:" .. brandId ..":"
local otherInfo = read_redis("127.0.0.1", 1116, {breadcrumbKey, brandKey}) or {}
local breadcrumbStr = otherInfo[1]
local brandStr = otherInfo[2]
if breadcrumbStr then
basicInfo["breadcrumb"] = cjson_decode(breadcrumbStr)
if brandStr then
basicInfo["brand"] = cjson_decode(brandStr)
if not breadcrumbStr and not brandStr then
ngx_log(ngx_ERR, "redis not found other info, back to http, skuId : ", brandId)
local otherInfoStr = read_http({type="other", ps3Id = ps3Id, brandId = brandId})
if not otherInfoStr then
ngx_log(ngx_ERR, "http not found other info, skuId : ", skuId)
local otherInfo = cjson_decode(otherInfoStr)
basicInfo["breadcrumb"] = otherInfo["breadcrumb"]
basicInfo["brand"] = otherInfo["brand"]
local name = basicInfo["name"]
basicInfo["unicodeName"] = item.utf8_to_unicode(name)
basicInfo["moreName"] = item.trunc(name, 10)
item.init_breadcrumb(basicInfo)
item.init_expand(basicInfo)
item.init_color_size(basicInfo)
local template = require "resty.template"
template.caching(true)
template.render("item.html", basicInfo)
整个逻辑分为四部分:1、获取基本信息;2、根据基本信息中的关联关系获取其他信息;3、初始化/格式化数据;4、渲染模板。
初始化模块
vim /usr/chapter7/lualib/item.lua
local bit = require("bit")
local utf8 = require("utf8")
local cjson = require("cjson")
local cjson_encode = cjson.encode
local bit_band = bit.band
local bit_bor = bit.bor
local bit_lshift = bit.lshift
local string_format = string.format
local string_byte = string.byte
local table_concat = table.concat
local function utf8_to_unicode(str)
if not str or str == "" or str == ngx.null then
return nil
local res, seq, val = {}, 0, nil
for i = 1,
local c = string_byte(str, i)
if seq == 0 then
if val then
seq = c & 0x80 and 1 or c & 0xE0 and 2 or c & 0xF0 and 3 or
c & 0xF8 and 4 or
if seq == 0 then
ngx.log(ngx.ERR, 'invalid UTF-8 character sequence' .. ",,," .. tostring(str))
return str
val = bit_band(c, 2 ^ (8 - seq) - 1)
val = bit_bor(bit_lshift(val, 6), bit_band(c, 0x3F))
seq = seq - 1
if val then
return str
return "\\u" .. table_concat(res, "\\u")
local function trunc(str, len)
if not str then
return nil
if utf8.len(str) & len then
return utf8.sub(str, 1, len) .. "..."
return str
local function init_breadcrumb(info)
local breadcrumb = info["breadcrumb"]
if not breadcrumb then
local ps1Id = breadcrumb[1][1]
local ps2Id = breadcrumb[2][1]
local ps3Id = breadcrumb[3][1]
local ps1Url = "/"
local ps2Url = "/shouji.html"
local ps3Url = "/list.html?cat=" .. ps1Id .. "," .. ps2Id .. "," .. ps3Id
breadcrumb[1][3] = ps1Url
breadcrumb[2][3] = ps2Url
breadcrumb[3][3] = ps3Url
local function init_expand(info)
local expands = info["expands"]
if not expands then
for _, e in ipairs(expands) do
if type(e[2]) == "table" then
e[2] = table_concat(e[2], ",")
local function init_color_size(info)
local colorSize = info["colorSize"]
local colorSizeJson = cjson_encode(colorSize)
local colorList = {}
local sizeList = {}
info["colorSizeJson"] = colorSizeJson
info["colorList"] = colorList
info["sizeList"] = sizeList
local colorSet = {}
local sizeSet = {}
for _, cz in ipairs(colorSize) do
local color = cz["Color"]
local size = cz["Size"]
if color and color ~= "" and not colorSet[color] then
colorList[
colorSet[color] = true
if size and size ~= "" and not sizeSet[size] then
sizeSet[size] = ""
local _M = {
utf8_to_unicode = utf8_to_unicode,
trunc = trunc,
init_breadcrumb = init_breadcrumb,
init_expand = init_expand,
init_color_size = init_color_size
模板html片段
var pageConfig = {
compatible: true,
product: {
skuid: {* skuId *},
name: '{* unicodeName *}',
skuidkey:'AFC266EFC926D34E91C879',
href: '/{* skuId *}.html',
src: '{* imgs[1] *}',
cat: [{* ps1Id *},{* ps2Id *},{* ps3Id *}],
brand: {* brandId *},
tips: false,
venderId:0,
shopId:'0',
specialAttrs:["HYKHSP-0","isDistribution","isHaveYB","isSelfService-0","isWeChatStock-0","packType","IsNewGoods","isCanUseDQ","isSupportCard","isCanUseJQ","isOverseaPurchase-0","is7ToReturn-1","isCanVAT"],
videoPath:'',
desc: '/desc/{* skuId *}'
var warestatus = 1;
{% if colorSizeJson then %} var ColorSize = {* colorSizeJson *};{% end %}
try{(function(flag){ if(!flag){return;} if(window.location.hash == '#m'){var exp = new Date();exp.setTime(exp.getTime() + 30 * 24 * 60 * 60 * 1000);document.cookie = "pcm=1;expires=" + exp.toGMTString() + ";path=/;";return;}else{var cook=document.cookie.match(new RegExp("(^| )pcm=([^;]*)(;|$)"));if(cook&&cook.length&2&&unescape(cook[2])=="2"){flag=false;}} var userAgent = navigator.userA if(userAgent){ userAgent = userAgent.toUpperCase();if(userAgent.indexOf("PAD")&-1){return;} var mobilePhoneList = ["IOS","IPHONE","ANDROID","WINDOWS PHONE"];for(var i=0,len=mobilePhoneList.i&i++){ if(userAgent.indexOf(mobilePhoneList[i])&-1){var url="/product/"+pageConfig.product.skuid+".html";if(flag){window.showtouchurl=true;}else{window.location.href =}break;}}}})((function(){var json={"6881":3,"1195":3,"10011":3,"6980":3,"12360":3};if(json[pageConfig.product.cat[0]+""]==1||json[pageConfig.product.cat[1]+""]==2||json[pageConfig.product.cat[2]+""]==3){return false;}else{return true;}})());}catch(e){}
class="breadcrumb"&
& href='{* breadcrumb[1][3] *}'&{* breadcrumb[1][2] *}&&
href='{* breadcrumb[2][3] *}'&{* breadcrumb[2][2] *}&
href='{* breadcrumb[3][3] *}'&{* breadcrumb[3][2] *}&
{% if brand then %}
href='/pinpai/{* ps3Id *}-{* brandId *}.html'&{* brand['name'] *}&
href='/{* skuId *}.html'&{* moreName *}&
set $template_root "/usr/chapter7";
location ~ ^/(\d+).html$ {
default_type 'text/html';
charset utf-8;
set $skuId $1;
content_by_lua_file /usr/chapter7/item.
local cache
对于其他信息,对数据一致性要求不敏感,而且数据量很少,完全可以在本地缓存全量;而且可以设置如5-10分钟的过期时间是完全可以接受的;因此可以lua_shared_dict全局内存进行缓存。具体逻辑可以参考
local nginx_shared = ngx.shared
local local_cache = nginx_shared.item_local_cache
local function cache_get(key)
if not local_cache then
return nil
return local_cache:get(key)
local function cache_set(key, value)
if not local_cache then
return nil
return local_cache:set(key, value, 10 * 60)
local function get(ip, port, keys)
local tables = {}
local fetchKeys = {}
local resp = nil
local status = STATUS_OK
local has_value = false
for i, key in ipairs(keys) do
local value = cache_get(key)
if value then
if value == "" then
value = nil
tables[key] = value
has_value = true
fetchKeys[
status, resp = redis_get(ip, port, fetchKeys[1])
status, resp = redis_mget(ip, port, fetchKeys)
if status == STATUS_OK then
for i = 1,
local key = fetchKeys[i]
local value = nil
value = resp
value = get_data(resp, i)
tables[key] = value
has_value = true
cache_set(key, value or "", ttl)
if has_value and status == STATUS_NOT_FOUND then
status = STATUS_OK
return status, tables
nginx proxy cache
为了防止恶意刷页面/热点页面访问频繁,我们可以使用nginx proxy_cache做页面缓存,当然更好的选择是使用CDN技术,如通过Apache Traffic Server、Squid、Varnish。
1、nginx.conf配置
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 256 8k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_temp_path /usr/servers/nginx/proxy_
proxy_cache_path
/usr/servers/nginx/proxy_cache levels=1:2 keys_zone=cache_item:200m inactive=1m max_size=30g;
增加proxy_cache的配置,可以通过挂载一块内存作为缓存的存储空间。更多配置规则请参考。
2、nginx_chapter7.conf配置
与server指令配置同级
map $host $item_dynamic {
即如果域名为则item_dynamic=1。
location ~ ^/(\d+).html$ {
set $skuId $1;
if ($host !~ "^(item|item2015)\.jd\.com$") {
return 403;
expires 3m;
proxy_cache cache_
proxy_cache_key $uri;
proxy_cache_bypass $item_dynamic;
proxy_no_cache $item_dynamic;
proxy_cache_valid 200 301 3m;
proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_pass_request_
proxy_set_header Host $host;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://127.0.0.1/proxy/$skuId.
add_header X-Cache '$upstream_cache_status';
location ~ ^/proxy/(\d+).html$ {
allow 127.0.0.1;
keepalive_timeout
keepalive_requests
default_type 'text/html';
charset utf-8;
set $skuId $1;
content_by_lua_file /usr/chapter7/item.
expires:设置响应缓存头信息,此处是3分钟;将会得到Cache-Control:max-age=180和类似Expires:Sat, 28 Feb :10 GMT的响应头;
proxy_cache:使用之前在nginx.conf中配置的cache_item缓存;
proxy_cache_key:缓存key为uri,不包括host和参数,这样不管用户怎么通过在url上加随机数都是走缓存的;
proxy_cache_bypass:nginx不从缓存取响应的条件,可以写多个;如果存在一个字符串条件且不是“0”,那么nginx就不会从缓存中取响应内容;此处如果我们使用的host为时就不会从缓存取响应内容;
proxy_no_cache:nginx不将响应内容写入缓存的条件,可以写多个;如果存在一个字符串条件且不是“0”,那么nginx就不会从将响应内容写入缓存;此处如果我们使用的host为时就不会将响应内容写入缓存;
proxy_cache_valid:为不同的响应状态码设置不同的缓存时间,此处我们对200、301缓存3分钟;
proxy_cache_use_stale:什么情况下使用不新鲜(过期)的缓存内容;配置和proxy_next_upstream内容类似;此处配置了如果连接出错、超时、404、500等都会使用不新鲜的缓存内容;此外我们配置了updating配置,通过配置它可以在nginx正在更新缓存(其中一个Worker进程)时(其他的Worker进程)使用不新鲜的缓存进行响应,这样可以减少回源的数量;
proxy_pass_request_headers:我们不需要请求头,所以不传递;
proxy_http_version 1.1和proxy_set_header Connection “”:支持keepalive;
add_header X-Cache ‘$upstream_cache_status’:添加是否缓存命中的响应头;比如命中HIT、不命中MISS、不走缓存BYPASS;比如命中会看到X-Cache:HIT响应头;
allow/deny:允许和拒绝访问的ip列表,此处我们只允许本机访问;
keepalive_timeout 30s和keepalive_requests 1000:支持keepalive;
nginx_chapter7.conf清理缓存配置
location /purge {
127.0.0.1;
192.168.0.0/16;
proxy_cache_purge
cache_item $arg_url;
3、修改item.lua代码
--添加Last-Modified,用于响应304缓存
ngx.header["Last-Modified"] = ngx.http_time(ngx.now())
local template = require "resty.template"
template.caching(true)
template.render("item.html", basicInfo)
在渲染模板前设置Last-Modified,用于判断内容是否变更的条件,默认Nginx通过等于去比较,也可以通过配置if_modified_since指令来支持小于等于比较;如果请求头发送的If-Modified-Since和Last-Modified匹配则返回304响应,即内容没有变更,使用本地缓存。此处可能看到了我们的Last-Modified是当前时间,不是商品信息变更的时间;商品信息变更时间由:商品信息变更时间、面包屑变更时间和品牌变更时间三者决定的,因此实际应用时应该取三者最大的;还一个问题就是模板内容可能变了,但是商品信息没有变,此时使用Last-Modified得到的内容可能是错误的,所以可以通过使用ETag技术来解决这个问题,ETag可以认为是内容的一个摘要,内容变更后摘要就变了。
修改nginx.conf配置文件
gzip_min_length
gzip_buffers
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types
text/plain application/x-javascript text/css application/
gzip_vary on;
此处我们指定至少4k时才压缩,如果数据太小压缩没有意义。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:22507次
排名:千里之外
原创:59篇
转载:71篇
(1)(8)(38)(8)(6)(9)(8)(9)(7)(8)(1)(4)(1)(4)(7)(7)(4)}

我要回帖

更多关于 前端页面 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信