电商系统架构设计系列(七):如何构建一个电商的商品搜索系统?

上篇文章中,我给你留了一个思考题:如何构建一个商品搜索系统?

今天这篇文章,我们来说一下电商的商品搜索系统。

引言

搜索这个特性可以说是无处不在,现在很少有网站或者系统不提供搜索功能了,所以,即使你不是一个专业做搜索的程序员,也难免会遇到一些搜索相关的需求。搜索这个东西,表面上看功能很简单,就是一个搜索框,输入关键字,然后搜出来想要的内容就好了。

搜索背后的实现,可以非常简单,简单到什么程度呢?我们就用一个 SQL,LIKE 一下就能实现;也可以很复杂,复杂到什么程度呢?不说百度谷歌这种专业做搜索的公司,其他非专业做搜索的互联网大厂,搜索团队大多是千人规模,这里面不仅有程序员,还有算法工程师、业务专家等等。二者的区别也仅仅是,搜索速度的快慢,以及搜出来的内容好坏而已。

这篇文章,我们就以电商中的商品搜索作为例子,来讲一下,如何用 ES(Elasticsearch) 来快速、低成本地构建一个体验还不错的搜索系统。

理解倒排索引机制

刚刚我们说了,既然我们的数据大多都是存在数据库里,用 SQL 的 LIKE 也能实现匹配,也能搜出结果,为什么还要专门做一套搜索系统呢?我们先来分析一下,为什么数据库不适合做搜索。

搜索的核心需求是全文匹配,对于全文匹配,数据库的索引是根本派不上用场的,那只能全表扫描。全表扫描已经非常慢了,这还不算,还需要在每条记录上做全文匹配,也就是一个字一个字的比对,这个速度就更慢了。所以,使用数据来做搜索,性能上完全没法满足要求。

那 ES 是怎么来解决搜索问题的呢?我们来举个例子说明一下,假设我们有这样两个商品,一个是烟台红富士苹果,一个是苹果手机 iPhone XS Max。

 这个表里面的 DOCID 就是唯一标识一条记录的 ID,和数据库里面的主键是类似的。

为了能够支持快速地全文搜索,ES 中对于文本采用了一种特殊的索引:倒排索引(Inverted Index)。那我们看一下在 ES 中,这两条商品数据倒排索引长什么样?请看下面这个表。

 可以看到,这个倒排索引的表,它是以单词作为索引的 Key,然后每个单词的倒排索引的值是一个列表,这个列表的元素就是含有这个单词的商品记录的 DOCID。

这个倒排索引怎么构建的呢?

当我们往 ES 写入商品记录的时候,ES 会先对需要搜索的字段,也就是商品标题进行分词。分词就是把一段连续的文本按照语义拆分成多个单词。然后 ES 按照单词来给商品记录做索引,就形成了上面那个表一样的倒排索引。

当我们搜索关键字“苹果手机”的时候,ES 会对关键字也进行分词,比如说,“苹果手机”被分为“苹果”和“手机”。然后,ES 会在倒排索引中去搜索我们输入的每个关键字分词,搜索结果应该是:

 666 和 888 这两条记录都能匹配上搜索的关键词,但是 888 这个商品比 666 这个商品匹配度更高,因为它两个单词都能匹配上,所以按照匹配度把结果做一个排序,最终返回的搜索结果就是:

苹果Apple iPhone XS Max (A2104) 256GB 金色 移动联通电信 4G手机双卡双待

烟台红富士苹果5kg 一级铂金大果 单果 230g 以上 新鲜水果

看起来搜索的效果还是不错的。

为什么倒排索引可以做到快速搜索?我们一起来分析一下上面这个例子的查找性能。

这个搜索过程,其实就是对上面的倒排索引做了二次查找,一次找“苹果”,一次找“手机”。注意,整个搜索过程中,我们没有做过任何文本的模糊匹配。ES 的存储引擎存储倒排索引时,肯定不是像我们上面表格中展示那样存成一个二维表,实际上它的物理存储结构和 MySQL 的 InnoDB 的索引是差不多的,都是一颗查找树。

