Zookeeper(一) 什么是分布式协调技术

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u011414629/article/details/100171686

什么是分布式协调技术

分布式协调技术主要用来解决分布式环境中多个进程之间的同步控制, 让他们有序的去访问某种临界资源, 防止造成脏数据的产生

在这图中有三台机器, 每台机器各跑一个应用程序, 然后我们将这三台机器通过网络将其连接起来, 构成一个系统为用户提供服务, 对用户来说这个系统的架构是透明的, 他感觉不到这个系统是一个什么样的架构, 那么就可以把这种系统成为一个分布式系统

在这个分布式系统中如何对进程进行调度, 我假设在第一台机器上挂载了一个资源, 然后这三个物理分布的进程都要竞争这个资源, 但我们又不希望他们同时进行访问, 这时候我们就需要一个协调器, 来让他们有序的来访问这个资源, 这个协调器就是我们经常提到的那个锁, 比如说进程-1在使用该资源的时候, 会先去获得锁, 进程1获得锁以后会对该资源保持独占, 这样其他进程就无法访问该资源, 进程1用完该资源后就将锁释放掉, 让其他进程来获得锁, 那么通过这个锁机制, 我们就能保证了分布式系统多个进程能够有序的访问该临界资源, 那么我们把这个分布式环境下的这个锁叫做分布式锁, 这个分布式锁也就是我们分布式协调技术实现的核心内容

分布式协调的本质就是实现分布式锁, 当订单服务1来取商品的时候, 就要将该商品锁住, 那么订单服务2就取不到该商品, 取不到就不操作了, 就保证了该商品的安全处理. 而Zookeeper就是分布式锁的实现框架。

什么是分布式锁

为了防止分布式系统中多个进程之间相互干扰, 我们需要一种分布式协调技术来对这些进程进行协调。而这个分布式协调技术的核心就是来实现这个分布式锁

为什么要使用分布式锁

  1. 成员变量A存在于JVM1, JVM2, JVM3三个JVM内存中
  2. 成员变量A同时都会在JVM分配一块内存, 三个请求发过来同时对这个变量操作, 显然结果是不对的
  3. 不是同时发过来, 三个请求分别操作三个不同JVM内存区域的数据, 变量A之间不存在共享, 也不具有可见性, 处理的结果也是不对的

如果我们业务中确实存在这个场景的话, 我们就需要一种方法来解决这个问题, 这就是分布式锁要解决的问题

分布式锁应该具有哪些条件

  1. 在分布式系统环境下, 一个方法在同一时间只能被一个机器的一个线程执行
  2. 高可用的获取锁和释放锁
  3. 高性能的获取锁与释放锁
  4. 具备可重入特性(可理解为重新进入, 由多于一个任务并发使用, 而不必担心数据错误)
  5. 具备锁失效机制, 防止死锁

具备非阻塞锁特性, 即没有获取到锁将直接返回获取锁失败

分布式锁的实现有哪些

  1. Memcached: 利用Memcached的add命令. 此命令是原子性操作, 只有在key不存在的情况下, 才能add成功, 也就意味着线程得到了锁
  2. Redis: 和Memcached的方式类似, 利用Redis的setnx命令。此命令同样是原子性操作, 只有在key不存在的情况下, 才能set成功
  3. Zookeeper: 利用Zookeeper的顺序临时节点, 来实现分布式锁和等待队列。Zookeeper设计的初衷, 就是为了实现分布式锁服务的。

Chubby: Google公司实现的粗粒度分布式锁服务, 底层利用了Paxos一致性算法

通过Redis分布式锁的实现理解基本概念

分布式锁实现的三个核心要素

加锁

最简单的方式是使用setnx命令。key是锁的唯一标识, 按业务来决定命名。比如想要给一种商品的秒杀活动加锁, 可以给key命名为lock_sale_商品ID。而value设置成什么呢? 我们可以设置为1. 加锁的伪代码如下:

setnx(lock_sale_商品ID, 1)

当一个线程执行setnx返回1, 说明key原本不存在, 该线程成功得到了锁; 当一个线程执行setnx返回0, 说明key已经存在, 该线程抢锁失败

解锁

有加锁就得有解锁。当得到的线程执行完任务, 需要释放锁, 以便其他线程可以进入。释放锁的最简单的方式是执行del指令, 伪代码如下

del(lock_sale_商品ID)

1  先去Redis使用setnx(商品ID, 1) -> 命令加锁 setnx不是加锁的命令, 它只是设置数据的命令,

2. 如果发现redis里有这笔数据, 说明已经加锁了

3. 发现redis里没有数据, 说明可以获得锁

4. 使用redis的del命令删除商品ID

5. 锁超时, 30秒 -> expire

锁超时

JVM1获得锁操作数据, 操作到一半, JVM1宕机了, 这笔数据还在Redis里一直被锁着, 所以需要设置锁超时.

单体应用中多线程访问公共资源, 通过同步代码块, 有序的访问资源. 而现在是分布式系统, 分布式系统就变成了多个进程, 同步代码块已经用不了了, 问题变成了进程之间如何同步, 所以才需要有分布式锁来解决进程间的同步执行, 有序执行问题,

如果是不同进程直接访问数据库会比较麻烦, 所以中间会有一个redis存在, 想要操作一个数据时, 先从数据库里获取资源, 第二步要写时, 将数据到Redis里去看redis中有没有这个商品的数据. 无论外面怎么并发, 到Redis时都变成顺序执行了。

三个致命问题

1. 非原子性操作

1.1 setnx

         宕机

1.2 expire

         如果在setnx后, 宕机了, 还是会造成死锁的问题

         解决方案

         redis提供了setnx(key, value, expire), 一次性操作就不会出现死锁问题, 这个命令只有redis2.6以上版本才支持

2. 误删锁

2.1 set(key, value, expire)

         2.2 数据没有操作完, 30秒, 30秒内没有操作完成, 锁被释放掉了

         2.3 操作完成, del(商品ID), 删除的是JVM2的锁

        

         解决方案:

         判断是不是自己的锁

         set(商品id, threaded, 30)

         带着商品ID和线程ID, 先判断是不是我JVM1的锁, 是则删除

3. 基于第2个问题之上

         JVM1要判断是否处理完成

         JVM1 数据处理完成后才能释放锁

         JVM1增加守护线程

猜你喜欢

转载自blog.csdn.net/u011414629/article/details/100171686
今日推荐