一、微服务概述

1.1、 什么是微服务?

微服务是一种架构模式,或者说是一种架构风格,它把一个项目拆分成独立的多个服务,并且多个服务是可以独立运行的,而每个服务都会占用线程。

分布式架构会遇到的四个核心问题

  1. 这么多服务,客户端该如何去访问
  2. 这么多服务,服务之间如何进行通信
  3. 这么多服务,如何管理
  4. 服务挂了,该怎么办

解决方案:SpringCloud,是一套生态,就是来解决以上分布式架构的4个问题

1、Spring Clould NetFlix, 出来了一套解决方案!一站式解决

  1. Api网关, zuul组件
  2. Feign --> HttpClient --> Http的通信方式,同步并阻塞
  3. 服务注册与发现, Eureka
  4. 熔断机制, Hystrix

2、Apache Dubbo zookeeper, 第二套解决系统

  1. Api : 没有! 要么找第三方组件,要么自己实现
  2. Dubbo是一个高性能的基于Java实现的 RPC通信框架
  3. 服务注册与发现 zookeeper

3、SpringCloud Alibaba 一站式解决方案!

万变不离其宗,一通百通!

  1. API网关,服务路由
  2. HTTP, RPC框架,异步调用
  3. 服务注册与发现,高可用
  4. 熔断机制,服务降级

1.2、微服务优缺点

优点:

  • 单一职责原则;
  • 每个服务足够内聚,足够小,代码容易理解,这样能聚焦一个指定的业务功能或业务需求;
  • 开发简单,开发效率高,一个服务可能就是专一的只干一件事;
  • 微服务能够被小团队单独开发,这个团队只需2-5个开发人员组成;
  • 微服务是松耦合的,是有功能意义的服务,无论是在开发阶段或部署阶段都是独立的;
  • 微服务能使用不同的语言开发;
  • 易于和第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如jenkins,Hudson,bamboo;
  • 微服务易于被一个开发人员理解,修改和维护,这样小团队能够更关注自己的工作成果,无需通过合作才能体现价值;
  • 微服务允许利用和融合最新技术;
  • 微服务只是业务逻辑的代码,不会和HTML,CSS,或其他的界面混合;
  • 每个微服务都有自己的存储能力,可以有自己的数据库,也可以有统一的数据库;

缺点:

  • 开发人员要处理分布式系统的复杂性;
  • 多服务运维难度,随着服务的增加,运维的压力也在增大;
  • 系统部署依赖问题;
  • 服务间通信成本问题;
  • 数据一致性问题;
  • 系统集成测试问题;
  • 性能和监控问题;

1.3、微服务技术栈有那些?

微服务技术条目 落地技术
服务开发 SpringBoot、Spring、SpringMVC等
服务配置与管理 Netfix公司的Archaius、阿里的Diamond等
服务注册与发现 Eureka、Consul、Zookeeper等
服务调用 Rest、PRC、gRPC
服务熔断器 Hystrix、Envoy等
负载均衡 Ribbon、Nginx等
服务接口调用(客户端调用服务的简化工具) Fegin等
消息队列 Kafka、RabbitMQ、ActiveMQ等
服务配置中心管理 SpringCloudConfig、Chef等
服务路由(API网关) Zuul等
服务监控 Zabbix、Nagios、Metrics、Specatator等
全链路追踪 Zipkin、Brave、Dapper等
数据流操作开发包 SpringCloud Stream(封装与Redis,Rabbit,Kafka等发送接收消息)
时间消息总栈 SpringCloud Bus
服务部署 Docker、OpenStack、Kubernetes等

2、 SpringCloud Rest学习环境搭建

2.1、介绍

我们会使用一个Dept部门模块做一个微服务通用案例

Consumer消费者(Client)通过REST调用Provider提供者(Server)提供的服务。

一个父工程带着多个Moudule子模块

MicroServiceCloud父工程(Project)下初次带着3个子模块(Module)

  • springcloud-api 【封装的整体entity/接口/公共配置等】
  • springcloud-consumer-deptt-80【服务消费者】
  • springcloud-provider-dept-8001【服务提供者】

2.2、创建父项目

  • 新建父工程项目springcloud,切记Packageing是pom模式
  • 主要是定义POM文件,将后续各个子模块公用的jar包等统一提取出来,类似一个抽象父类

