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

news2024/11/20 2:23:59

一.引言

某些热点数据,我们提前如果能够预判到的话,可以提前人工给数据加缓存,也就是缓存预热,将其缓存在本地或者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=GMT
    driver-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
     */
    @Bean
    public 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-App
  caffeine-size: 10000
  push-period: 1000
  etcd-server: http://localhost:2379

app-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即可
    }
}

代码测试:

  

    @Autowired
    private HotKeyPoemMapper hotKeyPoemMapper;
    @Autowired
    private PoemNameMapper poemNameMapper;
    private RestHighLevelClient restHighLevelClient = new RestHighLevelClient(RestClient.builder(
            HttpHost.create("http://192.168.184.128:9200")
    ));

    /**
     * 查询所有古诗
     *
     * @param name
     * @return
     */
    @Override
    public 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 返回fasle
        PoemNameVO 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.coloradmin.cn/o/2199870.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

imageJ 将多图中的同一条划线数据用曲线展示

1、导入图片&#xff0c;将所需的图片放进同一个文件夹&#xff0c;按顺序命名 2、划线 3、导出数据及画曲线 1&#xff09;得到单图的曲线上的图像数据&#xff1a;选中图片 -----> ctrl k 2&#xff09;将多张图像的同一条曲线位置的图像数据在同一数轴上画出曲线 步骤…

SpringBoot中applicaiton.properties转换成application.yml 插件

点击idea中的File----->点击setting----->点击Plugins-----> 输入 Convert Yaml and Properties File ----->然后Install下载 -----> 点击OK应用 最后选择Application.properties----->右击Convert YAML and properties File----->即转换成功了

VUE项目与原生Javascript Js功能模块的整合-政府项目涉及Ukey使用和开发

一、VUE项目与原生Javascript Js功能模块的整合 因为产品要整体做三级等保验证&#xff0c;而等保需要做密码安全评价&#xff08;密评&#xff09;&#xff0c;最终的方案就是需要使用 Ukey 来登录管理后台&#xff0c;而这最终涉及在我们的VUE项目中去调用第三方 UKEY 厂商提…

浙大数据结构:08-图8 How Long Does It Take

这道题算是较为简单的拓扑排序题&#xff0c;难度不大 机翻 1、条件准备 n,m为n个结点&#xff0c;m条边。 tim数组存到该结点完成的最早时间&#xff0c;会一点点更新 graph存有向边的时间 indegree数组存每个结点的入度 #include <iostream> #include <vector&g…

扫描电镜是用来测什么的?

扫描电镜是一种用于对样品进行微观尺度形貌观测和分析的仪器。它能够提供高分辨率的图像&#xff0c;帮助科学家和工程师了解样品的微观结构和特性。 一、扫描电镜的一般测量功能 微观形貌观测 扫描电镜可以清晰地观察到样品表面的微观形貌&#xff0c;如颗粒的形状、大小、…

【Oracle APEX开发小技巧9】通过页面设置文本大写避免upper()函数转换占用额外资源

进行规范改造登录函数和存储过程时&#xff0c;发现有些应用的登录函数/存储过程中有upper()函数的使用&#xff0c;因为登录时输入工号&#xff0c;默认无更改&#xff0c;为了与数据库中存储的数据对应&#xff0c;通过upper()将其文本中字母设置为大写。 若要解决这个问题&…

C++初阶---C++入门(下)

目录 一、内联函数 1.内联函数的定义与底层机制 0x01.内联函数的定义 0x02.内联函数的底层机制 2.内联函数的优缺点 优点&#xff1a; 缺点&#xff1a; 3.内联函数的使用建议 4.内联函数的注意事项 二、auto关键字&#xff08;C11&#xff09; 1.代码示例 2.auto使…

flask项目框架搭建

目录结构 blueprints python包&#xff0c;蓝图文件&#xff0c;相当于路由组的概念,方便模块化开发 例如auth.py文件 from flask import Blueprint, render_templatebp Blueprint("auth", __name__, url_prefix"/auth")bp.route("/login") d…

李沐 X 动手学深度学习 深度学习介绍 学习笔记

x轴是不同的模式&#xff1a;符号学---概率模型---机器学习y轴是我们想做的东西&#xff08;问题领域&#xff09;&#xff1a;感知&#xff08;了解这是什么东西&#xff0c;能看见这个物体&#xff09;---&#xff08;做&#xff09;推理&#xff08;基于我看到的东西想象未来…

揭秘猫咪掉毛的真实原因有哪些?掉毛飞毛宠物空气净化器来救援!

