redis实现分布式锁,go实现完整code

news2024/11/26 10:38:44

Redis分布式锁

Redis 分布式锁是一种使用 Redis 数据库实现分布式锁的方式,可以保证在分布式环境中同一时间只有一个实例可以访问共享资源。

实现机制

以下是实现其加锁步骤:

在这里插入图片描述

获取锁

在 Redis 中,一个相同的key代表一把锁。是否拥有这把锁,需要判断keyvalue是否是自己设置的,同时还要判断锁是否已经过期。

  • 首先通过get命令去获取锁,如果获取不到说明还没有加锁
  • 如果还没有加锁我们就可以去通过set命令去加锁,并且需要设置一个expire过期时间防止成为一个长生不老锁,那如果业务还没有执行完锁就释放了怎么办呢?这个后面会提到续锁
  • 如果获取到了key说明已经被其他实例抢到了锁,加锁失败
  • 加锁失败还需要根据一些操作例如超时时间内去重试加锁,直到加锁成功或者超时

这些操作都需要原子性操作,需要用lua脚本进行封装

lock.lua
val = redis.call('get', KEYS[1])
if val == false then
    return redis.call('set', KEYS[1], ARGV[1], 'EX', ARGV[2])
elseif val == ARGV[1] then
    redis.call('expire', KEYS[1], ARGV[2])
    return 'OK'
else
    return ''
end

释放锁

释放锁的时候就是把key删除,不过删除的时候需要判断是不是自己加的锁

unlock.lua
if redis.call('get', KEYS[1]) == ARGV[1] then
    return redis.call('del', KEYS[1])
else
    return 0
end

Go 实现分布式锁

结构体字段配置

// redis客户端连接
type Client struct {
	client  redis.Cmdable
	varFunc func() string
	g       singleflight.Group
}

// 锁的结构体
type Lock struct {
	client     redis.Cmdable
	key        string
	value      string
	expiration time.Duration
	unlock     chan struct{}
	unlockOne  sync.Once
}

// NewClient creates a *Client
func NewClient(client redis.Cmdable) *Client {
	return &Client{
		client: client,
		varFunc: func() string {
			return uuid.New().String()
		},
	}
}

// 重试策略
type RetryStrategy interface {
	// Next determines the time interval for Lock
	// and whether Lock to retry
	Next() (time.Duration, bool)
}

// 周期性重试
type FixedIntervalRetry struct {
	Interval time.Duration
	Max      int
	cnt      int
}

lua 脚本,使用go的embed映射到luaLock string

var (
	ErrFailedToPreemptLock = errors.New("redis-lock: failed to lock")
	ErrLockNotHold         = errors.New("redis-lock: lock not hold")
	ErrLockTimeout         = errors.New("redis-lock: lock timeout")

	//go:embed lua/unlock.lua
	luaUnlock string

	//go:embed lua/refresh.lua
	luaRefresh string

	//go:embed lua/lock.lua
	luaLock string
)

加锁Lock

加锁时有两种方案,一种是比较简单的( TryLock )尝试加锁,只需要传个过期时间,另一种是比较完善的( Lock )加锁,会有超时策略等

func newLock(client redis.Cmdable, key string, value string, expiration time.Duration) *Lock {
	return &Lock{
		client:     client,
		key:        key,
		value:      value,
		expiration: expiration,
		unlock:     make(chan struct{}, 1),
	}
}

// TryLock tries to acquire a lock
func (c *Client) TryLock(ctx context.Context,
	key string,
	expiration time.Duration) (*Lock, error) {
	val := c.varFunc()
	ok, err := c.client.SetNX(ctx, key, val, expiration).Result()
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, ErrFailedToPreemptLock
	}
	return newLock(c.client, key, val, expiration), nil
}

// Lock tries to acquire a lock with timeout and retry strategy
func (c *Client) Lock(ctx context.Context,
	key string,
	expiration time.Duration,
	timeout time.Duration, retry RetryStrategy) (*Lock, error) {
	var timer *time.Timer
	val := c.varFunc()
	for {
		lCtx, cancel := context.WithTimeout(ctx, timeout)
		res, err := c.client.Eval(lCtx, luaLock, []string{key}, val, expiration.Seconds()).Result()
		cancel()
		if err != nil && !errors.Is(err, context.DeadlineExceeded) {
			return nil, err
		}

		if res == "OK" {
			return newLock(c.client, key, val, expiration), nil
		}

		interval, ok := retry.Next()
		if !ok {
			return nil, ErrLockTimeout
		}
		if timer == nil {
			timer = time.NewTimer(interval)
		} else {
			timer.Reset(interval)
		}
		select {
		case <-timer.C:
		case <-ctx.Done():
			return nil, ctx.Err()
		}
	}
}