pom.xml

  1. <!--打包方式 pom-->
  2. <packaging>pom</packaging>
  3. <properties>
  4. <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  5. <maven.compiler.source>1.8</maven.compiler.source>
  6. <maven.compiler.target>1.8</maven.compiler.target>
  7. <logback-core.version>1.2.3</logback-core.version>
  8. <junit.version>4.12</junit.version>
  9. <log4j.version>1.2.17</log4j.version>
  10. <lombok.version>1.16.18</lombok.version>
  11. </properties>
  12. <!--依赖管理的作用是依赖没有被激活-->
  13. <dependencyManagement>
  14. <dependencies>
  15. <!-- springCloud的依赖 -->
  16. <dependency>
  17. <groupId>org.springframework.cloud</groupId>
  18. <artifactId>spring-cloud-dependencies</artifactId>
  19. <version>Hoxton.SR1</version>
  20. <type>pom</type>
  21. <scope>import</scope>
  22. </dependency>
  23. <!--springBoot-->
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-dependencies</artifactId>
  27. <version>2.2.6.RELEASE</version>
  28. <type>pom</type>
  29. <scope>import</scope>
  30. </dependency>
  31. <!--数据库-->
  32. <dependency>
  33. <groupId>mysql</groupId>
  34. <artifactId>mysql-connector-java</artifactId>
  35. <version>5.1.47</version>
  36. </dependency>
  37. <!--数据源-->
  38. <dependency>
  39. <groupId>com.alibaba</groupId>
  40. <artifactId>druid</artifactId>
  41. <version>1.2.10</version>
  42. </dependency>
  43. <!--springBoot 启动器-->
  44. <dependency>
  45. <groupId>org.mybatis.spring.boot</groupId>
  46. <artifactId>mybatis-spring-boot-starter</artifactId>
  47. <version>2.2.2</version>
  48. </dependency>
  49. <!--日志测试~-->
  50. <dependency>
  51. <groupId>ch.qos.logback</groupId>
  52. <artifactId>logback-core</artifactId>
  53. <version>${logback-core.version}</version>
  54. </dependency>
  55. <dependency>
  56. <groupId>junit</groupId>
  57. <artifactId>junit</artifactId>
  58. <version>${junit.version}</version>
  59. </dependency>
  60. <dependency>
  61. <groupId>log4j</groupId>
  62. <artifactId>log4j</artifactId>
  63. <version>${log4j.version}</version>
  64. </dependency>
  65. <dependency>
  66. <groupId>org.projectlombok</groupId>
  67. <artifactId>lombok</artifactId>
  68. <version>${lombok.version}</version>
  69. </dependency>
  70. </dependencies>
  71. </dependencyManagement>

springcloud-consumer-deptt-80访问springcloud-provider-dept-8001下的controller使用REST方式

DeptConsumerController.java

  1. @RestController
  2. public class DeptConsumerController {
  3. // 理解:消费者,不应该有service层~
  4. // RestTemplate ... 供我们直接调用就可以了! 注册到spring中
  5. // (url,实体:Map, Class<T> responseType )
  6. @Autowired
  7. private RestTemplate restTemplate; // 提供多种便捷访问远程http服务的方法,简单的Restful服务模板~
  8. // Ribbon,我们这里的地址,应该是一个变量,通过服务名来访问
  9. // private static final String REST_URL_PREFIX = "http://localhost:8001";
  10. private static final String REST_URL_PREFIX = "http://SPRINGCLOUD-PROVIDER-DEPT";
  11. @RequestMapping("/consumer/dept/add")
  12. public boolean add(Dept dept){
  13. return restTemplate.postForObject(REST_URL_PREFIX+"/dept/add",dept,Boolean.class);
  14. }
  15. @RequestMapping("/consumer/dept/get/{id}")
  16. public Dept get(@PathVariable("id") long id){
  17. return restTemplate.getForObject(REST_URL_PREFIX+"/dept/get/"+id,Dept.class);
  18. }
  19. @RequestMapping("/consumer/dept/list")
  20. public List<Dept> list(){
  21. return restTemplate.getForObject(REST_URL_PREFIX+"/dept/list",List.class);
  22. }
  23. }

使用RestTemplate现需要放入Spring容器中

在config文件夹中创建ConfigBean.java

  1. @Configuration
  2. public class ConfigBean { // @Configuration 相当于 spring中的 applicationContext.xml
  3. // 配置负载均衡实现RestTemplate
  4. // IRule
  5. // RoundRobinRule 轮询
  6. // RandomRule 随机
  7. // AvailabilityFilteringRule : 会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
  8. // RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
  9. @Bean
  10. @LoadBalanced // Ribbon
  11. public RestTemplate getRestTemplate(){
  12. return new RestTemplate();
  13. }
  14. }

springcloud-provider-dept-8001的dao接口调用springcloud-api模块下的pojo,拿到数据库信息

  1. <!--我们需要拿到实体类,所以要配置api module-->
  2. <dependency>
  3. <groupId>org.shaui</groupId>
  4. <artifactId>springcloud-api</artifactId>
  5. <version>1.0-SNAPSHOT</version>
  6. </dependency>

DeptDao.java

  1. @Mapper
  2. @Repository
  3. public interface DeptDao {
  4. public Boolean addDept(Dept dept);
  5. public Dept queryById(long id);
  6. public List<Dept> queryAll();
  7. }

然后编写service,最后编写DeptController.java来控制视图的跳转
DeptController.java

  1. // 提供Restful服务
  2. @RestController
  3. public class DeptController {
  4. @Autowired
  5. DeptService deptService;
  6. // 获取一些配置的信息,获得具体的微服务
  7. @Autowired
  8. private DiscoveryClient client;
  9. @PostMapping("/dept/add")
  10. public boolean addDept(Dept dept){
  11. return deptService.addDept(dept);
  12. }
  13. @GetMapping("/dept/get/{id}")
  14. public Dept get(@PathVariable("id") long id){
  15. return deptService.queryById(id);
  16. }
  17. @GetMapping("/dept/list")
  18. public List<Dept> queryAll(){
  19. return deptService.queryAll();
  20. }
  21. // 注册进来的微服务~,获取一些消息~(团队开发会用到)
  22. // 使用时应该在主启动类添加该注解 @EnableDiscoveryClient // 服务发现~
  23. @GetMapping("/dept/discovery")
  24. public Object discovery(){
  25. List<String> services = client.getServices();
  26. System.out.println("discovery=>services:"+services);
  27. // 得到一个具体的微服务信息,通过具体的微服务id,applicationName:
  28. List<ServiceInstance> instances = client.getInstances("SPRINGCLOUD-PROVIDER-DEPT");
  29. for (ServiceInstance instance : instances) {
  30. System.out.println(
  31. instance.getHost()+"\t"+
  32. instance.getPort()+"\t"+
  33. instance.getUri()+"\t"+
  34. instance.getInstanceId()
  35. );
  36. }
  37. return this.client;
  38. }
  39. }

