Golang使用etcd构建分布式锁案例
在本教程中,我们将学习如何使用Go和etcd构建分布式锁系统。分布式锁系统对于管理对分布式系统中共享资源的并发访问至关重要。它有助于维护一致性,防止竞争条件,并确保在任何给定时间只有一个进程独占访问资源。
我们将使用Go作为编程语言,并使用etcd作为分布式键值存储。Go的并发特性和对分布式系统的出色支持使其成为本教程的理想选择。Etcd是一种高度可靠的分布式键值存储,被许多大型系统(如Kubernetes)用于配置管理和服务发现。
在本教程结束时,你将能够构建一个分布式锁系统,Go开发人员可以使用该系统来管理对其应用程序中共享资源的访问。
环境准备
要学习本教程,你应该具备:
- 具备Go编程语言基础知识
- 在你的系统上安装Etcd或访问Etcd服务器
- Go安装在你的系统上(版本1.13或更高版本)
新建Go项目
首先,让我们用必要的依赖项创建一个新的Go项目:
$ mkdir distributed-lock && cd distributed-lock
$ go mod init example.com/distributed-lock
$ go get go.etcd.io/etcd/clientv3
这将设置一个新的Go项目并下载etcd客户端库。
实现加锁和解锁功能
现在是时候实现Lock和Unlock函数了。我们将创建名为lock的新文件。首先导入必要的包并定义结构来保存锁信息:
package mainimport ("context""log""time""go.etcd.io/etcd/clientv3"
)type DistributedLock struct {Key stringValue stringLeaseID clientv3.LeaseIDetcdClient *clientv3.Client
}
接下来,我们将实现Lock函数。该函数将执行以下步骤:
- 创建具有指定TTL(生存时间)的新租约
- 将锁键值对存入附带租约的etcd中
- 如果锁已被占用,则处理错误并重试
func (dl *DistributedLock) Lock(ctx context.Context, ttl int64) error {lease, err := dl.etcdClient.Grant(ctx, ttl)if err != nil {return err}_, err = dl.etcdClient.Put(ctx, dl.Key, dl.Value, clientv3.WithLease(lease.ID))if err != nil {return err}dl.LeaseID = lease.IDlog.Printf("Lock acquired: %s", dl.Key)return nil
}
-
ctx context.Context
:这是 Go 语言中用于传递上下文信息的参数,常用于控制操作的超时、取消等情况,比如在分布式环境中协调多个操作的生命周期,确保它们能按照预期执行或者在合适的时候被取消。 -
ttl int64
:代表 “Time To Live”(存活时间),通常是以秒为单位的时间长度,用于指定分布式锁的有效时长,过了这个时长,锁可能会自动释放,以防止锁被长期占用导致死锁等问题。 -
这里使用
dl.etcdClient
再次调用Put
方法,它的作用是向etcd
中写入键值对(Key-Value
)。具体来说,写入的键是dl.Key
(从代码推测应该是用于标识这个分布式锁的唯一键,是DistributedLock
结构体中的一个字段),值是dl.Value
(同样是结构体中的字段,可能是和锁相关的一些其他附属信息等)。 -
关键的一点是通过
clientv3.WithLease(lease.ID)
这个选项将前面申请到的租约的ID
关联到这个要写入的键值对上,意思是这个键值对的存在时长会受租约的控制,当租约到期(达到ttl
设定的时间)时,etcd
会自动删除这个键值对,从而实现了分布式锁的自动释放机制。和前面获取租约类似,Put
操作也可能出错,所以同样进行错误判断,如果err
不为空,就返回错误给调用者。
现在,让我们实现Unlock函数。该函数将执行以下步骤:
- 删除etcd中的锁键值对
- 解除租赁
func (dl *DistributedLock) Unlock(ctx context.Context) error {_, err := dl.etcdClient.Delete(ctx, dl.Key)if err != nil {return err}_, err = dl.etcdClient.Revoke(ctx, dl.LeaseID)if err != nil {return err}log.Printf("Lock released: %s", dl.Key)return nil
}
测试分布式锁
最后,让我们创建一个简单的测试应用程序来查看分布式锁的实际情况。基本上。Go文件,添加以下代码:
package mainimport ("context""fmt""os""time""go.etcd.io/etcd/clientv3"
)func main() {endpoints := []string{"localhost:2379"}cfg := clientv3.Config{Endpoints: endpoints,DialTimeout: 5 * time.Second,}client, err := clientv3.New(cfg)if err != nil {fmt.Printf("Error connecting to etcd: %v", err)os.Exit(1)}defer client.Close()ctx := context.Background()lockKey := "my-lock"lockValue := "my-value"dl := DistributedLock{Key: lockKey,Value: lockValue,etcdClient: client,}err = dl.Lock(ctx, 10)if err != nil {fmt.Printf("Error acquiring lock: %v", err)os.Exit(1)}// Simulate a critical sectiontime.Sleep(5 * time.Second)err = dl.Unlock(ctx)if err != nil {fmt.Printf("Error releasing lock: %v", err)os.Exit(1)}
}
使用以下命令运行测试应用程序:
$ go run main.go
如果一切正常,您应该看到锁在5秒睡眠后被获取和释放。
重构实现失败重试
以下是在原代码基础上添加申请锁失败重试机制的示例代码,在 Go 语言中可以使用循环结合退避策略等方式来实现,以下是一种常见的实现思路及代码示例,假设使用了time
包来进行时间控制以及添加了适当的错误处理和重试次数限制等逻辑:
package mainimport ("context""fmt""go.etcd.io/etcd/clientv3""log""time"
)type DistributedLock struct {etcdClient *clientv3.ClientKey stringValue stringLeaseID clientv3.LeaseID
}func (dl *DistributedLock) Lock(ctx context.Context, ttl int64) error {maxRetries := 3 // 最大重试次数,可根据实际情况调整retryDelay := 1 * time.Second // 初始重试间隔时间,可根据实际情况调整for retry := 0; retry < maxRetries; retry++ {lease, err := dl.etcdClient.Grant(ctx, ttl)if err!= nil {if retry == maxRetries-1 {return fmt.Errorf("failed to grant lease after %d retries: %v", maxRetries, err)}// 等待一段时间后重试,这里可以采用退避策略,比如指数退避等,此处简单使用固定间隔time.Sleep(retryDelay)continue}_, err = dl.etcdClient.Put(ctx, dl.Key, dl.Value, clientv3.WithLease(lease.ID))if err!= nil {if retry == maxRetries-1 {return fmt.Errorf("failed to put key-value with lease after %d retries: %v", maxRetries, err)}// 释放本次申请到的租约,避免资源浪费,虽然租约到期也会自动释放,但及时释放更好_, _ = dl.etcdClient.Revoke(ctx, lease.ID)time.Sleep(retryDelay)continue}dl.LeaseID = lease.IDlog.Printf("Lock acquired: %s", dl.Key)return nil}return fmt.Errorf("exceeded max retries for lock acquisition")
}
出来重试部分,其他逻辑不变,这里解释第二部分重试逻辑:
- 当租约申请成功后,尝试将键值对写入
etcd
并关联租约,若这个操作出现错误(err
不为nil
),同样有如下处理: - 先判断是否达到最大重试次数,如果是,返回带有详细失败信息的
error
告知调用者设置键值关联租约操作经过多次重试后失败以及具体错误原因。 - 如果没达到最大重试次数,为了避免已经申请到的租约一直占用资源(虽然租约到期会自动释放,但及时主动释放更合理),调用
dl.etcdClient.Revoke
方法来撤销(释放)刚刚申请到的租约,然后让当前协程暂停执行retryDelay
设定的时间间隔后,通过continue
进入下一次循环,再次尝试整个申请锁的流程。
总结
在本教程中,我们使用Go等语言构建了简单分布式锁系统。该系统可用于远程Go开发人员管理对其应用程序中共享资源的访问,确保分布式系统中的一致性和防止竞争条件。
相关文章:

