17611538698
webmaster@21cto.com

高可用可伸缩架构经验谈

资讯 0 2104 2017-04-02 12:00:48

RE1ynyJ.png

 
现阶段,分布式架构能需要解决的问题如下:
第一个就是高可用。
举一个简单的单机房高可用的例子,你肯定不希望因为一个磁盘损坏一个机器损坏一个交换机损坏,就导致整个系统的服务不可用了。
第二个,就是在系统容量增长的情况下如何实现可伸缩。
第三个是偏管理方面。如果能够让每个七到十人的小组的工作是独立的服务单元,各个小组之间的工作有比较清晰的边界,他们合作起来会方便很多。
第四个是服务质量,比如说中国就有南北之间的网络,如果你能让北方的用户直接访问北方的机房,南方用户访问你南方的机房,如果你有合适的机制能够让这些用户的所有的操作都能够整合起来,当做一个网站来用,这就是用多机房提高服务质量。
最后是一个更高的需求,机房突然毁掉了怎么办?如果你的业务是比较关键的业务,你肯定希望做到能够跨机房可用。
今天我讲的内容主要针对前面两点,单机房高可用和可伸缩这个问题,对常规的服务我们怎么解决。


什么是高可用和可伸缩


高可用,从狭义来讲,单台机器宕机后用户完全无感知;从延伸来讲,不仅有机器的概念,还有各种设备,包括主机、网卡、交换机、网线这些都要做到用户无感知。
还有,你可能部署了一大堆服务,可能某一个单个服务也会产生异常,比如打开的连接数过多,也许机器的CPU突然升高导致服务劣化。如何做到单个服务异常用户也无感知,也属于高可用的范畴。
什么叫可伸缩?业务量增长的时候,如果能做到业务量增长的时候只需要买了机器放上去,软件一部署,系统容量就增长了,这就是可伸缩。
今天我简单的把系统分成四个层次,分别讲它们的高可用和可扩展方案。首先是入口层,也就是Nginx这个层,Nginx、Apache这种层面的东西。第二层是业务层,就是经常写的(GRB)这类的代码,第三层是缓存层,第四层是数据库。


高可用问题的解决方案


