当前位置:   article > 正文

微服务架构与SpringCloud

微服务架构与springcloud

0 微服务

  • 一种架构模式/风格,提倡将单一的应用程序划分为一组小的服务,服务运行在自己的进程,之间相互协作完成最终服务

  • 解决微服务四大问题:服务治理

    1. 服务很多,客户端怎么访问

    2. 服务之间如何通信

    3. 服务怎么管理

    4. 服务出现重大故障时怎么办

  • 设计原则:

    • 单一职责:每个服务独立,有界限的工作,只关注自己的业务,做到高内聚

    • 服务自治:独立开发、测试、构建、部署和运行,与其他服务解耦

    • 轻量级通信:服务之间的调用是轻量级的,并且能够跨平台、跨语言;如RESTFUL风格和利用消息队列通信

    • 粒度进化:服务的粒度随着业务和用户的发展而进化

  • 解决方案:

    • SpringCloud + NetFlix:一站式解决方案

    • SpringCloud + Alibaba:一站式;精简SpringCloud + NetFlix

    • Apache Dubbo + Zookeeper:半自动,需要整合各部分

      • 没有API,需整合第三方

      • Dubbo:RPC通信

      • Zookeeper:注册与发现;下载一个安装包,启动服务器端server即可

      • 借助第三方

  • CAP:对数据的关注

    • consistency-强一致性:并发场景下数据在每个节点/系统中必须一致

    • Availability-可用性:系统对外提供服务必须一直处于可用状态,在任何故障下,客户端都能在合理时间下获得服务端非错误的响应

    • Partiton tolerance-分区容错性:遇到任何网络分区(不同节点分布在不同的子网络中)故障,系统仍能对外提供服务

    • 一个分布式系统最多只能同时满足CP或AP:因为网络总是不可靠的:当网络故障时,不能对A系统和B系统进行数据同步,即不满足P,而AB是可访问的,但它们像单机系统而非分布式;所以P必须满足;当满足P时,需要等待数据同步,在此期间不能让被同步的系统被外部访问,否则数据不一致,因此满足CP;否则为了让系统能被访问,只能牺牲C,此时满足AP

1 SpringCloud

  • 基于SpringBoot的一套微服务解决方案,组件及对应解决方案(基本来自Netfix)包括:

    • 服务开发:SpringBoot

    • 服务注册与发现:Zookeeper,Eureka,Consul,Nacos

    • 服务分布式配置:Spring Config,Nacos

    • 服务负载与调用:Ribbon,OpenFeign/FeignLoadBalancer

    • 服务网关:Zuul,gateway

    • 服务熔断降级/限流:Hystrix,Sentienl

  • Spring + Alibaba:

    • 官网https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.mdhttps://spring.io/projects/spring-cloud-alibaba

    • 引入依赖(父pom.xml):具体配套的各个组件版本参考官网推荐

      1. <dependency>
      2. <groupId>com.alibaba.cloud</groupId>
      3.    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
      4.    <version>2.1.0.RELEASE</version>
      5.    <type>pom</type>
      6.    <scope>import</scope>
      7. </dependency>
      8. <!-- 老版本:H版
      9. <dependency>
      10. <groupId>org.springframework.cloud</groupId>
      11. <artifactId>spring-cloud-dependencies</artifactId>
      12. <version>Hoxton.SR1</version> <type>pom</type>
      13. <scope>import</scope>
      14. </dependency>
      15. -->

    • 主要功能:

      • 服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway,Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控

      • 服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持

      • 分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新

      • 消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力

      • 分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题

      • 阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据

      • 分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行

      • 阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道

    • 组件:

      • Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定

      • Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台

      • RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务

      • Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架

      • Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案

      • Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据

      • Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时任务调度服务

      • Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道

2 微服务开发过程

2.1 新建父子module

  • 父module:

    1. <groupId>
    2. <artifactId>
    3. <version>
    4. <package>pom</package>
    5. <子moudule列表;在父module上右键,新建子module后此列表自动生成 -->
    6. <module>
    7. <!-- 统一版本管理 -->
    8. <projecties>
    9. <projcet.build.sourceEncoding>UTF-8
    10. <maven.compiler.source>1.8
    11. <maven.compiler.target>1.8
    12. <junit.version>
    13. <log4j.version>
    14. <lombok.version>
    15. <mysql.version>
    16. <druid.version>
    17. <mybatis.spring.boot.version>
    18. </projecties>
    19. <!-- 子模块继承,版本控制 -->
    20. <dependencyManagement>
    21. <dependencies>
    22. <!-- 一依赖列表,每个依赖包含了gav(groupId,artifactId,version) -->
    23. <dependency>...
    24. <dependencies>
    25. <dependencyManagement>
  • 子module:

    • 子module依次向上查找直到denpendencyManagement中的依赖;子项目声明了相同的依赖且未声明版本,则从父module继承而来并引入(父module只声明,不引入);声明了版本则使用自己的而不继承

    • 服务提供者、消费者属于子module

