中间件面试题

1.1 Redis

1.1.1 redis集群

Redis本身就支持集群操作redis_cluster,集群至少需要3主3从,且每个实例使用不同的配置文件,主从不用配置,集群会自己选举主数据库和从数据库,为了保证选举过程最后能选出leader,就一定不能出现两台机器得票相同的僵局,所以一般的,要求集群的server数量一定要是奇数,也就是2n+1台,并且,如果集群出现问题,其中存活的机器必须大于n+1台,否则leader无法获得多数server的支持,系统就自动挂掉。所以一般是3个或者3个以上的奇数节点。

Redis 2.8中提供了哨兵模式来实现自动化的系统监控和故障恢复功能。哨兵的作用就是监控redis主、从数据库是否正常运行,主数据库出现故障自动将从数据库转换为主数据库

我们公司搭建的redis集群是用的ruby脚本配合搭建的,我们一共搭建了6台服务器,3主3备,他们之间通信的原理是有一个乒乓协议进行通信的,我再给你说下一他们往里存储数据的机制吧,其实这个redis搭建好集群以后每个节点都存放着一个hash槽,每次往里存储数据的时候,redis都会根据存储进来的key值算出一个hash值,通过这个hash值可以判断到底应该存储到哪一个哈希槽中,取的时候也是这么取的,这就是我了解的redis集群

1.1.2 缓存穿透、缓存击穿、缓存雪崩、缓存预热、缓存同步

缓存穿透

缓存穿透是指用户查询数据,在数据库没有,自然在缓存中也不会有。这样就导致用户查询的时候,在缓存中找不到,每次都要去数据库再查询一遍,然后返回空,这就相当于进行了两次无用的查询。像这样请求就绕过缓存直接查数据库,这也是经常提的缓存命中率问题

解决办法

最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层存储系统的查询压力。另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空,不管是数据不存在,还是系统故障,我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存击穿

是指一个key非常热点,在不停的扛着大并发,大并发集中对这一个key不停进行访问,当这个key在失效的瞬间,持续的大并发就穿破缓存,直接请求数据库,就像在一个屏障上凿开了一个洞.

解决办法

1:设置热点key永不过期,2:设置redis分布式锁

缓存雪崩

是指在某一个时间段,缓存集中过期失效,比方说:我们设置缓存时采用了相同的过期时间,在同一时刻出现大面积的缓存过期,所有原本应该访问缓存的请求都去查询数据库了,而对数据库CPU和内存造成巨大压力,严重的会造成数据库宕机。从而形成一系列连锁反应,造成整个系统崩溃。

解决办法

一个简单方案就是缓存失效时间分散开,不设置固定的实效时间,采用随机失效的策略来解决。

最多的解决方案就是锁,或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上

缓存预热

缓存预热就是系统上线后,将相关的缓存数据直接加载到缓存系统。这样就可以避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题!用户直接查询事先被预热的缓存数据!

操作方式:

1、直接写个缓存刷新页面,上线时手工操作下;

2、数据量不大,可以在项目启动的时候自动进行加载;

缓存同步

1、定时去清理过期的缓存;

2.、当有用户请求过来时,再判断这个请求所用到的缓存是否过期,过期的话就去底层系统得到新数据并更新缓存

1.1.3 redis持久化方式

redis提供了两种持久化方式

RDB:RDB方式是一种快照式的持久化方法,将某一时刻的数据持久化到磁盘中,redis在进行数据持久化的过程中,会先将数据写入到一个临时文件中,待持久化过程都结束了,才会用这个临时文件替换上次持久化好的文件。正是这种特性,让我们可以随时来进行备份,因为快照文件总是完整可用的。

•对于RDB方式,redis会单独创建一个子进程来进行持久化,而主进程是不会进行任何IO操作的,这样就确保了redis极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效
AOF:AOF方式是将执行过的写指令记录下来,在数据恢复时按照丛前到后的顺序再将指令执行一遍

AOF命令以redis协议追加保存每次写的操作到文件末尾.默认的AOF持久化策略是每秒钟写入磁盘一次,因为在这种情况下,redis仍然可以保持很好的处理性能,即使redis故障,也只会丢失最近1秒钟的数据。

如果再追加日志时,遇到磁盘满了,redis提供的有redis-check-aof工具进行修复,文件大小超过阈值会进行压缩,所以断电,磁盘满不会影响aof文件的可用

1.1.4 redis设置过期时间的关键字

expire,如果在代码中就是直接调用此方法在里面传入key,过期时间

1.1.5 redis的删除策略和淘汰策略

1.1.5.1 删除策略

1.定时删除(时间换空间)
定时删除时给key都设置一个过期的时间,当达到删除时间节点时,立即执行对key的删除。

 优点:节约内存,到时间执行删除,释放内存空间;
 缺点:CPU资源占用率过高,当其他任务在执行时,会导致两者同时进行,会影响两者的效率;
 总结:用处理器的性能换取存储空间(时间换空间),适用范围:小内存,强CPU。

