关于分布式系统:为什么要使用zookeeper
本文题目为《为什么要应用zookeeper》,然而本文并不是专门介绍zookeeper原理及其应用办法的文章。如果你在网上搜寻为什么要应用zookeeper,肯定能能到从zookeeper原理、实用场景到Zab算法原理等各种各样的介绍,然而看过之后是不是还是懵懵懂懂,只是学会了一些全面的、具体的知识点,还是不能文章题目的问题。zookeeper应用一种名为Zab的共识算法实现,除了Zab算法之外还有Paxos、Multi-Paxos、Raft等共识算法,实现上也有cubby、etcd、consul等独立的中间件和像Redis哨兵模式一样的嵌入式实现,这些实现都是基于相似的底层逻辑为了实用于不同场景下的工程学落地,本文的重点内容是共性的底层原理而不是具体的软件应用领导。 多线程与锁我以如何实现分布式锁为切入点,将多线程编程、锁、分布式系统、分布式系统一致性模型(线性一致性、最终一致性)、CAP定理、复制冗余、容错容灾、共识算法等一众概念有机联合起来。采纳层层递进的形式对相干概念及其互相分割开展阐述,不仅让你能将零散的知识点串连成线,而且还能站在理论利用的角度对相干概念从新思考。 之所以用分布式锁来举例,是因为在编程畛域,锁这个概念太广泛了,在多线程编程场景有同步锁、共享锁、排他所、自旋锁和锁降级等与锁无关的概念,在数据库畛域也有行级锁、表级锁、读锁、写锁、谓词锁和间隙锁等各种名词概念。实质上锁就是一种有肯定排他性的资源占有机制,一旦一方持有某个对象的锁,另一方就不能持有同一对象雷同的锁。 那么什么是分布式锁呢?要答复这个问题咱们须要先理解单机状况下锁的原理。在单机多线程编程中,咱们须要同步机制来保障共享变量的可见性和原子性。如何了解可见性和原子性呢?我用一个经典的计数器代码举例。 class Counter{ private int sum=0; public int count(int increment){ return sum += increment }}代码很简略,有过多线程编程教训的人都应该晓得count()办法在单线程下工作失常,然而在多线程场景下就会生效。原则上一个线程循环执行一百遍count(1)和一百个线程每个线程执行一遍count(1)后果应该都是100,然而理论执行的后果大概率是不雷同,这种单线程下执行正确然而多线程下执行逻辑不正确的状况咱们称之为线程不平安。 为什么在多线程下执行后果不正确呢?首先当两个线程同时执行sum=sum+1这条语句的时候,语句并不是原子性的,而是一个读操作和一个写操作。有可能两个线程都同时读取到了sum的值为0,加1操作后sum的值被两次赋值为1,这就像第一个线程的操作被第二个线程笼罩了一下,咱们称之为笼罩更新(表1)。 工夫/线程T1T2t1读取到sum值为0 t2 读取到sum值为0t3执行sum=0+1操作 t4 执行sum=0+1操作(表1) 接下来咱们再说可见性,即便两个线程不是同时读取sum的值,也有可能当一个线程批改了sum值之后,另一个线程不能及时看到最新的批改后的值。这是因为当初的CPU为了执行效率,为每个线程调配了一个寄存器,线程对内存的赋值不是间接更新,而是先更新本人的寄存器,而后CPU异步的将寄存器的值刷新到内存。因为寄存器的读写性能远远大于内存,所以这种异步的读写形式能够大幅度晋升CPU执行效率,让CPU时钟不会因为期待IO操作而暂停。 工夫/线程T1T2t1读取到sum值为0 t2执行sum=0+1操作 t3 读取到sum值为0t4 执行sum=0+1操作(表2) 咱们须要同步机制保障count()的原子性和可见性 class Counter{ private int sum=0; public synchronized int count(int increment){ return sum += increment }}如果替换为锁的语义,这段代码就相当于 class Counter{ private int sum=0; public int count(int increment){ lock(); sum += increment unlock(); return sum; }}lock()和unlock()办法都是伪代码,相当于加锁和解锁操作。一个线程调用了lock()办法获取到锁之后才能够执行前面的语句,执行结束后调用unlock()办法开释锁。此时如果另一个线程也调用lock()办法就会因无奈获取到锁而期待,直到第一个线程执行结束后开释锁。锁岂但能保障代码执行的原子性,还能保障变量的可见性,获取到锁之后的线程读取的任何共享变量肯定是它最新的值,不会获取到其余线程批改后的过期值。 咱们再来放大一下lock()外部细节。显然为了保障获取锁的排他性,咱们须要先去判断线程是否曾经取得了锁,如果还没有线程取得锁就给以后线程加锁,如果曾经有其余线程曾经获取了锁就期待。显然获取锁自身也须要保障原子性和可见性,所以lock()办法必须是一个同步(synchronized)办法,unlock()也是一样的情理。 在强调一下,加解锁办法自身都要具备原子性和可见性是一个重要的概念,前面咱们会用到。 public synchronized void lock(){ if(!hasLocked()){ locked(); return; }else{ awaited(); }}注:以上所有代码均为伪代码,只为阐明锁的作用及原理,无需深究 ...