这篇3万字的Java后端面试总结,面试官看了瑟瑟发抖(二)

2022-05-05 16:56:14 浏览数 (1)

我们接着上期的~

中间件

Kafka相关

❝问:怎么保证kafka消息的顺序性,kafka消费端数据不丢失❞

「顺序性:」

如上图,分生产者顺序发送,和消费者顺序消费。

对于顺序发送,我们需要知道,当数据写入一个partition时,可以保证顺序性,所以如果有一批数据需要保证顺序,那么给这批数据指定一个key即可。

代码语言:javascript复制
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发布订阅

❝问:Redis分布式锁底层怎么实现❞

代码语言:javascript复制
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)三大类,具体在节点创建过程中,通过组合使用,可以生成以下四种组合型节点类型:

  1. 「持久节点(PERSISTENT)」:持久节点是 ZooKeeper中最常见的一种节点类型。所谓持久节点,是指该数据节点被创建后,就会一直存在于 ZooKeeper服务器上,直到有删除操作来主动清除这个节点。
  2. 「持久顺序节点(PERSISTENT SEQUENTIAL)」:持久顺序节点的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在ZooKeeper中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录下每个子节点创建的先后顺序。基于这个顺序特性,在创建子节点的时候,可以设置这个标记,那么在创建节点过程中, ZooKeeper会自动为给定节点名加上一个数字后缀,作为一个新的、完整的节点名。另外需要注意的是,这个数字后缀的上限是整型的最大值。
  3. 「临时节点(EPHEMERAL)」:和持久节点不同的是,临时节点的生命周期和客户端的会话绑定在一起,也就是说,如果客户端会话失效,那么这个节点就会被自动清理掉。注意,这里提到的是客户端会话失效,而非TCP连接断开。另外, ZooKeeper规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。
  4. 「临时顺序节点(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

未完待续

0 人点赞