基于netty实现rpc框架-spring boot服务端

简介:

基于netty实现rpc框架-spring boot服务端

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调用。

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

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

1
2
3
4
public interface INettyService {

    String getString();
}

其实现类:

1
2
3
4
5
6
7
8
9
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注解。

1
2
3
4
5
6
7
8
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({NettyServerScannerRegistrar.class, NettyServerApplicationContextAware.class})
public @interface NettyServerScan {

    String[] basePackages();
}

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

1
2
3
4
5
6
7
8
9
10
11
@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注册。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 basePackages = new ArrayList();
        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端口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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() * 2; 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, 1024) .childOption(ChannelOption.SO_KEEPALIVE, true) .childOption(ChannelOption.TCP_NODELAY, true) .childHandler(this); String[] hostAndPort = this.remoteAddress.split(":"); if (hostAndPort == null || hostAndPort.length != 2) { throw new RuntimeException("remoteAddress is error."); } ChannelFuture cf = bootstrap.bind(hostAndPort[0], Integer.parseInt(hostAndPort[1])).sync(); // todo 信息写入注册中心 // registry.register(serverAddress); logger.info("netty 服务器启动.监听端口:" + hostAndPort[1]); // 等待服务端监听端口关闭 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(0, 0, 60)); pipeline.addLast(new JSONEncoder()); pipeline.addLast(new JSONDecoder()); pipeline.addLast(this.handler); } }

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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注册的支持。

相关文章
|
7天前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
74 14
|
6月前
|
开发框架 前端开发 网络协议
Spring Boot结合Netty和WebSocket,实现后台向前端实时推送信息
【10月更文挑战第18天】 在现代互联网应用中,实时通信变得越来越重要。WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为客户端和服务器之间的实时数据传输提供了一种高效的解决方案。Netty作为一个高性能、事件驱动的NIO框架,它基于Java NIO实现了异步和事件驱动的网络应用程序。Spring Boot是一个基于Spring框架的微服务开发框架,它提供了许多开箱即用的功能和简化配置的机制。本文将详细介绍如何使用Spring Boot集成Netty和WebSocket,实现后台向前端推送信息的功能。
1498 1
|
6月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
101 4
|
6月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 实现动态路由和菜单功能,快速搭建前后端分离的应用框架。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,包括版本兼容性、安全性、性能调优等方面。
346 1
|
6月前
|
网络协议 前端开发
netty的TCP服务端和客户端实现
本文介绍了使用Netty框架实现TCP服务端和客户端的步骤,包括添加Netty依赖、编写服务端和客户端的代码,涉及NioEventLoopGroup、ServerBootstrap、Bootstrap、ChannelInitializer等核心组件,以及如何启动服务端监听和客户端连接。
441 4
|
6月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
101 0
|
5月前
|
存储 Java 关系型数据库
在Spring Boot中整合Seata框架实现分布式事务
可以在 Spring Boot 中成功整合 Seata 框架,实现分布式事务的管理和处理。在实际应用中,还需要根据具体的业务需求和技术架构进行进一步的优化和调整。同时,要注意处理各种可能出现的问题,以保障分布式事务的顺利执行。
311 53
|
4月前
|
IDE Java 测试技术
互联网应用主流框架整合之Spring Boot开发
通过本文的介绍,我们详细探讨了Spring Boot开发的核心概念和实践方法,包括项目结构、数据访问层、服务层、控制层、配置管理、单元测试以及部署与运行。Spring Boot通过简化配置和强大的生态系统,使得互联网应用的开发更加高效和可靠。希望本文能够帮助开发者快速掌握Spring Boot,并在实际项目中灵活应用。
111 5
|
5月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
369 2
|
6月前
|
NoSQL Java Redis
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。
这篇文章介绍了Redis的基本命令,并展示了如何使用Netty框架直接与Redis服务器进行通信,包括设置Netty客户端、编写处理程序以及初始化Channel的完整示例代码。
144 1
redis的基本命令,并用netty操作redis(不使用springboot或者spring框架)就单纯的用netty搞。