我们接着上期的~
中间件
Kafka相关
❝问:怎么保证kafka消息的顺序性,kafka消费端数据不丢失❞
「顺序性:」
如上图,分生产者顺序发送,和消费者顺序消费。
对于顺序发送,我们需要知道,当数据写入一个partition
时,可以保证顺序性,所以如果有一批数据需要保证顺序,那么给这批数据指定一个key即可。
public ListenableFuture<SendResult<K, V>> send(String topic, K key, @Nullable V data) {
ProducerRecord<K, V> producerRecord = new ProducerRecord(topic, key, data);
return this.doSend(producerRecord);
}
如上代码,调用KafkaTemplate
的这个send方法。
对于顺序消费,需要将一个partition的数据发送到一个暂存队列中,然后再将这个队列仍给一个线程,这样保证顺序的数据是一个线程获取的。
「消费数据不丢失:」
消费数据丢失情况可能是,消费者已经拿到数据,将offset提交给了kafka或者zookeeper,但是这个数据还没有实际使用,比如保存到数据库中。这个时候消费者服务挂掉。当消费者服务重启时。导致数据没有存入库中,但offset显示已经消费,消费者再次去消费,拿不到数据了。关于解决办法:将自动提交offset改为手动提交,只有业务正真结束才提交offset。
❝问:Kafka实现分布式事务❞
如果服务A调用服务B,那么服务A就是生产者,服务B就是消费者。
如上图,不一定是kafka,消息中间件都可以处理分布式事务问题。在消息中间件处理数据过程中,并不需要处理事务回滚问题。我们需要保证两件事:
代码语言:javascript复制1.生产者数据发到kafka一定成功
2.消费者从kafka消费数据一定成功
如何让保证数据百分百发送到kafka?将生产的数据存一份到数据做兜底,如果生产者收不到kafka的回执,采用重试机制,将数据库中的数据发送到kafka中,直到成功,修改数据库中的数据发送状态。
由于这种方式是异步的,生产者和消费者是非耦合的,所以消费者自行从kafka中消费数据。消费成功,消费者会提交偏移量给Zookeeper(新版本是消费者将偏移量提交给kafka了)。
❝问:kafka为什么快,以及如何选型消息队列❞
「为什么快:」
- 顺序读写
- NIO多路复用模型,减少系统调用
- 零拷贝,减少系统调用
「选型:」
特性 | ActiveMQ | RabbitMQ | RocketMQ | Kafka |
---|---|---|---|---|
单机吞吐量 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 万级,吞吐量比RocketMQ和Kafka要低了一个数量级 | 10万级,RocketMQ也是可以支撑高吞吐的一种MQ | 10万级别,这是kafka最大的优点,就是吞吐量高。 一般配合大数据类的系统来进行实时数据计算、日志采集等场景 |
topic数量对吞吐量的影响 | topic可以达到几百,几千个的级别,吞吐量会有较小幅度的下降 这是RocketMQ的一大优势,在同等机器下,可以支撑大量的topic | topic从几十个到几百个的时候,吞吐量会大幅度下降 所以在同等机器下,kafka尽量保证topic数量不要过多。如果要支撑大规模topic,需要增加更多的机器资源 | ||
时效性 | ms级 | 微秒级,这是rabbitmq的一大特点,延迟是最低的 | ms级 | 延迟在ms级以内 |
可用性 | 高,基于主从架构实现高可用性 | 高,基于主从架构实现高可用性 | 非常高,分布式架构 | 非常高,kafka是分布式的,一个数据多个副本,少数机器宕机,不会丢失数据,不会导致不可用 |
消息可靠性 | 有较低的概率丢失数据 | 经过参数优化配置,可以做到0丢失 | 经过参数优化配置,消息可以做到0丢失 | |
功能支持 | MQ领域的功能极其完备 | 基于erlang开发,所以并发能力很强,性能极其好,延时很低 | MQ功能较为完善,还是分布式的,扩展性好 | 功能较为简单,主要支持简单的MQ功能,在大数据领域的实时计算以及日志采集被大规模使用,是事实上的标准 |
优劣势总结 | 非常成熟,功能强大,在业内大量的公司以及项目中都有应用 偶尔会有较低概率丢失消息 而且现在社区以及国内应用都越来越少,官方社区现在对ActiveMQ 5.x维护越来越少,几个月才发布一个版本 而且确实主要是基于解耦和异步来用的,较少在大规模吞吐的场景中使用 | erlang语言开发,性能极其好,延时很低; 吞吐量到万级,MQ功能比较完备 而且开源提供的管理界面非常棒,用起来很好用 社区相对比较活跃,几乎每个月都发布几个版本分 在国内一些互联网公司近几年用rabbitmq也比较多一些 但是问题也是显而易见的,RabbitMQ确实吞吐量会低一些,这是因为他做的实现机制比较重。 而且erlang开发,国内有几个公司有实力做erlang源码级别的研究和定制?如果说你没这个实力的话,确实偶尔会有一些问题,你很难去看懂源码,你公司对这个东西的掌控很弱,基本职能依赖于开源社区的快速维护和修复bug。 而且rabbitmq集群动态扩展会很麻烦,不过这个我觉得还好。其实主要是erlang语言本身带来的问题。很难读源码,很难定制和掌控。 | 接口简单易用,而且毕竟在阿里大规模应用过,有阿里品牌保障 日处理消息上百亿之多,可以做到大规模吞吐,性能也非常好,分布式扩展也很方便,社区维护还可以,可靠性和可用性都是ok的,还可以支撑大规模的topic数量,支持复杂MQ业务场景 而且一个很大的优势在于,阿里出品都是java系的,我们可以自己阅读源码,定制自己公司的MQ,可以掌控 社区活跃度相对较为一般,不过也还可以,文档相对来说简单一些,然后接口这块不是按照标准JMS规范走的有些系统要迁移需要修改大量代码 还有就是阿里出台的技术,你得做好这个技术万一被抛弃,社区黄掉的风险,那如果你们公司有技术实力我觉得用RocketMQ挺好的 | kafka的特点其实很明显,就是仅仅提供较少的核心功能,但是提供超高的吞吐量,ms级的延迟,极高的可用性以及可靠性,而且分布式可以任意扩展 同时kafka最好是支撑较少的topic数量即可,保证其超高吞吐量 而且kafka唯一的一点劣势是有可能消息重复消费,那么对数据准确性会造成极其轻微的影响,在大数据领域中以及日志采集中,这点轻微影响可以忽略 这个特性天然适合大数据实时计算以及日志收集 |
总结:ActiveMQ较老的系统会使用,现在社区不活跃,不推荐使用;
RabbitMQ比较稳定,吞吐量万级别,适合小公司,小业务系统使用;
RocketMQ吞吐量10万级别,适合高并发,阿里开源,社区活跃;
Kafka吞吐量10万级别,适合日志采集,大数据实时计算。非常稳定,社区活跃。
Redis相关
❝问:Redis发布订阅使用场景❞
见文章:Redis发布订阅
代码语言:javascript复制❝问:Redis分布式锁底层怎么实现❞
1. setnx 过期时间 用lua脚本保证原子性
2. 锁持有心跳检测(防止未解锁,锁失效问题)
3. 线程自选获取锁
Redisson框架已有实现
❝问:怎么处理缓存雪崩,缓存穿透的场景❞
见文章:Redis进阶
❝问:限流操作❞
见文章:用Redis实现接口限流
❝问:分布式缓存与JVM的缓存区别❞
JVM缓存如List,Map。在单机内有效。如果集群部署,就会使缓存失效,需要全局的缓存,所以需要使用Redis等缓存中间件。
❝问:接口幂等如何实现❞
Github地址:https://github.com/lvshen9/demo-lvshen/tree/master/src/main/java/com/lvshen/demo/autoidempotent
❝问:Redis分布式锁主从同步问题❞
RedLock算法解决主从同步问题。
❝问:Session共享❞
Redis实现,已经有成熟的API,可集成SpringBoot。
jar包 引入
代码语言:javascript复制<!--session redis-->
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
直接使用,Session已经存入Redis中了。
代码语言:javascript复制@RequestMapping(value = "/testSession",method = RequestMethod.GET)
public String testSession(HttpSession session, Model model) {
List<Member> members = memberService.listMember();
System.out.println("sessionId------>" session.getId());
model.addAttribute("member", JSON.toJSONString(members));
session.setAttribute("member",JSON.toJSONString(members));
return "hello world";
}
Zookeeper
❝问:Zookeeper使用场景❞
见文章:手写Zookeeper分布式锁
❝问:Zookeeper底层结构,选举原理,最小集群数❞
文章来源:https://www.cnblogs.com/wuzhenzhao/p/9983231.html
Zookeeper本质还是一个存储容器,以节点的形式存储数据。
在 ZooKeeper中,每个数据节点都是有生命周期的,其生命周期的长短取决于数据节点的节点类型。在 ZooKeeper中,节点类型可以分为持久节点(PERSISTENT)、临时节点(EPHEMERAL)和顺序节点(SEQUENTIAL)三大类,具体在节点创建过程中,通过组合使用,可以生成以下四种组合型节点类型:
- 「持久节点(PERSISTENT)」:持久节点是 ZooKeeper中最常见的一种节点类型。所谓持久节点,是指该数据节点被创建后,就会一直存在于 ZooKeeper服务器上,直到有删除操作来主动清除这个节点。
- 「持久顺序节点(PERSISTENT SEQUENTIAL)」:持久顺序节点的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在ZooKeeper中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录下每个子节点创建的先后顺序。基于这个顺序特性,在创建子节点的时候,可以设置这个标记,那么在创建节点过程中, ZooKeeper会自动为给定节点名加上一个数字后缀,作为一个新的、完整的节点名。另外需要注意的是,这个数字后缀的上限是整型的最大值。
- 「临时节点(EPHEMERAL)」:和持久节点不同的是,临时节点的生命周期和客户端的会话绑定在一起,也就是说,如果客户端会话失效,那么这个节点就会被自动清理掉。注意,这里提到的是客户端会话失效,而非TCP连接断开。另外, ZooKeeper规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。
- 「临时顺序节点(EPHEMERAL SEQUENTIAL)」:临时顺序节点的基本特性和临时节点也是一致的,同样是在临时节点的基础上,添加了顺序的特性。
「刚启动时的选举:」
Leader 选举会分两个过程启动的时候的 leader 选举、 leader 崩溃的时候的的选举服务器启动时的 leader 选举每个节点启动的时候状态都是 LOOKING,处于观望状态,接下来就开始进行选主流程进行 Leader 选举,至少需要两台机器,我们选取 3 台机器组成的服务器集群为例。在集群初始化阶段,当有一台服务器 Server1 启动时,它本身是无法进行和完成 Leader 选举,当第二台服务器 Server2 启动时,这个时候两台机器可以相互通信,每台机器都试图找到 Leader,于是进入 Leader 选举过程。选举过程如下:
(1) 每个 Server 发出一个投票。由于是初始情况,Server1和 Server2 都会将自己作为 Leader 服务器来进行投票,每次投票会包含所推举的服务器的 myid 和 ZXID、epoch,使用(myid, ZXID,epoch)来表示,此时 Server1的投票为(1, 0),Server2 的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
(2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票(epoch)、是否来自LOOKING状态的服务器。
(3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行 PK,PK 规则如下
i. 优先检查 ZXID。ZXID 比较大的服务器优先作为Leader
ii. 如果 ZXID 相同,那么就比较 myid。myid 较大的服务器作为 Leader 服务器。
对于 Server1 而言,它的投票是(1, 0),接收 Server2的投票为(2, 0),首先会比较两者的 ZXID,均为 0,再比较 myid,此时 Server2 的 myid 最大,于是更新自己的投票为(2, 0),然后重新投票,对于 Server2 而言,它不需要更新自己的投票,只是再次向集群中所有机器发出上一次投票信息即可。
(4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于 Server1、Server2 而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出了 Leader。
(5) 改变服务器状态。一旦确定了 Leader,每个服务器就会更新自己的状态,如果是 Follower,那么就变更为FOLLOWING,如果是 Leader,就变更为 LEADING。
「运行时的选举:」
当集群中的 leader 服务器出现宕机或者不可用的情况时,那么整个集群将无法对外提供服务,而是进入新一轮的Leader 选举,服务器运行期间的 Leader 选举和启动时期的 Leader 选举基本过程是一致的。
(1) 变更状态。Leader 挂后,余下的非 Observer 服务器都会将自己的服务器状态变更为 LOOKING,然后开始进入 Leader 选举过程。
(2) 每个 Server 会发出一个投票。在运行期间,每个服务器上的 ZXID 可能不同,此时假定 Server1 的 ZXID 为123,Server3的ZXID为122;在第一轮投票中,Server1和 Server3 都会投自己,产生投票(1, 123),(3, 122),然后各自将投票发送给集群中所有机器。接收来自各个服务器的投票。与启动时过程相同。
(3) 处理投票。与启动时过程相同,此时,Server1 将会成为 Leader。
(4) 统计投票。与启动时过程相同。
(5) 改变服务器的状态。与启动时过程相同
❝问:ElasticSearch有用怎创建索引的么❞
这里我们不说原理,我们来说Java API怎么创建索引。
举个例子:
代码语言:javascript复制/**
* 创建索引库
*
* @author lvshen
* @date 2020年08月01日
*
* 需求:创建一个索引库为:msg消息队列,类型为:tweet,id为1
* 索引库的名称必须为小写
* @throws IOException
*/
@Test
public void addIndex() throws IOException {
IndexResponse response = client.prepareIndex("msg", "tweet", "1").setSource(XContentFactory.jsonBuilder()
.startObject().field("userName", "lvshen")
.field("sendDate", new Date())
.field("msg", "Lvshen的技术小屋")
.endObject()).get();
logger.info("索引名称:" response.getIndex() "n类型:" response.getType()
"n文档ID:" response.getId() "n当前实例状态:" response.status());
}
秒杀设计
❝问:如何设计一个秒杀系统❞
这个是面试阿里的时候问的一个问题。问题比较泛。要考虑
代码语言:javascript复制并发情况下数据库能不能扛住,Mysql 最高并发估计在万级别。如果秒杀用户上千万以上,要考虑分库分表,读写分离,使用缓存,还有使用消息中间件在高峰时期削峰填谷(并发串行化)。也要考虑服务容错,服务降级,以实现系统高可用
考虑线程安全,扣减库存,线程安全问题,防止多扣,可使用数据库状态机思想,但要考虑批量修改时死锁问题。可使用Redis原子性,做库存扣减。
下面总结下需要注意的点:
1.不能超卖(直接的经济损失,平台信誉受损
2.防黑产、黄牛(阿里月饼门):机器的请求速度比人的手速快太多了
3.瞬间爆发的高流量
- 典型的读多写少的场景(cache缓存)
- 页面静态化,利用cdn服务器缓存前端文件
- 按钮置灰3秒、(利用风控规则过滤掉非法用户)
- 接口层可以做开关限流(一旦抢购结束则直接返回失败)
- 堆机器,搭建集群利用nginx做负载均衡
- 热点隔离增加资源有限放流(熔断)多次请求合并为一次
4.尽量把请求拦截在上层,Mysql单机读能力为5k,写能力为3k。redis单机读能力最高可达10w,写能力能达到3-5W。
5.预热,运营人员提前将数据写入Redis。
秒杀流程图:
代码语言:javascript复制graph LR
A[秒杀开始] --> B[用户点击秒杀链接]
B --> C[抢购资格]
C --> | 否| D>结束并且拉黑]
C --> | 是 | E[库存是否足够]
E --> | 是 | F>生成订单]
E --> | 否 | G>活动结束]
G --> H{修改Redis的活动状态}
文章推荐:https://mp.weixin.qq.com/s/Q8dWP5c0TJH8fqQdslSTKg
未完待续