10.商品详情缓存实现

文章目录

缓存预热

缓存冷启动,redis启动后,一点数据都没有,直接就对外提供服务了,mysql就裸奔

(1)提前给redis中灌入部分数据,再提供服务
(2)肯定不可能将所有数据都写入redis,因为数据量太大了,第一耗费的时间太长了,第二根本redis容纳不下所有的数据
(3)需要根据当天的具体访问情况,实时统计出访问频率较高的热数据
(4)然后将访问频率较高的热数据写入redis中,肯定是热数据也比较多,我们也得多个服务并行读取数据去写,并行的分布式的缓存预热
(5)然后将灌入了热数据的redis对外提供服务,这样就不至于冷启动,直接让数据库裸奔了

1、nginx+lua将访问流量上报到kafka中

要统计出来当前最新的实时的热数据是哪些,我们就得将商品详情页访问的请求对应的流浪,日志,实时上报到kafka中

2、storm从kafka中消费数据,实时统计出每个商品的访问次数,访问次数基于LRU内存数据结构的存储方案

优先用内存中的一个LRUMap去存放,性能高,而且没有外部依赖

我之前做过的一些项目,不光是这个项目,还有很多其他的,一些广告计费类的系统,storm

否则的话,依赖redis,我们就是要防止redis挂掉数据丢失的情况,就不合适了; 用mysql,扛不住高并发读写; 用hbase,hadoop生态系统,维护麻烦,太重了

其实我们只要统计出最近一段时间访问最频繁的商品,然后对它们进行访问计数,同时维护出一个前N个访问最多的商品list即可

热数据,最近一段时间,可以拿到最近一段,比如最近1个小时,最近5分钟,1万个商品请求,统计出最近这段时间内每个商品的访问次数,排序,做出一个排名前N的list

计算好每个task大致要存放的商品访问次数的数量,计算出大小

然后构建一个LRUMap,apache commons collections有开源的实现,设定好map的最大大小,就会自动根据LRU算法去剔除多余的数据,保证内存使用限制

即使有部分数据被干掉了,然后下次来重新开始计数,也没关系,因为如果它被LRU算法干掉,那么它就不是热数据,说明最近一段时间都很少访问了

3、每个storm task启动的时候,基于zk分布式锁,将自己的id写入zk同一个节点中

4、每个storm task负责完成自己这里的热数据的统计,每隔一段时间,就遍历一下这个map,然后维护一个前3个商品的list,更新这个list

5、写一个后台线程,每隔一段时间,比如1分钟,都将排名前3的热数据list,同步到zk中去,存储到这个storm task对应的一个znode中去

6、我们需要一个服务,比如说,代码可以跟缓存数据生产服务放一起,但是也可以放单独的服务

服务可能部署了很多个实例

每次服务启动的时候,就会去拿到一个storm task的列表,然后根据taskid,一个一个的去尝试获取taskid对应的znode的zk分布式锁

如果能获取到分布式锁的话,那么就将那个storm task对应的热数据的list取出来

然后将数据从mysql中查询出来,写入缓存中,进行缓存的预热,多个服务实例,分布式的并行的去做,基于zk分布式锁做了协调了,分布式并行缓存的预热

storm+hbase(海量数据的分布式非关系型数据库),用storm计算完数据,灌入到hbase中
如果用storm+mysql,数据量大了的话,很容易把mysql整崩


在nginx这一层,接收到访问请求的时候,就把请求的流量上报发送给kafka

这样的话,storm才能去消费kafka中的实时的访问日志,然后去进行缓存热数据的统计

用得技术方案非常简单,从lua脚本直接创建一个kafka producer,发送数据到kafka

之前搭建的nginx架构是:node3上的nginx作为转发nginx,node1和node2上作为应用nginx;

首先在node1上

cd /usr/local
wget https://github.com/doujiang24/lua-resty-kafka/archive/master.zip
安装unzip命令
 yum install -y unzip