2.2 构建Client

  • 新建N个服务提供者Client-Service Provider和服务消费者Client-Service Consumer的module;子pom引入需要的依赖

  • 专属application.yml配置:

    • 主流配置:基本和单体项目的yml文件配置相同,如端口号,应用名称(此时成为微服务名称),数据源,mybatis配置

    • 集成不同的组件的配置

  • 编写业务:Server一般包含了所有MVC模块,而Client一般做服务的调用,可能仅存在Controller层,配合RestTemplate(@Bean引入)简单粗暴死板调用服务接口

  • 项目重构:将公共部分抽取成module,如Client中调用接口需要传递bean对象参数,而此bean的定义与Server中定义的是一致的;抽取到common的module中,然后用maven的insiclean,install打包部署到本地;在需要的module中引入该依赖包
  • 测试:Maven中可以跳过单元测试,取消勾选lifecycle中的test模块(闪电图标)以减少多个微服务启动的时长;开发环境开启(正式环境不能开启)热部署提高开发效率

3 Eureka

  • 采用CS架构设计,即包含组件Server和Client;Server是服务器,作为服务注册中心Registry提供微服务Client注册,监控各个Client是否正常运行;服务节点信息存储于服务注册表;Client客户端连接Registry实现RPC远程服务调用;注册后的Client默认30秒每次向Server发送心跳,默认3个心跳周期内未检测到节点的心跳后移除该服务节点;注重AP;已停更

  • 自我保护机制:用来实现高可用

    • 默认情况Server在一定时间内未接收Client实例的心跳时(微服务不可用),会注销该实例;但通常这不是Client有问题,而很可能是因为Server与Client之间网络不通或延迟等故障,因此默认3个心跳周期内开启自动保护机制,即保护模式用于Server与Client存在网络分区的场景下的自我保护

    • 保护模式:Server尝试保护服务注册表中的信息,不清除其中的数据即不注销任何微服务

3.1 Server实现

  • 编写module的引入依赖spring-cloud-starter-netflix-eureka-server;编写yml配置;无需业务实现;启动类开启@EnableEurekaServer

  • Server集群:RPC最核心问题是是否高可用(AP);因此搭建多个注册中心实现负载均衡和故障容错;集群Server需要互相注册,相互守望

    1. server:
    2. port: 7001 # 假设端口7001;集群则新建类似module,如7002
    3.  
    4. eureka:
    5.   instance:
    6. hostname: myServer7001.com # 服务器名称;
    7. # 集群时因为module都在一台机器上,所以需要修改post文件,给localhost起多个别名,如myServer7002.com
    8.   client:
    9.   register-with-eureka: false # 是否向Registry注册本服务
    10.   fetch-registry: false # 是否去Registry中获取其他服务的地址
    11.   service-url:
    12. defaultZone: http://${euraka.instance.hostname}:${server.port}/eureka/ # 与本Server交互的地址,即http://myServer:7001/euraka/
    13. # 集群版需要相互注册,互相守望
    14. # defaultZone: http://myServer7002.com/euraka/
    15. server:
    16. enable-self-preservation: false # 关闭默认开启的自我保护机制
    17. eviction-interval-timer-in-ms: 2000 # 多少时间后注销服务
  • 访问http://myServer7001.com:7001或http://localhost:7001,看到Spring Eureka界面;集群版也可这么访问7002,且7001页面的DS Replicas列表显示7002,7002上显示7001

