当前位置: 首页 > 科技观察

一篇文章给大家带来了etcd和分布式锁

时间:2023-03-13 18:31:23 科技观察

1。实现分布式锁的组件在分布式系统中,常用的实现分布式锁的组件有:Redis、zookeeper等。下面分别介绍一下它们各自的特点对比:从上图可以看出三个组件各自的特点,其中对于分布式锁来说最重要的一点就是对CP的要求。但是Redis集群不支持CP,但是支持AP。虽然官方也给出了redlock解决方案,但是由于需要部署多个实例(超过半数的实例都认为成功),所以部署维护比较复杂。所以在对一致性要求高的业务场景(电商、银行支付),一般会选择使用zookeeper或者etcd。对比zookeeper和etcd,如果考虑性能、并发、维护成本。由于etcd是用Go语言开发的,所以不依赖其他任何东西,直接编译成二进制可执行文件更有优势。本文选择etcd来讨论一些要点。2、AP为什么不好做分布式锁?在CAP理论中,由于分布式系统中多节点通信不可避免的网络延迟和丢包问题,必然会造成网络分区。在网络分区的情况下,一般有两种选择:CP或AP。①选择AP模型实现分布式锁时,客户端通过集群主节点加锁成功后,会立即获得加锁成功的反馈。此时如果主节点还没来得及同步数据到从节点就宕机了,系统会从从节点中选出一个节点作为新的主节点,新的主节点没有对应的锁数据到旧的主节点,导致其他客户端在新的主节点上获得相同的锁。这时候会导致多个进程/线程/协程对同一个关键资源数据进行操作,会造成数据不一致等问题。②选择CP模型实现分布式锁。只有主节点将数据同步到1/2以上的从节点后,才算锁定成功。此时,如果主节点因为某些原因宕机,系统会从从节点中选择一个数据较新的从节点作为新的主节点,避免数据丢失等问题。所以对于分布式锁来说,AP模型在对数据一致性要求比较强的场景下并不是一个好的选择。如果可以容忍少量数据丢失,考虑到维护成本等因素,可以首选AP模型的Redis。3、分布式锁的特点及操作对于分布式锁,操作的动作包括:获取锁、释放锁在业务处理过程中,启动另一个线程/协程来更新锁4、关于etcd的官方文档永远是最好的学习资料.etcd的官方介绍是这样说的:分布式系统使用etcd作为一个一致的键值存储,用于配置管理、服务发现和分布式工作的协调。许多组织使用etcd来实现生产系统,例如容器调度程序、服务发现服务和分布式数据存储。使用etcd的常见分布式模式包括领导者选举、分布式锁和监控机器活动。分布式系统使用etcd作为配置管理、服务发现和协调分布式工作的一致键值存储。许多组织使用etcd来实现容器调度程序、服务发现服务和分布式数据存储等生产系统。包括领导人选举、分布式锁和监控机器活跃度。https://etcd.io/docs/v3.4/learning/why/分布式锁只是etcd可以实现的众多功能之一。服务注册和发现在etcd中会用的比较多。官方也对比了很多组件,整理如下:通过对比,可以看出它们各自的特点。至于选择哪一个,您可能心中有自己的答案。5、etcd实现了分布式锁的相关接口。对于分布式锁,主要使用etcd对应的增删改签接口。//KV:Key-value相关操作typeKVinterface{//Storage.Put(ctxcontext.Context,key,valstring,opts...OpOption)(*PutResponse,error)//Get.Get(ctxcontext.Context,keystring,opts...OpOption)(*GetResponse,error)//Delete.Delete(ctxcontext.Context,keystring,opts...OpOption)(*DeleteResponse,error)//压缩rev.Compact指定版本之前的历史数据(ctxcontext.Context,revint64,opts...CompactOption)(*CompactResponse,error)//通用的操作执行命令,可以用来遍历操作集。Put/Get/Delete也是基于Do.Do(ctxcontext.Context,opOp)(OpResponse,error)//创建事务,只支持If/Then/Else/Commit操作.Txn(ctxcontext.Context)Txn}//Lease:租约相关操作typeLeaseinterface{//分配租约.Grant(ctxcontext.Context,ttlint64)(*LeaseGrantResponse,error)//释放租约.Revoke(ctxcontext.Context,idLeaseID)(*LeaseRevokeResponse,error)//获取剩余TTL时间.TimeToLive(ctxcontext.Context,idLeaseID,opts...LeaseOption)(*LeaseTimeToLiveResponse,error)//获取所有leases.Leases(ctxcontext.Context)(*LeaseLeasesResponse,error)//续订保持活跃.KeepAlive(ctxcontext.Context,idLeaseID)(<-chan*LeaseKeepAliveResponse,error)//只续激活一次.KeepAliveOnce(ctxcontext.Context,idLeaseID)(*LeaseKeepAliveResponse,error)//关闭续激活功能.Close()error}6.etcd实现分布式锁代码示例包mainimport("context""fmt""go.etcd.io/etcd/clientv3""time")varconfclientv3.Config//lockstructuretypeEtcdMutexstruct{Ttlint64//leasetimeConfclientv3.Config//etcd集群配置Keystring//etcd的keycancelcontext.CancelFunc//关闭functxnclientv3.Txnleaseclien进行续租tv3.LeaseleaseIDclientv3.LeaseID}//初始化锁func(em*EtcdMutex)init()error{varerrerrorvarctxcontext.Contextclient,err:=clientv3.New(em.Conf)iferr!=nil{returnerr}em.txn=clientv3.NewKV(client).Txn(context.TODO())em.lease=clientv3.NewLease(client)leaseResp,err:=em.lease.Grant(context.TODO(),em.Ttl)iferr!=nil{returnerr}ctx,em.cancel=context.WithCancel(context.TODO())em.leaseID=leaseResp.ID_,err=em.lease.KeepAlive(ctx,em.leaseID)returnerr}//获取锁func(em*EtcdMutex)Lock()error{err:=em.init()iferr!=nil{returnerr}//LOCKem.txn.If(clientv3.Compare(clientv3.CreateRevision(em.Key),"=",0)).Then(clientv3.OpPut(em.Key,"",clientv3.WithLease(em.leaseID))).Else()txnResp,err:=em.txn.Commit()iferr!=nil{returnerr}//判断txn。如果条件为真if!txnResp.Succeeded{returnfmt.Errorf("Lockgrabfailed")}returnnil}//释放锁func(em*EtcdMutex)UnLock(){//租约自动到期,立即到期//cancel取消续订Rent,revoke则立即过期em.cancel()em.lease.Revoke(context.TODO(),em.leaseID)fmt.Println("释放锁")}//groutine1functry2lock1(){eMutex1:=&EtcdMutex{Conf:conf,Ttl:10,Key:"lock",}err:=eMutex1.Lock()iferr!=nil{fmt.Println("groutine1抢锁失败")return}defereMutex1.UnLock()fmt.Println("groutine1成功抢到锁")time.Sleep(10*time.Second)}//groutine2functry2lock2(){eMutex2:=&EtcdMutex{Conf:conf,Ttl:10,Key:"lock",}err:=eMutex2.Lock()iferr!=nil{fmt.Println("groutine2锁获取失败")return}defereMutex2.UnLock()fmt.Println("grooutine2锁获取成功")}//测试代码funcEtcdRunTester(){conf=clientv3.Config{Endpoints:[]string{"127.0.0.1:2379"},DialTimeout:5*time.Second,}//启动两个协程竞争锁gotry2lock1()gotry2lock2()time.Sleep(300*time.Second)}总结可以提供分布式锁功能的组件有很多,但是各有各的脾气和个性。至于选择哪个组件,要看数据对业务的重要性,数据要求强一致性,推荐支持CP的etcd和zookeeper,允许少量丢失且不需要强一致性的数据推荐支持支持AP的Redis。