解锁unLock

// Unlock releases the lock
func (l *Lock) Unlock(ctx context.Context) error {
    res, err := l.client.Eval(ctx, luaUnlock, []string{l.key}, l.value).Int64()
    defer func() {
       l.unlockOne.Do(func() {
          l.unlock <- struct{}{}
          close(l.unlock)
       })
    }()
    if errors.Is(err, redis.Nil) {
       return ErrLockNotHold
    }
    if err != nil {
       return err
    }
    if res != 1 {
       return ErrLockNotHold
    }
    return nil
}

小结

  • 使用分布式锁本身会有各种各样的问题,需要自己去处理异常情况例如超时等
  • 对锁的操作一定要判断是不是自己加的那把锁,否则会误删会导致业务错误
  • 对锁的续约部分我们下一篇再讲

本文go的代码是完整的,可以直接copy使用,有兴趣的小伙伴可以去使用一下

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2232108.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

flink 自定义kudu connector中使用Metrics计数平均吞吐量,并推送到自定义kafkaReporter

文章目录 前言1. Registering metrics2. Metrics 的类型2.1 counter2.2 Gauge2.3 Histogram2.4 meter 3. 指标划分3.1 指标所属的范围3.2 默认所属 4. 自定义kudu connector中使用Metrics4.1 sink算子继承RichFunction4.2 注册指标4.3 计数逻辑4.4 自定义Reporter&#xff0c;推…

柯桥日语培训|N1常考语法:~(よ)うが/(よ)うと——“无论……都……”

&#xff5e;&#xff08;よ&#xff09;うが&#xff0f;&#xff08;よ&#xff09;うと 接续&#xff1a;動意向形&#xff0f;イ形→かろう&#xff0f;名、ナ形→だろう・であろう&#xff0b;が&#xff0f;と 说明&#xff1a;表示假定条件的逆接&#xff0c;无论前项如…

一个基于Zookeeper+Dubbo3+SpringBoot3的完整微服务调用程序示例代码

一、关于 Dubbo3 的一些优化改进介绍 Dubbo3 的官方文档地址&#xff1a; https://cn.dubbo.apache.org/zh-cn/overview/what/overview/ 其针对一些问题进行了优化和改变。个人整理3个小的方面&#xff1a; 1. 在服务注册方面使用 DubboService 注解&#xff0c;不再使用 Servi…

电能表预付费系统-标准传输规范(STS)(33)

6.5.4.4 Key rotation process 按键旋转过程 The entire key is rotated one bit position to the left as illustrated in Figure 15.整个密钥向左旋转一个位&#xff0c;如图15所示。 6.5.4.5 Worked example to generate TokenData for a TransferCredit token using the S…

时序数据库是什么:概念、特点与分类简析

时序数据与时序数据库的“保姆级”科普&#xff01; 作为将数据价值转化为产能能效的“核心大脑”&#xff0c;数据库的发展依然处于加速期&#xff0c;面向不同数据类型的数据库类型也在不断增加。 在众多细分领域数据库类型中&#xff0c;伴随制造业数字化转型的行业趋势和多…

【创建型】单例模式

单例模式使用的场景&#xff1a;需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即&#xff1a;重量级对象)&#xff0c;但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等) 1. 饿汉式&#xff08;静态常量&#xf…

6.0、静态路由

路由器最主要的功能就是转发数据包。路由器转发数据包时需要查找路由表&#xff08;你可以理解为地图&#xff09;&#xff0c;管理员可以直接手动配置路由表&#xff0c;这就是静态路由。 1.什么是路由&#xff1f; 在网络世界中&#xff0c;路由是指数据包在网络中的传输路…

工业通信网关的各项功能解析-天拓四方

在工业自动化和智能制造的浪潮中&#xff0c;工业通信网关作为连接工业现场与互联网的重要桥梁&#xff0c;发挥着至关重要的作用。它不仅实现了不同网络协议之间的转换&#xff0c;还在数据采集、设备控制、网络管理等方面展现出强大的功能。 一、协议转换功能 工业通信网关…

数据结构与算法——Java实现 53.力扣938题——二叉搜索树的范围和

生命的意义 在于活出自我 而不是成为别人眼中的你 —— 24.11.3 938. 二叉搜索树的范围和 给定二叉搜索树的根结点 root&#xff0c;返回值位于范围 [low, high] 之间的所有结点的值的和。 示例 1&#xff1a; 输入&#xff1a;root [10,5,15,3,7,null,18], low 7, high 15 …

TensorRT-LLM的k8s弹性伸缩部署方案

