在后端中如何实现幂等和去重?

本文讲述了面试中候选人关于去重和幂等概念的理解,讨论了布隆过滤器的应用局限性,以及在项目实践中如何选择数据结构和存储方式实现去重和幂等,涉及Redis、数据库索引和分布式解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

面试官要不你来讲讲你最近在看的点呗?可以拉出来一起讨论下

候选者:最近在看「去重」和「幂等」相关的内容

面试官那你就先来聊聊你对「去重」和「幂等」的理解吧

候选者:我认为「幂等」和「去重」它们很像,我也说不出他们之间的严格区别

候选者:我说下我个人的理解,我也不知道对不对

候选者:「去重」是对请求或者消息在「一定时间内」进行去重「N次」

候选者:「幂等」则是保证请求或消息在「任意时间内」进行处理,都需要保证它的结果是一致的

候选者:不论是「去重」还是「幂等」,都需要对有一个「唯一 Key」,并且有地方对唯一Key进行「存储」

候选者:以项目举例,我维护的「消息管理平台」是有「去重」的功能的:「5分钟相同内容消息去重」「1小时内模板去重」「一天内渠道达到N次阈值去重」…

候选者:再次强调下「幂等」和「去重」的本质:「唯一Key」+「存储」

面试官那你是怎么做的呢

候选者:不同的业务场景,唯一Key是不一样的,由业务决定

候选者:存储选择挺多的,比如「本地缓存」/「Redis」/「MySQL」/「HBase」等等,具体选取什么,也跟业务有关

候选者:比如说,在「消息管理平台」这个场景下,我存储选择的「Redis」(读写性能优越),Redis也有「过期时间」方便解决「一定时间内」的问题

候选者:而唯一Key,自然就是根据不同的业务构建不同的。

候选者:比如说「5分钟相同内容消息去重」,我直接MD5请求参数作为唯一Key。「1小时模板去重」则是「模板ID+userId」作为唯一Key,「一天内渠道去重」则是「渠道ID+userId」作为唯一Key…

面试官既然提到了「去重」了,你听过布隆过滤器吗?

候选者:自然是知道的啦

面试官来讲讲布隆过滤器吧,你为什么不用呢?

候选者:布隆过滤器的底层数据结构可以理解为bitmap,bitmap也可以简单理解为是一个数组,元素只存储0和1,所以它占用的空间相对较小

候选者:当一个元素要存入bitmap时,其实是要去看存储到bitmap的哪个位置,这时一般用的就是哈希算法,存进去的位置标记为1

候选者:标记为1的位置表示存在,标记为0的位置标示不存在

候选者:布隆过滤器是可以以较低的空间占用来判断元素是否存在进而用于去重,但是它也有对应的缺点

候选者:只要使用哈希算法离不开「哈希冲突」,导致有存在「误判」的情况

候选者:在布隆过滤器中,如果元素判定为存在,那该元素「未必」真实存在。如果元素判定为不存在,那就肯定是不存在

候选者:这应该不用我多解释了吧?(结合「哈希算法」和「标记为1的位置表示存在,标记为0的位置标示不存在」这两者就能得出上面结论)

候选者:布隆过滤器也不能「删除」元素(也是哈希算法的局限性,在布隆过滤器中是不能准确定位一个元素的)

候选者:如果要用的话,布隆过滤器的实现可以直接上Guava已经实现好的,不过这个是单机的

候选者:而分布式下的布隆过滤器,一般现在会用Redis,但也不是没个公司都会部署布隆过滤器的Redis版(还是有局限,像我以前公司就没有)

