reids 是没有提供分布式锁的。但是有使用reids实现分布式锁的第三方库golang库(go-redsync:https://github.com/go-redsync/redsync)。分布式锁应用场景:分布式系统下的超卖,定时任务的重复执行等…
请查看我的文章 《Redis 分布式锁》
这里使用redsync解决超卖问题作为例子
先安装库redsync库
go get github.com/go-redsync/redsync/v4
安装redis库
# 请根据redis的版本选择对应的版本库
go get github.com/go-redis/redis/v8
redsync提供的锁基于(go-redis或Redisgo库)
这里redis使用 go-redis库
redsync.New 支持传入多个redis实例(宏锁)
package main
import (
"fmt"
goredislib "github.com/go-redis/redis/v8"
"github.com/go-redsync/redsync/v4"
"github.com/go-redsync/redsync/v4/redis/goredis/v8"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"sync"
"time"
)
type Inventory struct {
gorm.Model
ID uint `gorm:"primarykey"`
Num *int `gorm:"type:bigint not null;default:20"`
}
func (i Inventory) TableName() string {
return "inventory"
}
func getConnect() *gorm.DB {
//换成自己的数据库连接
dsn := "root:Xrx@1994113@tcp(127.0.0.1:3306)/proxy?charset=utf8mb4&parseTime=True&loc=Local"
newLogger := logger.New(
//os.Stdout 标准输出,控制台打印
// 以\r\n来作为打印间隔
// log.LstdFlags 前面这串: 2022/08/13 15:22:34
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second, // 慢 SQL 阈值
LogLevel: logger.Info, // 日志级别
IgnoreRecordNotFoundError: true, // 是否忽略ErrRecordNotFound(记录未找到)错误
Colorful: true, //是否开启彩色打印
},
)
// 全局模式
open, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
//禁止创建外键
DisableForeignKeyConstraintWhenMigrating: true,
Logger: newLogger,
})
if err != nil {
panic(err)
}
return open
}
func main() {
connect := getConnect()
// redis连接
c := goredislib.NewClient(&goredislib.Options{
Addr: "localhost:6379",
Password: "",
DB: 0,
//PoolSize: 60,
})
//在redis上进行扩展
pool := goredis.NewPool(c)
//实例
r := redsync.New(pool)
id := 2
//创建锁,并将默认的过期时间从8秒改为10秒
lock := r.NewMutex(fmt.Sprintf("goods_%d", id), redsync.WithExpiry(time.Second*10))
var wg sync.WaitGroup
wg.Add(23)
for i := 0; i < 23; i++ {
go func() {
defer wg.Done()
if err := lock.Lock(); err != nil {
panic(err)
}
defer func() {
if ok, err := lock.Unlock(); !ok || err != nil {
panic("unlock failed")
}
}()
inv := Inventory{}
connect.Where(Inventory{ID: uint(id)}).Find(&inv)
//判断是否查询,异常,自己去完善,略......
if *inv.Num < 1 {
fmt.Println("库存不足无法扣除")
return
}
result := connect.Model(&Inventory{}).Where("id = ?", id).Update("num", *inv.Num-1)
if result.Error != nil || result.RowsAffected == 0 {
fmt.Println("库存扣减失败")
} else {
fmt.Println("库存扣减成功")
}
}()
}
wg.Wait()
}
由于一台redis实例容易单点故障。
主从模式可能由于主节点挂掉后的未同步或部分同步,导致锁混乱。
提出了宏锁概念。开启多台独立的redis做同样的操作(并行),以多数redis成功的结果作为导向( success instance >= 1/2),删除失败的加锁操作。来保证锁正确和可用。