3.2 Client实现

  • 编写module的引入依赖spring-cloud-starter-netflix-eureka-client;编写yml配置;业务类实现;启动类开启@EnableEurekaClient
  • Client集群:编写N个Client服务并注册;但name全部为同一个,即对外暴露的不再是服务地址而是服务名称;测试版可以在代码中使用@Vlaue("${server.port}")获取到服务,在写死的URL中使用它来调用指定的服务;实际开发中肯定不能写死,而是以负载均衡来调用适合的服务;若Consumer用RestTemplate来调用服务,@Bean注入RestTemplate的方法上用@LoadBalance开启负载均衡机制
  1. server:
  2. port: 8001 # 假设端口8001;提供了服务user/get
  3.  
  4. spring:
  5. application:
  6. name: cloud-XXX-service # 服务名称,服务注册与发现的依据
  7. datasource: # 数据源配置
  8.  
  9. eureka:
  10. client:
  11. register-with-eureka: true
  12. fetch-registry: true
  13. service-url:
  14. defaultZone: http://myServer:7001/euraka/ # 服务注册地址
  15. # Server集群版:注册到每一个Server
  16. # defaultZone: http://myServer7001.com:7001/eureka,http://myServer7002.com:7002/eureka
  17.  
  18.   # 可选配置,服务信息完善,显示主机ip等;需要导入web和actuator依赖
  19.   instance:
  20.   instance-id: myServer8001
  21.   prefer-ip-address: true # 访问路径显示ip
  22.   lease-renewal-interval-in-seconds: 30 # 向Server发送心跳的频率,默认30s
  23.   lease-expiration-dutation-in-seconds: 90 # Server收到最后一次心跳后等待下一次心跳的最大时间,默认90s;超时则注销服务
  24.  
  25. mybatis: # 其他配置
  • 服务启动后,可在Spring Eureka界面服务注册列表中看到此微服务节点信息;服务启动顺序为Server,Client-Service Provider,Client-Service Comsumer
  • 服务发现Discovery:在业务代码中,可以使用DiscoveryClient类获取微服务信息列表;启动类上需要@EnableDiscoveryClient
    1.  @Resource
    2.  private DiscoveryClient discoveryClient;
    3.  public Object discovery() {
    4.      List<String> services = discoveryClient.getServices(); // 注意别导错包
    5.      List<ServiceInstance> instances = discoveryClient.getInstances("服务名称");
    6.  }
  • RestTemplate:Spring3.0开始支持的一个HTTP请求工具,提供常见的REST请求方案的模版,如GET、POST、PUT、DELETE请求,及一些通用的请求执行方法,如exchange()、 execute();它继承自 InterceptingHttpAccessor且实现了RestOperations接口(它定义了基本的RESTful操作);请求方法:
    • getForObject():返回ResponseEntity对象,包含响应头,状态码等信息
    • getForEntity()/postForEntity():返回bean对象(json)
    • Post请求多一个postForLocation()

3 Zookeeper

  • 分布式协调工具,可以实现服务注册与发现功能;引入依赖spring-cloud-starter-zookeeper-discovery;Server不用自己编写module,而是直接启动安装好的Zookeeper客户端;启动类上使用@EnableDiscoveryClient;注重CP

  • yml配置:

    1. server:
    2. port: 8001
    3.  
    4. spring:
    5. application:
    6. name: cloud-XXX-service # 服务名称,服务注册与发现的依据
    7. cloud:
    8. zookeeper:
    9. connect-string: ip port # zookeeper服务器/注册中心所在机器IP;集群版为IP port列表
  • 没有自我保护机制;注重CP;服务节点是临时的,重启后serviceId变化

4 Consul

  • Go语言编写的(Eureka和Zookeeper是Java编写的);提供服务注册与发现功能且有Http和DNS两种实现方式(Eureka的接口暴露通过Http,Zookeeper通过客户端);提供多种健康监测;使用key-value形式存储;支持多个数据中心;注重CP
  • 安装Consul客户端,命令consul agent dev开发模式启动做Server
  • 依赖spring-cloud-starter-consul-discovery;启动类上@EnableDiscoveryClient;yml配置:
    1. server:
    2. port: 8001
    3.  
    4. spring:
    5. application:
    6. name: cloud-XXX-service # 服务名称,服务注册与发现的依据
    7. cloud:
    8. consule:
    9. host: localhost
    10. port: 7001
    11. discovery:
    12. service-name: ${spring.application.name}

5 Ribbon

  • Netflix开源项目;一套客户端,负载均衡工具,提供负载均衡算法和服务调用;客户端提供一系列完善的配置项,如连接超时、重试;配置文件罗列所有后的机器,Ribbon帮助自动基于某种规则去连接某个机器

  • LoadBalance:LB,分为进程内式的本地负载均衡,在客户端实现;和Ngnix等也可以实现的集中式负载均衡,在服务器端实现

  • 流程:先从集群的Server中选择负载较少的;再根据指定的策略从该Server的服务注册表中选择合适的服务;策略包括轮询,随机,权重(根据响应时间加权),重试

  • IRule:核心组件,策略算法规则接口RoundRobinRule轮询,RandomRule随机,RetryRule重试以及其他复合算法实现,:

    • 默认先使用轮询,失败后在一定时间内重试

    • 使用规则或替换默认轮询规则:编写配置类并@Bean注入IRule;官方规定不能放在@ComponentScan扫描包及子包下,否则会被所有Ribbon客户端共享,达不到特殊定制的目的;启动类@RibbonClient(name="服务名", configuration=配置类.class)

    • 轮询:请求总数与服务总数求余,余数就是服务的索引;getInstances()获取的就是服务实例集合List< ServiceInstance>

    • 算法原理与源码:

  • 新版本依赖spring-cload-starter-netflix-eureka-client已经包含spring-cloud-starter-netflix-ribbon