2.惰性删除(空间换时间)
惰性删除的含义是:当要删除的数据到达给定时间时,先不进行删除操作;等待下一次访问时,若数据已过期则进行删除,客户端返回不存在,数据未过期,则返回数据。

 优点:CPU的使用率大大降低,减轻其压力;
 缺点:内存空间占用率较高,会存在长期占用内存的数据
 总结:使用存储空间换取处理器性能(空间换时间),适用范围:大内存,弱CPU

1.1.5.2 淘汰策略

在前面所说的删除策略,它针对的是 expire 命令进行的操作,也就是说那些具有时效性的数据,如果针对那些并没有过期,或者是内存中的数据没有一个带有有效期,全是永久性数据,这时候删除策略就不起作用了,这时可以使用淘汰策略

1.检测带有时效性的数据进行淘汰:使用次数少的,即将过期的

2.检测全库的数据进行淘汰:使用次数少的,即将过期的,任意选择数据淘汰

1.1.6 redis的数据类型及使用场景

String:是简单的key-value类型,场景:微博数, 粉丝数

Hash:使用key-value类型,不过value是一个map类型,可存储部分变更数据,如用户信息等

List:就是双向链表,支持反向查找和遍历,也是比较重要的一个类型,比如论坛的关注列表,粉丝列表等都可以用Redis的list结构来实现

set:与list类似是一个列表的功能,特殊之处在于set是可以自动排重的

Sorted Set:与set类似,区别是set不是自动有序的,而sorted set可以通过用户额外提供一个优先级(score)的参数来为成员排序,并且是插入有序的,即自动排序,场景:排行榜

1.1.7 redis的单线程怎么理解

在Redis6.0版本之前是采用单线程设计,6.0之后呢引入了多线程设计概念。他这个单线程我觉得是针对于它的读写操作和网络处理来讲的,在Redis中其他的后台操作,例如异步删除、集群数据同步、持久化,这些都是需要单独的线程去完成,可以理解为Redis整体式多线程架构,只是有些功能是采用单线程设计。不过Redis的多线程机制在6.0版本中也是默认不开启的,需要我们修改配置文件去手动开启。

1.1.8 为什么使用用单线程?

因为使用单线程可以避免线程切换带来的开销,并且也不需要去加锁保证它的线程安全性,也不会产生死锁等情况影响性能问题,为了避免这类问题所以在读写方面还是采用了单线程设计。

1.1.9 单线程为什么这么快?

第一,他是基于内存操作的,相比磁盘读写来讲速度自然很快。第二,Redis采用了IO多路复用技术,极大地提高了系统的吞吐量。对于内存系统来说,多次读写都是在一个CPU上,没有上下文切换效率就是最高的!既然单线程容易实现,而且 CPU 不会成为瓶颈,那就顺理成章的采用单线程的方案了(毕竟采用多线程会有很多麻烦)。

1.1.10 6.0版本之后为什么引入了多线程?

Redis的性能瓶颈不在CPU,而在内存和网络。内存不够,可以做数据结构优化。所以网络才是关键,网络IO在执行期间大量占用了CPU,所以如果网络IO时引入多线程,对性能提升很大。
所以总结起来,Redis6.0支持多线程有两个原因:
1)充分利用CPU多核特性,6.0之前主线程只能利用一个核数
2)多线程任务可以分摊Redis同步IO读写负荷。

1.1.11 为什么要使用redis

项目中使用redis,主要是从两个角度去考虑:性能和并发
性能:我们在碰到需要执行耗时特别久,且结果不频繁变动的SQL,就特别适合将运行结果放入缓存。这样,后面的请求就去缓存中读取,使得请求能够迅速响应。
并发:在大并发的情况下,所有的请求直接访问数据库,数据库会出现连接异常。这个时候,就需要使用redis做一个缓冲操作,让请求先访问到redis,而不是直接访问数据库。

1.1.12 如何保证redis和数据库保持同步

根据不同的使用场景会有不同的方案比如

第一种:先删除缓存,再更新数据库

这种方式获取的数据是旧的,先删除缓存,在修改时查询缓存不存在,读取数据库,写入缓存,最终,缓存和数据库的数据是一致的,但仍然是旧的数据。

第二种:先更新数据库,再删除缓存

假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生

(1)缓存刚好失效

(2)请求A查询数据库,得一个旧值

(3)请求B将新值写入数据库

(4)请求B删除缓存

(5)请求A将查到的旧值写入缓存

如果发生上述情况,会发生脏数据。

第三种:给所有的缓存一个失效期

第三种方案可以说是一个大杀器,任何不一致,都可以靠失效期解决,失效期越短,数据一致性越高。但是失效期越短,查数据库就会越频繁。因此失效期应该根据业务来定。

1.并发不高的情况:

读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;

写: 写mysql->成功,再写redis;

2.并发高的情况:

读: 读redis->没有,读mysql->把mysql数据写回redis,有的话直接从redis中取;

写:异步话,先写入redis的缓存,就直接返回;定期或特定动作将数据保存到mysql,可以做到多次更新,一次保存;

