• 基于Netty的高性能RPC框架(分布式缓存、雪花算法、幂等性)



    前言

    解决了幂等性不能在多台服务器实现的问题,处理涉及分布式缓存、雪花算法、时钟回拨等有挑战性的问题。


    介绍

    VERSION: 2.1.0 VERSION VERSION 2.1.0 2.1.0 JDK: 8.0 JDK JDK 8.0 8.0 NACOS: 1.43 NACOS NACOS 1.43 1.43 NETTY: 4.1.75.Final NETTY NETTY 4.1.75.Final 4.1.75.Final LICENCE: MIT LICENCE LICENCE MIT MIT

    一个分布式微服务RPC框架 | 英文说明文档 | SpringBoot整合RPC

    • 基于SocketNetty异步非阻塞通信的解决方案;
    • 支持分布式超时重试机制、幂等历史结果淘汰策略、异步缓存实现高效通信;
    • 实现采用Jedis/Lettuce两种基于雪花算法的id生成器;
    • 支持JDK内置SPI机制,实现接口与实现解耦;
    • 注册中心高可用性,提供集群注册中心,所有注册节点宕机后仍能通过缓存为用户持续提供服务;
    • 提供个性化服务,推出个性化服务name、服务group,适合在测试、实验和正式环境的服务,以及为后期版本的兼容、维护和升级提供更好的服务;
    • 提供集群注册中心宕机重启服务;
    • 提供服务无限制横向扩展;
    • 提供服务的两种负载均衡策略,如随机和轮询负载;
    • 提供请求超时重试,且保障业务执行的幂等性,超时重试能降低线程池任务的延迟,线程池保障了高并发场景下线程数创建数量的稳定,却因而带来延迟问题,处理该问题可以启用重试请求,且重试达到阈值将放弃请求,认为该服务暂时不可用,造成业务损耗,请慎用;
    • 提供自定义注解扩展服务,使用代理扩展,能无侵入式扩展个性化服务;
    • 提供可扩展的序列化服务,目前提供KryoJackson两种序列化方式;
    • 提供日志框架Logback
    • 提供Netty可扩展的通信协议,通信协议头使用与Class一样的16位魔数0xCAFEBABE、包辨识id,用来辨识请求包和响应包、res长度,用来防止粘包,以及最后的res,内部加入检验码和唯一识别id,让服务器能高效地同时处理多个不同请求包或重发请求包,以及包校验;
    • 支持秒级时钟回拨服务端采取主动屏蔽客户端请求策略、分级以上时钟回拨服务端采取主动下线策略

    架构图

    • 重试机制架构图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J8gbRzY3-1669348876917)(https://yupeng-tuchuang.oss-cn-shenzhen.aliyuncs.com/超时重试.png)]

    • 服务发现与注册架构图

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tms834Hy-1669348876920)(https://yupeng-tuchuang.oss-cn-shenzhen.aliyuncs.com/服务发现与注册.png)]

    1. 服务提供

    • 负载均衡策略
    • 序列化策略
    • 自动发现和注销服务
    • 注册中心
    • 单机与集群

    2. 安全策略

    • 心跳机制
    • 信息摘要
    • 超时重试机制
    • 幂等性
    • 雪花算法

    3. 设计模式

    • 单例模式
    • 动态代理
    • 静态工厂
    • 建造者
    • 策略模式
    • Future(观察者)

    亮点

    1. 信息摘要算法的应用

    对于信息摘要算法的使用,其实并不难,在数据包中添加 String 类型的成员变量 checkCode 用来防伪的就可以实现。

    • 原理

    发送端把原信息用HASH函数加密成摘要,然后把数字摘要和原信息一起发送到接收端,接收端也用HASH函数把原消息加密为摘要,看两个摘要是否相同,若相同,则表明信息的完整.否则不完整。

    • 实现

    客户端在发出 请求包,服务端会在该请求包在请求执行的结果的内容转成字节码后,使用 MD5 单向加密成为 唯一的信息摘要(128 比特,16 字节)存储到响应包对应的成员变量 checkCode 中,所以客户端拿到响应包后,最有利用价值的地方(请求要执行的结果被改动),那么 checkCode 将不能保证一致性,这就是信息摘要的原理应用。

    安全性再增强

    考虑到这只是针对客户需求的结果返回一致性,并不能确保请求包之间存在相同的请求内容,所以引入了请求 id

    每个包都会生成唯一的 requestId,发出请求包后,该包只能由该请求发出的客户端所接受,就算两处有一点被对方恶意改动了,客户端都会报错并丢弃收到的响应包,不会拆包后去返回给用户。

    如果不是单单改动了返回结果,而是将结果跟信息摘要都修改了,对方很难保证修改的内容加密后与修改后的信息摘要一致,因为要保证一致的数据传输协议和数据编解码。

    2. 心跳机制

    心跳机制的 RPC 上应用的很广泛,本项目对心跳机制的实现很简单,而且应对措施是服务端强制断开连接,当然有些 RPC 框架实现了服务端去主动尝试重连。

    • 原理

    对于心跳机制的应用,其实是使用了 Netty 框架中的一个 handler 处理器,通过该 处理器,去定时发送心跳包,让服务端知道该客户端保持活性状态。

    • 实现

    利用了 Netty 框架中的 IdleStateEvent 事件监听器,重写userEventTriggered() 方法,在服务端监听读操作,读取客户端的 写操作,在客户端监听写操作,监听本身是否还在活动,即有没有向服务端发送请求。

    如果客户端没有主动断开与服务端的连接,而继续保持连接着,那么客户端的写操作超时后,也就是客户端的监听器监听到客户端没有的规定时间内做出写操作事件,那么这时客户端该处理器主动发送心跳包给服务端,保证客户端让服务端确保自己保持着活性。

    3. SPI 机制

    资源目录META-INF/services下新建接口全限定名作为文件名,内容为实现类全限定名,支持JDK内置SPI

    本质通过反射来无参构造创建实例,如果构造函数涉及到通过参数来实现注入成员,那么可将接口转为抽象类,抽象类暴露set方法来让子类重写,从而间接实现注入。

    该机制将注册中心逻辑层处理服务发现和注册的接口时实现分离到配置文件META-INF/services,从而更好地去支持其他插件,如ZookeeperEureka的扩展。

    应用到的配置文件:

    • cn.fyupeng.discovery.ServiceDiscovery
    cn.fyupeng.discovery.NacosServiceDiscovery
    
    • 1
    • cn.fyupeng.provider.ServiceProvider
    cn.fyupeng.provider.DefaultServiceProvider
    
    • 1
    • cn.fyupeng.registry.ServiceRegistry
    cn.fyupeng.registry.NacosServiceRegistry
    
    • 1

    4. IO 异步非阻塞

    IO 异步非阻塞 能够让客户端在请求数据时处于阻塞状态,而且能够在请求数据返回时间段里去处理自己感兴趣的事情。

    • 原理

    使用 java8 出世的 CompletableFuture 并发工具类,能够异步处理数据,并在将来需要时获取。

    • 实现

    数据在服务端与客户端之间的通道 channel 中传输,客户端向通道发出请求包,需要等待服务端返回,这时可使用 CompletableFuture 作为返回结果,只需让客户端读取到数据后,将结果通过 complete()方法将值放进去后,在将来时通过get()方法获取结果。

    5. RNF 协议

    /**
         * 自定义对象头 协议 16 字节
         * 4 字节 魔数
         * 4 字节 协议包类型
         * 4 字节 序列化类型
         * 4 字节 数据长度
         *
         *       The transmission protocol is as follows :
         * +---------------+---------------+-----------------+-------------+
         * | Magic Number  | Package Type  | Serializer Type | Data Length |
         * | 4 bytes       | 4 bytes       | 4 bytes         | 4 bytes     |
         * +---------------+---------------+-----------------+-------------+
         * |                           Data Bytes                          |
         * |                       Length: ${Data Length}                  |
         * +---------------+---------------+-----------------+-------------+
         */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    快速开始

    1.依赖

    1.1 直接引入

    首先引入两个jar包文件rpc-core-1.0.0.jarrpc-core-1.0.0-jar-with-dependencies.jar

    jar包中包括字节码文件和java源码,引入后会自动把classsources一并引入,源码可作为参考

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Kgr1A8uB-1669348876923)(https://yupeng-tuchuang.oss-cn-shenzhen.aliyuncs.com/依赖.png)]

    1.2 maven引入

    引入以下maven,会一并引入rpc-common与默认使用的注册中心nacos-client相关依赖

    <dependency>
        <groupId>cn.fyupenggroupId>
        <artifactId>rpc-coreartifactId>
        <version>2.0.4version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    最新版本2.1.0还处于测试阶段,引入雪花算法、分布式缓存解决2.0.0版本超时仅单机可用而分布式失效问题。

    <dependency>
      <groupId>cn.fyupenggroupId
      >rpc-coreartifactId>
      <version>2.1.0version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    阿里仓库10月份开始处于系统升级,有些版本还没同步过去,推荐另一个maven官方仓库:

    <mirror>
      <id>repo1mavenid>
      <mirrorOf>*mirrorOf>
      <name>maven公共仓库name>
      <url>https://repo1.maven.org/maven2url>
    mirror>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2. 启动 Nacos

    -m:模式standalone:单机

    命令使用:

    startup -m standalone
    
    • 1

    注意:开源RPC 默认使用 nacos 指定的本地端口号 8848

    官方文档:https://nacos.io/zh-cn/docs/quick-start.html

    优势:

    选用Nacos作为注册中心,是因为有较高的可用性,可实现服务长期可靠

    • 注册中心服务列表在本地保存一份
    • 宕机节点在自动重启恢复期间,服务依旧可用

    Nacos 启动效果:

    效果

    3. 提供接口

    public interface HelloService {
        String sayHello(String message);
    }
    
    • 1
    • 2
    • 3

    4. 启动服务

    • 真实服务
    @Service
    public class HelloServiceImpl implements HelloService {
        @Override
        public String sayHello(String message) {
            return "hello, here is service!";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 服务启动器
    @ServiceScan
    public class MyServer {
        public static void main(String[] args) {
            try {
                NettyServer nettyServer = new NettyServer("127.0.0.1", 5000, SerializerCode.KRYO.getCode());
                nettyServer.start();
            } catch (RpcException e) {
                e.printStackTrace();
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意:增加注解cn.fyupeng.Servicecn.fyupeng.ServiceScan才可被自动发现服务扫描并注册到 nacos

    5. 启动客户端

    初始化客户端时连接服务端有两种方式:

    • 直连
    • 使用负载均衡
    public class MyClient {
        public static void main(String[] args) {
            RoundRobinLoadBalancer roundRobinLoadBalancer = new RoundRobinLoadBalancer();
            NettyClient nettyClient = new NettyClient(roundRobinLoadBalancer, CommonSerializer.KRYO_SERIALIZER);
    
            RpcClientProxy rpcClientProxy = new RpcClientProxy(nettyClient);
            HelloService helloService = rpcClientProxy.getProxy(HelloService.class);
            String result = helloService.sayHello("hello");
            System.out.println(result);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    5. 额外配置

    5.1 配置文件
    • 项目方式启动

    在 resources 中加入 resource.properties

    主机名使用localhost127.0.0.1指定

    cn.fyupeng.nacos.register-addr=localhost:8848
    
    • 1
    • Jar方式启动

    ,兼容springboot的外部启动配置文件注入,需要在Jar包同目录下新建config文件夹,在config中与springboot一样注入配置文件,只不过springboot注入的配置文件默认约束名为application.properties,而rpc-netty-framework默认约束名为resource.properties

    5.2 日志配置

    resources 中加入 logback.xml

    
    <configuration>
      <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
          
          <pattern>%date{HH:mm:ss.SSS} [%level] %c [%t] - %m%npattern>
        encoder>
      appender>
    
      <root level="info">
        <appender-ref ref="console"/>
      root>
    configuration>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    除此之外,框架还提供了 Socket 方式的 Rpc 服务

    6. 场景应用

    • 支持 springBoot 集成

    为了支持springBoot集成logback日志,继承rpc-netty-framework使用同一套日志,抛弃nacos-client内置的slf4j-apicommons-loging原有Jar包,因为该框架会导致在整合springboot时,出现重复的日志绑定和日志打印方法的参数兼容问题,使用jcl-over-slf4j-api可解决该问题;

    springboot1.02.0版本中,不使用它默认版本的spring-boot-starter-log4j,推荐使用1.3.8.RELEASE
    springboot简单配置如下

    <dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
            <exclusions>
                
                <exclusion>
                    <groupId>org.springframework.bootgroupId>
                    <artifactId>spring-boot-starter-loggingartifactId>
                exclusion>
                
                <exclusion>
                    <groupId>org.springframeworkgroupId>
                    <artifactId>spring-jclartifactId>
                exclusion>
            exclusions>
        dependency>
        
        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.10version>
        dependency>
        
        <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-log4jartifactId>
        <version>1.3.8.RELEASEversion>
        dependency>
    dependencies>
    
    
    • 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

    7. 高可用集群

    cn.fyupeng.nacos.cluster.use=true
    cn.fyupeng.nacos.cluster.load-balancer=random
    cn.fyupeng.nacos.cluster.nodes=192.168.10.1:8847,192.168.10.1:8848,192.168.10.1:8849
    
    • 1
    • 2
    • 3
    • 使用方法及注意要点

    默认情况下,cn.fyupeng.nacos.cluster.use服从约定优于配置设定,且默认值为false,表示默认不打开集群;

    cn.fyupeng.nacos.cluster.load-balancercn.fyupeng.nacos.cluster.nodes使用前必须先打开集群模式

    • 集群节点负载策略
      • random:随机策略
      • round:轮询策略

    集群节点理论上允许无限扩展,可使用分隔符[;,|]扩展配置

    • 集群节点容错切换
      • 节点宕机:遇到节点宕机将重新从节点配置列表中选举新的正常节点,否则无限重试

    8. 超时重试机制

    默认不使用重试机制,为了保证服务的正确性,因为无法保证幂等性。

    原因是客户端无法探测是客户端网络传输过程出现问题,或者是服务端正确接收后返回途中网络传输出现问题,因为如果是前者那么重试后能保证幂等性,如果为后者,可能将导致多次同个业务的执行,这对客户端来说结果是非一致的。

    超时重试处理会导致出现幂等性问题,因此在服务器中利用HashSet添加请求id来做超时处理

    • 超时重试:cn.fyupeng.anotion.Reference注解提供重试次数、超时时间和异步时间三个配置参数,其中:
      • 重试次数:服务端未能在超时时间内 响应,允许触发超时的次数
      • 超时时间:即客户端最长允许等待 服务端时长,超时即触发重试机制
      • 异步时间:即等待服务端异步响应的时间,且只能在超时重试机制使用,非超时重试情况下默认使用阻塞等待方式

    示例:

    private static RandomLoadBalancer randomLoadBalancer = new RandomLoadBalancer();
        private static NettyClient nettyClient = new NettyClient(randomLoadBalancer, CommonSerializer.KRYO_SERIALIZER);
        private static RpcClientProxy rpcClientProxy = new RpcClientProxy(nettyClient);
    
        @Reference(retries = 2, timeout = 3000, asyncTime = 5000)
        private static HelloWorldService service = rpcClientProxy.getProxy(HelloWorldService.class, Client.class);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重试的实现也不难,采用代理 + for + 参数来实现即可。

    核心代码实现:

    for (int i = 0; i <= retries; i++) {
        long startTime = System.currentTimeMillis();
    
        CompletableFuture<RpcResponse> completableFuture = (CompletableFuture<RpcResponse>) rpcClient.sendRequest(rpcRequest);
        try {
            rpcResponse = completableFuture.get(asyncTime, TimeUnit.MILLISECONDS);
        } catch (TimeoutException e) {
            // 忽视 超时引发的异常,自行处理,防止程序中断
            timeoutRes.incrementAndGet();
            if (timeout >= asyncTime) {
                log.warn("asyncTime [ {} ] should be greater than timeout [ {} ]", asyncTime, timeout);
            }
            continue;
        }
    
        long endTime = System.currentTimeMillis();
        long handleTime = endTime - startTime;
        if (handleTime >= timeout) {
            // 超时重试
            log.warn("invoke service timeout and retry to invoke");
        } else {
            // 没有超时不用再重试
            // 进一步校验包
            if (RpcMessageChecker.check(rpcRequest, rpcResponse)) {
                res.incrementAndGet();
                return rpcResponse.getData();
            }
        }
    }
    
    • 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
    • 幂等性

    重试机制服务端要保证重试包的一次执行原则,即要实现幂等性。

    实现思路:借助分布式缓存对首次请求包进行异步缓存请求id,分布式缓存选型Redis,客户端选型Jedis与Lettuce,给定一个key失效时间满足重试机制,减少再次清理缓存的步骤。

    幂等性作用在失效时间,也影响到超时机制重试次数以及高并发场景,有雪花算法的优势,不同时间上不会生成重复id,于是可以大胆将失效时间设置大些,这取决于你能够承担多少内存而转而去考虑内存瓶颈的问题。

    9. 雪花算法

    /**
         * 自定义 分布式唯一号 id
         *  1 位 符号位
         * 41 位 时间戳
         * 10 位 工作机器 id
         * 12 位 并发序列号
         *
         *       The distribute unique id is as follows :
         * +---------------++---------------+---------------+-----------------+
         * |     Sign      |     epoch     |    workerId   |     sequence     |
         * |    1 bits     |    41 bits    |    10 bits   |      12 bits      |
         * +---------------++---------------+---------------+-----------------+
         */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 介绍

    雪花算法:主要由时间戳、机器码、序列码这三者组成,各个部分有代表的意义。

    标志位

    表示符号位,固定值为0,表示正数。

    时间戳

    时间戳代表运行时长,它由41比特组成,最高可使用69年,由当前系统时间戳与服务启动当天凌晨时间戳的差值表示。

    机器码

    机器码代表分布式节点,它由10比特组成,最高可表示1024个机器码,默认采用当前服务主机号hashCode、高低位异或和分布式缓存算法生成,机器码生成异常时由SecureRandom使用随机种子、AtomicLongCAS锁生成,当机器码数量最大时将抛出异常WorkerIdCantApplyException

    序列号

    序列号代码并发量,在同一毫秒内发生作用,即毫秒并发时作为一部分唯一值,它由12比特组成,最高可表示4096个序列号。

    • 应用场景

    (1)多服务节点负载实现业务持久化主键唯一

    (2)毫秒内请求并发数最高可达到4095,可适当改变序列号满足不同场景

    (3)满足基本有序的唯一号id,有利于高效索引查询和维护

    (4)id号前6位附加当前时间年月日日志查询,可作为日志记录

    而在RPC中主要采用雪花算法实现了请求包的唯一识别号,因为UUID生成唯一性和时间持续性比雪花算法更好,但它id值是非递增序列,在索引建立和维护时代价更高。

    雪花算法生成的id基本有序递增,可作为索引值,而且维护成本低,代价是强依赖机器时钟,为了尽可能发挥它的优势和减少不足,对最近的时间内保存了时间戳与序列号,回拨即获取当时序列号,有则自增,无则阻塞恢复到时钟回拨前的时间戳,回拨时间过大抛异常中断,而且服务器重启时小概率可能出现回拨会从而导致id值重复的问题。

    cn.fyupeng.redis.server-addr=127.0.0.1:6379
    cn.fyupeng.redis.server-auth=true
    cn.fyupeng.redis.server-pwd=123456
    
    • 1
    • 2
    • 3

    除此以外,超时重试机制,在分布式场景下,第二次重试会通过负载均衡策略负载到其他服务节点,利用雪花算法弥补了分布式场景下的无法解决幂等性的问题。

    超时重试采用Jedis/Lettuce两种实现缓存的方式,可分别在服务端、客户端配置相应的缓存连接客户端方式:

    cn.fyupeng.redis.server-way=lettuce
    cn.fyupeng.redis.client-way=jedis
    cn.fyupeng.redis.client-async=true
    
    • 1
    • 2
    • 3

    如何选择JRedisHelperLRedisHelper呢?

    JRedisHelper

    • 线程安全
    • synchronizedlock的悲观锁机制
    • 不提供线程池
    • 连接数为1
    • 同步操作
    • 提供依据主机号缓存和获取机器id
    • 提供依据请求id缓存和获取请求结果

    LRedisHelper

    • 线程安全
    • 提供线程池
    • 连接数稳定且由线程池提供
    • 异步/同步操作
    • 提供依据主机号缓存和获取机器id
    • 提供依据请求id缓存和获取请求结果

    特别提醒

    高并发请求不会出现请求号重复的情况,当前最高毫秒级并发4096,而超时机制、LRedisHelper线程池对连接的超时控制等配置参数还不成熟,具体应用场景可自行下载源码修改参数。

    10. 异常解决

    • ServiceNotFoundException

    抛出异常ServiceNotFoundException

    堆栈信息:service instances size is zero, can't provide service! please start server first!

    正常情况下,一般的错误从报错中可以引导解决。

    解决真实服务不存在的情况,导致负载均衡中使用的策略出现异常的情况,修复后会强制抛出ServiceNotFoundException,或许大部分情况是服务未启动。

    当然,推荐真实服务应该在服务启动器的内层包中,同层可能会不起作用。

    除非使用注解注明包名@ServiceScan("com.fyupeng")

    其他情况下,如出现服务端无反应,而且服务已经成功注册到注册中心,那么你就得检查下服务端与客户端中接口命名的包名是否一致,如不一致,也是无法被自动发现服务从注册中心发现的,这样最常见的报错也是service instances size is zero

    • ReceiveResponseException

    抛出异常data in package is modified Exception

    信息摘要算法的实现,使用的是String类型的equals方法,所以客户端在编写Service接口时,如果返回类型不是八大基本类型 + String 类型,也就是复杂对象类型,那么要重写toString方法。

    不使用Object默认的toString方法,因为它默认打印信息为16位的内存地址,在做校验中,发送的包和请求获取的包是需要重新实例化的,说白了就是深克隆,必须 重写Object原有toString方法。

    为了避免该情况发生,建议所有PoJoVO类必须重写toString方法,其实就是所有真实业务方法返回类型的实体,必须重写toString方法。

    如返回体有嵌套复杂对象,所有复杂对象均要重写toString只要满足不同对象但内容相同的toString方法打印信息一致,数据完整性检测才不会误报。

    • RegisterFailedException

    抛出异常Failed to register service Exception

    原因是注册中心没有启动或者注册中心地址端口指定不明,或者因为防火墙问题,导致Nacos所在服务器的端口访问失败。

    使用该框架时,需注意以下两点:

    (1) 支持注册本地地址,如 localhost或127.0.0.1,则注册地址会解析成公网地址;

    (2) 支持注册内网地址和外网地址,则地址为对应内网地址或外网地址,不会将其解析;

    • NotSuchMethodException
      抛出异常java.lang.NoSuchMethodError: org.slf4j.spi.LocationAwareLogger.log

    出现该异常的原因依赖包依赖了jcl-over-slf4jjar包,与springboot-starter-log4j中提供的jcl-over-slf4j重复了,建议手动删除rpc-core-1.0.0-jar-with-dependenceies.jarorg.apache.commons

    • DecoderException

    抛出异常:com.esotericsoftware.kryo.KryoException: Class cannot be created (missing no-arg constructor): java.lang.StackTraceElement

    主要是因为Kryo序列化和反序列化是通过无参构造反射创建的,所以使用到Pojo类,首先必须对其创建无参构造函数,否则将抛出该异常,并且无法正常执行。

    • InvocationTargetException

    抛出异常:Serialization trace:stackTrace (java.lang.reflect.InvocationTargetException)

    主要也是反射调用失败,主要原因还是反射执行目标函数失败,缺少相关函数,可能是构造函数或者其他方法参数问题。

    • AnnotationMissingException

    抛出异常:cn.fyupeng.exception.AnnotationMissingException

    由打印信息中可知,追踪AbstractRpcServer类信息打印

    cn.fyupeng.net.AbstractRpcServer [main] - mainClassName: jdk.internal.reflect.DirectMethodHandleAccessor
    
    • 1

    如果mainClassName不为@ServiceScan注解标记所在类名,则需要到包cn.fyupeng.util.ReflectUtil下修改或重写getStackTrace方法,将没有过滤的包名加进过滤列表即可,这可能与JDK的版本有关。

    • OutOfMemoryError

    抛出异常java.lang.OutOfMemoryError: Requested array size exceeds VM limit

    基本不可能会抛出该错误,由于考虑到并发请求,可能导致,如果请求包分包,会出现很多问题,所以每次请求只发送一个请求包,如在应用场景需要发送大数据,比如发表文章等等,需要手动去重写使用的序列化类的serialize方法

    例如:KryoSerializer可以重写serialize方法中写缓存的大小,默认为4096,超出该大小会很容易报数组越界异常问题。

    /**
     * bufferSize: 缓存大小
     */
    Output output = new Output(byteArrayOutputStream,100000))
    
    • 1
    • 2
    • 3
    • 4
    • RetryTimeoutExcepton

    抛出异常cn.fyupeng.exception.AnnotationMissingException

    在启用重试机制后,客户端超过重试次数仍未能成功调用服务,即可认为服务不可用,并抛出超时重试异常。

    抛出该异常后,将中断该线程,其线程还未执行的任务将终止,默认不会开启重试机制,则不会抛出该异常。

    • InvalidSystemClockException

    抛出异常cn.fyupeng.idworker.exception.InvalidSystemClockException

    雪花算法生成中是有很小概率出现时钟回拨,时间回拨需要解决id值重复的问题,故而有可能抛出InvalidSystemClockException中断异常,逻辑不可处理异常。

    • WorkerIdCantApplyException

    抛出异常cn.fyupeng.idworker.exception.WorkerIdCantApplyException

    雪花算法生成中,借助IdWorker生成器生成分布式唯一id时,是借助了机器码,当机器码数量生成达到最大值将不可再申请,这时将抛出中断异常WorkerIdCantApplyException

    11. 版本追踪

    1.0版本
    • [ #1.0.1 ]:解决真实分布式场景下出现的注册服务找不到的逻辑问题;

    • [ #1.0.2 ]:解耦注册中心的地址绑定,可到启动器所在工程项目的资源下配置resource.properties文件;

    • [ #1.0.4.RELEASE ]:修复Jar方式部署项目后注册到注册中心的服务未能被发现的问题,解耦Jar包启动配置文件的注入,约束名相同会覆盖项目原有配置信息;

    • [ #1.0.5 ]:将心跳机制打印配置默认级别为trace,默认日志级别为info,需要开启到logback.xml启用。

    • [ #1.0.6 ]:默认请求包大小为4096字节,扩容为100000字节,满足日常的100000字的数据包,不推荐发送大数据包,如有需求看异常OutOfMemoryError说明。

    • [ #1.0.10 ]: 修复负载均衡出现select失败问题,提供配置中心高可用集群节点注入配置、负载均衡配置、容错自动切换

    2.0版本
    • [ #2.0.0 ]:优化1.0版本,2.0版本问世超时重试机制,使用到幂等性来解决业务损失问题,提高业务可靠性。

    • [ #2.0.1 ]:版本维护

    • [ #2.0.2 ]:修复集群配置中心宕机重试和负载问题

    • [ #2.0.3 ]:提供个性化服务版本号,支持各种场景,如测试和正式场景,让服务具有更好的兼容性,支持版本维护和升级。

    • [ #2.0.4 ]:支持SPI机制,接口与实现解耦。

    • [ #2.1.0 ]:引入雪花算法与分布式缓存,2.0.0版本仅支持单机幂等性,修复分布式场景失效问题,采用轮询负载+超时机制,能高效解决服务超时问题。

    12. 开发说明

    有二次开发能力的,可直接对源码修改,最后在工程目录下使用命令mvn clean package,可将核心包和依赖包打包到rpc-netty-framework\rpc-core\target目录下,本项目为开源项目,如认为对本项目开发者采纳,请在开源后最后追加原创作者GitHub链接 https://github.com/fyupeng ,感谢配合!

  • 相关阅读:
    【云原生之k8s】kubernetes原理
    css3的text(文本)
    Eureka 学习笔记(2)加载eureka-server.properties中的配置
    【数据结构与算法】- 图(算法)
    Java 包
    shapely 笔记:STR TREE
    Go素数筛选分析
    MVCC解决的问题是什么
    从刘维尔方程到Velocity-Verlet算法
    celery apply_async定时任务重复执行问题
  • 原文地址:https://blog.csdn.net/F15217283411/article/details/128035697