入口层
入口层高可用,应该算是比较通用的技术,就是心跳,Keepalived就提供这个功能。比如说简单的做法,我们有两台机器做互备,一台机器的IP是1234,一台是1235,那我们再申请一个IP 1236,我们称之为心跳IP。
这个心跳IP平时绑在机器A上面,如果机器A宕机了,机器B就主动把这个IP抢过去,那么就达到A宕机了B顶上,B宕机了A顶上,那么只需要做一个简单的事情,把DNS直接绑到1236上面,用户根据域名访问你的服务,你的服务指向一个心跳IP,不管你哪台机器宕机了,这个心跳IP都是活着的,至少在入口这个层面你就不用担心机器死机造成可用性问题。
那么keepalived有什么使用限制?
第一,两台机器必须在同一个网段,由于路由的原因,跨网段没有办法抢这个IP,路由不到。第二个问题是服务监听,像以前绑定多个IP的时候,可以监听一个特定的IP。
现在来讲,在心跳上无法做到这一点,因为IP的监听必须是对称的。B没有持有这个IP的时候,整个监听的服务是起不来的。最简单的做法,你就全部监听在所有的IP上就可以了。第三点就是服务器的利用率会下降,因为是主备模式,本来一个机器一千兆,你可能觉得两个可以扛住两个千兆的流量,现在可能只能扛住一个千兆的流量。
一个常见的误区是什么?我用DNS,两个机器各申请一个公共IP,我把域名同时定位到两个IP,是不是这样就高可用了?
其实这样是有问题的。这样带来的问题是说,当用户区访问的时候,是从域名解析IP,不是一个IP失败了就去尝试另外一个IP,而是每个用户都有一半的概率去拿到这个已经宕机的IP。这样做一台机器宕机,50%的用户就无法访问了。业务层
业务层如何做到高可用?业务层不要有状态,状态要分散到缓存层和数据库。以前我们特别是用Java,把很多技术线或者其他东西扔到内存里面,这样会导致整个业务的状态会嵌在业务里面,那么这台业务服务器死机了,里面的数据就丢掉了,对于你的业务来讲就是很难容忍的一件事情。
第二如果你没有状态,前面是一个接入层,Nginx,后面是多台的业务服务器,那么一台业务服务器只会通过反向代理,把剩下的流量打到剩余的服务器上面去,如果这台服务器宕掉了或者做常规的软件更新,对用户来讲是完全无感知的。
有一个很时髦的技术叫做cookie session,就是我把整个session的状态扔到cookie。以前session要么在内存要么在缓存层,要么在数据库。把它放到session,这有点坑,主要是如果你有很多可以让用户反复尝试但是又会限制次数的这种情况的时候,用cookie session就会让用户有机会进行存放攻击。
你的用户错误次数一般是记在session里面,用户可以用以前的cookie session为你重新发起请求,你的计数器就失效了。个人不推荐使用这个技术,坑确实比较大,放在缓存层也好数据库层也好,成本还是可控的,当然还是要根据规模来评估一下。缓存层
缓存层的高可用直接做是比较麻烦,或者成本比较高,最常见的做法是缓存层分得足够细就可以了。比如说我们只有两台机器当缓存,其中一台宕掉的情况下我们就会有50%的请求直接压到数据库,数据库可能就直接宕掉了。
如果缓存的规模分得比较细,我有十台机器,每台只有十分之一的容量,那么宕掉一台,数据库的请求量比总量要增加10%,增加10%对数据库来讲就比较方便。那么一个简单的部署方法,我比较推荐缓存层和业务层混合部署。
业务层推荐到十台机器,缓存层也推荐到十台机器,就可以比较简单的做到这一点。如果之前规模小的话压力也不会太大,这个事情还比较好做一点。就是说缓存天然是没有高可用问题的,唯一要担心的是,如果缓存,死机之后会不会把数据库压垮,这个问题是唯一需要考虑好的。
前面提到缓存有可能会压到数据库,那么分散是一个办法,但是有些特殊的案例下即使是分散也是无法容忍的,因为有可能你的整个数据计算的过程非常繁琐。最简单的就是缓存启用主从两台,主服务器活着的时候主服务器读,主从服务器都写。
主服务器宕机之后,从服务升级为主服务器,主服务器恢复之后变成新的从服务器。这样可以保存缓存的数据依然有效,这种模式的变种比较多,大多数需要两倍的服务器。我也见过需要1.5倍服务器的情况,A服务器做一部分缓存,B服务器做一部分缓存,C作为A和B的抑或。A宕了B和C也可以用。数据库
缓存层来讲很简单,要么就是说把它分散弄得细一点,压到数据库上面也无所谓。要么如果你真担心它压到数据库之后有问题,最简单的就是上一个主从。
数据库的高可用手段来讲,差不多从业界来讲,在产品层面都解决得差不多了,SQL这一系列的,MySQL,有主从模式,有主主模式,都可以满足需求。MongoDB也有ReplicaSet的概念,基本能够满足大家的需求,大部分数据库都解决这样的问题了,问题不大。


可伸缩问题的解决方案


