1、异步处理
2、代码解耦
3、流量削峰
4、日志处理
优点:异步、解耦、削峰
缺点:系统可用性降低、系统复杂性提高、数据一致性问题
- 引入MQ后,MQ宕机导致业务系统受影响,必须保证MQ的高可用
- 引入MQ后,需要保证消息不丢失,保证不重复消费、消费的幂等性、顺序消费
1. RabbitMQ 的高可用性 RabbitMQ 有三种模式:单机模式、普通集群模式、镜像集群模式 - 单机模式:一台机器,无法保证高可用 - 普通集群模式:只同步queue的元数据,无高可用,主要提高吞吐量 - 镜像集群模式:同步queue的元数据,同步消息到多个实例上,queue全量数据,带宽压力大,扩容难 2. Kafka 的高可用性 - 由多个 broker 组成,每个 broker 是一个节点 - 创建一个 topic,这个 topic 可以划分为多个 partition - 每个 partition 可以存在于不同的 broker 上,每个 partition 就放一部分数据 - 每个 partition 在其他 broker 上存在副本,多个 partition 选举 leader 负责数据同步及对外提供服务 > 天然的分布式消息队列,就是说一个 topic 的数据,是分散放在多个机器上的,每个机器就放一部分数据4、如何保证消息不被重复消费?或者说,如何保证消息消费的幂等性?
所谓幂等性是指多次执行与执行一次的结果是一致的 - 出现幂等性的情况 1.生产者发出消息,MQ接收到消息,未成功返回 ack 给生产者,生产者重试再次发送消息,造成MQ接收到重复消息 即:生产者发送重复消息 2.消费者消费消息,消费者消费MQ的消息,消费者未成功返回 ack 给MQ,MQ再次将消息发送给其他消费者或当前消费者 即:消费者重复消费消息 - 解决办法 1.生产者发送重复消息:给消息设置全局唯一的消息ID,MQ保证不接受重复ID的消息 2.消费者重复消费消息:使用业务ID或消息ID,每次消费消息时用ID先判断该消息是否已消费(mysql或redis)5、如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题?
- 丢失消息的环节 1.生产者丢消息:消息发出,网络闪断,MQ未收到消息,消息丢失 2.MQ丢消息:MQ宕机,消息丢失 3.消费者丢消息:消费者从MQ获取到消息并返回ack,未来得及处理进程挂掉,消息丢失 - 解决办法 1.rabbitmq 1)生产者:①开启rabbitmq事务(耗性能) ②开启/confirm/i模式(ack/nack回调)生产者重试 2)MQ:开启rabbitmq的持久化,消息写入之后会持久化到磁盘 3)消费者:关闭rabbitmq自动ack,消息处理完手动 ack 确认 2.kafka 1)生产者:设置 acks=all,leader接收到消息,所有follower都同步到消息才认为成功,失败就重试 2)MQ:①replication.factor大于1,要求partition至少2个副本,②min.insync.replicas大于1,保证至少一 个follower与leader同步没掉队,③producer设置acks=all,④producer设置retries=MAX 3)消费者:关闭自动提交offset,在处理完之后自己手动提交offset6、如何保证消息的顺序性?
1.RabbitMQ保证消息顺序性 生产者消息按顺序发送到同一个queue中;一个消费者监听这个queue进行顺序消费或者单线程取消息不处理消息,在此 线程中对任务进行区分排序(queue中存在其他信息),然后开启多线程分类按序进行消费 - 其他消息并行处理 2.Kafka保证消息的顺序性 生产者指定key单线程写消息,所有相同的key的消息会被分发到同一个partition,并且有序;单线程消费或者单线程 取消息不处理消息,在此线程按key对任务进行区分排序(分区中还有其他key的消息),然后开启多线程进行分类消费7、大量消息在 MQ 里长时间积压,该如何解决?
- 问题本质:消费端出现问题了,不消费或消费速度慢 1)修复消费者的问题,保证其能正常消费消息,然后停掉现有的消费者 2)新建临时 topic,partition 或 queue 为原来的10到20倍的数量 3)写一个临时分发数据的消费者程序,这个程序主要是消费积压的信息不做耗时处理,直接将信息轮训写入到临时topic ,queue中 4)征用临时机器部署消费者程序,每一批消费者消费一个临时的queue中的数据 5)消费完积压数据后,恢复原有架构8、MQ 中的消息过期失效了怎么办?
- 问题本质:MQ设置了过期时间,积压超过一定时间会被清理掉 1)访问低峰后编写程序,找到丢失的那批数据,重新将这些消息写入到MQ中9、RabbitMQ 有哪些重要的角色?
生产者:消息的创建者,负责创建和推送数据到消息服务器 消费者:消息的接收方,用于处理数据和确认消息 代理:就是RabbitMQ本身,用于扮演快递的角色,本身并不生产消息10、RabbitMQ 有哪些重要的组件?
ConnectionFactory(连接管理器):应用程序与RabbitMQ之间建立连接的管理器
Channel(信道):消息推送使用的通道
Exchange(交换器):用于接受、分配消息
Queue(队列):用于存储生产者的消息
RoutingKey(路由键):生产者将消息发送给交换器的时候,会指定一个RoutingKey,用来指定这个消息的路由规则,
这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。
BindingKey(绑定键):用于把交换器的消息绑定到队列上
11、RabbitMQ 有几种广播类型?
direct(默认方式):最基础最简单的模式,发送方把消息发送给订阅方,如果有多个订阅者,默认采取轮询的方式进
行消息发送。
headers:与 direct 类似,只是性能很差,此类型几乎用不到。
fanout:分发模式,把消费分发给所有订阅者。
topic:匹配订阅模式,使用正则匹配到消息队列,能匹配到的都能接收到。
12、Kafka 可以脱离 zookeeper 单独使用吗?为什么?
kafka 不能脱离 zookeeper 单独使用 kafka 使用 zookeeper 管理和协调 kafka 的节点服务器13、Kafka 有几种数据保留的策略?
1)按照过期时间保留 2)按照存储的消息大小保留 > log.retention.hours=1 #超过1个小时就清理数据 > log.segment.bytes=5000 #数据量超过5000byte就清理数据 > log.cleanup.interval.mins=100 #指定日志每隔多久检查看是否可以被删除,默认1分钟 > log.retention.check.interval.ms=300 #文件大小检查的周期时间触发 log.cleanup.policy中设置的策略14、Kafka 的分区策略有哪些?
- kafka分区的本质就是为了解决数据的写入和消费时的负载均衡以及数据的容错 1)随机策略 2)轮询策略(kafka默认) 3)按照key的分区策略,Key-ordering 4)自定义分区策略,需要实现 org.apache.kafka.clients.producer.Partitioner 接口中的分区方法二、缓存 1、谈下你对 Redis 的了解?
1)Redis是一种基于键值对的NoSQL数据库(非关系型数据库);是一个key-value存储系统 2)高性能、可靠性 Redis将数据存储在内存中,读写性能高;Redis提供了 RDB和AOF持久化,可将内存数据存盘,避免断电数据丢失 3)支持多种数据类型,常见的如 string、list、hash、set、zset、bitmaps、hyperloglog、geo 4)应用场景广泛:常作为缓存使用,分布式锁、数据共享等2、Redis 一般都有哪些使用场景?
1、热点数据的缓存 2、限时业务的运用(TTL) 3、计数器相关问题(INCR INCRBY) 4、排行榜相关问题(ZSET) 5、分布式锁(SETNX + TTL)3、Redis 有哪些常见的功能?
1)基于内存的缓存 2)持久化 3)复制与哨兵 4)集群 5)LRU事件驱动 6)事务 7)发布订阅4、Redis 支持的数据类型有哪些?
1)String(字符类型) 2)Hash(散列类型) 3)List(列表类型) 4)Set(集合类型) 5)SortedSet(有序集合类型,简称zset) 6)Bitmap(位图) 7)HyperLogLog(统计) 8)GEO(地理)5、Redis 为什么是单线程的?
Redis 很快,因为基于内存的操作,CPU不是Redis的瓶颈,单线程容易实现,且CPU不会成为瓶颈,因此使用单线程6、Redis 为什么这么快?
1)完全基于内存 2)数据结构简单,对数据操作也简单,Redis中的数据结构是专门进行设计的 3)采用单线程,避免了不必要的上下文切换和竞争条件 4)使用多路I/O复用模型7、什么是缓存穿透?怎么解决?
- 问题:缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,
这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透
- 办法:
① 布隆过滤器:将所有存在的key哈希到一个足够大的bitmap中,那么查询存在的key一定会被处理(进入数据库查询)
然后进行缓存,布隆过滤器存在误判,即:查询不存在的key可能会被处理(hash函数的问题),几率会
比较小,使用布隆过滤器就需要接受一定的误判率
② 缓存空对象,缓存null值,并设置过期时间,一般过期时间设置的短一点(会存在短时间的数据一致性问题)
8、什么是缓存雪崩?该如何解决?
- 问题:缓存雪崩是指缓存中数据大批量到过期时间而查询数据量巨大,引起数据库压力过大甚至down机,和缓存击穿
不同的是,缓存击穿指并发查同一条数据,缓存雪崩是不同数据都过期了,很多数据都查不到从而查询数据库
- 办法:
① 针对不同的缓存key设置不同的过期时间
② 双缓存策略,双缓存设置不同的过期时间,外层缓存时间短于内层缓存时间
③ 热点数据不过期
④ 缓存方法加锁排队
9、 怎么保证缓存和数据库数据的一致性?
缓存淘汰 OR 缓存更新?
- 选择缓存淘汰:修改复杂数据时,缓存更新成本较高,推荐缓存淘汰
先淘汰缓存还是先更新数据库?
- 选择先淘汰缓存,再更新数据库:若先更新数据库,再淘汰缓存时失败,那么后续请求将得到脏数据,直至缓存过期
若先淘汰缓存,再更新数据库,即使数据库更新失败了,也只会再做一次缓存的动作
不会出现脏数据的情况,业务影响较小
- 延时双删策略
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库
出现了数据不一致问题,采用延时双删策略得以解决:
public void write(String key,Object data){
redisUtils.del(key);
db.update(data);
Thread.sleep(100);
redisUtils.del(key);
}
10、Redis 持久化有几种方式?
1)RDB持久化:将Reids在内存中的数据库记录定时dump到磁盘上 - dbfilename dump.rdb save 900 1 save 300 10 save 60 10000 rdbcompression yes rdbchecksum yes dir ./ 2)AOF(append only file)持久化:将Reids的操作日志以追加的方式写入文件 - appendonly yes appendfilename "appendonly.aof" appendfsync everysec 二者区别: - RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘,实际操作过程是fork一个子进程,先将数据集写 入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储 - AOF持久化以日志的形式记录服务器所处理的每一个增删改操作,查询操作不会记录,以文本的方式记录,可以打开 文件看到详细的操作记录,文件较大,对aof文件 rewrite11、Redis 怎么实现分布式锁?
- 分布式锁需要解决的问题 ① 互斥性:任意时刻只允许一个客户端拥有锁,不能同时多个客户端获取锁 ② 安全性:锁只能被持有该锁的用户删除,而不能被其他用户删除 ③ 死锁:客户端宕机等原因导致锁没有被删除,其他客户端无法获取锁 ④ 容错:部分节点宕机,客户端仍然能获取锁或者释放锁 - 单机锁到分布式锁过程 1)synchronized 锁或 Lock 锁,单机版锁,跨 JVM 无法解决超卖问题 2)取消单机锁,利用 Redis 的 SETNX + DEL 命令实现分布式加解锁 3)SETNX + DEL 如果中间出现异常,锁将无法释放,必须在 finally 代码块中释放锁 4)如果 Redis 所在机器宕机了,代码层面无法走到 finally 块,无法保证锁一定被释放,需要设置锁的过期时间 5)加锁和设置过期时间还需要保证原子性,SETNX 时设置 EXPIRE 6)加解锁只能是同一个客户端,SETNX 加锁时设置动态的value,解锁时对value进行判断,匹配才能进行解锁 7)判断与解锁也必须保证原子性,可使用lua脚本或事务 8)过期时间需要根据业务执行时间动态设置,需要有额外的看门狗watchDog监视并进行续期 9)Redis 集群环境下,值复制延时导致多个客户端同时加锁成功 -解决方法:RedLock 算法 -> 使用 Redisson > 解锁优化:redissonLock.isLocked() && redissonLock.isHeldByCurrentThread() 判断再解锁12、Redis 过期数据删除策略有哪些?
1)定时删除:立即删除过期的数据,对内存友好,对CPU不友好(拿时间换空间)
2)惰性删除:数据过期后不做处理,等下次访问该数据时发现已过期,将其删除并返回空值(拿空间换时间)
3)定期删除:折中方案,每隔一段时间执行一次删除过期数据操作,并通过限制删除操作执行的时长和频率来减少删除操
作对CPU时间的影响,做法为:周期性轮训 redis 库中的有实效性数据,采用随机抽取的策略,利用过期
数据占比的方式控制删除频度
> 惰性删除和定期删除都存在数据没有被抽到的情况,如果大量过期的数据没有被删除导致数据堆积,redis内存空间将
会被耗尽从而导致OOM,因此出现兜底方案:Redis淘汰策略
13、Redis 淘汰策略有哪些?
配置参数:maxmemory-policy 1)noeviction:不会驱逐任何key 2)allkeys-lru:对所有key使用LRU算法进行删除(最近最少使用:距离上一次访问时间最长) 3)volatile-lru:对所有设置了过期时间的key使用LRU算法进行删除 4)allkeys-random:对所有key随机删除 5)volatile-random:对所有设置了过期时间的key随机删除 6)volatile-ttl:删除马上要过期的key 7)allkeys-lfu:对所有key使用LFU算法进行删除(最不经常使用:最近时间使用次数很少) 8)volatile-lfu:对所有设置了过期时间的key使用LFU算法进行删除 两个维度:过期键 与 所有键 四个方面:lru、lfu、random、ttl14、Redis 常见性能问题和解决方案?
1)Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件 2)如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次 3)为了主从复制的速度和连接的稳定性, Master 和 Slave 最好在同一个局域网内 4)尽量避免在压力很大的主库上增加从库 5)主从复制不要用图状结构,用单向链表结构更为稳定 Master <- Slave1 <- Slave2 <-Slave3…15、Redis 各种数据类型的应用场景?
1)string - 商品编号、订单号采用 INCR 命令生成 - 文章阅读量、点赞数和在看数 INCR 命令生成 2)hash 购物车: - 新增商品:hset shopcar:uid1024 334488 1 - 新增商品:hset shopcar:uid1024 334477 1 - 增加商品:hincrby shopcar:uid1024 334477 1 - 商品总数:hlen shopcar:uid1024 - 全部商品:hgetall shopcar:uid1024 3)list 微信文章订阅公众号 - 推送文章 LPUSH likearticle:onebyId 666 888 - 订阅的全部文章一次显示10条 LPUSH likearticle:onebyId 0 10 4)set 微信抽奖小程序 - 点击了立即参与按钮,则执行 sadd key useId - 多少人参与了抽奖:SCARD key - 随机抽奖2个人,元素不删除:SRANDMEMBER key 2 - 随机抽奖3个人,元素会删除:SPOP key 3 朋友圈点赞 - 新增点赞:SADD pub:msgID 点赞用户ID1 点赞用户ID2 - 取消点赞:SREM pub:msgID 点赞用户ID - 展现所有点赞过的用户:SMEMBERS pub:msgID - 点赞用户数统计,就是常见的点赞红色数字:SCARD pub.msgID - 判断某个朋友是否对楼主点赞过:SISMEMBER pub:msgID 用户ID1 共同好友 - 共同关注的好友:SINTER 我关注的人 Ta关注的人 可能认识的人 - QQ共同好友:SDIFF 我的好友 Ta的好友 5)zset 商品销售对商品进行排序显示 - 添加2个商品:ZADD goods:sellsort 9 1001 15 1002 - 商品销量增加:ZINCRBY goods:sellsort 2 1001 - 商品销量前10名:ZRANGE goods:sellsort 0 10 WITHSCORES 抖音热搜 - 点击视频增加播放量:ZINCRBY hotvcr:20200919 15 八佰 2 花木兰 - 展示当日排行前10条:ZREVRANGE hotvcr:20200919 0 9 WITHSCORES



