百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

Redis入门与实践(附项目真实案例代码)

mhr18 2024-11-01 12:20 16 浏览 0 评论

原文 :https://www.cnblogs.com/Java3y/p/15921480.html

我是3y,一年 CRUD 经验用十年的 markdown 程序员:man|type_1_2:?:computer:常年被誉为优质八股文选手

今天继续更新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设置唯一索引。在订单类的场景是必须的,但在一个核心发消息的系统里,可能并没那么重要。

相关推荐

几种 TCP 连接中出现 RST 的情况

现在是一个网络时代了。应该不少程序员在编程中需要考虑多机、局域网、广域网的各种问题。所以网络知识也是避免不了学习的。而且笔者一直觉得TCP/IP网络知识在一个程序员知识体系中必需占有一席之地的。在...

Redis连接使用报RDB error错误

该错误信息:Errorinexecution;nestedexceptionisio.lettuce.core.RedisCommandExecutionException:MISC...

lua 语法介绍与 NGINX lua 高级用法实战操作

一、概述lua是一种轻量小巧的脚本语言,用标准C语言编写并以源代码形式开放,其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。官网:https://www.lua.org/二、l...

Python教程——20.协程 - 2

异步编程asyncio.Future对象Task继承Future,Task对象内部中的await结果的处理基于Future对象来的在Future对象中会保存当前执行的这个协程任务的状态,如果当...

“我的足迹”、“浏览历史”,Redis如何快速记录与展示?

咱们在网上“买买买”、“逛逛逛”的时候,总会留下各种各样的“足迹”。无论是电商APP里你最近浏览过的商品,视频网站上你刚刚看过的剧集,还是新闻客户端里你点开过的文章……这些“历史记录”,有时候还真挺有...

你手机上的“消息推送”,Redis可能参与其中

手机上那些时不时就“叮咚”一下的消息推送,确实是咱们数字生活里不可或缺的一部分。这篇咱们就来聊聊,Redis这位“消息灵通人士”,是如何在这场“信息接力赛”中大显身手,确保那些重要的、有趣的通知,能够...

短视频APP的“附近的人”,Redis如何快速匹配?

刷短视频,除了看各种搞笑段子、才艺展示,有时候是不是也想看看“同城”或者“附近”的人都在发些啥有意思的内容?或者,平台也会时不时地给你推荐一些“附近正在直播”的主播,让你感觉一下子拉近了和这个虚拟世界...

微信朋友圈的点赞、评论,Redis在背后默默付出

微信朋友圈,这片小小的“自留地”,承载了我们多少喜怒哀乐、生活点滴啊!一张精心修饰的照片,一段随感而发的文字,发出去之后,最期待的是什么?那必须是屏幕下方不断冒出来的小红心和一条条真诚(或者商业互吹)...

网站登录老是掉线?Redis帮你记住你是谁!

有没有过这样的糟心体验?你好不容易登录了一个网站,刚看了两篇帖子,或者购物车里刚加了几件宝贝,结果一刷新页面,或者稍微离开了一会儿,回来就发现——“哎?我怎么又退出了?!”又得重新输入用户名、密码、...

你常用的APP,哪些地方可能用到了Redis?(猜想与分析)

咱们现在的生活,简直是离不开各种各样的手机APP了!从早上睁眼刷新闻,到中午点外卖,再到晚上刷短视频、玩游戏,一天到头,指尖在屏幕上就没停过。这些APP为了让我们用得爽、用得顺心,背后可是使出了浑身解...

Redis是啥?为啥程序员天天挂嘴边?小白也能看懂!

这Redis到底是何方神圣?为啥那些天天在电脑前敲代码的程序员小哥哥小姐姐们,老是把它挂在嘴边,好像离了它地球都不转了似的?别担心,咱们今天不说那些听了就头大的代码和术语,就用大白话,保证你听完一拍大...

面试官:请你说说Redis为什么这么快?

1)Redis是基于内存的存储数据库,绝大部分的命令处理只是纯粹的内存操作,内存的读写速度非常快。2)Redis是单进程线程的服务(实际上一个正在运行的RedisServer肯定不止一个线程,但只有...

有了强大的关系型数据库,为什么还需要Redis?

在数字世界的浩瀚海洋中,关系型数据库,例如我们熟知的MySQL、PostgreSQL或Oracle,无疑是那些承载着核心业务数据、坚如磐石的“国家图书馆”或“银行金库”。它们以严谨的结构、强大的事务处...

Java 中间件数据可靠性串讲:从 MQ 、MySQL、Redis 不丢失的保障之道

引言在现代分布式系统中,中间件扮演着至关重要的角色,它们是构建高可用、高性能、高可扩展应用架构的基石。消息队列(MQ)、数据库(如MySQL)、缓存(如Redis)等是其中最具代表性的组件。然而,...

运维部署方式之——虚机部署

标准化使用作業系统:LinuxCentOS7自动化方式通过Ansible系统初始化playbook来管理。目的系统初始化工作是一个简单、繁复的工作,从云网得到的虚拟主机只是一个基础的系统环境,...

取消回复欢迎 发表评论: