百亿级数据存储架构能力
生产需求上,百亿级数据存储架构, 一般来说,需要具备以下四个能力:
-
高并发的在线ACID事务 (分库分表)
-
高并发的在线搜索 (倒排表副本)
-
海量数据的离线处理 (高可用+全量副本)
-
冗余表双写能力 (不同业务维度的副本)
百亿级数据存储架构,需要使用ES完成高性能、跨库搜索
便于商品的聚合搜索,高速搜索,采用两大优化方案:
-
把商品数据冗余存储在Elasticsearch中,实现高速搜索
-
把商品数据冗余存储在redis 中,实现高速缓存

很多的时候,要求保持很高的数据一致性。比如:
-
要求 mysql 与 es 做到秒级别的数据同步。
-
要求 mysql 与 redis 做到秒级别的数据同步。
-
要求 mysql 与 hbase 做到秒级别的数据同步。
1 ElasticSearch 从入门到工业级使用
1.1 什么是全文检索
将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之索引。
例如:字典。字典的拼音表和部首检字表就相当于字典的索引,对每一个字的解释是非结构化的,如果字典没有音节表和部首检字表,在茫茫辞海中找一个字只能顺序扫描。
然而字的某些信息可以提取出来进行结构化处理,比如读音,就比较结构化,分声母和韵母,分别只有几种可以一一列举,于是将读音拿出来按一定的顺序排列,每一项读音都指向此字的详细解释的页数。
我们搜索时按结构化的拼音搜到读音,然后按其指向的页数,便可找到我们的非结构化数据—也即对字的解释。 这种先建立索引,再对索引进行搜索的过程就叫全文检索(Full-text Search)。
虽然创建索引的过程也是非常耗时的,但是索引一旦创建就可以多次使用,全文检索主要处理的是查询,所以耗时间创建索引是值得的。

比如使用全文检索,所搜索“生化机”

(有可能是手抖打错了,本来是生化危机),但是期望需要出来右侧的 4条 记录。
有 4条 数据将每条数据进行词条拆分。
如“生化危机电影”拆成:生化、危机、电影 关键词(拆分结果与策略算法有关)每个关键词将对应包含此关键词的数据 ID 搜索的时候,直接匹配这些关键词,就能拿到包含关键词的数据这个过程就叫做全文检索。
而词条拆分和词条对应的 ID 这个就是倒排索引的的基本原理。
对比数据库的缺陷
MySQL如果没有索引的情况下,共有100万条,按照之前的思路,其实就要扫描100万次,而且每次扫描,都需要匹配那个文本所有的字符,确认是否包含搜索的关键词,而且还不能将搜索词拆解开来进行检索

利用倒排索引 进行搜索的话,假设100万条数据,拆分出来的词语,假设有1000万个词语,那么在倒排索引中,就有1000万行,我们可能并不需要搜索1000万次。很可能说,在搜索到第一次的时候,我们就可以找到这个搜索词对应的数据。也可能是第100次,或者第1000次
全文检索使用场景
-
维基百科,类似百度百科,牙膏,牙膏的维基百科,全文检索,高亮,搜索推荐
-
The Guardian(国外新闻网站),类似搜狐新闻,用户行为日志(点击,浏览,收藏,评论)+社交网络数据(对某某新闻的相关看法),数据分析,给到每篇新闻文章的作者,让他知道他的文章的公众反馈(好,坏,热门,垃圾,鄙视,崇拜)
-
Stack Overflow(国外的程序异常讨论论坛),IT问题,程序的报错,提交上去,有人会跟你讨论和回答,全文检索,搜索相关问题和答案,程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应的答案
-
GitHub(开源代码管理),搜索上千亿行代码(5)电商网站,检索商品
-
日志数据分析,logstash采集日志,ES进行复杂的数据分析(ELK技术,elasticsearch+logstash+kibana)
-
商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户,比如说订阅牙膏的监控,如果高露洁牙膏的家庭套装低于50块钱,就通知我,我就去买
-
BI系统,商业智能,Business Intelligence。比如说有个大型商场集团,BI,分析一下某某区域最近3年的用户消费金额的趋势以及用户群体的组成构成,产出相关的数张报表,**区,最近3年,每年消费金额呈现100%的增长,而且用户群体85%是高级白领,开一个新商场。ES执行数据分析和挖掘,Kibana进行数据可视化
1.2 ES简介
Elaticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。
es也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
Elasticsearch是面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。
在Elasticsearch中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。Elasticsearch比传统关系型数据库如下:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
Elasticsearch提供多种语言支持,其中Java的客户端为 Java REST Client 。
而它又分成两种:高级和低级的。高级包含更多的功能,如果把高级比作MyBatis的话,那么低级就相当于JDBC,是基于Netty和Server通讯相关。高级的 Client类似Mybatis是对于Low Level的封装。