Golang使用etcd构建分布式锁案例
在本教程中,我们将学习如何使用Go和etcd构建分布式锁系统。分布式锁系统对于管理对分布式系统中共享资源的并发访问至关重要。它有助于维护一致性,防止竞争条件,并确保在任何给定时间只有一个进程独占访问资源。 我们将使用Go作为编程语言&am…...

Windows 和 Ubuntu 双系统安装
复现论文的时候,个别包只有Linux版本,并且源码编译比较麻烦,所以干脆直接安装一个双系统(WinUbuntu),方便复现论文。 参考视频链接:Windows 和 Ubuntu 双系统的安装和卸载 0.所需工具 4G以上U…...
多媒体文件解复用(Demuxing)过程
多媒体文件的解复用(Demuxing)过程指的是从一个多媒体容器文件(如 MP4、MKV、AVI 等)中提取不同类型的多媒体数据流(例如视频流、音频流、字幕流等)的过程。 容器文件本身并不包含实际的视频或音频数据&…...

从 Zuul 迁移到 Spring Cloud Gateway:一步步实现服务网关的升级
从 Zuul 迁移到 Spring Cloud Gateway:一步步实现服务网关的升级 迁移前的准备工作迁移步骤详解第一步:查看源码第二步:启动类迁移第三步:引入 Gateway 依赖第四步 编写bootstrap.yaml第五步:替换路由配置第六步&#…...
qt之插件编译
QtXlsxWriter sudo apt install qtbase5-private-dev git clone https://github.com/dbzhang800/QtXlsxWriter.git cd QtXlsxWriter/ qmake make -j6 sudo make install #将生成的lib 及 include copy至项目路径的lib 及include里项目配置: QT xlsxbluetoo…...