第四种:加锁,使线程顺序执行

如果一个服务部署到了多个机器,就变成了分布式锁,或者是分布式队列按顺序去操作数据库或者 Redis,带来的副作用就是:数据库本来是并发的,现在变成串行的了,加锁或者排队执行的方案降低了系统性能,所以这个方案看起来不太可行。

第五种:采用双删

先删除缓存,再更新数据库,当更新数据后休眠一段时间再删除一次缓存。

方案推荐两种:

1:项目整合quartz等定时任务框架,去实现延时3–5s再去执行最后一步任务 。(推荐使用)

2:创建线程池,线程池中拿一个线程,线程体中延时3-5s再去执行最后一步任务

第六种:异步更新缓存(基于订阅binlog的同步机制)

其实最重要的就是解决数据库和redis的原子性问题,可以这么做,写线程只负责改数据库,不要去参与Redis同步的问题,Redis同步交给一个严格顺序的pipeline解决。这个pipeline的流程是,当数据库发生数据变化会立即产生binlog日志,我们可以借助阿里的canal组件去监听binlog,同时解析binlog,将解析的结果以消息的方式发送给MQ。MQ要保证严格顺序,再通过消费者去消费消息,将最新的数据覆盖更新到Redis,到此就能解决缓存的同步。

1.1.13 什么是哨兵机制

它的主要功能是对Redis实例(包括主节点和从节点)的运行状态进行监控。当主节点出现故障时,哨兵机制能够通过一系列的流程实现主从切换和故障转移,以保障整个Redis系统的可用性。

这种机制的出现是为了解决主从复制的一些缺点,例如在主节点出现问题时需要人工介入将备用节点改为主机,以及主节点的写能力和存储能力有限的缺陷。

1.1.14 redis有哪些特性?

  • 支持数据的持久化:Redis可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • 支持多种数据结构:不仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等复杂数据结构的存储。
  • 支持事务:Redis的事务命令序列化,按顺序执行,具有原子性。主要有三个阶段:开始事务、命令入队和执行事务。相关命令有MULTI/EXEC/DISCARD。
  • 支持发布订阅(Pub/Sub)模式:这是一种消息通讯模式,由Pub发送消息,而Sub接收消息。Redis客户端可以订阅任意数量的频道。
  • 支持主从复制和集群模式:这增强了Redis的读负载能力和容错能力。
  • 具备高性能处理IO的能力:官方应该统计的是Redis能读的速度是110000次/s,写的速度是81000次/s。

在缓存、消息队列、排行榜等场景广泛应用。

1.1.15 redisson分布式锁

Redisson是一个基于Redis的分布式锁实现方案,它充分利用了Redis键值数据库提供的一系列优势Redisson是一个基于Redis的分布式锁实现方案,它充分利用了Redis键值数据库提供的一系列优势,基于Java实用工具包中常用接口,为使用者提供了一系列具有分布式特性的常用工具类。原本作为单机多线程并发程序的工具包现在获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。

在实现分布式锁的过程中,Redisson主要依赖于Redis的单线程模型和原子性操作特性。获取锁的过程大致如下:首先使用SETNX命令尝试获取锁,如果返回值为1,表示获取锁成功。为了防止锁被其他进程或线程持有时间过长,可以为锁设置一个过期时间,当超过这个时间锁还未被释放时,其他进程或线程可以重新竞争锁。当进程或线程处理完业务后,会通过DEL命令将该key删除,从而释放锁,以允许其他进程或线程获取该锁。

此外,Redisson还用到了WatchDog(看门狗)机制来解决锁续期的问题。其基本思想是在获取锁成功后启动一个WatchDog看门狗,每隔一段时间就去检测锁是否还在有效期内,如果发现锁快要过期了,就会立即续命,从而保证锁不会因为过期而被其他进程或线程抢走。

Redisson还提供了可重入锁的功能。可重入锁是一种支持同一个线程多次获取同一把锁的锁,它在执行过程中可以避免死锁的发生。

1.1.16 redis分布式锁

Redis分布式锁的实现主要依赖于Redis的单线程模型和原子性操作特性。Redis采用单线程模型来处理多个客户端的请求,这意味着在Redis中执行的每个命令都是原子性的,不存在线程安全问题。

在实现分布式锁时,主要使用了以下两个命令:

SETNX:该命令用于尝试设置一个带有过期时间的锁。如果锁不存在,则设置成功并返回1;如果锁已存在,则不执行任何操作并返回0。
GET:该命令用于检查锁是否属于当前客户端。如果是,则执行临界区代码;如果不是,则释放锁并重试。
获取锁的过程大致如下:首先使用SETNX命令尝试获取锁,如果返回值为1,表示获取锁成功。为了防止锁被其他进程或线程持有时间过长,可以为锁设置一个过期时间,当超过这个时间锁还未被释放时,其他进程或线程可以重新竞争锁。当进程或线程处理完业务后,会通过DEL命令将该key删除,从而释放锁,以允许其他进程或线程获取该锁。

1.2 ElasticSearch

