springCloud

SpringCloud概述

微服务架构概述

“微服务”源于MartinFowler的博文Microservices。

Martin说:微服务是系统架构上的一种设计风格,它的主旨是将一个原本独立的系统拆成多个小型服务,这些小型服务都在各自独立的进程中运行,服务之间通过基于HTTP的RESTful API进行通信协作。被拆分成的每一个小型服务都围绕着系统中的某一项或者某些耦合度较高的业务功能进行构建,并且每个服务都维护着自身的数据存储、业务开发、自动化测试案例以及独立部署机制。由于有了轻量级的通信协作基础,所以这些微服务可以使用不同的语言来编写。

微服务架构

简而言之,微服务就是开发一组小型服务的方式来开发一个独立的应用系统,每个小型服务都运行在自己的进程中,并采用HTTP资源API轻量级的机制来互相通信。这些服务围绕业务功能进行构建,并能通过全自动的部署机制来进行独立部署。这些微服务可以使用不同的语言来编写,并且可以使用不同的数据库存储技术。

微服务优点

  1. 易于开发和维护
  2. 单个微服务启动快
  3. 故障隔离
  4. 局部修改容易且部署快
  5. 技术栈不受限制

SpringCloud官网

官网: http://projects.spring.io/spring-cloud/

手册: http://cloud.spring.io/spring-cloud-static/Dalston.SR2/

中文: https://springcloud.cc/

核心功能

  • configuration management 配置中心
  • service discovery 服务发现
  • circuit breakers 断路器
  • intelligent routing 智能路由
  • micro-proxy 微代理
  • control bus 控制总线
  • one-time tokens 一次性令牌
  • global locks 全局锁
  • leadership election 选举算法
  • distributed sessions 分布式会话
  • cluster state 集群状态

核心架构图

规划内容和步骤

  • 注册中心Eureka eureka+ provider-user + consumer-client
  • 前端负载均衡Ribbon consumer-ribbon
  • RESTFul简易封装 consumer-ribbon-feign
  • 断路器支持 consumer-ribbon-feign-hystrix
  • API网关 Zuul gateway-zuul
  • 异构开发语言Sidecar sidecar+ nodejs
  • 配置中心config configserver+consumer-ribbon-feign-hystrix

Eureka注册中心

eureka的注册中心原理

zhuceCenter

注意它的特点,结构类似于MessageQueue消息队列,服务(提供者、消费者)先都注册到注册中心。对于服务的消费者,**它的特点在于,不会每次都去注册中心获取,而是有本地缓存,加快访问性能。内部含有心跳机制,当注册中心信息改变,自动快速获取新的信息到本地。**心跳机制还保证分布式环境下,某个服务失败后,自动列表从注册中心移除。注册中心中保证所有可用的链接。

注册中心相关配置

自我保护模式

什么是自我保护模式?默认配置下,如果Eureka Server每分钟收到心跳续约的数量低于一个阈值(instance的数量(60/每个instance的心跳间隔秒数)自我保护系数),并且持续15分钟,就会触发自我保护。在自我保护模式中,Eureka Server会保护服务注册表中的信息,不再注销任何服务实例。当它收到的心跳数重新恢复到阈值以上时,该Eureka Server节点就会自动退出自我保护模式。它的设计哲学前面提到过,那就是宁可保留错误的服务注册信息,也不盲目注销任何可能健康的服务实例。

该模式可以通过eureka.server.enable-self-preservation = false来禁用,同时eureka.instance.lease-renewal-interval-in-seconds可以用来更改心跳间隔。

调用关系图

下图可见,对于负载均衡的Ribbon而言,它是基于消费者端的

eurekaService

注册中心搭建

pom.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>eureka.server</groupId>
<artifactId>eureka-server</artifactId>
<version>0.0.1-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath />
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
security:
basic:
enabled: true
user:
name: user
password: password123
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://user:password123@localhost:8761/eureka
logging:
level:
root: INFO

服务启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ypjiao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class RunServices {
public static void main(String[] args) {
SpringApplication.run(RunServices.class,args);
}
}

地址:http://localhost:8761

启动后可通过地址访问各种微服务实例

Eureka服务提供者

服务提供者搭建

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ypjiao</groupId>
<artifactId>eureka-supplier</artifactId>
<version>0.0.1-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
server:
port: 7900
spring:
application:
name: supplier-user # 服务的名称,名称可以相同用于标识是什么服务,且方便用于负载均衡识别对应的服务
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
logging:
level:
root: INFO

Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ypjiao.controller;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SupplierController {
@RequestMapping("/supplier/{name}")
public String getName(@PathVariable String name){
return "hello,"+name;
}
}

服务启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.ypjiao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
//Eureka客户端
@EnableEurekaClient
public class RunSupplier1 {
public static void main(String[] args) {
SpringApplication.run(RunSupplier1.class,args);
}
}