对倒排索引做两次查找,也就是对树进行二次查找,它的时间复杂度,类似于 MySQL 中的二次命中索引的查找。显然,这个查找速度,比用 MySQL 全表扫描加上模糊匹配的方式,要快好几个数量级。

如何在 ES 中构建商品的索引?

理解了倒排索引的原理之后,我们一起用 ES 构建一个商品索引,简单实现一个商品搜索系统。虽然 ES 是为搜索而生的,但本质上,它仍然是一个存储系统。ES 里面的一些概念,基本上都可以在关系数据库中找到对应的名词,为了便于你快速理解这些概念,我把这些概念的对应关系列出来,你可以对照理解。

在 ES 里面,数据的逻辑结构类似于 MongoDB,每条数据称为一个 DOCUMENT,简称 DOC。DOC 就是一个 JSON 对象,DOC 中的每个 JSON 字段,在 ES 中称为 FIELD,把一组具有相同字段的 DOC 存放在一起,存放它们的逻辑容器叫 INDEX,这些 DOC 的 JSON 结构称为 MAPPING。这里面最不好理解的就是这个 INDEX,它实际上类似于 MySQL 中表的概念,而不是我们通常理解的用于查找数据的索引。

ES 是一个用 Java 开发的服务端程序,除了 Java 以外就没有什么外部依赖了,安装部署都非常简单,具体你可以参照它的官方文档先把 ES 安装好,也可以参考我的ELK教程安装好ES。

另外,为了能让 ES 支持中文分词,需要给 ES 安装一个中文的分词插件IK Analysis for Elasticsearch,这个插件的作用就是告诉 ES 怎么对中文文本进行分词。

为了能实现商品搜索,我们需要先把商品信息存放到 ES 中,首先我们先定义存放在 ES 中商品的数据结构,也就是 MAPPING。

我们这个 MAPPING 只要两个字段就够了,sku_id 就是商品 ID,title 保存商品的标题,当用户在搜索商品的时候,我们在 ES 中来匹配商品标题,返回符合条件商品的 sku_id 列表。

ES 默认提供了标准的 RESTful 接口,不需要客户端,直接使用 HTTP 协议就可以访问,可以使用curl通过命令行来操作 ES。

接下来我们使用上面这个 MAPPING 创建 INDEX,类似于 MySQL 中创建一个表。

curl -X PUT "localhost:9200/sku" -H 'Content-Type: application/json' -d '{"mappings": {"properties": {"sku_id": {"type": "long"},"title": {"type": "text","analyzer": "ik_max_word","search_analyzer": "ik_max_word"}}}
}'
{"acknowledged":true,"shards_acknowledged":true,"index":"sku"}

这里面,使用 PUT 方法创建一个 INDEX,INDEX 的名称是“sku”,直接写在请求的 URL 中。请求的 BODY 是一个 JSON 对象,内容就是我们上面定义的 MAPPING,也就是数据结构。这里面需要注意一下,由于我们要在 title 这个字段上进行全文搜索,所以我们把数据类型定义为 text,并指定使用我们刚刚安装的中文分词插件 IK 作为这个字段的分词器。

创建好 INDEX 之后,就可以往 INDEX 中写入商品数据,插入数据需要使用 HTTP POST 方法:

curl -X POST "localhost:9200/sku/_doc/" -H 'Content-Type: application/json' -d '{"sku_id": 100002860826,"title": "烟台红富士苹果 5kg 一级铂金大果 单果230g以上 新鲜水果"
}'
{"_index":"sku","_type":"_doc","_id":"yxQVSHABiy2kuAJG8ilW","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":0,"_primary_term":1}curl -X POST "localhost:9200/sku/_doc/" -H 'Content-Type: application/json' -d '{"sku_id": 100000177760,"title": "苹果 Apple iPhone XS Max (A2104) 256GB 金色 移动联通电信4G手机 双卡双待"
}'
{"_index":"sku","_type":"_doc","_id":"zBQWSHABiy2kuAJGgim1","_version":1,"result":"created","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":1,"_primary_term":1}