pandas一行拆成多行
import pandas as pd df pd.DataFrame({Country:[China,US,Japan,EU,UK/Australia, UK/Netherland],Number:[100, 150, 120, 90, 30, 2],Value: [1, 2, 3, 4, 5, 6],label: list(abcdef)})# 法一 推荐 df2df.drop(Country, axis1).join(df[Country].str.split(/, expandTrue).…...
今天调了个转速的小BUG
同事说转速表有个bug,转速停止后,继电器没有恢复到初始状态。若停止之前是报警,继电器吸合,则停止后继电器还是吸合。我心想不会啊,这软件都弄了好几年了,一直也没出现过状况。 经过与调试同事的沟通&#…...

第三节、电机定速转动【51单片机-TB6600驱动器-步进电机教程】
摘要:本节介绍用定时器定时的方式,精准控制脉冲时间,从而控制步进电机速度 一、计算过程 1.1 电机每一步的角速度等于走这一步所花费的时间,走一步角度等于步距角,走一步的时间等于一个脉冲的时间 w s t e p t … ……...

从一个Bug谈前端响应拦截器的应用
一、问题场景 今天在开发商品管理系统时,遇到了一个有趣的问题:当添加重复的商品编号时,页面同时弹出了两条 "商品编号已存在" 错误提示: 这个问题暴露了前端错误处理机制的混乱,让我们从这个问题出发&…...

JS进阶DAY4|节点操作
嘿👋 今天我们要一起深入探索JavaScript中的DOM操作,这是前端开发中不可或缺的技能。🌟 准备好了吗?让我们一起跳进DOM的海洋,看看怎么用代码操控网页的结构吧! 目录 1. 增加节点 1.1 使用 appendChild 方…...

