冗余设计
冗余设计
工程学中的冗余是指系统为了提升其可靠度,刻意配置重复的零件或是机能。冗余一般是为了备用,或是失效安全的考量,也有可能是了提升系统性能,像是卫星导航系统接收器,或是多线程电脑处理。
在计算机科学中,主要有以下几种不同的冗余,分别是:
- 硬件冗余,例如双重模块冗余及三重模块冗余
- 信息冗余,例如错误检测与纠正方法
- 时间冗余,进行同一个任务数次,例如执行同一个程式数次,或是传输同一笔资料数次等
- 软件冗余,例如 N 版本编程(N2Version Programming, NVP),是由 Elmendor 提出并经 Avinienis 等人完善与实现的一种静态容错技术,它使用多个不同的软件版本利用决策机制和前向恢复实现容错。另一种由软件冗余衍生,但应用在硬件上的冗余是不同功能冗余,像是车辆中同时有机械刹车及油压刹车。此作法是用软件中的例子,二个独立由不同人写作,有相同功能的模组,针对同一信号产生相同的输出。
一个网站,从基础的硬件层,到操作系统层,到数据库层,到应用程序层,再到网络层,都有可能产生单点故障。如果要有效地消除单点故障,最重要的一点是设计的时候要尽量避免引入单点,随着架构的变化,定期审查系统潜在的单点也是有必要的。如下面一些常见的审查:
IDC 机房的单点,至少要两个 IDC 共存 机房的电力双线冗余,UPS、柴发随时准备 增加硬盘,做镜像,让出错的概率降低; 网卡与网线的单点问题,绑定多块网卡; 数据库至少要做好一主一备 负载均衡、反向代理至少一个集群有两个节点 靠谱的 DNS 解析 …
在应用程序层,一般来说是增加应用的部署节点,然后用一定的策略进行流量的分配。但实际上,我们需要根据应用的场景来做细分,针对不同的应用场景,解决单点问题需要思考和关注的点是不同的。毕竟应用里面有复杂的业务逻辑,是直接面向用户产生业务价值的。
有状态应用
一般而言我们把应用层分成无状态应用和有状态应用,针对这两种类型的应用,我们做冗余的方法是完全不一样的。针对无状态的应用,处理上相对比较简单。我可以增加应用的部署节点,然后把流量随机分配到这些应用结点上即可。典型的就是 Nginx 集群、简单的业务系统,这些系统没有状态,水平扩展集群即可提升集群的整体冗余能力。
有状态的应用处理起来就相对比较复杂了。
状态同步
有状态的应用,即应用内存中会记录运算过程中的状态数据,而整个功能的完成要依赖这些状态数据,但由于这些状态数据只存在某个应用结点上,那么如果前端流量随机打入后端应用结点,会造成处理结果的不准确。针对这种情况,我们通常有两种解决方法,
调整流量分配规则,即采用一定的 hash 算法,根据请求标识把请求打入特定的后端应用,但这种方案需要注意的点就是采用 requestId%应用机器数 的 hash 方法时,当后端应用结点变化时(机器上下线),会造成所有请求流入的应用结点改变,这时可以考虑采用一致性 hash 算法来解决; 共享存储系统,将运算过程中产生的状态数据存入共享存储系统,所有应用结点通过共享存储系统读取状态信息达到同步的效果,共享存储系统可以是 DB,分布式缓存等等,根据应用场景而定;
操作互斥
在解决状态同步的问题之后,还会涉及到的操作互斥的问题,即某些操作按照业务上的要求是不能并发进行的。这就涉及到了多个后端应用结点之间如何进行协同的问题。这个问题通常有下面几种解决思路:
-
分布式锁,通过抢锁的机制,由拿到锁的应用结点来执行操作。分布式锁可以利用 db 的行级锁,zookeeper 或 etcd 等分布式协调系统,或者 tair 一类的分布式缓存来实现;
-
分区模式,就是对整体任务进行分割,某个后端应用结点或某几个后端应用结点只负责处理一个分区的任务,这样应用结点之间的处理任务的操作就不会产生突出。这里面需要注意的两点就是, 如何分区的划分规则和应用结点所属分区变化引起的服务抖动;
主控结点的冗余
如果应用集群存在一些运行集群管理任务的主控结点,那么针对主控结点的单点问题,我们根据应用结点的部署类型可分别做如下冗余设计方案。
-
独立部署模式,主控结点逻辑与任务处理逻辑不在同一应用结点内,是独立进行服务部署的,可以采用一主一备的模式进行冗余,备份服务监控主服务工作状态,在主服务出现故障时,可切换到备份服务;
-
对等部署模式,应用集群中所有应用结点都是对等的,可以在应用集群中根据某种规则选出一台主服务器,只由主服务器来完成管理任务,如果主服务挂掉,可以采用同样的规则重新进行选主。选主的具体实现可参考 paxos 或 raft 的分布式共识机制;