JD HotKey 入门使用

JD HotKey 入门使用

根据官方仓库描述:hotkey 是京东APP后台热数据探测框架,历经多次高压压测和京东 618、双 11 大促考验。

  • 作用:

    自动统计接口调用次数,人为配置规则来判断是不是热点数据。并且可支持分布式。

    例子 :

    • 现在有一个接口是 /api/qusetion/{id} 有时候不知道怎么回事这个接口在 1s 内用户访问了100w 次,如果每次都去请求像 mysql 这样的持久化数据库, IO 花费的资源是巨大,造成资源浪费不说,搞不好数据库压力太大导致直接挂了(那后果就大了……)。当然我们会想到用像 redis 去做一层缓存,redis 没命中才去找 mysql . 但是 redis 是需要我们人为手动的去控制那些数据该缓存,那些数据该预热的。那比如某天我家哥哥本来没那么火的(你根本不会想到为我家哥哥的发帖数据进行 redis 缓存)突然一天发了个打篮球的视频就是火了,全网点击量库库库往上涨,每秒点击量 100W 次,你没有为这些数据做缓存,用户疯狂攻击你的 mysql 导致挂了整个系统都瘫痪了(你甚至还在睡梦中,第二天带上假发去上班却发现工位上多了一份离职申请书)

    • 你坐下操作将我家哥哥的数据存到 redis ,再重启 mysql ,终于系统又能正常运行了。打了一杯水,然后上个厕所,再从 gitee 上拉了 JDHotKey 的项目顺便把那个什么 喜人申请书撕得稀碎

    • 所以 JDHotKey 针对这个例子干了什么可以避免上面这种情况的发生呢?

      • 统计访问次数,服务器将接口的访问记录上报给worker,由 worker 来统计这个接口 在 某段内被访问过多少次。(当然不能服务器自己统计啦,因为又不只自己一台服务器提供了这个接口的服务)
      • 判断是不是热值,这个是按照我们自己配置的规则来设置的,比如 我觉得 1s 内访问>=1W次那这个就是热点数据了
      • 缓存这个这个热点数据,下次在访问的时候就从缓存中那就行了,别老来烦 mysql 这位差点让我丢掉工作的大哥
      • 缓存是有存活时间的,时间到了就没有缓存了 又去访问 mysql (mysql才是最新的数据),那么下次访问又过于频繁,又缓存了一次而且还是最新的数据 妙啊。
  • 架构图(从官网扒的)

  • 使用
    • 安装 etcd 并启动 (把它当作一个注册中心来看就行,比 zookeeper 轻便 性能高 毕竟是 go 写的)

      直接从 github 上下载最新版本的 Etcd 即可,选择对应的操作系统版本:

      下载之后解压,双击 etcd.exe 可以看到刷的一下日志就启动了 默认占用的是 2379端口

    • 安装并使用 JDHotKey

      hotkey 官方仓库 下载源码后打开可以看到如下目录结构

      首先打开 worker 模块可以看看 配置文件,更改一些配置避免冲突,我这改了一个端口 默认是8080 的我改为 8111,这里也能看到 work是通过 netty 去连接 etcd 并且 10s 上传一次心跳…… etcd 服务的地址 是 本地的 2379端口(所以要先启动 etcd 服务)

      我改完的样子:

      netty:
        port: ${nettyPort:11111}
        heartBeat: ${heartBeat:10}
        timeOut: ${timeOut:5000}
      local:
        address: ${localAddress:} #有些获取到的ip不能用,需要手工配worker的地址
      open:
        timeout: ${openTimeOut:true}
        monitor: ${openMonitor:false}  #开启持续无key发送监控,如果持续1分钟没发来key,就断开和etcd的连接,之后重建和客户端连接
      thread:
        count: ${threadCount:0}
      caffeine:
        minutes: ${caffeineMinutes:1}
      disruptor:
        bufferSize: ${bufferSize:2} #必须是2的整数倍
      #etcd的地址,如有多个用逗号分隔
      etcd:
        server: ${etcdServer:http://127.0.0.1:2379} #etcd的地址,重要!!!
        workerPath: ${workerPath:default} #该worker放到哪个path下,譬如放/app1下,则该worker只能被app1使用,不会为其他client提供服务
      server:
        port: 8111
      

在打开看一下 dashboard 的配置,可以看到这里有数据库配置,那我们就得按照自己数据库来改了 比如改 username password 自行配置

这是我改完的样子:  改了端口号为 8112 数据库相关配置 连接的库是 hotkey。

既然要连接数据库,那我们肯定要创建一个数据库并准备一些表,在 dashboard  模块的配置文件同级目录下就有一个 db.sql 文件 我们只要创建一个名为 hotkey 的库然后运行这个文件ok了

```java
server :
  port : 8112
  servlet :
    context-path : /
spring :
  resources:
    static-locations: classpath:/resources,classpath:/static
  profiles :
    active : dev
    # 服务模块
  devtools:
    restart:
      # 热部署开关
      enabled: true
  mvc:   #静态文件
    static-path-pattern : /static/**
  #模板引擎
  thymeleaf:
    model: HTML5
    prefix: classpath:/templates/
    suffix: .html
    #指定编码
    encoding: utf-8
    #禁用缓存 默认false
    cache: false
  jackson:
    time-zone: GMT+8
    date-format: yyyy-MM-dd HH:mm:ss
  datasource:
    username: ${MYSQL_USER:root}
    password: ${MYSQL_PASS:123456}
    url: jdbc:mysql://${MYSQL_HOST:localhost}:3306/hotkey?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC&useTimezone=true&serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver
    hikari:
      max-lifetime: 120000
      idle-timeout: 60000
      connection-timeout: 30000
      maximum-pool-size: 32
      minimum-idle: 10
pagehelper:
    helperDialect: mysql
    reasonable: true
    supportMethodsArguments: true
    params: count=countSql
#mybatis:
#  configuration:
#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

etcd:
  server: ${etcdServer:http://127.0.0.1:2379}
```

好了整完这些配置我们就可以试着运行一下了。

先运行一下 dashboard 项目 如果没有报错就可以看到成功连接 etcd 信息,如果数据库、端口连接有问题就会报错

![](http://images.eliauk.fun/202424011407-I.png)

因为刚才我们为 dashboard 项目配置的端口是8112所以我们可以打开 http://localhost:8112/   用户名是 admin 密码是 123456 (也在配置文件中配置的) 登录可以看到如下画面

![](http://images.eliauk.fun/202424011411-w.png)

我们点一下节点信息菜单会发现列表是空,那是因为我们没有启动 worker 项目,待会我们启动了worker 项目可以再回头看看

![](http://images.eliauk.fun/202424011420-L.png)

再来启动一下 worker 项目 我们可以看到 worker 在定时上传心跳 和 统计 那就说明启动成功了

![](http://images.eliauk.fun/202424011414-e.png)

这时可以回头 去看 节点信息是否多一列数据 为当前你启动的 worker 的信息。因为它俩连的etcd 是同一个服务当然可以互相发现。


两个项目都可以启动就就可以先停掉了,使用 maven 插件打包一下这整个项目

![](http://images.eliauk.fun/202424011423-6.png)

找到 client 项目的 with-dependdencies 这个 jar 包 这可是好东西

![](http://images.eliauk.fun/202424011425-u.png) 
  • 在 springboot 项目中使用
    • 先将刚才我们得到的 jar包 复制到自己项目中,这里我放到 lib 目录下 改了个名其实就是那个 with-dependdencies的 jar 包

    • 在pom.xml文件中引入这个包 (system 引入可能会造成一些依赖版本冲突问题,但是maven仓库的hotkey-client 又不是很好用………… )
      <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>
      
    • 编写配置文件 (官方示例)
      @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();
          }
      }
      

      接着我们就可以配置 在项目的配置文件 application.yml 中加入 (可以自行配置 这里假设我们的springboot 应用就叫 gg-video)

      # 热 key 探测
      hotkey:
        app-name: gg-video
        caffeine-size: 10000 # 缓存内存大小
        push-period: 1000    # 每 1000 毫秒上报一次要统计的接口调用量
        etcd-server: http://localhost:2379 # etcd 服务地址 要从这里知道到底那些接口需要统计上报
      
    • 实际使用

      可以在需要自动检测热点数据的接口 编写类似代码 只做演示就做简单点就行

      public VideoVO getVideoVOById(Integer id) {
          String key = "video_" + id;
          // 如果是热 key
          if (JdHotKeyStore.isHotKey(key)){
              // 从本地缓存中获取缓存值
              Object cache = JdHotKeyStore.getValue(key);
              if (cache!=null){
                  // 如果缓存中有值,直接返回缓存的值
                  return (VideoVO) cache;
              }
          }
      
          // 查询数据库获取值
          VideoVO vo = videoService.getById(id);
          
          // 设置本地缓存 (不是热key 不会设置成功的放心用)
          JdHotKeyStore.smartSet(key, vo);
          
          return vo;
      }
      

      所以 JdHotKeyStore 是按照什么规则去判断它是不是 热key 呢? 当然是你指定的啦,你的规矩就是规矩!!!

      启动 dashboard 项目并配置一下规则

      • 先登录进去给这个项目添加一个负责人,添加负责人的时候顺便可以创建项目,可以看到 所属APP 要和我们自己写的配置文件中的 项目名称一致,并且添加的这个人角色可以是负责人。 账户 昵称 密码之类的可以随意。

      • 编写规则,可以看到只有第四步需要我们编写代码,而且页面还很贴心的说明了每个字段的含义,理解起来so easy。

        这是配置的格式,一个app内可以为很多接口配置规则,所以是一个数组你可以继续往下写

        [
            {
                "duration": 600,
                "key": "video_",
                "prefix": true,
                "interval": 5,
                "threshold": 10,
                "desc": "热门题库缓存"
            }
        ]
        

      这个规则的意思是,判断 video_ 开头的 key,如果 5 秒访问 10 次,就认为它是一个 热点数据 (注意我们并没有缓存的,只是认为这个数据访问量有些大比较热门,至于要不要缓存,或者说关闭访问都还是我们在自己写代码中来控制的,灵活性更高,例如你发现其实就是被某个用户恶意刷流量,爬虫之类的你干嘛要给他做缓存啊应该不给他刷 )

  • 配置好规则 启动 Worker 启动 Dashboard 启动你自己项目

    可以看到自己项目 也在想像etcd 上传心跳 ,并且你如果现在再更改或者添加规则,自己的项目的控制台也会输出你配置的规则,说明项目也在监听这个规则,配置规则不仅不会影响到当前项目的运行,而且还很快的被读到。

    • 这时你可以模拟一些 5s 内访问数据10次的操作

      public static void main(String[] args) {
          for (int i = 0; i < 10; i++) {
              getVideoVOById(1);
          }
      }
      
    • 执行完之后你就可以到 Dashboard 中的热点数据看到 video_1 在列表中,说明认为这个是个热点数据,并且有存活时间,然后第11次执行getVideoVOById(1);若存活时间未到期则取的是缓存的数据。

  • 简单疑问

    这下你终于放心了,不用你自己设置热点数据,它自己就能按照你的规矩发现热点数据,再按照你的代码逻辑去处理这些热点数据。

    不过你在想,我这么用的话缓存数据存在哪了?

    JDHotKet框架 使用的缓存是Caffeine为缓存。存到JVM 的内存中了

    你也可以不用 JdHotKeyStore.smartSet(key, vo); 来存,继续使用你的 redis 作为缓存。当然取数据也要从 redis 中去取

    总之,JDHotKet 最大的帮助就是 帮你发现了那些数据是热点数据,至于你要对热点数据干什么 它可以毫不干涉。

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

    如果已经是热 key 则不会再 push 上报,离过期还有 2 秒内的时候,会再次 push上报,如果还是符合配置的规则,这样这个 key 可能被继续设置为热 key。 源码可以看到哦

  • 哦哦哦 忘了介绍JdHotKeyStore有什么作用了

    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 上报到探测集群进行数量探测。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值