手写一个RPC框架

169 阅读9分钟

手写RPC

课程目标:完成自定义rpc框架的编写  

0、工程搭建

1、导入基本工程结构,并熟悉基本代码。

1、RPC服务端实现

0、引导类

hrpc-server模块中编写RpcServerBootstrap,引导整个rpc服务启动

@Configuration
public class RpcServerBootstrap {


    @Autowired
    private RpcServerRnner rpcServerRnner;


    @PostConstruct
    public void initRpcServer() {
        rpcServerRnner.run();
    }
}

 

1、启动器

hrpc-server模块中编写RPC服务端启动器com.itheima.rpc.server.boot.RpcServerRnner,先完成服务注册的功能

@Component
public class RpcServerRnner {




    @Autowired
    RpcRegistry registry;


    /**
     * 启动rpc server
     */
    public void run() {
        //1 服务注册
        registry.serviceRegistry();
        //2 启动服务,监听端口,等待接收请求


    }
}

2、服务注册

hrpc-server模块中编写com.itheima.rpc.server.registry.RpcRegistry接口及使用zk的实现

public interface RpcRegistry {


    /**
     * 服务注册
     */
    void serviceRegistry();
}

 

基于zk的实现com.itheima.rpc.server.registry.zk.ZkRegistry

@Component
@Slf4j
public class ZkRegistry  implements RpcRegistry {


    @Autowired
    private SpringBeanFactory springBeanFactory;
    
    @Autowired
    private ServerZKit zKit;


    @Autowired
    private RpcServerConfiguration rpcServerConfiguration;


    @Override
    public void serviceRegistry() {
        // 1、找到所有标有注解HrpcService的类,将服务信息写入到zk中
        Map<String, Object> annotationClass = SpringBeanFactory.getBeanListByAnnotationClass(HrpcService.class);
        if (annotationClass!=null && annotationClass.size() > 0) {
            // 2、创建服务根节点
            zKit.createRootNode();
            String ip = IpUtil.getRealIp();
            for (Object bean : annotationClass.values()) {
                //3、获取bean上的HrpcService注解
                HrpcService hrpcService = bean.getClass().getAnnotation(HrpcService.class);
                //4、获取注解的属性 interfacesclass
                Class<?> interfaceClass = hrpcService.interfaceClass();
                //5、创建接口
                String serviceName = interfaceClass.getName();
                zKit.createPersistentNode(serviceName);
                //6、在该接口下创建临时节点信息:ip:rpcport
                String node = ip + ":" + rpcServerConfiguration.getRpcPort();
                zKit.createNode(serviceName + "/" + node);
                log.info("服务{}-{}注册成功",serviceName,node);
            }
        }
    }
}

 

在服务提供方service-provider中,在服务启动时,启动rpc服务,添加包扫描即可

@SpringBootApplication(scanBasePackages = {"com.itheima.shop","com.itheima.rpc"})
// @ComponentScan({"com.itheima.shop","com.itheima.rpc"})
public class ShopServiceApplication {


    public static void main(String[] args) {
        SpringApplication.run(ShopServiceApplication.class,args);
    }


}

 

启动测试,查看服务是否能正常注册

 

3、NettyServer

完成启动器第二步,启动服务,监听端口,等待接收请求

hrpc-server模块中编写com.itheima.rpc.server.boot.RpcServer接口,用于启动rpc服务

public interface RpcServer {


    void start();
}

编写基于netty的服务端com.itheima.rpc.server.boot.nett.NettServer

@Component
@Slf4j
public class NettServer implements RpcServer {




    @Autowired
    private RpcServerConfiguration rpcServerConfiguration;


    @Override
    public void start() {
        EventLoopGroup boss = new NioEventLoopGroup(1,new DefaultThreadFactory("boss"));
        EventLoopGroup worker = new NioEventLoopGroup(0,new DefaultThreadFactory("worker"));
        UnorderedThreadPoolEventExecutor business =new UnorderedThreadPoolEventExecutor(NettyRuntime.availableProcessors() * 2, new DefaultThreadFactory("business"));
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss,worker)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO))
                .option(ChannelOption.SO_BACKLOG,1024)
                .childOption(ChannelOption.TCP_NODELAY,true)
                .childOption(ChannelOption.SO_KEEPALIVE,true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {


                    }
                });
            //绑定端口启动
            ChannelFuture future = serverBootstrap.bind(rpcServerConfiguration.getRpcPort()).sync();
            //阻塞等待服务关闭
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            log.error("rpc server start error,msg={}",e.getMessage());
        } finally {
            //shutdown
            boss.shutdownGracefully();
            worker.shutdownGracefully();
            business.shutdownGracefully();
        }
    }
}

 

4、服务端编解码

hrpc-core模块中编写对应的编解码器和相应的handler

一次编解码器

public class FrameDecoder extends LengthFieldBasedFrameDecoder {


    public FrameDecoder() {
        super(Integer.MAX_VALUE, 0, 2, 0, 2);
    }
}

 

public class FrameEncoder extends LengthFieldPrepender {
    public FrameEncoder() {
        super(2, 0);
    }
}

 

二次编解码

@Slf4j
public class RpcRequestDecoder  extends MessageToMessageDecoder<ByteBuf> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) throws Exception {
        try {
            int length = msg.readableBytes();
            byte[] bytes = new byte[length];
            msg.readBytes(bytes);
            RpcRequest rpcRequest = ProtostuffUtil.deserialize(bytes, RpcRequest.class);
            out.add(rpcRequest);
        } catch (Exception e) {
            log.error("RpcRequestDecoder decode error,msg={}",e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

 

@Slf4j
public class RpcResponseEncoder extends MessageToMessageEncoder<RpcResponse> {
    @Override
    protected void encode(ChannelHandlerContext ctx, RpcResponse msg, List<Object> out) throws Exception {
        try {
            byte[] bytes = ProtostuffUtil.serialize(msg);
            ByteBuf buffer = ctx.alloc().buffer(bytes.length);
            buffer.writeBytes(bytes);
            out.add(buffer);
        } catch (Exception e) {
            log.error("RpcResponseEncoder encode error,msg={}",e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

 

5、RpcRequestHandler

编写服务端处理请求返回响应的handler:com.itheima.rpc.netty.handler.RpcRequestHandler

@Slf4j
@ChannelHandler.Sharable
public class RpcRequestHandler  extends SimpleChannelInboundHandler<RpcRequest> {


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcRequest request) throws Exception {
        log.info("rpc服务端接收到的请求为:{}",request);
        //1、获取请求参数
        RpcResponse response = new RpcResponse();
        String requestId = request.getRequestId();
        response.setRequestId(requestId);
        String className = request.getClassName();
        String methodName = request.getMethodName();
        Class<?>[] parameterTypes = request.getParameterTypes();
        Object[] parameters = request.getParameters();
        try {
            //2找到目标bean
            Object targetBean = SpringBeanFactory.getBean(Class.forName(className));
            Method targetMethod = targetBean.getClass().getMethod(methodName, parameterTypes);
            //3执行目标方法获取结果
            Object result = targetMethod.invoke(targetBean, parameters);
            //4 封装响应结果
            response.setResult(result);
        } catch (Throwable e) {
            response.setCause(e);
            log.error("rpc server invoke error,msg={}",e.getMessage());
        } finally {
            log.info("服务端执行成功,响应为:{}",response);
            ctx.channel().writeAndFlush(response);
        }
    }




    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.error("服务端出现异常,异常信息为:{}",cause.getCause());
        super.exceptionCaught(ctx, cause);
    }
}

 

6、启动测试

NettServerinitChannel中添加对应的编解码器和业务处理器

@Override
public void start() {
    EventLoopGroup boss = new NioEventLoopGroup(1,new DefaultThreadFactory("boss"));
    EventLoopGroup worker = new NioEventLoopGroup(0,new DefaultThreadFactory("worker"));
    UnorderedThreadPoolEventExecutor business =new UnorderedThreadPoolEventExecutor(NettyRuntime.availableProcessors() * 2, new DefaultThreadFactory("business"));
    RpcRequestHandler rpcRequestHandler = new RpcRequestHandler();
    try {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(boss,worker)
            .channel(NioServerSocketChannel.class)
            .handler(new LoggingHandler(LogLevel.INFO))
            .option(ChannelOption.SO_BACKLOG,1024)
            .childOption(ChannelOption.TCP_NODELAY,true)
            .childOption(ChannelOption.SO_KEEPALIVE,true)
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast("logHandler",new LoggingHandler(LogLevel.INFO));
                    pipeline.addLast("FrameEncoder",new FrameEncoder());
                    pipeline.addLast("RpcResponseEncoder",new RpcResponseEncoder());


                    pipeline.addLast("FrameDecoder",new FrameDecoder());
                    pipeline.addLast("RpcRequestDecoder",new RpcRequestDecoder());
                    pipeline.addLast(business,"RpcRequestHandler",rpcRequestHandler);
                }
            });
        //绑定端口启动
        ChannelFuture future = serverBootstrap.bind(rpcServerConfiguration.getRpcPort()).sync();
        //阻塞等待服务关闭
        future.channel().closeFuture().sync();
    } catch (Exception e) {
        log.error("rpc server start error,msg={}",e.getMessage());
    } finally {
        //shutdown
        boss.shutdownGracefully();
        worker.shutdownGracefully();
        business.shutdownGracefully();
    }
}

 

 

在启动器RpcServerRnner中添加启动服务的代码

@Component
public class RpcServerRnner {


    @Autowired
    RpcRegistry registry;


    @Autowired
    private RpcServer rpcServer;


    /**
     * 启动rpc server
     */
    public void run() {
        //1 服务注册
        registry.serviceRegistry();
        //2 启动服务,监听端口,等待接收请求
        rpcServer.start();
    }
}

 

启动测试,看是否出错

 

2、RPC客户端实现

1、引导类

hrpc-client模块中编写:com.itheima.rpc.client.boot.RpcBootstrap

@Configuration
public class RpcBootstrap {




    @Autowired
    private RpcClientRunner rpcClientRunner;


    @PostConstruct
    public void initRpcClient() {
        rpcClientRunner.run();
    }
}

 

2、启动器

编写com.itheima.rpc.client.boot.RpcClientRunner

@Component
@Slf4j
public class RpcClientRunner {


    @Autowired
    private RpcServiceDiscovery serviceDiscovery;


    public void run() {
        //1、开启服务发现
        serviceDiscovery.serviceDiscovery();
        //2、客户端controller中如果有HrpcRemote注解,需要创建代理并注入,通过自定义RpcAnnotationProcessor
    }
}

 

3、服务发现

编写RpcServiceDiscovery接口和实现ZkServiceDiscovery

package com.itheima.rpc.client.discovery;


public interface RpcServiceDiscovery {


    void serviceDiscovery();
}

 

基于zk的实现:com.itheima.rpc.client.discovery.zk.ZkServiceDiscovery

@Component
@Slf4j
public class ZkServiceDiscovery implements RpcServiceDiscovery {


    @Autowired
    private ClientZKit clientZKit;


    @Autowired
    private ServiceProviderCache providerCache;


    @Override
    public void serviceDiscovery() {
        //1、拉取所有服务接口列表
        List<String> allService = clientZKit.getServiceList();
        if (!allService.isEmpty()) {
            for (String serviceName : allService) {
                //2、获取该接口下的节点列表
                List<ServiceProvider> providers = clientZKit.getServiceInfos(serviceName);
                //3、缓存该服务的所有节点信息
                log.info("订阅的服务名为={},服务提供者有={}",serviceName,providers);
                providerCache.put(serviceName,providers);
                //4、监听该服务下是所有节点信息变化
                clientZKit.subscribeZKEvent(serviceName);
            }
        }
    }
}

 

启动测试,查看是否能拉去到相关信息!

 

4、动态代理

客户端controller中如果有HrpcRemote注解,需要创建代理,通过自定义RpcAnnotationProcessor

hrpc-client模块中编写RpcAnnotationProcessor,在postProcessAfterInitialization方法中去检测如果bean的某个属性上有HrpcRemote注解,则创建代理,并完成注入

@Slf4j
@Component
public class RpcAnnotationProcessor implements BeanFactoryPostProcessor, BeanPostProcessor, ApplicationContextAware {


    private ProxyFactory proxyFactory;


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        
    }


    /**
     * 扫描bean上的HrpcRemote注解,创建代理并注入
     * @param bean
     * @param beanName
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }


    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        Field[] fields = bean.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                if (! field.isAccessible()) {
                    field.setAccessible(true);
                }
                HrpcRemote hrpcRemote = field.getAnnotation(HrpcRemote.class);
                if (hrpcRemote != null) {
                    //创建代理对象
                    Object proxy = proxyFactory.newProxyInstance(field.getType());
                    log.info("为HrpcRemote注解标注的属性生成的代理对象:{}",proxy);
                    if (proxy != null) {
                        //可以选择存入容器,可以通过autowired自动注入,这里可以直接注入
                        field.set(bean, proxy);
                    }
                }
            } catch (Throwable e) {
                log.error("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
            }
        }
        return bean;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.proxyFactory = applicationContext.getBean(ProxyFactory.class);
    }
}

 

5、封装RPC请求

在代理方法的拦截中完成RPC请求

public class CglibProxyCallBackHandler implements MethodInterceptor {




    public Object intercept(Object o, Method method, Object[] parameters, MethodProxy methodProxy) throws Throwable {
        //放过toString,hashcode,equals等方法,采用spring工具类
        if ( ReflectionUtils.isObjectMethod(method)) {
            return method.invoke(method.getDeclaringClass().newInstance(),parameters);
        }
        //1、获取rpc请求所需的参数
        String requestId = RequestIdUtil.requestId();
        String className = method.getDeclaringClass().getName();
        String methodName = method.getName();
        Class<?>[] parameterTypes = method.getParameterTypes();
        //2、构建rpc请求对象
        RpcRequest request = RpcRequest.builder()
                .requestId(requestId)
                .className(className)
                .methodName(methodName)
                .parameterTypes(parameterTypes)
                .parameters(parameters)
                .build();
        //3、发送rpc请求获取响应
         RpcRequestManager rpcRequestManager = SpringBeanFactory.getBean(RpcRequestManager.class);
        if (rpcRequestManager==null) {
            throw new RpcException("spring ioc exception");
        }
        RpcResponse response = rpcRequestManager.sendRequest(request);
        //4、返回结果数据
        return response.getResult();
    }
}

 

6、请求管理器

hrpc-client模块中编写com.itheima.rpc.client.request.RpcRequestManager

@Slf4j
@Component
public class RpcRequestManager {




    @Autowired
    private ServiceProviderCache providerCache;


    public RpcResponse sendRequest(RpcRequest request) {
        // 1、获取可用发服务节点
        List<ServiceProvider> serviceProviders = providerCache.get(request.getClassName());
        if (CollectionUtils.isEmpty(serviceProviders)) {
            log.info("客户端没有发现可用发服务节点");
            throw new RpcException(StatusEnum.NOT_FOUND_SERVICE_PROVINDER);
        }
        ServiceProvider serviceProvider = serviceProviders.get(0);
        if (serviceProvider!=null) {
            return requestByNetty(request,serviceProvider);
        }else {
            throw new RpcException(StatusEnum.NOT_FOUND_SERVICE_PROVINDER);
        }
    }
    private RpcResponse requestByNetty(RpcRequest request, ServiceProvider serviceProvider) {
        EventLoopGroup worker = new NioEventLoopGroup(0,new DefaultThreadFactory("worker"));
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(worker)
            .channel(NioSocketChannel.class)
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    
                }
            });
        //建立连接
        try {
            ChannelFuture future = bootstrap.connect(serviceProvider.getServerIp(),serviceProvider.getRpcPort()).sync();
            //如果连接成功
            if (future.isSuccess()) {
                //向服务端发送数据
                ChannelFuture channelFuture = future.channel().writeAndFlush(request);


                return null;
            }
        } catch (Exception e) {
            log.error("remote rpc request error,msg={}",e.getCause());
            throw new RpcException(e);
        }
        return new RpcResponse();
    }
}    

 

7、客户端编解码

hrpc-core模块中编写相关编解码器,响应处理器handler,一次编解码可用复用,编写二次请求编码,响应解码,响应处理器handler

请求编码:RpcRequestEncoder

@Slf4j
public class RpcRequestEncoder extends MessageToMessageEncoder<RpcRequest> {
    @Override
    protected void encode(ChannelHandlerContext ctx, RpcRequest request, List<Object> out) throws Exception {
        try {
            byte[] bytes = ProtostuffUtil.serialize(request);
            ByteBuf buf = ctx.alloc().buffer(bytes.length);
            buf.writeBytes(bytes);
            out.add(buf);
        } catch (Exception e) {
            log.error("RpcRequestEncoder error,msg={}",e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

响应解码:RpcResponseDecoder

@Slf4j
public class RpcResponseDecoder extends MessageToMessageDecoder<ByteBuf> {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
        try {
            int length = buf.readableBytes();
            byte[] bytes = new byte[length];
            buf.readBytes(bytes);
            RpcResponse response = ProtostuffUtil.deserialize(bytes, RpcResponse.class);
            out.add(response);
        } catch (Exception e) {
            log.error("RpcResponseDecoder decode error,msg={}",e.getMessage());
            throw new RuntimeException(e);
        }
    }
}

 

8、RpcResponseHandler

编写响应处理器handler:com.itheima.rpc.netty.handler.RpcResponseHandler

@Slf4j
@ChannelHandler.Sharable
    public class RpcResponseHandler extends SimpleChannelInboundHandler<RpcResponse> {


        @Override
        protected void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
            // 处理响应结果
        }
    }

 

pipeline中添加对应的编解码器

RpcResponseHandler responseHandler = new RpcResponseHandler();//复用
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(worker)
    .channel(NioSocketChannel.class)
    .handler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new LoggingHandler(LogLevel.INFO));
            //添加请求编码器
            pipeline.addLast("FrameEncoder",new FrameEncoder());
            pipeline.addLast("RpcReqeustEncoder",new RpcRequestEncoder());


            pipeline.addLast("FrameDecoder",new FrameDecoder());
            pipeline.addLast("RpcResponseDecoder",new RpcResponseDecoder());
            pipeline.addLast("RpcResponseHandler",responseHandler);
        }
    });

 

9、异步获取结果

如何拿到RpcResponseHandler中的响应数据呢?RpcResponseHandler是在netty的worker线程中执行的,如何在当前请求的线程中获取结果?:Future

@Autowired
private RpcClientConfiguration clientConfiguration;
//////


ChannelFuture future = bootstrap.connect(serviceProvider.getServerIp(),serviceProvider.getRpcPort()).sync();//连接服务端
//如果连接成功
if (future.isSuccess()) {
    RequestPromise requestPromise = new RequestPromise(future.channel().eventLoop());
    RpcRequestHolder.addRequestPromise(request.getRequestId(),requestPromise);
    //向服务端发送数据
    ChannelFuture channelFuture = future.channel().writeAndFlush(request);
    //添加发数据结果回调监听
    channelFuture.addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            //如果没有发送成功,移除requetPromise
            if (!future.isSuccess()) {
                RpcRequestHolder.removeRequestPromise(request.getRequestId());
            }
        }
    });


    //设置结果回调监听
    requestPromise.addListener(new FutureListener<RpcResponse>() {
        @Override
        public void operationComplete(Future<RpcResponse> future) throws Exception {
            if (!future.isSuccess()) {
                RpcRequestHolder.removeRequestPromise(request.getRequestId());
            }
        }
    });
    //获取返回结果
    RpcResponse response = (RpcResponse) requestPromise.get(clientConfiguration.getConnectTimeout(), TimeUnit.SECONDS);
    RpcRequestHolder.removeRequestPromise(request.getRequestId());
    return response;
}

同时在RpcResponseHandler中设置成功,触发监听回调

@Slf4j
@ChannelHandler.Sharable
public class RpcResponseHandler extends SimpleChannelInboundHandler<RpcResponse> {


    @Override
    protected void channelRead0(ChannelHandlerContext ctx, RpcResponse response) throws Exception {
        log.info("---rpc调用成功,返回结果为:{}",response);
        RequestPromise requestPromise = RpcRequestHolder.getRequestPromise(response.getRequestId());
        if (requestPromise!=null) {
            //通知回调监听
            requestPromise.setSuccess(response);
        }
    }
}

 

启动service-providerservice-consumer进行测试!

访问:http://localhost:8080/order/getOrder?userId=123&orderNo=adfa

 

10、连接复用

使用jmeter压测发现一个优化点,每次请求都创建连接新的连接,导致资源快速被耗尽

如何优化:客户端复用对服务端的连接,使用RpcRequestHolder中的channelMappingMap

然后对RpcRequestManager中的requestByNetty方法进行改造,改造完成如下:

private RpcResponse requestByNetty(RpcRequest request, ServiceProvider serviceProvider) {
    //判断客户端是否已有该服务提供者的连接了,如果有则复用
    if (!RpcRequestHolder.channelExist(serviceProvider.getServerIp(),serviceProvider.getRpcPort())) {//注意取反
        EventLoopGroup worker = new NioEventLoopGroup(0,new DefaultThreadFactory("worker"));
        RpcResponseHandler responseHandler = new RpcResponseHandler();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(worker)
            .channel(NioSocketChannel.class)
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new LoggingHandler(LogLevel.INFO));
                    //添加请求编码器
                    pipeline.addLast("FrameEncoder",new FrameEncoder());
                    pipeline.addLast("RpcReqeustEncoder",new RpcRequestEncoder());


                    pipeline.addLast("FrameDecoder",new FrameDecoder());
                    pipeline.addLast("RpcResponseDecoder",new RpcResponseDecoder());
                    pipeline.addLast("RpcResponseHandler",responseHandler);
                }
            });
        try {
            ChannelFuture future = bootstrap.connect(serviceProvider.getServerIp(),serviceProvider.getRpcPort()).sync();
            if (future.isSuccess()) {
                RpcRequestHolder.addChannelMapping(new ChannelMapping(serviceProvider.getServerIp(),serviceProvider.getRpcPort(),future.channel()));
            }
        } catch (InterruptedException e) {
            log.error("客户端连接创建失败");
        }
    }


    //获取该客户端的channel
    Channel channel = RpcRequestHolder.getChannel(serviceProvider.getServerIp(),serviceProvider.getRpcPort());
    try {
        RequestPromise requestPromise = new RequestPromise(channel.eventLoop());//modifi
        RpcRequestHolder.addRequestPromise(request.getRequestId(),requestPromise);
        //向服务端发送数据
        ChannelFuture channelFuture = channel.writeAndFlush(request);//modifi
        //添加发数据结果回调监听
        channelFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                //如果没有发送成功,移除requetPromise
                if (!future.isSuccess()) {
                    RpcRequestHolder.removeRequestPromise(request.getRequestId());
                }
            }
        });
        //设置结果回调监听
        requestPromise.addListener(new FutureListener<RpcResponse>() {
            @Override
            public void operationComplete(Future<RpcResponse> future) throws Exception {
                //如果没有响应成功 移除requetPromise
                if (!future.isSuccess()) {
                    RpcRequestHolder.removeRequestPromise(request.getRequestId());
                }
            }
        });
        //获取返回结果
        RpcResponse response = (RpcResponse) requestPromise.get(clientConfiguration.getConnectTimeout(), TimeUnit.SECONDS);
        RpcRequestHolder.removeRequestPromise(request.getRequestId());
        return response;
    } catch (Exception e) {
        log.error("remote rpc request error,msg={}",e.getCause());
        throw new RpcException(e);
    }
}

 

启动,压测,查看结果

作业:能否将创建连接改成异步的?以应对面对需要创建大量连接时提高吞吐。

 

11、负载均衡

1、在hrpc-client模块中编写各个负载均衡的实现,实现接口com.itheima.rpc.client.cluster.LoadBalanceStrategy

 

哈希负载:

@HrpcLoadBalance(strategy = "hash")
public class HashLoadBalanceStrategy implements LoadBalanceStrategy {
    @Override
    public ServiceProvider select(List<ServiceProvider> serviceProviders) {
        /**
         * 1、获取客户端ip
         * 2、获取ip hash
         * 3、index = hash % serviceProviders.size()
         * 4、get(index)
         */
        String ip = IpUtil.getRealIp();
        int hashCode = ip.hashCode();
        int index = Math.abs(hashCode % serviceProviders.size());
        return serviceProviders.get(index);
    }
}

 

