基于 MySQL + Tablestore 分层存储架构的大规模订单系统实践-订单搜索篇

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 背景在大规模订单系统中,存在以下常见需求:查询某店铺过去一段时间成交额查询某品牌商品在过去一周内的成交额查询在某店铺购物的客户列表……因此,开发者对于数据库在非主键查询、多列的自由组合查询等复杂查询需求上会有比较高的要求。传统的订单系统会使用 Elasticsearch 或者 Solr 来实现这一需求,但伴随而来的是更高的系统复杂度和更加昂贵的系统维护成本。Tablestore 的多元索引,能够支

背景

在大规模订单系统中,存在以下常见需求:

  • 查询某店铺过去一段时间成交额
  • 查询某品牌商品在过去一周内的成交额
  • 查询在某店铺购物的客户列表
  • ……

因此,开发者对于数据库在非主键查询、多列的自由组合查询等复杂查询需求上会有比较高的要求。传统的订单系统会使用 Elasticsearch 或者 Solr 来实现这一需求,但伴随而来的是更高的系统复杂度和更加昂贵的系统维护成本。

Tablestore 的多元索引,能够支持此类数据检索工作,且具有操作简单、维护成本低等特点,可以将开发者从索引建立、数据同步、集群维护等工作中解放出来。本文将简要介绍多元索引,展示如何在 Tablestore 实例上创建多元索引,并通过JAVA代码展示利用多元索引实现搜索需求。

多元索引简介

Tablestore 的多元索引,底层使用自研索引引擎,基于倒排索引和列式存储,可以支持非主键列查询、全文检索、前缀查询、模糊查询、多字段自由组合查询、嵌套查询、地理位置查询和统计聚合(max、min、count、sum、avg、distinct_count、group_by)等复杂查询功能。不同于 MySQL 等传统数据库的索引使用方式,多元索引无最左匹配原则限制,使用时非常灵活。一般情况下一张表只需要创建一个多元索引即可。

其架构如图。数据在 Tablestore 的基础表中写入,基础表中的增量数据会通过异步的方式被拉入多元索引。由于这个异步操作,多元索引中的数据相比于基础表数据存在一定延迟,这个延迟在几秒到十几秒的量级。由图可以看出,基于主键列的读取会由基础表进行支持;而多元索引会承担相对更加复杂的非主键列查询、全文检索、组合查询、聚合查询等查询功能。架构实现了不同流量的分离,部分实现了读写分离。

 

更详细的多元索引介绍可以参考:多元索引简介

多元索引创建

索引创建

进入Tablestore控制台首页。点击创建的 Tablestore 实例。

点击订单表 order_contract 对应的索引管理,进入索引管理界面。

点击创建多元索引

输入索引名称。选择手动录入索引字段。这里,选择订单 id(oId)、商品品牌(p_brand)、商品名称(p_name)、客户名称(c_name)、卖家名称(s_name)、商品单价(p_price)、支付时间(pay_time)、客户 id(c_id)、卖家 id(s_id)、交易金额(total_price)作为索引字段。点击确定完成索引创建。

可以在索引管理页看到索引相关记录。

索引同步

多元索引创建后,需要同步存量数据,同步过程中,同步状态显示为存量;数据同步结束后,同步状态显示为增量。此时可以在行数统计处看到记录总数。

索引查询

点击搜索。

添加查询字段,选择精确查询,输入需要查询的

搜索结果如下。

JAVA 查询

订单表 order_contract 中记录数约为一亿二百万条。

多元索引创建后,可以直接通过 SDK 读取多元索引中的数据。pom 引入 SDK 。

 <dependency>
     <groupId>com.aliyun.openservices</groupId>
     <artifactId>tablestore</artifactId>
     <version>5.10.3</version>
 </dependency>

精确查询 

