一、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)中,此时如果执行搜索操作,这个新文档还不能被索引到。
ES 会每隔 1 秒时间(这个时间可以修改)进行一次刷新操作(refresh),此时在这 1 秒时间内写入内存的新文档都会被写入一个文件系统缓存(filesystem cache)中,并构成一个分段(segment)。此时这个 segment 里的文档可以被搜索到,但是尚未写入硬盘,即如果此时发生断电,则这些文档可能会丢失。
不断有新的文档写入,则这一过程将不断重复执行。每隔一秒将生成一个新的 segment,而 translog 文件将越来越大。
每隔 30 分钟或者 translog 文件变得很大,则执行一次 fsync 操作。此时所有在文件系统缓存中的 segment 将被写入磁盘,而 translog 将被删除(此后会生成新的 translog)
由上面的流程可以看出,在两次 fsync 操作之间,存储在内存和文件系统缓存中的文档是不安全的,一旦出现断电这些文档就会丢失。所以 ES 引入了 translog 来记录两次 fsync 之间所有的操作,这样机器从故障中恢复或者重新启动,ES 便可以根据 translog 进行还原。
当然,translog 本身也是文件,存在于内存当中,如果发生断电一样会丢失。因此,ES 会在每隔 5 秒时间或是一次写入请求完成后将 translog 写入磁盘。可以认为一个对文档的操作一旦写入磁盘便是安全的可以复原的,因此只有在当前操作记录被写入磁盘,ES 才会将操作成功的结果返回发送此操作请求的客户端。
此外,由于每一秒就会生成一个新的 segment,很快将会有大量的 segment。对于一个分片进行查询请求,将会轮流查询分片中的所有 segment,这将降低搜索效率。因此 ES 会自动启动合并 segment 的工作,将一部分相似大小的 segment合并成一个新的大 segment。合并的过程实际上是创建了一个新的 segment,当新 segment 被写入磁盘,所有被合并的旧 segment 被清除。
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:管理、暂存、转发消息,你可以把它理解成微信服务器
- 消息接收者:接收和处理消息的人,就是原来的服务提供方
在异步调用中,发送者不再直接同步调用接收者的业务接口,而是发送一条消息投递给消息Broker。然后接收者根据自己的需求从消息Broker那里订阅消息。每当发送方发送消息后,接受者都能获取消息并处理。 这样,发送消息的人和接收消息的人就完全解耦了。
还是以余额支付业务为例:
除了扣减余额、更新支付流水单状态以外,其它调用逻辑全部取消。而是改为发送一条消息到Broker。而相关的微服务都可以订阅消息通知,一旦消息到达Broker,则会分发给每一个订阅了的微服务,处理各自的业务。
假如产品经理提出了新的需求,比如要在支付成功后更新用户积分。支付代码完全不用变更,而仅仅是让积分服务也订阅消息即可:
不管后期增加了多少消息订阅者,作为支付服务来讲,执行问扣减余额、更新支付流水状态后,发送消息即可。业务耗时仅仅是这三部分业务耗时,仅仅100ms,大大提高了业务性能。
另外,不管是交易服务、通知服务,还是积分服务,他们的业务与支付关联度低。现在采用了异步调用,解除了耦合,他们即便执行过程中出现了故障,也不会影响到支付服务。
综上,异步调用的优势包括:
耦合度更低
性能更好
业务拓展性强
故障隔离,避免级联失败
当然,异步通信也并非完美无缺,它存在下列缺点:
完全依赖于Broker的可靠性、安全性和性能
架构复杂,后期维护和调试麻烦
2.2、架构
其中包含几个概念:
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相同的队列。
直连交换机X上绑定了两个队列。第一个队列绑定了绑定o键range, 第二个队列有两个绑定键: black和green。
在这种场景下,一 个消息在布时指定了路由键为orange将会只被路由到队列Q1 I 路由键为black 和green的消息都将被路由到队列Q2。其他的消息都将被丢失。
同一个绑定键可以绑定到不同的队列上去, 可以增加一个交换机X与队列Q2的绑定键,在这种清况下,直连交换机将会和广播交换机有着相同的行为, 将消息推送到所有匹配的队列。一个路由键为black的消息将会同时被推送到队列Q1和Q2。
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 交换机有点 相似 使用特定路由键发送的消息 将被发送到所有使用匹配绑定键绑定的队列 ,然而 ,绑定键有两个特殊的情况:
*表示匹配任意一个单词
#表示匹配任意—个或多个单词
如:
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提供了三种部署模式,其中普通模式和单节点模式都有可能导致消息丢失。而集群镜像模式可以确保即使某个节点宕机,其他节点也可以接管服务,从而保证消息不会丢失。
版权归原作者 Albatroes 所有, 如有侵权,请联系我们删除。