idempotence

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 7, 2023 License: Apache-2.0 Imports: 3 Imported by: 1

README

幂等框架概要设计

引言

编写目的

​ 本文主要用户阐述【幂等框架概要设计】实施技术方案和思路

系统总体架构

背景说明

​ 分布式系统由众多微服务组成,微服务之间必然存在大量的网络调用。下图是一个服务间调用异常的例子,用户提交订单之后,请求到A服务,A服务落单之后,开始调用B服务,但是在A调用B的过程中,存在很多不确定性,例如B服务执行超时了,RPC直接返回A请求超时了,然后A返回给用户一些错误提示,但实际情况是B有可能执行是成功的,只是执行时间过长
​ 用户看到错误提示之后,往往会选择在界面上重复点击,导致重复调用,如果B是个支付服务的话,用户重复点击可能导致同一个订单被扣多次钱。不仅仅是用户可能触发重复调用,定时任务、消息投递和机器重新启动都可能会出现重复执行的情况。在分布式系统里,服务调用出现各种异常的情况是很常见的,这些异常情况往往会使得系统间的状态不一致,所以需要容错补偿设计,最常见的方法就是调用方实现合理的重试策略,被调用方实现应对重试的幂等策略

幂等

​ 幂等(idempotence)是一个数学与计算机学概念,常见于抽象代数中。 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。 幂等函数,或幂等方法,是指可以使用相同参数重复执行,并能获得相同结果的函数。 这些函数不会影响系统状态,也不用担心重复执行会对系统造成改变,体现在业务中,就是同一个接口,多次发起同一个业务请求,业务只执行一次

幂等实现思路

处理方式
  1. 第一种,调用方访问公共服务平台接口超时时,返回清晰明确的提醒给用户,告知执行结果未知,让用户自己判断是否重试

  2. 第二种,调用方调用其他接口,来查询超时操作的结果,明确超时操作对应的业务,是执行成功了还是失败了,然后再基于明确的结果做处理。但是这种处理方法存在一个问题,那就是并不是所有的业务操作,都方便查询操作结果

  3. 第三种,调用方在遇到接口超时之后,直接发起重试操作。这样就需要接口支持幂等。我们可以选择在业务代码中触发重试,也可以将重试的操作放到框架中完成。因为偶尔发生的超时,在正常的业务逻辑中编写一大坨补救代码,这样做会影响到代码的可读性,有点划不来。当然,如果项目中需要支持超时重试的业务不多,那对于仅有几个业务,特殊处理一下也未尝不可。但是,如果项目中需要支持超时重试的业务比较多,我们最好是把超时重试这些非业务相关的逻辑,统一在框架层面解决

总结

​ 对响应时间敏感的调用方来说,服务对象是移动端用户,过长的等待时间,不如直接返回超时给用户。这种情况下,第一种处理方式是比较推荐的,但是,对响应时间不敏感的调用方来说,比如 Job 类的调用方,推荐选择后两种处理方式,能够提高处理的成功率,第二种处理方法,有一定的局限性,因为并不是所有业务操作都方便查询是否执行成功

系统说明

安装
go get e.coding.net/yhyang/golib/idempotence
使用说明
幂等号
import (
	"e.coding.net/yhyang/golib/idempotence"
    "e.coding.net/yhyang/golib/idempotence/id"
  	"e.coding.net/yhyang/golib/idempotence/storage"

)
func foo(){
	// 初始化连接池
	storage.Init()
	storage.SetRedisPoolConfig(&storage.PoolTimeConfig{
		InitialCap:  5,
		MaxCap:      200,
		MaxIdle:     100,
		IdleTimeout: 30 * time.Second,
	})
  // 获取幂等id
  idempotence := id.NewGenerator()
  idempotenceID, err := idempotence.GenerateID()
  
  // 检测是否存在,没有则保存
  // 根据SaveIfAbsent返回的bool进行判断,机制和Redis的SetNX一致
  idempotenceService := idempotence.NewIdempotence(storage.Redis) //使用redis
  ok := idempotenceService.SaveIfAbsent(idempotenceID id.IdempotenceID)
  
  // 删除
  isDeleteOk := idempotenceService.Delete(idempotenceID id.IdempotenceID)
}

