面试必问【中间件分布式】——redis,消息队列,es,高可用,分库分表

面试要点

分布式cap原理

consistency一致性
Availability 高可用性
partition tolerance 分区容错性
zookeeper可以满足cp,
三个性质无法同时满足,所以分布式系统为满足高可用性,牺牲一致性,只需要保证数据的最终一致性。只要这个最终时间是在用户可以接受的范围内即可

分布式锁的实现

1、zookeeper有序临时节点实现分布式锁,但是zookeeper本来效率就不高,节点的实现和删除都需要比较大的开销
2、redis setnx加过期时间

高级分布式锁的实现Redisson

Redisson实现Redis分布式锁
可重入锁 RLock lock = redisson.getLock(“anyLock”);
公平锁 RLock fairLock = redisson.getFairLock(“anyLock”);
信号量 RSemaphore semaphore = redisson.getSemaphore(“semaphore”);
闭锁 RCountDownLatch latch = redisson.getCountDownLatch(“anyCountDownLatch”);

七种事务传播行为
(1).REQUIRED<默认> 如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。
(2)SUPPORTS 如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务的方式继续运行。
(3)MANDATORY 如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。
(4)REQUIRES_NEW 重新创建一个新的事务,如果当前存在事务,延缓当前的事务。
(5)NOT_SUPPORTED 以非事务的方式运行,如果当前存在事务,暂停当前的事务。
(6)NEVER 以非事务的方式运行,如果当前存在事务,则抛出异常。
(7)NESTED 如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。

线程池工作流程
提交一个任务,线程池里存活的核心线程数小于corePoolSize时,线程池会创建一个核心线程去处理提交的任务
如果线程池核心线程数已满,即线程数已经等于corePoolSize,一个新提交的任务,会被放进任务队列workQueue排队等待执行。
当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列workQueue也满,判断线程数是否达到maximumPoolSize,即最大线程数是否已满,如果没到达,创建非核心线程执行提交的任务。
如果当前的线程数达到了maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。
sleep与wait区别
sleep属于线程类,wait属于object类;sleep不释放锁
拒绝策略
AbortPolicy直接抛出异常阻止线程运行;
CallerRunsPolicy如果被丢弃的线程任务未关闭,则执行该线程;
DiscardOldestPolicy移除队列最早线程尝试提交当前任务
DiscardPolicy丢弃当前任务,不做处理

在我们实际使用中,线程池的大小配置多少合适?线程池的参数配置要注意什么
对于计算密集型,设置 线程数 = CPU数 + 1,通常能实现最优的利用率。
对于I/O密集型,网上常见的说法是设置 线程数 = CPU数 * 2

可达性分析哪些对象可以作为gc root?
1、虚拟机栈栈帧中,局部变量表中的变量引用的对象
2、方法区的静态变量和静态
3、本地方法栈中本地方法引用的对象

项目中新对象多的新生代设置大一些,持久化大对象多的,设置老年代大一些
为了减少full gc 希望能够尽量持久化常用对象在老年代中,所以可以把老年代设置大一些
1)更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
2)更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率

OOM问题定位方法
(1):jmap -heap 10765如上图,可以查看新生代,老生代堆内存的分配大小以及使用情况;
(2):jstat 查看GC收集情况
(3):jmap -dump:live,format=b,file=到本地
(4):通过MAT工具打开分析

zookeeper实现高并发的分布式锁方案

create /test laogong // 创建永久节点
create -e /test laogong // 创建临时节点
create -s /test // 创建顺序节点
create -e -s /test // 创建临时顺序节点
在线程并发执行的逻辑中,利用zookeeper中节点不能重复创建的特性,线程执行前先创建一个临时顺序的节点,
1、由于是顺序的,所有线程监听前一个节点是否被释放,会减少羊群效应
2、节点是临时的,如果线程所在的服务挂了,就可以迅速删除节点,其他线程得以执行
缺点:
1、Zk性能上可能并没有缓存服务那么高。
2、网络抖动,session连接断了,zk以为挂了,删除临时节点,其他客户端就可以获取到分布式锁了

redis一致性hash
计算数据的hash值,根据值,用机器数量取余定位机器 hash(a.png) % 4 = 2,不会遍历所有机器
用服务器ip和机器码计算hash位置,形成圆环
1、减少一台服务,仅影响服务器到其环空间中前一台服务器
2、增加一台服务也是一样,即沿着逆时针方向行走遇到的第一台服务器
解决一致性hash倾斜问题,增加虚拟节点,即一个机器多个hash位置,多个点

数据库事务隔离级别
4个隔离级别
1、读未提交
事务B可以读到事务A未提交的数据,破坏了隔离性,一旦事务A回滚,事务B读到的就是脏数据
2、读提交
一个事务只会查询到其他事务已提交的数据,但是事务会读到已提交数据,会造成这个事务重复读数据时数据改变,不可重复读 破坏了一致性(update和delete)
A事务更新或者删除数据,提交后,B事务第一次查询和第二次查询数据不一致,也就是不可重复读
3、可重复读
a事务对自己未存在的数据多次读取,第一次不存在,第二次存在,是一致性(insert)
例子,事务A查询数据是7条,事务Binsert一条变成8条,提交。因为加的是行锁,不是表锁,然后A 事务更新表某个字段的所有值发现更新了8条数据,A事务幻读
4、串行化

