基于京东:HotKey实现自动缓存热点Key!!!

一.引言

某些热点数据,我们提前如果能够预判到的话,可以提前人工给数据加缓存,也就是缓存预热,将其缓存在本地或者Redis中,提高访问性能同时,减低数据库压力,也减轻后端服务的压力。但是,有些时候,我们无法预料到哪些数据是热点,比如一个冷门数据,突然变成一个热点数据,没来得及缓存,突然被大量访问,系统不就故障了吗!?

因此,我们就需要帮助我们快速发现热点Key,并且自动缓存哪就不解决太多问题了嘛!!!因此京东的这个HotKey中间件就来了。

二.介绍

官网:JDHotKey

介绍:

对任意突发性的无法预先感知的热点数据,包括并不限于热点数据(如突发大量请求同一个商品)、热用户(如恶意爬虫刷子)、热接口(突发海量请求同一个接口)等,进行毫秒级精准探测到。然后对这些热数据、热用户等,推送到所有服务端JVM内存中,以大幅减轻对后端数据存储层的冲击,并可以由使用者决定如何分配、使用这些热key(譬如对热商品做本地缓存、对热用户进行拒绝访问、对热接口进行熔断或返回默认值)。这些热数据在整个服务端集群内保持一致性,并且业务隔离,worker端性能强悍。

京东APP后台热数据探测框架,历经多次高压压测和2020年京东618、双11大促考验。

在上线运行的这段时间内,每天探测的key数量数十亿计,精准捕获了大量爬虫、刷子用户,另准确探测大量热门商品并毫秒级推送到各个服务端内存,大幅降低了热数据对数据层的查询压力,提升了应用性能。

 

核心组件
它的主要核心组件如下:
1) Etcd 集群
Etcd 作为一个高性能的配置中心,可以以极小的资源占用,提供高效的监听订阅服务。主要用于存放规则配置,各 worker的 ip 地址,以及探测出的热 key、手工添加的热 key 等。Etcd 常用于配置中心和注册中心
2) client端iar包
就是在服务中添加的引用jar,引入后,就可以便捷地去判断某 key 是否热 key。同时,该 jar 完成了 key 上报、监听 Etcd
里的 rule 变化、worker 信息变化、热 key 变化,对热 key 进行本地 Caffeine 缓存等。
3) worker端集群
worker 端是一个独立部署的 Java 程序,启动后会连接 Etcd,并定期上报自己的 ip 信息,供 client 端获取地址并进行长连
接。之后,主要就是对各个 client 发来的待测 key 进行 累加计算,当达到 Etcd 里设定的 rule 阈值后,将热 key 推送到各
个client.
4) dashboard 控制台
控制台是一个带可视化界面的 Java 程序,也是连接到 Etcd,之后在控制台设置各个 APP 的 key 规则,譬如 2 秒出现,20次算热 key。然后当 worker 探测出来热 key 后,会将 key 发往 etcd,dashboard 也会监听热 key 信息,进行入库保存记录。同时,dashboard 也可以手工添加、删除热 key,供各个 client 端监听。


更详细的内容,可见京东技术团队 官方的文章,最具可信度。

三.安装

1. Etcd 安装:

在etcd下载页面下载对应操作系统的etcd,https://github.com/etcd-io/etcd/releases 使用3.4.x以上。 

下载后解压压缩包,会得到3个脚本

  • etcd:etcd 服务本身
  • etcdctl:客户端,用于操作 etcd,比如读写数据
  • etcdutl:备份恢复工具

输入etcd 启动!!! 

执行 etcd 脚本后,可以启动 etcd 服务,服务默认占用 2379 和 2380 端口,作用分别如下:

  • ·2379:提供 HTTP API服务,和 etcdct 交互
  • ·2380:集群中节点间通讯 

2. worker安装

从 hotkey 官方仓库 下载源码 ->>>> 下载地址

