Dubbo笔记 ⑲ :隐式参数传递

一、前言

本系列为个人Dubbo学习笔记,内容基于《深度剖析Apache Dubbo 核心技术内幕》, 过程参考官方源码分析文章,仅用于个人笔记记录。本文分析基于Dubbo2.7.0版本,由于个人理解的局限性,若文中不免出现错误,感谢指正。


Dubbo提供了隐式参数传递的功能,即服务调用方可以通过RpcContext.getContext().setAttachment() 方法设置附加属性键值对,然后设置的键值对可以在服务提供方服务方法内获取。

二、简易demo

@Service
public class MainSimpleDemoServiceImpl implements SimpleDemoService {
    @Override
    public String sayHello(String msg) {
    	// 这里会将上下文的参数获取到并返回
        Object context = RpcContext.getContext().getAttachment("context");
        return "MainSimpleDemoServiceImpl : " + msg + " context = " + context;
    }
}

// 消费者。设置参数 context =  SimpleConsumer
public class SimpleConsumer {

    public static void main(String[] args) throws InterruptedException {
        ReferenceConfig<SimpleDemoService> referenceConfig = DubboUtil.referenceConfig("dubbo-consumer", SimpleDemoService.class);
        referenceConfig.setMonitor("http://localhost:8080");
        SimpleDemoService demoService = referenceConfig.get();
        // 消费者设置隐式参数 context = SimpleConsumer,提供者可以获取到该参数
        RpcContext.getContext().setAttachment("context", "SimpleConsumer");
        // 输出 MainSimpleDemoServiceImpl : SimpleConsumer context = SimpleConsumer
        System.out.println(demoService.sayHello("SimpleConsumer"));
    }
    
}

// 服务提供者
public class SimpleProvider {
    public static void main(String[] args) throws IOException {
        ServiceConfig<SimpleDemoService> serviceConfig = DubboUtil.serviceConfig("dubbo-provider", SimpleDemoService.class, new MainSimpleDemoServiceImpl());
        serviceConfig.export();
        System.out.println("service is start");
        System.in.read();
    }
}

这里可以看到,消费者在调用sayHello 前,在上下文中通过 RpcContext.getContext().setAttachment("context", "SimpleConsumer"); 保存了隐式参数 context = SimpleConsumer。在提供者端可以通过 RpcContext.getContext().getAttachment("context"); 获取到隐式参数的值。

不过需要注意的是,上下文参数是一次性的,即设置一次参数只能获取一次。

三、源码分析

Dubbo 隐式参数实现依赖于 ConsumerContextFilter & ContextFilter 完成,下面我们来看具体逻辑:

1. AbstractClusterInvoker

消费者发起调用时,会在 AbstractClusterInvoker#invoke 方法中将 Rpc上下文中的附件(Attachments) 作为调用附件传递给提供者端。具体如下:


当消费者进行服务调用时会依照下面的时序图:
在这里插入图片描述
其中第五步的过程如下:

MockClusterInvoker#invoke => AbstractClusterInvoker#invoke =>  FailoverClusterInvoker#doInvoke

AbstractClusterInvoker#invoke的实现如下:

    @Override
    public Result invoke(final Invocation invocation) throws RpcException {
        checkWhetherDestroyed();

        // binding attachments into invocation.
        // 获取上下文参数
        Map<String, String> contextAttachments = RpcContext.getContext().getAttachments();
        // 如果上下文参数不为空,则添加到 RpcInvocation 中。RpcInvocation会随着网络调用传递到提供者端
        if (contextAttachments != null && contextAttachments.size() != 0) {
            ((RpcInvocation) invocation).addAttachments(contextAttachments);
        }
		// 获取服务提供者列表
        List<Invoker<T>> invokers = list(invocation);
        // 初始化负载均衡策略
        LoadBalance loadbalance = initLoadBalance(invokers, invocation);
        RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
        // 进行调用
        return doInvoke(invocation, invokers, loadbalance);
    }

2. ConsumerContextFilter

ConsumerContextFilter 作用域消费者端。作用是在服务调用结束后清除上下文中的附件信息。

@Activate(group = Constants.CONSUMER, order = -10000)
public class ConsumerContextFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    	// 设置上下文的信息
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
                .setLocalAddress(NetUtils.getLocalHost(), 0)
                .setRemoteAddress(invoker.getUrl().getHost(),
                        invoker.getUrl().getPort());
        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        try {
            // TODO should we clear server context?
            // 清除 ServerContext
            RpcContext.removeServerContext();
            // 进行服务调用
            return invoker.invoke(invocation);
        } finally {
            // TODO removeContext? but we need to save future for RpcContext.getFuture() API. If clear attachments here, attachments will not available when postProcessResult is invoked.
            // 清除当前线程中添加的附加属性
            RpcContext.getContext().clearAttachments();
        }
    }

    @Override
    public Result onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
    	// 调用返回时设置 ServerContext
        RpcContext.getServerContext().setAttachments(result.getAttachments());
        return result;
    }
}