入口层
入口层怎么提供可伸缩性?最简单的,就直接铺机器,机器放多一点,DNS加上IP,访问的时候自然到某个IP去了,这样就可以了。
简单铺机器不能说不行,但是有一个问题,域名解析这个阶段是不保证的,一个域名解析到10个IP,那是均匀的,但是绝大部分域名解析服务器根本没法做到这一点,你会发现大部分请求会只使用前面几个IP,然后这个优化效果就很不稳定。
所以如果你真的流量大到你的入口都扛不住了,最简单的就是你的入口用一些比较大的服务器,比如说以前你有千兆网,那上万兆,现在也不贵。上万兆不行再上40GB、百GB的服务器现在也是比较成熟的技术了。业务服务器就隐藏在内网,隐藏在内网之后,这是一种方式,就是如果你是基于HTTP的话。
如果是非HTTP的话,经常有一些客户端,比如游戏客户端或者什么客户端,那么在客户端做一个调度,客户端访问之前先去一个小的服务端问有哪些入口IP可用,你拿到入口IP之后再随机挑一个,这样也能做到比较均匀的调度,同时也比较方便,因为这个入口也不一定需要用大入口。特别是一些非HTTP业务,比如直播、游戏等等用这种方式会比较方便一点。业务层
业务层,它可伸缩的手段跟前面说的差不多,无状态就好了。没有状态,不管是上两个还是上十个都是差不多的,你继续往前扩,Nginx做反向代理的话,后面的服务多上一点无所谓,我们试过几十个或者几百个,对Nginx都不是太大的压力。所以这些方面来讲,我觉得还可以。缓存层
缓存层的伸缩性,codis或者是Redis 3.0是解决了这个问题,Redis 3.0比较新,codis是豌豆夹的方案。如果想自己搭,最简单的解决方式是,如果低峰期间,比如半夜两到三点,数据库能扛住,那么直接把缓存下线掉,再上一个新的缓存系统,这是最简单有效的办法。
扛不住,比如说是个国际站点,整天都没有低峰时段,或者说确实数据库不是设计了能扛住无缓存的压力的话,我们把缓存分成三种类型,第一种是强一致性缓存,也就是说我们无法接受从缓存里面拿到错误的数据,比如用户的余额,如果是错误的话就相当于一个不应该允许被接受的转账或者是购买请求,有可能就会被接受,这肯定是不行的。
或者说你这个缓存是多级的,你自己拿到一个错误数据,你的下游会继续拿。下游拿到之后,如果真的被下游缓存住再要清理就非常麻烦。还不如直接在我这一层直接避免错误的数据。
第二个是弱一致性的缓存,只要达到最终一致性就可以了。能够在接受一段时间内从缓存拿到错误数据这些,比如说微博的转发数,这就是属于这种类型的缓存。
第三种是不变型的缓存,也就是说缓存这个key对应的值不会变更。比如我要做破解密码的工具,从SHA1反推密码,这样的结果不会变,或者其他复杂公式的计算结果。这是不变型的缓存。这三种的处理方式有点不一样。
弱一致性和不变型缓存的扩容比较方便,用一致性Hash就可以,强一致性情况稍微复杂一点。为什么用一致性Hash?一致性Hash会带来一个问题,比如我们缓存从9台扩容到10台,简单Hash的情况下会带来什么问题?简单的Hash,以前的Hash是对9取余,现在是对10取余,那么90%的情况下这个数之前Hash得到的和现在得到的完全不一样。
那么扩容的情况下带讲,90%的缓存会失效,这个时候对数据库的冲击会非常大,可能整个就宕机了,客户就会感知到。用一致性Hash的话,一致性Hash加上虚拟节点的情况下,一般加一个节点加了10%的节点,只有10%的缓存会失效,一般后台数据库能扛得住,这个问题就解决掉了,不用担心这个时候对数据库冲击过大的问题。
强一致性缓存会带来一个什么问题?第一个,因为缓存客户端的配置的更新时间,其实是有微小的差异的。而这个时间的窗么会拿到过期数据。也就是说,我们有一个缓存系统,然后你的业务服务器会去这个缓存系统取数据,这个缓存系统的配置是9台还是10台,这个东西是一份配置存在业务服务器里面的,那么当你缓存服务器配置更新的时候,你要通知业务服务器去更新新的配置。
这个配置的时间窗会有一个微小的差异,导致时间窗里面拿到过期的数据。第二种情况,你有可能扩容,扩容之后你再裁撤节点,因为你会发现缓存出问题了或者是其他的问题,要裁撤节点。这个时候就会拿到脏数据,比如A的key之前在机器1,扩容之后在机器2,数据更新,裁撤节点后key回到机器1,这时候就会有脏数据的问题。这个时候就要进行特殊处理把这个问题解决掉。
解决问题要就比较简单,要么做到永不减少节点,节点数只允许增加,不允许裁撤节点。当然这意味着我们需要引入一些高可用的技术避免节点坏掉。第二种方式就是说,要么节点的调整间隔的时间要大于数据有效时间。比如我们刚才提到,节点增加了,再减少的时候它回去会拿到脏数据。如果我拿到的脏数据是过期的就无所谓。那么你数据有效期一个小时,我们就规定这个节点的调整不得低于一个小时,这样就可以回避掉这个问题。
问题一就是说,会稍微有点绕,我们为了这种将一致性还要用得上这个缓存,我们就需要让应用段去感知这个Hash更新的事情。首先我们先把两套Hash的配置都更新到客户端,第二个逐步客户端改为只有两套Hash结果一致的情况下会使用缓存,其余的情况下从数据库读,同时两边缓存都开始写入。最后逐个客户端通知使用新配置。这样可以保证在这个过程中,保持Hash一致性的东西。数据库
数据库方面反而是说,你能找到的文档是最多的。之前讲到大部分压力都来自于数据库,而不是业务层或者是入口层。
现在好一点了,由于SSD的引入,容量比较大,而数据库的技术变慢了不能很好的利用这个容量。我们常用的几种手法,比如水平拆分,用户量比较大,预先根据用户的ID拆成100个表,临时往一台机器上面装。未来就留下了足够的空间,未来可以扩展到100台机器上边去。
第二种类型就是垂直拆分,最典型的案例就是比如要做一个电子商务的网站,每个商品有一些最基础的信息,最后还有一个非常详细的信息页面,如果把信息页面和之前的信息在同一个数据库,那么取这种简单的基础信息的时候也会把详细信息带出来。带出来的话,整个取的效率就很低。那么你把详细信息分到单独的表里面去,前面的索引、数据库大小都可以所,相当于你的性能就提高了。
第三种你可以做一些定期滚动,比如说像交易记录,要看之前的单独做一次查询。这样对应的交易日志可以把三个月之前的就滚动到一个更大的数据库里面去了,这边用SSD那边就用(seta),性能提高了,成本的需求也比较低一点。


