Easy-RPC开源项目学习-客户端

项目地址:https://gitcode.com/shaogezhu/easy-rpc


一、自定义注解

使用注解@EasyRpcReference用于声明一个需要远程调用服务的客户端。其中包含一些服务调用参数设置,具体可以见源码com/shaogezhu/easy/rpc/spring/starter/common/EasyRpcReference.java

二、客户端自动配置类

代码如下,其中最主要的为两个接口。

public class RpcClientAutoConfiguration implements BeanPostProcessor, ApplicationListener<ApplicationReadyEvent> {

    private static RpcReference rpcReference = null;
    private static Client client = null;
    private volatile boolean needInitClient = false;
    private volatile boolean hasInitClientConfig = false;
    private static final Logger LOGGER = LoggerFactory.getLogger(RpcClientAutoConfiguration.class);
    
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {...}
    
    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {...}
 }

第一个接口BeanPostProcessor,与服务端不同的是,实现该接口的作用是在Bean注入前的调用postProcessAfterInitialization方法,以便通过反射生成代理类。第二个接口ApplicationListener,顾名思义在容器启动时进行监听,实现方法onApplicationEvent

1.postProcessAfterInitialization() 方法

代码如下,在该方法主要实现代理类的功能。在每一个Bean进行注入之前,通过反射获取类的注解,field.isAnnotationPresent(EasyRpcReference.class)判断当前类是否是一个远程调用客户端类。

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(EasyRpcReference.class)) {
                if (!hasInitClientConfig) {
                    client = new Client();
                    try {
                        client.initClientConfig();
                        rpcReference = client.initClientApplication();
                    } catch (Exception e) {
                        LOGGER.error("[IRpcClientAutoConfiguration] postProcessAfterInitialization has error ", e);
                        throw new RuntimeException(e);
                    }
                    hasInitClientConfig = true;
                }
                needInitClient = true;
                EasyRpcReference easyRpcReference = field.getAnnotation(EasyRpcReference.class);
                try {
                    field.setAccessible(true);
                    Object refObj = field.get(bean);
                    RpcReferenceWrapper rpcReferenceWrapper = new RpcReferenceWrapper();
                    rpcReferenceWrapper.setAimClass(field.getType());
                    rpcReferenceWrapper.setGroup(easyRpcReference.group());
                    rpcReferenceWrapper.setServiceToken(easyRpcReference.serviceToken());
                    rpcReferenceWrapper.setUrl(easyRpcReference.url());
                    rpcReferenceWrapper.setTimeOut(easyRpcReference.timeOut());
                    //失败重试次数
                    rpcReferenceWrapper.setRetry(easyRpcReference.retry());
                    rpcReferenceWrapper.setAsync(easyRpcReference.async());
                    // 得到代理对象
                    refObj = rpcReference.get(rpcReferenceWrapper);
                    // 注入属性
                    field.set(bean, refObj);
                    client.doSubscribeService(field.getType());
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        }
        return bean;
    }

第一步,对Netty客户端进行初始化,client.initClientConfig()
第二部,初始化完成通过client.initClientApplication()得到静态远程调用类RpcReference
第三步,实现代理类后,再对静态远程调用类进行修饰,添加有关服务调用的配置信息,例如URL、Token等。
第四步,进行Bean实例的注入field.set(bean, refObj),此时该Bean就为代理对象的实例。
第五步,服务订阅client.doSubscribeService(field.getType())

2.onApplicationEvent() 方法

该方法主要是在Springboot容器启动时,同时启动Netty客户端建立client.doConnectServer()与Netty服务端连接和开启异步发送线程client.startClient()

    @Override
    public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
        if (needInitClient && client != null) {
            LOGGER.info(" ================== [{}] started success ================== ", CLIENT_CONFIG.getApplicationName());
            client.doConnectServer();
            client.startClient();
        }
    }

三、关键类和方法

1.RpcReference远程调用类

远程调用类代码如下。其中ProxyFactory是代理工厂,支持多种实现。使用<T> T get(RpcReferenceWrapper<T> rpcReferenceWrapper)方法来生成对应的代理类。

