demo地址

https://gitee.com/syher/grave-netty

RPC介绍

首先了解一下RPC:远程过程调用。简单点说就是本地应用可以调用远程服务器的接口。那么通过什么方式调用远程接口呢?说白了RPC只是一种概念。他的调用可以基于HTTP实现,也可以基于TCP/IP实现。甚至私人定制的通讯协议。

当然,私人定制通讯协议成本过高且不具备通用性。我们不做展开讨论(其实我也展不开。。。)。那为什么不使用HTTP协议呢?受限于HTTP协议层级过高,数据传输效率不如TCP/IP。所以RPC远程调用一般采用TCP/IP实现。即调用socket方法。

RPC实现原理

1. 客户端发起远程服务调用。

2. 客户端将类信息、调用方法和入参信息通过socket通道发送给服务端。

3. 服务端解析数据包,调用本地接口。

5.将执行结果通过socket返回给客户端。

6.客户端拿到并解析返回结果。

RPC实现

java如何实现一个rpc框架,其实就是按照上面的原理再做一些详细的补充。比如通过动态代理封装客户端的数据包、通过反射机制实现服务端实现类的调用等等。

今天,我们先基于spring boot + netty 做rpc服务端的实现。

首先,做一个注解用于标识接口提供rpc调用。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Service {
String name() default "";
}

  

该注解用于提供服务的实现类上。

public interface INettyService {

    String getString();
}

  

其实现类:

package com.braska.grave.netty.server.service;

@Service // 该注解为自定义rpc服务注解
public class NettyService implements INettyService {
@Override
public String getString() {
return "welcome to use netty rpc.";
}
}

  

接着,定义一个注解用来扫描指定包名下的Service注解。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({NettyServerScannerRegistrar.class, NettyServerApplicationContextAware.class})
public @interface NettyServerScan { String[] basePackages();
}

  

该注解用于spring boot启动类上,参数basePackages指定服务所在的包路径。

@SpringBootApplication
@NettyServerScan(basePackages = {
"com.braska.grave.netty.server.service"
})
public class GraveNettyServerApplication { public static void main(String[] args) {
SpringApplication.run(GraveNettyServerApplication.class, args);
} }

  

NettyServerScannerRegistrar类处理服务的spring bean注册。

public class NettyServerScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {

    @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     
    // 创建扫描器实例
NettyServerInterfaceScanner scanner = new NettyServerInterfaceScanner(registry);
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
} AnnotationAttributes annoAttrs =
AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(NettyServerScan.class.getName())); List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
     // 只扫描指定的注解。
scanner.setAnnotationClass(Service.class);
scanner.registerFilters();      // 将basePackages里面的通过@Service注解的类注册成spring bean。
scanner.doScan(StringUtils.toStringArray(basePackages));
}
}

  

NettyServerApplicationContextAware类,暴露socket server端口。

public class NettyServerApplicationContextAware implements ApplicationContextAware, InitializingBean {
private static final Logger logger = Logger.getLogger(NettyServerApplicationContextAware.class.getName());    // 存储接口与实现类的映射,其中key是接口名。value是实现类的bean。
private Map<String, Object> serviceMap = new HashMap<>();    // 服务worker。包含netty socket服务端生命周期及读写。
ServerWorker runner; @Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
String address = applicationContext.getEnvironment().getProperty("remoteAddress"); Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Service.class);
for (Object serviceBean : beans.values()) { Class<?> clazz = serviceBean.getClass(); Class<?>[] interfaces = clazz.getInterfaces(); for (Class<?> inter : interfaces) {
String interfaceName = inter.getName();
serviceMap.put(interfaceName, serviceBean);
}
}      // 创建netty worker对象
runner = new ServerWorker(address, serviceMap);
} @Override
public void afterPropertiesSet() throws Exception {      // 创建netty socketServer及通道处理器
runner.open();
}
}

  

ServerWorker类的open方法。

public class ServerWorker extends ChannelInitializer {