6 OpenFeign

  • Feign:轻量级声明式WebService客户端,使编写Web服务客户端变得简单;即改善Ribbon+RestTemplate模式下的不够灵活的RestTemplate对Http请求的封装,用Feign帮助定义和实现依赖的服务的接口并用注解@Feign方式配置

    • 在Consumer模块中用Feign的注解@FeignClient(value="微服务名称")来定义一个接口,调用接口中方法就调用了Provider服务

    • 启动类上@EnableFeignClients激活Feign

  • OpenFeign:SpingCloud在Feign基础上进行封装,使其支持SpringMVC的注解和HttpMessagerConverters;把@GetMapping()等位于Controller层的注解复制到Server层接口的方法上去,以标识去Provider中查找对应调用的方法;Controller开发不变

  • 超时控制:Provider中服务执行时间与客户端调用该服务愿意等待执行结果的时间约定,OpenFeign默认1秒:超时控制在配置文件中配置ribbon-Readtimeout和ConnectTimeout

  • 日志监控:获取服务执行的具体情况;配置文件中配置logging-level,;日志级别有NONE即默认不显示日志,BASIC即仅记录请求方法、URL、响应状态码和执行时间,HEADERS即包含了BASIC显示信息的请求头和响应头信息,FULL即包含了HEADERS的请求体和响应体信息,这些Bean使用@Bean方式注入

7 Hystrix

  • 服务之间互相调用和依赖越来越多;当其中某一个服务出现故障如执行时间很久,其他服务将受到影响如一直等待,导致级联故障或服务雪崩;Hystrix即为处理分布式系统的延迟和容错的开源库,以提高系统高可用AP特性

  • 服务降级fallback

    • 断路器:在某个服务故障时,通过断路器的故障监控,向服务调用者返回一个符合预期的、可处理的备选响应,而不是长时间等待或抛出调用方无法处理的异常;这类似于执行if-else的最后那个兜底else

    • 超时,异常,服务熔断,线程池满等,都会触发服务降级

  • 服务熔断break:达到最大服务访问量后,直接拒绝访问,即最严重的服务降级处理;当监测到异常节点的调用正常后,能够自动恢复链路调用;Hystrix的阈值默认每5秒内20次调用失败则启动熔断机制

  • 服务限流flowlimit:严禁一窝蜂式访问,避免拥挤,采用排队有序进行,如1秒类最多允许有限个访问

  • 构建Hystrix:

    • 针对Provider:业务类方法上启用@HystrixCommand;启动类启动@EnableCircuitBreaker

      • fallbackMethod属性指定兜底方法

      • commandProperties属性指定规则:

        • 简单规则如超时时间;在设置的超时时间内,正常执行方法并返回结果给调用者,超时或异常后执行兜底方法并返回

        • 设置服务熔断相关规则:@HystrixProperties列表,属性值包括:

          1. @HystrixCommand(fallbackMethod = "fallbackMethod, commandProperties = {
          2. // 是否打开断路器
          3. @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
          4. // 请求总数阈值,即最大请求次数:在时间窗内达到了阈值,则开启服务熔断,默认20
          5. @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
          6. // 时间窗口期:断路后该时间内都会拒绝请求,过时后重新尝试接受请求,处于半开状态;即“断路器是否打开需要统计的时间内的请求数和失败数”中的时间
          7. @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
          8. // 错误百分比阈值:时间窗内异常请求占总请求次数的比例,超过时开启熔断
          9. @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
          10. })
          11. public void method() {}
          • 熔断后,再有的请求直接调用服务降级的兜底逻辑

          • 半开状态:熔断一段时间后(默认5秒),尝试让请求进入,若依然失败,则继续熔断,否则慢慢放请求进入;重复判断-熔断-放行

    • 针对Consumer:开发同理;启动类用@EnableHystrix,配置文件配置启用feign-hystrix-enable

  • 重构兜底方法:每个业务方法都对应一个兜底方法和业务方法与兜底方法存在于一个模块中,都是明显不好的设计
    • @DefaultProperties:业务类上使用该注解来开发全局兜底配置方法实现复用,defaultFallback属性指定默认兜底方法,以作用于类中全部方法;如果方法有自定义则覆盖默认的;方法没有@HystrixCommand的即没有启用服务降级
    • 解耦:新建类实现OpenFeign中的业务接口,重写里面的方法即统一为业务方法进行兜底处理;即正常调用时走业务接口中调用的,异常或超时后走子类的
  • HystrixDashboard:仪表盘,Hystrix服务监控图形化,可以监测请求数,失败数等各种信息;同理建立一个module,引入依赖,@EnableHystrixDashboard开启功能;被监控的服务需要引入actuatior依赖;启动后即可看到页面

  • JMeter:高并发压力测试软件,模拟大量并发请求