public class RpcReference {
    public ProxyFactory proxyFactory;
    public RpcReference(ProxyFactory proxyFactory) {
        this.proxyFactory = proxyFactory;
    }
    /**
     * 根据接口类型获取代理对象
     */
    public <T> T get(RpcReferenceWrapper<T> rpcReferenceWrapper) throws Throwable {
        initGlobalRpcReferenceConfig(rpcReferenceWrapper);
        return proxyFactory.getProxy(rpcReferenceWrapper);
    }
    private void initGlobalRpcReferenceConfig(RpcReferenceWrapper<?> rpcReferenceWrapper) {
        if (CommonUtil.isEmpty(rpcReferenceWrapper.getTimeOut())) {
            rpcReferenceWrapper.setTimeOut(CLIENT_CONFIG.getTimeOut());
        }
    }
}
--------------------------------------------------------------------------------------------
// 代理工厂接口
public interface ProxyFactory {
    <T> T getProxy(final RpcReferenceWrapper<T> rpcReferenceWrapper) throws Throwable;
}
// 代理工厂的具体实现
public class JavassistProxyFactory implements ProxyFactory {

    @Override
    public <T> T getProxy(RpcReferenceWrapper<T> rpcReferenceWrapper) throws Throwable {
        return (T) ProxyGenerator.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                rpcReferenceWrapper.getAimClass(), new JavassistInvocationHandler(rpcReferenceWrapper));
    }
}
--------------------------------------------------------------------------------------------

2.Client客户端类

该类是Client的核心类。

public class Client {

    private AbstractRegister abstractRegister;

    private final Bootstrap bootstrap = new Bootstrap();

    public Bootstrap getBootstrap() { return bootstrap; }
    
	public void initClientConfig() { CLIENT_CONFIG = PropertiesBootstrap.loadClientConfigFromLocal(); }
	
	public RpcReference initClientApplication() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {...}
	/**
     * 启动服务之前需要预先订阅对应的dubbo服务
     */
    public void doSubscribeService(Class<?> serviceBean) {...}
     /**
     * 开始和各个provider建立连接
     */
    public void doConnectServer() {...}
	 /**
     * 开启发送线程,专门从事将数据包发送给服务端
     */
    public void startClient() {...}
     /**
     * 异步发送信息任务
     */
    class AsyncSendJob implements Runnable {...}
}

2.1 initClientApplication()方法

该方法主要实现以下几个步骤。

public RpcReference initClientApplication() throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
		// 1.初始化Netty设置
		NioEventLoopGroup clientGroup = new NioEventLoopGroup();
		Bootstrap bootstrap = new Bootstrap();
		......
        // 2.初始化连接器
        ConnectionHandler.setBootstrap(bootstrap);

        // 3.初始化监听器
        RpcListenerLoader rpcListenerLoader = new RpcListenerLoader();
        rpcListenerLoader.init();
        
        // 4.初始化路由策略
        String routerStrategy = CLIENT_CONFIG.getRouterStrategy();
        EXTENSION_LOADER.loadExtension(Router.class);
		......

        // 5.初始化序列化器
        String clientSerialize = CLIENT_CONFIG.getClientSerialize();
        EXTENSION_LOADER.loadExtension(SerializeFactory.class);
        ......

        // 6.初始化过滤链
        ClientFilterChain clientFilterChain = new ClientFilterChain();
        EXTENSION_LOADER.loadExtension(ClientFilter.class);
        ......

        // 7.初始化代理工厂
        String proxyType = CLIENT_CONFIG.getProxyType();
        EXTENSION_LOADER.loadExtension(ProxyFactory.class);
        ......
        
        return new RpcReference((ProxyFactory) proxyTypeClass.newInstance());
}
  1. 初始化Netty设置,重点是添加ClientHandler来处理远程调用结果。
        NioEventLoopGroup clientGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(clientGroup);
        bootstrap.channel(NioSocketChannel.class);
        bootstrap.handler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                //初始化管道,包含了编解码器和客户端响应类
                ByteBuf delimiter = Unpooled.copiedBuffer(DEFAULT_DECODE_CHAR.getBytes());
                // DelimiterBasedFrameDecoder是将特殊符号作为一则消息的结束符,以此来解决TCP中粘包问题
                ch.pipeline().addLast(new DelimiterBasedFrameDecoder(CLIENT_CONFIG.getMaxServerRespDataSize(), delimiter));
                ch.pipeline().addLast(new RpcEncoder());
                ch.pipeline().addLast(new RpcDecoder());
                ch.pipeline().addLast(new ClientHandler());
            }
        });
		---------------------------------------ClientHandler.java------------------------------------------
        @Override
	    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
	    	// 转型
	        RpcProtocol rpcProtocol = (RpcProtocol) msg;
	        // 反序列化
	        RpcInvocation rpcInvocation = CLIENT_SERIALIZE_FACTORY.deserialize(rpcProtocol.getContent(), RpcInvocation.class);
	        if (rpcInvocation.getE() != null) {
	            rpcInvocation.getE().printStackTrace();
	        }
	        //通过之前发送的uuid来注入匹配的响应数值
	        if(!RESP_MAP.containsKey(rpcInvocation.getUuid())){
	            throw new IllegalArgumentException("server response is error!");
	        }
			// 保存处理结果<key:UUID,value:对象>
	        RESP_MAP.put(rpcInvocation.getUuid(), rpcInvocation);
	        ReferenceCountUtil.release(msg);
	    }
		--------------------------------------------------------------------------------------------
  1. 初始化连接器,ConnectionHandler作用是统一管理连接的状态。
    在这里插入图片描述
  2. 其他各类序列化器、过滤器等的实例化,使用SPI的方式。

