我是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/portpassword就搞掂了。

对于客户端,我们就直接使用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面试,对线面试官系列持续更新中!

【对线面试官+从零编写Java项目】 持续高强度更新中!求star!!原创不易!!求三连!!

Gitee链接:https://gitee.com/austin

GitHub链接:https://github.com/austin

Redis入门与实践(附项目真实案例代码)的更多相关文章

  1. 使用Express连接mysql详细教程(附项目的完整代码我放在结尾了)

    使用Express连接mysql详细教程(附项目的完整代码我放在结尾了) 要使用Express连接本地数据库 我们首先需要安装好Express的依赖 我们使用这个框架呢首先要有一点ajax的基础 如果 ...

  2. Python 从入门到实践 试一试 参考代码

    这两天学习Python 看了python从入门到实践的书籍,里面有课后题“试一试” 然后就跟着写了,代码在以下地址,如果需要自取 https://files.cnblogs.com/files/fud ...

  3. 随手小代码——《Python编程 从入门到实践》项目1:外星人入侵

    =================================版权声明================================= 版权声明:原创文章 禁止转载  请通过右侧公告中的“联系邮 ...

  4. Python:从入门到实践--第十一章--测试代码--练习

    #1.城市和国家:编写一个函数,它接受两个形参:一个城市名和一个国家名. #这个函数返回一个格式为City,Country的字符串,如Santiago,Chile.将这个函数 #存储在一个名为city ...

  5. redis入门到精通系列(二):redis操作的两个实践案例

    在前面一篇博客中我们已经学完了redis的五种数据类型操作,回顾一下,五种操作类型分别为:字符串类型(string).列表类型(list).散列类型(hash).集合类型(set).有序集合类型(so ...

  6. python编程从入门到实践 alien invasion 项目源码

    现在上传一个 python编程从入门到实践 alien invasion 项目源码 以供大家学习参考 跟官方版本可能不太一样,因为是自己写的 也算是给新手一个参考 我用的环境是pycharm 可能需要 ...

  7. 《SaltStack技术入门与实践》—— 实践案例 <中小型Web架构>3 Memcached配置管理

    实践案例 <中小型Web架构>3 Memcached配置管理 本章节参考<SaltStack技术入门与实践>,感谢该书作者: 刘继伟.沈灿.赵舜东 Memcached介绍 Me ...

  8. Redis基于客户端分片的集群案例(待实践)

    说明: 下面的示例基本都是基于Linux去实现,目的是为了环境的统一,以便于把性能调整到最优.且基于Java.建议生产环境不要使用Windows/Mac OS这些. 在Java领域,基于客户端进行分片 ...

  9. python入门神书!|python编程从入门到实践|内附网盘链接带提取码|

    点击此处进入网盘下载地址 提取码:o39n 全书共有20章,书中的简介如下: 本书旨在让你尽快学会 Python ,以便能够编写能正确运行的程序 —— 游戏.数据可视化和 Web 应用程序,同时掌握让 ...

随机推荐

  1. 深入理解Java虚拟机之自己编译JDK

    题外话 最近在阅读<深入理解Java虚拟机>,其中有一小节实战是自己编译JDK,实际操作下来后遇到问题不少,为此特地记录,也希望可以给大家带来一些参考! 前置准备 平台及工具:Window ...

  2. Lucene8.5.x全文检索工具

    本文的资源展示: hotword:是热词的文本,比如不是词语的中文,但是是什么人名或者公司名称的词语,需要分词成一个词语的将需要的加入hotword.dic stopword:无意义的词放入的词典,或 ...

  3. 阿里神器 Seata 实现 TCC模式 解决分布式事务,真香!

    今天这篇文章介绍一下Seata如何实现TCC事务模式,文章目录如下: 什么是TCC模式? TCC(Try Confirm Cancel)方案是一种应用层面侵入业务的两阶段提交.是目前最火的一种柔性事务 ...

  4. vscode控制台中文乱码

    原因 vscode中文控制台乱码原因是调用的cmd的显示. 所以问题实际上是cmd的显示中文乱码问题.当然还有其他方法仅仅修改vscode的显示,这里不在说明. cmd中国版本windows默认是93 ...

  5. C++构造函数语义学(一)(基于C++对象模型)

    如果一个类没有自己的构造函数,编译器会在需要的时候为其合成一个出来,俗称:合成默认构造函数.但是请注意是在需要的时候,并不是所有情况. 请看下面代码: 1 #include<iostream&g ...

  6. 【Azure Developer】使用 Azure Python SDK时,遇见 The resource principal named https://management.azure.com was not found in the tenant China Azure问题的解决办法

    问题描述 在使用Python SDK时候,登录到China Azure (Mooncake)并访问AlertsManagement资源时候,时常遇见  EnvironmentCredential: A ...

  7. Hbase 项目

     需求分析 1) 微博内容的浏览,数据库表设计 2) 用户社交体现:关注用户,取关用户 3) 拉取关注的人的微博内容 表结构 代码实现 1) 创建命名空间以及表名的定义 2) 创建微博内容表 3) 创 ...

  8. 「 MySQL高级篇 」MySQL索引原理,设计原则

    大家好,我是melo,一名大二后台练习生,大年初三,我又来充当反内卷第一人了!!! 专栏引言 MySQL,一个熟悉又陌生的名词,早在学习Javaweb的时候,我们就用到了MySQL数据库,在那个阶段, ...

  9. TensorFlow 机器学习秘籍中文第二版·翻译完成

    原文:TensorFlow Machine Learning Cookbook 协议:CC BY-NC-SA 4.0 不要担心自己的形象,只关心如何实现目标.--<原则>,生活原则 2.3 ...

  10. react 局部更新的关键算法 DOM diff算法

    下图是diff算法结构的详细解析: 要点总结:DIFF算法在执行时有三个维度,分别是Tree DIFF.Component DIFF和Element DIFF,执行时按顺序依次执行,它们的差异仅仅因为 ...