8 Sentinel

  • 国内resilience4j用的少

9 Gateway

  • 代替Zuul:路由网关提供一种简单有效方式来对API进行路由,且基于Filter链方式提供网关基本功能,如安全、监控/指标、限流;与Zuul相反,采用非阻塞的异步/响应式API,支持链式(函数式编程);SpringCloud Gateway基于WebFlux框架实现,WebFlux底层使用了高性能的Reactor模式通信框架Netty

  • 位置:外部请求进来,负载均衡后,添加一层网关,然后去请求微服务

  • 核心:

    • 路由:构建网关的基本模块,由id和目标url+一系列断言和过滤器组成;断言成功则匹配该路由

    • 断言Predicate:匹配请求中请求头和请求参数等所有内容

    • 过滤Filter:GatewayFilter实例,对被路由或路由后的请求的额外操作

  • 搭建:开发无业务模块的Client并配置:

    1. server:
    2. port: 9001
    3. spring:
    4. application:
    5. name: cloud-XXX-service
    6. cloud:
    7. gateway:
    8.   routes: # 路由,对每一个需要网关的请求
    9.   - id: routeId # 路由id,唯一
    10.   url: http://... # 被路由的网关地址;添加网关就相当于通过代理去访问真实的请求服务,访问此URL变成了访问9001,访问的接口为Predicates列表
    11.   predicates:
    12.   - Path: /user/get/** # 断言路径相匹配
    13.   - After: # 断言在指定的时间之后
    14.   - ... # 其他:cookie匹配,头部匹配...
    15.   filter: # 一般使用配置类自定义过滤器
    16.  
    17.   - id:...
    18.   ...
    19. discovery:
    20.   locator:
    21.   enabled: true # 开启动态路由功能,利用微服务名而不是URL进行路由
    22. # 另一种路由方式是硬编写配置类并注入RouteLocator,它的方法实现是链式

10 Config

  • 每个微服务都需要必要的配置;ConfigServer提供集中式的、动态的配置管理;不同环境不同配置,各微服务向分布式配置中心拉取自己的配置;配置信息以REST接口形式暴露
  • 新建module,引入config-server依赖;配置文件编写:
    1. spring:
    2. applicaiton:
    3. name: cloud-config-center
    4. cloud:
    5. config:
    6. server:
    7. git:
    8. url: git@github.com... # 多个存储在git仓库上的不同环境的配置文件
    9. search-paths:# 文件所在目录
    10. label: master
    11. eureka:
    12. client:
    13. ...
  • application.yml是应用级配置,bootstrap.yml是系统级配置,优先级更高
  • @RefreshScope用于Controller上,动态刷新

11 Bus

  • 消息总线

12 Stream

  • 消息驱动

13 Sleuth

  • 链路追踪

14 Nacos

  • 替代Eureka做服务注册中心;替代ConfigServer做服务配置中心;自动支持负载均衡(nacos-discovery包下可看到集成了netflix-ribbon);支持AP和CP的切换

  • 安装:官网下载后,直接启动;验证安装是否成功:访问localhost:8848/nacos,账号密码默认nacos;项目总依赖spring-cloud-alibaba-dependencies

  • 服务管理

    • 编写module,引入nacos-discovery依赖

    • yml配置:nacos注册地址为服务发现中心即localhost:8848,可省略服务消费者配置消费访问的服务提供者名称

    • 启动类配置;业务编写;。。。最终启动此服务,在服务管理-服务列表中可看到此服务

    • 详细参看官网:https://spring.io/projects/spring-cloud-alibaba,选择某个版本后的Reference Doc即查看文档

  • 配置管理:

    • 编写module;编写bootstrap.yml,如配置与discovery同级的config,其下的server-addr为配置中心地址,file-extension指定配置格式,一般都为yaml即git上的配置文件;编写application.yml,如spring-profile-active激活不同的环境

    • nacos界面配置列表配置服务;参考官网

  • 集群管理

15 Seata

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/blog/article/detail/57427
推荐阅读
相关标签
  

闽ICP备14008679号