注意:::    JDK 的版本必须小于 17!否则会报找不到类,因为有些类jdk17已经弃用了!!!
的错误!
项目导入 IDEA后,打开 worker 模块。worker 是一个 Spring Boot 项目,启动前需要先修改 applicaiton.yml 中的配置。
比如端口配置( 8111) 

修改完配置后,直接点击WorkerApplication 启动即可,
如下图,此时 worker 就已经正常启动,并且连接上 Etcd 了:

3. 启动 hotkey 控制台

接着打开 dashboard 项目,执行 resource 目录下的 db.sql文件,创建 dashboard 所需的库表。hotkey 依赖 MySQL
储用户账号信息、热点阈值规则等。
在执行脚本前,记得先配置好 MySQL 连接,并且在 SQL 脚本文件中创建和指定数据库
执行脚本过程如图

修改端口及数据库配置 

server:port: 8182
spring:datasource:username: ${MYSQL_USER:root}password: ${MYSQL_PASS:1234}url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/hotkey_db?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&useTimezone=true&serverTimezone=GMTdriver-class-name: com.mysql.cj.jdbc.Driver
etcd:server: ${etcdServer:http://127.0.0.1:2379}

dashboard 也是一个 SpringBoot 项目,直接在 IDEA 内执行 DashboardApplication 启动即可
访问 http://127.0.0.1:8182,端口就是你自己配置的 即可看到界面: 

初始Sql的时候,会有一个默认账号: admin 密码: 123456 (加密了的) 登录:

初次使用时需要先添加 APP。建议先在用户管理菜单中,添加一个新用户,设置昵称为 APP 名称、并填写所属 APP,如
GCXF-App,密码此处就设置为 123456。之后就可以登录这个新建的用户来给应用设置规则了(当然也可以使用 admin 账
户添加),而且系统会自动创建一个 APP。

随后,在规则配置中,选择对应的 APP,新增对应的热点探测规则:

我这里的意思就是判断poem_开头的key,如果5秒内访问 如果5秒访问 10 次,就会被推送到jvm 内存中,将这个热 key 缓存3 分钟。


对应的规则配置如下:

[{"key":"poem_","prefix":true,"interval":5,"threshold":10,"duration":180,"desc":"搜索热点key"}
]

  • key:(*代表任意以key为前缀) 只要到时候client端上报这样子的key于这边相互匹配就可以完成统计
  • prefix:是否前缀,  
  • interval:间隔时间(秒),
  • threshold:阈值, 
  • duration-缓存时间(秒):默认60
  • desc:描述

 4. 引入 hotkey client

有2 种引入 hotkey client 的方式:
1.手动源码打包
2.通过 Maven 远程仓库 引入
由于 Maven 远程仓库的包引用量过少,而且不具备官方权威性,所以更推荐通过 hotkey 源码手动打包。
所以选择方式 1,手动将 hotkey 源码中的 client 模块通过 Maven 打成 jar 包:

在我们引入的项目中新键一个lib文件,然后放入client的jar包

 

引入依赖;

    <!-- hotkey --><dependency><artifactId>hotkey-client</artifactId><groupId>com.jd.platform.hotkey</groupId><version>0.0.4-SNAPSHOT</version><scope>system</scope><systemPath>${project.basedir}/lib/hotkey-client-0.0.4-SNAPSHOT.jar</systemPath></dependency>

 引入依赖后,在代码中编写初始化 client 的配置类,会读取配置文件并执行初始化逻辑

@Configuration
@ConfigurationProperties(prefix = "hotkey")
@Data
public class HotKeyConfig {/*** Etcd 服务器完整地址*/private String etcdServer = "http://127.0.0.1:2379";/*** 应用名称*/private String appName = "app";/*** 本地缓存最大数量*/private int caffeineSize = 10000;/*** 批量推送 key 的间隔时间*/private long pushPeriod = 1000L;/*** 初始化 hotkey*/@Beanpublic void initHotkey() {ClientStarter.Builder builder = new ClientStarter.Builder();ClientStarter starter = builder.setAppName(appName).setCaffeineSize(caffeineSize).setPushPeriod(pushPeriod).setEtcdServer(etcdServer).build();starter.startPipeline();}
}

注:这里spring-boot不要太高,因为不允许是void的返回值的(也可以用提供的接口来解决),所有干脆调到3.0.2下就可以了

pom.xml


# 热 key 探测
hotkey:app-name: GCXF-Appcaffeine-size: 10000push-period: 1000etcd-server: http://localhost:2379app-name: GCXF-App: 这是一个键值对,其中app-name是键,GCXF-App是值。这表示应用程序的名称被设置为GCXF-App。这个值可能用于标识这个配置所属的应用程序。caffeine-size: 10000: 这也是一个键值对,其中caffeine-size是键,10000是值。这个值可能代表某种缓存(可能是指Caffeine缓存库)的大小设置,单位可能是条目数、字节或其他,具体取决于上下文。Caffeine是一个高性能的Java缓存库。push-period: 1000: 这是一个表示时间间隔的键值对,意味着这个操作每秒去Push一次 上报一次。etcd-server: http://localhost:2379: 这个值指定了etcd服务器的地址和端口,客户端可以通过这个地址与etcd服务器进行通信。

这里的App-name与你先去在etcd中配置app必须一致

启动:可以看到有一个客户端链接了

OK,我们就可以使用了

四.使用

主要有如下4个方法可供使用

  1. boolean JdHotKeyStore.isHotKey(String key)
  2. Object JdHotKeyStore.get(String key)
  3. void JdHotKeyStore.smartSet(String key, Object value)
  4. Object JdHotKeyStore.getValue(String key)

1 boolean isHotKey(String key) ,该方法会返回该key是否是热key,如果是返回true,如果不是返回false,并且会将key上报到探测集群进行数量计算。该方法通常用于判断只需要判断key是否热、不需要缓存value的场景,如刷子用户、接口访问频率等。

2 Object get(String key),该方法返回该key本地缓存的value值,可用于判断是热key后,再去获取本地缓存的value值,通常用于redis热key缓存

3 void smartSet(String key, Object value),方法给热key赋值value,如果是热key,该方法才会赋值,非热key,什么也不做

4 Object getValue(String key),该方法是一个整合方法,相当于isHotKey和get两个方法的整合,该方法直接返回本地缓存的value。 如果是热key,则存在两种情况,1是返回value,2是返回null。返回null是因为尚未给它set真正的value,返回非null说明已经调用过set方法了,本地缓存value有值了。 如果不是热key,则返回null,并且将key上报到探测集群进行数量探测。

 官网推荐的最佳实践:

1 判断用户是否是刷子

if (JdHotKeyStore.isHotKey(“pin__” + thePin)) {// 进行限流
}

2 判断商品id是否是热点

Object skuInfo = JdHotKeyStore.getValue("skuId__" + skuId);
if(skuInfo == null) {JdHotKeyStore.smartSet("skuId__" + skuId, theSkuInfo);
} else {// 使用缓存好的 value 即可
}

或者这样:

if (JdHotKeyStore.isHotKey(key)) {//注意是get,不是getValue。getValue会获取并上报,get是纯粹的本地获取Object skuInfo = JdHotKeyStore.get("skuId__" + skuId);if(skuInfo == null) {JdHotKeyStore.smartSet("skuId__" + skuId, theSkuInfo);} else {//使用缓存好的value即可}
}

代码测试:

  @Autowiredprivate HotKeyPoemMapper hotKeyPoemMapper;@Autowiredprivate PoemNameMapper poemNameMapper;private RestHighLevelClient restHighLevelClient = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.184.128:9200")));/*** 查询所有古诗** @param name* @return*/@Overridepublic PoemNameVO selectPoemByName(String name) {String key = "poem_" + name;// 判断是否是热搜if (JdHotKeyStore.isHotKey(key)) {// 获取缓存Object poemNameVO = JdHotKeyStore.get(key);if (poemNameVO != null) {return (PoemNameVO) poemNameVO;}}PoemName poenNmae = null;poenNmae = poemNameMapper.selectPoemByName(name);if (poenNmae == null) {throw new PoenException(MessageContast.POEM_ERROR);}PoemNameVO poemNameVO = new PoemNameVO();//获取注释List<poemExplain> poemExplains = poemNameMapper.selectBypoenID(Long.valueOf(poenNmae.getId()));//判断是否有该收藏古诗 1返回true null 返回faslePoemNameVO poemNameVO1 = poemNameMapper.selectCollectByHeader(name);if (poemNameVO1 == null) {poemNameVO.setTrue(false);} else {poemNameVO.setTrue(true);}List<String> list = new ArrayList<>();//获取全文集合String[] split = poenNmae.getAllpoem().split("。");for (String s : split) {list.add(s);}poemNameVO.setAllpoem(list);list = new ArrayList<>();//获取背景集合String[] split1 = poenNmae.getPoemDrop().split("。");for (String s : split1) {list.add(s);}poemNameVO.setPoemDrops(list);//复制传参BeanUtils.copyProperties(poenNmae, poemNameVO);poemNameVO.setPoemExplain(poemExplains);if(JdHotKeyStore.isHotKey(key)){// 设置缓存JdHotKeyStore.smartSet(key, poemNameVO);//并且给热点key累加排行poemNameMapper.sumTheHotKey(name);}return poemNameVO;}

因为上面我们配置了5秒内访问10次就会变成热点Key,之后我们就会把这个数据存储到本地缓存中,下次访问的时候就会直接从本地缓存中去读取了,并不会在去查询数据库了。 

我们测试一下

通过接口5秒内访问了10次后 

然后可以看间 事实热点已经有了

 当我们再次访问,就不会查询数据库了,都是通过本地缓存来查询,可以感觉到非常块!

源码;

1) 热 key 会自动续期吗?否则可能出现缓存雪崩的问题?

  public static boolean isHotKey(String key) {try {if (!inRule(key)) {return false;} else {boolean isHot = isHot(key);if (!isHot) {HotKeyPusher.push(key, (KeyType)null);} else {ValueModel valueModel = getValueSimple(key);if (isNearExpire(valueModel)) {HotKeyPusher.push(key, (KeyType)null);}}KeyHandlerFactory.getCounter().collect(new KeyHotModel(key, isHot));return isHot;}} catch (Exception var3) {return false;}}public static Object get(String key) {ValueModel value = getValueSimple(key);if (value == null) {return null;} else {Object object = value.getValue();return object instanceof Integer && Constant.MAGIC_NUMBER == (Integer)object ? null : object;}}

