以下内容都是腾讯WeTest《浅析分布式系统》博文的摘抄,说是摘抄其实跟原文照搬差不了多少,原文地址腾讯WeTest浅析分布式系统
1. 什么是分布式系统
要理解这个问题可以从它的适用场景入手,一个非常适用于高承载量互联网业务的系统。什么叫高承载量,简单地可以理解为就是业务有非常高的被访问频率,如一个网站或者一个APP或者一网游或视频直播等每天有百万千万甚至亿级别用户访问。
当一个互联网业务架构不能够满足高承载量的需求时,对于用户的感受就是:网页打开慢,游戏或者视频卡顿。
2. 分布式系统的特性
一个分布式系统应该具备以下几点特性:
高吞吐、高并发、低延迟和负载均衡。
高吞吐
高吞吐指的是业务系统可以同时承载大量的用户,关注的是整个系统能同时服务的用户数,它们由多台服务器协作一起完成。高并发
高并发指的是业务系统在承载海量用户的时候,每台服务器程序都能够尽量多的同时处理多个任务。低延迟
低延迟指的是业务系统在面对海量用户时仍能够很快的返回计算结果。
如果业务系统架构不合理,当有大量用户同时访问系统时很可能造成请求排队,当排队长度过长,还会导致系统内存耗尽、带宽被占满等问题。如果因为排队失败在采取重试的策略,则会进一步增加延迟。
一个合理的分布式系统会采用将用户请求进行分拣和分发的做法,尽快的让更多的服务器来处理用户的请求,但如果分发的层次过多又会增加系统的延迟。负载均衡
互联网业务面向整个互联网的用户,他们位于不同时区,不同地理位置,所使用的网络线路也不一样,考虑到这种情况,分布式系统就需要在不同的地理位置和网络线路中部署服务器。这些不同位置的服务器依然属于一个分布式系统,他们同时处理不同用户相同的业务请求,这即实现了负载均衡。
3. 分布式系统如何承载海量业务
网站业务利用DNS多个IP的A记录提高业务承载量
这里不谈DNS整个分布式系统的工作方式,只说下DNS配置A记录时指定多个IP地址,当用户请求某网站的IP地址时,随机解析一个IP给他,另一用户在去解析这一网站,DNS随机解析另外的IP给这一用户,这样具备不同IP地址的服务器就可以共同承载同一网站的业务。
但是上面这种方式,在很多互联网业务中并不可行。如需要用户进行登录的业务,用户在登录某一服务器后发起多个请求,如果把这些请求随机转发到不同的服务器上,那用户的登录状态会丢失,造成一些请求处理失败。这时就要考虑将用户的cookie或者登录凭据转发给其它服务器,以实现会话保持效果。分布式系统逻辑
典型的三层式分布式系统逻辑包括:接入层、逻辑层、数据层。
- 分布式业务架构示意图
在实际的业务架构中,分布式系统会设计成多个层次的,为了把请求交给正确的进程处理,需要设计很多专门用于转发请求的进程和服务器,这些进程常以Proxy或者Router命名。这些代理很多时候是通过TCP来连接前端和后端,但是tcp有故障后不容易恢复的问题且其网络编程复杂,所以有了更好的进程间通讯机制----消息队列。
为了让分层模式(代理、路由)变得高效简单,又采用了很多相关技术。如:
-
并发模型
计算机中大部分的程序都会处理同时到达的多个请求。程序会同时获得多个输入,需要返回多个输出。在这个处理过程中,还会碰到等待和阻塞的情况,如程序要等待数据库处理完成,等待向另一个进程请求结果等。如果把这些请求一个挨着一个地处理,这些空闲的等待时间将白白浪费,造成用户的响应延迟增加,系统的吞吐量极度下降。所以计算机程序会使用并发模型,同时处理多个请求,常用的并发模型技术有2种。-
多线程
多线程的代码,每个线程中的代码是按先后顺序执行的,但由于同时运行着多个线程,需要给很多数据加锁,避免对同一个数据的处理逻辑出错。但这些锁又可能导致线程出现死锁。多线程的不足:在多线程模型中,线程反复切换会导致不必要的开销,每个线程都需要一个独立的栈空间,在多线程并行运行的时候,这些栈的数据可能需要来回拷贝,需要额外消耗CPU,同时线程越多占用的内存空间也越多。异步回调模型可以解决这些问题。
异步回调模型
异步回调基于非阻塞的I/O操作实现,在代码中就表现为在调用读写函数的时候不会卡在那一句函数调用而是立即返回有无数据的结果。Linux系统中的epoll技术,就是利用底层内核的机制,可以快速查找到有数据可以读写的连接、文件。由于每个操作都是非阻塞的,所以一个进程就可以处理大量并发的请求。也正因为只有一个进程,所以不会出现多线程中两个函数的语句交错执行需要数据锁以保证执行不会出错。
异步非阻塞技术,没有了数据锁,也没有多线程间频繁切换的额外开销,所以它很适合对吞吐量和并发数有较高要求的系统。
-
缓冲技术
缓冲技术可以降低业务延迟。适用场景
如一个网站如果每个HTTP请求都需要去读写mysql数据库,数据库很快就会回为连接数占满而出现停止响应,网站就会出现人一多就卡死的情况。一般情况下数据库允许的最大连接数要远小于web应用的并发连接数的。为了减少对数据库的连接和访问,可以将数据库中查询的结果存放到更快的设备上,如果没有相关联的修改,就可以直接用缓存去响应客户端请求。最典型的缓冲系统
Memcache是最典型的web应用缓冲系统。当用户请求过来时,先检查Memcache中是否有缓存,如果有则用缓存去响应请求,如果没有则去后续查询然后回应请求并会写入Memcache中,下次有同样的请求过来时就直接使用Memcache中的缓存响应请求。
Memcache不能直接组建一个集群系统,如果一个Memcache不够用,就要手工用代码去分配,哪些数据应该去哪个Memcache进程。
Memcache的每笔请求,都要经过网络传输,才能去拉取内存中的数据,而没有将请求者本身的内存利用起来。为了解决这个问题就可以利用LRU算法,把数据放在一个哈希表结构的堆内存中。
因为Memcache不支持集群,为了又能将缓存分布到不同的机器上,因此出现了读写分离技术,即缓存每次写都写到多个缓冲进程中去,但是读的时候可以读取任何一个进程。读写分离的方案很适用于有明显读写不平衡的业务数据。
对于读写没有明显不平衡的业务系统如:社区、游戏,读写分离就不在适用于它们。在这些系统中要同时将请求方本地内存和远端进程的内存缓存结合起来使用,就需要使用到“一致性哈希算法”,以实现一个数据不在同时写往多个缓存进程上,而是按一定规律分布在多个进程上。这样做的好处是当某一个进程失效时,不会影响整个集群中所有的缓存数据,其它缓存进程不用修改缓存记录。
- 存储技术
分布式系统有一个著名的CAP原则:
CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。---------百度百科
在很多互联网业务中并不需要很复杂的关系型数据库,数据表来存储数据,对于索引也只是需要能够根据主索引搜索即可, 所以就产生了NoSQL数据库,早期的有MangoDB,现在比较流行的是Redis。
NoSQL相较于SQL型数据库具有更快、承载量更大的优势。它们只需要按照一条索引来检索和写入数据,利用分布式进行部署,在部署时我们可以按这条主索引来定义数据存放的进程(服务器)。这样就可以很方便的将数据存放在不同的服务器上,形成分布式部署。
4. 分布式系统在管理上存在的问题
- 硬件故障率
拥有众多机器的分布式系统比只有一台或者少量机器的系统,出现硬件故障的机率要高得多。所以分布式系统软件在架构时要考虑到硬件故障的情况,在故障发生时,不要出现一台服务器硬件故障导致事个服务器集群工作不正常。
分布式系统中网络线路出现故障的机率也很高,这时要让系统软件具备自我维护和冗余的功能。
- 资源利用率
分布式系统中如果硬件资源不够用了,可以对硬件进行扩容,增加更多的服务器,但软件层面的扩容要复杂得多,可能需要停掉整个集群,然后修改各种配置才能重新启动集群。
对于线上业务进行扩容,有可能会造成用户数据丢失或者错误。所以线上扩容适用于无状态的服务上,我们都知道HTTP就是属于无状态的连接,所以web服务一般可以进行在线扩容。但对于类似游戏这种有状态的服务几乎是不可能进行在线扩容的。
当硬件资源处于闲置状态时,分布式系统还应该对硬件资源进行缩容,将这部分资源放入其它的业务系统中。
- 软件服务内容更新
为了修改业务系统中的bug,或者发布新的功能等操作,要对软件进行不断的迭代升级。 在只有一台或少量机器的业务系统中还好办,只需要把软件包拷贝过去,然后安装配置即可,但在分布式系统中机器数量众多,所以服务端软件需要开发相应的软件更新,版本升级的功能,预先对于配置文件、命令行参数、系统变量的使用做一些规划,让安装部署的工具运行更快更可靠。
软件版本升级,有时候还涉及到数据格式的更改,如修改数据表结构,所以软件在架构之初就应该考虑到这些,做些预设准备。
客户端程序的升级,有时候会涉及到跟服务器的通信协议改变的问题,而一般客户端升级不会和服务器同步,这就会造成一个问题,网络中需要为不同版本的客户端部署不同的服务端系统。为了避免同时维护多套服务器系统,在软件架构之初时应该使用“版本兼容”的协议定义方式。
-
数据统计
分布式系统的日志数据一般需要集中到一起,统一进行分析统计。当分布式系统规模越来越大时,这个日志数据的量会变得非常大,统计工作非常消耗计算机资源。经典的分布式统计模型有Google的Map Reduce模型,这种模型可以利用大量服务器进行统计工作,但实际使用中由于它统计的数据格式与常见的SQL差别很大,所以最后往往还需要借助MySQL进行更细层面的统计。
5. 解决分布式系统管理性问题的基本手段
- 目录服务
一个自动化程度高的分布式系统会动态保存系统中的进程状态,如自己负责的模块、自己的负载情况、对某些数据的掌握等,还有进程和其它相关进程的对应关系,如IP地址和端口。这样一来整个分布式系统才能让程序自己去做容灾和负载均衡。这个自动的过程就依赖于目录服务。
一个目录服务是用来记录集群中运行的进程的状态的。集群中的进程会自动和目录服务关联,这样系统在容灾、扩容、负载均衡时可以自动根据目录服务里的数据来调整请求的目的地,从而绕开故障机器,连接新的服务器。
zookeeper是用来实现目录服务的开源软件。目录服务需要进行集群部署以防单点故障导致整个分布式系统工作异常。zookeeper可以启动奇数个进程,以组成一个集群。zookeeper的数据存储结构,是一个类似文件目录的树状系统,所以需要利用它的功能,把每个进程都绑定到其中一个分支上,通过检查这些分支来进行请求的转发,还可以在这些分支上标记负载状态,以实现负载均衡。
- 消息队列服务
计算机系统中两个进程要跨机器通讯,需要用到tcp/udp协议,如果直接使用网络API去编写跨进程的程序,需要写大量底层的socket代码,需要处理如何找到交互数据的进程,如何保障数据包不至于丢失,如果通讯的对方进程挂了,或者进程要重启应该如何处理,这些问题在程序开发时都要考虑到。
分布式系统中进程间更有效的通讯模型是消息队列模型。这一模型就是把进程间的交互,抽象成对一个个消息的处理,这些消息本身都有一些队列,也就是管道来对消息进行缓存,每个进程可以访问一个或者多个队列,从里面读取消息(消费)或写入消息(生产)。消息本身的路由,是由存放的队列决定的,这样就把复杂的路由问题,变成了如何管理静态的队列的问题。
- 事务系统
分布式系统中的一个事务可能分布在不同的进程上,任何一个进程都可能出现故障,要在分布式系统上解决事务问题,必须有两个核心工具:一个是稳定的状态存储系统,另一个是方便可靠的广播系统。
一个事务的处理过程都必须在整个集群中可见,需要将事务处理每步的状态都写到目录服务上。
- 自动部署工具
分布式系统最大的需求,是在运行时进行服务容量的变更:扩容或者缩容。而在分布式系统中某些节点故障的时候,需要新的节点来恢复工作。在分布式系统中,一般采用池的方式来管理服务,在有需要的时候将某服务器在不同的服务间进行转换。。
分布式系统中服务的变化需要用到自动的软件部署工具,如Chef,这一可编程的通用部署系统,比rpm更简便。
Docker也具备强大的自动部署能力,下图为Docker的原理示意图:
- 日志服务
服务器端的日志是开发测试,排除BUG,了解软件运行情况非常有用的工具。现代的分布式系统中日志会有一些标准化的规范:- 日志必须是一行一行的。
- 每行日志都应该有一些统一的头部。
- 日志的输出应该是分等级的(Debug、info、warn、error、fatal)。
- 日志的头部应该有一些字段可以用于缩小日志查找范围,如用户ID、IP等(日志染色)。
- 日志还应该具有回滚功能,保持固定大小的多个文件。
在开源界有名为log4x的日志家族,最为出名的是log4j日志组件库。
- 日志收集
在分布式系统中需要有一个系统专门用来收集各服务器上的日志文件,对它们进行统一集中管理,分析并产生预警信息,从而实现对整个分布式系统运行状况的监控。由于日志文件众多,一般会采用分布式文件系统来进行存放源源不断到达的日志,这些日志一般通过UDP协议发送过来。然后在这个系统中还有一个类似Map Reduce的架构以实现对海量日志信息的快速分拣及预警。下图为常用的分布式系统中日志服务的架构示意图:
6. 分布式系统给开发带来的挑战及应对
分布式系统软件开发除了满足业务需求的功能外,还需要一些功能来让多进程的系统稳定可靠地运行,这些功能的实现可以交由微服务框架来完成。
现在流行的微服务框架有EJB(企业JavaBean)、WebService。微服务框架包含了服务进程之间通讯所涉及的消息的路由、编码解码、服务状态的读写等,在开发分布式系统软件时候使用微服务框架可以简化这一过程。 WebService则是把复杂的路由、编解码等操作简化成常见的一次HTTP操作。
分布式系统复杂之处就在于需要把容灾、扩容、负载均衡等功能都融合到跨进程调用里。所以使用一套通用的代码来为所有的跨进程通讯,统一的实现容灾、扩容、负载均衡、过载保护、状态缓存命中等非功能性需求,可以大大简化整个分布式系统的复杂性。
微服务框架在路由阶段会对整个集群所有节点的状态进行观察,如哪些地址上运行了哪些服务的进程,以及这些服务进程的负载如何,是否可用,然后对于有状态的服务,还会使用一致性哈希算法,去尽量试图提高缓存的命中率。当集群中的节点状态发生变化的时候,微服务框架下的所有节点都能尽快的获得这个变化的情况,重新规划服务路由方向,从而实现自动化的路由选择,避开那些负载过高或失效的节点。
- 异步编程工具
为了解决回调函数对于代码可读性的破坏作用,可以采用协程来进行改进,所谓的协程类似于多线程,但区别在于协程不会同时运行,它只是在需要阻塞的地方,用Yield()切换出去执行其他协程,然后当阻塞结束后,用Resume()回到刚刚切换的位置继续往下执行。
Future/Promise模型也是用于改善回调函数的写法,这种写法的基本思路是”一次性把所有回调写到一起”。
lamda模型是另一种改善回调函数的方法,lamda意味着闭包。
- 云服务模型
现在常用的云服务模型有三种:IaaS、PaaS、SaaS。
在分布式系统中要集中管理被不同的网络和硬件切割成小块的计算资源是比较难的。
随着虚拟化技术的发展,可以把被分割的计算单元,更智能的统一起来,最常见的就是IaaS技术,当我们可以用一个服务器硬件运行多个虚拟的服务器操作系统时,需要维护的硬件数量就会成倍下降。而PaaS技术可以为某一种特定的编程模型,统一的进行系统运行环境的部署维护,而不在需要一台台服务器去安装系统去部署应用软件。
随着业务模型成熟到可以抽象为一些固定的软件时,分布式系统会变得更加易用,计算能力不再是代码和库,而是一个个通过网络提供服务的云---SaaS,这样使用者只需要申请一个接口,填上预期的容量额度就能直接使用了。
7. 分布式系统问题的解决方式汇总
8. 总结
腾讯WeTest的这篇干货长文,对于接触Linux生态圈还不长的我来说,实在是干货满满。对于从整体上理解分布式系统很有帮助,所以近乎原版地将它摘抄在此,值得多读几次加深理解。