测试查询结果

3、Eureka服务注册中心

3.1 什么是Eureka

  • Netflix在涉及Eureka时,遵循的就是API原则.
  • Eureka是Netflix的有个子模块,也是核心模块之一。Eureka是基于REST的服务,用于定位服务,以实现云端中间件层服务发现和故障转移,服务注册与发现对于微服务来说是非常重要的,有了服务注册与发现,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类似于Dubbo的注册中心,比如Zookeeper.

3.2 原理理解

  • Eureka基本的架构

    • Springcloud 封装了Netflix公司开发的Eureka模块来实现服务注册与发现 (对比Zookeeper).
    • Eureka采用了C-S的架构设计,EurekaServer作为服务注册功能的服务器,他是服务注册中心.
    • 而系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心跳连接。这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,Springcloud 的一些其他模块 (比如Zuul) 就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑.
  • Eureka 包含两个组件:Eureka Server 和 Eureka Client.
  • Eureka Server 提供服务注册,各个节点启动后,回在EurekaServer中进行注册,这样Eureka Server中的服务注册表中将会储存所有课用服务节点的信息,服务节点的信息可以在界面中直观的看到.
  • Eureka Client 是一个Java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向EurekaServer发送心跳 (默认周期为30秒) 。如果Eureka Server在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除掉 (默认周期为90s).

3.3 构建步骤

1、eureka-server

  1. springcloud-eureka-7002创建maven工程
  2. pom.xml
    1. <!--导入Eureka Server依赖-->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-eureka-server</artifactId>
    5. <version>1.4.6.RELEASE</version>
    6. </dependency>
  1. application.yaml ```yaml server: port: 7002

Eureka配置

eureka: instance: hostname: localhost # Eureka服务器的实例名称 client: register-with-eureka: false # 表示是否向Eureka注册中心注册自己 fetch-registry: false # fetch-registry 如果为false,则表示自己是注册中心 service-url: # 注册中心地址7002 - 7004 defaultZone: http://localhost:7003/eureka/,http://localhost:7004/eureka/

  1. 4. 编写启动类
  2. ```java
  3. @SpringBootApplication
  4. @EnableEurekaServer // EnableEurekaServer 服务端的启动类,可以接收别人注册过来~
  5. public class EurekaService_7002 {
  6. public static void main(String[] args) {
  7. SpringApplication.run(EurekaService_7002.class,args);
  8. }
  9. }
  1. 启动成功后访问 http://localhost:7002/

创建多个eureka-server,实现eureka集群配置

2、eureka-client

调整之前创建的springlouc-provider-dept-8001

  1. 导入Eureca依赖
    1. <!-- eureka -->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-eureka</artifactId>
    5. <version>1.4.6.RELEASE</version>
    6. </dependency>
  1. application中新增Eureca配置
    1. # Eureka的配置,服务注册到哪里
    2. eureka:
    3. client:
    4. service-url:
    5. defaultZone: http://localhost:7002/eureka/,http://localhost:7003/eureka/
    6. instance:
    7. instance-id: springcloud-provider-dept8001 # 修改eureka上的默认描述信息!
  1. 为主启动类添加注解
    1. @EnableEurekaClient // 在服务启动后自动注册到Eureka中!
  1. 测试成功
  2. 修改Eureka上的默认描述信息
    如果此时停掉springcloud-provider-dept-8001 等30s后 监控会开启保护机制
  3. 配置关于服务加载的监控信息
    pom.xml中添加依赖
    1. <!--actuator完善监控信息-->
    2. <dependency>
    3. <groupId>org.springframework.boot</groupId>
    4. <artifactId>spring-boot-starter-actuator</artifactId>
    5. </dependency>

    application.yaml中添加配置
    跳转到http://localhost:7001/actuator/info,能看到自己配置的信息

3. EureKa自我保护机制:好死不如赖活着

一句话总结就是:某时刻某一个微服务不可用,eureka不会立即清理,依旧会对该微服务的信息进行保存!

4.对比和Zookeeper区别

1、回顾CAP原则
RDBMS (MySQL\Oracle\sqlServer) ===> ACID

NoSQL (Redis\MongoDB) ===> CAP

2、ACID是什么?

  • A (Atomicity) 原子性
  • C (Consistency) 一致性
  • I (Isolation) 隔离性
  • D (Durability) 持久性

3、CAP是什么?

  • C (Consistency) 强一致性
  • A (Availability) 可用性
  • P (Partition tolerance) 分区容错性
  • CAP的三进二:CA、AP、CP

4、CAP理论的核心

  • 一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求
  • 根据CAP原理,将NoSQL数据库分成了满足CA原则,满足CP原则和满足AP原则三大类
    • CA:单点集群,满足一致性,可用性的系统,通常可扩展性较差
    • CP:满足一致性,分区容错的系统,通常性能不是特别高
    • AP:满足可用性,分区容错的系统,通常可能对一致性要求低一些

