让程序员头疼的微服务下数据聚合join(二)
回首
上文中我们介绍了冗余方案来解决了数据聚合join。
聚合服务
本文我们介绍另一种方案,聚合服务,如下图
从上图中,聚合服务先从订单服务、用户服务、商品服务中各自取出数据,然后再聚合服务中进行计算、组装。
此聚合服务的架构,在任何一个大厂,比如:阿里、京东、美团等,他们在网站页面上的数据,大部分都是通过后台的分布式聚合服务组装出来的。通常叫做聚合服务层(或者叫BFF层);此架构在复杂业务的微服务架构中普遍存在。
当然引入此方案,我们的系统架构的复杂度就高了一点,而且会衍生出其他的问题。
N + 1 问题
有的时候,为了获得A和B服务的聚合数据,可能A只需要调用一次,但是B却需要调用N次才能获取完整数据。
这个就是软件开发领域臭名昭著的N + 1问题,它通常是性能杀手
数据量问题
聚合服务把订单服务、用户服务、商品服务的部分数据加载到本地内存,然后才能进行聚合运算。但访问量大的时候,聚合服务会占用大量的内存开销,严重时可能会造成内存OOM。
业务服务发展问题
随着业务发展,业务服务也会越来越多,那么聚合服务需要聚合的数据也会越来越多。而且有些聚合出来的数据是可以重复利用的,如果不做缓存的话,那么会出现大量频繁和重复的聚合运算,白白消耗大量的服务器资源。
如何解决上面的问题?
数据分发 + 预聚合
此解决方案也叫CQRS模式
英文全称是Command/Query Responsibility Segregation;命令/查询职责分离模式
如上图所示,左边是Command命令端,通常只负责写入。右边是Query查询端,通常只负责读取。底层一般是数据分发技术,如MQ(kafka),将命令端的变更数据,通过MQ中,聚合服务订阅MQ,把数据聚合存储到Redis、ES、MongoDB或HBase等。
查询端调用聚合服务就可以查询预先聚合的数据。
小伙伴想一下,这个方案是不是跟数据库的视图View很类似,视图View也是聚合了数据,保障了查询性能。
写入端的数据存储,通常采用传统的SQL数据库。而查询端则可以根据需要选择适合的存储机制,比如如果通过KV健查询的话,可以选择Redis;通过关键字查询的话,可以采用elasticsearch;大数据领域可以采用Spark/Flink平台。
那左边的更新的数据,如何发送到MQ中呢?老顾开源的【彩虹桥】项目,采用了阿里的canal同步binlog实现发送到MQ中。有兴趣的小伙伴可以去了解一下。
项目地址:
https://gitee.com/gujiachun/bridge.git
最终一致性问题
按照上面的方案,能够支持大量的并发查询;非常好的解决了分布式数据Join。但是因为有MQ的分发,导致查询端会有一定的延迟问题。
按照上面的架构,延迟最多1-2秒。
如果有些业务对时效性要求很高,那应该怎么解决呢?
乐观更新
UI在发出请求后,马上更新UI,页面反应已经更新的数据状态。
比方说你点赞了某社交网站上的视频或图片,页面马上会显示一颗红心。然后页面后台再通过ajax等方式查询更新结果,
如果确认更新成功,那就不需要做什么;
如果确认更新失败,只需将页面状态回滚即可。
这种方式仅适用于一些简单的场景
拉模式
UI向命令端发出请求时,请求中带上版本号,然后通过ajax等方式不断轮询查询端,并检查更新后的视图的版本号是否和请求的版本号一致,直到版本号匹配为止,也就是等到视图明确更新成功或失败为止。
发布订阅模式
UI向命令端发出请求,同时通过websocket等方式订阅在查询端,查询端更新好视图,通过发消息通知UI更新页面展示。
总结
到此老顾就介绍完了微服务下的数据Join方案。小伙伴可以自行选择不同的方案,各有千秋。