美文网首页
分布式锁

分布式锁

作者: wayyyy | 来源:发表于2021-11-07 18:05 被阅读0次

redis 实现分布式锁

环境准备
  • SETNX(SET if Not eXists 如果不存在,则 SET)命令
    setnx key value,只有当key不存在的情况下,将key设置为value;若key存在,不做任何操作,结果成功返回1,失败返回0
  • SETEX 命令
    setex key seconds value:将key值设置为value,并将设置key的生存周期
  • SET 命令
    SET {key} {value} [ex seconds] [px milliseconds] [nx|xx]
    - ex seconds 设置秒级别的过期时间
    - px milliseconds 设置毫秒级别的过期时间
    - nx 键必须不存在,才可以设置成功,用于添加
    - xx  键必须存在,才可以设置成功,用于更新
    
  • docker 启动一个 redis 实例
    docker run --name redis1 -p 6679:6379 -d redis redis-server --appendonly yes
    
redis 是单实例

实现分布式锁需要的很早期命令SETNX,这个命令表示 SET If Not eXists,即如果 key 不存在,才会设置它的值,否则什么也不做。

两个客户端进程可以执行这个命令,达到互斥,就可以实现一个分布式锁。
启动一个redis-cli1

# redis-cli -h 127.0.0.1 -p 6679 
127.0.0.1:6679> SETNX lock 1
(integer) 1

再启动另一个redis-cli2:

redis-cli -h 127.0.0.1 -p 6679
127.0.0.1:6679> SETNX lock 1
(integer) 0
127.0.0.1:6679> get lock 
"1"

redis-cli1 使用del 释放锁

redis-cli2 获取锁:

上面的实现已经基本实现了互斥的功能,但还不够,试想:假设当客户端1拿到锁后,进程挂了,那么就会导致这个客户端一直占用这个锁,其他客户端也就永远拿不到这个锁了。

那想说,这样也好办,再使用命令给这个锁设置一个过期时间:

127.0.0.1:6679> setex lock 10 1
OK
127.0.0.1:6679> ttl lock
(integer) 6

这样,无论客户端是否异常,这个锁都可以在一定时间后被自动释放,其它客户端依旧可以拿到锁。

但就是如此,还是会遇到2个问题:

  • 锁过期时间怎么确定?
    假设客户端 1 操作共享资源需要耗时很长,那可能还没有工作做完,就导致锁被自动释放。
    这个问题,目前没有好的解决办法,只好业务层去评估合适的过期时间。
  • 释放别人的锁
    锁过期发生后,锁被客户端2持有,这时客户端 1 释放了客户端 2 持有的锁
    这种问题本质在于每个客户端在释放锁时,并没有检查这把锁是否还归自己持有,所以就会发生释放别人锁的风险。为了解决这一问题,客户端在加锁时,可以设置一个唯一标识 进去,可以是自己的线程 ID,也可以是一个 UUID。

Demo:

package main

import (
    "fmt"
    "github.com/go-redis/redis"
    "sync"
    "time"
)

var lockKey = "counter_lock"
var counterKey = "counter"

func incr(grNum int) {
    client := redis.NewClient(&redis.Options{
        Addr:     "192.168.48.139:6679",
        Password: "",
        DB:       0,
    })

    // lock
    resp := client.SetNX(lockKey, 1, time.Second*5)
    lockSuccess, err := resp.Result()
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(grNum, "lock result: ", lockSuccess)
    if !lockSuccess {
        return
    }
    
    // counter++
    getResp := client.Get(counterKey)
    cntValue, err := getResp.Int64()
    if err == nil {
        cntValue++
        resp := client.Set(counterKey, cntValue, 0)
        _, err := resp.Result()
        if err != nil {
            fmt.Println(grNum, "set value error", err.Error())
        }
    } else {
        fmt.Println(grNum, "get value error", err.Error())
    }

    fmt.Println(grNum, "current counter is ", cntValue)

    // unlock
    delResp := client.Del(lockKey)
    unlockSuccess, err := delResp.Result()
    if err == nil && unlockSuccess > 0 {
        fmt.Println(grNum, "unlock success")
    } else {
        fmt.Println(grNum, "unlock failed", err)
    }
}