1.3 ES基本概念
-
索引库
ElasticSearch将它的数据存储在一个或多个索引(index)中。
用SQL领域的术语来类比,索引就像数据库,可以向索引写入文档或者从索引中读取文档,并通过ElasticSearch内部使用Lucene将数据写入索引或从索引中检索数据。
Elastic Search使用倒排索引(Inverted Index)来做快速的全文搜索,这点与数据库不同,一般数据库 的索引,用B+Tree来实现。
Relational DB | Databases | Tables | 表结构 | Rows | Columns |
ElasticSearch |
Indices |
Types |
映射mapping |
Documents |
Fields 字段 |
索引库就是存储索引的保存在磁盘上的一系列的文件。里面存储了建立好的索引信息以及文档对象。一个索引库相当于数据库中的一张表。

-
document对象
获取原始内容的目的是为了索引,在索引前需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),域中存储内容。
每个文档都有一个唯一的编号,就是文档id。document对象相当于表中的一条记录。文档(document)是ElasticSearch中的主要实体。
对所有使用ElasticSearch的案例来说,他们最终都可以归结为对文档的搜索。文档由字段构成。

-
field对象
如果我们把document看做是数据库中一条记录的话,field相当于是记录中的字段。field是索引库中存储数据的最小单位。field的数据类型大致可以分为数值类型和文本类型,一般需要查询的字段都是文本类型的,field的还有如下属性:
-
是否分词:是否对域的内容进行分词处理。前提是我们要对域的内容进行查询。
-
是否索引:将Field分析后的词或整个Field值进行索引,只有索引方可搜索到。比如:商品名称、商品简介分析后进行索引,订单号、身份证号不用分词但也要索引,这些将来都要作为查询条件。
-
是否存储:将Field值存储在文档中,存储在文档中的Field才可以从Document中获取。比如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。
-
term对象
从文档对象中拆分出来的每个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term。
term中包含两部分一部分是文档的域名,另一部分是单词的内容。term是创建索引的关键词对象。
-
类型(type)
每个文档都有与之对应的类型(type)定义。
这允许用户在一个索引中存储多种文档类型,并为不同文 档提供类型提供不同的映射。 type的版本迭代
-
5.x及以前版本一个index有一个或者多个type
-
6.X版本一个index只有一个index
-
7.X版本移除了type,type相关的所有内容全部变成Deprecated,为了兼容升级和过渡,所有的7.X版本es数据写入后type字段都默认被置为_doc
-
8.X版本完全废弃type
-
映射(mapping)
mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。
-
分片(shards)
代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。
5.X默认不能通过配置文件定义分片 ES默认5:1 5个主分片,每个分片,1个副本分片
-
副本(replicas)
代表索引副本,es可以设置多个索引的副本,副本的作用:
-
提高系统的容错性,当个某个节点某个分片损坏或丢失时可以从副本中恢复。
-
是提高es的查询效率,es会自动对搜索请求进行负载均衡。
-
集群(cluster)
代表一个集群,集群中有多个节点(node),其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个es集群通信是等价的。
2 安装和DSL的使用
2.1 安装ES
使用docker安装单点Elasticsearch,步骤如下:
docker network create elastic
docker pull docker.elastic.co/elasticsearch/elasticsearch:7.15.2
docker run -di --name es --net elastic -p 192.168.56.181:9200:9200 -p 192.168.56.181:9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.15.2
9200端口(Web管理平台端口) 9300(服务默认端口)
浏览器输入地址访问:http://192.168.56.181:9200/