【Web】2023安洵杯第六届网络安全挑战赛 WP
目录 Whats my name easy_unserialize signal Swagger docs 赛题链接:GitHub - D0g3-Lab/i-SOON_CTF_2023: 2023 第六届安洵杯 题目环境/源码 Whats my name 第一段正则用于匹配以 include 结尾的字符串,并且在 include 之前,可以有任…...
go 语言中协程和GMP模型
为什么需要协程? 协程用来更加精细地利用线程,支撑超高的并发的。协程,从 runtime 的角度看,协程就是一个被调度的 g 结构体。 G 就是协程,M 是线程,P 是为了优化多线程并发时,会抢夺协程队列的…...
coco数据集转换SAM2格式
coco是一个大json汇总了所有train的标签 SAM2训练一张图对应一个json标签 import json import os from pycocotools import mask as mask_utils import numpy as np import cv2def poly2mask(points, width, height):points_array np.array(points, dtypenp.int32).reshape(-…...
【CMD、PowerShell和Bash设置代理】
【CMD、PowerShell和Bash设置代理】 1. CMD(命令提示符)临时设置代理(只对当前会话有效):查看当前代理设置:清除临时代理设置:永久设置代理(对所有新的 CMD 会话有效)&am…...
22智能 代码作业集合
3-2 #include <stdio.h>int main() {int a 21;int b 10;int c ;c a b;printf("Line 1 - c 的值是 %d\n", c );c a - b;printf("Line 2 - c 的值是 %d\n", c );c a * b;printf("Line 3 - c 的值是 %d\n", c );c a / b;printf("…...

实现一个简单的后台架子(侧边栏菜单渲染,折叠,黑白主题,组件主题色,全屏,路由快捷栏)
目录 侧边栏菜单渲染 侧边栏折叠 黑白主题 全屏切换 切换组件主题色 tab快捷栏 代码 侧边栏菜单渲染 结合ElementPlus组件库进行实现 新建的Vue3项目,引入了格式化样式normalize.css和ElementPlus,并进行了全局引入 并进行了全局引入 设置高度为100% 粘贴ElementPlus的…...

vue3-canvas实现在图片上框选标记(放大,缩小,移动,删除)
双图版本(模板对比) 业务描述:模板与图片对比,只操作模板框选的位置进行色差对比,传框选坐标位置给后端,返回对比结果显示 draw.js文件: 新增了 createUuid,和求取两个数组差集的方…...

unity3d—demo(2d人物左右移动发射子弹)
目录 人物代码示例: 子弹代码示例: 总结上面代码: 注意点: 人物代码示例: using System.Collections; using System.Collections.Generic; using UnityEngine;public class PlayerTiao : MonoBehaviour {public f…...
【ETCD】【源码阅读】 深入解析 raftNode.start`函数:Raft 核心启动逻辑剖析
raftNode.start方法 是 etcd 中 Raft 模块的核心启动点,其职责是管理 Raft 状态机的状态变迁、日志处理及集群通信等逻辑。通过对源码的逐行分析,我们将全面揭示其运行机制,探讨其设计背后的分布式系统理念。 函数核心结构 raftNode.start 方…...

Robust Depth Enhancement via Polarization Prompt Fusion Tuning
paper:论文地址 code:github项目地址 今天给大家分享一篇2024CVPR上的文章,文章是用偏振做提示学习,做深度估计的。模型架构图如下 这篇博客不是讲这篇论文的内容,感兴趣的自己去看paper,主要是分享环境&…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
OpenLayers 可视化之热力图
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 热力图(Heatmap)又叫热点图,是一种通过特殊高亮显示事物密度分布、变化趋势的数据可视化技术。采用颜色的深浅来显示…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...

Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
鸿蒙(HarmonyOS5)实现跳一跳小游戏
下面我将介绍如何使用鸿蒙的ArkUI框架,实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...

数据结构第5章:树和二叉树完全指南(自整理详细图文笔记)
名人说:莫道桑榆晚,为霞尚满天。——刘禹锡(刘梦得,诗豪) 原创笔记:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 上一篇:《数据结构第4章 数组和广义表》…...