springCloud入门2

一、服务负载均衡

1.为什么需要服务负载均衡?

为了提供并发量,有时同一个服务提供者可以部署多个(商品服务)。这个客户端在调用时要根据一定的负责均衡策略完成负载调用。

2.服务提供者集群

实际生产就是同一个服务多部署几台服务器,开发时就是用端口来区分。
第一步,拷贝一份服务提供者
第二步,修改主类,改名
第三步,修改端口
第四步,重启注册中心,看到两个服务提供者即可

3.服务消费者常见负载均衡实现技术

ribbon、feign

4.ribbon负载均衡调用

4.1.什么是ribbon?

Ribbon是一个客户端负载均衡器
Ribbon是Netflix发布的云中间层服务开源项目,主要功能是提供客户端负载均衡算法。Ribbon客户端组件提供一系列完善的配置项,如,连接超时,重试等。简单的说,Ribbon是一个客户端负载均衡器,我们可以在配置文件中列出load Balancer后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器,我们也很容易使用Ribbon实现自定义的负载均衡算法。

Ribbon是一个客户端负载均衡器,它可以按照一定规则来完成多态服务器负载均衡调用,这些规则还支持自定义

4.2.集成原理

在这里插入图片描述

4.3.负载均衡测试

第一步,创建项目
第二步,导入jar包

    <dependency>
        <groupId>cn.itcast</groupId>
        <artifactId>product-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--springboot支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!--eureka客户端支持 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--客户端负载均衡实现 ribbon-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    </dependency>

第三步,配置yml

server:
  port: 8006
spring:
  application:
    name: PRODUCT-CONSUMERS
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8003/eureka,http://localhost:8004/eureka #告诉服务提供者要把服务注册到哪儿
  instance:
    prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
    ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找

第四步,代码

@Configuration
public class CfgBean {
    @Bean
    @LoadBalanced//开启负载均衡
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

@RestController
@RequestMapping("/productConsumers")
public class ProductConsumersController {
    @Autowired
    private RestTemplate restTemplate;
    private final String URL_PREFIX="http://PRODUCT-SERVICE/productService/";
    @RequestMapping("/{id}")
    public Product getUser(@PathVariable("id")Long id){
        String url = URL_PREFIX+id;
        return restTemplate.getForObject(url,Product.class );
    }
}

第五步,测试
访问http://localhost:8006/productConsumers/1
不断刷新会出现轮询效果

4.4.负载均衡策略

ribbon通过服务名获取到服务列表后,要根据一定规则来选择一个服务实例来完成调用.这个规则就叫负载均衡策略.

  1. IRule
    通过配置不同IRule的子类,可以选择不同负载均衡策略,也就是从服务列表以特定策略选择一个服务来完成调用。当然也可以自定义。所以负载均衡策略可以分为内置和自定义。
  2. 内置负载均衡策略
内置负载均衡规则类规则描述
RoundRobinRule(默认)简单轮询服务列表来选择服务器。它是Ribbon默认的负载均衡规则。
AvailabilityFilteringRule对以下两种服务器进行忽略:(1)在默认情况下,这台服务器如果3次连接失败,这台服务器就会被设置为“短路”状态。短路状态将持续30秒,如果再次连接失败,短路的持续时间就会几何级地增加。注意:可以通过修改配置loadbalancer.<clientName>.connectionFailureCountThreshold来修改连接失败多少次之后被设置为短路状态。默认是3次。(2)并发数过高的服务器。如果一个服务器的并发连接数过高,配置了AvailabilityFilteringRule规则的客户端也会将其忽略。并发连接数的上线,可以由客户端的<clientName>.<clientConfigNameSpace>.ActiveConnectionsLimit属性进行配置。
WeightedResponseTimeRule为每一个服务器赋予一个权重值。服务器响应时间越长,这个服务器的权重就越小。这个规则会随机选择服务器,这个权重值会影响服务器的选择。
ZoneAvoidanceRule以区域可用的服务器为基础进行服务器的选择。使用Zone对服务器进行分类,这个Zone可以理解为一个机房、一个机架等。
BestAvailableRule忽略哪些短路的服务器,并选择并发数较低的服务器。
RandomRule随机选择一个可用的服务器。
Retry重试机制的选择逻辑
@Configuration
public class CfgBean {
    @Bean
    @LoadBalanced//开启负载均衡
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }

    @Bean//随机负载均衡策略
    public IRule myRule(){
        return new RandomRule();
    }
}

5.Feign负载均衡

5.1.什么是feign?

Feign是一个声明式的Web Service客户端,它的目的就是让Web Service调用更加简单。Feign提供了HTTP请求的模板,通过编写简单的接口和插入注解,就可以定义好HTTP请求的参数、格式、地址等信息。而Feign则会完全代理HTTP请求,我们只需要像调用方法一样调用它就可以完成服务请求及相关处理。Feign整合了Ribbon和Hystrix(关于Hystrix我们后面再讲),可以让我们不再需要显式地使用这两个组件。

总的来说,Feign具有如下特性:
可插拔的注解支持,包括Feign注解和JAX-RS注解;
支持可插拔的HTTP编码器和解码器;
支持Hystrix和它的Fallback;
支持Ribbon的负载均衡;
支持HTTP请求和响应的压缩。
这看起来有点像我们springmvc模式的Controller层的RequestMapping映射。这种模式是我们非常喜欢的。Feign是用@FeignClient来映射服务的。

Feign是以接口方式进行调用,而不是通过RestTemplate来调用,feign底层还是ribbon,它进行了封装,调用更加方便

5.2.操作

第一步,创建maven项目(模块)
第二步,导入jar包

    <dependency>
        <groupId>cn.itcast</groupId>
        <artifactId>product-common</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    <!--springboot支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!--eureka客户端支持 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--feign的支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
</dependencies>

第三步,配置yml

server:
  port: 8007
spring:
  application:
    name: PRODUCT-CONSUMERS
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8003/eureka,http://localhost:8004/eureka #告诉服务提供者要把服务注册到哪儿
  instance:
    prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
    ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找

第四步,入口类

@SpringBootApplication
@EnableEurekaClient
//@EnableFeignClients(basePackages = "cn.itsource.microservice") //不在当前包下面
@EnableFeignClients//client接口在当前包下面
public class ProductConsumers8007Application {
    public static void main(String[] args) {
        SpringApplication.run(ProductConsumers8007Application.class);
    }
}

第五步,测试,写接口
方案1:就在服务消费者里面开一个包来写-----采纳
方案2:公共代码user-client(不推荐)给服务消费者依赖

@FeignClient(value="PRODUCT-SERVICE")
@RequestMapping("/productService")
public interface ProductClients {
    @GetMapping("/{id}")
    public Product getProduct(@PathVariable(name="id")Long id);
}

注意:要和服务提供者里面访问地址和参数等保持一致。
注意:client只是服务消费者使用,不是属于公共,不应该放到interface.可以直接放到服务消费者里面或者另开一个模块但是只给服务消费依赖。

二、Hystrix断路器

1.为什么需要 Hystrix断路器?

1.1.存在问题

在理想状态下,一个应用依赖的服务都是健康可用的,我们可以正常的处理所有的请求。
雪崩现象:由于某些服务出问题,导致很多问题的现象就叫雪崩.

1.2.解决方案

对依赖做隔离,Hystrix就是处理依赖隔离的框架,同时也是可以帮我们做依赖服务的治理和监控.。

Hystrix是保证微服务群健壮框架,做了隔离,熔断,降级等操作.最终达到不会由于某一个服务出问题而导致雪崩现象,让整体群死掉.

2.Hystrix简介

Hystrix是国外知名的视频网站Netflix所开源的非常流行的高可用架构框架。Hystrix能够完美的解决分布式系统架构中打造高可用服务面临的一系列技术难题。

Hystrix “豪猪”,具有自我保护的能力。hystrix 通过如下机制来解决雪崩效应问题。

资源隔离(限流):包括线程池隔离和信号量隔离,限制调用分布式服务的资源使用,某一个调用的服务出现问题不会影响其他服务调用。

熔断:当失败率达到阀值自动触发降级(如因网络故障/超时造成的失败率高),熔断器触发的快速失败会进行快速恢复。

