分布式锁:不同实现方式实践测评(下)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 分布式锁:不同实现方式实践测评(下)

2 分布式锁引入

2.1 分布式锁的概念

先说锁

线程是进程的一个实体,同一进程下的多个线程可以进行资源的共享,多个线程共享一个资源时则会进行资源的竞争进而引发线程异常。

基于此类问题,我们引入锁这个概念,锁,是一种线程中的一种同步机制。通过加锁我们就可以实现对共享资源的互斥访问。

为什么会出现分布式锁?

因为集群环境下,无法避免要把一个项目部署成多个节点,但是数据的一致性导致每个节点访问的数据都是一样的,至此我们可以把每一个项目节点都当做一个线程,整个分布式集群当做一个进程,数据就是多个节点共享的资源,因此难免会引发分布式环境下的多线程问题。

网络异常,图片无法展示
|


2.2 Redis实现分布式锁

package redis_lock
import (
   "github.com/go-redis/redis/v8"
   "github.com/go-redsync/redsync/v4"
   "github.com/go-redsync/redsync/v4/redis/goredis/v8"
   "time"
)
type RLock struct {
   Mutex *redsync.Mutex
}
func NewRLock(key string, expire ...time.Duration) *RLock {
   client := redis.NewClient(&redis.Options{
      Addr: "localhost:6379",
   })
   pool := goredis.NewPool(client)
   rs := redsync.New(pool)
   option := redsync.WithExpiry(time.Second * 5)
   if len(expire) == 1 {
      option = redsync.WithExpiry(expire[0])
   }
   mutex := rs.NewMutex(key, option)
   return &RLock{Mutex: mutex}
}
func (r *RLock) Lock() error {
   return r.Mutex.Lock()
}
func (r *RLock) Unlock() error {
   if ok, err := r.Mutex.Unlock(); !ok || err != nil {
      return err
   }
   return nil
}
复制代码

2.3 etcd实现分布式锁

package etcd_lock
import (
   "context"
   "fmt"
   "go.etcd.io/etcd/client/v3"
   "go.etcd.io/etcd/client/v3/concurrency"
   "time"
)
type EtcdLock struct {
   Mutex *concurrency.Mutex
}
func NewEtcdLock(key string, expire ...time.Duration) *EtcdLock {
   timeOut := time.Second * 5
   if len(expire) == 1 {
      timeOut = expire[0]
   }
   cli, err := clientv3.New(clientv3.Config{
      Endpoints:   []string{"127.0.0.1:2379"},
      DialTimeout: time.Second * 5,
   })
   if err != nil {
      fmt.Println(err)
   }
   s1, err := concurrency.NewSession(cli, concurrency.WithTTL(int(timeOut/time.Second)))
   if err != nil {
      fmt.Println(err)
   }
   return &EtcdLock{Mutex: concurrency.NewMutex(s1, key)}
}
func (r *EtcdLock) Lock(ctx context.Context) error {
   return r.Mutex.Lock(ctx)
}
func (r *EtcdLock) Unlock(ctx context.Context) error {
   return r.Mutex.Unlock(ctx)
}
复制代码

2.4 Zookeeper实现分布式锁

package zk_lock
import (
   "fmt"
   "time"
   "github.com/go-zookeeper/zk"
)
type ZkLock struct {
   ZLock *zk.Lock
}
func NewZkLock(key string, expire ...time.Duration) *ZkLock {
   timeOut := time.Second * 5
   if len(expire) == 1 {
      timeOut = expire[0]
   }
   c, _, err := zk.Connect([]string{"127.0.0.1:2181"}, timeOut)
   if err != nil {
      fmt.Println(err)
   }
   lock := zk.NewLock(c, fmt.Sprintf("/zkLock/lock-%s", key), zk.WorldACL(zk.PermAll))
   return &ZkLock{ZLock: lock}
}
func (z *ZkLock) Lock() error {
   return z.ZLock.Lock()
}
func (z ZkLock) Unlock() error {
   return z.ZLock.Unlock()
}
复制代码