1.2.1 ELK

“ELK”其实就是:Elasticsearch、Logstash 和 Kibana的简称。

Elasticsearch 负责日志搜索(一般通过代码中输出日志的关键字、时间、日志级别检索)。

Logstash 是服务器端数据处理管道,它能够同时将分布式、集群不同节点的日志进行统一采集管理,然后将数据发送给 Elasticsearch 。

Kibana其实就是操作es的一个可视化工具, 可以对 Elasticsearch 中的数据使用图形和图表进行可视化展示或关键字检索,我们一般会在服务的logback文件中对日志的输出路径、日志级别、线程标识包括logstash的安装路劲进行配置,确保logstash能找到管理的日志文件,然后在logstash的配置文件中配置es的安装路径,当logstash拿到实时的日志,及时上传到es库。

1.2.2 说说 es 的集群架构,索引数据大小,分片有多少,以及一些调优手段

比如:ES 集群架构 13 个节点,索引根据通道不同共 20+索引,根据日期,每日递增 20+,索引:10分片,每日递增 1 亿+数据,每个通道每天索引大小控制:150GB 之内

设计调优

根据业务增量需求,采取基于日期模板创建索引,通过 roll over API 滚动索引;

使用别名进行索引管理;每天凌晨定时对索引做合并操作,以释放空间;

采取冷热分离机制,热数据存储到硬盘,提高检索效率;

1.2.3 elasticSearch如何进行master选举

第一步:确认候选主节点数达标,elasticsearch.yml 设置的值 discovery.zen.minimum_master_nodes;
第二步:对所有候选主节点根据nodeId字典排序,每次选举每个节点都把自己所知道节点排一次序,然后选出第一个节点,暂且认为它是master节点。
第三步:如果对某个节点的投票数达到一定的值(候选主节点数n/2+1)并且该节点自己也选举自己,那这个节点就是master。否则重新选举一直到满足上述条件。

1.2.4 elasticSearch数据导入

如果数据量较小,可以通过kibana直接导入,如果数据量比较大,我们是通过代码分批导入,当然后期有个别数据的更新,我们在es更新后及时更新mysql,如果超大量的数据,代码导入一旦出现问题,不好排查出现问题的数据位置,所以可以使用curl工具进行大批量导入。我们公司因为数据量不是特别大,基本都是代码导入的,首先从数据库批量查出,然后将字段以key value的形式存入到一个map集合,当然这个map集合转成json的格式要和es库文档中的格式保持一致,将map封装到request对象中,通过resthighlevelclient的index方法提交request对象,数据就自动导入了。当然这个是第一次的批量导入,我们这个项目再后期一旦有审核过的文章,会直接通知es进行新数据导入。

1.2.5 分片

分片就是模拟内部集群,实现数据高可用的一个主从配置

1.2.6 倒排索引

ES查询数据的方式就是依赖倒排索引,倒排索引,就是关键字文档的映射。
查询数据时,根据关键字先扫描分词列表,然后根据匹配到的分词在映射查询到相关的文档数据。

优点:检索的快速响应是一个最为关键的性能
缺点:倒排表的建立和维护都较为复杂(索引建立由于在后台进行,尽管效率相对低一些,但不会影响整个搜索引擎的效率)

1.2.7 正排索引

正排索引是从文档到关键字的映射(已知文档求关键字),按文档逐个查询,类似于关系数据表里逐条数据查询,并以like模糊匹配

优点:易于维护:新增的话直接跟在原来的后面,删除的话直接删除某一条即可
缺点:查询时间长,检索效率低下

1.2.8 在并发情况下,Elasticsearch 如果保证读写一致?

可以通过版本号使用乐观并发控制,以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;

1.2.9 详细描述一下 Elasticsearch 更新和删除文档的过程

(1)删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;

(2)磁盘上的每个段都有一个相应的.del 文件。当删除请求发送后,文档并没有真的被删除,而是在.del 文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段。

(3)在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del 文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结果中被过滤掉

1.2.10 ES运行机制(原理)

es的搜索主要是两部分,一个是Query阶段,一个是Fatch阶段,就是查询和获取阶段

那么查询阶段的话是这样

1、收到用户请求,交由协调节点(coordinating node),那么coordinating node 会随机选择所有分片或者副本(保证所有都覆盖到),发送查询请求,接下来被选中的分片分别执行查询并排序,最后coordinating node 整合数据,根据排序值再取from+size的文档id

Fatch阶段是这样

1、通过Query阶段拿到文档Id列表,随后去对应的分片上获取文档详情数据,coordinating node 向相关分片发送multi_get请求各个分片返回文档详细数据,coordinating node拼接结果并返回给客户

1.2.11 mysql和es怎么保证数据同步

