40分钟学 Go 语言高并发:分布式锁实现

分布式锁实现

一、概述

分布式锁是分布式系统中的一个重要组件,用于协调分布式环境下的资源访问和并发控制。我们将从锁设计、死锁预防、性能优化和容错处理四个维度深入学习。

学习目标

维度重点内容掌握程度
锁设计基于Redis/etcd的锁实现原理必须掌握
死锁预防超时机制、重入机制必须掌握
性能优化锁粒度控制、读写分离重点掌握
容错处理节点故障、网络分区重点掌握

二、实现流程图

在这里插入图片描述

三、基础锁实现

让我们首先实现一个基于Redis的分布式锁基础版本:

package distlockimport ("context""crypto/rand""encoding/base64""errors""time""github.com/go-redis/redis/v8"
)type DistributedLock struct {client     *redis.Clientkey        stringvalue      stringexpiration time.Duration
}// NewDistributedLock 创建一个新的分布式锁实例
func NewDistributedLock(client *redis.Client, key string, expiration time.Duration) (*DistributedLock, error) {// 生成随机值作为锁的标识b := make([]byte, 16)_, err := rand.Read(b)if err != nil {return nil, err}value := base64.StdEncoding.EncodeToString(b)return &DistributedLock{client:     client,key:        key,value:      value,expiration: expiration,}, nil
}// TryLock 尝试获取锁
func (dl *DistributedLock) TryLock(ctx context.Context) (bool, error) {return dl.client.SetNX(ctx, dl.key, dl.value, dl.expiration).Result()
}// Unlock 释放锁
func (dl *DistributedLock) Unlock(ctx context.Context) error {script := `if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])elsereturn 0end`result, err := dl.client.Eval(ctx, script, []string{dl.key}, dl.value).Result()if err != nil {return err}if result == 0 {return errors.New("lock not held")}return nil
}// RefreshLock 刷新锁的过期时间
func (dl *DistributedLock) RefreshLock(ctx context.Context) error {script := `if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("pexpire", KEYS[1], ARGV[2])elsereturn 0end`result, err := dl.client.Eval(ctx,script,[]string{dl.key},dl.value,dl.expiration.Milliseconds(),).Result()if err != nil {return err}if result == 0 {return errors.New("lock not held")}return nil
}// IsLocked 检查锁是否被持有
func (dl *DistributedLock) IsLocked(ctx context.Context) (bool, error) {exists, err := dl.client.Exists(ctx, dl.key).Result()if err != nil {return false, err}return exists == 1, nil
}

四、增强版锁实现(带可重入特性)

下面是一个支持可重入的分布式锁实现:

package distlockimport ("context""encoding/json""errors""sync""time""github.com/go-redis/redis/v8"
)type LockInfo struct {Owner     string `json:"owner"`Count     int    `json:"count"`Timestamp int64  `json:"timestamp"`
}type ReentrantLock struct {client     *redis.Clientkey        stringowner      stringexpiration time.Durationmu         sync.Mutex
}// NewReentrantLock 创建可重入锁
func NewReentrantLock(client *redis.Client, key string, owner string, expiration time.Duration) *ReentrantLock {return &ReentrantLock{client:     client,key:        key,owner:      owner,expiration: expiration,}
}// Lock 获取可重入锁
func (rl *ReentrantLock) Lock(ctx context.Context) error {rl.mu.Lock()defer rl.mu.Unlock()script := `local lockInfo = redis.call('get', KEYS[1])if not lockInfo then-- 锁不存在,创建新锁redis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])return 1endlocal info = cjson.decode(lockInfo)if info.owner == ARGV[3] then-- 重入锁info.count = info.count + 1info.timestamp = tonumber(ARGV[4])redis.call('set', KEYS[1], cjson.encode(info), 'PX', ARGV[2])return 1endreturn 0`lockInfo := LockInfo{Owner:     rl.owner,Count:     1,Timestamp: time.Now().UnixNano(),}lockInfoJSON, err := json.Marshal(lockInfo)if err != nil {return err}result, err := rl.client.Eval(ctx,script,[]string{rl.key},string(lockInfoJSON),rl.expiration.Milliseconds(),rl.owner,time.Now().UnixNano(),).Result()if err != nil {return err}if result.(int64) == 0 {return errors.New("failed to acquire lock")}return nil
}// Unlock 释放可重入锁
func (rl *ReentrantLock) Unlock(ctx context.Context) error {rl.mu.Lock()defer rl.mu.Unlock()script := `local lockInfo = redis.call('get', KEYS[1])if not lockInfo thenreturn 0endlocal info = cjson.decode(lockInfo)if info.owner ~= ARGV[1] thenreturn -1endinfo.count = info.count - 1if info.count <= 0 thenredis.call('del', KEYS[1])return 1elseredis.call('set', KEYS[1], cjson.encode(info), 'PX', ARGV[2])return 1end`result, err := rl.client.Eval(ctx,script,[]string{rl.key},rl.owner,rl.expiration.Milliseconds(),).Result()if err != nil {return err}switch result.(int64) {case -1:return errors.New("lock held by another owner")case 0:return errors.New("lock not held")default:return nil}
}// RefreshLock 刷新锁的过期时间
func (rl *ReentrantLock) RefreshLock(ctx context.Context) error {script := `local lockInfo = redis.call('get', KEYS[1])if not lockInfo thenreturn 0endlocal info = cjson.decode(lockInfo)if info.owner ~= ARGV[1] thenreturn 0endinfo.timestamp = tonumber(ARGV[3])redis.call('set', KEYS[1], cjson.encode(info), 'PX', ARGV[2])return 1`result, err := rl.client.Eval(ctx,script,[]string{rl.key},rl.owner,rl.expiration.Milliseconds(),time.Now().UnixNano(),).Result()if err != nil {return err}if result.(int64) == 0 {return errors.New("lock not held")}return nil
}

五、死锁预防机制

1. 超时机制

  • 所有锁操作都设置了过期时间
  • 使用看门狗机制自动续期
  • 防止客户端崩溃导致的死锁

2. 死锁检测

检测项处理方式实现难度
循环等待资源有序分配中等
持有等待一次性申请所有资源简单
不可剥夺超时自动释放简单
互斥访问读写分离较难

六、性能优化策略

1. 锁粒度优化

  • 降低锁粒度,提高并发度
  • 使用多粒度锁机制
  • 实现分段锁

2. 读写分离