-
系统参数配置
es发现重启启动失败了,这是什么原因?
这与我们刚才修改的配置有关,因为elasticsearch在启动的时候会进行一些检查,比如最多打开的文件的个数以及虚拟内存区域数量等等,如果你放开了此配置,意味着需要打开更多的文件以及虚拟内存,所以我们还需要系统调优 修改vi /etc/security/limits.conf ,追加内容 (nofile是单个进程允许打开的最大文件个数 soft nofile 是软限制 hard nofile是硬限制 )
* soft nofile 65536
* hard nofile 65536
修改vi /etc/sysctl.conf,追加内容 (限制一个进程可以拥有的VMA(虚拟内存区域)的数量 )
vm.max_map_count=655360
执行下面命令 修改内核参数马上生效
sysctl -p
重新启动虚拟机,再次启动容器,发现已经可以启动并远程访问
reboot
2.2 安装Kibana
Kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索、查看交互存储在Elasticsearch索引中的数据。使用Kibana,可以通过各种图表进行高级数据分析及展示。
Kibana让海量数据更容易理解。它操作简单,基于浏览器的用户界面可以快速创建仪表板(dashboard)实时显示Elasticsearch查询动态。
设置Kibana非常简单。
无需编码或者额外的基础架构,几分钟内就可以完成Kibana安装并启动Elasticsearch索引监测。
Query DSL是一个Java开源框架用于构建类型安全的SQL查询语句。
采用API代替传统的拼接字符串来构造查询语句。
目前QueryDSL支持的平台包括JPA,JDO,SQL,Java Collections,RDF,Lucene,Hibernate Search。elasticsearch提供了一整套基于JSON的DSL语言来定义查询。
docker pull docker.elastic.co/kibana/kibana:7.15.2
docker run -di --name kb --net elastic -p 192.168.56.181:5601:5601 -e "ELASTICSEARCH_HOSTS=http://192.168.56.181:9200" docker.elastic.co/kibana/kibana:7.15.2
-
拉取kibana镜像
docker pull docker.elastic.co/kibana/kibana:7.15.2
-
安装kibana容器
docker run -di --name kb --net elastic -p 192.168.56.181:5601:5601 -e "ELASTICSEARCH_HOSTS=http://192.168.56.181:9200" docker.elastic.co/kibana/kibana:7.15.2
-
修改配置文件
docker exec -it kb /bin/bash
vi config/kibana.yml
好像不改也可以,因为上面docker启动有了ES地址
#修改elasticsarch.hosts: [ "http://elasticsearch:9200" ],如下:
elasticsearch.hosts: [ "http://192.168.56.181:9200" ]
2.3 启动ES

启动完之后的效果

接下来,可以访问es
http://cdh1:9200

接下来,可以访问 Kibana
默认的地址 http://cdh1:5601

2.4 使用 DSL 操作ES
在 Kibana的开发工具界面,可以执行 DSL 去进行ES的查询。