unzip master.zip  ---解压得到 lua-resty-kafka-master目录

cp -rf /usr/local/lua-resty-kafka-master/lib/resty /usr/hello/lualib/

如果以上操作以前,nginx就已经启动了,那么就reload一下,如果没有启动那么就启动一下

/usr/servers/nginx/sbin/nginx -s reload

然后在node2上做同样的事情

然后在node1上修改product.lua脚本

[root@node1 lua]# cd /usr/hello/lua
[root@node1 lua]# ll
总用量 8
-rw-r--r--. 1 root root   38 719 06:50 hello.lua
-rw-r--r--. 1 root root 1944 719 19:39 product.lua
[root@node1 lua]# vi product.lua 

--begin add
ngx.say("this is node1 或者 node2")

local cjson = require("cjson")
local producer = require("resty.kafka.producer")

local broker_list = {
     { host = "10.1.218.22", port = 9092 },
     { host = "10.1.218.26", port = 9092 },
     { host = "10.1.218.24", port = 9092 }
}

local log_json = {}
log_json["headers"] = ngx.req.get_headers()

local log_json = {}
log_json["request_module"] = "product_detail_info"
log_json["headers"] = ngx.req.get_headers()
log_json["uri_args"] = ngx.req.get_uri_args()
log_json["body"] = ngx.req.read_body()
log_json["http_version"] = ngx.req.http_version()
log_json["method"] = ngx.req.get_method()
log_json["raw_reader"] = ngx.req.raw_header()
log_json["body_data"] = ngx.req.get_body_data()

local message = cjson.encode(log_json)
ngx.say("kafka",message)

--end add 
local uri_args = ngx.req.get_uri_args()
local productId = uri_args["productId"]
local shopId = uri_args["shopId"]

--begin add
local async_producer = producer:new(broker_list, { producer_type = "async"})
local ok, err = async_producer:send("access-log", productId, message)
ngx.say(ok)

if not ok then 
   ngx.log(ngx.ERR, "kafka send err:", err)
   return
end

--end add

-- get nginx share cache
local cache_ngx = ngx.shared.my_cache

local productCacheKey = "product_info_"..productId
local shopCacheKey = "shop_info_"..shopId

local productCache = cache_ngx:get(productCacheKey)
local shopCache = cache_ngx:get(shopCacheKey)

-- if not found value from nginx cache

if productCache == "" or productCache == nil then
     local http = require("resty.http")
     local httpc = http:new()

     local resp,err = httpc:request_uri("http://10.1.218.189:8080",{
           method = "GET",
           path = "/getProductInfo?productId="..productId,
           keepalive = false 
      })
      productCache = resp.body
      cache_ngx:set(productCacheKey, productCache, 10 * 60)

end 


if shopCache == "" or shopCache == nil then

    local http = require("resty.http")
    local httpc = http.new()

    local resp,err = httpc:request_uri("http://10.1.218.189:8080",{
      method = "GET",
      path = "/getShopInfo?shopId="..shopId,
      keepalive = false
     })

     shopCache = resp.body
     cache_ngx:set(shopCacheKey, shopCache, 10 * 60)
end

local productCacheJSON = cjson.decode(productCache)
local shopCacheJSON = cjson.decode(shopCache)


local context = {
        productId = productCacheJSON.id,
        productName = productCacheJSON.name,
        productPrice = productCacheJSON.price,
        productPictureList = productCacheJSON.pictureList,
        productSpecification = productCacheJSON.specification,
        productService = productCacheJSON.service,
        productColor = productCacheJSON.color,
        productSize = productCacheJSON.size,
        shopId = shopCacheJSON.id,
        shopName = shopCacheJSON.name,
        shopLevel = shopCacheJSON.level,
        shopGoodCommentRate = shopCacheJSON.goodCommentRate
}

local template = require("resty.template")
template.render("product.html",context)

然后检验语法,重新reload nginx