总结


这里简单总结一下,把业务系统分几个层,入口层、业务层、缓存层、数据库层。在这几个层里面分别有不同的技术去解决高可用和可伸缩的问题。比如入口层,以心跳解决高可用,用平行部署解决可伸缩。
业务层很简单,千万记住,不要有状态,至少没有状态的时候,这两问题解决起来很简单。如果有状态,你就要想很多办法去解决这两个问题。缓存层,第一是减小粒度,避免对数据库冲击。要么用redis 3.0,要么是采用一致性Hash,把伸缩带来的冲击降到最低。数据库,一个是主从模式,一个是拆分与滚动的操作。
最后讲一下,不一定我们一定要到一定规模的时候才去考虑高可用和可伸缩的事情,因为高可用和可伸缩,我觉得对我或者对大部分架构师来讲,这个东西的成本并不高。比如一个简单的这种模型的业务服务,简单的一个前端业务服务和数据库模型,我们简单的用两台设备就可以达到所有的高可用和可伸缩的配置。最开始两台机器完全一样的部署,前面一个NGX,两个配心跳,中间是业务,最后是数据库,一个主一个从。当业务量逐步增加的时候,第一步把数据库分出去,第二步把业务和前端做一个分离,第三步业务层做水平伸缩,第四步加缓存层的东西。不要认为业务量小的时候就随便搭,真出了篓子就不太好了。我见过一些类似的案例,比如某App在大访问量的时候没有扛住,这时候客户就流失掉了,再要把客户找回来就是很痛苦的事情。作为一个负责任的架构师,你先把这个东西搞定掉,既然成本不高,干嘛不搞定?
前面提到了很多高可用和可伸缩的东西,作为可伸缩的东西来讲,我们现在的很多设计更多是基于我们自己对服务器的操作系统和网络的理解,这种理解我是觉得在未来,就像李骏提到的,未来可能是不必要的。
这方面的一些尝试很早就进行了,比如GAE,应该是2008年左右的产品,到现在已经8个年头了,GAE是不算太成功的尝试,从GAE到Heroku和Cloudfoundry,而现在是有机会的年代,docker到Kubernete和mesos,你的整个机房就是一台设备,我只需要把业务部署进去,我并不关心部署到的是哪台机器上。我只关心的是我部署的机房是哪个,这个机房分配的资源是多少,拷贝数是多少。如果我只是部署了机房而不是机器,那么天然的这个系统就承诺了一个东西,就是高可用。我连机器都不知道,那机器死机这样的概念就不存在了。
第二个,我可以随意调控这个业务的份数,也就是意味着达到了第二点,可伸缩。也许再过两三年,等这些技术成熟的时候,就直接有一些很现成的东西可以用了。


作者:李道兵
来源:七牛云博客


评论