3 实践测评过程与需要注意的问题

以Zookeeper加锁的代码举例:

func RunServer() {
  http.HandleFunc("/buyBook", func(w http.ResponseWriter, r *http.Request) {
    username := r.URL.Query().Get("name")
    buyNum := r.URL.Query().Get("num")
    zkLock := zk_lock.NewZkLock(username, time.Second*3)
    err := zkLock.Lock()
    if err != nil {
      fmt.Errorf("Lock err = %s",err)
      w.Write([]byte("购买失败"))
      return
    }
    defer func() {
      err = zkLock.Unlock()
      if err != nil {
        fmt.Errorf("UnLock err = %s",err)
      }
    }()
    resp := service.BuyBook(username, cast.ToInt64(buyNum))
    _, err = w.Write([]byte(resp))
    if err != nil {
      fmt.Errorf("write err %s", err)
    }
  })
  err := http.ListenAndServe(":8081", nil)
  if err != nil {
    fmt.Errorf("Http run err %s ", err)
  }
}
复制代码

实践过程就不是很难了,就是一个加锁和解锁的过程,但是要注意的一些问题:

  • 锁过期时间。分布式锁设置过期时间可以确保在未来的一定时间内,无论获得锁的节点发生了什么问题,最终锁都能被释放掉。但是时间也不能过短,防止业务还没有执行完锁就失效了。
  • 锁的全局唯一标识。
  • 锁的合理释放。我们要考虑在业务执行完成或发生异常时锁也能得到释放。

4 结论

经过Jmeter的分析报告,我们汇总成了一张表格:

网络异常,图片无法展示
|


依照表格我们可以得出结论:

Redis是三者中吞吐量、平均响应时间最优的一种方式,但是相对而言不如Zookeeper更加稳定,etcd在虽然在各个维度都不如Redis和Zookeeper,但是它仍然是一款比较优秀的云原生领域分布式注册中心,在集群环境中,Redis会产生脑裂、主从同步失败等安全问题,etcd则可以很大程度上屏蔽此类问题,所以我们不能只关注表面的数据,同时也要兼顾每个组件背后的原理和安全性。

