0


一些中间件ES、RabbitMq

一、Elasticsearch-全文检索

1.1 ElasticSearch介绍

1.1.1 ElasticSearch概述

​ Elasticsearch (简称ES)是一个分布式、高扩展、高实时的、RESTful 风格的搜索与数据分析引擎。它能很方便的使大量数据具有搜索、分析和探索的能力。充分利用Elasticsearch的水平伸缩性,能使数据在生产环境变得更有价值。 Elasticsearch 的实现原理主要分为以下几个步骤,首先用户将数据提交到Elasticsearch 数据库中再通过分词控制器去将对应的语句分词,将其权重和分词结果一并存入数据当用户搜索数据时候,再根据权重将结果排名,打分,再将返回结果呈现给用户

​ Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档相当于Mysql中的行),用JSON作为文档序列化的格式,比如下面这条用户数据:

{"name":"John","sex":"Male","age":25,"birthDate":"1990/05/01","about":"I love to go rock climbing","interests":["sports","music"]}

用Mysql这样的数据库存储就会容易想到建立一张User表,有各个字段等,在ElasticSearch里这就是一个文档,当然这个文档会属于一个User的类型,各种各样的类型存在于一个索引当中。这里有一份简易的将Elasticsearch和关系型数据术语对照表:

在这里插入图片描述

1.1.2 索引(Index)

​ 一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母),并且当我们要对这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。

​ 能搜索的数据必须索引,这样的好处是可以提高查询速度,比如:新华字典前面的目录就是索引的意思,目录可以提高查询速度。

Elasticsearch索引的精髓:一切设计都是为了提高搜索的性能。

1.1.3 类型(Type)

​ 在一个索引中,你可以定义一种或多种类型。

​ 一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。不同的版本,类型发生了不同的变化
版本Type5.x支持多种type6.x只能有一种type7.x****默认不再支持自定义索引类型(默认类型为:_doc)

1.1.4 文档(Document)

​ 一个文档是一个可被索引的基础信息单元,也就是一条数据

​ 比如:你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。

​ 在一个index/type里面,你可以存储任意多的文档。

1.1.5 字段(Field)

​ 相当于是数据表的字段,对文档数据根据不同属性进行的分类标识。

1.1.6 映射(Mapping)

​ mapping是处理数据的方式和规则方面做一些限制,如:某个字段的数据类型(text、keyword、long、integer、short、byte、double、float、boolean、date、object)、默认值、分析器(只有数据类型是text可以被分词)、是否被索引(有些字段比如邮箱就没有参加索引的必要)等等。这些都是映射里面可以设置的,其它就是处理ES里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。

1.1.7 IK分词器简介

​ IKAnalyzer是一个开源的,基于Java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出 了3个大版本。最初,它是以开源项目Lucene为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。

IK分词器3.0的特性如下:

1)采用了特有的“正向迭代最细粒度切分算法“,具有60万字/秒的高速处理能力。

2)采用了多子处理器分析模式,支持:英文字母(IP地址、Email、URL)、数字(日期,常用中文数量词,罗马数字,科学计数法),中文词汇(姓名、地名处理)等分词处理。

3)对中英联合支持不是很好,在这方面的处理比较麻烦.需再做一次查询,同时是支持个人词条的优化的词典存储,更小的内存占用。

4)支持用户词典扩展定义。

5)针对Lucene全文检索优化的查询分析器IKQueryParser;采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高Lucene检索的命中率。

IK分词器有两个模式:

l ik_max_word:会将文本做最细粒度的拆分

ik_smart:会做最粗粒度的拆分,智能拆分

1.1.8 分片&副本

什么是 Shard (分片)?

​ 一个 索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有 10亿文档的索引占据 1TB 的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。

​ 为了解决这个问题,Elasticsearch 提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的索引,这个索引可以被放置到集群中的任何节点上。

​ 分片之所以重要,主要有两方面的原因:

允许你水平分割/扩展你的内容容量
允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量
一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由 Elasticsearch 管理的,对于作为用户的你来说,这些都是透明的
什么是 Replica (副本)?

副本是一个分片的精确复制,每个分片可以有零个或多个副本。副本的作用:

​ 提高系统的容错性,当某个节点某个分片损坏或丢失时,可以从副本中恢复。
​ 提高 ES 查询效率,ES 会自动对搜索请求进行负载均衡。

1.2、ElasticSearch 读写原理

1.2.1、 写操作(Write):针对文档的 CRUD 操作

索引新文档(Create)

​ 当用户向一个节点提交了一个索引新文档的请求,节点会计算新文档应该加入到哪个分片(shard)中。每个节点都有每个分片存储在哪个节点的信息,因此协调节点会将请求发送给对应的节点。注意这个请求会发送给主分片,等主分片完成索引,会并行将请求发送到其所有副本分片,保证每个分片都持有最新数据。

​ 每次写入新文档时,都会先写入内存中,并将这一操作写入一个 translog 文件(transaction log)中,此时如果执行搜索操作,这个新文档还不能被索引到

img

​ ES 会每隔 1 秒时间(这个时间可以修改)进行一次刷新操作(refresh),此时在这 1 秒时间内写入内存的新文档都会被写入一个文件系统缓存(filesystem cache)中,并构成一个分段(segment)。此时这个 segment 里的文档可以被搜索到,但是尚未写入硬盘,即如果此时发生断电,则这些文档可能会丢失。

img

​ 不断有新的文档写入,则这一过程将不断重复执行。每隔一秒将生成一个新的 segment,而 translog 文件将越来越大。

img

​ 每隔 30 分钟或者 translog 文件变得很大,则执行一次 fsync 操作。此时所有在文件系统缓存中的 segment 将被写入磁盘,而 translog 将被删除(此后会生成新的 translog)

img

​ 由上面的流程可以看出,在两次 fsync 操作之间,存储在内存和文件系统缓存中的文档是不安全的,一旦出现断电这些文档就会丢失。所以 ES 引入了 translog 来记录两次 fsync 之间所有的操作,这样机器从故障中恢复或者重新启动,ES 便可以根据 translog 进行还原。

​ 当然,translog 本身也是文件,存在于内存当中,如果发生断电一样会丢失。因此,ES 会在每隔 5 秒时间或是一次写入请求完成后将 translog 写入磁盘。可以认为一个对文档的操作一旦写入磁盘便是安全的可以复原的,因此只有在当前操作记录被写入磁盘,ES 才会将操作成功的结果返回发送此操作请求的客户端。

​ 此外,由于每一秒就会生成一个新的 segment,很快将会有大量的 segment。对于一个分片进行查询请求,将会轮流查询分片中的所有 segment,这将降低搜索效率。因此 ES 会自动启动合并 segment 的工作,将一部分相似大小的 segment合并成一个新的大 segment。合并的过程实际上是创建了一个新的 segment,当新 segment 被写入磁盘,所有被合并的旧 segment 被清除。

img

img

1.2.2、 更新(Update)和删除(Delete)文档

ES 的索引是不能修改的,因此更新和 删除 操作并不是直接在原索引上直接执行。

​ 每一个磁盘上的 segment 都会维护一个 del 文件,用来记录被删除的文件。每当用户提出一个删除请求,文档并没有被真正删除,索引也没有发生改变,而是在 del 文件中标记该文档已被删除。因此被删除的文档依然可以被检索到,只是在返回检索结果时被过滤掉了。每次在启动 segment 合并工作时,那些被标记为删除的文档才会被真正删除。

​ 更新文档会首先查找原文档,得到该文档的版本号。然后将修改后的文档写入内存,此过程与写入一个新文档相同。同时,旧版本文档被标记为删除,同理,该文档可以被搜索到,只是最终被过滤掉。

1.2.3、读操作(Read):查询过程

​ 查询的过程大体上分为查询(query)和取回(fetch)两个阶段。这个节点的任务是广播查询请求到所有相关分片,并将它们的响应整合成全局排序后的结果集合,这个结果集合会返回给客户端。

查询阶段

当一个节点接收到一个搜索请求,则这个节点就变成了协调节点。

​ 第一步是广播请求到索引中每一个节点的分片拷贝。 查询请求可以被某个主分片或某个副本分片处理,协调节点将在之后的请求中轮询所有的分片拷贝来分摊负载。

​ 每个分片将会在本地构建一个优先级队列。如果客户端要求返回结果排序中从第 from 名开始的数量为 size 的结果集,则每个节点都需要生成一个 from+size 大小的结果集,因此优先级队列的大小也是 from+size。分片仅会返回一个轻量级的结果给协调节点,包含结果集中的每一个文档的 ID 和进行排序所需要的信息。

协调节点会将所有分片的结果汇总,并进行全局排序,得到最终的查询排序结果。此时查询阶段结束。

取回阶段

​ 查询过程得到的是一个排序结果,标记出哪些文档是符合搜索要求的,此时仍然需要获取这些文档返回客户端。

​ 协调节点会确定实际需要返回的文档,并向含有该文档的分片发送get请求;分片获取文档返回给协调节点;协调节点将结果返回给客户端。

二、RabbitMQ

2.1、异步调用

异步调用方式其实就是基于消息通知的方式,一般包含三个角色:

  • 消息发送者:投递消息的人,就是原来的调用方
  • 消息Broker:管理、暂存、转发消息,你可以把它理解成微信服务器
  • 消息接收者:接收和处理消息的人,就是原来的服务提供方

img

在异步调用中,发送者不再直接同步调用接收者的业务接口,而是发送一条消息投递给消息Broker。然后接收者根据自己的需求从消息Broker那里订阅消息。每当发送方发送消息后,接受者都能获取消息并处理。 这样,发送消息的人和接收消息的人就完全解耦了。

还是以余额支付业务为例:

img

除了扣减余额、更新支付流水单状态以外,其它调用逻辑全部取消。而是改为发送一条消息到Broker。而相关的微服务都可以订阅消息通知,一旦消息到达Broker,则会分发给每一个订阅了的微服务,处理各自的业务。

假如产品经理提出了新的需求,比如要在支付成功后更新用户积分。支付代码完全不用变更,而仅仅是让积分服务也订阅消息即可:

img

不管后期增加了多少消息订阅者,作为支付服务来讲,执行问扣减余额、更新支付流水状态后,发送消息即可。业务耗时仅仅是这三部分业务耗时,仅仅100ms,大大提高了业务性能。

另外,不管是交易服务、通知服务,还是积分服务,他们的业务与支付关联度低。现在采用了异步调用,解除了耦合,他们即便执行过程中出现了故障,也不会影响到支付服务。

综上,异步调用的优势包括:

耦合度更低

性能更好

业务拓展性强

故障隔离,避免级联失败

当然,异步通信也并非完美无缺,它存在下列缺点:

完全依赖于Broker的可靠性、安全性和性能

架构复杂,后期维护和调试麻烦

2.2、架构

img

其中包含几个概念:

publisher:生产者,也就是发送消息的一方

consumer:消费者,也就是消费消息的一方

queue:队列,存储消息。生产者投递的消息会暂存在消息队列中,等待消费者处理

exchange:交换机,负责消息路由。生产者发送的消息由交换机决定投递到哪个队列。

virtual host:虚拟主机,起到数据隔离的作用。每个虚拟主机相互独立,有各自的exchange、queue

Routing Key: 路由关键字,用于指定这个消息的路由规则,需要与交换器类型和绑定键(Binding Key)联合使用才能最终生效。
Binding:绑定,通过绑定将交换器和队列关联起来,一般会指定一个BindingKey,通过BindingKey,交换器就知道将消息路由给哪个队列了。
Connection :网络连接,比如一个TCP连接,用于连接到具体broker
Channel: 信道,AMQP 命令都是在信道中进行的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接,一个TCP连接可以用多个信道。客户端可以建立多个channel,每个channel表示一个会话任务。
Message:消息,由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。

2.3、交换机类型和区别

2.3.1、直连交换机: Direct exchange(默认)

直连交换机的路由算法非常简单: 将消息推送到binding key与该消息的routing key相同的队列。

image.png

直连交换机X上绑定了两个队列。第一个队列绑定了绑定o键range, 第二个队列有两个绑定键: black和green。

在这种场景下,一 个消息在布时指定了路由键为orange将会只被路由到队列Q1 I 路由键为black 和green的消息都将被路由到队列Q2。其他的消息都将被丢失。

同一个绑定键可以绑定到不同的队列上去, 可以增加一个交换机X与队列Q2的绑定键,在这种清况下,直连交换机将会和广播交换机有着相同的行为, 将消息推送到所有匹配的队列。一个路由键为black的消息将会同时被推送到队列Q1和Q2。

image.png

2.3.2、 主题交换机: Topic exchange

直连交换机的缺点:

直连交换机的 routing_key方案非常简单 ,如果我们希望一 条消息发送给多个队列 ,那么这个交换机需 要绑定上非常多的 routing_key.

