v4.0 目标是引入可插拔的请求/响应处理链,并通过 Testcontainers 实现测试环境隔离:
- Filter 接口:
preFilter/postFilter,支持短路和后置处理 - CacheFilter:Consumer 端结果缓存,相同请求命中缓存直接返回,节约网络开销
- MockFilter:按方法签名拦截请求返回预设值,用于测试场景
- Filter 链:按
order()排序,preFilter顺序执行,postFilter逆序执行 - Testcontainers:ZkRegistryCenter 集成测试不依赖本地 Zookeeper,自动启动隔离容器
marpc-core/src/main/java/com/malinghan/marpc/
└── filter/ # v4.0 新增
├── Filter.java # Filter 接口
├── CacheFilter.java # Consumer 端缓存
└── MockFilter.java # 接口 Mock
marpc-core/src/test/java/com/malinghan/marpc/
├── filter/
│ └── FilterChainTest.java # Filter 单元测试(无需 Spring 容器)
└── registry/
└── ZkRegistryCenterTest.java # Zookeeper 集成测试(Testcontainers)
marpc-demo-consumer/src/main/java/com/malinghan/marpc/consumer/
├── Scene5CacheFilter.java # 缓存场景演示
└── Scene6MockFilter.java # Mock 场景演示
public interface Filter {
/**
* 前置处理。返回非 null 时短路,跳过后续 Filter 和远程调用。
*/
RpcResponse preFilter(RpcRequest request);
/**
* 后置处理,可修改或记录响应。
*/
void postFilter(RpcRequest request, RpcResponse response);
/**
* 执行顺序,数值越小越先执行。默认 0。
*/
default int order() { return 0; }
}Consumer 调用
│
▼
┌─────────────────────────────────────────┐
│ preFilter 顺序执行(order 小的先执行) │
│ MockFilter(order=0) → CacheFilter(order=10) │
│ 任意返回非 null → 短路,直接返回 │
└─────────────────────────────────────────┘
│ 全部 miss
▼
远程调用(HTTP POST /marpc)
│
▼
┌─────────────────────────────────────────┐
│ postFilter 逆序执行 │
│ CacheFilter(写入缓存) → MockFilter(无操作) │
└─────────────────────────────────────────┘
│
▼
返回结果
MarpcInvocationHandler 核心逻辑:
// preFilter:任意 Filter 返回非 null 则短路
for (Filter filter : filters) {
RpcResponse shortCircuit = filter.preFilter(request);
if (shortCircuit != null) {
return convertResponse(method, shortCircuit);
}
}
// 发起远程调用
RpcResponse response = post(instance, request);
// postFilter:逆序执行
List<Filter> reversed = new ArrayList<>(filters);
Collections.reverse(reversed);
for (Filter filter : reversed) {
filter.postFilter(request, response);
}缓存 key = service#methodSign#args,命中时短路跳过远程调用:
@Override
public RpcResponse preFilter(RpcRequest request) {
String key = buildKey(request);
RpcResponse cached = cache.get(key);
if (cached != null) {
log.info("[CacheFilter] 命中缓存: {}", key);
return cached; // 短路
}
return null;
}
@Override
public void postFilter(RpcRequest request, RpcResponse response) {
if (response.isStatus()) {
cache.put(buildKey(request), response); // 成功响应才缓存
}
}按方法签名注册 mock 规则,匹配时短路返回预设值:
// 注册 mock 规则
mockFilter.mock("hello@1_java.lang.String", "mocked-response");
// 清除所有 mock
mockFilter.clearMocks();通过 application.yaml 开关控制:
marpc:
filter:
cache:
enabled: true # 启用 CacheFilter
mock:
enabled: true # 启用 MockFilterMarpcConfig 根据配置组装 Filter 链:
@Bean
public List<Filter> filterChain(CacheFilter cacheFilter, MockFilter mockFilter) {
List<Filter> chain = new ArrayList<>();
if (mockEnabled) chain.add(mockFilter); // order=0,最先执行
if (cacheEnabled) chain.add(cacheFilter); // order=10,次之
return chain;
}<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.19.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.19.7</version>
<scope>test</scope>
</dependency>@Testcontainers
class ZkRegistryCenterTest {
@Container
static final GenericContainer<?> ZK = new GenericContainer<>(
DockerImageName.parse("zookeeper:3.8"))
.withExposedPorts(2181);
@BeforeEach
void setUp() {
String zkAddress = ZK.getHost() + ":" + ZK.getMappedPort(2181);
registry = new ZkRegistryCenter(zkAddress, "test-app", "test");
registry.start();
}
...
}每个测试方法使用独立的 Zookeeper 容器,测试结束自动销毁,完全隔离。
Consumer 启动后自动执行 6 个场景:
=== Scene5: CacheFilter 缓存效果 ===
注意观察日志:第二次调用应出现 [CacheFilter] 命中缓存
[PASS] 第一次调用(缓存 miss) => hello, cache-test
[CacheFilter] 写入缓存: ...HelloService#hello@1_java.lang.String#[cache-test]
[CacheFilter] 命中缓存: ...HelloService#hello@1_java.lang.String#[cache-test]
[PASS] 第二次调用(缓存 hit) => hello, cache-test
[PASS] 不同参数(缓存 miss) => hello, other
最终缓存条目数: 2
=== Scene5 完成 ===
=== Scene6: MockFilter 拦截返回 ===
已注册 mock: hello@1_java.lang.String -> mocked-response
[MockFilter] 拦截请求: hello@1_java.lang.String -> mock=mocked-response
[PASS] mock 拦截(不发网络请求) => mocked-response
已清除 mock 规则
[PASS] mock 清除后恢复正常调用 => hello, world
=== Scene6 完成 ===
FilterChainTest 不依赖 Spring 容器,直接验证 Filter 逻辑:
| 测试方法 | 验证内容 |
|---|---|
cache_miss_returnsNull |
缓存未命中返回 null |
cache_hit_returnsShortCircuit |
缓存命中返回短路响应 |
cache_failedResponse_notCached |
失败响应不写入缓存 |
cache_differentArgs_separateEntries |
不同参数独立缓存条目 |
cache_clear_removesAllEntries |
clear() 清空缓存 |
mock_matchingSign_returnsShortCircuit |
签名匹配时短路 |
mock_noMatchingSign_returnsNull |
签名不匹配返回 null |
mock_clearMocks_noLongerMatches |
clearMocks() 后不再拦截 |
order_mockBeforeCache |
MockFilter(0) 先于 CacheFilter(10) |
shortCircuit_mockPreventsRemoteCall |
preFilter 非 null 即短路 |
ZkRegistryCenterTest 使用 Testcontainers 验证注册中心:
| 测试方法 | 验证内容 |
|---|---|
register_and_fetchAll |
注册后可查询到实例 |
unregister_removesInstance |
注销后实例消失 |
fetchAll_noService_returnsEmpty |
未注册服务返回空列表 |
register_multipleInstances |
多实例注册 |
subscribe_notifiedOnChange |
订阅后变更触发回调 |
# 单元测试(无需 Docker)
mvn test -pl marpc-core -Dtest=FilterChainTest
# 集成测试(需要 Docker,Testcontainers 自动拉取镜像)
mvn test -pl marpc-core -Dtest=ZkRegistryCenterTest
# 全部测试
mvn test -pl marpc-core| 变更点 | v3.0 | v4.0 |
|---|---|---|
| Filter 机制 | 无 | Filter 接口 + CacheFilter + MockFilter |
| 缓存 | 无 | CacheFilter,命中时短路跳过远程调用 |
| Mock | 无 | MockFilter,按方法签名拦截返回预设值 |
| Filter 顺序 | — | order() 控制,preFilter 顺序,postFilter 逆序 |
| 测试隔离 | 依赖本地 Zookeeper | Testcontainers 自动启动隔离容器 |
MarpcInvocationHandler |
直接调用 | 先走 Filter 链,再发起远程调用 |
ConsumerBootstrap |
无 Filter | 接收 List<Filter> 传入代理 |
MarpcConfig |
无 Filter Bean | CacheFilter、MockFilter、filterChain Bean |
| Consumer 配置 | — | marpc.filter.cache.enabled / marpc.filter.mock.enabled |
- 重试机制:失败自动重试,支持配置重试次数和退避策略
- 熔断器:半开状态恢复,故障实例自动隔离
- 路由策略:灰度发布、按权重路由
- Netty 传输:替换 OkHttp + Spring MVC,提升吞吐量