Java高级架构师教你如何正常掌握分布式系统的大部分技术

分布式系统提高承载量的基本手段

分层模型(路由、代理)

使用多态服务器来协同完成计算任务,最简单的思路就是,让每个服务器都能完成全部的请求,然后把请求随机的发给任何一个服务器处理。最早期的互联网应用中,DNS轮询就是这样的做法:当用户输入一个域名试图访问某个网站,这个域名会被解释成多个IP地址中的一个,随后这个网站的访问请求,就被发往对应IP的服务器了,这样多个服务器(多个IP地址)就能一起解决处理大量的用户请求。

然而,单纯的请求随机转发,并不能解决一切问题。比如我们很多互联网业务,都是需要用户登录的。在登录某一个服务器后,用户会发起多个请求,如果我们把这些请求随机的转发到不同的服务器上,那么用户登录的状态就会丢失,造成一些请求处理失败。简单的依靠一层服务转发是不够的,所以我们会增加一批服务器,这些服务器会根据用户的Cookie,或者用户的登录凭据,来再次转发给后面具体处理业务的服务器。

除了登录的需求外,我们还发现,很多数据是需要数据库来处理的,而我们的这些数据往往都只能集中到一个数据库中,否则在查询的时候就会丢失其他服务器上存放的数据结果。所以往往我们还会把数据库单独出来成为一批专用的服务器。

至此,我们就会发现,一个典型的三层结构出现了:接入、逻辑、存储。然而,这种三层结果,并不就能包医百病。例如,当我们需要让用户在线互动(网游就是典型) ,那么分割在不同逻辑服务器上的在线状态数据,是无法知道对方的,这样我们就需要专门做一个类似互动服务器的专门系统,让用户登录的时候,也同时记录一份数据到它那里,表明某个用户登录在某个服务器上,而所有的互动操作,要先经过这个互动服务器,才能正确的把消息转发到目标用户的服务器上。

又例如,当我们在使用网上论坛(BBS)系统的时候,我们发的文章,不可能只写入一个数据库里,因为太多人的阅读请求会拖死这个数据库。我们常常会按论坛板块来写入不同的数据库,又或者是同时写入多个数据库。这样把文章数据分别存放到不同的服务器上,才能应对大量的操作请求。然而,用户在读取文章的时候,就需要有一个专门的程序,去查找具体文章在哪一个服务器上,这时候我们就要架设一个专门的代理层,把所有的文章请求先转交给它,由它按照我们预设的存储计划,去找对应的数据库获取数据。

根据上面的例子来看,分布式系统虽然具有三层典型的结构,但是实际上往往不止有三层,而是根据业务需求,会设计成多个层次的。为了把请求转交给正确的进程处理,我们而设计很多专门用于转发请求的进程和服务器。这些进程我们常常以Proxy或者Router来命名,一个多层结构常常会具备各种各样的Proxy进程。这些代理进程,很多时候都是通过TCP来连接前后两端。然而,TCP虽然简单,但是却会有故障后不容易恢复的问题。而且TCP的网络编程,也是有点复杂的。——所以,人们设计出更好进程间通讯机制:消息队列。

尽管通过各种Proxy或者Router进程能组建出强大的分布式系统,但是其管理的复杂性也是非常高的。所以人们在分层模式的基础上,想出了更多的方法,来让这种分层模式的程序变得更简单高效的方法。

并发模型(多线程、异步)

当我们在编写服务器端程序是,我们会明确的知道,大部分的程序,都是会处理同时到达的多个请求的。因此我们不能好像HelloWorld那么简单的,从一个简单的输入计算出输出来。因为我们会同时获得很多个输入,需要返回很多个输出。在这些处理的过程中,往往我们还会碰到需要“等待”或“阻塞”的情况,比如我们的程序要等待数据库处理结果,等待向另外一个进程请求结果等等……如果我们把请求一个挨着一个的处理,那么这些空闲的等待时间将白白浪费,造成用户的响应延时增加,以及整体系统的吞吐量极度下降。

