首页 > 其他分享 >基于京东:HotKey实现自动缓存热点Key!!!

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

时间:2024-10-09 22:50:37浏览次数:9  
标签:缓存 key value Key etcd HotKey null String

一.引言

某些热点数据,我们提前如果能够预判到的话,可以提前人工给数据加缓存,也就是缓存预热,将其缓存在本地或者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。
以利用控制台手动删除:

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

标签:缓存,key,value,Key,etcd,HotKey,null,String
From: https://blog.csdn.net/2301_77058976/article/details/142769349

相关文章

  • Redis学习之缓存预热、缓存雪崩、缓存击穿、缓存穿透
    一、缓存预热    缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。问题排查1.请求数量较高2.主从之间数据吞吐量较大,数据同步操作频度较......
  • 解密网易云音乐Web端的请求参数params与encSecKey
    网易云音乐作为一款功能强大的音乐流媒体平台,为用户提供了丰富的音乐体验和社区互动。然而,当用户希望批量下载音乐资源,尤其是整个歌单或某位歌手的全部歌曲时,受限于版权保护和平台策略,官方渠道通常无法直接实现这一需求。在这种情况下,一些技术爱好者或开发者可能会转向使用......
  • chrome清除特定域名缓存
    在特定情况下,用户可能需要仅清除特定网站的缓存而不是全部浏览器缓存。这通常发生在以下几种场景:网站在更新后未正确加载新内容、开发人员在调试或部署过程中需要测试最新更改、缓存中的过时数据引起功能问题、或用户遇到特定站点的显示和性能异常时。通过清除特定域名的......
  • kubekey 快速构建重构测试k8s 环境 allinone单机 or cluster 集群
    exportKKZONE=cncurl-sfLhttps://get-kk.kubesphere.io|VERSION=v3.0.13sh-生成配置k8s集群yml指定k8s版本及管理面板./kkcreateconfig--with-kubernetesv1.23.10--with-kubespherev3.4.1apiVersion:kubekey.kubesphere.io/v1alpha2kind:Clustermetada......
  • GM_registerMenuCommand()注册菜单模板,TemperMonkey油猴脚本开发
    注册菜单点击事件注册实例设置默认值将handle(手握实例的变量)赋值给一个变量functionmenu_Func_click(){GM_setValue('Func',!GM_getValue('Func'));//开关GM_unregisterMenuCommand(menu_Func);//卸载再注册//强制等待下一个事件循环setTimeo......
  • 怎么清除浏览器缓存?浏览器缓存清理的方法步骤是什么?
    打开浏览器,点击右上角的三个点,然后选择设置按钮。点击“隐私设置和安全性”按钮。点击“清除浏览数据”。选择时间范围,点击清除数据即可,这样就可以将浏览器中的浏览记录、Cookie、缓存的图片和文件清除了。第一步打开edge浏览器,点击右上角的三个点,选择设置。第二步点击选......
  • 宝塔开启Redis高速缓存功能方法
    Redis是一种高性能的键值存储系统,具有多种优势,适用于网站缓存场景。以下是Redis的主要优点:多数据结构支持:Redis支持简单的键值对(K/V)类型的数据。还支持列表(List)、集合(Set)、有序集合(ZSet)和哈希表(Hash)等多种数据结构。主从模式支持:Redis支持主从复制模式,可以轻松实......
  • 帝国CMS后台添加信息报错Duplicate entry xx for key PRIMARY
    当在帝国CMS后台添加信息时遇到 Duplicateentry'xx'forkey'PRIMARY' 的错误时,通常是因为主键冲突。以下是一些解决该问题的方法:方法1:后台修复数据库进入后台:登录帝国CMS后台。进入 系统 -> 备份与恢复数据 -> 备份数据。修复数据表:在页面底部,找到 修复......
  • DBeaver 连接 mysql 报错:Public Key Retrieval is not allowed
    前言DBeaver连接mysql报错:PublicKeyRetrievalisnotallowed遇到"PublicKeyRetrievalisnotallowed"错误时,通常意味着你正在使用的身份验证方法需要加密连接,但是没有正确地配置客户端或服务器来支持这种加密。解决第一种可以在连接字符串中添加 allowPublicKey......
  • 【Shiro】3.Springboot实现缓存
    最近已经快速入门了Shiro。对于登录、授权、认证等方法,每次都是从数据库直接查询。如果登录的人员过多,对数据库来说,是一项压力。如何减轻数据库的压力。EhCache实现缓存集成Redis实现Shiro缓存(推荐使用)在此之前,我们已经简单学会EhCache和Reids的使用。EhCache实现缓......