目录
ElasticSearch 是如何 分散读写压力 和 实现高可用的?
在elasticSearch中添加文档的过程描述(保证数据不丢失):
Elasticsearch 文档更新和删除过程描述(文档是final):
甚麽是elastic Search ?
是一个开源的分布式存储文档和搜索引擎,是一个用java编写的全文搜索引擎。
特点:
1.基本上能够实现近实时搜索;(数据发生改变的时候,ES基本上是能够马上发现);
2.分布式:(无需人工搭建集群,solr 需要使用Zookeeper 注册中心来搭建集群)
3.所有的API都遵循Rest风格;
搜索引擎的大体上的实现方式:
答: 以古诗为例,当我们想搜索包含 “月” 字的古诗的时候,要想快速的找到这个字,就得建立索引,称为反向索引, 每一个字对应一个句古诗这个太浪费空间了,这样就可以以整个古诗的名字为目标,这样的话,就可以建立比较节省空间的索引。
搜索引擎的三大步骤:
- 获取内容 ;
- 进行分词:对获取的内容进行拆分,对这些词建立索引 --->以古诗的名字为目标;
- 建立索引:
基本名词:
- 索引: ----》 相当于 mysql 中的数据库
- 类型: ----》 相当于 mysql 中的table表
- 文档: ----》 相当于 表中的一行数据
描述一首古诗放置再elasticSearch:
问题一:keyword 和 text 类型 都是表示字符串类型,他们之间的区别?
答:
keyword类型的字符串是不会分词的,直接根据完整的字符串内容建立索引;
Text 类型在存入的时候要先进行分词,然后根据分词的内容建立反向索引;
ElasticSearch 是如何 分散读写压力 和 实现高可用的?
分散读写压力: 数据分片;
高可用: 建立集群:master--slave 模式 :
注意:
只有建立索引和类型才会经过master ,数据的写入,读取,有一个简单的读取规则,可以route到集群的任何一个节点;所以数据写入的压力是分散到这个集群的;
cluter:
上图中的节点是由一个主节点和五个从节点组成。而且可以看出进行了数据分片, 每个数据片(又可以分为primary shard 和 replicate shard )又遍布在不同的节点的;
每个节点,在配置文件中指定他所属的集群,以及他是甚麽类型的节点;
ElasticSearch 的三种节点以及特点:
主节点 (分发管理分片 ;索引以及类型的创建):
建立集群后elasticSearch会在所有的节点中选取一个节点作为主节点,
主节点的功能是: 协调集群中的任务,如: 跨节点分发分片,同步相关节点的索引以及类型的创建以及删除主节点负责ping 所有其他节点,判断是否有节点已经挂掉;
在较大的群集中,用户可以启动不存储任何数据的专用主节点(通过将node.data:false添加到配置文件中),以提高可靠性。在高使用率环境中,将主角色从数据节点移开有助于确保始终有足够的资源分配给只有符合主节点的节点才能处理的任务。
从节点: (执行搜索)
每个数据节点都是以分片的形式存储数据的;并执行与搜索索引聚合数据相关的操作。
客户端节点(路由节点,负载均衡器):
如果将node.master和node.data设置为false,则最终会得到一个客户端节点,该节点旨在充当负载均衡器,以帮助路由索引和搜索请求。客户端节点有助于承担一些搜索工作负载,以便数据和符合条件的节点可以专注于其核心任务。
根据您的使用情况,客户端节点可能不是必需的,因为数据节点能够自己处理请求路由。但是,如果您的搜索/索引工作负载足够大,可以通过使用专用客户端节点来帮助路由请求,那么将客户端节点添加到群集是有意义的。
索引存储在一个或多个主分片和零个或多个副本分片上,每个分片都是Lucene的完整实例,就像迷你搜索引擎一样。
创建索引时,可以指定主分片的数量,以及每个主分片的副本数。 默认值为每个索引五个主分片,每个主分片一个副本。 创建索引后,无法更改主分片的数量,因此请谨慎选择,否则稍后可能需要重新编制索引。 可以根据需要稍后更新副本的数量。 为了防止数据丢失,主节点确保每个副本分片不会分配到与其主分片相同的节点。
一个搜索请求是怎样运行的?
- 客户端向节点二发送搜索请求:
- 节点二此时为协调节点,将查询的请求发送到每个分片的副本或者主副本;
3. 每个分片在本地执行查询搜索并构建一个匹配文档的大小为 from + size 的优先队列。PS:在搜索的时候是会查询Filesystem Cache的,但是有部分数据还在Memory Buffer,所以搜索是近实时的。每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点.节点2合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
4.节点2整合选取需要的信息;节点2找出需要提取的文档,并向相关分片发送多GET请求, 取回阶段,协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每个分片加载并丰富文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了,协调节点返回结果给客户端
5. 每个分片加载文档并将它们返回到节点2
6.节点2将搜索结果传递给客户端。
问题来了:
ElasticSearch是如何实现Master选取的?
Elasticsearch的选主是ZenDiscovery模块负责的,主要包含Ping(节点之间通过这个RPC来发现彼此)和Unicast(单播模块包含一个主机列表以控制哪些节点需要ping通)这两部分;
对所有可以成为master的节点(node.master: true)根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个(第0位)节点,暂且认为它是master节点
如果对某个节点的投票数达到一定的值(可以成为master节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。
master节点的职责主要包括集群、节点和索引的管理,不负责文档级别的管理;
elasticsearch如何避免脑裂问题?
当集群中master候选的个数不小于3个(node.master:
true)。可以通过discovery.zen.minimum_master_nodes
这个参数的设置来避免脑裂,设置为(N/2)+1。
这里node.master : true 是说明你是有资格成为master,并不是指你就是master。是皇子,不是皇帝。假如有10个皇子,这里应该设置为(10/2)+1=6,这6个皇子合谋做决策,选出新的皇帝。另外的4个皇子,即使他们全聚一起也才四个人,不足合谋的最低人数限制,他们不能选出新皇帝。
假如discovery.zen.minimum_master_nodes 设置的个数为5,有恰好有10个master备选节点,会出现什么情况呢?5个皇子组成一波,选一个皇帝出来,另外5个皇子也够了人数限制,他们也能选出一个皇帝来。此时一个天下两个皇帝,在es中就是脑裂。
假如集群master候选节点为2的时候,这种情况是不合理的,最好把另外一个node.master改成false。如果我们不改节点设置,还是套上面的(N/2)+1公式,此时discovery.zen.minimum_master_nodes应该设置为2。这就出现一个问题,两个master备选节点,只要有一个挂,就选不出master了。
我还是用皇子的例子来说明。假如先皇在位的时候规定,必须他的两个皇子都在的时候,才能从中2选1 继承皇位。万一有个皇子出意外挂掉了,就剩下一个皇子,天下不就没有新皇帝了么。
客户端在和集群进行链接的时候,如何选择特定的节点执行请求?
TransportClient利用transport模块远程连接一个elasticsearch集群。它并不加入到集群中,只是简单的获得一个或者多个初始化的transport地址,并以 轮询 的方式与这些地址进行通信。
Lucene 中的段的概念:
- 索引是由多个段组成的,段本身是一个功能齐全的倒排索引。
- 段属于不可变的,不可用于更改,只能创建新的,丢失旧的。
- 对于一个搜索请求而言,索引中所有的段都会被搜索,所以要尽量减少段的数量。
- ElasticSearch会合并小段到一个大段,提交新的合并到磁盘,并删除那些旧的小段;
在elasticSearch中添加文档的过程描述(保证数据不丢失):
协调节点--》发起增添文档的请求--》分片节点
分片节点---收到请求---》Memory Buffer -------定时(refresh)写入----》FileSystem Cache ----->写入到磁盘
为了防止在内存中的缓存出现丢失的情况,(Memory Buffer 和 FileSystem Cache )ES是通过translog的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到translog中,当Filesystem cache中的数据写入到磁盘中时,才会清除掉,这个过程叫做flush,flush触发的时机是定时触发(默认30分钟)或者translog变得太大(默认为512M)时。
Elasticsearch 文档更新和删除过程描述(文档是final):
1.删除和更新也都是写操作,但是Elasticsearch中的文档是不可变的,因此不能被删除或者改动以展示其变更;
2.磁盘上的每个段都有一个相应的.del文件。当删除请求发送后,文档并没有真的被删除,而是在.del文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del文件中被标记为删除的文档将不会被写入新段。
3.在新的文档被创建时,Elasticsearch会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉。
在并发情况下,Elasticsearch如果保证读写一致?
写一致性:
1.可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;
2.另外对于写操作,一致性级别支持quorum/one/all,默认为quorum,即只有当大多数分片可用时才允许写操作。但即使大多数可用,也可能存在因为网络等原因导致写入副本失败,这样该副本被认为故障,分片将会在一个不同的节点上重建。
put /index/type/id?consistency=quorum
one:要求我们这个写操作,只要有一个primary shard是active活跃可用的,就可以执行
all:要求我们这个写操作,必须所有的primary shard和replica shard都是活跃的,才可以执行这个写操作
quorum:默认的值,要求所有的shard中,必须是大部分的shard都是活跃的,可用的,才可以执行这个写操作
quorum机制
写之前必须确保大多数shard都可用,int( (primary + number_of_replicas) / 2 ) + 1,当number_of_replicas>1时才生效,number_of_replicas是在索引中的的设置,用来定义复制分片的数量,如果只有一台机器(没有备机),那么就为0。
timeout
quorum不齐全时,会wait(等待),默认1分钟,可以设置timeout手动去调,默认单位毫秒。
等待期间,期望活跃的shard数量可以增加,最后实在不行,就会timeout,我们其实可以在写操作的时候,加一个timeout参数,比如说
PUT /index/type/id?timeout=30s
读一致性:
对于读操作,
1.设置replication为sync(默认),这使得写操作在主分片和副本分片都完成后才会返回;
2.如果设置replication为async时,通过设置搜索请求参数_preference为primary来查询主分片,确保文档是最新版本
ES如何来实现快速近实时搜索?
一个Index由若干段组成,搜索的时候按段搜索,我们索引一条段后,每个段会通过fsync 操作持久化到磁盘,而fsync 操作比较耗时,如果每索引一条数据都做这个full commit(rsync)操作,提交和查询的时延都非常之大,所以在这种情况下做不到实时的一个搜索。
FileSystem Cache 与 refresh
针对这个问题的解决是在Elasticsearch和磁盘之间引入一层称为FileSystem Cache的系统缓存,正是由于这层cache的存在才使得es能够拥有更快搜索响应能力。
新的文档数据写入缓存区
缓存区的数据写入一个新段
es中新增的document会被收集到indexing buffer区后被重写成一个segment然后直接写入filesystem cache中,这个操作是非常轻量级的,相对耗时较少,之后经过一定的间隔或外部触发后才会被flush到磁盘上,这个操作非常耗时。但只要sengment文件被写入cache后,这个sengment就可以打开和查询,从而确保在短时间内就可以搜到,而不用执行一个full commit也就是fsync操作,这是一个非常轻量级的处理方式而且是可以高频次的被执行,而不会破坏es的性能。
在elasticsearch里面,这个轻量级的写入和打开一个cache中的segment的操作叫做refresh,默认情况下,es集群中的每个shard会每隔1秒自动refresh一次,这就是我们为什么说es是近实时的搜索引擎而不是实时的,也就是说给索引插入一条数据后,我们需要等待1秒才能被搜到这条数据,这是es对写入和查询一个平衡的设置方式,这样设置既提升了es的索引写入效率同时也使得es能够近实时检索数据
未完待续:
语法的使用:
1.索引库的增删查:
索引库:
创建索引:
PUT heima
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
删除索引: DELETE 索引库名;
查看索引: GET /索引库名
Type(table) 的增删查:
在索引库中创建type(也就是类型):相当于数据库的表结构:
PUT /索引库名/_mapping/类型名称
{
"properties": {
"字段名": {
"type": "类型", // 字段的类型: text,object,integer等;
"index": true,//是否进行索引
"store": true,//这里是已经存储了一份,是否需要再进行存储,
默认为 false;
"analyzer": "分词器"
}
}
}
例如在heima这个库中创建一个goods的商品表;
{
"heima": {
"mappings": {
"goods": {
"properties": {
"images": {
"type": "keyword",
"index": false
},
"price": {
"type": "float"
},
"title": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
}
}
(post)向数据库表中增添数据:
POST /heima/goods/
{
"title":"小米手机",
"images":"http://image.leyou.com/12479122.jpg",
"price":2699.00
}
重要的查询语句:
1.查询整个数据库索引:
向查询一个数据库中所有的数据:
GET /heima/_search
{
"query":{
"match_all": {}
}
}
查询的基本语法: GET /索引库名/_search { "query":{ "查询类型":{ "查询条件":"查询条件值" } } }
对查询后返回的数据的解析:
{ "took": 2, //查询所花费的时间 "timed_out": false, //查询是否超时 "_shards": { //分片信息 "total": 3, "successful": 3, "skipped": 0, "failed": 0 }, "hits": { //查询结果: "total": 2, //查询的条数 "max_score": 1, //文档得分的最高分; "hits": [ { "_index": "heima", //查询的每条文档的具体详细信息,归属以及数据 "_type": "goods", "_id": "2", "_score": 1, "_source": { "title": "大米手机", "images": "http://image.leyou.com/12479122.jpg", "price": 2899 } }, { "_index": "heima", "_type": "goods", "_id": "r9c1KGMBIhaxtY5rlRKv", "_score": 1, "_source": { "title": "小米手机", "images": "http://image.leyou.com/12479122.jpg", "price": 2699 } } ] } }
查询的语法:
- 分词查询:
`match`类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是or的关系
```json
GET /heima/_search
{
"query":{
"match":{
"title":"小米电视"
}
}
}
```
- 精确查询,,要求包含整个词的相当于 分词后 and :
```json
GET /heima/_search
{
"query":{
"match": {
"title": {
"query": "小米电视",
"operator": "and"
}
}
}
}
```
在这个查询中,只有包含 小米和电视 的才能被查询到;
应用场景: 如果用户给了一个查询 key ,被分词 为 5个词,想查询只包含其中4个词的查询:
`match` 查询支持 `minimum_should_match` 最小匹配参数, 这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。我们可以将其设置为某个具体数字,更常用的做法是将其设置为一个`百分数`,因为我们无法控制用户搜索时输入的单词数量:
```json
GET /heima/_search
{
"query":{
"match":{
"title":{
"query":"小米曲面电视",
"minimum_should_match": "75%"
}
}
}
}
```
本例中,搜索语句可以分为3个词,如果使用and关系,需要同时满足3个词才会被搜索到。这里我们采用最小品牌数:75%,那么也就是说只要匹配到总词条数量的75%即可,这里3*75% 约等于2。所以只要包含2个词条就算满足条件了。
应用场景: 如果我们想在name和title两个字段中查找 包含 “小米” 的匹配项:
```json
GET /heima/_search
{
"query":{
"multi_match": {
"query": "小米",
"fields": [ "title", "subTitle" ]
}
}
}
```
本例中,我们会在title字段和subtitle字段中查询`小米`这个词
精确值匹配:一点也不差的那种:这里用到了term: 精确匹配;
```json
GET /heima/_search
{
"query":{
"term":{
"price":2699.00
}
}
}
``
精确匹配,一次性多个值进行匹配查找:
```json
GET /heima/_search
{
"query":{
"terms":{
"price":[2699.00,2899.00,3899.00]
}
}
}
```
设定返回的字段 :
include : 设置那些字段显示;
Exclude: 设置那些字段不能显示;
```json
GET /heima/_search
{
"_source": {
"includes":["title","price"]
},
"query": {
"term": {
"price": 2699
}
}
}
```
高级查询:
布尔组合(bool)
`bool`把各种其它查询通过`must`(与)、`must_not`(非)、`should`(或)的方式进行组合
```json
GET /heima/_search
{
"query":{
"bool":{
"must": { "match": { "title": "大米" }},
"must_not": { "match": { "title": "电视" }},
"should": { "match": { "title": "手机" }}
}
}
}
```
范围查询: 精确值字段的范围查询:
`range` 查询找出那些落在指定区间内的数字或者时间
```json
GET /heima/_search
{
"query":{
"range": {
"price": {
"gte": 1000.0,
"lt": 2800.00
}
}
}
}
``
`range`查询允许以下字符:
| 操作符 | 说明 |
| :----: | :------: |
| gt | 大于 |
| gte | 大于等于 |
| lt | 小于 |
| lte | 小于等于 |
模糊查询:
我们可以通过`fuzziness`来指定允许的编辑距离:
```json
GET /heima/_search
{
"query": {
"fuzzy": {
"title": {
"value":"appla",
"fuzziness":1
}
}
}
}
```
## 3.4 过滤(filter)
> **条件查询中进行过滤**
所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用`filter`方式:
```json
GET /heima/_search
{
"query":{
"bool":{
"must":{ "match": { "title": "小米手机" }},
"filter":{
"range":{"price":{"gt":2000.00,"lt":3800.00}}
}
}
}
}
```
注意:`filter`中还可以再次进行`bool`组合条件过滤。
应用场景Java CLIENT API查询: