手写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、启动测试
在NettServer的initChannel中添加对应的编解码器和业务处理器
@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-provider和service-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、在RpcRequestManager的sendRequest方法中添加负载均衡机制
@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、启动测试不同负载均衡策略!