你管这个叫Dubbo?(一)

hexiaox810
发布于 2022-6-2 16:39
浏览
0收藏

 

RPC框架的实现
又到年初了,大家又要开始准备面试了。为了方便大家,我就写几篇面试相关的文章吧,这次是Dubbo

相信很多小伙伴已经看了很多Dubbo的八股文了。比如,Dubbo支持哪些序列化框架,支持哪些注册中心,支持哪些集群容错策略,支持服务降级吗?但是你知道Dubbo服务导出和服务引入的过程吗?服务降级是如何实现的?等等

本文就从源码的角度来分享一下Dubbo的整个调用过程(放心,图示为主,辅助一少部分源码)

「RPC框架的实现基本上都是如下架构」

你管这个叫Dubbo?(一)-鸿蒙开发者社区

一个RPC调用的过程如下

  1. 调用方发送请求后由代理类将调用的方法,参数组装成能进行网络传输的消息体
  2. 调用方代理类将消息体发送到提供方
  3. 提供方代理类将消息进行解码,得到调用的方法和参数
  4. 提供方代理类执行相应的方法,并将结果返回
    「协议,编解码,序列化的部分不是本文的重点,我就不分析了,有兴趣的可以看我之前的文章。」

首先来手写一个极简版的RPC框架,以便你对上面的流程有一个更深的认识

手写一个简单的PRC框架

封装网络请求对象

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RpcRequest implements Serializable {

    private String interfaceName;
    private String methodName;
    private Class<?>[] paramTypes;
    private Object[] parameters;
}

根据interfaceName可以确定需要调用的接口,methodName和paramTypes则可以确定要调用接口的方法名,定位到具体的方法,传入参数即可调用方法

封装调用接口

封装接口到api模块,producer端写实现逻辑,consumer端写调用逻辑

public interface HelloService {
    String sayHello(String content);
}
public interface UpperCaseService {
    String toUpperCase(String content);
}

开发producer端

public class HelloServiceImpl implements HelloService {

    @Override
    public String sayHello(String content) {
        return "hello " + content;
    }
}
public class UpperCaseServiceImpl implements UpperCaseService {

    @Override
    public String toUpperCase(String content) {
        return content.toUpperCase();
    }
}

ServiceMap保存了producer端接口名和接口实现类的映射关系,这样可以根据请求对象的接口名,找到对应的实现类

public class ServiceMap {

    // 接口名 -> 接口实现类
    private static Map<String, Object> serviceMap = new HashMap<>();

    public static void registerService(String serviceKey, Object service) {
        serviceMap.put(serviceKey, service);
    }

    public static Object lookupService(String serviceKey) {
        return serviceMap.get(serviceKey);
    }
}

为了提高服务端的并发度,我们将每一个请求的处理过程放到线程池中

@Slf4j
public class RequestHandler implements Runnable {

    private Socket socket;

    public RequestHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
             ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())) {
            RpcRequest rpcRequest = (RpcRequest) inputStream.readObject();
            Object service = ServiceMap.lookupService(rpcRequest.getInterfaceName());
            Method method = service.getClass().getMethod(rpcRequest.getMethodName(), rpcRequest.getParamTypes());
            Object result = method.invoke(service, rpcRequest.getParameters());
            outputStream.writeObject(result);
        } catch (Exception e) {
            log.error("invoke method error", e);
            throw new RuntimeException("invoke method error");
        }
    }

}

 

启动服务端

public class RpcProviderMain {

    private static final ExecutorService executorService = Executors.newCachedThreadPool();

    public static void main(String[] args) throws Exception {

        HelloService helloService = new HelloServiceImpl();
        UpperCaseService upperCaseService = new UpperCaseServiceImpl();
        // 将需要暴露的接口注册到serviceMap中
        ServiceMap.registerService(HelloService.class.getName(), helloService);
        ServiceMap.registerService(UpperCaseService.class.getName(), upperCaseService);

        ServerSocket serverSocket = new ServerSocket(8080);

        while (true) {
            // 获取一个套接字(阻塞)。所以为了并行,来一个请求,开一个线程处理
            // 为了复用线程,用了threadPool
            final Socket socket = serverSocket.accept();
            executorService.execute(new RequestHandler(socket));
        }
    }
}

 

开发consumer端

前面说过,我们要通过动态代理对象解耦方法调用和网络调用,所以接下来我们就写一下动态代理对象的实现逻辑

生成一个代理对象的过程很简单

  1. 实现InvocationHandler接口,在invoke方法中增加代理逻辑
  2. 调用Proxy.newProxyInstance方法生成代理对象,3个参数分别是ClassLoader,代理对象需要实现的接口数组,InvocationHandler接口实现类
  3. 当执行代理执行实现的接口方法时,会调用到InvocationHandler#invoke,这个方法中增加了代理逻辑哈。
    public class ConsumerProxy {
    
        public static <T> T getProxy(final Class<T> interfaceClass, final String host, final int port) {
    
            return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
                    new Class<?>[]{interfaceClass}, new ConsumerInvocationHandler(host, port));
        }
    }​

可以看到代理对象的主要功能就是组装请求参数,然后发起网络调用

@Slf4j
public class ConsumerInvocationHandler implements InvocationHandler {

    private String host;
    private Integer port;

    public ConsumerInvocationHandler(String host, Integer port) {
        this.host = host;
        this.port = port;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        try (Socket socket = new Socket(host, port);
             ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
             ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
            RpcRequest rpcRequest = RpcRequest.builder()
                    .interfaceName(method.getDeclaringClass().getName())
                    .methodName(method.getName())
                    .paramTypes(method.getParameterTypes())
                    .parameters(args).build();
            outputStream.writeObject(rpcRequest);
            Object result = inputStream.readObject();
            return result;
        } catch (Exception e) {
            log.error("consumer invoke error", e);
            throw new RuntimeException("consumer invoke error");
        }
    }
}

 

此时我们只需要通过ConsumerProxy#getProxy方法,就能很方便的获取到代理对象。通过代理对象调用远程方法和调用本地方法一样方便

public class RpcConsumerMain {

    public static void main(String[] args)  {

        // 因为这是一个小demo,就不拆分多模块了
        // 这个HelloService是通过网络调用的HelloServiceImpl,而不是本地调用
        HelloService helloService = ConsumerProxy.getProxy(HelloService.class, "127.0.0.1", 8080);
        // hello world
        System.out.println(helloService.sayHello("world"));
        UpperCaseService upperCaseService = ConsumerProxy.getProxy(UpperCaseService.class, "127.0.0.1", 8080);
        // THIS IS CONTENT
        System.out.println(upperCaseService.toUpperCase("this is content"));
    }
}

至此我们已经把一个RPC框架最核心的功能就实现了,是不是很简单。「其实Dubbo的源码也很简单,只不过增加了很多扩展功能,所以大家有时候会认为比较难。」

所以我们就来分析一下核心的扩展功能。比如Filter,服务降级,集群容错等是如何实现的?其他的扩展功能,比如支持多种注册中心,支持多种序列化框架,支持多种协议,基本不会打交道,所以就不浪费时间了

从前面的图示我们知道,代理类在服务调用和响应过程中扮演着重要的角色。「在Dubbo中,代理类有个专有名词叫做Invoker,而Dubbo中就是通过对这个Invoker不断进行代理增加各种新功能的」

 

文章转自公众号:Java识堂

分类
标签
已于2022-6-2 16:39:35修改
收藏
回复
举报
回复
    相关推荐