介绍
RPC(远程过程调用)可以分为两部分:远程过程以及过程调用。远程过程是指每台机器上提供的服务,过程调用就是对远程过程调用以及数据传输。
优势
RPC带来的优势其实就是分布式架构带来的优势。在RPC的支持下,可以实现模块的分布式部署,可以实现更好的维护性,扩展性以及协同式开发。
缺点
RPC的出现为构建分布式架构带来了便利,但是分布式系统本身的问题也被暴漏了下来。带来的问题有四:
- 通信延迟
跨机器、网络出现的通信延迟的概率一定比同一台机器的进程间通信大。编解码也会带来性能损耗。而且网络通信也是不可靠的会出现乱序、错误、丢数据等问题。 - 地址空间隔离
内存地址在一台机器上才有效。 - 局部故障
故障的发现和通知需要引入新的组件,故障的类型判断也会变得复杂,比如是网络链路故障还是机器故障,继而会存在数据不一致问题,故障节点和正常节点会出现数据不一致问题。 - 并发问题
RPC和分布式框架的区别
RPC实现了服务消费者调用方Client与服务提供实现方Server之间的点对点调用流程,一般来说,包括stub、通信、数据的序列化/反序列化。调用方与服务提供方一般采用直连的调用方式。而分布式服务框架,除了包括RPC的特性,还包括多台Server提供服务的负载均衡策略及实现,服务的注册、发布与引入,以及服务的高可用策略、服务治理等特性。
这里挖个小坑 下几篇文章我来分享我搭建SpringCloudAlibaba的过程。
rpc的网络交互大致流程:
client向server发送request后开始等待。
server收到、处理、回复client。
client收到response做出反应。
搭建过程
我会使用到Netty进行RPC的搭建。
- 首先我们先要建立公共辅助类
- RPC 通信实体
/**
* RPC 通信实体
* @author lx
* @date 2022/8/2 15:38
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RpcMessage implements Serializable {
private static final long serialVersionUID = 430507739718447406L;
/**
* interface接口名
*/
private String name;
/**
* 方法名
*/
private String methodName;
/**
* 参数类型
*/
private Class<?>[] parTypes;
/**
* 参数
*/
private Object[] pars;
/**
* 结果值
*/
private Object result;
}
- 其次是RPC使用的辅助标签
/**
* RPC标签
* @author lx
* @date 2022/8/2 15:36
*/
@Target(value = {ElementType.TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RpcServer {
}
- 然后是生产者端
需要配置如下
@Configuration
public class ClientBeanConfig {
@Bean
public NettyClient nettyClient() {
return new NettyClient();
}
@Bean
public NettyClientBeanPostProcessor nettyClientBeanPostProcessor(NettyClient nettyClient) {
return new NettyClientBeanPostProcessor(nettyClient);
}
}
@ChannelHandler.Sharable
public class ClientHandle extends SimpleChannelInboundHandler<RpcMessage> {
/**
* 定义消息Map,将连接通道Channel作为key,消息返回值作为value
*/
private final ConcurrentMap<Channel, RpcMessage> rpcMessageConcurrentMap;
public ClientHandle(ConcurrentMap<Channel, RpcMessage> rpcMessageConcurrentMap) {
this.rpcMessageConcurrentMap = rpcMessageConcurrentMap;
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcMessage rpcMessage) throws Exception {
System.out.println("客户端收到服务端消息:"+ rpcMessage);
rpcMessageConcurrentMap.put(channelHandlerContext.channel(), rpcMessage);
}
}
开启标签
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration({ClientBeanConfig.class})
public @interface EnableNettyClient {
}
public class NettyClient {
private Channel channel;
/**
* 存放请求编号与响应对象的映射关系
*/
private final ConcurrentMap<Channel, RpcMessage> rpcMessageConcurrentMap = new ConcurrentHashMap<>();
public RpcMessage send(int port, final RpcMessage rpcMessage) {
//客户端需要一个事件循环组
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())))
.addLast(new ObjectEncoder())
.addLast(new ClientHandle(rpcMessageConcurrentMap));
}
});
final ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", port).syncUninterruptibly();
System.out.println("连接服务端成功: " + channelFuture.channel().remoteAddress());
channel = channelFuture.channel();
channel.writeAndFlush(rpcMessage);
System.out.println("发送数据成功:"+ rpcMessage);
channel.closeFuture().syncUninterruptibly();
return rpcMessageConcurrentMap.get(channel);
} catch (Exception e) {
System.out.println("client exception"+ e);
return null;
} finally {
group.shutdownGracefully();
//移除请求编号和响应对象直接的映射关系
rpcMessageConcurrentMap.remove(channel);
}
}
public void stop() {
channel.close();
}
}
连接发送信息
/**
* Netty客户端Bean后置处理器
* 实现Spring后置处理器接口:BeanPostProcessor
* 在Bean对象在实例化和依赖注入完毕后,在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的
* @author lx
* @date 2022/8/2 15:50
*/
public class NettyClientBeanPostProcessor implements BeanPostProcessor {
private final NettyClient nettyClient;
public NettyClientBeanPostProcessor(NettyClient nettyClient) {
this.nettyClient = nettyClient;
}
/**
* 实例化、依赖注入完毕,在调用显示的初始化之前完成一些定制的初始化任务
* 注意:方法返回值不能为null
* 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到Bean实例对象
* 因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
*/
@Override
public Object postProcessBeforeInitialization(Object bean, @Nullable String beanName) throws BeansException {
//获取实例Class
Class<?> beanClass = bean.getClass();
do {
//获取该类所有字段
Field[] fields = beanClass.getDeclaredFields();
for (Field field : fields) {
//判断该字段是否拥有@RpcServer
if (field.getAnnotation(RpcServer.class) != null) {
field.setAccessible(true);
try {
//通过JDK动态代理获取该类的代理对象
Object o = Proxy.newProxyInstance(field.getType().getClassLoader(), new Class[]{field.getType()}, new ClientInvocationHandle(nettyClient));
//将代理类注入该字段
field.set(bean, o);
System.out.println("创建代理类 ===>>> "+beanName);
} catch (IllegalAccessException e) {
System.out.println(e.getMessage());
}
}
}
} while ((beanClass = beanClass.getSuperclass()) != null);
return bean;
}
/**
* 实例化、依赖注入、初始化完毕时执行
* 注意:方法返回值不能为null
* 如果返回null那么在后续初始化方法将报空指针异常或者通过getBean()方法获取不到Bean实例对象
* 因为后置处理器从Spring IoC容器中取出bean实例对象没有再次放回IoC容器中
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 可以根据beanName不同执行不同的处理操作
return bean;
}
/**
* JDK动态代理处理器
*/
static class ClientInvocationHandle implements InvocationHandler {
private final NettyClient nettyClient;
public ClientInvocationHandle(NettyClient nettyClient) {
this.nettyClient = nettyClient;
}
/**
* 代理方法调用
*
* @param proxy 代理类
* @param method 方法
* @param args 参数
* @return 返回值
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
//组装Netty参数
RpcMessage rpcMessage = RpcMessage.builder()
.name(method.getDeclaringClass().getName())
.methodName(method.getName())
.parTypes(method.getParameterTypes())
.pars(args)
.build();
//调用Netty,发送数据
RpcMessage send = nettyClient.send(1111, rpcMessage);
System.out.println("接收到服务端数据:"+send+", 返回结果值 ====》》》》"+ send.getResult());
return send.getResult();
}
}
}
controller 业务加标签@RpcServer
最后启动项加上该标签@EnableNettyClient
- 消费端配置
/**
* Netty服务端
*
* @author lx
* @date 2022/8/2 16:30
**/
@Slf4j
public class NettyServer {
/**
* server端处理器
*/
private final ServerHandle serverHandle;
/**
* 服务端通道
*/
private Channel channel;
/**
* 构造器
*
* @param serverHandle server处理器
*/
public NettyServer(ServerHandle serverHandle) {
this.serverHandle = serverHandle;
}
/**
* 启动
*
* @param port 启动端口
*/
public void start(int port) {
EventLoopGroup boss = new NioEventLoopGroup(1);
EventLoopGroup worker = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new ObjectDecoder(ClassResolvers.weakCachingConcurrentResolver(this.getClass().getClassLoader())))
.addLast(new ObjectEncoder())
.addLast(serverHandle);
}
});
final ChannelFuture channelFuture = serverBootstrap.bind(port).syncUninterruptibly();
log.info("服务端启动-端口: {}", port);
channel = channelFuture.channel();
channel.closeFuture().syncUninterruptibly();
} catch (Exception e) {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
/**
* 关闭当前通道
*/
public void stop() {
channel.close();
}
}
收到后调用方法
/**
* Netty Server端Handle处理类,消息体RpcMessage
* 实现ApplicationContextAware接口:该接口可以加载获取到所有的 spring bean。
* 实现了这个接口的bean,当spring容器初始化的时候,会自动的将ApplicationContext注入进来
*
* @author ZC
* @date 2021/3/1 22:15
*/
@Slf4j
@ChannelHandler.Sharable
public class ServerHandle extends SimpleChannelInboundHandler<RpcMessage> implements ApplicationContextAware {
private Map<String, Object> serviceMap;
/**
* 在类被Spring容器加载时会自动执行setApplicationAware
*
* @param applicationContext Spring上下文
* @throws BeansException 异常信息
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//从Spring容器中获取到所有拥有@RpcServer注解的Beans集合,Map<Name(对象类型,对象全路径名),实例对象>
Map<String, Object> beansWithAnnotation = applicationContext.getBeansWithAnnotation(RpcServer.class);
log.info("被@RpcServer注解加载的Bean: {}", beansWithAnnotation);
if (beansWithAnnotation.size() > 0) {
Map<String, Object> map = new ConcurrentHashMap<>(16);
for (Object o : beansWithAnnotation.values()) {
//获取该实例对象实现的接口Class
Class<?> anInterface = o.getClass().getInterfaces()[0];
//获取该接口类名,作为Key,实例对象作为Value
map.put(anInterface.getName(), o);
}
//使用变量接住map
serviceMap = map;
}
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("客户端连接了: {}", ctx.channel().remoteAddress());
super.channelActive(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.error("异常信息");
cause.printStackTrace();
super.exceptionCaught(ctx, cause);
}
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, RpcMessage rpcMessage) throws Exception {
log.info("客户端发送的消息:{}", rpcMessage);
//从Map中获取实例对象
Object service = serviceMap.get(rpcMessage.getName());
//获取调用方法
Method method = service.getClass().getMethod(rpcMessage.getMethodName(), rpcMessage.getParTypes());
method.setAccessible(true);
//反射调用实例对象方法,获取返回值
Object result = method.invoke(service, rpcMessage.getPars());
rpcMessage.setResult(JSONUtil.toJsonStr(result));
log.info("回给客户端的消息:{}", rpcMessage);
//Netty服务端将数据写会Channel并发送给客户端,同时添加一个监听器,当所有数据包发送完成后,关闭通道
channelHandlerContext.writeAndFlush(rpcMessage).addListener(ChannelFutureListener.CLOSE);
}
}
实现类加上该标签 @RpcServer
启动项加上启动标签@EnableNettyServer
这套基础的RPC就搭建完成了。