作为三猫家庭&#xff0c;日常家里的毛发、异味一直是困扰我很久的难题。最近窗外的世界柳絮满天飘&#xff0c;家里猫毛满飞&#xff0c;养猫家庭应该都不陌生吧&#xff0c;鼻子嘴巴甚至是眼睛里总感觉有猫毛。不管猫砂换的多勤快&#xff0c;也总能闻到阵阵臭味&#xff01;…

【git】git add时warning:LF will replaced by CRLF

git add时warning&#xff1a;LF will replaced by CRLF 一&#xff0c;问题现象二&#xff0c;问题原因&解决方法 一&#xff0c;问题现象 二&#xff0c;问题原因&解决方法 这个警告的原因是 Git 在进行文件添加操作时&#xff0c;发现行尾结束符不一致。 在不同的…

Klick‘r3.0.4 |智能自动点击,高效省力

Klick’r 是一款专为 Android 设计的开源自动点击工具&#xff0c;能识别屏幕上的图像并进行相应操作。支持游戏中的自动点击、应用测试及日常任务自动化。 大小&#xff1a;27M 百度网盘&#xff1a;https://pan.baidu.com/s/1881Zfevph6_2Zhdc-H_R4A?pwdolxt 夸克网盘&…

定点数和浮点数的详细介绍(一)定义、范围、位宽

1.定点数 1.1定点数描述 定点数包括定点小数(纯小数)、定点整数(纯整数)、整数和小数位数固定的实数。 1.2定点小数: 小数点默认在符号位后面,首位为符号位,其他为数值位(在用二进制代表小数时,例如0xFF,就表示0.5+0.25+0.125+0.0625+0.03125........) 例如,用…

【p2p、分布式,区块链笔记 UPNP】: Libupnp的线程池简述

线程池在网络编程中是一个关键的组成部分&#xff0c;尤其是处理高并发请求时&#xff0c;线程池可以显著提高系统的性能和资源利用效率。它的关键组成部分包括以下几个要素&#xff1a;任务队列&#xff1a;一个&#xff08;或多个&#xff09;用于存放待执行任务的队列。任务…

简单花20分钟学会top 命令手册 (linux上的任务管理器)

1. 介绍 top 是一个常用的 Linux 命令行工具&#xff0c;用于实时监视系统资源和进程的运行情况。用户可以通过 top 命令查看系统的 CPU 使用率、内存占用情况、进程列表等重要信息&#xff0c;帮助快速了解系统运行状态并进行性能监控。该工具可以认为相当于windows上的任务管…

什么是MAC地址?有必要隐藏MAC地址吗?

你是否曾经停下来思考&#xff0c;每当你上网时&#xff0c;你的数字足迹可能会泄露你的个人信息&#xff1f;可能你会问&#xff0c;MAC地址是什么&#xff1f;简单来说&#xff0c;每台联网的电脑MAC地址都是独一无二的&#xff0c;就像是你设备在网络世界中的身份证。它能帮…

MySQL多表查询案例

先看我的表数据 dept表 emp表 salgrade表 student表 course表 student_course表 1. 查询员工的姓名&#xff0c; 年龄&#xff0c; 职位&#xff0c; 部门信息&#xff08;隐式内连接&#xff09; -- 表&#xff1a;emp&#xff0c; dept -- 连接条件 emp.dept_id dept.id …

那些年,我们一起追过的函数:揭秘数学世界里的‘网红’们

在数学中&#xff0c;函数是一种特殊的关系&#xff0c;它将一个集合中的每个元素&#xff08;称为自变量或输入&#xff09;映射到另一个集合中的唯一元素&#xff08;称为因变量或输出&#xff09;。根据函数的性质和定义域、值域的不同&#xff0c;可以将函数分为多种类型。…

七氟烷麻醉药市场研究:未来几年年复合增长率CAGR为4.2%

七氟烷是一种吸入麻醉剂&#xff0c;用于在外科手术过程中诱导和维持全身麻醉。七氟烷是一种挥发性麻醉剂&#xff0c;常用于在外科手术过程中诱导和维持全身麻醉。它因起效快和作用消失快而受到青睐&#xff0c;是成人和儿科患者的理想选择。七氟烷通常通过吸入起作用&#xf…

如何使用Colly库进行大规模数据抓取?

在互联网时代&#xff0c;数据的价值日益凸显&#xff0c;大规模数据抓取成为获取信息的重要手段。Go语言因其高效的并发处理能力&#xff0c;成为编写大规模爬虫的首选语言。Colly库作为Go语言中一个轻量级且功能强大的爬虫框架&#xff0c;能够满足大规模数据抓取的需求。本文…