真的够可以的,基于Netty实现了RPC框架

news2025/2/26 22:50:30

RPC全称Remote Procedure Call,即远程过程调用,对于调用者无感知这是一个远程调用功能。目前流行的开源RPC 框架有阿里的Dubbo、Google 的 gRPC、Twitter 的Finagle 等。本次RPC框架的设计主要参考的是阿里的Dubbo,这里Netty 基本上是作为架构的技术底层而存在的,主要完成高性能的网络通信,从而实现高效的远程调用。

Dubbo的架构与Spring

其实在之前的文章中《谈谈京东的服务框架》,探讨过Dubbo的组成和架构。

 

 

另外使用Dubbo最方便的地方在于它可以和Spring非常方便的集成,Dubbo对于配置的优化也是随着Spring一脉相承的,从最早的XML形式到后来的注解方式以及自动装配,都是在不断地简化开发过程来提高开发效率。

Dubbo在Spring框架中的工作流程:

1、Spring的IOC容器启动

2、把服务注册到注册中心(zookeeper软件)中

3、消费者启动时会把它需要用到的服务从注册中心拉取下来

4、提供者的地址发生改变时,注册中心会马上通知消费者

5、根据注册中心中的服务地址直接就可以调用提供者了,如果调用了提供者,就会把提供者的地址主动缓存起来

6、监控消费者调用提供者的次数

RPC实现的关键

1、序列化与反序列化

在远程过程调用时,客户端跟服务端是不同的进程,甚至有时候客户端用Java,服务端用C++。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式,这个过程叫序列化和反序列化,同理,从服务端返回的值也需要序列化反序列化的过程。在序列化的时候,我们选择Netty自身的对象序列化器。

 

2、数据网络传输

解决了序列化的问题,那么剩下的就是如何把数据参数传到生产者,网络传输层需要把序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端,虽然大部分RPC框架都采用了TCP作为传输协议,其实UDP也可以作为传输协议的,基于TCP和UDP我们可以自定义任意规则的协议,加之我们要使用NIO通信方式作为高性能网络服务的前提,于是Netty似乎更符合我们Java程序员的口味,Netty真香!

3、告诉注册中心我要调谁

现在调用参数的序列化和网络传输都已经具备,但是还有个问题,那就是消费者要调用谁的问题,一个函数或者方法,我们可以理解为一个服务,这些服务注册在注册中心上面,只有当消费者告诉注册中心要调用谁,才可以进行远程调用。所以不但要把将要调用的服务的参数传过去,也要把要调用的服务信息传过去。

简易RPC框架的架构

 

Dubbo 核心模块主要有四个:Registry 注册中心、Provider 服务提供者、Consumer 服务消费者、Monitor监控,为了方便直接砍掉了监控模块,同时把服务提供者模块与注册中心模块写在一起,通过实现自己的简易IOC容器,完成对服务提供者的实例化。

关于使用Netty进行Socket编程的部分可以参考Netty的官网 或者我之前的博客《Netty编码实战与Channel生命周期》,在这里Netty的编码技巧和方式不作为本文的重点。

RPC框架编码实现

首先需要引入的依赖如下(Netty + Lombok):

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.6.Final</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.8</version>
</dependency>

1、Registry与Provider

目录结构如下:

───src
    └─main
        ├─java
        │  └─edu
        │      └─xpu
        │          └─rpc
        │              ├─api
        │              │      IRpcCalc.java
        │              │      IRpcHello.java
        │              │
        │              ├─core
        │              │      InvokerMessage.java
        │              │
        │              ├─provider
        │              │      RpcCalcProvider.java
        │              │      RpcHelloProvider.java
        │              │
        │              └─registry
        │                      MyRegistryHandler.java
        │                      RpcRegistry.java
        │
        └─resources
───pom.xml

IRpcCalc.java与IRpcHello.java是两个Service接口。IRpcCalc.java内容如下,完成模拟业务加、减、乘、除运算

public interface IRpcCalc {
    // 加
    int add(int a, int b);

    // 减
    int sub(int a, int b);

    // 乘
    int mul(int a, int b);

    // 除
    int div(int a, int b);
}

IRpcHello.java,测试服务是否可用:

public interface IRpcHello {
    String hello(String name);
}

至此API 模块就定义完成了,非常简单的两个接口。接下来,我们要确定传输规则,也就是传输协议,协议内容当然要自定义,才能体现出Netty 的优势。

设计一个InvokerMessage类,里面包含了服务名称、调用方法、参数列表、参数值,这就是我们自定义协议的协议包:

@Data
public class InvokerMessage implements Serializable {
    private String className; // 服务名称
    private String methodName; // 调用哪个方法
    private Class<?>[] params; // 参数列表
    private Object[] values; // 参数值
}

通过定义这样的协议类,就能知道我们需要调用哪个服务,服务中的哪个方法,方法需要传递的参数列表(参数类型+参数值),这些信息正确传递过去了才能拿到正确的调用返回值。

接下来创建这两个服务的具体实现类,IRpcHello的实现类如下:

public class RpcHelloProvider implements IRpcHello {
    public String hello(String name) {
        return "Hello, " + name + "!";
    }
}

IRpcCalc的实现类如下:

public class RpcCalcProvider implements IRpcCalc {
    @Override
    public int add(int a, int b) {
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        return a - b;
    }

    @Override
    public int mul(int a, int b) {
        return a * b;
    }

    @Override
    public int div(int a, int b) {
        return a / b;
    }
}

Registry 注册中心主要功能就是负责将所有Provider的服务名称和服务引用地址注册到一个容器中(这里为了方便直接使用接口类名作为服务名称,前提是假定我们每个服务只有一个实现类),并对外发布。Registry 应该要启动一个对外的服务,很显然应该作为服务端,并提供一个对外可以访问的端口。先启动一个Netty服务,创建RpcRegistry 类,RpcRegistry.java的具体代码如下:

public class RpcRegistry {
    private final int port;
    public RpcRegistry(int port){
        this.port = port;
    }

    public void start(){
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 处理拆包、粘包的编解码器
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            pipeline.addLast(new LengthFieldPrepender(4));
                            // 处理序列化的编解码器
                            pipeline.addLast("encoder", new ObjectEncoder());
                            pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            // 自己的业务逻辑
                            pipeline.addLast(new MyRegistryHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true); // 设置长连接

            ChannelFuture channelFuture = serverBootstrap.bind(this.port).sync();
            System.out.println("RPC Registry start listen at " + this.port);
            channelFuture.channel().closeFuture().sync();
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
    public static void main(String[] args) {
        new RpcRegistry(8080).start();
    }
}

接下来只需要实现我们自己的Handler即可,创建MyRegistryHandler.java,内容如下:

public class MyRegistryHandler extends ChannelInboundHandlerAdapter {
    // 在注册中心注册服务需要有容器存放
    public static ConcurrentHashMap<String, Object> registryMap = new ConcurrentHashMap<>();

    // 类名的缓存位置
    private static final List<String> classCache = new ArrayList<>();

    // 约定,只要是写在provider下所有的类都认为是一个可以对完提供服务的实现类
    // edu.xpu.rpc.provider

    public MyRegistryHandler(){
        scanClass("edu.xpu.rpc.provider");
        doRegister();
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Object result = new Object();
        // 客户端传过来的调用信息
        InvokerMessage request = (InvokerMessage)msg;
        // 先判断有没有这个服务
        String serverClassName = request.getClassName();
        if(registryMap.containsKey(serverClassName)){
            // 获取服务对象
            Object clazz = registryMap.get(serverClassName);
            Method method = clazz.getClass().getMethod(request.getMethodName(), request.getParams());
            result = method.invoke(clazz, request.getValues());
            System.out.println("request=" + request);
            System.out.println("result=" + result);
        }
        ctx.writeAndFlush(result);
        ctx.close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }


    // 实现简易IOC容器
    // 扫描出包里面所有的Class
    private void scanClass(String packageName){
        ClassLoader classLoader = this.getClass().getClassLoader();
        URL url = classLoader.getResource(packageName.replaceAll("\\.", "/"));
        File dir = new File(url.getFile());
        File[] files = dir.listFiles();
        for (File file: files){
            if(file.isDirectory()){
                scanClass(packageName + "." + file.getName());
            }else{
                // 拿出类名
                String className = packageName + "." + file.getName().replace(".class", "").trim();
                classCache.add(className);
            }
        }
    }