候选者:所以,目前我负责的项目都是没有用布隆过滤器的(:

候选者:如果「去重」开销比较大,可以考虑建立「多层过滤」的逻辑

候选者:比如,先看看『本地缓存』能不能过滤一部分,剩下「强校验」交由『远程存储』(常见的Redis或者DB)进行二次过滤

面试官:嗯,那我就想起你上一次回答Kafka的时候了

面试官:当时你说在处理订单时实现了at least one + 幂等

面试官:幂等处理时:前置过滤使用的是Redis,强一致校验时使用的是DB唯一索引,也是为了提高性能,对吧?

面试官:唯一Key 好像就是 「订单编号 + 订单状态」

候选者面试官你记性真的好!

候选者:一般我们需要对数据强一致性校验,就直接上MySQL(DB),毕竟有事务的支持

候选者:「本地缓存」如果业务适合,那可以作为一个「前置」判断

候选者:Redis高性能读写,前置判断和后置均可(:

候选者:而HBase则一般用于庞大数据量的场景下(Redis内存太贵,DB不够灵活也不适合单表存大量数据)

候选者:至于幂等,一般的存储还是「Redis」和「数据库」

候选者:最最最最常见的就是数据库「唯一索引」来实现幂等(我所负责的好几个项目都是用这个)

候选者:构建「唯一Key」是业务相关的事了(:一般是用自己的业务ID进行拼接,生成一个”有意义”的唯一Key

候选者:当然,也有用「Redis」和「MySQL」实现分布式锁来实现幂等的(:

候选者:但Redis分布式锁是不能完全保证安全的,而MySQL实现分布式锁(乐观锁和悲观锁还是看业务吧,我是没用到过的)

候选者:网上有很多实现「幂等」的方案,本质上都是围绕着「存储」和「唯一Key」做了些变种,然后取了个名字…

候选者:总的来说,换汤不换药(:

面试官:嗯…了解了

【对线面试官-移动端】系列 一周两篇持续更新中!

【对线面试官-电脑端】系列 一周两篇持续更新中!

原创不易!!求三连!!

<think>好的,我现在需要回答用户关于等性校验的实现方法、原理示例的问题。首先,我需要回忆一下等性的定义,确保自己理解正确。等性是指多次执行同一操作对资源的影响与一次执行相同。比如,用户多次提交表单,系统只处理一次,避免复扣款等问题。 接下来,我需要整理常见的实现方式。可能包括唯一标识符、状态机、Token机制、数据库唯一约束版本号控制等。每个方法都需要简要说明原理,并给出示例,可能还需要结合用户提供的引用内容。例如,引用[1]中提到的唯一交易号,可以用来生成请求ID,确保复请求被识别。 然后,关于原理部分,需要解释等性校验如何保证操作的唯一性,可能涉及请求识别、状态检查资源锁定等步骤。要确保逻辑清晰,让用户理解整个校验过程是如何运作的。 示例部分,可能需要用代码或具体场景来说明。比如,使用Token机制时,前先获取Token,提交时携带Token,后端验证后删除,防止复提交。或者数据库唯一索引防止复插入数据。结合引用中的例子,如“setTrue()”函数是等的,可以举例说明。 还需要注意用户的引用材料,引用[1]详细说明了等的定义应用,引用[2]提到了CRC校验的正反向,但可能与等性关联不大,所以可能不需要深入。不过,如果有类似校验机制,可以类比,但这里可能不适用。 最后,生成相关问题时要围绕等性的其他方面,比如与其他校验的区别、适用场景、分布式系统中的应用常见问题等。确保问题有针对性,帮助用户进一步学习。 检查是否有遗漏,比如是否涵盖了所有主要的实现方式,原理是否解释清楚,示例是否贴切。另外,要使用中文,确保LaTeX格式正确,行内公式用$...$,独立公式用$$...$$,但可能在这个回答中公式不多,但如果有的话要注意格式。比如,在解释状态机时,可能需要用到状态转换的表达式,但暂时没想到具体的数学公式,可能不需要。 现在,组织内容的结构,先介绍等性校验的概念,再分点说明实现方式,详细解释原理,给出示例,最后相关问题。确保每个部分自然衔接,引用正确,比如在提到唯一交易号时引用[^1]。</think>### 等性校验的实现方法与原理 #### 一、等性校验的核心概念 等性校验的核心是确保**同一操作多次执行的结果与一次执行结果一致**。例如,用户多次提交订单时,系统应仅处理一次请求,避免复扣款或数据不一致。 --- #### 二、实现方式及原理 1. **唯一标识符(Request ID)** - **原理**:为每个请求分配唯一ID(如UUID、流水号),服务通过存储校验该ID防止复处理。 - **示例**: ```java // 生成唯一请求ID String requestId = UUID.randomUUID().toString(); // 服务校验逻辑 if (redis.exists(requestId)) { throw new DuplicateRequestException("复请求"); } redis.set(requestId, "processing", 60); // 设置过期时间 ``` 2. **Token机制(防止复提交)** - **原理**:客户在操作前先申请Token,提交时携带Token,服务验证后立即失效。 - **示例**: ```python # 生成Token并返回给前 token = generate_token() cache.set("user:123:token", token, timeout=300) # 提交时校验Token submitted_token = request.POST.get("token") if cache.get("user:123:token") != submitted_token: return error("无效或复的Token") cache.delete("user:123:token") # 删除Token,确保一次性使用 ``` 3. **状态机(State Machine)** - **原理**:通过资源状态流转限制操作的有效性。例如订单状态从“待支付”变为“已支付”后,支付操作不再执行。 - **示例**: ```sql UPDATE orders SET status = 'paid' WHERE order_id = '1001' AND status = 'unpaid'; -- 仅当状态为unpaid时才更新,确保等 ``` 4. **数据库唯一约束** - **原理**:利用数据库唯一索引或联合唯一键防止复数据插入。 - **示例**: ```sql ALTER TABLE payments ADD UNIQUE (order_id, payment_type); -- 唯一约束 -- 插入时若复会触发异常 ``` 5. **版本号控制(乐观锁)** - **原理**:为资源增加版本号字段,更新时校验版本号是否匹配。 - **示例**: ```java // 更新账户余额时携带版本号 UPDATE accounts SET balance = balance - 100, version = version + 1 WHERE account_id = 'A001' AND version = 3; ``` --- #### 三、原理总结 等性校验的实现依赖于以下关键步骤: 1. **请求识别**:通过唯一标识符或Token区分不同请求。 2. **状态检查**:校验资源当前状态是否允许执行操作(如状态机、版本号)。 3. **资源锁定**:在操作期间锁定资源或标记为“处理中”,避免并发冲突。 --- #### 四、典型场景示例 - **支付系统**:用户多次点击支付按钮时,通过订单ID+支付流水号的唯一约束,确保仅扣款一次。 - **消息队列消费**:消费者处理消息时,根据消息ID,避免复消费(如RocketMQ的`Message ID`)。 ---
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值