所以在如何同时处理多个请求的问题上,业界有2个典型的方案。一种是多线程,一种是异步。在早期的系统中,多线程或多进程是最常用的技术。这种技术的代码编写起来比较简单,因为每个线程中的代码都肯定是按先后顺序执行的。但是由于同时运行着多个线程,所以你无法保障多个线程之间的代码的先后顺序。这对于需要处理同一个数据的逻辑来说,是一个非常严重的问题,最简单的例子就是显示某个新闻的阅读量。两个++操作同时运行,有可能结果只加了1,而不是2。所以多线程下,我们常常要加很多数据的锁,而这些锁又反过来可能导致线程的死锁。

因此异步回调模型在随后比多线程更加流行,除了多线程的死锁问题外,异步还能解决多线程下,线程反复切换导致不必要的开销的问题:每个线程都需要一个独立的栈空间,在多线程并行运行的时候,这些栈的数据可能需要来回的拷贝,这额外消耗了CPU。同时由于每个线程都需要占用栈空间,所以在大量线程存在的时候,内存的消耗也是巨大的。而异步回调模型则能很好的解决这些问题,不过异步回调更像是“手工版”的并行处理,需要开发者自己去实现如何“并行”的问题。

异步回调基于非阻塞的I/O操作(网络和文件),这样我们就不用在调用读写函数的时候“卡”在那一句函数调用,而是立刻返回“有无数据”的结果。而Linux的epoll技术,则利用底层内核的机制,让我们可以快速的“查找”到有数据可以读写的连接\文件。由于每个操作都是非阻塞的,所以我们的程序可以只用一个进程,就处理大量并发的请求。因为只有一个进程,所以所有的数据处理,其顺序都是固定的,不可能出现多线程中,两个函数的语句交错执行的情况,因此也不需要各种“锁”。从这个角度看,异步非阻塞的技术,是大大简化了开发的过程。由于只有一个线程,也不需要有线程切换之类的开销,所以异步非阻塞成为很多对吞吐量、并发有较高要求的系统首选。

int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

缓冲技术

在互联网服务中,大部分的用户交互,都是需要立刻返回结果的,所以对于延迟有一定的要求。而类似网络游戏之类服务,延迟更是要求缩短到几十毫秒以内。所以为了降低延迟,缓冲是互联网服务中最常见的技术之一。

早期的WEB系统中,如果每个HTTP请求的处理,都去数据库(MySQL)读写一次,那么数据库很快就会因为连接数占满而停止响应。因为一般的数据库,支持的连接数都只有几百,而WEB的应用的并发请求,轻松能到几千。这也是很多设计不良的网站人一多就卡死的最直接原因。为了尽量减少对数据库的连接和访问,人们设计了很多缓冲系统——把从数据库中查询的结果存放到更快的设施上,如果没有相关联的修改,就直接从这里读。

最典型的WEB应用缓冲系统是Memcache。由于PHP本身的线程结构,是不带状态的。早期PHP本身甚至连操作“堆”内存的方法都没有,所以那些持久的状态,就一定要存放到另外一个进程里。而Memcache就是一个简单可靠的存放临时状态的开源软件。很多PHP应用现在的处理逻辑,都是先从数据库读取数据,然后写入Memcache;当下次请求来的时候,先尝试从Memcache里面读取数据,这样就有可能大大减少对数据库的访问。

然而Memcache本身是一个独立的服务器进程,这个进程自身并不带特别的集群功能。也就是说这些Memcache进程,并不能直接组建成一个统一的集群。如果一个Memcache不够用,我们就要手工用代码去分配,哪些数据应该去哪个Memcache进程。——这对于真正的大型分布式网站来说,管理一个这样的缓冲系统,是一个很繁琐的工作。