   // socket ip:port
private String remoteAddress; // 实现类的beanMap
private Map<String, Object> serviceMap;

// netty channel处理器
NettyServerHandler handler;public void open() {
try {
int parallel = Runtime.getRuntime().availableProcessors() * ;
ServerBootstrap bootstrap = new ServerBootstrap();
this.bossGroup = new NioEventLoopGroup();
// todo 使用线程池,提高并发能力
this.workerGroup = new NioEventLoopGroup(parallel);
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, )
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(this);
String[] hostAndPort = this.remoteAddress.split(":");
if (hostAndPort == null || hostAndPort.length != ) {
throw new RuntimeException("remoteAddress is error.");
}
ChannelFuture cf = bootstrap.bind(hostAndPort[], Integer.parseInt(hostAndPort[])).sync();
// todo 信息写入注册中心
// registry.register(serverAddress);
logger.info("netty 服务器启动.监听端口:" + hostAndPort[]);
// 等待服务端监听端口关闭
cf.channel().closeFuture().sync();
} catch (Exception e) {
logger.log(Level.SEVERE, "netty server open failed.", e);
this.bossGroup.shutdownGracefully();
this.workerGroup.shutdownGracefully();
}
} @Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new IdleStateHandler(, , ));
pipeline.addLast(new JSONEncoder());
pipeline.addLast(new JSONDecoder());
pipeline.addLast(this.handler);
}
}

NettyServerHandler服务端channel处理器,继承ChannelInboundHandlerAdapter。

@ChannelHandler.Sharable
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private Map<String, Object> serviceMap; public NettyServerHandler(Map<String, Object> serviceMap) {
this.serviceMap = serviceMap;
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {      // 解析客户端发送过来的数据。包含类名、方法名、入参等信息。
Request request = JSON.parseObject(msg.toString(), Request.class); Response response = new Response();
response.setRequestId(request.getId());
try {        // 调用本地实现类
Object res = this.handler(request);
response.setData(res);
} catch (Exception e) {
response.setCode(-1);
response.setError(e.getMessage());
logger.log(Level.SEVERE, "请求调用失败", e);
}      // 返回处理结果给客户端
ctx.writeAndFlush(response);
} private Object handler(Request request) throws Exception {
String className = request.getClassName();      // 通过className从beanMap映射中找到托管给spring的bean实现类。
Object serviceBean = serviceMap.get(className);
String methodName = request.getMethodName();
Object[] parameters = request.getParameters();      // 通过反射机制调用实现类。并返回调用结果。
return MethodUtils.invokeMethod(serviceBean, methodName, parameters);
}
}

  

至此,rpc服务端的实现就完成了。

一路看下来,服务端的代码实现还是比较简单的。核心代码只有两个类:ServerWorker和NettyServerHandler。其余的都是对spring bean注册的支持。