3. ContextFilter

ContextFilter 作用于提供者端,作用是将Rpc 调用参数中的附件保存到当前 Rpc 上下文中。

@Activate(group = Constants.PROVIDER, order = -10000)
public class ContextFilter implements Filter {

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
    	// 获取 Invocation 中的属性添加到上下文中
        Map<String, String> attachments = invocation.getAttachments();
        if (attachments != null) {
        	// 移除一些系统参数
            attachments = new HashMap<String, String>(attachments);
            attachments.remove(Constants.PATH_KEY);
            attachments.remove(Constants.GROUP_KEY);
            attachments.remove(Constants.VERSION_KEY);
            attachments.remove(Constants.DUBBO_VERSION_KEY);
            attachments.remove(Constants.TOKEN_KEY);
            attachments.remove(Constants.TIMEOUT_KEY);
            // Remove async property to avoid being passed to the following invoke chain.
            attachments.remove(Constants.ASYNC_KEY);
            attachments.remove(Constants.TAG_KEY);
            attachments.remove(Constants.FORCE_USE_TAG);
        }
        RpcContext.getContext()
                .setInvoker(invoker)
                .setInvocation(invocation)
//                .setAttachments(attachments)  // merged from dubbox
                .setLocalAddress(invoker.getUrl().getHost(),
                        invoker.getUrl().getPort());

        // merged from dubbox
        // we may already added some attachments into RpcContext before this filter (e.g. in rest protocol)
        // 如果附加属性不为空则设置到上下文对象中。
        if (attachments != null) {
            if (RpcContext.getContext().getAttachments() != null) {
                RpcContext.getContext().getAttachments().putAll(attachments);
            } else {
                RpcContext.getContext().setAttachments(attachments);
            }
        }

        if (invocation instanceof RpcInvocation) {
            ((RpcInvocation) invocation).setInvoker(invoker);
        }
        try {
            return invoker.invoke(invocation);
        } finally {
            // IMPORTANT! For async scenario, we must remove context from current thread, so we always create a new RpcContext for the next invoke for the same thread.
            // 调用结束移除 上下文信息
            RpcContext.removeContext();
            RpcContext.removeServerContext();
        }
    }

    @Override
    public Result onResponse(Result result, Invoker<?> invoker, Invocation invocation) {
        // pass attachments to result
        // 处理结束后,向 ServerContext 中添加附件属性,Result 会随着网络请求返回给 消费者
       
        result.addAttachments(RpcContext.getServerContext().getAttachments());
        return result;
    }
}

四、总结

Dubbo的隐式参数实现比较简单:

  1. 服务调用时在上下文中设置隐式参数
  2. AbstractClusterInvoker#invoke 将上下文中的隐式参数保存到调用参数中
  3. ContextFilter 将调用参数中的隐式参数取出,设置到当前Rpc上下文中,供服务方法使用。

1. 个人的疑问

我们在上面消费者向提供者传递隐式参数的传递是通过 RpcContext.getContext() 来实现,在上面的代码中还看到一个 RpcContext.getServerContext(),用于提供者向消费者传递隐式参数,其相关逻辑如下:

  1. 当消费者发起调用后,提供者端在ContextFilter#invoke 方法中,服务提供者通过 invoker.invoke(invocation);进行服务调用。

  2. ContextFilter#onResponse 方法中结果完成后将 RpcContext.getServerContext() 中的附件保存到 Result 中。
    在这里插入图片描述

  3. 结果返回到消费者端后,ConsumerContextFilter#onResponse 方法会 将接收到的 Result 的附件保存到当前 RpcContext.getServerContext() 中供消费者使用。
    在这里插入图片描述


上述逻辑貌似可以通过 ServerContext 给消费者端传递参数,但是在第一步中,消费者发起调用,当调用结束后会后会清除当前 RpcContext.getServerContext()RpcContext.getContext();,之后再去调用ContextFilter#onResponse。这也就是说,我们在调用方法中无法通过 RpcContext.getServerContext().setAttachment 的来设置附件返回给消费者端。因为调用方法中所使用的上下文在调用结束后就会被清除。
在这里插入图片描述


以上:内容部分参考
《深度剖析Apache Dubbo 核心技术内幕》
https://dubbo.apache.org/zh/docs/v2.7/dev/source/
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值