5、作为分布式服务注册中心,Eureka比Zookeeper好在哪里?
著名的CAP理论指出,一个分布式系统不可能同时满足C (一致性) 、A (可用性) 、P (容错性),由于分区容错性P再分布式系统中是必须要保证的,因此我们只能再A和C之间进行权衡。

  • Zookeeper 保证的是 CP —> 满足一致性,分区容错的系统,通常性能不是特别高
  • Eureka 保证的是 AP —> 满足可用性,分区容错的系统,通常可能对一致性要求低一些

Zookeeper保证的是CP

当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的注册信息,但不能接收服务直接down掉不可用。也就是说,服务注册功能对可用性的要求要高于一致性。但zookeeper会出现这样一种情况,当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30-120s,且选举期间整个zookeeper集群是不可用的,这就导致在选举期间注册服务瘫痪。在云部署的环境下,因为网络问题使得zookeeper集群失去master节点是较大概率发生的事件,虽然服务最终能够恢复,但是,漫长的选举时间导致注册长期不可用,是不可容忍的。

Eureka保证的是AP

Eureka看明白了这一点,因此在设计时就优先保证可用性。Eureka各个节点都是平等的,几个节点挂掉不会影响正常节点的工作,剩余的节点依然可以提供注册和查询服务。而Eureka的客户端在向某个Eureka注册时,如果发现连接失败,则会自动切换至其他节点,只要有一台Eureka还在,就能保住注册服务的可用性,只不过查到的信息可能不是最新的,除此之外,Eureka还有之中自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

  • Eureka不在从注册列表中移除因为长时间没收到心跳而应该过期的服务
  • Eureka仍然能够接受新服务的注册和查询请求,但是不会被同步到其他节点上 (即保证当前节点依然可用)
  • 当网络稳定时,当前实例新的注册信息会被同步到其他节点中

因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使整个注册服务瘫痪

4. Ribbon:负载均衡(基于客户端)

4.1 负载均衡以及Ribbon

Ribbon是什么?

  • Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。
  • 简单的说,Ribbon 是 Netflix 发布的开源项目,主要功能是提供客户端的软件负载均衡算法,将 Netflix 的中间层服务连接在一起。Ribbon 的客户端组件提供一系列完整的配置项,如:连接超时、重试等。简单的说,就是在配置文件中列出 LoadBalancer (简称LB:负载均衡) 后面所有的及其,Ribbon 会自动的帮助你基于某种规则 (如简单轮询,随机连接等等) 去连接这些机器。我们也容易使用 Ribbon 实现自定义的负载均衡算法!

Ribbon能干嘛?

  • LB,即负载均衡 (LoadBalancer) ,在微服务或分布式集群中经常用的一种应用。
  • 负载均衡简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA (高用)。
  • 常见的负载均衡软件有 Nginx、Lvs 等等。
  • Dubbo、SpringCloud 中均给我们提供了负载均衡,SpringCloud 的负载均衡算法可以自定义。
  • 负载均衡简单分类:
    • 集中式LB
      • 即在服务的提供方和消费方之间使用独立的LB设施,如Nginx(反向代理服务器),由该设施负责把访问请求通过某种策略转发至服务的提供方!
    • 进程式 LB
      • 将LB逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选出一个合适的服务器。
      • Ribbon 就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址!

4.2 集成Ribbon

springcloud-consumer-dept-80向pom.xml中添加Ribbon和Eureka依赖

  1. <!--Ribbon-->
  2. <dependency>
  3. <groupId>org.springframework.cloud</groupId>
  4. <artifactId>spring-cloud-starter-ribbon</artifactId>
  5. <version>1.4.6.RELEASE</version>
  6. </dependency>
  7. <!--Eureka: Ribbon需要从Eureka服务中心获取要拿什么-->
  8. <dependency>
  9. <groupId>org.springframework.cloud</groupId>
  10. <artifactId>spring-cloud-starter-eureka</artifactId>
  11. <version>1.4.6.RELEASE</version>
  12. </dependency>

在application.yaml文件中配置Eureka

  1. # Eureka配置
  2. eureka:
  3. client:
  4. register-with-eureka: false # 不向Eureka注册自己!
  5. service-url:
  6. defaultZone: http://localhost:7002/eureka/,http://localhost:7003/eureka/

主启动类加上@EnableEurekaClient注解,开启Eureka

  1. // Ribbon 和 Eureka 整合以后,客户端可以直接调用,不用关心IP地址和端口号~
  2. @SpringBootApplication
  3. @EnableEurekaClient
  4. public class DeptConsumer_80 {
  5. public static void main(String[] args) {
  6. SpringApplication.run(DeptConsumer_80.class,args);
  7. }
  8. }

自定义Spring配置类:ConfigBean.java 配置负载均衡实现RestTemplate

  1. @Configuration
  2. public class ConfigBean { // @Configuration 相当于 spring中的 applicationContext.xml
  3. @Bean
  4. @LoadBalanced // 配置负载均衡实现RestTemplate
  5. public RestTemplate getRestTemplate(){
  6. return new RestTemplate();
  7. }
  8. }

每次访问http://localhost/consumer/dept/list随机访问集群中某个服务提供者,这种情况叫做轮询,轮询算法在SpringCloud中可以自定义。

如何切换或者自定义规则呢?

