文章目录
这篇文章原本叫《如何做到不连表查询》,因为我对这个事一直耿耿于怀。在上家公司我经常被连表折磨(连的多性能差),最多的有二三十张。20年看阿里Java开发手册里面提到表连接不要超过三张,我一直把这个当作笑话。直到入职这家新公司,现在有四个多月了,我没连一张表。
年初的时候,看完了《凤凰架构》,上个月看完了《微服务架构设计模式》,加上这段时间的思考,对微服务有了新的理解。
一、引出问题
举一个例子,在上家公司有一个列表叫线索列表,列表展示的字段有60多个,离谱的的是这里面很多字段都要筛选。下面的每一个字段都要去关联一张或多张表才可以做到筛选
- 是否关注公众号
- 是否添加企微
- 用户标签
- 最后一次call时间
- 意向分
- 用户触达次数
- 重点客户标签
- ...
在离职不久前,还接到了一个离谱的需求:要求记录用户的每次访问标记,并且可以支持随意标记搜索,比如用户访问了A、B、C、D, 那么搜索其中任意一个就都可以搜索出来这个用户。
这些表里面有很多是百万、千万级别的。之所以系统还能运行,全靠硬件。
不可否认,经过一轮轮的折磨,我的SQL功底要强了不少。
二、微服务
2-1、微服务的技术
我翻看我之前写的博客,找到我最开始学微服务是在20年2月,发布了一篇名为《SpringCloud项目整合【eureka+ribbon+zuul+hystrix+hystrix dashboard】》的博客。
其实就如这篇博客的名字一样,最开始的微服务是Cloud,它的代表是很多对应的组件,我把这一套称之为微服务1.0。对于微服务1.0我并没有实战经验,因为在我学完它不久之后我发现了微服务1.1也就是(SpringCloud Alibaba)。
为什么说SpringCloud Alibaba是1.1,因为本质上它和微服务1.0很相似,每一个组件,在Cloud Alibaba里面都有一个与之对应的组件。21年4月,我写一篇SpringCloud Alibaba整合的博客,完整的SpringCloud Alibaba我是没有用过,但用过一些它的组件,比如现在公司的服务注册与发现用的是Nacos。
21年9月,我入职了一家新公司,这家公司用的是Kubernetes,这是微服务2.0。在23年3月我写了一篇谈谈我理解的SpringCloud和Kubernetes的区别, Kubernetes相较于Cloud那一套有了很大的改进,Cloud的一切都是嵌入到代码里面,而Kubernetes意在剥离这些东西,在使用Kubernetes的时候,我们的微服务本质上就是一个SpringBoot的单体项目一样(当然这个很大一部分原因是我们没有解决微服务里面所有的问题,比如服务降级限流这些)。
最近我又在书里面看到了未来的方向,服务网格和无服务器。从单体到微服务的演变过程中带了很多的问题,如:注册与发现、负载、全局配置、分布式事务、服务的熔断降级、服务之间的通信等。
在Cloud里面每一个问题,都有对应着一个技术组件,在Kubernetes 里也有对应的组件,但Kubernetes里面的组件相较于Cloud的来说它藏的深一点了,比如服务的注册发现、服务的负载上了Kubernetes就自动完成了。
其实上面的这些问题和业务本身没多大关系,我所理解的服务网格就是进一步弱化这些组件,让这些问题的解决变成一个通用的方案,以后只需要把我们写的服务(一个干净的boot服务)丢到服务网格中,就自然了拥有了这些功能。(这只是我暂时的一个理解,等我学习了之后后续会再出一篇博客来讲解)
2-2、微服务的目的
开始的开始我们用的是单体,所有的代码都写在一个服务,修改任何东西都是牵一发而动全身的,不利于系统的稳定和快速的开发,我理解这个就是微服务出现的目的。
上面讲了微服务会带来很多的问题,从而需要引入很多新的组件来解决这些问题,每一个组件都是一个新的技术。然后大家都被带偏了------去追求新的技术,很长一段时间(或许就是写这篇文章之前)微服务在我的脑海里就等于各个组件。
所以我们疲于奔命,学完一个又一个组件,但心里却不承认微服务,只觉得它很复杂,给我们的开发带来了更多的问题,而它的好处又体会不了太多。这是因为我们忽略的微服务最重要的是服务的拆分,而不是这些个组件。
好吧,其实大家不去学习服务拆分,转而学各个组件,这是对的。因为服务拆分很虚,且和业务强相关,面试也不问,面试问的更多的还是各个组件。
三、微服务的拆分
讲道理,我不太会微服务的拆分,因为我刚刚才意识到服务拆分的重要性,而服务拆分和业务强相关,不像技术组件那么具体,会与不会一眼鉴定。但我也可以讲讲我最近关于服务拆分的一点感悟。
以往我们的开发前后端产品这三个角色其实是很割裂的,大家都忙各自的。然而要想让微服务拆分的好是需要这三方一起配合,尤其是产品相当重要,产品可以不懂技术,但不可以不懂系统。
服务拆分有一个简单的点就是,各个服务维护自己的表,比如A服务想要获取B服务的数据,一定是要B服务提供接口,而不能是A服务去直接连B服务的表,如果某个特别复杂的业务必须要连表才可以,那就弄一个聚合服务出来,对于各个服务的表的增、删、改一定要控制在自己的服务里。
每个服务经过一段时间的开发都相对稳定,但新的服务也会一直有,新服务总免不了要去查询旧服务的数据,然而旧服务的增删改查早就准备好了,接口都是现成的,直接用即可。(以前我老大告诉我这个的时候,我说那得提供多少个接口啊,现在看来还是太年轻。其实所需的接口不会很多,有些表是不需要对外提供的)
每个服务都只维护自己的数据,跨服务去拿数据其实是有代价的,产品要知道哪个数据属于哪个服务(或者说是哪个模块),而不能一股脑的觉得数据都在数据库,随便拿,随便组合,如果是这样就会把系统做烂。
注:使用dubbo看似和openFegin一样,都是和本地一样的调用,但实际上差别很大openFegin还是用的Http协议,dubbo的性能要强很多。这就为组装数据提供了有力的帮助。
四、不连表查询
想想我们的系统真的有那么复杂吗?报表不应该属于业务系统,让报表去查业务系统它很容易导致数据库崩溃,抛开报表,我们的系统是不是就真的只有增删改查了?
需要连表的地方也就只有列表和详细。
我们要把列表理解成展示某个资源的地方(一个资源的数据应该存在一张表里面),作如下思考:
- 这个字段是否真的有必要在列表展示(列表长度有限,寸土寸金),是否真的有人去看
- 纵观阿里云控制台列表发现几乎没有一个列表让你可以不限制条件去搜索的
- 搜索的时候真的需要模糊吗?想一想作为系统的使用者,假如有个通过用户手机号来搜索的功能,你去哪里拿到用户一半的手机号
- 如果真的有个别要展示其它服务的数据,就通过rpc获取,然后组装到列表中去
- 字典数据统一先查询,再通过内存匹配
再举一个实际的例子,我们是做医疗系统的,有一个很重要的数据"病人",所有的数据都是围绕这个病人来的,大部人业务数据都冗余了病人的id,但大部人业务的列表都要基于病人的手机号、唯一编号、姓名来查询,我们是怎么做的呢?
- 正常情况下,我的第一反应就是 left join user 搞定,如果是这样,那你就多连了一张表,而且这个user表在其它服务是没资格去访问它的
- 我们提供一个三合一的Dubbo接口,先通过这个接口返回病人的id,再去业务表in这个id,这样in的数据永远不会多
对于详情,一个详情能展示多少个资源的信息呢?千万不要让资源污染(各个资源的数据放一起),一个详情接口可以返回很多资源的数据,但不应该放在一个实体里面,应该是很多个实体。而每一个资源都是单表的查询。
所以聊下来,全都是单表的查询,会存在什么性能呢?每一个数据的出入口都统一了,不会出现乱拿数据的现象,系统的维护性自然就高了。
这看起来一点也不简单,实际上也非常的困难。需要上下齐心一致,对每个角色的要求也很高。当然这并非不可实现,我现在的公司就是这样实践的。
五、微服务的好处
按照上面的拆分后,最大的一个好处就是,写代码变得很简单了,不会再有任何的性能问题,也不存在什么困难的问题,个人只需要当好螺丝钉,好好的写自己的业务代码就好了,幸福指数会很高。
它甚至比单体服务还要简单很多,试想一下,所有的操作都是单表的,最多就是做一下数据的聚合,这有多简单?这个也不是百分百的好处,相对连表就可以得出数据,它需要多次的rpc来组合数据(前期的开发会慢一些)。
六、微服务的坏处
这里我只说个人的坏处,它和好处是呼应的,试想一下,在这种环境下长时间的写代码你会有什么进步?除非你是微服务的掌舵人。
看似代码变得很简单,但它对人设计要求会高一些,你提供的Dubbo接口是给别人用的,你不能随意更改,在设计阶段需要多思考。
最近公司的前端在做一个事情就是,写Node作为聚合服务,让Node去调用后台的A、B服务来做数据的聚合。(这个好像也是阿里开创的)如果只是简单的数据聚合为什么还要Java呢?
而在不知多远的未来,服务网格和无服务的到来之后,Java不知是否还有用武之地。
不过也不必过于担心,但就目前国内来讲,大概率不会,因为老板和甲方是不讲道的,它们不懂微服务,这会导致你压根无法拆分成微服务,或者即便是拆分了,也无法维护起来,最终还是回归到了单体微服务(虽然拆分成了多个服务,实际上写的还是单体代码)。
拆分不合理的微服务,本质上就不是微服务,它不但没有给你带来好处,相反会带来很多的负担,服务多,入口乱七八糟的,很难维护。
七、应付当下
人在江湖生不由己,吃饭第一,如果没办法说服业务方,或者没有给力的团队,也有一些很拙劣的办法来达到业务目的。
在连表到了极限的时候,还可以尝试一些其它的办法。比如列表要展示是否关注公众号,可以把这个字段冗余到主表里面,每次用户关注/取消关注都实时去更新主表的字段。
具体的操作有两种方式
- 埋点,在关注/取消关注的地方发出一个事件,让线索服务感知然后去更新
- 监控数据库(本质上也就是监控binlog),当数据库的这个字段发生了变动发出事件,这个有很多的工具 比如 maxwell
这种方式也只能解决一对一的关系数据,如果是一对多还是要连表来查询。但是我们同样可以洗表,把N张表、洗成一张。
这种代价其实是很大的,要维护数据的一致性。
另外一个相对好点的方式是用搜索引擎,比如ES、MongoDB它们并非传统的关系型数据库,数据扩展性相对高一些,但依旧要面临数据的一致性问题。