这里面我们插入了两条商品数据,一个烟台红富士,一个 iPhone 手机。然后就可以直接进行商品搜索了,搜索使用 HTTP GET 方法。

curl -X GET 'localhost:9200/sku/_search?pretty' -H 'Content-Type: application/json' -d '{"query" : { "match" : { "title" : "苹果手机" }}
}'
{"took" : 23,"timed_out" : false,"_shards" : {"total" : 1,"successful" : 1,"skipped" : 0,"failed" : 0},"hits" : {"total" : {"value" : 2,"relation" : "eq"},"max_score" : 0.8594865,"hits" : [{"_index" : "sku","_type" : "_doc","_id" : "zBQWSHABiy2kuAJGgim1","_score" : 0.8594865,"_source" : {"sku_id" : 100000177760,"title" : "苹果 Apple iPhone XS Max (A2104) 256GB 金色 移动联通电信4G手机 双卡双待"}},{"_index" : "sku","_type" : "_doc","_id" : "yxQVSHABiy2kuAJG8ilW","_score" : 0.18577608,"_source" : {"sku_id" : 100002860826,"title" : "烟台红富士苹果 5kg 一级铂金大果 单果230g以上 新鲜水果"}}]}
}

我们先看一下请求中的 URL,其中的“sku”代表要在 sku 这个 INDEX 内进行查找,“_search”是一个关键字,表示要进行搜索,参数 pretty 表示格式化返回的 JSON,这样方便阅读。再看一下请求 BODY 的 JSON,query 中的 match 表示要进行全文匹配,匹配的字段就是 title,关键字是“苹果手机”。

可以看到,在返回结果中,匹配到了 2 条商品记录,和我们在前面讲解倒排索引时,预期返回的结果是一致的。

回顾一下使用 ES 构建商品搜索服务的整个过程:首先安装 ES 并启动服务,然后创建一个 INDEX,定义 MAPPING,写入数据后,执行查询并返回查询结果,其实,这个过程和我们使用数据库时,先建表、插入数据然后查询的过程,就是一样的。所以,你就把 ES 当做一个支持全文搜索的数据库来使用就行了。

总结

ES 本质上是一个支持全文搜索的分布式内存数据库,特别适合用于构建搜索系统。ES 之所以能有非常好的全文搜索性能,最重要的原因就是采用了倒排索引。

倒排索引是一种特别为搜索而设计的索引结构,倒排索引先对需要索引的字段进行分词,然后以分词为索引组成一个查找树,这样就把一个全文匹配的查找转换成了对树的查找,这是倒排索引能够快速进行搜索的根本原因。

但是,倒排索引相比于一般数据库采用的 B 树索引,它的写入和更新性能都比较差,因此倒排索引也只是适合全文搜索,不适合更新频繁的交易类数据。

感谢阅读,如果你觉得这篇文章对你有一些启发,也欢迎把它分享给你的朋友。

思考题

订单数据越来越多,数据库越来越慢该怎么办?

期待、欢迎你留言或在线联系,与我一起讨论交流,“一起学习,一起成长”。

上一篇文章

电商系统架构设计系列(六):电商的「账户系统」设计要特别考虑哪些问题?


推荐阅读

  • 技术破局,业绩狂飙十倍:亿级电商平台重构大揭秘
  • 当我们聊高并发时,到底是在聊什么?如何真正地掌握高并发设计能力?
  • 微服务架构实战 - 我的经验分享总结2019(系统架构师)架构演进过程-从信息流架构到电商中台架构​​​​​​

系列分享

  • Elasticsearch教程
  • 微服务架构实战
  • 架构思维成长系列
  • 电商系统架构设计系列

------------------------------------------------------

------------------------------------------------------

我的CSDN主页

关于我(个人域名,更多我的信息)

我的开源项目集Github

期望和大家 一起学习,一起成长,共勉,O(∩_∩)O谢谢

