总结一下上家公司的架构演变过程

前言

  虽然公司由于各种原因倒闭解散了,后来起死回生也没有在回去,回头再来看看这个系统是如何一步步演变和进化的。
  该系统具备的功能:
  • 用户模块:用户注册和管理
  • 学校模块:二手商品展示和管理,学校活动的发布和参与
  • 消息模块:学生之间的消息交流和学校的系统通知

 

阶段一、单机构建网站

  入公司的初期,我们只有一台服务器,在这台单机上跑了我们所有的程序和软件。此时我们使用一个容器tomcat,然后使用一些开源的框架如maven+spring+hibernate;最后再选择一个数据库管理系统来存储数据,那就是免费的mysql,然后通过JDBC进行数据库的连接和操作。
  把以上的所有软件都装载同一台机器上,应用跑起来了,也算是一个小系统了。,就是这么的简单粗暴,你没有猜错没有管理后台,要发发布一个活动需要手动去数据库插入数据!!!!!!多么痛的领悟!!
此时系统大致如下:
1

阶段二、应用服务器与数据库分离

  随着APP的上线开始推广,访问量逐步上升,服务器的负载越来越高,在服务器还没有超载的时候,我们就开始做准备,提升网站的负载能力。我们优化了一次代码,但是并没有什么卵用,服务器负载依旧居高不下,主要是数据库读写特别厉害强占了资源后一直使用资源得不到释放,导致tomcat响应也非常慢,APP请求经常超时,要么给服务器加配置,做数据库和服务的分离!!
      吐槽一下阿里云的增加服务器配置,加一倍的配置和新买一台没有太大差距,所以果断的新增加了一台服务器!不仅可以有效地提高系统的负载能力,而且性价比高,为以后进一步分离也做好了准备。此时我们把数据库,web服务器拆分开来,这样不仅提高了单台机器的负载能力,也提高了容灾能力。
应用服务器与数据库分开后的架构如下图所示:
2

阶段三、应用服务器集群

  随着访问量继续增加,单台应用服务器已经无法满足需求了。因为我们在升级过程中,需要停止tomcat来升级war包,人数众多的情况下这肯定是不允许的!以前晚上在使用低谷期,进行升级的方法不在适用了,因为我们发现在凌晨都会有人在使用APP,这使得我们必须改变策略.
       我们需要把应用服务器从一台变成了两台甚至多台,把用户的请求分散到不同的服务器中,从而提高负载能力并且保证服务的不间断运行。多台应用服务器之间没有直接的交互,他们都是依赖数据库各自对外提供服务。著名的做故障切换的软件有keepalived,keepalived是一个类似于layer3、4、7交换机制的软件,他不是某个具体软件故障切换的专属品,而是可以适用于各种软件的一款产品。keepalived配合上ipvsadm又可以做负载均衡,可谓是神器。
系统演变到这里,将会出现下面四个问题
  1. 用户的请求由谁来转发到到具体的应用服务器
  2. 有什么转发的算法
  3. 应用服务器如何返回用户的请求
  4. 用户如果每次访问到的服务器不一样,那么如何维护session的一致性

我们来看看解决问题的方案

  • 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、第二个问题即是集群调度算法问题,常见的调度算法有10种。

1、rr 轮询调度算法。顾名思义,轮询分发请求。
优点:实现简单
缺点:不考虑每台服务器的处理能力
2、wrr 加权调度算法。我们给每个服务器设置权值weight,负载均衡调度器根据权值调度服务器,服务器被调用的次数跟权值成正比。
优点:考虑了服务器处理能力的不同
3、sh 原地址散列:提取用户IP,根据散列函数得出一个key,再根据静态映射表,查处对应的value,即目标服务器IP。过目标机器超负荷,则返回空。
4、dh 目标地址散列:同上,只是现在提取的是目标地址的IP来做哈希。
优点:以上两种算法的都能实现同一个用户访问同一个服务器。
5、lc 最少连接。优先把请求转发给连接数少的服务器。
优点:使得集群中各个服务器的负载更加均匀。
6、wlc 加权最少连接。在lc的基础上,为每台服务器加上权值。算法为:(活动连接数*256+非活动连接数)÷权重 ,计算出来的值小的服务器优先被选择。
优点:可以根据服务器的能力分配请求。
7、sed 最短期望延迟。其实sed跟wlc类似,区别是不考虑非活动连接数。算法为:(活动连接数+1)*256÷权重,同样计算出来的值小的服务器优先被选择。
8、nq 永不排队。改进的sed算法。我们想一下什么情况下才能“永不排队”,那就是服务器的连接数为0的时候,那么假如有服务器连接数为0,均衡器直接把请求转发给它,无需经过sed的计算。
9、LBLC 基于局部性的最少连接。均衡器根据请求的目的IP地址,找出该IP地址最近被使用的服务器,把请求转发之,若该服务器超载,最采用最少连接数算法。
10、LBLCR 带复制的基于局部性的最少连接。均衡器根据请求的目的IP地址,找出该IP地址最近使用的“服务器”,注意,并不是具体某个服务器,然后采用最少连接数从该组中挑出具体的某台服务器出来,把请求转发之。若该服务器超载,那么根据最少连接数算法,在集群的本服务器组的服务器中,找出一台服务器出来,加入本服务器组,然后把请求转发之。
 

  • 3、第三个问题是集群模式问题,一般3种解决方案:

1、NAT:负载均衡器接收用户的请求,转发给具体服务器,服务器处理完请求返回给均衡器,均衡器再重新返回给用户。
2、DR:负载均衡器接收用户的请求,转发给具体服务器,服务器出来玩请求后直接返回给用户。需要系统支持IP Tunneling协议,难以跨平台。
3、TUN:同上,但无需IP Tunneling协议,跨平台性好,大部分系统都可以支持。
4、第四个问题是session问题,一般有4种解决方案:

  • 4、Session Sticky。session sticky就是把同一个用户在某一个会话中的请求,都分配到固定的某一台服务器中,这样我们就不需要解决跨服务器的session问题了,常见的算法有ip_hash法,即上面提到的两种散列算法。

优点:实现简单。
缺点:应用服务器重启则session消失。
2、Session Replication。session replication就是在集群中复制session,使得每个服务器都保存有全部用户的session数据。
优点:减轻负载均衡服务器的压力,不需要要实现ip_hasp算法来转发请求。
缺点:复制时宽带开销大,访问量大的话session占用内存大且浪费。
3、Session数据集中存储:session数据集中存储就是利用数据库来存储session数据,实现了session和应用服务器的解耦。
优点:相比session replication的方案,集群间对于宽带和内存的压力减少了很多。
缺点:需要维护存储session的数据库。
4、Cookie Base:cookie base就是把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本身并没有提供session同步的解决方案,而apache则提供了session共享的支持。

 
最后我们选了阿里云的负载均衡服务,他保证100%的稳定消除了入口单点的问题,并且提供了加权轮询和最小连接数,以及回话保持的功能,自动剔除有问题的服务器!
 
这让我想起一个段子:
三位女的都去一个公司应聘总裁助理的岗位,面试过程中一个表现平平,一个表现的非常好,一个表现的非常差!最后总裁选了胸最大的一个!这个故事告诉我们一定要模清楚顾客最后的需求,就好像我们选负载均衡做集群一样,我们需要一个非常稳定的转发服务器,并且不能有单点问题,能实现自动剔除有问题的服务器,以及回话保持!这完全符合了我们的所有需求,只是要花点银子!当然是选择在部署最快最高效的方式.用老板的话说就是在创业公司跑最快的最不容死啊(最后被无情打脸)

此时的系统的结构如下
3
 

阶段四、数据库读写分离化

  上面我们总是假设数据库负载正常,但随着访问量的的提高,数据库的负载也在慢慢增大。那么可能有人马上就想到跟应用服务器一样,把数据库一份为二再负载均衡即可。但对于数据库来说,并没有那么简单。假如我们简单的把数据库一分为二,然后对于数据库的请求,分别负载到A机器和B机器,那么显而易见会造成两台数据库数据不统一的问题。那么对于这种情况,我们可以先考虑使用读写分离的方式。
  读写分离后的数据库系统结构如下:
这个结构变化后也会带来两个问题
  1. 主从数据库之间数据同步问题
  2. 应用对于数据源的选择问题
  解决问题方案
  1. 我们可以使用MYSQL自带的master+slave的方式实现主从复制。
  2. 采用第三方数据库中间件,Amoeba。Amoeba是一个以MySQL为底层数据存储,并对应用提供MySQL协议接口的proxy。它集中地响应应用的请求,依据用户事先设置的规则,将SQL请求发送到特定的数据库上执行。基于此可以实现负载均衡、读写分离、高可用性等需求。与MySQL官方的MySQL Proxy相比,作者强调的是amoeba配置的方便(基于XML的配置文件,用SQLJEP语法书写规则,比基于lua脚本的MySQL Proxy简单)。

4

阶段五、用缓存技术缓解读库的压力

1、后台应用层和数据库层的缓存

  随着业务的变更以及用户访问量的增加,逐渐出现了许多用户访问同一部分内容的情况(校园圈),对于这些比较热门的内容,没必要每次都从数据库读取。我们可以使用缓存技术,例如可以使用google的开源缓存技术guava或者使用memcacahe作为应用层的缓存,也可以使用redis作为数据库层的缓存。
2、登陆认证信息token的缓存
  除了数据缓存,还有用户登陆的后认证的token信息。
  优点
  • 减轻数据库的压力
  • 大幅度提高访问速度
  缺点
  • 需要维护缓存服务器
  • 提高了编码的复杂性

值得一提的是
缓存集群的调度算法不同与上面提到的应用服务器和数据库。最好采用“一致性哈希算法”,这样才能提高命中率。这个就不展开讲了,有兴趣的可以查阅相关资料。
 

  加入缓存后的结构
5

 未完待续,后面继续补充