2.2 doSubscribeService()方法

该方法重点是进行服务的订阅,在Zookeeper上创建节点保存消费中信息。

    public void doSubscribeService(Class<?> serviceBean) {
        if (abstractRegister == null) {
            try {
                // 初始化注册中心
                String registerType = CLIENT_CONFIG.getRegisterType();
                EXTENSION_LOADER.loadExtension(RegistryService.class);
                LinkedHashMap<String, Class<?>> registerMap = EXTENSION_LOADER_CLASS_CACHE.get(RegistryService.class.getName());
                Class<?> registerClass = registerMap.get(registerType);
                abstractRegister = (AbstractRegister) registerClass.newInstance();
            } catch (Exception e) {
                throw new RuntimeException("registryServiceType unKnow,error is ", e);
            }
        }
        // 设置对应的属性
        URL url = new URL();
        url.setApplicationName(CLIENT_CONFIG.getApplicationName());
        url.setServiceName(serviceBean.getName());
        url.addParameter("host", CommonUtil.getIpAddress());
        // 根据服务名称在zk上获取服务权重相关信息
        Map<String, String> result = abstractRegister.getServiceWeightMap(serviceBean.getName());
        // 每个服务对应的ip:port映射,一对多
        URL_MAP.put(serviceBean.getName(), result);
        // 服务订阅
        abstractRegister.subscribe(url);
    }
    ---------------------------------------ZookeeperRegister.java------------------------------------------
    @Override
    public void subscribe(URL url) {
        if (!this.zkClient.existNode(ROOT)) {
            zkClient.createPersistentData(ROOT, "");
        }
        String urlStr = URL.buildConsumerUrlStr(url);
        if (zkClient.existNode(getConsumerPath(url))) {
            zkClient.deleteNode(getConsumerPath(url));
        }
        zkClient.createTemporarySeqData(getConsumerPath(url), urlStr);
        super.subscribe(url);
    }
    ---------------------------------------------------------------------------------------------------

创建结果如下图。
在这里插入图片描述

2.3 doConnectServer()方法

