2000字范文,分享全网优秀范文,学习好帮手!
2000字范文 > 12.亿级流量电商详情页系统的大型高并发与高可用缓存架构实战----热点缓存的解决方案

12.亿级流量电商详情页系统的大型高并发与高可用缓存架构实战----热点缓存的解决方案

时间:2022-10-16 04:47:46

相关推荐

12.亿级流量电商详情页系统的大型高并发与高可用缓存架构实战----热点缓存的解决方案

热数据 -> 热数据的统计 -> redis中缓存的预热 -> 避免新系统刚上线,或者是redis崩溃数据丢失后重启,redis中没有数据,redis冷启动 -> 大量流量直接到数据库

redis启动前,必须确保其中是有部分热数据的缓存的

瞬间的缓存热点

1、在storm中,实时的计算出瞬间出现的热点

有很多种算法,给大家介绍一种我们的比较简单的算法

某个storm task,上面算出了1万个商品的访问次数,LRUMap

频率高一些,每隔5秒,去遍历一次LRUMap,将其中的访问次数进行排序,统计出往后排的95%的商品访问次数的平均值

1000

999

888

777

666

50

60

80

100

120

比如说,95%的商品,访问次数的平均值是100

然后,从最前面开始,往后遍历,去找有没有瞬间出现的热点数据

1000,95%的平均值(100)的10倍,这个时候要设定一个阈值,比如说超出95%平均值得n倍,5倍

我们就认为是瞬间出现的热点数据,判断其可能在短时间内继续扩大的访问量,甚至达到平均值几十倍,或者几百倍

当遍历,发现说第一个商品的访问次数,小于平均值的5倍,就安全了,就break掉这个循环

热点数据,热数据,不是一个概念

有100个商品,前10个商品比较热,都访问量在500左右,其他的普通商品,访问量都在200左右,就说前10个商品是热数据

统计出来

预热的时候,将这些热数据放在缓存中去预热就可以了

热点,前面某个商品的访问量,瞬间超出了普通商品的10倍,或者100倍,1000倍,热点

2、storm这里,会直接发送http请求到nginx上,nginx上用lua脚本去处理这个请求

storm会将热点本身对应的productId,发送到流量分发的nginx上面去,放在本地缓存中

storm会将热点对应的完整的缓存数据,发送到所有的应用nginx服务器上去,直接放在本地缓存中

3、流量分发nginx的分发策略降级

流量分发nginx,加一个逻辑,就是每次访问一个商品详情页的时候,如果发现它是个热点,那么立即做流量分发策略的降级

hash策略,同一个productId的访问都同一台应用nginx服务器上

降级成对这个热点商品,流量分发采取随机负载均衡发送到所有的后端应用nginx服务器上去

瞬间将热点缓存数据的访问,从hash分发,全部到一台nginx,变成了,负载均衡发送到多台nginx上去

避免说大量的流量全部集中到一台机器,50万的访问量到一台nginx,5台应用nginx,每台就可以承载10万的访问量

4、storm还需要保存下来上次识别出来的热点list

下次去识别的时候,这次的热点list跟上次的热点list做一下diff,看看可能有的商品已经不是热点了

热点的取消的逻辑,发送http请求到流量分发的nginx上去,取消掉对应的热点数据,从nginx本地缓存中,删除

在node1、node2、node3上

[root@node3 ~]# cd /usr/hello/

vi hello.conf