降级机制:超时降级、资源不足时(线程或信号量)降级,降级后可以配合降级接口返回托底数据。

缓存:提供了请求缓存、请求合并实现。

2.1.分析

隔离:每次调用都只影响自己
熔断:调用同一个服务多个失败,后面再来调用时,就暂时不调用,隔一段时间再进行调用,如果能调用,就恢复。
降级:要给我一个保底处理。 --一点反应都没有,给他报一个错

2.2.资源隔离&限流

在这里插入图片描述

2.3.服务熔断

正常情况下,断路器处于关闭状态(Closed),
如果调用持续出错或者超时,电路被打开进入熔断状态(Open),后续一段时间内的所有调用都会被拒绝(Fail Fast),
一段时间以后,保护器会尝试进入半熔断状态(Half-Open),允许少量请求进来尝试,
如果调用仍然失败,则回到熔断状态
如果调用成功,则回到电路闭合状态;
熔断的参数配置

Hystrix提供了如下的几个关键参数,来对一个熔断器进行配置:

circuitBreaker.requestVolumeThreshold //滑动窗口的大小,默认为20
circuitBreaker.sleepWindowInMilliseconds //过多长时间,熔断器再次检测是否开启,默认为5000,即5s钟
circuitBreaker.errorThresholdPercentage //错误率,默认50%

3个参数放在一起,所表达的意思就是:
每当20个请求中,有50%失败时,熔断器就会打开,此时再调用此服务,将会直接返回失败,不再调远程服务。直到5s钟之后,重新检测该触发条件,判断是否把熔断器关闭,或者继续打开。

2.4.服务降级

有了熔断,就得有降级。所谓降级,就是当某个服务熔断之后,服务器将不再被调用,此时客户端可以自己准备一个本地的fallback回调,返回一个缺省值。
这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强,当然这也要看适合的业务场景。

3.Hystrix实现(服务提供者实现)

第一步,拷贝一份服务提供者
第二步,导入jar包

 <!--断路器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

第三步,代码

主类:
@SpringBootApplication
@EnableEurekaClient
@EnableHystrix//开启hystrix
public class ProductService8008Application {
    public static void main(String[] args) {
        SpringApplication.run(ProductService8008Application.class,args);
    }
}

服务代码:
@RestController
@RequestMapping("/productService")
public class ProductServiceController {
    @GetMapping("/{id}")
    @HystrixCommand(fallbackMethod = "myFallback")
    public Product getProduct(@PathVariable(name="id")Long id){
        if(id.intValue()==2){
            throw new RuntimeException("报错了,本服务已被熔断");
        }
        return new Product(id,"hello spring cloud8005");
    }
    private Product myFallback(Long id){
        return new Product(id,"抱歉,本产品不存在");
    }
}

第四步,测试
注意:
1 熔断是在服务提供方做
2 调用时不能使用feign调用(有自己熔断机制),要用ribbon调用

4.Feign实现

4.1.为什么要用feign?

问题:
     每个方法都要加回调并且耦合
解决方案:
     可以使用spring面向切面编程,为feign的接口创建一个代理对象,完成对服务调用,当发现熔断后就调用同名托底方法.

4.2.feign实现(服务消费者实现)

第一步,拷贝一份服务消费者
第二步,导入jar包
第三步,配置yml

server:
  port: 8009
spring:
  application:
    name: PRODUCT-CONSUMERS
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8003/eureka,http://localhost:8004/eureka #告诉服务提供者要把服务注册到哪儿
  instance:
    prefer-ip-address: true # 当调用getHostname获取实例的hostname时,返回ip而不是host名称
    ip-address: 127.0.0.1 # 指定自己的ip信息,不指定的话会自己寻找
feign:
  hystrix:
    enabled: true #开启熔断支持
  client:
    config:
      remote-service:           #服务名,填写default为所有服务
        connectTimeout: 3000
        readTimeout: 3000
hystrix:
  command:
    default:
      execution:
        isolation:
          thread: 3000

