常见的分布式锁实现方案
1.基于mysql的分布式锁
悲观锁
使用sql语句实现悲观锁
# 1.在一个终端开启互斥锁以后, 另一个终端执行SELECT * FROM inventories WHERE goods_id=3 FOR UPDATE的时候也能获取该互斥锁# 2.如果查询的字段设置了索引, 该方法锁住的是SELECT选中的行# 3.如果在没有索引的字段上查询, 那么行锁会升级成表锁# 4.如果是在有索引的字段上查询, 没有查询到数据, 不会锁行, 也不会锁表# 5.在没有索引的字段上查询, 即时没有查询到数据, 也会锁表SELECT @@autocommit; // 这一行仅做查询使用(查询autocommit的值)# 需要关闭autocommitSET autocommit=0;SELECT @@autocommit; // 这一行仅做查询使用(查询autocommit的值)SELECT * FROM inventories WHERE goods_id=3 FOR UPDATE;COMMIT; // commit以后会释放锁
// 另一客户端如果不使用"FOR UPDATE"是不会获取锁的SELECT * FROM inventories WHERE goods_id=3 FOR UPDATE的时候也能获取该互斥锁
使用gorm实现悲观锁
// 这一行相当于是SET autocommit=0;并且选中FOR UPDATE的行res := tx.Clauses(clause.Locking{Strength: "UPDATE",}).Where(&model.Inventory{GoodsID: goodsInfo.GoodsID,}).First(&inv)// commit之后就会释放tx.Commit()
乐观锁
for _, goodsInfo := range info.GoodsInfo {i := 0for {i++var inv model.Inventory// 查询库存信息// 悲观锁//res := tx.Clauses(clause.Locking{// Strength: "UPDATE",//}).Where(&model.Inventory{// GoodsID: goodsInfo.GoodsID,//}).First(&inv)res := global.DB.Where(&model.Inventory{GoodsID: goodsInfo.GoodsID,}).First(&inv)if res.Error != nil && res.Error != gorm.ErrRecordNotFound {tx.Rollback()return nil, status.Error(codes.Internal, res.Error.Error())}if res.RowsAffected == 0 {tx.Rollback()return nil, status.Error(codes.NotFound, "没有该商品库存")}// 保存版本号version := inv.Versionif inv.Stocks < goodsInfo.Num {tx.Rollback()return nil, status.Error(codes.ResourceExhausted, "库存不足")}// 开始扣减库存res = tx.Model(&model.Inventory{}).Where(&model.Inventory{GoodsID: goodsInfo.GoodsID,Version: version,}).Updates(map[string]interface{}{"stocks": inv.Stocks - goodsInfo.Num,"version": inv.Version + 1,})//res = tx.Model(&model.Inventory{}).Where("goods_id = ? and version = ?", goodsInfo.GoodsID,// version).Updates(model.Inventory{// Stocks: inv.Stocks - goodsInfo.Num,// Version: inv.Version + 1,//})if res.Error != nil && res.Error != gorm.ErrRecordNotFound {fmt.Println("Error", res.Error.Error())tx.Rollback()return nil, status.Error(codes.Internal, res.Error.Error())}// 失败重试(库存扣减失败会进入for循环)if res.RowsAffected > 0 {break}}}
2.基于redis的分布式锁
参考地址: https://github.com/go-redsync/redsync
package mainimport (goredislib "github.com/go-redis/redis/v8""github.com/go-redsync/redsync/v4""github.com/go-redsync/redsync/v4/redis/goredis/v8")func main() {// Create a pool with go-redis (or redigo) which is the pool redisync will// use while communicating with Redis. This can also be any pool that// implements the `redis.Pool` interface.client := goredislib.NewClient(&goredislib.Options{Addr: "localhost:6379",})pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)// Create an instance of redisync to be used to obtain a mutual exclusion// lock.rs := redsync.New(pool)// Obtain a new mutex by using the same name for all instances wanting the// same lock.mutexname := "my-global-mutex"mutex := rs.NewMutex(mutexname)// Obtain a lock for our given mutex. After this is successful, no one else// can obtain the same lock (the same mutex name) until we unlock it.if err := mutex.Lock(); err != nil {panic(err)}// Do your work that requires the lock.// Release the lock so other processes or threads can obtain a lock.if ok, err := mutex.Unlock(); !ok || err != nil {panic("unlock failed")}}
实现原理:
setnx是一个原子性操作: 如果这个key不存在, 就设置它, 存在会返回0, 设置成功返回1