redis单线程为什么执行速度这么快?
(1):纯内存操作,避免大量访问数据库,减少直接读取磁盘数据, 不受磁盘时间限制
(2):单线程操作,避免了不必要的上下文切换和竞争条件, 不存在多线程,不存在死锁
(3):采用了非阻塞I/O多路复用机制

redis数据底层
String
Hash 数组+链表 1.Reids的Hash采用链地址法来处理冲突
list 比如twitter的关注列表,粉丝列表
list的实现为一个双向链表,即可以支持反向查找和遍历
Set: value为null的HashMap,实际就是通过计算hash的方式来快速排重的
zset:内部使用HashMap和跳跃表(SkipList)来保证数据的存储和有序

redis事务
(1):Multi开启事务
(2):Exec执行事务块内命令
(3):Discard 取消事务
(4):Watch 监视一个或多个key,如果事务执行前key被改动,事务将打断

redis更新时数据丢失

redis哨兵****哨兵至少需要 3 个实例,来保证自己的健壮性。
集群监控:负责监控 Redis master 和 slave 进程是否正常工作。
消息通知:如果某个 Redis 实例有故障,那么哨兵负责发送消息作为报警通知给管理员。
故障转移:如果 master node 挂掉了,会自动转移到 slave node 上。
配置中心:如果故障转移发生了,通知 client 客户端新的 master 地址。

Redis 过期策略是:定期删除+走内存淘汰机制
noeviction: 当内存不足以容纳新写入数据时,新写入操作会报错,这个一般没人用吧,实在是太恶心了。
allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。
allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个
key,这个一般没人用吧,为啥要随机,肯定是把最近最少使用的 key 给干掉啊。
volatile-lru:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,移除最近最少使用的 key(这个一般不太合适)。
volatile-random:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。
volatile-ttl:当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。
缓存淘汰策略
(1):先进先出算
法(FIFO)
(2):最近使用最少Least Frequently Used(LFU)
(3):最长时间未被使用的Least Recently Used(LRU)
当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致LRU命中率急剧下降,缓存污染情况比较严重

redis过期key删除策略
(1):惰性删除,cpu友好,但是浪费cpu资源
(2):定时删除(不常用)
(3):定期删除,cpu友好,节省空间

zuul
zuul网关,负载均衡,分发请求,身份验证,流量监控 @EnableEurekaClient

LVS +nginx实现高可用的负载均衡
lvs工作在第4层,双机热备,几乎所有应用做负载均衡,抗负载能力强,仅做请求流量的分发
nginx HTTP和反向代理服务器,工作在第7层,占有内存少,并发能力强 公司是统一出口IP 对ip做

正向代理
在客户端(浏览器)配置代理服务器,通过代理服务器进行互联网访问
反向代理
把请求发送到反向代理服务器,由反向代理服务器去选择目标服务器获取数据后,在返回给客户端
此时反向代理服务器和目标服务器对外就是一个服务器,暴露的是代理服务器地址,隐藏了真实服务器IP地址

redis和数据库缓存不一致问题

由前端虚拟负载均衡器和后端真实服务器群组成;
请求发送给虚拟服务器后其根据包转发策略以及负载均衡调度算法转发给真实服务器
所谓四层(lvs,f5)就是基于IP+端口的负载均衡;七层(nginx)就是基于URL等应用层信息的负载均衡

缓存雪崩 同一时刻大量缓存失效;
处理方法:
如果是热点key,设置key永不失效;
(2):设置不同的缓存失效时间
(3):双层缓存策略C1为短期,C2为长期
(4):redsi和memcache缓存,请求->redis->memcache->db;
(1):定时更新缓存

缓存穿透 频繁查询不存在数据;
处理方法:
(1):查询结果为null仍然缓存这个null结果,设置不超过5分钟过期时间 避免DB数据为空也每次都进行数据库查询
(2):布隆过滤器,所有可能存在的数据映射到足够大的bitmap中 google布隆过滤器
布隆过滤器,只能往里边添加数据,而不能够删除数据,用布谷鸟过滤器

缓存击穿 并发访问热点key 缓存中某一时刻失效了,因而大量并发请求打到数据库上
1、redis分布式锁,并且必须有过期时间,不然锁住了结果进程掉线就没办法了,并且要原子操作,setex
2、如果是主从redis,在主redis加锁就好。如果请求能到从,其实已经分流了
3、如果一定都要加锁,用Redlock ,主从中超过半数加锁就可

不能加synconized锁,java锁只能锁住一个对象。

热点key出现造成集群访问量倾斜解决办法
(1):使用本地缓存
(2): 利用分片算法的特性,对key进行打散处理(给hot key加上前缀或者后缀,把一个hotkey 的数量变成 redis 实例个数N的倍数M,从而由访问一个 redis key 变成访问 N * M 个redis key)