Eureka服务消费者

服务消费者搭建

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ypjiao</groupId>
<artifactId>ribbon-client</artifactId>
<packaging>pom</packaging>
<version>0.0.1-SNAPSHOT</version>
<modules>
<module>Tomorow</module>
</modules>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
server:
port: 7902
spring:
application:
name: ribbon-client
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
logging:
level:
root: INFO

Controller.java

  • RestTemplate对象是在RunApp中声明并创建的,用它才可以实现负载均衡,同时注意url中的地址为VIP虚拟IP,为application.yml中配置的application-name。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.ypjiao.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class RibbonController {
@Autowired
RestTemplate restTemplate;
@RequestMapping("/ribbon/{name}")
String getName(@PathVariable String name){
String url = "http://SUPPLIER-USER/supplier/"+name;
return restTemplate.getForObject(url,String.class);
}

}

服务启动类

  • @LoadBalanced注解:用于制定RestTemplate对象的负载均衡
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.ypjiao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class RunRibbon {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RunRibbon.class,args);
}
}

Ribbon负载均衡

Ribbon概述

  • Ribbon是基于客户端的,因此需要在消费者客户端中进行配置

Ribbon

负载均衡策略

常见的负载均衡策略有三种

  1. 第一种也是默认为轮询
  2. 第二种为random随机
  3. 第三种为WeightedResponseTimeRule,响应时间

负载均衡型消费者搭建

ribbon其实无异于普通消费者,只需对代码中Template对象添加负载均衡注解@LoadBlance即可,此外还需切换调用虚拟IP的方法来实现负载均衡

ribbon相关jar包已存在于eureka的jar包中,因此无需更改pom文件

Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.ypjiao.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class RibbonController {
@Autowired
RestTemplate restTemplate;
@RequestMapping("/ribbon/{name}")
String getName(@PathVariable String name){
String url = "http://SUPPLIER-USER/supplier/"+name;
//VIP虚拟IP,提供者的application-name:provider-user
return restTemplate.getForObject(url,String.class);
}

}

服务启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.ypjiao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
public class RunRibbon {
@Bean
@LoadBalanced //Ribbon负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RunRibbon.class,args);
}
}

Ribbon随机负载算法配置

Ribbon规则配置类

需先自定义一个规则配置类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;

/**
*
* 自定义Ribbon配置
* 规定:这个类不能在@ComponentScan@SpringBootApplication本包和子包下,否则引起@RibbonClients扫描冲突
* 注意:随机第一次打断点进入,之后多次刷新就不进入,可能由于本地缓存原因
*/
@Configuration
public class RibbonRuleConfig {
@Bean
public IRule ribbonRule(){
return new RandomRule();
}
}

服务启动类

在原有的基础上新增@RibbonClient注解配置规则即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.ypjiao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(name="provider-user", configuration=RibbonRuleConfig.class)
public class RunRibbon {
@Bean
@LoadBalanced //Ribbon负载均衡
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(RunRibbon.class,args);
}
}

Feign服务消费者(申明式客户端)

Feign概述

Feigh是一个声明式web服务客户端。它能让开发web服务变得容易。使用Feign需要创建一个接口并注解它。它拥有包括Feign注解和JAX-RS注解的可插拔支持。它还支持可插拔的编码器和解码器。Spring Cloud拥有Spring MVC支持,并使用Spring Web中默认同样的HttpMessageConverters。在使用Feign时,SpringCloud集成了Ribbon和Eureka来提供负载均衡的HTTP客户端。

总结:Feign简化HttpClient开发,封装了JAX-RS和SprinMVC的注解,学习成本很低。

Feign原理

  1. 首先通过@EnableFeignCleints注解开启FeignCleint
  2. 根据Feign的规则实现接口,并加@FeignCleint注解
  3. 程序启动后,会进行包扫描,扫描所有的@ FeignCleint的注解的类,并将这些信息注入到ioc容器中。
  4. 当接口的方法被调用,通过jdk的代理,来生成具体的RequesTemplate
  5. RequesTemplate在生成Request
  6. Request交给Client去处理,其中Client可以是HttpUrlConnection、HttpClient也可以是Okhttp
  7. 最后Client被封装到LoadBalanceClient类,这个类结合类Ribbon做到了负载均衡。

原文链接:https://blog.csdn.net/forezp/article/details/73480304

服务提供者搭建

pom.xml

  • 注意,Feign是基于Hystrix的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ypjiao</groupId>
<artifactId>feign=client</artifactId>
<version>0.0.1-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- Hystrix,Feign是基于Hystrix的 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<!-- Feign依赖,声明式开发 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
<!-- Eureka依赖,连接注册中心的都需要有这个依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

application.yml

1
2
3
4
5
6
7
8
9
10
server:
port: 7903
spring:
application:
name: feign-client
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka

FeignService.java

以接口对外暴露,从而封装底层操作。

  • 注解:
    • @FeignClient:申明该接口为Feign管理
      • value值中指定对应的服务提供者
      • config:指定具体的配置类
      • fallback:Feign的降级
    • @RequestMapping:将来作为请求模板的一员指定请求路径
    • @PathVariable:指定请求参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.ypjiao.controller;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
// 这个接口相当于把原来的服务提供者项目当成一个Service类
@FeignClient(value = "supplier-user")
public interface FeignService {
/*
* Feign中没有原生的@GetMapping/@PostMapping/@DeleteMapping/@PutMapping,要指定需要用method进行
*/
@RequestMapping("/supplier/{name}")
String getName(@PathVariable("name") String value);
}

FeignConfig.java

Feign相关配置设置

1
2
3
4
5
6
7
8
@Configuration
public class FeignConfig {
@Bean
public Retryer feignRetryer() {
return new Retryer.Default(100, SECONDS.toMillis(1), 5);
}

}

Controller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.ypjiao.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FeignController {
@Autowired
FeignService feignService;
@RequestMapping("/ypFeign/{name}")
String getName(@PathVariable String name){
return feignService.getName(name);
}
}

服务启动类

  • 注解说明
    • @EnableFeignClients:开启Feign功能,这样容器才回去扫描带有@FeignClient注解的接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ypjiao;

import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
import org.springframework.cloud.netflix.feign.EnableFeignClients;

@SpringCloudApplication
@EnableFeignClients
public class RunFeign {
public static void main(String[] args) {
SpringApplication.run(RunFeign.class,args);
}
}

Feign实现降级

Fallback.java

直接实现消费者的CartFeign接口,给每个方法设置异常时的处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.ypjiao.feign;

import org.springframework.stereotype.Component;

@Component //微服务访问异常则启用降级
public class FeignServiceFallback implements FeignService{
@Override
public SysResult mycart(Long userId) {
return SysResult.build(400, "mycart error.");
}
@Override
public SysResult save(Cart cart) {
return SysResult.build(400, "save error.");
}
@Override
public SysResult update(Cart cart) {
return SysResult.build(400, "update error.");
}
@Override
public SysResult delete(Cart cart) {
return SysResult.build(400, "delete error.");
}
}

修改FeignService.java

需指定降级配置

1
2
3
4
5
6
7
8
9
10
11
12
package com.ypjiao.feign;

import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@FeignClient(value = "supplier-user", fallback = CartFeignFallback.class)
public interface FeignService {
@RequestMapping("/supplier/{name}")
String getName(@PathVariable("name") String value);
}

修改Application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
server:
port: 9001

spring:
application:
name: jt-cart

eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka

#feign集成hystrix必须开启
feign:
hystrix:
enabled: true

logging:
level:
root: INFO

Feign调用过程

processFeign

首先,提供者provider-user和消费者custorm-feign都注册到Eureka中。用户请求feign中的controller,feign中的controller调用feign定义的接口方法。接口的方法根据注解去找到eureka注册中心中的provider-user地址,然后请求远程provider-user所在服务器的地址,然后调用远程的provider-user提供者的具体服务。提供者响应返回json,json被feign封装传输给“接口”的返回值,“接口”在返回给feign的controller,最终响应给用户。

Hystrix断路器

Hystrx断路器概述

  • 微服务架构存在的问题

微服务的设计,服务分散在多个服务器上,服务之间互相调用,要调用的服务由于跨网络跨服务器调用,响应速度明显比传统项目单机调用慢很多,甚至由于网络涌动的不稳定的现象发生导致调用超时;还有类似级联失败、雪崩效应(依赖的基础服务宕机,关联的服务导致失败甚至宕机,就像滚雪球一样层层失败。)

如何解决这类新的问题呢?传统的机制就是超时机制

  • 超时机制

良好的设计,在通过网络请求其他服务时,都必须设置超时时间。正常情况下,一个远程调用几十毫秒内返回。当要调用的服务不可用时或者网络问题,响应时间要等超时,如HttpClient几十秒才超时返回。通常,**一次远程调用对应一个线程/进程,如果大量的线程/进程得不到释放,并且越积越多,服务资源就会被耗尽,从而导致资深服务不可用。**所以必须为每个请求设置超时时间

特别像微服务这样基于多个服务,服务之间都是远程调用,如果一个服务长时间等待,用户体验会极差的,那怎么办呢?断路器模式应运而生。

  • 熔断机制

**断路器可以实现快速失败,如果它在一段时间内检测到许多失败,如超时,就会强迫其以后的多个调用快速失败,**不再请求所依赖的服务,从而防止应用程序不断地尝试执行可能会失败的操作,这样应用程序可以继续执行而不用等待修正错误,或者浪费CPU时间去等待长时间的超时。断路器也可以使应用程序能够诊断错误是否已经修正,如果已经修正,应用程序会再次尝试调用操作。

