我是3y,一年CRUD
教训用十年的markdown
程序员长年被誉为优质八股文选手
明天持续更新austin我的项目,如果还没看过该系列的同学能够点开我的历史文章回顾下,在看的过程中不要遗记了点赞哟!倡议不要漏了或者跳着看,不然这篇就看不懂了,之前写过的知识点和业务我就不再赘述啦。
明天要实现的是handler
生产音讯后,实现平台性去重的性能。
01、什么是去重和幂等
这个话题我之前在《对线面试官》系列就曾经分享过了,这块面试也会常常问到,能够再跟大家一起温习下
「幂等」和「去重」的实质:「惟一Key」+「存储」
惟一Key如何构建以及抉择用什么存储,都是业务决定的。「本地缓存」如果业务适合,能够作为「前置」筛选出一部分,把其余存储作为「后置」,用这种模式来进步性能。
今日要聊的Redis,它领有着高性能读写,前置筛选和后置判断均可,austin我的项目的去重性能就是依赖着Redis而实现的。
02、装置Redis
先疾速过一遍Redis的应用姿态吧(如果对此不感兴趣的能够间接跳到05解说相干的业务和代码设计)
装置Redis的环境跟上次Kafka是一样的,为了不便我就持续用docker-compose
的形式来进行啦。
环境:CentOS 7.6 64bit
首先,咱们新建一个文件夹redis
,而后在该目录下创立出data
文件夹、redis.conf
文件和docker-compose.yaml
文件
redis.conf
文件的内容如下(前面的配置可在这更改,比方requirepass 我指定的明码为austin
)
protected-mode noport 6379timeout 0save 900 1 save 300 10save 60 10000rdbcompression yesdbfilename dump.rdbdir /dataappendonly yesappendfsync everysecrequirepass austin
docker-compose.yaml
的文件内容如下:
version: '3'services: redis: image: redis:latest container_name: redis restart: always ports: - 6379:6379 volumes: - ./redis.conf:/usr/local/etc/redis/redis.conf:rw - ./data:/data:rw command: /bin/bash -c "redis-server /usr/local/etc/redis/redis.conf "
配置的工作就完了,如果是云服务器,记得开redis端口6379
03、启动Redis
启动Redis跟之前装置Kafka的时候就差不多啦
docker-compose up -ddocker psdocker exec -it redis redis-cli
进入redis客户端了之后,咱们想看验证下是否失常。(在正式输出命令之前,咱们须要通过明码校验,在配置文件下配置的明码是austin
)
而后随便看看命令是不是失常就OK啦
04、Java中应用Redis
在SpringBoot环境下,应用Redis就非常简单了(再次体现出应用SpringBoot的益处)。咱们只须要在pom文件下引入对应的依赖,并且在配置文件下配置host
/port
和password
就搞掂了。
对于客户端,咱们就间接应用RedisTemplate就好了,它是对客户端的高度封装,曾经挺好使的了。
05、去重性能业务
任何的性能代码实现都离不开业务场景,在聊代码实现之前,先聊业务!平时在做需要的时候,我也始终崇奉着:先搞懂业务要做什么,再实现性能。
去重该性能在austin我的项目里我是把它定位是:平台性功能。要了解这点很重要!不要想着把业务的各种的去重逻辑都在平台上做,这是不合理的。
这里只能是把共性的去重性能给做掉,跟业务强挂钩应由业务方自行实现。所以,我目前在这里实现的是:
- 5分钟内雷同用户如果收到雷同的内容,则应该被过滤掉。实现理由:很有可能因为MQ反复生产又或是业务方不审慎调用,导致雷同的音讯在短时间内被austin生产,进而发送给用户。有了该去重,咱们能够在肯定水平下缩小事变的产生。
- 一天内雷同的用户如果曾经收到某渠道内容5次,则应该被过滤掉。实现理由:在经营或者业务推送下,有可能某些用户在一天内会屡次收到推送音讯。防止对用户带来过多的打搅,从总体定下规定一天内用户只能收到N条音讯。
不排除随着业务的倒退,还有些须要咱们去做的去重性能,但还是要记住,咱们这里不跟业务强挂钩。
当咱们的外围性能依赖其余中间件的时候,咱们尽可能防止因为中间件的异样导致咱们外围的性能无奈失常应用。比方,redis如果挂了,也不应该影响咱们失常音讯的下发,它只能影响到去重的性能。
06、去重性能代码总览
在之前,咱们曾经从Kafka拉取音讯后,而后把音讯放到各自的线程池进行解决了,去重的性能咱们只须要在发送之前就好了。
我将去重的逻辑对立形象为:在X时间段内达到了Y阈值。去重实现的步骤能够简略分为:
- 从Redis获取记录
- 判断Redis存在的记录是否符合条件
- 符合条件的则去重,不符合条件的则从新塞进Redis
这里我应用的是模板办法模式,deduplication
办法曾经定义好了定位,当有新的去重逻辑须要接入的时候,只须要继承AbstractDeduplicationService
来实现deduplicationSingleKey
办法即可。
比方,我以雷同内容发送给同一个用户的去重逻辑为例:
07、去重代码具体实现
在这场景下,我应用Redis都是用批量操作来缩小申请Redis的次数的,这对于咱们这种业务场景(在生产的时候须要大量申请Redis,应用批量操作晋升还是很大的)
因为我感觉应用的场景还是蛮多的,所以我封装了个RedisUtils工具类,并且能够发现的是:我对操作Redis的中央都用try catch
来包住。即使是Redis出了故障,我的外围业务也不会受到影响。
08、你的代码有Bug!
不晓得看完下面的代码你们有没有看出问题,有喜爱点赞的帅逼就很间接看出两个问题:
- 你的去重性能为什么是在发送音讯之前就做了?万一你发送音讯失败了怎么办?
- 你的去重性能存在并发的问题吧?假如我有两条一样的音讯,生产的线程有多个,而后该两条线程同时查问Redis,发现都不在Redis内,那这不就有并发的问题吗
没错,下面这两个问题都是存在的。然而,我这边都不会去解决。
先来看第一个问题:
对于这个问题,我能扯出的理由有两个:
- 假如我发送音讯失败了,在该零碎也不会通过回溯MQ的形式去从新发送音讯(回溯MQ从新生产影响太大了)。咱们齐全能够把发送失败的
userId
给记录下来(前面会把相干的日志零碎给欠缺),有了userId
当前,咱们手动批量从新发就好了。这里手动也不须要业务方调用接口,间接通过相似excel
的形式导入就好了。 - 在业务上,很多发送音讯的场景即使真的丢了几条数据,都不会被发现。有的音讯很重要,但有更多的音讯并没那么重要,并且咱们即使在调用接口才把数据写入Redis,但很多渠道的音讯其实在调用接口后,也不晓得是否真正发送到用户上了。
再来看第二个问题:
如果咱们要仅靠Redis来实现去重的性能,想要齐全没有并发的问题,那得上lua
脚本,但上lua
脚本是须要老本的。去重的实现须要依赖两个操作:查问和插入。查问后如果没有,则须要增加。那查问和插入须要放弃原子性能力防止并发的问题
再把视角拉回到咱们为什么要实现去重性能:
当存在事变的时候,咱们去重能肯定保障到绝大多数的音讯不会反复下发。对于整体性的规定,并发音讯发送而导致规定被毁坏的概率是十分的低。
09、总结
这篇文章简要讲述了Redis的装置以及在SpringBoot中如何应用Redis,次要阐明了为什么要实现去重的性能以及代码的设计和性能的具体实现。
技术是离不开业务的,有可能咱们设计或实现的代码对于强一致性是有疏漏的,但如果零碎的整体是更简略和高效,且业务可承受的时候,这不是不能够的。
这是一种trade-off
衡量,要保证数据不失落和不反复个别状况是须要编写更多的代码和损耗零碎性能等能力换来的。我能够在生产音讯的时候实现at least once
语义,保证数据不失落。我能够在生产音讯的时候,实现真正的幂等,上游调用的时候不会反复。
但这些都是有条件的,要实现at least once
语义,须要手动ack
。要实现幂等,须要用redis lua
或者把记录写入MySQL
构建惟一key并把该key设置惟一索引。在订单类的场景是必须的,但在一个外围发消息的零碎里,可能并没那么重要。
No Bug,All Feature!
关注我的微信公众号【Java3y】除了技术我还会聊点日常,有些话只能轻轻说~ 【对线面试官+从零编写Java我的项目】 继续高强度更新中!求star!!原创不易!!求三连!!
源码Gitee链接:gitee.com/austin
源码GitHub链接:github.com/austin