手把手教你写个RPC应用-客户端

121 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情

定义rpc调用代理注解

当该注解被调用时,导入RpcProxy、RpcBeanDefinitionRegistryPostProcessor

@Target(ElementType.TYPE) 
@Retention(RetentionPolicy.RUNTIME) 
@Component 
@Import({RpcProxy.class, RpcBeanDefinitionRegistryPostProcessor.class}) 
public @interface RpcCustomClient { }

bean注册器

指定包加载类

@Slf4j 
public class RpcBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {     
    @Override     
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {         
        RpcScanner rpcScanner = new RpcScanner(registry);         
        // RPC接口所在的包名         
        rpcScanner.scan(BaseRpcConstant.BASE_RPC_PACKAGES);     
    }     
    略... 
}

自定义rpc 扫描器

在IOC 容器初始化阶段,执行

public class RpcScanner extends ClassPathBeanDefinitionScanner {     
    public RpcScanner(BeanDefinitionRegistry registry) {         
        super(registry);     
    }     
    @Override     
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {         
        // 获取指定包下的类         
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);         
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {             
            BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();             
            // 获取构造函数参数结合,添加指定类型的泛型;             
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());             
            // 修改当前类bean的类型名称,当从BeanFactory获取对象时,实际调用RpcFactoryBean的 getObject方法             
            log.info("doScan -> beanDefinition={}", beanDefinition.getBeanClassName());             
            beanDefinition.setBeanClassName(RpcFactoryBean.class.getName());         
        }         
        return beanDefinitionHolders;     
    }     
    略... 
}

rpc bean 工厂

在运行时创建一个动态代理类对象

public class RpcFactoryBean<T> implements FactoryBean<T> {     
    private Class<T> interfaceClass;     
    
    @Autowired     
    private RpcProxy rpcProxy;     
    public RpcFactoryBean(Class<T> interfaceClass) {         
        this.interfaceClass = interfaceClass;     
    }     
    @Override     
    public T getObject() {         
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, rpcProxy);     
    }     
    @Override     
    public Class<?> getObjectType() {         
        return interfaceClass;     
    } 
}

RPC服务动态代理

public class RpcProxy implements InvocationHandler {     
    @Autowired     
    private RpcClient rpcClient;     
    public void setRpcClient(RpcClient rpcClient) {         
        this.rpcClient = rpcClient;     
    }     
    @Override     
    public Object invoke(Object proxy, Method method, Object[] args) {         
        RpcRequest rpcRequest = new RpcRequest();         
        rpcRequest.setRequestType(0);         
        rpcRequest.setClassName(method.getDeclaringClass().getName());         
        rpcRequest.setMethodName(method.getName());         
        rpcRequest.setParameters(args);         
        rpcRequest.setParameterTypes(method.getParameterTypes());         
        log.info("invoke -> rpcRequest={}", JSON.toJSONString(rpcRequest));         
        RpcResponse rpcResponse = rpcClient.send(rpcRequest);         
        return rpcResponse.getResult();     
    } 
}

handler处理器

消息处理核心代码,利用SynchronousQueue的特性,实现接口消息回调

private Map<String, SynchronousQueue<RpcResponse>> results; 
public RpcClientInboundHandler(Map<String, SynchronousQueue<RpcResponse>> results) {    
    this.results = results; 
} 
@Override 
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {     
    RpcResponse rpcResponse = (RpcResponse) msg;     
    log.info("收到服务器响应 -> {}", JSON.toJSONString(rpcResponse));     
    // 取出结果容器,将response放进queue中     
    if(null != rpcResponse && 0 == rpcResponse.getRequestType()){         
        SynchronousQueue<RpcResponse> queue = results.get(rpcResponse.getRequestId());         
        queue.put(rpcResponse);     
    } 
}

启动客户端

加载指定注解

@RpcCustomClient 
@Slf4j 
public class RpcConfig {     
    @Value("${rpc.remote.hosts}")     
    private String remoteIpPorts;      
        
    @Bean     
    public RpcClient start(){         
        RpcClient rpcClient = new RpcClient();         
        rpcClient.setRemoteIps(remoteIpPorts);         
        log.info("remoteIpPorts={}", remoteIpPorts);         
        return rpcClient;     
     } 
 }

启动服务,初始化信息

代码中,TcpPools为TCP连接池,管理tcp通道计数和连接地址,解决服务单点问题,此处暂略

@Slf4j 
public class RpcClient implements InitializingBean {     
    private String remoteIpPorts;     
    private List<Bootstrap> bootstraps = new ArrayList<>();     
    
    public void setRemoteIps(String remoteIpPorts) {         
        this.remoteIpPorts = remoteIpPorts;     
    }     
    private final Map<String, SynchronousQueue<RpcResponse>> results = new ConcurrentHashMap<>();     
    public RpcClient() {     }     
    
    @Override     
    public void afterPropertiesSet() throws Exception {         
        for (String remoteIpPort : remoteIpPorts.split(";")) {             
            String[] data = remoteIpPort.split(":");             
            // 客户端启动类,绑定端口,host             
            Bootstrap bootstrap = new Bootstrap().remoteAddress(data[0], Integer.valueOf(data[1]));             
            bootstraps.add(bootstrap);             
            NioEventLoopGroup worker = new NioEventLoopGroup(1);             
            bootstrap.group(worker)                     
                .channel(NioSocketChannel.class)                     
                .handler(new ChannelInitializer<SocketChannel>() {                         
                    @Override                         
                    protected void initChannel(SocketChannel channel) throws Exception {                             
                        ChannelPipeline pipeline = channel.pipeline();                             
                        // 超时设定                             
                        pipeline.addLast(new IdleStateHandler(0, 0, 10));                             
                        pipeline.addLast(new JsonEncoder());                             
                        pipeline.addLast(new JsonDecoder());                             
                        pipeline.addLast(new RpcClientInboundHandler(results));                         
                    }                     
               });         
         }     
    }     
       
    public RpcResponse send(RpcRequest rpcRequest) {         
        RpcResponse rpcResponse;         
        rpcRequest.setRequestId(UUID.randomUUID().toString());         
        try {             
            Channel channel = TcpPools.getCannle(bootstraps);             
            log.info("发送请求 -> {}", JSON.toJSONString(rpcRequest));             
            channel.writeAndFlush(rpcRequest);             
            SynchronousQueue<RpcResponse> queue = new SynchronousQueue<>();             
            results.put(rpcRequest.getRequestId(), queue);             
            // 阻塞等待获取响应             
            rpcResponse = queue.poll();             
            int count = 0;             
            while (null == rpcResponse && count < 100){                 
                Thread.sleep(100);                 
                rpcResponse = queue.poll();                 
                count ++;             
            }             
            if(null == rpcResponse){                 
                channel.close();                 
                throw new RuntimeException("调用rpc超时");             
            }             
            if (!rpcResponse.isSuccess()) {                 
                throw new RuntimeException("调用结果异常,异常信息:" + rpcResponse.getErrorMessage());             
            }         
       } catch (Exception e) {             
           log.error("send -> error", e);             
           throw new RuntimeException(e.getLocalizedMessage());         
       } finally {             
           results.remove(rpcRequest.getRequestId());         
       }         
       return rpcResponse;     
   } 
}

总结

通过这个rpc示例服务,知道了怎么构建并运行一个rpc客户端及服务器。示例虽然难度不大,但基于netty实现的服务原理都大同小异,只要掌握了基础,就可以基于此实现更为复杂优秀的应用了。