返回顶部
首页 > 资讯 > 精选 >go语言分布式id生成器及分布式锁源码分析
  • 704
分享到

go语言分布式id生成器及分布式锁源码分析

2023-07-05 22:07:03 704人浏览 安东尼
摘要

本文小编为大家详细介绍“Go语言分布式id生成器及分布式锁源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“go语言分布式id生成器及分布式锁源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。分布式 i

本文小编为大家详细介绍“Go语言分布式id生成器及分布式源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“go语言分布式id生成器及分布式锁源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

分布式 id 生成器

在分布式场景中,唯一 id 的生成算比较重要。

而通常在高并发场景中,需要类似 MySQL 自增 id 一样不断增长且又不会重复的 id,即 Mysql 的主键 id。

比如,在电商 618 或者双 11 搞活动的时候,一般在 0 点 开始,会有千万到亿级的订单量写入,每秒大概需要处理 10 万加的订单。

而在将订单插入数据库之前,我们在业务上需要给订单一个唯一的 id,即利用 idMaker 生存唯一的订单号,再插入数据库内。如果生成的 id 是随机且没有含义的纯数字的话,在大订单量的情况下,对数据库进行增删改查时就不能起到提高效率的作用。所以 此 id 应该应该包含一些时间信息,机器信息等,这样即使后端的系统对消息进行了分库分表,也能够以时间顺序对这些消息进行排序了。

比较典型的就是推特的【雪花算法】了,在以上场景下可以算是最优解,原理如图:

go语言分布式id生成器及分布式锁源码分析

首先确定的是,id 数值长度是 64 位,int64 类型,除去开头的符号位 unused ,其它可以分为四个部分:

  • 41 位来表示收到请求时的时间戳,单位为毫秒

  • 5 位表示数据中心的 id

  • 5 位表求机器的实例 id

  • 12 位为循环自增 id,到达 1111,1111,1111 后归就会 0

以上机制原理生成的 id,可以支持一台机器在一毫秒内能够产生 4096 条消息。也就是一秒共 409.6w 条消息。单单从值域上来讲是完全够用。

数据中心 id 加上实例 id 共有 10 位,每个数据中心可以部署 32 台实例,搭建 32 个数据中心,所以可以一共部署 1024 台实例。

而 41 位的时间戳(毫秒为单位)能够使用 69 年。

worker_id 如何分配

timestamp(时间戳),datacenter_id(数据中心),worker_id(机器 ID) 和 sequence_id(序号) 这四个字段中,timestamp 和 sequence_id 是由程序在运行期生成的。但 datacenter_id 和 worker_id 需要在部署阶段就要能够获取得到,并且一旦程序启动之后,就是不可更改的了,因为如果可以随意更改,可能会造成最终生成的 id 有冲突。

不过一般不同数据中心的机器,会提供对应的获取数据中心 id 的 api,因此 datacenter_id 我们可以在部署阶段轻松地获取到。而 worker_id 是我们逻辑上给机器分配的一个 id,比较简单的做法就是由能够提供这种自增 id 功能的工具来支持,比如 mysql:

mysql> insert into a (ip) values("10.115.4.66");Query OK, 1 row affected (0.00 sec)mysql> select last_insert_id();+------------------+| last_insert_id() |+------------------+|                2 |+------------------+1 row in set (0.00 sec)

从 MySql 中获取到 worker_id 之后,就把这个 worker_id 直接持久化到本地,以避免每次上线时都需要获取新的 worker_id。让单实例的 worker_id 可以始终保持不变。

但是,使用 MySQL 的话,相当于给 id 生成服务增加了一个外部依赖。当然依赖越多,服务的运维成本就会增加。

考虑到集群中即使有单个 id 生成服务的实例挂了,也就是损失一段时间的一部分 id,所以我们也可以更简单暴力一些,把 worker_id 直接写在 worker 的配置中,上线时,由部署脚本完成 worker_id 字段替换即可。

开源示例:标准雪花算法

GitHub.com/bwmarrin/snowflake 是一个相对轻量级的 snowflake 的 Go 实现。其文档对各位使用的定义如下图所示:

go语言分布式id生成器及分布式锁源码分析