可以采用以下几种方式:
1.定时任务:通过编写定时任务程序,定期从MySQL中读取数据并写入到Elasticsearch中。这种方式需要自己实现数据的转换和同步逻辑,并且需要考虑数据冲突、重复等问题。
2.数据中间件:使用一些专门用于数据同步的数据中间件,如Canal、Debezium等。这些中间件可以监控MySQL的binlog日志,实时将数据变化同步到Elasticsearch中。这种方式不需要自己编写代码,但需要安装和配置中间件。
3.数据库插件:一些数据库厂商提供了与Elasticsearch集成的插件,如MySQL Connector for Elasticsearch。这种方式可以通过SQL语句直接将数据同步到Elasticsearch中,但需要安装和配置插件。

无论采用哪种方式,都需要注意以下几点:
1.数据一致性:在同步过程中要保证数据的一致性,避免出现数据丢失或重复的情况。
2.性能问题:由于数据同步涉及到大量的数据传输和处理,可能会对系统的性能产生影响,需要进行性能测试和优化。
3.异常处理:在同步过程中可能会出现各种异常情况,需要做好异常处理和日志记录,以便及时发现和解决问题。

1.3 消息中间件

1.3.1 rabbitmq的特性及为什么要使用?

特性:

  • 消息确认机制 (自动确认和手动确认) 默认使用自动确认
    • 自动确认: acknowledge=‘none’
    • 手动确认: acknowledge=‘manual’
  • 持久化
    • 交换器持久化、队列持久化和消息的持久化
  • 过期时间(TTL)
    • 跟Redis的过期时间一样

为什么要使用:

  • 异步处理 : 调用者无需等待

    将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间

  • 应用程序**解耦合 ** : 解决了系统之间耦合调用的问题

    MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。

  • 削峰 : 抵御洪峰流量,保护了主业务

    并发量大的时候,所有的请求直接怼到数据库,造成数据库连接异常.

    比如:

    在下单的时候就会往数据库写数据,但是在高峰期时候,并发量会突然激增,这时候直接访问数据库数据库会卡死. 这时候可以通过mq将消息保存起来,然后按照自己的消费能力来消费,这样慢慢写入数据库。

1.3.2 如何保证数据一定被发送?

使用本地消息表(mysql)和定时任务(quartz)和MQ的响应机制(ComfirmFallback,ReturnFallback)

在发送消息时,在本地的mysql数据库中进行记录一条待发送信息.一个定时任务不断检查,是否发送成功,如果发送成功,将记录状态修改.如果发送失败者再次发送

1.3.3 rabbitmq如何保证消息不丢失?保证数据一定会被消费,如何保证?(两者一个意思)

RabbitMQ中,消息丢失可以简单的分为三种:生产者丢失消息,消费者丢失消息和消息队列丢失消息

1.3.3.1 生产者丢失消息

解决方案: 从生产者弄丢数据这个角度来看,RabbitMQ提供transaction和confirm模式来确保生产者不丢消息

1.3.3.2 消息队列丢失消息

解决方案: 消息持久化。durable设置为true

1.3.3.3 消费者丢失消息

解决方案:

设置为手动ack确认机制,当消费者出现异常或者服务宕机时,MQ服务器不会删除该消息,而是会把消息重发给绑定该队列的消费者,
消息不重复消费,ack确认机制是幂等的,也就是多次确认同一消息,和单独确认该消息一次的效果是一样的

生产者把消息发送给MQ之后,MQ收到消息在给生产者返回ack的时候,即使网络中断了,再次重试也不会对先前的消息造成影响。同时,RabbitMQ等消息队列中间件也提供了生产端的confirm机制和消费端的消息确认,可以确保消息在传递过程中的可靠性和一致性。默认情况下,消息消费者是自动确认消息的,但如果要手动确认则需要修改确认模式为manual。

解决方案:使用本地消息去重表,每次消费先到表中查询是否消费的标识,如果消费过就直接ack回滚。

1.3.4 rabbitmq实现数据同步后,消费者挂掉这么办,回滚代码这么实现?

实现ReturnCallback接口重写returnedMessage方法,回滚发送方数据同步操作,具体回滚可以读取发送方记录表中业务ID然后进行回退操作。

1.3.5 消息堆积是如何产生,如何解决消息堆积?

当消息生产的速度长时间,远远大于消费的速度时。就会造成消息堆积,比如

  • 生产者突然大量发布消息

  • 消费者挂掉

    解决

    • 增加消费者的多线程处理
    • 增加多个消费者

1.3.6 rabbitmq常用的消息模式?

RabbitMQ工作模式:

1、简单模式 HelloWorld

​ 一个生产者、一个消费者

2、工作队列模式 Work Queue

​ 一个生产者、多个消费者(竞争关系)

3、发布订阅模式 Publish/subscribe

​ 需要设置类型为fanout的交换机,并且交换机和队列进行绑定,当发送消息到交换机后,交换机会将消息发送到绑定的队列

4、路由模式 Routing

​ 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

5、通配符模式 Topic

​ 需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

1.3.7 rabbitmq什么时候变成死信队列?如何发送延时消息?

  • 消息拒绝并且没有设置重新入队
  • 消息过期
  • 消息堆积,并且队列达到最大长度,先入队的消息会变成DLX 死信队列交换机