Scaling LLMs with NVIDIA Triton and NVIDIA TensorRT-LLM Using Kubernetes | NVIDIA Technical Blog 一共涉及4个k8s组件&#xff1a; 1. Deployment&#xff1a;跑起来N个pod&#xff1b;指定NVIDIA官方的triton&trt-llm的docker image&#xff0c;指定好model放在哪个…

高亮无惧烈日,强力巨彩租赁屏点亮户外“视”界

在户外显示领域&#xff0c;一款性能出色、适应性强、维护便捷的租赁屏无疑是众多主办方和广告商的首选。强力巨彩旗下的幻云系列租赁屏具备画面清晰、无水波纹、性能稳定、高亮度等诸多优势&#xff0c;可应用于各大户外显示场所&#xff0c;是户外租赁屏市场的明星产品。   …

批量删除redis数据【亲测可用】

文章目录 引言I redis客户端基础操作key的命名规则批量查询keyII 批量删除key使用连接工具进行分组shell脚本示例其他方法III 知识扩展:控制短信验证码获取频率引言 批量删除redis数据的应用: 例如缓存数据使用了新的key存储,需要删除废弃的key。RedisTemplate的key序列化采…

Mysql开发规范

开发规范 对象命名 命名规范的对象&#xff0c;是指数据库SCHEMA、表TABLE、字段COLUMN、索引INDEX、约束CONSTRAINTS等 【强制】凡是需要命名的对象&#xff0c;其标识符不能超过30个字符【强制】名称必须以英文字母开头&#xff0c;不得以 _(下划线) 作为起始和终止字母【…

Web应用性能测试工具 - httpstat

在数字化时代&#xff0c;网站的性能直接影响用户体验和业务成功。你是否曾经在浏览网页时&#xff0c;遇到加载缓慢的困扰&#xff1f;在这个快速变化的互联网环境中&#xff0c;如何快速诊断和优化Web应用的性能呢&#xff1f;今天&#xff0c;我们将探讨一个强大的工具——h…

(57)MATLAB使用迫零均衡器和MMSE均衡器的BPSK调制系统仿真

文章目录 前言一、仿真测试模型二、仿真代码三、仿真结果四、迫零均衡器和MMSE均衡器的实现1.均衡器的MATLAB实现2.均衡器的性能测试 总结 前言 本文给出仿真模型与MATLAB代码&#xff0c;分别使用具有ISI的三个不同传输特性的信道&#xff0c;仿真测试了使用迫零均衡器和MMSE…

用ChatGPT提升工作效率:从理论到实际应用

伴人工智能技术的迅速演进&#xff0c;像ChatGPT这类语言模型已成为提升工作效率的关键工具。这类模型不仅具备处理海量数据的能力&#xff0c;还能自动化许多日常任务&#xff0c;从而提高决策的准确性。本文将深入探讨如何在工作中利用ChatGPT等AI工具提升效率&#xff0c;涵…

MySQL FIND_IN_SET 函数详解

文章目录 1. 基本语法2. 使用场景3. 实战示例3.1 基础查询示例3.2 与其他函数结合使用3.3 动态条件查询 4. 性能考虑5. 常见问题和解决方案5.1 大小写敏感问题5.2 空值处理5.3 模糊匹配 6. 总结 1. 基本语法 FIND_IN_SET 函数的基本语法如下&#xff1a; FIND_IN_SET(str, st…

「Mac畅玩鸿蒙与硬件15」鸿蒙UI组件篇5 - Slider 和 Progress 组件

Slider 和 Progress 是鸿蒙系统中的常用 UI 组件。Slider 控制数值输入&#xff0c;如音量调节&#xff1b;Progress 显示任务的完成状态&#xff0c;如下载进度。本文通过代码示例展示如何使用这些组件&#xff0c;并涵盖 进度条类型介绍、节流优化、状态同步 和 定时器动态更…

ZDH权限-扩展支持数据权限

目录 项目源码 预览地址 安装包下载地址 ZDH权限模块 ZDH权限扩展更细粒度方案 第一种方案&#xff1a; 第二种方案&#xff1a; ZDH权限扩展支持数据权限-新增属性 总结 感谢支持 项目源码 zdh_web: GitHub - zhaoyachao/zdh_web: 大数据采集,抽取平台 预览地址 后…

私有化视频平台EasyCVR海康大华宇视视频平台视频诊断技术是如何实时监测视频质量的?

在现代视频监控系统中&#xff0c;确保视频流的质量和稳定性至关重要。随着技术的进步&#xff0c;视频诊断技术已经成为实时监测视频质量的关键工具。这种技术通过智能分析算法对视频流进行实时评估和处理&#xff0c;能够自动识别视频中的各种质量问题&#xff0c;并给出相应…