You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在gRPC客户端拦截器的AsyncUnaryCall方法中实现异步调用

gRPC客户端拦截器实现缓存的解决方案

问题描述

为gRPC客户端调用实现缓存功能时,自定义拦截器重写AsyncUnaryCall方法遇到两个核心问题:

  1. 方法返回类型是AsyncUnaryCall<TResponse>而非Task,无法直接标记async并使用await调用异步缓存方法,导致代码编译失败
  2. 直接调用continuation委托会立即触发gRPC服务器请求,无法实现"先查缓存、命中则跳过请求"的逻辑

原问题代码示例

internal class MyCacheInterceptor : Interceptor
{
    private readonly IMyCacheService _cacheService;

    public MyCacheInterceptor(IMyCacheService cacheService)
    {
        _cacheService = cacheService;
    }

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        var key = GetCacheKey(request, context);
        var cacheValue = await _cacheService.GetCacheAsync<TResponse>(key);
        if (cacheValue != null)
        {
            var test = new AsyncUnaryCall<TResponse>(
                Task.FromResult(cacheValue),
                null!,
                null!,
                null!,
                null!);
        }
        else
        {
            return base.AsyncUnaryCall(request, context, continuation);
        }
    }
}

解决方案

核心思路是包装AsyncUnaryCall的ResponseAsync属性,将缓存逻辑嵌入到异步响应任务中,既满足AsyncUnaryCall的返回类型要求,又实现缓存优先的逻辑。

修改后的完整代码

internal class MyCacheInterceptor : Interceptor
{
    private readonly IMyCacheService _cacheService;

    public MyCacheInterceptor(IMyCacheService cacheService)
    {
        _cacheService = cacheService;
    }

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request, 
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        var cacheKey = GetCacheKey(request, context);
        
        // 封装缓存逻辑的异步响应方法
        async Task<TResponse> CachedResponseAsync()
        {
            // 1. 优先查询缓存
            var cachedValue = await _cacheService.GetCacheAsync<TResponse>(cacheKey);
            if (cachedValue != null)
            {
                return cachedValue;
            }

            // 2. 缓存未命中,执行gRPC请求
            var grpcCall = continuation(request, context);
            var response = await grpcCall.ResponseAsync;

            // 3. 将响应结果存入缓存(可根据需求添加过期时间等配置)
            await _cacheService.SetCacheAsync(cacheKey, response);

            return response;
        }

        // 创建原始调用对象(仅初始化,不会立即发起请求)
        var originalCall = continuation(request, context);
        
        // 返回包装后的AsyncUnaryCall,替换ResponseAsync为我们的缓存逻辑
        return new AsyncUnaryCall<TResponse>(
            CachedResponseAsync(),
            originalCall.ResponseHeadersAsync,
            originalCall.GetStatus,
            originalCall.GetTrailers,
            originalCall.Dispose);
    }

    // 生成唯一缓存键的方法(可根据业务需求自定义)
    private string GetCacheKey<TRequest>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context)
    {
        var methodFullName = $"{context.Method.ServiceName}.{context.Method.Name}";
        var requestSignature = System.Text.Json.JsonSerializer.Serialize(request);
        return $"{methodFullName}:{requestSignature}";
    }
}

关键说明

  • 异步逻辑封装:通过局部异步方法CachedResponseAsync处理缓存查询、gRPC请求、缓存更新,合法使用await,同时适配AsyncUnaryCall的返回要求
  • 延迟请求触发:调用continuation(request, context)仅创建gRPC调用对象,不会立即发送请求,只有当await grpcCall.ResponseAsync时才会真正发起服务器请求
  • 完整功能保留:复用原始调用的ResponseHeadersAsync、GetStatus、Dispose等属性,保证gRPC的元数据、状态处理、资源释放等功能正常工作

注意事项

  • 缓存键的生成需保证全局唯一,避免不同请求的缓存值冲突
  • 可根据业务需求添加缓存过期策略、缓存失效逻辑(比如主动清除缓存)
  • 对于有状态或实时性要求高的接口,需谨慎使用缓存,避免数据一致性问题
  • 建议在CachedResponseAsync中添加异常处理,捕获缓存操作或gRPC请求的异常并做相应处理

内容的提问来源于stack exchange,提问作者Alexanr

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 08:55:23