解决方案系列-集群选主(基于 DB 唯一键)

2,064 阅读5分钟

一个业务量很小的系统,所有的代码都放在一个项目中,部署在一台服务器上。所有的服务都由这台服务器提供,这就是常说的单机模式;我们知道单机模式的缺点是:1、处理能力有限,2、存在单点问题。单机模式大致如下图所示:

为了解决这些问题,出现了集群模式。

常见的集群模式方案

集群主要的使用场景就是为了分担请求的压力,也就是在几个服务器上部署相同的应用程序,来分担客户端请求。集群主要是通过加机器来解决问题,对于问题本身是不会做任何分解的。(PS:分解了就是分布式)。那么常见的集群模式有哪几种呢?

主备,冷备模式,主写主读

所有的写请求都由 master 负责处理,如果请求打在 slave 上,则 slave 会将请求转发给 master 处理,架构图如下图所示:

冷备模式主要是在请求量不大,且对于数据状态有要求的情况(比如所有的数据其实都是 master 内存存储),提供一个从机用于备用,在 master 出现问题的时候能够及时顶上去。

主备,主写从读

基于冷备模式,衍生出主写从读,也就是 master 会将数据同步给各个 slave,然后就可以分担掉读请求的处理压力,架构图如下图所示:

这种模式算是比较常见的,像数据库的主从模式。

对等,广播

这种场景是客户端的请求可能需要指定的集群中的某个 server 来处理,常见的比如配置中心,配置中心客户端会选择与集群中的某个 server 建立连接,当通过管控端推送配置指令下来时,因为没法指定到具体的 server 去推送,所以当请求推到集群的某台机器时,该机器会向集群中的其他机器广播此次请求,谁持有这个客户端的连接,谁去处理。架构图如下图所示:

纯对等(无状态)

最后一种就是纯对等的,集群中的机器谁处理都行;这种就是广义说的集群模式,通过扩机器解决高并发。

集群选主

集群选主指是上述提到的主备模式下进行的,也就是有状态的情况,无状态场景不需要进行选主操作。集群选主很容易,困难的是选主之后的操作(比如如何协调和感知集群中的机器状态),还有集群内机器发生变更之后的操作(如如何分配客户端连接,这里就会涉及到一个非常常见的问题:一致性 hash)。本篇主要介绍集群选主,所以对于这两个问题不做过多的探讨。

集群选主的方式有很多种,本篇只介绍通过 ”争抢锁“ 的选主方式,即在集群中机器启动时会通过争抢某个”非共享“资源,谁抢到谁来当 master。那么基于此,我们可以罗列以下几种常见的”非共享“资源:

  • DB 的唯一键插入
  • zk 的节点创建
  • redis 的原子写

基于 DB 的唯一键插入实现选主

下面以 DB 的唯一键插入 为例来简单介绍下选主的逻辑。

数据库唯一键:unique key,用来保证对应的字段中的数据唯一。

机器表设计&唯一键设置

所有集群中的机器启动时都需要将自己的机器信息写到 DB 中。这里创建一个唯一键:uk_master,包括两个字段,分别是 机器状态和一个 master_lock(master 锁)。

-- ----------------------------
-- Table structure for cluster_servers
-- ----------------------------
DROP TABLE IF EXISTS `cluster_servers`;
CREATE TABLE `cluster_servers` (
  `id` int(11NOT NULL AUTO_INCREMENT COMMENT '主键',
  `host_name` varchar(128NOT NULL COMMENT '主机名',
  `ip` varchar(16NOT NULL COMMENT 'ip 地址',
  `is_master` int(4NOT NULL COMMENT '是否是 master',
  `status` varchar(32NOT NULL COMMENT '机器状态',
  `master_lock` varchar(128NOT NULL COMMENT 'master 锁',
  `heartbeat` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '心跳时间',
  `gmt_sql_server_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'sql 执行时间',
  `gmt_modify` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间',
  `gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_master` (`status`,`master_lock`USING BTREE
ENGINE=InnoDB DEFAULT CHARSET=utf8;

争抢唯一键

这里就是谁更新数据状态成功,谁就是 master,没有争抢成功则是 slave。

<update id="setMaster" >
    update cluster_servers set is_master = #{isMaster}, master_lock = #{masterLock}, status = #{status}, heartbeat = CURRENT_TIMESTAMP, gmt_modify = CURRENT_TIMESTAMP where host_name = #{hostName}
</update>

集群选主的过程

基于此,集群选主的大致过程可以通过下图描述:

这里的像 slave 的后置任务,比如开启监听 master 状态,这样在 master 出现问题时就会触发新的选举。master 的后置任务一个是开始监听各个 slave 的状态,如果 slave 出现问题,则可以及时的将此 slave 踢出集群,其他则需要根据具体的业务情况来看。

总结

本篇主要介绍了集群的几种基本形态,然后基于需要选主的场景进行了简单分析;最后提供了基于 DB 进行集群选主的一种可行性方案,并介绍了大体的选主流程。需要补充一点,基于 DB 选主和维持心跳本身是比较重的,强依赖 DB 的状态,如果 DB 有问题则集群状态可能会出现一些非预期的情况或者导致集群直接不可用,所以大家在选择具体的方式时,还是要结合业务的具体场景来选择。