因此人们开始考虑设计一些更高效的缓冲系统:从性能上来说,Memcache的每笔请求,都要经过网络传输,才能去拉取内存中的数据。这无疑是有一点浪费的,因为请求者本身的内存,也是可以存放数据的。——这就是促成了很多利用请求方内存的缓冲算法和技术,其中最简单的就是使用LRU算法,把数据放在一个哈希表结构的堆内存中。

而Memcache的不具备集群功能,也是一个用户的痛点。于是很多人开始设计,如何让数据缓存分不到不同的机器上。最简单的思路是所谓读写分离,也就是缓存每次写,都写到多个缓冲进程上记录,而读则可以随机读任何一个进程。在业务数据有明显的读写不平衡差距上,效果是非常好的。

然而,并不是所有的业务都能简单的用读写分离来解决问题,比如一些在线互动的互联网业务,比如社区、游戏。这些业务的数据读写频率并没很大的差异,而且也要求很高的延迟。因此人们又再想办法,把本地内存和远端进程的内存缓存结合起来使用,让数据具备两级缓存。同时,一个数据不在同时的复制存在所有的缓存进程上,而是按一定规律分布在多个进程上。——这种分布规律使用的算法,最流行的就是所谓“一致性哈希”。这种算法的好处是,当某一个进程失效挂掉,不需要把整个集群中所有的缓存数据,都重新修改一次位置。你可以想象一下,如果我们的数据缓存分布,是用简单的以数据的ID对进程数取模,那么一旦进程数变化,每个数据存放的进程位置都可能变化,这对于服务器的故障容忍是不利的。

Orcale公司旗下有一款叫Coherence的产品,是在缓存系统上设计比较好的。这个产品是一个商业产品,支持利用本地内存缓存和远程进程缓存协作。集群进程是完全自管理的,还支持在数据缓存所在进程,进行用户定义的计算(处理器功能),这就不仅仅是缓存了,还是一个分布式的计算系统。

存储技术(NoSQL)

相信CAP理论大家已经耳熟能详,然而在互联发展的早期,大家都还在使用MySQL的时候,如何让数据库存放更多的数据,承载更多的连接,很多团队都是绞尽脑汁。甚至于有很多业务,主要的数据存储方式是文件,数据库反而变成是辅助的设施了。

然而,当NoSQL兴起,大家突然发现,其实很多互联网业务,其数据格式是如此的简单,很多时候根部不需要关系型数据库那种复杂的表格。对于索引的要求往往也只是根据主索引搜索。而更复杂的全文搜索,本身数据库也做不到。所以现在相当多的高并发的互联网业务,首选NoSQL来做存储设施。最早的NoSQL数据库有MangoDB等,现在最流行的似乎就是Redis了。甚至有些团队,把Redis也当成缓冲系统的一部分,实际上也是认可Redis的性能优势。

NoSQL除了更快、承载量更大以外,更重要的特点是,这种数据存储方式,只能按照一条索引来检索和写入。这样的需求约束,带来了分布上的好处,我们可以按这条主索引,来定义数据存放的进程(服务器)。这样一个数据库的数据,就能很方便的存放在不同的服务器上。在分布式系统的必然趋势下,数据存储层终于也找到了分布的方法。

分布式系统在可管理性上造成的问题

分布式系统并不是简单的把一堆服务器一起运行起来就能满足需求的。对比单机或少量服务器的集群,有一些特别需要解决的问题等待着我们。


总结
最后,每一位读到这里的网友,感谢你们能耐心地看完。觉得收获的话可以点个关注收藏转发一波喔,谢谢大佬们支持!希望在成为一名更优秀的Java程序员的道路上,我们可以一起学习、一起进步!都能赢取白富美,走向架构师的人生巅峰!
想了解学习Java方面的技术内容以及Java技术视频的内容可加群:722040762 验证码:666(CSDN)欢迎大家的加入哟!
 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值