# 美团日常实习一面
# Redis
# 了解Redis分布式的部署方法吗?
- 主从复制:一个主节点负责写操作,其余从节点从通过复制主节点的数据进行读操作。如果主节点挂了需要手动将其他从节点升级,中间可能出现数据丢失。
- 哨兵模式:在主从复制节点的基础上增加了哨兵节点,负责检查主节点是否正常,并自动进行主从切换。仍旧可能会数据丢失和存在上限问题。
- 集群模式:整个集群由多个节点组成,数据被分片存储在多个主节点上,每个主节点都有一个或多个从节点,支持动态扩容和监控并进行主从切换的能力,可以解决Redis分布式需求。不过只能使用0号数据库。
# 介绍一下主从集群搭建?
- 搭建主从集群可以分为配置和启动两个步骤。配置主节点时,需要修改端口,开启后台运行,设置数据存储目录后启动,配置从节点需要通过replicaof指定主节点地址和端口,开启从节点只读后启动;启动完成后,连接主节点使用info replication可以看到从节点信息,连接从节点可以看到从节点角色slave,如果能从主节点写入数据并从从节点读取则完成了基础的主从集群搭建。
# Redis的淘汰策略?
- Redis的淘汰策略一共有八种,可以分为不进行数据淘汰和进行数据淘汰两类。不进行数据淘汰的策略noevict,进行数据淘汰的策略有lru、lfu、random、ttl几种。
- no-eviction:表示当运行内存超过最大设置内存时,不淘汰数据,同时不再提供服务,返回错误。
- volatile-random:随机淘汰设置了过期时间的任意键值。
- allkeys-random:随机淘汰任意键值。
- volatile-lru:淘汰设置了过期时间的键值中最久未使用的键值。
- allkeys-lru:淘汰最久未使用的键值。
- volatile-lfu:淘汰设置了过期时间的键值中最少使用的键值。
- allkeys-lfu:淘汰最少使用的键值。
- volatile-ttl:淘汰设置了过期时间的键值中更早过期的键值。
# Redis缓存三剑客的各种触发原因和解决办法?
- 缓存穿透:用户访问的数据不在缓存和数据库中,大量请求进入数据库且无法通过缓存恢复数据解决,数据库压力过大。
- 可以通过对非法请求的限制、缓存空值或默认值以及使用布隆过滤器快速判断数据是否存在来解决,防止大量数据查询数据库。
- 缓存击穿:缓存中的某个热点数据过期,此时大量的请求访问了该热点数据,就无法从缓存中读取,直接缓存在数据库,数据库被高并发的请求冲垮。
- 可以使用互斥锁方案或者不给热点数据设置过期时间,由后台线程异步更新缓存。
- 缓存雪崩:大量缓存数据集中在一段时间内过期失效或者Redis故障宕机时,Redis无法处理请求,全部请求访问数据库,数据库压力过大会引起连锁反应,整个系统崩溃。
- 针对大量数据同时过期,通过给数据过期时间加随机数、给访问数据库的请求添加互斥锁或者使用后台线程定时更新缓存解决。
- 针对Redis故障宕机,通过服务熔断或请求限流机制,也可以选择构建Redis缓存高可靠集群。
# Kafka
# Kafka和RocketMQ有什么区别?实现细节呢?
- Kafka和RocketMQ都是主流的分布式消息队列,其中Kafka的优势在于高吞吐量,支持集群部署,但是由于它在收到消息时会先写入到磁盘缓冲区而不是写到磁盘,可能会造成数据丢失,使用场景比较受限。RocketMQ则支持很多功能,比如延迟队列,消息事务等,也有很高的吞吐量并支持大规模集群部署,由于其使用mmap+write实现零拷贝技术,而kafka使用sendfile实现零拷贝,性能比Kafka稍弱。
- 当追求极高吞吐量和性能时选择Kafka,如果存在业务需求适合使用RocketMQ。
- 两者的主要差异在于消息处理模型中,Kafka是顺序写+顺序读,生产与消费是线性的,RocketMQ是顺序写+无序读,兼顾写性能和队列灵活性。
# Kafka怎么处理消息积压?
- 增加消费者实例,提高消息的消费速度从而缓解积压问题,确保消费者组中的消费者数量不超过分区数量,一个分区同一时间只能被一个消费者消费。
- 临时创建topic并增加分区,提高消息的并行处理能力,创建新分区后平衡消费者组,让更多消费者可以同时消费消息。
# 怎么保证Kafka处理消息有序且不重复?
- Kafka可以保证在同一个分区内消息有序。
- 生产者确保写入顺序:可以通过自定义分区器为消息指定相同的key,确保将消息写入同一分区和顺序性。
- 消费者确保顺序消费:消费者在消费消息时,使用单线程消费同一分区消息,保证按顺序处理消息,如果使用多线程消费同一分区可能会造成处理重复或者乱序。
- 出现重复消费可能是因为消费者没有提交偏移量或者没有成功提交偏移量。
- 使用Kafka的事务机制,确保消费过程中消息处理和偏移量更新不会出现部分成功的情况。
- 消费者手动提交偏移量,如果某个消息已经处理过,就明确告知kafka该消息已经消费,不再分配给其他消费者。
# SpringBoot
# SpringBoot框架有什么优势?
- SpringBoot的主要优势在于可以简化开发、快速启动以及自动化配置。
- SpringBoot提供了一系列开箱即用的组件和自动配置,简化了项目的配置和开发过程,能够更专注于业务逻辑的实现。
- SpringBoot提供了快速的应用程序启动方式,可以通过内嵌的Tomcat或Jetty等Servlet容器来启动应用程序。
- SpringBoot提供了一种基于配置的方式来管理应用程序的配置,通过配置文件或环境变量来配置应用程序。
# 核心注解有哪些?
- @SpringBootApplication:标注主应用程序类,标识一个SpringBoot应用程序入口点,同时启用自动配置和组件扫描。
- @Controller:标识控制器类作为Spring MVC的Controller,处理HTTP请求。
- @RestController:结合@Controller和@ResponseBody,用于标识控制器类,返回RESTful接口。
- @Service:标识类作为服务层的Bean。
- @Repository:标识类作为数据访问层的Bean,用于与数据库交互。
- @Component:通用的Spring组件注解,表示一个受Spring管理的组件。
- @Autowired:自动注入Spring容器中的Bean,用在构造方法、字段、Setter方法上。
- @Qualifier:与@Autowired一起使用,指定注入时使用的Bean名称。
- @Value:用于从属性文件或配置中读取值,将值注入到成员变量中。
- @RequestMapping:映射HTTP请求路径到Controller方法,可以用在类级别和方法级别。
- @GetMapping、@PostMapping、@PutMapping、@DeleteMapping:简化@RequestMapping的GET,POST,PUT,DELETE请求方法。
- @Profile:用于定义不同环境下的配置,可以标识在类或方法上。
- @Async:将方法标记为异步执行。
- @Configuration:指定一个类为配置类,其中定义的Bean会被Spring容器管理。
- @EnableAutoConfiguration:用于启用Spring Boot的自动配置功能。
- @Bean:定义一个Bean,并交给Spring容器管理。
# SpringBoot的自动装配有了解吗?
- SpringBoot的自动装配是基于Spring Framework的条件化配置和@EnableAutoConfiguration注解实现的,可以通过注解或少量配置就能在SpringBoot帮助下开启和配置各种功能。
- 在@EnableAutoConfiguration注解中,@AutoConfigurationPackage注解将项目src的main包下所有组件注册到容器中,@Import({AutoConfigurationImportSelector.class})是自动装配的核心。
- AutoConfigurationImportSelector类实现了ImportSelector接口,实现自动配置的选择和导入,能够分析项目的类路径和条件决定哪些自动配置类需要被导入。
# @Autowired和@Resource的区别?
- @Autowired来自Spring框架,是Spring自带的依赖注入注解,@Resource来自JDK标准库,属于原生注解。
- @Autowired按照类型匹配,@Resource按照名称匹配,若名称匹配失败,则按照类型匹配。
- @Autowired只有一个required参数,表示依赖是否必须存在。@Resource有name和type等参数,name表示名称,type表示类型。
- @Autowired适合Spring生态的项目,与Spring的其他注解兼容,支持构造器注入,方法注入和字段注入。@Resource不支持构造器注入,主要用于字段注入和方法注入。
# SpringBoot的启动流程?
- SpringBoot的启动流程可以分成初始化环境、配置加载、上下文初始化、自动装配几个阶段。
- 在标注了@SpringBootApplication的主类的main方法中找到run()方法并创建一个SpringApplication对象。
- 进入run()方法,创建应用监听器SpringApplicationRunListeners开始监听。
- 加载SpringBoot配置环境,把配置环境加入监听对象中。
- 加载应用上下文,当做run方法的返回对象。
- 创建Spring容器,实现starter自动化配置和bean的实例化等工作。
# 多线程
# 实现多线程编程的方式有哪些?
- 继承Thread类,重写run()方法,通过start()方法启动线程。
- 实现Runnable接口,实现run()方法,将实例传入Thread构造器,通过start()方法启动线程。
- 实现Callable接口(配合Future/FutureTask),实现call()方法,可以返回结果并抛出异常。
- 使用线程池(Executor框架)管理线程,提交Runnable或Callable任务,实现线程复用,提高性能。
1. 继承Thread方法和实现Runnable方法示意图