基于netty实现rpc框架-spring boot服务端的更多相关文章

  1. 基于netty实现rpc框架-spring boot客户端

    上篇讲了RPC服务端的实现.原理就是解析netty通道数据拿到类.方法及入参等信息,然后通过java反射机制调用本地接口返回结果.没有用到很复杂的技术. 这篇我们将客户端的实现.说白了客户端的任务很简 ...

  2. 这样基于Netty重构RPC框架你不可能知道

    原创申明:本文由公众号[猿灯塔]原创,转载请说明出处标注 今天是猿灯塔“365天原创计划”第5天. 今天呢!灯塔君跟大家讲: 基于Netty重构RPC框架 一.CyclicBarrier方法说明 1. ...

  3. Java框架Spring Boot & 服务治理框架Dubbo & 应用容器引擎Docker 实现微服务发布

    微服务系统架构实践 开发语言Java 8 框架使用Spring boot 服务治理框架Dubbo 容器部署Docker 持续集成Gitlab CI 持续部署Piplin 注册中心Zookeeper 服 ...

  4. 《Java 编写基于 Netty 的 RPC 框架》

    一 简单概念 RPC: ( Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO :当阻塞 ...

  5. 基于Netty重构RPC框架

    下面的这张图,大概很多小伙伴都见到过,这是Dubbo 官网中的一张图描述了项目架构的演进过程.随着互联网的发展,网站应用的规模不断扩大,常规的垂直应用架构已无法应对,分布式服务架构以及流动计算架构势在 ...

  6. java编写基于netty的RPC框架

    一 简单概念 RPC:(Remote Procedure Call),远程调用过程,是通过网络调用远程计算机的进程中某个方法,从而获取到想要的数据,过程如同调用本地的方法一样. 阻塞IO:当阻塞I/O ...

  7. 基于vue的nuxt框架cnode社区服务端渲染

    nuxt-cnode 基于vue的nuxt框架仿的cnode社区服务端渲染,主要是为了seo优化以及首屏加载速度 线上地址 http://nuxt-cnode.foreversnsd.cngithub ...

  8. DIY一些基于netty的开源框架

    几款基于netty的开源框架,有益于对netty的理解和学习! 基于netty的http server框架 https://github.com/TogetherOS/cicada 基于netty的即 ...

  9. 基于Netty打造RPC服务器设计经验谈

    自从在园子里,发表了两篇如何基于Netty构建RPC服务器的文章:谈谈如何使用Netty开发实现高性能的RPC服务器.Netty实现高性能RPC服务器优化篇之消息序列化 之后,收到了很多同行.园友们热 ...

随机推荐

  1. 加油站问题 Gas Station

    2019-06-01 17:09:30 问题描述: 问题求解: 其实本题本质上是一个数学题. [定理] 对于一个循环数组,如果这个数组整体和 SUM >= 0,那么必然可以在数组中找到这么一个元 ...

  2. GO语言web框架Gin之完全指南

    GO语言web框架Gin之完全指南 作为一款企业级生产力的web框架,gin的优势是显而易见的,高性能,轻量级,易用的api,以及众多的使用者,都为这个框架注入了可靠的因素.截止目前为止,github ...

  3. 一个完整的机器学习项目在Python中的演练(二)

    大家往往会选择一本数据科学相关书籍或者完成一门在线课程来学习和掌握机器学习.但是,实际情况往往是,学完之后反而并不清楚这些技术怎样才能被用在实际的项目流程中.就像你的脑海中已经有了一块块"拼 ...

  4. Kaggle竞赛入门:决策树算法的Python实现

    本文翻译自kaggle learn,也就是kaggle官方最快入门kaggle竞赛的教程,强调python编程实践和数学思想(而没有涉及数学细节),笔者在不影响算法和程序理解的基础上删除了一些不必要的 ...

  5. Reactor模式和Proactor模式

    Reactor 主线程往epoll内核事件表中注册socket上的读就绪事件 主线程调用epoll_wait等待socket上有数据可读 当socket上有数据可读时,epoll_wait通知主线程, ...

  6. [codevs]1250斐波那契数列<矩阵乘法&快速幂>

    题目描述 Description 定义:f0=f1=1, fn=fn-1+fn-2(n>=2).{fi}称为Fibonacci数列. 输入n,求fn mod q.其中1<=q<=30 ...

  7. CentOS 6.5 nginx+tomcat+ssl配置

    本文档用于指导在CentOS 6.5下使用nginx反向代理tomcat,并在nginx端支持ssl. 安装nginx.参见CentOS 6 nginx安装. SSL证书申请.参见腾讯SSL证书申请和 ...

  8. 人生靠反省,Java靠泛型

    昨天有同事问 UserService.XxxService 都会调用 Dao 的 insert.update ... ...,这些重复的代码,有没有办法变得灵活一些? 巧了,和咱们分享的主题刚好碰上, ...

  9. 想读Spring源码?先从这篇「 极简教程」开始吧...

    为什么要阅读源码?这是一个有趣的问题,类似的问题还有,为什么要看书?为什么要爬山? 这也是一个哲学问题,我想每个人都有不同的答案,下面我是对阅读源码好处的一些思考. (PS:也欢迎你在评论区留言补充) ...

  10. python中装饰器的使用

    看个例子: # 定义装饰器函数 def log(func): """ 接受一个函数作为参数,并返回一个函数 :param func: :return: "&qu ...