Skip to content

Latest commit

 

History

History
311 lines (245 loc) · 9.24 KB

v4.0.md

File metadata and controls

311 lines (245 loc) · 9.24 KB

marpc v4.0 — 可插拔 Filter 链 & Testcontainers 测试隔离

版本概述

v4.0 目标是引入可插拔的请求/响应处理链,并通过 Testcontainers 实现测试环境隔离:

  1. Filter 接口:preFilter / postFilter,支持短路和后置处理
  2. CacheFilter:Consumer 端结果缓存,相同请求命中缓存直接返回,节约网络开销
  3. MockFilter:按方法签名拦截请求返回预设值,用于测试场景
  4. Filter 链:按 order() 排序,preFilter 顺序执行,postFilter 逆序执行
  5. 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 场景演示

核心功能

1. Filter 接口

public interface Filter {
    /**
     * 前置处理。返回非 null 时短路,跳过后续 Filter 和远程调用。
     */
    RpcResponse preFilter(RpcRequest request);

    /**
     * 后置处理,可修改或记录响应。
     */
    void postFilter(RpcRequest request, RpcResponse response);

    /**
     * 执行顺序,数值越小越先执行。默认 0。
     */
    default int order() { return 0; }
}

2. Filter 链执行流程

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);
}

3. CacheFilter

缓存 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);  // 成功响应才缓存
    }
}

4. MockFilter

按方法签名注册 mock 规则,匹配时短路返回预设值:

// 注册 mock 规则
mockFilter.mock("hello@1_java.lang.String", "mocked-response");

// 清除所有 mock
mockFilter.clearMocks();

5. Filter 链配置

通过 application.yaml 开关控制:

marpc:
  filter:
    cache:
      enabled: true   # 启用 CacheFilter
    mock:
      enabled: true   # 启用 MockFilter

MarpcConfig 根据配置组装 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;
}

Testcontainers 测试隔离

依赖

<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>

ZkRegistryCenterTest

@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 容器,测试结束自动销毁,完全隔离。


Demo 测试场景

Consumer 启动后自动执行 6 个场景:

Scene5:CacheFilter 缓存效果

=== 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 拦截返回

=== 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 升级要点

变更点 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

后续规划(v5.0)

  • 重试机制:失败自动重试,支持配置重试次数和退避策略
  • 熔断器:半开状态恢复,故障实例自动隔离
  • 路由策略:灰度发布、按权重路由
  • Netty 传输:替换 OkHttp + Spring MVC,提升吞吐量