2. 实现Callable接口示意图

# 线程池的核心参数有哪些?
- 线程池的构造函数有七个参数,分别是核心线程数(corePoolSize)、最大线程数(maximumPoolSize)、空闲线程存活时间(keepAliveTime)、时间单位(TimeUnit)、线程池任务队列(workQueue)、线程工厂(ThreadFactory)和拒绝策略(RejectedExecutionHandler)。
- 线程池的参数决定了线程池的并发能力,资源占用和任务处理策略,需要根据实际场景进行合理配置。
# 线程池的执行流程是什么?
- 当提交任务时,先判断核心线程池是否已满,未满则创建核心线程执行任务;
- 若已满,判断任务队列是否已满,未满则将任务入队等待;
- 若队列也满,判断线程池是否达到最大线程数,未达到则创建非核心线程执行任务;
- 若已达最大线程数,则执行拒绝策略(如抛出异常、丢弃任务等)。
- 执行完任务的线程会循环从队列取任务执行,直至无任务且超过空闲时间(针对非核心线程)则销毁。

# 线程有哪些状态?
- 线程的状态能够反映线程在生命周期的不同阶段,由JVM和操作系统共同管理,状态转换一般就是“就绪-运行-阻塞/等待-就绪”的循环,直到线程终止。1. NEW:尚未启动线程状态。线程创建,还没有调用start方法,未分配系统资源。2. RUNNABLE:
就绪状态 已经获取除了CPU外全部资源,等待操作系统调度分配CPU时间片。
正在运行状态 线程获得时间片,执行run() 方法。两个状态合并称为Runnable。3. BLOCKED:阻塞状态。竞争同步锁失败,等待锁释放。4. WAITING:等待状态的线程正在等待另一线程执行特定的操作。5. TIMED_WAITING:线程在指定时间内等待,超时后自动唤醒。6. TERMINATED:线程完成执行,终止状态。

评论
验证登录状态...