概述
今年一直在和团队做微服务的架构改造(相关的一些详情,有兴趣的朋友,可以参见之前的这篇分享)。但是做过改造的朋友都知道 从“All-In-One” 到 “Micro-Service” 都需要迈过的一个坎,那就是垂直分库, 根据不同的子服务,将数据库拆分为不同的子服务库。
那么问题就来了,在开始做微服务改造前,我发现在摇旺的老系统中,有很多后台报表或者前端详情页所需的数据是通过SQL Join来完成的。但是,我们微服务改造后,每个服务背后的数据库已经在分布不同的实例中了,所以我们已经不能继续简单在SQL中使用join了,那么解决“跨库Join”就摆上了议事日程。
通过讨论和调研,垂直分库后,对于“跨库查询”的解决,可以采用以下几个思路:
1. 依赖字段较少:字段冗余
A库中的Tab1表需要关联B库中的Tab2表中的字段F, 我们就将字段F冗余到表Tab1中,那么查询时候,Tab1和Tab2就不需要做Join,单独查A库中的Tab1表就可以解决问题。
这是一个野路子,因为这是违反正常的范式设计的,但在依赖字段较少的情况下还是可以解决问题的,达到空间来换取时间的目的。不过这个方法最大的短板在于2点: 1. 依赖字段不能太多,2. 数据一致性问题。Tab2中的F字段一但改变,必须要同步到Tab1中,否则就会引起脏数据的问题。所以,需要在业务代码建立必要的同步机制,如果出错,还需要考虑引入人工补偿。
2. 依赖字段较多:表同步
在很多场景下,我们字段的依赖是很多的,乃至查询的时候可能需要跨多张表,这个时候方法1就无法直接用了,我们就需要进行表级别的数据同步,可以采用ETL工具来做到跨库的表同步。不过需要注意的是,数据同步不建议实时性过高,否则数据库的性能会受到比较大的影响。所以对于实时性不高的查询要求,表同步还是比较奏效的。
3. 静态字段依赖:数据字典表
对于不同库中的静态字段,可以建立一张数据字典表,可以将这类表在其他每个数据库中均保存一份,从而避免跨库join查询。如果静态数据表中的某些字段数据需要修改,可以采用一套脚本统一更新。
4. 服务层代码进行数据组装
通过各种服务查询到一个数据集,通过代码进行二次组装,然后生成我们需要返回给前端的对象。在实践过程中,对于处理过的查询集,我们可以将它们缓存在我们的分布式缓存中,减少服务间的RPC调用次数和数据库的查询压力。同时,注意设置好过期时间,把控好数据一致性和有效性。
const Koa = require('koa');
const Router = require('koa-router');
const redis = require('redis');
const { promisify } = require('util');
let app = new Koa();
let router = new Router();
let redisClient =www.yongshiyule178.com/ createRedisClient({
// ip为docker-compose.yml配置的redis-server别名 rd,可在应用所在容器查看dns配置
ip: 'rd',
port: 6379,
prefix: '',
db: 1,
password: null
});
function createRedisClient({port, ip, prefix, db}) {
let client = redis www.mcyulegw.com.createClient(port, ip, {
prefix,
db,
no_ready_check: true
});
client.on('reconnecting', (err)=>{
console.warn(`redis client reconnecting, delay ${err.delay}ms and attempt ${err.attempt}`);
});
client.on('error', function (err) {
console.error('Redis error!',err);
});
client.on('ready', function() {
console.info(`redis初始化完成,就绪: ${ip}:${port}/${db}`);
});
return client;
}
function execReturnPromise(cmd, args) {
return new Promise((res,rej)=>{
redisClient.send_www.tiaotiaoylzc.com command(cmd, args, (e,reply)=>{
if(e){
rej(e);
}else{
res(reply);
function batchReturnPromise() {
return new Promise((res,rej)=>{
let b = redisClient.batch();
b.exec = promisify(b.exec);
res(b);
router.get('/', async (ctx,www.yongshi123.cn next) => {
await execReturnPromise('set',['testkey','helloworld']);
let ret = await www.michenggw.com execReturnPromise('get',['testkey']);
ctx.body = {
status: 'ok',
result: ret,
router.get('/batch', async (ctx, next) => {
await execReturnPromise('set',['testkey','helloworld, batch!']);
let batch = await batchReturnPromise(www.zhongyiyuL.cn);
for(let i=0;i < 10;i++){
batch.get('testkey');
}
let ret = await batch.exec();
ctx.body = {
status: 'ok',
result: ret,
.use(router.routes(www.pingguoyul.cn))
.use(router.allowedMethods())
.listen(8090);
以上就是4种应对跨库Join的思路,实战中,一定是将这4类方案进行组合使用的,同时,需要注意的是,相比这些解决思路,更重要的是表结构的合理设计。否则要彻底解决跨库是很困难的。
分布式事务的处理方式
除此之外,分库后,还有一个难题,就是分布式事务的处理。具体的事例,可以参见我之前的这两篇文章1和 文章2。里面会提到在微服务下,服务间事务回滚的几个思路,希望对大家有用
最后
以上就是饱满外套为你收集整理的微服务改造中解决跨库问题的思路的全部内容,希望文章能够帮你解决微服务改造中解决跨库问题的思路所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复