Go语言使用Etcd实现分布式锁

 更新时间:2023年05月21日 11:49:45   作者:small_to_large  
etcd是近几年比较火热的一个开源的、分布式的键值对数据存储系统,本文将介绍如何利用Etcd实现分布式锁,感兴趣的小伙伴可以跟随小编一起了解一下

1 分布式锁概述

谈到分布式锁,必然是因为单机锁无法满足要求,在现阶段微服务多实例部署的情况下,单机语言级别的锁,无法满足并发互斥资源的安全访问。常见的单机锁如Java的jvm锁Locksynchronized,golang的Mutex等 对于分布式锁有很多种实现方式,常见的有以下几种:

  • 基于数据库:通过数据库事务锁例如for update操作
  • 基于缓存中间件:redis分布式锁、etcd分布式锁等
  • 基于ZK临时节点:zookeeper 临时节点实现分布式锁

每种方式实现的分布式锁各有优缺点简单介绍一下:

  • 数据库实现不用额外引入新的中间件,减少系统的依赖性和不稳定性,但性能不会太高,且并发量大时,对数据库压力比较大。
  • ZK实现分布式,因为zk满足了CP,能够保证其数据一致性,不会出现加锁成功后又丢失的问题,但相反性能会降低,并且可用性降低 CAP A不是满足的,详细可以自行了解zk细节
  • redis 实现:最大的优点性能高,能保证AP,保证其高可用。但无法保证一致性,因为redis满足的是AP,可能存在某一个时间节点集群数据S-M同步不一致。

2 分布式锁要点

实现分布式锁需要满足一下几点:

  • 锁载体:redis 受用 K-V 键值作为锁载体,ZK使用临时节点作为载体
  • 锁租期:进程持有分布式锁后不能一直占用,如果因为宕机情况造成锁释放失败,就会一直占用,reds 可以设置过期时间,zk临时节点也会自动删除。
  • 其他要求:比如减少惊群效应、可重入机制、公平锁机制,不同的实现方式有的不能完全满足。

分布式锁选择:

  • qps不大的情况下,那种方式都可以
  • 结合目前技术体系,在不引入新的技术中间件情况下解决问题
  • qps并发极高,但容忍极少的数据丢失或者不一致,建议使用redis实现分布式锁
  • 如果业务要求任何情况下都不允许数据丢失,可以使用zk或者etcd实现

3 Etcd 实现机制

  • 锁载体: 使用 k-v 结构实现
  • 锁租期: Etcd 通过lease可以对 kv 设置租约,当租约到期,kv 将失效删除;避免长时间占用锁不释放放。
  • 自动续期: Etcd 可以对租约进行自动续期,通过KeepAlive实现
  • 公平锁: 多个程序同时抢锁时,会根据 Revision 值大小依次获得锁,可以有效避免 “惊群效应”,公平获取。
  • Watch 机制: 监听机制,Watch 机制支持 Watch 某个固定的 key或者目录, key 或目录发生变化,客户端可以收到通知。

4 代码实现

操作步骤:

  • 初始化客户端
  • 创建一个session并设置默认租期30s
  • 获取指定前缀的锁对象
  • 加锁
  • 执行业务
  • 释放锁

代码:

package main
import (
	"context"
	clientv3 "go.etcd.io/etcd/client/v3"
	"go.etcd.io/etcd/client/v3/concurrency"
	"log"
	"time"
)
func main() {
	// 初始化客户端
	log.Println("客户端初始化")
	client, err := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"}, DialTimeout: time.Second * 3})
	if err != nil {
		log.Fatalf("客户端初始化失败:%v\n", err)
	}
	// 创建一个session并设置默认租期30s,即锁默认超过30s会自动释放(内部会自动续期Etcd KeepAlive)
	log.Println("Session初始化")
	session, err := concurrency.NewSession(client, concurrency.WithTTL(30))
	if err != nil {
		log.Fatalf("Session初始化失败:%v\n", err)
		return
	}
	defer func(session *concurrency.Session) {
		err := session.Close()
		if err != nil {
			log.Fatalf("Session关闭失败:%v\n", err)
		}
	}(session)
	// 获取指定前缀的锁对象
	mutex := concurrency.NewMutex(session, "my-lock")
	// 加锁默认等待3s
	log.Println("TryLock加锁失败不会等待")
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()
	err = mutex.TryLock(ctx)
	if err != nil {
		log.Fatalf("加锁失败立即返回:%v\n", err)
		return
	}
	//log.Println("加锁最多等待3s")
	//ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
	//defer cancel()
	//err = mutex.Lock(ctx)
	//if err != nil {
	//	log.Fatalf("加锁失败:%v\n", err)
	//	return
	//}
	// Exe biz
	log.Println("加锁成功开始执行业务")
	for i := 1; i <= 10; i++ {
		time.Sleep(time.Second)
		log.Printf("执行 %%%d ...", i*10)
	}
	// 释放锁
	err = mutex.Unlock(context.TODO())
	if err != nil {
		log.Fatalf("释放锁失败:%v\n", err)
		return
	}
	log.Println("释放锁完成")
}

测试结果 

到此这篇关于Go语言使用Etcd实现分布式锁的文章就介绍到这了,更多相关Go Etcd分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • gin通过go build -tags实现json包切换及库分析

    gin通过go build -tags实现json包切换及库分析

    这篇文章主要为大家介绍了gin通过go build -tags实现json包切换及库分析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-09-09
  • Go+Vue开发一个线上外卖应用的流程(用户名密码和图形验证码)

    Go+Vue开发一个线上外卖应用的流程(用户名密码和图形验证码)

    这篇文章主要介绍了Go+Vue开发一个线上外卖应用(用户名密码和图形验证码),本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • Go 语言垃圾回收机制从入门到理解

    Go 语言垃圾回收机制从入门到理解

    本文主要介绍了Go 语言垃圾回收机制从入门到理解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2025-09-09
  • Go语言copy()实现切片复制

    Go语言copy()实现切片复制

    本文主要介绍了Go语言copy()实现切片复制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-04-04
  • golang中字符串和数字转换方法

    golang中字符串和数字转换方法

    在Golang中,可以使用strconv包中的Itoa()和Atoi()函数进行字符串与数字之间的转换,Itoa()用于将数字转换为字符串,Atoi()则用于将字符串转换回数字,本文介绍golang中字符串和数字转换方法,感兴趣的朋友一起看看吧
    2024-09-09
  • 一文带你分析Go语言中的http请求过程

    一文带你分析Go语言中的http请求过程

    在go中开发后端,最基础的就是使用net/http包,本文将使用一个hello,world程序来进行debug,来探究在Go语言中http请求的过程,希望对大家有所帮助
    2026-02-02
  • 深入了解Golang为什么需要超时控制

    深入了解Golang为什么需要超时控制

    本文将介绍为什么需要超时控制,然后详细介绍Go语言中实现超时控制的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起了解一下
    2023-05-05
  • 源码剖析Golang如何fork一个进程

    源码剖析Golang如何fork一个进程

    创建一个新进程分为两个步骤,一个是fork系统调用,一个是execve 系统调用,本文将从源码的角度带大家剖析一下Golang是如何fork一个进程的
    2023-06-06
  • 深入浅出Go语言:手把手教你高效生成与解析JSON数据

    深入浅出Go语言:手把手教你高效生成与解析JSON数据

    本文将带你一步步走进Go语言的世界,教你如何高效生成与解析JSON数据,无论你是初学者还是经验丰富的开发者,都能在本文中找到实用的技巧和灵感,本文内容简洁明了,示例丰富,让你在阅读的过程中轻松掌握Go语言生成与解析JSON数据的技巧,需要的朋友可以参考下
    2024-02-02
  • 一文带你掌握Golang中的值类型和引用类型

    一文带你掌握Golang中的值类型和引用类型

    在 Golang 中,数据类型可以分为两大类:值类型(Value Types)和引用类型(Reference Types),理解这两种类型的区别对于理解 Golang 中的数据传递和内存管理是很重要的,下面就跟随小编一起深入了解一下它们吧
    2024-01-01

最新评论