目录
前言
项目地址:eshop-study
切换到相应分支:
缓存冷启动
缓存冷启动即缓存空的情况下启动,两种情形出现:
- 新系统
第一次上线
,此时在缓存里可能是没有数据的 - 系统在线上稳定运行着,但是突然间重要的
redis缓存
全盘崩溃了,而且不幸的是,数据全都无法找回来
系统第一次上线启动,系统在redis故障的情况下重新启动,在高并发的场景下出现的问题:
解决:redis重启过程中保证mysql不挂掉
缓存预热
缓存冷启动:redis启动后,
一点数据都没有
,直接就对外提供服务了,mysql裸奔状态
- 提前给redis中灌入部分数据,再提供服务
- 不可能将所有数据都写入redis,因为数据量太大,第一耗费的时间太长,第二根本redis容纳不下所有的数据
- 需要根据当天的具体访问情况,实时统计出访问频率较高的热数据
- 然后将访问频率较高的热数据写入redis中,肯定是热数据也比较多,多个服务并行读取数据去写,
并行的分布式的缓存预热
- 然后将灌入了热数据的redis对外提供服务,这样就不至于冷启动,直接让数据库裸奔
开发方案
访问流量上报
nginx+lua
将访问流量上报到 kafka
中
要统计出来当前最新的实时的热数据是哪些,将商品详情页访问的请求对应的流量,日志,实时上报到kafka中
实时统计流量访问次数
storm
从kafka中消费数据,实时统计出每个商品的访问次数,访问次数基于LRU内存数据结构
的存储方案
- 优先用
storm内存
中的一个LRUMap
去存放,性能高,而且没有外部依赖 - 如果使用
redis
,还要防止redis挂掉数据丢失,依赖耦合度高;用mysql
,扛不住高并发读写;用hbase
,hadoop生态系统,维护麻烦,太重 - 其实我们只要统计出最近一段时间访问最频繁的商品流量,然后对它们进行访问计数,同时维护出一个
前N个访问最多的商品list
即可 - 热数据,最近一段时间,比如最近1个小时,最近5分钟,1万个商品请求,统计出最近这段时间内每个商品的访问次数,排序,做出一个排名前N的list
- 计算好每个
storm task
要存放的商品访问次数的数量,计算出大小 - 然后构建一个
LRUMap
,apache commons collections
有开源的实现,设定好map的最大大小,就会自动根据LRU算法
去剔除多余的数据,保证内存使用限制 - 即使有部分数据被干掉,因为如果它被LRU算法干掉,那么它就不是热数据,说明最近一段时间都很少访问了,下一轮重新统计
数据恢复
- 每个
storm task
启动的时候,基于zk分布式锁,将自己的task id
写入zk
同一个节点中 - 每个
storm task
负责完成自己的热数据的统计,每隔一段时间,就遍历一下这个LRUmap
,然后维护一个前3个商品的list,更新这个list
实际生产中可能1000个,10000个商品的list
-
写一个后台线程,每隔一段时间,比如1分钟,都将排名前3的热数据list,同步到
zk
中去,存储到这个storm task
的id对应的一个znode
中去 -
这个服务代码可以跟缓存数据生产服务放一起,但是也可以放单独的服务
-
服务可能部署了很多个实例,每次服务启动的时候,就会去拿到一个
storm task
的列表,然后根据taskid
,一个一个的去尝试获取taskid
对应的znode
的zk分布式锁
-
当获取到分布式锁,将该
storm task
对应的热数据的list取出来,然后将数据从mysql
中查询出来,写入缓存中,进行缓存的预热; -
多个服务实例,分布式的并行的去做,基于zk分布式锁协调,分布式并行缓存的预热。
实战项目
nginx+lua实现实时上报kafka
基于nginx+lua完成商品详情页访问流量实时上报kafka的开发。
storm
消费kafka
中实时的访问日志,然后去进行缓存热数据的统计- 技术方案非常简单,从
lua脚本
直接创建一个kafka producer
,发送数据到kafka
- 下载
lua+kafak
脚本库
# eshop-cache01: 192.168.0.106
# eshop-cache02: 192.168.0.107
cd /usr/local
# 如果下载最新版本,nginx也要升级最新版本,否则lua脚本会执行错误
wget https://github.com/doujiang24/lua-resty-kafka/archive/v0.05.zip
yum install -y unzip
unzip lua-resty-kafka-0.05.zip
cp -rf /usr/local/lua-resty-kafka-master/lib/resty /usr/hello/lualib
eshop-cache01: 192.168.0.106
、eshop-cache02: 192.168.0.107
nginx添加下面配置:
vim /usr/servers/nginx/conf/nginx.conf
resolver 8.8.8.8;
- 修改kafka配置,重启三个kafka进程
vi /usr/local/kafka/config/server.properties
advertised.host.name = 192.168.0.106
# 重启三台服务器中kafka进程
nohup bin/kafka-server-start.sh config/server.properties &
- 启动原来写的
eshop-cache
缓存服务,因为nginx重启后,本地缓存可能没了;项目地址:https://blog.csdn.net/qq_34246646/article/details/104596143 - 发送商品请求消息到后台服务之前,上报到kafka:
vi /usr/hello/lua/product.lua
echop-cache01: 192.168.0.106,echop-cache02: 192.168.0.107
-- 上报数据到kafka
local cjson = require("cjson")
local producer = require("resty.kafka.producer")
local broker_list = {
{
host = "192.168.0.106", port = 9092 },
{
host = "192.168.0.107", port = 9092 },
{
host = "192.168.0.108", port = 9092 }
}
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);
-- 获取请求参数
local uri_args = ngx.req.get_uri_args()
local productId