在springcloud-provider-dept-80模块下的ConfigBean中进行配置,切换使用不同的规则

  1. @Configuration
  2. public class ConfigBean {//@Configuration -- spring applicationContext.xml
  3. /**
  4. * IRule:
  5. * RoundRobinRule 轮询策略
  6. * RandomRule 随机策略
  7. * AvailabilityFilteringRule : 会先过滤掉,跳闸,访问故障的服务~,对剩下的进行轮询~
  8. * RetryRule : 会先按照轮询获取服务~,如果服务获取失败,则会在指定的时间内进行,重试
  9. */
  10. @Bean
  11. public IRule myRule() {
  12. return new RandomRule();//使用随机策略
  13. //return new RoundRobinRule();//使用轮询策略
  14. //return new AvailabilityFilteringRule();//使用轮询策略
  15. //return new RetryRule();//使用轮询策略
  16. }
  17. }

也可以自定义规则,在myrule包下自定义一个配置类ShuaiRule.java,注意:该包不要和主启动类所在的包同级,要跟启动类所在包同级

ShuaiRule.java

  1. @Configuration
  2. public class ShuaiRule {
  3. public IRule myRule(){
  4. // return new ShuaiRandomRule(); // 默认是轮询,现在我们定义为~ ShuaiRandomRule
  5. return new RoundRobinRule(); // 默认是轮询,现在我们定义为~ ShuaiRandomRule
  6. }
  7. }

ShuaiRandomRule.java

  1. public class ShuaiRandomRule extends AbstractLoadBalancerRule {
  2. // 每个服务,访问5次~,换下一个服务(3个)
  3. // total=0 ,默认=0, 如果=5, 我们指向下一个服务节点
  4. // index=0, 默认0, 如果total=5, index+1
  5. private int total=0; // 被调用的次数
  6. private int currentIndex = 0; // 当前谁提供的服务~
  7. public Server choose(ILoadBalancer lb, Object key) {
  8. if (lb == null) {
  9. return null;
  10. }
  11. Server server = null;
  12. while (server == null) {
  13. if (Thread.interrupted()) {
  14. return null;
  15. }
  16. List<Server> upList = lb.getReachableServers(); // 获得活着的服务
  17. List<Server> allList = lb.getAllServers(); // 获得全部服务
  18. int serverCount = allList.size();
  19. if (serverCount == 0) {
  20. return null;
  21. }
  22. // int index = chooseRandomInt(serverCount); // 生成区间随机数
  23. // server = upList.get(index); // 从活着的服务中,随机获取一个~
  24. // =======================================================
  25. if (total<5){
  26. server = upList.get(currentIndex);
  27. total++;
  28. }else {
  29. total = 0;
  30. currentIndex++;
  31. if (currentIndex>upList.size()){
  32. currentIndex = 0;
  33. }
  34. server = upList.get(currentIndex); // 从活着的服务中,获取指定的服务来进行操作
  35. }
  36. // =======================================================
  37. }
  38. return server;
  39. }
  40. protected int chooseRandomInt(int serverCount) {
  41. return ThreadLocalRandom.current().nextInt(serverCount);
  42. }
  43. @Override
  44. public Server choose(Object key) {
  45. return choose(getLoadBalancer(), key);
  46. }
  47. @Override
  48. public void initWithNiwsConfig(IClientConfig clientConfig) {
  49. // TODO Auto-generated method stub
  50. }
  51. }

主启动类开启负载均衡并指定自定义的MyRule配置类

  1. // Ribbon 和 Eureka 整合以后,客户端可以直接调用,不用关心IP地址和端口号~
  2. @SpringBootApplication
  3. @EnableEurekaClient
  4. // 在微服务启动的时候就能去加载我们自定义Ribbon类
  5. @RibbonClient(name = "SPRINGCLOUD-PROVIDER-DEPT",configuration = ShuaiRandomRule.class)
  6. public class DeptConsumer_80 {
  7. public static void main(String[] args) {
  8. SpringApplication.run(DeptConsumer_80.class,args);
  9. }
  10. }

5、Feign:负载均衡(基于服务端)

5.1 Feign简介

Feign是声明式Web Service客户端,它让微服务之间的调用变得更简单,类似controller调用service。SpringCloud集成了Ribbon和Eureka,可以使用Feigin提供负载均衡的http客户端

只需要创建一个接口,然后添加注解即可~

Feign,主要是社区版,大家都习惯面向接口编程。这个是很多开发人员的规范。调用微服务访问两种方法

  1. 微服务名字 【ribbon】
  2. 接口和注解 【feign】

