大家好,我3y啊。由于去重逻辑重构了几次,好多股东直呼看不懂,于是我今天再安排一波对代码的解析吧。austin支持两种去重的类型:N分钟相同内容达到N次去重和一天内N次相同渠道频次去重。
在最开始,我的第一版实现是这样的:
(资料图片)
publicvoidduplication(TaskInfotaskInfo){//配置示例:{"contentDeduplication":{"num":1,"time":300},"frequencyDeduplication":{"num":5}}JSONObjectproperty=JSON.parseObject(config.getProperty(DEDUPLICATION_RULE_KEY,AustinConstant.APOLLO_DEFAULT_VALUE_JSON_OBJECT));JSONObjectcontentDeduplication=property.getJSONObject(CONTENT_DEDUPLICATION);JSONObjectfrequencyDeduplication=property.getJSONObject(FREQUENCY_DEDUPLICATION);//文案去重DeduplicationParamcontentParams=DeduplicationParam.builder().deduplicationTime(contentDeduplication.getLong(TIME)).countNum(contentDeduplication.getInteger(NUM)).taskInfo(taskInfo).anchorState(AnchorState.CONTENT_DEDUPLICATION).build();contentDeduplicationService.deduplication(contentParams);//运营总规则去重(一天内用户收到最多同一个渠道的消息次数)Longseconds=(DateUtil.endOfDay(newDate()).getTime()-DateUtil.current())/1000;DeduplicationParambusinessParams=DeduplicationParam.builder().deduplicationTime(seconds).countNum(frequencyDeduplication.getInteger(NUM)).taskInfo(taskInfo).anchorState(AnchorState.RULE_DEDUPLICATION).build();frequencyDeduplicationService.deduplication(businessParams);}
那时候很简单,基本主体逻辑都写在这个入口上了,应该都能看得懂。后来,群里滴滴哥表示这种代码不行,不能一眼看出来它干了什么。于是怒提了一波pull request重构了一版,入口是这样的:
publicvoidduplication(TaskInfotaskInfo){//配置样例:{"contentDeduplication":{"num":1,"time":300},"frequencyDeduplication":{"num":5}}Stringdeduplication=config.getProperty(DeduplicationConstants.DEDUPLICATION_RULE_KEY,AustinConstant.APOLLO_DEFAULT_VALUE_JSON_OBJECT);//去重DEDUPLICATION_LIST.forEach(key->{DeduplicationParamdeduplicationParam=builderFactory.select(key).build(deduplication,key);if(deduplicationParam!=null){deduplicationParam.setTaskInfo(taskInfo);DeduplicationServicededuplicationService=findService(key+SERVICE);deduplicationService.deduplication(deduplicationParam);}});}
我猜想他的思路就是把构建去重参数和选择具体的去重服务给封装起来了,在最外层的代码看起来就很简洁了。后来又跟他聊了下,他的设计思路是这样的:考虑到以后会有其他规则的去重就把去重逻辑单独封装起来了,之后用策略模版的设计模式进行了重构,重构后的代码 模版不变,支持各种不同策略的去重,扩展性更高更强更简洁
确实牛逼。
我基于上面的思路微改了下入口,代码最终演变成这样:
publicvoidduplication(TaskInfotaskInfo){//配置样例:{"deduplication_10":{"num":1,"time":300},"deduplication_20":{"num":5}}StringdeduplicationConfig=config.getProperty(DEDUPLICATION_RULE_KEY,CommonConstant.EMPTY_JSON_OBJECT);//去重ListdeduplicationList=DeduplicationType.getDeduplicationList();for(IntegerdeduplicationType:deduplicationList){DeduplicationParamdeduplicationParam=deduplicationHolder.selectBuilder(deduplicationType).build(deduplicationConfig,taskInfo);if(Objects.nonNull(deduplicationParam)){deduplicationHolder.selectService(deduplicationType).deduplication(deduplicationParam);}}}
到这,应该大多数人还能跟上吧?在讲具体的代码之前,我们先来简单看看去重功能的代码结构(这会对后面看代码有帮助)
去重的逻辑可以统一抽象为:在X时间段内达到了Y阈值,还记得我曾经说过:「去重」的本质:「业务Key」+「存储」。那么去重实现的步骤可以简单分为(我这边存储就用的Redis):
通过Key从Redis获取记录判断该Key在Redis的记录是否符合条件符合条件的则去重,不符合条件的则重新塞进Redis更新记录为了方便调整去重的参数,我把X时间段和Y阈值都放到了配置里{"deduplication_10":{"num":1,"time":300},"deduplication_20":{"num":5}}。目前有两种去重的具体实现:
1、5分钟内相同用户如果收到相同的内容,则应该被过滤掉
2、一天内相同的用户如果已经收到某渠道内容5次,则应该被过滤掉
从配置中心拿到配置信息了以后,Builder就是根据这两种类型去构建出DeduplicationParam,就是以下代码:
DeduplicationParamdeduplicationParam=deduplicationHolder.selectBuilder(deduplicationType).build(deduplicationConfig,taskInfo);
Builder和DeduplicationService都用了类似的写法(在子类初始化的时候指定类型,在父类统一接收,放到Map里管理)
而统一管理着这些服务有个中心的地方,我把这取名为DeduplicationHolder
/***@authorhuskey*@date2022/1/18*/@ServicepublicclassDeduplicationHolder{privatefinalMapbuilderHolder=newHashMap<>(4);privatefinalMap serviceHolder=newHashMap<>(4);publicBuilderselectBuilder(Integerkey){returnbuilderHolder.get(key);}publicDeduplicationServiceselectService(Integerkey){returnserviceHolder.get(key);}publicvoidputBuilder(Integerkey,Builderbuilder){builderHolder.put(key,builder);}publicvoidputService(Integerkey,DeduplicationServiceservice){serviceHolder.put(key,service);}}
前面提到的业务Key,是在AbstractDeduplicationService的子类下构建的:
而具体的去重逻辑实现则都在LimitService下,{一天内相同的用户如果已经收到某渠道内容5次}是在SimpleLimitService中处理使用mget和pipelineSetEX就完成了实现。而{5分钟内相同用户如果收到相同的内容}是在SlideWindowLimitService中处理,使用了lua脚本完成了实现。
LimitService的代码都来源于@caolongxiu的pull request,建议大家可以对比commit再学习一番:https://gitee.com/zhongfucheng/austin/pulls/19
1、频次去重采用普通的计数去重方法,限制的是每天发送的条数。
2、内容去重采用的是新开发的基于redis中zset的滑动窗口去重,可以做到严格控制单位时间内的频次。
3、redis使用lua脚本来保证原子性和减少网络io的损耗
4、redis的key增加前缀做到数据隔离(后期可能有动态更换去重方法的需求)
5、把具体限流去重方法从DeduplicationService抽取出来,DeduplicationService只需设置构造器注入时注入的AbstractLimitService(具体限流去重服务)类型即可动态更换去重的方法 6、使用雪花算法生成zset的唯一value,score使用的是当前的时间戳
针对滑动窗口去重,有会引申出新的问题:limit.lua的逻辑?为什么要移除时间窗口的之前的数据?为什么ARGV[4]参数要唯一?为什么要expire?
A: 使用滑动窗口可以保证N分钟达到N次进行去重。滑动窗口可以回顾下TCP的,也可以回顾下刷LeetCode时的一些题,那这为什么要移除,就不陌生了。
为什么ARGV[4]要唯一,具体可以看看zadd这条命令,我们只需要保证每次add进窗口内的成员是唯一的,那么就不会触发有更新的操作(我认为这样设计会更加简单些),而唯一Key用雪花算法比较方便。
为什么expire?,如果这个key只被调用一次。那就很有可能在redis内存常驻了,expire能避免这种情况。
推荐项目最后再叨叨吧,很多人可能会发一段截图,跑来问我为什么要这样写,为什么要以这种方式实现,能不能以这种方式实现。这时候,我更想看到的是:你已经实现了第二种方式了,然后探讨你写的这种方案好不好,现有的代码差在哪里。
毕竟问问题很简单,我又不是客服,总不能没诚意的问题我都得一一回答吧。
如果想学Java项目的,我还是强烈推荐我的开源项目消息推送平台Austin,可以用作毕业设计,可以用作校招,可以看看生产环境是怎么推送消息的。
仓库地址(可点击阅读原文跳转):https://gitee.com/zhongfucheng/austin
我开通了股东服务内容,感兴趣可以点击下方看看,主要针对的是项目哟
VIP服务
-
解剖屎山,寻觅黄金之第二弹大家好,我3y啊。由于去重逻辑重构了几次,好多股东直呼看不懂,于是我今天再安排一波对代码的解析吧。aust
-
辽宁省抚顺市2023-05-17 01:18发布雷电黄色预警音频解说一、辽宁省抚顺市天气预报1、雷电黄色预警信号。2、预计未来1到2小时,清原县将出现雷电天气,并将
-
初二物理计算题及答案简单题_初二物理计算题及答案1、1,甲、乙两地的铁路线长2430千米,火车从甲地开往乙地途中通过一个1050米长的隧道,用了1分10秒钟。2、求
-
世界观热点:财位哪个方位打麻将_财位是哪个方位1、大家经常听到财位一词,家中财位在哪里呢?来分析一下: 所谓“财位”,风水学上说法不一,主要有以下
-
采用传统文化设计 红旗H9+不息艺术版发布日前,一汽红旗品牌发布了红旗H9+不息艺术版车型。这款新车基于红旗H9+打造而来,采用了中国传统工艺,例如
-
十二巫祖的故事_十二巫祖-热点聚焦1、十二巫祖的名字分别是:火神祝融,水神共工,战神刑天。2、以及帝江,后羿,夸父。3、雷神,蓐收,句芒
-
焦点快看:圆滚滚萌萌哒分不清?大熊猫“认脸”教程来了萌兰、花花、七仔……熊猫界的顶流们,个个萌到心化。网友:为什么感觉大熊猫长相都一样!别急~最新一批熊
-
中性笔如何消除字迹_中性粒细胞百分比偏低是什么意思1、你好中性粒细胞百分比44 4%偏低(55%一70%)淋巴细胞百分比46 8%偏高(20%-40%)中性粒细胞在血液的
-
马桶水箱漏水的原因|世界速讯抽水马桶水箱漏水之设计不当,使水箱配件各机构在动作时产生干扰,招致漏水。比方水箱放水时浮球及浮球杆下
-
【法治公安建设年㊶】冰城公安为群众挽回经济损失46万元“真心感谢哈尔滨的经侦民警,没有他们,我这15万元真就打水漂儿了,当我接到返还通知时,我们一家激动得一
-
当前观察:中国航海博物馆5月19日中国旅游日门票半价优惠(图源:上海本地宝)中国航海博物馆519旅游日门票半价优惠【活动时间】:2023年5月19日【优惠政策】:成人
-
焦点滚动:5月16日国内市场糠醛出厂行情平稳2023年5月16日,我国国内糠醛(国标,工业级,250公斤 桶)河南、山东、河北企业出厂报7300-7800元 吨左右,
-
受贿1.7亿余元!赵忠厚受贿、贪污、巨额财产来源不明、洗钱案开庭2023年5月15日至16日,黑龙江省七台河市中级人民法院一审公开开庭审理了黑龙江省人大教育科学文化卫生委员
-
搭载1.5T混动系统 福特蒙迪欧混动版申报图曝光-天天资讯日前,我们从工信部最新一期申报目录中获取到了一组福特蒙迪欧混动版车型的申报图,新车搭载1 5T混动系统,
-
京雄高速成功上跨京良路房山线 讯息昨天(5月15日),京雄高速公路(北京段)上跨京良路及地铁房山线钢箱梁已顶推就位。北京日报记者邓伟摄北
-
不老泉好词好句几页_不老泉好词好句1、不老泉好词好句 毫不相干出人意料悠然自得 盛气凌人与世无争漫不经心 归根结底不以为然娴熟迅
-
新华全媒+|产业链上抓创新 海河实验室“探访记”无需双手操作,通过“意念”就可以隔空打字,这样的科幻场景,在今年天津成立的脑机交互与人机共融海河实验
-
5月16日午后有雨,局地大风冰雹!北京公交提前备战5月16日午后有雨,局地大风冰雹!北京公交提前备战
-
李德维:认为郭台铭会跳槽民众党的人太小看他 全球消息外界疑虑郭台铭若未被国民党征召将翻桌,蓝委李德维认为,那些认为郭台铭没获得征召会选择和民众党合作的人
-
第二批创新型县(市)建设名单公布 广西三地入选近日,科技部发布通知,确定河北省黄骅市等92个县(市)为第二批创新型县(市)建设县(市)。其中,广西合浦县、
-
大乐透23054期一等奖开出6注,花落5省,1.5万复式猜中3+2大乐透第23054期开奖号码已公布出来了,前区号码:19、25、30、31、34、后区号码:07、09、这期重点出号在
-
River Island 与 Reskinned 的回收计划推出 eBay 二手商品商店_环球动态作为Reskinned回收计划的一部分,RiverIsland将开设一家eBay商店,出售二手商品。此举是时
-
男子拳击世锦赛落幕 中国队收获一枚银牌新华社北京5月15日电据国际拳联官网消息,2023男子拳击世锦赛当地时间14日在乌兹别克斯坦首都塔什干结束。
-
当前消息!辽宁队在CBA就没对手了?昨晚辽宁男篮以106比70战胜浙江队,总比分4比0横扫对手,夺得队史第三个总冠军。
-
天天视点!99元5斤小龙虾外卖净重仅2.9斤 店家称打的时候不是很均匀5月14日一早,记者在外卖平台网站上看到,许多商家都推出了小龙虾套餐,优惠幅度前所未有,满眼都是“超级
-
4月份我国外汇市场运行平稳 境内外汇供求基本平衡国家外汇管理局15日发布数据显示,4月份,银行结售汇顺差55亿美元;企业、个人等非银行部门涉外收入与支出
-
人力资源服务公司业务范围_人力资源服务公司经营范围|世界视讯1、人力资源管理经营范围示例:(一)、需要办理前置审批的经营范围【许可经营范围】:人力资源招聘,劳务
-
因地制宜 科学补碘——皖浙部分地区碘缺乏病防治一线观察5月15日是我国第30个“防治碘缺乏病日”,今年的活动主题是“科学补碘三十年,利国利民保健康”。碘缺乏病
-
小期贷贷款逾期32年延迟还款征信有什么影响 全球独家网贷逾期一般会上征信,有些借贷机构在用户逾期后一天后就会上报给征信机构,而有些借贷机构则是会在几天后
-
托蒂:希望穆里尼奥和迪巴拉留下 我们距离欧联杯决赛只剩90分钟 每日焦点托蒂:希望穆里尼奥和迪巴拉留下我们距离欧联杯决赛只剩90分钟,罗马,欧联杯,韩国足球,阿根廷足球,足球运动