又fix了一个bug
收到用户反馈,功能不好用了:
“这个列表是坏了嘛 我今天给一下午都点不开”
抓紧在本地复现了下:
测试环境是没有问题的。
线上的页面打开慢、还会报错:"request timeout"
去日志中查了下,是查询TableStore时报错了:
content:2022-07-28 20:06:44[7e165900997904910398fe22 ][http-nio-8080-exec-11] ERROR c.z.common.dto.ApiResponse - failure :{"datas":null,"message":"request timeout","path":"/v1/waitInvoicePage","status":500,"timestamp":1659011104445}Caused by: com.alicloud.openservices.tablestore.TableStoreException: request timeoutcom.alicloud.openservices.tablestore.TableStoreException:request timeout
表格存储(Tablestore)面向海量结构化数据提供 Serverless 表存储服务,同时针对物联网场景深度优化提供一站式的 IoTstore 解决方案。适用于海量账单、IM 消息、物联网、车联网、风控、推荐等场景中的结构化数据存储,提供海量数据低成本存储、毫秒级的在线数据查询和检索以及灵活的数据分析能力。
根据日志中的traceId,去全链路跟踪平台上查看下整体情况:
异常:com.alicloud.openservices.tablestore.TableStoreException:request timeoutat com.alicloud.openservices.tablestore.core.CallbackImpledFuture.getResultWithoutLock(CallbackImpledFuture.java:107)at com.alicloud.openservices.tablestore.core.CallbackImpledFuture.get(CallbackImpledFuture.java:89)at com.alicloud.openservices.tablestore.SyncClient.waitForFuture(SyncClient.java:502)at com.alicloud.openservices.tablestore.SyncClient.search(SyncClient.java:467)
就是查tableStore失败了,在日志平台查下看到,因为查询参数太长,日志平台直接进行了截断!!!
估计此处有坑
{ "dataSize": 26, "type": "STRING", "value": "2103241740451151094605"},
{"dataSize": 26, "type": "STRING", "value": "3241741331950505477"},
{"dataSize": 26, "type": "STRING", "valu收起
继续看日志。前端传来的查询参数并不多,
但在对TableStore进行查询时,却传了这么多参数,应该有情况!
继续跟。梳理下这个服务的业务逻辑。这个业务域的数据主要由一个Header表和一个Item表来承载。
一条完整的业务数据包含一条Header数据和至少一条Item数据。
这个服务返回的数据需要满足以下特征:
上面的需求翻译成sql是这样的:
select header.filed_name1,item.filed_name2,item.filed_name3 from header ,item
where header.id=item.headerId and header.status='Finished'
and item.filed_name_x='指定的筛选条件'
and item.filed_name_y='指定的筛选条件'
。。。其它筛选条件 。。。
查到此处,是不是有小朋友有疑问了:不就是一个sql搞定的事,为什么报错的是TableStore,而不是MySql?
这是个好问题! 如果header表和item表中数据量在10万以下,直接用上面的SQL就可以把这个需求摆平了。
如果数据量是百万级时,估计就会慢得像蜗牛。
对数据库有了解的同学,肯定会讲sql查询慢,可以通过加索引来解决! 是的,sql查询慢时,要考虑加索引。
如果查询条件有19个,并且都是可选的,
这种情况下,索引该怎么加? 要想解决速度问题,至少要加15+索引。一些区分度低的字段,就不适合加索引
这也是没有查MySql,而是查询存在TableStore上的数据的原因之一。
索引:在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。 索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。
为什么uat环境正常,但线上环境坏了?
ua环境和线上,这两个环境上的数据量级是不同的。
uat的数据不到2万,线上的数据是百万级,接近千万。 这也是为什么测试环境是ok的,到了线上,功能就坏了的原因。
经过上面的排查,来简单梳理一下:
从数据角度拆解这个需求,就是所需数据存在两个张表,这两张表中是1:n的关系。 这个需求中的筛选条件,Header表中有,Item表中也有。
问题:
headder_id_list 在线上的数量是50万+,并且随着业务开展还会持续增加,
直接in这么多数据,TableStore也抗不住,直接尥蹶子了
com.alicloud.openservices.tablestore.TableStoreException:request timeout
根据上面的分析,可以得到以下判断:
问题来了:是哪个环节做错了,导致这个功能坏了?
这样看来,数据模型没错,技术方案在方向上也没有明显的问题,
只是没考虑到TableStore不能很好支持in超过5万+的数据
那么,问题的根子在哪? 消费数据的维度与持久化数据的维度不一致。 存数据,是结论先行、以上统下。拆成Header表、Item表。 消费数据,是以局部看整体。Item上的数据体现了一个具体业务的整体情况。
这个不一致,引发了线上功能的各种坑。
解决方案: 数据异构。建立一个Item维度的一个异构数据,或者理解为CQRS模型中的查询Model也可以。 这个异构数据包含了业务需求中涉及到的所有数据,数据查询都来查这个Model。即SRP:将写、读操作拆到独立的上下文。
最终的技术方案如下:
什么是异构?简单的说就是指一个整体中包含有不同的成分的特性,即这个整体由多个不同的成分构成。 什么是数据异构?按照不同查询维度建立表结构,这样就可以按照这种不同维度进行查询。 数据异构有查询维度异构、聚合数据异构等。 如何完成异构数据的落地? 通过MQ机制接收数据变更,然后原子化存储到合适的存储引擎,如TableStore、Redis、ES或持久化KV存储。 数据闭环和数据异构其实是一个概念,目的都是实现数据的自我控制,当其它系统出问题时不影响自己的系统,或者自己出问题时不影响其它系统。一般通过消息队列来实现数据分发。 CQRS模式【命令查询的责任分离,Command Query Responsibility Segregation】:通过将数据存储和数据查询这两个关注点分离。通过分离读写操作,可以实现数据异构,进而提升查询性能。 C(Command)端负责数据存储,Q(Query)端负责数据查询,Q端的数据通过C端产生的Event来同步。
优化前:报错或者耗时24s+
优化后: 没有报错了。最少耗时33ms
在大数据量、多筛选条件、高访问量时,使用数据异构是非常有效的。
不过数据异构也增加了架构的复杂度,需要团队根据各自的技术积累,慎重权衡。
异构可以通过订阅MQ或者解析Binlog来实现业务数据存储和构建异构数据这两件事的解耦。
~~~~
相逢一笑泯恩仇。 政治是政治,私交是私交。 我可以与你的政治主张不同,但我可以跟你是好朋友。 逆旅行人: 外任十多年,苏东坡吃尽了变法派的苦头。当司马光决定废除新法时,他又唱起了反调。 满肚子不合时宜,哈哈哈哈 九死南荒吾不恨,兹游奇绝冠平生 两人在青州十年安逸的生活,是最治愈她的记忆 昨日莫名惴惴不安,若有所失,无法平静。 奇怪了