常见的分布式锁实现方案

1.基于mysql的分布式锁

悲观锁

使用sql语句实现悲观锁

  1. # 1.在一个终端开启互斥锁以后, 另一个终端执行SELECT * FROM inventories WHERE goods_id=3 FOR UPDATE的时候也能获取该互斥锁
  2. # 2.如果查询的字段设置了索引, 该方法锁住的是SELECT选中的行
  3. # 3.如果在没有索引的字段上查询, 那么行锁会升级成表锁
  4. # 4.如果是在有索引的字段上查询, 没有查询到数据, 不会锁行, 也不会锁表
  5. # 5.在没有索引的字段上查询, 即时没有查询到数据, 也会锁表
  6. SELECT @@autocommit; // 这一行仅做查询使用(查询autocommit的值)
  7. # 需要关闭autocommit
  8. SET autocommit=0;
  9. SELECT @@autocommit; // 这一行仅做查询使用(查询autocommit的值)
  10. SELECT * FROM inventories WHERE goods_id=3 FOR UPDATE;
  11. COMMIT; // commit以后会释放锁
  1. // 另一客户端如果不使用"FOR UPDATE"是不会获取锁的
  2. SELECT * FROM inventories WHERE goods_id=3 FOR UPDATE的时候也能获取该互斥锁

使用gorm实现悲观锁

  1. // 这一行相当于是SET autocommit=0;并且选中FOR UPDATE的行
  2. res := tx.Clauses(clause.Locking{
  3. Strength: "UPDATE",
  4. }).Where(&model.Inventory{
  5. GoodsID: goodsInfo.GoodsID,
  6. }).First(&inv)
  7. // commit之后就会释放
  8. tx.Commit()

乐观锁

  1. for _, goodsInfo := range info.GoodsInfo {
  2. i := 0
  3. for {
  4. i++
  5. var inv model.Inventory
  6. // 查询库存信息
  7. // 悲观锁
  8. //res := tx.Clauses(clause.Locking{
  9. // Strength: "UPDATE",
  10. //}).Where(&model.Inventory{
  11. // GoodsID: goodsInfo.GoodsID,
  12. //}).First(&inv)
  13. res := global.DB.Where(&model.Inventory{
  14. GoodsID: goodsInfo.GoodsID,
  15. }).First(&inv)
  16. if res.Error != nil && res.Error != gorm.ErrRecordNotFound {
  17. tx.Rollback()
  18. return nil, status.Error(codes.Internal, res.Error.Error())
  19. }
  20. if res.RowsAffected == 0 {
  21. tx.Rollback()
  22. return nil, status.Error(codes.NotFound, "没有该商品库存")
  23. }
  24. // 保存版本号
  25. version := inv.Version
  26. if inv.Stocks < goodsInfo.Num {
  27. tx.Rollback()
  28. return nil, status.Error(codes.ResourceExhausted, "库存不足")
  29. }
  30. // 开始扣减库存
  31. res = tx.Model(&model.Inventory{}).Where(&model.Inventory{
  32. GoodsID: goodsInfo.GoodsID,
  33. Version: version,
  34. }).Updates(map[string]interface{}{
  35. "stocks": inv.Stocks - goodsInfo.Num,
  36. "version": inv.Version + 1,
  37. })
  38. //res = tx.Model(&model.Inventory{}).Where("goods_id = ? and version = ?", goodsInfo.GoodsID,
  39. // version).Updates(model.Inventory{
  40. // Stocks: inv.Stocks - goodsInfo.Num,
  41. // Version: inv.Version + 1,
  42. //})
  43. if res.Error != nil && res.Error != gorm.ErrRecordNotFound {
  44. fmt.Println("Error", res.Error.Error())
  45. tx.Rollback()
  46. return nil, status.Error(codes.Internal, res.Error.Error())
  47. }
  48. // 失败重试(库存扣减失败会进入for循环)
  49. if res.RowsAffected > 0 {
  50. break
  51. }
  52. }
  53. }

2.基于redis的分布式锁

参考地址: https://github.com/go-redsync/redsync

  1. package main
  2. import (
  3. goredislib "github.com/go-redis/redis/v8"
  4. "github.com/go-redsync/redsync/v4"
  5. "github.com/go-redsync/redsync/v4/redis/goredis/v8"
  6. )
  7. func main() {
  8. // Create a pool with go-redis (or redigo) which is the pool redisync will
  9. // use while communicating with Redis. This can also be any pool that
  10. // implements the `redis.Pool` interface.
  11. client := goredislib.NewClient(&goredislib.Options{
  12. Addr: "localhost:6379",
  13. })
  14. pool := goredis.NewPool(client) // or, pool := redigo.NewPool(...)
  15. // Create an instance of redisync to be used to obtain a mutual exclusion
  16. // lock.
  17. rs := redsync.New(pool)
  18. // Obtain a new mutex by using the same name for all instances wanting the
  19. // same lock.
  20. mutexname := "my-global-mutex"
  21. mutex := rs.NewMutex(mutexname)
  22. // Obtain a lock for our given mutex. After this is successful, no one else
  23. // can obtain the same lock (the same mutex name) until we unlock it.
  24. if err := mutex.Lock(); err != nil {
  25. panic(err)
  26. }
  27. // Do your work that requires the lock.
  28. // Release the lock so other processes or threads can obtain a lock.
  29. if ok, err := mutex.Unlock(); !ok || err != nil {
  30. panic("unlock failed")
  31. }
  32. }

实现原理:

setnx是一个原子性操作: 如果这个key不存在, 就设置它, 存在会返回0, 设置成功返回1

3.基于zookeeper的分布式事务锁