golang结合etcd简单实现分布式锁

etcd:
	分布式高可用集群KV存储

etcd与Raft的关系:
	Raft是强一致性的集群日志同步算法
	etcd是一个分布式KV存储
	etcd利用raft算法在集群中同步key-value

quorum模型:
	集群需要2N+1个节点
	replication:日志在leader生成,向follower复制,达到各个节点的日志序列最终一致
	term:任期,重新选举产生的leader,其term单调递增
	log index:日志行在日志序列的下标

交互协议:
	etcd采用的HTTP+JSON协议,性能低效
	SDK内置GRPC协议,性能高效
重要特性:
	底层存储室按key有序排列的,可以顺序遍历
	因为key有序,所以etcd天然支持按目录结构高效遍历
	支持事物,提供类似if...then...esle...的食物能力
	基于租约机制实现key的TTL过期
Key有序存储:
	存储引擎室按key有序排列的:
lease租约:
	实现一个key的自动过期

代码实现:

package main
import (
	"fmt"
	"go.etcd.io/etcd/clientv3"
	"golang.org/x/net/context"
	"time"
)

//分布式事务的乐观锁
func main() {
	var (
		conf                   clientv3.Config
		client                 *clientv3.Client
		kv                     clientv3.KV
		lease                  clientv3.Lease
		leaseGrantResp         *clientv3.LeaseGrantResponse
		leaseId                clientv3.LeaseID
		LeaseKeepAliveResponse <-chan *clientv3.LeaseKeepAliveResponse
		leaseResp              *clientv3.LeaseKeepAliveResponse
		ctx                    context.Context
		CancelFunc             context.CancelFunc
		txn                    clientv3.Txn
		txnResp                *clientv3.TxnResponse
		err error
	)

	conf = clientv3.Config{
		Endpoints:   []string{"127.0.0.1:2379"},
		DialTimeout: 5 * time.Second,
	}
	//链接
	if client, err = clientv3.New(conf); err != nil {
		fmt.Println("连接失败,Error:", err)
		return
	}
	//创建KV
	//kv = clientv3.NewKV(client)
	//lease实现自动过期
	//op操作
	//txn事务:if else then

	//1、上锁(创建租约,自动续租,拿着租约去抢占一个KEY)
	lease = clientv3.NewLease(client)                                     //创建一个租约
	if leaseGrantResp, err = lease.Grant(context.TODO(), 5); err != nil { //申请10秒的租约
		fmt.Println(err)
		return
	}

	//拿到租约id
	leaseId = leaseGrantResp.ID
	//准备一个用于取消自动续租的context
	ctx, CancelFunc = context.WithCancel(context.TODO())
	defer CancelFunc()                                                           //确保程序退出后,自动续租停止
	defer lease.Revoke(ctx, leaseId)                                             //立即销毁租约s
	if LeaseKeepAliveResponse, err = lease.KeepAlive(ctx, leaseId); err != nil { //自动续约
		fmt.Println(err)
		return
	}
	go func() { //处理续租
		for {
			select {
			case leaseResp = <-LeaseKeepAliveResponse:
				if LeaseKeepAliveResponse == nil {
					fmt.Println("租约到期")
					goto END
				} else {
					fmt.Println("收到自动续租应答:", leaseResp.ID)
				}
			}
		}
	END:
	}()
	//抢key
	//if不存在key,then设置它,esle强锁失败
	kv = clientv3.NewKV(client)
	txn = kv.Txn(context.TODO()) //创建事务
	txn.If(clientv3.Compare(clientv3.CreateRevision("/cron/lock/job9"), "=", 0)).
		Then(clientv3.OpPut("/cron/lock/job9", "job9", clientv3.WithLease(leaseId))).
		Else(clientv3.OpGet("/cron/lock/job9"))
	if txnResp, err = txn.Commit(); err != nil {
		fmt.Println(err)
		return
	}
	//判断是否抢到了锁
	if !txnResp.Succeeded {
		fmt.Println("锁被占用:", 	string(txnResp.Responses[0].GetResponseRange().Kvs[0].Value))
		return
	}
	//2、处理业务
	fmt.Println("处理任务")
	time.Sleep(10 * time.Second)
	//3、释放锁(取消自动续租,释放租约):defer会把租约释放掉,关联的KV就被删除了
}

你可能感兴趣的:(golang,etcd)