本文分享自华为云社区《Sermant在异地多活场景下的实践》,作者:华为云开源。
Sermant社区在1.3.0和1.4.0版本相继推出了消息队列禁止消费插件和数据库禁写插件,分别用于解决异地多活场景下的故障切流和保护数据一致性问题。本文将对Sermant在异地多活场景下的实践进行剖析。
对于一个软件系统,我们希望当系统出现故障时仍然可以正常对外提供服务,软件系统的这种特性称之为高可用, 异地多活架构便是用来解决高可用问题的。
最早的系统架构一般为单机架构,当数据库出现故障时,可能会导致业务长时间中断。为了解决这一问题,数据库发展为由主库和从库组成,主库负责读和写操作,从库只提供读操作,主数据库的数据会实时同步至从数据库保持数据的一致性和完整性。当主库出现问题时,从库切换为主库继续工作。不过,这些服务都部署在同一机房甚至是同一个机柜,当机房出现故障后,系统仍然不能正常对外提供服务。
此时,同城双活成为很好的解决方案,在一个城市部署两个机房,两个机房部署相同的软件环境,并且均提供服务。当其中一个机房出现故障时,可以将流量切换至另一个机房继续执行,以保证系统的高可用。如图一所示,机房1数据库为主数据库,两个机房所有的写操作均操作机房1的主数据库,读操作则可以读取本机房的数据库。两个机房部署的物理距离较近,同时两个机房可以使用专线进行网络连接,因此不同机房服务调用的网络延迟较低,机房2的服务写入机房1的数据库时延在可接受范围内。
图一:同城双活架构图
同城双活架构很好地解决了软件系统的高可用问题,但是城市如果出现了自然灾害,比如地震、水灾等,这些部署在同一城市的所有机房仍然会受到损害从而停止提供服务。并且因为这些灾害的破坏性较强,系统修复的周期也会相对漫长,会严重影响公司业务的正常运行。在这种情况下,很明显需要这些机房部署在不同的地域,同时这些地域的地理距离需要足够遥远,这样就能抵抗自然灾害的风险,这就是异地多活架构的由来和价值所在。
针对上图,机房1和机房2如果部署在两个城市,就变成了异地双活,为了更好的抵御风险,可以在多个地域部署机房,这样异地双活就升级为了异地多活。
异地多活的架构图如图二所示,客户端的流量通过路由层分发至不同的地域机房执行。和同城双活架构不同的一点在于不同地域的机房物理距离遥远,部署网络专线的成本巨大且不现实,不同机房之间访问的网络时延是不可忽视的,因此需要操作本机房内的数据库,不能跨机房操作。在异地多活架构下,每个机房的数据库均为主库,不同机房的数据会同步至中心机房,并由中心机房再同步至其他机房。因为所有机房的数据库都可以写入,当不同机房修改同一条数据时,就不可避免的引入了数据冲突的问题。为了解决数据冲突,可以在路由层根据分片策略使一些流量固定转发到某一机房,流量分片的策略可以基于业务类型或地理位置。通过流量分片,保证同一用户的相关请求,会路由至同一个机房内完成所有业务操作,并且机房内的流量保证只在本机房内流转,降低网络延迟。
图二:异地多活架构图
异地多活架构通过在不同地域部署机房对外提供服务来抵御自然灾害带来的风险,是实现系统高可用的有效手段。但是,异地多活架构也使系统变得更加复杂,在故障切流、数据一致性等方面引入了新的需求:
针对以上两个典型问题,Sermant分别开发了消息队列禁止消费插件和数据库禁写插件来处理,下文将详细介绍。
消息队列禁止消费插件允许微服务在运行态根据实际需求动态调整消费者对消息队列中间件的消费行为,确保在非正常环境或状态下,业务处理流程中的消息得到妥善管理,避免不必要的业务影响。例如,在异地多活架构系统中,如果发生区域性故障需要对流量做切流处理时,可在发生故障的可用区开启消息队列禁止消费功能,让正常可用区的消费者来处理业务,避免故障区域消费流量从而导致业务异常,保障系统的高可用。待故障处理完成后,可重新开启消费。
消息队列禁止消费插件目前支持Kafka和RocketMQ两种消息中间件。在Kafka方面,该插件实现了Topic级别的禁止和恢复消费功能。对于RocketMQ, 控制消费的粒度为消费者实例级别。Sermant支持通过配置中心下发需要禁止消费的消息队列类型和具体Topic。
关于消费队列禁止消费插件更多的介绍、配置说明和场景演示等请参考官网文档消息队列禁止消费。
应用场景:某软件系统使用Kafka作为消息队列,生产者往topic-test主题生产消息,该topic消息包含四个partition。可用区A和可用区B各有两个消费者加入test消费者组并消费topic-test的消息,每个消费者各分配一个partition,其中可用区A和可用区B分布在不同地域,即异地多活的两个机房。如下图所示。
该场景下,消费者服务通过挂载Sermant的消息队列禁止消费插件运行后,可以实时控制消费者消费的主题,从而确保在非正常环境或状态下,业务处理流程中的消息得到妥善管理。
当可用区A发生故障后,可用区A的消费者应该停止消费。在可用区A下发全局配置禁止消费者A和消费者B消费topic-test主题,并释放已分配的消息队列。
消息队列禁止消费插件的配置如下所示,enableKafkaProhibition表示开启Kafka队列禁消费能力,kafkaTopics指明需要禁止消费的订阅主题Topic。下发配置的方式请参考官网文档消息队列禁止消费:
enableKafkaProhibition: true kafkaTopics: - topic-test
配置下发后,可用区A的消费者停止消费,可用区B消费者重新分配topic-test主题的partition,如下图所示。
待可用区A恢复正常后,可以重新通过动态配置中心下发配置,开启消费者A和B对topic-test主题的消费。开启消费配置下发后Kafka将触发重平衡,可用区A和B的消费者重新分配partition。
消息队列禁止消费插件实现了异地多活场景下消息队列的故障切流能力,保障了系统的可用性。
服务在挂载数据库禁写插件启动后,可以动态开启或关闭对指定数据库的禁止写入能力。在异地多活场景下,用户希望停止对个别或全部数据库的写入操作,仅允许读取数据,以保证数据库系统的数据完整性、一致性和安全性。比如,某业务数据库全局数据写入仅允许操作中心机房,通过开启数据库禁写插件,使路由异常流量写入非中心机房数据库失败;多地多写场景下,对流量手动切流前,被切流的机房先禁止写入数据库,等待其他机房数据同步完成后,再进行切流。以上场景中数据库禁写插件的使用保障了数据库数据的一致性。
数据库禁写插件目前支持MySQL、MongoDB、PostgreSQL和OpenGauss数据库。在微服务运行时,可以通过配置中心下发禁写的数据库类型和名称。支持禁写的具体写操作和插件使用方式请参考官网文档数据库禁写。
应用场景:异地多活架构下,某业务微服务用于修改商品库存等全局数据,同时全局数据保存在名为global的MySQL数据库中。对于该全局数据,写操作仅允许操作中心机房的global数据库,其他机房的global数据库只能读取数据。为了保证数据一致性,当修改全局数据时,该流量在路由层被路由至中心机房执行,其他读操作可路由至任意机房,如下图所示。
当路由层对写全局数据的流量发生路由错误从而在非中心机房执行时,如果中心机房和非中心机房同时修改同一商品的数量,就可能导致数据冲突问题,为了防止这种情况的发生,业务微服务可以挂载Sermant的数据库禁写插件,禁止在非中心机房写入global数据库。
在非中心机房禁止写入global数据库,需要通过动态配置中心下发如下配置:
enableMySqlWriteProhibition: true mySqlDatabases: - global
其中,enableMySqlWriteProhibition表示开启对MySQL数据库的禁写能力,mySqlDatabases用于指明具体的禁写数据库名称,本示例为global数据库。
下发配置后,当路由异常的流量在非中心机房写入global数据库时,数据库禁写插件对业务微服务抛出java.sql.SQLException异常,并禁止写入该数据库。业务系统需要处理该异常,比如加入重试操作重新路由该流量至中心机房执行,以保证系统的正常运行,执行逻辑如下图所示。
数据库禁写插件在异地多活场景下禁止对指定数据库的写入能力可以防止异常流量的写操作,保证不同机房数据库的数据一致性。
在异地多活场景下,Sermant的消息队列禁止消费插件可以实现可用区故障时消息队列的切流问题,让正常可用区的消费者消费数据;数据库禁写插件则用于禁止写入指定的数据库,并且不影响读数据库,防止发生数据冲突问题。
Sermant在异地多活场景实现了丰富的服务治理能力,未来,Sermant还将持续发力,逐步构建更加完善的服务治理能力体系。
Sermant作为专注于服务治理领域的字节码增强框架,致力于提供高性能、可扩展、易接入、功能丰富的服务治理体验,并会在每个版本中做好性能、功能、体验的看护,广泛欢迎大家的加入。