手写通俗简版RPC通信框架,你上你也行!

用通俗的语言讲RPC原理 各位吴彦祖&刘亦菲你好,俺是一名偷偷卷RPC的程序员龙哥???? 开始之前看下大纲 本文努力做到通俗一点,也画图辅助你理解,提到俺接下来的flag俺不会忘记,主要是尝试理解并慢慢深入RPC,最终能自己实现一个简单的RPC。这是系列的开篇。当然只有自己亲身理解做过,俺才会讲给同志们,我也把基础的知识点全面在系列梳理一遍,2022年做点有意义的,希望多多支持。 1 RPC定义的理解 RPC(Remote Procedure Call)翻译过来 远程过程调用,简单解释下字面的意思哈,远程肯定是指要跨机器 阅读详情

背景

        上篇文章我简单的介绍了自己打造的通俗简版RPC通信框架,也得到了大家的支持,短短一个星期的阅读量就上千了,虽然之后涨得很慢,哈哈,但是也很感动~

        如果大家对此项目还感兴趣的话,可到GitHub上瞄瞄:Netty-RPC

        上篇文章链接:《SpringBoot2+Netty打造通俗简版RPC通信框架》

        在介绍后续新增功能前,我们先回顾一下最简单的RPC通信的流程,流程图如下:

        我们可以看到其实整个流程其实是非常的简单的:客户端接收前端发送的请求,封装好请求Packet根据配置打开Netty通道进行通信,服务端接收请求Packet,解析并且根据请求信息,反射获取实现类调用方法,得到结果并封装好响应Packet然后返回结果给客户端,客户端获取结果响应给前端。

新增功能

        因为是模仿Dubbo造RPC通信框架,那么Dubbo基本的功能我们当然也必须得有啦,下面列出的是我后续新增的优化:

  1. 单一长连接
  2. 服务注解,并且带版本号
  3. 增加注册中心
  4. 处理Netty客户端或服务端主动断开异常
  5. 业务处理使用自定义业务线程池

     详细的代码大家可到我的github上阅读。

详细介绍

        首先,我们先看一下带注册中心后的流程图:

        我们可以看到多了个Zookeeper作为注册中心,然后就到了监听缓存列表,和服务缓存列表。大家不理解这两个列表不重要,下面我将继续详细的介绍一下我做这些功能的思路。

1、单一长连接:

        首先上一下简单的流程图:

        思路非常的简单:就是使用内存缓存缓存起来,结构就是Map,key为IP:Port  value为channel。

        在没有注册中心时,服务端的IP和PORT是写在配置文件里的,我们直接获取IP信息,并且判断在【Channel缓存列表】是否有此IP对应缓存起来的的Channel,有则获取直接进行RPC通信,否则创建新Channel进行RPC通信,记得还要缓存起来。
        而在有注册中心时,我们需要在请求Packet中获取需要提供的服务名,然后根据服务名在【服务缓存列表】获取所有提供此服务的所有应用IP,然后就是判断IP列表在【Channel缓存列表】中是否有缓存的Channel,有则获取并直接进行RPC通信;
否则,遍历应用IP列表,直到创建新Channel通信并且连接成功,然后将Channel缓存起来。最后,进行RPC通信然后等待获取Result即可。

2、服务注解:

        我们知道需要进行RPC通信的接口都会加上@NettyRPC注解,然后在服务端这,每次都是使用Reflections框架扫描出指定路径下的所有类,再判断是否有服务的实现类,有则利用反射进行方法调用。这听起来可是相当浪费时间,哈哈。

        这时候,我们可以利用Spring框架来去除掉这个扫描的动作。首先,我们提供一个@NettyRpcService注解,来标识所有RPC服务的实现类。然后我们创建一个类,实现接口ApplicationContextAware。然后我们可以利用ApplicationContext的getBeansWithAnnotation方法类根据指定注解获取Bean,我们这里当然是指定前面的@NettyRPCService注解了,然后我们利用内存缓存来缓存起来【提供RPC服务的实现类列表】,结构为Map:key为接口名+版本号,value为bean实例。那么之后的大家应该就懂了,我们再也不必每次都使用Reflections框架了。

package com.hyf.rpc.netty.server.config;

import com.hyf.rpc.netty.anno.NettyRPCService;
import com.hyf.rpc.netty.properties.NettyProperties;
import com.hyf.rpc.netty.server.NettyServer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Howinfun
 * @desc Netty服务提供者启动&扫面存储提供服务的实现类
 * @date 2019/7/18
 */
@Component
public class NettyServerInitConfig implements ApplicationListener<ContextRefreshedEvent>, ApplicationContextAware {