func main() {
    client := redis.NewClient(&redis.Options{
        Addr:     "192.168.48.139:6679",
        Password: "",
        DB:       0,
    })

    resp := client.Set(counterKey, 0, 0)
    _, err := resp.Result()
    if err != nil {
        fmt.Println("set value error", err.Error())
        return
    }

    var wg sync.WaitGroup
    for i := 0; i < 10; i++ {
        time.Sleep(time.Microsecond * 100)
        wg.Add(1)
        go func(grNum int) {
            defer wg.Done()
            incr(grNum)
        }(i)
    }
    wg.Wait()
}

输出:


image.png
redis 是多实例

上面分析的场景都是,锁在单个Redis 实例中可能产生的问题,而实际在使用 Redis 时,一般会采用 主从集群 + 哨兵的模式部署,用来保证可用性。

那当主从发生切换时,这个分布式锁会依旧安全吗?
想想这样的场景:客户端 1 在主库上执行 SET 命令,加锁成功,此时,主库异常宕机,SET 命令还未同步到从库上(主从复制是异步的)从库被哨兵提升为新主库,这时锁在新的主库上就丢失了。

为了解决这一问题
TODO

zookeeper 实现分布式锁

TODO

zookeeper 的锁基于Redis 锁不同之处在于Lock成功之前会一直阻塞,这与单价的 mutex.Lock 行为相似。
这种分布式阻塞锁适合分布式任务调度场景,但不适合高频次持锁时间段的抢锁场景。

etcd 实现分布式锁

TODO

总结:如何挑选合适的分布式锁?

在业务规模不大,QPS很小的情况下,使用哪种分布式锁都差不多,如果公司内已有可以使用的zookeeper,etcd 或者 redis 集群,那就尽量在不引入新技术栈的情况下满足业务需求。


参考资料
1、

相关文章

  • 分布式锁

    为什么要用分布式锁 数据库乐观锁redis分布式锁zookeeper分布式锁 使用分布式锁的场景 实现分布式锁的方...

  • 什么是分布式锁?几种分布式锁分别是怎么实现的?

    一、什么是分布式锁: 1、什么是分布式锁: 分布式锁,即分布式系统中的锁。在单体应用中我们通过锁解决的是控制共享资...

  • 4:Redis 分布式锁 (文末有项目连接)

    1:什么是缓存分布式锁 2:分布式锁的关键代码 3:业务代码使用分布式缓存锁 4:业务代码使用分布式缓存锁 5:测...

  • 锁(2)-- 分布式锁

    前言: 锁分3种:java锁、分布式锁、DB锁 分布式锁的几种实现方式 目前几乎很多大型网站及应用都是分布式部署...

  • java锁的概念

    参考文档探究分布式并发锁并发编程-锁的发展和主流分布式锁比较总结从构建分布式秒杀系统聊聊分布式锁探索并发编程(六)...

  • Redis实现分布式锁

    分布式下的分布式锁一般实现有三种: 基于数据库的乐观锁 基于redis的分布式锁 基于zookeeper的分布式锁...

  • 分布式锁

    为什么要用分布式锁? 分布式锁是悲观锁的实现; 如果采用乐观锁的方案就用不着分布式锁了。 能用乐观锁的地方尽量用乐...

  • 3.10:分布式锁

    本文将梳理微服务架构下,分布式锁的常用方案。整体包含以下三部分: 分布式锁的提出 分布式锁主流方案 分布式锁选择 ...

  • Redis实现分布式锁

    1. 分布式锁分类 数据库乐观锁 基于Redis的分布式锁 基于ZooKeeper的分布式锁 2. 组件依赖 po...

  • 大佬浅谈分布式锁

    redis 实现 redis 分布锁一、redis 实现分布式锁(可重入锁)redission 实现分布式锁1、对...

网友评论

      本文标题:分布式锁

      本文链接:https://www.haomeiwen.com/subject/wzgbgktx.html