从过去的 OA、CRM、ERP 等单机即可满足要求的系统到现代互联网时代各大公司的分布式、微服务平台,互联网架构正在经历着巨大的变革,技术也在不断的更新迭代。
图片来自 Pexels
这也意味着众多软件开发者们的压力和挑战正在不断的加大,这种新技术更新的速度甚至让我们望而却步。
而我们需要做的恐怕不仅仅是学习那么简单了,更要从宏观的角度根据当前的技术形势及时做出更符合我们发展前景的决定。
这篇文章作者会跟大家一起探究互联网架构的演变历程,并对每个历程中的相关技术及应用做出合理的解释。
希望各位也能参考架构的这些发展过程,结合自己当前的项目架构有一个适当的定位,同时对自己未来应该学习的东西做出明确的计划和安排。
架构演变
大型互联网公司系统的业务模块、技术分布非常复杂,但是架构和技术永远都不是架构师设计出来的,而是跟随公司业务的发展不断进行升级演变出来的,废话不多说请听胖达娓娓道来。
①单机构建系统
这个不用多说,刚开始接触 Java 的同学应该都非常清楚,做毕设或者平时练手最多的图书馆小项目基本都是这种架构。
一个简单的应用,配置一下数据库连接,然后部署到自己电脑的 Tomcat 服务器上,启动之后兴奋的不得了。
②Nginx 负载均衡+服务器集群
试想一下,如果我们一时兴起做了一个个人博客并且部署到了我们的服务器上,采用的是单机的构建方式。
后来因为博客质量高竟然火了,访问量快速增加,单台服务器已经无法满足我们的需求,时不时的就有粉丝抱怨博客没法访问了,是不是很头疼?
稳住,这波不慌,这个时候我建议首先想想如何给那台可怜的服务器泄泄火,让服务器的压力降下来。
有一个办法就是给它加一个或者多个伙伴一块来分摊下压力,把这么多请求分散到每个伙伴身上,从而提高这种负载的能力。
但是伙伴是有了,一系列的问题也就来了,我整理了一下,统一回答如下:
问题一:这么多伙伴,应该识别什么样的指令来接收用户的请求呢?
其实完全不用担心伙伴们识别什么样的指令,只要让它傻傻的站在那等待分配就可以了,因为有一种东西叫做负载均衡器,专门给服务器分配这种请求。
如果你不知道 F5,那你应该知道 Nginx,土豪钱多的公司一般会选择前者这种硬件负载均衡器。
但是大多数互联网公司会选择后者,因为能从软件的角度解决的问题为啥用硬件呢,谁不想省钱啊?
了解 Nginx,必须要知道它的三种功能:
反向代理:了解反向代理,首先要清楚什么是正向代理,相信大家访问国外的学习资源例如某 Hub(你懂的)的时候都用过 FQ 软件 -VPN,这种通过 VPN 访问谷歌、Youtube 等国外网站的过程中,我们知道我们的访问目标服务器是什么,这其实就是正向网络代理。
而反向代理则不同,就像我们上图中所看到的多台服务器,如果经过反向代理,那我们其实并不知道实际访问的服务器是哪一台。
因为我们的请求被前面架设的 Nginx 自动分配给了某一台服务器,就比如说我们打 10086 人工客服,你一定记得“你好先生,我是 10011 号话务员,很高兴为您服务”这样的话。
我们在打电话的时候并不知道由哪一个话务员来为我们服务,这些分配过程都是由 10086 服务台自动进行的,这里的 10086 服务台其实就是我们系统中的反向代理服务器,也即图中的 Nginx。
动静分离:在做 Web 开发的时候大家都清楚,JS、HTML、图片资源、文件资源这些都属于静态资源,供用户直接访问,并不需要编译或者解释,是一些放在那里就可以用的东西。
而 JSP、Java 文件这些东西其实都需要被 Tomcat 服务器解释一遍才能被机器识别,但是如果把它们都放在一起供用户访问,那每台服务器的压力岂不是很大?
这个时候我们就可以做动静的分离,将这些静态的文件放置到 Nginx 服务器上,后面的 Tomcat 服务器用来放动态的 JSP、Java 文件,这样的话就变向的给服务器降低了压力。
负载均衡:这个很明显了,简单来说,通过架设 Nginx 服务器,经过一定的均衡算法将用户的请求合理分发给后面的服务器。
这个过程很好的降低了请求负载,让每一台服务器都能舒舒服服的承载请求,做好自己的工作。
问题二:能确定伙伴之间公平分散请求吗?
这个问题就具体到了 Nginx 的均衡算法问题,只有通过合适的算法均衡用户请求到每台服务器上才能保证服务器不打架不撂挑子,否则其中某台服务器不高兴突然间罢工,剩下的服务器可就遭殃了。
其实 Nginx 均衡算法总共有十种,但是常用的一般是下面几种:
LC 算法(最近最少使用连接算法):这种算法的规则就是判断哪一台服务器一定时间段内的连接数比较少,就把用户请求分发给它,其实就是谁的活少分配给谁,不能让他太闲也不能让其他服务器太忙,否则就会掐架了。
轮询算法:轮询这种算法还是比较公平的,类似我们上学的时候排了一张值日表,周一的时候是小红,周二的时候是小明等等等等,这样就把活平均分配给了每一个人也即每一台服务器。
IP_Hash 算法:这种算法是通过 IP 取模的方式制定服务器,首先通过 IP 字段转换成 Hash 值,将取到的 Hash 值与负载服务器的总数取模,按照模值获取负载 IP 列表中的服务器,最终确定是哪一台服务器来承载这次请求。
这种方式因为 IP 的 Hash 值一致性原因,每一台 IP 访问的都是固定的服务器,用的是同一台服务器上的 Session,从而解决了 Session 一致性的问题。
问题三:这么多服务器怎么返回用户的请求呢?
这个问题换一种问法就是通过什么样的集群搭建模式来处理网络问题,常用的包括下面几种:
NAT 模式:也称为网络地址传输模式,用户在实际访问项目的时候实际上并不是直接去访问 Tomcat 服务器,而首先要经过第一台 Nginx 服务器。
但是这台服务器的 IP 是虚拟 IP,真实要访问的 IP 其实是后面的 Tomcat 服务器 IP。
那么在这一步就需要根据均衡算法在配置中取出后面 Tomcat 服务器的真实 IP 并做网络跳转,已达到访问的目的。
在返回用户请求的时候,也是如此,必须通过 Tomcat 服务器的网络跳转访问到 Nginx,继而将请求返回到用户方。
DR 模式:也称为直接路由模式,这种方式相较于 NAT 模式有一个区别就是在返回用户请求的时候,不再通过中间服务器进行转发,而是直接转发给了用户。
这样做的目的其实也提高了网络传输的速度,降低了 Nginx 服务器的压力。
问题四:用户每次都去跟不一样的伙伴勾兑,这次找伙伴 1,下次找伙伴 2,那怎么保证 Session 一致呢?
这种情况是做负载均衡经常遇到的一个问题,如果不做处理,经常会遇到 Session 丢失的问题,处理这个问题一般有下面几种方法:
IP_Hash 算法固定 Session:就像上面均衡算法所说的,通过 IP 的 Hash 值取模,固定访问某台服务器。
这样就确保了用户的 Session 每次访问都保存在同一台服务器上,不会出现找不到的现象,从而实现 Session 一致性。
Session广播:也称为 Session 复制,就是指每个用户登录之后,只要是访问了我的服务器记录了 Session,就会把 Session 的信息在所有的服务器上复制一份。
这样就能保证每台服务器都包含这个用户的 Session,当然这也是非常浪费的。
Session 持久化:就是将 Session 的信息保存到数据库、文件或者内存中,用这种持久化的方式将其保存到一台公共服务器上。
这样就能确保了 Session 一致性,而我们一般采用的方式就是将其保存到 Redis 中,方便存取而且内存读取的效率很高。
客户端存储:基于 Cookie 实现,将 Session 直接存储到 Cookie 中,这种方案其实不是很安全,虽然能够做到加密,但是道高一尺魔高一丈,总会有方法破解,而且 Cookie 最多只能为 4K,大小受到限制。
上面讲到的这种架构方式,确实能够短期内解决个人博客访问量激增带来的问题,但是有没有想过一个问题,Nginx 也是一台服务器,如果他挂了怎么办?还有谁能来给我们分配任务呢?难道让这些伙伴干瞪眼嘛?
这种情况引出了一个面试题:如何避免单点故障?不慌,接着来看下一种方案。
③HA 高可用+负载均衡+服务器集群
想要解决这种问题,大家一定可以想到,再加一台 Nginx 不就行了嘛?
没错,引入 HA 高可用就能解决这个问题,HA 高可用是目前企业防止核心计算机系统因故障停机的最有效手段,但是如何实现这种高可用让两台 Nginx 互相切换呢?
下面还需要了解两个新鲜的技术:LVS+KeepAlived。
LVS 实现负载均衡:看到这个小标题,有人可能要喷我,说你都用了 Nginx 做了负载均衡了,为啥还要用 LVS 这种东西呢?
别着急首先了解下LVS:LVS 的英文全称是 Linux Virtual Server,即 Linux 虚拟服务器。
它是由国防科大毕业的章文嵩博士开展的一个开源项目,在 Linux 内存 2.6 版本中已经成为内核的一部分。
LVS 主要用于多服务器的负载均衡,工作在七层模型中的第四层网络层,可以实现高性能,高可用的服务器集群技术,并且可把许多低性能的服务器组合在一起形成一个超级服务器。
它的配置非常简单,有多种负载均衡的方法,而且稳定可靠,即使在集群的服务器中某台服务器无法正常工作,也不影响整体效果,另外它的可扩展性也非常好。
那么这种解释能解决疑惑吗?当然不能,且看下面:
由于 LVS 属于内核级别的优化,而且工作在网络层,当做为负载均衡服务器的 Nginx 和 LVS 处理相同的请求时,所有的请求和响应流量都会经过 Nginx。
但是使用 LVS 时,仅请求流量经过 LVS 的网络,响应流量由后端服务器的网络返回。
这样的话,如果有大量的请求出现,那因为带宽问题,LVS 的效率就会有显著的表现,Nginx 的瓶颈也就出现了。
但是仅仅使用 LVS 作为负载均衡的话,一旦后端接受到请求的服务器出了问题,那么这次请求就失败了。
但是如果在 LVS 的后端在添加一个 Nginx(或者多个),让 Nginx 做动静分离和反向代理,每个 Nginx 后端再有几台应用服务器。
那么结合两者的优势,既能避免单 Nginx 的流量集中瓶颈,又能避免单 LVS 时请求失败的问题,何乐而不为呢?
KeepAlived 实现 HA 高可用:KeepAlived 从字面意思上来讲就是保持活着,比如说我们两台 Tomcat 服务器就是两个小伙伴,两个小伙伴商量着,要不然咱们一个人干活一个人歇着。
但是时常的要互相询问一句:“老铁?你累吗?还能行不?”,如果这个时候他回复还行,那么我就安心玩我的就行了。
但是如果他一直没搭理我,怎么问怎么不应声,那它应该是累了,你是不是需要把他的活接过来啊,没人干肯定不行。
严肃点哈,KeepAlived 技术其实主要就是通过发送心跳包的方式,在每台服务器之间进行状态侦查,如果发现有宕机或者停止运行的服务器,立刻让闲置服务器运行起来实现主备复制和转移。
当然这其中还需要一种容错技术来将系统运行的状态恢复到本应该有的状态,避免因为某台服务器的宕机影响执行的结果。
④CDN 内容分发网络+Varnish 服务器集群
随着个人博客用户量的不断提升,我们的项目虽然已经可以应付的了这么多用户量了,但是略微还有点慢,又或者有点卡。
而且还有一个问题,中国的网络环境一般都是南方多用电信网络,北方多用移动联通的网络,电信的网络访问联通的网络明显能感觉的出来会特别卡。
如果我们的博客放到了北方的联通网络上,而大量的用户来自于南方,对用户来讲岂不是很痛苦。
每次访问的时候下载一些静态资源都会特别的慢,时间久了,可能会损失大量的用户。
如何解决这些卡、慢的问题呢,下面再来看几种优化技术:
CDN 内容分发网络:CDN 内容分发网络是构建在网络之上的内容加速器,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
也就是说,如果我们系统中有一个 jquery.min.js,直接放在我们北方的网通服务器上,南方的用户下载会特别的慢。
但是如果我们直接用某个高速 CDN 服务器上的 Jquery 资源引入,那用户下载的速度岂不是会快速提高,用户体验也会变得更好。
Varnish 服务器集群:Varnish 这个词很多人都很陌生,其实它就是一个 HTTP 加速器和反向代理服务器,也是为了优化方案而引入的技术。
这种技术一般被应用在 Nginx 后面,在 Tomcat 的前面,主要用来优化 Nginx 的动静分离功能。
之前提到将 HTML、JS 这些静态的资源都放置到 Nginx 服务器中,Java、JSP 等文件放置到 Tomcat 服务器中。
这样能够更好的分摊 Tomcat 服务器的压力,但是这个地方还有优化的空间,想一想是否在开发过程中存在一些 JSP 文件、Java 文件基本上是不变的。
也就是说不管怎么调用,这些东西短期内都是固定的,那我们能不能把这些文件也提出来,让 Nginx 直接调用以降低 Tomcat 服务器压力呢?
答案是可以的,直接将一些短期内不变得文件配置进 Varnish 服务器中即可。
⑤数据库读写分离
之前的部分我们对 Tomcat 服务器左侧进行了优化,但是右侧的数据库还是孤零零的只有一台。
这样的话,个人博客项目还好只有读操作,能够应付得了,但是如果我们扩展业务,将博客做成了大型的博客平台,有大量的用户频繁进行读写操作,那我们的数据库还能应付嘛?
肯定需要优化了,首先我们会想到的就是把数据库的读写进行分离。数据库读写的分离一般需要设置主从数据库,主库只用来写数据,从库用来读取数据。
而两个数据库之间则需要实现数据的同步,否则会出现数据差异,实现数据库同步的方法有很多,MySQL 提供了一种方法是 Binlog,目前还是比较普遍的,具体的操作配置步骤这里不再描述,以后有机会会尝试写写。
⑥NOSQL 数据库+分布式搜索引擎
读写分离做完之后,过不了多久会发现,如此多的用户读写数据,主从库的压力也在日益攀升,变得越来越大,那么如何优化读取、写入数据的速度呢?
引入 Redis 数据库:既然我们的 MySQL 数据库读写压力那么大,那么我们就在它的前面添加一层 NOSQL 内存数据库作为盾牌。
Redis 作为常用的 NOSQL 数据库一直以来深受大家的欢迎,而且因为是内存中读写数据,所以效率也是非常的高。
将它放到关系数据库的前面,用来存放一些高频率、热点的常用搜索数据,能够抵抗大量的搜索压力。
这样的话在读写分离,Redis 分摊压力的情况下,MySQL 的数据库压力会大规模降低。
增加分布式搜索引擎:你以为这就完了吗,不,还不够,为了优化搜索的速度,给用户带来更好的体验效果,引入分布式搜索引擎才是好的选择,目前行业内使用广泛的分布式搜索引擎非 Elasticsearch 莫属。
Elasticsearch 技术简称 ES,是一个基于 Lucene 的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于 RESTful Web 接口。
Elasticsearch 是用 Java 开发的,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。
Elasticsearch 分布式搜索引擎可以进行分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索,并且可以扩展到上百台服务器,处理 PB 级别的结构化或非结构化数据。
这样的话我们就可以通过这种方式构建索引,再去查询就更加减轻了关系数据库的压力。
⑦NOSQL 数据库(HA)+分库分表+MyCat
随着博客平台的发展,海量用户的暴增,搜索类目的多样性,用户体验的实时性,对网站提出了更高的要求,必须满足高并发、高可用、高性能的要求才能继续业务的发展。
对于我们程序员而言要做的仍然需要更新当前的架构技术,以面对更强大的冲击和考验,那么我们还要如何优化我们的系统呢?第七个阶段我们又要引入三种方案:
NOSQL 数据库的 HA:通过在 MySQL 服务器前面添加 Redis 服务器确实能够抵御不少的对 MySQL 数据库的读写压力。
但是对于前面的盾牌来讲,难道它就没有压力吗,怎么降低 Redis 服务器的压力呢?
首先我们会想到去配置 Redis 集群的方式加一台或者几台服务器,但是配置 Redis 集群其实经常会出现错误。
而且对于 Redis 集群来说,经常要求 Redis 服务器可以自动扩容,为了解决这些问题更好的管理 Redis 集群,我们可以选择引入分布式 Redis 解决方案。
前段时间 Redis 官方的 3.0 出了稳定版,3.0 就可以支持集群管理的功能。这里选择的是国产的分布式 Redis 解决方案-Codis。
Codis 是一个分布式 Redis 解决方案,对于上层的应用来说,连接到 Codis Proxy 和连接原生的 Redis Server 没有明显的区别。
上层应用可以像使用单机的 Redis 一样使用,Codis 底层会处理请求的转发,不停机的数据迁移等工作,所有后边的一切事情,对于前面的客户端来说是透明的,可以简单的认为后边连接的是一个内存无限大的 Redis 服务。
分库分表:随着业务的递增,分库分表的技术也需要运用到如此庞大的项目中,主要是因为各种业务表正在变得越来越大,例如用户表、博客表等等。
你会想到如果每个业务表都能分开存放那该多好,添加多个数据库服务器,每个服务器负责一块业务表的维护管理。
数据库 1 存放用户数据,数据库 2 存放博客信息,从而达到进一步细分数据库的目的,而这种拆分数据库的方式就叫做垂直拆分方式。
还有一种拆分方式是这样的,比如说我们的用户表数据量非常非常大,一张表数据达到了 8 千万,那我们查询读取这张表的时候会不会特别慢。
即便你已经垂直拆分到了一个服务器上进行管理,但是你仍然不能解决一张表 8 千万的问题,那么如何解决呢?
聪明的你会想到分表存放,这种方式就是水平拆分方式,将用户表分成表 1、表 2、表 3......
通过这样的方式你可以将这 8 千万的用户分到子表中,而具体的分表方式可以采用很多种方案,例如进行 ID 取模,具体方式由于篇幅问题不再描述。
MyCat:分库分表之后,你会发现数据库压力真的变小了很多,但是也会有很多不方便的事情。
比如说:
- 分布式事务提交问题。
- 分库分表的运维和获取问题。
- 跨数据库的 Join 聚合查询问题。
- MySQL 中自增字段(AUTO_INCREMENT)还能用吗?
- 某些约束条件在分库分表的环境下会不会特别复杂了?
这个时候你会用到分库分表中间件 MyCat,它是一个彻底开源的,面向企业应用开发的大数据库集群,支持事务、ACID、可以替代 MySQL 的加强版数据库。
一个可以视为 MySQL 集群的企业级数据库,用来替代昂贵的 Oracle 集群 ,一个融合内存缓存技术、NoSQL 技术、HDFS 大数据的新型 SQL Server,结合传统数据库和新型分布式数据仓库的新一代数据库中间件产品。
⑧分布式文件系统
在系统的发展过程中,会发现有一些图片、视频、Excel 等不同格式的大文件也需要做出处理。
比如说大型系统中的用户头像,8 千万的用户就需要有 8 千万的头像,每张头像都需要占用一定的存储空间(一般来说 4K 到几百 M 都有可能)。
那么如何去处理这些文件的存储呢?保存到数据库中技术上完全可以,但是仅限于说说哈,如果实际这么做了可能你的系统会面临很大的压力。
为了解决这种问题,就出现了分布式文件系统这样的技术,典型比较通用的包括 MogileFS、FastDFS。
MogileFS 是一个开源的分布式文件存储系统,是由 LiveJournal 旗下的 Danga Interactive 公司开发。
目前使用 MogileFS 的公司非常多,如日本排名靠前的几个互联公司以及国内的 Yupoo(又拍)、digg、豆瓣、大众点评、搜狗等,分别为所在的组织或公司管理着海量的图片。
以大众点评为例,用户全部图片均有 MogileFS 存储,数据量已经达到 500TB 以上。
FastDFS 是由阿里数据库大神开发,是一个开源的轻量级分布式文件系统,它对文件进行管理。
功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。
⑨应用服务化拆分+消息中间件
如果思考过京东、美团、淘宝等等这些大型互联网公司的业务模块,你会发现他们每增加一块业务,其实就是在增加一个业务组件。
比如说某生活服务公司的业务分布如图所示:
在上图中会发现业务组件部分包括了用户、支付、搜索、推荐、地理位置等等。
其中的每一个业务组件其实都对应着一项服务,而且都有专门的数据库服务器进行维护管理,也就是之前提到的分库分表。
而分库分表拆分的是数据,那如何对业务进行拆分,将每一种业务分成一种服务,需要什么服务就去调用什么服务,从而让系统更加的专一和精确。
如果对应到我们的架构上,就需要在数据层和应用层添加一个层面——服务组件层,其实这种架构方式就是应用服务化拆分。
提到应用服务化拆分,就不得不提及服务化治理框架,这里就需要引入三种主流的应用服务化技术:
- Zookeeper
- Dubbo
- 消息解耦异步的消息中间件技术:MQ 消息队列
Zookeeper:一般来说,Zookeeper 常常跟 Dubbo 是配合使用的,因为 Dubbo 需要进行服务注册。
而 ZooKeeper 一个最常用的使用场景就是用于担任服务生产者和服务消费者的注册中心。
服务生产者将自己提供的服务注册到 ZooKeeper 中心,服务的消费者在进行服务调用的时候先到 ZooKeeper 中查找服务,获取到服务生产者的详细信息之后,再去调用服务生产者的内容与数据。
Dubbo:提到服务治理,Dubbo 绝对是一个优秀的服务治理框架,它可以通过透明化的远程方法调用。
就像调用本地方法一样调用远程方法,只需简单配置即可,这样不管是扩展了几种业务组件,都可以像调用本地方法一样调用其他的业务方法。
它使用起来非常方便,用过的人一般都知道,只需要加一个注解就可以使用其方法,而且调用的效率非常高。
MQ 消息队列:MQ 消息队列已经逐渐成为企业 IT 系统内部通信的核心手段。
它具有低耦合、可靠投递、广播、流量控制、最终一致性等一系列功能,成为异步 RPC 的主要手段之一。
比如在电商系统中,来自订单下载转化后的大量订单需要推送到物流配送管理系统中,就需要通过 MQ 这种技术来处理让物流系统慢慢的按照数据库能承受的并发量,从消息队列中拉取并配送订单,从而让流程更加有序、稳定。
当今市面上有很多主流的消息中间件,如老牌的 ActiveMQ、RabbitMQ,炙手可热的 Kafka,阿里巴巴自主开发 RocketMQ 等。
⑩微服务架构
上一个架构演变的阶段作为目前主流的系统架构来说,完全可以抵住当前流量所带来的压力,但是未来随着业务和用户量的增长,仍然还会有更大的挑战出现。
2012 年微服务的概念被提了出来,它的基本思想在于考虑围绕着业务领域组件来创建应用,这些应用可独立地进行开发、管理和加速。
在分散的组件中使用微服务云架构和平台,使部署、管理和服务功能交付变得更加简单。
在微服务架构中,每个服务都是自我包含的,并且实现了单一的业务功能,而这种架构也必将成为未来的发展趋势,目前也有很多微服务的框架已经落地并迅速发展,比如说 Spring Cloud 微服务框架。
Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发。
如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。
在未来的微服务盛行的趋势下,Spring Cloud 也必将成为 Java 程序员必须掌握的框架之一了。
总结
在这篇互联网架构的演变中,我只是简单的对一些技术进行了说明,重点说明的是每一层的架构所引入的技术到底是为什么会出现在这一层,具体解决了什么样的实际问题。
不管怎么样,技术发展如此快速的时代,我们每一个程序员都不应该一直埋头于技术的研究,偶尔抬起头看看架构的发展和未来的趋势,或许对我们的程序之路有一个更宏观的了解。只有这样,我们才能离职业危机更远。