SpringCloud极限速通版

1.SpringCloud概述

1.1 什么是微服务

1.1.1 单体架构

        业务所有功能都打包在一个war包或jar包,这种方式就是单体架构,单体架构的应用就是单体应用。这种架构开发简单,部署简单,一个项目包含所有功能;省去了多个项目之间交互和调用消耗,直接部署在一个服务器即可。

单体面临的问题:

  • 后端服务器压力大,负载高
  • 业务场景发展变复杂之后,单体应用越来越大,各个业务之间耦合度也会越来越高:某一个功能挂了 ,全挂了 。

解决方案:

        横向:添加服务器 -> 集群

        纵向:把整块业务划分为不同的模块 -> 分布式

1.1.2 集群和分布式架构

        集群:将一个完整的系统部署到多个服务器上,每个服务器都能提供系统的所有服务,多个服务器通过负载均衡调度完成任务,每个服务器称为节点

        分布式:将一个系统拆分为多个子系统,多个子系统部署到多个服务器上,多个服务器的子系统完成一个特点任务

集群和分布式的联系:

  • 从概念上:集群是多个计算机做相同的事,分布式是多个计算机做不同的事
  • 从功能上:集群每个点功能相同和互相替代,分布式的节点业务实现不同
  • 从关系上:在实践中,二者互相配合:分布式某一个节点是一个集群;分布式架构建立在集群上,二者通称分布式架构

1.1.3 微服务架构

        按照业务拆分的话,会有一些重复功能实现:因此我们可以把通用的,被多个上层服务调用的共享业务,提取成独立的基础服务,组成一个个微小的服务:微服务

    

简单来说:微服务是很小的服务,小到一个服务只对应一个单一的功能,只做一件事,这个服务可以单独部署运行

微服务之间使用Rest或者RPC通信        

分布式与微服务的区分

分布式:服务拆分,拆了就行

微服务:指非常微笑的服务,不能再拆分的服务,更细粒度的垂直拆分

微服务是一种经过良好架构设计的分布式架构方案;

1.2 什么是SpringCloud

SpringCloud是分布式微服务架构的一站式解决方案

注意版本一致

本文将以SpringCloudAlibaba为主进行讲解简单的入门及项目运用

2. SpringCloud运用

2.1 环境与工程搭建

win和linux都需要安装:JDK21+MySQL5.7

Linux安装jdk21:

wget https://download.oracle.com/java/17/latest/jdk-17_linux-x64_bin.rpm

yum -y install jdk-21_linux-x64_bin.rpm

java -version

2.2 服务拆分

将模拟实现一个demo级别的电商平台,先从服务拆分起手:

问题来了如何划分?一般遵循以下原则:

1.单一职责原则

指的是一个类应该专注于单一功能,不要存在多于一个导致类变更的原因

2.服务自治

自己可以独立自理,每一个服务都可以独立开发构建部署测试...

3.单向依赖

不能存在循环依赖、双向依赖:循环依赖 A调用B,B调用C,C调用A  ,双向依赖 A调用B,B调用A。

要注意的是:服务拆分不一定一定要遵循原则,具体情况具体划分以业务为主。

以商品订单为例:

把服务拆分为订单服务、商品服务

订单服务:提供订单ID,获取订单详细信息

商品服务:根据商品ID,返回商品详细信息

2.3 数据准备

订单表商品表

2.4 工程搭建

2.4.1 创建父子工程

创建Maven项目

2.4.2 订单、商品服务实现

简单的controller、service、mapper层实现即可

2.4.3 远程调用

我们想调用订单服务获取订单信息,订单信息中的商品ID在去调用商品服务,获取商品信息。

我们是用封装好的RestTemplate,通过http访问来实现远程调用。

public class OrderService {
    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public OrderInfo selectOrderById(Integer orderId){
        OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
        ProductInfo productInfo = restTemplate.getForObject("http://order/selectProductById?productId=" + orderInfo.getProductId(), ProductInfo.class);
        orderInfo.setProductInfo(productInfo);
        return orderInfo;
    }
}
RestTemplate

Rest:表现层  资源  状态转移

资源:网络上的数据文本图片等

表现层:资源的表现形式 文本的表现形式是txt

状态转移:通过网络访问资源,对资源进行修改(引起资源状态的变化)

简单来说:Rest就是描述网络中client和server的一种交互形式,rest本身不是用,只是一种风格

Restful

满足Rest风格的接口就是Restful API

风格主要特征:

1.资源:资源可以是一个图片,对资源进行处理

2.统一接口:

RestTemplate由Spring提供,封装HTTP调用,并强制使用Resful风格,它会处理HTTP连接和关闭,只需要提供资源地址和参数即可

来看以上的代码出现的问题:

1、url是写死的,url出现变动需要手动修改

2、多机器部署如何处理

3、返回结果如何共用

....

SpringCloud将解决以上等等问题...

3.Eureka

注册中心三个角色:

服务注册:服务提供者启动时,向Registry注册自己的服务,并且向Registry定期发送心跳包汇报存活状态

服务发现:服务消费者从注册中心查询服务提供者的地址,并通过该地址去调用提供者的接口,服务发现一大重要作用:提供给服务消费者一个可用的服务列表

3.1 CAP理论

一致性

强一致性:主库从库无论何时,对外提供的服务都是一致的

弱一致性:随着时间的推移,最终达到一致性

可用性

对所有请求都有响应:这个请求可能是错误的也要响应

分区容错性

在网络分区的情况下,系统依然可用对外提供服务

举一个例子:

在银行中,所有的银行工作人员对客户,讲的利率都是一致的,这就是一致性

无论何时,银行人员对客户利率查询请求,都是有答案的,这个答案可能是旧的,这就是可用性

如果一个工作人员请假,银行依旧运行

P一定做到(分区容错性)

保证一致性的情况下,主从库同步期间不对外同步,因此无法保证可用性

保证可用性,数据同步的时候无法保证强一致性

只能二选一

3.2 常见的注册中心

1. zookeeper
Zookeeper的官方并没有说它是一个注册中心,但是国内Java体系,大部分的集群环境都是依赖Zookeeper来完成注册中心的功能.
2. Eureka
Eureka是Netflix开发的基于REST的服务发现框架,主要用于服务注册,管理,负载均衡和服务故障转移.官方声明在Eureka2.0版本停止维护,不建议使用.但是Eureka是SpringCloud服务注册/发现的默认实现,所以目前还是有很多公司在使用.
3. Nacos
Nacos是Spring Cloud Alibaba架构中重要的组件,除了服务注册,服务发现功能之外, Nacos还支持配置管理,流量管理,DNS,动态DNS等多种特性.
 

3.3 Eureka搭建注册中心

已淘汰,相关组件学习请看其他资料,下文有Nacos

3.负载均衡

负载均衡(LB)是高并发、高可用不可缺少的关键组件

当服务流量增大时,通常会采用增加机器的方式进行扩容,负载均衡就是在多个机器或其他资源中,按照一定的规则(不一定是平均)进行合理的分配

 负载均衡分为客户端负载均衡和服务端负载均衡,二者的区分顾名思义就是负载均衡器在哪里实现,常见的服务端负载均衡器Nginx,常见的客户端负载均衡器为SpringCloudLoadBalancer

3.1 SpringCloudBalancer

3.1.1 简单入门

添加注解、修改远程调用代码,把ip和端口号改成服务名

3.1.2 负载均衡策略

只支持轮询和随机选择:

轮询:服务器轮流的处理请求

随机选择:随机选择一个后端的服务器处理请求

自定义负载均衡策略

要注意:该自定义类不需要@Configuration注释,在组件扫描范围内

使用自定义负载均衡策略,name:该负载均衡器对哪个服务生效;config:该负载均衡策略用哪个策略来实现(图内是自己实现的自定义负载均衡策略)

 3.2 原理

LoadBalancer的实现,主要是LoadBalancerInterceptor

拿到url,从url拿到host获取服务名称,判断不能为空

第一步就是根据服务名称获取负载均衡策略(像上文提到的的自定义均衡策略注解中name属性),如果负载均衡策略为空就返回null,不为空就调用loadBalancerResponse.getServer方法

轮询的策略:

4. Nacos

在最初开源时, Nacos选择进⾏内部三个产品合并统⼀开源(Configserver ⾮持久注册中⼼,VIPServer 持久化注册中⼼,Diamond 配置中⼼). 定位为:⼀个更易于构建云原⽣应⽤的动态服务发现, 配置管理 和服务管理平台. 所以Nacos是⼀个注册中⼼组件, 但它⼜不仅仅是注册中⼼组件.

4.1 Nacos安装

Release 2.2.3 (May 25th, 2023) · alibaba/nacos · GitHub

下载之后解压:

Nacos启动默认是集群模式,自我学习需要修改

以文本编辑器打开start.cmd 进行修改

修改端口号是在conf下application.properties修改port

4.2 快速入门

4.2.1 环境搭建

父子项目都需要添加相对应的依赖,loadBalancer依赖也需加入。要注意的是SpringBoot和Nacos版本有要求。

配置文件

4.2.2 远程调用

4.2.3 服务下线与权重配置

Nacos在控制台上对某一个机器进行下线,IDEA上看这个机器实际还在运行,消费者发送请求(url,restTemplate)不会请求到已下线的机器;

Nacos控制台还可以配置节点的权重

不过要注意的是,如果加了LoadBalancer的话是没办法使用到Nacos权重的:

配置文件修改即可

4.2.4 同集群优先访问

在分布式架构中,一个应用会有多个实例,如何部署?

Nacos把同⼀个机房内的实例, 划分为⼀个集群. 所以同集群优先访问, 在⼀定程度上也可以理解为同机 房优先访问.

因此微服务访问时, 应尽量访问同机房的实例. 当本机房内实例不可⽤时, 才访问其他机房的实例

⽐如order-service 在上海机房, product-service 在北京和上海机房都有实例, 那我们希望可以优先访 问上海机房, 如果上海机房没有实例, 或者实例不可⽤, 再访问北京机房的实例. 通常情况下, 因为同⼀个 机房的机器属于⼀个局域⽹, 局域⽹访问速度更快⼀点!

需要开启Nacos的负载均衡策略


Nacos负载均衡总结

1.支持服务上下线

2.支持权重的配置(非严格)

3.同集群优先访问

2、3前提是需要开启Nacos负载均衡

4.3 健康检查

Nacos作为注册中心,需要感知服务的健康状态

Nacos有俩种健康检查机制

客户端主动上报机制

客户端主动以心跳上报机制告知服务器健康状态,默认心跳间隔5s

nacos会在超过15s未收到心跳后将实例设置为不健康状态,超过30s自动删除

服务器端反向探测机制

nacos主动探知客户端健康状态,默认间隔20s

健康检查失败后,标记为不健康,不会删除

但要注意的是这俩机制不能自主设置, 这俩机制与Nacos的服务实例类型有关 

Nacos服务实例类型

Nacos的服务实例(注册的节点)分为临时实例和⾮临时实例.

• 临时实例: 如果实例宕机超过⼀定时间, 会从服务列表剔除, 默认类型.

• ⾮临时实例: 如果实例宕机, 不会从服务列表剔除, 也可以叫永久实例

修改非临时:配置文件中修改即可

spring:
 cloud:
  nacos:
    discovery:
     ephemeral: false # 设置为⾮临时实例

总结:

4.4 环境隔离

 

配置文件中

spring.cloud.nacos.discovery.namespace:填写命名空间的ID

环境不同的服务是无法进行调用的

4.5 配置中心

除了注册中⼼和负载均衡之外, Nacos还是⼀个配置中⼼, 具备配置管理的功能

为什么需要配置中心?

当前项⽬的配置都在代码中, 会存在以下问题:

1. 配置⽂件修改时, 服务需要重新部署. 微服务架构中, ⼀个服务可能有成百个实例, 挨个部署⽐较⿇ 烦, 且容易出错.

2. 多⼈开发时, 配置⽂件可能需要经常修改, 使⽤同⼀个配置⽂件容易冲突 

流程

1.服务启动时, 从配置中⼼读取配置项的内容, 进⾏初始化.

2. 配置项修改时, 通知微服务, 实现配置的更新加载

使用

1、添加配置、引入依赖

2、修改配置文件

配置管理的命名空间 不是 服务管理(不同环境)的命名空间,二者并无关系只是叫法一样

添加依赖

<dependency>
 <groupId>com.alibaba.cloud</groupId>
 <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringCloud 2020.*之后版本需要引⼊bootstrap-->
<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-bootstrap</artifactId>
</dependency>

再创建一个配置文件bootstrap.yml

微服务启动前, 需要先获取nacos中配置, 并与application.yml配置合并. 在微服务运⾏之前, Nacos要求 必须使⽤ bootstrap.properties 配置⽂件来配置Nacos Server 地址

spring:
    application:
        name: 服务名称
 cloud:
     nacos:
         config:
             server-addr: IP地址

配置中心和注册中心是隔离的

配置中心详解

设置命名空间

修改bootstrap配置文件

spring.cloud.nacos.config.namespace=配置中心的命名空间ID

Data ID

官方文档

prefix 默认为 spring.application.name 的值, 也可以通过配置项 spring.cloud.nacos.config.prefix 来配置.

• spring.profiles.active 即为当前环境对应的 profile. 当 spring.profiles.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 ${prefix}.${fileextension}

• file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。

⽬前只⽀持 properties 和 yaml 类型.

默认为properties. 微服务启动时, 会从Nacos读取多个配置⽂件:

1. ${prefix}-${spring.profiles.active}.${file-extension} 如: product-servicedev.properties

2. ${prefix}.${file-extension} , 如: product-service.properties

3. ${prefix} 如product-service 

三个⽂件的优先级为: product-service-dev.properties > product-service.properties > productservice

5.服务部署

1. 修改数据库, Nacos等相关配置

2. 对两个服务进⾏打包

3. 上传jar到Linux服务器

4. 启动Nacos 启动前最好把data数据删除掉

5. 启动服务 nohup

Nacos与Eureka的区别

共同点: 

都⽀持服务注册服务拉取

区别:

1.功能 :Nacos除了服务发现和注册之外, 还提供了配置中⼼, 流量管理DNS服务等功能.

2. CAP理论

Eureka遵循AP原则, Nacos可以切换AP和CP模式,默认AP.

Nacos 根据配置识别CP或者AP模式.

如果注册Nacos的Client的节点是临时节点, 那么Nacos对这个 Client节点的效果就是AP, 反之是CP. AP和CP可以同时混合存在.

3. 服务发现

Eureka:基于拉模式. Eureka Client会定期从Server拉取服务信息, 有缓存, 默认每30秒拉取⼀次. Nacos:基于推送模式. 服务列表有变化时实时推送给订阅者, 服务端和客⼾端保持⼼跳连接. 

6. openFeign

前面我们使用RestTempalte会带来几个问题:url拼接复杂,代码可读性差,风格不统一

微服务之间通讯方式通常有俩种:RPC和HTTP

SpringCloud默认使用HTTP:RestTemplate和openFeign

RPC(Remote Procedure Call)远程过程调⽤,是⼀种通过⽹络从远程计算机上请求服务,⽽不需 要了解底层⽹络通信细节。RPC可以使⽤多种⽹络协议进⾏通信, 如HTTP、TCP、UDP等, 并且在 TCP/IP⽹络四层模型中跨越了传输层和应⽤层。简⾔之RPC就是像调⽤本地⽅法⼀样调⽤远程⽅法

6.1 介绍openFeign

OpenFeign 是⼀个声明式Web Service 客⼾端. 它让微服务之间的调⽤变得更简单, 类似controller 调⽤service, 只需要创建⼀个接⼝,然后添加注解即可使⽤OpenFeign 

可以简单理解为 Netflix Feign 是OpenFeign的祖先, 或者说OpenFeign 是Netflix Feign的升级版. OpenFeign 是Feign的⼀个更强⼤更灵活的实现

Spring Cloud Feign 是 Spring 对 Feign 的封装, 将 Feign 项⽬集成到 Spring Cloud ⽣态系统中.

受 Feign 更名影响,

Spring Cloud Feign 也有两个 starter

• spring-cloud-starter-feign

• spring-cloud-starter-openfeign (使用这个)

6.2 快速上手

Feign客户端

@FeignClient(value = "product-service", path = "/product")
public interface ProductApi {
 @RequestMapping("/{productId}")
 ProductInfo getProductById(@PathVariable("productId") Integer productId);
}

@FeignClient 注解作⽤在接⼝上, 参数说明:

• name/value:指定FeignClient的名称, 也就是微服务的名称, ⽤于服务发现, Feign底层会使⽤ Spring Cloud LoadBalance进⾏负载均衡. 也可以使⽤ url 属性指定⼀个具体的url.

• path: 定义当前FeignClient的统⼀前缀

6.3 参数传递

6.4 最佳实践

通过观察, 我们也能看出来, Feign的客⼾端与服务器端⾮常相似

继承

我们可以定义好⼀个接⼝, 服务提供⽅实现这个接⼝, 服务消费⽅编写Feign 接⼝的时候, 直接继承这个 接⼝

具体参考: Spring Cloud OpenFeign Features :: Spring Cloud Openfeign

分为几步操作:

在创建一个module:product-api模块

复制 ProductApi, ProductInfo 到product-api模块中

public interface ProductInterface {
 @RequestMapping("/{productId}")
 ProductInfo getProductById(@PathVariable("productId") Integer productId);
 @RequestMapping("/p1")
 String p1(@RequestParam("id") Integer id);
 @RequestMapping("/p2")
 String p2(@RequestParam("id")Integer id,@RequestParam("name")String name);
 @RequestMapping("/p3")
 String p3(@SpringQueryMap ProductInfo productInfo);
 @RequestMapping("/p4")
 String p4(@RequestBody ProductInfo productInfo);
}

目录结构:

maven将此模块打包进本地仓库中,供其他模块调用(加依赖即可调用)

服务提供⽅实现接⼝ ProductInterface

@RequestMapping("/product")
@RestController
public class ProductController implements ProductInterface {
 @Autowired
 private ProductService productService;
 @RequestMapping("/{productId}")
 public ProductInfo getProductById(@PathVariable("productId") Integer
productId){
 System.out.println("收到请求,Id:"+productId);
 return productService.selectProductById(productId);
 }
 @RequestMapping("/p1")
 public String p1(Integer id){
 return "p1接收到参数:"+id;
 }
 @RequestMapping("/p2")
 public String p2(Integer id,String name){
 return "p2接收到参数,id:"+id +",name:"+name;
 }
 @RequestMapping("/p3")
 public String p3(ProductInfo productInfo){
 return "接收到对象, productInfo:"+productInfo;
 }
 @RequestMapping("/p4")
 public String p4(@RequestBody ProductInfo productInfo){
 return "接收到对象, productInfo:"+productInfo;
 }
}

Feign客户端可以删除了

抽取

 简单来说创建一个module,把Feign客户端完整的复制过去,加入到Maven仓库,供其他服务使用

客户端(谁调用的)加注解

在启动类中加!!

@EnableFeignClients(basePackages = {"com.bite.api"})

也可以指定需要加载的Feign客⼾端

@EnableFeignClients(clients = {ProductApi.class})

服务部署

确认配置

打包

部署

7. 统一服务入口 Gateway

当前的所有微服务接口都是直接对外暴露的,可以直接通过外部访问,为了保证安全性。服务端实现的微服务接⼝通常都带有⼀定的权限校验机制. 由于使⽤了微服务, 原本⼀个应⽤的的多个 模块拆分成了多个应⽤, 我们不得不实现多次校验逻辑。

如果是多个服务都需要校验一下权限,那也太麻烦了:Gateway应运而生

7.1 什么是API网关

API⽹关(简称⽹关)也是⼀个服务, 通常是后端服务的唯⼀⼊⼝. 它的定义类似设计模式中的Facade模式 (⻔⾯模式, 也称外观模式). 它就类似整个微服务架构的⻔⾯, 所有的外部客⼾端访问, 都需要经过它来进 ⾏调度和过滤

核心功能

权限校验:网关没过,后面的服务访问不到

动态路由:⼀切请求先经过⽹关, 但⽹关不处理业务, ⽽是根据某种规则, 把请求转发到某个微服务

负载均衡:当路由的⽬标服务有多个时, 还需要做负载均衡     

限流:请求流量过⾼时, 按照⽹关中配置微服务能够接受的流量进⾏放⾏, 避免服务压⼒过⼤

7.2 快速入门

创建一个服务

引入依赖   网关服务添加配置

eg:

uri:目标服务地址

predicates:路由条件(满足后面的条件,会把这个请求转发到uri的地址上)

7.3 Route Predicate Factories

predicate是java8提供的函数式编程接口,接收一个参数并返回一个布尔值用于条件过滤。

Route Predicate Factories (路由断⾔⼯⼚, 也称为路由谓词⼯⼚, 此处谓词表⽰⼀个函数), 在Spring Cloud Gateway中, Predicate提供了路由规则的匹配机制

我们配置文件中写的断言规则是字符串,这些字符串会被RoutePredicateFactory读取并处理,转变为路由判断条件。

更多的条件详见:Route Predicate Factories :: Spring Cloud Gateway

当有多个路由条件的时候,他们的关系是and

7.4 Gateway Filter Factories(网关过滤工厂)

Predicate是决定了请求由哪一个路由处理,Filter则是在请求处理前后加一些逻辑

分俩种类型Post和Pre(根据作用时间划分的)

从作用范围上:

GatewayFilter(应用到单个路由上或一组路由上)和GlobalFilter(应用到所有的路由上,对所有的请求都生效)

7.4.1 GatewayFilter

例子:

GatewayFilter Factories :: Spring Cloud Gateway

默认过滤器

针对全部的路由生效:spring.cloud.gateway.default-filters这个属性加一个filter列表

7.4.2 GlobalFilter

作用与GatewayFilter相同,只不过的是GlobalFilter是应用所有路由请求

Global Filters :: Spring Cloud Gateway

配置文件进行添加

spring.cloud.gateway:

7.5 过滤器执行顺序

请求路由后,global和gateway Filter合并到一个过滤器链中,并进行排序,依次执行过滤器

每个过滤器都有一个int类型的order值,其值越小优先级越高;

当order值一致时,执行顺序是Derfault > Gateway > Global

7.6 自定义过滤器

7.6.1 自定义GatewayFilter

需要实现GatewayFilterFactory,SpringBoot默认实现的是AbstractGatewayFilterFactory

主体类:

@Slf4j
@Component
public class CustomGatewayFilterFactory extends AbstractGatewayFilterFactory<CustomConfig> implements Ordered {
    public CustomGatewayFilterFactory() {
        super(CustomConfig.class);
    }

    @Override
    public GatewayFilter apply(CustomConfig config) {
        return new GatewayFilter() {
            /**
             * ServerWebExchange: HTTP 请求-响应交互契约, 提供了对HTTP请求和响应的访问
             * GatewayFilterChain: 过滤器链
             * Mono: Reactor的核心类, 数据流发布者,Mono最多只能触发一个事件.可以把Mono用在异步完成任务时,发出通知
             * chain.filter(exchange)  执行请求
             * Mono.fromRunnable()  创建一个包含Runnable元素的数据流
             */
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                //Pre类型   执行请求   Post类型
                log.info("Pre Filter, config:{} ",config);  //Pre类型过滤器代码逻辑
                return chain.filter(exchange).then(Mono.fromRunnable(()->{
                    log.info("Post Filter....");  //Post类型过滤器代码逻辑
                }));
            }
        };
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

配置类

import lombok.Data;

@Data
public class CustomConfig {
    private String name;
}

使用配置文件

7.6.2 自定义GlobalFilter

@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("Pre Global Filter");
        return chain.filter(exchange).then(Mono.fromRunnable(()->{
            log.info("Post Global Filter...");
        }));
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值