死信队列实现方式:首先创建一个交换机来当做死信交换机,再创建一个队列与这个死信交换机进行绑定就称作死信队列。其次创建一个交换机来当做正常交换机,在创建一个队列与这个正常交换机进行绑定,同时将死信交换机和死信路由键配置到这个正常队列里面。这样,当一条带有存活时间的消息通过正常交换机发送过来时,首先进入正常队列里面,然后到了存活时间,就会通过死信交换机根据路由键发送到死信队列里面,然后消费者消费死信队列里的消息,就达到了延迟消费的目的

1.3.8 kafka中的broker 是干什么的

broker 是消息的代理,Producers往Brokers里面的指定Topic中写消息,Consumers从Brokers里面拉取指定Topic的消息,然后进行业务处理,broker在中间起到一个代理保存消息的中转站

1.3.9 kafka中的 zookeeper 起到什么作用,可以不用zookeeper么

Kafka中的zookeeper起到了管理和协调Kafka集群的作用,它负责维护Kafka集群中的元数据,如broker的注册信息、topic的创建、分区的分配等,同时也负责实现Kafka的容错机制,保证Kafka集群的高可用性。
不可以不用zookeeper,Kafka依赖zookeeper来管理集群,如果不用zookeeper,Kafka将无法正常工作,broker依然依赖于ZK,zookeeper 在kafka中还用来选举controller 和 检测broker是否存活等等。

1.3.10 kafka 为什么那么快

Kafka速度快在于,它把所有的消息都变成一个的文件。通过mmap(内存文件映射)提高I/O速度,写入数据的时候它是末尾添加所以速度最优;读取数据的时候配合sendfile直接暴力输出。所以对于一个MQ的评价只以速度来看,世界上没人能干的过Kafka,我们设计的时候其实还是根据业务定MQ。阿里的RocketMQ也是这种模式,只不过是用Java写的。

1.3.11 rabbitma和kafka的区别

RabbitMQ和Kafka都是消息队列中间件,RabbitMQ是一个消息代理,主要用于处理异步通信和解耦,它支持多种消息协议,如AMQP、STOMP、MLLP等。相反,Apache Kafka是一个分布式流式系统,主要用于处理实时数据流。

一个重要的区别在于他们的性能特性。Kafka的吞吐量更高,这得益于其Zero Copy机制和磁盘顺序读写,大大减少了数据的复制次数和寻道等待的时间。而RabbitMQ则更适合处理批量消息,支持多种消息模式,包括发布/订阅、请求/响应等。

此外,两者在应用场景上也有所不同。对于需要实时处理大量数据的场景,例如日志收集、实时分析等,Kafka通常是更好的选择。而对于需要处理复杂业务逻辑的场景,例如订单处理、支付结算等,RabbitMQ可能更合适。

1.3.12 项目中为什么使用kafka,而不使用rabbitmaq

1.高吞吐量和低延迟:Kafka可以处理大容量、高吞吐量和实时数据流,每秒能够处理数百万个事件。同时,由于其Zero Copy机制和磁盘顺序读写,Kafka的延迟非常低。
2.无界的数据流:Kafka使用无界的数据流,即数据持续地流入到指定的主题中,不会被删除或过期,除非达到了预设的保留期限或容量限制。
3.可扩展性和耐用性:Kafka的设计使其具有高度的可扩展性和耐用性。它可以处理大量的数据,并且通过将所有数据写入磁盘来提供高度的耐用性。
4.基于拉取模型的消息排序:由于分区的存在,Kafka使用拉取模型是合理的。Kafka可以在没有消费者相互竞争的情况下提供消息排序,这种方法让用户可以利用消息批处理来实现高效的消息传递。
然而,值得注意的是,对于一些吞吐量没那么大的项目来说,引入Kafka可能会增加系统的复杂度,主要体现在配置和维护上的复杂性。此外,虽然Kafka提供了高度的耐用性,但由于其更灵活的架构,Kafka可能具有更高的延迟。因此,在选择是否使用Kafka时,需要根据项目的具体需求和场景进行权衡。

1.3.13 如何保证消息的幂等性

产生的原因可能是网络延迟或者消费者出现问题,或者延迟消费,造成MQ重试补偿,重试的过程中会重复消费。
保证消息的幂等性主要有以下几种方法:

业务逻辑层面:通过在业务代码中添加逻辑判断,例如检查是否已经处理过该消息,从而避免重复处理。可以添加请求头设置消息id,当消息被消费后将消息id保存到数据库或者缓存中,下次消费时判断是否存在,如果存在则就是消费过了,如果没有则添加消息id。
数据库层面:为数据库表添加唯一索引或约束,确保同一消息不会被重复插入或更新。
消息队列层面:使用消息队列来确保消息至少被消费一次,但要注意,消息队列本身不能保证消费端的幂等性。这通常需要基于具体的业务场景来实现。
事务控制:通过分布式事务或者其他事务控制机制,确保一组操作要么全部成功,要么全部失败,从而达到幂等效果。
定时任务与重试机制:在某些情况下,可以通过定时任务或自动重试机制来确保消息最终被处理,但这需要注意避免死循环和重复处理的问题。
去重表或日志:在消息处理系统中,可以使用去重表或日志来记录已经处理过的消息ID或内容,从而避免重复处理。
不同的业务场景可能需要不同的幂等性实现方式。例如,支付、订单等与金钱挂钩的服务,保证接口幂等性尤为重要。因此,在实现幂等性时,需要根据具体的业务需求和系统架构来灵活选择适合的方法。