搜索购买过某品牌的用户。传入需要搜索的品牌,通过多元索引 order_contract_index 以及品牌字段 p_brand 进行搜索。

 public List<String> getUserByBrand(String brand) {

        // 组装请求参数
        SearchQuery searchQuery = new SearchQuery();
        searchQuery.setGetTotalCount(true);

        BoolQuery boolQuery = new BoolQuery();

        TermQuery applierNameQuery = new TermQuery();
        applierNameQuery.setFieldName("p_brand");
        applierNameQuery.setTerm(ColumnValue.fromString(brand));

        boolQuery.setMustQueries(Arrays.asList(
                applierNameQuery
        ));

        searchQuery.setQuery(boolQuery);

        SearchRequest searchRequest = new SearchRequest("order_contract", "order_contract_index", searchQuery);
        SearchRequest.ColumnsToGet columnsToGet = new SearchRequest.ColumnsToGet();
        columnsToGet.setReturnAll(true);
        searchRequest.setColumnsToGet(columnsToGet);

        // 进行搜索
        SearchResponse response = syncClient.search(searchRequest);

        // 解析返回数据
        List<String> userList = new ArrayList<>();
        if (response != null && !CollectionUtils.isEmpty(response.getRows())) {
            List<Row> item = response.getRows();
            for (Row r : item) {
                userList.add(r.getColumn("c_id").get(0).getValue().asString());
            }
        }

        return userList;
    }

范围查询

搜索在某店铺购买的商品单价在 500 元到 600 元之间的用户。

    public List<String> searchByBrandAndKey(String brand, Double high, Double low) {

        // 组装请求参数
        SearchQuery searchQuery = new SearchQuery();
        searchQuery.setGetTotalCount(true);

        BoolQuery boolQuery = new BoolQuery();

        TermQuery applierNameQuery = new TermQuery();
        applierNameQuery.setFieldName("p_brand");
        applierNameQuery.setTerm(ColumnValue.fromString(brand));

        RangeQuery rangeQuery = new RangeQuery();
        rangeQuery.setFieldName("p_price");
        rangeQuery.setFrom(ColumnValue.fromDouble(low), true);
        rangeQuery.setTo(ColumnValue.fromDouble(high),true);
        
        boolQuery.setMustQueries(Arrays.asList(
                applierNameQuery,
                rangeQuery
        ));

        searchQuery.setQuery(boolQuery);

        SearchRequest searchRequest = new SearchRequest("order_contract", "order_contract_index", searchQuery);
        SearchRequest.ColumnsToGet columnsToGet = new SearchRequest.ColumnsToGet();
        columnsToGet.setReturnAll(true);
        searchRequest.setColumnsToGet(columnsToGet);

        // 进行搜索
        SearchResponse response = syncClient.search(searchRequest);

        // 解析返回数据
        List<String> userList = new ArrayList<>();
        if (response != null && !CollectionUtils.isEmpty(response.getRows())) {
            List<Row> item = response.getRows();
            for (Row r : item) {
                userList.add(r.getColumn("c_id").get(0).getValue().asString());
            }
        }

        return userList;
    }

通配符查询

搜索购买过包含关键字的商品的客户。

    public List<String> searchByKeyInProductName(String key) {
        // 组装请求参数
        SearchQuery searchQuery = new SearchQuery();
        searchQuery.setGetTotalCount(true);

        BoolQuery boolQuery = new BoolQuery();

        WildcardQuery wildcardQuery = new WildcardQuery();
        wildcardQuery.setFieldName("p_name");
        wildcardQuery.setValue("*" + key + "*");

        boolQuery.setMustQueries(Arrays.asList(
                wildcardQuery
        ));

        searchQuery.setQuery(boolQuery);

        SearchRequest searchRequest = new SearchRequest("order_contract", "order_contract_index", searchQuery);
        SearchRequest.ColumnsToGet columnsToGet = new SearchRequest.ColumnsToGet();
        columnsToGet.setReturnAll(true);
        searchRequest.setColumnsToGet(columnsToGet);

        // 进行搜索
        SearchResponse response = syncClient.search(searchRequest);

        // 解析返回数据
        List<String> userList = new ArrayList<>();
        if (response != null && !CollectionUtils.isEmpty(response.getRows())) {
            List<Row> item = response.getRows();
            for (Row r : item) {
                userList.add(r.getColumn("c_id").get(0).getValue().asString());
            }
        }

        return userList;

    }

