先来说一个问题,就是zookeeper leader选举问题
直接说结论,通过逻辑时钟(zxid)来保证事件发生的因果关系,因果最新的节点优先被选为主,其他小弟跟着同步,如果大家因果相同那就用server id决断作为兜底方案。解释下什么逻辑上时钟?通过数值来记录事件的因果关系,比如a去银行取了100块,然后把钱借给b了。这里b得到钱的果就是先于a取钱这个果的,也就是通过因果来界定时间的先后顺序,但是逻辑时钟不保证实际时间的先后顺序存在因果关系,如果需要可能需要向量时钟才能解决。
知道上面这些后,就可以回答zookeeper的选举过程及原理了,这种解决问题的思路是:在特定的时间里只存在特定的数据版本,或者说通过更细的维度来区分事件发生的先后顺序以此来解决数据冲突所带来的一致性问题,zk更关注的数据的一致性,以及通过减少不一致性(如选举机制)来间接达到服务的高可用性,因为分布式系统里面注定要面对CAP理论里面的Paration网络分区所带来的数据一致性和服务可用性选择的问题。
类似的原理应用还有原子类里面CAS的ABA问题解决方案,加入版本号来区分数据,只不过它适用的是多线程环境共享数据更新问题,还Mysql里面的MVCC多版本控制,根据各个事物开始时间的节点,在redo log中看到的数据版本不一致,来解决数据可以重复读取问题,能解决部分幻地问题但无法完全解决,会不会幻读取决于当前事物操作的数据是否有早于它的事务也进行了操作,如果是就能看到不算幻读,如果不是就是幻读,注意这里的操作指的是修改,而不是读取,幻读转指对数据的“修改”,于是就出现了锁,这个是多进程或线程间通信绕不开的永恒话题,mysql管它叫next key锁(间隙锁+行锁),这个锁也不是随便加的,而是根据查询数据的范围和查询列的索引情况来综合分析加多大,比如唯一索引a,值为10,20,30,那你现在修改10这条数据,那只会加10的行锁,由于唯一索引不会重复,所以就只加10这条数据,rr级别下读这条10也不会幻读,如果修改的是>20,那为了防止有其他事务对20后的数据改动,会加(20,0)所有的锁,这个是锁与索引的关系。还有Git的版本控制,每次提交都代表了代码库的一个版本。Git通过SHA-1哈希作为每次提交的唯一标识,用户可以基于这些提交点来合并分支、回退错误和理解开发历史,每个提交点都保持了代码在那一刻的完整视图。
这些都是通过时间或版本来定义先后顺序,以保证事件发生的顺序,在并发环境下保证了事件的顺序就对数据的一致性多了一种保证。
说到一致性就不得不聊为啥会出现一致性这种问题,那要说到人们为追求性能,在计算机领域做出的很多尝试和应用,但也带来了很多问题,同时为了解决冲突想出了很多办法,在计算机里面,从硬件到操作系统到软件,比如人们为了解决计算机的处理能力,不断的改进cpu的制成工艺,能耗,散热等,当单核cpu到达物理瓶颈时,类似的理论有摩尔定律,每18月cpu性能翻一番,除了工艺改进,还有其他手段比如叠buff一样,加入多核cpu,又为每一核引入超线程技术,从1971年第一颗商用cpu Intel 4004仅有约0.092 MIPS(百万指令每秒)的处理能力,到现在的intel 14900k随便每秒百亿条处理指令的能力,可以说性能的提升在这50多年里提升可以说是指数级别的增长了,cpu上去了,但其他也要跟上,为了解决cpu性能和硬盘间的巨大差距,又引入了内存,是的,你没说错,一开的电脑是没存的,早期的计算机系统主要使用磁带或者卡片等介质来存储程序和数据,而不是像今天的内存那样直接存储在芯片上,当硬件上去了,基于硬件的软件,包括操作系统,应用程序等就需要来适应这种情况,就像经济基础决定上层建筑。
说到这里就还有个问题就是进程间是如何通信的?使用的IPC机制,目前常用的有System V IPC和POSIX IPC机制,后者对前者进行了改良优化,改良啥了呢,比如支持了队列异步通知,还有支持映射文件或匿名内存到进程的地址空间,这两个太重要了,比如在IO界很有名的异步非堵塞的AIO、还有被kafka用的如火纯青的零拷贝用到的就是mmap,不过它高性能原因还有其他的因素,像数据压缩、批量发送、多队列、顺序写入等等。当然零拷贝有很多种实现方式比如sendfile/spelice/vmsplice等等,有兴趣的同学可以自行去研究,这里就不一一介绍了。接下来我大概说下这两种技术的运行原理。
正常调用linux的read/write操作文件的步骤,里外里倒腾需要4次copy,可以见cpu内核参与程度
,参与过程还不能干啥事,就得整个线程专门干这事。
用mmap零拷贝技术,直接让系统把数据拷贝到物理内存,完了告诉用mmap映射这部分数据到用户进程,用户进程直接
就能操作数据,操作完了由系统负责刷脏页,直接2次搞定,而这copy文件操作cpu初始化个dma(一种硬件功能)让它干这个活
它自个去干其他的,这就是零拷贝,这个零说的就是用户空间级别不存在拷贝数据,用户操作数据就完了,其它刷脏页让系统干,这个
刷脏在分布式系统中间件非常常见,比如mysql的redo log日志,redis的数据备份rdb和aof。
还有一个就是AIO模型用的epoll,常见的IO模型有BIO/NIO/AIO,堵塞/非堵塞/异步IO,这里碍于篇幅就不说概念了,直接看图
这是BIO的模式,使用IO多路复用,内核出一个线程,为了找出哪些FD状态发生变化,一直遍历所有FD(文件描述符),哪怕只有几个FD
变化了,也会返回所有FD列表,让用户线程去遍历这100W条数据,线程调用系统内核api就用的select,这api特点是一次传所有要监听的FD,而且每次调用
select一次就要传所有的FD,返回也是返回所有FD,所以无论用户线程还是内核两头不吃好,数据量小还好,一旦大起来,性能将严重影响。
而AIO的epoll就不同了,无论从最开始的用户调用系统内核的epoll,每个线程可以只注册自己感兴趣的事件,而且只要线程后续不去修改
这些事件,后面就不用再注册了,每次来了事件都会回调通知线程,做到了一次注册,多次使用,在内核这边也只遍历epoll里面特定的事件
不是去遍历所有,返回时也只返回之前注册过的FD事件,性能与前面的select模式完全不一样,数据越大这个差距越明显。
ok说完了IPC机制的改进点,我们再接着说下进程间这些通信的方式,队列,内存共享、信号量。
在unix系统里面最开始用的就是管道进行通信,同时它也是进程间特有的通信方式,使用场景单一,传输数据量有限,流向单一,通常用于父子进程通信比如linux系统一启动会创建一个主进程,后续所有进程都是其子进程,还有redis当中做rdb备份用的bgsave指令,会fork一个子进程进行通信,用的就是管道,还有网络socket套接字,用的也是管道,为了实现双向通信,会建立两个管道一个request,一个response,从某种程度讲更像一个简单的捆绑式的生存和消费者模型;
而队列更像是改良版的管道,无形一对一和单向也不需要绑定生产和消费者,可以同时面对多个进程进行数据通信共享,但它强调数据的顺序性FIFO,同时进程要拿到数据需要从队列里调用系统内核api copy到自己的进程空间,这也会影响共享的效率。内存共享则是把管道和队列的一些限制都放开,直接就共享一大片内存段,这里用户进程获取共享内存的数据有两种方式,一种是直接复制一份到自己的虚拟空间也就是正常调用read/write APi接口,而另一种是用mmap做映射也是上面说的零拷贝,用户进程不用多绕一层系统内核的copy直接就操作数据;
而信号量我认为它是一种对内存共享并发读的控制,限制共享的范围或协调多个进程的工作,它本质是基于cpu硬件级别的原子指令操作,可以指定资源的数量来控制来访问的进程数量,而锁本质是基于cpu硬件级别的内存屏蔽指令实现的,个人认为它和信号量区别就是它更强调对资源独占性,通过独占性在某一个时间精度内与其它进程区隔开来达到多线程间在逻辑时间上的先后顺序,所以信号量和锁是同步机制,而队列和内存共享是进程共享方式,管道也是一种共享方式。
随着时代的发展互联网也在发展,各种跨国宽带,各个国家都加入WTO,越来越多的人开始使用互联网,尤其是智能手机带动的移动互联网,带起了一大波应用,人们对应用性能的要求越来越高,一个应用几千万甚至上亿的人在使用,单节点的问题也越发明显,比如受限于计算机硬件发展水平,性能已经成为瓶颈,人们日益增长的需求无法简单的通过堆叠硬件来解决,同时应用功能也越做越大,所有功能都耦合在一起,开发和运维成本直线上升,一个小发生问题,整个系统宕机,严重影响业务连续性和可用性,急需一种扩展性强,能单节点性能问题,把耦合的应用解耦的方案。
这个方案就是分布式系统,把一个大应用拆分成多个小应用,分开部署,这种从空间维度把整体把应用和数据都被拆分,要扩容非常简单加节点就可以,可伸缩性高,也容易维护,但数据被分成多个节点了,而这些节点间数据的同步通过套接字来进行传输,一知道传输就需要时间,前面说了一个数据版本的确定需要通过一个时间范围来界定,当节点间时间相差过大会导致各节点间数据出现多版本可能性越高,到不是怕多版本,怕的是为了解决这些数据在各节点不一致的问题,需要进行一致性和可用性的割舍。
说到这就不得不说CAP里面,Consistency/Availability/Partition tolerance,在分布式系统中,分区容忍性通常是必需的,因为网络故障是不可避免的。这就导致系统设计者需要在一致性和可用性之间做出选择,这也和物理学中,熵是系统混乱程度的量度,系统总是倾向于状态更加混乱(熵增)。在分布式系统中,数据和服务的分散(分区)增加了系统的复杂性和不确定性(类似于熵增),而系统设计的目标之一是通过增加冗余、同步等手段,尽量降低这种“熵增”,维持系统的一致性和稳定性。冗余可以降低因单点故障所导致的不确定性和不确定性,间接提高系统可用性,同步则是保持各副本间的一种手段,常见的分布式系统都有容易,比如kafka多副本,rocketmq的多个broker,mysql集群的主从,redis cluster主从等等,为的就是通过冗余来提高系统的鲁棒性、稳定性和容错能力。
今天先写到这里,后面有机会再写写分布式系统中间件常见的问题以及解决方案,以及设计一个好的中间件需要具备哪些条件,业界一些通用套路又是啥,拜拜!
标签:计算机,FD,互联网,内存,应用,进程,线程,数据,cpu From: https://www.cnblogs.com/killeroppo2/p/18191287