1.3.14 什么是消费重试?

当消息发送到Broker成功,在被消费者消费时如果消费者没有正常消费,此时消息会重试消费。消费重试存在两
种场景:
1)消息没有被消费者接收,比如消费者与broker存在网络异常。此种情况消息会一直被消费重试。
2)当消息已经被消费者成功接收,但是在进行消息处理时出现异常,消费端无法向Broker返回成功,这种情况下RocketMQ会不断重试。

如果消息消费时出现异常怎么办?

消息会按照延迟消息的延迟时间等级(1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h)
从第3级开始重试,每试一次如果还不成功则延迟等级加1。
比如:一条消息消费失败,等待10s(第3级)进行重试,如果还没有被成功消费则延迟等级加1,即按第4级别延迟等待,等30s继续进行重试,如此进行下去,直到重试16次。
当重试了16次还未被成功消费将会投递到死信队列,到达死信队列的消息将不再被消费。实际生产中的处理策略是什么呢?
实际生产中不会让消息重试这么多次,通常在重试一定的次数后将消息写入数据库,由另外单独的程序或人工去处理。

1.3.15 消费者在消费消息时,如果业务出现异常,怎么做?

可以采用消息重试机制,RabbitMQ默认开启重试机制,底层采用@RabbitHandler注解使用AOP的拦截,如果消费者没有问题,自动提交事务,如果拦截到异常,实现补偿,缓存消息到服务端,但是它默认会一直重试,可以修改时间。

1.4 Dubbo

1.4.1 什么是dubbo

Dubbo 是阿里巴巴开源的基于 Java 的高性能 RPC 分布式服务框架,提供了高性能和透明化的 RPC 远程服务调用方案

1.4.2 dubbo的原理

在这里插入图片描述

节点角色说明:
	Provider: 暴露服务的服务提供方(service)。
	Consumer: 调用远程服务的服务消费方(controller)。
	Registry: 服务注册与发现的注册中心。
	Monitor: 统计服务的调用次调和调用时间的监控中心。
	Container: 服务运行容器。
调用关系说明:
	0. 服务容器负责启动,加载,运行服务提供者。
	1. 服务提供者在启动时,向注册中心注册自己提供的服务。
	2. 服务消费者在启动时,向注册中心订阅自己所需的服务。
	3. 注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。
	4. 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。
	5. 服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。

1.4.3 dubbo支持哪些通讯协议?

1、默认dubbo协议 采用单一长连接和NIO异步通讯,适合于小数据量大并发的服务调用,以及服务消费者机器数远大于服务提供者机器数的情况
  不适合传送大数据量的服务,比如传文件,传视频等,除非请求量很低
2、RMI协议:采用阻塞式短连接和JDK标准序列化方式,多个短连接,适合消费者和提供者数量差不多,适用于文件的传输
3、Hessian协议:用于集成 Hessian的服务,Hessian 底层采用 Http 通讯,采用 Servlet 暴露服务,Dubbo 缺省内嵌 Jetty 作为服务器实现。
4、http协议:基于http表单的远程调用协议
5、webservice:可以和原生 WebService 服务互操作,一方暴露接口,乙方调用
6、thrift 协议:当前 dubbo 支持的 thrift 协议是对 thrift 原生协议 [2] 的扩展,在原生协议的基础上添加了一些额外的头信息,比如 service name,magic number 等。使用 dubbo thrift 协议同样需要使用 thrift 的 idl compiler 编译生成相应的 java 代码,后续版本中会在这方面做一些增强。
7、memcached 协议:基于 memcached 实现的 RPC 协议。
8、redis 协议:基于Redis 实现的 RPC 协议。
9、rest 协议:就是 RestFull,实现的REST调用支持

1.4.4 dubbo使用步骤

1.在pom文件中添加dubbo依赖
2.在spring配置文件中配置dubbo的约束头信息
3.在spring配置文件中配置dubbo应用名称,协议名称,服务地址,ip和端口号,通过注解声明服务发布或者调用
4.启动注册中心,发布服务暴露接口
5.可以通过dubbo-admin中的监控中心查看和监控接口情况

1.4.5 dubbo注册中心启动,服务提供者和服务消费通信后,注册中心挂了会不会对dubbo有影响?

不会的,因为 dubbo 的注册中心,使用的是 zookeeper,只是提供目录服务,将发布的服务信息存储在 dubbo 目录中,服务消费者根据声明的调用服务,去注册中心查找服务,找不到后会重试三次,如果服务都无法调用再报异常,但是有的时候系统是分布式服务,就会可能重试多次,一旦服务提供者和服务消费者通讯后,则不再需要注册中心,通过服务的 URL 地址也可以调用服务。所以,只要通信过,即使注册中心宕机了,原来通信的服务依然可以使用,没有影响,但是新发布的服务则无法被发布到服务目录中。

