我是 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 no
port 6379
timeout 0
save 900 1
save 300 10
save 60 10000
rdbcompression yes
dbfilename dump.rdb
dir /data
appendonly yes
appendfsync everysec
requirepass 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 -d
docker ps
docker 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