当线上 Redis 机器出现问题时,优化和恢复的步骤如下:
确认问题
监控工具:使用 Redis 自带的 INFO 命令或监控工具(如 Prometheus、Grafana)查看内存、CPU、网络等指标。
日志检查:查看 Redis 日志(/var/log/redis/redis-server.log)寻找错误信息。
快速恢复
重启 Redis:若问题严重,可尝试重启 Redis 服务。
1 | sudo systemctl restart redis |
主从切换:如果是主从架构,考虑切换到从节点。
内存优化
数据清理:删除不必要的数据或设置过期时间。
1 | DEL key_name |
内存淘汰策略:调整 maxmemory-policy,如 allkeys-lru 或 volatile-lru。
1 | CONFIG SET maxmemory-policy allkeys-lru |
数据分片:使用 Redis Cluster 将数据分布到多个实例。
性能优化
持久化配置:根据需求调整 appendonly 和 save 配置。
1 | CONFIG SET appendonly yes |
连接池:使用连接池减少连接开销。
Pipeline:使用 Pipeline 减少网络往返次数。
架构优化
读写分离:将读操作分流到从节点。
缓存分层:结合本地缓存(如 Guava)和分布式缓存(如 Redis)使用。
长期监控与告警
监控工具:持续监控 Redis 性能。
告警设置:设置内存、CPU、连接数等关键指标的告警。
其他优化
升级硬件:增加内存或使用更高性能的机器。
升级 Redis:确保使用最新稳定版本。
1 | # 查看当前内存淘汰策略 |
通过这些步骤,可以有效优化 Redis 性能,避免类似问题再次发生。
Spring Bean 的生命周期包括以下几个阶段:
实例化(Instantiation)
Spring 容器根据配置或注解创建 Bean 实例,通常通过构造函数或工厂方法完成。
属性赋值(Populate Properties)
容器通过依赖注入为 Bean 的属性赋值,支持通过 setter 方法、字段注入或构造函数注入。
BeanNameAware 和 BeanFactoryAware
如果 Bean 实现了 BeanNameAware 或 BeanFactoryAware 接口,容器会分别调用 setBeanName 和 setBeanFactory 方法。
前置初始化(Before Initialization)
调用 BeanPostProcessor 的 postProcessBeforeInitialization 方法,允许在初始化前进行自定义处理。
初始化(Initialization)
InitializingBean 接口,调用 afterPropertiesSet 方法。init-method,调用该方法。后置初始化(After Initialization)
调用 BeanPostProcessor 的 postProcessAfterInitialization 方法,允许在初始化后进行自定义处理。
使用(In Use)
Bean 初始化完成后,可以被应用程序使用。
销毁(Destruction)
DisposableBean 接口,调用 destroy 方法。destroy-method,调用该方法。Spring Bean 的生命周期从实例化开始,经过属性赋值、初始化等阶段,最终在容器关闭时销毁。开发者可以通过实现特定接口或配置自定义方法干预这些阶段。
在 JVM 中,以下几种情况可能导致 OOM(OutOfMemoryError):
Java 堆空间不足
-Xmx 参数,优化代码减少对象创建或及时释放无用对象。永久代(Java 8 之前)或元空间(Java 8 及之后)不足
-XX:MaxPermSize(永久代)或 -XX:MaxMetaspaceSize(元空间)参数,减少动态类生成或卸载无用类。方法区内存不足
栈空间不足
-Xss 参数,优化递归或减少线程栈深度。直接内存不足
-XX:MaxDirectMemorySize 参数,确保及时释放直接内存。GC 开销过大
创建本地线程失败
数组大小超出限制
Integer.MAX_VALUE。OOM 可能由多种原因引起,需根据具体场景调整 JVM 参数或优化代码。
在生成 RDB 文件时,Redis 通过 写时复制(Copy-On-Write, COW) 机制处理请求,确保数据一致性和性能。具体过程如下:
写时复制机制
处理请求
RDB 文件生成完成
性能影响
配置选项
save: 配置自动生成 RDB 的条件。stop-writes-on-bgsave-error: RDB 生成失败时是否停止写操作。Redis 通过 COW 机制在生成 RDB 文件时继续处理请求,确保数据一致性。虽然可能增加内存和 CPU 开销,但整体影响可控。
TCP 和 UDP 是两种主要的传输层协议,主要区别如下:
连接方式
可靠性
数据传输方式
速度与效率
拥塞控制
头部开销
应用场景
TCP: 可靠、面向连接、速度较慢,适合需要高可靠性的应用。
UDP: 不可靠、无连接、速度快,适合实时性要求高的应用。
线上 CPU 飙高时,可以按照以下步骤排查问题:
定位高 CPU 进程
使用 top 命令:
1 | top |
按 P 键按 CPU 使用率排序,找到占用 CPU 最高的进程。
定位高 CPU 线程
使用 top -H -p <PID>:
1 | top -H -p <PID> |
查看该进程下各线程的 CPU 使用情况,找到占用最高的线程。
线程 ID 转换
将线程 ID 转换为十六进制:
1 | printf "%x\n" <TID> |
用于后续在堆栈信息中定位。
获取线程堆栈信息
使用 jstack:
1 | jstack <PID> > thread_dump.log |
导出 Java 进程的线程堆栈信息,查找对应线程的堆栈。
分析堆栈信息
thread_dump.log 中搜索转换后的线程 ID,分析线程正在执行的代码,定位问题。检查 GC 情况
使用 jstat:
1 | jstat -gcutil <PID> 1000 |
查看 GC 情况,频繁的 Full GC 可能导致 CPU 飙高。
检查系统调用
使用 strace:
1 | strace -p <PID> -c |
分析系统调用,排查是否存在频繁的系统调用导致 CPU 飙高。
检查 I/O 情况
使用 iostat:
1 | iostat -x 1 |
查看磁盘 I/O 情况,排查是否因 I/O 等待导致 CPU 飙高。
检查网络情况
使用 netstat:
1 | netstat -anp | grep <PID> |
查看网络连接情况,排查是否因网络问题导致 CPU 飙高。
检查代码逻辑
代码审查:
检查是否存在死循环、频繁的对象创建与销毁、不合理的算法等。
通过以上步骤,逐步定位 CPU 飙高的原因,常见原因包括:
死循环或无限递归
频繁的 GC
高并发下的锁竞争
频繁的系统调用或 I/O 操作
在 Java 中,volatile 关键字用于确保变量的可见性和有序性,但不保证原子性。具体作用如下:
可见性
volatile 变量的值,其他线程能立即看到最新的值。volatile 变量不会被线程缓存,每次读写都直接操作主内存。有序性
volatile 变量的读写操作前后会插入内存屏障,禁止重排序。不保证原子性
volatile 不能保证复合操作的原子性。例如,volatile 修饰的 int 变量进行自增操作(i++)不是原子的。状态标志: 用于多线程间的状态标志,如:
1 | volatile boolean running = true; |
双重检查锁定(Double-Checked Locking): 用于单例模式的双重检查锁定,如:
1 | class Singleton { |
volatile 关键字用于确保变量的可见性和有序性,但不保证原子性。适用于状态标志和双重检查锁定等场景。
使用 jstat 命令
命令:
1 | jstat -gc <PID> |
输出:
S0C, S1C: Survivor 区容量S0U, S1U: Survivor 区使用量EC, EU: Eden 区容量和使用量OC, OU: 老年代容量和使用量MC, MU: 元空间容量和使用量CCSC, CCSU: 压缩类空间容量和使用量YGC, YGCT: Young GC 次数和时间FGC, FGCT: Full GC 次数和时间GCT: 总 GC 时间使用 jmap 命令
生成堆转储文件:
1 | jmap -dump:format=b,file=heapdump.hprof <PID> |
查看堆内存摘要:
1 | jmap -heap <PID> |
使用 jconsole 或 VisualVM
jconsole 和 VisualVM 提供实时内存使用情况、线程、类加载等信息。自动生成堆转储文件
启动参数:
1 | -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump |
作用: JVM 在 OOM 时自动生成堆转储文件。
分析堆转储文件
使用 jhat:
1 | jhat heapdump.hprof |
通过浏览器访问 http://localhost:7000 查看分析结果。
使用 Eclipse MAT:
查看 GC 日志
启动参数:
1 | -Xloggc:/path/to/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps |
分析: 检查 GC 日志,确认是否因频繁 Full GC 导致 OOM。
检查线程堆栈
使用 jstack:
1 | jstack <PID> > thread_dump.log |
分析: 检查线程堆栈,确认是否存在死锁或线程阻塞。
实时分析: 使用 jstat、jmap、jconsole 或 VisualVM 监控内存使用。
OOM 分析: 通过堆转储文件、GC 日志和线程堆栈定位问题,常用工具包括 jhat 和 Eclipse MAT。
Spring MVC 是一个基于 Java 的 Web 框架,用于构建 Web 应用程序。其工作原理主要围绕 DispatcherServlet 展开,以下是其核心流程:
请求到达 DispatcherServlet
DispatcherServlet 处理,它是前端控制器,负责请求的分发。HandlerMapping
DispatcherServlet 通过 HandlerMapping 查找处理请求的控制器(Controller)。HandlerMapping 包括 RequestMappingHandlerMapping,它根据 @RequestMapping 注解匹配请求。调用控制器
DispatcherServlet 调用控制器的方法处理请求。执行控制器方法
ModelAndView
ModelAndView 对象,包含视图名称和模型数据。视图解析
DispatcherServlet 使用 ViewResolver 解析视图名称,找到对应的视图对象(如 JSP、Thymeleaf 模板)。渲染视图
返回响应
DispatcherServlet 将渲染后的内容返回给客户端。DispatcherServlet: 前端控制器,负责请求分发和响应返回。
HandlerMapping: 映射请求到控制器。
Controller: 处理请求,返回 ModelAndView。
ModelAndView: 包含视图名称和模型数据。
ViewResolver: 解析视图名称,找到视图对象。
View: 负责渲染响应内容。
1 |
|
1 | <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> |
/WEB-INF/views/hello.jsp)1 | <html> |
Spring MVC 通过 DispatcherServlet 协调各组件处理请求,流程包括请求映射、控制器处理、视图解析和渲染响应。核心组件包括 DispatcherServlet、HandlerMapping、Controller、ModelAndView、ViewResolver 和 View。
分布式系统
微服务架构
区别
分布式系统是一个广泛的概念,涉及多个计算机节点的协调工作,而微服务架构是一种特定的分布式系统架构风格,强调将单个应用程序分解为独立的小型服务。分布式系统关注节点间的协调和系统整体特性,微服务架构则着重于服务的独立性和可伸缩性。
什么是 ABA 问题?
ABA 问题的影响
ABA 问题的解决方法
Java 中的解决方案
工作原理:包含一个引用和一个整数标志。使用 compareAndSet 方法时,需要提供期望的引用和标志,以及新的引用和标志。只有当当前引用和标志与期望值匹配时,才更新为新的值和标志。
示例代码:
1 | import java.util.concurrent.atomic.AtomicStampedReference; |
解释:初始值为 0,标志为 0。首先将值改为 1,标志变为 1;然后将值改回 0,标志变为 2。最后尝试将值从 0 改为 2,期望标志为 0,实际标志为 2,CAS 操作失败,从而避免了 ABA 问题。
结论
在 Java 中,ABA 问题是指在并发环境中,一个变量的值从 A 变为 B,再变回 A,导致看起来没有变化,但实际上有过中间状态的修改。这会引起一些依赖于值变化的算法失效。为了解决这个问题,Java 提供了 AtomicStampedReference 和 AtomicMarkableReference 类,通过附加一个标志或版本号,确保在进行 CAS 操作时,能够检测到中间的变化,从而避免 ABA 问题。
什么是 Redis 哨兵机制?
哨兵机制的主要功能
哨兵机制的工作原理
哨兵机制的配置
sentinel.conf 文件来配置哨兵节点。sentinel monitor <master-name> <ip> <port> <quorum>:监控主节点,quorum 表示多少个哨兵节点同意才能进行故障转移。sentinel down-after-milliseconds <master-name> <milliseconds>:主节点在多少毫秒内没有响应才认为故障。sentinel failover-timeout <master-name> <milliseconds>:故障转移的超时时间。结论
Redis 哨兵机制是一种用于监控和自动故障转移的系统,通过部署多个哨兵节点来确保 Redis 集群的高可用性。哨兵节点监控主节点和从节点的状态,当主节点故障时,哨兵节点会选举一个新的主节点并更新从节点,同时通知客户端主节点的变更。
什么是 TCP 粘包和拆包?
粘包和拆包的原因
粘包和拆包的影响
解决粘包和拆包的方法
结论
TCP 粘包和拆包是由于 TCP 协议的字节流特性导致的,发送和接收数据时可能会出现多个数据包被合并或一个数据包被分割的情况。为了解决这些问题,需要在应用层进行消息定界和缓冲区管理,确保数据的正确传输和解析。
Java 线程的生命周期
Java 线程的生命周期包括以下几个主要状态:
new 关键字创建一个 Thread 对象时,线程处于新建状态。此时,线程还没有开始执行。start() 方法后,线程进入可运行状态。此时,线程还没有开始执行,但它已经准备好可以被线程调度器调度执行。run() 方法中的代码。wait() 方法。在阻塞状态下,线程不会被调度执行。wait() 方法或其他等待方法后,进入等待状态。线程将一直等待,直到其他线程调用 notify() 或 notifyAll() 方法唤醒它。sleep(long millis) 或 wait(long millis),进入超时等待状态。线程将在指定时间后自动唤醒。run() 方法执行完毕或因异常退出时,线程进入终止状态。此时,线程无法再被调度执行。线程状态转换
start() 方法。wait() 方法。wait() 方法或其他等待方法。run() 方法执行完毕或因异常退出。线程状态的查询
可以使用 Thread 类的 getState() 方法来查询线程的当前状态。该方法返回一个 Thread.State 枚举值,表示线程的当前状态。
线程的状态转换由线程调度器和线程自身的操作共同决定。例如,调用 start() 方法会使线程从新建状态进入可运行状态,而调用 wait() 方法会使线程从运行状态进入等待状态。
理解线程的生命周期对于编写多线程程序和处理线程同步问题非常重要,因为它帮助开发者控制线程的行为和确保线程之间的正确交互。
结论
Java 线程的生命周期包括新建、可运行、运行、阻塞、等待、超时等待和终止几个主要状态。线程的状态转换由线程调度器和线程自身的操作共同决定。理解线程的生命周期对于编写多线程程序和处理线程同步问题非常重要。
在 MySQL 中创建索引时,需要注意以下事项:
选择合适的列:
WHERE、JOIN、ORDER BY 和 GROUP BY 中的列创建索引。索引类型:
TEXT 类型列。复合索引:
索引数量:
索引大小:
索引维护:
OPTIMIZE TABLE 或 ANALYZE TABLE 维护索引。EXPLAIN 分析查询执行计划,确保索引有效。锁问题:
ALTER TABLE ... ALGORITHM=INPLACE, LOCK=NONE 减少锁表时间。索引与存储引擎:
前缀索引:
避免冗余索引:
(A, B),再创建 (A) 就是冗余的。索引与查询优化:
USE INDEX 或 FORCE INDEX 强制使用特定索引。索引与分区表:
通过合理设计和使用索引,可以显著提升查询性能,但需权衡读写开销。
Netty 是一个高性能的网络应用框架,广泛用于构建异步、事件驱动的网络应用。它采用了多种设计模式来提升灵活性和可扩展性,主要包括以下几种:
Reactor 模式
EventLoop 和 EventLoopGroup 处理事件,EventLoop 负责监听和分发事件,EventLoopGroup 管理多个 EventLoop。责任链模式 (Chain of Responsibility)
ChannelPipeline 和 ChannelHandler 实现,ChannelPipeline 是处理器链,ChannelHandler 是具体处理器。观察者模式 (Observer)
ChannelFuture 和 ChannelPromise 实现异步通知机制,ChannelFuture 表示异步操作结果,ChannelPromise 是可写的 ChannelFuture。工厂模式 (Factory)
ChannelFactory 和 EventLoopGroup 创建 Channel 和 EventLoop 实例。单例模式 (Singleton)
GlobalEventExecutor 使用单例模式,确保全局唯一实例。建造者模式 (Builder)
ServerBootstrap 和 Bootstrap 构建 Channel 和 EventLoopGroup,简化配置和初始化。适配器模式 (Adapter)
ChannelHandlerAdapter 提供默认实现,简化自定义 ChannelHandler 的开发。装饰器模式 (Decorator)
WrappedByteBuf 和 CompositeByteBuf 对 ByteBuf 进行功能扩展。策略模式 (Strategy)
EventExecutorChooser 选择不同的 EventExecutor 策略。模板方法模式 (Template Method)
描述: 定义一个算法的骨架,将某些步骤延迟到子类中实现。
实现: 通过 AbstractChannel 和 AbstractEventLoop 提供默认实现,子类可重写特定方法。
代理模式 (Proxy)
描述: 为其他对象提供代理以控制访问。
实现: 通过 ProxyHandler 实现代理功能,如 SSL/TLS 加密。
Netty 通过这些设计模式实现了高扩展性、灵活性和高性能,开发者可以根据需求灵活组合这些模式,构建高效、可维护的网络应用。
依赖注入(DI)的定义
依赖注入的类型
Spring 中的依赖注入
@Autowired)来定义 bean 和依赖关系。依赖注入的好处
最终答案
Spring 中的 DI 是依赖注入,通过 IOC 容器将对象的依赖关系由外部注入,实现松耦合和控制反转。
Redis 主从复制的概述
主从复制的实现原理
主从复制的配置
slaveof 指令,指定从节点连接的主节点 IP 和端口。主从复制的优势
最终答案
Redis 主从复制通过主节点将数据同步到从节点,实现数据冗余和读写分离。主节点记录所有写命令到复制缓冲区,从节点连接主节点后接收并执行这些命令,保持数据一致。同时,主从节点之间通过心跳机制监控连接状态,确保系统的高可用性。
不推荐为数据库建立索引的情况
在数据库管理中,索引能够提升查询性能,但并非所有情况下都适用。以下是一些不推荐建立索引的情景:
数据量非常小的表
频繁更新的表
低选择性的列
经常进行大量插入、更新或删除操作的表
全文搜索需求
非常大的表
查询不常用的列
联合索引中的前导列选择不当
覆盖索引不适用的情况
数据库性能已经足够好
在决定是否建立索引时,应综合考虑表的大小、数据更新频率、查询模式及硬件资源等因素,并通过性能测试验证索引的有效性。
在数据量小、频繁更新、低选择性列、高频率数据修改、全文搜索需求、表过大、查询不常用列、联合索引前导列选择不当、覆盖索引不适用以及数据库性能已足够好的情况下,不推荐为数据库建立索引。
Seata 概述
定义
目标
架构
工作原理
应用场景
总结
Seata 是一个开源的分布式事务解决方案,旨在提供高性能和可靠性的全局事务服务,确保分布式系统中事务的原子性、一致性、隔离性和持久性。它通过协调多个资源管理器,实现全局事务的管理和控制,适用于微服务架构中的分布式事务场景。