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 存储 上海通过&…

autogen 源码 (UserProxyAgent 类)

目录 1. 原始代码2. 代码测试3. 代码的运行逻辑4. UserProxyAgent 类的核心功能5. UserProxyAgent 类的使用6. 运行时流程7. 总结 1. 原始代码 import asyncio from inspect import iscoroutinefunction from typing import Awaitable, Callable, List, Optional, Sequence, U…

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

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

taro小程序马甲包插件

插件名 maloulab/taro-plugins-socksuppet-ci maloulab/taro-plugins-socksuppet-ci安装 yarn add maloulab/taro-plugins-socksuppet-ci or npm i maloulab/taro-plugins-socksuppet-ci插件描述 taro官方是提供了小程序集成插件的tarojs/plugin-mini-ci &#xff0c;且支持…

SpringBoot 基于 MVC 高校办公室行政事务管理系统:设计构思与实现范例展示

摘 要 身处网络时代&#xff0c;随着网络系统体系发展的不断成熟和完善&#xff0c;人们的生活也随之发生了很大的变化&#xff0c;人们在追求较高物质生活的同时&#xff0c;也在想着如何使自身的精神内涵得到提升&#xff0c;而读书就是人们获得精神享受非常重要的途径。为了…

vue 通过 image-conversion 实现图片压缩

简介 vue项目中&#xff0c;上传图片时如果图片很大&#xff0c;通过 image-conversion 压缩到指定大小 1. 安装依赖 npm i image-conversion --save2. 引用 import * as imageConversion from image-conversion3. 使用 const newFile new Promise((resolve) > {// 压…

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…

【基础编程】面向对象编程(OOP)详细解析

面向对象编程&#xff08;OOP&#xff09;详细解析 一、核心概念解析 1. 类&#xff08;Class&#xff09; 类是构建对象的蓝图&#xff0c;定义了对象的属性和方法。通过类的实例化&#xff0c;生成实际的对象。 关键特点&#xff1a; 属性&#xff08;字段&#xff09;&…

车载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…

EasyRTC支持嵌入式智能硬件与微信小程序实时通话

基础建设如此发达的时代&#xff0c;各种物联网设备都越来越普及&#xff0c;尤其是可穿戴设备和智能家居设备的发展&#xff0c;而在物联网设备中&#xff0c;视频物联网设备又是特别受人关注的设备&#xff0c;因为他们具备有看得见的属性&#xff0c;像智能家居里面的摄像头…

00. Nginx-知识网络

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