更多查询

除了上文提到的查询方式外,多元索引还支持许多丰富的查询方式,例如模糊查询、地理位置查询、多条件组合查询、嵌套查询等等。同时还支持统计聚合、排序、并发导出数据等功能,更多关于多元索引的介绍可参考官网多元索引

与 MySQL 索引比对

多元索引在复杂的组合检索、聚合检索场景下,比 MySQL 更具有优势。

  • 多元索引不需要遵守最左匹配原则,可以一张索引支持所有需求。而 MySQL 需要针对不同需求建立多个索引,索引数据占用空间大,难以维护。
  • 多元索引支持 非主键列的条件查询、任意列的自由组合查询、And ,Or,Not等关系查询、全文检索、地理位置查询、前缀查询、模糊查询、嵌套结构查询、Null值查询、统计聚合(min、max、sum、avg、count、distinct_count和group_by)。功能层面远强于 MySQL 索引。

下面给出几个大规模订单场景下的需求以及实现样例并对比性能。

基于订单金额、状态等组合检索

需求:搜索 2021 年 6 月 30 日零点以来成交额在 2000 元以上,且商品品牌中包含特定关键字的订单,按商品单价倒序排列取前 1000。

对应 SQL如下,执行时间分钟级。MySQL 中建立有p_price,total_price,pay_time 的联合索引。符合筛选条件的记录数约为 16W 条。

select * from order_contract 
where total_price > 2000 and pay_time > 1624982400000000
and p_brand like "%牌22%" order by p_price desc limit 1000

JAVA 中访问 Tablestore 代码如下,执行时间秒级。

 SearchRequest searchRequest = SearchRequest.newBuilder()
                .tableName("order_contract")
                .indexName("order_contract_index")
                .searchQuery(
                        SearchQuery.newBuilder()
                                .query(QueryBuilders.bool().must(QueryBuilders.range("total_price").greaterThan(2000))
                                .must(QueryBuilders.wildcard("p_brand","*牌22*"))
                                .must(QueryBuilders.range("pay_time").greaterThan(1624982400000000L)))
                                .sort(new Sort(Arrays.asList(new FieldSort("p_price", SortOrder.DESC))))
                                .limit(1000)
                                .build())
                .build();

        SearchResponse response = syncClient.search(searchRequest);

报表分析、运营推广

需求:统计 2021 年 6 月 30 日零点以来,下单金额最高的 100 个客户。涉及记录数大于 1200W 条。

对应 SQL如下,执行时间约两分半。MySQL 建有 pay_time, c_id, total_price 的联合索引。

SELECT c_id ,sum(total_price) as a FROM order_contract where pay_time >= '2021-06-30 00:00:00'
group by c_id 
order by a desc limit 100

JAVA 中访问 Tablestore 代码如下,执行时间约为15秒。

 SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("order_contract")
            .indexName("order_contract_index")
            .addColumnsToGet("c_id","total_price")
            .searchQuery(
                    SearchQuery.newBuilder()
                            .query(QueryBuilders.range("pay_time").greaterThan(1624982400000000L))
                            .addGroupBy(GroupByBuilders.groupByField("c_id","c_id")
                                    .addGroupBySorter(GroupBySorter.subAggSortInDesc("sumPrice"))
                                    .addSubAggregation(AggregationBuilders.sum("sumPrice", "total_price"))
                            .size(100))
                            .build())
            .build();

    // 进行搜索
    SearchResponse response = syncClient.search(searchRequest);

总结

Tablestore 的多元索引功能对类似海量订单场景下的搜索功能提供了较好的支持。使用多元索引,开发者可以以更小的开发成本、更低的运维成本,实现订单搜索这样的需求。

