1. int数据类型做了什么优化
Java在处理整数类型时,进行了多种优化,主要体现在编译器层面和JVM层面,目的是提高性能、减少内存开销。
- 常量池优化
- Java中的Integer类有一个缓存机制,对于值在-128到127之间的int数字,Integer对象会被缓存。
- 当我们使用Integer.valueOf(int) 或自动装箱将int转为Integer对象时,如果值在缓存范围内,JVM会返回缓存中的对象,而不>去创建新对象。
- 好处:减少内存开销,避免频繁创建和销毁小范围整数对象。
- 寄存器分配优化
- JVM会将一些频繁使用的int类型变量直接分配到CPU寄存器中,而不是内存,从而加快访问速度。
- 逃逸分析
- JVM优化器会分析变量是否逃逸出线程或方法范围,如果一个局部int变量不会逃逸,JVM可以将其直接分配在栈上。
Java针对int类型的优化体现在多个层面:缓存机制减少小对象分配,寄存器分配加快访问速度,逃逸分析减少堆内存的使用,这些优化共同作用,使得int类型的运算在Java中既高效又节省内存。
2. HashMap考点
- hashmap初始数组长度
Java中HashMap采用数组+链表/红黑树结构实现,当HashMap被创建时,
默认的数组初始长度为16
,并且容量总是2的幂次方。
- 频繁发生冲突怎么办
在HashMap中,键的哈希值通过数组索引定位到桶。当多个键的哈希值映射到同一个索引位置时,就会发生哈希冲突。HashMap采用拉链法来解决冲突。即把发生冲突的元素以链表的形式存储到同一个桶中。
如何优化频繁冲突
- 扩容:当负载因子超过阈值(默认0.75)时,触发扩容并重新分布元素。
- 树化(链表转红黑树):当桶中的链表长度超过一定阈值(默认8),且数组长度超过64,则将链表转化为红黑树,提高查找效率。
- 频繁发生冲突是否一定转化为红黑树
不一定,当链表长度超过8时,同时还需要满足数组的容量大于64,才会转化为红黑树如果容量小于64则会先尝试扩容。
- 红黑树何时退化回链表
- 红黑树退化: 当删除操作导致某个桶中的元素数量低于6时,该红黑树会退化回链表。
- 原因:这是为了降低内存和计算开销,在元素较少时,链表的操作比红黑树更高效。
- 扩容为什么要2倍
扩容时,hashMap的容量总是倍增(16->32->64)。原因是,
- 保证哈希值分布均匀:倍增可以保证元素更均匀的重新分布。
- 哈希优化:由于容量总是2的幂次方,(n-1) & hash的操作可以快速定位元素所在的桶,且能均匀分布哈希值。
- 性能优化:按位与比求余运算更高效。
- 为什么要二次哈希
二次哈希是为了解决哈希冲突,提高哈希分布的均匀性。
HashMap中使用了一种类似二次哈希的优化机制:扰动函数。这是因为直接使用对象的hashcode()可能导致不同对象的哈希值在低位上分布不均匀。
通过将高16位和低16位异或,使哈希值的所有位都参与运算
,避免因为低位不均匀而导致冲突集中在某些桶上。这种方法相当于一种轻量级的哈希,提升了哈希分布的均匀性。
3. ConcurrentHashMap vs HashMap
- ConcurrentHashMap 原理
Java 1.7 实现
- 使用分段锁:ConcurrentHashMap将数据划分为多个Segment,每个Segment是一个小型HashMap,不同线程可以并发的操作不同的Segment,从而减少锁争用。
- 缺点:虽然分段锁提高了并发性能,但是粒度较大,且结构复杂。
Java 1.8 实现
- 不再使用Segment,而是直接使用 数组+链表+红黑树的结构,类似于HashMap。
- 锁的粒度优化:对于冲突的桶位置使用synchronize关键字而非ReentrantLock进行加锁。避免对整个表加锁,从而实现更细粒度的锁控制。
当链表长度超过8时,会将链表转化为红黑树,以提高查询性能。
- 为什么 Java 1.8 中用 synchronized 而不是 ReentrantLock
- 性能优化:在Jdk1.6之后,synchronize在竞争不激烈的情况下性能非常接近甚至优于ReentrantLock,并且synchronize可以被JVM优化(锁消除、锁粗化)。
- 相比ReentrantLock,synchronize语法更简单
- 两个 Map 的 Key 可以为空吗?
HashMap:允许1个键为空,如果有多个null键,后续的put会覆盖前面的值。
ConcurrentHashMap:不允许null键,原因是,在并发环境中难以判断某个get操作返回null是因为键不存在还是因为值为null。
- Map的value可以为空吗
HashMap:允许 null 值。可以有多个键对应 null 作为值
ConcurrentHashMap:不允许 null 值。这是为了避免在并发环境下产生歧义(如:get() 返回 null 时,无法判断是键不存在还是值为 null)。
4. 动态配置底层原理解析
动态配置是一种常见的技术,用于在应用运行期间动态更新配置而无需重启应用。他在微服务、分布式系统和云环境中非常常见。保证了系统的灵活性和配置的实时性。
- 配置源
配置源是存储配置数据的地方,决定了配置的来源和存储形式,常见的配置源包括:
- 本地文件(yaml、json):适用于小型应用和简单场景
- 数据库(MySQL、Redis):用于存储和管理配置数据
- 远程配置中心(Apollo、Nacos):集中式配置管理,适用于微服务和分布式系统。
- 配置管理和配置中心
配置管理系统负责对配置进行集中管理、版本控制、权限管理等。典型的配置中心系统(如Apollo、Nacos)提供:
- 集中式管理:配置通过配置中心统一管理,支持多环境、版本控制和权限控制。
- 灰度发布:配置更新可以逐步应用到部分实例,减少风险。
- 配置分组和命名空间:配置按服务、环境分组,便于管理和隔离。
- 配置推送机制
动态配置的关键在于配置变化时,系统能够及时获取到最新配置,推送机制常用的实现方式包括
:
- 长轮询:客户端定期发起请求,检查配置是否更新。
- WebSocket:建立持久连接,服务端主动推送配置更新。
- 消息队列:通过发布/订阅模式,配置变更消息推送给各个客户端。
- 监听机制和回调
客户端通过注册监听器来监控配置的变化,一旦配置中心检测到变更,会触发通知,客户端的监听器收到通知后执行回调函数,实时更新配置。此过程通过以下步骤实现:
- 注册监听器:在客户端启动时,注册对某些配置项的监听。
- 触发回调:配置变更时,回调函数会动态调整应用的行为。
- 热加载:无需重启应用,配置变更后立即生效。
- 缓存与容错
为了提升系统性能并增强容错性,动态配置系统通常在客户端实现本地缓存:
- 缓存机制:客户端会将最新的配置存储在内存中或者磁盘上,以减少频繁从远程获取配置的开销。
- 回腿机制:当配置中心不可用时,系统可从缓存中读取配置,保证系统的稳定运行。
- 一致性和可靠性
分布式环境下,确保各个实例间配置的一致性和可用性非常重要。
- 配置一致性:通过强一致性或者最终一致性模型,确保所有服务实例获取到相同的配置。
- 故障处理:配置中心或者推送机制故障时,客户端可使用缓存或者默认配置,保障服务不因配置不可用而中断。
总结
动态配置的底层原理依赖于集中化管理的配置中心、推送机制、监听和回调、缓存和一致性处理等技术手段,保证配置的实时性、灵活性和系统的稳定性。这些系统共同协作,确保系统在运行时可以根据配置动态调整行为,满足分布式系统和微服务架构下的需求。
5. 利用线程池批量删除数据,数据量突然增大怎么办
- 调整线程池参数
首先要确保线程池的配置能够适应数据量的变化,可以通过以下几个方面调整线程池的参数:
- 核心线程数(corePoolSize):增加核心线程数,使系统在高负载时能够并行处理更多任务。
- 最大线程数:最大线程数设定需要考虑到服务器的资源,避免超过机器的承载能力。
- 队列长度:增加队列长度,可以临时缓存更多待处理的任务,防止任务提交失败。
- 动态调整线程池
可以通过动态调整线程池的配置
,使线程池根据系统的当前负载情况自适应调整线程数量。这可以通过监控系统资源(如CPU使用率、内存占用等)来动态调节线程池的核心线程数和最大线程数。
- 动态线程池调整:使用ThreadPoolExecutor的setCorePoolSize和setMaximumPoolSize方法在运行时调整线程池的参数。
- 自适应调整策略:利用监控工具或者自定义的策略,根据任务的队列长度、处理速度、系统资源使用来调整线程池的大小。例如,如果队列中任务积压过多,则可以临时增加线程数;当队列清空后在减少线程数。
- 任务分批处理
在数据量突增时,直接处理所有数据可能导致系统负载过高。为避免这种情况,可以分批处理的方式降低一次性任务量:
- 批量处理:将大量数据分成若干个批次,每批处理一定的数据量。
- 限流机制:对每批任务的执行进行限流,限制单位时间内的任务量,防止过多任务同时提交给线程池造成系统崩溃。
- 异步任务队列
如果数据量剧增且任务处理时间较长,可以考虑引入消息队列,将删除操作作为异步任务进行处理:
- 异步执行:任务提交到队列后,线程池从队列中获取任务并逐步处理,这样即使数据量增大,也不会直接冲击系统。
- 任务调度:消息队列可以结合调度系统来分时段执行任务,防止集中处理任务带来的资源冲击。
- 监控和预警
要确保在数据量突增的情况下,系统能够及时感知压力的增加,并根据情况调整资源。为此,通过以下方式进行监控和预警:
- 线程池监控:监控线程池的活跃线程数、任务队列长度、任务完成速度等指标。通过这些数据可以判断是否需要调整线程池的配置。
- 系统资源监控:监控CPU、内存、磁盘IO等资源的使用情况。当资源占用率过高时,及时发出预警或者启动保护措施(如暂停任务提交、降级处理)
- 任务耗时监控:监控每个任务的执行耗时,如果耗时大幅度增加,可以分析是否是任务逻辑出现了瓶颈。
- 合理设置拒绝策略
当任务量超过线程池的处理能力时,线程池的拒绝策略会被触发,常见的拒绝策略包括:
- AbortPolicy:直接丢弃,并且抛出异常
- CallerRunsPolicy:由调用者线程执行该任务(降低性能但是不丢失任务)
- DiscardPolicy:直接丢弃任务
- DiscardOldestPolicy:丢弃最早的任务,腾出空间接收新的任务。
6. InnoDB的数据结构
InnoDb底层数据结构主要包括
B+树、页、段、表空间、聚簇索引
等。
- 页
InnoDB将数据划分为若干个页,这是磁盘和内存交互的基本单位,在InnoDB中,页的大小默认为16KB,无论读取一行还是多行数据,都会将这些行所在的整个页加载到内存中。
- 段(
数据库的基本分配单位,包括索引段和数据段
)
- 定义:段是由一个或者多个区组成的存储结构,是数据库中的分配单位。
- 类型:包括索引段和数据段,分别用于存储索引结构和数据记录。
- 区
区是比页大一级的存储结构,由多个连续的页组成。在InnoDB中,一个区默认包含64个连续的页,因此,一个区的大小为64*16KB=1MB。- 表空间
InnoDB将所有表的数据存储在表空间中,表空间是InnoDB存储数据的逻辑概念
,实际由数据文件组成。表空间可以是独立的,也可以 是共享的。
- 系统表空间:InnoDb默认的表空间,存储多个表的数据、索引、元数据等。系统表空间由ibdata1文件表示。
- 独立表空间:每张表有独立的.ibd文件存储表的数据和索引。
- B+树
InnoDB的表和索引都是通过B+树结构来实现的,这是InnoDB最重要的存储结构之一。
- 聚簇索引:InnoDB的每张表默认通过主键构建一个聚簇索引。聚簇索引存储表的实际数据,数据行按照主键顺序存储在叶子节点中。因此,表本身即是聚簇索引。
- 叶子结点:包含实际的数据行
- 非叶子结点:包含索引键值及指向下层节点的指针。
聚簇索引的特点是:查询效率高,特别是基于主键的查询,因为数据行和索引一体化存储。- 辅助索引:出了主键索引,表上其他的列可以创建辅助索引。辅助索引的叶子结点存储的是主键值,而非实际的数据行。当通过辅助索引查询时,需要先通过辅助索引找到主键,然后在通过主键在聚簇索引中定位到实际数据。
7. B+树和红黑树的区别
特性 | B+树 | 红黑树 |
---|---|---|
结构 | 多路平衡查找树 | 二叉搜索树 |
节点内容 | 非叶子结点只存储索引,叶子结点存储全部数据 | 每个节点存储数据 |
平衡性 | 严格平衡,所有叶子结点在同一层 | 自平衡,红黑性质维持平衡 |
应用场景 | 磁盘存储 | 内存存储 |
插入/删除 | 节点分裂和合并 | 重新着色和旋转 |
顺序访问 | 高效,叶子结点链表连接 | 可以,但是效率较低 |
适用性 | 适合大规模数据,顺序和范围查询 | 适合小数据集,快速查找、动态更新 |
8. B+树和B树的区别
特性 | B树 | B+ 树 |
---|---|---|
数据存储位置 | 数据和索引存储在所有节点 | 数据仅存储在叶子结点 |
节点结构 | 每个节点包含数据和索引 | 内部节点只包含索引,叶子结点存储数据 |
顺序访问性能 | 不高,需要中序遍历全树 | 高效,叶子结点链表连接 |
查询效率 | 查询路径可能较短 | 所有查询都到达叶子结点 |
范围查询效率 | 不擅长范围查询 | 非常高效,适合范围查询 |
应用场景 | 内存中高效查找,插入和删除 | 大规模数据存储和索引,尤其适合磁盘存储 |
9. MySQL三种日志
- 重做日志(redo log)
- 作用:记录事务执行后的状态,用于恢复未写入数据文件但以成功事务更新的数据。
在发生故障时,如果尚有脏页(已修改但未写入磁盘的数据页)未写入磁盘,MySQL可以在重启时根据Redo Log进行重做,以确保事务的持久性
。- 写入时机:事务开始之后就产生Redo log,并且在事务的执行过程中逐步写入Redo Log文件,而不是随着事务的提交才写入。当对应事务的脏页写入到磁盘之后,Redo log使命就已经完成,其占用的空间可以被重用。
- 回滚日志(Undo Log)
- 作用:
保证数据的原子性,保存了事务发生之前的数据的一个版本,可以用于回滚事务。同时,Undo Log还可以提供多版本并发控制下的读(MVCC),即非锁定读
。- 写入时机:事务开始之前,将当前的数据版本生成undo log,当事务提交之后,undo log并不能立即被删除,而是放入待清理链表,由purge线程判断是否有其他事务在使用undo段中表的上一个事务之前的版本信息,从而决定是否可以清理undo log的日志空间。
- 二进制日志(binlog)
- 作用:记录所有修改数据库数据的操作,包括数据的增删改等。binlog可以用于备份、恢复和复制。
- 产生时机:事务提交时,一次性将事务中的SQL语句按照一定的格式记录到binlog中,与Redo Log不同,Redo Log是在事务开始之后就开始逐步写入磁盘,binlog在事务提交时一次性写入。
10. Spring循环依赖,以及三级缓存分别放的是什么
Spring循环依赖指在Spring框架中,两个或者多个Bean相互之间持有对方,形成一个闭环的依赖关系。例如,Bean A依赖于Bean B。Bean B也依赖于Bean A。就构成了循环依赖。
为了解决Spring中的循环依赖问题,Spring框架引入了三级缓存机制,三级缓存分别存放以下内容
- 一级缓存
- 存放已经完全实例化的单例Bean对象
- 在Bean第一次创建并初始化之后,其实例会存放到该缓存中。
- 二级缓存
- 存放已经提前实例化但未完全初始化的单例Bean对象。
- 在创建Bean的过程中,
如果需要解决循环依赖问题,会将正在创建的Bean实例暂时放入该缓存中。
- 三级缓存
- 存放已经提前实例化但未完全初始化的Bean的工厂对象。
- 在创建Bean的过程中,如果需要解决循环依赖问题,会将正在创建的Bean的工厂对象(用于生成Bean实例)暂时放入该缓存中。
当Spring容器在创建Bean时,如果发现存在循环依赖,会先从一级缓存中查找是否已经存在完全初始化的Bean实例。如果不存在,则继续到二级缓存中查找是否已经存在提前实例化的Bean对象。如果二级缓存也不存在,那么会到三级缓存中查找是否存在Bean的工厂对象。如果找到了工厂对象,Spring容器会使用该工厂对象来生成Bean实例,并将其放入二级缓存中,同时删除三级缓存中的工厂对象。这样,当另一个Bean依赖这个Bean时,就可以从二级缓存中获取到已经提前实例化的Bean对象,从而解决循环依赖问题。
11. 分代ZGC
分代ZGC 是一个针对Java虚拟机的垃圾收集器,旨在为应用提供低延迟的垃圾回收过程。ZGC是一种并发、基于区域的、压缩型垃圾收集器,主要设计目标是减少应用程序的停顿时间,尤其是对大堆(数十GB甚至数TB)的支持。
分代特性
在JDK17之前,ZGC不是一个分代的垃圾收集器,他将整个堆作为一个单一的生成来处理。然而, 从17开始,ZGC添加了分代收集的特性,这意味着ZGC开始支持对年轻代和老年代的不同处理,可以更高效的管理Java堆,特别是对那些对象生命周期差异大的应用程序。分代ZGC的主要优势包括:
- 更快的垃圾回收:通过区分年轻代和老年代,ZGC可以针对性的进行更快速的垃圾回收。年轻代对象通常有更高的死亡率,因此,集中在年轻代的垃圾回收可以更快的释放空间,减少整体的GC时间。
- 减少延迟:ZGC旨在最大程度减少停顿时间,即使在进行全堆回收时也尽量减少对应用程序的干扰。
- 改善内存压缩:ZGC使用了一种称为“染色指针”的技术,这种技术使得在运行时进行内存压缩成为可能,而不需要全面停顿整个应用。
12. 硬链接和软链接的区别
在UNIX和类UNIX操作系统中,硬链接和软链接是两种用于创建文件间引用的方法。这两种链接的概念和实现有明显的区别,适用于不同的场景。
- 硬链接
- 定义:硬链接是指多个文件名链接到同一个文件数据块的链接方式。
- 工作原理:通过在文件系统中创建多个文件名,并将这些文件名指向同一个索引节点,实现对同一文件数据的访问。
- 软链接
- 定义:软链接相当于源文件的快捷方式,包含了另一个文件的路径名。
- 工作原理:软链接文件记录了源文件的路径,通过路径找到源文件,然后读取源文件属性,根据inode找到存放源文件的块,读取内容后显示。
主要区别
- 本质:
- 硬链接:硬链接和源文件是同一文件的不同名称,他们有相同的inode。
- 软链接:软链接和源文件不是同一个文件,软链接包含了指向源文件的路径名。
- 创建方式
- 硬链接:ln
- 软链接:使用ln -s
- 链接对象
- 硬链接:只能对已存在的文件进行创建,不能对目录进行创建,也不能跨文件系统。
- 软链接:可以对不存在的文件或者目录创建,也可以跨文件系统。
- 删除影响
- 硬链接:删除源文件或者硬链接文件之一,不影响其他具有相同inode文件,只有当所有链接都被删除时,文件内容才会被真正删除。
- 软链接:删除源文件后,软链接失效。删除软链接本身对源文件没有影响。
13. inode是什么
-
inode定义
inode是unix/linux文件系统中的一个数据结构,它包含了关于文件或目录的元数据,如文件大小、拥有者、权限、时间戳等。这些元数据是操作系统管理和访问文件所必需的。 -
inode内容
- 文件元数据:如文件大小、文件类型、文件权限、文件所有者、文件所属组等。
- 指向数据块的指针:inode还包含了指向文件数据块的指针,这些指针告诉操作系统文件数据存储在磁盘的哪个位置。
- 链接数:记录有多少个文件名指向该inode,当链接数将为0时,inode及其对应的数据块将被系统回收。
14. 怎么看一个Java线程的资源耗尽
- 使用JVisualVM
JVisualVM是一个功能强大的工具,提供了对Java应用程序的深入监视和故障排查功能,可以显示线程的CPU和内存使用情况,还可以对线程进行实时的性能分析。
步骤如下:
- 启动JVisualVM
- 连接到目标Java应用程序
- 点击监视选项卡来查看CPU和内存的使用情况
- 使用线程选项卡查看活动线程的详细信息和状态
- 还可以进行线程转储,有助于识别死锁或者其他同步问题。
- 使用JConsole
步骤如下
- 启动JConsole并连接到Java应用程序
- 使用线程选项卡,可以看到所有运行的线程及其状态,以及死锁检测功能。
- 使用jstack
用于生成java应用程序中所有线程的堆栈跟踪,对于识别线程的当前活动和可能的锁争用问题非常有用。
15. RPC和HTTP协议有什么区别
RPC和HTTP都是在网络中进行数据交换和通信的协议,他们之间的主要区别如下:
- 设计目的:
- RPC:RPC设计用于在不同的计算机之间执行代码,允许程序调用另一台机器上的程序或者服务,就好像是本地程序一样。
RCP抽象了网络通信的细节,让开发者可以像调用本地函数一样调用远程函数
。- HTTP:
Http是为了web通信设计的应用层协议,主要用于在客户端和服务器之间传输超文本
,是构建万维网的基础,使用简单请求-响应模型。
- 使用方式
- RPC: RPC关注于操作和数据的远程执行,通常用于客户端和服务器之间的内部通信,如分布式系统中服务间的交互。RPC可以基于多种底层网络协议实现,包括HTTP、TCP、UDP。
- HTTP:Http用于传输文档和数据,支持广泛的数据类型和操作,非常适合公开的API和Web服务,Http请求包括一个方法,用于定义客户端希望服务器执行的操作。
- 性能和效率
- RPC:RPC设计时考虑了效率和低延迟,因此通常在性能上比HTTP更优,尤其是当使用非HTTP传输(如TCP或者UDP)时。
- HTTP:
Http协议相对简单,易于实现和使用,但可能在处理大量小的、频繁的请求时不如专门的RPC协议高效。
- 兼容性和适用性
- RPC:RPC适用于需要高性能、严格接口定义和内部通信优化的环境/
- HTTP:http由于其在互联网上的普遍性和广泛支持,适用于需要广泛兼容性的场景。
RPC和HTTP在设计和使用场景上有明显的区别,选择使用哪一个通常取决于你的具体需求,如是否需要跨网络调用代码、是否侧重于网络服务的互操作性,以及对性能的具体要求。在许多现代应用中,两者也常常结合使用,例如,使用HTTP作为传输协议的gRPC,这样既利用了HTTP的广泛支持,也优化了远程调用的效率。
16. 为什么索引使用B+树而不是B树
- 所有值都在叶子结点
B+树的一个关键特点是所有的数据记录(或对数据记录的引用)都存在于叶子节点中,而中间节点仅存储键值用于导航。这与B树不同,B树的每个节点都可以存储键值和数据记录。这种设计有几个优点:
- 提高查询效率:由于所有数据值都存储在叶子结点,查询操作可以更一致的执行,因为每次查找都需要搜索到叶子节点,这减少了在中间节点产生的数据读取,使得查询预测性更好,性能更稳定。
- 优化磁盘读取:B+树内部节点更小,
因为他们不存储实际的数据记录,只存储键。意味着内部节点可以加载更多的键,从而减少了达到所需记录所需的磁盘IO次数。
- 叶子结点的顺序访问
B+树的叶子节点之间通过指针相连,形成一个链表。这对于执行范围查询(即返回一系列值的查询)非常有用,因为一旦到达范围的开始部分,就可以简单地遍历叶子节点链表直到范围的末端。这种顺序访问大大优化了范围搜索的效率,而在B树中实现这种操作则需要更多的树遍历操作。- 更加稳定的性能
由于B+树在叶子节点上维持所有数据记录,并且所有叶子节点具有相同的深度,所以每次搜索的路径长度相同,这保证了每次搜索操作的性能稳定性。- 高效的分页性能
数据库系统和现代文件系统通常使用固定大小的页来存储数据,B+树因其结构特点,更适合与分页系统结合,内部节点由于不存储数据,可以存储更多的键,从而更有效的利用每个页的空间。使得B+树可以在给定的页空间中存储更大的索引,并减少访问数据时所需的页数。
B+树在数据库索引方面提供了更高效的查询性能、更好的IO效率和更优的磁盘空间利用率。因此,对于需要大量范围查询、顺序访问和优化磁盘使用的应用,如数据库和文件系统索引,B+树通常是更优的选择。
17. Dubbo服务请求失败怎么处理
- 超时配置:在Dubbo中,可以通过配置服务提供者和消费者的超时时间来控制请求的响应时间,避免长时间等待无响应服务而导致的系统瘫痪。
- 重试机制:为了增加服务调用的成功率,可以为服务消费者配置重试次数,但需要注意的是,不是所有类型的服务都适合重试,比如那些非幂等操作。
- 负载均衡:Dubbo支持多种负载均衡策略,如随机、轮询、最少活跃调用等,通过合理的负载均衡策略,可以避免部分服务节点过载,分散风险。
- 集群容错:Dubbo提供了多种集群容错方式,如Failover(失败自动切换)、Failfast(快速失败)、Failsafe(失败安全)、Failback(失败自动恢复)等。这些容错机制可以帮助系统在面对部分节点失败时,依旧能保持一定的服务能力。
- 异常处理:在服务消费者端,应该适当的捕获和处理可能发生的异常,例如远程调用异常、业务异常等。
- 监控和告警:利用Dubbo提供的监控中心、记录服务调用的详细数据,如调用次数、失败次数、平均响应时间等。
- 服务降级:在系统压力过大或者外部依赖失败时,可以采用服务降级策略,暂时关闭部分非核心业务。
17. 数据库分布式事务
分布式事务指在分布式系统中,涉及多个数据库、服务、消息队列等资源。并且需要保证这些资源上的操作要么全部成功提交,要么全部失败回滚的一种机制。
- 背景和需求
在分布式系统中,由于网络分区、服务故障、数据不一致等问题,传统的单一数据库事务已经无法满足需求,因此,需要引入分布式事务来确保数据的一致性和可靠性。分布式事务的实现需要在业务代码中编写额外的逻辑来处理事务的准备、提交、回滚等操作。
- 常见的解决方案
- 两阶段提交(2pc)
- 准备阶段:协调者向所有参与者发送准备请求,参与者执行事务准备,并向协调者发送准备就绪的通知。
- 提交阶段:如果所有参与者都准备就绪, 则协调者向所有参与者发送提交请求,参与者执行事务提交,如果任何一个参与者失败或者无法提交,所有参与者都会被要求回滚事务。
- 优缺点:2PC的优点在于简单易懂,且在理想情况下能够保证分布式系统中事务的原子性和一致性。然而,他也存在一些问题,比如阻塞问题,
在准备阶段,如果协调者发生故障或者网络异常,参与者会一直等待。
- 三阶段提交(3pc)
- CanCommit阶段:协调者询问所有参与者是否可以执行事务提交,参与者根据自身情况返回Yes或者No。
- PreCommit阶段: 如果所有参与者都回复Yes,协调者会像他们发送预提交请求,参与者执行事务操作,并将Undo和Redo日志写入本机事务日志,然后发送ACK响应给协调者,如果有参与者返回No或者等待超时,协调者会发送中断请求。
- DoCommit阶段:如果协调者收到了所有参与者的Ack响应,他们会像所有参与者发送doCommit请求,参与者收到doCommit请求后,正式执行事务提交操作,并在完成后释放资源。
- 优缺点:3PC相较于2PC的优点在于引入了超时机制,解决了2PC的阻塞问题。然而,如果参与者收到preCommit消息后,如果网络出现分区,协调者和参与者无法进行后续的通信,可能导致数据不一致。
- TCC模式
- Try阶段:系统会尝试执行事务操作的所有必要检查和准备工作,以确保事务能够成功执行。如果所有的检查都通过,系统会记录下所有的尝试操作,但并不会实际执行任务。
- Confirm阶段:系统会执行事务操作,并且确认之前所做的尝试操作,如果事务操作成功执行,系统将确认并提交事务。
- Cancel阶段:如果在Try阶段或者Confirm阶段发生了错误或者异常,系统将进入取消阶段,执行逆向操作,取消之前尝试阶段所做的操作。