    /** 提供RPC服务的实现类  key为接口名+版本号,value为bean实例*/
    public static final Map<String,Object> beanMap = new HashMap<>(10);

    @Autowired
    private NettyServer nettyServer;
    @Autowired
    private NettyProperties nettyProperties;
    /**
     * 当ApplicationContext初始或刷新完毕触发
     * @param event
     */
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if (nettyProperties.getServerPort() != null){
            nettyServer.start();
        }
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // 遍历带有NettyRPCService注释的服务实现类
        Map<String,Object> beans = applicationContext.getBeansWithAnnotation(NettyRPCService.class);
        if (beans != null && beans.size() > 0) {
            for (Object serviceBean : beans.values()) {
                String interfaceName = serviceBean.getClass().getAnnotation(NettyRPCService.class).value().getName();
                String version = serviceBean.getClass().getAnnotation(NettyRPCService.class).version();
                beanMap.put(interfaceName+version, serviceBean);
            }
        }
        System.out.println(beanMap.toString());

    }
}

3、注册中心:

        这个上一下简单的流程图先:

        顺便看一下Zookeeper的数据结构:

        我这里使用的是Zookeeper作为注册中心。首先大家得自己安装一个Zookeeper服务。我们做注册中心是利用Zookeeper的监听事件,当然了,Zookeeper原生的监听事件是利用Watcher,而且是一次性的,所以不用。我会使用第三方框架Curator。Curator引入了Cache来实现对Zookeeper服务端事件监听,Cache事件监听可以理解为一个本地缓存视图与远程Zookeeper视图的对比过程。Cache提供了反复注册的功能。Cache分为两类注册类型:节点监听和子节点监听。

        首先是服务端:在zookeeper的/root下创建名为配置文件中namespace(例如rpc-server)的节点,然后我们可在上面的扫描带@NettyRPCService实现类缓存起来的同时,每一个实现类就往/root/namespace下创建一个节点,节点名为@NettyRPCService的value().getName+version(),节点内容为IPPojo(ip+提供netty服务端口号)的Json字符串。
        再来客户端:客户端主要是要做监听,首先是监听(使用PathChildrenCache,可监听子节点的增删改)根节点/root,如果有节点新增,则表明有新应用提供服务:这时候我们又要继续对此节点做监听(也是使用PathChildrenCache),并且将PathChildrenCache缓存到内存缓存中【监听缓存列表】。如果有节点删除,则表明有应用不提供服务了:这时候我们将监听关闭掉,并且从【监听缓存列表】中删除,并且从【服务缓存列表】删除应用提供的所有服务。根节点/root下面的子节点才是真正提供的RPC服务:当新增,需要将节点信息缓存到内存缓存中【服务缓存列表】;当删除,从【服务缓存列表】中删除对应数据。

4、处理Netty客户端或服务端主动异常:

        客户端:在RPCResponsPacketHandler中重写exceptionCaught方法。首先是根据标识从【Channel缓存列表】中移除此Channel,然后根据标识从【服务缓存列表】中移除对应的所有服务,最后关闭通道ctx.channel().close()。
        服务端:直接关闭通道即可,ctx.channel().close();

5、业务处理使用自定义业务线程池:     

        首先自定义一个线程池,根据自己需求去设计 核心线程数、最大线程数、线程保持活跃时间、队列、拒绝策略等。然后在业务处理除直接新建一个任务(实现Runnable接口)提交到线程池处理即可。

package com.hyf.rpc.netty.common;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Howinfun
 * @desc
 * @date 2019/7/12
 */
@Slf4j
public class TaskThreadPool {

    public static final TaskThreadPool INSTANCE  = new TaskThreadPool();
    private final ThreadPoolExecutor executor;
    private TaskThreadPool(){
        /**
         * 核心线程数:10
         * 最大线程数:20
         * 线程保持活跃时间:60s
         * 队列:阻塞队列,最多存放100个任务
         * 拒绝策略:任务将被放弃
         */
        this.executor = new ThreadPoolExecutor(10,
                                            20,
                                            60, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100),new ThreadPoolExecutor.CallerRunsPolicy());
    }
    public Future submit(Runnable task){
        log.info("业务线程池执行任务中...");
        Future future = executor.submit(task);
        return future;
    }
}

最后:

        到这里就基本已经全部结束了,虽然总体做得不是很好,但是起码自己经历了从0到1的所谓手写框架了,哈哈哈。同时,也将学到的Netty和Zookeeper等技术稍微实战了一下子,算是很满足了。也感谢很多同学们对我文章的阅读和支持,非常感谢。接下来,得想想干点什么。。。。

深入浅出 RPC - 浅出篇 近几年的项目中,服务化和微服务化渐渐成为中大型分布式系统架构的主流方式,而 RPC 在其中扮演着关键的作用。 在平时的日常开发中我们都在隐式或显式的使用 RPC,一些刚入行的程序员会感觉 RPC 比较神秘,而一些有多年使用 RPC 经验的程序员虽然使用经验丰富,但有些对其原理也不甚了了。 缺乏对原理层面的理解,往往也会造成开发中的一些误用。 本文分上下两篇《浅出篇》和《深入篇》,其目标就是想尝试 阅读详情

相关推荐

Linux中断子系统分析之(四):驱动程序申请中断

中断子系统中有一个重要的设计机制,那就是Top-half和Bottom-half,将紧急的工作放置在Top-half中来处理,而将耗时的工作放置在Bottom-half中来处理。 如果中断不分上下半部处理,那么意味着只有等上一个中断完成处理后才会打开中断,下一个中断才能得到响应。当某个中断处理处理时间较长时,很有可能就会造成其他中断丢失而无法响应,影响系统性能。 中断分成上下半部处理可以提高中断的响应能力,在上半部处理完成后便将中断打开,这样就可以响应其他中断了,等到中断退出的时候再进行下半部的处理。为了进

m0_46525308的博客 1226

RPC通讯框架详解

RPC基本理论 从单机走向分布式,产生了很多分布式的通信方式 最古老也是最有效,并且永不过时的,TCP/UDP的二进制传输。事实上所有的通信方式归根结底都是TCP/UDP CORBA Common Object Request Broker Architecture:古老而复杂的,支持面向对象的通信协议 web Service (SOA SOAP RDDI WSDL…):基于http + xml的标准化Web API RestFul (Representational State Transfer),回归

eyes++的博客 3968

TongWeb微服务部署实战:Nacos注册与网关路由的疑难解析

本文分享了将Spring Cloud微服务迁移至TongWeb应用服务器时,解决Nacos服务注册失败与Spring Cloud Gateway路由失效的实战经验。针对Nacos注册问题,通过实现ApplicationRunner接口并利用JMX获取容器端口,实现了手动注册;针对网关路由问题,建议将基于WebFlux的Gateway模块改为JAR包独立部署,以保障其原生运行环境。文章为在TongWeb上进行微服务部署提供了关键的问题排查思路与混合架构部署的最佳实践。

weixin_29209801的博客 240

rpc详解rpc框架

在互联网应用开发中,随着业务的复杂度增加,一般都会采用分布式架构。分布式架构的核心,就是利用多台普通的计算机组成一个庞大的复杂计算网络,提供高并发、高性能、高可用的系统能力支撑。在分布式架构中,原本的单体应用服务被拆分成多个独立部署的服务,分布式在计算机网络上,这些服务必然,需要通过网络进行数据通信和交互。而RPC框架,就是解决在分布式架构中,各个服务之间的网络通信问题的框架。在Java应用中,JDK 1.1版本的时候就提供了对RPC的支持框架,叫做RMI。由于RMI不能实现跨域语言的远程调用。

刘皇叔说Java的博客 7079

用Omniverse Kit开发第一个USD插件:从环境配置到实战案例(Python 3.7版)

本文详细介绍了如何使用NVIDIA Omniverse Kit和Python 3.7开发第一个USD插件,涵盖从环境配置到实战案例的全过程。通过创建可实时同步材质属性的USD插件,开发者可以快速掌握Omniverse开发框架,提升3D内容创作效率。

x8y9z0的博客 587

深入解析UEFI PEI阶段:PeiCore的三次调用与内存管理机制

本文深入解析UEFI启动流程中的PEI阶段,重点探讨PeiCore的三次调用与内存管理机制。详细介绍了PeiCore在临时内存中的初始化、堆栈切换至永久内存的过程,以及最终交接给DXE阶段的关键步骤,为开发者提供调试技巧与实战案例,助力解决UEFI启动问题。

weixin_28452161的博客 650

RPC 框架

学习记录

学习记录 3757

Apollo和autoware规划算法代码ros移植-路径规划可跑工程分享

Apollo6.0规划核心代码移植到Ros工程,可编译移植分享

weixin_39735688的博客 8509

RPC框架

两者都常用于实现服务,在这个层面最本质的区别是RPC服务主要工作在TCP协议之上(也可以在HTTP协议),而HTTP服务工作在HTTP协议之上。RPC效率优势明显,在实际开发中,客户端和服务端在技术方案中约定客户端的调用参数和服务端的返回参数之后就可以各自开发,任何客户端只要按照接口定义的规范发送入参都可以调用该RPC服务,服务端也能按接口定义的规范出参返回计算结果。通过HTTP URL调用的服务,浏览器访问本质上也算HTTP服务,不同的是需要客户端浏览器渲染服务端返回的结果。

weixin_45272882的博客 2226

Windows Server+SqlServer Always On实战:5步搞定高可用集群搭建

本文详细介绍了在Windows Server环境中,通过5个关键步骤搭建SQL Server Always On高可用集群的实战指南。内容涵盖从底层故障转移集群配置、SQL Server实例启用Always On功能,到创建可用性组和侦听器的完整流程,并提供了核心运维与故障排查思路,帮助企业构建稳定可靠的数据库高可用架构。

milk5的博客 858

SPEI计算避坑指南:gma中Distribution和FitMethod参数怎么选?

本文深入探讨了在SPEI计算中如何科学选择gma库中的Distribution和FitMethod参数,帮助研究者避开常见陷阱。详细分析了LogLogistic、Gamma和Pearson3三种分布的适用场景,以及LMoment与MLE拟合方法的优缺点,为气象干旱研究提供实用指南。

weixin_30308101的博客 250

RPC(Remote Procedure Call Protocol)远程过程调用协议框架:gRPC

这里面的 build 其实是为了安装 protobuf 插件,里面其实有 2 个插件我们需要用到,分别为 protobuf:compile 和 protobuf:compile-javanano,当我们直接执行时,会生成左侧文件,其中 GreeterGrpc 提供调用接口,Hello 开头的文件功能主要是对数据进行序列化,然后处理入参和返回值。由于是开源框架,通信的双方可以进行二次开发,所以客户端和服务器端之间的通信会更加专注于业务层面的内容,减少了对由 gRPC 框架实现的底层通信的关注。

u013250861的博客 858

数字孪生教程大全之 我们如何构建数字孪生?数字孪生的五个级别都是什么

文章摘要 什么是数字孪生? 我们如何构建数字孪生? 开发智能数字孪生系统的框架 什么是数字孪生? 数字孪生是一个不言自明的术语,用于识别物理系统的数字化版本。数字孪生的最早参考来自大卫·格伦特(David Gelernter )的小说作品。在他 1995 年的书中,Gelernter 博士为读者提供了未来有趣的可能性——万维网。前网络书籍谈到了计算机系统上的镜像数字世界。有些人可能会争辩说,我们的 Meta-verse 是新的数字世界——现实世界的数字孪生。但是,还有其他可能性,时间可能会讲述一个更好的

iCloudEnd的博客 3634

SpringBoot2+Netty打造通俗简版RPC通信框架

2019-07-19:完成基本RPC通信! 2019-07-22:优化此框架,实现单一长连接! 2019-07-24:继续优化此框架:1、增加服务提供注解(带版本号),然后利用Spring框架的在启动时立刻保存提供服务的实现类。2、优化NettyConfig(区分消费者和提供者配置)! 2019-07-27:优化此框架:增加注册中心,使用Zookeeper作为注册中心。 接下来:继续优化此框架,例如Netty方面的、还有增加Redis作为注册中心等等 2019-07-30:已完成。

Howinfun的博客 4461

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

RPC框架:从原理到选型,一文带你搞懂RPC

而 Spring Cloud 就像品牌机,在 Spring Source 的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础有足够的了解。一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。Spring Cloud 基于 Spring Boot,为微服务体系开发中的架构问题,提供了一整套的解决方案——服务注册与发现,服务消费,服务保护与熔断,网关,分布式调用追踪,分布式配置管理等。

daobuxinzi的博客 6万+

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

手写 RPC 框架

RPC(远程过程调用)是一种分布式系统通信技术,允许开发者像调用本地方法一样调用远程服务,屏蔽网络通信细节。本文详细介绍了一个完整的RPC框架实现方案,包含六大核心模块:序列化层(基于FastJSON2)、网络传输层(基于Netty NIO)、服务注册中心(ZooKeeper实现)、动态代理层(JDK动态代理)、负载均衡(轮询算法)以及注解驱动开发(@RpcService/@RpcReference)。框架采用模块化设计,支持服务自动注册发现、高性能通信和负载均衡,并通过Spring集成实现开箱即用。

果酱 の 博客 1233

手写一个RPC框架

一、项目介绍 ①、项目基础介绍 【 simple-rpc】是一款简单的rpc服务框架,主要用于学习造轮子!以便于透彻的理解RPC原理和过程,以及spring、springboot、netty中相关技术运用。 【 simple-rpc】主要实现了两个版本 【simple-rpc-like-feign分支】实现了类似于openfeign调用相关功能(openFeign底层通信是基于HTTP,一般是使用HttpClient等相关HTTP客户端发起的调用(这也是openfeign比较低效的原因之一)。在simpl

wenpan的博客 1155

手写RPC框架(文末附代码)

目录 RPC是什么? 应用场景 RPC 优点 写RPC框架需要具备哪些知识? RPC原理(摘自:什么情况下使用 RPC ? - 知乎) Netty框架 具体代码 代码目录 api provider consumer proxy registry protocol 本地调用和远程调用 产生的对象有什么区别呢? RPC的调用速度如何? RPC是什么? RPC是远程过程调用(Remote Procedure Call)的缩写形式。通俗的理解就是我调用了一个函数func(args

trigger的博客 2494

什么是RPC框架

本文属于【夯实Dubbo】系列文章,该系列旨在用通俗易懂的语言,带大家了解和学习 Dubbo 技术,希望能给读者带来一些干货。系列目录如下(可能随着写作的进行,会...

yayaqwl的博客 5572

常用的RPC框架

1. 为什么要使用RPC? RPC(remote procedure call)是指远程过程调用,比如两台服务器A和B,A服务器上部署一个应用,B服务器上部署一个应用,A服务器上的应用想调用B服务器上的应用提供的接口,由于不在一个内存空间,不能直接调用,所以需要通过网络来表达调用的语义和传达调用的数据。 RPC(remote procedure call,远程过程调用): 首

u013952133的博客 4万+

RPC框架的简单理解

远程过程调用)是一种允许运行在一台计算机上的程序调用另一台计算机上子程序的技术。这种技术屏蔽了底层的网络通信细节,使得程序间的远程通信如同本地调用一样简单。RPC机制使得开发者能够构建分布式计算系统,其中不同的组件可以分布在不同的计算机上,但它们之间可以像在同一台机器上一样相互调用。一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。比较正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

lcy57__的博客 1289

RPC远程调用通俗理解

先从一个案例来讲RPC 为了提升饭店的服务能力,饭店从一开始只有一个负责所有事情的厨师发展成有厨师、切菜师、备菜师等多个角色。在饭店只有一个厨师的时候,厨师想要做出一道美味的番茄炒蛋的时候,他需要自己洗番茄、切番茄、打鸡蛋、炒菜。整个过程不需要其他人参与自己就完全可以完成了。这就是古老的集中式应用中,一台单体计算机就可以搞定所有事情了 制作番茄炒蛋{ 厨师->洗菜->切菜->炒菜 } 随着饭店发展,需要明确分工,让专业的人负责专业的事儿。所以,整个做菜过程中不再只有厨师参与了。需

weixin_42408447的博客 4789

Java:什么是RPC框架?

RPC(Remote Procedure Call Protocol)是一种使客户端像调用本地对象一样调用远程计算机对象的协议,简化了网络通信的复杂性。其关键步骤包括服务接口定义、服务端和客户端创建、数据序列化、通信协议配置、服务发现等。随着访问量增长,单机系统难以满足需求,RPC框架通过跨平台支持和高效通信提升扩展性。常见RPC框架有Spring Cloud、Dubbo、gRPC、Thrift等,它们提供跨语言支持和自动生成工具,简化分布式服务开发并提升系统性能。

Poboll的博客 2528

这次带你手写一个分布式RPC框架!

一.概述 什么是RPC? 远程服务调用 官方:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的思想 通俗一点:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。 市面上常见的rpc框架:dobbo,springCloud,gRPC...

Javatutouhouduan的博客 587

谁能用通俗的语言解释一下什么是 RPC 框架?

首先说下什么是RPC?RPC全称Remote Procedure Call,即远程过程调用。 RPC的主要目标是让构建分布式计算(应用)更容易、透明,在提供强大的远程调用能力时不损失本地调用的语义简洁性。为实现该目标,RPC框架需提供一种透明调用机制,让使用者不必显式的区分本地调用和远程调用。 RPC框架负责屏蔽底层的传输方式(TCP或者UDP)、序列化方式(XML/JSON/二进制)和通信细节。 我在面试中常常会问:既然已经有http协议了,为什么我们还需要RPC方式来实现分布式系统? (更多大厂RPC常

九章算法的博客 1274
上一篇: SpringBoot2+Netty打造通俗简版RPC通信框架
下一篇: 基于GeoHash算法的四大经纬度计算方案
Howinfun
博客等级 码龄10年 165粉丝 213原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值