断路器几个状态

hystrixProcess

  • 关闭:当访问没有问题时,断路器处于关闭未使用。
  • 打开:当访问开始出现异常,错误次数增多,达到阀值时就会打开断路器,这样服务直接访问断路器,进行快速失败返回。
  • 半开:那服务一直走断路器,系统就没法用了,万一被调用的服务已经稳定了呢。断路器的优势就来了,过一定时间窗口后(若干秒)它就会自动分流一部分服务再去尝试访问之前失败的服务。如果继续失败,那就不再转发,如果成功了,成功率高了,那会关闭断路器。

断路器搭建

  • 由于Feign是基于Hystrix因此pom文件和Feign一致
  • SpringClouldApplication注解内置开启hystrix

application.yml

1
2
3
4
5
6
7
8
9
10
server:
port: 9001
spring:
application:
name: consumer-feign-hystrix
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka

修改FeignController

  • 注解说明
    • @HystrixCommand:为注解的方法指定断路方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.ypjiao.controller;

import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class FeignController {
@Autowired
FeignService feignService;
@RequestMapping("/ypFeign/{name}")
@HystrixCommand(fallbackMethod = "fallbackfeign")
String getName(@PathVariable String name){
return feignService.getName(name);
}
//对应上面的方法,参数必须一致,当访问失败时,hystrix直接回调用此方法
String fallbackfeign(String name){
return "我是断路器,你好,"+name;
}
}

Zuul网关

API网关概述

通常来说,使用
API 网关是更好的解决方式。API 网关是一个服务器,也可以说是进入系统的唯一节点。API 网关封装内部系统的架构,并且提供 API 给各个客户端。它还可能还具备授权、监控、负载均衡、缓存、请求分片和管理、静态响应处理等功能。下图展示了一个适应当前架构的 API 网关

Zuul搭建

Zuul的核心还yml配置文件,用映射关系来匹配请求和访问的服务

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ypjiao</groupId>
<artifactId>eureka-zull</artifactId>
<version>0.0.1-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
port: 7904

spring:
application:
name: zull-service

eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka

logging:
level:
root: INFO

zuul:
routes:
app-a:
path: /user/**
serviceId: feign-client
app-b:
path: /sidecar/**
serviceId: eureka-sidecar

服务启动类

  • 注解说明
    • @EnableZuulProxy:开启zuul配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ypjiao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@EnableZuulProxy
@SpringBootApplication
public class RunZull {
public static void main(String[] args) {
SpringApplication.run(RunZull.class,args);
}
}

基于Zuul的断路器

如果继续使用基于消费者端的Hystrix断路器,则断路器会失效,因此只能在zuul中配置断路器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.ypjiao.fallback;

import org.springframework.cloud.netflix.zuul.filters.route.ZuulFallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

@Component //Zuul实现熔断机制
public class ZullFallback implements ZuulFallbackProvider {
@Override
public String getRoute() {
return "*";
}

@Override
public ClientHttpResponse fallbackResponse() {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST;
}

@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.BAD_REQUEST.value();
}

@Override
public String getStatusText() throws IOException {
return HttpStatus.BAD_REQUEST.getReasonPhrase();
}

@Override
public void close() {

}
//具体返回
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(("fallbackzull"+ZullFallback.this.getRoute()).getBytes());
}

@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
};
}
}

sidecar异构系统

sidecar概述

如果有非jvm的开发语言开发的项目,想使用Eureka注册中心、Ribbon负载均衡、ConfigServer配置中心,就可以使用Sidecar。Sidecar可以通过Http api的方式访问。异构语言项目应该实现一个心跳检查,这样Eureka就能知道程序的死活。

注意:运行Sidecar必须在断路器、Eureka客户端、Zuul之上。

异构系统搭建

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ypjiao</groupId>
<artifactId>eureka-sidecar</artifactId>
<version>0.0.1-SNAPSHOT</version>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.4.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-netflix-sidecar</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

applicaton.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
server:
port: 7905
spring:
application:
name: eureka-sidecar
eureka:
client:
serviceUrl:
defaultZone: http://user:password123@localhost:8761/eureka
# 通过health-uri检测异构系统是否有效
sidecar:
port: 8060
health-uri: http://localhost:8060/health.json

服务启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.ypjiao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.sidecar.EnableSidecar;

@EnableSidecar
@SpringBootApplication
public class RunSideCar {
public static void main(String[] args) {
SpringApplication.run(RunSideCar.class,args);
}
}


springCloud
https://andrewjiao.github.io/2020/03/19/springCloud/
作者
Andrew_Jiao
发布于
2020年3月19日
许可协议