查看原文
其他

精选文章|客户端分片到Proxy分片,如丝般顺滑的平稳迁移

yjh 得物技术 2023-01-17



客户端分片到Proxy分片,

如丝般顺滑的平稳迁移


背景

随着订单数量的增多,以及大促时需要扛住比平时多N倍的流量,单库单表的瓶颈日益显现。

需要对数据库进行水平拆分,目前订单使用的是客户端分片的方式进行拆分,采用Sharding-Jdbc框架实现。


Sharding-Jdbc后改名为ShardingSphere,提供client和proxy方式进行数据库的拆分,目前订单使用的是client方式。


client方式的优势是实现简单,只需要通过简单的配置即可完成拆分操作。

在本地通过分片进行计算,得到真实的库和表进行路由,性能相对较高。

不依赖于三方,没有单点故障。


client方式的劣势是每个项目都要去管理分片,读写分离等信息,没办法统一进行管理。

当需要升级的时候只能所有项目都进行升级,没办法统一升级。

最难的点在于需要推动各个业务域进行升级,升级的周期较长,对业务方的压力也比较大。

相对于proxy方式,client方式连接数占用的比较多,一个数据源10个连接,部署10个实例就是100个连接。


proxy方式指的是部署一个独立的服务,这个服务会实现Mysql协议,应用中只需要连接这个独立的proxy服务,把它当做一个完整的独立的数据库使用即可。

分库分表等规则全部由这个proxy去管理,对应用透明。


彩虹桥DAL是得物基础架构组研发的proxy方式的代理服务,有了彩虹桥就可以统一管理所有数据库,可以对数据库操作做限流保护,可以做压测时的数据自动进入影子库,可以监控慢SQL等等。


接入方案

直接修改老数据源-改成连接彩虹桥



最简单,最快速的方式就是直接将所有老的数据源改成新的代理数据源,然后重新发布就可以了。

这个方案确实快,但是不够好,不够完美,不够稳定,为啥?


这其实跟改接口是一样的道理,当某天加了个新的需求,你会发现直接在老接口的基础上改一下就完成了。改完后测试发现当前没问题,但是老版本的产品还在用,然后兼容出问题了。这种场景下如果你是对接口进行升级,之前是v1,现在改成v2,老的逻辑完全不动,就完美了呀。


如果我们直接改成最新的方式,那么如何平滑的发布上线呢?


全部发布,万一彩虹桥那边配置有问题,或者有其他的问题导致不可用,你这不就尴尬了么,当然也可以回滚历史版本。


另一种方式就是灰度发布一台机器,进行测试,没问题后全部发布,有问题就不发,其实也不错。就怕过了一段时间,万一彩虹桥有Bug, 我们还是得回滚到接入彩虹桥之前的版本。


新增一套数据源 - 支持开关切换到新数据源,下线老数据源


新增数据源,支持开关切换就跟升级API版本是一样的逻辑。保持老逻辑不变,上线时还是走老的数据源,然后通过开关动态切换到新的数据源,完成上线动作。


当然这边也会出现上面提到的问题,比如彩虹桥不可用之类的情况,也可以通过灰度配置的方式来测试。将开关配置灰度到某个节点上,只切这个节点的数据源,进行测试,没问题后可以扩大范围。有问题开关一关就还原到之前的逻辑了,无影响。


如果用了一段时间,彩虹桥有Bug的话同样改下开关的配置即可回滚,也不用重新发布项目。


此方案不足的点就是会占用一分部数据库的连接,因为老的数据源还存在。此时需要改代码去掉老的数据源,就能释放了。如果后面还要去改代码,那还能叫完美的方案么?


这点我也考虑到了,还是通过配置开关来关闭老的数据源,但是这个操作得重启服务,重启后就只有一套数据源了。(请确保以后只用彩虹桥的方式才用此开关


实现步骤

  1. 配置2套新数据源,连接彩虹桥的地址,订单的库分为老订单库和新的分库分表库。

  2. 新增2个动态数据源,用于开关动态切换,使用AbstractRoutingDataSource管理。

3. 老数据源通过@ConditionalOnProperty来控制是否加载,默认加载,用于后期下线老数据源。

4. 读写分离兼容client和proxy方式。

5. 分片算法重写,之前用的Sharding-Jdbc3.X版本,新的彩虹桥基于5.X版本深度定制开发,在自定义算法这块有变化,目前彩虹桥的分片算法全部在彩虹桥的扩展包中,不在订单里面。


注意事项

select last_insert_id()不支持


在insert中通过select last_insert_id()实时返回当前插入的自增ID场景需要修改,目前订单中就一个地方用到了,而且上层其实没消费这个ID, 如果后面有其他场景需要获取刚插入的ID可以手动提前获取分布式ID,然后再用这个ID存到表中。


强制走从节点查询


老的读写分离用的Sharding-Jdbc做的,正常情况下默认查询走从节点,其他走主节点。而现在订单里面默认都是走主节点,如果需要强制走从节点需要使用HintManagerHolder.clear() 清空Hint信息来实现。


猜测最开始没有从节点,业务都走主节点。后面加了从节点,如果用默认的方式查询都会走从节点,但是在某些业务场景中一致性比较高,必须走主节点查询,相对于标记出哪些查询走从节点来说,默认全部走主节点更稳。然后根据业务场景再标记是否要走从节点,这样几乎不会影响老的逻辑。


接入彩虹桥后,默认是彩虹桥和Sharding-Jdbc两套数据源共存的,所以在HintManagerHolder.clear() 这块也需要做兼容,同时支持彩虹桥和Sharding-Jdbc方式的强制走从节点。


第一步:兼容老代码


将所有使用HintManagerHolder.clear()改成SqlHintUtils.clear()。

public class SqlHintUtils {
   /**     * 清空Hint信息     */    public static void clear() {        // ShardingJdbc        HintManagerHolder.clear();        // 彩虹桥DAL        BifrostContext.clearNotAutoClearPrimary();    }
}


第二步:新增注解强制走从节点,比代码更优雅


以后有新的查询需要走从节点就在Dao方法上增加@ForceSlave注解即可,此注解只能作用于Dao方法上,加在其它层无效。如果Dao方法上加了注解,那么方法内所有的查询操作都将走从节点。


老的clear相关的代码其实可以用注解代替,但为了保险起见还是不改变原有的方式,新的可以用注解的方式。


参考文档

shardingsphere:https://shardingsphere.apache.org/document/current/cn/overview/





关注得物技术,携手走向技术的云端

文|yjh



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存