前言

分布式锁是控制分布式系统之间同步接见共享资源的一种方式。在分布式系统中,经常需要协调他们的动作。若是差别的系统或是同一个系统的差别主机之间共享了一个或一组资源,那么接见这些资源的时刻,往往需要互斥来防止相互滋扰来保证一致性,在这种情形下,便需要使用到分布式锁。

etcd分布式锁设计

  1. 排他性:随便时刻,只能有一个机械的一个线程能获取到锁。

通过在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
			}
		}
	}
}
  1. 容错性:只要分布式锁服务集群节点大部分存活,client就可以举行加锁解锁操作。
    etcd基于Raft算法,确保集群中数据一致性。

  2. 制止死锁:分布式锁一定能获得释放,纵然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")

	<-m2Locked
	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), "<", v1),
	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客户端实现分布式锁,听说不会泛起锁丢失的情形)。

,

Sunbet

Sunbet www.698739.com Sunbet是进入Sunbet的主力站点。Sunbet开放Sunbet会员开户网址、Sunbet代理开户、Sunbet手机版下载、Sunbet电脑客户端下载等业务。www.sunbet.us值得您的信赖!

Allbet Gaming声明:该文看法仅代表作者自己,与阳光在线无关。转载请注明:联博以太坊:etcd分布式锁及事务
发布评论

分享到:

allbet gmaing开户:高血糖除了不能吃糖外,这5种食物也不宜吃,导致血糖飙升就晚了
1 条回复
  1. 环球UG
    环球UG
    (2020-06-14 00:06:25) 1#

    Allbetwww.aLLbetgame.us欢迎进入Allbet平台(Allbet Gaming):www.aLLbetgame.us,欧博平台开放欧博(Allbet)开户、欧博(Allbet)代理开户、欧博(Allbet)电脑客户端、欧博(Allbet)APP下载等业务。致敬作者

发表评论

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。