第四步,入口类
如果接口包结构不按照规范来,额外加入扫描包.@ComponentScan
第五步,代码
定义托底方法的工厂类,指定给对于client,动态代理类就是实现如果调用失败,会调用托底工厂里面的同名方法。

//ProductClients:表示对该接口进行托底处理
@Component
public class ProductClientsFallbackFactory  implements FallbackFactory<ProductClients> {

    @Override
    public ProductClients create(Throwable throwable) {
        return new ProductClients() {
            @Override
            public Product getProduct(Long id) {
                return new Product(id,"有异常,被feign熔断了");
            }
        };
    }
}

//调用服务名字
@FeignClient(value="PRODUCT-SERVICE",fallbackFactory = ProductClientsFallbackFactory.class)
@RequestMapping("/productService")
public interface ProductClients {
    @GetMapping("/{id}")
    public Product getProduct(@PathVariable(name="id")Long id);
}

第六步,测试

三、zuul路由网关

1.概述

1.1.是什么?

Zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
Zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。Zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门,也要注册入Eureka.
在这里插入图片描述

1.2.为什么需要?

微服务架构体系中,通常一个业务系统会有很多的微服务,比如:OrderService、ProductService、UserService…,为了让调用更简单,一般会在这些服务前端再封装一层,Zuul为我们外部调用提供了统一的入口,由于zuul注册达到Eureka当中,就能获取Eureka里面管理的服务。

1.3.官网

https://github.com/Netflix/zuul

2.基本操作

2.1.创建一个maven项目(模块)

2.2.导入jar包

        <!--springboot支持-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--eureka客户端支持 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

2.3.修改yml

server:
  port: 8010
spring:
  application:
    name: ZUUL-GATEWAY
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8003/eureka,http://localhost:8004/eureka #告诉服务提供者要把服务注册到哪儿
  instance:
    instance-id: gateway-9527.com
    prefer-ip-address: false

2.4.修改入口类

@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy//开启zuul
public class ProductService8010Application {
    public static void main(String[] args) {
        SpringApplication.run(ProductService8010Application.class,args);
    }
}

2.5.启动测试

直接访问地址:http://localhost:8001/productService/1
路由访问地址:http://localhost:8010/product-service/productService/1

3.路由访问映射规则

  1. 安全加固:不用服务名,映射路径
  2. 忽略原来服务名访问:原来模式不可以访问
  3. 加上统一前缀
