TableStore设计目标是快速读写简单的表数据,所以对单列大小限制在2M Byte(一次写入超大cell会引起系统波动),对大部分用户来说,这个值都是足够的,但是仍然有一些用户碰到了如下问题:用户自身绝大部分cell都能保证低于2M,按时有极个别的Cell高达20M,此时如果专门为这些大cell使用另外的存储无疑增加了系统复杂度,于是“如何利用Table Store存取大cell”就被提了出来。
我们给出的方案如下:
- 对需要写入的大cell按照规定大小拆分,比如1M,比如10M的cell会被拆成10个1M的片段,称为S1,S2…S10
- 写入流程如下,针对某一行,逐个写入S1,S2…S10,每次写都要携带一个状态列,该状态列是保证读写一致性的重要手段,详细描述如下:
a) 状态列由三部分组成,分别是写入ID,已经写完的片段集合,未写完的片段集合
b) 每次有大cell写入事件都生成新的UUID作为该次事件的ID,防止并发写带来的数据不一致
c) 第一次写,状态列内容如下{ID;;S1,S2…S10}, 里面有三个字段,以字符;分割,第一个是该次写入事件ID;第二个表示已经写入成功的cell片段,因为是第一次写,所以是空;第三个表示等待写入的cell片段,因为是第一次写,所以是S1,S2…S10都属于待写入
d) 第一次写成功后,S1 cell片段已经成功写入,此时继续写入S2片段,新状态列内容变为{ID;S1;S2,S3…S10}, 此时要使用TableStore的带条件更新功能,必须检查TableStore里面的状态列内容等于原状态列内容,然后才能写入新的状态列;否则说明有多线程并发写该列,要终止此次写入,重新开始
e) 按照上面办法依次写入S2,S3…S10片段
-
读取流程如下,
a) 尝试读取状态列,如果读不到,说明该行没有大cell,跳过 b) 读到了状态列之后,检查里面待写入cell片段是否为空,如果不为空,说明正在写入,等待并重试 c) 如果状态列中待写入cell片段为空,说明写入已经完成,则开始读,每次读过来的状态列都要跟上一次读的状态列比较,如果发现不同,则重新读取 d) 所有列读出来后,拼接返回给调用方
我们给出了示例的代码并测试了32M大cell写入,整个写事件耗时26s,读事件耗时13s。
注意事项:
- 上面的方案是基于已有的SDK构建的,并不会修改SDK,所以该方案和SDK发布没有绑定关系;
- 如果写过程中应用进程down,会留下垃圾数据(读不会成功,因为部分数据校验会失败),用户可以选择主动清理,或者重新覆盖;
- 上述功能依赖新版本的限制项放开,近期会升级,紧急需求可以直接论坛发帖。
源码:https://github.com/aliyun/aliyun-tablestore-big-cell-example