分布式概念:分布式锁(分布式互斥的一种实现方式)

分布式系统中,多个节点都需要访问一个临界资源,但是同一时刻只能有一个节点可以访问,为了解决这个问题就是要通过分布式互斥来实现;分布式锁就是实现分布式互斥的一种实现方式。

锁是实现多线程同时访问同一共享资源,保证同一时刻只有一个线程可访问共享资源所做的一种标记。
分布式锁是指分布式环境下,系统部署在多个机器中,实现多进程分布式互斥的一种锁。为了保证多个进程能看到锁,锁被存在公共存储(比如 Redis、数据库等三方存储中),以实现多个进程并发访问同一个临界资源,同一时刻只有一个进程可访问共享资源,确保数据的一致性。

分布式锁的三种实现方法
1.基于关系数据库实现分布式锁
(1)创建一张锁表,然后通过操作该表中的数据来实现。
(2)当我们要锁住某个资源时,就在该表中增加一条记录,想要释放锁的时候就删除这条记录。
(3)数据库对共享资源做了唯一性约束,如果有多个请求被同时提交到数据库的话,数据库会保证只有一个操作可以成功,操作成功的那个线程就获得了访问共享资源的锁。
缺点:
1.因为数据库需要落到硬盘上,频繁读取数据库会导致 IO 开销大,因此这种分布式锁适用于并发量低,对性能要求低的场景。
2.容易引起单点故障


2.基于缓存实现分布式锁
把数据存放在计算机内存中,不需要写入磁盘,减少了 IO 读写。
实例:Redis 通过队列来维持进程访问共享资源的先后顺序。
(1)Redis 通常可以使用 setnx(key, value) 函数来实现分布式锁。
(2)key 和 value 就是基于缓存的分布式锁的两个属性,其中 key 表示锁 id,value = currentTime + timeOut,表示当前时间 + 超时时间。
(3)某个进程获得 key 这把锁后,如果在 value 的时间内未释放锁,系统就会主动释放锁。
(4)setnx 函数的返回值有 0 和 1:返回 1,说明该服务器获得锁,setnx 将 key 对应的 value 设置为当前时间 + 锁的有效时间。返回 0,说明其他服务器已经获得了锁,进程不能进入临界区。该服务器可以不断尝试 setnx 操作,以获得锁。


3.基于 ZooKeeper 实现分布式锁
ZooKeeper 基于树形数据存储结构实现分布式锁。
节点类型:
持久节点。这是默认的节点类型,一直存在于 ZooKeeper 中。
持久顺序节点。也就是说,在创建节点时,ZooKeeper 根据节点创建的时间顺序对节点进行编号。
临时节点。与持久节点不同,当客户端与 ZooKeeper 断开连接后,该进程创建的临时节点就会被删除。
临时顺序节点。按时间顺序编号的临时节点。

以电商售卖吹风机的场景为例。假设用户 A、B、C 同时在 11 月 11 日的零点整提交了购买吹风机的请求,ZooKeeper 会采用如下方法来实现分布式锁:
1.在与该方法对应的持久节点 shared_lock 的目录下,为每个进程创建一个临时顺序节点。吹风机就是一个拥有 shared_lock 的目录,当有人买吹风机时,会为他创建一个临时顺序节点。
2.每个进程获取 shared_lock 目录下的所有临时节点列表,注册子节点变更的 Watcher,并监听节点。
3.每个节点确定自己的编号是否是 shared_lock 下所有子节点中最小的,若最小,则获得锁。例如,用户 A 的订单最先到服务器,因此创建了编号为 1 的临时顺序节点 LockNode1。该节点的编号是持久节点目录下最小的,因此获取到分布式锁,可以访问临界资源,从而可以购买吹风机。
4.若本进程对应的临时节点编号不是最小的,则分为两种情况:
   a. 本进程为读请求,如果比自己序号小的节点中有写请求,则等待;
   b. 本进程为写请求,如果比自己序号小的节点中有读请求,则等待。

例如,用户 B 也想要买吹风机,但在他之前,用户 C 想看看吹风机的库存量。因此,用户 B 只能等用户 A 买完吹风机、用户 C 查询完库存量后,才能购买吹风机。

ZooKeeper 分布式锁的可靠性最高,有封装好的框架,很容易实现分布式锁的功能,并且几乎解决了数据库锁和缓存式锁的不足

etcd分布式锁实现原理:

1.利用租约在etcd集群中创建一个key,这个key有两种形态,存在和不存在,而这两种形态就是互斥量。
2.如果这个key不存在,那么线程创建key,成功则获取到锁,该key就为存在状态。
3.如果该key已经存在,那么线程就不能创建key,则获取锁失败。

锁结构体:

在使用该锁时,需要传入Ttl,Conf,Key字段来初始化锁

type EtcdMutex struct {
    Ttl int64  //租约时间
    Conf clientv3.Config  //etcd集群配置
    Key string   //etcd的key
    cancel context.CancelFunc  //关闭续租的func
    lease clientv3.Lease
    leaseID clientv3.LeaseID
    txn clientv3.Txn
}

初始化锁:

func(em *EtcdMutex)init()error{
    var err error
    var ctx context.Context
    client,err := clientv3.New(em.Conf)
    if err != nil{
        return err
    }
    em.txn = clientv3.NewKV(client).Txn(context.TODO())
    em.lease = clientv3.NewLease(client)
    leaseResp,err := em.lease.Grant(context.TODO(),em.Ttl)
    if err != nil{
        return err
    }
    ctx,em.cancel = context.WithCancel(context.TODO())
    em.leaseID = leaseResp.ID
    _,err = em.lease.KeepAlive(ctx,em.leaseID)
    return err
}

获取锁:


func(em *EtcdMutex)Lock()error{
    err := em.init()
    if err != nil{
        return err
    }
    //LOCK:
        em.txn.If(clientv3.Compare(clientv3.CreateRevision(em.Key),"=",0)).
            Then(clientv3.OpPut(em.Key,"",clientv3.WithLease(em.leaseID))).
            Else()
    txnResp,err := em.txn.Commit()
    if err != nil{
        return err
    }
    if !txnResp.Succeeded{   //判断txn.if条件是否成立
        return fmt.Errof("抢锁失败")        
    }
    return nil
}

释放锁:

func(em *EtcdMutex)UnLock(){
    em.cancel()
    em.lease.Revoke(context.TODO(),em.leaseID)
    fmt.Println("释放了锁")
}

调用锁:

func main(){
    var conf = clientv3.Config{
        Endpoints:   []string{"172.16.196.129:2380", "192.168.50.250:2380"},
        DialTimeout: 5 * time.Second,
    }
    eMutex1 := &EtcdMutex{
        Conf:conf,
        Ttl:10,
        Key:"lock",
    }
     eMutex2 := &EtcdMutex{
        Conf:conf,
        Ttl:10,
        Key:"lock",
    }
    //groutine1 
    go func() {
        err := eMutex1.Lock()
        if err != nil{
            fmt.Println("groutine1抢锁失败")
            fmt.Println(err)
            return
        }
        fmt.Println("groutine1抢锁成功")
        time.Sleep(10*time.Second)
        defer eMutex.UnLock()
    }()

    //groutine2
    go func() {
        err := eMutex2.Lock()
        if err != nil{
            fmt.Println("groutine2抢锁失败")
            fmt.Println(err)
            return
        }
        fmt.Println("groutine2抢锁成功")
        defer eMutex.UnLock()
    }()
    time.Sleep(30*time.Second)
}

 

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页