假设每个交换机上都绑定一堆的 routing_key连接到各个队列上。那么消息的管理 就会异常地困难。

主题交换机的特点:

发送到主题交换机的 消息不能有任意的 routing key, 必须是由点号分开的一串单词,这些单词可以是任意的,但通常是与消息相关的一些特征。

如以下是几个有效的routing key:

“stock.usd.nyse”, “nyse.vmw”, “quick.orange.rabb 代”, routing key的单词可以 有很多,最大限制是255 bytes。

Topic 交换机的 逻辑与 direct 交换机有点 相似 使用特定路由键发送的消息 将被发送到所有使用匹配绑定键绑定的队列 ,然而 ,绑定键有两个特殊的情况:

*表示匹配任意一个单词

#表示匹配任意—个或多个单词

image.png

如:

routing key quick.orange.rabbit-> queue Ql, Q2

routing key lazy.orange.elephant-> queue Ql,Q2

延申:

当一个队列的绑定键是"#",它将会接收所有的消息,而不再考虑所接收消息的路由键。

当一个队列的绑定键没有用到"#"和’*"时,它又像 direct 交换一样工作。

2.3.3、扇形交换机: Fanout exchange

扇形交换机是最基本的交换机类型,它所能做的事清非常简单广播消息

扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要'思考”,所以扇形交换机处理消息的速度也是所有的交换机类型里面最快的。

2.3.4、Dead Letter Exchange(死信交换机)

RabbitMQ作为一个高级消息中间件,提出了死信交换器的概念。

这种交互器专门处理死了的信息(被拒绝可以重新投递的信息不能算死的)。

消息变成死信一般是以下三种情况:

①、消息被拒绝,并且设置requeue参数为false。

②、消息过期(默认情况下Rabbit中的消息不过期,但是可以设置队列的过期时间和信息的过期的效果)

③、队列达到最大长度(一般当设置了最大队列长度或大小并达到最大值时)

当满足上面三种情况时,消息会变成死信消息,并通过死信交换机投递到相应的队列中。

我们只需要监听相应队列,就可以对死信消息进行最后的处理。

订单超时处理:

生产者生产一条1分钟后超时的订单信息到正常交换机exchange-a中,消息匹配到队列queue-a,但一分钟后仍未消费。
消息会被投递到死信交换机dlx-exchange中,并发送到私信队列中。
死信队列dlx-queue的消费者拿到信息后,根据消息去查询订单的状态,如果仍然是未支付状态,将订单状态更新为超时状态。

2.4、RabbitMQ如何保证消息队列丢数据但消息不丢失

1、持久化消息:RabbitMQ默认将消息保存在内存中,如果服务重启或宕机,消息就会丢失。因此,需要对消息进行持久化处理,以便即使在服务重启或宕机的情况下,消息也不会丢失。持久化消息需要满足以下三个条件:Exchange设置持久化、Queue设置持久化和Message持久化发送。

2、生产者确认机制:在发送消息时,将信道设置为confirm模式,消息进入该信道后,会被指派给一个唯一ID。一旦消息被投递到所匹配的队列后,RabbitMQ就会发送给生产者一个确认。这种机制可以确保消息被成功发送到队列中,避免因网络问题或消费者消费失败而导致消息丢失。

3、消费者手动确认:当消费者消费消息时,如果未消费完毕就出现了异常,导致消息丢失,就需要关闭自动确认,改为手动确认消息。手动确认可以确保消费者成功消费了消息,避免了因自动确认机制的问题而导致消息丢失。

4、死信队列:死信是RabbitMQ中的一种消息机制,当在消费消息时,如果队列里的消息出现以下情况,如被拒绝、超时或消费者异常等,消息就会被送到一个特定的死信队列中。死信队列可以帮助排查问题,同时也可以确保消息不会丢失。

5、集群镜像模式:RabbitMQ提供了三种部署模式,其中普通模式和单节点模式都有可能导致消息丢失。而集群镜像模式可以确保即使某个节点宕机,其他节点也可以接管服务,从而保证消息不会丢失。


本文转载自: https://blog.csdn.net/bingqyujie/article/details/136337740
版权归原作者 Albatroes 所有, 如有侵权,请联系我们删除。

“一些中间件ES、RabbitMq”的评论:

还没有评论