分析:

然后看下源码,就知道为什么了。源码中的逻辑是,首先会校验这个key是否在规则中,如果不是当然返回fasle,然后才判断是否是热点key,如果已经是热 key ,返回缓存值但是不会再 push,离过期还有2秒内的时候,会再次 push,这样这个 key 可能被继续设置为热 key。

也就是说,如果一个 key 持续被访问,很有可能在过期前一直被设置为热点,减少了出现雪崩问题的可能性。

2.)能够和 redis 分布式缓存结合
热 key 探测 = 热 key 发现 +本地缓存。可以只利用热 key 的判断方法,来给我们判断哪些是热Key,不利用热 key 的存储方法即可,通过换成redis存储也是可以

方法:

1.不是热 key,就查数据库。对于热 key,写缓存时,再判断一下是否为热 key,是热 key 才设置 Redis 分布式缓存。后续的热 key 就可以从分布式缓存中获取值。(缓存存储的技术或者位置变了)
2.利用热 key 探测的本地缓存,将原本査数据库的逻辑改为査 Redis,Redis 查不到才查询数据库,形成多级缓存。
 

 3.) 如何更新本地缓存
需要有一个入口让缓存失效,进行人工干预。hotkey 提供了 JdHotKeystore.remove()方法,可以手动删除本地缓存并移除热点 key。
以利用控制台手动删除:

不过一般情况下,热点信息一般都是不太会变更的数据,过期时间设置短一点即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/446241.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大顶堆优化dp,带限制的子序列

前言&#xff1a;看到这个题目的时候我们可以用大顶堆记录前面的最大值&#xff0c;这样我们转移的时候就少了很多繁琐的查询 题目地址 class Solution { public:int constrainedSubsetSum(vector<int>& nums, int k) {int n nums.size();vector<int> ans nu…

【计算机网络 - 基础问题】每日 3 题(三十八)

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?typeblog &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞…

小米路由器刷机istoreOS,愉快上网

istoreOS与openwrt openwrt是一个开源的路由器系统&#xff0c;市场上所有小米路由器的内部系统都是基于openwrt进行二次开发形成的&#xff0c;做了硬件适配和功能上的阉割&#xff0c;不太好用。 istoreos是小宝团队基于openwrt制作的一个发行版&#xff0c;更适合中国宝宝体…

计算机网络:数据链路层 —— 网络适配器与 MAC 地址

文章目录 网络适配器使用网络适配器网络适配器类型 MAC 地址MAC 地址格式MAC 地址类型MAC 地址发送顺序数据接收MAC 地址泄露问题 网络适配器 要将计算机连接到以太网&#xff0c;需要使用相应的网络适配器&#xff08;Adapter)&#xff0c;网络适配器一般简称为“网卡”。在计…