最后,做一个小总结,分布式锁是一个相对复杂的组件,除了本文所讲述的以外,如果想要更好的使用分布式锁,还需要考虑其背后的诸多问题,比如锁操作的原子性、一致性、可重入性等,这些当然也与不同组件背后的算法相关,由于篇幅有限就没有一一详解,当然除了etcd、Redis、Zookeeper等组件之外,还有许多方式可以实现分布式锁,比如高性能的关系型数据库、MySQL乐观锁等等,都需要我们针对自身的业务进行选择。其实无论是一般的线程锁,还是分布式锁的作用都是一样的,只是作用的范围大小不同。只是范围越大技术复杂度就越大。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
8天前
|
机器学习/深度学习 存储 运维
分布式机器学习系统:设计原理、优化策略与实践经验
本文详细探讨了分布式机器学习系统的发展现状与挑战,重点分析了数据并行、模型并行等核心训练范式,以及参数服务器、优化器等关键组件的设计与实现。文章还深入讨论了混合精度训练、梯度累积、ZeRO优化器等高级特性,旨在提供一套全面的技术解决方案,以应对超大规模模型训练中的计算、存储及通信挑战。
32 4
|
3月前
|
运维 Kubernetes 调度
阿里云容器服务 ACK One 分布式云容器企业落地实践
3年前的云栖大会,我们发布分布式云容器平台ACK One,随着3年的发展,很高兴看到ACK One在混合云,分布式云领域帮助到越来越多的客户,今天给大家汇报下ACK One 3年来的发展演进,以及如何帮助客户解决分布式领域多云多集群管理的挑战。
阿里云容器服务 ACK One 分布式云容器企业落地实践
|
4月前
|
存储 分布式计算 Hadoop
【揭秘Hadoop背后的秘密!】HDFS读写流程大曝光:从理论到实践,带你深入了解Hadoop分布式文件系统!
【8月更文挑战第24天】Hadoop分布式文件系统(HDFS)是Hadoop生态系统的关键组件,专为大规模数据集提供高效率存储及访问。本文深入解析HDFS数据读写流程并附带示例代码。HDFS采用NameNode和DataNode架构,前者负责元数据管理,后者承担数据块存储任务。文章通过Java示例演示了如何利用Hadoop API实现数据的写入与读取,有助于理解HDFS的工作原理及其在大数据处理中的应用价值。
114 1
|
4月前
|
机器学习/深度学习 人工智能 负载均衡
【AI大模型】分布式训练:深入探索与实践优化
在人工智能的浩瀚宇宙中,AI大模型以其惊人的性能和广泛的应用前景,正引领着技术创新的浪潮。然而,随着模型参数的指数级增长,传统的单机训练方式已难以满足需求。分布式训练作为应对这一挑战的关键技术,正逐渐成为AI研发中的标配。
209 5
|
4月前
|
存储 Kubernetes 监控
深入浅出分布式事务:理论与实践
在数字化时代的浪潮中,分布式系统如同星辰大海般浩瀚而深邃。本文将带你航行于这片星辰大海,探索分布式事务的奥秘。我们将从事务的基本概念出发,逐步深入到分布式事务的核心机制,最后通过一个实战案例,让你亲自体验分布式事务的魅力。让我们一起揭开分布式事务的神秘面纱,领略其背后的科学与艺术。
94 1
|
4月前
|
Go API 数据库
[go 面试] 分布式事务框架选择与实践
[go 面试] 分布式事务框架选择与实践
|
4月前
|
UED 存储 数据管理
深度解析 Uno Platform 离线状态处理技巧:从网络检测到本地存储同步,全方位提升跨平台应用在无网环境下的用户体验与数据管理策略
【8月更文挑战第31天】处理离线状态下的用户体验是现代应用开发的关键。本文通过在线笔记应用案例,介绍如何使用 Uno Platform 优雅地应对离线状态。首先,利用 `NetworkInformation` 类检测网络状态;其次,使用 SQLite 实现离线存储;然后,在网络恢复时同步数据;最后,通过 UI 反馈提升用户体验。
104 0
|
4月前
|
机器学习/深度学习 TensorFlow 数据处理
分布式训练在TensorFlow中的全面应用指南:掌握多机多卡配置与实践技巧,让大规模数据集训练变得轻而易举,大幅提升模型训练效率与性能
【8月更文挑战第31天】本文详细介绍了如何在Tensorflow中实现多机多卡的分布式训练,涵盖环境配置、模型定义、数据处理及训练执行等关键环节。通过具体示例代码,展示了使用`MultiWorkerMirroredStrategy`进行分布式训练的过程,帮助读者更好地应对大规模数据集与复杂模型带来的挑战,提升训练效率。
96 0
|
4月前
|
存储 负载均衡 中间件
构建可扩展的分布式数据库:技术策略与实践
【8月更文挑战第3天】构建可扩展的分布式数据库是一个复杂而具有挑战性的任务。通过采用数据分片、复制与一致性模型、分布式事务管理和负载均衡与自动扩展等关键技术策略,并合理设计节点、架构模式和网络拓扑等关键组件,可以构建出高可用性、高性能和可扩展的分布式数据库系统。然而,在实际应用中还需要注意解决数据一致性、故障恢复与容错性以及分布式事务的复杂性等挑战。随着技术的不断发展和创新,相信分布式数据库系统将在未来发挥更加重要的作用。
|
4月前
|
消息中间件 存储 Kafka
微服务实践之分布式定时任务
微服务实践之分布式定时任务