    // 把扫描到的Class实例化,放到Map中
    // 注册的服务名称就叫做接口的名字 [约定优于配置]
    private void doRegister(){
        if(classCache.size() == 0) return;
        for (String className: classCache){
            try {
                Class<?> clazz = Class.forName(className);
                // 服务名称
                Class<?> anInterface = clazz.getInterfaces()[0];
                registryMap.put(anInterface.getName(), clazz.newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

在这里还通过反射实现了简易的IOC容器,先递归扫描provider包底下的类,把这些类的对象作为服务对象放到IOC容器中进行管理,由于IOC是一个Map实现的,所以将类名作为服务名称,也就是Key,服务对象作为Value。根据消费者传过来的服务名称,就可以找到对应的服务,到此,Registry和Provider已经全部写完了。

2、consumer

目录结构如下:

└─src
    ├─main
    │  ├─java
    │  │  └─edu
    │  │      └─xpu
    │  │          └─rpc
    │  │              ├─api
    │  │              │      IRpcCalc.java
    │  │              │      IRpcHello.java
    │  │              │
    │  │              ├─consumer
    │  │              │  │  RpcConsumer.java
    │  │              │  │
    │  │              │  └─proxy
    │  │              │          RpcProxy.java
    │  │              │          RpcProxyHandler.java
    │  │              │
    │  │              └─core
    │  │                      InvokerMessage.java
    │  │
    │  └─resources
    └─test
        └─java
└─ pom.xml

在看客户端的实现之前,先梳理一下RPC流程。API 模块中的接口只在服务端实现了。因此,客户端调用API 中定义的某一个接口方法时,实际上是要发起一次网络请求去调用服务端的某一个服务。而这个网络请求首先被注册中心接收,由注册中心先确定需要调用的服务的位置,再将请求转发至真实的服务实现,最终调用服务端代码,将返回值通过网络传输给客户端。整个过程对于客户端而言是完全无感知的,就像调用本地方法一样,所以必定要对客户端的API接口做代理,隐藏网络请求的细节。

 

由上图的流程图可知,要让用户调用无感知,必须创建出代理类来完成网络请求的操作。

RpcProxy.java如下:

public class RpcProxy {
    public static <T> T create(Class<?> clazz) {
        //clazz传进来本身就是interface
        MethodProxy proxy = new MethodProxy(clazz);
        T result = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz} , proxy);
        return result;
    }

    private static class MethodProxy implements InvocationHandler {
        private Class<?> clazz;

        public MethodProxy(Class<?> clazz) {
            this.clazz = clazz;
        }
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 如果传进来是一个已实现的具体类
            if (Object.class.equals(method.getDeclaringClass())) {
                try {
                    return method.invoke(this, args);
                } catch (Throwable t) {
                    t.printStackTrace();
                }
                // 如果传进来的是一个接口(核心)
            } else {
                return rpcInvoke(method, args);
            }
            return null;
        }

        // 实现接口的核心方法
        public Object rpcInvoke(Method method, Object[] args) {
            // 传输协议封装
            InvokerMessage invokerMessage = new InvokerMessage();
            invokerMessage.setClassName(this.clazz.getName());
            invokerMessage.setMethodName(method.getName());
            invokerMessage.setValues(args);
            invokerMessage.setParams(method.getParameterTypes());

            final RpcProxyHandler consumerHandler = new RpcProxyHandler();
            EventLoopGroup group = new NioEventLoopGroup();

            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)
                        .channel(NioSocketChannel.class)
                        .option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
                                pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                                //自定义协议编码器
                                pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                                //对象参数类型编码器
                                pipeline.addLast("encoder", new ObjectEncoder());
                                //对象参数类型解码器
                                pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                                pipeline.addLast("handler", consumerHandler);
                            }
                        });
                ChannelFuture future = bootstrap.connect("localhost", 8080).sync();
                future.channel().writeAndFlush(invokerMessage).sync();
                future.channel().closeFuture().sync();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                group.shutdownGracefully();
            }
            return consumerHandler.getResponse();
        }
    }
}

我们通过传进来的接口对象,获得了要调用的服务名,服务方法名,参数类型列表,参数列表,这样就把自定义的RPC协议包封装好了,只需要把协议包发出去等待结果返回即可,所以为了接收返回值数据还需要自定义一个接收用的Handler,RpcProxyHandlerdiamante如下:

public class RpcProxyHandler extends ChannelInboundHandlerAdapter {
    private Object result;

    public Object getResponse() {
        return result;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        result = msg;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("client exception is general");
    }
}

这样就算是完成了整个流程,下面开始测试一下吧,测试的RpcConsumer.java代码如下:

public class RpcConsumer {
    public static void main(String[] args) {
        // 本机之间的正常调用
        // IRpcHello iRpcHello = new RpcHelloProvider();
        // iRpcHello.hello("Tom");

        // 肯定是用动态代理来实现的
        // 传给它接口,返回一个接口的实例,伪代理
        IRpcHello rpcHello = RpcProxy.create(IRpcHello.class);
        System.out.println(rpcHello.hello("ZouChangLin"));

        int a = 10;
        int b = 5;
        IRpcCalc iRpcCalc = RpcProxy.create(IRpcCalc.class);

        System.out.println(String.format("%d + %d = %d", a, b, iRpcCalc.add(a, b)));
        System.out.println(String.format("%d - %d = %d ", a, b, iRpcCalc.sub(a, b)));
        System.out.println(String.format("%d * %d = %d", a, b, iRpcCalc.mul(a, b)));
        System.out.println(String.format("%d / %d = %d", a, b, iRpcCalc.div(a, b)));
    }
}

3、效果测试

先开启Registry,运行端口是8080:

 

开启consumer开始调用

 

调用完成后可以看到调用结果正确,并且在Registry这边也看到了日志:

 

可以发现,简易RPC框架顺利完工!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39361.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

1. Spring Boot 3 入门学习教程之开发第一个 Spring Boot 应用程序

Spring Boot 3 入门学习教程之开发第一个 Spring Boot 应用程序0. 前言1. Spring Boot 介绍2. 系统要求2.1 Servlet容器2.2 GraalVM Native Image&#xff08;GraalVM 原生镜像&#xff09;3. 安装Spring Boot 开发环境3.1 安装JDK3.2 安装Spring Boot构建工具3.2.1 方式一&…

C++标准库分析总结(九)——<仿函数/函数对象>

目录 1.functor仿函数简介 2 仿函数的分类 3 仿函数使用 4 仿函数可适配的条件 1.functor仿函数简介 仿函数是STL中最简单的部分&#xff0c;存在的本质就是为STL算法部分服务的&#xff0c;一般不单独使用。仿函数&#xff08;functors&#xff09;又称为函数对象&…

【InnoDB Cluster】修改已有集群实例名称及成员实例选项

【InnoDB Cluster】修改已有集群实例名称&#xff0c;成员实例名称和选项 文章目录【InnoDB Cluster】修改已有集群实例名称&#xff0c;成员实例名称和选项修改名称修改已有集群实例名称修改已有集群实例的成员实例名称修改成员服务器操作系统的主机名直接修改元数据库中的表使…

力扣(LeetCode)88. 合并两个有序数组(C++)

朴素思想 朴素思想&#xff0c;开第三个数组&#xff0c;对 nums1nums1nums1 和 nums2nums2nums2 进行二路归并。 class Solution { public:void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {vector<int> nums3(mn);int i 0,j …

2.2 Linux启动初始化文件系统

为了方便了解和调试我们的Linux系统,我们需要将proc,debugfs,tmp等挂载起来,否则我们我发了解系统的进程,负载等信息,如下是未进行任何挂载时,我们无法通过ps等方法查看系统任何进程信息: 一,挂载proc fs proc是一个伪文件系统,(伪文件系统只存在内存中,而不占用存…

Node.js 入门教程 2 Node.js 简史

Node.js 入门教程 Node.js官方入门教程 Node.js中文网 本文仅用于学习记录&#xff0c;不存在任何商业用途&#xff0c;如侵删 文章目录Node.js 入门教程2 Node.js 简史2.1 一点历史2.2 20092.3 20102.4 20112.5 20122.6 20132.7 20142.8 20152.9 20162.10 20172.11 20182.12 2…

聊一聊微服务常见配置中心工作原理

0. 环境 nacos版本&#xff1a;1.4.1 Spring Cloud : 2020.0.2 Spring Boot &#xff1a;2.4.4 Spring Cloud alibaba: 2.2.5.RELEASE Spring Cloud openFeign 2.2.2.RELEASE 测试代码&#xff1a;github.com/hsfxuebao/s… 1. 配置中心基础 1.1 为什么要用配置中心&…

Js逆向教程-15滑块流程 极验

作者&#xff1a;虚坏叔叔 博客&#xff1a;https://xuhss.com 早餐店不会开到晚上&#xff0c;想吃的人早就来了&#xff01;&#x1f604; Js逆向教程-15滑块流程 极验 一、滑块是什么&#xff1f; 区分是否是机器人。根据滑动轨迹区分是否是人操作的。 滑块肯定有滑动条 …

亚马逊云科技持续创新、领势而行,re:Invent颠覆想象

当一行行代码成为托起数字社会的基础架构&#xff0c;社会发展开始面临真正意义上的变革与重塑。作为云计算领域的探路者与引领者&#xff0c;亚马逊云科技持续创新、领势而行&#xff0c;正不断塑造并颠覆着大众关于云计算未来的想象。 2006年 开端 2006年&#xff0c;亚马逊…

【单片机基础】I2C通信-基于STC89C52RC

文章目录1、IIC总线结构2、IIC总线传输协议3、完成工程代码1、IIC总线结构 IIC总线是philips公司在八十年代初推出的一种串行、半双工总线。主要用于近距离、低速的芯片之间通信&#xff1b;IIC总线有两根双向的信号线&#xff0c;一根数据线SDA用于收发数据一根时钟线SCL用于…

Mac 使用paralles 从零搭建hadoop集群

目录 1. 虚机的安装与配置 1.1 安装parallels 1.2 安装fedora系统 1.3 fedora的配置 1.3.1 内存和硬盘配置 1.3.2 网络配置 1.3.3 共享文件夹 1.4 虚拟机克隆 与 加载 2. 免密登录 2.1 分别查看master&#xff0c; slave01&#xff0c;slave02 的ip 2.2 查看各虚机的…

关于mpy电压采集模块的那些事儿(ads1256 ads8688 ad7606)

先来个人生碎碎念&#xff0c;越狱第三次失败了&#xff0c;而且第四次也可能失败&#xff0c;没错就是这么尴尬&#xff0c;绝对越狱的事件还有917天&#xff0c;其实比较麻木了&#xff0c;越狱成功与否都无所谓了&#xff0c;其实现在出去&#xff0c;外头的疫情&#xff0c…

【学生网页设计作业源码】基于HTML+CSS+JavaScript简单的大学生书店(13个页面) 二手书店电子商务网站模板源码

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

常见的数据结构基本介绍

文章目录常见的数据结构介绍栈和队列的介绍数组数据结构链表数据结构二叉树和二叉查找树平衡二叉树红黑树结构常见的数据结构介绍 数据结构是计算机底层存储、组织数据的方式。是指数据相互之间是以什么方式排列在一起的。 通常情况下&#xff0c;精心选择的数据结构可以带来更…

应用ceph文件系统存储(ceph-13.2.10)

记录&#xff1a;333 场景&#xff1a;在CentOS 7.9操作系统上&#xff0c;部署ceph-13.2.10集群。应用ceph文件系统(ceph file system)&#xff1b;主要是创建ceph文件系统、客户端挂载ceph文件系统等操作。 版本&#xff1a; 操作系统&#xff1a;CentOS 7.9 ceph版本&am…

JS获取音频的总时长,解决audio.duration 为 NaN || Infinity 问题

目录一、需求分析二、发现问题1.使用浏览器内置播放器<audio>无法显示时长2.获取总时长为 NaN || Infinity三、疑惑点四、解决方案一、需求分析 后端的接口中包含音频链接&#xff0c;前端需要自定义一个播放器播放音频。 二、发现问题 1.使用浏览器内置播放器<audi…

JPA 中使用 @OneToMany 、@ManyToOne 等关系映射注解

JPA 做 ORM(Object Relational Mapping&#xff0c;对象关系映射)时&#xff0c;为了开发效率&#xff0c;通常会在实体类上用 hibernate 的关系关联注解。 包括&#xff1a;OneToOne、 OneToMany 、ManyToOne 、ManyToMany 、JoinTable、以及 JoinColumn 以及 OrderBy&#xf…

汽车Automotive > SOME/IP应用学习

目录 SOME/IP介绍 SOME/IP主要功能 SOME/IP协议 SOME/IP服务类型 SOME/IP-举例 SOME/IP各模块协议 SOME/IP-基础元件 SOME/IP-SoAD SOME/IP-SD协议 SOME/IP-SD举例 SOME/IP-TP协议 SOME/IP-TP举例 SOME/IP介绍 SOME/IP ( Scalable service-Oriented Middleware ove…

面向大规模队列,百万并发的多优先级消费系统设计

大规模队列的核心诉求&#xff0c;不仅需要「快」&#xff0c;还需要兼顾「公平」。01 引言 HTTP是一种常用的通信协议&#xff0c;除了常见网站访问、上传下载&#xff0c;HTTP协议还经常被用在消息推送场景上。 设想你搭建了一个电商平台&#xff0c;有很多大型商家入驻了该…

Kotlin协程Flow浅析

Kotlin协程中的Flow主要用于处理复杂的异步数据&#xff0c;以一种”流“的方式&#xff0c;从上到下依次处理&#xff0c;和RxJava的处理方式类型&#xff0c;但是比后者更加强大。 Flow基本概念 Flow中基本上有三个概念&#xff0c;即 发送方&#xff0c;处理中间层&#x…