es开发,常常需要用到DSL语法去定义好 es 的查询语句。
就像 mysql 开发,需要提前定义好 sql 语句,并进行 sql 的执行和测试一样。
2.4.1 DSL 定义基本介绍
DSL(Domain Specific Language),一种特定领域的查询语言,用于构建复杂的查询和聚合操作。
在Elasticsearch中,可用DSL语法来定义查询和过滤条件,以及执行聚合操作。DSL语法 具有JSON格式,因此它非常易于阅读和编写。
2.4.2 DSL 定义语法说明
(1)关键字(Keywords)
-
DSL通常会定义一组关键字,这些关键字具有特殊含义,并在DSL中起到关键作用。关键字通常不能用作标识符或变量名。
-
示例:在一个简单的数学表达式DSL中,可能会定义关键字如"add"、"subtract"等来表示加法和减法操作。
(2)标识符(Identifiers)
-
标识符是用来表示变量名、函数名或其他用户定义的名称。它们需要遵循特定的命名规则,如大小写敏感、不包含特殊字符等。
-
示例:在一个配置文件DSL中,可以使用标识符来表示不同的配置项,如"username"、"password"等。
(3)表达式(Expressions)
-
表达式是DSL中最基本的构建块,用于计算或产生某个值。表达式可以包括变量、常量、运算符和函数调用。
-
示例:在一个数学表达式DSL中,可以将"2 + 3"作为一个表达式,计算结果为5。
(4)运算符(Operators)
-
运算符用于执行各种操作,例如算术运算、逻辑运算、比较运算等。DSL中的运算符根据所涉及的领域和需求而定。
-
示例:在一个布尔表达式DSL中,可以定义逻辑运算符如"and"、"or"用于连接多个条件。
(5)函数调用(Function Calls)
-
DSL可以支持函数调用,允许用户使用预定义或自定义的函数来完成特定的任务。函数调用通常由函数名称和传递给函数的参数组成。
-
示例:在一个日期处理DSL中,可以定义函数"formatDate(date, format)",其中"date"是日期值,"format"是日期格式字符串。
(6)控制流(Control Flow)
-
控制流语句用于控制程序的执行流程,例如条件语句(if-else)和循环语句(while、for)等。DSL可以支持特定的控制流语句来满足领域特定需求。
-
示例:在一个工作流程DSL中,可以使用条件语句来判断某个条件是否满足并执行相应的操作。
(7)注释(Comments)
-
注释用于向DSL代码添加说明性文本,以便开发人员理解和维护代码。注释通常不会被编译或执行,仅用于阅读目的。
-
示例:在DSL中,可以使用双斜杠(//)或特定的注释标记来添加注释,如:“// 这是一个示例注释”。
2.4.3 DSL常见语法
略
3 ES 的分词器
3.1 倒排索引

如上图
-
左边的是正排索引,通过文档的id如查找文档的内容
-
右边的是倒排索引,通过单词统计次数以及文档的位置
如Elasticsearch出现的次数为3,在id=1,id=2,id=3都出现过,且位置分别为1,0,0
3.2 默认分词器
默认分词器对于英文分词的效果如下
POST /_analyze
{
"text": "You can use Elasticsearch to store, search, and manage data",
"analyzer": "standard"
}
结果如下:

默认分词器对于中文的分词
POST /_analyze
{
"text": "中华人民共和国人民大会堂",
"analyzer": "standard"
}

3.3 中文分词
中文分词是中文文本处理的一个基础步骤,也是中文人机自然语言交互的基础模块。不同于英文的是,中文句子中没有词的界限,因此在进行中文自然语言处理时,通常需要先进行分词,
分词效果将直接影响词性、句法树等模块的效果。当然分词只是一个工具,场景不同,要求也不同。
部分分词工具如下:
-
中科院计算所NLPIR:http://ictclas.nlpir.org/nlpir/
-
ansj分词器:https://github.com/NLPchina/ansj_seg
-
哈工大的LTP:https://github.com/HIT-SCIR/ltp
-
清华大学THULAC:https://github.com/thunlp/THULAC
-
斯坦福分词器:https://nlp.stanford.edu/software/segmenter.shtml
-
Hanlp分词器:https://github.com/hankcs/HanLP
-
结巴分词:https://github.com/yanyiwu/cppjieba
-
KCWS分词器(字嵌入+Bi-LSTM+CRF):https://github.com/koth/kcws
-
ZPar:https://github.com/frcchang/zpar/releases
-
IKAnalyzer:https://github.com/wks/ik-analyzer
3.4 IK分词器
IK分词器下载地址https://github.com/medcl/elasticsearch-analysis-ik/releases 将ik分词器上传到服务器上,然后解压,并改名字为ik
mkdir ~/ik
mv elasticsearch-analysis-ik-7.15.2.zip ~/ik
unzip elasticsearch-analysis-ik-7.15.2.zip
将ik目录拷贝到docker容器的plugins目录下
docker cp ./ik es:/usr/share/elasticsearch/plugins
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包,IK分词器分为两种模式
-
ik_max_word:会将文本做最细粒度的拆分
比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国、中华人民、中华、华人、人民共和国、人民、共和国、大会堂、大会、会堂等词语。
POST /_analyze
{
"text":"中华人民共和国人民大会堂",
"analyzer":"ik_max_word"
}

-
ik_smart:会做最粗粒度的拆分
比如会将“中华人民共和国人民大会堂”拆分为中华人民共和国、人民大会堂。
POST /_analyze
{
"text": "中华人民共和国人民大会堂",
"analyzer": "ik_smart"
}

3.5 自定义扩展字典
IK分词器的两种模式的最佳实践是:索引时用ik_max_word,搜索时用ik_smart,索引时最大化的将文章内容分词,搜索时更精确的搜索到想要的结果。
举个例子:用户输入“华为手机”搜索,此时应该搜索出“华为手机”的商品,而不是“华为”和“手机”这两个词,这样会搜索出华为其它的商品,
此时使用ik_smart和ik_max_word都会将华为手机拆分为华为和手机两个词,那些只包括“华为”这个词的信息也被搜索出来了,我的目标是搜索只包含华为手机这个词的信息,这没有满足我的目标。
ik_smart默认情况下分词“华为手机”,依然会分成两个词“华为”和“手机”,这时需要使用自定义扩展字典
-
进入es
docker exec -it es /bin/bash
-
增加自定义字典文件
如果容器编辑乱码,可以在宿主机编辑,然后拷贝到容器中
#进入ik配置目录
cd plugins/ik/config/
vi new_word.dic
内容如下:
老铁
王者荣耀
洪荒之力
共有产权房
一带一路
java日知录
华为手机
-
修改配置文件
vi IKAnalyzer.cfg.xml
内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">new_word.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
-
拷贝到宿主机
docker cp es:/usr/share/elasticsearch/plugins/elasticsearch-analysis-ik/config ~/ik
-
重启
docker restart es
4 高级的DSL 查询
ES提供基于DSL(Domain Specific Language)的索引查询模式,DSL查询基于JSON定义查询。
Wikipedia对于DSL的定义"为了解决某一类任务而专门设计的计算机语言"。
大师Martin Fowler对于DSL定义“DSL 通过在表达能力上做的妥协换取在某一领域内的高效”。
我们在使用ES的时候,避免不了使用DSL语句去查询,就像使用关系型数据库的时候要学会SQL语法一样。

Elasticsearch provides a full Query DSL (Domain Specific Language) based on JSON to define queries. Think of the Query DSL as an AST (Abstract Syntax Tree) of queries
4.1 管理索引
查看所有的索引
GET _cat/indices?v

-
删除某个索引
DELETE /skuinfo
-
新增索引
PUT /user
-
创建映射
PUT /user/_mapping
{
"properties": {