Feign能干什么?

  • Feign旨在使编写Java Http客户端变得更容易
  • 前面在使用Ribbon + RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一个客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义,在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它 (类似以前Dao接口上标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon 时,自动封装服务调用客户端的开发量。

Feign默认集成了Ribbon

  • 利用Ribbon维护了MicroServiceCloud-Dept的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。

5.2 Feign的使用步骤

  1. 添加feign依赖

    1. <!--feign-->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-feign</artifactId>
    5. <version>1.4.6.RELEASE</version>
    6. </dependency>


    通过Feign实现:DeptConsumerController.java
    主配置类:

  2. 改造springcloud-api模块
    pom.xml添加feign依赖

    1. <!--feign-->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-feign</artifactId>
    5. <version>1.4.6.RELEASE</version>
    6. </dependency>


    新建service包,并新建DeptClientService.java接口

6、Hystrix:服务熔断

6.1 服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”,如果扇出的链路上某个微服务的调用响应时间过长,或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几十秒内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障,这些都表示需要对故障和延迟进行隔离和管理,以达到单个依赖关系的失败而不影响整个应用程序或系统运行。

我们需要,弃车保帅!

6.2 什么是Hystrix?

Hystrix是一个应用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整个体系服务失败,避免级联故障,以提高分布式系统的弹性。

“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控 (类似熔断保险丝) ,向调用方返回一个服务预期的,可处理的备选响应 (FallBack) ,而不是长时间的等待或者抛出调用方法无法处理的异常,这样就可以保证了服务调用方的线程不会被长时间,不必要的占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

6.2 Hystrix能干嘛?

  • 服务降级
  • 服务熔断
  • 服务限流
  • 接近实时的监控

6.3 服务熔断

什么是服务熔断?

熔断机制是赌赢雪崩效应的一种微服务链路保护机制。

当扇出链路的某个微服务不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。检测到该节点微服务调用响应正常后恢复调用链路。在SpringCloud框架里熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阀值缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是:@HystrixCommand。

服务熔断解决如下问题:

  • 当所依赖的对象不稳定时,能够起到快速失败的目的;
  • 快速失败后,能够根据一定的算法动态试探所依赖对象是否恢复。

入门案例

  1. 导入hystrix依赖
    1. <!-- hystrix -->
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-hystrix</artifactId>
    5. <version>1.4.6.RELEASE</version>
    6. </dependency>
  1. 调整yaml配置文件 ```yaml server: port: 8001

mybatis配置

mybatis: type-aliases-package: com.shuai.springcloud.pojo config-location: classpath:mybatis/mybatis-config.xml mapper-locations: classpath:mybatis/mapper/*.xml

spring的配置

spring: application: name: springcloud-provider-dept

datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: org.gjt.mm.mysql.Driver url: jdbc:mysql://localhost:3306/db01?useUnicode=true&characterEncoding=utf-8 username: root password: shuai

Eureka的配置,服务注册到哪里

eureka: client: service-url: defaultZone: http://localhost:7002/eureka/,http://localhost:7003/eureka/ instance: instance-id: springcloud-provider-dept-hystrix-8001 # 修改eureka上的默认描述信息! prefer-ip-address: true # true,可以显示服务的IP地址

info配置

info: app.name: shuai-springcloud company.name: shaoshuai-1024

  1. 3. 修改controller
  2. ```java
  3. // 提供Restful服务
  4. @RestController
  5. public class DeptController {
  6. @Autowired
  7. DeptService deptService;
  8. @GetMapping("/dept/get/{id}")
  9. @HystrixCommand(fallbackMethod = "hystrixGet")
  10. public Dept get(@PathVariable("id") long id){
  11. Dept dept = deptService.queryById(id);
  12. if (dept==null){
  13. throw new RuntimeException("id=>"+id+",不存在该用户,或者信息无法找到~");
  14. }
  15. return dept;
  16. }
  17. // 备用方法
  18. public Dept hystrixGet(@PathVariable("id") long id){
  19. return new Dept()
  20. .setDeptno(id)
  21. .setDname("id=>"+id+"没有对应的信息,null--@Hystrix")
  22. .setDb_source("no this database in MySQL");
  23. }
  24. }
  1. 为主启动类添加对熔断的支持注解@EnableCircuitBreaker
    1. // 启动类
    2. @SpringBootApplication
    3. @EnableEurekaClient // 在服务启动后自动注册到Eureka中!
    4. @EnableDiscoveryClient // 服务发现~
    5. @EnableCircuitBreaker // 添加对熔断的支持
    6. public class DeptProvide_hystrix_8001 {
    7. public static void main(String[] args) {
    8. SpringApplication.run(DeptProvide_hystrix_8001.class,args);
    9. }
    10. }
  1. 测试
    使用熔断后,当访问一个不存在的id时,前台页会展示我们写好的报错数据

6.4 服务降级

什么是服务降级?

服务降级是指 当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理,或换种简单的方式处理,从而释放服务器资源以保证核心业务正常运作或高效运作。说白了,就是尽可能的把系统资源让给优先级高的服务

服务降级需要考虑的问题
  • 1)那些服务是核心服务,哪些服务是非核心服务
  • 2)那些服务可以支持降级,那些服务不能支持降级,降级策略是什么
  • 3)除服务降级之外是否存在更复杂的业务放通场景,策略是什么?

自动降级分类

1)超时降级:主要配置好超时时间和超时重试次数和机制,并使用异步机制探测回复情况

2)失败次数降级:主要是一些不稳定的api,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况

3)故障降级:比如要调用的远程服务挂掉了(网络故障、DNS故障、http服务返回错误的状态码、rpc服务抛出异常),则可以直接降级。降级后的处理方案有:默认值(比如库存服务挂了,返回默认现货)、兜底数据(比如广告挂了,返回提前准备好的一些静态页面)、缓存(之前暂存的一些缓存数据)

4)限流降级:秒杀或者抢购一些限购商品时,此时可能会因为访问量太大而导致系统崩溃,此时会使用限流来进行限制访问量,当达到限流阀值,后续请求会被降级;降级后的处理方案可以是:排队页面(将用户导流到排队页面等一会重试)、无货(直接告知用户没货了)、错误页(如活动太火爆了,稍后重试)。

入门案例

