elasticsearch学习
1.介绍
Elasticsearch是一个基于Lucene的搜索服务器,提供了一个分布式多用户能力的全文搜索引擎,基于Restful web接口。仅支持json格式,自带有分布式协调管理功能。
2.lucene与elasticsearch的关系
lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来的应用。
3.与其他数据存储进行比较
redis | mysql | es | hbase | hadoop/hive | |
---|---|---|---|---|---|
容量 | 低 | 中 | 较大 | 海量 | 海量 |
查询时效性 | 极高 | 中等 | 较高 | 中等 | 低 |
查询灵活性 | 较差k-v模式 | 非常好,支持sql | 较好,关联查询弱,但是可以全文检索,DSL语言可以处理过滤,匹配,排序,聚合等各种操作 | 较差,主要靠rowkey,scan的话性能不行,或者建立二级索引 | 非常好,支持sql |
写入速度 | 较快 | 中等 | 较快 | 较快 | 慢 |
一致性/事务 | 弱 | 强 | 弱 | 弱 | 弱 |
4.特点
- 天然集群,天然分片
es把数据分成多个shard,多个shard可以组成一份完整的数据,这些shard可以分布在集群的各个节点上,随着数据的不断增加,集群可以增加多个分片,把多个分片放到多个机子上,已达到负载均衡,横向扩展。
在实际运算过程中,每个查询任务提交到某一个节点,该节点必须负责将数据进行整理汇聚,在返回给客户端,也就是一个简单的节点上进行map计算,在一个固定的节点上进行reduces得到最终结果向客户端返回。 - 天然索引
所有数据默认进行倒排索引(全文搜索引擎主流方式)
每个记录保存数据时,都不会直接存入数据库,系统先会对数据分词,然后按照倒排索引结构保存
搜索时同样会先把搜索关键词进行分词,还会根据匹配程度进行打分,次数越多位置越靠前
lucenu为倒排索引(Term Dictionary)部分又增加了一层Term Index 结构,用于快速定位,而Term Index是缓存在内存中的,所以速度快但同时更消耗资源(内存,磁盘)。
- 实时分析
5.修改es配置文件
- /config/elasticsearch.yml
①集群名称必须相同
②同一集群,单个节点名称必须不同;是否有资格选举为master;是否存储数据
③网络访问(bind_host内网)和内部通信(publish_host外网)接口
④自发现配置:新节点向集群报道的主机名(集群通信端口9300,外部访问端口9200)
⑤把bootstrap自检程序关掉
⑥集群中最少的master数,如果集群中最少master数少于指定的数,将无法启动,官方推荐master数设置为:集群数/2+1
6.修改linux系统配置(改完后重启)
问题1:max file descriptors [4096] for elasticsearch process likely too low, increase to at least [65536] elasticsearch
原因:系统允许 Elasticsearch 打开的最大文件数需要修改成65536
解决:vi /etc/security/limits.conf
添加内容:
*soft nofile 65536
*hard nofile 131072
*soft nproc 2048
*hard nproc 65536
问题2:max number of threads [1024] for user [judy2] likely too low, increase to at least [2048] (CentOS7.x 不用改)
原因:允许最大进程数修该成4096
解决:vi /etc/security/limits.d/90-nproc.conf
修改如下内容:
*soft nproc 4096
问题3:max virtual memory areas vm.max_map_count [65530] likely too low, increase to at least [262144] (CentOS7.x 不用改)
原因:一个进程可以拥有的虚拟内存区域的数量。
解决:可零时提高vm.max_map_count的大小
命令:sysctl -w vm.max_map_count=262144
上述方法修改之后,如果重启虚拟机将失效,所以:
在 vim /etc/sysctl.conf文件最后添加一行
vm.max_map_count=262144
即可永久修改
7.集群启动脚本
8.集群验证
curl http://kqkj101:9200/_cat/nodes?v(查询各个节点状态)
9.head插件
- 下载head插件
Get https://github.com/mobz/elasticsearch-head/archive/elasticsearch-head-master.zip
也可以用git下载,前提yum install git
unzip elasticsearch-head-master.zip - 安装node.js
wget https://npm.taobao.org/mirrors/node/latest-v4.x/node-v4.4.7-linux-x64.tar.gz
tar -zxvf node-v9.9.0-linux-x64.tar.gz - 添加node.js到环境变量
source /etc/profile - 测试 node -v | npm -v
- 安装grunt(grunt是一个很方便的构建工具,可以进行打包压缩、测试、执行等等的工作)
进入到elasticsearch-head-master
npm install -g grunt-cli
npm install
(npm install -g cnpm --registry=https://registry.npm.taobao.org) - 修改elasticearch.yml(head插件才能访问es)
添加:http.cors.enabled: true
http.cors.allow-origin: “*” - 修改Gruntfile.js(注意’,’)
打开elasticsearch-head-master/Gruntfile.js,找到下面connect属性,新增hostname:’’:
connect: {
server: {
options: {
hostname: '’,
port: 9100,
base: ‘.’,
keepalive: true
}
}
} - 启动elasticsearch-head
进入elasticsearch-head目录,执行命令:grunt server
后台启动 nohup grunt server &exit - 关闭head插件
ps -aux|grep head
kill 进程号
10.logstash
- vim ./config/log4j_to_es.conf
//创建nginx_logstash.conf文件
input {//输入
/*file {//从文件中读取数据
path=>[""]//文件位置
exclue=>"*.gz"//不读取哪些文件
sincedb_path=>""//记录sincedb文件路径
type=>""
start_position=>"beginning"//或end,是否重头读取文件
}*/
stdin{//输入插件,可以管道插入,也可以终端交互插入
codec=>"plain"
tags=>["test"]//类型为array,自定义事件的tag
type=>"std"//类型为string,自定义该事件类型,用于后续判断
add_field=>{"key":"value"}//类型为hash,为该事件添加字段
}
}
filter {
grok {//①基于正则表达式提供丰富可重用模式pattern;②基于此可以将非结构化数据做结构化处理
match => {
"message" => '%{IPORHOST:remote_ip} - %{DATA:user_name} \[%{HTTPDATE:time}\] "%{WORD:request_action} %{DATA:request} HTTP/%{NUMBER:http_version}" %{NUMBER:response} %{NUMBER:bytes} "%{DATA:referrer}" "%{DATA:agent}"'
}
}
date {//将字符串类型的时间字段转换为时间戳类型,方便后续处理
match => [ "time", "dd/MMM/YYYY:HH:mm:ss Z" ]
locale => en
}
geoip {//添加地理位置信息
source => "remote_ip"
target => "geoip"
}
mutate{}进行增加修改删除替换等字段相关处理
output {//输出
stdout {
codec=>rubydebug
}
}
logstash启动解析nginx文件
head -n 2 /home/elk1/nginx_logs|./logstash -f ../config/nginx_logstash.conf
- ./bin/logstash -f config/log4j_to_es.conf
10.kibana(端口:5601)
- /config/kibana.yml
11.IK分词
- ①在/plugins/目录下建立ik文件夹
- ②把elasticsearch-analysis-ik-6.6.0.zip拷贝到该目录下,执行unzip进行解压缩
- ③修改分词词库/ik/config/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"></entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<entry key="remote_ext_dict">http://192.168.67.163/fenci/myword.txt </entry>
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
远程扩展词典可以利用nginx发布静态资源nginx.conf
server{
listen 80;
server_name 127.0.0.1;
location /fenci/{
root es;
}
并在/usr/local/nginx/下建/es/fenci目录下加myword.txt,每一行代表一个词(重启es,nginx),历史数据不会进行分词,如想对历史数据重新分词,执行
POST /dul/_doc/_update_by_query?conflicts=proceed
当遇到冲突而导致整个更新过程失败时,更新过程是不会回滚的。如果不想因为冲突导致整个更新过程终止,可以在url中添加参数conflicts=proceed
- ④重启es
12.ES增删改查
- text支持分词,keyword只能全部内容匹配。
- ik_smart粗粒度分词器(不重复),ik_max_word细粒度分词
- es客户端选择
①TransportClient为代表的ES的原生客户端,不能执行原生dsl语句必须使用它的java api方法
②以Rest Api为主的missing client,最典型的jest。这种客户端可以直接使用dsl语句拼接成字符串,直接传给服务端,然后返回json字符串在解析
1)创建索引语句
PUT /dul/
{
"mappings": {
"_doc":{
"properties":{
"user_id":{
"type":"keyword"
},
"sku_name":{
"type":"text",
"analyzer": "ik_max_word"
},
"sku_category1_name":{
"type":"text",
"analyzer": "ik_max_word"
},
"dt":{
"type":"keyword",
"index":"false"
}
}
}
}
}
2)查看mapping
GET /dul/_mapping/
结果:
{
"dul" : {
"mappings" : {
"_doc" : {
"properties" : {
"dt" : {
"type" : "keyword",
"index" : false
},
"sku_category1_name" : {
"type" : "text",
"analyzer" : "ik_max_word"
},
"sku_name" : {
"type" : "text",
"analyzer" : "ik_max_word"
},
"user_id" : {
"type" : "keyword"
}
}
}
}
}
}
3)新增文档(和修改-整体替换无区别)
PUT /dul/_doc/3 //POST自动生成22位uuid
{
"user_id":"2",
"sku_name":"我是美国人",
"sku_category1_name":"红米 平板电脑",
"dt":"K"
}
4)修改某一个字段
POST /dul/_doc/3/_update
{
"doc":{
"dt":"D"
}
}
5)用id查找
GET /dul/_doc/3
6)删除一个文档
DELETE /dul/_doc/3
7)搜索全部数据
GET /dul/_doc/_search
结果:
{
"took" : 11, //耗费时间(毫秒)
"timed_out" : false,//是否超时
"_shards" : {
"total" : 5,//发送给全部5个分片
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,//命中3条数据
"max_score" : 1.0,//最大评分
"hits" : [//结果
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"user_id" : "2",
"sku_name" : "我是美国人",
"sku_category1_name" : "红米 平板电脑",
"dt" : "K"
}
},
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"user_id" : "1",
"sku_name" : "我是中国人",
"sku_category1_name" : "小米手机 平板手机",
"dt" : "K"
}
}
]
}
}
8)分词查询
GET /dul/_doc/_search
{
"query":{
"match":{"sku_category1_name":"平板手机"}
}
}
结果:
{
"took" : 19,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : 0.970927,
"hits" : [
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.970927,
"_source" : {
"user_id" : "1",
"sku_name" : "我是中国人",
"sku_category1_name" : "小米手机 平板手机",
"dt" : "K"
}
},
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.2876821,
"_source" : {
"user_id" : "2",
"sku_name" : "我是美国人",
"sku_category1_name" : "红米 平板电脑",
"dt" : "K"
}
}
]
}
}
9)短语查询:不使用分词,直接原始数据中匹配(指定顺序)
GET /dul/_doc/_search
{
"query":{
"match_phrase":{"sku_category1_name":"平板电脑"}
}
}
结果:
{
"took" : 6,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.5753642,
"hits" : [
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.5753642,
"_source" : {
"user_id" : "2",
"sku_name" : "我是美国人",
"sku_category1_name" : "红米 平板电脑",
"dt" : "K"
}
}
]
}
}
10)fuzzy查询(对非常接近的词进行查询匹配,消耗性能)
GET /dul/_doc/_search
{
"query":{
"fuzzy":{"sku_category1_name":"rad"}
}
}
11)查询前过滤
Bool Query
filter | 只过滤符合条件的文档,不计算相关性得分,es有缓存功能,提升效率 |
must | 文档必须符合must中的所有条件,会影响相关性得分 |
must_not | 文档中必须不符合must_not中的所有条件 |
should | 文档可以符合should中的条件(一个或多个),会影响相关性得分 |
GET /dul/_doc/_search
{
"query":{
"bool":{
"filter":[{
"term":{
"user_id":2
}
}],
"must":{
"match":{
"sku_category1_name":"平板"
}
}
}
}
}
结果:
{
"took" : 24,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.2876821,
"_source" : {
"user_id" : "2",
"sku_name" : "我是美国人",
"sku_category1_name" : "红米 平板电脑",
"dt" : "K"
}
}
]
}
}
12)按范围过滤
操作符介绍:
gt--------------------------------------大于
lt---------------------------------------小于
gte------------------------------------大于等于
lte-------------------------------------小于等于
GET /dul/_doc/_search
{
"query":{
"bool":{
"filter":{
"range":{
"user_id":{"gte":3}
}
}
}
}
}
结果:
{
"took" : 17,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.0,
"hits" : [
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.0,
"_source" : {
"user_id" : "3",
"sku_name" : "我是法国人",
"sku_category1_name" : "红米 电脑",
"dt" : "K"
}
}
]
}
}
13)排序
GET /dul/_doc/_search
{
"query":{
"match":{
"sku_category1_name":"平板"
}
},
/** "query":{
"match":{
"sku_category1_name":{
"query":"平板电脑",
"operator": "and"//合并(or或者)控制单词间匹配关系(顺序不指定)
}
}
}, **/
"sort":{
"user_id":{
"order":"asc"
}
}
}
结果:
{
"took" : 16,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : null,
"hits" : [
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "1",
"_score" : null,
"_source" : {
"user_id" : "1",
"sku_name" : "我是中国人",
"sku_category1_name" : "小米手机 平板手机",
"dt" : "K"
},
"sort" : [
"1"
]
},
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "2",
"_score" : null,
"_source" : {
"user_id" : "2",
"sku_name" : "我是美国人",
"sku_category1_name" : "红米 平板电脑",
"dt" : "K"
},
"sort" : [
"2"
]
}
]
}
}
14)分页查询
GET /dul/_doc/_search
{
"query":{
"match_all":{
}
},
"from":0,
"size":2
}
结果:
{
"took" : 5,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 1.0,
"hits" : [
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"user_id" : "2",
"sku_name" : "我是美国人",
"sku_category1_name" : "红米 平板电脑",
"dt" : "K"
}
},
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"user_id" : "1",
"sku_name" : "我是中国人",
"sku_category1_name" : "小米手机 平板手机",
"dt" : "K"
}
}
]
}
}
15)查询指定字段
GET /dul/_doc/_search
{
"query":{
"match_all":{
}
},
"from":0,
"size":3,
"sort":{
"user_id":{
"order":"desc"
}
},
"_source":["sku_name","dt"]
}
结果:
{
"took" : 16,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 2,
"max_score" : null,
"hits" : [
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "1",
"_score" : null,
"_source" : {
"user_id" : "1",
"sku_name" : "我是中国人",
"sku_category1_name" : "小米手机 平板手机",
"dt" : "K"
},
"sort" : [
"1"
]
},
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "2",
"_score" : null,
"_source" : {
"user_id" : "2",
"sku_name" : "我是美国人",
"sku_category1_name" : "红米 平板电脑",
"dt" : "K"
},
"sort" : [
"2"
]
}
]
}
}
16)聚合
GET /dul/_doc/_search
{
"aggs":{
"groupby_name":{
"terms":{
"field":"user_id"
}
}
}
}
结果:
{
"took" : 13,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 3,
"max_score" : 1.0,
"hits" : [
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_source" : {
"user_id" : "2",
"sku_name" : "我是美国人",
"sku_category1_name" : "红米 平板电脑",
"dt" : "K"
}
},
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"user_id" : "1",
"sku_name" : "我是中国人",
"sku_category1_name" : "小米手机 平板手机",
"dt" : "K"
}
},
{
"_index" : "dul",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_source" : {
"user_id" : "3",
"sku_name" : "我是法国人",
"sku_category1_name" : "红米 电脑",
"dt" : "K"
}
}
]
},
"aggregations" : {
"groupby_name" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "1",
"doc_count" : 1
},
{
"key" : "2",
"doc_count" : 1
},
{
"key" : "3",
"doc_count" : 1
}
]
}
}
}
17)检索多个文档(检索确定的字段,也可以定义一个_source参数)
POST /_mget
{
"docs" : [
{
"_index" : "website",
"_type" : "blog",
"_id" : 2
},
{
"_index" : "website",
"_type" : "pageviews",
"_id" : 1,
"_source": "views"
}
]
}
结果:
{
"docs" : [
{
"_index" : "website",
"_id" : "2",
"_type" : "blog",
"found" : true,
"_source" : {
"text" : "This is a piece of cake...",
"title" : "My first external blog entry"
},
"_version" : 10
},
{
"_index" : "website",
"_id" : "1",
"_type" : "pageviews",
"found" : true,
"_version" : 2,
"_source" : {
"views" : 2
}
}
]
}
13.es写数据过程(写入primary shard,然后同步给所有的replca shard)
客户端选择一个node发送请求过去,这个node就是协调节点,协调节点对document进行路由,将请求发送给对用的node(有primary shard),实际的node上的primary shard 处理请求,将数据同步到replica node,协调节点如果发现primary node和所有的replica node 都搞定之后,就返回结果给客户端。
底层原理:先写入内存buffer,在buffer里的时候数据是搜索不到的,同时将数据写入到translog日志文件,如果buffer快满了,或者到一定时间,就会将内存buffer数据refresh到一个新的segment file中,但是此时数据不是直接进入segment file磁盘文件,而是进入os cache,这个过程就是refresh。每隔一秒钟,es将buffer中的数据写入一个新的segment file,每秒钟会产生一个新的磁盘文件segment file,这个segment file 中就存储最近1秒内buffer中写入的数据。但是如果buffer里面此时没有数据,那当然就不会执行refresh操作,如果buffer里面有数据,默认1秒钟执行一次refresh操作,刷入一个新的segment file中。
操作系统里面,磁盘文件其实都有一个东西叫做os cache,即操作系统缓存,就是说数据写入磁盘空间之前会先进入os cache,先进入操作系统级别的一个内存缓存中去,只要buffer中的数据被refresh操作刷入os cache中,这个数据就可以被搜索到。
es准实时:默认是一秒种refresh一次的,所以es是准实时的,因为写入到数据1秒之后才能被看到。可以通过es的restful api或者java api,手动执行一次refresh操作,就是手动将buffer中的数据刷入到os cache中,让数据立马就可以被搜索到,只要数据被输入到os cache中,buffer就会被清空了,因为不需要保留buffer了,数据在translog里面已经持久化到磁盘去一份了。
上述步骤不断重复,新的数据不断进入buffer和translog,不断将buffer数据写入一个有一个新的segment file中去,每次refresh完buffer清空,translog保留。随着这个进程的不断推进,translog会变得越来越大。当translog达到一定长度的时候就会触发commit操作。
commit操作发生的第一步,就是将buffer中现有的数据refresh到os cache,清空buffer。然后,将一个commit point写入磁盘文件,里面标识着这个commit point 对应的所有的segment file,同时强行将os cache中目前所有的数据都fsync(同步内存中所有已修改文件数据到存储设备)到磁盘文件中去。最后清空现有的translog日志文件,重启一个translog,此时commit操作完成。
这个commit操作默认叫做flush。默认30分钟自动执行一次flush,但如果translog过大,也会触发flush。flush操作对应着commit的全过程,我们可以通过es api,手动执行flush操作,手动将os cache中的数据fsync强刷到磁盘上去。
translog日志作用:执行commit操作之前,数据要么停留在buffer中,要么停留在os cache中,无论是buffer还是os cache都是内存,一旦这机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件translog中,一旦此时机器宕机,再次重启时,es会自动读取translog日志文件中的数据,恢复到内存buffer和os cache中去。
translog其实也是先写入os cache的,默认每隔5秒刷一次到磁盘中去,所以默认情况下可能有5秒的数据仅仅停留在buffer或者translog文件的os cache中,如果此时机器挂了,会丢失5秒的数据。
总结:数据先写入内存buffer中,然后每隔一秒,将数据refresh到os cache,到了os cache数据就能被搜索到(所以说es从写入到能搜索到中间会有一秒的延迟)。每隔5秒,将数据写入到translog文件(如果此时宕机,内存数据全没了,最多会有5秒的数据丢失),translog大到一定程度,或者默认每隔30mins,会触发commit操作,将缓冲区的数据flush到segment file磁盘文件中。
数据写入到segment file之后,同时就建立好了倒排索引。
删除底层原理:commit的时候会生成一个.del文件,里面将某个doc标识为deleted状态,那么搜索的时候根据.del文件就知道这个doc是否被删除了。
更新:就是将原来的doc标识为deleted状态,然后重新写入一条数据。
buffer每refresh一次,就会产生一个segment file,所以默认情况下是1秒钟一个segment file,这样下来segment file会越来越多,此时会定期执行merge。每次merge的时候,会将多个segment file合并成一个,同时这里会将标识为deleted的doc给物理删除掉,然后新的segment file写入磁盘,这里会写一个commit point,标识所有新的segment file,然后打开segment file供搜索使用,同时删除旧的segment file。
14.es读数据过程(从primary shard或replica shard读取,采用的是随机轮询算法)
客户端发送请求到任意一个node,成为协调节点。协调节点对doc id 进行哈希路由,将请求发送给对应的node,此时会使用round-robin随机轮询算法,在primary shard以及其所有replica中随机选择一个,让读请求负载均衡。接收请求的node返回document给协调节点。协调节点返回document给客户端
15.es搜索数据过程
客户端发送请求到一个协调节点,协调节点将搜索请求转发到所有的shard对应的primary shard 或 replica shard
query phase(查询阶段):每个shard将自己的搜索结果(doc id)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果;
fetch phase(获取阶段):接着由协调节点根据doc id 去各个节点上拉取实际的document数据,最终返回给客户端。
16.jest
util类
object EsUtil {
private val ES_HOST ="http://kqkj101"
private val ES_HTTP_PORT = 9200
private var factory:JestClientFactory = null
/**
* 获取客户端
*/
def getClient = {
if(factory == null) build()
factory.getObject
}
/**
*关闭客户端
*/
def close(client : JestClient): Unit ={
if(!Objects.isNull(client))
try{
client.shutdownClient()
} catch{
case e:Exception => e.printStackTrace()
}
}
/**
* 建立连接
*/
private def build(): Unit ={
factory = new JestClientFactory
factory.setHttpClientConfig(new HttpClientConfig.Builder(ES_HOST+":"+ES_HTTP_PORT).multiThreaded(true)
.maxTotalConnection(20)//连接总数
.connTimeout(10000).readTimeout(10000).build)
}
/**
* 批量
*/
def excuteIndexBulk(indexName:String,list: List[Any]): Unit ={
val jest = getClient
val bulkBuilder = new Bulk.Builder().defaultIndex(indexName).defaultType("_doc")
for (doc <- list){
val index = new Index.Builder(doc).build()
bulkBuilder.addAction(index)
}
val items = jest.execute(bulkBuilder.build()).getItems
println(s"保存=${items.size()}")
close(jest)
}
}
例子
1.@Override
public Integer getDauTotal(String date) {
//方式一
/*String query = "{\n" +
" \"query\" : {\n" +
" \"bool\" : {\n" +
" \"filter\" : {\n" +
" \"term\" : {\n" +
" \"logDate\" : \"2020-05-02\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }";*/
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.filter(new TermQueryBuilder("logDate",date));
searchSourceBuilder.query(boolQueryBuilder);
String query = searchSourceBuilder.toString();
Search search = new Search.Builder(query).addIndex(GmallConstant.ES_INDEX_DAU).addType("_doc").build();
Integer total = 0;
try {
SearchResult searchResult = jestClient.execute(search);
total = searchResult.getTotal();
} catch (IOException e) {
e.printStackTrace();
}
return total;
}
2. @Override
public Double getOrderAmout(String date) {
String query = "{\n" +
" \"query\": {\n" +
" \"bool\": {\n" +
" \"filter\": {\n" +
" \"term\": {\n" +
" \"createDate\": \"2019-02-13\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }, \n" +
" \"aggs\": {\n" +
" \"sum_totalAmount\": {\n" +
" \"sum\": {\n" +
" \"field\": \"totalAmount\"\n" +
" }\n" +
" }\n" +
"}";
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.filter(new TermQueryBuilder("createDate",date));
SumBuilder aggsBuilder = AggregationBuilders.sum("sum_totalAmount").field("totalAmount");
searchSourceBuilder.query(boolQueryBuilder).aggregation(aggsBuilder);
Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex(GmallConstant.ES_INDEX_ORDER).addType("_doc").build();
Double sum_totalAmount = 0D;
try {
SearchResult searchResult = jestClient.execute(search);
sum_totalAmount = searchResult.getAggregations().getSumAggregation("sum_totalAmount").getSum();
} catch (IOException e) {
e.printStackTrace();
}
return sum_totalAmount;
}
3. @Override
public Map getOrderAmoutHourMap(String date) {
String query = "{\n" +
" \"query\": {\n" +
" \"bool\": {\n" +
" \"filter\": {\n" +
" \"term\": {\n" +
" \"createDate\": \"2019-02-13\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }, \n" +
" \n" +
" \"aggs\": {\n" +
" \"tm\": {\n" +
" \"terms\": {\n" +
" \"field\": \"createHour\"\n" +
" }\n" +
" , \"aggs\": {\n" +
" \"sum_totalAmount\": {\n" +
" \"sum\": {\n" +
" \"field\": \"totalAmount\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
//过滤
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
boolQueryBuilder.filter(new TermQueryBuilder("createDate",date));
//聚合
TermsBuilder termsBuilder = AggregationBuilders.terms("groupby_createHour").field("createHour").size(24);
SumBuilder sumBuilder = AggregationBuilders.sum("sum_totalAmount").field("totalAmount");
//自聚合
termsBuilder.subAggregation(sumBuilder);
searchSourceBuilder.query(boolQueryBuilder).aggregation(termsBuilder);
Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex(GmallConstant.ES_INDEX_ORDER).addType("_doc").build();
Map<String,Double> hourMap = new HashMap<>();
try {
SearchResult searchResult = jestClient.execute(search);
List<TermsAggregation.Entry> buckets = searchResult.getAggregations().getTermsAggregation("groupby_createHour").getBuckets();
for (TermsAggregation.Entry bucket : buckets){
Double hourAmount = bucket.getSumAggregation("sum_totalAmount").getSum();
String hourkey = bucket.getKey();
hourMap.put(hourkey, hourAmount);
}
} catch (IOException e) {
e.printStackTrace();
}
return hourMap;
}
3. @Override
public Map getSaleDetailMap(String date, String keyword, int pageNo, int pageSize, String aggsFieldName, int aggsSize) {
String query = "{\n" +
" \"query\": {\n" +
" \"bool\": {\n" +
" \"filter\": {\n" +
" \"term\": {\n" +
" \"dt\": \"2019-02-14\"\n" +
" }\n" +
" }, \n" +
" \"must\": [\n" +
" {\"match\":{\n" +
" \"sku_name\": {\n" +
" \"query\": \"小米手机\",\n" +
" \"operator\": \"and\"\n" +
" }\n" +
" } \n" +
" \n" +
" }\n" +
" ] \n" +
" }\n" +
" }\n" +
" , \"aggs\": {\n" +
" \"groupby_age\": {\n" +
" \"terms\": {\n" +
" \"field\": \"user_age\" \n" +
" }\n" +
" }\n" +
" }\n" +
" ,\n" +
" \"size\": 2\n" +
" , \"from\": 0\n" +
"}";
Integer total = 0;
List<Map> aggsList = new ArrayList<>();
Map aggsMap = new HashMap();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
//过滤
boolQueryBuilder.filter(new TermQueryBuilder("dt",date));
//全文匹配
boolQueryBuilder.must(new MatchQueryBuilder("sku_name",keyword).operator(MatchQueryBuilder.Operator.AND));
searchSourceBuilder.query(boolQueryBuilder);
//聚合
TermsBuilder termsBuilder = AggregationBuilders.terms("groupby_" + aggsFieldName).field(aggsFieldName).size(aggsSize);
searchSourceBuilder.aggregation(termsBuilder);
//分页
searchSourceBuilder.from(pageNo-1);
searchSourceBuilder.size(pageSize);
Search search = new Search.Builder(searchSourceBuilder.toString()).addIndex(GmallConstant.ES_INDEX_SALE).addType("_doc").build();
try {
SearchResult searchResult = jestClient.execute(search);
total = searchResult.getTotal();
List<SearchResult.Hit<Map, Void>> hits = searchResult.getHits(Map.class);
for (SearchResult.Hit hit : hits){
aggsList.add((Map) hit.source);
}
//去聚合结果
List<TermsAggregation.Entry> buckets = searchResult.getAggregations().getTermsAggregation("groupby_" + aggsFieldName).getBuckets();
for (TermsAggregation.Entry bucket : buckets) {
aggsMap.put(bucket.getKey(),bucket.getCount());
}
} catch (IOException e) {
e.printStackTrace();
}
Map saleMap = new HashMap();
saleMap.put("total",total);
saleMap.put("detail",aggsList);
saleMap.put("aggsMap",aggsMap);
return saleMap;
}