SpringCloud=分布式微服务构架的一站式解决方案,是多种微服务架构落地技术的集合体,俗称微服务全家桶,是微服务开发的主流技术栈
Spring Boot 2.X 版
Spring Cloud H版
Spring Boot 与 Spring Cloud 兼容性查看
SpringBoot与SpringCloud对应的版本选择
SpringCloud Version | SpringBoot Version |
---|---|
2021.0.x aka Jubilee | 2.6.x, 2.7.x (Starting with 2021.0.3) |
2020.0.x aka Ilford | 2.4.x, 2.5.x (Starting with 2020.0.3) |
Hoxton | 2.2.x, 2.3.x (Starting with SR5) |
Greenwich | 2.1.x |
Finchley | 2.0.x |
Edgware | 1.5.x |
Dalston | 1.5.x |
选项 | 版本 |
---|---|
cloud | Hoxton.SR1 |
boot | 2.2.2.REALEASE |
cloud alibaba | 2.1.0.REALEASE |
java | java8 |
Maven | 3.5以上 |
MySQL | 5.7以上 |
<?xml version="1.0" encoding="UTF-8"?>
<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.blb</groupId>
<artifactId>spring-cloud</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>spring-cloud-payment8001</module>
</modules>
<packaging>pom</packaging>
<name>Maven</name>
<!-- FIXME change it to the project's website -->
<url>http://maven.apache.org/</url>
<inceptionYear>2001</inceptionYear>
<distributionManagement>
<site>
<id>website</id>
<url>scp://webhost.company.com/www/website</url>
</site>
</distributionManagement>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>5.1.47</mysql.version>
<druid.verison>1.1.16</druid.verison>
<mybatis.spring.boot.verison>1.3.0</mybatis.spring.boot.verison>
</properties>
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- MySql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- Druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.verison}</version>
</dependency>
<!-- mybatis-springboot整合 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.verison}</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<!-- log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.10.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<configuration>
<locales>en,fr</locales>
</configuration>
</plugin>
</plugins>
</build>
<!-- <reporting>-->
<!-- <plugins>-->
<!-- <plugin>-->
<!-- <artifactId>maven-project-info-reports-plugin</artifactId>-->
<!-- </plugin>-->
<!-- </plugins>-->
<!-- </reporting>-->
</project>
主要代码
业务层
package com.blb.controller;
import com.blb.domain.CommentResult;
import com.blb.domain.Payment;
import com.blb.service.PaymentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "支付模块")
@RestController
@RequestMapping("/payment")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@GetMapping("/text")
@ApiOperation("text")
public String text()
{
return "111";
}
@GetMapping("/listPayment")
@ApiOperation("listPayment")
public CommentResult listPayent()
{
List<Payment> paymentList = paymentService.listPayment();
return new CommentResult(200,"ok",paymentList);
}
@GetMapping("/getPaymentById/{id}")
@ApiOperation("getPaymentById")
public CommentResult getPaymentById(@PathVariable("id") long id)
{
Payment paymentById = paymentService.getPaymentById(id);
return new CommentResult(200,"ok",paymentById);
}
@PostMapping("/insertPayment")
@ApiOperation("插入支付信息")
public CommentResult insertPayment(@RequestBody Payment payment)
{
if(payment==null)
{
return new CommentResult(200,"插入信息为空",null);
}
int i = paymentService.insertPayment(payment);
return new CommentResult(200,"ok",i);
}
}
server层
package com.blb.service.imp;
import com.blb.domain.Payment;
import com.blb.mapper.PaymentMapper;
import com.blb.service.PaymentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PaymentServiceImpl implements PaymentService {
@Autowired
private PaymentMapper paymentMapper;
@Override
public List<Payment> listPayment() {
List<Payment> paymentList = paymentMapper.selectList(null);
return paymentList;
}
/**通过id查找支付信息**/
@Override
public Payment getPaymentById(long id) {
return paymentMapper.selectById(id);
}
@Override
public int insertPayment(Payment payment) {
if(payment==null)//传入对象为空
return 0;
return paymentMapper.insert(payment);
}
}
主要代码
配置类
package com.blb.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
controller层
package com.blb.coltroller;
import com.blb.domain.CommentResult;
import com.blb.domain.Payment;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apiguardian.api.API;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
@RestController
@Api(tags = "消费者订单模块")
@RequestMapping("/customer")
public class OrderController {
public static final String PRIMAL_URL="http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getPayment/{id}")
@ApiOperation("通过id得到消费信息")
public CommentResult getPayment(@PathVariable("id") long id)
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/getPaymentById/"+id,CommentResult.class);
}
@GetMapping("/listPayment")
@ApiOperation("得到消费所有信息")
public CommentResult getPayment()
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/listPayment",CommentResult.class);
}
@GetMapping("/addPayment")
@ApiOperation("插入指定订单")
public CommentResult<Payment> addPayment(Payment payment)
{
return restTemplate.postForObject(PRIMAL_URL+"/payment/insertPayment",payment,CommentResult.class);
}
}
Spring cloud封装了Netflix公司开发Eureka模块来实现服务治理
在传统的rpc远程调用框架中,管理每个服务与服务之间的依赖关系比较复杂,管理比较复杂,所有需要使用服务治理,管理服务与服务之间的依赖关系,可以实现服务调用、负载均衡、容错等,实现服务发现与注册
Eureka采用了CS的设计架构,Eureka Server作为服务功能的服务器,他就是服务注册中心,而系统中其他的微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过Eureka Server来监控系统中各个微服务是否正常的运行
在服务注册与发现中,有一个注册中心,当服务器启动的时候,会把当前服务器的信息比如:服务地址、通讯地址等以别名的方式注册到注册中心上,另一方面消费者(服务提供者),以该别名的形式去注册中心上获取到实际的服务通讯地址,然后在实现本地RPC调用RPC远程调用。框架核心设计思想在于注册中心,因为服务注册中心管理每个服务与每个服务之间的依赖关系(服务治理概念)。在任何rpc远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))
Eureka Server和Eureka Cilent
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到
是一个java客户端,用于简化EurekaServer的交互,客户端也同时具备一个内置的、使用轮询(round-join)负载算法的负载均衡器。在应用启动后,将会想Eureka Server发送心跳(默认周期为30秒)如果EurekaServer在多个心跳周期内没有接收到每个节点的心跳,EurekaServer将会从服务注册表中把这个服务节点移除(默认90秒)
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>spring-cloud</artifactId>
<groupId>com.blb</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<!--Eureka server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--引入自己通用的jar包,可以使用payment支付entity-->
<dependency>
<groupId>com.blb</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!--bootweb和autuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
server:
port: 7001
eureka:
instance:
hostname: localhost # eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #fasle表示自己端就是注册中心 我的指责是维护服务实例 并不需要去检索服务
service-url:
defaultZone : http://${eureka.instance.hostname}:${server.port}/eureka/
package com.blb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置
eureka:
client:
register-with-eureka: true # 表示自己是否注册进EurekaServer默认为true
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone : http://localhost:7001/eureka
启动类加上注解@EnableEurekaClient
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置
spring:
application:
name: cloud-consumer-order
eureka:
client:
register-with-eureka: true #表示是否将自己注册进EurekaServer 默认为true
fetchRegistry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
defaultZone: http://localhost:7001/eureka
启动类
@SpringBootApplication
@EnableEurekaClient
public class CloudConsumerOrder80 {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerOrder80.class,args);
}
}
微服务RPC远程服务调用的核心
高可用,试想你的注册中心只有一个only one,如果他出故障了会导致整个服务环境不可用,解决办法是搭建Eureka注册中心集群,实现负载均衡+故障容错(互相注册,互相守望)
使用方法
在原来项目的基础上上一个名为cloud-eureka-server7002的module,内容和cloud-eureka-server7001内容一样
7001中yaml的配置为
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #fasle表示自己端就是注册中心 我的指责是维护服务实例 并不需要去检索服务
service-url:
# defaultZone : http://eureka7002.com:7002/eureka/
defaultZone : http://eureka7002.com:7002
7002中yml配置为
server:
port: 7002
eureka:
instance:
hostname: eureka7002.com # eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #fasle表示自己端就是注册中心 我的指责是维护服务实例 并不需要去检索服务
service-url:
# defaultZone : http://eureka7001.com:7001/eureka/
defaultZone : http://eureka7001.com:7001
在C:\Windows\System32\drivers\etcwe文件下的hosts文件改配置
增加以下两行配置
修改yml,增加eureka的服务地址
server:
port: 8002
spring:
application:
name: cloud-payment-service #微服务的名称
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/class?serverTimezone=UTC&useSSL=false
username: root
password: root
mybatis:
configuration:
map-underscore-to-camel-case: true
eureka:
client:
register-with-eureka: true # 表示自己是否注册进EurekaServer默认为true
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
# defaultZone : http://localhost:7001/eureka
defaultZone : http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
spring-cloud-payment8001的修改和spring-cloud-payment8002修改一样,改yml文件
server:
port: 80
spring:
application:
name: cloud-consumer-order
eureka:
client:
register-with-eureka: true #表示是否将自己注册进EurekaServer 默认为true
fetchRegistry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
# defaultZone: http://localhost:7001/eureka
defaultZone : http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
package com.blb.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ApplicationContextConfig {
@Bean
@LoadBalanced //赋予了RestTemplate负载均衡的能力
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
根据eureka修改访问路径
OrderVController中进行修改
@RestController
@Api(tags = "消费者订单模块")
@RequestMapping("/customer")
public class OrderController {
// public static final String PRIMAL_URL="http://localhost:8001";
public static final String PRIMAL_URL="http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/getPayment/{id}")
@ApiOperation("通过id得到消费信息")
public CommentResult getPayment(@PathVariable("id") long id)
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/getPaymentById/"+id,CommentResult.class);
}
@GetMapping("/listPayment")
@ApiOperation("得到消费所有信息")
public CommentResult getPayment()
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/listPayment",CommentResult.class);
}
@GetMapping("/addPayment")
@ApiOperation("插入指定订单")
public CommentResult<Payment> addPayment(Payment payment)
{
return restTemplate.postForObject(PRIMAL_URL+"/payment/insertPayment",payment,CommentResult.class);
}
}
保护模式主要用于一组客户端和Eureka Server之间存在网络分区场景下的保护,一旦进入保护模式,Eureka Server将会尝试保护器注册表中的信息,不再删除服务注册表中的数据,也就是不会注销任何微服务
某时刻某一个微服务不可用了,Eureka不会立即清理,依旧会对该服务信息进行保存,属于CAP里面的AP分支
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com # eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己
fetch-registry: false #fasle表示自己端就是注册中心 我的指责是维护服务实例 并不需要去检索服务
service-url:
# defaultZone : http://eureka7002.com:7002/eureka/
defaultZone : http://eureka7002.com:7002
server:
# 关闭自我保护机制,保证不可用服务及时删除
enable-self-preservation: false
eviction-interval-timer-in-ms: 2000
server:
port: 8001
spring:
application:
name: cloud-payment-service #微服务的名称
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/class?serverTimezone=UTC&useSSL=false
username: root
password: root
mybatis:
configuration:
map-underscore-to-camel-case: true
eureka:
client:
register-with-eureka: true # 表示自己是否注册进EurekaServer默认为true
fetch-registry: true # 是否从EurekaServer抓取已有的注册信息 默认为true 单节点为所谓 集群必须设置为true才能配合ribbon使用负载均衡
service-url:
# defaultZone : http://localhost:7001/eureka
defaultZone : http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
instance:
instance-id: payment8001 #修改服务名称
prefer-ip-address: true # ip显示
# erureka客户端向服务器发送心跳的时间间隔,单位为秒(默认为30秒)
lease-renewal-interval-in-seconds: 1
# Eureka服务端在收到最后一次心跳后等到时间上限,单位为秒(默认30秒),超时将删除服务
lease-expiration-duration-in-seconds: 2
Spring Cloud Ribbon是基于Netfix Ribbon实现的一套客户端 负载均衡的工具
简单的说,Ribbon 是Netfix发布的开源项目,主要的功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项如:超时连接,重试等。简单的说,就是在配置文件中列出Load Balance(简称LB)后面所有的机器,Ribbon会自动帮助你基于某种规则(如:轮询,随机连接等)、去连接这些机器,我们很容易使用Ribbon实现自定义的负载均衡算法
Ribbon已经进入维护模式!未来的替换方案:Load Banlancer
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)
常见的负载均衡有软件 Nginx LVS 硬件F5等
Nginx是服务器负载均衡,客户端所有请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的
Ribbon 本地负载均衡,再调用微服务接口时候,会在注册中心上获取注册信息服务列表之后缓存到JVM本地,从而在本地实现RPC远程服务调用技术
集中式LB:即在服务的消费方和提供方之间使用独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。
将LB逻辑集成到消费方,消费方从服务注册中心或知有哪些地址可用,然后自己再从这些地址中选择出一个何时的服务器。
Ribbon就属于是进程内LB, 它只是一个类库,集成与消费方进程,消费方通过它来获取到服务提供方的地址。
总结:Ribbon其实就是一个软负载均衡的客户端组件。
它可以和其他所需请求的客户端结合使用,和eureka结合只是其中一个实例
Ribbon在工作时分为两步:
Eureka Client 会自己带着Ribbon 所以不需要添加Ribbon依赖
getForObject() // 返回对象为响应体中数据转化成的对象,基本上可以理解为JSON
getForEntity() // 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等。
getForObject
public static final String PRIMAL_URL="http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
//返回对象为响应体中数据转化成的对象,基本上可以理解为json
@GetMapping("/getPayment/{id}")
@ApiOperation("通过id得到消费信息")
public CommentResult getPayment(@PathVariable("id") long id)
{
return restTemplate.getForObject(PRIMAL_URL+"/payment/getPaymentById/"+id,CommentResult.class);
}
getForEntity
public static final String PRIMAL_URL="http://CLOUD-PAYMENT-SERVICE";
@Autowired
private RestTemplate restTemplate;
// 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等。
@GetMapping("/getForEntity/{id}")
@ApiOperation("通过id得到实体类")
public CommentResult<Payment> getpayment(@PathVariable("id") long id)
{
// System.out.println(id);
// 返回对象为ResponseEntity对象,包含了响应中的一些重要信息,比如响应头,响应状态码,响应体等。
ResponseEntity<CommentResult> entity = restTemplate.getForEntity(PRIMAL_URL + "/payment/getPaymentById/"+id, CommentResult.class);
//判断是否成功
if(entity.getStatusCode().is2xxSuccessful())
{
return entity.getBody();
}
else
return new CommentResult<>(400,"操作失败");
}
@Configuration
public class MySelfRule {
@Bean
public IRule myRule()
{
return new RandomRule();
}
}
负载均衡算法:rest接口第几次请求数 % 服务器集群总数量 = 实际调用服务器位置下标,每次服务重启后rest接口计数从1开始。
List<ServiceInstance> instances = doscoveryClient.getInstances("cloud-payment-service");
List[0] instances = 127.0.0.1:8002;
List[1] instances = 127.0.0.1:8001;
8001 + 8002 组合为集群,他们共计2台机器,集群总数为2,按照轮询算法原理:
当总请求数为1时:1 % 2 = 1,对应下标为 1,则获得服务地址为 127.0.0.1:8001
当总请求数为2时:2 % 2 = 0,对应下标为 0,则获得服务地址为 127.0.0.1:8002
当总请求数为3时:3 % 2 = 1,对应下标为 1,则获得服务地址为 127.0.0.1:8001
当总请求数为4时:4 % 2 = 0,对应下标为 0,则获得服务地址为 127.0.0.1:8002
如此类推
Feign是一个声明式WebService客户端,使用Feign能让编写Web Service客户端更加简单,只需要创建一个接口并添加注解即可
他的使用方法时定义一个服务接口然后在上面添加注解,Feign也支持可插拔式的编码器和解码器。Spring Cloud 对Feign进行了封装。使其支持了SpringMVC 标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
Feign旨在使编写Java Http客户端变得更加容易。
前面在使用Ribbon + RestTemplate时,利用RestTemplate 对http请求的封装处理,形成一套模板化的调用方法,但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多出调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务的调用。所以,Feign在此基础上做了进一步的封装,由它来帮助我们定义和实现依赖服务接口的定义。在Feign的实现下,我们只需要创建一个接口并使用注解的方式来配置它(以前是Dao接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可) 即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
利用Ribbon维护了 [payment]的服务列表信息,并且通过轮询实现了客户端的负载均衡,而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,简单而优雅的实现了服务调用。
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>com.blbgroupId>
<artifactId>spring-cloudartifactId>
<version>1.0-SNAPSHOTversion>
<relativePath/>
parent>
<groupId>com.blbgroupId>
<artifactId>cloud-consumer-feign-order80artifactId>
<version>0.0.1-SNAPSHOTversion>
<name>cloud-consumer-feign-order80name>
<description>cloud-consumer-feign-order80description>
<properties>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.blbgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.10.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.10.0version>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
server:
port: 80
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
register-with-eureka: false
spring:
application:
name: cloud-feign-consumer
# 设置 feign 客户端超时时间(OpenFeign 默认支持 ribbon)
ribbon:
# 值的是建立连接所用的时间,使用与网络状态正常的情况,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectionTimeout: 5000
logging:
level:
com.blb.cloudconsumerfeignorder80.service.PaymentFeignService: debug
@SpringBootApplication
@EnableEurekaClient
// name 写要负载均衡访问的provider的微服务名字
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class CloudConsumerOrder80 {
public static void main(String[] args) {
SpringApplication.run(CloudConsumerOrder80.class,args);
}
}
@Component
//@Service
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/getPaymentById/{id}")
@ApiOperation("getPaymentById")
public CommentResult getPaymentById(@PathVariable("id") long id);
}
5.2 写controller
provider的controller这里声明的方法 要和 远程调用服务接口中的 方法签命保持一致
@Autowired
private PaymentFeignService paymentFeignService;
@GetMapping("/consumer/payment/get/{id}")
public CommentResult getPaymentById(@PathVariable("id")long id )
{
return paymentFeignService.getPaymentById(id);
}
默认Feign客户端只等待一秒钟,但是服务段处理需要超过1秒钟,导致Feign客户端不想等待了,直接返回报错。
为了避免这种请况,有时候我们需要设置Feign客户端的超时控制
在yml中配置
# 设置 feign 客户端超时时间(OpenFeign 默认支持 ribbon)
ribbon:
# 值的是建立连接所用的时间,使用与网络状态正常的情况,两端连接所用的时间
ReadTimeout: 5000
# 指的是建立连接后从服务器读取到可用资源所用的时间
ConnectionTimeout: 5000
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。
说白了就是:对Feign接口的调用情况进行监控和输出。
NONE | 默认的,不显示任何日志 |
---|---|
BASIC | 仅记录请求方法、URL、响应状态码及执行时间 |
HEADERS | 除了BASIC中定义的信息之外,还有请求和响应的头信息 |
FULL | 除了HEADERS中定义的信息外,还有请求和响应的正文及元数据。 |
@Configuration
public class FeignConfig {
@Bean
Logger.Level feignLoggerLever()
{
return Logger.Level.FULL;
}
}
logging:
level:
# feign 日志以什么级别监控哪个接口
com.blb.cloudconsumerfeignorder80.service.PaymentFeignService: debug
复杂分布式体系机构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的“扇出”如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”。
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和,比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
通常当你发现一个模块下某个实例失败后,这时候这个模块依然还会接收流量,然而这个有问题的模块还调用了其他模块,这样就会发生级联故障,或者叫雪崩。
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时,异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
断路器本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
服务器繁忙,请稍后再试,不让客户端等待并立刻返回一个好友提示。fallback
哪些情况会触发降级:
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示 break
就是保险丝 : 服务的降级—>进而熔断—>恢复调用链路
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行 flowlimit
搭建基础平台:从正确—>错误—>降级熔断—>恢复
以此平台 演示 Hystrix 服务降级 服务熔断 服务限流
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.blbgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 8001
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
spring:
application:
name: cloud-provider-hystrix-payment
@RestController
@Slf4j
@Api(tags = "hystrix测试")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
@ApiOperation("正常")
public String paymentInfoOk(@PathVariable("id") int id)
{
String result = paymentService.paymentInfo_ok(id);
log.info(result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
@ApiOperation("超时")
public String paymentInfoTimeOut(@PathVariable("id") int id) throws InterruptedException {
Thread.sleep(3000);
String result = paymentService.paymentInfo_timeOut(id);
log.info(result);
return result;
}
}
@Service
public class PaymentServiceImpl implements PaymentService {
/**
* 正常访问
* @param id
* @return
*/
@Override
public String paymentInfo_ok(Integer id) {
return "线程次: "+Thread.currentThread().getName()+" paymentInfo_ok"+id;
}
/**
* 时间超时
* @param id
* @return
*/
@Override
public String paymentInfo_timeOut(Integer id) {
return "线程次: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id;
}
}
能够测试通过,但是访问 /test/hystrix/timeout/1 时会等待3秒钟
在上诉测试非高并发的情形下还能勉强满足,但是当我们开启Jmeter,来20000个并发压死8001,2000个请求都去访问/payment/hystrix/timeout,浏览器转圈圈
以上还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端80直接被拖死
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>com.blbgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 80
eureka:
client:
register-with-eureka: false
# fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka
@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") int id);
@GetMapping("`/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") int id);
}
@RestController
public class OrderHystrixController {
@Autowired
private PaymentService paymentService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
private String hystrisOk(@PathVariable("id") Integer id)
{
String infoOk = paymentService.paymentInfoOk(id);
return infoOk;
}
@GetMapping("/consumer/payment/hystrix/timeOk/{id}")
private String hystrisTomeOut(@PathVariable("id") Integer id)
{
String infoOk = paymentService.paymentInfoTimeOut(id);
return infoOk;
}
}
测试发现的问题:8081同一层次的其他接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕,80此时调用8081,客户端访问响应缓慢
对cloud-provider-hystrix-payment80进行改造
@Service
public class PaymentServiceImpl implements PaymentService {
/**
* 正常访问
* @param id
* @return
*/
@Override
public String paymentInfo_ok(Integer id) {
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_ok"+id+"o(n_n)o哈哈";
}
/**
* 时间超时
* 设置自身调用超时时间的峰值,峰值内可以正常运行,如果在峰值外就执行备用方法 做降级服务fallback
* 如果不设置峰值的话,直接在方法里抛出 10 / 0 by zero 异常 也会调用备用方法(异常也会调用fallback方法)
* 设置时间为3s而方法延时到5s会调用fallbackMethod
* 一旦调用服务方法失败并抛出错误信息后,会自动调用@HystrixCommand(标注好的fallbackMethod调用类中指定的方法
* @param id
* @return
*/
@HystrixCommand(fallbackMethod ="paymentInfo_timeOutHandler",
commandProperties={
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
})
@Override
public String paymentInfo_timeOut(Integer id) {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id+"o(n_n)o哈哈";
}
public String paymentInfo_timeOutHandler(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id+"o(u_u)o呜呜";
}
}
@EnableEurekaClient
@SpringBootApplication
@EnableCircuitBreaker
public class CloudProviderHystrixPayment8001Application {
public static void main(String[] args) {
SpringApplication.run(CloudProviderHystrixPayment8001Application.class, args);
}
}
解决问题:
解决了每个方法都需要有兜底的方法的痛处
解决了兜底方法和业务逻辑混在一起的问题
解决每个方法都要有兜底方法问题
@DefaultProperties(defaultFallback = “方法名”)
标注在类上,表示没有指定@HystrisCommand(fallbackMethod=“方法名”)的方法就是用@DefaultProperties(defaultFallback=“方法名”)所指定的做备用方法。
注意:就算使用全局降级配置 也需要在方法上添加@HystrisCommand 注解
1:1每个方法配置一个服务降级的方法,技术上可以,实际中不可用
1:N 除了个别重要核心业务有专属,其他普通的可以通过@DefaultProperties(defaultFallback= “”)统一跳转到统一处理结果页面
@Service
@DefaultProperties(defaultFallback = "error")
public class PaymentServiceImpl implements PaymentService {
/**
* 正常访问
* @param id
* @return
*/
@Override
public String paymentInfo_ok(Integer id) {
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_ok"+id+"o(n_n)o哈哈";
}
//使用指定的降级服务
// @HystrixCommand(fallbackMethod ="paymentInfo_timeOutHandler",
// commandProperties={
// @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
// })
//使用全局的降级服务
@HystrixCommand
@Override
public String paymentInfo_timeOut(Integer id) {
System.out.println(1/0);
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id+"o(n_n)o哈哈";
}
public String paymentInfo_timeOutHandler(Integer id)
{
return "线程池: "+Thread.currentThread().getName()+" paymentInfo_TimeOut"+id+"o(u_u)o呜呜";
}
public String error()
{
return "报错了";
}
}
本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系
只需要为Feign客户端定义的远程调用接口添加一个服务降级处理的实现类即可实现解耦合
未来面临的异常
运行
超时
宕机
对cloud-consumer-feign-hystrix-orser80进行改造
@Service
public class PaymentServiceImpl implements PaymentService {
@Override
public String paymentInfoOk(int id) {
return "调用服务器出现异常";
}
@Override
public String paymentInfoTimeOut(int id) {
return "调用服务器出现异常";
}
}
@Service
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentServiceImpl.class)
public interface PaymentService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfoOk(@PathVariable("id") int id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfoTimeOut(@PathVariable("id") int id);
}
熔断器:一句话就是家里的保险丝
熔断机制式应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,自动恢复调用链路。
熔断状态: 开启 关闭 半开启
在SpringCloud框架中,熔断机制通过Hystrix实现Hystrix会监控微服务间调用的状况。
当失败的调用到一定阈值,缺省时5秒内20此调用失败,就会启动熔断机制,熔断机制的注解是@HystrixCommand
大神论文:https://martinfowler.com/bliki/CircuitBreaker.html
//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",
commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60")//失败率达到多少后跳闸
})
public String paymentCircuitBreaker(Integer id)
{
if(id<0)
{
throw new RuntimeException("*******id不为负数");
}
String s = IdUtil.simpleUUID();
return Thread.currentThread().getName()+"\t"+"调用成功,流水号"+s;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id")Integer id)
{
return id+"为负数,请稍后在试";
}
//服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id")Integer id)
{
String s = paymentService.paymentCircuitBreaker(id);
log.info(s);
return s;
}
测试
在web页面 输入参数为负数会进入到 兜底方法,如果输入负数的次数 【在一个10秒钟的窗口期,如果有10个请求 60%都失败了就熔断】符合这个要求,那么就会触发熔断机制,然后你再输入正数都不会执行成功了!慢慢的他自己会检测到后台输入好几个正数了,就会自动关闭熔断
快照时间窗:
断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
请求总数阈值:
在快照时间窗内,必须满足请求总数阈值才有资格熔断,默认为20,意味着在10秒内,如果该Hystrix命令的调用次数不足20次,即使所有的请求都超时或者其他原因失败了,断路器都不会打开。
错误百分比阈值:
当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阈值情况下,这时候就会将断路器打开。
断路器开始或者关闭条件
当满足一定的阈值的时候(默认是10秒内超过20个请求次数)
当失败率达到一定的时候(默认10秒内超过50%的请求失败)
到达以上阈值,断路器将会开启
当开启的时候,所有的请求都不会进行转发。
一段时间后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发,如果成功,断路器关闭,如果失败,继续开启
断路器打开之后
再有请求调用的时候,将不会调用主逻辑,而是直接调用降级的fallback方法,通过断路器,实现了自动的发现错误并将降级逻辑升级为主逻辑,减少响应延迟的效果。
原来的主逻辑要如何恢复?
对于这一问题mhystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑。
当休眠时间窗到期,断路器将进入半开状态,释放给一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合。
主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard)Hystrix会持续的记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等,Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控,Spring Cloud提供了Hystrix Dashboard的整合,对监控内容转化成可视化页面。
Hystrix 做服务监控还需要创建一个模块,而阿里巴巴的sentinel 直接给你要给网站就能使用
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
<version>3.0.2version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 9001
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
spring:
application:
name: cloud-consumer-hystrix-dashboard
@SpringBootApplication
@EnableHystrixDashboard
public class CouldConsumerHystrixDashboard9001Application {
public static void main(String[] args) {
SpringApplication.run(CouldConsumerHystrixDashboard9001Application.class, args);
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
/**
*此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
*ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
*只要在自己的项目里配置上下面的servlet就可以了
*/
@Bean
public ServletRegistrationBean getServlet() {
HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
registrationBean.setLoadOnStartup(1);
registrationBean.addUrlMappings("/hystrix.stream");
registrationBean.setName("HystrixMetricsStreamServlet");
return registrationBean;
}
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用Zuul网关;但在2.x版本中,zuul的升级就是一直跳票,SpringCloud最后自己研发了一个网关代替Zuul那就是 SpringCloud Gateway ,gateway是zuul 1.x版本的替代。
Gateway是在Spring生态系统之上架构的API网关服务,基于Spring 5,Spring Boot2 和Project Reactor技术。Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等。
SpringCloud Gateway作为Spring cloud生态系统中的网关,目标是代替 Zuul,在SpringCloud2.0以上版本中,没有对新版本Zuul 2.0以上实现最新高性能版本进行集成,仍然还是使用的Zuul 1.x非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty,【说穿了就SpringCloud Gateway是异步非阻塞式】
springcloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关的基本功能,例如:安全,监控/指标,和限流
springcloud Gateway使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
在SpringCloud Finchley 正式版之前(现在H版),SpringCloud推荐的网关是Netflix提供的zuul。
Zuul1.x 是一个基于阻塞 I/O的API网关
Zuul1.x 基于Servlet2.5使用阻塞架构它不支持任何长连接 (如WebSocket)Zuul的设计模式和Nginx较像,每次I/O操作都是从工作线程中选择一个执行,请求线程被阻塞到工作线程完成,但是差别是Nginx用C++实现,Zuul用java实现,而JVM本身会有第一次加载较慢的情况,使得Zuul的性能相对较差。
Zuul 2.x理念更加先进,像基于Netty非阻塞和支持长连接,但SpringCloud目前还没有整合。Zuul2.x的性能较Zuul 1.x有较大的提升。在性能方面,根据官方提供的基准测试,Spring Cloud Gateway的RPS(每秒请求次数)是Zuul的1.6倍。
Spring Cloud Gateway建立在Spring Framework 5、project Reactor和Spring Boot2 之上,使用非阻塞API
Spring Cloud Gateway 还支持WebSocket,并且与Spring紧密集成拥有更好的开发体验。
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.blbgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>1.0-SNAPSHOTversion>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrixartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://eureka7001.com:7001/eu
@SpringBootApplication
@EnableEurekaClient
public class CloudGetway9527Application {
public static void main(String[] args) {
SpringApplication.run(CloudGetway9527Application.class, args);
}
}
cloud-provider-payment8001看看controller的访问地址,以getPaymentById和listPayment为例,因为目前不想暴露8001端口,希望我8001外面套一层9527
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/getPaymentById/** #断言,路径相匹配的进行路由
# 上面表示 如果要访问http://localhost:8001/payment/getPaymentById/**需要
# http://localhost:9527//payment/getPaymentById/**
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/listPayment #断言,路径相匹配的进行路由
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: h
分别启动7001、8001、9527网关
添加网关之前可以访问:http://localhost:8001·/payment/listPayment
添加网关之后可以访问:http://localhost:9527/payment/listPayment
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/getPaymentById/** #断言,路径相匹配的进行路由
# 上面表示 如果要访问http://localhost:8001/get/payment/** 需要
# http://localhost:9527/get/payment/**
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/listPayment #断言,路径相匹配的进行路由
@Configuration
public class GatewayConfig {
/**
* 配置了一个名为path_route_springcloud的路由规则
* 当访问地址为http://localhost9527/guonei是自动转发到地址http://news.baidu.com/guonei
* @param builder
* @return
*/
@Bean
public RouteLocator customerRouteLocator(RouteLocatorBuilder builder)
{
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("path_route_springcloud",r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
默认情况下Gateway会根据注册中心注册的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
开启动态路由:java spring.cloud.gateway.discovery.locator.enabled:true;
在添加uri的时候,开头是 lb://微服务名
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启冲注册中心动态‘创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: lb://CLOUD-PAYMENT-SERVICE
predicates:
- Path=/payment/getPaymentById/** #断言,路径相匹配的进行路由
# 上面表示 如果要访问http://localhost:8001/get/payment/** 需要
# http://localhost:9527/get/payment/**
- id: payment_routh2
# uri: http://localhost:8001
uri: lb://CLOUD-PAYMENT-SERVICE
predicates:
- Path=/payment/listPayment #断言,路径相匹配的进行路由
eureka:
client:
fetch-registry: true
register-with-eureka: true
service-url:
# defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
defaultZone: http://eureka7001.com:7001/eureka
测试
分别启动7001、Payment8001、Payment8002、9527网关
因为开启了8001和8002两个端口,所以网关负载均衡的效果是 8001/8002切换
实现了路由的切换
gateway启动时打印的信息
Spring Cloud Gateway 将路由匹配作为Spring WebFlux Handler Mapping基础架构的一部分。
Spring Cloud Gateway 包括许多内置的Route Predicate 工厂,所有的这些Predicate都和Http请求的不同属性匹配,多个Route Predicate可以进行组合。
Spring Cloud Gateway 创建route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给Route,SpringCloud Gateway包含许多内置的Route Predicate Factories.
所有的 这些谓词都匹配Http的请求的各种属性,多种谓词工厂可以组合,并通过逻辑and
常用的Route Predicate
java After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
java After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
java Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
java Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
java Cookie=username,atguigu #并且Cookie是username=zhangshuai才能访问
Host=**.atguigu.com
Method=GET
Query=username, \d+ #要有参数名称并且是正整数才能路由
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
#- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
#- Cookie=username,zhangshuai #并且Cookie是username=zhangshuai才能访问
#- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Method=GET
#- Query=username, \d+ #要有参数名称并且是正整数才能路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
过滤器可用于修改进入HTTP请求和返回的HTTP响应,路由过滤器只能在指定路由进行使用
SpringCloud Gateway内置了多种路由过滤器,他们都是由GatewayFilter的工厂类来产生
Filter声明周期
pre、post
filter种类
GatewayFilter、GlobalFilter
impiemerts GlobalFilter,Ordered
全局日志记录
统一网关鉴定
@Component
@Slf4j
public class GlobalFilter implements Ordered, org.springframework.cloud.gateway.filter.GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String username = exchange.getRequest().getQueryParams().getFirst("username");
if(StringUtils.isEmpty(username)){
log.info("*****用户名为Null 非法用户,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
}
微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能够运行,所以一套集中式的,动态的配置管理设施是必不可少的
SpringCloud Config为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置
SpringCloud Config 分为服务端和客户端两部分。
服务端也成为分布式配置中心,他是一个独立的微服务应用,用来连接配置服务器并未客户端提供获取配置信息,加密/解密等信息访问接口.
客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用git来存储配置信息,这样既有助于对环境配置进行版本管理,并且可以通过git客户端来方便的管理和访问配置内容。