AirServer v7.2.7 破解版 – iPhone屏幕镜像工具

AirServer v7.2.7 破解版 – iPhone屏幕镜像工具可以将你的Mac转变为一个通用的镜像接收器&#xff0c;除了无法接收Miracast外&#xff0c;你可以使用内置的AirPlay或Google Cast基于屏幕投影功能来镜像你的设备屏幕&#xff1b;一次一个或同时投影到AirServer。用户可以从任何…

TGRS 2024 面向雾天遥感图像的定向目标检测算法

TGRS 2024 | 面向雾天遥感图像的定向目标检测算法 论文信息 摘要 目前&#xff0c;大量工作集中在航空目标检测上&#xff0c;并取得了良好的结果。尽管这些方法在传统数据集上取得了有希望的结果&#xff0c;但在恶劣天气条件下捕获的低质量图像中定位对象仍然具有挑战性。目…

数据库课程 CMU15-445 2023 Fall Project-2 Extendible Hash Index

0 实验结果 tips:完成项目的前提不需要一定看视频 1 数据结构&#xff1a;扩展哈希 解释下这张图&#xff1a; 图中header的最大深度2&#xff0c;directory最大深度2&#xff0c;桶的容量2。 最开始的时候只有一个header。 插入第一个数据&#xff0c;假设这个数据对应的哈希…

[自然语言处理]RNN

1 传统RNN模型与LSTM import torch import torch.nn as nntorch.manual_seed(6)# todo:基础RNN模型 def dem01():参数1&#xff1a;input_size 每个词的词向量维度&#xff08;输入层神经元的个数&#xff09;参数2&#xff1a;hidden_size 隐藏层神经元的个数参数3&#xff1a…

【puppeteer】wvp-puppeteer制作 过程

目录 最后的结论 制作windows&ubuntu的docker 重启桌面上的docker 命令重启 通过 Docker Desktop 图形界面重启 制作centos docker 测试 参考文档 最后的结论 ubuntu && windows 使用 dualvenregistry:5000/wvp-puppeteer:1.0 centos7 使用&#xff1a;…

RabbitMQ事务模块

目录 消息分发​​​​​​​ 负载均衡 幂等性保障 顺序性保障 顺序性保障方案 二号策略:分区消费 三号策略:消息确认机制 四号策略: 消息积压 RabbitMQ集群 选举过程 RabbitMQ是基于AMQP协议实现的,该协议实现了事务机制&#xff0c;要么全部成功&#xff0c;要么全…

