微服务架构上篇:5. 基于etcd实现分布式锁

简介: 微服务架构上篇:5. 基于etcd实现分布式锁

1. 前言



通过 etcd 实现分布式锁,同样需要满足一致性互斥性可靠性等要求。etcd 中的事务 txnlease 租约以及 watch 监听特性,能够使得基于 etcd 实现上述要求的分布式锁


2. 思路分析



2.1 正常获取锁(etcd的事务IF-Then-Else)


通过 etcd 的事务特性可以帮助我们实现一致性和互斥性。etcd 的事务特性,使用的 IF-Then-Else 语句,IF 语言判断 etcd 服务端是否存在指定的 key,即该 key 创建版本号 create_revision 是否为 0 来检查 key 是否已存在,因为该 key 已存在的话,它的 create_revision 版本号就不是 0。满足 IF 条件的情况下则使用 then 执行 put 操作,否则 else 语句返回抢锁失败的结果。当然,除了使用 key 是否创建成功作为 IF 的判断依据,还可以创建前缀相同的 key,比较这些 key 的 revision 来判断分布式锁应该属于哪个请求。


2.2 获取锁异常


客户端请求在获取到分布式锁之后,如果发生异常,需要及时将锁给释放掉。因此需要租约,当我们申请分布式锁的时候需要指定租约时间。超过 lease 租期时间将会自动释放锁,保证了业务的可用性。是不是这样就够了呢?在执行业务逻辑时,如果客户端发起的是一个耗时的操作,操作未完成的请情况下,租约时间过期,导致其他请求获取到分布式锁,造成不一致。这种情况下则需要续租,即刷新租约,使得客户端能够和 etcd 服务端保持心跳。


3. 实现分布式锁的流程图



我们基于如上分析的思路,绘制出实现 etcd 分布式锁的流程图,如下所示:

640.png


4. 代码实现



package main
import (
 "context"
 "fmt"
 "github.com/coreos/etcd/clientv3"
 "time"
)
func main() {
 // 客户端配置
 config := clientv3.Config{
  Endpoints:   []string{"localhost:2379"},
  DialTimeout: 5 * time.Second,
 }
 var client *clientv3.Client
 var err error
 // 建立连接
 if client, err = clientv3.New(config); err != nil {
  fmt.Println(err)
  return
 }
 // 1. 上锁并创建租约
 lease := clientv3.NewLease(client)
 var leaseGrantResp *clientv3.LeaseGrantResponse
 if leaseGrantResp, err = lease.Grant(context.TODO(), 5); err != nil {
  panic(err)
 }
 leaseId := leaseGrantResp.ID
 // 2 自动续约
 // 创建一个可取消的租约,主要是为了退出的时候能够释放
 ctx, cancelFunc := context.WithCancel(context.TODO())
 // 3. 释放租约
 defer cancelFunc()
 defer lease.Revoke(context.TODO(), leaseId)
 if keepRespChan, err := lease.KeepAlive(ctx, leaseId); err != nil {
  panic(err)
 } else {
  // 续约应答
  go func() {
   for {
    select {
    case keepResp := <-keepRespChan:
     if keepRespChan == nil {
      fmt.Println("租约已经失效了")
      goto END
     } else { // 每秒会续租一次, 所以就会受到一次应答
      fmt.Println("收到自动续租应答:", keepResp.ID)
     }
    }
   }
  END:
  }()
 }
 // 1.3 在租约时间内去抢锁(etcd 里面的锁就是一个 key)
 kv := clientv3.NewKV(client)
 // 创建事务
 txn := kv.Txn(context.TODO())
 //if 不存在 key,then 设置它,else 抢锁失败
 txn.If(clientv3.Compare(clientv3.CreateRevision("lock"), "=", 0)).
  Then(clientv3.OpPut("lock", "g", clientv3.WithLease(leaseId))).
  Else(clientv3.OpGet("lock"))
 // 提交事务
 if txnResp, err := txn.Commit(); err != nil {
  panic(err)
 } else {
  if !txnResp.Succeeded {
   fmt.Println("锁被占用:", string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))
   return
  }
  // 抢到锁后执行业务逻辑,没有抢到退出
  fmt.Println("处理任务")
  time.Sleep(5 * time.Second)
 }
}


预期的执行结果如下所示:

收到自动续租应答: 6825622810871743294
处理任务
收到自动续租应答: 6825622810871743294
收到自动续租应答: 6825622810871743294
Process finished with exit code 0


总得来说,如上关于 etcd 分布式锁的实现过程分为四个步骤:

  • 客户端初始化与建立连接;
  • 创建租约,自动续租;
  • 创建事务,获取锁;
  • 执行业务逻辑,最后释放锁。


创建租约的时候,需要创建一个可取消的租约,主要是为了退出的时候能够释放。释放锁对应的步骤,在上面的 defer 语句中。当 defer 租约关掉的时候,分布式锁对应的 key 就会被释放掉了。


5. 小结



本文主要介绍了基于 etcd 实现分布式锁的案例。首先介绍了分布式锁产生的背景以及必要性,分布式架构不同于单体架构,涉及到多服务之间多个实例的调用,跨进程的情况下使用编程语言自带的并发原语没有办法实现数据的一致性,因此分布式锁出现,用来解决分布式环境中的资源互斥操作。


接着本文重点介绍了基于 etcd 实现分布式锁的方案,根据 etcd 的特点,利用事务 txn、lease 租约以及 watch 监测实现分布式锁。