该方法向服务提供者建立连接,使用ConnectionHandler来建立连接。

    public void doConnectServer() {
        for (URL providerUrl : SUBSCRIBE_SERVICE_LIST) {
            List<String> providerIps = abstractRegister.getProviderIps(providerUrl.getServiceName());
            for (String providerIp : providerIps) {
                try {
                	// 建立连接
                    ConnectionHandler.connect(providerUrl.getServiceName(), providerIp);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            URL url = new URL();
            url.setServiceName(providerUrl.getServiceName());
            url.addParameter("providerIps", JSON.toJSONString(providerIps));
            //客户端在此新增一个订阅的功能
            abstractRegister.doAfterSubscribe(url);
        }
    }

2.4 startClient()方法

该方法主要是启动异步发送消息线程。SEND_QUEUE是一个阻塞队列,用来储存发送任务。在getChannelFuture方法中会进行负载均衡策略和消息的过滤。

    /**
     * 开启发送线程,专门从事将数据包发送给服务端
     */
    public void startClient() {
        Thread asyncSendJob = new Thread(new AsyncSendJob(), "ClientAsyncSendJobThread");
        asyncSendJob.start();
    }

    /**
     * 异步发送信息任务
     */
    class AsyncSendJob implements Runnable {
        public AsyncSendJob() { }
        @Override
        public void run() {
            while (true) {
                try {
                    //阻塞模式
                    RpcInvocation data = SEND_QUEUE.take();
                    //进行序列化
                    byte[] serialize = CLIENT_SERIALIZE_FACTORY.serialize(data);
                    //将RpcInvocation封装到RpcProtocol对象中,然后发送给服务端
                    RpcProtocol rpcProtocol = new RpcProtocol(serialize);
                    //获取netty通道,负载均衡策略
                    ChannelFuture channelFuture = ConnectionHandler.getChannelFuture(data);
                    //netty的通道负责发送数据给服务端
                    channelFuture.channel().writeAndFlush(rpcProtocol);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    ---------------------------------------CommonClientCache.java------------------------------------------
    public static BlockingQueue<RpcInvocation> SEND_QUEUE = new ArrayBlockingQueue<>(100);
    -------------------------------------------------------------------------------------------------------

3.JavassistInvocationHandler代理类

Javassist代理为例。通过此方法使用Javassist方式动态代理服务消费者类。

    @Override
    public <T> T getProxy(RpcReferenceWrapper<T> rpcReferenceWrapper) throws Throwable {
        return (T) ProxyGenerator.newProxyInstance(Thread.currentThread().getContextClassLoader(),
                rpcReferenceWrapper.getAimClass(), new JavassistInvocationHandler(rpcReferenceWrapper));
    }

在该类的invoke方法中,实现了消费者向服务提供者进行服务请求的操作。采用SEND_QUEUE异步的方式发送消息,并且实现了重试次数和超时的功能。在一定时间内,消费者会不断尝试从RESP_MAP中根据uuid获取结果,消费者每一次请求都以一个唯一标识作为区分。

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RpcInvocation rpcInvocation = new RpcInvocation();
        rpcInvocation.setArgs(args);
        rpcInvocation.setTargetMethod(method.getName());
        rpcInvocation.setTargetServiceName(rpcReferenceWrapper.getAimClass().getName());
        rpcInvocation.setAttachments(rpcReferenceWrapper.getAttatchments());
        //注入uuid,对每一次的请求都做单独区分
        rpcInvocation.setUuid(UUID.randomUUID().toString());
        rpcInvocation.setRetry(rpcReferenceWrapper.getRetry());
        RESP_MAP.put(rpcInvocation.getUuid(), OBJECT);
        //将请求的参数放入到发送队列中
        SEND_QUEUE.add(rpcInvocation);
        //如果是异步请求,就没有必要再在RESP_MAP中判断是否有响应结果了
        if (rpcReferenceWrapper.isAsync()) {
            return null;
        }
        long beginTime = System.currentTimeMillis();
        while (System.currentTimeMillis() - beginTime < timeOut || rpcInvocation.getRetry() > 0) {
            Object object = RESP_MAP.get(rpcInvocation.getUuid());
            if (object instanceof RpcInvocation) {
                RpcInvocation rpcInvocationResp = (RpcInvocation) object;
                //异常结果+有重试次数=异常重试
                if (rpcInvocationResp.getE() != null && rpcInvocationResp.getRetry() > 0) {
                    //重新请求
                    rpcInvocation.setE(null);
                    rpcInvocation.setResponse(null);
                    rpcInvocation.setRetry(rpcInvocation.getRetry() - 1);
                    RESP_MAP.put(rpcInvocation.getUuid(), OBJECT);
                    SEND_QUEUE.add(rpcInvocation);
                    beginTime = System.currentTimeMillis();
                } else {
                    RESP_MAP.remove(rpcInvocation.getUuid());
                    return rpcInvocationResp.getResponse();
                }
            }
            //超时重试
            if (System.currentTimeMillis() - beginTime > timeOut) {
                //重新请求
                rpcInvocation.setResponse(null);
                //每次重试之后都会将retry值扣减1
                rpcInvocation.setRetry(rpcInvocation.getRetry() - 1);
                RESP_MAP.put(rpcInvocation.getUuid(), OBJECT);
                SEND_QUEUE.add(rpcInvocation);
                //充值请求开始的时间
                beginTime = System.currentTimeMillis();
            }
        }
        RESP_MAP.remove(rpcInvocation.getUuid());
        throw new TimeoutException("client wait server's response timeout!");
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值