前言
大家好,本文是博主在负责对企业项目新技术引入时,考虑不周的点,文中以第一视角对整个过程进行复盘以及事后分析。以 Mycat 为例。希望对大家有所帮助~
一、背景
在一次会议内容提到一个新的技术,是我没有涉及到的领域—分库分表技术预研,当时会议提到该领域其中一个技术点 Mycat
技术点,该技术是我没有涉及到,而在此次会议被安排负责该项技术的预研。
二、需求
接下来,我就花时间去了解 Mycat
这个东西,网上各种找资料,说实话,资料还是非常的多的,当时的想法就是满足现有的需求。当时 Leader 给我的任务是针对部分数据量大的表,进行单库分表。将这些需要分表的表按照月份进行分表。
比如订单表,将订单表分成:m_order_202201, m_order_202202, m_order_202203...
我就针对这个需求,去学习 Mycat 如何去实现按月分表。
三、Demo 环境搭建
每学习一个新的技术点,亦或是引入一个新的技术,需要将该知识点最简化。先搭建一个 Demo 出来,然后在基于 Demo 进行扩展。我觉得这样是可以充分的降低出现预期外的问题,使我们的学习、引入或集成更加的轻便。
很快,我在网上下载好了 Mycat 的程序,进行按月分表的实操。
一切从简,我首先是创建了一个测试表,表里总共两个字段,在 Mycat 中配置使用很简单的针对日期分表的规则。此时是很成功的,内心是无比的喜悦,觉得这个东西也没啥,非常简单。当天就快快乐乐的下班啦~
3.1、初遇问题
然而,第二天上班的时候,我发现一个问题,就是原有 Mycat 程序提供的按月分表,仅支持 date
和 datetime
字段类型的分表。我们的订单表(m_order)的创建时间(created_time),是bigint
类型的,也就是我们插入的创建时间是一个时间戳,而 Mycat 对时间戳方式的分表是不支持的,此时我上网找了半天,也没有针对时间戳进行分表的方案。
3.2、解决问题
突然想到 Mycat 是开源的,我是不是能增加一个按时间戳分表的方式呢?
说干就干,马上在 GitHub 上找到 Mycat 的源码,Fork 后拉取到本地。找到官方写的按月分表的类后,我也照着原有的代码,写了一个按时间戳分表的类,将其编译后,放入中间件源码包内,并配置分表规则。具体可以看我之前写的博客。Mycat实现单库水平分表、按月分表。
四、方案制定
此时按时间戳分表方式我们也解决了。但是面临一个问题,就是 Leader 是否允许咱们这样做?
临时做了几个方案,将其汇报给了 Leader ,看是否开会讨论一下方案。方案如下:
- 方案一:
在需要分表的表中增加一个字段,例如插入时间(insertDate)类型为datetime或者date,根据该插入字段进行分表。
优点:满足现有分表规则,不需要修改中间件源码。
缺点:需要修改代码,插入的时候,需要增加插入时间字段。 - 方案二:
修改源码,增加适配时间戳分表方式。
优点:最大程度降低了代码和中间件的耦合度,代码几乎不需要进行更改。
缺点:由于是自己写的分库(或分表)规则,对于以后中间件的升级不友好。 - 方案三:
按范围分表,提前规划好分表字段某个范围属于哪个分表。
优点:满足现有分表规则。
缺点:根据时间戳配置麻烦。 - 方案四:
选择其他分表方式,根据其他字段及规则进行分表。例如:取模。
优点:易于配置。
缺点:所有的表都会用起来,数据散列分布。
将这四个方案提给 Leader 后,跟 Leader 讨论后。 Leader 觉得不更改中间件源码,采用方案一,增加字段来进行分表。
原因就是觉得更改源码后,后续升级会比较麻烦。但是我觉得增加字段有点麻烦,此时这个分表的事情也不紧急,我就去问了一下Mycat的官方开发人员。如下图:
五、实战-项目集成
突然有一天下午,我们有一个地方项目爆出 MySQL 突然挂了,怎么也启动不起来,后面我们排查后发现是表损坏了。第二天上午,我们 Leader 就跟我们开了一个会,说单表数据量太大了,Mycat分表这个得提上日程了,刚好最近有一个地方项目要进行升级,顺便就把这个Mycat分表的推上去用。接下来我就拉取了一个 Mycat 的分支版本,并搭建配置 Mycat 版本的环境。
5.1、再遇问题
我们将其配置好后,发现项目启动后疯狂报错,各种 SQL 不兼容的问题,当时我人都懵了。
经过排查,发现我们原先引入的 Activity 工作流完全没办法用了,里面一些内置的 SQL 是不支持的。这块我是考虑不周的,没有对 SQL 进行充分的兼容测试。当天下班,我都在自责。
然后我马不停蹄的去总结了一些SQL不兼容的地方。如下:
增加(insert)
- insert插入的时候必须要带字段名,必须带分表字段
- insert into x select from y 的sql语句不被支持
删除(delete)
- 支持子查询删除方式delete from a where a.id in (select id from b), 但表不能起别名
修改(update)
- 不支持多表更新
- 分表字段不能被更新
查询(select)
- select中DISTINCT不支持对a.*的查询
- distinct 和 select 查询列中用子查询的会冲突,二者选其一
- 子查询where条件中不支持使用or
- 查询最好能够按照分表字段,否则查询效率不是太好
- 关于order by排序,Order by 后的排序字段必须是select的一部分,查询全局表的时候除外
然后我马不停蹄的将其汇报给 Leader ,并说明了我在这块考虑不周全,没有做好。
5.2、总结问题
此时我的问题已经很明显了。对新技术没有进行充分的了解和熟悉,没有对此项技术进行充分的测试,没有考虑到新技术引入进来所带来的耦合度的提升。各种各样的原因吧,没有考虑清楚。
六、持续引入集成
到这里了,大家可能会很好奇,为啥有这么多不支持的点了,还要继续引入呢?
结合当时的项目和业务使用场景,以及 Leader 对 Mycat 技术栈的信任,工作流模块是可以直接干掉的(因为这个东西没有什么人用)。我们就开会讨论,后续如何的集成 Mycat 分表;此时我们已经消耗了大半个月的时间了。紧接着我们就评估集成后的改动工作量。我和另一位后端同学评估出来的工作量大概是10个工作日,随后 Leader 就协调了一位后端同学跟我一起改,协调了另外一位测试同学帮助我们测试。
6.1、项目改动
集成Mycat后,项目SQL改动进行的如火如荼。发现了很多改动都是差不多的,大部分都是在子查询中加了 OR 的条件查询,改动起来也很快。
遇到一些查询结果不一致的,还需要在代码中调整。例如,连表查询统计数量的时候,我要统计近七天的数据,Mycat会将每一个分表后的表的这个日期都统计一遍。假设我统计2022-01-01到2022-01-07的数据,此时我的订单表(m_order)分成了12个表,那么2022-01-01会有12条数据,加起来就一共有 7*12 = 84 条数据。接着我们还要通过代码进行分组(还好这块 JDK1.8 的 stream 流操作丝滑)。在这之中,也将之前一些同事写的bug优化了~
6.2、致命问题
我和另一位后端同学正改的起劲的时,突然发现一个致命的问题,就是有一个地方进行新增操作的时候,增加了Spring 的注解 @Transactional
导致业务层代码一直挂起,无法响应。这里分享出来,感兴趣的同学可以看一下。代码如下:
Controller层
@RestController
@RequestMapping("/order")
@Slf4j
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/save")
public Object save(Order order) {
int save = orderService.save(order);
log.info("保存完成==>" + save);
if(save > 0) {
return "success";
}
return "fail";
}
}
Service层
public interface OrderService {
public int save(Order order);
}
Service实现
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private OrderMapper orderMapper;
@Override
public int save(Order order) {
log.info("开始保存数据");
int insert = orderMapper.insert(order);
log.info("插入数据==>" + insert);
return insert;
}
}
Mapper层
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
}
日志输出
开始保存数据
插入数据==>1
Controller层的日志一直没有输出,也就是进入到Service层执行完业务代码后,一直没有出来。此时我也不知道啥问题,应该也有人遇到这个问题吧,我就打开了 GitHub,在 Issues 检索,发现了这个。如下图:
当时想,不妨试试,反正要不了多少时间。结果,还真的可以了,数据也进入到数据库了,此时有些人可能觉得已经解决了这个问题,但是并没有。事务是何等的重要,没有事务,无法保证程序数据的一致性和完整性,这将是致命的大问题。
6.3、寻找解决方案
我在找了一周的解决方案后,没有找到一个合适的解决方案。例如:XA事务,虽然可以回滚,但是经过测试,是非隔离的,会出现更多的问题。
之后,我跟一位朋友聊天时,问了问他以前是否使用过Mycat?如下是我跟朋友的聊天,大家可以感受一下。
我:你们以前用过Mycat
吗?朋友:用过,是好几年前的事情了,但是几年前
Mycat
停更过后,也就再也没有使用过Mycat
了。我:之前公司使用过
ShardingSphere-JDBC
,但是觉得跟代码耦合度太高了。朋友:
ShardingSphere
有好几种模式,一种叫ShardingSphere-JDBC
,属于SDK
形式嵌入的,还有跟Mycat
对标的ShardingSphere-Proxy
,这个才是独立的部署服务器的,就看咱们怎么用。
虽然短短几句,我意识到自己有很多的不足。技术广度不够,没有仔细的去比对各项同类技术,导致技术选型的时候出现了许多的问题。其次是对技术的关注度不够,Mycat已经停更过,咱都不知道。
后续就是朋友建议我把 Spring 的 DEBUG 日志和 Mycat 的 DEBUG 日志打出来,分析看看。我到现在也没分析出来啥问题(手动难过)。
第二种就是同事建议我是否可以手动的去打开、提交或者回滚事务,也是行不通的,根本不回滚。
这个问题困扰了我很久,迟迟无法解决(手动头痛)。
GitHub上也有很多类似的问题,一直没有得到解决
6.4、最终方案
后来实在是解决不了了,就跟 Leader 说明了情况,从各个方面演示这个问题,以及我针对这个问题找的解决方案。他也暂时没有好的解决方法。
当时项目有新需求需要紧急开发了,也不消耗时间去考虑其他分表方案了。最终决定后续可能采用数据归档的方式来处理。
七、总结
经历了这件事情,从开始到后面不完美的闭环,意识到了自己很多的不足:
- 学习一个新技术,只关注眼前,只听别人说有多好用,自己怀着猜想的方式去学习,别人说可以这样,殊不知还有很多坑等着自己去踩。
- 在引入一个新的技术点,尤其是针对数据源的这类技术,考虑不全面,没有对整个项目的使用场景进行一个充分的测试。
- 对技术的关注度不够,技术点是否有巨大变动,完全不知。
- 技术广度不够,对于自己学习的技术,没有形成一个知识网络,导致技术选型的时候,没有方案可选。
读到这里,想必你也学习到了博主的一些经验,不管是之后学习或是工作或是其他方面有帮助的话,别忘了三连支持博主呀~
我是微枫Micromaple,期待你的关注~💪💪💪