本文对 Tablestore 多元索引做了简要介绍,并展示了如何创建索引,以及如何在JAVA程序中利用创建的索引进行搜索。

附录

代码 git 地址:https://github.com/aliyun/tablestore-examples

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1天前
|
设计模式 监控 持续交付
深入理解微服务架构:从理论到实践
【6月更文挑战第22天】微服务架构作为现代软件开发的基石,其理念和实践已经深入人心。本文将通过一个实际案例,探讨如何将微服务架构的理论应用到实践中去,包括设计原则、技术选型、以及实施过程中可能遇到的挑战和相应的解决策略。我们将看到,尽管微服务带来了许多优势,但在实际应用中也不可避免地会遇到一系列问题,需要开发者具备深厚的技术功底和丰富的实践经验才能妥善应对。
|
3天前
|
存储 弹性计算 安全
构建高效企业应用架构:阿里云产品组合实践深度解析
该方案展现了阿里云产品组合的强大能力和灵活性,不仅满足了当前业务需求,也为未来的扩展打下了坚实的基础。希望本文的分享能为读者在设计自己的IT解决方案时提供一定的参考和启发。
20 1
|
4天前
|
存储 运维 监控
云原生架构下的微服务治理实践
【6月更文挑战第19天】在数字化转型的浪潮中,云原生技术以其灵活、可扩展的特性成为企业IT架构升级的首选。本文深入探讨了在云原生架构下,如何有效实施微服务治理,包括服务发现、配置管理、服务监控和故障处理等方面的最佳实践。文章旨在为读者提供一套全面的微服务治理框架,帮助团队构建更加稳定、高效的分布式系统。
8 2
|
3天前
|
关系型数据库 MySQL Linux
Linux系统中Mysql5.7建立远程连接
Linux系统中Mysql5.7建立远程连接
4 0
|
3天前
|
监控 Cloud Native 安全
云原生架构下的微服务治理实践
本文旨在深入探讨在云原生环境下,如何有效实施微服务治理。通过分析微服务架构的核心价值与挑战,结合具体的云平台工具和最佳实践,文章详细阐述了服务发现、配置管理、弹性设计等关键治理策略。此外,文章还提供了关于如何在保障系统可观测性的同时,确保安全性和合规性的实用建议。读者将获得一套完整的微服务治理框架,以及在云原生旅程中应对复杂问题的能力提升。
|
3天前
|
Kubernetes 负载均衡 Cloud Native
云原生架构下的微服务治理实践
在云计算的浪潮中,云原生架构以其弹性、可扩展性及高效的资源利用成为企业数字化转型的首选。本文将深入探讨云原生环境下微服务的治理策略,包括服务发现、配置管理、流量控制等关键组件的应用,并通过案例分析展示如何构建健壮、灵活的微服务系统。
13 0
|
3天前
|
监控 持续交付 开发者
探索后端开发中的微服务架构实践
在软件工程的广阔天地中,微服务架构以其独特的魅力和优势逐渐成为后端开发的新宠。本文将深入探讨微服务的核心概念、设计原则以及在实际项目中的实施策略和面临的挑战,旨在为后端开发者提供一套清晰的微服务实践指南。
|
4天前
|
运维 监控 API
打造高效后端:微服务架构在现代应用中的实践
本篇文章探讨了微服务架构在现代应用中的实际应用,重点介绍了其优势、挑战以及最佳实践。通过具体案例和技术细节,我们将深入了解如何设计、实现和维护一个高效的微服务架构,以满足不断变化的业务需求。
16 0
|
4天前
|
存储 消息中间件 运维
从单体到微服务:架构演进中的技术挑战与解决方案
在软件开发的过程中,系统架构的选择对项目的成功与否起到至关重要的作用。本文将深入探讨从单体架构向微服务架构演进过程中所遇到的技术挑战,并提供相应的解决方案。
23 0
|
2天前
|
监控 持续交付 数据安全/隐私保护
Python进行微服务架构的监控
【6月更文挑战第16天】
17 5
Python进行微服务架构的监控

热门文章

最新文章