基于Tablestore打造亿量级订单管理解决方案
一、方案背景
订单系统存在于各行各业,如电商订单、银行流水、运营商话费账单等,是一个非常广泛、通用的系统。对于这类系统,在过去十几年发展中已经形成了经典的做法。但是随着互联网的发展,以及各企业对数据的重视,需要存储和持久化的订单量越来越大。数据的重视程度与数据规模的膨胀带来了新的挑战。
需求场景
某电商平台A,需要进行持久化所有平台产生的订单数据。同时,基于所有的订单数据,系统又需要向外提供面向多种角色:消费者、店家、平台三类人群的多元化的查询服务。消费者可以查询自己的历史订单,商家可以统计热销产品,平台也可以分析用户行为、平台交易规模等。主要查询方式涵盖订单的多维度检索,以及订单数据的分析、统计等,例如:
面向消费者:【A消费者】*【近1年】*【卖出电脑】订单查询;
面向售货员:【B售货员】*【近1个月】销售订单;
......
技术点
在订单场景中,技术上通常需要考虑的技术点,主要包含如下几个方面:
- 查询能力:需要具备丰富的查询类型,如多维度、范围、模糊查询等,同时具备排序、统计等功能;
- 数据量:存储海量数据的同时,满足强一致、高可用、低成本等要求;
- 服务性能:应对高并发请求高并发的同时,保证低延迟;
实现多维、实时查询功能,是订单管理解决方案的核心功能,官网控制台地址:项目样例
二、方案演进
应对订单场景,电商通常会采用MySQL传统方案。借助关系型数据库强大的查询能力,用户可直接通过SQL语句实现订单数据的多维度查询、数据统计等。所谓数据膨胀,分为横向、纵向两种,横向即不断迭代引入的新字段维度,纵向即总的存储数据量。在面对这两种订单数据膨胀上,单MySql方案逐渐变得吃力。 SQL + NoSQL的组合方案(以下称:组合方案)便应运而生,借助两个数据库各自的优势分别解决不同场景各自的需求。但组合方案同样也带来了新的问题,组合方案牺牲空间成本,同时也增加了开发工作量与运维复杂度。在保证数据一致性上产生额外开销。
下面让我们看一下如下几个常规方案:
常规方案
1、MySql分库分表方案
MySql自身拥有强大的数据查询、分析功能,基于MyQql创建订单系统,可以应对订单数据多维查询、统计场景。伴随着订单数据量的增加,用户会采取分库、分表方案应对,通过这种伪分布式方案,解决数据膨胀带来的问题。但数据一旦达到瓶颈,便需要重新创建更大规模的分库+数据的全量迁移,麻烦就会不断出现。数据迭代、膨胀带来的困扰,是MySql方案难于逾越的。仅仅依靠MySql的传统订单方案短板凸显。
1、数据纵向(数据规模)膨胀:采用分库分表方案,MySql在部署时需要预估分库规模,数据量一旦达到上限后,重新部署并做数据全量迁移;
2、数据横向(字段维度)膨胀:schema需预定义,迭代新增新字段变更复杂。而维度到达一定量后影响数据库性能;
2、MySql+HBase方案
引入双数据的方案应运而生,通过实时数据、历史数据分存的方案,可以一定程度解决数据量膨胀问题。该方案将数据归类成两部分存储:实时数据、历史数据。同时通过数据同步服务,将过期数据同步至历史数据。
1、实时订单数据(例如:近3个月的订单):将实时订单存入MySql数据库。实时订单的总量膨胀的速度得到了限制,同时保证了实时数据的多维查询、分析能力;
2、历史订单数据(例如:3个月以前的订单):将历史订单数据存入HBase,借助于HBase这一分布式NoSql数据库,有效应对了订单数据膨胀困扰。也保证了历史订单数据的持久化;
但是,该方案牺牲了历史订单数据对用户、商家、平台的使用价值,假设了历史数据的需求频率极低。但是一旦有需求,便需要全表扫描,查询速度慢、IO成本很高。而维护数据同步又带来了数据一致性、同步运维成本飙升等难题;
3、MySql+Elasticsearch方案
组合方案还有MySql+Elasticsearch,该方案同样是将数据分两部分存储,可以一定程度解决订单索引维度增长问题。用户自己维护数据同步服务,保证两部分数据的一致性;
1、全量数据:将全量的订单数据存入MySql数据库,订单ID之外的数据整体存为一个字段。该全量数据作为持久化存储,也用于非索引字段的反查;
2、查询数据:仅将需要检索的字段存入Elasticsearch(基于Lucene分布式索引数据库),借助于Elasticsearch的索引能力,提供可以应付维度膨胀的订单数据,然后必要时反查MySql获取订单完整信息;
该方案应付了数据维度膨胀带来的困扰,但是随着订单量的不断膨胀,MySql扩展性差的问题再次暴露出来。同时数据同步至Elasticsearch的方案,开发、运维成本很高,方案选择也存在弊端。
能力分析 | MySql | HBase | Elasticsearch | TableStore |
---|---|---|---|---|
存储方式 | 行存储 | 列存储 | 索引存储 | 列存储+索引存储 |
扩展性 | 单机、扩展性差 | 水平扩展 | 水平扩展 | (自动)水平扩展 |
一致性 | 强一致性 | 强一致性、时序一致性 | 强一致性、时序一致性 | |
检索 | 较弱的支持 | 不支持 | 支持 | 支持 |
数据量 | ~ 1T,~亿行 | ~10 PB,~万亿行 | ~1 PB,~千亿行 | ~10 PB,~万亿行 |
表格存储(TableStore)方案
如果使用表格存储(TableStore)研发的多元索引(SearchIndex)方案,则可以完美地解决亿量级订单系统问题。TableStore具有即开即用,按量收费等特点。多元索引随时创建,是海量电商订单元数据管理的优质方案。
TableStore作为阿里云提供的一款全托管、分布式NoSql型数据存储服务,具有【海量数据存储】、【热点数据自动分片】、【海量数据多维检索】等功能,天然地解决了订单数据大爆炸这一挑战;
同时,SearchIndex功能在保证用户数据高可用的基础上,提供了数据多维度搜索、统计等能力。针对多种场景创建多种索引,实现多种模式的检索。用户可以仅在需要的时候创建、开通索引。由TableStore来保证数据同步的一致性,这极大的降低了用户的方案设计、服务运维、代码开发等工作量。
基于表格存储搭建的订单系统页面一览
样例内嵌在表格存储控制台中,用户可登录控制台体验系统(若为表格存储的新用户,需要点击开通服务后体验,开通免费,订单数据存储在公共实例中,体验不消耗用户存储、流量、Cu)。
注:该样例提供了【亿量级】订单数据。官网控制台地址:项目样例
二、搭建准备
若您对于亿量级订单系统的体验不错,希望开始自己系统的搭建之旅,只需按照如下步骤便可以着手搭建了:
1、开通表格存储
通过控制台开通表格存储服务,表格存储即开即用(后付费),采用按量付费方式,已为用户提供足够功能测试的免费额度。表格存储官网控制台、免费额度说明。
2、创建实例
通过控制台创建表格存储实例,选择支持多元索引的Region。(当前阶段SearchIndex功能尚未商业化,暂时开放北京、上海、深圳、杭州四地,后续逐渐开放)
创建实例后,提交工单申请多元索引功能邀测(现多元索引功能已商业化,无需申请)。
3、SDK下载
使用具有多元索引(SearchIndex)的SDK,官网地址,暂时java、go、node.js三种SDK增加了新功能
java-SDK
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>tablestore</artifactId>
<version>4.8.0</version>
</dependency>
go-SDK
$ go get github.com/aliyun/aliyun-tablestore-go-sdk
Nodejs-SDK
$ npm install tablestore@4.1.0
4、表设计
订单系统不仅仅是订单一张数据表,它应包含:消费者表、售货员表、产品表、供货商表、交易订单表、支付订单表等。在本样例中,主要使用最基本的四张表(消费者表、售货员表、产品表、交易订单表),仅以订单表举例如下:
表名:order_contract
列名 | 数据类型 | 索引类型 | 字段说明 |
---|---|---|---|
_id(主键列) | String | MD5(oId)避免热点 | |
oId(主键列) | String | KEYWORD | 订单编号 |
pName | String | TEXT | 产品名,TEXT类型索引可模糊查询,但不能排序 |
totalPrice | double | DOUBLE | 订单总价 |
orderTime | long | LONG | 下单时间(时间戳) |
... | ... | ... | ... |
三、开始搭建(核心代码)
1、创建数据表
四张表:订单表、消费者表、售货员表、产品表
用户仅需维护一个实例,按如下方式创建:通过控制台创建、管理数据表(用户也可以通过SDK直接创建):
2、创建数据表索引
TableStore自动做全量、增量的索引数据同步:用户可以通过控制台创建、管理SearchIndex(用户也可通过SDK创建):
3、数据导入
插入部分测试数据(控制台样例中插入了1亿条数据,用户自己可以通过控制台插入少量测试数据);
订单号 | 订单(md5)(主键) | 消费者编号 | 消费者姓名 | 售货员编号 | 售货员姓名 | 产品编号 | 产品名 | 产品品牌 | 产品类型 | 下单时间 | 支付时间 | 支付状态 | 产品单价 | 数量 | 总价钱 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
o0000000000 | c49f5fd5aba33159accae0d3ecd749a7 | c0019 | 消陈九 | s0020 | 售楚十 | p0003004 | vivo x21 | vivo | 手机 | 2018-07-17 21:00:00 | 否 | 2498.99 | 2 | 4997.98 |
消费者编号(主键) | 消费者姓名 | 消费者积分 | 注册时间 |
---|---|---|---|
c0001 | 消赵一 | 818 | 2018-07-07 14:33:51 |
售货员编号(主键) | 售货员姓名 | 售货员积分 | 入职日期 |
---|---|---|---|
s0001 | 售赵一 | 613 | 2018-07-07 14:27:59 |
产品编号(主键) | 产品名 | 产品品牌 | 产品类型 | 产品单价 | 新增时间 |
---|---|---|---|---|---|
p0001001 | iphone 6 | 苹果 | 手机 | 6969.00 | 2018-07-07 14:44:39 |
4、数据读取
数据读取分为两类:
主键读取
基于原生表格存储的主键列获取:getRow, getRange, batchGetRow等。主键读取用于索引(自动)反查,用户也可以提供主键(订单md5)的单条查询的页面,亿量级下查询速度极快。单主键查询方式不支持多维度检索;
索引读取
基于新SearchIndex功能Query:search接口。用户可以自由设计索引字段的多维度条件组合查询。通过设置选择不同的查询参数,构建不同的查询条件、不同排序方式;目前支持:精确查询、范围查询、前缀查询、匹配查询、通配符查询、短语匹配查询、分词字符串查询,并通过布尔与、或组合。
如【c0001号消费者,且消费在99.99以上的订单】组合方式如下:
List<Query> mustQueries = new ArrayList<Query>();
TermQuery termQuery = new TermQuery();
termQuery.setFieldName("cId");
termQuery.setTerm(ColumnValue.fromString("c0001"));
mustQueries.add(termQuery);
RangeQuery rangeQuery = new RangeQuery();
rangeQuery.setFieldName("totalPrice");
rangeQuery.setFrom(ColumnValue.fromDouble(99.99));
mustQueries.add(rangeQuery);
BoolQuery boolQuery = new BoolQuery();
boolQuery.setMustQueries(mustQueries);
基于Tablestore多元索引打造亿量级店铺搜索系统
一、方案背景
对于一套GEO管理系统,其核心点与瓶颈在于数据库的存储性能与查询能力;一方面,存储服务需要应对海量数据的低延迟存、读,另一方面,存储服务也要提供高效的GEO+多维度数据检索。表格存储(TableStore),作为一款Serverless分布式NoSQL数据库,完全具备该系统的需求。
下面我们将基于TableStore打造一个【亿量级GEO管理系统】;
需求场景
某店铺搜索平台,提供了亿量级的店铺信息。用户通过平台提供的PC端、移动端网页,按照自己的需求维度组合,搜索用户心仪的店铺。平台需要在地图上展示店铺的具体位置、店铺详细信息、店铺主页的跳转;
维度一:【距离1km内】【人均100以内】【评分最高】【奶茶店】;
维度二:【杭州市内】【评分最高的】【沈家*】店铺;
......
实现快速、多维GEO查询功能,是GEO管理解决方案的核心功能,样例如下:
注:该样例提供了【亿量级】店铺数据。官网控制台样例地址:项目样例
基于表格存储搭建的店铺搜索系统页面一览,样例内嵌在表格存储控制台中,用户可登录控制台体验系统(若为表格存储的新用户,需要点击开通服务后体验,开通免费,订单数据存储在公共实例中,体验不消耗用户存储、流量、Cu)。
表格存储(TableStore)方案
使用表格存储(TableStore)研发的多元索引(SearchIndex)方案,可以轻松搭建一套:亿量级店铺搜索系统。多元索引功能可以创建GEO索引、分词字符串索引等,为用户提供了GEO检索、多维组合检索等能力,用户可随时创建,存量、增量数据自动同步。
TableStore作为阿里云提供的一款全托管、零运维的分布式NoSql型数据存储服务,具有【海量数据存储】、【热点数据自动分片】、【海量数据多维检索】等功能,有效的地解决了GEO数据量大膨胀这一挑战;
用户可以仅在需要的时候创建、开通索引。由TableStore来保证数据同步的一致性,这极大的降低了用户的方案设计、服务运维、代码开发等工作量。
二、搭建准备
若您对于基于TableStore实现的【亿量级店铺搜索系统】体验不错,并希望开始自己系统的搭建之旅,只需按照如下步骤便可以着手搭建了:
1、开通表格存储
通过控制台开通表格存储服务,表格存储即开即用(后付费),采用按量付费方式,已为用户提供足够功能测试的免费额度。表格存储官网控制台、免费额度说明。
2、创建实例
通过控制台创建表格存储实例,选择支持多元索引的Region。(当前阶段SearchIndex功能尚未商业化,暂时开放北京,上海,杭州和深圳四地,其余地区将逐渐开放)
创建实例后,提交工单申请多元索引功能邀测(现多元索引功能已商业化,无需申请)。
3、SDK下载
使用具有多元索引(SearchIndex)的SDK,官网地址,暂时java、go、node.js三种SDK增加了新功能
java-SDK
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>tablestore</artifactId>
<version>4.8.0</version>
</dependency>
go-SDK
$ go get github.com/aliyun/aliyun-tablestore-go-sdk
Nodejs-SDK
$ npm install tablestore@4.1.0
4、表设计
店铺检索系统样例,仅简易使用一张店铺表,主要包含字段:店铺类型、店铺名称、店铺地理位置、店铺平均评分、人均消费消等。表设计如下:
表名:geo_positon
列名 | 数据类型 | 索引类型 | 字段说明 |
---|---|---|---|
_id(主键列) | String | MD5(pId)避免热点 | |
pId | Stirng | 店铺编号 | |
type | String | KEYWORD | 类型 |
name | String | TEXT | 店铺名,TEXT类型索引可模糊查询,但不能排序 |
pos | String | GEO_POINT | 店铺位置:"30.132,120.082"(纬度,精度) |
point | double | DOUBLE | 评分 |
... | ... | ... | ... |
三、开始搭建(核心代码)
1、创建数据表
用户仅需在完成邀测的实例下创建“店铺信息表”:通过控制台创建、管理数据表(用户也可以通过SDK直接创建):
2、创建数据表索引
TableStore自动做全量、增量的索引数据同步:用户可以通过控制台创建索引、管理索引(也可以通过SDK创建索引)
3、数据导入
插入测试数据(控制台样例中插入了1亿条数据,用户自己可以插入少量测试数据);
店铺编号 | 店铺(md5)(主键) | 类型 | 店铺名称 | 店铺位置 | 店铺评分 | 人均消费 | ||
---|---|---|---|---|---|---|---|---|
o0057022192 | 0000000f470ef0f548b925ceffe1a7e3 | 杭帮菜 | 韩村杭帮菜 | 36.76613,111.41461 | 2.87 | 63.67 |
4、数据读取
数据读取分为两类:
主键读取
基于原生表格存储的主键列获取:getRow, getRange, batchGetRow等。主键读取用于索引(自动)反查,用户也可以提供主键(订单md5)的单条查询的页面,亿量级下查询速度极快。单主键查询方式不支持多维度检索;
索引读取(店铺查询)
基于新SearchIndex功能Query:search接口。用户可以自由设计索引字段的多维度条件组合查询。通过设置选择不同的查询参数,构建不同的查询条件、不同排序方式;目前支持:精确查询、范围查询、前缀查询、匹配查询、通配符查询、短语匹配查询、分词字符串查询,并通过布尔与、或组合。
如【"36.76613,111.41461"周边1km米范围内的奶茶店】,查询条件如下:
List<Query> mustQueries = new ArrayList<Query>();
TermQuery termQuery = new TermQuery();
termQuery.setFieldName("type");
termQuery.setTerm(ColumnValue.fromString(奶茶));
mustQueries.add(termQuery);
GeoDistanceQuery geoDistanceQuery = new GeoDistanceQuery();
geoDistanceQuery.setFieldName("pos");
geoDistanceQuery.setCenterPoint("36.76613,111.41461");
geoDistanceQuery.setDistanceInMeter(1000);
mustQueries.add(geoDistanceQuery);
BoolQuery boolQuery = new BoolQuery();
boolQuery.setMustQueries(mustQueries);
基于Tablestore的海量保险单查询平台
背景
随着人们风险意识的提高与普及,越来越多的人愿意为自己与家人投一份保险,保险行业的飞速发展也带来了许多问题:海量的保险单该如何存储?如何高效地对保险单进行检索?传统的解决方案一般使用MySQL等关系型数据库对数据进行持久化与检索,但是随着数据量的上涨如何进行水平扩展变成了一个问题。近年来使用NoSQL这种分布式架构的存储引擎来存储海量数据越来越流行,Tablestore是阿里云自研的分布式NoSQL服务,有高并发低延迟、易于水平扩展等特点,可以很好地解决保险行业保险单存储的规模以及各种检索等需求。
需求分析
某公司推出一款寿险产品,每一位投保人投保后会在后台系统中产生一条订单数据,同时投保人可以通过平台查看自己的保单;与此同时,保险经纪人也可以通过平台多保单进行检索、追踪以及轻量级分析。具体需求整理如下:
-
保险用户
- 在线投保,生成保单
- 根据保单生效日期、失效日期检索保单
- 查看自己的保单详情
-
保险经纪人
- 根据投保人查询保单
- 根据保单生效日期、失效日期检索保单
- 查询某个用户快要失效的保单
- 根据受益人的受益百分比查询保单
- 根据保费对保单以及用户进行排序
技术选型
MySQL
作为一款开源的开源关系型数据库,MySQL有高性能、低成本以及可靠性好等特点,用户可以通过JDBC等工具使用SQL语句对数据库进行增删改查。但是MySQL也有他的不足:
- 随着数据量的膨胀无法水平扩展
- 对保险单的多维查询需求支持不佳
- 在大数量的情况下,写入性能较差
对于这些问题,第一条可以通过一些例如分库分表等手段解决,但是用户维护成本较高;第二条的话由于MySQL的索引引擎的实现,MySQL并不适合对数据进行多维检索分析,一般业界也会将数据导入到Elasticsearch、Solr等搜索引擎中对数据进行检索分析,但是这样同样提高了运维的复杂度。
Tablestore
Tablestore是阿里云基于谷歌Bigtable论文研发的一款分布式数据库产品,可以提供超大规模的存储容量,天然的分布式架构也提供了易于横向扩展的特性,理论上可以存储的数据量是不受限制的。在存储引擎上与MySQL相比Tablestore使用的是LSM-Tree,此种数据结构有天然的写入性能优势,特别适合存储保险单这类写多读少的数据。同时用户将数据托管在Tablestore上无需做任何的运维管理,大大减低了开发运维成本。
在数据检索方面,2019年年初Tablestore推出了多元索引(SearchIndex)功能,支持多维查询、GEO查询、分词查询等功能,完全满足保险单数据查询与轻量级分析的需求。多元索引与MySQL索引不同的是,用户只需要在需要索引的列上打开索引,既可以与其他列进行各种组合查询,而无需创建类似MySQL的联合索引。
所以基于Tablestore,我们给出如下的系统架构:
表结构设计
我们这边以普通的寿险保单为例,一张保单有如下的几类数据
- 保单ID
- 产品名称
- 用户信息(投保人用户ID、经纪人用户ID)
- 投保人信息、被保人信息与收益人信息
同时我们是支持了多受益人,这边我们选用了SearchIndex的NESTED数据类型来满足相关需求。具体Tablestore表与SearchIndex索引设计如下:
列类型 | 字段名 | 数据类型 | 索引数据类型 | 说明 |
---|---|---|---|---|
主键列 | policy_id_md5 | String | 不索引 | 保单ID的MD5,保证数据打散 |
属性列 | policy_id | String | KEYWORD | 保单ID,全局唯一 |
product_name | String | KEYWORD | 产品名称 | |
operate_time | Long | LONG | 投保日期,UNIX时间戳,毫秒 | |
effective_time | Long | LONG | 生效日期 | |
expiration_time | Long | LONG | 失效日期 | |
applier_user_id | String | KEYWORD | 投保人用户ID | |
broker_user_id | String | KEYWORD | 经纪人用户ID | |
applier_name | String | KEYWORD | 投保人姓名 | |
applier_id | String | KEYWORD | 投保人证件号 | |
applier_gender | String | KEYWORD | 投保人性别 | |
insured_name | String | KEYWORD | 被保人姓名 | |
insured_id | String | KEYWORD | 被保人证件号 | |
insured_gender | String | KEYWORD | 被保人性别 | |
beneficiary_info | String | NESTED | 多受益人信息 | |
premium | Long | LONG | 保费 | |
profit | Long | LONG | 保额 |
受益人信息在SearchIndex中为NESTED类型,在写入Tablestore表是需要写入一个JSON数组字符串,例如
[
{
"name": "Tom",
"id": "110101199909221111",
"gender": "male",
"benifit_percentage": 30
},
{
"name": "Lucy",
"id": "110102199201031411",
"gender": "female",
"benifit_percentage": 70
}
]
具体的字段类型如下
字段 | 索引数据类型 | 说明 |
---|---|---|
name | KEYWORD | 受益人姓名 |
id | KEYWORD | 受益人证件号 |
gender | KEYWORD | 受益人性别 |
benefit_percentage | LONG | 受益百分比 |
代码示例
代码可以在github上找到:https://github.com/aliyun/tablestore-examples/tree/master/demos/insurance-policy-management
SDK引入
引入Tablestore SDK 4.11.0
<dependency>
<groupId>com.aliyun.openservices</groupId>
<artifactId>tablestore</artifactId>
<version>4.11.0</version>
</dependency>
创建Tablestore表与多元索引
创建表
// Create table
TableMeta tableMeta = new TableMeta(tableName);
// Only one primary key column: policy_id_md5 with type String
tableMeta.addPrimaryKeyColumn(POLICY_ID_MD5, PrimaryKeyType.STRING);
// Set TTL to -1, never expire; Set maxVersions 1, only have one version per column
TableOptions tableOptions = new TableOptions(-1, 1);
CreateTableRequest createTableRequest = new CreateTableRequest(tableMeta, tableOptions);
syncClient.createTable(createTableRequest);
创建索引
CreateSearchIndexRequest createRequest = new CreateSearchIndexRequest(tableName, indexName);
IndexSchema indexSchema = new IndexSchema();
indexSchema.addFieldSchema(new FieldSchema(POLICY_ID, FieldType.KEYWORD));
// Other filed ommited
// Create nested Field
List<FieldSchema> beneficiaryInfoSchema = new ArrayList<>();
beneficiaryInfoSchema.add(new FieldSchema(NAME, FieldType.KEYWORD));
beneficiaryInfoSchema.add(new FieldSchema(ID, FieldType.KEYWORD));
beneficiaryInfoSchema.add(new FieldSchema(GENDER, FieldType.KEYWORD));
beneficiaryInfoSchema.add(new FieldSchema(BENEFIT_PERCENTAGE, FieldType.LONG));
indexSchema.addFieldSchema(new FieldSchema(BENEFICIARY_INFO, FieldType.NESTED).setSubFieldSchemas(beneficiaryInfoSchema));
createRequest.setIndexSchema(indexSchema);
syncClient.createSearchIndex(createRequest);
数据写入
本示例仅展示插入一条数据的样例,批量插入可以看demo中的ImportExampleData
RowPutChange rowPutChange = new RowPutChange(tableName);
// primary key
PrimaryKey primaryKey = new PrimaryKey(new PrimaryKeyColumn[]{
new PrimaryKeyColumn(POLICY_ID_MD5, PrimaryKeyValue.fromString(DigestUtils.md5Hex("POLICY_000000001837128")))
});
rowPutChange.setPrimaryKey(primaryKey);
// value columns
rowPutChange.addColumn(POLICY_ID, ColumnValue.fromString("POLICY_000000001837128"))
// Other columns are omitted
.addColumn(PROFIT, ColumnValue.fromLong(10_000L));
// nested field, must fill with a JSON array, you can use jackson or gson to generate JSON array
rowPutChange.addColumn(BENEFICIARY_INFO, ColumnValue.fromString("[\n" +
" {\n" +
" \"name\": \"Kelly Evans\",\n" +
" \"id\": \"285278192706139313\",\n" +
" \"gender\": \"male\",\n" +
" \"benefitPercentage\": 14\n" +
" },\n" +
" {\n" +
" \"name\": \"Ida Clark\",\n" +
" \"id\": \"418688200511062045\",\n" +
" \"gender\": \"male\",\n" +
" \"benefitPercentage\": 27\n" +
" },\n" +
" {\n" +
" \"name\": \"Corey King\",\n" +
" \"id\": \"909243194601171631\",\n" +
" \"gender\": \"female\",\n" +
" \"benefitPercentage\": 18\n" +
" },\n" +
" {\n" +
" \"name\": \"Susan Evans\",\n" +
" \"id\": \"288912191305043117\",\n" +
" \"gender\": \"female\",\n" +
" \"benefitPercentage\": 41\n" +
" }\n" +
"]"));
PutRowRequest putRowRequest = new PutRowRequest(rowPutChange);
syncClient.putRow(putRowRequest);
数据查询
通过申请人姓名和保单过期时间搜索
本需求是一个AND查询,在多元索引中,我们只需要将两个Query
放到BoolQuery
的mustQueries中即可:
SearchQuery searchQuery = new SearchQuery();
searchQuery.setGetTotalCount(true);
BoolQuery boolQuery = new BoolQuery();
TermQuery applierNameQuery = new TermQuery();
applierNameQuery.setFieldName(APPLIER_NAME);
applierNameQuery.setTerm(ColumnValue.fromString("Vernon Richardson"));
RangeQuery expirationTimeQuery = new RangeQuery();
expirationTimeQuery.setFieldName(EXPIRATION_TIME);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
Date date;
try {
date = simpleDateFormat.parse("2019-03-04");
} catch (ParseException e) {
throw new RuntimeException(e);
}
expirationTimeQuery.setTo(ColumnValue.fromLong(date.getTime()), true);
// use BoolQuery to combine other queries
boolQuery.setMustQueries(Arrays.asList(
applierNameQuery,
expirationTimeQuery
));
searchQuery.setQuery(boolQuery);
SearchRequest searchRequest = new SearchRequest(tableName, indexName, searchQuery);
// specify columns to get
SearchRequest.ColumnsToGet columnsToGet = new SearchRequest.ColumnsToGet();
// get all columns
columnsToGet.setReturnAll(true);
searchRequest.setColumnsToGet(columnsToGet);
SearchResponse searchResponse = syncClient.search(searchRequest);
通过受益人姓名和受益比例查询
受益人字段我们在多元索引中保存为nested类型,这边我们使用NestedQuery
来完成此功能:
SearchQuery searchQuery = new SearchQuery();
searchQuery.setGetTotalCount(true);
NestedQuery nestedQuery = new NestedQuery();
// search for nested field beneficiary_info
nestedQuery.setPath(BENEFICIARY_INFO);
nestedQuery.setScoreMode(ScoreMode.Avg);
BoolQuery boolQuery = new BoolQuery();
TermQuery beneficiaryNameQuery = new TermQuery();
// concat field name with `.`
beneficiaryNameQuery.setFieldName(BENEFICIARY_INFO + "." + NAME);
beneficiaryNameQuery.setTerm(ColumnValue.fromString("Tyrone Lee"));
RangeQuery profitPercentageQuery = new RangeQuery();
// concat field name with `.`
profitPercentageQuery.setFieldName(BENEFICIARY_INFO + "." + BENEFIT_PERCENTAGE);
profitPercentageQuery.setFrom(ColumnValue.fromLong(50), true);
boolQuery.setMustQueries(Arrays.asList(
beneficiaryNameQuery,
profitPercentageQuery
));
nestedQuery.setQuery(boolQuery);
searchQuery.setQuery(nestedQuery);
SearchRequest searchRequest = new SearchRequest(tableName, indexName, searchQuery);
// specify columns to get
SearchRequest.ColumnsToGet columnsToGet = new SearchRequest.ColumnsToGet();
// get all columns
columnsToGet.setReturnAll(true);
searchRequest.setColumnsToGet(columnsToGet);
SearchResponse searchResponse = syncClient.search(searchRequest);
其他示例
其他示例可以参照样例代码中的SearchPolicyExample
。
4、数据读取
数据读取分为两类:
主键读取
基于原生表格存储的主键列获取:getRow, getRange, batchGetRow等。主键读取用于索引(自动)反查,用户也可以提供主键(文件编号md5)的单条查询的页面,亿量级下查询速度保持在十毫秒量级。单主键查询方式不支持多维度检索;
索引读取
基于新SearchIndex功能Query:search接口。用户可以自由设计索引字段的多维度条件组合查询。通过设置选择不同的查询参数,构建不同的查询条件、不同排序方式;目前支持:精确查询、范围查询、前缀查询、匹配查询、通配符查询、短语匹配查询、分词字符串查询、嵌套查询、GEO查询,并通过布尔与、或组合。
如【标签为:表格存储,创建时间[2018-01-01, 2018-12-01)】文件的信息:(SDK与控制查询)
List<Query> mustQueries = new ArrayList<Query>();
//嵌套字段Query
TermQuery termQuery = new TermQuery();
termQuery.setFieldName("tags.tag");
termQuery.setTerm(ColumnValue.fromString("表格存储"));
NestedQuery nestedQuery = new NestedQuery();
nestedQuery.setPath("tags");
nestedQuery.setScoreMode(ScoreMode.Avg);
nestedQuery.setQuery(termQuery);
mustQueries.add(nestedQuery);
//范围Query
RangeQuery rangeQuery = new RangeQuery();
rangeQuery.setFieldName("createdAt");
rangeQuery.setFrom(ColumnValue.fromLong(1514793600000, true);
rangeQuery.setTo(ColumnValue.fromLong(1543651200000, false);
mustQueries.add(rangeQuery);
//精确Query
TermQuery termQuery = new TermQuery();
termQuery.setFieldName("type");
termQuery.setTerm(ColumnValue.fromString("image"));
mustQueries.add(termQuery);
BoolQuery boolQuery = new BoolQuery();
boolQuery.setMustQueries(mustQueries);