zuul:
  routes:
    productService.serviceId: product-service # 服务名
    productService.path: /product/** # 把myUser打头的所有请求都转发给user-provider
  ignored-services: "*" #所有服务都不允许以服务名来访问
  prefix: "/services" #加一个统一前缀

4.过滤器

Zuul作为网关的其中一个重要功能,就是实现请求的鉴权。而这个动作我们往往是通过Zuul提供的过滤器来实现的。

4.1.ZuulFilter

ZuulFilter是过滤器的顶级父类。

  • shouldFilter:返回一个Boolean值,判断该过滤器是否需要执行。返回true执行,返回false不执行。
  • run:过滤器的具体业务逻辑。
  • filterType:返回字符串,代表过滤器的类型。包含以下4种:
    • pre:请求在被路由之前执行
    • routing:在路由请求时调用
    • post:在routing和errror过滤器之后调用
    • error:处理请求时发生错误调用
  • filterOrder:通过返回的int值来定义过滤器的执行顺序,数字越小优先级越高。

4.2.过滤器执行周期

在这里插入图片描述- 正常流程:

  • 请求到达首先会经过pre类型过滤器,而后到达routing类型,进行路由,请求就到达真正的服务提供者,执行请求,返回结果后,会到达post过滤器。而后返回响应。
  • 异常流程:
    • 整个过程中,pre或者routing过滤器出现异常,都会直接进入error过滤器,再error处理完毕后,会将请求交给POST过滤器,最后返回给用户。
    • 如果是error过滤器自己出现异常,最终也会进入POST过滤器,而后返回。
    • 如果是POST过滤器出现异常,会跳转到error过滤器,但是与pre和routing不同的时,请求不会再到达POST过滤器了。

4.3.使用场景

  • 请求鉴权:一般放在pre类型,如果发现没有访问权限,直接就拦截了
  • 异常处理:一般会在error类型和post类型过滤器中结合来处理。
  • 服务调用时长统计:pre和post结合使用。

4.4.自定义过滤器

@Component
public class LogginFilter extends ZuulFilter {
@Override
//过滤器类型
    public String filterType() {
        return "pre";
    }
@Override
    public int filterOrder() {
        return 1;
    }
    @Override
//返回值为true,代表过滤器生效
    public boolean shouldFilter() {
        return true;
    }
@Override
    public Object run() throws ZuulException {
        //获取Zuul提供的请求上下文对象
        RequestContext currentContext = RequestContext.getCurrentContext();
        //从上下文中获取request对象
        HttpServletRequest request = currentContext.getRequest();
        //从请求中获取token
        String token = request.getHeader("token");
        //判断
        if(token==null ||token.equals("")){
            // 没有token,登录校验失败,拦截
            currentContext.setSendZuulResponse(false);
            // 返回401状态码。也可以考虑重定向到登录页。
            currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
        }
        return null;
    }
}

测试 postman

5.负载均衡与熔断

Zuul中默认就已经集成了Ribbon负载均衡和Hystix熔断机制。但是所有的超时策略都是走的默认值,比如熔断超时时间只有1S,很容易就触发了。因此建议我们手动进行配置:

zuul:
 retryable: true
  ribbon:
    ConnectTimeout: 250 # 连接超时时间(ms)
    ReadTimeout: 2000 # 通信超时时间(ms)
    OkToRetryOnAllOperations: true # 是否对所有操作重试
    MaxAutoRetriesNextServer: 2 # 同一服务不同实例的重试次数
    MaxAutoRetries: 1 # 同一实例的重试次数
  hystrix:
    command:
      default:
        execution:
          isolation:
            thread:
              timeoutInMillisecond: 3000 # 熔断超时时长:3000ms

四、SpringCloud Config分布式配置中心

1.简介

1.1.是什么?

在分布式系统中,由于服务数量巨多,为了方便服务配置文件统一管理,实时更新,所以需要分布式配置中心组件。在Spring Cloud中,有分布式配置中心组件spring cloud config ,它支持配置服务放在配置服务的内存中(即本地),也支持放在远程Git仓库中。在spring cloud config 组件中,分两个角色,一是config server,二是config client。

1.2.架构

在这里插入图片描述

1.3.做什么

在这里插入图片描述和github集成

2.操作

2.1.服务端配置

第一步,创建maven项目(模块)
第二步,导入jar包

<!--springboot支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!--eureka客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--配置中心支持-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>

第三步,配置yml

server:
  port: 1299
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8003/eureka,http://localhost:8004/eureka # 集群
  instance:
    prefer-ip-address: true
spring:
  application:
    name: spring-cloud-config-server
  cloud:
    config:
      server:
        git:
          uri: https://github.com/hpyao/microservice-config.git
          username: hpyao
          password: Itsource20190329

第四步,启动类

@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer//启动配置服务端
public class ConfigServer1299Application {
    public static void main(String[] args) {
        SpringApplication.run(ConfigServer1299Application.class);
    }
}

第五步,测试
http://localhost:1299/application-user/test
http://localhost:1299/application-user/dev

2.2.客户端配置

第一步,新建项目,读取github的配置文件
第二步,导入jar包

 <!--springboot支持-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
    </dependency>
    <!--eureka客户端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    <!--configclient端-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>

第三步,准备yml
全局bootstrap.yml(级别比application.yml高)

server:
  port: 4599
spring:
  cloud:
    config:
      name: application-user #github上面名称
      profile: dev #环境
      label: master #分支
      uri: http://127.0.0.1:1299 #配置服务器
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8003/eureka,http://localhost:8004/eureka # 集群
  instance:
    prefer-ip-address: true

第四步,入口类

@SpringBootApplication
@EnableEurekaClient
public class ConfigClient4599Application {
    public static void main(String[] args) {
        SpringApplication.run(ConfigClient4599Application.class);
    }
}

第五步,测试
如果读取到就会有application.name,这个名字会显示在Eureka里面,通过Eureka上面名字判断是否读取到了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值