SpringCloud(alibaba)

1 技术栈

在这里插入图片描述

2 整体架构

在这里插入图片描述

3 网关(Gateway)

Gateway 是异步非阻塞的模型,底层使用的Netty框架,可和springwebFlux、springMvc结合使用。

3.1 网关的功能

  • 身份认证和权限校验;
  • 服务路由、负载均衡;
  • 请求限流,熔断;

3.2 三大组件

  1. Route(路由): 路由是构建网关的基本模块,它由ID、目标URI、一系列的断言和过滤器组成。如断言为true则匹配该路由;
  2. Predicate(断言):参考的是Java8的java.util.function.Predicate,开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由;
  3. Filter(过滤):指的是Spring框架中GatewayFilter的实例,使用过滤器可以在请求被路由前或者之后对请求进行修改。
    在这里插入图片描述

web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。 Predicate 就是我们的匹配条件;而 Filter 就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标 URI,就可以实现一个具体的 Route (路由)了。

3.3 工作流程

在这里插入图片描述

  1. 客户端(Gateway Client)向 Spring Cloud Gateway 发出请求
  2. 然后在 Gateway Handler Mapper 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。
  3. Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
  4. 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

Filter在“pre”类型的过滤器可以做参数校验权限校验流量监控日志输出协议转换等,在“post”类型的过滤器中可以做响应内容响应头的修改日志的输出**,流量监控**等有着非常重要的作用。

3.4 路由断言工厂Route Predicate Factory

gateway包含了许多内置的路由断言工厂。所有的这些断言匹配不同的HTTP请求属性。你能组合这些路由断言工厂。我们一般是在配置文件中配置Predicates,当然我们也可以自定义Predicate,如下:

@Component
public class CustomeRoutePredicateFactory extends AbstractRoutePredicateFactory<PathRoutePredicateFactory.Config> {

