解决方案(2) 分布式二级幂等

前言

api服务里,幂等场景在【写入】场景非常重要。场景包括了:

  • 修改任务进度
  • 领取奖品
  • 充值
  • ……

幂等的作用,是防止某一次操作,在并发调用时,写入操作意外地执行了不止一次。(理论上,不论执行多少次,执行结果,都应该和一次相同)

幂等的实现往往围绕某一个key, key具备一定的业务含义:

  • 基于用户id 和用户请求限频有关
  • 基于订单id 和充值相关

幂等和限频的关系是:

  • 幂等比限频约束更精准
  • 幂等一般固定为限频为单次
  • 幂等的作用是防止并发造成强相关数据幻读。限频的作用是,降低单节点负载。

幂等是如何保障【写入】场景的安全的

以基于订单id的幂等为例。用户凭借订单id来获取道具,同一个时间,发了15次请求,那么:

  • 基于数据库处理并发幂等,update game_order set has_present=2 where order_id=? and has_present=1
  • 第一个请求修改订单状态为已赠送,并下发道具
  • 后14个请求,修改订单状态时,因为无法查询到未赠送的订单(第一个请求已经将它置为了已赠送),所以基于订单id已赠送,后续的14个请求将不会继续赠送道具。

二级幂等

很显然,除了第一个请求是有效请求,后续14个请求,都意外的打入了db,这在高流量的应用中是不可取的。所以,二级幂等非常重要。

二级幂等具备以下特性:

  • 幂等key和一级幂等key强相关
  • 可以具备时效
  • 不会打入db层
  • 分布式服务可靠

由上述的特性,可以使用Redis来实现这一层二级幂等。

func Once(conn redis.Conn, key string, seconds int) bool {
    
    
	if seconds == -2 {
    
    
		seconds = int(TomorrowZero().Sub(time.Now()).Seconds())
	}

	rs, e := redis.String(conn.Do("set", key, "done", "ex", seconds, "nx"))
	if e == redis.ErrNil {
    
    
		return false
	}
	if rs == "OK" {
    
    
		return true
	}
	return false
}

基于前面的订单场景,那么使用时,表达为

if !Once(
    conn,
    fmt.Sprintf("app:shop_exchange_order:mideng:%s", orderId),
    3
) {
    
    
    fmt.Println("该订单已经被处理了")
    return 
}

含义: 某个订单的兑换场景,3秒内,只会放行一条记录。

  • 2级幂等和1级幂等的key是强相关的。数据库为一级幂等,直接使用order_id。缓存使用2级幂等,key是order_id包装的字符串
  • 缓存3秒后过期。
  • redis是内存缓存,后14个请求,不会进入数据库
  • 分布式服务共用一个redis集群/节点,所以分布式会共享这个key的状态,是分布式可用的

猜你喜欢

转载自blog.csdn.net/fwhezfwhez/article/details/113849811