Go项目(幂等性)

文章介绍了微服务中防止服务雪崩的超时和重试机制,并探讨了幂等性的重要性,提出了通过唯一索引、token和锁(悲观锁、乐观锁、分布式锁)来实现幂等性的方法。强调了设计幂等性接口对于确保系统稳定性的关键作用。

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

简介

  • 前一篇为了避免因消息的重复发送导致一个订单的库存归还多次,我们新建了一张表 StockSellDetail
  • 其实这里涉及到幂等性,但在此之前,先来了解一些微服务中的常见问题
    • 注:微服务不一定是分布式系统,分布式系统也不一定是微服务形式

服务雪崩

  • 由于微服务之间相互调用,如果底层服务挂掉或者压力过大,会逐步影响到其他所有服务,导致整个系统崩溃
    1
  • 避免雪崩最基本的就是要加调用间的超时机制
  • 超时是为了保护服务,但如果 Provider(被调用方) 只是偶尔抖动,那么超时后直接放弃,不做后续处理,就会导致当前请求错误,也会带来业务损失,因此,我们还要加重试机制
  • 重试也可能会导致问题,比如产生多个重复消息,如果重复消费会造成损失,这就要考虑幂等性
    2
  • 先加入重试机制
    • 可以通过中间件的方式实现
    • 可以先添加 DialOption,拨号后再给每个接口单独设置 option,因为 grpc 接口默认有 ...grpc.CallOption
    • 也可以设置并添加 options,再放在 grpc.Dial 里,这样就给所有调用的接口都加了超时重试
      var opts []grpc.DialOption
      // 设置并添加超时重试option
      retryOpts := []grpc_retry.CallOption{
      	// 重试3次
      	grpc_retry.WithMax(3),
      	grpc_retry.WithPerRetryTimeout(1 * time.Second),
      	// 出现这些返回值都会重试
      	grpc_retry.WithCodes(codes.Unknown, codes.DeadlineExceeded, codes.Unavailable),
      }
      opts = append(opts, grpc.WithUnaryInterceptor(grpc_retry.UnaryClientInterceptor(retryOpts...)))
      conn, err := grpc.Dial("127.0.0.1:50052", opts...)
      if err != nil {
      	panic(err)
      }
      defer conn.Close()
      
      c := proto.NewGreeterClient(conn)
      r, err := c.SayHello(context.Background(), &proto.HelloRequest{Name: "roykun"})
      

幂等性

  • 什么是幂等性
    • idempotent、idempotence,是一个数学与计算机学概念,常见于抽象代数中
    • 简而言之,就是指一个操作,具有不论执行多少次,产生的效果和返回的结果都是都一样的性质
  • 并不是所有接口都要考虑幂等性,一般只需关注 POST/PUT 接口
  • 常见的幂等性实现方案
    • 设置唯一索引或唯一组合索引
    • 悲观锁
    • 乐观锁
    • 分布式锁

unique index

  • 比如在用户操作微服务中,用户表的 Mobile 字段做了如下设置
    Mobile string `gorm:"index:idx_mobile;unique;type:varchar(11);not null"`
    

token

  • 有些表是不适合设置唯一索引,比如购物车表,允许用户添加多个,但不能因为网络延迟用户多点了几次就多添加了几件
  • 这种情况可以让前端根据当前页面生成 token,存到 redis
    • 处理之前先检查 redis 中是否有相同的 token
    • 如果没有,存入 redis,继续后面的逻辑
  • 注:这种情况是允许用户在新的页面添加相同商品的,这是用户的事,不归程序员管

  • 之前通过建表,也就是 select + insert 的方式避免重复归还库存,但这种方式只适合并发不高的系统
  • 高并发环境下只能通过加锁的方式
  • 悲观锁
    • 获取数据的时候加锁
      select * from table_xxx where id='xxx' for update;
      
    • id 字段一定要是主键或者唯一索引,不然会锁表
    • 放在事务块中才能生效,查询出来后修改数据,事务提交后解锁
    • 根据业务逻辑,锁定时间可能会很长,要根据实际情况判断是否使用
  • 乐观锁
    • 乐观锁只是在更新数据那一刻锁表,其他时间不锁表,所以相对于悲观锁,效率更高
    • 乐观锁的实现方式多种多样,可以通过 version 或者其他限制条件
    • 同样的,乐观锁的更新操作,最好用主键或者唯一索引,这样是行锁,否则更新时会锁表
      update table_xxx set name=xxx,version=version+1 where id=xxx and version=xxx
      update table_xxx set avai_amount=avai_amount-xxx where id=xxx and avai_amount-xxx >= 0
      
  • 分布式锁
    • 如果是分布是系统,构建全局唯一索引比较困难,此时可以通过第三方的系统(redis或zookeeper)引入分布式锁
    • 前面也介绍过,这里不再赘述

小结

  • 幂等与是不是分布式高并发没有关系,关键是操作是不是幂等的
  • 一个幂等的操作如:把编号为 5 的记录的 A 字段设置为 0,这种操作不管执行多少次都是幂等的
  • 一个非幂等的操作如:把编号为 5 的记录的 A 字段增加 1 这种操作显然就不是幂等的
  • 要做到幂等性,可以从接口上就不设计任何非幂等的操作
    • 譬如说需求是:当用户点赞时,将答案的赞同数量 +1
    • 可以改为,当用户点赞时,答案赞同表中增加一条记录:包括用户、答案,要获取赞同数量,让答案赞同表统计
  • 应该培养设计接口时考虑幂等性的习惯
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑞士_R

修行不易...

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值