一、什么是锁?
- 锁: 是一种同步机制,用于在多个执行线程的环境中强制对资源的访问进行限制。
- 分布式锁: 在编程语言中就是一个变量,该变量在同一时刻只能有一个线程拥有,以便保护共享数据在同一时刻只有一个线程去操作。而分布式锁也是锁,即分布式系统中的锁。该锁是用于解决在分布式系统中控制共享资源访问的问题的。
- 常见的使用场景:
- 库存扣减,防止超卖
- 数据幂等性,防止表单重复提交
- 分布式锁主要特征:
- 互斥:同一时刻只能有一个线程获得锁。
- 可重入:当一个线程获取锁后,还可以再次获取这个锁,避免死锁发生。
- 高可用:当小部分节点挂掉后,仍然能够对外提供服务。
- 高性能:要做到高并发、低延迟。
二、分布式锁实现方式
基于数据库 (并发较小)
- selelct … for update 悲观锁实现分布式锁
- 基于数据库的悲观锁实现分布式锁 select … for update
- MySQL中通过select…for update实现悲观锁
- select…for update仅适用于InnoDB,且查询需命中,事务需在手动提交下才能生效
- 乐观锁
- 数据表中增加version字段,修改字段时判断版本和历史版本是否一致如果一致可以直接修改,如果不一致则进行重新请求 可以使用while
基于Redis实现分布式锁(setnx+expire)
设置锁,设置过期时间
# setnx key value
set lock_1 1
# expire key time 单位秒
expire lock_1 10
逻辑执行完毕手动操作释放锁
# 释放锁
del key
上面那种实现方式存在一种问题,如果在执行完 setnx 之后,执行expire之前,服务器 crash 或重启了导致加的这个锁没有设置过期时间,就会导致死锁的情况(别的线程就永远获取不到锁了)
为了解决上述问题我们可以使用一个命令来设置锁并设置过期时间
#set key ex 10 nx 设置锁和设置过期时间一个命令完成
set lock_1 ex 10 nx
上面的方式可能存在新的问题,如果业务没有执行完毕,因为过期时间导致的了锁被释放。这样就失去了锁的安全性,有些小伙伴认为,稍微把锁过期时间设置长一些就可以啦。其实问题的关键就在于我们不确定要设置多长时间,时间太短就会导致锁过期释放,业务没执行完,时间太长就会使系统运行效率下降. 我们设想一下,是否可以给获得锁的线程,开启一个定时守护线程,每隔一段时间检查锁是否还存在,存在则对锁的过期时间延长,防止锁过期提前释放。
我么可以使用看门口 watch_dog,当我面的业务代码执行时我们设置一个线程,执行循环操作,循环时间大致为锁过期时间的一半,如果锁还存在,那么我们重新设置过期时间,当我们的业务逻辑代码执行完毕之后,关闭这个线程。
3.golang使用redsync
- 安装redis
# 拉取镜像 docker pull redis # 启动 docker run -p 6379:6379 --name myredis -d redis
- golang代码说明
文档地址package main import ( goredislib "github.com/redis/go-redis/v9" "github.com/go-redsync/redsync/v4" "github.com/go-redsync/redsync/v4/redis/goredis/v9" ) 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",//redis 地址 端口 }) 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") } }
总结
总的来说,Redsync 是一个强大、健壮且易于使用的分布式锁解决方案,它能够轻松地融入你的 Go 应用程序,提升系统的并发控制能力。无论是新手还是经验丰富的开发者,都能从中受益。让我们一起探索 Redsync 的潜力,打造更可靠的分布式系统吧!