如果突然机器掉电会怎样?
1、取决于aof日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。
2、要求性能还是每秒fsync 每秒写磁盘

Redis有哪些数据结构呀?

字符串String、字典Hash、列表List、集合Set、有序集合SortedSet。
HyperLogLog、Geo、Pub/Sub。

如果你还想加分,那你说还玩过Redis Module,像BloomFilter,RedisSearch,Redis-ML,这个时候面

避免缓存击穿的利器之BloomFilter

如果有大量的key需要设置同一时间过期,一般需要注意什么?

1、过期时间分散一些。
2、令牌桶或者消息队列或者负载均衡控制流量
3、二级缓存

电商首页经常会使用定时任务刷新缓存,可能大量的数据失效时间都十分集中,如果失效时间一样,又刚好在失效的时间点大量用户涌入,就有可能造成缓存雪崩

Redis分布式锁么,它是什么回事?

setnx 争抢锁,expire加过期时间释放

如果在setnx之后执行expire之前进程意外crash或者要重启维护了,那会怎么样?

set指令有非常复杂的参数,这个应该是可以同时把setnx和expire合成一条指令来用的!

假如Redis里面有1亿个key,其中有10w个key是以某个固定的已知的前缀开头的,如何将它们全部找出来?

使用keys指令可以扫出指定模式的key列表。

对方接着追问:

如果这个redis正在给线上的业务提供服务,那使用keys指令会有什么问题?

redis的单线程的 scan指令

这个时候你要回答redis关键的一个特性:redis的单线程的。keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

使用过Redis做异步队列么,你是怎么用的?

一般使用list结构作为队列,rpush生产消息,lpop消费消息。当lpop没有消息的时候,要适当sleep一会再重试。

如果对方追问可不可以不用sleep呢?

list还有个指令叫blpop,在没有消息的时候,它会阻塞住直到消息到来。

如果对方接着追问能不能生产一次消费多次呢?

使用pub/sub主题订阅者模式,可以实现 1:N 的消息队列。

如果对方继续追问 pub/su b有什么缺点?

在消费者下线的情况下,生产的消息会丢失,得使用专业的消息队列如RocketMQ等。

Redis如何实现延时队列?

使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

Redis是怎么持久化的?服务主从数据怎么交互的?

**RDB做镜像全量持久化,AOF做增量持久化。**因为RDB会耗费较长时间,不够实时,在停机的时候会导致大量丢失数据,所以需要AOF来配合使用。在redis实例重启时,会使用RDB持久化文件重新构建内存,再使用AOF重放近期的操作指令来实现完整恢复重启之前的状态。

这里很好理解,把RDB理解为一整个表全量的数据,AOF理解为每次操作的日志就好了,服务器重启的时候先把表的数据全部搞进去,但是他可能不完整,你再回放一下日志,数据不就完整了嘛。不过Redis本身的机制是 AOF持久化开启且存在AOF文件时,优先加载AOF文件;AOF关闭或者AOF文件不存在时,加载RDB文件;加载AOF/RDB文件城后,Redis启动成功; AOF/RDB文件存在错误时,Redis启动失败并打印错误信息

对方追问那如果突然机器掉电会怎样?

取决于AOF日志sync属性的配置,如果不要求性能,在每条写指令时都sync一下磁盘,就不会丢失数据。但是在高性能的要求下每次都sync是不现实的,一般都使用定时sync,比如1s1次,这个时候最多就会丢失1s的数据。

对方追问RDB的原理是什么?

你给出两个词汇就可以了**,fork和cow。fork是指redis通过创建子进程来进行RDB操作,cow指的是copy on write,**子进程创建后,父子进程共享数据段,父进程继续提供读写服务,写脏的页面数据会逐渐和子进程分离开来。

Pipeline有什么好处,为什么要用pipeline?

可以将多次IO往返的时间缩减为一次,前提是pipeline执行的指令之间没有因果相关性。使用redis-benchmark进行压测的时候可以发现影响redis的QPS峰值的一个重要因素是pipeline批次指令的数目。

Redis的同步机制了解么?

Redis可以使用主从同步,从从同步。第一次同步时,主节点做一次bgsave,并同时将后续修改操作记录到内存buffer,待完成后将RDB文件全量同步到复制节点,复制节点接受完成后将RDB镜像加载到内存。加载完成后,再通知主节点将期间修改的操作记录同步到复制节点进行重放就完成了同步过程。后续的增量数据通过AOF日志同步即可,有点类似数据库的binlog。

是否使用过Redis集群,集群的高可用怎么保证,集群的原理是什么?

Redis Sentinal着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

Redis Cluster着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

redis实现分布式锁,redission中分布式锁的添加
zookeeper实现分布式锁
setnx

分库分表

springBoot的配置如何自动加载的
springCloud微服务的几个组件,具体怎么使用
sql优化怎么查看执行计划
索引是否有必要加,索引b+数和hash,索引如果没有主键,怎么生成主键
主键自增序列

猜你喜欢

转载自blog.csdn.net/u010010600/article/details/109135004