package distlockimport ("context""fmt""time""github.com/go-redis/redis/v8"
)type RWLock struct {client     *redis.Clientkey        stringowner      stringexpiration time.Duration
}func NewRWLock(client *redis.Client, key string, owner string, expiration time.Duration) *RWLock {return &RWLock{client:     client,key:        key,owner:      owner,expiration: expiration,}
}// RLock 获取读锁
func (rwl *RWLock) RLock(ctx context.Context) error {script := `-- 检查是否存在写锁if redis.call('exists', KEYS[1] .. ':write') == 1 thenreturn 0end-- 增加读锁计数local count = redis.call('incr', KEYS[1] .. ':read')redis.call('pexpire', KEYS[1] .. ':read', ARGV[1])-- 记录读锁持有者redis.call('hset', KEYS[1] .. ':readers', ARGV[2], '1')redis.call('pexpire', KEYS[1] .. ':readers', ARGV[1])return 1`result, err := rwl.client.Eval(ctx,script,[]string{rwl.key},rwl.expiration.Milliseconds(),rwl.owner,).Result()if err != nil {return fmt.Errorf("failed to acquire read lock: %v", err)}if result.(int64) == 0 {return fmt.Errorf("write lock exists")}return nil
}// RUnlock 释放读锁
func (rwl *RWLock) RUnlock(ctx context.Context) error {script := `-- 检查读锁是否存在if redis.call('exists', KEYS[1] .. ':read') == 0 thenreturn 0end-- 检查当前客户端是否持有读锁if redis.call('hexists', KEYS[1] .. ':readers', ARGV[1]) == 0 thenreturn -1end-- 移除读锁持有者记录redis.call('hdel', KEYS[1] .. ':readers', ARGV[1])-- 减少读锁计数local count = redis.call('decr', KEYS[1] .. ':read')if count <= 0 thenredis.call('del', KEYS[1] .. ':read')redis.call('del', KEYS[1] .. ':readers')endreturn 1`result, err := rwl.client.Eval(ctx,script,[]string{rwl.key},rwl.owner,).Result()if err != nil {return fmt.Errorf("failed to release read lock: %v", err)}switch result.(int64) {case -1:return fmt.Errorf("read lock not held by this client")case 0:return fmt.Errorf("read lock not exists")default:return nil}
}// Lock 获取写锁
func (rwl *RWLock) Lock(ctx context.Context) error {script := `-- 检查是否存在读锁或写锁if redis.call('exists', KEYS[1] .. ':read') == 1 orredis.call('exists', KEYS[1] .. ':write') == 1 thenreturn 0end-- 设置写锁redis.call('set', KEYS[1] .. ':write', ARGV[1], 'PX', ARGV[2])return 1`result, err := rwl.client.Eval(ctx,script,[]string{rwl.key},rwl.owner,rwl.expiration.Milliseconds(),).Result()if err != nil {return fmt.Errorf("failed to acquire write lock: %v", err)}if result.(int64) == 0 {return fmt.Errorf("lock exists")}return nil
}// Unlock 释放写锁
func (rwl *RWLock) Unlock(ctx context.Context) error {script := `-- 检查写锁是否存在且属于当前客户端local value = redis.call('get', KEYS[1] .. ':write')if not value thenreturn 0endif value ~= ARGV[1] thenreturn -1end-- 删除写锁redis.call('del', KEYS[1] .. ':write')return 1`result, err := rwl.client.Eval(ctx,script,[]string{rwl.key},rwl.owner,).Result()if err != nil {return fmt.Errorf("failed to release write lock: %v", err)}switch result.(int64) {case -1:return fmt.Errorf("write lock not held by this client")case 0:return fmt.Errorf("write lock not exists")default:return nil}
}

七、容错处理

1. 容错机制设计

在这里插入图片描述

2. 故障处理实现

