常见的分布式锁实现方案
1.基于mysql的分布式锁
悲观锁
使用sql语句实现悲观锁
# 1.在一个终端开启互斥锁以后, 另一个终端执行SELECT * FROM inventories WHERE goods_id=3 FOR UPDATE的时候也能获取该互斥锁
# 2.如果查询的字段设置了索引, 该方法锁住的是SELECT选中的行
# 3.如果在没有索引的字段上查询, 那么行锁会升级成表锁
# 4.如果是在有索引的字段上查询, 没有查询到数据, 不会锁行, 也不会锁表
# 5.在没有索引的字段上查询, 即时没有查询到数据, 也会锁表
SELECT @@autocommit; // 这一行仅做查询使用(查询autocommit的值)
# 需要关闭autocommit
SET 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 := 0
for {
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.Version
if 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 main
import (
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