    public CustomeRoutePredicateFactory() {
        super(PathRoutePredicateFactory.Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(PathRoutePredicateFactory.Config config) {
        System.out.println("TokenRoutePredicateFactory Start...");
        return exchange -> {
            ServerHttpRequest request = exchange.getRequest();
            //take information from the request to see if it
            //matches configuration.
            return matches(config, request);
        };
    }

    private boolean matches(PathRoutePredicateFactory.Config config, ServerHttpRequest request) {
        System.out.println(request.getBody());
        RequestPath path = request.getPath();
        if(path.toString().contains("gateway")) {
        	return false;
		}
        return true;
    }
}

配置文件properties:

spring: 
  cloud: 
    gateway: 
      routes: 
      - id: path_route
        uri: lb://PROVIDER  # lb的意思是负载均衡
        predicates:
            - Custome=/gateway/**    # 这个Custome就是自定义类CustomeRoutePredicateFactory的前缀,仿照PathRoutePredicateFactory写的
		filters:
			- name: Hystrix    # 表示filter中使用Hystrix来做熔断降级
			args:
				name: fallbackcmd  #名字唯一即可
                fallbackUri: forward:/defaultfallback  # 在本gateway中产生的异常或超时将调用本gateway中的control层中的defaultfallback

网关路由可以配置的内容包括:

  1. 路由id:路由唯一标示;
  2. uri:路由目的地,支持lb和http两种 predicates;
  3. 路由断言,判断请求是否符合要求,符合则转发到路由目的地 filters;
  4. 路由过滤器,处理请求或响应。

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件,例如Path=/user/**是按照路径匹配,这个规则是由org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的。
像这样的断言工厂在SpringCloudGateway还有十几个:
在这里插入图片描述

3.5 路由过滤器(Filter Chain)

Gateway 的 Filter 有三种:全局Filter默认 Filter自定义 Filter
全局Filter一般是我们定义,实现implements GlobalFilter, Ordered两个接口,然后重写两个方法即可。一个是filter方法,一个是getOrder方法。全局过滤器可以存在多个,多个的时候根据getOrder方法的返回值大小进行排序执行,请求服务前的优先级越高,服务返回后执行的优先级越低。如果 order 值相同,则按照如下顺序执行:
在这里插入图片描述

@Component
public class MyLogGateWayFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        System.out.println("time:" + new Date() + "\t 执行了自定义的全局过滤器: " + "MyLogGateWayFilter" + "hello");

        String uname = exchange.getRequest().getQueryParams().getFirst("uname");
        if (uname == null) {
            System.out.println("****用户名为null,无法登录");
            exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
            return exchange.getResponse().setComplete();
        }
        // 这个就是继续执行的意思
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 1;
    }
}

以下示例分别显示了如何设置全局前置和后置过滤器:其实还是基于全局过滤器,只不过展现的方式不一样了,这里是直接通过bean注解来注入到容器,然后使用的是匿名类。注释掉的代码是官网给的案例!

@Configuration
public class GateWayFilter {

    @Bean
    public GlobalFilter customGlobalFilter() {
//        return (exchange, chain) -> exchange.getPrincipal()
//                .map(Principal::getName)
//                .defaultIfEmpty("Default User")
//                .map(userName -> {
//                    //adds header to proxied request
//                    exchange.getRequest().mutate().header("CUSTOM-REQUEST-HEADER", userName).build();
//                    return exchange;
//                })
//                .flatMap(chain::filter);
        return new GlobalFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                System.out.println("请求前执行,这里可以放请求前的逻辑");
                exchange.getRequest().mutate().header("CUSTOM-REQUEST-HEADER", "lisi").build();
                return chain.filter(exchange);
            }
        };
    }

    @Bean
    public GlobalFilter customGlobalPostFilter() {
//              return (exchange, chain) -> chain.filter(exchange)
//                .then(Mono.just(exchange))
//                .map(serverWebExchange -> {
//                    //adds header to response
//                    serverWebExchange.getResponse().getHeaders().set("CUSTOM-RESPONSE-HEADER",
//                            HttpStatus.OK.equals(serverWebExchange.getResponse().getStatusCode()) ? "It worked" : "It did not work");
//                    return serverWebExchange;
//                })
//                .then();

        return new GlobalFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
                return chain.filter(exchange).then(Mono.just(exchange)).map(serverWebExchange -> {
                    System.out.println("请求后执行,这里是当网关拿到转发服务的请求响应后会执行");
                    //adds header to response
                    serverWebExchange.getResponse().getHeaders().set("CUSTOM-RESPONSE-HEADER",
                            HttpStatus.OK.equals(serverWebExchange.getResponse().getStatusCode()) ? "It worked" : "It did not work");
                    return serverWebExchange;
                }).then();
            }
        };
    }
}

Gateway也为我们提供了很多的内置Filter,即默认的Filter,像AddRequestHeaderGatewayFilter,AddRequestParameterGatewayFilter等等。在yml中配置也只需要写前缀即可,如下:

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        - AddRequestParameter=red, blue

下面讲几个常用的gateway内置的Filter:ForwardRoutingFilter和ReactiveLoadBalancerClientFilter。

  • ForwardRoutingFilter:这个过滤器将URI视为是可改变的属性。当URL有forward修饰时,例如:forward://localendpoint,它会使用spring的DispatcherHandle来处理请求,换句话说,就是用DispatcherHandle来处理forward请求转发。
  • ReactiveLoadBalancerClientFilter:就是用来负载均衡的。当URL有lb时,例如:lb://myservice,它使用springCloud的ReactLoadBalancer去解析服务名为myservice的微服务(将这个myservice解析为实际的主机和端口,并替换当前的URI),找到这个微服务的集群,并负载均衡到某一台,执行服务。
spring:
  cloud:
    gateway:
      routes:
      - id: myRoute
        uri: lb://service
        predicates:
        - Path=/service/**

注意:gateway支持所有的loadbalancer特性。

3.6 Gateway限流

gateway作为网关,与其他网关技术不同的是它能实现限流。
gateway使用的是令牌桶算法实现限流。常见的限流算法有:

  1. 计数器算法:以QPS为100举例,如果1秒内,前200ms请求数量到达了100,后面800ms中的请求都会被拒绝,这种情况称为"突刺现象“;

  2. 漏桶算法:可以解决突刺现象。比如创建一个很大的队列来接收请求,一个较小的线程池来处理请求。但是也有极限情况,当队列满了时, 请求也会拒绝掉。固定生产速率和消费速率。

  3. 令牌桶算法:可以说是漏桶算法的改进。在桶中放令牌,请求获取令牌后才能继续执行。如果桶中无令牌,请求可以选择进行等待或直接拒绝。固定生产令牌速率,但是消费速率看桶中令牌情况。等同于生产熟虑=消费速率。

在项目中使用gateway网关做限流一般结合的redis,使用令牌桶算法。
或者结合sential, 用滑动窗口的计数器的方式限流。

4 Nacos 注册中心和配置中心

Nacos是阿里巴巴开源的一个对微服务架构中服务发现,配置管理和服务管理平台。

4.1 Nacos功能特性

  • 服务发现与健康监测
  • 动态配置管理
  • 动态DNS服务
  • 服务和元数据管理(管理平台的角度,nacos也有一个ui页面,可以看到注册的服务以及实例信息(元数据信息等),动态的服务权重调整,动态服务优雅下线,都可以去做)

4.2 注册中心原理

在这里插入图片描述
nacos服务之间通过distro协议互相同步数据。微服务只需要和其中一个nacos集群中一个节点连接即可。
在这里插入图片描述
nacos心跳机制
在这里插入图片描述
nacos和client之间采取推拉结合的交互方式:

  • client可以通过定时任务每隔10s向nacos发起查询请求,如果服务列表改变nacos就会返回新列表.
  • 本地服务实例发生变化时(即server实例注册成功或者心跳停止断开链接),nacos会主动通过UDP协议推送到client,udp协议非常快,不需要保持长连接。

4.3 心跳机制

服务的健康检查分为两种模式:

客户端上报模式:客户端通过心跳上报的方式告知nacos 注册中心健康状态(默认心跳间隔5s,nacos将超过超过15s未收到心跳的实例设置为不健康,超过30s将实例删除)
服务端主动检测:nacos主动检查客户端的健康状态(默认时间间隔20s,健康检查失败后会设置为不健康,不会立即删除)。

nacos 目前的instance有一个ephemeral字段属性,该字段表示实例是否是临时实例还是持久化实例。如果是临时实例则不会在nacos中持久化,需要通过心跳上报,如果一段时间没有上报心跳,则会被nacos服务端删除。删除后如果又重新开始上报,则会重新实例注册。而持久化实例会被nacos服务端持久化,此时即使注册实例的进程不存在,这个实例也不会删除,只会将健康状态设置成不健康。

这里就涉及到了nacos的AP和CP模式 ,默认是AP,即nacos的client的节点注册时ephemeral=true,那么nacos集群中这个client节点就是AP,采用的是distro 协议,而ephemeral=false时就是CP采用的是raft协议实现。

4.4 自我保护机制

nacos也有自我保护机制(当前健康实例数/当前服务总实例数),值为0-1之间的浮点类型。正常情况下nacos 只会健康的实例。单在高并发场景,如果只返回健康实例的话,流量洪峰到来可能直接打垮剩下的健康实例,产生雪崩效应。

保护阈值存在的意义在于当服务A健康实例数/总实例数 < 保护阈值时,Nacos会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者,消费者可能访问到不健康的实例,请求失败,但这样远比造成雪崩要好。牺牲了请求,保证了整个系统的可用。

4.5 动态配置更新原理

Nacos采用的是长轮询的方式:

  1. 客户端发起一次轮询请求到服务器端,当服务器端的配置没有任何变更的时候,这个连接会一直打开,直到服务端有配置变更或者连接超时之后才返回。
  2. 再通过Spring的ApplicationContext.publishEvent() 发布事件的方式去刷新容器。

其实主要是靠Nacos—config包下的NacosContextRefresherNacosConfigManager这两个核心。
NacosContextRefresher主要是做桥梁的作用,通过ApplicationContext获取上下文信息,通过ApplicationListener来通知事件的发布更新Spring容器。
NacosConfigManager作为配置的启动,创建长轮询的定时任务,定时执行任务获取更新的配置。
在这里插入图片描述

  1. 客户端的 pull request(查询请求)会被Nacos Server 端放入队列中。
  2. Nacos配置更新或者超时了,则返回结果。

5 Sentinal服务限流降级

5.1 流控规则

在这里插入图片描述

  • 资源名:唯一名称,默认请求路径

  • 针对来源:Sentinel可以针对调用者进行限流,填写微服务名,指定对哪个微服务进行限流 ,默认default(不区分来源,全部限制)

  • 阈值类型/单机阈值:
    ○ QPS(每秒钟的请求数量):当调用该接口的QPS达到了阈值的时候,进行限流;
    ○ 线程数:当调用该接口的线程数达到阈值时,进行限流

  • 是否集群:不需要集群

  • 流控模式:
    ○直接:接口达到限流条件时,直接限流
    ○关联:当关联的资源达到阈值时,就限流自己
    ○链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值,就可以限流)[api级别的针对来源]

  • 流控效果
    ○快速失败:直接失败,就异常
    ○Warm Up:根据codeFactor(冷加载因子,默认为3)的值,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值

注意:针对url制定的流控规则会走系统默认的规则,而针对SentinelResource中的资源value制定的流控规则会走自定义的blockHandler方法。

5.2 降级规则

在这里插入图片描述

  • RT:平均响应时间(DEGRADE_GRADE_RT):当1s内持续进入5个请求,对应时刻的平均响应时间(秒级)均超过阈值(以ms为单位),那么接下来的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会出现熔断(抛出DegradeException)。注意Sentinel默认统计的RT上限为4900ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项-Dcsp.sentinel.statistic.max.rt=xxx来配置。

  • 异常比例:异常比例(DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量>=5,并且每秒异常总数占通过量的比值超过阈值(DegradeRule中的count)之后,资源进入降级状态,即在接下来的时间窗口(DegradeRule中的timeWindow,以s为单位)之内,对这个方法的调用都会自动地返回。异常比例的阈值范围是[0.0,1.0],代表0%~100%。

  • 异常数
    异常数(DEGRADE_GRADE_EXCEPTION_COUNT):当资源近1分钟的异常数目超过阈值之后,会进行熔断。注意通过时间窗口是分钟级别的,若timeWindow小于60s,则结束熔断状态后仍可能再进入熔断状态。

5.3 热点规则

在这里插入图片描述

5.4 相关注解

  • @SentinelResource(value=“”, blockHandlerClass=“”, blockHandler=“”) :定义资源

6.Ribbon负载均衡

6.1 负载均衡算法

负载算法描述
RandomRule随机
RoundRobinRule轮询
AvailabilityFilteringRule先过滤掉由于多次访问故障的服务,以及并发连接数超过阈值的服务,然后对剩下的服务按照轮询策略进行访问;
WeightedResponseTimeRule根据平均响应时间计算所有服务的权重,响应时间越快服务权重就越大被选中的概率即越高,如果服务刚启动时统计信息不足,则使用RoundRobinRule策略,待统计信息足够会切换到该WeightedResponseTimeRule策略;
RetryRule先按照RoundRobinRule策略分发,如果分发到的服务不能访问,则在指定时间内进行重试,分发其他可用的服务;
BestAvailableRule先过滤掉由于多次访问故障的服务,然后选择一个并发量最小的服务;
ZoneAvoidanceRule综合判断服务节点所在区域的性能和服务节点的可用性,来决定选择哪个服务;

6.2 Ribbon和Nginx区别

  • Nginx是服务器负载均衡,客户端所有请求都会交给nginx,然后由nginx实现转发请求。即负载均衡是由服务端实现描述
    的。
  • Ribbon本地(客户端)负载均衡,在调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术。Spring Cloud对Ribbon做了二次封装,可以让我们使用RestTemplate的服务请求,自动转换成客户端负载均衡的服务调用。

7 Feign和OpenFeign

  • Feign是Spring Cloud组件中的一个轻量级 RESTful 的 HTTP 服务客户端 Feign 内置了 Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。

  • OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@Feignclient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

7.1 Feign使用

客户端接口类

/**
 * POST请求
 */
@Body("{person}")
@RequestLine("POST /feign/provider/body1")
@Headers({"content-type:application/json"})
String consumer1(@Param("person") User person);

/**
 * GET请求
 */
@RequestLine("GET /feign/provider/param3?names={names}")
public String testCollection(@Param("names") List<String> names);

7.2 OpenFeign使用

  • 客户端配置
feign:
  hystrix:
    enabled: true
  client:
    refresh-enabled: true
  httpclient:
    connection-timeout: 500
  • 启动类加上@EnableFeignClients注解

  • 定义客户端接口类

@FeignClient(value = "provider",fallbackFactory = TestFallbackFactory.class)
public interface OpenFeignService {
        @RequestMapping("/open")
        @LoadBalanced
        public String getName();
}

8 链路追踪

8.1 Spring Cloud Sleuth

作为微服务间的链路追踪解决方案,需要做到3点:

  • 数据收集
  • 数据存储
  • 数据展示

大型分布式系统的追踪数据分为实时数据和全量数据:

  • 实时数据主要用于问题排查。
  • 全量数据主要用于性能优化。
    在这里插入图片描述
    生成数据的服务追踪单元,由客户端发起请求,在请求到达服务边界上,到被调用服务返回客户端响应为止,这个过程被称为一个Trace,服务间的调用通过一个TraceId串联起来,调用链路中经过的服务被称为Span,最终,一个服务追踪单元的基本组成就是由一个Trace和多个Span顺序组成的结构。把这些带有Span的Trace记录下来,就可以描绘出一幅系统的服务拓扑图。附带上Span中的响应时间,以及请求成功与否等信息,就可以在发生问题的时候,找到异常的服务。
    在这里插入图片描述

Spring Cloud Sleuth 为服务间调用提供了链路追踪,通过查看整个调用链路,可以方便的知道一个服务请求经过了哪些服务,每个服务处理花费了多长以及定位调用异常点,除此以外,通过Sleuth,还可以确定:

  • 耗时分析: 通过 Sleuth 可以很方便的了解到每个采样请求的耗时,从而分析出哪些服务调用比较耗时;
  • 可视化错误: 对于程序未捕捉的异常,可以通过集成 Zipkin 服务界面上看到;
  • 链路优化: 对于调用比较频繁的服务,可以针对这些服务实施一些优化措施。

Sleuth需要与Zipkin做集成,将信息发送到 Zipkin,利用 Zipkin 的存储来存储信息,利用 Zipkin UI 来展示数据。

8.2 Zipkin

Zipkin 是 Twitter 的一个开源项目,它基于 Google Dapper 实现,它致力于收集服务的定时数据,以解决微服务架构中的延迟问题,包括数据的收集存储查找展现

我们通过在各个服务上做链路追踪后,将数据传输到Zipkin做存储,Zipkin提供了友好的UI通过使用TraceId或者服务名称等作为查询条件查询调用链路和服务间的依赖关系。

在这里插入图片描述
如上图为zipkin与服务集群集成的基础架构图,图中主要由4个组件构成:

  • Collector:收集器组件,它主要用于处理从外部系统发送过来的跟踪信息,将这些信息转换为 Zipkin 内部处理的 Span 格式,以支持后续的存储、分析、展示等功能。
  • Storage:存储组件,它主要对处理收集器接收到的跟踪信息,默认会将这些信息存储在内存中,也可以修改此存储策略,通过使用其他存储组件将跟踪信息存储到数据库中,如 Mysql 和 Elasticsearch 等。
  • RESTful API:API 组件,它主要用来提供外部访问接口。比如给客户端展示跟踪信息,或是外接系统访问以实现监控等。
  • Web UI:UI 组件,基于 API 组件实现的上层应用。通过 UI 组件用户可以方便而有直观地查询和分析跟踪信息。

9 SpringBoot Admin

Spring Boot Admin 是一个开源的社区项目,用于管理和监控 Spring Boot 应用程序。应用程序可以通过 http 的方式,或 Spring Cloud 服务发现机制注册到 SBA 中,然后就可以实现对 Spring Boot 项目的可视化管理.

Spring Boot Admin 可以监控 Spring Boot 单机或集群项目,它提供详细的健康 (Health)信息内存信息JVM 系统和环境属性垃圾回收信息日志设置和查看定时任务查看Spring Boot 缓存查看和管理等功能

在这里插入图片描述

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Cloud Alibaba是一个基于Spring Cloud的微服务框架。它提供了一系列的分布式开发工具和服务,帮助开发人员构建和管理微服务架构。关于Spring Cloud Alibaba的更多知识,你可以参考Spring Cloud中文网和Spring Cloud Alibaba官网提供的文档。这些文档会详细介绍Spring Cloud Alibaba的使用方法和实现原理。同时,你也可以关注这些网站,与大家一起学习Spring Cloud Alibaba的最新动态和最佳实践。另外,如果你想了解Spring Cloud AlibabaSpring CloudSpring Boot的版本关系,可以参考相关的发布文档。这些文档中会列出Spring Cloud Alibaba发布的版本以及对应的适配Spring CloudSpring Boot的版本信息。希望这些资源能够帮助你更好地理解和使用Spring Cloud Alibaba。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [第一章 :微服务的架构介绍发展](https://blog.csdn.net/qq_42897427/article/details/104921630)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [Spring Cloud Alibaba【什么是Nacos、Nacos Server下载安装 、Docker安装Nacos Server服务、微服务聚合父...](https://blog.csdn.net/m0_58719994/article/details/128264869)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值