状态机

只支持Redis存储

import (
	"e.coding.net/yhyang/golib/idempotence"
    "e.coding.net/yhyang/golib/idempotence/id"
  	"e.coding.net/yhyang/golib/idempotence/storage"

)
func foo(){
	// 初始化连接池
	storage.Init()
	storage.SetRedisPoolConfig(&storage.PoolTimeConfig{
		InitialCap:  5,
		MaxCap:      200,
		MaxIdle:     100,
		IdleTimeout: 30 * time.Second,
	})
  // 设置状态 不存在则设置
  ok:=idempotence.NewIdempotence(storage.Redis).SaveStatusIfAbsent("k", "status")
  // 检测状态是否正确 满足其中一个状态值即可
  ok:=idempotence.NewIdempotence(storage.Redis).CheckStatus("k", []string{"status","status1"})
   // 检测状态并且设置新的 满足其中一个状态值即可
  ok:=idempotence.NewIdempotence(storage.Redis).CheckAndSetStatus("k", []string{"start","status1"},"new status")
}

实现说明

​ idempotence提供了两种存储方式,redis和mongo

// 存储方式
const (
	// Mongo Mongo
	Mongo IdempotenceStorage = iota
	// Redis Redis
	Redis
)

​ 在id选用方式上,提供了两种方式,Snowflake和UUID

// id 生成方式
const (
	// Snowflake Snowflake
	Snowflake int8 = iota
	// UUID UUID
	UUID
)
函数说明
1.IdempotenceID生成
	参数    idType	id选用方式(0 Snowflake,  1  UUID)
	函数	NewGenerator().GenerateID(idType int8)(IdempotenceID, error)
	规则	id + ":" + svc + ":" + date
	示例	1243542314:mall-order-svc:2006-01-02
2.检测是否存在,不存在会新增,类比redis的SetNX
	SaveIfAbsent(idempotenceID id.IdempotenceID)
3.删除
	Delete(idempotenceID id.IdempotenceID)
使用约定

​ 考虑到幂等处理的时间差不会过大,系统会自动删除超过30天的数据

待完成和优化点

​ mongo实现的方式中,数据都在一张表中,如果30天内的数据过大,会有一定的查询压力

引用

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Idempotence

type Idempotence struct {
	// contains filtered or unexported fields
}

Idempotence Idempotence

func NewIdempotence

func NewIdempotence(idempotenceStorage storage.IdempotenceStorage) *Idempotence

NewIdempotence NewIdempotence

func (*Idempotence) CheckAndSetStatus

func (idempotence *Idempotence) CheckAndSetStatus(id string, status []string, newStatus string) bool

CheckAndSetStatus 检测状态

func (*Idempotence) CheckStatus

func (idempotence *Idempotence) CheckStatus(id string, status []string) bool

CheckStatus 检测状态

func (*Idempotence) Delete

func (idempotence *Idempotence) Delete(idempotenceID id.IdempotenceID) bool

Delete 删除

func (*Idempotence) SaveIfAbsent

func (idempotence *Idempotence) SaveIfAbsent(idempotenceID id.IdempotenceID) bool

SaveIfAbsent 如果没有则保存

func (*Idempotence) SaveStatusAndExpireIfAbsent

func (idempotence *Idempotence) SaveStatusAndExpireIfAbsent(id, status string, expire time.Duration) bool

SaveStatusAndExpireIfAbsent 设置状态

func (*Idempotence) SaveStatusIfAbsent

func (idempotence *Idempotence) SaveStatusIfAbsent(id, status string) bool

SaveStatusIfAbsent 设置状态

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL