https://www.martinfowler.com/articles/microservices.html 微服务microservices
http://blog.cuicc.com/blog/2015/07/22/microservices/
In short(简言之), the microservice architectural style 【架构风格】[1] is an approach to developing a single application as a suite of small services【独立应用变成一套小服务】, each running in its own process and communicating with lightweight(轻量级沟通) mechanisms(每一个都运行在自己的进程内(容器)), often an HTTP resource API(用HTTP,将功能写成能接受请求). These services are built around business capabilities (独立业务能力)and independently deployable by fully automated deployment machinery(应该自动化独立部署). There is a bare minimum of centralized management of these services(应该有一个能管理这些服务的中心), which may be written in different programming languages (独立开发语言)and use different data storage technologies(独立的数据存储)
《分布式系统原理与范型》定义:“分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统”。
分布式系统(distributed system)是建立在网络之上的软件系统。
集群指的是将几台服务器集中在一起,实现同一业务。
分布式中的每一个节点,都可以做集群。 而集群并不一定就是分布式的。
单一应用架构
当网站流量很小时,只需一个应用,将所有功能都部署在一起,以减少部署节点和成本。此时,用于简化增删改查工作量的数据访问框架(ORM)是关键。
垂直应用架构
当访问量逐渐增大,单一应用增加机器带来的加速度越来越小,将应用拆成互不相干的几个应用,以提升效率。此时,用于加速前端页面开发的Web框架(MVC)是关键。
分布式服务架构
当垂直应用越来越多,应用之间交互不可避免,将核心业务抽取出来,作为独立的服务,逐渐形成稳定的服务中心,使前端应用能更快速的响应多变的市场需求。此时,用于提高业务复用及整合的分布式服务框架(RPC)是关键。
流动计算架构
当服务越来越多,容量的评估,小服务资源的浪费等问题逐渐显现,此时需增加一个调度中心基于访问压力实时管理集群容量,提高集群利用率。此时,用于提高机器利用率的资源调度和治理中心(SOA)是关键。
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。
它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。
即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
RPC
Netty(Socket)+自定义序列化
RestAPI (严格来说,SpringCloud是使用Rest方式进行服务之间交互的,不属于RPC)
HTTP+JSON
服务集群部署
数据库主从+双机热备
主-备方式(Active-Standby方式)
主-备方式即指的是一台服务器处于某种业务的激活状态(即Active状态),另一台服务器处于该业务的备用状态(即Standby状态)。
双主机方式(Active-Active方式)
双主机方式即指两种不同业务分别在两台服务器上互为主备状态(即Active-Standby和Standby-Active状态)
保存某个服务所在地址等信息,方便调用者实时获取其他服务信息
服务注册
服务提供者
服务发现
服务消费者
动态将请求派发给比较闲的服务器
策略:
轮询(Round Robin)
加权轮询(Weighted Round Robin)
随机Random
哈希Hash
最小连接数LC
最短响应时间LRT
服务之间复杂调用,一个服务不可用,导致整个系统受影响不可用
某个服务频繁超时,直接将其短路,快速返回mock(模拟/虚拟)值
限制某个服务每秒的调用本服务的频率
API网关要做很多工作,它作为一个系统的后端总入口,承载着所有服务的组合路由转换等工作,除此之外,我们一般也会把安全,限流,缓存,日志,监控,重试,熔断等放到 API 网关来做
追踪服务的调用链,记录整个系统执行请求过程。如:请求响应时间,判断链中的哪些服务属于慢服务(可能存在问题,需要改善)。
Elastic Compute Service(ECS)弹性计算服务
动态扩容,压榨服务器闲时能力
例如:双11,618,高峰时多配置些服务器,平时减少多余的服务器配置(用于其他服务应用),避免资源浪费
物联网( IoT ,Internet of things )即“万物相连的互联网”,是互联网基础上的延伸和扩展的网络,将各种信息传感设备与互联网结合起来而形成的一个巨大网络,实现在任何时间、任何地点,人、机、物的互联互通。
Breaker dashboard 断路器仪表板
Distributed Tracing分布式跟踪 (分布式处理程序链跟踪用于监视网络等待时间,并可视化通过微服务的请求流)
https://docs.spring.io/spring-boot/docs/2.2.2.RELEASE/reference/htmlsingle/
https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/
英文困难的同学,也不耽误学习的
https://www.bookstack.cn/read/spring-cloud-docs/docs-index.md
SpringBoot2.X版和SpringCloud H版
SpringCloud Alibaba 2.1
git源码地址:
https://github.com/spring-projects/spring-boot/releases/
SpringBoot2.0新特性:
https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.0-Release-Notes
通过上面官网发现,Boot官方强烈建议你升级到2.X以上版本
springboot(截至2020.4.4)
git源码地址: https://github.com/spring-projects/spring-cloud/wiki
官网: https://spring.io/projects/spring-cloud
官网看Cloud版本
Cloud命名规则
Spring Cloud采用了英国伦敦地铁站的名称来命名,并由地铁站名称字母A-Z依次类推的形式来发布迭代版本。
Spring Cloud 是一个由许多子项目组成的综合项目,各子项目有不同的发布节奏。为了管理SpringCloud与各子项目的版本依赖关系,发布了一个清单,其中包括了某个SpringCloud版本对应的子项目版本。为了避免SpringCloud版本号与子项目版本号混淆,SpringCloud版本采用了名称而非版本号的命名,这些版本的名字采用了伦敦地铁站的名字,根据字母表的顺序来应对版本时间顺序。例如Angel是第一个版本,Brixton是第二个版本。当SpringCloud的发布内容积累到临界点或者一个重大BUG被解决后,会发布一个"service releases"版本,简称SRX版本,比如Greenwich.SR2就是SpringCloud发布的Greenwich版本的第二个SRX版本。
SpringCloud(截至2020.4.4)
https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/
最新版本对应关系:截止2020年4月初
https://cloud.spring.io/spring-cloud-static/Hoxton.SR3/reference/html/spring-cloud.html
更详细的版本对应查看方法: https://start.spring.io/actuator/info
o cloud
• Hoxton.SR1
o boot
• 2.2.2.RELEASE
o cloud Alibaba
• 2.1.0.RELEASE
o java
• JAVA8
o maven
• 3.5及以上
o mysql
• 5.7及以上
<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>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud2020artifactId>
<version>1.0-SNAPSHOTversion>
<packaging>pompackaging>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<junit.version>4.12junit.version>
<log4j.version>1.2.17log4j.version>
<lombok.version>1.16.18lombok.version>
<mysql.version>5.1.47mysql.version>
<druid.version>1.1.16druid.version>
<mybatis.spring.boot.version>1.3.0mybatis.spring.boot.version>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>2.2.2.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR1version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>${mysql.version}version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>${druid.version}version>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>${mybatis.spring.boot.version}version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>${junit.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>${log4j.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<version>${lombok.version}version>
<optional>trueoptional>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<fork>truefork>
<addResources>trueaddResources>
configuration>
plugin>
plugins>
build>
project>
Maven中的dependencyManagement和dependencies区别
maven中跳过单元测试【可选】
<modules>
<module>cloud-provider-payment8001</module>
</modules>
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-provider-payment8001artifactId>
<dependencies>
<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.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druid-spring-boot-starterartifactId>
<version>1.1.10version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
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>
project>
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/cloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:/mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}
CREATE DATABASE IF NOT EXISTS cloud2020 DEFAULT CHARACTER SET utf8 ;
USE cloud2020 ;
DROP TABLE IF EXISTS payment ;
CREATE TABLE payment (
id BIGINT (20) NOT NULL AUTO_INCREMENT COMMENT 'ID',
SERIAL VARCHAR (300) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE = INNODB AUTO_INCREMENT = 33 DEFAULT CHARSET = utf8 ;
INSERT INTO payment (id, SERIAL) VALUES(31, '尚硅谷001'),(32, 'atguigu002') ;
package com.atguigu.springcloud.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private Long id;
private String serial;
}
package com.atguigu.springcloud.entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult <T> implements Serializable{
private Integer code;
private String message;
private T data;
public CommonResult(Integer code,String message){
this(code,message,null);//如果这行报错,请安装lombok插件
}
}
https://www.projectlombok.org/
@Data:提供getter/setter
@NoArgsConstructor, 无参构造器 @RequiredArgsConstructor @AllArgsConstructor 全参数构造器
@EqualsAndHashCode:提供equals和hashCode方法
@Getter/@Setter
@Slf4j 内置log对象,直接调用日志方法输出日志
package com.atguigu.springcloud.dao;
import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Component //代替@Repository声明bean
@Mapper //mybatis提供的,等价:@MapperScan("com.atguigu.springcloud.dao")
//@Repository //spring提供的。在此,只是为了声明bean对象
public interface PaymentDao {
public int create(Payment payment);
public Payment getPaymentById(@Param("id") Long id);
}
src\main\resources\mapper\PaymentMapper.xml
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.springcloud.dao.PaymentDao">
<insert id="create" useGeneratedKeys="true" keyProperty="id">
insert into payment(serial) values(#{serial});
insert>
<resultMap id="BaseResultMap" type="com.atguigu.springcloud.entities.Payment">
<id column="id" property="id" jdbcType="BIGINT">id>
<result column="serial" property="serial" jdbcType="VARCHAR">result>
resultMap>
<select id="getPaymentById" parameterType="Long" resultMap="BaseResultMap">
select * from payment where id=#{id}
select>
mapper>
package com.atguigu.springcloud.service;
import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;
public interface PaymentService {
public int create(Payment payment); //写
public Payment getPaymentById(Long id); //读取
}
package com.atguigu.springcloud.service.impl;
import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class PaymentServiceImpl implements PaymentService {
@Resource
//@Autowired
private PaymentDao paymentDao;
public int create(Payment payment){
return paymentDao.create(payment);
}
public Payment getPaymentById( Long id){
return paymentDao.getPaymentById(id);
}
}
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@PostMapping(value = "/payment/create")
public CommonResult<Payment> create(Payment payment){ //埋雷
int result = paymentService.create(payment);
log.info("*****插入结果:"+result);
if (result>0){ //成功
return new CommonResult(200,"插入数据库成功",result);
}else {
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping(value = "/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
Payment payment = paymentService.getPaymentById(id);
log.info("*****查询结果:"+payment);
if (payment!=null){ //说明有数据,能查询成功
return new CommonResult(200,"查询成功",payment);
}else {
return new CommonResult(444,"没有对应记录,查询ID:"+id,null);
}
}
}
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
下一段配置黏贴到父工程当中的pom里
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<configuration>
<fork>truefork>
<addResources>trueaddResources>
configuration>
plugin>
plugins>
build>
Ctrl+Shift+Alt+/选择Registry…
compiler.automake.allow.when.app.running -> 自动编译
compile.document.save.trigger.delay -> 自动更新文件;它主要是针对静态文件如JS CSS的更新,将延迟时间减少后,直接按F5刷新页面就能看到效果!
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-consumer-order80artifactId>
<dependencies>
<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>
project>
server:
port: 80
spring:
application:
name: cloud-consumer-order80
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
(将cloud-provider-payment8001工程下的entities包下的两个实体类复制过来)
是什么
RestTemplate提供了多种便捷访问远程Http服务的方法,是一种简单便捷的访问Restful服务模板类,是Spring 提供的用于访问Rest服务的客户端模板工具集
官网及使用
官网地址: https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html
使用RestTemplate访问Restful接口非常的简单粗暴无脑。(url,requestMap,ResponseBean.class)这三个参数分别代表REST请求地址、请求参数、Http响应转换被转换成的对象类型。
ApplicationContextConfig
package com.atguigu.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
//@Configuration
@SpringBootConfiguration
public class ApplicationContextConfig {
@Bean
//@LoadBalanced
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderController {
public static final String PAYMENT_URL = "http://localhost:8001";
@Resource
private RestTemplate restTemplate;
@PostMapping("/consumer/payment/create")
public CommonResult<Payment> create(Payment payment){
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class); //写操作
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult<Payment> getPayment(@PathVariable("id") Long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+id,CommonResult.class);
}
}
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-api-commonsartifactId>
<dependencies>
<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>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>5.1.0version>
dependency>
dependencies>
project>
Payment实体
CommonResult通用封装类
删除各自的原先有过的entities
各自黏贴POM内容,依赖于cloud-api-commons公共项目
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
SpringCloud封装了Netflix公司开发的Eureka模块来实现服务治理。
在传统的RPC远程调用框架中,管理每个服务与服务之间依赖关系比较复杂、所以需要进行服务治理,管理服务与服务之间依赖关联,以实现服务调用,负载均衡、容错等,实现服务发现与注册。
Eureka采用了CS的设计架构,Eureka Server作为服务注册功能的服务器,它是服务注册中心。
而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员可以通过Eureka Server来监控系统中各个微服务是否正常运行。
在服务注册与发现中,有一个注册中心。当服务器启动的时候,会把当前自己服务器的信息,比如:服务通讯地址等以别名方式注册到注册中心上。
另一方(消费者服务),以该别名的方式去注册中心上获取到实际的服务通讯地址,然后,再实现本地RPC远程调用。
RPC远程调用框架核心设计思想:在于注册中心,因为使用注册中心管理每个服务与服务之间的一个依赖关系(服务治理概念)。
在任何RPC远程框架中,都会有一个注册中心(存放服务地址相关信息(接口地址))。
Eureka Server提供服务注册服务
各个微服务节点通过配置启动后,会在Eureka Server中进行注册,这样Eureka Server中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到。
Eureka Client通过注册中心进行访问
是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的、使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会在Eureka Server发送心跳(默认周期30秒)。如果Eureka Server在多个心跳周期内没有收到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移出(默认90秒)
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-eureka-server7001artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
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>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
dependency>
dependencies>
project>
1.X和2.X的对比说明
1.X版本
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-eurekaartifactId>
dependency>
2.X版本
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
dependency>
server:
port: 7001
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false
fetchRegistry: false
service-url:
defaultZone: http://localhost:7001/eureka
@EnableEurekaServer
http://localhost:7001/
EurekaClient端cloud-provider-payment8001将注册进EurekaServer成为服务提供者provider
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
主启动
@EnableEurekaClient
测试
先启动EurekaServer
http://localhost:7001/
EurekaClient端cloud-consumer-order80将注册进EurekaServer成为服务消费者consumer
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
主启动
@EnableEurekaClient
测试
4) 测试查询:http://localhost/consumer/payment/get/31
5) 测试添加:postman测试添加
6) 测试8001服务和80服务效果一样
Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端负载均衡的工具。
简单的说,Ribbon是Netflix发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。
Ribbon客户端组件提供一系列完善的配置项,如:连接超时,重试等。
简单的说,就是在配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机连接等)去连接这些机器。
https://github.com/Netflix/ribbon/wiki/Getting-Started
Ribbon目前也进入维护模式
未来替换方案
Spring Cloud LoadBalancer
1. LB(负载均衡)
Ribbon在工作时分成两步:
第一步,先选择EurekaServer,它优先选择在同一个区域内负载较少的server。
第二步,再根据用户指定的策略,在从server取到的服务注册列表中选择一个地址。其中Ribbon提供了多种策略。比如:轮询、随机和根据响应时间加权。
总结:Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-ribbonartifactId>
dependency>
注意:这个不需要手动引用,Eureka客户端自带Ribbon
修改cloud-consumer-order80
注意配置细节
官方文档明确给出警告:
https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#customizing-the-ribbon-client
这个自定义配置类不能放在@ComponentScan所扫描的当前包下以及子包下,否则我们自定义的这个配置类就会被所有的Ribbon客户端所共享,达不到特殊化订制的目的了。
com.atguigu.myrule
package com.atguigu.myrule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MySelfRule {
@Bean
public IRule myRule(){
return new RandomRule();//定义为随机
}
}
package com.atguigu.springcloud;
import com.atguigu.myrule.MySelfRule;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.ribbon.RibbonClient;
@EnableEurekaClient
@SpringBootApplication
@RibbonClient(name = "CLOUD-PAYMENT-SERVICE",configuration = MySelfRule.class)
public class OrderMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderMain80.class,args);
}
}
http://localhost/consumer/payment/get/31
Feign是一个声明式的web服务客户端,让编写web服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可
SpringCloud对Feign进行了封装,使其支持了SpringMVC标准注解和HttpMessageConverters。Feign可以与Eureka和Ribbon组合使用以支持负载均衡。
https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#spring-cloud-openfeign
https://github.com/spring-cloud/spring-cloud-openfeign
Feign能干什么?
Feign旨在使用编写Java Http客户端变得更容易。
前面在使用Ribbon+RestTemplate时,利用RestTemplate对Http请求的封装处理,形成了一套模板化的调用方法。
但是在实际开发中,由于对服务依赖的调用可能不止一处,往往一个接口会被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装这些依赖服务端额调用。所以,Feign在此基础上做了进一步封装,由他来帮助我们定义和实现依赖服务接口的定义。
在Feign的实现下,我们只需创建一个接口并使用注解的方式来配置它(以前是DAO接口上面标注Mapper注解,现在是一个微服务接口上面标注一个Feign注解即可),即可完成对服务提供方的接口绑定,简化了使用Spring Cloud Ribbon时,自动封装服务调用客户端的开发量。
Feign集成了 Ribbon
利用Ribbon维护了Payment的服务列表信息,并且通过轮询实现了客户端的负载均衡。而与Ribbon不同的是,通过Feign只需要定义服务绑定接口且以声明式的方法,优雅而简单的实现了服务调用。
Feign和OpenFeign两者区别
微服务调用接口+@FeignClient
注意:openFeign也是自带bibbon
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-consumer-feign-order80artifactId>
<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.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
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>
project>
server:
port: 80
spring:
application:
name: cloud-consumer-feign-order80
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderFeignMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderFeignMain80.class,args);
}
}
package com.atguigu.springcloud.service;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import feign.Param;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")//指定远程调用微服务的名称
public interface PaymentFeignService {
@GetMapping(value = "/payment/get/{id}")
public CommonResult getPaymentById(@PathVariable("id") Long id);
}
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentFeignService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
public class OrderFeignController {
@Resource
private PaymentFeignService paymentFeignService; //调用远程的微服接口
@GetMapping(value = "/consumer/payment/get/{id}")
public CommonResult<Payment> getPaymentById(@PathVariable("id") Long id){
return paymentFeignService.getPaymentById(id);
}
}
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
try { TimeUnit.SECONDS.sleep(3); }catch (Exception e) {e.printStackTrace();} //单位秒
return serverPort;
}
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
return paymentFeignService.paymentFeignTimeout();
}
默认Feign客户端只等待一秒钟,但是,服务端处理需要超过1秒钟,导致Feign客户端不想等待了,直接报错。
为了避免这样的情况,有时候我们需要设置Feign客户端的超时控制,也即Ribbon的超时时间,因为Feign集成了Ribbon进行负载均衡。
Feign设置超时时间
使用Feign调用接口分两层,ribbon的调用和hystrix的调用,所以ribbon的超时时间和Hystrix的超时时间的结合就是Feign的超时时间
#设置Feign客户端超时时间(openfeign默认支持ribbon)
ribbon:
ReadTimeout: 3000
ConnectTimeout: 3000
MaxAutoRetries: 1 #同一台实例最大重试次数,不包括首次调用
MaxAutoRetriesNextServer: 1 #重试负载均衡其他的实例最大重试次数,不包括首次调用
OkToRetryOnAllOperations: false #是否所有操作都重试
#hystrix的超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: true
isolation:
thread:
timeoutInMilliseconds: 9000
一般情况下 都是 ribbon 的超时时间(<)hystrix的超时时间(因为涉及到ribbon的重试机制)
因为ribbon的重试机制和Feign的重试机制有冲突,所以源码中默认关闭Feign的重试机制,源码如下
要开启Feign的重试机制如下:(Feign默认重试五次 源码中有)
@Bean
Retryer feignRetryer() {
return new Retryer.Default();
}
根据上面的参数计算重试的次数:MaxAutoRetries+MaxAutoRetriesNextServer+(MaxAutoRetries *MaxAutoRetriesNextServer) 即重试3次 则一共产生4次调用
如果在重试期间,时间超过了hystrix的超时时间,便会立即执行熔断,fallback。所以要根据上面配置的参数计算hystrix的超时时间,使得在重试期间不能达到hystrix的超时时间,不然重试机制就会没有意义
hystrix超时时间的计算: (1 + MaxAutoRetries + MaxAutoRetriesNextServer) * ReadTimeout 即按照以上的配置 hystrix的超时时间应该配置为 (1+1+1)*3=9秒
当ribbon超时后且hystrix没有超时,便会采取重试机制。当OkToRetryOnAllOperations设置为false时,只会对get请求进行重试。如果设置为true,便会对所有的请求进行重试,如果是put或post等写操作,如果服务器接口没做幂等性,会产生不好的结果,所以OkToRetryOnAllOperations慎用。
如果不配置ribbon的重试次数,默认会重试一次
注意:
默认情况下,GET方式请求无论是连接异常还是读取异常,都会进行重试
非GET方式请求,只有连接异常时,才会进行重试
Feign提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解Feign中Http请求的细节。说白了就是对Feign接口的调用情况进行监控和输出。
NONE:默认的,不显示任何日志
BASIC:仅记录请求方法、RUL、响应状态码及执行时间
HEADERS:除了BASIC中定义的信息之外,还有请求和响应的头信息
FULL:除了HEADERS中定义的信息之外,还有请求和响应的正文及元数据
package com.atguigu.springcloud.config;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}
logging:
level:
com.atguigu.springcloud.service.PaymentFeignService: debug
http://localhost/consumer/payment/get/31
复杂分布式体系结构中的应用程序有数十个依赖关系,每一个依赖关系在某些时候将不可避免的失败。
服务雪崩
多个微服务之间调用的时候,假如微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的"扇出"。
如果扇出的链路上某个微服务的调用响应的时间过长或者不可用,对微服A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的"雪崩效应"。
对于高流量的应用来说,单一的后端依赖可能会导致所有的服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,
Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(Fallback),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
服务降级
服务熔断
接近实时的监控
。。。
https://github.com/Netflix/Hystrix/wiki/How-To-Use
https://github.com/Netflix/Hystrix
服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示
哪些情况会触发降级
程序运行异常
超时自动降级
服务熔断触发服务降级
线程池/信号量打满也会导致服务降级
人工降级
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
就是保险丝
服务的降级->进而熔断->恢复调用链路(降级累计到一定程度就熔断)
秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-provider-hystrix-payment8001artifactId>
<dependencies>
<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>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
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>
project>
server:
port: 8001
spring:
application:
name: cloud-hystrix-payment-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}
package com.atguigu.springcloud.service;
public interface PaymentService {
public String paymentInfo_OK(Integer id);
public String payment_Timeout(Integer id);
}
package com.atguigu.springcloud.service.impl;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentServiceImpl implements PaymentService {
//成功
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"哈哈哈" ;
}
//失败
public String payment_Timeout(Integer id){
int timeNumber = 3;
try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
}
}
Controller
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;
@Value("${server.port}")
private String serverPort;
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("*******result:"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*******result:"+result);
return result;
}
}
下载地址:https://archive.apache.org/dist/jmeter/binaries/
开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务
压测的过程中再来访问一下微服务
http://localhost:8001/payment/hystrix/ok/31
http://localhost:8001/payment/hystrix/timeout/31
演示结果
两个都在自己转圈圈
为什么会被卡死
tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。
上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-consumer-feign-hystrix-order80artifactId>
<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.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
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>
project>
server:
port: 80
spring:
application:
name: cloud-provider-hystrix-payment-service
eureka:
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka/
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}
PaymentHystrixService
package com.atguigu.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("CLOUD-HYSTRIX-PAYMENT-SERVICE")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String payment_Timeout(@PathVariable("id") Integer id);
}
OrderHystrixController
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
log.info("*******result:"+result);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.payment_Timeout(id);
log.info("*******result:"+result);
return result;
}
}
http://localhost/consumer/payment/hystrix/ok/32
2W个线程压8001
消费端80微服务再去访问正常的OK微服务8001地址
http://localhost/consumer/payment/hystrix/timeout/32
消费者80,呜呜呜
要么转圈圈等待
要么消费端报超时错误
8001同一层次的其他接口服务被困死,因为tomcat线程里面的工作线程已经被挤占完毕
80此时调用8001,客户端访问响应缓慢,转圈圈
正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生
超时导致服务器变慢(转圈)
超时不再等待
出错(宕机或程序运行出错)
出错要有兜底
解决
对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级
@HystrixCommand
设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback
业务类启用阶级处理:使用@HystrixCommand注解来干活。
package com.atguigu.springcloud.service.impl;
import com.atguigu.springcloud.service.PaymentService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentServiceImpl implements PaymentService {
@Override
public String paymentInfo_OK(Integer id) {
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id="+id +" \t O(∩_∩)O哈哈~";
}
//超时降级演示
@HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") //5秒钟以内就是正常的业务逻辑
})
@Override
public String payment_Timeout(Integer id) {
//int timeNumber = 3; //小于等于3秒算是正常情况
int timeNumber = 15; //模拟非正常情况
//int i = 1/0 ; //模拟非正常情况
try {
TimeUnit.SECONDS.sleep(timeNumber);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "线程池:"+Thread.currentThread().getName()+" payment_Timeout,id="+id+" \t o(╥﹏╥)o 耗时:"+timeNumber;
}
//兜底方法,上面方法出问题,我来处理,返回一个出错信息
public String payment_TimeoutHandler(Integer id) {
return "线程池:"+Thread.currentThread().getName()+" payment_TimeoutHandler,系统繁忙,请稍后再试\t o(╥﹏╥)o ";
}
}
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法
主启动类激活
@EnableCircuitBreaker
测试超时和算数异常,都会走兜底方法——服务降级
http://localhost:8001/payment/hystrix/timeout/1
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
//超时降级演示
@HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")//超过1.5秒就降级自己
})
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
//int age= 1/0;
String result = paymentHystrixService.payment_Timeout(id);
log.info("*******result:"+result);
return result;
}
//兜底方法,上面方法出问题,我来处理,返回一个出错信息
public String payment_TimeoutHandler(Integer id) {
return "我是消费者80,对方支付系统繁忙请10秒后再试。或自己运行出错,请检查自己。";
}
每个业务方法对应一个兜底的方法,代码膨胀,代码耦合
统一通用处理和自定义独立处理的分开
package com.atguigu.springcloud.controller;
import com.atguigu.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod") //全局的
public class OrderHystrixController {
@Resource
private PaymentHystrixService paymentHystrixService;
@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
return result;
}
@HystrixCommand
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
int age = 10/0;
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}
//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}
//下面是全局fallback方法
public String payment_Global_FallbackMethod(){
return "Global异常处理信息,请稍后再试,(┬_┬)";
}
}
测试结果
服务降级,客户端去调用服务端,碰上服务端宕机或关闭
本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
未来我们要面对的异常
运行
超时
宕机
再看我们的业务类PaymentController
修改cloud-consumer-feign-hystrix-order80
根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理
PaymentFallbackService类实现PaymentFeignClientService接口
package com.atguigu.springcloud.service;
import org.springframework.stereotype.Component;
@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
}
@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
}
}
YML
feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
PaymentFeignClientService接口
package com.atguigu.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}
单个eureka先启动7001
PaymentHystrixMain8001启动
正常访问测试:http://localhost/consumer/payment/hystrix/ok/31
故意关闭微服务8001
客户端自己调用提升
此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器
一句话就是家里保险丝
熔断机制概述
熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
当检测到该节点微服务调用响应正常后,恢复调用链路。
在SpringCloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状态,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand
https://martinfowler.com/bliki/CircuitBreaker.html
com.netflix.hystrix.HystrixCommandProperties
//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //当在配置时间窗口内达到此数量的失败后,打开断路,默认20个
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //断路多久以后开始尝试是否恢复,默认5s
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //出错百分比阈值,当达到此阈值后,开始短路。默认50%
})
public String paymentCircuitBreaker(Integer id){
if (id < 0){
throw new RuntimeException("*****id 不能负数");
}
String serialNumber = IdUtil.simpleUUID();//hutool.cn工具包
return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能负数,请稍候再试,(┬_┬)/~~ id: " +id;
}
//===服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("*******result:"+result);
return result;
}
自测cloud-provider-hystrix-payment8001
正确: http://localhost:8001/payment/circuit/31
错误: http://localhost:8001/payment/circuit/-31
一次正确一次错误trytry
重点测试
多次错误(狂点),然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行访问,需要慢慢的恢复链路
熔断打开
请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入熔断状态
熔断关闭
熔断关闭不会对服务进行熔断
熔断半开
部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
① 官网步骤
② 断路器在什么情况下开始起作用
//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "20"), //当快照时间窗(默认10秒)内达到此数量才有资格打开断路,默认20个
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "50000"), //断路多久以后开始尝试是否恢复,默认5s
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "50"), //出错百分比阈值,当达到此阈值后,开始短路。默认50%
})
涉及到断路器的三个重要参数:快照时间窗、请求总数阈值、错误百分比阈值。
1、快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
2、请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断。默认20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
3、错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阈值情况下,这时候就会将断路器打开。
③断路器开启或者关闭的条件
当满足一定阀值的时候(默认10秒内超过20个请求次数)
当失败率达到一定的时候(默认10秒内超过50%请求失败)
到达以上阀值,断路器将会开启
当开启的时候,所有请求都不会进行转发
一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5
④ 断路器打开之后
1:再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallbak。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
2:原来的主逻辑要如何恢复呢?
对于这一个问题,hystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
⑤ All配置
后面讲
除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stram项目实现了对以上指示的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-consumer-hystrix-dashboard9001artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
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>
project>
server:
port: 9001
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardMain9001.class,args);
}
}
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
http://localhost:9001/hystrix
注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径
/**
*此配置是为了服务监控而配置,与服务容错本身无关,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;
}
Unable to connect to Command Metric Stream
404
9001监控8001
http://localhost:9001/hystrix
http://localhost:8001/hystrix.stream
测试地址
http://localhost:8001/payment/circuit/31
http://localhost:8001/payment/circuit/-31
上述测试通过
ok
先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的
监控结果,成功
监控结果,失败
1圈
实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色<黄色<橙色<红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实体中快速的发现故障实例和高压力实例。
1线
曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势。
整图说明
整图说明2
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
Cloud全家桶中有个很重要的组件就是网关,在1.x版本中都是采用的Zuul网关https://github.com/Netflix/zuul/wiki
但在2.x版本中,zuul的升级一直跳票,SpringCloud最后自己研发了一个网关代替Zull,那就是SpringCloud Geteway;
Gateway是在spring生态系统之上构建的API网关服务,基于Spring5,SpringBoot2和Project Reactor等技术。
Gateway旨在提供一种简单而有效的方式来对API进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等
SpringCloud Gateway是SpringCloud的一个全新项目,基于Spring5.0+SpringBoot2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。
为了提升网关的性能,SpringCloud Gatway是基于WebFlux框架实现的,而WebFlux框架底层则使用了高性能的Reactor模式通讯框架Netty。
SpringCloud Gateway的目标提供统一的路由方式且基于Filter链的方式提供了网关基本的功能,例如:安全、监控/指标、和限流。
Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
源码架构
反向代理
鉴权
流量控制
熔断
日志监控
。。。。。。
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如果断言为true则匹配该路由
参考的是java8的java.util.function.Predicate开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。
Web请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
Predicate就是我们的匹配条件: 而Filter,就是可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标uri,就可以实现一个具体的路由了。
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gateway-how-it-works
客户端向Spring Cloud Gateway发出请求。然后在Gateway Handler Mapping中找到与请求匹配的路由,将其发送到Gateway Web Handler.
Handler再通过指定的过滤器链来将请求发送给我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Filter在"pre"类型的过滤器可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在"post"类型的过滤器中可以做响应内容、响应头的修改,日志的输出,流量控制等有着非常重要的作用
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloud-gateway-gateway9527artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<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>
dependencies>
project>
server:
port: 9527
spring:
application:
name: cloud-gateway
eureka:
instance:
hostname: cloud-gateway-service
client:
register-with-eureka: true
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka
无
package com.atguigu.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run( GateWayMain9527.class,args);
}
}
我们目前不想暴露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/get/** #断言,路径相匹配的进行路由
- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://localhost:7001/eureka
启动7001:cloud-eureka-server7001
启动8001:cloud-provider-payment8001
启动9527网关:cloud-gateway-gateway9527
访问说明
添加网关前: http://localhost:8001/payment/get/31
添加网关后: http://localhost:9527/payment/get/31
默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能
一个eureka7001+两个服务提供者8001/8002
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/** #断言,路径相匹配的进行路由
eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://localhost:7001/eureka
需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。
lb://serviceName是spring cloud gateway在微服务中自动为我们创建的负载均衡uri
http://localhost:9527/payment/lb
8001/8002两个端口切换
启动我们的gatewat9527,查看启动日志
Spring Cloud Gateway将路由匹配作为Spring WebFlux HandlerMapper基础框架的一部分。
Spring Cloud Gateway包括许多内置的Route Predicate工厂。所有这些Predicate都与HTTP请求的不同属性匹配。多个Route Predicate工厂可以进行组合
Spring Cloud Gateway创建Route对象时,使用RoutePredicateFactory创建Predicate对象,Predicate对象可以赋值给 Route。Spring Cloud Gateway包含许
多内置的Route Predicate Factories。
所有这些谓词都匹配HTTP请求的不同属性。多种谓词工厂可以组合,并通过逻辑and 。
ZonedDateTime zonedDateTime = ZonedDateTime.now();
System.out.println(zonedDateTime);
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
• 测试:没到时间进行测试报错
• YML
- Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
• YML
- Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
• YML
- Cookie=username,atguigu #并且Cookie是username=zhangshuai才能访问
• 不带cookies访问
• 带上cookies访问
curl下载地址:https://curl.haxx.se/download.html
• 加入curl返回中文乱码(帖子): https://blog.csdn.net/leedee/article/details/82685636
YML: - Host=**.atguigu.com
YML:- Method=GET
YML:
YML: - Query=username, \d+ #要有参数名称并且是正整数才能路由
• All
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://localhost:7001/eureka
说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理
路由过滤器可用于修改进入的HTTP请求和返回的HTTP响应,路由过滤器只能指定路由进行使用。
SpringCloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
• pre
在业务逻辑之前
• post
在业务逻辑之后
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.2.RELEASE/reference/html/#gatewayfilter-factories
两个主要接口介绍
impiemerts GlobalFilter ,Ordered
全局日志记录
统一网关鉴权
。。。。。。
package com.atguigu.springcloud.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Date;
@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info("*********come in MyLogGateWayFilter: "+new Date());
String uname = 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;
}
}
启动
正确:http://localhost:9527/payment/lb?username=z3
错误:http://localhost:9527/payment/lb?uname=z3
问题
在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的服务节点调用来协同产生最后的请求结果,每一个前端请求都会形成一个复杂的分布式服务调用链路,链路中的任何一环出现高延时或错误都会引起整个请求最后的失败。
Spring Cloud Sleuth提供了一套完整的服务跟踪的解决方案
在分布式系统中提供追踪解决方案并且兼容支持了zipkin(负责展现)
https://github.com/spring-cloud/spring-cloud-sleuth
https://cloud.spring.io/spring-cloud-sleuth/reference/html/
下载
SpringCloud从F版起已不需要自己构建Zipkin server了,只需要调用jar包即可
https://dl.bintray.com/openzipkin/maven/io/zipkin/java/zipkin-server/
zipkin-server-2.12.9.exec.jar
运行jar
java -jar zipkin-server-2.12.9-exec.jar
运行控制台
http://localhost:9411/zipkin/
术语
完整的调用链路
表示一请求链路,一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来。
上图what
一条链路通过Trace Id唯一标识,Span标识发起的请求信息,各span通过parent id 关联起来。
整个链路的依赖关系如下:
名词解释
Trace:类似于树结构的Span集合,表示一条调用链路,存在唯一标识
span:表示调用链路来源,通俗的理解span就是一次请求信息
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-zipkinartifactId>
dependency>
server:
port: 8001
spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
#采样率值介于0~1之间,1表示全部采样
probability: 1
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/cloud2020?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: root
mybatis:
mapperLocations: classpath:/mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities
eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka
@GetMapping("/payment/zipkin")
public String paymentZipkin(){
return "hi ,i'am paymentzipkin server,welcome to atguigu,O(∩_∩)O哈哈~";
}
方)
org.springframework.cloud
spring-cloud-starter-zipkin
server:
port: 80
spring:
application:
name: cloud-order-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: false
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机
defaultZone: http://localhost:7001/eureka
#集群
#defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版
//==> zipkin+sleuth
@GetMapping("/consumer/payment/zipkin")
public String paymentZipkin(){
String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
return result;
}
80调用8001几次测试下
Spring Cloud Netflix项目进入维护模式
https://spring.io/blog/2018/12/12/spring-cloud-greenwich-rc1-available-now
说明:
Spring Cloud Netflix项目进入维护模式
最近,Netflix宣布Hystrix正在进入维护模式。自2016年以来,Ribbon已处于类似状态。
虽然Hystrix和Ribbon现已处于维护模式,但它们仍然在Netflix大规模部署。
Hystrix Dashboard和Turbine已被Atlas取代。这些项目的最后一次提交是2年和4年前。Zuul1和Archaius1都被后来不兼容的版本所取代。
这不包括Eureka或并发限制模块。
什么是维护模式?
将模块置于维护模式,意味着SpringCloud团队将不会再向模块添加新功能。我们将修复block级别的bug以及安全问题,我们也会考虑并审查社区的小型pull request。
我们打算继续支持这些模块,直到Greenwich版本被普遍采用至少一年。
进入维护模式意味着什么?
Spring Cloud Netflix将不再开放新的组件
我们都知道SpringCloud版本迭代算是比较快的,因而出现了很多重大ISSU都还来不及Fix就又推出另一个Release了。
进入维护模式意思就是目前以致以后一段时间SpringCloud netflix提供的服务和功能就这么多了,不再开发新的组件和功能了。以后将以维护和Merge分支Pull Request为主。
新组件功能将以其他替代的方式实现
诞生:2018.10.31,Spring Cloud Alibaba正式入驻了Spring Cloud官网孵化器,并在Maven中央库发布了第一个版本。
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
服务限流降级:默认支持 WebServlet、WebFlux, OpenFeign、RestTemplate、Spring Cloud Gateway, Zuul, Dubbo 和 RocketMQ 限流降级功能的接入,可以在运行时通过控制台实时修改限流降级规则,还支持查看限流降级 Metrics 监控。
服务注册与发现:适配 Spring Cloud 服务注册与发现标准,默认集成了 Ribbon 的支持。
分布式配置管理:支持分布式系统中的外部化配置,配置更改时自动刷新。
消息驱动能力:基于 Spring Cloud Stream 为微服务应用构建消息驱动能力。
分布式事务:使用 @GlobalTransactional 注解, 高效并且对业务零侵入地解决分布式事务问题。。
阿里云对象存储:阿里云提供的海量、安全、低成本、高可靠的云存储服务。支持在任何应用、任何时间、任何地点存储和访问任意类型的数据。
分布式任务调度:提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。同时提供分布式的任务执行模型,如网格任务。网格任务支持海量子任务均匀分配到所有 Worker(schedulerx-client)上执行。
阿里云短信服务:覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
一整套解决方案,简单理解就是替换Netflix那一套
Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。
Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。
RocketMQ:一款开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务。
Dubbo:Apache Dubbo™ 是一款高性能 Java RPC 框架。
Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案。
Alibaba Cloud ACM:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品。
Alibaba Cloud OSS: 阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。
Alibaba Cloud SchedulerX: 阿里中间件团队开发的一款分布式任务调度产品,提供秒级、精准、高可靠、高可用的定时(基于 Cron 表达式)任务调度服务。
Alibaba Cloud SMS: 覆盖全球的短信服务,友好、高效、智能的互联化通讯能力,帮助企业迅速搭建客户触达通道。
官网:https://spring.io/projects/spring-cloud-alibaba#overview
英文
https://github.com/alibaba/spring-cloud-alibaba
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
中文
https://github.com/alibaba/spring-cloud-alibaba/blob/master/README-zh.md
前四个字母分别为Naming和Configuration的前两个字母,最后的s为Service
一个更易于构建云原生应用的动态服务发现,配置管理和服务管理中心
Nacos:Dynamic Naming and Configuration Service
Nacos就是注册中心+配置中心的组合
等价于:Nacos = Eureka+Config+Bus
替代Eureka做服务注册中心
替代Config做服务配置中心
https://github.com/alibaba/Nacos
官网文档
https://nacos.io/zh-cn/index.html
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
据说nacos在阿里巴巴内部有超过10万的实例运行,已经过了类似双十一等各种大型流量的考验
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
https://github.com/alibaba/nacos/releases/tag/1.1.4
http://localhost:8848/nacos
默认账号密码都是nacos
https://spring.io/projects/spring-cloud-alibaba#learn
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html
父POM
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.1.0.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
本模块POM
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
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
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*' #默认只公开了/health和/info端点,要想暴露所有端点只需设置成星号
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class PaymentMain9001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9001.class,args);
}
}
package com.atguigu.springcloud.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class PaymentController{
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Long id) {
return "nacos registry, serverPort: "+ serverPort+"\t id"+id;
}
}
http://localhost:9001/payment/nacos/1
nacos控制台
nacos服务注册中心+服务提供者9001都ok了
新建cloudalibaba-provider-payment9002
9002其他步骤你懂的
或者取巧不想新建重复体力劳动,直接拷贝虚拟端口映射
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
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>
为什么nacos支持负载均衡
server:
port: 83
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: localhost:8848
#消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者【可选】,注意:nacos-payment-provider含有IP和端口)
service-url:
nacos-user-service: http://nacos-payment-provider
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain83{
public static void main(String[] args){
SpringApplication.run(OrderNacosMain83.class,args);
}
}
ApplicationContextBean
package com.atguigu.springcloud.alibaba.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
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
package com.atguigu.springcloud.alibaba.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderNacosController{
@Resource
private RestTemplate restTemplate;
@Value("${service-url.nacos-user-service}")
private String serverURL;
@GetMapping(value = "/consumer/payment/nacos/{id}")
public String paymentInfo(@PathVariable("id") Long id){
return restTemplate.getForObject(serverURL+"/payment/nacos/"+id,String.class);
}
}
nacos控制台
http://localhost:83/consumer/payment/nacos/1
83访问9001/9002,轮询负载OK
CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可得兼。
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。
如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件, 因为只有独一数据,不会出现数据不一致的情况,此时C和P两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了CP系统,但是CAP不可同时满足。
因此在进行分布式架构设计时,必须做出取舍。当前一般是通过分布式缓存中各节点的最终一致性来提高系统的性能,通过使用多节点之间的数据异步复制技术来实现集群化的数据一致性。
curl -X PUT ‘$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP’
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
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>
Nacos同springcloud-config一样,在项目初始化时,要保证先从配置中心进行配置拉取,拉取配置之后,才能保证项目的正常启动
springboot中配置文件的加载是存在优先级顺序的,bootstrap优先级高于application
bootstrap.yml
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #服务注册中心地址
config:
server-addr: localhost:8848 #配置中心地址
file-extension: yaml #指定yaml格式的配置(yml和yaml都可以)
#${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
#nacos-config-client-dev.yaml (一定要与file-extension值保持一致)
application.yml
spring:
profiles:
active: dev #表示开发环境
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class NacosConfigClientMain3377{
public static void main(String[] args) {
SpringApplication.run(NacosConfigClientMain3377.class, args);
}
}
package com.atguigu.springcloud.alibaba.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RefreshScope //通过SpringCould原生注解@RefreshScope实现配置自动更新
public class ConfigClientController{
@Value("${config.info}") //对应nacos配置:nacos-config-client-dev.yaml
private String configInfo;
@GetMapping("/config/info")
public String getConfigInfo() {
return configInfo;
}
}
Nacos中的匹配规则
Nacos中的dataid的组成格式与SpringBoot配置文件中的匹配规则
官网 https://nacos.io/zh-cn/docs/quick-start-spring-cloud.html
最后公式:
s p r i n g . a p p l i c a t i o n . n a m e − {spring.application.name}- spring.application.name−{spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
nacos-config-client-dev.yaml
Nacos界面配置对应
设置DataId
公式: s p r i n g . a p p l i c a t i o n . n a m e − {spring.application.name}- spring.application.name−{spring.profile.active}.${spring.cloud.nacos.config.file-extension}
小总结说明
历史配置:Nacos会记录配置文件的历史版本默认保留30天
启动前需要在nacos客户端-配置管理-配置管理栏目下有没有对应的yaml配置文件
运行cloud-config-nacos-client3377的主启动类
调用接口查看配置信息: http://localhost:3377/config/info
修改Nacos中的yaml配置文件,查看配置已经刷新
多环境多项目管理
问题1
• 实际开发中,通常一个系统会准备
• dev开发环境
• test测试环境
• prod生产环境
• 如何保证指定环境启动时服务能正确读取到 Nacos上相应环境的配置文件呢?
问题2
• 一个大型分布式微服务心痛会有很多微服务子项目
• 每一个微服务项目又会相应的开发环境、测试环境、预发环境、正式环境….
• 那怎么对这些微服务配置进行管理呢?
• 配置管理
最外层的namespace是可以用于区分部署环境的,Group和DataID逻辑上区分两个目标对象。
默认情况:Namespace=public,Group=DEFAULT_GROUP,默认Cluster是DEFAULT
Nacos默认的命名空间是public,Namespace主要用来实现隔离。
比方说我们现在有三个环境:开发、测试、生产环境,我们就可以创建三个Namespace,不同的 Namespace之间是隔离的。
Group默认是DEFAULT_GROUP,Group可以把不同的微服务划分到同一个分组里面去。Service就是微服务;一个Service可以包含多个Cluster(集群),Nacos默认Cluster是DEFAULT,Cluster是对指定微服务的一个虚拟划分。
比方说为了容灾,将Service微服务分别部署在了杭州机房和广州机房,这时就可以给杭州机房的Service微服务起一个集群名称(HZ),给广州机房的Service微服务起一个集群名字(GZ),还可以尽量让同一个机房的微服务互相调用,以提升性能。
最后是Instance,就是微服务的实例。
指定spring.profile.active和配置文件的DataID来使不同环境下读取不同的配置
默认空间+默认分组+新建dev和test两个DataID
新建dev配置DataID
新建test配置DataID
通过spring.profile.active属性就能进行多环境下配置文件的读取
测试
http://localhost:3377/config/info
配置是什么就加载什么
通过Group实现环境区分
新建Group
在nacos图形界面控制台上面新建配置文件DataID
在config下增加一条group的配置即可。可配置为DEV_GROUP或TEST_GROUP
新建dev/test的Namespace
回到服务管理-服务列表查看
nacos-config-client-dev.yaml
config:
info: 9f62d48c-ef2e-4d83-a9fb-c9db5833f93b DEFAULT_GROUP nacos-config-client-dev.yaml
# nacos配置
server:
port: 3377
spring:
application:
name: nacos-config-client
cloud:
nacos:
discovery:
server-addr: localhost:8848 #Nacos服务注册中心地址
config:
server-addr: localhost:8848 #Nacos作为配置中心地址
file-extension: yaml #指定yaml格式的配置
group: DEV_GROUP
namespace: 7d8f0f5a-6a53-4785-9686-dd460158e5d4
# ${spring.application.name}-${spring.profile.active}.${spring.cloud.nacos.config.file-extension}
# nacos-config-client-dev.yaml
application
spring:
profiles:
active: dev # 表示开发环境
#active: test # 表示测试环境
#active: info
图解:
https://nacos.io/zh-cn/docs/cluster-mode-quick-start.html
集群部署架构图
推荐用户把所有服务列表放到一个vip下面,然后挂到一个域名下面
http://ip1:port/openAPI 直连ip模式,机器挂则需要修改ip才可以使用。
http://VIP:port/openAPI 挂载VIP模式,直连vip即可,下面挂server真实ip,可读性不好。
http://nacos.com:port/openAPI 域名 + VIP模式,可读性好,而且换ip方便,推荐模式
默认Nacos使用嵌入式数据库实现数据的存储。所以,如果启动多个默认配置下的Nacos节点,数据存储是存在一致性问题多的。
为了解决这个问题,Nacos采用了集中式存储的方式来支持集群化部署,目前只支持MySQL 的存储。
https://nacos.io/zh-cn/docs/deployment.html
https://github.com/alibaba/nacos/blob/develop/config/pom.xml
nacos-server-1.1.4\nacos\conf目录下找到sql脚本
nacos-mysql.sql
执行脚本
nacos-server-1.1.4\nacos\conf目录下找到application.properties
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://11.162.196.16:3306/nacos_devtest?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=nacos_devtest
db.password=youdontknow
https://github.com/alibaba/nacos/releases/tag/1.1.4
nacos-server-1.1.4.tar.gz
spring.datasource.platform=mysql
db.num=1
db.url.0=jdbc:mysql://192.168.137.128:3306/nacos_config?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true
db.user=zhang3
db.password=123456
梳理出3台nacos机器的不同服务端口号
复制出cluster.conf
内容
192.168.137.128:3333
192.168.137.128:4444
192.168.137.128:5555
这个IP不能写127.0.0.1,必须是Linux命令hostname -i能够识别的IP
/mynacos/nacos/bin目录下有startup.sh
在什么地方,修改什么,怎么修改
思考
/mynacos/nacos/bin目录下有startup.sh
平时单机版的启动,都是./startup.sh即可。
但是
集群启动,我们希望可以类似其它软件的shell命令,传递不同的端口号启动不同的nacos实例。
命令:./startup.sh -p 3333表示启动端口号为3333的nacos服务器实例,和上一步的cluster.conf配置的一致。
修改内容
修改前
执行方式
启动时,需要修改内存大小,否则,内存可能不够用。
查看进程:ps -ef | grep nacos
修改nginx的配置文件:vim /usr/local/nginx/conf/nginx.conf
nginx.conf
upstream cluster{
server 192.168.137.128:3333;
server 192.168.137.128:4444;
server 192.168.137.128:5555;
}
server{
listen 1111;
server_name 192.168.137.128;
location / {
proxy_pass http://cluster;
}
....省略
启动Nginx:
测试通过nginx访问nacos
https:// 192.168.137.128:1111/nacos/#/login
新建一个配置测试
微服务cloudalibaba-provider-payment9002启动注册进nacos集群
yml
server-addr: 写你自己的虚拟机ip:1111
结果
https://github.com/alibaba/Sentinel
中文
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
https://github.com/alibaba/Sentinel/releases
https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel
服务使用中的各种问题
服务雪崩
服务降级
服务熔断
服务限流
Sentinel 分为两个部分:
核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
后台
前台8080
http://localhost:8848/nacos/#/login
<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>cloud2020artifactId>
<groupId>com.atguigu.springcloudgroupId>
<version>1.0-SNAPSHOTversion>
parent>
<modelVersion>4.0.0modelVersion>
<artifactId>cloudalibaba-sentinel-service8401artifactId>
<dependencies>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
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>cn.hutoolgroupId>
<artifactId>hutool-allartifactId>
<version>4.6.3version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
project>
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719 #默认8719,应用与Sentinel控制台交互的端口,应用本地会起一个该端口占用HttpServer
management:
endpoints:
web:
exposure:
include: '*'
https://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-features.html#production-ready-endpoints
package com.atguigu.springcloud.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401{
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
package com.atguigu.springcloud.alibaba.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class FlowLimitController{
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
return "------testB";
}
}
java -jar sentinel-dashboard-1.7.0
空空如也,啥都没有
Sentinel采用的懒加载说明
执行一次访问即可
http://localhost:8401/testA
http://localhost:8401/testB
效果
结论
sentinel8080正在监控微服务8401
• 直接->快速失败
系统默认
• 测试QPS
配置及说明
• 表示1秒钟内查询1次就是OK,若超过次数1,就直接-快速失败,报默认错误
快速点击访问: http://localhost:8401/testA
结果
• Blocked by Sentinel (flow limiting)
• 测试线程数
快速点击访问: http://localhost:8401/testA
结果
• 不会出现Blocked by Sentinel (flow limiting)(线程处理请求很快)
但是,在映射方法里添加sleep后,同样也会出现Blocked by Sentinel (flow limiting)默认提示信息。
思考???
• 直接调用默认报错信息,技术方面OK but,是否应该有我们自己的后续处理?
• 类似有一个fallback的兜底方法?
• 访问testB成功
设置并发访问参数
• Run
大批量线程高并发访问B,导致A失效了
• 运行后发现testA挂了
点击访问http://localhost:8401/testA
结果
• Blocked by Sentinel (flow limiting)
• 多个请求调用了同一个微服务
• 家庭作业试试
直接失败,抛出异常:Blocked by Sentinel (flow limiting)
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
• 说明
公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
• 官网: https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6#%E5%9F%BA%E4%BA%8Eqps%E5%B9%B6%E5%8F%91%E6%95%B0%E7%9A%84%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
限流 冷启动
https://github.com/alibaba/Sentinel/wiki/%E9%99%90%E6%B5%81—%E5%86%B7%E5%90%AF%E5%8A%A8
• 源码
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
• Warmup配置
默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
案例:阈值为10 + 预热时长设置5秒。
系统初始化的阈值为10/3约等于3,即阈值刚开始为3;然后过了5秒后阈值才慢慢升高,恢复到10
多次点击http://localhost:8401/testB
刚开始不行,后续慢慢OK
应用场景
• 如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。
3. 排队等待
• 匀速排队,让请求以均匀的速度通过,阈值类型必须设置成QPS,否则无效。
• 设置含义:/testB每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
• 源码:com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController
• 测试
增加打印语句
@GetMapping("/testB")
public String testB() {
log.info(Thread.currentThread().getName()+"\t ...testB");
return "------testB";
}
增加线程组:直接10个线程并发,排队被依次处理
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
o 整体介绍
• RT(平均响应时间,秒级)
平均响应时间 (DEGRADE_GRADE_RT):超过阈值 且 时间窗口内的请求>=5,两个条件同时满足后触发降级,窗口期过后关闭断路器
RT 最大4900 ms,更大的需要通过启动配置项
-Dcsp.sentinel.statistic.max.rt=xxx 来配置。
异常比例(秒级)
QPS>=5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
异常数(分钟级)
异常数(分钟统计)超过阈值时,触发降级;时间窗口结束后,关闭降级
o 进一步说明
• Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如:调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误。
• 当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。
o Sentinel的断路器是没有半开状态的
• 半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix
• 复习Hystrix
• 是什么
@GetMapping("/testD")
public String testD(){
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
log.info("testD 测试RT");
return "------testD";
}
配置
结论
永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒处理完本次任务,
如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了
后续停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK
@GetMapping("/testD")
public String testD() {
log.info("testD 测试异常比例");
int age = 10/0;
return "------testD";
}
配置
演示
结论
• 按照上述配置
• 单独访问一次,必然来一次报错一次(int age=10/0;),调一次错一次
• 开启jmeter后,直接高并发送请求,多次调用达到我们的配置条件了。
• 断路器开启(保险丝跳闸),微服务不可用了,不再报错error 而是服务降级了。
@GetMapping("/testE")
public String testE(){
log.info("testE 测试异常数");
int age = 10/0;
return "------testE 测试异常数";
}
配置
• http://localhost:8401/testE
• 第一次访问绝对报错,因为除数不能为零,我们看到error窗口,但是达到5次报错后,进入熔断后降级。
手动连续点5次后,进入降级
o 是什么
https://github.com/alibaba/Sentinel/wiki/热点参数限流
o 兜底方法
o 分为系统默认和客户自定义,两种
• 之前的case,限流出问题后,都是用sentinel系统默认的提示: Blocked by Sentinel(flow limiting)
• 我们能不能自定义?类似hystrix,某个方法出现问题了,就找对应的兜底降级方法?
• 结论
从@HystrixCommand到@SentinelResource
@GetMapping("/testHotKey")
@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")
public String testHotKey(@RequestParam(value = "p1",required = false) String p1,
@RequestParam(value = "p2",required = false) String p2) {
//int age = 10/0;
return "------testHotKey";
}
//兜底方法
public String deal_testHotKey (String p1, String p2, BlockException exception){
return "------deal_testHotKey,o(╥﹏╥)o";
}
com.alibaba.csp.sentinel.slots.block.BlockException
o 配置
o 默认
• @SentinelResource(value = “testHotKey”)
• 异常打到了前台用户界面,不友好
o 自定义
• @SentinelResource(value = “testHotKey”,blockHandler = “deal_testHotKey”) //value值与资源名一致即可
• 方法testHostKey里面第一个参数只要QPS超过每秒1次,马上降级处理
o 测试
• error (1秒1下可以,但是,超过则降级,和p1参数有关)
http://localhost:8401/testHotKey?p1=abc
• error(1秒1下可以,但是,超过则降级,和p1参数有关)
http://localhost:8401/testHotKey?p1=abc&p2=33
• right(狂点不会触发降级,与p2参数无关)
http://localhost:8401/testHotKey?p2=abc
o 上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
o 特殊情况
• 普通
超过1秒钟一个后,达到阈值1后马上被限流
• 我们期望p1参数当它是某个特殊值时,它的限流值和平时不一样
• 特例
假如当p1的值等于5时,它的阈值可以达到200
o 配置
• 添加按钮不能忘
o 测试
http://localhost:8401/testHotKey?p1=5 对
http://localhost:8401/testHotKey?p1=3 错
• 当p1等于5的时候,阈值变为200
• 当p1不等于5的时候,阈值就是平常的1
o 前提条件
• 热点参数的注意点,参数必须是基本类型或者String
o 手贱添加异常看看…
o @SentinelResource
• 处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理
o RuntimeException
• Int age = 10/0;这个是java运行时报出的运行时异常RuntimeException,@SentinelResource不管
o 总结:
• @SentinelResource主管配置出错,运行出错该走异常走异常
https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81
…