1、 Sharding-JDBC:做最轻量级的数据库中间层
基于关系型数据库的水平扩展方案有很多开源的解决方案,例如dbproxy、mycat,是通过改写mysql协议,将中间件封装成“mysql”,后端的读写分离等策略对开发者不可见,常见的架构如下:
但是这种代理方式简化了开发过程,对于开发者无需关系读写分离及故障隔离机制,但是会有一定的性能损失,dbproxy的高可用改造也至关重要,给运维同学带来较大的工作量。
Sharding-JDBC 采用在 JDBC 协议层扩展分库分表,是一个以 jar 形式提供服务的轻量级组件,其核心思路是小而美地完成最核心的事情。
Sharding JDBC能够实现在应用层实现分库分表及故障隔离机制,能够给开发者最大的灵活性去控制数据库读写,同时Sharding JDBC是通过直连数据库节点的方式与数据库交互,降低了代理层的性能损失,同时运维只需要关心节点间数据的同步即可,无需考虑Proxy层的高可用问题。
2、数据库节点故障自动隔离机制实现
对于某些节点故障,我们程序应该能及时感知并对其进行隔离,等节点故障恢复后,程序应该有自动恢复的能力,利用Sharding JDBC 能够很方便的实现该能力。
(1)数据源健康检查隔离算法实现
如何确定数据源已经不可用,偶发的网络抖动等问题是不是应该立马隔离该数据源,我们的策略如下:
每5秒钟对数据库进行一次心跳检测,1分钟内错误率高于80%,则认为该节点已经不可用,实现对该节点的故障标记。
故障标记后继续对该节点进行心跳检测,如果心跳检测连续5次正常,我们认为数据库已经恢复,去除该节点的故障标记。
通俗来讲,我们可以在一分钟开始到一分钟结束的时候这个时间段来统计错误率,那么如何控制边界问题,通俗来说:比如说上一分钟后半段跟下一分钟的前半段错误率已经达到了80%,理论上应该隔离这种情况,但是我们的模型可能不能够处理这个问题,因此我们引入了滑动窗口的概念。
我们利用了Guava的Evicting Queue(“排挤队列”),该队列在队列满的时候队头的元素会自动出队列。
如果当次数据源心跳检测失败,我们会将当前的时间戳放入队列中,下次再次故障的时候,在入队列的时候会首先检查当前的时间戳与队头的时间戳的差值是不是在一分钟之内,如果不在一分钟之内,即便是故障率达到80%也会忽略该隔离标记,如果在一分钟之内并且错误次数不少于10次(5秒钟一次,一分钟检测12次,错误率阈值80%, 12*0.8=9.6,向上取整10),则标记对该数据源的隔离。
接下来可以通过Sharding JDBC的自定义MasterSlaveLoadBalanceAlgorithm,实现对故障节点的隔离:
重写getDataSource方法,我们实现通过生成随机数随机获取数据源方法,AUTO_ISOLATE_DATASOURCE_NAMES为标记故障的数据源名称Map,(MANUAL_ISOLATE_DATASOURCE_NAMES为我们实现的手动隔离机制),如果随机获取的数据源被标记故障,则获取下一个。
我们会将所有数据源组成首尾相连的环,这样可实现对数据源的全部检索,而不是单链表数组,例如下图的单链表,假设我们被故障标记的节点为40、50号,此时hash正好映射到40号,我们会按顺序往下找,50号还是被隔离的,再继续就到nil节点了,导致正常的10、20、30不能够被利用。
如果是首尾相连的结构,我们在找到50的下一个时候可以重新到对头10,继续寻找可用节点,当然如果所有的节点故障,我们遍历一圈就会停止,不会无限制死循环。