在我们上面的案例中,抢锁失败,客户端就直接返回了。那么当该锁被释放之后,或者持有锁的客户端出现了故障退出了,其他锁如何快速获取锁呢?所以上述代码可以基于 watch 监测特性进行改进,各位同学可以自行试试。(可以参考:https://github.com/zieckey/etcdsync)

相关文章
|
10天前
|
存储 JSON 数据库
Elasticsearch 分布式架构解析
【9月更文第2天】Elasticsearch 是一个分布式的搜索和分析引擎,以其高可扩展性和实时性著称。它基于 Lucene 开发,但提供了更高级别的抽象,使得开发者能够轻松地构建复杂的搜索应用。本文将深入探讨 Elasticsearch 的分布式存储和检索机制,解释其背后的原理及其优势。
39 5
|
14天前
|
Kubernetes Cloud Native Docker
云原生之旅:从容器到微服务的架构演变
【8月更文挑战第29天】在数字化时代的浪潮下,云原生技术以其灵活性、可扩展性和弹性管理成为企业数字化转型的关键。本文将通过浅显易懂的语言和生动的比喻,带领读者了解云原生的基本概念,探索容器化技术的奥秘,并深入微服务架构的世界。我们将一起见证代码如何转化为现实中的服务,实现快速迭代和高效部署。无论你是初学者还是有经验的开发者,这篇文章都会为你打开一扇通往云原生世界的大门。
|
3天前
|
监控 负载均衡 应用服务中间件
探索微服务架构下的API网关设计与实践
在数字化浪潮中,微服务架构以其灵活性和可扩展性成为企业IT架构的宠儿。本文将深入浅出地介绍微服务架构下API网关的关键作用,探讨其设计原则与实践要点,旨在帮助读者更好地理解和应用API网关,优化微服务间的通信效率和安全性,实现服务的高可用性和伸缩性。
13 3
|
6天前
|
存储 Java Maven
从零到微服务专家:用Micronaut框架轻松构建未来架构
【9月更文挑战第5天】在现代软件开发中,微服务架构因提升应用的可伸缩性和灵活性而广受欢迎。Micronaut 是一个轻量级的 Java 框架,适合构建微服务。本文介绍如何从零开始使用 Micronaut 搭建微服务架构,包括设置开发环境、创建 Maven 项目并添加 Micronaut 依赖,编写主类启动应用,以及添加控制器处理 HTTP 请求。通过示例代码展示如何实现简单的 “Hello, World!” 功能,并介绍如何通过添加更多依赖来扩展应用功能,如数据访问、验证和安全性等。Micronaut 的强大和灵活性使你能够快速构建复杂的微服务系统。
26 5
|
11天前
|
Java 数据库连接 微服务
揭秘微服务架构下的数据魔方:Hibernate如何玩转分布式持久化,实现秒级响应的秘密武器?
【8月更文挑战第31天】微服务架构通过将系统拆分成独立服务,提升了可维护性和扩展性,但也带来了数据一致性和事务管理等挑战。Hibernate 作为强大的 ORM 工具,在微服务中发挥关键作用,通过二级缓存和分布式事务支持,简化了对象关系映射,并提供了有效的持久化策略。其二级缓存机制减少数据库访问,提升性能;支持 JTA 保证跨服务事务一致性;乐观锁机制解决并发数据冲突。合理配置 Hibernate 可助力构建高效稳定的分布式系统。
24 0
|
11天前
|
数据库 Java 数据库连接
Hibernate 实体监听器竟如魔法精灵,在 CRUD 操作中掀起自动化风暴!
【8月更文挑战第31天】在软件开发中,效率与自动化至关重要。Hibernate 通过其强大的持久化框架提供了实体监听器这一利器,自动处理 CRUD 操作中的重复任务,如生成唯一标识符、记录更新时间和执行清理操作,从而大幅提升开发效率并减少错误。下面通过示例代码展示了如何定义监听器类,并在实体类中使用 `@EntityListeners` 注解来指定监听器,实现自动化任务。这不仅简化了开发流程,还能根据具体需求灵活应用,满足各种业务场景。
21 0
|
11天前
|
前端开发 微服务 API
微服务浪潮下的JSF革新:如何在分散式架构中构建统一而强大的Web界面
【8月更文挑战第31天】随着微服务架构的兴起,企业将应用拆分成小型、独立的服务以提高系统可维护性和可扩展性。本文探讨如何在微服务架构下构建和部署JavaServer Faces (JSF) 应用,通过RESTful服务实现前后端分离,提升灵活性和适应性。
29 0
|
11天前
|
负载均衡 监控 JavaScript
探索微服务架构下的API网关模式
【8月更文挑战第31天】在微服务的大潮中,API网关不仅是流量的守门人,更是服务间通信的桥梁。本文将带你深入理解API网关的核心概念、设计要点及其在微服务架构中的重要作用,同时通过代码示例揭示如何利用API网关提升系统的灵活性与扩展性。
|
12天前
|
NoSQL API 数据库
揭秘!Flask如何一键解锁RESTful API高效微服务?打造未来互联网架构的隐形力量!
【8月更文挑战第31天】本文介绍如何使用 Flask 构建高效且易维护的 RESTful 微服务,涵盖环境搭建、基本应用创建及代码详解。通过示例展示用户管理系统的 CRUD 操作,并讨论数据库集成、错误处理、认证授权、性能优化及文档生成等高级主题,助力开发者打造强大的后端支持。
20 0
|
13天前
|
消息中间件 监控 Kafka
Producer 与微服务架构的集成
【8月更文第29天】在现代软件开发中,微服务架构因其灵活性和可扩展性而被广泛采用。这种架构允许将复杂的系统分解为更小、更易于管理的服务。消息传递是连接这些服务的关键部分,而消息生产者(Producer)则是消息传递中的重要角色。本文将探讨如何将消息生产者无缝集成到基于微服务的应用程序中,并提供一个使用 Python 和 Kafka 的示例。
26 0

热门文章

最新文章