[root@node1 lua]# /usr/servers/nginx/sbin/nginx -t
nginx: the configuration file /usr/servers/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /usr/servers/nginx/conf/nginx.conf test is successful
[root@node1 lua]# /usr/servers/nginx/sbin/nginx -s reload

其中--add --add 中间的内容是本次新增加的,同样修改node2中的product.lua脚本

确保已经启动了node1,node2,node3上的zk,三个节点上的kafka都启动中

在node1上,创建topic

cd /usr/local/kafka/

bin/kafka-topics.sh --zookeeper 10.1.218.22:2181,10.1.218.26:2181,10.1.218.24:2181 --topic access-log --replication-factor 1 --partitions 1 --create
启动消费者
bin/kafka-console-consumer.sh --zookeeper 10.1.218.22:2181,10.1.218.26:2181,10.1.218.24:2181 --topic access-log --from-beginning

请求

http://10.1.218.24/hello?method=product&productId=1&shopId=1
返回了

this is node2 
kafka{"request_module":"product_detail_info","raw_reader":"GET \/product?productId=1&shopId=1 HTTP\/1.1\r\nHost: 10.1.218.26\r\nUser-Agent: lua-resty-http\/0.17.0-beta.1 (Lua) ngx_lua\/9014\r\n\r\n","http_version":1.1,"method":"GET","uri_args":{"productId":"1","shopId":"1"},"headers":{"host":"10.1.218.26","user-agent":"lua-resty-http\/0.17.0-beta.1 (Lua) ngx_lua\/9014"}} 
true 
商品id: 1
商品名称: iphone7
商品图片列表: a.jpg,b.jpg
商品规格: iphone7的规格
商品售后服务: 售后服务
商品颜色: 红色,白色,黑色
商品大小: S
店铺id: 1
店铺名称: 小王手机店
店铺评级: 5
店铺好评率: 99

请求

http://10.1.218.24/hello?method=product&productId=22&shopId=1

this is node1 kafka{"request_module":"product_detail_info","raw_reader":"GET \/product?productId=22&shopId=1 HTTP\/1.1\r\nHost: 10.1.218.22\r\nUser-Agent: lua-resty-http\/0.17.0-beta.1 (Lua) ngx_lua\/9014\r\n\r\n","http_version":1.1,"method":"GET","uri_args":{"productId":"22","shopId":"1"},"headers":{"host":"10.1.218.22","user-agent":"lua-resty-http\/0.17.0-beta.1 (Lua) ngx_lua\/9014"}} true 商品id: 22
商品名称: iphone7
商品图片列表: a.jpg,b.jpg
商品规格: iphone7的规格
商品售后服务: 售后服务
商品颜色: 红色,白色,黑色
商品大小: S
店铺id: 1
店铺名称: 小王手机店
店铺评级: 5
店铺好评率: 99

我们发现提示lua发送kafka已经成功了,但是kafka消费者却收不到消息

修改需要在node1和node2中,修改nginx.conf文件,http部分,加入resolver 8.8.8.8;

vi /usr/servers/nginx/conf/nginx.conf

http {
   resolver 8.8.8.8;
   --忽略

(3)需要在三个节点上,kafka配置文件sever.properties中加入advertised.host.name = 当前机器ip,重启三个kafka进程

vi /usr/local/kafka/config/server.properties

# Hostname the broker will advertise to producers and consumers. If not set, it uses the
# value for "host.name" if configured.  Otherwise, it will use the value returned from
# java.net.InetAddress.getCanonicalHostName().
#advertised.host.name=<hostname routable by clients>
advertised.host.name=10.1.218.22

我又尝试在三个节点中,在server.properteis中新增

 advertised.listeners=PLAINTEXT://10.1.218.22:9092      node1
 advertised.listeners=PLAINTEXT://10.1.218.26:9092   node2
 advertised.listeners=PLAINTEXT://10.1.218.24:9092       node3

但是我本地尝试了以后,lua发送消息,kafka依然接受不到消息,奔溃!!!!!!!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值