随机策略:

@HrpcLoadBalance(strategy = "random")
public class RandomLoadBalanceStrategy implements LoadBalanceStrategy {
    @Override
    public ServiceProvider select(List<ServiceProvider> serviceProviders) {
        /**
         * [0,len)
         */
        int len = serviceProviders.size();
        int index = RandomUtils.nextInt(0,len);
        return serviceProviders.get(index);
    }
}

 

轮询策略:

@HrpcLoadBalance(strategy = "polling")
@Slf4j
public class PollingLoadBalanceStrategy implements LoadBalanceStrategy {


    private int index;


    private ReentrantLock lock = new ReentrantLock();


    @Override
    public ServiceProvider select(List<ServiceProvider> serviceProviders) {
        try {
            lock.tryLock(10, TimeUnit.SECONDS);
            if(index>=serviceProviders.size()) {
                index = 0;
            }
            ServiceProvider serviceProvider = serviceProviders.get(index);
            index++;
            return serviceProvider;
        } catch (InterruptedException e) {
            log.error("轮询策略获取锁失败,msg={}",e.getMessage());
        } finally {
            lock.unlock();
        }
        return null;
    }
}

 

加权随机策略:

@HrpcLoadBalance(strategy = "weight_random")
public class WeightRandomLoadBalanceStrategy implements LoadBalanceStrategy {
    @Override
    public ServiceProvider select(List<ServiceProvider> serviceProviders) {
        /**
         * 按照权重创建一个新的待选集合
         * 在新的集合中随机选择
         */
        List<ServiceProvider> newList = new ArrayList<>();
        for (ServiceProvider serviceProvider : serviceProviders) {
            //获取该节点的权重
            int weight = serviceProvider.getWeight();
            //根据权重向新集合中添加节点
            for (int i=0;i<weight;i++) {
                newList.add(serviceProvider);
            }
        }
        //在新集合中进行随机选取
        int index = RandomUtils.nextInt(0,newList.size());
        return newList.get(index);
    }
}