1.4.6 dubbo访问超时可能是什么问题,如何解决

dubbo 超时后,会自动三次,如服务提供者为集群,则会切换服务提供者,如果三次都没有连接到服务提供者,则抛出超时异常。
如果是因为网络延迟,可以适当增加连接超时时间

1.5 Zookeeper

1.5.1 什么是zookeeper?

zookeeper是一种分布式协调服务,可以再分布式系统中共享配置,协调锁资源,提供命名服务,zookeeper的数据模型很像数据结构当中的树,也和文件系统目录相似,zookeeper的数据存储也同样是基于节点,这种节点叫做Znode。但是,不同于树的节点,Znode的引用方式是路径引用

1.5.2 为什么要使用zookeeper?

我们使用 zookeeper 主要是因为他的服务注册及服务发现功能,主要是用于 dubbo 的注册中心。当时我们在用 dubbo时,官方推荐的 zookeeper 作为注册中心,但是现在貌似都在提去 zookeeper 化,甚至于阿里都开始不再提倡使用zookeeper,自己也是开源了 nacos 动态配置文件中心,允许线上动态切换配置文件,以及服务发现和服务注册功能。我大概了解了下,分布式系统的三大指标 CAP,zookeeper 主要是 CP,要求强一致性,但是 springcloud、nacos 都是 AP,要求可用性,即使注册中心除了问题也不会影响到服务,所以现在好多公司都在将 RPC 通讯有原来的springboot+dubbo 再向 springboot+springcloud 过度。

1.5.3 zookeeper有哪些特点?

主要提供文件系统、通知机制的服务功能,作为服务中间件,主要是用于治理分布式系统中的服务,Zookeeper 保证了如下分布式一致性特性:
集群:Zookeeper是一个领导者(Leader),多个跟随者(Follower)组成的集群。
高可用性:集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。
全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。
更新请求顺序进行:来自同一个Client的更新请求按其发送顺序依次执行。
数据更新原子性:一次数据更新要么成功,要么失败。
实时性:在一定时间范围内,Client能读到最新数据。
从设计模式角度来看,zk是一个基于观察者设计模式的框架,它负责管理跟存储大家都关心的数据,然后接受观察者的注册,数据反生变化zk会通知在zk上注册的观察者做出反应。Zookeeper是一个分布式协调系统,满足CP性,跟SpringCloud中的Eureka满足AP不一样。

1.5.4 zookeeper的通知机制是什么?

Zookeeper 允许客户端向服务端的某个 znode 注册一个 Watcher 监听,当服务端的一些指定事件,触发了这个 Watcher ,服务端会向指定客户端发送一个事件通知来实现分布式的通知功能,然后客户端根据 Watcher 通知状态和事件类型做出业务上的改变。大致分为三个步骤
客户端注册 Watcher
服务端处理 Watcher
客户端回调 Watcher
client端会对某个 znode 建立一个 watcher 事件,当该 znode 发生变化时,这些 client 会收到 zk 的通知,然后 client 可以根据 znode 变化来做出业务上的改变等。

1.5.5 Zookeeper 对节点的 watch 监听通知是永久的吗

不是,一次性的。无论是服务端还是客户端,一旦一个 Watcher 被触发, Zookeeper 都会将其从相应的存储中移除。这样的设计有效的减轻了服务端的压力,不然对于更新非常频繁的节点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的压力都非常大。

1.5.6 说说Zookeeper中的脑裂?

简单点来说,脑裂(Split-Brain) 就是比如当你的 cluster 里面有两个节点,它们都知道在这个cluster 里需要选举出一个 master。那么当它们两个之间的通信完全没有问题的时候,就会达成共识,选出其中一个作为 master。但是如果它们之间的通信出了问题,那么两个结点都会觉得现在没有 master,所以每个都把自己选举成 master,于是 cluster 里面就会有两个 master。关于zookeeper宕机情况,在分布式系统中这些都是有监控者来判断的,但是监控者也很难判定其他的节点的状态,唯一一个可靠的途径就是心跳,Zookeeper也是使用心跳来判断客户端是否仍然活着

1.6 FastDFS

1.6.1 FastDFS介绍

FastDFS是一个开源的轻量级分布式文件系统,可以实现文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。比较适合以文件为载体的在线服务,如相册网站、视频网站等等。

FastDFS 架构包括 Tracker server(调度器) 和 Storage server(存储器)。客户端请求 Tracker server 进行文件上传、下载,通过Tracker server 调度最终由 Storage server 完成文件上传和下载。

1.6.2 上传流程

在这里插入图片描述

客户端上传文件后存储服务器将文件 ID 返回给客户端,此文件 ID 用于以后访问该文件的索引信息。文件索引信息包括:组名,虚拟磁盘路径,数据两级目录,文件名。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