前言

分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在分布式系统中,常常需要协调他们的动作。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要互斥来防止彼此干扰来保证一致性,在这种情况下,便需要使用到分布式锁。

etcd分布式锁设计

排他性:任意时刻,只能有一个机器的一个线程能获取到锁。

通过在etcd中存入key值来实现上锁,删除key实现解锁,参考下面伪代码:

func Lock(key string, cli *clientv3.Client) error {

//获取key,判断是否存在锁

resp, err := cli.Get(context.Background(), key)

if err != nil {

return err

}

//锁存在,返回上锁失败

if len(resp.Kvs) > 0 {

return errors.New("lock fail")

}

_, err = cli.Put(context.Background(), key, "lock")

if err != nil {

return err

}

return nil

}

//删除key,解锁

func UnLock(key string, cli *clientv3.Client) error {

_, err := cli.Delete(context.Background(), key)

return err

}

当发现已上锁时,直接返回lock fail。也可以处理成等待解锁,解锁后竞争锁。

//等待key删除后再竞争锁

func waitDelete(key string, cli *clientv3.Client) {

rch := cli.Watch(context.Background(), key)

for wresp := range rch {

for _, ev := range wresp.Events {

switch ev.Type {

case mvccpb.DELETE: //删除

return

}

}

}

}

容错性:只要分布式锁服务集群节点大部分存活,client就可以进行加锁解锁操作。

etcd基于Raft算法,确保集群中数据一致性。

避免死锁:分布式锁一定能得到释放,即使client在释放之前崩溃。

上面分布式锁设计有缺陷,假如client获取到锁后程序直接崩了,没有解锁,那其他线程也无法拿到锁,导致死锁出现。

通过给key设定leases来避免死锁,但是leases过期时间设多长呢?假如设了30秒,而上锁后的操作比30秒大,会导致以下问题:

操作没完成,锁被别人占用了,不安全

操作完成后,进行解锁,这时候把别人占用的锁解开了

解决方案:给key添加过期时间后,以Keep leases alive方式延续leases,当client正常持有锁时,锁不会过期;当client程序崩掉后,程序不能执行Keep leases alive,从而让锁过期,避免死锁。看以下伪代码:

//上锁

func Lock(key string, cli *clientv3.Client) error {

//获取key,判断是否存在锁

resp, err := cli.Get(context.Background(), key)

if err != nil {

return err

}

//锁存在,等待解锁后再竞争锁

if len(resp.Kvs) > 0 {

waitDelete(key, cli)

return Lock(key)

}

//设置key过期时间

resp, err := cli.Grant(context.TODO(), 30)

if err != nil {

return err

}

//设置key并绑定过期时间

_, err = cli.Put(context.Background(), key, "lock", clientv3.WithLease(resp.ID))

if err != nil {

return err

}

//延续key的过期时间

_, err = cli.KeepAlive(context.TODO(), resp.ID)

if err != nil {

return err

}

return nil

}

//通过让key值过期来解锁

func UnLock(resp *clientv3.LeaseGrantResponse, cli *clientv3.Client) error {

_, err := cli.Revoke(context.TODO(), resp.ID)

return err

}

经过以上步骤,我们初步完成了分布式锁设计。其实官方已经实现了分布式锁,它大致原理和上述有出入,接下来我们看下如何使用官方的分布式锁。

etcd分布式锁使用

func ExampleMutex_Lock() {

cli, err := clientv3.New(clientv3.Config{Endpoints: endpoints})

if err != nil {

log.Fatal(err)

}

defer cli.Close()

// create two separate sessions for lock competition

s1, err := concurrency.NewSession(cli)

if err != nil {

log.Fatal(err)

}

defer s1.Close()

m1 := concurrency.NewMutex(s1, "/my-lock/")

s2, err := concurrency.NewSession(cli)

if err != nil {

log.Fatal(err)

}

defer s2.Close()

m2 := concurrency.NewMutex(s2, "/my-lock/")

// acquire lock for s1

if err := m1.Lock(context.TODO()); err != nil {

log.Fatal(err)

}

fmt.Println("acquired lock for s1")

m2Locked := make(chan struct{})

go func() {

defer close(m2Locked)

// wait until s1 is locks /my-lock/

if err := m2.Lock(context.TODO()); err != nil {

log.Fatal(err)

}

}()

if err := m1.Unlock(context.TODO()); err != nil {

log.Fatal(err)

}

fmt.Println("released lock for s1")

fmt.Println("acquired lock for s2")

// Output:

// acquired lock for s1

// released lock for s1

// acquired lock for s2

}

此代码来源于官方文档,etcd分布式锁使用起来很方便。

etcd事务

顺便介绍一下etcd事务,先看这段伪代码:

Txn(context.TODO()).If(//如果以下判断条件成立

Compare(Value(k1), "

Compare(Version(k1), "=", 2)

).Then(//则执行Then代码段

OpPut(k2,v2), OpPut(k3,v3)

).Else(//否则执行Else代码段

OpPut(k4,v4), OpPut(k5,v5)

).Commit()//最后提交事务

使用例子,代码来自官方文档:

func ExampleKV_txn() {

cli, err := clientv3.New(clientv3.Config{

Endpoints: endpoints,

DialTimeout: dialTimeout,

})

if err != nil {

log.Fatal(err)

}

defer cli.Close()

kvc := clientv3.NewKV(cli)

_, err = kvc.Put(context.TODO(), "key", "xyz")

if err != nil {

log.Fatal(err)

}

ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)

_, err = kvc.Txn(ctx).

// txn value comparisons are lexical

If(clientv3.Compare(clientv3.Value("key"), ">", "abc")).

// the "Then" runs, since "xyz" > "abc"

Then(clientv3.OpPut("key", "XYZ")).

// the "Else" does not run

Else(clientv3.OpPut("key", "ABC")).

Commit()

cancel()

if err != nil {

log.Fatal(err)

}

gresp, err := kvc.Get(context.TODO(), "key")

cancel()

if err != nil {

log.Fatal(err)

}

for _, ev := range gresp.Kvs {

fmt.Printf("%s : %s\n", ev.Key, ev.Value)

}

// Output: key : XYZ

}

