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

news2024/11/25 21:16:08

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

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

引言

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

搜索背后的实现,可以非常简单,简单到什么程度呢?我们就用一个 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.coloradmin.cn/o/834956.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity Shader:常用的C#与shader交互的方法

俗话说久病成医,虽然不是专业技术美术,但代码写久了自然会积累一些常用的shader交互方法。零零散散的,总结如下: 1,改变UGUI的材质球属性 有时候我们需要改变ui的一些属性,从而实现想要的效果。通常UGUI上…

GEE:矢量数据去除重复值(输出样本点数据的标签信息)

作者:CSDN @ _养乐多_ 本文记录了在GoogleEarthEngine(GEE)平台上,将样本点数据中某个字段的值去除重复值,并将剩下的值打印到控制台的代码。该代码可以用于快速在GEE平台上查询土地利用分类信息中landcover的类别信息。 矢量数据信息如下所示, 打印结果如下所示, 文章…

【Linux】【docker】安装sonarQube免费社区版9.9

文章目录 sonarQube 镜像容器Linux 安装镜像出现 Permission denied的异常安装sonarQube 中文包重启服务 代码上传到sonarQube扫描配置 JS TS Php Go Python sonarQube 镜像容器 老样子第一步还是打开镜像容器官网https://hub.docker.com搜索sonarqube官方推荐的挂载目录 我就按…

LeetCode 热题 100 JavaScript --226. 翻转二叉树

给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 3&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 提示&#xff1a; 树中节点数目范围在 [0, 100] 内 -100 < Node.val < 100 var invertTree function(root…

使用“纯”Servlet做一个单表的CRUD操作

1. 项目说明 介绍&#xff1a; 这里我们使用 纯粹 的 Servlet 完成单表【对部门的】的增删改查操作。&#xff08;B/S结构的。&#xff09; 结构图 初始的欢迎页面 部门列表页面 部门详情 修改部门 删除部门&#xff1a; 新增部门&#xff1a; 2. 具体对应的功能的代码实现 …

为什么马斯克和奥特曼都想重振加密货币?

1、前言 加密货币已经死了吗&#xff1f;这个问题的答案取决于谁来回答。一个加密爱好者会给你一百个不同的理由来解释为什么加密货币没有死。特斯拉CEO埃隆马斯克和OpenAI CEO 山姆奥特曼都对加密货币及其在塑造未来世界中的潜在作用有着浓厚的兴趣。 在过去很长一段时间里&…

Ubuntu18.04 安装opencv 4.8.0教程

1. 安装准备 安装前需要下载一些必须的依赖项。 不同版本opencv依赖会有不同&#xff0c;具体见官网opencv安装 sudo apt-get install build-essential sudo apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt-…

视频汇聚平台EasyCVR视频广场侧边栏支持拖拽

为了提升用户体验以及让平台的操作更加符合用户使用习惯&#xff0c;我们在EasyCVR v3.3版本中&#xff0c;支持面包屑侧边栏的广场视频、分组列表、收藏这三个模块拖拽排序&#xff0c;并且该操作在视频广场、视频调阅、电子地图、录像回放等页面均能支持。 TSINGSEE青犀视频…

InfluxDB2如何求增量数据

需求 项目中需要接入电表设备&#xff0c;求用电量。 按天和设备统计用电量 按天统计用电量 统计总用电量 存在的问题 difference 函数可以求增量&#xff0c;但是以上计算均存在一个问题&#xff0c;比如xx设备有8.1号和8.2号的数据&#xff0c;我统计每天的用电量&#xf…

单篇笔记曝光248万+,素颜、寸头…小红书女性种草新趋势分析!

最近&#xff0c;小红书上刮起一阵素颜、寸头&#xff0c;拒绝美丽绑架的风潮&#xff0c;他们称之为“脱美役”&#xff0c;即脱离美丽枷锁&#xff0c;做自己&#xff0c;接纳原本的自己。这是女性觉醒的又一阵风&#xff0c;品牌要如何跟上这波种草新趋势呢&#xff1f; 单篇…

Swish for MacBook触控板窗口管理软件

