1. 分布式架构要解决的主要问题
1)高可用:举个单机房高可用的例子,你肯定不希望因为一个磁盘损坏,或一个机器损坏,或者一个交换机损坏,就导致整个系统的服务不可用了。
2)在系统容量增长的情况下,如何实现可伸缩。
3)偏管理的方面:如果能够让7--10人的小组的工作是独立的服务单元,各个小组之间的工作有比较清晰的边界,这样他们合作起来会方便很多。
4)服务质量:有南北方机房,让北方的用户访问北方的机房,南方的用户访问南方的机房,如果你有合适的机制能够让这些用户的所有的操作都能够整合起来,当做一个网站来用,这就是用多机房来提高服务质量。
5)更高的需求:如果机房突然毁掉怎么办?如果你的业务是关键业务,你肯定希望做到能跨机房可用。
今天主要讲前两点,单机房的高可用和可伸缩的问题,对常规服务我们怎样解决。
2. 什么是高可用和可伸缩
1)高可用:狭义来讲,单台机器宕机后,用户全无感知。从延伸来讲,不仅有机器的概念,还有各种设备,包括主机、网卡、交换机、网线这些都要做到用户无感知。还有,你可能部署了一大堆服务,可能某一个单个服务也会产生异常,比如打开的连接数过多,也许机器的CUP突然升高导致服务劣化。
2)可伸缩:当业务量增涨的时候,如果能做到业务量增涨的时候只需要买了机器放上去,软件一部署,系统容量就增长了,这就是可伸缩。
今天,我简单把系统分成四个层次,分别将他们的高可用和可扩展方案。
1)入口处,也就是Nginx这个层, Ngx, apache这种层面的东西
2)业务层
3)缓存层
4)数据库层
3. 高可用问题的解决方案
1) 入口层:
入口层的高可用性,用的就是心跳,Keepalived就提供这个功能。
注:Keepalived: Keepalived的作用是检测web服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的web服务器从系统中剔除,当web服务器工作正常后Keepalived自动将web服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的web服务器。
Keepavlied工作原理:ping, 监听端口,监控用户设定的服务器程序运行是否正常
Layer3,4&7工作在IP/TCP协议栈的IP层,TCP层,及应用层,原理分别如下:
Layer3:Keepalived使用Layer3的方式工作式时,Keepalived会定期向服务器群中的服务器发送一个ICMP的数据包(既我们平时用的Ping程序),如果发现某台服务的IP地址没有激活,Keepalived便报告这台服务器失效,并将它从服务器群中剔除,这种情况的典型例子是某台服务器被非法关机。Layer3的方式是以服务器的IP地址是否有效作为服务器工作正常与否的标准。
Layer4:如果您理解了Layer3的方式,Layer4就容易了。Layer4主要以TCP端口的状态来决定服务器工作正常与否。如web server的服务端口一般是80,如果Keepalived检测到80端口没有启动,则Keepalived将把这台服务器从服务器群中剔除。
Layer7:Layer7就是工作在具体的应用层了,比Layer3,Layer4要复杂一点,在网络上占用的带宽也要大一些。Keepalived将根据用户的设定检查服务器程序的运行是否正常,如果与用户的设定不相符,则Keepalived将把服务器从服务器群中剔除。
比如说简单的做法,我们用两台机器做互备,一台机器的IP是1234, 一台是1235,那么我们再申请一个IP1236, 我们称之为心跳IP. 这个心跳IP平时绑在机器A上,如果A宕机了,机器B就主动把这个IP抢过去,那么就达到了A宕机了B补上去,B宕机了A补上去,这只需要做一个简单的事情,把DNS直接绑到1236上面,用户根据域名访问你的服务,你的服务只想一个心跳的IP,不管你哪台机器宕机了,这个心跳IP都是活着的,这样至少在入口层我们不用担心机器司机造成的可用性问题。
Keepalived有什么使用限制呢?
第一,两台机器必须在同一个网段,由于路由的原因,跨网段不能抢这个IP, 路由不到。
第二,是服务监听,无法通过监听单个IP来绑定多个IP, 因为IP的监听必须是对称的。最简单的做法是,你就全部监听所有的IP.
第三,服务器的利用率会下降,因为是主备模式,本来一个机器可以1000M,你可能觉得两个机器可以抗住2000M流量,可实际上2台机器还是只能1000M。
2)业务层:推荐采用无状态,即不适用进程内缓存。-- 貌似扯淡,不用缓存怎样保证高性能。
业务层不要有状态,状态要分散到缓存层和数据库层。当我们用Java的时候,把很多技术线或者其他东西扔到内存里面,这样会导致整个业务的状态会嵌在业务层里面,那么如果这台服务器死机了,里面的数据就丢掉了(在融云,通过redis/ssdb保证缓存的数据在宕机后不丢失)。第二,如果你没有状态,签名是一个接入层,nginx,后面是多台业务服务器,那么一台业务服务器只会通过反向代理,把剩下的流量达到剩余的服务器上面去,如果这台服务器宕机了对用户来讲是完全无感知的。
3)缓存层:
缓存层做高可用直接做成本很高。最常见的做法是,把缓存层分得足够细。比如我们只有两台机器当缓存,其中一台宕机了,我们就会有50%的请求直接压到数据库,数据库可能就直接宕机了。如果我们把缓存的规模分的比较细,我有10台机器,每台只有1/10的容量,那么宕机一台,数据库的请求量比总量要增加10%,增加10%对数据库来讲就更容易承受。我比较推荐把业务层和缓存层混合部署,即业务层部署10台机器,缓存层也部署10台机器。就是说,缓存天生是没有高可用问题可言的,唯一要担心的是,一旦缓存宕机,会不会把数据库压垮。
对于某种特殊情况,即时缓存分散,也是无法容忍的,最简单的办法是缓存启用主从两台,主服务器活着的时候,从主服务器读,主从服务器都写。主服务器宕机的话,从服务器升级为主服务器,主服务器恢复之后变成新的从服务器。这样可以保存缓存的数据依然有效,这种模式的变种比较多,大多数需要两倍的服务器。
我也见过1.5倍服务器的情况,A服务器做一部分缓存,B服务器做一部分缓存,C作为A和B的异或。A宕机了,B和C也可以用。
4)数据库:
数据库的高可用性来讲,在业界的产品层面都解决得差不多了,SQL这一些列的,例如MySQL,有主从模式,有主主模式,都可以满足需求。
4. 可伸缩问题的解决方案
1)入口层:
最简单的,直接加机器,机器放多一点,DNS加上IP, 访问的时候自然都某个IP上去了。
简单加机器不能说不行,但有一个问题,域名解析这个阶段是不保证的,一个域名解析到10个IP,那是均匀的,但绝大部分域名解析服务器根本无法做到这一点,你会发现大部分请求只会使用前面几个IP,然后这个优化效果就很不稳定。所以如果你真的流量大到你的入口都扛不住了,最简单的就是你的入口用一些比较大的服务器(比如融云的CMP),比如说以前你有千兆网,那上万兆,现在也不贵,上万兆不行再上40GB, 百GB的服务器现在也是比较成熟的技术了。业务服务器就隐藏在内网,隐藏在内网之后,这是一种方式,如果你是基于HTTP的话。如果非HTTP,经常有一些客户端,比如游戏客户端,那么在客户端做一个调度,客户端访问之前先去一个小的服务器问有哪些IP可用,你拿到入口之后,再随机挑一个,这样也能做到比较均匀的调用。
2)业务层:业务层的可伸缩,只要保证无状态就好了。
3)缓存层:
缓存层的伸缩性,redis3.0解决了这个问题。如果想自己搭,最简单的解决方式是,如果低峰期间,比如半夜2,3点,数据库能抗住,那么直接把缓存下线掉,再上一个新的缓存系统。扛不住的话,比如是个国际站点,整天都没有低峰时段,或者说确实数据库不是设计了能抗住无缓存压力的话,我们把缓存分成三种类型,第一种是强一致性缓存,也就是说,我们无法接受从缓存里面拿到错误的数据,比如缓存用户余额。第二种是弱一致性缓存,只要达到最终一致性就可以了,能够在接受一段时间内从缓存拿到错误数据,比如微博的转发数。第三种是不变型的缓存,也就是说缓存这个key对应的值不会变更,比如我们要做解密工具,从SHA1反推密码,这样的结果不会变,或者其他复杂公式计算结果。
这三种缓存的处理方式不同。
若一致性缓存和不变型缓存扩容比较方便,用一致性hash就可以,强一致性情况会稍微复杂。比如,我们的业务服务器回去缓存系统取数据,至于这个缓存系统的配置是9台还是10台,是有一份配置文件保存在业务服务器里面的,如果你要更新缓存服务器配置的时候,你要通知业务服务器去修改这个配置,这时会有个微小的时间差,导致拿到的是过期的数据。第二种情况,你有可能扩容,扩容之后你再删除节点,比如你发现缓存出问题了或者有其他问题,这时候会拿到脏数据,比如A的key之前在机器1, 扩容后再机器2,数据更新,删除机器2后Key回到机器1,这时候会有脏数据的问题,这样比如用特殊处理把这个问题解决掉。
最简单的办法是只增不减,这当然意味着我们要引入一些高可用的技术避免单个节点坏掉。第二种方式,要么节点调整时间大于数据的有效时间,这样可以规避掉这一问题。
4)数据库
我们常用的几种办法,
办法1. 水平拆分,比如用户量比较大,预先根据用户的ID拆分成100个表,临时往一台机器上面装,未来可以扩展到100台机器上去。
办法2. 垂直拆分,典型的案例是一个电子商务网站,每个商品有一些最基础的信息,最后还有一个非常详细的信息页面,如果把信息页面和之前的信息存放在同一个数据库,那么取这种简单的基础信息的时候,也会把详细信息带出来,带出来的话,整个取的效率就很低。那么你把详细信息分到单独的表里面去,签名的索引,数据库大小都可以缩,相当于你的性能提高了。
办法3. 你可以做一些定期滚动,比如交易记录,要看之前记录的话需要做查询。这样对应的交易日志可以把三个月之前的就滚动到一个更大的数据库里面去了
总结:
把业务系统分成几个层,入口层、业务层、缓存层、数据库层。在这几个层里面有不同的技术去解决高可用和可伸缩的问题。比如入口层,用心跳解决高可用,用平行部署解决可伸缩。业务层很简单,千万记住,不要有状态,至少没有状态的时候,这两个问题解决很简单。缓存层,第一是减小粒度,避免对数据库的冲击。要么用redis3.0, 要么用一致性hash, 把伸缩带来的冲击降到最低。数据库,一个是主从模式,一个是水平或垂直拆分与定期滚动。
最后讲一下,我们不一定要到一定规模的时候采取考虑高可用和可伸缩的事情,因为高可用和可伸缩,对于大部分架构师来讲,这个东西成本并不高。比如一个简单的这种模型的业务服务,简单的一个前端业务和数据库模型,我们简单的用两台机器就可以达到所有的高可用和可伸缩的配置。最开始两台机器完全一样的部署,前面一个Nginx,两个配心跳,中间是业务,最后是数据库,一个主一个从。当业务量逐步增加的时候,第一步把数据库分出去,第二步把业务和前端做一个分离,第三部业务层做水平伸缩,第四部加缓存层。不要认为业务量小的时候就随便搭,真出了篓子就不太好了。比如某app在大访问量的时候没有抗住,这时候客户就流失掉了,再要把客户找回来就不容易了。作为一个负责任的架构师,你先把这个东西搞定掉,既然成本不高,干嘛不先搞定?
1)高可用:举个单机房高可用的例子,你肯定不希望因为一个磁盘损坏,或一个机器损坏,或者一个交换机损坏,就导致整个系统的服务不可用了。
2)在系统容量增长的情况下,如何实现可伸缩。
3)偏管理的方面:如果能够让7--10人的小组的工作是独立的服务单元,各个小组之间的工作有比较清晰的边界,这样他们合作起来会方便很多。
4)服务质量:有南北方机房,让北方的用户访问北方的机房,南方的用户访问南方的机房,如果你有合适的机制能够让这些用户的所有的操作都能够整合起来,当做一个网站来用,这就是用多机房来提高服务质量。
5)更高的需求:如果机房突然毁掉怎么办?如果你的业务是关键业务,你肯定希望做到能跨机房可用。
今天主要讲前两点,单机房的高可用和可伸缩的问题,对常规服务我们怎样解决。
2. 什么是高可用和可伸缩
1)高可用:狭义来讲,单台机器宕机后,用户全无感知。从延伸来讲,不仅有机器的概念,还有各种设备,包括主机、网卡、交换机、网线这些都要做到用户无感知。还有,你可能部署了一大堆服务,可能某一个单个服务也会产生异常,比如打开的连接数过多,也许机器的CUP突然升高导致服务劣化。
2)可伸缩:当业务量增涨的时候,如果能做到业务量增涨的时候只需要买了机器放上去,软件一部署,系统容量就增长了,这就是可伸缩。
今天,我简单把系统分成四个层次,分别将他们的高可用和可扩展方案。
1)入口处,也就是Nginx这个层, Ngx, apache这种层面的东西
2)业务层
3)缓存层
4)数据库层
3. 高可用问题的解决方案
1) 入口层:
入口层的高可用性,用的就是心跳,Keepalived就提供这个功能。
注:Keepalived: Keepalived的作用是检测web服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的web服务器从系统中剔除,当web服务器工作正常后Keepalived自动将web服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的web服务器。
Keepavlied工作原理:ping, 监听端口,监控用户设定的服务器程序运行是否正常
Layer3,4&7工作在IP/TCP协议栈的IP层,TCP层,及应用层,原理分别如下:
Layer3:Keepalived使用Layer3的方式工作式时,Keepalived会定期向服务器群中的服务器发送一个ICMP的数据包(既我们平时用的Ping程序),如果发现某台服务的IP地址没有激活,Keepalived便报告这台服务器失效,并将它从服务器群中剔除,这种情况的典型例子是某台服务器被非法关机。Layer3的方式是以服务器的IP地址是否有效作为服务器工作正常与否的标准。
Layer4:如果您理解了Layer3的方式,Layer4就容易了。Layer4主要以TCP端口的状态来决定服务器工作正常与否。如web server的服务端口一般是80,如果Keepalived检测到80端口没有启动,则Keepalived将把这台服务器从服务器群中剔除。
Layer7:Layer7就是工作在具体的应用层了,比Layer3,Layer4要复杂一点,在网络上占用的带宽也要大一些。Keepalived将根据用户的设定检查服务器程序的运行是否正常,如果与用户的设定不相符,则Keepalived将把服务器从服务器群中剔除。
比如说简单的做法,我们用两台机器做互备,一台机器的IP是1234, 一台是1235,那么我们再申请一个IP1236, 我们称之为心跳IP. 这个心跳IP平时绑在机器A上,如果A宕机了,机器B就主动把这个IP抢过去,那么就达到了A宕机了B补上去,B宕机了A补上去,这只需要做一个简单的事情,把DNS直接绑到1236上面,用户根据域名访问你的服务,你的服务只想一个心跳的IP,不管你哪台机器宕机了,这个心跳IP都是活着的,这样至少在入口层我们不用担心机器司机造成的可用性问题。
Keepalived有什么使用限制呢?
第一,两台机器必须在同一个网段,由于路由的原因,跨网段不能抢这个IP, 路由不到。
第二,是服务监听,无法通过监听单个IP来绑定多个IP, 因为IP的监听必须是对称的。最简单的做法是,你就全部监听所有的IP.
第三,服务器的利用率会下降,因为是主备模式,本来一个机器可以1000M,你可能觉得两个机器可以抗住2000M流量,可实际上2台机器还是只能1000M。
2)业务层:推荐采用无状态,即不适用进程内缓存。-- 貌似扯淡,不用缓存怎样保证高性能。
业务层不要有状态,状态要分散到缓存层和数据库层。当我们用Java的时候,把很多技术线或者其他东西扔到内存里面,这样会导致整个业务的状态会嵌在业务层里面,那么如果这台服务器死机了,里面的数据就丢掉了(在融云,通过redis/ssdb保证缓存的数据在宕机后不丢失)。第二,如果你没有状态,签名是一个接入层,nginx,后面是多台业务服务器,那么一台业务服务器只会通过反向代理,把剩下的流量达到剩余的服务器上面去,如果这台服务器宕机了对用户来讲是完全无感知的。
3)缓存层:
缓存层做高可用直接做成本很高。最常见的做法是,把缓存层分得足够细。比如我们只有两台机器当缓存,其中一台宕机了,我们就会有50%的请求直接压到数据库,数据库可能就直接宕机了。如果我们把缓存的规模分的比较细,我有10台机器,每台只有1/10的容量,那么宕机一台,数据库的请求量比总量要增加10%,增加10%对数据库来讲就更容易承受。我比较推荐把业务层和缓存层混合部署,即业务层部署10台机器,缓存层也部署10台机器。就是说,缓存天生是没有高可用问题可言的,唯一要担心的是,一旦缓存宕机,会不会把数据库压垮。
对于某种特殊情况,即时缓存分散,也是无法容忍的,最简单的办法是缓存启用主从两台,主服务器活着的时候,从主服务器读,主从服务器都写。主服务器宕机的话,从服务器升级为主服务器,主服务器恢复之后变成新的从服务器。这样可以保存缓存的数据依然有效,这种模式的变种比较多,大多数需要两倍的服务器。
我也见过1.5倍服务器的情况,A服务器做一部分缓存,B服务器做一部分缓存,C作为A和B的异或。A宕机了,B和C也可以用。
4)数据库:
数据库的高可用性来讲,在业界的产品层面都解决得差不多了,SQL这一些列的,例如MySQL,有主从模式,有主主模式,都可以满足需求。
4. 可伸缩问题的解决方案
1)入口层:
最简单的,直接加机器,机器放多一点,DNS加上IP, 访问的时候自然都某个IP上去了。
简单加机器不能说不行,但有一个问题,域名解析这个阶段是不保证的,一个域名解析到10个IP,那是均匀的,但绝大部分域名解析服务器根本无法做到这一点,你会发现大部分请求只会使用前面几个IP,然后这个优化效果就很不稳定。所以如果你真的流量大到你的入口都扛不住了,最简单的就是你的入口用一些比较大的服务器(比如融云的CMP),比如说以前你有千兆网,那上万兆,现在也不贵,上万兆不行再上40GB, 百GB的服务器现在也是比较成熟的技术了。业务服务器就隐藏在内网,隐藏在内网之后,这是一种方式,如果你是基于HTTP的话。如果非HTTP,经常有一些客户端,比如游戏客户端,那么在客户端做一个调度,客户端访问之前先去一个小的服务器问有哪些IP可用,你拿到入口之后,再随机挑一个,这样也能做到比较均匀的调用。
2)业务层:业务层的可伸缩,只要保证无状态就好了。
3)缓存层:
缓存层的伸缩性,redis3.0解决了这个问题。如果想自己搭,最简单的解决方式是,如果低峰期间,比如半夜2,3点,数据库能抗住,那么直接把缓存下线掉,再上一个新的缓存系统。扛不住的话,比如是个国际站点,整天都没有低峰时段,或者说确实数据库不是设计了能抗住无缓存压力的话,我们把缓存分成三种类型,第一种是强一致性缓存,也就是说,我们无法接受从缓存里面拿到错误的数据,比如缓存用户余额。第二种是弱一致性缓存,只要达到最终一致性就可以了,能够在接受一段时间内从缓存拿到错误数据,比如微博的转发数。第三种是不变型的缓存,也就是说缓存这个key对应的值不会变更,比如我们要做解密工具,从SHA1反推密码,这样的结果不会变,或者其他复杂公式计算结果。
这三种缓存的处理方式不同。
若一致性缓存和不变型缓存扩容比较方便,用一致性hash就可以,强一致性情况会稍微复杂。比如,我们的业务服务器回去缓存系统取数据,至于这个缓存系统的配置是9台还是10台,是有一份配置文件保存在业务服务器里面的,如果你要更新缓存服务器配置的时候,你要通知业务服务器去修改这个配置,这时会有个微小的时间差,导致拿到的是过期的数据。第二种情况,你有可能扩容,扩容之后你再删除节点,比如你发现缓存出问题了或者有其他问题,这时候会拿到脏数据,比如A的key之前在机器1, 扩容后再机器2,数据更新,删除机器2后Key回到机器1,这时候会有脏数据的问题,这样比如用特殊处理把这个问题解决掉。
最简单的办法是只增不减,这当然意味着我们要引入一些高可用的技术避免单个节点坏掉。第二种方式,要么节点调整时间大于数据的有效时间,这样可以规避掉这一问题。
4)数据库
我们常用的几种办法,
办法1. 水平拆分,比如用户量比较大,预先根据用户的ID拆分成100个表,临时往一台机器上面装,未来可以扩展到100台机器上去。
办法2. 垂直拆分,典型的案例是一个电子商务网站,每个商品有一些最基础的信息,最后还有一个非常详细的信息页面,如果把信息页面和之前的信息存放在同一个数据库,那么取这种简单的基础信息的时候,也会把详细信息带出来,带出来的话,整个取的效率就很低。那么你把详细信息分到单独的表里面去,签名的索引,数据库大小都可以缩,相当于你的性能提高了。
办法3. 你可以做一些定期滚动,比如交易记录,要看之前记录的话需要做查询。这样对应的交易日志可以把三个月之前的就滚动到一个更大的数据库里面去了
总结:
把业务系统分成几个层,入口层、业务层、缓存层、数据库层。在这几个层里面有不同的技术去解决高可用和可伸缩的问题。比如入口层,用心跳解决高可用,用平行部署解决可伸缩。业务层很简单,千万记住,不要有状态,至少没有状态的时候,这两个问题解决很简单。缓存层,第一是减小粒度,避免对数据库的冲击。要么用redis3.0, 要么用一致性hash, 把伸缩带来的冲击降到最低。数据库,一个是主从模式,一个是水平或垂直拆分与定期滚动。
最后讲一下,我们不一定要到一定规模的时候采取考虑高可用和可伸缩的事情,因为高可用和可伸缩,对于大部分架构师来讲,这个东西成本并不高。比如一个简单的这种模型的业务服务,简单的一个前端业务和数据库模型,我们简单的用两台机器就可以达到所有的高可用和可伸缩的配置。最开始两台机器完全一样的部署,前面一个Nginx,两个配心跳,中间是业务,最后是数据库,一个主一个从。当业务量逐步增加的时候,第一步把数据库分出去,第二步把业务和前端做一个分离,第三部业务层做水平伸缩,第四部加缓存层。不要认为业务量小的时候就随便搭,真出了篓子就不太好了。比如某app在大访问量的时候没有抗住,这时候客户就流失掉了,再要把客户找回来就不容易了。作为一个负责任的架构师,你先把这个东西搞定掉,既然成本不高,干嘛不先搞定?