【周年福利Round2】都0202年了,您还不会Elasticsearch?
本文字数:8775字
预计阅读时间:22分钟
什么是 Elasticsearch?它是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。
无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。但是,Lucene非常复杂,使用之前需要深入了解检索的相关知识来理解它是如何工作的。Elasticsearch(以下用ES代替)是一个基于Lucene的分布式可扩展的实时分析搜索引擎。它的目的是通过简单的RESTfulAPI来隐藏Lucene的复杂性,从而让全文搜索变得简单。
基本概念
为了加深理解,再将它与我们熟悉的MySql数据库做下对比:
ES | MySql |
---|---|
索引 | 数据库 |
类型 | 表 |
文档 | 数据行 |
字段 | 数据列 |
隐射 | 列定义 |
另外,需要说明一下,从ES7的版本开始, 类型(Type)这个概念将被完全移除。这一改动,使整个ES的数据结构又轻量了不少,每个索引下的文档只有一种数据结构,在索引数据的管理上也更方便了。由于我参与的项目使用的是ES5.3版本,所以后面的讲解都是基于该版本的。
那么,现在思考一个问题:
在一个ES实例的某个索引中,它的文档数量比较大,甚至超过了硬盘的容量,这时,ES要如何进行存储?
其实,在大数据场景下,我们的ES都是以集群
的方式部署的,集群中的每一个节点
就是一个ES实例。在这种模式下,我们的索引数据不再集中存储到一个实例中,而是被切分成多个分片
,这些分片被分配到不同的节点中进行存储,另外,为了保证高可用,每个分片至少还会有一个副本分片,并与主分片分开进行存储。集群整体结构如下图所示:
在ES7.0之前的版本中,主分片和副本分片的默认数量是5和1,7.0之后,主分片和副本分片的默认数量都为1。主副分片的数量可以在创建索引时手动指定。
应用场景
作为一个分布式的搜索引擎,在实际的应用中,ES不仅被用来实现全文检索功能,还被广泛应用于日志分析和数据分析等多种场景,大幅度降低了维护多套专用系统的成本:
全文检索
这是ES的核心功能,不必多说。
日志分析
ES结合Logstash和Kibana这两大开源软件,组成的ELK系统,打通了日志从采集到清洗再到存储和可视化的整个过程,使得日志搜索、分析和可视化变得不再棘手,是目前日志处理领域的技术首选。
数据分析
借助与ES很好的时间序列处理能力、丰富的数据统计功能以及分布式的特点,使得ES逐渐成为了数据分析和可视化的理想工具。
ES安装
好了,概念就介绍到这,现在让我们开始动手安装ES。
ES是基于java开发的,所以安装之前需要确保本机上已经安装有jdk,ES对java依赖如下:
ES5.x以上需要Java8 以上的版本 ES6.5以上开始支持Java 11 从ES7.0开始,ES内置了Java环境
下面以ES5.3.3版本为例来讲解ES的安装,其他版本安装与此差别不大。
下载并解压
在官网https://www.elastic.co/cn/downloads/elasticsearch 下载相应版本的压缩包并解压。
进入bin目录,执行elasticsearch脚本既可启动
cd bin
./elasticsearch
可能遇到的问题
问题1 can not run elasticsearch as root
当使用root用户启动ES时,会报如下错误
所以,需要为ES新建一个运行账号
# 新建一个elasticsearch的用户组
groupadd elasticsearch
# 在elasticsearch用户组下面建立一个elasticsearch的用户
useradd -g elasticsearch elasticsearch
将elasticsearch目录的所有者给该账号
chown -R elasticsearch:elasticsearch elasticsearch-5.3.3/
接下来切换到elasticsearch用户,并启动ES
su elasticsearch
./elasticsearch
如果你运气好,就可能会遇到另一个问题,就是下面的问题2。
这个问题也很好解决,我们只需要修改系统的最大虚拟内存区域的数值就行 退回到root账号下,修改配置文件/etc/sysctl.conf
vi /etc/sysctl.conf
在文件末尾添加vm.max_map_count的配置,值大于等于262144即可 添加完成后执行如下命令重新加载系统参数
sysctl -p
上面的问题解决后,切换到elasticsearch账号,就可以顺利启动ES了。
从文本到索引
现在我们就可以开始操作ES了。首先从建立一个空索引开始。
建立索引
创建索引很简单,因为ES对各类操作都提供了RESTful API接口,我们可以很方便的使用这些接口来与ES交互。下面是一个最基本的创建索引请求:
PUT /test //put一个创建索引的请求,索引名为test
{
"settings": {}, //这里可以配置一些索引的参数,如主副分片数量
"mappings": { //这里就是定义文档类型和字段的地方啦
"type1": { //定义文档类型:type1
"properties": {
"field1": { //定义字段名:field1
"type": "text", //字段类型:text
"analyzer": "standard"
}
}
}
}
}
这个请求会在ES中创建一个名为test的索引,索引中包含一个名为type1的类型,该类型有一个字段field1,字段类型为text,该字段在索引和搜索时都使用standard分析器。分析器不懂没关系,后面会讲。
其实,还有一种更简单的方法,就是不创建索引,直接put一个文档到ES,比如:
PUT test/type1/1 //将数据提交到test索引,文档类型为type1,id为1
{
"user": "kimchy",
"post_date": "2009-11-15T14:12:12",
"message": "trying out Elasticsearch"
}
执行这条请求的时候,如果ES发现test索引不存在,便会根据文档的字段和内容,自动创建索引和mapping信息,如下:
"mappings": {
"type1": {
"properties": {
"message": {
"type": "text", //自动为message字段设置为text类型
"fields": { //为message创建的子字段
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"post_date": {
"type": "date" //自动为post_date字段设置为date类型
},
"user": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
可以看到,ES会根据文档的原始信息,为字段映射对应的类型(如将post_date字段类型设为Date),另外,对每个text类型的字段,都会增加一个fields子域,这个子域的作用就是扩展出原文档没有的字段,本例中,最终的存储字段有message,message.keywod,post_date,user,user.keyword这个5个。
但是,通过这种方式创建的索引,结构往往比较简单,对于复杂点的系统,一般是不太能满足业务需要的。所以,比较建议的做法还是预先通过手动定义mapping来创建索引。
分析器
接下来,我们来看看ES是怎样将处理我们提交的文本数据并最终存入索引的。当收到一条索引请求时,ES会用一种类似于E TL的方式对文档进行处理和分析。比如判断文本中是否需要进行特殊字符处理、是否需要进行大小写转换、以何种方式分词和是否要去停词等等。这些分析操作被ES封装在一个叫做分析器(Analyzer)的模块中。
分析器主要由三个子模块组成:字符过滤器:对文本进行粗略的清洗工作,如去除原始文本的HTML标记,把字符“&”转换为单词“and”,或者模式匹配替换等。分词器:将文本分割成一系列被称为词汇单元(token)的独立原子元素,每个token大致能与自然语言中的“单词”对应起来。表征过滤器:对分词后的词汇进行修改词(例如将字符转为小写),去掉词(例如停词“a”,“and”,“也”,“又”等等)或者增加词(例如同义词像“jump”和“leap”等等)等操作。
实际使用时,这一系列的转换操作是根据具体业务需求来决定的,ES已经帮我们预定义了多种常用的分析器,如Keyword Analyzer,Pattern Analyzer和Whitespace Analyzer等,同时也预定义了很多字符过滤器、分词器和表征过滤器,方便我们使用。想了解更多的内建分析器可参考官方文档。
另外,ES也支持用户自定义分析器,甚至联合Lucene的token工具和过滤器创建自定义的分析器。比如,在创建索引时,我们可以使用pattern分析器自定义一个自己的数字分析器digit_analyzer,并在mapping中使用它:
PUT /test
{
"settings": {
"analysis": {
"analyzer": {
//定义分析器
"digit_analyzer": {
"tokenizer": "digit_tokenizer"
}
},
"tokenizer": {
//定义分词器
"digit_tokenizer": {
"type": "pattern", //设置为模式匹配分词器
"pattern": "[^(0-9)]" //只匹配数字
}
}
}
},
"mappings": { //文档的mapping配置
"_default_": { //文档的类型
"properties": {
"userCode": { //字段
"type": "text", //设置userCode字段的类型
"analyzer": "digit_analyzer" //使用自定义分析器
},
…… //省略其他字段的配置
}
}
}
}
分析器不仅仅会被用在索引阶段,在执行某些搜索时,ES也会使用分析器,对查询关键字进行过滤和分词操作。默认情况下,索引和搜索阶段使用的是同一个分析器,即analyzer指定的分析器,若想使用不同的分析器,可以在mappings中设置search_analyzer,如:
"mappings": {
"_default_": {
"properties": {
"user": {
"type": "text",
"analyzer": "standard",
"search_analyzer": "standard" //指定搜索时用的分析器
}
}
}
}
中文分词
分析器中最重要的一个模块就是分词器(Tokenizer),因为分词的好坏决定了最终搜索的效果。
虽然,ES预定义了很多分词器,但是,对中文的支持却非常有限。在分词时,它们只会将中文分成一个一个的汉字,无法按照词语划分。于是,有人就专门为ES开发了支持中文的分词器插件,常见的中文分词插件有:ik、结巴和THULAC等等。
其中,ik分词器是使用最广泛的,下面就简单介绍一下ik分词器以及在ES中如何使用。
ik提供了两种分词模式,ik_max_word和ik_smart模式:
ik_max_word | ik_smart |
---|---|
会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合。 | 会将文本做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”。 |
索引时,为了提供索引的覆盖范围,通常会采用ik_max_word分析器。比如:
"userName": {
"type": "text",
"analyzer": "ik_max_word" //使用ik_max_word模式
}
搜索时,可以根据业务需求的不同,选择两种模式中的任意一种。
倒排索引
读到这里,你或许会有个疑惑:ES为什么要对文本进行分词呢?
答案是:为了建立倒排索引。
索引,我们都知道,是为了加快速数据检索的,那何为倒排索引?从字面上理解,感觉是倒着排序的索引,然而,感觉这东西,有时候其实并不可靠。
倒排索引的英文原名叫Inverted Index,翻译过来应该叫“反向索引”,是相对于“正向索引”(Forward Index)而言的。
正向索引,简单理解就是在文档中找关键词,而反向索引就是通过关键词找文档。
比如,下面三篇文档:
文档ID | 文档内容 |
---|---|
1 | 元数据是数据的数据 |
2 | 容器就是某种镜像的实例 |
3 | 设计就是代码,代码就是设计 |
它们的正向索引结构大致如下:
Key | Value |
---|---|
1 | 元数据,数据 |
2 | 容器,就是,某种,镜像,实例 |
3 | 设计,就是,代码,设计 |
当我们搜索“代码”在那篇文档出现时,就要遍历索引中每个Key下的Value,判断其中是否含有“代码”这个关键词,若有,则返回对应的Key值。当数据量很大时,这样的检索方式将非常消耗资源,相当于每个文档都要遍历一遍。
于是人们就发明了反向索引。还是上面的三篇文档,反向索引的结构大致如下:
Key | Value |
---|---|
元数据 | 1 |
数据 | 1 |
容器 | 2 |
就是 | 2,3 |
…… | …… |
代码 | 3 |
设计 | 3 |
同样还是搜索“代码”这个关键字,但是,反向索引只要遍历Key就能立马的找到对应的文档ID,这速度,简直是绿皮换高铁啊。
在 ES中,文档中的每个字段都会有自己的倒排索引。我们可以在 mappings 中去设置index为false来对某些字段不做索引,这样做可以节省存储空间,但同时也会导致这个字段无法搜索了。
"mappings": {
"_default_": {
"properties": {
"userName": {
"type": "text",
"analyzer": "standard",
"index": false
},
"profile": {
"type": "text",
"index": "false", //指定profile字段不创建索引
}
}
}
}
搜索
索引有了,数据也存进去了,接下来我们就可以开始进行搜索了。
常用搜索方式
ES提供了如match匹配、filter过滤器、range范围查询、布尔查询、aggregations聚合等丰富的基于json的搜索方式。
这些搜索方式大致分为两类:基础搜索
和复合搜索
。
基础搜索,是对文档中指定的一个或多个字段进行搜索的方式,如term,match或range搜索等:
term搜索:
GET /index_name/_search //搜索api
{
"query": {
"term": {// term搜索,不对关键字进行分词
"user": "张三" //搜索user字段为“张三”的文档
}
}
}
martch搜索:
GET /index_name/_search
{
"query": {
"match": { // match搜索,会对关键字进行分词
// 搜索content字段中含有“搜狐”或“媒体”的文档
"content": "搜狐媒体"
}
}
}
range搜索:
GET /index_name/_search
{
"query": {
"range": { // range搜索
"age": { // 搜索age字段值在10~20之间的文档
"gte": 10,
"lte": 20
}
}
}
}
复合搜索,是将多个基础搜索或其他复合搜索组合起来,从而实现更加复杂的逻辑搜索的一种搜索方式,如bool或dis_max搜索等。
bool搜索:
POST /index_name/_search
{
"query": {
"bool": { // bool搜索
"must": { // 与
"term": { "user" : "kimchy" }// term搜索
}
"must_not": { // 非
"range": { // range搜索
"age" : { "gte" : 10, "lte" : 20 }
}
},
"should" : [ // 或
{ "term" : { "tag" : "wow" } },
{ "match" : { "addr" : "streat" } }// match搜索
],
"minimum_should_match" : 1
}
}
}
dis_max搜索:
GET /index_name/_search
{
"query": {
"dis_max": { // dis_max搜索
"tie_breaker": 0.7,
"boost": 1.2,
"queries": [
{
"term": { "age" : 34 } // term搜索
},
{
"term": { "age" : 35 } // term搜索
}
]
}
}
}
更多的搜索方式可以参看ES的官方文档Query DSL部分。
搜索结果字段说明
下面这段json代码就是ES返回的查询结果。
{
"took": 1, // 当前搜索花费的毫秒数
"timed_out": false, // 当前搜索是否超时
"_shards": { // 当前搜索查找的分片情况
"total": 5,
"successful": 5,
"failed": 0
},
"hits": { // 搜素结果都在这个字段下
"total": 443, // 总共命中条件的文档个数
"max_score": 1, // 最大的文档评分值
"hits": [ // 具体的文档数据
{
"_index": "test_index",
"_type": "myDoc",
"_id": "1380224",
"_score": 1,
"_source": {
"name": "ESTest",
"desc": "This is a desc test"
}
},
......
]
}
}
下面,我们来说说几个重要的字段:
took
这个字段表示当前搜索总共花费的毫秒数。
time_out
这个字段告诉我们当前查询是否超时了。当然,一般情况下是不会超时的,因为默认情况下,ES是没有配置超时时间的。我们可以通过修改ES的配置项
search.default_search_timeout
来设置全局超时时间,或者在搜索请求上通过timeout
参数设置当前请求的超时时间,如GET /test_index/_search?timeout=10ms
需要注意的是timeout不会停止查询的进行,当查询超时了,在后台,可能依旧在执行查询,尽管超时结果已经被返回。
_shards
该字段表示参与查询的分片数(total字段),有多少是成功的(successful字段),有多少的是失败的(failed字段)。
hits
这是所有返回字段中最重要的一个字段,我们需要的文档数据都在这里面。
hits的第一个字段
total
告诉我们匹配文档的总数。后面的hits数组里,包含着我们需要的文档数据,每个文档都包含
_index
,_type
,_id
及_score
这些基础信息字段和_source
源数据字段max_score
指的是所有匹配文档中_score的最大值,这里涉及到ES文档打分机制的知识点,由于不是本文的重点,这里就不扩展了。
分页和排序
分页和排序可以说是我们业务开发中最常用的功能了。在ES中使用分页排序查询就像在SQL中写LIMIT和ORDER BY一样简单
想要分页查询,只需要在搜索请求中指定from和size字段即可,如下所示:
GET /_search
{
"from": 100, //从第100条数据开始
"size": 10, //查询10条数据
"query": {
"term": { "user" : "kimchy" }
}
}
但是,这种查询方式有一个性能问题,就是,当from很大时,比如5000,ES会查出前5010条数据到内存,然后返回最后10条给我们,这样查询时间将会变长,严重的情况下,内存还有可能会爆掉,oh my god!不过,ES还提供了另一种分页查询方式:scroll查询
POST /_search?scroll=1m
{
"size": 100,
"query": {
"match" : {
"title" : "elasticsearch"
}
}
}
这个查询结果会出现一个scroll_id,就是下面这个,scroll=1m表示这个scroll_id维持的时间是1分钟,如果这1分钟没有继续查询,那么这个id就会失效。
"_scroll_id": "DnF1ZXJ5VGhlbkZldGNoAgAAAAAAEddYFldScjNIUDZKVDBPbk1CeG ZBRmJXVFEAAAAAABHXWRZXUnIzSFA2SlQwT25NQnhmQUZiV1RR"
接下来,我们就可以用这个_scroll_id查询下一页了:
POST /_search/scroll
{
"scroll": "1m",
"scroll_id": "DnF1ZXJ5VGhlbkZldGNoAgAAAAAAEddYFldScjNIUDZKVDBPbK1CeGZBRmJXVFEAAAAAABHXWRZXUnIzSFA2SlQwT25NQnhmQUZiV1RR"
}
scroll查询相当于维护了一份当前索引段的快照信息,这个快照信息是你执行这个scroll查询时的快照。在这个查询后的任何新索引进来的数据,都不会在这个快照中查询到。
这种查询方式还有一个弊端,那就是无法指定页数查询,只能按顺序一页一页查询。
排序的话,相对就简单多了,下面是一个带多级排序的查询请求:
GET /_search
{
"sort" : [
"name", // 按name排序
{ "age" : "desc" }, // 按age倒序排序
"_score" // 使用ES默认排序
],
"query" : {
"term" : { "user" : "kimchy" }
}
}
上手实践
光说不练假把式,下面我们以一个简单的用户名查询功能为例,来将上面的部分知识点串起来。
我们知道,用户名一般是由中文,英文和数字组成的。这里,我使用ik中文分词分词器来对用户名进行分词,并analyzer配置为ik_max_word模式,为了将用户名尽量拆分成细粒度的词,增加搜索时的覆盖率。
PUT /user_index
{
"mappings": {
"user": {
"properties": {
"userName": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
}
}
我们先往索引中put几条数据:
POST /_bulk //批量导入数据接口
{ "index" : { "_index" : "user_index", "_type": "user", "_id" : "1" } }
{ "userName" : "杯具人生" }
{ "index" : { "_index" : "user_index", "_type": "user", "_id" : "2" } }
{ "userName" : "旋转跳跃" }
{ "index" : { "_index" : "user_index", "_type": "user", "_id" : "3" } }
{ "userName" : "一杯茶" }
{ "index" : { "_index" : "user_index", "_type": "user", "_id" : "4" } }
{ "userName" : "茶杯和茶碗" }
{ "index" : { "_index" : "user_index", "_type": "user", "_id" : "5" } }
{ "userName" : "茶禅一味" }
数据添加成功后,我们就可以开始进行搜索了。
我们用match搜索试试。match搜索时,ES会先对关键字进行分词,然后再对每个分词进行检索,比如,搜索用户名中包含“茶杯”的用户:
GET /user_index/user/_search
{
"query": {
"match": {
"userName": "茶杯"
}
}
}
返回结果中不仅出现了包含“茶杯”的用户,还返回了包含“茶”或“杯”的用户:
"hits": {
"total": 4,
"max_score": 2.034024,
"hits": [
{
"_index": "user_index",
"_type": "user",
"_id": "4",
"_score": 2.034024,
"_source": {
"userName": "茶杯和茶碗"
}
},
{
"_index": "user_index",
"_type": "user",
"_id": "3",
"_score": 0.5753642,
"_source": {
"userName": "一杯茶"
}
},
{
"_index": "user_index",
"_type": "user",
"_id": "5",
"_score": 0.2824934,
"_source": {
"userName": "茶禅一味"
}
},
{
"_index": "user_index",
"_type": "user",
"_id": "1",
"_score": 0.25316024,
"_source": {
"userName": "杯具人生"
}
}
]
}
这是因为,ES的match搜索会先对关键字“茶杯”进行分词,分成“茶杯”、“茶”和“杯”三个词,然后再去索引中搜索含有这三个词之一的文本。
如果我们只想搜索包含“茶杯”的用户怎么办呢?解决方法是设置match搜索的operatior参数为“and”,使检索出来结果包含所有的关键字。
GET /user_index/user/_search
{
"query": {
"match": {
"userName": {
"query": "茶杯",
"operator": "and"
}
}
}
}
现在的返回结果就只有一条了:
"hits": {
"total": 1,
"max_score": 2.034024,
"hits": [
{
"_index": "user_index",
"_type": "user",
"_id": "4",
"_score": 2.034024,
"_source": {
"userName": "茶杯和茶碗"
}
}
]
}
接下来,我们再插入另一组数据:
POST /_bulk
{ "index" : { "_index" : "user_index", "_type" : "user", "_id" : "6" } }
{ "userName" : "我湖KobeBryant" }
{ "index" : { "_index" : "user_index", "_type" : "user", "_id" : "7" } }
{ "userName" : "Kobe最帅" }
{ "index" : { "_index" : "user_index", "_type" : "user", "_id" : "8" } }
{ "userName" : "彭彭520" }
{ "index" : { "_index" : "user_index", "_type" : "user", "_id" : "9" } }
{ "userName" : "彭于晏小粉" }
{ "index" : { "_index" : "user_index", "_type" : "user", "_id" : "10" } }
{ "userName" : "莫逆于心" }
现在,我们来搜索“kobe”试试,看看能不能搜到 我湖KobeBryant
和 Kobe最帅
这两条数据:
GET /user_index/user/_search
{
"query": {
"match": {
"userName": {
"query": "kobe",
"operator": "and"
}
}
}
}
搜索结果如下,ES只返回了Kobe最帅”
,却没有 我湖KobeBryant
这条数据。
"hits": [
{
"_index": "user_index",
"_type": "user",
"_id": "7",
"_score": 0.6099695,
"_source": {
"userName": "Kobe最帅"
}
}
]
这是为什么呢?
我们已经知道,“我湖KobeBryant”在ES中是被分词后存入倒排索引的,既然没被搜索到,那有没有可能是“KobeBryant”这个词没有被拆分?我们不妨来看看这个用户名的分词情况。
在ES中,提供了专门用来测试分词器的接口:_analyze,我们就用这个接口来看看:
GET /user_index/_analyze
{
"analyzer": "ik_max_word",
"text": "我湖KobeBryant"
}
返回结果如下:
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "湖",
"start_offset": 1,
"end_offset": 2,
"type": "CN_WORD",
"position": 1
},
{
"token": "kobebryant",
"start_offset": 2,
"end_offset": 12,
"type": "ENGLISH",
"position": 2
}
]
}
可以看到,ik分词器将“我湖KobeBryant”分成了3个词,其中“KobeBryant”被当成了一个词,所以,在倒排索引中,当搜索到key=kobe时,对应的value中是没有“我湖KobeBryant”这条文档的,难怪我们搜索不出来。
找到问题原因了,那就解决它。我们可以改写ik分词器的源码,让它支持驼峰式英文的拆分,但是,这个方法不仅工程量大而且还容易出bug。一个比较简单可行的方法就是在存入ES前对文档进行预处理,比如通过加空格的方式将“KobeBryant”转换成“Kobe Bryant”,因为ik分词器是支持空格分词的。这样的处理方式实现起来不仅简单,而且在后期也方便扩展和维护。
转换以后的分词情况如下:
GET /user_index/_analyze
{
"analyzer": "ik_max_word",
"text": "我湖Kobe Bryant"
}
//返回
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "CN_CHAR",
"position": 0
},
{
"token": "湖",
"start_offset": 1,
"end_offset": 2,
"type": "CN_WORD",
"position": 1
},
{
"token": "kobe",
"start_offset": 2,
"end_offset": 6,
"type": "ENGLISH",
"position": 2
},
{
"token": "bryant",
"start_offset": 7,
"end_offset": 13,
"type": "ENGLISH",
"position": 3
}
]
}
最后,我们就可以将想要用户名给搜索出来啦。
"hits": [
{
"_index": "user_index",
"_type": "user",
"_id": "6",
"_score": 1.2336597,
"_source": {
"userName": "我湖Kobe Bryant"
}
},
{
"_index": "user_index",
"_type": "user",
"_id": "7",
"_score": 0.6099695,
"_source": {
"userName": "Kobe最帅"
}
}
]
只不过,这样搜索出来的用户名“我湖Kobe Bryant”是带有空格的,解决办法也简单,我们只要根据查到的用户ID去缓存中拿取正确的用户名就可以了,或者在原文档中增加一个冗余字段(比如:originUserName)来存储原用户名,这样我们就能直接从搜索结果中获取正确的用户名了。
至此,一个用户名查询的核心功能就完成了。
总结
周年福利Round2
文末...福利继续!
-本期赠书-
《大型互联网应用轻量级架构实战》
全面介绍了基于轻量级架构来构建大型互联网应用,包括轻量级架构概述、Servlet、Spring、单元测试、集成测试、事务管理、Spring Security、MyBatis、MyBatis 高级应用、SMM 技术集成、SMM 分层、基于 SMM 架构的互联网应用、使用 NGINX 实现高可用、使用 Redis 实现高并发等内容。
同时,书中所介绍的技术方案皆为业界主流的技术,极具前瞻性。本书除了讲解轻量级架构的理论知识,还会在每个知识点上辅以大量的代码案例,使理论可以联系实际,具备更强的可操作性。本书主要面向对分布式系统、轻量级应用有使用需求的学生、软件开发人员、系统架构师。
参与方式
文末留言板留言,留言点赞前3名各获赠书一本;
特别提醒:活动有效期自发布日起七天内;
获奖公布
公布时间及位置:8月20日头条推送文末;
特别提醒:兑奖截止至9月3日,请参与读者及时兑奖;
上期赠书获奖公示
恭喜:“狼喜欢夏天”、“曦岸”、“阿啦马德里”
以上读者请添加小编微信:sohu-tech20 兑奖。
本次活动最终解释权归搜狐技术产品公众号所有
也许你还想看
(▼点击文章标题或封面查看)
加入搜狐技术作者天团
千元稿费等你来!
戳这里!☛