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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
表格存储 Tablestore,50G 2个月
简介: 背景在大规模订单系统中,存在以下常见需求:查询某店铺过去一段时间成交额查询某品牌商品在过去一周内的成交额查询在某店铺购物的客户列表……因此,开发者对于数据库在非主键查询、多列的自由组合查询等复杂查询需求上会有比较高的要求。传统的订单系统会使用 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

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
18天前
|
数据采集 机器学习/深度学习 运维
量化合约系统开发架构入门
量化合约系统核心在于数据、策略、风控与执行四大模块的协同,构建从数据到决策再到执行的闭环工作流。强调可追溯、可复现与可观测性,避免常见误区如重回测轻验证、忽视数据质量或滞后风控。初学者应以MVP为起点,结合回测框架与实时风控实践,逐步迭代。详见相关入门与实战资料。
|
1月前
|
前端开发 JavaScript BI
如何开发车辆管理系统中的车务管理板块(附架构图+流程图+代码参考)
本文介绍了中小企业如何通过车务管理模块提升车辆管理效率。许多企业在管理车辆时仍依赖人工流程,导致违章处理延误、年检过期、维修费用虚高等问题频发。将这些流程数字化,可显著降低合规风险、提升维修追溯性、优化调度与资产利用率。文章详细介绍了车务管理模块的功能清单、数据模型、系统架构、API与前端设计、开发技巧与落地建议,以及实现效果与验收标准。同时提供了数据库建表SQL、后端Node.js/TypeScript代码示例与前端React表单设计参考,帮助企业快速搭建并上线系统,实现合规与成本控制的双重优化。
|
1月前
|
机器学习/深度学习 人工智能 缓存
面向边缘通用智能的多大语言模型系统:架构、信任与编排——论文阅读
本文提出面向边缘通用智能的多大语言模型(Multi-LLM)系统,通过协同架构、信任机制与动态编排,突破传统边缘AI的局限。融合合作、竞争与集成三种范式,结合模型压缩、分布式推理与上下文优化技术,实现高效、可靠、低延迟的边缘智能,推动复杂场景下的泛化与自主决策能力。
171 3
面向边缘通用智能的多大语言模型系统:架构、信任与编排——论文阅读
|
1月前
|
人工智能 自然语言处理 安全
AI助教系统:基于大模型与智能体架构的新一代教育技术引擎
AI助教系统融合大语言模型、教育知识图谱、多模态交互与智能体架构,实现精准学情诊断、个性化辅导与主动教学。支持图文语音输入,本地化部署保障隐私,重构“教、学、评、辅”全链路,推动因材施教落地,助力教育数字化转型。(238字)
|
1月前
|
监控 数据可视化 数据库
低代码的系统化演进:从工具逻辑到平台架构的技术解读
低代码正从开发工具演变为支撑企业架构的智能平台,融合可视化开发、AI引擎与开放生态,实现高效构建、自动化运维与跨场景协同,推动数字化转型迈向智能化、系统化新阶段。
|
26天前
|
存储 人工智能 搜索推荐
拔俗AI助教系统:基于大模型与智能体架构的新一代教育技术引擎
AI助教融合大语言模型、教育知识图谱、多模态感知与智能体技术,重构“教、学、评、辅”全链路。通过微调LLM、精准诊断错因、多模态交互与自主任务规划,实现个性化教学。轻量化部署与隐私保护设计保障落地安全,未来将向情感感知与教育深度协同演进。(238字)
|
26天前
|
机器学习/深度学习 人工智能 搜索推荐
拔俗AI学伴智能体系统:基于大模型与智能体架构的下一代个性化学习引擎
AI学伴智能体系统融合大模型、多模态理解与自主决策,打造具备思考能力的个性化学习伙伴。通过动态推理、长期记忆、任务规划与教学逻辑优化,实现千人千面的自适应教育,助力因材施教落地,推动教育公平与效率双提升。(238字)
|
1月前
|
Cloud Native Serverless API
微服务架构实战指南:从单体应用到云原生的蜕变之路
🌟蒋星熠Jaxonic,代码为舟的星际旅人。深耕微服务架构,擅以DDD拆分服务、构建高可用通信与治理体系。分享从单体到云原生的实战经验,探索技术演进的无限可能。
微服务架构实战指南:从单体应用到云原生的蜕变之路
|
4月前
|
缓存 Cloud Native Java
Java 面试微服务架构与云原生技术实操内容及核心考点梳理 Java 面试
本内容涵盖Java面试核心技术实操,包括微服务架构(Spring Cloud Alibaba)、响应式编程(WebFlux)、容器化(Docker+K8s)、函数式编程、多级缓存、分库分表、链路追踪(Skywalking)等大厂高频考点,助你系统提升面试能力。
206 0

热门文章

最新文章

推荐镜像

更多