如果你有任何建议,或想学习的知识,可与我一起讨论交流

欢迎交流问题,可加个人QQ 469580884,

或者,加我的群号 751925591,一起探讨交流问题

不讲虚的,只做实干家

Talk is cheap,show me the code

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/74526.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django的生命周期流程图(补充)、路由层urls.py文件、无名分组和有名分组、反向解析(无名反向解析、有名反向解析)、路由分发、伪静态

一、orm的增删改查方法(补充) 1. 查询resmodels.表名(类名).objects.all()[0]resmodels.表名(类名).objects.filter(usernameusername, passwordpassword).all()res models.表名(类名).objects.first() # 判断,判断数据是否有# res如果查询…

三、基本流程控制结构

3.1结构化程序设计 基本控制结构&#xff1a; 顺序结构选择结构循环结构 C语句&#xff1a; 说明语句控制语句函数调用语句表达式语句空语句复合语句 3.2选择结构语句 if语句&#xff1a; &#xff08;1&#xff09;单选条件语句 if(表达式) 语句 if(x>y) cout<&l…

DASCTF 2023 0X401七月暑期挑战赛web复现

目录 <1> Web (1) EzFlask(python原型链污染&flask-pin) (2) MyPicDisk(xpath注入&文件名注入) (3) ez_cms(pearcmd文件包含) (4) ez_py(django框架 session处pickle反序列化) <1> Web (1) EzFlask(python原型链污染&flask-pin) 进入题目 得到源…

SpringBoot统一功能处理

我们要实现以下3个目标&#xff1a; 统一用户登录权限统一数据格式返回统一异常处理 1.用户的登录权限校验 1.1Spring AOP用户统一登录验证问题 Aspect Component public class UserAspect {// 定义切点controller包下、子孙包下所有类的所有方法Pointcut("execution(…

使用多数据源dynamic-datasource-spring-boot-starter遇到的问题记录

记录使用多数据源dynamic-datasource-spring-boot-starter遇到的问题&#xff1a; 1、工程启动失败 缺少clickhouse连接驱动&#xff0c;引入对应的maven依赖 <!--ck连接驱动--><dependency><groupId>ru.yandex.clickhouse</groupId><artifactId>…

webshell详解

Webshell详解 一、 Webshell 介绍二 、 基础常见webshell案例 一、 Webshell 介绍 概念 webshell就是以asp、php、jsp或者cgi等网页文件形式存在的一种命令执行环境&#xff0c;也可以将其称做为一种网页后门。黑客在入侵了一个网站后&#xff0c;通常会将asp或php后门文件与…

国标GB28181安防视频平台EasyGBS大批量通道接入后,创建角色接口未响应的排查

国标GB28181协议视频平台EasyGBS是基于国标GB28181协议的视频云服务平台&#xff0c;支持多路设备同时接入&#xff0c;并对多平台、多终端分发出RTSP、RTMP、FLV、HLS、WebRTC等格式的视频流。平台可提供视频监控直播、云端录像、云存储、检索回放、智能告警、语音对讲、平台级…

【并发专题】操作系统模型及三级缓存架构

目录 课程内容一、冯诺依曼计算机模型详解1.计算机五大核心组成部分2.CPU内部结构3.CPU缓存结构4.CPU读取存储器数据过程5.CPU为何要有高速缓存 学习总结 课程内容 一、冯诺依曼计算机模型详解 现代计算机模型是基于-冯诺依曼计算机模型 计算机在运行时&#xff0c;先从内存中…

day20-101. 对称二叉树

101. 对称二叉树 力扣题目链接 给定一个二叉树&#xff0c;检查它是否是镜像对称的。 思路 镜像对称必要的条件就是根节点的左右子树互相对称 左子树的左孩子 右子树的右孩子左子树的右孩子 右子树的左孩子 递归 使用递归前要确定递归的顺序&#xff0c;是前序、后序还…

目标识别数据集互相转换——xml、txt、json数据格式互转