注意:服务注册的时候需要将节点的权重写到zk,服务拉取的时候将节点权重保存下来(作业)

 

2、有了策略,如何根据客户端的配置,动态选择策略呢?编写com.itheima.rpc.client.cluster.DefaultStartegyProvider

@Component
public class DefaultStartegyProvider implements StartegyProvider, ApplicationContextAware {


    @Autowired
    private RpcClientConfiguration rpcClientConfiguration;


    LoadBalanceStrategy strategy;


    @Override
    public LoadBalanceStrategy getStrategy() {
        return strategy;
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> map =applicationContext.getBeansWithAnnotation(HrpcLoadBalance.class);
        for (Object bean : map.values()) {
            HrpcLoadBalance hrpcLoadBalance = bean.getClass().getAnnotation(HrpcLoadBalance.class);
            if (hrpcLoadBalance.strategy().equals(rpcClientConfiguration.getRpcClientClusterStrategy())) {
                strategy = (LoadBalanceStrategy) bean;
                break;
            }
        }
    }
}

 

3、在RpcRequestManagersendRequest方法中添加负载均衡机制

@Autowired
private StartegyProvider startegyProvider;


public RpcResponse sendRequest(RpcRequest request) {
    // 1、获取可用发服务节点
    List<ServiceProvider> serviceProviders = providerCache.get(request.getClassName());
    if (CollectionUtils.isEmpty(serviceProviders)) {
        log.info("客户端没有发现可用发服务节点");
        throw new RpcException(StatusEnum.NOT_FOUND_SERVICE_PROVINDER);
    }
    //ServiceProvider serviceProvider = serviceProviders.get(0);
    LoadBalanceStrategy strategy = startegyProvider.getStrategy();
    ServiceProvider serviceProvider = strategy.select(serviceProviders);//进行负载均衡
    if (serviceProvider !=null) {
        return requestByNetty(request, serviceProvider);
    }else {
        throw new RpcException(StatusEnum.NOT_FOUND_SERVICE_PROVINDER);
    }
}

 

4、启动测试不同负载均衡策略!