在springcloud-api模块下的service包中新建降级配置类DeptClientServiceFallBackFactory.java

  1. // 降级~
  2. @Component
  3. public class DeptClientServiceFallbackFactory implements FallbackFactory {
  4. @Override
  5. public Object create(Throwable throwable) {
  6. return new DeptClientService() {
  7. @Override
  8. public Dept queryById(Long id) {
  9. return new Dept()
  10. .setDeptno(id)
  11. .setDname("id=>"+id+"没有对应的信息,客户端提供了降级的信息,这个服务现在已经被关闭")
  12. .setDb_source("没有数据~");
  13. }
  14. @Override
  15. public List<Dept> queryAll() {
  16. return null;
  17. }
  18. @Override
  19. public boolean addDept(Dept dept) {
  20. return false;
  21. }
  22. };
  23. }
  24. }

在DeptClientService中指定降级配置类DeptClientServiceFallBackFactory

  1. @Component
  2. @FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceFallbackFactory.class)
  3. public interface DeptClientService {
  4. @GetMapping("/dept/get/{id}")
  5. public Dept queryById(@PathVariable("id") Long id);
  6. @GetMapping("/dept/list")
  7. public List<Dept> queryAll();
  8. @GetMapping("/dept/add")
  9. public boolean addDept(Dept dept);
  10. }

springcloud-consumer-dept-feign模块中开启降级:

  1. server:
  2. port: 80
  3. # Eureka配置
  4. eureka:
  5. client:
  6. register-with-eureka: false # 不向Eureka注册自己!
  7. service-url:
  8. defaultZone: http://localhost:7002/eureka/,http://localhost:7003/eureka/
  9. # 开启降级 feign.hystrix
  10. feign:
  11. hystrix:
  12. enabled: true

6.5 服务熔断和降级的区别

  • 服务熔断—>服务端:某个服务超时或异常,引起熔断~,类似于保险丝(自我熔断)
  • 服务降级—>客户端:从整体网站请求负载考虑,当某个服务熔断或者关闭之后,服务将不再被调用,此时在客户端,我们可以准备一个 FallBackFactory ,返回一个默认的值(缺省值)。会导致整体的服务下降,但是好歹能用,比直接挂掉强。
  • 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;管理目标的层次不太一样,熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
  • 实现方式不太一样,服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断。

熔断,降级,限流:

限流:限制并发的请求访问量,超过阈值则拒绝;

降级:服务分优先级,牺牲非核心服务(不可用),保证核心服务稳定;从整体负荷考虑;

熔断:依赖的下游服务故障触发熔断,避免引发本系统崩溃;系统自动执行和恢复

6.6 Dashboard 流监控

7、Zull路由网关

什么是zuul?

Zull包含了对请求的路由(用来跳转的)和过滤两个最主要功能:

其中路由功能负责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础,而过滤器功能则负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。Zuul和Eureka进行整合,将Zuul自身注册为Eureka服务治理下的应用,同时从Eureka中获得其他服务的消息,也即以后的访问微服务都是通过Zuul跳转后获得。

注意:Zuul 服务最终还是会注册进 Eureka

提供:代理 + 路由 + 过滤 三大功能!

Zuul 能干嘛?

  • 路由
  • 过滤

7.1、入门案例

  1. 导入依赖
    1. <dependencies>
    2. <!--导入zuul依赖-->
    3. <dependency>
    4. <groupId>org.springframework.cloud</groupId>
    5. <artifactId>spring-cloud-starter-zuul</artifactId>
    6. <version>1.4.6.RELEASE</version>
    7. </dependency>
    8. <!--Hystrix依赖-->
    9. <dependency>
    10. <groupId>org.springframework.cloud</groupId>
    11. <artifactId>spring-cloud-starter-hystrix</artifactId>
    12. <version>1.4.6.RELEASE</version>
    13. </dependency>
    14. <!--dashboard依赖-->
    15. <dependency>
    16. <groupId>org.springframework.cloud</groupId>
    17. <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
    18. <version>1.4.6.RELEASE</version>
    19. </dependency>
    20. <!--Ribbon-->
    21. <dependency>
    22. <groupId>org.springframework.cloud</groupId>
    23. <artifactId>spring-cloud-starter-ribbon</artifactId>
    24. <version>1.4.6.RELEASE</version>
    25. </dependency>
    26. <!--Eureka-->
    27. <dependency>
    28. <groupId>org.springframework.cloud</groupId>
    29. <artifactId>spring-cloud-starter-eureka</artifactId>
    30. <version>1.4.6.RELEASE</version>
    31. </dependency>
    32. <!--实体类+web-->
    33. <dependency>
    34. <groupId>org.shaui</groupId>
    35. <artifactId>springcloud-api</artifactId>
    36. <version>1.0-SNAPSHOT</version>
    37. </dependency>
    38. <dependency>
    39. <groupId>org.springframework.boot</groupId>
    40. <artifactId>spring-boot-starter-web</artifactId>
    41. </dependency>
    42. <!--热部署-->
    43. <dependency>
    44. <groupId>org.springframework.boot</groupId>
    45. <artifactId>spring-boot-devtools</artifactId>
    46. </dependency>
    47. </dependencies>
  1. application.yaml ```yaml server: port: 9527

spring的配置

spring: application: name: springcloud-zuul

Eureka的配置,服务注册到哪里

eureka: client: service-url: defaultZone: http://localhost:7002/eureka/,http://localhost:7003/eureka/ instance: instance-id: zuul9527.com # 修改eureka上的默认描述信息! prefer-ip-address: true # true,可以显示服务的IP地址

info配置

info: app.name: shuai-springcloud company.name: shaoshuai-1024

zuul: routes: mydept.serviceId: springcloud-provider-dept mydept.path: /mydept/**

微服务名称隐藏,为了安全

ignored-services: “*” # 不能再使用这个路径访问了,ignored: 忽略 prefix: /shuai # 设置公共的前缀

  1. 3. 主启动类
  2. ```java
  3. @SpringBootApplication
  4. @EnableZuulProxy // 开启Zuul
  5. public class ZuulApplication_9527 {
  6. public static void main(String[] args) {
  7. SpringApplication.run(ZuulApplication_9527.class,args);
  8. }
  9. }
  1. 测试访问
    localhost:9527://shuai/mydept/dept/get/1