Swish可以帮助您使用触控板&#xff0c;轻松对mac窗口进行管理&#xff0c;只需提前设置好预定的设置即可&#xff0c;非常方便&#xff01; 几乎所有的窗口管理工具用的都是快捷键或者鼠标拖移的方式来管理窗口&#xff0c;Swish 却另辟蹊径&#xff0c;为窗口管理引入了手势…

c语言——计算两个数值的最小公倍数

//计算两个数值的最小公倍数 //列如&#xff1a;4和6的最小公倍数是12. #include<stdio.h> int main() {int a,b,temp,i;printf("Input a&b:");scanf("%d,%d",&a,&b);if(a<b){tempa;ab;btemp;}for(ia;i>0;i)if(i%a0&&i%b0…

物联网|按键实验---学习I/O的输入及中断的编程|函数说明的格式|如何使用CMSIS的延时|读取通过外部中断实现按键捕获代码的实现及分析-学习笔记(14)

文章目录 通过外部中断实现按键捕获代码的实现及分析Tip1:函数说明的格式Tip2:如何使用CMSIS的延时GetTick函数原型stm32f407_intr_handle.c解析中断处理函数&#xff1a;void EXTI4_IRQHandler 调试流程软件模拟调试 两种代码的比较课后作业: 通过外部中断实现按键捕获代码的实…

【雕爷学编程】MicroPython动手做(39)——机器视觉之图像基础3

MixPY——让爱(AI)触手可及 MixPY布局 主控芯片&#xff1a;K210&#xff08;64位双核带硬件FPU和卷积加速器的 RISC-V CPU&#xff09; 显示屏&#xff1a;LCD_2.8寸 320*240分辨率&#xff0c;支持电阻触摸 摄像头&#xff1a;OV2640&#xff0c;200W像素 扬声器&#…

使用 PowerShell 来揪出端口罪魁祸首

问题&#xff1a; 在调试 Node.js 程序时经常出现端口被占用的情况是很常见的。为了找到具体是哪个进程占用了 3000 端口&#xff0c;我们可以借助一些工具来查找。下面将展示一种方法&#xff1a;使用 PowerShell 。 方法&#xff1a;使用 PowerShell 打开 PowerShell 终端…

TypeScript 类型断言

TypeScript 类型断言 简单来说类型断言就是 使用as关键词 强行指定获取到的结果类型 应用场景 // 类型断言: 强行指定获取到的结果类型// 应用场景// 页面上有一个 id 为 link 的 a 标签// 我们知道它是 a 标签// 但是 TS 不知道 // document.getElementById 的返回值是 HTMLE…

【JAVA BASE API】介绍Java基础API语法,包括JAVA8之后的时间日期等

博主&#xff1a;_LJaXi Or 東方幻想郷 专栏&#xff1a; Java | 从跨平台到跨行业 开发工具&#xff1a;IntelliJ IDEA JAVA BASE API Object 类clone 对象克隆toString() 转换字符串equals(Object obj) 地址比较 Objects 类Objects.equals(Object obj1, Object obj2) 非空比较…

react ant add/change created_at

1.引入ant的 Table import { Table, Space, Button, message } from antd; 2.获得接口的数据的时候增加上创建时间 const response await axios.get(${Config.BASE_URL}/api/v1/calculation_plans?token${getToken()});if (response.data.message ok) {const data respon…

Matlab的信号频谱分析——FFT变换

Matlab的信号频谱分析——FFT变换 Matlab的信号频谱分析 FFT是离散傅立叶变换的快速算法&#xff0c;可以将一个时域信号变换到频域。 有些信号在时域上是很难看出什么特征的。但是如果变换到频域之后&#xff0c;就很容易看出特征了。 这就是很多信号分析采用FFT变换的原因…

灵遨底盘驱动安装

文章目录 ROS Packages灵遨科技ROS包的安装安装ROS依赖包导入lingao_ros包到工作空间工作空间的环境source &#xff08;可选&#xff09; 通讯接口设置ROS Package 基本用法使用测试 ROS Packages lingao_base: 灵遨底盘驱动软件包&#xff0c;用于ROS的底盘通讯收发 lingao_…