VOC数据格式与YOLO数据格式互转 1.VOC数据格式 VOC&#xff08;Visual Object Classes&#xff09;是一个常用的计算机视觉数据集&#xff0c;它主要用于对象检测、分类和分割任务。VOC的标注格式&#xff0c;也被许多其他的数据集采用&#xff0c;因此理解这个数据格式是很重…

QT--day4(定时器事件、鼠标事件、键盘事件、绘制事件、实现画板、QT实现TCP服务器)

QT实现tcpf服务器代码&#xff1a;&#xff08;源文件&#xff09; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);//给服务器指针实例化空间server new QTc…

【图论】强连通分量进阶

一.作用 强连通分量可以判断环和进行缩点。还有一系列作用.... 这篇文章介绍缩点 二.题目 https://www.luogu.com.cn/problem/P2341 三.思路 我们分析可以知道当一个点没有出度时&#xff0c;则为最受欢迎的牛。但如果有多个出度&#xff0c;则没有最受欢迎的牛。 这是只有…

用户权限管理是保证企业图文档安全最有效的策略

企业拥有大量的图文档数据&#xff0c;涉及多个部门和员工&#xff0c;因此需要建立有效的用户权限管理策略&#xff0c;以保护图文档的安全。智橙平台将在线图文档管理与BOM系统的融合应用为企业提供了强大的权限管理功能&#xff0c;能够确保只有授权用户能够访问和编辑特定的…

Linux运维面试题(三)之数据库管理

Linux运维面试题&#xff08;三&#xff09;之数据库管理 1. SQL语句2.集群主从服务器原理主从故障切换单台Mysql达到性能瓶颈时&#xff0c;如何处理 3.索引&#xff08;软优化&#xff09;什么是索引索引的分类劣势&#xff08;优点&#xff1a;效率和减少数据表内排序和随机…

java实现5种不同的验证码图片,包括中文、算式等,并返回前端

导入以下依赖 <!--图片验证码--><dependency><groupId>com.github.whvcse</groupId><artifactId>easy-captcha</artifactId><version>1.6.2</version></dependency> 编写controller package com.anXin.user.controlle…

【vue】 Tinymce 富文本编辑器 不想让上传的图片转换成base64,而是链接

前言&#xff1a;最近项目上需要使用富文本编辑器&#xff0c;觉得tinymce很不错就用了&#xff0c;具体怎么在项目中使用参考 【vue】 vue2 中使用 Tinymce 富文本编辑器 【vue】 Tinymce 数据 回显问题 | 第一次正常回显后面&#xff0c;显示空白bug不能编辑 这两天又遇到了…

Open3D(C++) 根据索引提取点云

目录 一、功能概述1、主要函数2、源码二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。爬虫网站自重,把自己当个人 一、功能概述 1、主要函数 std::shared_ptr<PointCloud> SelectByIn

如何运行疑难解答程序来查找和修复Windows 10中的常见问题

如果Windows 10中出现问题&#xff0c;运行疑难解答可能会有所帮助。疑难解答人员可以为你找到并解决许多常见问题。 一、在控制面板中运行疑难解答 1、打开控制面板&#xff08;图标视图&#xff09;&#xff0c;然后单击“疑难解答”图标。 2、单击“疑难解答”中左上角的…

2023华数杯数学建模竞赛C题思路解析

如下为&#xff1a;2023华数杯数学建模竞赛C题 母亲身心健康对婴儿成长的影响 的思路解析 C题 母亲身心健康对婴儿成长的影响 母亲是婴儿生命中最重要的人之一&#xff0c;她不仅为婴儿提供营养物质和身体保护&#xff0c;还为婴儿提供情感支持和安全感。母亲心理健康状态的不…

O3DE的Pass

Pass介绍 Pass是具有输入和输出的渲染过程。 在最终渲染帧中看到的每个细节都是通过一系列Pass&#xff08;前一个Pass的输出是下一个Pass的输入&#xff09;计算出来的。Pass可以生成图像&#xff08;作为纹理、缓冲区或渲染目标&#xff09;。每个图像都包含关于场景的特定…