package distlockimport ("context""errors""sync""time""github.com/go-redis/redis/v8"
)type FaultTolerantLock struct {master     *redis.Clientslaves     []*redis.ClientlocalLock  sync.Mutexkey        stringowner      stringexpiration time.Duration
}func NewFaultTolerantLock(master *redis.Client,slaves []*redis.Client,key string,owner string,expiration time.Duration,
) *FaultTolerantLock {return &FaultTolerantLock{master:     master,slaves:     slaves,key:        key,owner:      owner,expiration: expiration,}
}// Lock 获取容错锁
func (ftl *FaultTolerantLock) Lock(ctx context.Context) error {// 1. 尝试在主节点获取锁if err := ftl.tryLockOnMaster(ctx); err == nil {return nil}// 2. 主节点失败,尝试在从节点获取锁if err := ftl.tryLockOnSlaves(ctx); err == nil {return nil}// 3. 所有Redis节点都失败,降级使用本地锁ftl.localLock.Lock()// 4. 启动后台协程尝试恢复到Redis锁go ftl.tryRecoverToRedis(context.Background())return nil
}func (ftl *FaultTolerantLock) tryLockOnMaster(ctx context.Context) error {script := `if redis.call('exists', KEYS[1]) == 0 thenredis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])return 1endreturn 0`result, err := ftl.master.Eval(ctx,script,[]string{ftl.key},ftl.owner,ftl.expiration.Milliseconds(),).Result()if err != nil {return err}if result.(int64) == 0 {return errors.New("lock exists")}return nil
}func (ftl *FaultTolerantLock) tryLockOnSlaves(ctx context.Context) error {// 需要在多数从节点上获取锁才算成功successCount := 0majorityCount := (len(ftl.slaves) / 2) + 1for _, slave := range ftl.slaves {if err := ftl.tryLockOnNode(ctx, slave); err == nil {successCount++if successCount >= majorityCount {return nil}}}return errors.New("failed to acquire lock on majority of slaves")
}func (ftl *FaultTolerantLock) tryLockOnNode(ctx context.Context, node *redis.Client) error {script := `if redis.call('exists', KEYS[1]) == 0 thenredis.call('set', KEYS[1], ARGV[1], 'PX', ARGV[2])return 1endreturn 0`result, err := node.Eval(ctx,script,[]string{ftl.key},ftl.owner,ftl.expiration.Milliseconds(),).Result()if err != nil {return err}if result.(int64) == 0 {return errors.New("lock exists")}return nil
}func (ftl *FaultTolerantLock) tryRecoverToRedis(ctx context.Context) {ticker := time.NewTicker(time.Second)defer ticker.Stop()for {select {case <-ctx.Done():returncase <-ticker.C:// 尝试恢复到Redis主节点if err := ftl.tryLockOnMaster(ctx); err == nil {ftl.localLock.Unlock()return}// 尝试恢复到Redis从节点if err := ftl.tryLockOnSlaves(ctx); err == nil {ftl.localLock.Unlock()return}}}
}// Unlock 释放锁
func (ftl *FaultTolerantLock) Unlock(ctx context.Context) error {// 尝试释放Redis锁if err := ftl.unlockRedis(ctx); err == nil {return nil}// Redis释放失败,释放本地锁ftl.localLock.Unlock()return nil
}func (ftl *FaultTolerantLock) unlockRedis(ctx context.Context) error {script := `if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])endreturn 0`// 先尝试在主节点释放result, err := ftl.master.Eval(ctx,script,[]string{ftl.key},ftl.owner,).Result()if err == nil && result.(int64) == 1 {return nil}// 主节点释放失败,尝试在从节点释放for _, slave := range ftl.slaves {result, err = slave.Eval(ctx,script,[]string{ftl.key},ftl.owner,).Result()if err == nil && result.(int64) == 1 {return nil}}return errors.New("failed to release lock on all nodes")
}

八、性能测试与监控

1. 性能指标

指标说明目标值
获取锁延迟从发起请求到获取锁的时间<50ms
释放锁延迟从发起释放到完成的时间<30ms
锁冲突率获取锁失败的比例<10%
QPS每秒处理的锁请求数>1000

2. 监控指标

  1. 系统监控

    • CPU使用率
    • 内存使用
    • 网络延迟
    • 磁盘IO
  2. 业务监控

    • 锁获取成功率
    • 锁超时次数
    • 死锁检测次数
    • 降级次数

九、最佳实践总结

  1. 锁设计

    • 使用唯一标识确保锁的归属
    • 合理设置超时时间
    • 实现可重入机制
    • 使用Lua脚本保证原子性
  2. 死锁预防

    • 实现超时自动释放
    • 避免循环等待
    • 实现锁的重入
    • 定期检测死锁
  3. 性能优化

    • 使用读写锁分离
    • 控制锁粒度
    • 批量处理
    • 使用本地缓存
  4. 容错处理

    • 实现主从切换
    • 支持优雅降级
    • 异步恢复机制
    • 多副本数据同步

怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

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

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

相关文章

今日分享开源酷炫大数据可视化大屏html模板

前言 虽然目前已有很多开源在线制作可视化大屏项目 但有时候为了项目赶工期上线&#xff0c;直接利用现成的可视化大屏html模板&#xff0c;配合开源低代码平台Microi吾码的接口引擎&#xff0c;半小时以内就能做一个成品 先上图 代码也非常简单&#xff0c;利用Microi吾码接口…

白鲸开源即将在Doris Summit Asia 2024展示新议题!

一年一度的 Apache Doris 峰会再次启航&#xff0c;Doris Summit Asia 2024 现已开启报名&#xff0c;将于 2024 年 12 月 14 日在深圳正式举办。此次峰会&#xff0c;将对实时极速、存算分离、湖仓一体、半结构化数据分析、向量索引、异步物化视图等诸多特性进行全方位解读&am…

vscode插件 live-server配置https

背景&#xff1a;前端有时候需要在本地搭建https环境测试某些内容&#xff08;如https下访问http资源&#xff0c;下载&#xff09; 步骤&#xff1a; 1.vscode集成开发软件(应该所有前端开发同学都安装了&#xff0c;我用webstorm&#xff0c;vscode备用) 2.vscode安装live…

Mac环境下brew安装LNMP

安装不同版本PHP 在Mac环境下同时运行多个版本的PHP&#xff0c;同Linux环境一样&#xff0c;都是将后台运行的php-fpm设置为不同的端口号&#xff0c;下面将已php7.2 和 php7.4为例 添加 tap 目的&#xff1a;homebrew仅保留最近的php版本&#xff0c;可能没有你需要的版本…

代发考试战报:12月近几日通过,题库已经更新至12月5号

代发考试战报&#xff1a;12月近几日通过&#xff0c;题库已经更新至12月5号&#xff0c;考试大约会遇到几个新题&#xff0c;就算遇到的新题全错&#xff0c;也不影响考试通过&#xff0c;HCIA-PM 12月2号上海通过&#xff0c;售前L3 H19-435 HCSP-Storage 存储 上海通过&…

沪合共融 “汽”势如虹 | 昂辉科技参加合肥上海新能源汽车产业融合对接会

为积极响应制造业重点产业链高质量发展行动号召&#xff0c;促进合肥、上海两地新能源汽车产业链上下游企业融合对接、协同发展&#xff0c;共同打造长三角世界级新能源汽车产业集群&#xff0c;11月28日&#xff0c;合肥市工信局组织部分县区工信部门及全市30余户新能源汽车产…

vite5+vue3+Ts5 开源图片预览器上线

images-viewer-vue3&#xff1a;一款Vue3的轻量级图像查看器&#xff0c;它基于Flip动画技术&#xff0c;支持PC和h5移动网页预览照片&#xff0c;如果它是Vue3开发的产品。 npm开源地址:https://www.npmjs.com/package/images-viewer-vue3?activeTabreadme Flip 动画 < …

人工智能驱动的骗局会模仿熟悉的声音

由于人工智能技术的进步&#xff0c;各种现代骗局变得越来越复杂。 这些骗局现在包括人工智能驱动的网络钓鱼技术&#xff0c;即使用人工智能模仿家人或朋友的声音和视频。 诈骗者使用来自社交媒体的内容来制作深度伪造内容&#xff0c;要求提供金钱或个人信息。个人应该通过…

qt QGraphicsScale详解

1、概述 QGraphicsScale是Qt框架中提供的一个类&#xff0c;它提供了一种简单而灵活的方式在QGraphicsView框架中实现缩放变换。通过设置水平和垂直缩放因子、缩放中心点&#xff0c;可以创建各种缩放效果&#xff0c;提升用户界面的交互性和视觉吸引力。结合QPropertyAnimati…

车载VR可视化解决方案

车载VR可视化解决方案是通过融合跟踪用户头部运动的特殊预测算法与惯性测量数据而开发的。该系统将大范围虚拟现实跟踪技术与IMU传感器相结合&#xff0c;为VR和AR应用打造了一套全面的运动跟踪与渲染流程&#xff0c;极大地方便了虚拟现实头显制造商定制可视化流程。 该车载VR…

Cesium 给大量建筑贴上PBR纹理

Cesium 给大量建筑贴上PBR纹理 —— 使用 TilesBuilder 从 SHP 文件转换 在Cesium中使用PBR&#xff08;物理基础渲染&#xff09;纹理给大量建筑物贴图时&#xff0c;TilesBuilder 是一个常用的图形化工具&#xff0c;它可以将原始数据转换成Cesium支持的 3D Tiles 格式。如果…

MySQL 性能优化详解

MySQL 性能优化详解 硬件升级系统配置优化调整buffer_pool数据预热降低日志的磁盘落盘 表结构设计优化SQL语句及索引优化SQL优化实战案例 MySQL性能优化我们可以从以下四个维度考虑&#xff1a;硬件升级、系统配置、表结构设计、SQL语句和索引。 从成本上来说&#xff1a;硬件升…

CSS 快速上手

目录 一. CSS概念 二. CSS语法 1. 基本语法规范 2. CSS的三种引入方式 (1) 行内样式 (2) 内部样式表 (3) 外部样式表 3. CSS选择器 (1) 标签选择器 (2) 类选择器 (3) id选择器 (4) 通配符选择器 (5) 复合选择器 <1> 空格 <2> 没有空格 <3> &q…

00. Nginx-知识网络

知识目录 语雀知识网络&#xff0c;点击“”-- 点击“”查看知识网络 01. Nginx-基础知识 02. Nginx-虚拟主机 03. Nginx-Web模块 04. Nginx-访问控制 05. Nginx-代理服务 06. Nginx-负载均衡 07. Nginx-动静分离 08. Nginx-平滑升级 09. Nginx-日志切割 10. Nginx-…

MATLAB数学建模之画图汇总

MATLAB是一种强大的数学软件&#xff0c;广泛应用于工程计算、控制设计、信号处理等领域。在数学建模中&#xff0c;MATLAB的绘图功能可以帮助我们直观地展示数据和模型结果。 1. 二维数据曲线图 1.1 绘制二维曲线的基本函数 plot函数用于绘制二维平面上的线性坐标曲线图&am…

HarmonyOS 5.0应用开发——UIAbility生命周期

【高心星出品】 文章目录 UIAbility组件创建AbilityUIAbility的生命周期Create状态WindowStageCreate状态Foreground和Background状态WindowStageWillDestroy状态Destroy状态 UIAbility组件 UIAbility组件是一种包含UI的应用组件&#xff0c;主要用于和用户交互。 UIAbility组…

轨道力学:兰伯特问题

轨道力学&#xff1a;兰伯特问题 引言 在轨道力学中&#xff0c;兰伯特问题是指在已知两个位置矢量和它们之间的飞行时间的情况下&#xff0c;求解连接这两个位置的轨道路径问题。该问题以18世纪的数学家约翰海因里希兰伯特&#xff08;Johann Heinrich Lambert&#xff09;命…

word实践:正文/标题/表图等的共用模板样式设置

说在前面 最近使用word新建文件很多&#xff0c;发现要给大毛病&#xff0c;每次新建一个word文件&#xff0c;标题/正文的字体、大小和间距都要重新设置一遍&#xff0c;而且每次设置这些样式都忘记了参数&#xff0c;今天记录一下&#xff0c;以便后续方便查看使用。现在就以…

MicroBlaze软核开发(一):Hello World

实现功能&#xff1a;使用 MicroBlaze软核 串口打印 Hello World Vivado版本&#xff1a;2018.3 目录 MicroBlaze介绍 vivado部分&#xff1a; 一、新建工程 二、配置MicroBlaze 三、添加Uart串口IP 四、生成HDL文件编译 SDK部分&#xff1a; 一、导出硬件启动SDK 二、…

Vue工程化开发中各文件的作用

1.main.js文件 main.js文件的主要作用&#xff1a;导入App.vue&#xff0c;基于App.vue创建结构渲染index.html。