8、 Spring Cloud Config 分布式配置

8.1 什么是SpringCloud config分布式配置中心?

spring cloud config 分为服务端和客户端两部分。

服务端也称为 分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密,解密信息等访问接口。

客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息。配置服务器默认采用git来存储配置信息,这样就有助于对环境配置进行版本管理。并且可用通过git客户端工具来方便的管理和访问配置内容。

spring cloud config 分布式配置中心能干嘛?

  • 集中式管理配置文件
  • 不同环境,不同配置,动态化的配置更新,分环境部署,比如 /dev /test /prod /beta /release
  • 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
  • 当配置发生变动时,服务不需要重启,即可感知到配置的变化,并应用新的配置
  • 将配置信息以REST接口的形式暴露

spring cloud config 分布式配置中心与Gitee整合

由于spring cloud config 默认使用git来存储配置文件 (也有其他方式,比如自持SVN 和本地文件),但是最推荐的还是git ,而且使用的是 http / https 访问的形式。

8.2 入门案例

服务端

  1. 新建springcloud-config-server-3344模块导入pom.xml依赖 ```xml

    org.springframework.cloud spring-cloud-config-server 2.1.1.RELEASE org.springframework.boot spring-boot-starter-web

  1. 2. resource下创建application.yml配置文件,Spring Cloud Config服务器从git存储库(必须提供)为远程客户端提供配置:
  2. ```yaml
  3. server:
  4. port: 3344
  5. spring:
  6. application:
  7. name: springcloud-config-server
  8. # 连接远程仓库
  9. cloud:
  10. config:
  11. server:
  12. git:
  13. uri: https://gitee.com/shaoshuai-1024/springcloud.git # https,不是 git
  14. # 通过 config-server 可以连接到git, 访问其中资源以及配置~
  1. 主启动类
    1. @SpringBootApplication
    2. @EnableConfigServer // 开启spring cloud config server服务
    3. public class config_server_3344 {
    4. public static void main(String[] args) {
    5. SpringApplication.run(config_server_3344.class,args);
    6. }
    7. }
  1. 将本地git仓库springcloud-config文件夹下新建的application.yml提交到码云仓库:
    HTTP服务具有以下格式的资源:
    1. /{application}/{profile}[/{label}]
    2. /{application}-{profile}.yml
    3. /{label}/{application}-{profile}.yml
    4. /{application}-{profile}.properties
    5. /{label}/{application}-{profile}.properties

    测试访问http://localhost:3344/application-dev.yml
    成功访问到远程gitee上的配合

客户端

  1. 将本地git仓库springcloud-config文件夹下新建的config-client.yml提交到码云仓库:
  2. 新建一个springcloud-config-client-3355模块,并导入依赖

    1. <dependencies>
    2. <dependency>
    3. <groupId>org.springframework.cloud</groupId>
    4. <artifactId>spring-cloud-starter-config</artifactId>
    5. <version>2.1.1.RELEASE</version>
    6. </dependency>
    7. <dependency>
    8. <groupId>org.springframework.boot</groupId>
    9. <artifactId>spring-boot-starter-actuator</artifactId>
    10. </dependency>
    11. <dependency>
    12. <groupId>org.springframework.boot</groupId>
    13. <artifactId>spring-boot-starter-web</artifactId>
    14. </dependency>
    15. </dependencies>
  1. resources下创建application.yml和bootstrap.yml配置文件
    bootstrap.yml 是系统级别的配置

    1. # 系统级别的配置
    2. spring:
    3. cloud:
    4. config:
    5. name: config-client # 需要从git上读取的资源名称,不要后缀
    6. profile: test
    7. label: master
    8. uri: http://localhost:3344


    application.yml 是用户级别的配置

  2. 创建controller包下的ConfigClientController.java 用于测试

    1. @RestController
    2. public class ConfigClientController {
    3. @Value("${spring.application.name}")
    4. private String applicationName;
    5. @Value("${eureka.client.service-url.defaultZone}")
    6. private String eurekaServer;
    7. @Value("${server.port}")
    8. private String port;
    9. @RequestMapping("/config")
    10. public String getConfig(){
    11. return "applicationName:"+applicationName+
    12. "eurekaServer"+eurekaServer+
    13. "port"+port;
    14. }
    15. }
  1. 主启动类
    1. @SpringBootApplication
    2. public class ConfigClient_3355 {
    3. public static void main(String[] args) {
    4. SpringApplication.run(ConfigClient_3355.class,args);
    5. }
    6. }
  1. 测试

同样的方式把:springcloud-provider-dept-8001和springcloud-eureka-7002的相关配置也放到远程,然后进行测试