Java后端框架知识点小结(实时更新)
- 框架汇总
-
- spring
- [spring MVC 与Springmvc](#spring MVC 与Springmvc)
- [mybatis与mybatis-plus、Hibernate 区别](#mybatis与mybatis-plus、Hibernate 区别)
- struct2
- springboot
- springcloud
- 消息中间件
- 线程与多线程
- redis
-
- 个人理解
- 核心应用场景
- 优势宰我
- 场景使用限制
- [三大经典问题 击穿、穿透、雪崩](#三大经典问题 击穿、穿透、雪崩)
- [持久化 (Persistence) - Redis怎么把内存数据存到硬盘?](#持久化 (Persistence) - Redis怎么把内存数据存到硬盘?)
- [淘汰策略 (Eviction Policies) - 内存满了,删谁?](#淘汰策略 (Eviction Policies) - 内存满了,删谁?)
- 主从、哨兵与集群 (High Availability)
- zookeeper,dubbo
-
- ZooKeeper:分布式协调服务
- Dubbo:分布式RPC框架
- [用ZooKeeper + Dubbo实现分布式系统](#用ZooKeeper + Dubbo实现分布式系统)
- 总结:
针对java的一些问题总结,有不对的大家可共同探讨,互相进步
框架汇总
我们接触到的后端框架基本上就是spring、springboot、spring MVC 与Springmvc、mybatis与mybatis-plus、Hibernate、struct2、springcloud等这些个框架,这也是面试绕不开的地方,现在对这些框架进行问题收缩。
spring
spring MVC 与Springmvc
Spring MVC 和 Springmvc 是同一个框架,只是书写方式不同。正确官方名称是 Spring MVC(Spring Model-View-Controller)
Spring MVC 的工作流程是怎样的?
java
1、客户端发送请求到 DispatcherServlet。
2、DispatcherServlet 查询 HandlerMapping 找到对应的 Controller。
3、DispatcherServlet 调用 HandlerAdapter 执行 Controller。
4、Controller 处理请求,返回 ModelAndView 对象(包含模型数据和视图名称)。
5、DispatcherServlet 通过 ViewResolver 解析视图名称,找到具体的 View 对象。
6、View 渲染模型数据,生成响应内容返回给客户端。
更详细的流程也大差不差,两者步骤差不多,记上面的就好。
xml
1.用户发送请求至前端控制器DispatcherServlet。
2.DispatcherServlet收到请求调用处理器映射器HandlerMapping。
3.处理器映射器根据请求url找到具体的处理器,
生成处理器执行链HandlerExecutionChain(包括处理器对象和处理器拦截器)一并返回给DispatcherServlet。
4.DispatcherServlet根据处理器Handler获取处理器适配器HandlerAdapter执行HandlerAdapter处理一系列的操作,
如:参数封装,数据格式转换,数据验证等操作。
5.执行处理器Handler(Controller,也叫页面控制器)。
6.Handler执行完成返回ModelAndView。
7.HandlerAdapter将Handler执行结果ModelAndView返回到DispatcherServlet。
8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9.ViewReslover解析后返回具体View。
10.DispatcherServlet对View进行渲染视图(即将模型数据model填充至视图中)。
11.DispatcherServlet响应用户。
mybatis与mybatis-plus、Hibernate 区别
| 特性 | MyBatis | MyBatis-Plus | Hibernate |
|---|---|---|---|
| 类型 | 半自动ORM框架 | MyBatis增强工具 | 全自动ORM框架 |
| SQL控制 | 手动编写SQL,完全控制 | 手动+自动生成SQL | 自动生成SQL,手动可优化 |
| 开发效率 | 中等,需写SQL | 高,自动CRUD | 高,自动生成SQL |
| 灵活性 | 极高,SQL完全自定义 | 高,支持自定义SQL | 中等,HQL/Criteria有限制 |
| 性能优化 | 直接优化SQL | 良好,内置优化 | 需要理解缓存机制 |
| 数据库移植 | 较差,SQL需适配 | 较差,SQL需适配 | 良好,HQL自动适配 |
| 缓存机制 | 一级、二级缓存 | 继承MyBatis缓存 | 一级、二级、查询缓存 |
| 适合场景 | 复杂SQL、性能敏感 | 快速开发、中小项目 | 传统企业、对象复杂 |
MyBatis-Plus相比MyBatis的主要改进是什么?
1、通用CRUD操作
java
// MyBatis:每个实体都需要编写CRUD SQL
// UserMapper.xml中编写insert、update、delete、select
// MyBatis-Plus:继承BaseMapper即获得CRUD方法
public interface UserMapper extends BaseMapper<User> {
// 无需编写基础CRUD方法
}
2、条件构造器:
java
// 链式调用,类型安全(Lambda方式)
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.eq(User::getName, "张三")
.between(User::getAge, 20, 30)
.orderByDesc(User::getCreateTime);
3、代码生成器:
java
// 自动生成Entity、Mapper、Service、Controller等
AutoGenerator generator = new AutoGenerator();
// 配置后一键生成所有代码
4、分页插件优化
java
// 更简单的分页使用方式
Page<User> page = new Page<>(1, 10);
IPage<User> result = userMapper.selectPage(page, wrapper);
5、全局配置:
yaml
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
MyBatis的缓存机制如何工作?与Hibernate缓存有何不同
一级缓存 :SqlSession级别缓存,默认开启。 生命周期短,线程不共享
- 范围:同一个SqlSession内,相同的查询语句和参数,第二次查询时不会再去数据库,而是直接从缓存返回结果。
- 失效条件:增删改操作(flush)、手动清理缓存、切换了SqlSession、查询条件不一样。
- 线程隔离:不同SqlSession之间互不影响。
二级缓存:Mapper级别/跨SqlSession的缓存,默认关闭。 需配置开启,多个会话共享,默认内存存储,支持第三方缓存实现。
- 范围:同一个Mapper命名空间下,不同SqlSession可以共享缓存。
- 失效条件:本Mapper下有更新操作或手动清理缓存。
- 生命周期:依赖MyBatis全局配置和Mapper开启了二级缓存;
- 持久化:可指定缓存实现(如Ehcache、Redis)。
对于高频读多写少业务可考虑开启二级缓存,并做好缓存失效设计;写多读少场景/强一致要求则慎用二级缓存
struct2
用得少,后面再补,面试也未必问
springboot
Spring Boot是基于Spring框架的快速开发框架,它通过自动配置、起步依赖和内嵌服务器等特性,简化Spring应用的创建、配置和部署过程。
java
1、版本锁定: 解决maven依赖版本冲突问题
2、起步依赖: 解决jar过多问题,比如spring-boot-starter-web为构建Web应用(包括RESTful应用)提供了所需的基本配置
3、自动配置: 解决配置文件过多问题
4、内置tomcat: 内嵌了如Tomcat、Jetty等Servlet容器,使得Web应用可以独立运行。
核心配置文件有application.properties或application.yml,它们用于配置应用的各种属性.【.yml和.properties两种类型】
作用:这是Spring Boot最核心的配置文件,可以用来设置各种属性值,包括但不限于:
- 服务器配置(如Tomcat的端口号server.port)
- 数据源配置(如数据库URL、用户名、密码spring.datasource.*)
- Spring MVC配置(如视图解析器prefix, suffix)
- 日志配置
- 自定义属性
spring Boot常用注解
@SpringBootApplication: SpringBootConfiguration配置类、componentScan扫描包、EnableAutoConfiguration导入其他配置类
@RestController: @ResponseBody和@Controller的作用。
@Component,@Service,@Controller,@Repository: 将类注入容器。
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping: 映射请求,只能接收的对应的请求。
@AutoWired: 按照类型匹配注入。
@Qualifier: 和AutoWired联合使用,在按照类型匹配的基础上,在按照名称匹配。
@Resource: 按照名称匹配依赖注入。
@Bean: 用于将方法返回值对象放入容器。
@RequestParam: 获取查询参数。即url?name=这种形式
@RequestBody: 该注解用于获取请求体数据(body),get没有请求体,故而一般用于post请求。@PathVariable: 获取路径参数。即url/{id}这种形式。
@Value: 将外部的值动态注入到 Bean 中。
@Value("${}"):可以获取配置文件的值。
@Value("#{}"):表示SpEl(Spring Expression Language是Spring表达式语言,可以在运行时查询和操作数据。)表达式通常用来获取 bean 的属性,或者调用 bean 的某个方法。
springcloud
SpringBoot是快速开发的Spring框架,SpringCloud是完整的微服务框架,SpringCloud依赖于SpringBoot
消息中间件
activeMQ、RocketMQ、RabbitMQ、Kafka
- 消息丢失问题(可靠性)
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 处理方式 | 持久化到数据库/文件,支持事务 | 消息持久化+确认机制(ACK) | 同步刷盘+同步复制 | 多副本同步(ISR)+ACK机制 |
| 银行应用场景示例 | 内部审批流程消息 | 跨行转账确认 | 贷款审批流程 | 交易流水采集 |
| 配置要点 | 配置持久化存储,开启事务 | publisher confirm + 队列持久化 + 消费者手动ACK | 同步刷盘(flushDiskType=SYNC_FLUSH) + 主从同步 | acks=all + min.insync.replicas=2 |
- 消息顺序性问题
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 处理方式 | 单个消费者保证顺序 | 单个队列保证顺序 | 分区顺序消息 | 分区内顺序保证 |
| 银行应用场景 | 客户操作日志 | 账户余额变更 | 贷款状态流转 | 交易流水排序 |
| 限制条件 | 同一队列,单消费者 | 需要单队列+单消费者 | 同一订单号hash到同一队列 | 同一key进入同一分区 |
- 高并发处理能力(吞吐量)
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 架构特点 | 传统JMS,单机扩展有限 | Erlang实现,集群扩展一般 | 分布式,多Master多Slave | 分布式分区,水平扩展强 |
| 银行适用场景 | 低频业务系统 | 支付核心系统 | 电商信贷双十一 | 全行交易日志 |
| 性能数据 | 万级TPS | 数万TPS | 十万级TPS | 百万级TPS |
- 消息堆积能力(存储)
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 存储方式 | 内存/文件/数据库 | 内存+磁盘,有上限 | 分布式文件存储 | 分布式日志存储 |
| 银行应用场景 | 短期消息存储 | 待处理支付订单 | 历史交易查询 | 监管数据归档 |
| 堆积时长 | 几天 | 几小时-几天 | 几个月 | 半年-一年 |
- 延迟消息支持
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 实现方式 | 支持Scheduled消息 | 死信队列+TTL或插件 | 内置18个延迟级别 | 无内置,需外部调度 |
| 银行应用示例 | 理财产品到期提醒 | 贷款逾期提醒 | 预约转账执行 | 批处理任务 |
| 精度 | 秒级 | 秒级 | 秒级 | 分钟级 |
- 事务消息支持
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 事务实现 | XA事务,JMS事务 | 发布者确认+事务 | 两阶段提交事务消息 | 0.11后支持事务 |
| 银行应用 | 跨系统转账 | 账户余额扣减 | 积分兑换 | 流式处理一致性 |
| 一致性保证 | 强一致 | 最终一致 | 最终一致 | 最终一致 |
- 消息路由能力
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 路由特性 | 虚拟主题,选择器 | Exchange路由键绑定 | Tag过滤,SQL过滤 | Topic+Partition |
| 银行应用 | 不同类型贷款分发 | 多机构监管报送 | 不同客户级别处理 | 按业务线分发 |
| 复杂度 | 中等 | 灵活复杂 | 简单 | 简单 |
问题1:RabbitMQ和Kafka在银行怎么选?
java
如果业务是【资金交易类】,必须保证【不丢消息】,选RabbitMQ。
例子:跨行转账,我们用RabbitMQ的:
1. 消息持久化(队列和消息都存磁盘)
2. 发布者确认(publisher confirm)
3. 消费者手动ACK
如果业务是【数据采集类】,要求【高吞吐】,选Kafka。
例子:全行交易流水采集,每天1亿条,用Kafka:
1. 多分区并行消费
2. 数据保留7天供多个系统消费
3. 吞吐量可达百万TPS
问题2:RocketMQ的事务消息怎么用?
java
1. 开始事务:用户点击"积分兑换"
2. 发送半消息:RocketMQ存消息但不让消费者看到
3. 执行本地事务:扣减积分
4. 提交/回滚:积分扣成功就提交消息,失败就回滚
5. 消费者处理:收到消息后增加账户余额
问题3:消息积压了怎么办?
不同中间件处理差异:
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 应急措施 | 限流+临时存储 | 紧急扩容消费者 | 消费者负载均衡 | 增加分区+消费者 |
| 银行实际做法 | 审批系统升级,暂停接收新消息 | 支付订单积压时,临时开10个消费者 | 双十一信贷审批,动态调整消费者数 | 从50分区扩到200分区 |
问题4:如何保证消息顺序?
按业务要求选择
java
1. 强顺序要求:RocketMQ顺序消息
场景:贷款审批(申请→初审→终审→放款)
2. 局部顺序:Kafka分区顺序
场景:同一账户的交易流水按时间排序
3. 弱顺序:RabbitMQ单队列
场景:客户操作日志,同一会话内有序
4. 无顺序要求:ActiveMQ/ZeroMQ
场景:营销推送、通知类消息
问题5:死信队列怎么用?
| 特性 | activeMQ | RabbitMQ | RocketMQ | Kafka |
|---|---|---|---|---|
| 死信处理 | 限流+临时存储 | 自动转死信队列 | 重试队列+死信 | 无内置,需自己实现 |
| 银行案例 | 贷款申请超时未处理,转异常队列 | 支付失败3次的消息进死信,人工处理 | 风控调用失败,重试3次后归档 | 异常交易数据转单独Topic |
一句话选择指南:
根据业务特征选型:
A问:这个业务最怕什么?
怕复杂 → ActiveMQ(内部系统)
怕丢钱 → RabbitMQ(转账、支付)
怕乱序 → RocketMQ(贷款审批)
怕堵车 → Kafka(交易流水)
A问:数据量多大?
每天<1000万 → RabbitMQ/ActiveMQ
每天1000万-1亿 → RocketMQ
每天>1亿 → Kafka
A问:要存多久?
几小时 → RabbitMQ
几天 → ActiveMQ
几个月 → RocketMQ
半年以上 → Kafka
当被问到区别时:
"在我们银行:
支付系统用RabbitMQ,因为转账必须100%可靠,它的确认机制最严谨
信贷系统用RocketMQ,因为贷款流程必须按顺序,它的顺序消息最成熟
大数据平台用Kafka,因为交易日志量大,它的吞吐量和存储能力最强
内部办公用ActiveMQ,因为简单够用,维护成本低
当被问到选型理由时:
"主要看三个指标:可靠性、顺序性、吞吐量。比如贷款审批,可靠性要求4个9,顺序性必须保证,吞吐量要求中等,所以选RocketMQ。如果是交易流水采集,可靠性要求3个9,顺序性要求分区内有序,吞吐量要求极高,所以选Kafka。"
线程与多线程
redis
个人理解
Redis就像一个超级快的、放在内存里的"多功能收纳柜"。它不像MySQL那种"大仓库"需要把东西写在纸上(存到硬盘)那么慢。它直接把最常用的小东西(数据)放在手边(内存),随用随取,速度极快。它不仅能放简单的键值对(key-value),还能放列表、集合等复杂结构
或者说,
Redis(Remote Dictionary Server)是一个开源、基于内存、支持持久化的高性能键值存储系统。它提供了丰富的数据结构,如String、Hash、List、Set、Sorted Set等,并支持事务、发布订阅、Lua脚本等高级功能。其主要特点包括高性能、丰富的数据类型、原子操作、持久化以及支持主从复制和高可用架构。它常被用作数据库、缓存和消息中间件。
核心应用场景
java
Redis的核心应用场景包括:缓存、分布式会话、排行榜、计数器、消息队列和秒杀等热点数据读写场景。
Redis的核心价值在于其高性能和丰富的数据结构,这使其在多种场景下成为首选解决方案:
缓存:作为前端缓存,有效降低数据库负载,提升响应速度,这是其最经典的应用。
分布式会话:集中存储用户会话状态,解决在集群环境下Session不一致的问题。
排行榜/计数器:利用Sorted Set和INCR等原子命令,轻松实现实时排行和计数功能。
消息系统:通过List实现简单的消息队列,或通过Pub/Sub模式实现发布订阅功能。
秒杀系统:承担秒杀活动中的极端高并发读写请求。
优势宰我
选择Redis而非其他方案的主要原因在于其显著优势:
1、极致性能:数据存储在内存中,读写操作时间复杂度大部分是O(1),可达10万+ QPS。
2、丰富的数据类型:支持String、Hash、List、Set、ZSet等多种数据结构,直接满足复杂业务场景,无需额外解析。
3、持久化支持:提供RDB和AOF两种持久化机制,保证数据可靠性,这是优于Memcached的关键一点。
4、高可用和分布式:通过Redis Sentinel(哨兵)和Redis Cluster(集群)模式提供高可用和横向扩展能力。
简而言之,在需要高性能、高并发读写和丰富数据结构支持的场景下,Redis是首选解决方案。
场景使用限制
| 核心适用场景 | 不适用场景 |
|---|---|
| 高性能读写需求:如缓存、会话存储 | 数据规模过大:受物理内存容量限制,不适合存储海量数据(如TB/PB级别),成本高昂 |
| 热点数据:需要被快速访问的少量数据 | 仅支持简单的键值查询,不支持SQL那样的关联查询、聚合查询等复杂操作 |
| 特殊数据结构需求:如利用ZSet实现排行榜,利用Incr实现计数器等 | 强事务一致性需求:虽然支持事务,但其设计目标是高性能而非强一致性。对数据一致性要求极高的金融级业务,仍应首选关系型数据库 |
三大经典问题 击穿、穿透、雪崩
| 问题 | 缓存穿透 | 缓存击穿 | 缓存雪崩 |
|---|---|---|---|
| 解释 | 查一个根本不存在的数据 | 一个超级热点key过期了,瞬间大量请求把这个数据库打穿了 | 大量的key在同一时间过期,或者Redis服务宕机了 |
| 举例 | 你故意请求一个id为 -1 的商品,缓存和数据库都没有。每次请求都直接打到数据库上,相当于穿透了缓存 | 一个明星爆出大瓜,微博缓存里这个热点key刚好过期了,瞬间千万请求都找不到缓存,全都去数据库查这条微博 | 双十一零点,大量商品缓存设置的都是2小时过期,结果在凌晨2点同时失效,所有请求瞬间涌向数据库,数据库直接"雪崩"般垮掉 |
| 解决办法 | 缓存空对象和布隆过滤器 | 设置永不过期或使用互斥锁 | 差异化过期时间、构建高可用架构、服务熔断降级 |
- 缓存穿透:查一个根本不存在的数据。
例子:你故意请求一个id为 -1 的商品,缓存和数据库都没有。每次请求都直接打到数据库上,相当于穿透了缓存。
解决:1. 对非法请求参数进行校验过滤。2. 即使数据库查不到,也往缓存里存一个空值(比如null),并设置一个短的过期时间,下次再来查这个id就直接返回空了。
- 缓存击穿:一个超级热点key过期了,瞬间大量请求把这个数据库打穿了。
例子:一个明星爆出大瓜,微博缓存里这个热点key刚好过期了,瞬间千万请求都找不到缓存,全都去数据库查这条微博。
解决:1. 设置热点数据永不过期。2. 或者使用互斥锁(Mutex Lock):只有一个请求能拿到锁去数据库查询重建缓存,其他请求等待并轮询缓存。
- 缓存雪崩:大量的key在同一时间过期,或者Redis服务宕机了。
例子:双十一零点,大量商品缓存设置的都是2小时过期,结果在凌晨2点同时失效,所有请求瞬间涌向数据库,数据库直接"雪崩"般垮掉。
解决:1. 给key的过期时间加上一个随机值,避免集体失效。2. 构建Redis高可用集群(哨兵或集群模式),防止单机宕机导致的全盘崩溃。
持久化 (Persistence) - Redis怎么把内存数据存到硬盘?
- RDB (快照):在某一个时间点,给所有数据拍一张完整的照片存下来。恢复时直接把这个照片读进内存。
优点:文件小,恢复速度快。
缺点:可能会丢失从上次拍照到现在的数据(通常配置为分钟级)。
- AOF (日志):记下所有写的命令(比如set, hmset)。恢复时把所有这些命令重新执行一遍。
优点:数据完整性高,最多丢失1秒的数据。
缺点:文件大,恢复速度慢。
通常两者结合使用,用AOF保证数据不丢失,用RDB做快速恢复和备份。
总结:Redis提供两种持久化机制:
RDB:通过快照保存数据,适合备份和快速恢复,但可能丢失较多数据。
AOF:记录所有写操作命令,数据完整性高,但文件体积大。
淘汰策略 (Eviction Policies) - 内存满了,删谁?
当Redis内存用完时,再有新数据进来,就要淘汰(删除)一些旧数据了。策略有:
-
noeviction:谁也不删,直接报错。(默认策略,生产环境一般不用)
-
allkeys-lru:最常用!在所有key中,淘汰最近最少使用的(LRU算法)。
-
volatile-lru:在设置了过期时间的key中,淘汰最近最少使用的。
-
allkeys-random:所有key里随机淘汰。
-
volatile-random:设了过期时间的key里随机淘汰。
-
volatile-ttl:淘汰剩余寿命最短的(TTL最小的)key。
生产环境一般用 allkeys-lru。 在内存不足时淘汰最久未使用的键
主从、哨兵与集群 (High Availability)
- 主从复制 (Master-Slave):一台主服务器(Master)负责写,多台从服务器(Slave)负责读和备份。数据从主服务器自动同步到从服务器。
目的:数据备份和读写分离(提高读性能)。
问题:主服务器挂了,需要人工手动把一台从服务器切换成主,麻烦且会停机。
- 哨兵 (Sentinel):主从复制的"自动故障转移"版。哨兵是一个独立的进程,它不提供服务,只负责监控所有Redis服务器。当发现主服务器挂了,它能自动投票选出一个新的主服务器,并通知客户端新的主地址。
目的:实现高可用,解决主从模式下的手动切换问题。
- 集群 (Cluster):数据分片存储。将海量数据分散存储在不同的Redis节点(node)上,每个节点只存一部分数据。同时,每个节点还可以有自己的主从复制结构。
目的:解决海量数据存储和高并发写的问题,实现横向扩展(scale out)。
总结,高可用方案:
主从复制:实现数据冗余和读写分离。
哨兵模式:在主从基础上实现了自动故障转移,是高可用解决方案。
集群模式:通过数据分片实现横向扩展,是大型分布式解决方案。
zookeeper,dubbo
ZooKeeper:分布式协调服务
1、通俗理解
想象一家大型连锁餐厅的后厨管理系统:
ZooKeeper = 餐厅的中央调度系统
数据节点(ZNode) = 每个厨房的任务卡槽
监听机制(Watch) = 厨师订阅特定任务卡槽的状态变化
当主厨(Leader)分配任务时,会在特定卡槽挂上任务单,订阅该卡槽的厨师(Watcher)立即收到通知并开始工作。
2、核心功能
java
// 面试关键词速记:
// 1. 配置中心(Configuration Center)
// 2. 命名服务(Naming Service)
// 3. 分布式锁(Distributed Lock)
// 4. 集群管理(Cluster Management)
// 5. 注册中心(Registry)
3、在项目中的实际应用
java
@Configuration
public class ZkConfig {
// 连接ZooKeeper集群
@Bean
public CuratorFramework curatorFramework() {
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
CuratorFramework client = CuratorFrameworkFactory.builder()
.connectString("zk1:2181,zk2:2181,zk3:2181") // 集群地址
.sessionTimeoutMs(5000)
.connectionTimeoutMs(3000)
.retryPolicy(retryPolicy)
.namespace("myapp") // 命名空间隔离
.build();
client.start();
return client;
}
}
@Service
public class ConfigService {
@Autowired
private CuratorFramework zkClient;
// 1. 配置中心实现:动态获取配置
public String getConfig(String path) throws Exception {
byte[] data = zkClient.getData()
.forPath("/config/database/url");
return new String(data);
}
// 2. 分布式锁实现
public boolean tryLock(String lockPath, long waitTime) throws Exception {
InterProcessMutex lock = new InterProcessMutex(zkClient, lockPath);
return lock.acquire(waitTime, TimeUnit.SECONDS);
}
// 3. 服务注册
public void registerService(String serviceName, String instanceInfo) throws Exception {
String path = "/services/" + serviceName + "/instance-";
// 创建临时顺序节点,客户端断开自动删除
zkClient.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL)
.forPath(path, instanceInfo.getBytes());
}
}
Dubbo:分布式RPC框架
RPC
Remote Procedure Call(远程过程调用)
RPC(Remote Procedure Call,远程过程调用)是一种计算机通信协议,允许程序调用另一个地址空间(通常是另一台机器上)的过程或函数,而程序员无需显式编码这个远程调用的细节。在分布式系统中,RPC 使得开发分布式应用就像开发本地应用一样简单。
通俗理解:就像你点外卖,你不需要知道厨房在哪里、厨师是谁,只需要在手机上下单(调用),外卖就会送到你家(返回结果)。RPC 就是程序世界的"外卖系统"。

java
// 订单服务需要调用用户服务获取用户信息
@Service
public class OrderServiceImpl implements OrderService {
// 通过Dubbo RPC调用用户服务
@DubboReference(version = "1.0.0")
private UserService userService;
@Override
public OrderDTO createOrder(Long userId, OrderCreateRequest request) {
// RPC调用:像调用本地方法一样调用远程服务
UserDTO user = userService.getUserById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 创建订单逻辑...
Order order = new Order();
order.setUserId(userId);
order.setUserName(user.getUsername());
order.setUserPhone(user.getPhone());
// 另一个RPC调用:扣减库存
inventoryService.reduceStock(request.getSkuId(), request.getQuantity());
return convertToDTO(order);
}
}
RPC 解决的问题?
| 问题 | RPC解决方案 | 代码示例 |
|---|---|---|
| 服务间通信复杂 | 透明化远程调用,开发者像调用本地方法一样 | userService.getUserById() |
| 网络编程繁琐 | 封装了socket连接、序列化、反序列化等细节 | Dubbo自动管理连接池 |
| 服务发现困难 | 集成注册中心(ZooKeeper/Nacos) | 自动发现服务提供者 |
| 负载均衡 | 内置多种负载均衡策略 | loadbalance="roundrobin" |
| 容错处理 | 提供重试、降级、熔断等机制 | retries=2, cluster="failover" |
RPC优势 (RPC 性能优化手段)
优势对比表:
| 对比维度 | HTTP/REST | RPC |
|---|---|---|
| 性能 | 较低(HTTP头大,序列化效率低) | 高(二进制协议,体积小) |
| 开发效率 | 需要定义API文档,手动封装 | 高(IDL定义,自动生成代码) |
| 服务治理 | 需要额外集成 | 内置(负载均衡、熔断等) |
| 传输效率 | 文本格式(JSON/XML) | 二进制(Protobuf/Hessian) |
性能优化手段(基于Dubbo实践):
java
@Configuration
public class RpcOptimizationConfig {
// 1. 连接池优化
@Bean
public ProtocolConfig protocolConfig() {
ProtocolConfig config = new ProtocolConfig();
config.setName("dubbo");
config.setPort(20880);
config.setThreadpool("fixed"); // 固定线程池
config.setThreads(500); // 线程数
config.setQueues(0); // 不排队,直接拒绝
config.setAccepts(1000); // 最大连接数
return config;
}
// 2. 序列化优化(使用Kryo替代Hessian2)
@Bean
public SerializationOptimizer optimizer() {
return new SerializationOptimizer() {
@Override
public Collection<Class> getSerializableClasses() {
List<Class> classes = new ArrayList<>();
classes.add(UserDTO.class);
classes.add(OrderDTO.class);
return classes;
}
};
}
// 3. 客户端配置优化
@Bean
public ConsumerConfig consumerConfig() {
ConsumerConfig config = new ConsumerConfig();
config.setTimeout(3000); // 超时时间
config.setRetries(2); // 重试次数(非幂等操作设为0)
config.setActives(100); // 每服务每消费者最大并发
config.setConnections(10); // 每个服务对每个提供者的连接数
return config;
}
}
其他优化手段:
-
异步调用:减少线程阻塞
-
请求合并:多个小请求合并为一个大请求
-
结果缓存:缓存频繁调用的结果
-
压缩传输:对大数据进行压缩
HTTP 与 RPC
为什么有了 HTTP 还要使用 RPC
java
// 场景1:HTTP RESTful API(适合外部系统调用)
@RestController
@RequestMapping("/api/v1")
public class ExternalUserController {
@GetMapping("/users/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
// HTTP优势:通用、标准、易调试、防火墙友好
User user = userService.getUserById(id);
return ResponseEntity.ok()
.header("X-RateLimit-Limit", "100")
.contentType(MediaType.APPLICATION_JSON)
.body(convertToResponse(user));
}
}
// 场景2:RPC调用(适合内部微服务间调用)
@Service
public class InternalOrderService {
@DubboReference(
version = "1.0.0",
timeout = 100, // 低延迟要求
connections = 5, // 多路复用
serialization = "kryo" // 高效序列化
)
private UserService userService; // 内部接口,无需HTTP包装
public void processOrder(Order order) {
// RPC优势:高性能、强类型、服务治理
UserDetail detail = userService.getUserDetail(order.getUserId());
// 处理订单...
}
}
| 场景特征 | 推荐协议 | 理由 |
|---|---|---|
| 外部系统调用 | HTTP/REST | 通用性、标准化 |
| 内部服务调用 | RPC | 性能、治理 |
| 高并发低延迟 | RPC | 效率、资源利用 |
| 多语言异构 | gRPC/Thrift | 跨语言支持 |
| 简单快速验证 | HTTP | 开发调试简便 |
RPC知识要点总结
| 问题 | 核心要点 | 关键词 |
|---|---|---|
| RPC是什么 | 远程过程调用,透明化网络通信 | 透明调用、跨进程、分布式 |
| 解决了什么 | 简化分布式系统开发,封装网络复杂性 | 服务通信、服务治理、负载均衡 |
| 优势 | 高性能、强类型、内置治理 | 二进制协议、IDL、服务发现 |
| vs HTTP | 内部用RPC(性能),外部用HTTP(通用) | 性能 vs 通用性 |
| 设计要点 | 七层架构:协议→序列化→网络→代理→注册→负载→监控 | 模块化设计、扩展性 |
| 底层实现 | 动态代理 + 网络通信 + 反射调用 | Netty、动态代理、反射 |
| 容错处理 | 重试、降级、熔断、限流 | 容错链、Fallback、Circuit Breaker |
| 跨语言 | IDL定义接口 + 多语言Stub生成 | gRPC、Protobuf、Thrift |
Dubbo
核心组件
Provider(提供者)、Consumer(消费者)、Registry(注册中心)、Monitor(监控中心)、Container(容器)。
Dubbo负载均衡策略?
java
// 1. Random LoadBalance:随机(默认)
// 2. RoundRobin LoadBalance:轮询
// 3. LeastActive LoadBalance:最少活跃调用
// 4. ConsistentHash LoadBalance:一致性Hash
Dubbo的服务降级
通过mock参数配置降级类,当服务调用失败时返回兜底数据:
java
@DubboReference(mock = "force:return null") // 强制返回null
@DubboReference(mock = "fail:throw Exception") // 失败抛异常
@DubboReference(mock = "com.example.MockService") // 自定义Mock类
用ZooKeeper + Dubbo实现分布式系统
ZooKeeper作为注册中心,Dubbo服务提供者启动时向ZK注册服务信息(临时节点),消费者从ZK获取服务列表并缓存,通过负载均衡策略调用提供者。ZK监控服务提供者的存活状态(临时节点自动删除机制)。
总结:
ZooKeeper记忆口诀:
java
一树(树形结构)
二类(持久/临时节点)
三应用(配置中心、分布式锁、注册中心)
四特性(顺序一致性、原子性、可靠性、实时性)
五命令(create/get/set/delete/exists)
Dubbo调用流程记忆:
xml
启动注册 → 订阅通知 → 远程调用 → 监控统计
↑ ↓ ↓ ↓
Provider → Registry → Consumer → Monitor
实战中的经验要点:
-
ZooKeeper集群:生产环境至少3个节点,部署奇数个(选举机制需要)
-
Dubbo线程池:根据业务类型设置合适的线程模型(如IO密集型可增大线程数)
-
超时设置:Dubbo超时时间 > 数据库/Redis等下游服务的超时时间
-
版本控制:使用Dubbo的version字段实现灰度发布