此库和标准的 snowflake 实现方式全完一致,使用也比较简单,直接上示例代码:

package mainimport (  "fmt" "github.com/bwmarrin/snowflake")func main() { node, err := snowflake.NewNode(1) if err != nil {  println(err.Error())  os.Exit(1) } for i := 0; i < 20; i++ {  id := node.Generate()  fmt.Printf("Int64  ID: %d\n", id)  fmt.Printf("String ID: %s\n", id)  fmt.Printf("ID Time  : %d\n", id.Time())  fmt.Printf("ID Node  : %d\n", id.Node())  fmt.Printf("ID Step  : %d\n", id.Step())  fmt.Println("--------- end ----------") }}

分布式锁

单机程序并发或并行修改全局共享变量时,需要对修改行为加锁。因为如果不加锁,多个协程序就会对该变量竞争,然后得到的结果就会不准确,或者说得到的结果不是我们所预期的,比如下面的例子:

package mainfunc main() { var wg sync.WaitGroup var count = 0 for i := 1; i < 1000; i++ {  wg.Add(1)  go func() {   defer wg.Done()   count++  }() } wg.Wait() fmt.Println(count)}

多次运行结果不同:

➜  go run main.go
884
➜  go run main.go
957
➜  go run main.go
923

预期的结果是:999

进程内加锁

而如果想要得到正确(预期)的结果,要把计数器的操作代码部分加上锁:

package mainimport ( "fmt" "sync")func main() { var wg sync.WaitGroup var lock sync.Mutex var count = 0 for i := 1; i < 1000; i++ {  wg.Add(1)  go func() {   defer wg.Done()   lock.Lock()  // 加锁   count++   lock.Unlock()  // 释放锁  }() } wg.Wait() fmt.Println(count)}

这样能够得到正确结果:

➜  go run main.go
999

尝试加锁 tryLock

在某些场景,我们往往只希望一个任务有单一的执行者,而不像计数器一样,所有的 Goroutine 都成功执行。后续的 Goroutine 在抢锁失败后,需要放弃执行,这时候就需要用到尝试加锁,即实现 trylock

尝试加锁,在加锁成功后执行后续流程,失败时不可以阻塞,而是直接返回加锁的结果。

在 Go 语言中可以用大小为 1 的 Channel 来模拟 trylock:

package mainimport (  "fmt"  "sync")type MyLock struct { lockCh chan struct{}}func NewLock() MyLock { var myLock MyLock myLock = MyLock{  lockCh:make(chan struct{}, 1), } myLock.lockCh <- struct{}{} return myLock}func (l *MyLock) Lock() bool { result := false select { case <-l.lockCh:  result = true default:  // 这里去掉就会阻塞,直到获取到锁 } return result}func (l *MyLock) Unlock() { l.lockCh <- struct{}{}}func main() { var wg sync.WaitGroup var count int l := NewLock() for i := 0; i < 10; i++ {  wg.Add(1)  go func() {   defer wg.Done()   if !l.Lock() {    fmt.Println("get lock failed")    return   }   count++   fmt.Println("count=", count)   l.Unlock()  }() } wg.Wait()}

每个 Goruntine 只有获取到锁(成功执行了 Lock)才会继续执行后续代码,然后在 Unlock()时可以保证 Lock 结构体里的 Channel 一定是空的,所以不会阻塞也不会失败。

在单机系统中,tryLock 并不是一个好选择,因为大量的 Goruntine 抢锁会无意义地占用 cpu 资源,这就是活锁,所有不建议使用这种锁。

基于 Redis 的 setnx 分布式锁

在分布式场景中,也需要“抢占”的逻辑,可以用 Redis 的 setnx 实现:

package mainimport ( "github.com/go-redis/redis" "sync" "time")func setnx() { client := redis.NewClient(&redis.Options{}) var lockKey = "counter_lock" var counterKey = "counter" // lock resp := client.SetNX(lockKey, 1, time.Second*6) lockStatus, err := resp.Result() if err != nil || !lockStatus {  println("lock failed")  return } // counter++ getResp := client.Get(counterKey) cntValue, err := getResp.Int64() if err == nil || err == redis.Nil {  cntValue++  resp := client.Set(counterKey, cntValue, 0)  _, err := resp.Result()  if err != nil {   println(err)  } } println("current counter is ", cntValue) // unlock delResp := client.Del(lockKey) unlockStatus, err := delResp.Result() if err == nil && unlockStatus > 0 {  println("unlock success") } else {  println("unlock failed", err) }}func main() { var wg sync.WaitGroup for i := 0; i < 10; i++ {  wg.Add(1)  go func() {   defer wg.Done()   setnx()  }() } wg.Wait()}

运行结果:

➜  go run main.go
lock failed
lock failed
lock failed
lock failed
lock failed
current counter is  34
lock failed
unlock success

通过上面的代码和执行结果可以看到,远程调用 setnx 运行流程上和单机的 troLock 非常相似,如果获取锁失败,那么相关的任务逻辑就不会继续向后执行。

setnx 很适合高并发场景下用来争抢一些“唯一”的资源。比如,商城秒杀的商品,在某个时间点,多个买家会对其进行下单并发争抢。这种场景我们没有办法依赖具体的时间来判断先后,因为不同设备的时间不能保证使用的是统一的时间,也就不能保证时序。

所以,我们需要依赖于这些请求到达 redis 节点的顺序来做正确的抢锁操作。

如果用户的网络环境比较差,是有可能抢不到的。

基于 ZooKeeper 分布式锁

基于 ZooKeeper 的锁与基于 Redis 的锁有点类似,不同之处在于 Lock 成功之前会一直阻塞,这与单机场景中的 mutex.Lock 很相似。

package mainimport ( "github.com/go-zookeeper/zk" "time")func main() { c, _, err := zk.Connect([]string{"127.0.0.1"}, time.Second) if err != nil {  panic(err) } l := zk.NewLock(c, "/lock", zk.WorldACL(zk.PermAll)) err = l.Lock() if err != nil {  panic(err) } println("lock success, do your business logic") time.Sleep(time.Second * 10) // 模拟业务处理 l.Unlock() println("unlock success, finish business logic")}

其原理也是基于临时 Sequence 节点和 watch API,例如我们这里使用的是 /lock 节点。

Lock 会在该节点下的节点列表中插入自己的值,只要节点下的子节点发生变化,就会通知所有 watch 该节点的程序。这时候程序会检查当前节点下最小的子节点的 id 是否与自己的一致。如果一致,说明加锁成功了。

这种分布式的阻塞锁比较适合分布式任务调度场景,但不适合高频次持锁时间短的抢锁场景。

一般基于强一致协议的锁适用于粗粒度的加锁操作。这里的粗粒度指锁占用时间较长。我们在使用时也应思考在自己的业务场景中使用是否合适。

读到这里,这篇“go语言分布式id生成器及分布式锁源码分析”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网精选频道。

--结束END--

本文标题: go语言分布式id生成器及分布式锁源码分析

本文链接: https://lsjlt.com/news/355736.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • go语言分布式id生成器及分布式锁源码分析
    本文小编为大家详细介绍“go语言分布式id生成器及分布式锁源码分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“go语言分布式id生成器及分布式锁源码分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。分布式 i...
    99+
    2023-07-05
  • go语言分布式id生成器及分布式锁介绍
    目录分布式 id 生成器worker_id 如何分配开源示例:标准雪花算法分布式锁进程内加锁尝试加锁 tryLock基于 Redis 的 setnx 分布式锁基于 ZooKeeper...
    99+
    2023-05-14
    go 分布式id生成器 锁 go 分布式锁
  • Go语言实现分布式锁
    目录1. go实现分布式锁1.1 redis_lock.go1.2 retry.go1.3 lock.lua1.4 lua_unlock.lua1.5 refresh.lua1.6 ...
    99+
    2023-01-14
    Go分布式锁 Go锁原理
  • Redisson分布式锁之加解锁源码分析
    这篇文章主要介绍“Redisson分布式锁之加解锁源码分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“Redisson分布式锁之加解锁源码分析”文章能帮助大家解决问题。锁的可重入性我们都知道,Ja...
    99+
    2023-07-05
  • Go开源项目分布式唯一ID生成系统
    目录前言项目背景项目使用HTTP 方式gRPC 方式本地开发项目架构前言 今天跟大家介绍一个开源项目:id-maker,主要功能是用来在分布式环境下生成唯一 ID。上周停更了一周,也...
    99+
    2024-04-02
  • 分布式Netty源码分析
    这篇文章主要介绍了分布式Netty源码分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇分布式Netty源码分析文章都会有所收获,下面我们一起来看看吧。服务器端demo看下一个简单的Netty服务器端的例子pu...
    99+
    2023-06-29
  • Leaf-分布式ID生成系统
    本文摘录于 https://tech.meituan.com/2017/04/21/mt-leaf.html 2017年04月21日   作者: 照东   ...
    99+
    2024-04-02
  • 什么是分布式ID生成器Tinyid
    本篇内容介绍了“什么是分布式ID生成器Tinyid”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Tinyi...
    99+
    2024-04-02
  • 分布式Netty源码EventLoopGroup分析
    这篇文章主要介绍“分布式Netty源码EventLoopGroup分析”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“分布式Netty源码EventLoopGroup分析”文章能帮助大家解决问题。Ev...
    99+
    2023-06-29
  • 分布式Netty源码分析EventLoopGroup及介绍
    目录EventLoopGroup介绍功能1:先来看看注册Channel功能2:执行一些Runnable任务EventLoop介绍NioEventLoop介绍EpollEventLoo...
    99+
    2024-04-02
  • redis分布式锁与zk分布式锁的对比分析
    目录Redis实现分布式锁原理能实现的锁类型注意事项 zk实现分布式锁原理能实现的锁类型两种锁的对比在分布式环境下,传统的jvm级别的锁会失效,那么分布式锁就是非常有必要的一个技术,一般我们可以通过redis,...
    99+
    2022-11-18
    redis分布式锁 分布式锁 zk分布式锁
  • 分布式ID的生成方式有哪些
    本篇内容主要讲解“分布式ID的生成方式有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“分布式ID的生成方式有哪些”吧!一、为什么要用分布式ID?在说分布式I...
    99+
    2024-04-02
  • ZooKeeper框架教程Curator分布式锁实现及源码分析
    目录  如何使用InterProcessMutex  实现思路   代码实现概述  InterProcessMutex源码分析&nb...
    99+
    2024-04-02
  • 基于Redis实现分布式单号及分布式ID(自定义规则生成)
    目录背景Redis实现方式代码实例单号生成枚举单号生成工具类单号生成接口单号生成接口实现使用测试总结背景 一些业务背景下,业务要求单号需要有区分不同的前缀,那么在分布式的架构下如何自...
    99+
    2024-04-02
  • 分布式Netty源码分析概览
    目录服务器端demoEventLoopGroup介绍功能1:先来看看注册Channel功能2:执行一些Runnable任务ChannelPipeline介绍bind过程sync介绍误...
    99+
    2024-04-02
  • redisson实现分布式锁的源码解析
    目录redisson测试代码加锁设计锁续期设计锁的自旋重试解锁设计撤销锁续期解锁成功唤排队线程 redisson redisson 实现分布式锁的机制如下: 依赖版本 implem...
    99+
    2024-04-02
  • Redisson分布式锁的源码解读分享
    目录前言前置知识分布式锁的思考Redis订阅/发布机制Redisson加锁订阅解锁看门狗前言 Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memor...
    99+
    2022-11-13
    Redisson分布式锁源码 Redisson 分布式锁
  • elasticsearch分布式及数据的功能源码分析
    从功能上说,可以分为两部分,分布式功能和数据功能。分布式功能主要是节点集群及集群附属功能如restful借口、集群性能检测功能等,数据功能主要是索引和搜索。代码上这些功能并不是完全独...
    99+
    2024-04-02
  • 分布式锁的原理及Redis怎么实现分布式锁
    这篇文章主要介绍“分布式锁的原理及Redis怎么实现分布式锁”,在日常操作中,相信很多人在分布式锁的原理及Redis怎么实现分布式锁问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解...
    99+
    2023-02-02
    redis
  • java分布式ID生成方法是什么
    本篇内容介绍了“java分布式ID生成方法是什么”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、需求缘起几乎所有的业务系统,都有生成一个唯...
    99+
    2023-06-04
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作