前言下面我们以JavaWeb为例搭建一个简单的电子商务系统,看看这个系统是如何一步步演进的。系统功能:用户模块:用户注册和产品管理模块:产品展示和交易管理模块:交易创建和文本管理阶段1.单机网站建设在网站的早期阶段,我们经常运行我们所有的程序和单个计算机软件上的程序。此时我们使用一个容器,比如Tomcat、Jetty、Jboss,然后直接使用JSP/Servlet技术,或者使用一些开源框架,比如Maven+Spring+Struts+Hibernate,Maven+Spring+SpringMVC+Mybatis。然后选择一个数据库管理系统来存储数据,比如MySQL、SqlServer、Oracle,然后通过JDBC连接和操作数据库。以上所有的软件,包括数据库和应用程序,都加载在同一台机器上,运行着应用程序,可以看成是一个小系统。此时系统运行结果如下:Phase2.应用服务器与数据库分离随着网站的上线,访问量逐渐增加,服务器的负载也逐渐增加。在服务器不超载的情况下,我们应该准备升级网站的负载能力。如果代码层面难以优化,在不提升单机性能的情况下,增加机器是一个很好的方法。不仅可以有效提高系统的负载能力,而且性价比高。额外的机器有什么用?这时候我们可以将数据库服务器和Web服务器分开,这样既增加了单机的负载能力,又提高了容灾能力。应用服务器与数据库分离后的架构如下图所示:阶段三:应用服务器集群随着访问量不断增加,单个应用服务器无法满足需求。假设数据库服务器没有压力,我们可以将应用服务器从一台改为两台或多台,将用户请求分散到不同的服务器上,从而增加负载能力。但是多个应用服务器之间没有直接交互,都依赖数据库对外提供服务。著名的故障转移软件是KeepAlived。KeepAlived是一款类似于三四七层交换机制的软件。它不是针对特定软件故障转移的专属产品,而是可以应用于各种软件的产品。KeepAlived结合ipvsadm还可以做负载均衡,可谓神器。我们以添加应用服务器为例。添加后的系统结构图如下:当系统演进到这里时,会出现以下四个问题:谁将用户的请求转发给具体的应用服务器?谁是转发者?可以使用算法和策略吗?应用服务器如何返回用户的请求?如果用户每次访问不同的服务器,如何保持会话的一致性?针对以上问题,常见的解决方案如下:1.负载均衡问题一般有以下5种解决方案:1)、HTTP重定向HTTP重定向是应用层的请求转发。用户的请求实际上已经到达了HTTP重定向负载均衡服务器。服务器要求根据算法重定向用户。用户收到重定向请求后,再次请求真实集群。优点:使用方便;缺点:性能较差。2)DNS域名解析负载均衡DNS域名解析负载均衡是指当用户请求DNS服务器获取该域名对应的IP地址时,DNS服务器经过负载均衡后直接给服务器IP。优点:交给DNS,我们不需要维护负载均衡服务器;缺点:当一个应用服务器挂掉时,无法及时通知DNS,DNS负载均衡的控制权在域名服务商手中,网站无法做更多的改进和更新。强大的管理。3)当用户的请求到达反向代理服务器(已到达网站机房)时,反向代理服务器根据算法将用户的请求转发给特定的服务器。常用的Apache和Nginx都可以作为反向代理服务器。优点:易于部署;缺点:代理服务器可能成为性能瓶颈,尤其是在一次上传大文件时。4)、IP层负载均衡请求到达负载均衡器后,负载均衡器修改请求的目的IP地址,实现请求的转发,实现负载均衡。优点:性能更好;缺点:负载均衡器的带宽成为瓶颈。5)、数据链路层负载均衡请求到达负载均衡器后,负载均衡器修改请求的MAC地址,实现负载均衡。与IP负载均衡不同的是,当请求访问完服务器后,直接返回给客户端。无需通过负载均衡器。2、集群调度和转发算法1)、rr轮询调度算法,顾名思义就是轮询和分发请求。优点:实现简单缺点:没有考虑每台服务器的处理能力2)、wrr加权调度算法我们为每台服务器设置权重Weight,负载均衡调度器根据权重调度服务器,服务器被访问的次数调用是与权重成正比的。优点:考虑服务器的不同处理能力3)、sh原地址hash算法提取用户IP,根据hash函数得到一个key,然后根据静态映射表查对应的值,即target服务器IP。如果目标机器过载,则返回空。优点:实现同一个用户可以访问同一个服务器。4)dh目标地址散列算法原理同上,只是现在提取目标地址的IP进行散列。优点:实现同一个用户可以访问同一个服务器。5)lc最少连接算法先将请求转发给连接数少的服务器。优点:使集群中各服务器的负载更加均匀。6)wlc加权最少连接算法在lc的基础上给每台服务器增加一个权重。算法为:(活跃连接数*256+不活跃连接数)÷权重,计算出的值较小的服务器优先被选中。优点:可以根据服务器的能力分发请求。7)sed的最短期望延迟算法其实和wlc类似,不同的是没有考虑不活跃的连接数。算法为:(活跃连接数+1)*256÷权重,优先选择计算值较小的服务器。8)、nq永不排队算法改进了sed算法。我们想一下在什么情况下我们可以“永不排队”,即当服务器连接数为0时,那么如果服务器连接数为0,balancer会直接把请求转发给它,而不经过计算sed。9)、LBLC基于局部最小连接算法。负载均衡器根据请求的目的IP地址找出最近使用IP地址的服务器,并将请求转发给它。如果服务器过载,最好使用最少连接数算法。10)、LBLCRwithreplication基于localityleastconnectionalgorithm的负载均衡器根据请求的目的IP地址找出该IP地址最近使用的“服务器组”。注意不是特定的服务器,然后用最少的连接数从组中挑出特定的服务器,将请求转发给它。如果服务器过载,则根据最少连接数算法,从集群中不属于服务器组的服务器中找出一台服务器,加入服务器组,然后转发请求。3.集群请求返回方式的问题1).NAT负载均衡器接收用户的请求并将其转发到特定的服务器。服务器处理完请求后返回给平衡器,平衡器再返回给用户。2).DR负载均衡器接收用户的请求并将其转发到特定的服务器。服务器出来播放请求后,直接返回给用户。系统需要支持IPTunneling协议,难以跨平台。3)、TUN同上,但不需要IPTunneling协议。它具有良好的跨平台性能,可以被大多数系统支持。4、集群会话一致性1)SessionSession是将同一个用户在某个会话中的请求分配给固定的服务器,这样我们就不需要解决跨服务器的会话问题。常见的算法有ip_hash算法,也就是上面提到的两种hash算法。优点:实现简单;缺点:应用服务器重启后session消失。2)、SessionReplicationSession复制就是在集群中复制session,让每台服务器保存所有用户的session数据。优点:减轻负载均衡服务器的压力,不需要实现ip_hasp算法转发请求;缺点:复制时网络带宽开销大,Session占用内存大,访问量大时浪费。3)会话数据集中存储会话数据集中存储就是利用数据库来存储会话数据,实现会话与应用服务器的解耦。优点:与Session复制方案相比,大大降低了集群间带宽和内存的压力;缺点:需要维护存储Session的数据库。4)、CookieBaseCookieBase是将Session存储在Cookie中,浏览器告诉应用服务器我的session是什么,也实现了session和应用服务器的解耦。优点:实现简单,基本免维护。缺点:cookie长度限制,安全性低,带宽消耗。值得一提的是,Nginx目前支持的负载均衡算法有wrr、sh(支持一致性哈希)、fair(lc)。但是,如果将Nginx用作平衡器,它也可以用作静态资源服务器。keepalived+ipvsadm比较强大。目前支持的算法有:rr、wrr、lc、wlc、lblc、sh、dh。Keepalived支持集群模式:NAT、DR、TUN。Nginx本身不提供会话同步解决方案,而Apache提供会话共享支持。解决了以上问题后,系统结构如下:阶段4,数据库读写分离上面我们一直假设数据库负载是正常的,但是随着访问量的增加,数据库负载也在慢慢增加.那么有人可能立马想到,就像应用服务器一样,把数据库一分为二,然后做负载均衡。但是对于数据库,事情就没那么简单了。如果我们简单地将数据库一分为二,然后将数据库请求分别加载到机器A和机器B上,显然会造成两个数据库数据不一致的问题。所以对于这种情况,我们可以先考虑使用读写分离和主从复制。读写分离后的系统结构如下:这种结构变化也会带来两个问题:主从数据库的数据同步。应用于数据源的选择。解决方案:使用MySQL内置的Master+Slave方式实现主从复制。使用第三方数据库中间件,如MyCat。MyCat由Cobar开发而来,Cobar是阿里开源的数据库中间件,后来停止开发。MyCat是国内一款比较优秀的MySql开源数据库分库分表中间件。第五阶段,利用搜索引擎缓解阅读数据库的压力。如果数据库是用来读数据库的,对于模糊搜索往往是无能为力的。即使做了读写分离,也没有解决这个问题。以我们提到的交易网站为例,发布的产品存储在数据库中,用户最常使用的功能是搜索产品,尤其是根据产品标题查找对应的产品。对于这种需求,我们一般会使用like函数来实现,但是这种方式代价很大,而且结果很不准确。此时我们可以利用搜索引擎的倒排索引来完成。搜索引擎的优点:可以大大提高查询速度和搜索准确率。搜索引擎开销的引入带来了大量的维护工作。我们需要自己实现索引的构建过程,设计全/增的构建方式来满足非实时和实时的查询需求。需要维护搜索引擎集群搜索引擎不能替代数据库,它解决的是某些场景下准确、快速、高效的“读取”操作,是否引入搜索引擎,需要综合考虑整个系统的需求。引入搜索引擎后的系统结构如下:Stage6.使用缓存缓解读取数据库的压力常用的缓存机制有页面级缓存、应用数据缓存和数据库缓存。随着应用层缓存和数据库层缓存访问量的增加,越来越多的用户访问了热门内容的同一部分。对于这些热门内容,没必要每次都从数据库中读取。我们可以使用缓存技术,比如Google开源的缓存技术Guava或者Memecahed作为应用层缓存,或者Redis作为数据库层缓存。另外,在某些场景下,关系型数据库并不是很适合。比如我想做一个“限制每天输入错误密码的次数”的功能。思路是记录用户在登录IP时是否登录错误以及错误次数,那么这个数据应该放在哪里呢?如果放在内存中,显然会占用过多的内容;如果是放在关系型数据库中,那么就需要创建数据库表,恢复对应的Javabean,还要写SQL等等。并分析我们要存储的数据,无非就是{ip:errorNumber}这样的key:value数据。对于这类数据,我们可以使用NOSQL数据库代替传统的关系型数据库。页面缓存除了数据缓存,还有页面缓存。比如使用HTML5的localstroage或者cookies。除了页面缓存带来的性能提升外,对于并发访问、页面替换频率低的页面,应尽可能使用页面静态技术。优点:减轻数据库压力,大大提高访问速度;缺点:需要维护缓存服务器,增加了编码的复杂度。值得一提的是,缓存集群的调度算法与上面提到的应用服务器和数据库不同。一致性哈希用于提高结果的概率。添加缓存后的系统结构如下:Stage7.数据库水平拆分和垂直拆分我们的网站发展到现在,交易数据、商品数据、用户数据还在同一个数据库中。虽然采用了增加缓存和读写分离的方式,但是随着数据库压力的不断增大,数据库数据量的瓶颈也越来越突出。这时候,我们可以选择纵向或者横向拆分数据。数据的垂直拆分垂直拆分是指将数据库中的不同业务数据拆分到不同的数据库中。结合现在的例子,就是把交易数据、商品数据、用户数据分开。优点:解决了所有业务放在一个数据库中的压力问题;可以根据业务特点做更多的优化。缺点:需要维护多个数据库的状态一致性和数据同步。问题:需要考虑原来的跨业务交易;跨数据库加入。问题解决:在应用层尽量避免跨数据库的分布式事务。如果一定要跨数据库,尽量在代码中控制。通过第三方中间件解决,比如上面提到的MyCat,MyCat提供了丰富的跨库Join解决方案,具体请参考MyCat官方文档。垂直拆分后的数据结构如下:数据水平拆分数据水平拆分就是将同一张表中的数据拆分到两个或多个数据库中。数据水平拆分的原因是某项业务的数据量或更新量已经达到了单个数据库的瓶颈。这时可以将表拆分到两个或多个数据库中。优点:如果能够克服以上问题,那么我们就可以很好的应对数据量和写入量的增长。问题:访问用户信息的应用系统需要解决SQL路由的问题,因为现在用户信息分为两个数据库,在进行数据操作的时候需要知道要操作的数据在哪里。主键也有不同的处理方式,比如原来的自增字段现在不能简单的继续使用。如果需要分页查询,会比较麻烦。问题解决:我们还是可以通过第三方中间件解决问题,比如MyCat。MyCat可以通过SQL解析模块来解析我们的SQL,然后根据我们的配置将请求转发到具体的数据库。我们可以通过UUID保证的自定义ID方案来解决它。MyCat还提供了丰富的分页查询方案,比如先从各个数据库进行分页查询,然后合并数据进行分页查询等。数据水平拆分后的结构如下:Stage8.应用的拆分微服务拆分应用随着业务的发展,业务越来越多,应用越来越大。我们需要思考如何避免让应用程序越来越臃肿。这就需要将应用拆解,从一个应用拆成两个或更多。仍然使用我们上面的示例,我们可以将用户、产品和交易分开。成为“用户、商品”和“用户、交易”两个子系统。拆分结构:问题:这种拆分之后,可能会有一些相同的代码,比如用户相关的代码,商品和交易都需要用户信息,所以两个系统都保留了类似的操作用户信息的代码。如何保证这些代码可以复用,是一个需要解决的问题。解决问题:走面向服务的SOA路线,解决频繁的公共服务。走SOA面向服务治理之路为了解决上面拆分应用后出现的问题,我们将公共服务进行拆分,形成面向服务的模型,简称SOA。采用服务后的体系结构:优点:相同的代码不会分散在不同的应用中。这些实现放在各个服务中心,这样可以更好的维护代码。我们把数据库的交互业务放在各个服务中心,让前端web应用更加关注与浏览器交互的工作。问题:如何进行远程服务调用?解决方法:可以通过如下引入消息中间件来解决。阶段9:引入消息中间件随着网站的不断发展,系统中可能会出现用不同语言开发的子模块和部署在不同平台上的子系统。此时,我们需要一个平台来传递可靠的、平台和语言无关的数据,并使负载均衡透明化,在调用过程中收集和分析调用数据,推测增长等一系列需求网站访问率。,来预测网站应该如何发展。开源的消息中间件包括阿里的Dubbo,可以与谷歌开源的分布式程序协调服务Zookeeper配合使用,实现服务器注册和发现。引入消息中间件后的结构:总结以上演进过程只是举例,并不适用于所有网站。在实践中,网站的演进过程与自身业务和遇到的不同问题密切相关,并没有固定的模式。只有认真分析,不断探索,才能找到适合自己网站的架构。
