Java整体知识架构详解-之中间件深入分析
2019-07-23 10:48:22 8 举报
AI智能生成
对中间件进行更深层次的分析应用,也是面试的知识点
作者其他创作
大纲/内容
Redis
KV模式
这种模式一般用来缓存用户信息,会话信息,商品信息等,通过设置过期时间,尽量保证用户一次会话内一直可以使用缓存数
打开缓存模式指令 config set maxmemory 20gb,当内存达到20gb时,Redis会进行淘汰策略
Redis淘汰策略,我前面已经有过介绍,这里就给个链接把
通常线上采用的策略为allkeys-lru或volatile-lru居多
特别注意的是,就算是最近最少使用淘汰策略也不是严格遵循在全部符合条件的数据中去淘汰的,它会取一部分数据,采用该策略淘汰,当内存到临界值再取一部分淘汰;原因当然很重要的点就是遍历全部数据太消耗cpu资源了,如果数据量很大的时候,严格遵循则可能花很长时间来淘汰数据
分布式锁
这个在进阶篇二的时候已经提到了,建议使用Redisson
自己写的设置过期时间如果步骤分开不是原子性的,可能导致死锁
延时队列
问题
Redis本身提供了消息队列的功能,当遇到锁冲突,此时该采取何种策略
解决方法
方式一:循环等待,由于redis是单线程的特性,循环等待可能会导致QPS的压力,浪费资源且增加后续请求响应时间
方式二:sleep一会再试,这样线程资源同样占用,并不能改变现状
方式三:直接放弃该数据,通知前端请稍后再试,这也是种方案,但影响用户体验
方式四:这里可以使用延时队列,也就是遇到锁冲突的时候,把消息放回延时队列,过一会再处理,redis本身没有该功能需要自己实现;当然专业的消息队列中间件比如Rabbitmq等也有这种功能
说明
延时队列这种方式,我认为如果自己的系统本身有用到RabbitMQ这种专业消息队列就没必要用Redis的队列,除非系统刚好只用到了Redis,为了节省部署MQ的资源,那么可以尝试用Redis的
定时任务
说明
和Quartz类似,Quartz是通过竞争数据库锁来确保只有一个定时任务执行,需要依赖机器时间一致,可以通过增加数据库锁的时间来缓解该问题
用Redis来竞争锁是同样的道理,相比用数据库似乎redis更简便一点;实际上只要能实现分布式锁功能的中间件,比如zookeeper都可以用来实现分布式的定时任务
开源实现
可以参考或直接拿来用
频率控制
作用
用以像贴吧评论的频率控制,限制用户多长时间只能评论多少,防止垃圾广告刷屏
说明
通过Redis中zset队列时间片中数量判断,来限制用户行为
服务发现
说明
想想看Redis是否能做服务发现,通常我们的服务发现都是用Eureka,zookeeper,nacos,consul等来做的,实际上Redis也可以实现
需求
服务注册
用zset来保存服务注册的信息和心跳时间
如何保障服务异常终止移除服务
额外线程执行清理工作
如何通知消费者列表变更
当服务列表变更时,递增版本号。消费者通过轮询版本号的变化来重加载服务列表。
增加一个全局版本号,当任意的服务列表版本号发生变更时,递增全局版本号。这样在正常情况下消费者只需要轮询全局版本号就可以了
当全局版本号发生变更时再挨个比对依赖的服务列表的子版本号,然后加载有变更的服务列表。
需求
服务注册
用zset来保存服务注册的信息和心跳时间
服务注册
用zset来保存服务注册的信息和心跳时间
用zset来保存服务注册的信息和心跳时间
如何保障服务异常终止移除服务
额外线程执行清理工作
如何通知消费者列表变更
当服务列表变更时,递增版本号。消费者通过轮询版本号的变化来重加载服务列表。
增加一个全局版本号,当任意的服务列表版本号发生变更时,递增全局版本号。这样在正常情况下消费者只需要轮询全局版本号就可以了
当全局版本号发生变更时再挨个比对依赖的服务列表的子版本号,然后加载有变更的服务列表。
位图
场景
比如用户签到,签到状态有三种,未签到,已签到,补签,而每天每个用户都有,如果将其用zset存放到redis,用户量一多,redis就撑不住了,所以有没有其它好办法存储用户签到?
方案
这时想到了位图,通过位图可以高效存储打卡记录
但位图也同样可能造成资源浪费,比如只有最后一位是1,其它都是0,这样实际只需要存最后一位就够了,这时可以考虑咆哮位图
模糊计数
Redis还有个存储类型HyperLogLog,可用于模糊计数,它有一定的误差,但占用空间很小,大概12k,可用于实时统计数量
如果需要准确计数,则可用到前面讲的咆哮位图
布隆过滤器
作用
用来判断数据是否存在,它可以100%准确判断数据不存在,但有一定概率判断错误数据存在
它可以存储大量的数据,比如一个用户id要64字节,用布隆过滤器只需要1个字节多点
这样的特性可以用来有效防止缓存穿透,比如用户状态查询,请求用户id参数通过布隆过滤器查询,如果不存在那么数据库肯定不存在,就不需要查数据库了
RabbitMQ
概述
MQ是我们常用到的消息中间件,用来传递消息,但为什么要用MQ,它又有什么问题?
作用
1. 异步处理
异步本质上是为了加速响应速度,比如A系统需要调用系统BCD,每个都耗时1s,如果串行执行则耗时3秒,而用MQ异步调用,则耗时为调用mq的耗时,比如20ms
2. 解耦
说明
我们常常说解耦,用MQ的第一反应很可能也是解耦,但什么场景该用,很多人都是没底的
解释
这里我结合一种需求来讲解,我们会遇到一种情况,A系统通过调用相同的接口把数据同步到BC系统,之后又多了D系统也需要同步,此时就需要改动A系统添加请求接口,之后可能又会有EF系统,或者突然B系统不需要调用该接口了,这样改写是不是很麻烦?此时如果用MQ,消息发到了中间件,其它系统有需要时再订阅就可以很方便了,此时A系统不需要随类似需求而改变
提醒
通常这样讲一遍也很少有人能记住,那我就提醒下各位切身利益者,如果你是A系统的维护者,不用类似的解耦方法,那么之后只要其他系统有需要你这个数据的需求,你就要改,不要说不就是一个接口吗,改改很简单;当这个接口是很久以前写的,你连业务都快忘了的时候,提出要改这个接口的需求,那你可能还要重新去梳理业务,看看这样写是否对其它业务产生影响,一想就头疼;所以对很久没改动的系统,有需求了我们通常也是尽量不改动原系统的
3. 削峰填谷
解释
我们有时候会面临接口在某个时间段大量请求的情况,此时可能导致系统奔溃,此时使用MQ可以把请求数据保存起来,那么订阅者就可以根据自己的消费能力,比如1000QPS,匀速进行消费,此时有大量数据堆积,那么这个速率可能维持很长一段时间。此时的消费速率曲线就又一开始突然增加到顶点然后奔溃,转成速率随时间到达一定高度维持平衡,持续一段时间再慢慢下降
问题
1. 异步处理的问题:必须是响应结果不需要立刻返回的接口或者保证响应结果正确性,比如订单的插入,如果能保证订单插入必定成功,那可以立即返回成功
2. 解耦的问题:看看解耦有什么问题?首先你多加了个中间件,就可能降低了系统的可靠性,你需要多做其它操作来保证系统可靠性,这个问题本质就是由异步引起的,你不能得到其它系统的返回结果,也就是说A系统不知道其它系统有没有收到我的数据;这个问题由MQ来保证,后续会讲
3. 削峰填谷的问题:这个同样,虽然能一定程度上保证系统不会因突然的大流量奔溃,但仅限在不需要立即得到处理结果,或结果可保证的情况下使用
缺点
系统可用性降低
多加一个中间件,带来的问题是如果MQ挂了怎么办,MQ挂了数据就没了,系统就不可运行了
系统的复杂度提升
引入MQ就需要考虑消息重复消费,消息丢失,消息的顺序问题等等,为解决这些问题可能又要引入新的机制
消息的一致性问题
这个问题就是我前面说的,A系统下了订单如果用MQ,表示下单一定是成功了,此时订单如果还没被处理,还在MQ中排队,我们必须要保证它被B系统正确处理了,如果B系统最终订单处理错误,那么AB系统的订单状态就不一致了,这就需要额外的操作来保证
MQ特性
如何保证消息不丢失?
生产者保证
比如RabbitMQ,提供了confirm机制,也就是说消息由系统A发送到MQ,MQ接收到后会返回个ack告诉A,该消息收到了
这里有些人可能对这个ack会理解错误,以为是消费者收到消息后返回的结果,因为自己测试往往收到消息很快,这个MQ中转过程往往感受不到,这里特地提醒下,加深印象
MQ保证
RabbitMQ提供了一个持久化的机制,也就是接收到消息存放到磁盘,此时哪怕是宕机了,在恢复之后仍旧会继续消费
读到这里我想很多人会和我有同样的疑惑,那宕机之后生产者生产的数据怎么办,还能找回来?还是说用其它机制保证?别急下面会说到
消费者保证
消费者B,同样有个ack机制来保证消费,当消费者收到MQ的消息后,默认会立即返回ack表示收到消息,但为了保证消息的正确消费,避免消费过程中宕机导致的消息丢失,我们可以进行手动的ack提交,也就是在完成消费后返回ack,这样就保证了消费者可靠
如何保证MQ的高可用性?
说明
高可用性就表示了当机器宕机,是否能保证系统的正常工作,这里用RabbitMQ的三种模式来说明
模式
单机模式
单机模式通常用来测试使用,该模式机器宕机MQ就不可用了
普通集群模式
何为普通集群,事实上它没有做到一台机器宕机保证系统可用,该做法主要是为了提高吞吐量,比如ABC三台集群,分别存放了queue-1,,queue-2,queue-3队列,此时请求发送到机器B,请求队列queue-1,那么机器B会把请求转发到机器A。
缺点
此做法也会导致单实例的瓶颈
镜像集群模式
此做法才是真正的高可用模式,它所创建的queue无论元数据还是消息都会存放在多个实例上,这样无论哪台机器宕机都能够提供服务;
缺点
性能开销比较大,消息同步所有机器,会造成带宽压力和消耗
扩展性低,同样无法解决单个queue消息量特别大的情况,无法线性拓展
RocketMQ
说明
已经讲了RabbitMQ,为什么特地讲下这个?有两点原因,第一当然是它是阿里巴巴的啊,你如果对阿里巴巴有一点想法就不能不了解这个o(╥﹏╥)o,第二就是很少有MQ带有的功能分布式事务,用来保证消息的最终一致性
特色
分布式事务
流程图
原理流程
1. 生产者向MQ发送half消息(half消息指没有被确认的消息,当half消息再次被确认时才会正式投递)
2. half消息发送后,MQ服务器返回确认给生产者
3. 生产者进行本地事务执行提交
4. 本地事务执行结果发送给MQ服务器,发送提交或回滚
5. 此时如果因为网络问题或者生产者突然宕机,导致MQ服务器没有收到确认消息,则MQ服务器在一定时间后将会向每个生产者发送回查消息以获取事务状态
6. 回查生产者本地事务状态
7. 生产者根据本地事务状态发送提交或回滚消息
8. MQ服务器将丢弃回滚消息,对二次确认的消息将进行投递消费者进行消费
总结
可以看出,所谓分布式事务只是保证了生产者本地事务和发送消息的原子性,而消费者消费MQ的消息是否成功,通过重试机制,默认重试16次后仍旧失败,则需要人工介入
提醒
从RocketMQ 3.0.8 之后 到 4.3.0 之前,在这期间的版本均不支持分布式事务消息。包括这在期间使用比较广泛的3.2.6 就是不支持分布式事务消息。所以现在如果要使用分布式事务,建议用4.3.0或之后的版本
0 条评论
下一页