Java——数组的定义与使用

各位看官&#xff1a;如果您觉得这篇文章对您有帮助的话 欢迎您分享给更多人哦 感谢大家的点赞收藏评论&#xff0c;感谢您的支持&#xff01;&#xff01;&#xff01; 一&#xff1a;数组的概念以及定义,初始化 1.1&#xff1a;数组概念以及定义 数组概念&#xff1a;可以看成…

四边形网格生成算法:Q-Morph(三)底边生成四边形

欢迎关注更多精彩 关注我&#xff0c;学习常用算法与数据结构&#xff0c;一题多解&#xff0c;降维打击。 参考论文&#xff1a;Q-Morph an indirect approach to advancing front quad meshing ε − π − θ ∈ ⋅ \varepsilon - \pi - \theta \in \cdot ε−π−θ∈⋅ …

通过redis实现高性能计费处理逻辑

计费服务一般都是跟资金相关&#xff0c;所以它在系统中是非常核心的模块&#xff0c;要保证服务的高可用、事务一致性、高性能。服务高可用需要集群部署&#xff0c;要保证事务一致性可以通过数据库来实现&#xff0c;但是只通过数据库却很难实现高性能的系统。 这篇文章通过使…

解锁5 大无水印热门短视频素材库

想让你的抖音视频更出彩吗&#xff1f;想知道那些爆款视频的素材源头吗&#xff1f;快来了解以下 5 个超棒的视频素材下载平台。 蛙学网 国内的视频素材佼佼者&#xff0c;有大量 4K 高清且无水印的素材&#xff0c;自然风光、情感生活等类别任你选&#xff0c;不少还免费&…

关于wordpress建站遇到的问题

&#x1f3c6;本文收录于《全栈Bug调优(实战版)》专栏&#xff0c;主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&am…

Spring WebFlux 核心原理(2-1)

1、Spring 响应式编程 1.1、早期响应式解决方案 响应式编程是构建响应式系统的主要候选方案。Spring 4.x 引入了 ListenableFuture 类&#xff0c;它扩展了 Java Future&#xff0c;并且可以基于 HTTP 请求实现异步执行操作。但是只有少数 Spring 4.x 组件支持新的 Java 8 Com…

瑞芯微RK3566/RK3568 Android11使用OTA升级固件方法,深圳触觉智能鸿蒙开发板演示,备战第九届华为ICT大赛

本文介绍瑞芯微RK3566/RK3568在Android11系统OTA升级固件方法&#xff0c;使用触觉智能的Purple Pi OH鸿蒙开发板演示&#xff0c;搭载了瑞芯微RK3566&#xff0c;Laval官方社区主荐&#xff01; 1、OTA包生成 在源码根目录上执行以下命令编译OTA包 # make installclean # …

【华为HCIP实战课程七】OSPF邻居关系排错MTU问题,网络工程师

一、MTU MUT默认1500,最大传输单元,一致性检测 [R3-GigabitEthernet0/0/1]mtu 1503//更改R3的MTU为1503 查看R3和SW1之间的OSPF邻居关系正常: 默认华为设备没有开启MTU一致性检测! [R3-GigabitEthernet0/0/1]ospf mtu-enable //手动开启MTU检测 [SW1-Vlanif30]ospf mtu…

【详细教程】如何使用YOLOv11进行图像与视频的目标检测

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

《数字信号处理》学习08-围线积分法(留数法)计算z 逆变换

目录 一&#xff0c;z逆变换相关概念 二&#xff0c;留数定理相关概念 三&#xff0c;习题 一&#xff0c;z逆变换相关概念 接下来开始学习z变换的反变换-z逆变换&#xff08;z反变化&#xff09;。 由象函数 求它的原序列 的过程就称为 逆变换。即 。 求z逆变换…