server {listen 80;server_name _;location /hello {default_type 'text/html';# lua_code_cache off;content_by_lua_file /usr/hello/lua/hello.lua;}#新增location /hot {default_type 'text/html';# lua_code_cache off;content_by_lua_file /usr/hello/lua/hot.lua;}}

在node3上,新增hot.lua

local uri_args = ngx.req.get_uri_args()local product_id = uri_args["productId"]local cache_ngx = ngx.shared.my_cachelocal hot_product_cache_key = "hot_product_"..product_idcache_ngx:set(hot_product_cache_key, "true", 60 * 60)

在node1和node2上新增hot.lua

local uri_args = ngx.req.get_uri_args()local product_id = uri_args["productId"]local product_info = uri_args["productInfo"]local product_cache_key = "product_info_"..product_idlocal cache_ngx = ngx.shared.my_cachecache_ngx:set(product_cache_key,product_info,60 * 60)

在项目eshop-strom中

在ProductCountBolt中新增线程:HotProductFindThread

用于 统计热点数据,并将热点信息发送到分发nginx和服务nginx上

往分发nginx发送信息 :热点产品id 集合

往服务nginx发送消息: 热点产品的完整信息

/*** 热门商品自动发现线程*/private class HotProductFindThread implements Runnable{@Overridepublic void run() {List<Map.Entry<Long/*商品id*/,Long/*访问量*/>> productCountList = new ArrayList<>();List<Long> productIdList = new ArrayList<Long>();List<Long/*商品id*/> hotProductIdList = new ArrayList<Long>();while(true){//1.将LRUMap中的商品按照访问次数,进行全局排序//2.计算95%的商品的访问次数的平均值//3.遍历排序后的商品访问次数,从最大的开始//4.如果某个商品的访问量是它的平均值的10倍,就默认为缓存的热点try{productCountList.clear();productIdList.clear();hotProductIdList.clear();if(productCountMap.size() == 0){Utils.sleep(100);continue;}log.info("[HotProductFindThread]打印productCountMap的长度:{}",productCountMap.size());//1.做全局的排序for (Map.Entry<Long, Long> productCountEntry : productCountMap.entrySet()) {if (productCountList.size() == 0) {productCountList.add(productCountEntry);} else {boolean bigger = false;for (int i = 0; i < productCountList.size(); i++) {Map.Entry<Long, Long> topProductCountEntry = productCountList.get(i);int lastIndex = productCountList.size() < productCountMap.size() ? productCountList.size() - 1 : productCountMap.size() - 2;//如果存在某个商品超过任意一个热门商品,则该热门商品后面的所有热门商品全部向后移动if (productCountEntry.getValue() > topProductCountEntry.getValue()) {for (int j = lastIndex; j >= i; j--) {//每次都会移除掉最后一个元素//如果j+1 等于topProductList.size的时候,直接set(j+1,值)是设置不进去的//需要先在topProductList中增加一位if(j+1 == productCountList.size()){productCountList.add(null);}productCountList.set(j + 1, productCountList.get(j));}//i的位置设置为最新的热门商品productCountList.set(i, productCountEntry);bigger = true;break;}}if (!bigger) {if (productCountList.size() < productCountMap.size()) {productCountList.add(productCountEntry);}}}}for(Map.Entry<Long,Long> entry : productCountList){productIdList.add(entry.getKey());}log.info("[HotProductFindThread]计算出一份排序后的商品访问次数列表:{}",productIdList);//2.计算出95%的商品的访问次数的平均值Integer caculateCount = (int)Math.floor(productCountList.size() * 0.95);Long totalCount = 0l;for(int i = productCountList.size()-1;i>=productCountList.size()-caculateCount;i--){totalCount += productCountList.get(i).getValue();}Long avgCount = totalCount / caculateCount;//3.从第一个元素开始遍历,判断是否是平均值的10倍的访问量for(Map.Entry<Long,Long> entry : productCountList){if(entry.getValue() > 10 * avgCount){//就标志为热点hotProductIdList.add(entry.getKey());//将缓存热点反向推送到流量分发的nginx中String distributeNginxURL = "http://10.1.218.24/hot?productId" + entry.getKey();HttpClientUtils.sendGetRequest(distributeNginxURL);//将缓存热点,那个商品的对饮的完整的缓存数据,发送请求到缓存服务去获取,反向推送到所有后端应用nginx服务端上去String cacheServiceURL = "http://localhost:8080/getProductInfo?productId="+entry.getKey();//请求eshop-cacheString response = HttpClientUtils.sendGetRequest(cacheServiceURL);String[] appNginxURLs = new String[]{"http://10.1.218.22/hot?productId="+entry.getKey()+"&productInfo="+response,"http://10.1.218.26/hot?productId="+entry.getKey()+"&productInfo="+response};for(String appNginxURL : appNginxURLs){HttpClientUtils.sendGetRequest(appNginxURL);}}}Utils.sleep(5000);}catch (Exception e){}}}}

local uri_args = ngx.req.get_uri_args()local productId = uri_args["productId"]local host = {"10.1.218.22","10.1.218.26"};#local hash = ngx.crc32_long(productId);#hash = (hash % 2 ) + 1;#local backend = "http://"..host[hash];local method = uri_args["method"];local shopId = uri_args["shopId"];local backend = ""local hot_product_key = "hot_product_"..productIdlocal cache_ngx = ngx.shared.my_cachelocal hot_product_flag = cache_ngx:get(hot_product_key)if hot_product_flag == "true" thenmath.randomseed(tostring(os.time()):reverse():sub(1, 7))local index = math.random(1, 2) backend = "http://"..hosts[index]elselocal hash = ngx.crc32_long(productId)local index = (hash % 2) + 1backend = "http://"..hosts[index]endlocal requestBody = "/"..method.."?productId="..productId.."&shopId="..shopIdlocal http = require "resty.http";local httpc = http:new()local resp, err = httpc:request_uri(backend, {method = "GET", path = requestBody,keepalive = false})if not resp thenngx.say("request error:",err)returnendngx.say(resp.body)httpc:close()

在node3上新增

cancel_host.lua

local uri_args = ngx.req.get_uri_args()local product_id = uri_args["productId"]local cache_ngx = ngx.shared.my_cachelocal hot_product_cache_key = "hot_product_"..product_idcache_ngx:set(hot_product_cache_key, "false", 60)

在hello.conf 中,新增

location /cancel_hot {default_type 'text/html';# lua_code_cache off;content_by_lua_file /usr/hello/lua/cancel_hot.lua;}

1.用户访问分发nginx,由nginx将请求转发到node1或者是node2上的nginx上,默认是根据商品id进行hash计算,再分发到node2或者node3的nginx上的2.node2和node3的nginx,将用户访问了哪个商品,发送到kafka上,topic是:access-log3.storm拓扑消费access-log中的信息,计算热门商品id列表,并将热门商品发送到node3上nginx上4.node3的nginx将热门商品记录到缓存中,当有请求过来,计算该商品是否存属于热门商品(在node3的nginx缓存中去查找),如果是热门商品,则该商品的访问,就不采用hash计算分发,而是采用随机的方式分发到node2或者node3上5.默认node3上的热门商品会保存一个小时,一个小时以后自动删除;6.且storm也会每隔几分钟计算,新的热门商品id列表,如果以前的热门商品id已经不再热门了,在storm中则会发送取消该商品热门的请求7.node3上的nginx收到取消某商品的热门 的请求,则将该商品id 的热门状态,设置为false,之后该商品的访问又变为hash分发了

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。