1、安装etcd
在etcd下载页面下载对应操作系统的etcd,https://github.com/etcd-io/etcd/releases 使用3.4.x以上。
2、启动worker(集群) 下载并编译好代码,将worker打包为jar,启动即可。如:
java -jar $JAVA_OPTS worker-0.0.1-SNAPSHOT.jar --etcd.server=${etcdServer}
worker可供配置项如下:
etcdServer为etcd集群的地址,用逗号分隔
JAVA_OPTS是配置的JVM相关,可根据实际情况配置
threadCount为处理key的线程数,不指定时由程序来计算。
workerPath代表该worker为哪个应用提供计算服务,譬如不同的应用appName需要用不同的worker进行隔离,以避免资源竞争。
3、启动控制台
下载并编译好dashboard项目,创建数据库并导入resource下db.sql文件。 配置一下application.yml里的数据库相关和etcdServer地址。
启动dashboard项目,访问ip:8081,即可看到界面。
其中节点信息里,即是当前已启动的worker列表。
规则配置就是为各app设置规则的地方,初次使用时需要先添加APP。在用户管理菜单中,添加一个新用户,设置他的APP名字,如sample。之后新添加的这个用户就可以登录dashboard给自己的APP设置规则了,登录密码默认123456。
如图就是一组规则,譬如其中as__开头的热key的规则就是interval-2秒内出现了threshold-10次就认为它是热key,它就会被推送到jvm内存中,并缓存60秒,prefix-true代表前缀匹配。那么在应用中,就可以把一组key,都用as__开头,用来探测。
4、client端接入使用
引入client的pom依赖。
在应用启动的地方初始化HotKey,譬如
@PostConstruct public void initHotkey() { ClientStarter.Builder builder = new ClientStarter.Builder(); ClientStarter starter = builder.setAppName("appName").setEtcdServer("http://1.8.8.4:2379,http://1.1.4.4:2379,http://1.1.1.1:2379").build(); starter.startPipeline(); }
其中还可以setCaffeineSize(int size)设置本地缓存最大数量,默认5万,setPushPeriod(Long period)设置批量推送key的间隔时间,默认500ms,该值越小,上报热key越频繁,相应越及时,建议根据实际情况调整,如单机每秒qps10个,那么0.5秒上报一次即可,否则是空跑。该值最小为1,即1ms上报一次。
注意:
如果原有项目里使用了guava,需要升级guava为以下版本,否则过低的guava版本可能发生jar包冲突。或者删除自己项目里的guava的maven依赖,guava升级不会影响原有任何逻辑。
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>28.2-jre</version> <scope>compile</scope> </dependency>
有时可能项目里没有直接依赖guava,但是引入的某个pom里引了guava,也需要将guava排除掉。
如果原有项目使用了fastjson,需要降为2.0.0版本以下, 在2.0.0版本以上,com.alibaba.fastjson.serializer.JSONLibDataFormatSerializer类已经删除。 导致JSON工具类com.jd.platform.hotkey.common.tool.FastJsonUtils初始化时找不到类。 规则配置的json转换有问题。 推荐使用与HotKey相同的版本:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.70</version> </dependency>
使用:
主要有如下4个方法可供使用
boolean JdHotKeyStore.isHotKey(String key)
Object JdHotKeyStore.get(String key)
void JdHotKeyStore.smartSet(String key, Object value)
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)) { //限流他,do your job }
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即可 } }
项目中的例子:
正常1秒点击1次,返回的都是notHot
连续疯狂点击发送,请求这个接口时,就会返回isHot