总结

如果发展到分布式服务阶段,且对数据的可靠性要求很高,选etcd实现分布式锁不会错。介于对ZooKeeper好感度不强,这里就不介绍ZooKeeper分布式锁了。一般的Redis分布式锁,可能出现锁丢失的情况(如果你是Java开发者,可以使用Redisson客户端实现分布式锁,据说不会出现锁丢失的情况)。

用java实现etcd分布式锁_etcd分布式锁及事务相关推荐

  1. Java程序猿笔记——基于redis分布式锁实现“秒杀”

    最近在项目中遇到了类似"秒杀"的业务场景,在本篇博客中,我将用一个非常简单的demo,阐述实现所谓"秒杀"的基本思路. 业务场景 所谓秒杀,从业务角度看,是短时 ...

  2. java mysql 分布式锁_Java分布式锁之数据库方式实现

    之前的文章<Java分布式锁实现>中列举了分布式锁的3种实现方式,分别是基于数据库实现,基于缓存实现和基于zookeeper实现.三种实现方式各有可取之处,本篇文章就详细讲解一下Java分 ...

  3. java 独占锁_锁分类(独占锁、分拆锁、分离锁、分布式锁)

    一.java内存模型 提到同步.锁,就必须提到Java的内存模型,为了提高程序的执行效率,java也吸收了传统应用程序的多级缓存体系. 在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且 ...

  4. redis分布式锁java代码_基于redis实现分布式锁

    " 在上一篇文章中介绍了动态配置定时任务,其中的原理跟spring 定时任务注解@Scheduled一样的,都是通过线程池和定义执行时间来控制.来思考一个问题,如果我们的定时任务在分布式微服 ...

  5. java 单例 读写锁_终极锁实战:单JVM锁+分布式锁

    目录 1.前言 2.单JVM锁 3.分布式锁 4.总结 =========正文分割线================= 1.前言 锁就像一把钥匙,需要加锁的代码就像一个房间.出现互斥操作的典型场景:多 ...

  6. Java锁?分布式锁?乐观锁?行锁?

    本文公众号来源:码农翻身 作者:刘欣 以故事的方式来通俗易懂讲解锁的概念,建议阅读! Tomcat的锁 Tomcat是这个系统的核心组成部分, 每当有用户请求过来,Tomcat就会从线程池里找个线程来 ...

  7. 面试官问:说说悲观锁、乐观锁、分布式锁?都在什么场景下使用?有什么技巧?...

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 如何确保一个方法,或者一块代码在高并发情况下,同一时间只能 ...

  8. 说说悲观锁、乐观锁、分布式锁

    作者 | 张飞洪 来源 | https://www.cnblogs.com/jackyfei/p/12142840.html 如何确保一个方法,或者一块代码在高并发情况下,同一时间只能被一个线程执行, ...

  9. 分布式事务、分布式锁、分布式session

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | cnblogs.com/heqiyoujing ...

最新文章

  1. 小程序统一服务消息_[miniblog]小程序订阅消息踩坑记
  2. springboot整个cas_SpringBoot集成SpringSecurity+CAS
  3. 解决jQuery和其它库的冲突
  4. FPGA之道(1)HDL代码风格
  5. [原创] hadoop学习笔记:wordcout程序实践
  6. java 文件编码 查询系统_javaweb垃圾分类查询系统、ssm+mysql
  7. python kotlin_Java和Python中类似Kotlin的生成器,续:附加参数
  8. 原生js实现一个tab栏的标签操作
  9. linux shell脚本备份mysql数据库
  10. java二维数组存储数据,从键盘上录入学生人数,考试科目数,以及每个学生每科分数,输出每个学生的最高分、最低分、总分、平均分
  11. DHCP+VTP(实验讲解+配置)
  12. php而且,PHP – David's Blog
  13. jQuery起点教程之有序化插件实例
  14. JAVA常用API或编程工具002---SpringSource Tool Suite:基于Eclipse的Spring应用开发环境
  15. verify code
  16. c语言开发刷机工具,移动叔叔专用mtk刷机工具
  17. 程序员学习的网站(持续收集中)
  18. 手机中的html管理器停用,任务管理器已被系统管理员停用该怎么办
  19. 【练习】新浪邮箱注册测试用例
  20. 北斗导航 | ION GNSS+ 2014到 ION GNSS+ 2017会议论文下载:ION 美国导航学会

热门文章

  1. C语言当中int,float,double,char这四个有什么区别?
  2. Vercel反向代理做CDN,免费给网站加速隐藏源站,可绑定域名
  3. C#LeetCode刷题之#219-存在重复元素 II​​​​​​​(Contains Duplicate II)
  4. typescript 静态_关于TypeScript静态成员的全部信息| TypeScript OOP
  5. mvp内粗泄露问题_如何在一小时内启动MVP服务器
  6. os系统好用的学术笔记软件_可靠软件系统的设计方法:学术文章摘要
  7. mysql 硬盘缓存_paip.mysql性能跟iops的以及硬盘缓存的关系_MySQL
  8. Python爬虫的经典多线程方式,生产者与消费者模型
  9. 用python偷偷给班级群女同学的颜值进行排名,排最后的大姐说开学要打爆我
  10. 面向开发者的机器学习(一)| 机器学习简介