SpringCloud 学习笔记
1. 简介
架构的演进
单体架构 -> SOA -> 微服务
1.1 微服务
1.1.1 什么是微服务?
是以开发一组小型服务的方式来开发一个独立的应用系统。其中每个小型服务都运行在自己的进程中,并经常采用HTTP资源API这样轻量级的机制来相互通信。这些服务围绕业务功能进行构建,并能够通过全自动的部署机制来进行独立部署。这些微服务可以通过不同的语言来编写,并且可以使用不同的存储技术。对这些为服务我们仅做最低限度的集中管理 - Martin Flowler
1.1.2 微服务有什么特点?
1) 一系列独立运行的为服务共同构建起整个系统
2) 每个微服务可以独立运行在自己的进程里
3) 每个微服务为独立业务开发,一个微服务一般完成某个特定功能,比如:订单管理,用户管理等;
4) 微服务之间通过一些轻量级的通信机制进行通信,例如REST API或RPC方式进行调用。
1.1.3 微服务的优点?
1) 易于开发和维护
2) 启动较快
3) 局部修改容易部署
4) 技术栈不受限制
5) 按需伸缩
6) DevOps
1.1.4 微服务带来的挑战?
1) 运维要求较高
2) 分布式的复杂性
3) 接口调整成本高
4) 重复劳动
1.1.5 设计原则
1) 单一职责
2) 服务自治
3) 轻量级通信
4) 接口明确
1.1.6 微服务开发框架
SpringCloud
Dubbo
Dropwizard
Consul, etcd&etc
1.1.7 微服务架构示例
1.2 SpringCloud
1.2.1 什么是SpringCloud
在SpringBoot基础之上,快速构建分布式系统的工具集
2. 环境搭建
SpringCloud基于SpringBoot构建,搭建SpringCloud环境,首先要确定使用哪个SpringCloud版本,可以在地址https://spring.io/projects/spring-cloud查看最新发布的SpringCloud版本。
2.1 搭建步骤
2.1.1 配置父pom.xml
在父pom中配置Springboot和定义SpringCloud的版本。
spring-cloud-1.x版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR5</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
spring-cloud-2.x版本
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2.1.2 配置Eureka
见注册中心章节
3. 注册中心
3.1 Eureka
3.1.1 Eureka简介
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。Spring Cloud将他集成在其子项目Spring-Cloud-Nexflix中,以实现Spring Cloud的服务发现功能。
3.1.2 Eureka原理
3.1.3 配置Eureka服务端
3.1.3.1 Maven依赖
springcloud1.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
springcloud2.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
3.1.3.2 单节点配置
单节点Standalone Eureka Server
application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost # 实例
prefer-ip-address: true
# ip-address: 127.0.0.1 # 配置实例的IP地址
lease-renewal-interval-in-seconds: 30 # 服务续约的时间间隔,默认30秒
lease-expiration-duration-in-seconds: 90 # 最小过期时长(每个30秒发一次心跳,如果90秒没有收到心跳,则认为过期)
client:
register-with-eureka: false # 是否注册到eureka
fetch-registry: false # 是否从注册中心拉取配置
registry-fetch-interval-seconds: 30 # 从注册中心拉起配置的时间间隔
service-url:
defaultZone: http://localhost:8761/eureka # 在哪个地址查找eureka实例
server:
wait-time-in-ms-when-sync-empty: 0 # 当 eureka启动时,不能从集群获取,应该等多久
enable-self-preservation: true # 是否开启自我保护
eviction-interval-timer-in-ms: 60000 # 服务失效剔除时长,默认
peer-eureka-nodes-update-interval-ms: 10000 # 获取数据间隔
enable-self-preservation
Eureka自我保护机制:当服务未按时进行心跳续约时,Eureka实例会统计服务实例最近15分钟心跳续约的比例是否低于85%。因为网络延时原因,心跳失败实例的比例很有可能超标,但是此时把服务剔除列表并不妥当,因为服务可能没有宕机。Eureka在这段时间内不会剔除任何服务实例,知道网络恢复正常。生产环境下这很有效,保证了大多数服务依然可用,不过也有可能获取到失败的服务实例,因此服务调用者必须做好服务调用的失败容错。
3.1.3.3 启动Eureka Server
@SpringBootApplication
@EnableEurekaServer
public class EurekaApp {
public static void main(String[] args) {
SpringApplication.run(EurekaApp.class, args);
}
}
3.1.3.4 高可用配置(Peer Awareness)
3.1.3.4.1 双节点高可用
1) 配置 hosts文件
127.0.0.1 peer1
127.0.0.1 peer2
2) 配置 hosts文件
application-peer1.yml
# 应用名
spring:
application:
name: eureka-server
# 提供服务端口
server:
port: 8761
# 提供服务的域名,这里在hosts文件中修改了
eureka:
instance:
hostname: peer1
# 向第二个注册中心注册自己
client:
service-url:
defaultZone: http://peer2:8762/eureka/
application-peer2.yml
# 应用名
spring:
application:
name: eureka-server
# 提供服务端口
server:
port: 8762
# 提供服务的域名,这里在hosts文件中修改了
eureka:
instance:
hostname: peer2
# 向第二个注册中心注册自己
client:
service-url:
defaultZone: http://peer1:8761/eureka/
3.1.3.4.2 三节点高可用
3) 配置 hosts文件
127.0.0.1 peer1
127.0.0.1 peer2
127.0.0.1 peer3
4) application.yml
spring:
application:
name: eureka-ha
---
server:
port: 8761
spring:
profiles: peer1
eureka:
instance:
hostname: peer1
client:
serviceUrl:
defaultZone: http://peer2:8762/eureka/,http://peer3:8762/eureka/
---
server:
port: 8762
spring:
profiles: peer2
eureka:
instance:
hostname: peer2
client:
serviceUrl:
defaultZone: http://peer3:8761/eureka/,http://peer1:8762/eureka/
---
server:
port: 8763
spring:
profiles: peer3
eureka:
instance:
hostname: peer3
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/
测试
1) 使用mvn package打包项目
2) 使用下面命令启动两个Eureka Server节点
java -jar discovery-cluster-0.0.1-SNAPSHOT.jar --spring.profiles.active=peer1
java -jar discovery-cluster-0.0.1-SNAPSHOT.jar --spring.profiles.active= peer2
3) 在浏览器上分别输入http://peer1:8761和http://peer2:8762查看注册的服务。
3.1.3.5 Zones和Regions的高可用
待补充
3.1.3.6 密码访问
1) 引入依赖包
<dependency>
<groupId>org
.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2) 添加用户名密码
在
application.properties
加入认证的用户名和密
spring:
security:
user:
name: root
password: root
3) 在访问路径上加入用户名密码
eureka.client.serviceUrl.defaultZone=http://${security.user.name}:${security.user.password}@127.0.0.1:${server.port}/eureka/
3.1.4 注册Eureka客户端
3.1.4.1 Maven依赖
springcloud2.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
3.1.4.2 单节点配置
application.yml
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
instanceId: ${spring.application.name}:${spring.application.instance_id:${spring.cloud.client.ip-address}:${server.port}}
eureka 客户端Zone url
eureka:
client:
serviceUrl:
defaultZone: http://soosky:123321@localhost:8761/eureka
是否健康检查
eureka:
client:
healthcheck:
enabled: true
状态页面和健康探测
eureka:
instance:
statusPageUrlPath: ${management.context-path}/info
healthCheckUrlPath: ${management.context-path}/health
实例名称和IP显示
eureka:
instance:
prefer-ip-address: true
instanceId: ${spring.application.name}:${spring.application.instance_id:${spring.cloud.client.ip-address}:${server.port}}
是否使用ribbon
ribbon:
eureka:
enabled: false
microservice-provider-user:
ribbon:
listOfServers: localhost:7900
自定义ribbon配置
microservice-consumer-movie-ribbon-properties-customizing:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
说明:标红的为实例名称
注册安全应用
https://TODO
3.1.4.3 高可用配置
eureka:
client:
serviceUrl:
defaultZone: http://peer1:8761/eureka/,http://peer2:8762/eureka/
3.1.5 Eureka配置概览
http://localhost:8761/eureka/apps
http://localhost:8761/eureka/apps/MICROSERVICE-PROVIDER-USER
Appendix: Compendium of Configuration Properties
4. 配置中心
应用程序的结构
SpringBoot会在启动的时候会运行引导程序,初始化一个应用程序上下文 ApplicationContext,ApplicationContext parentAppCxt = cxt.getParent();
父应用程序上下文会加载bootstrap.yml中的配置信息,应用程序上下文会加载application.yml中的配置信息,同时也获取到了父应用程序上下文初始化的配置信息。
4.1 配置服务端
提供访问配置的服务接口
对属性进行加密和解密
4.1.1 maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
4.1.2 application.yml中配置
spring.profiles: 可配置的值
l git: 从git仓库中加载配置文件
l subversion: 会从Svn仓库对读取配置文件
l native: 会从本地的文件系统中读取配置文件
l vault: 会从Vault中读取配置文件,Vault是一款资源控制工具,可对资源实现安全访问
4.1.2.1 基于Git的配置文件
git: 默认值,会从Git仓库读取配置文件
4.1.2.2 基于本地配置文件的配置
spring:
application:
name: ics-config-server
profiles:
active: native # 说明从本地加载配置文件
cloud:
config:
server:
native:
searchLocations: classpath:/conf
备注:如果配置文件使用了profile进行了区分环境(application-dev.yml, application-prod.yml),需要在命令行添加参数 spring.profiles.active=native,dev
4.1.2.3 基于SVN的配置
SVN内容如下
配置内容
spring:
profiles:
active: subversion
cloud:
config:
server:
svn:
uri: svn://www.kkwrite.com/demo-springcloud-config
username: wangke
password: wangke0809
default-label: conf
default-label 配置项目在svn下的目录名称
注意:配置文件的名称中间必须有一个“-”,如my-config.yml, 如果没有会访问不到
4.1.3 启动类
@SpringBootApplication
@EnableConfigServer
public class ConfigServerApp {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigServerApp.class).run(args);
}
}
访问:
http://localhost:8888/my-config.yml
4.1.4 配置访问用户名密码
1) 加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2) 服务端增加配置
application.yml
security:
user:
name: root
password: root
3) 客户端配置增加用户名密码
4.1.5 配置加密解密
4.1.5.1 JCE对称加密
4.1.5.1.1 下载 JCE
UnlimitedJCEPolicyJDK7.zip
将local_policy.jar, US_export_policy.jar 复制到JAVA_HOME/jre/lib/security目录下,替换掉之前的。
配置服务器加密端点:/encrypt
配置服务器解密端点:/decrypt
4.1.5.1.2 加密请求测试类
public class EncryptUtil {
public static void main(String[] args) throws Exception {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost("http://localhost:8888/encrypt");
HttpEntity entity = new StringEntity("soosky", Consts.UTF_8);
post.setEntity(entity);
HttpResponse response = client.execute(post);
String content = EntityUtils.toString(response.getEntity());
System.out.println("content = " + content);
}
}
4.1.5.1.3 解密请求测试类
public class DecryptUtil {
public static void main(String[] args) throws Exception {
CloseableHttpClient client = HttpClients.createDefault();
HttpPost post = new HttpPost("http://localhost:8888/decrypt");
HttpEntity entity = new StringEntity("dd2a9105661c9efe70a4151f60d6ca3554343f78889c75fc4286706f7d66c362", Consts.UTF_8);
post.setEntity(entity);
HttpResponse response = client.execute(post);
String content = EntityUtils.toString(response.getEntity());
System.out.println("content = " + content);
}
}
注意:要机密成功,spring-cloud版本经过测试是 Dalston.SR1 版本可以,Dalston.SR2、Dalston.SR3、Dalston.SR4、Dalston.SR5不能正常加密,待查找原因
<!-- spring-cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
4.1.5.1.4 SVN加密配置
test:
user:
name: wangke-dev
password: '{cipher}dd2a9105661c9efe70a4151f60d6ca3554343f78889c75fc4286706f7d66c362'
服务器碰到{cipher}开头的会自动解密
xxx.yml中需要加'; xxx.properties中不需要
4.1.5.2 jdk的keytool非对称加密
1) 使用jdk的keytool生成keystore
keytool -genkeypair -alias "wangke0809" -keyalg "RSA" -keystore "d: mpwang.keystore"
2) 复制wang.keystore到配置中心服务器的src/main/resources目录下
3) 配置keystore
encrypt:
keyStore:
location: classpath:/wang.keystore
password: 123321 # 密钥库密码
alias: wangke0809 # 密钥对的别名
secret: 123321 # 密钥口令
4) svn参数配置
test:
user:
name: wangke-dev
password: '{cipher}AQCvxSFCARy9i7o/IoPZN9ZFImF+lbrN0+aHVWveWL7mELob8r4IS+PN7fQdFE50yDzHS4Oyo8BpsrkinEOqgOfRWPRm2e0EB+/maZO/LVqiJDYVLiY9MrZhJdU/pZG/btRQbwVux9X+BNOIThWfkExa0wUi41zhw2z2IvlhvntERasLClnFcnTAVSly6dbmKhcJ2EZHTe0Gb7fgqS6poShWjT3dv9gegxz+EdXNhygCsMPfOs2RXs7ZYcLB7/o5sQufuNZ5oT28KvSGd89CcVyuy7IxzrFBKvYgW1ZBMz+rPhBCKI7jDq9yBr+hfr9WLfacVVrhNfa4FIE+L2IVHkBom5D1dmeXhw6gf/9SZtDMWFlVuBQxqg0Ar1DOhiweQIU='
4.1.6 检测配置仓库健康状态
spring:
profiles:
active: subversion
cloud:
config:
server:
svn:
uri: svn://www.kkwrite.com/demo-springcloud-config
username: wangke
password: wangke0809
default-label: conf
health:
repositories:
test-service:
label: conf
访问:http://localhost:8888/health
注意:如果服务器端加入了安全机制,相应的客户端也需要加入安全配置
4.1.7 服务器配置访问规则
在配置服务器上访问客户端的配置,服务器配置访问规则
² /{application}-{profile}.yml
例如:http://192.168.1.114:10021/redis-dev.yml
² /{application}/{profile}[/{label}]
² /{label}/{application}-{profile}.yml
² /{application}-{profile}.properties
² /{label}/{application}-{profile}.properties
例如 http://localhost:8888/my-config/dev/conf
4.2 配置客户端
绑定配置服务器,使用远程的属性来初始化Spring容器
对属性进行加密和解密
属性改变时,可以对他们进行重新加载
提供了与配置相关的几个管理端点
在初始化引导程序的上下文时,进行绑定配置服务器和属性解密等工作。
4.2.1 maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
或者
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
spring-cloud-starter-config中包含了spring-cloud-config-client
4.2.2 配置bootstrap.yml
1) 配置方式一
spring:
application:
name: demo-springcloud-config-client
cloud:
config:
uri: http://localhost:8888
profile: dev # 会读取 serviceId + dev 的配置文件
2) 配置方式二(不配置spring.application.name):
server:
port: 1091
spring:
cloud:
config:
uri: http://localhost:8888
profile: dev
name: my-config # 会读取 my-config-dev.yml 的配置文件
备注:如果不提供spring.cloud.config.name 和 spring.application.name默认读取application-dev.yml文件
读取多份配置文件:spring.cloud.config.profile=hystrix,zuul
读取目录配置:spring.cloud.config.label=…
3) 配置方式三(不配置uri,配置中心通过 service-id指定):
spring:
cloud:
config:
fail-fast: true
discovery:
enabled: true
service-id: demo-springcloud2-configuration
profile: dev
name: demo-springcloud2-zuul
4.2.3 启动类
@SpringBootApplication
public class ConfigClientApp {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigClientApp.class).run(args);
}
}
说明: SpringCloud2 启动类可以省略 @EurekaClient, @EnableDiscoveryClient 注解
4.2.4 测试Controller
@RestController
public class ClientCtrl {
@Autowired
private Environment env;
@GetMapping("/getprop")
public String getProp() {
return env.getProperty("test.user.name");
}
@GetMapping("/getpwd")
public String getPwd() {
return env.getProperty("test.user.password");
}
}
4.2.5 刷新配置
4.2.5.1 客户端添加依赖包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
4.2.5.2 暴露端点
SpringCloud 1.x
management:
security:
enabled: false
否则会报:
{
"timestamp":1514441597157,
"status":401,
"error":"Unauthorized",
"message":"Full authentication is required to access this resource.",
"path":"/refresh"
}
向刷新节点http://localhost:1091/refresh发送一个post请求,即可刷新配置。
SpringCloud 2.x
暴漏端点配置
management:
security:
enabled: false # 关闭验证
endpoints: # 开放监控端口
web:
exposure:
include: "*" # 这里暴露了所有端点,也可以只暴露 bus-refresh
向刷新端点http://localhost:8888/actuator/bus-refresh发送一个post请求,即可刷新配置。
4.2.5.3 刷新bean
Spring容器中有很多Bean,都是根据某个属性值进行初始化的,配置一旦更新,需要重建这个Bean实例,为了解决这个问题,Spring Cloud提供了@RefreshBean注解。
当/refresh端点被访问时,负责处理刷新的ContextRefresher类会先去远程的配置服务器刷新配置,然后再用RefreshBean的refreshAll方法来处理实例,使用@RefreshBean注释进行修饰的bean都会在缓存中销毁,当这些bean被再次引用时,就会创建新的实例,以达到刷新的效果
/**
* RegConsumer 配置类
*/
@Component("regConsumerConfig")
@RefreshScope
public class RegConsumerConfig {
@Value("${consumer.ssl.wx.apiclient_cert}")
private String apiclientCert;
public String getApiclientCert() {
return apiclientCert;
}
public void setApiclientCert(String apiclientCert) {
this.apiclientCert = apiclientCert;
}
}
4.2.6 客户端错误提前与重试
客户端启动的时候首先去连接配置中心,如果连接不上进行一定次数的重试,如果达到最大重试次数还是连接不上,则抛出异常,启动失败
加入依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
配置文件配置
spring:
cloud:
config:
failFast: true
4.2.7 安全配置
spring:
cloud:
config:
username: root
password: root
注意服务器端需要进行相应的安全配置
4.3 配置中心应用
4.3.1 将配置中心服务器注册到eureka
pom.xml
<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>
<parent>
<groupId>com.kkwrite.demo</groupId>
<artifactId>demo-springcloud-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>demo-springcloud-config-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency> -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
启动类
package com.kkwrite.demo.config;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.config.server.EnableConfigServer;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
@EnableConfigServer
public class ConfigServerApp {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigServerApp.class).run(args);
}
}
application.yml
server:
port: 8888
#encrypt:
# key: mykey
encrypt:
keyStore:
location: classpath:/wang.keystore
password: 123321 # 密钥库密码
alias: wangke0809 # 密钥对的别名
secret: 123321 # 密钥口令
spring:
application:
name: demo-springcloud-config-server
profiles:
active: subversion
cloud:
config:
server:
svn:
uri: svn://www.kkwrite.com/demo-springcloud-config
username: wangke
password: wangke0809
default-label: conf
health:
repositories:
test-service:
label: conf
#security:
# user:
# name: root
# password: root
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
instanceId: ${spring.application.name}:${spring.application.instance_id:${spring.cloud.client.ipAddress}:${server.port}}
4.3.2 将配置中心客户端注册到eureka
pom.xml
<?xml version="1.0"?>
<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>
<parent>
<groupId>com.kkwrite.demo</groupId>
<artifactId>demo-springcloud-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>
<artifactId>demo-springcloud-config-client</artifactId>
<name>demo-springcloud-config-client</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
</dependencies>
</project>
启动类
package com.kkwrite.demo.config;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class ConfigClientApp {
public static void main(String[] args) {
new SpringApplicationBuilder(ConfigClientApp.class).run(args);
}
}
bootstrap.yml
spring:
application:
name: demo-springcloud-config-client
cloud:
config:
#username: root
#password: root
failFast: true
discovery:
enabled: true
service-id: demo-springcloud-config-server
#uri: http://localhost:8888
profile: dev
name: my-config # 会读取 my-config-dev.yml 的配置文件
#label: client-label
application.yml
server:
port: 1091
management:
security:
enabled: false
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
instanceId: ${spring.application.name}:${spring.application.instance_id:${spring.cloud.client.ipAddress}:${server.port}}
5. Ribbon
负载均衡分客户端负载均衡,服务器端负载均衡。Ribbon是客户端负载均衡。
5.1 Ribbon
5.1.1 Maven依赖
springcloud1.x依赖
<!-- 因为之前的类已经引用了,所以这里不用再引入依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-ribbon</artifactId>
</dependency>
springcloud2.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
5.1.2 启用Ribbon
@SpringBootApplication
@EnableEurekaClient
public class ConsumerMovieRibbonApp {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
new SpringApplicationBuilder(ConsumerMovieRibbonApp.class).web(true).run(args);
}
}
5.1.3 RestTemplate和Ribbon调用服务端
@RestController
@RequestMapping("/moviectrl")
public class MovieCtrl {
@Autowired
private RestTemplate restTemplate;
@GetMapping("/findbyid/{id}")
public User findById(@PathVariable Long id) {
return restTemplate.getForObject("http://microservice-provider-user/userctrl/findbyid/" + id, User.class);
}
}
注意1:
如果没有加入ribbon(注入 RestTemplate时没有用 @LoadBalanced), 执行restTemplate.getForObject("http://microservice-provider-user/userctrl/findbyid/" + id, User.class)语句,其中中的虚拟IP(红色部分)会找不到,报UnKnowHost…错误!
5.1.4 Ribbon实现原理
@LoadBalanced注解标记RestTemplate配置到拦截器LoadBalancerClient。
通过拦截器 LoadBalancerClient 拦截restTemplate请求。
拦截接口 LoadBalancerClient
拦截实现 RibbonLoadBalancerClient
6. Feign
6.1 什么是Feign
Feign翻译中文是假装、装作的意思。
Feign是Netflix开发的声明式、模板化的HTTP客户端, Feign可以帮助我们更快捷、优雅地调用HTTP API,并且Feign还整合了Eureka和Ribbon。
Feign可以把Rest请求进行隐藏,伪装成类似SpringMVC的Controller一样,这样就不用再进行url拼接,参数拼接等。
6.2 Feign
6.2.1 Maven依赖
springcloud1.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
</dependency>
springcloud2.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
6.2.2 启用Feign
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class ConsumerFeignApp {
public static void main(String[] args) {
new SpringApplicationBuilder(ConsumerFeignApp.class).web(true).run(args);
}
}
6.2.3 Feign接口定义
/**
* name表示请求哪个微服务,这里请求的是 mcs-provider 微服务,name表示用户微服务的vip
* mcs-gateway 是网关,路由到了 mcs-provider
*/
@FeignClient(name = "mcs-gateway")
public interface UserClient {
/**
* 根据 id 查询用户
* 这里的请求路径为 服务提供者的对应方法的路径
* @param id
* @return
*/
@RequestMapping(value = "/getUserById/{id}", method = RequestMethod.GET)
public UserDTO getUserById(@PathVariable("id") Long id);
@RequestMapping(value = "/getUserByNames", method = RequestMethod.GET)
public List<UserDTO> getUserByNames(@RequestParam("username") String username, @RequestParam("chineseName") String chineseName);
@RequestMapping(value = "/getUsers", method = RequestMethod.POST)
public List<UserDTO> getUsers(@RequestBody UserQuery userQuery);
@RequestMapping(value = "/saveUser", method = RequestMethod.POST)
public UserDTO saveUser(@RequestBody UserVO userVO);
}
l name:指定 Feign Client 的名称,如果项目使用了 Ribbon,name 属性会作为微服务的名称,用于服务发现。
l url:url 一般用于调试,可以手动指定 @FeignClient 调用的地址。
l decode404:当发生404错误时,如果该字段为 true,会调用 decoder 进行解码,否则抛出 FeignException。
l configuration:Feign 配置类,可以自定义 Feign 的 Encoder、Decoder、LogLevel、Contract。
l fallback:定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback 指定的类必须实现 @FeignClient 标记的接口。
l fallbackFactory:工厂类,用于生成 fallback 类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码。
l path:定义当前 FeignClient 的统一前缀。
6.2.4 Feign接口调用
@RestController
@RequestMapping("/login")
public class LoginCtrl {
@Autowired
private UserClient userClient;
@GetMapping("/getUser/{id}")
public UserDTO getUser(@PathVariable Long id) {
System.out.println("id = " + id);
return userClient.getUserById(id);
}
@GetMapping("/getUserByUsername")
public List<UserDTO> getUserByUsername(String username) {
System.out.println("username = " + username);
return userClient.getUserByUsername(username);
}
@GetMapping("/getUserByNames")
public List<UserDTO> getUserByNames(String username, String chineseName) {
System.out.println("username = " + username + ", chineseName = " + chineseName);
return userClient.getUserByNames(username, chineseName);
}
@GetMapping("/getUserByBithdate")
public List<UserDTO> getUserByBithdate(Date birthdate) {
System.out.println("birthdate = " + birthdate);
return userClient.getUserByBirthdate(birthdate);
}
/**
* 根据 userQuery 查询用户
* 注意:http header 中需要设置 Content-Type:application/json 否则 UserQuery 对象属性无法获取到对应的值
* 参数:{"id":1,"username":"wangke","birthdate":1527156335494}
* @param userQuery
* @return
*/
@PostMapping("/getUsers")
public List<UserDTO> getUsers(@RequestBody UserQuery userQuery) {
System.out.println("userQuery = " + userQuery);
return userClient.getUsers(userQuery);
}
/**
* 保存用户
* 注意:http header 中需要设置 Content-Type:application/json 否则 UserQuery 对象属性无法获取到对应的值
* 参数:{"id":1,"userCode":"123","username":"wk","gmtModified":1527157033301}
* @param userVO
* @return
*/
@PostMapping("/saveUser")
public UserDTO saveUser(@RequestBody UserVO userVO) {
System.out.println("userVO = " + userVO);
UserDTO userDTO = userClient.saveUser(userVO);
System.out.println("userDTO = " + userDTO);
return userDTO;
}
/**
* 保存用户
* 注意:http header 中需要设置 Content-Type:application/json 否则 UserQuery 对象属性无法获取到对应的值
* 参数:[{"id":1,"userCode":"123","username":"wk","gmtModified":1527157033301},{"id":2,"userCode":"333","username":"wk3","gmtModified":1527157033302}]
* @param userVO
* @return
*/
@PostMapping("/saveUsers")
public List<UserDTO> saveUsers(@RequestBody List<UserVO> userVOs) {
System.out.println("userVOs = " + userVOs);
List<UserDTO> userDTOs = userClient.saveUsers(userVOs);
System.out.println("userDTOs = " + userDTOs);
return userDTOs;
}
}
6.2.5 Feign的配置
6.2.5.1 配置文件配置
ribbon: # 在 Feign 中 Ribbon 的配置
ConnectionTimeout: 1000 # Feign 连接超时配置,默认1000
ReadTimeOut: 1000 # 读取超时,默认1000
feign:
hystrix:
enabled: true # hystrix 熔断在 Feign 中的开关
请求和响应压缩配置
feign.compression.request.enabled=true
feign.compression.response.enabled=true
Feign request compression gives you settings similar to what you may set for your web server:
feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048
These properties allow you to be selective about the compressed media types and minimum request threshold length.
6.2.5.2 配置类配置
覆写Feign的默认配置
FeignClient 指定配置类
@FeignClient(name = "microservice-provider-user", configuration = Configuration1.class)
public interface UserFeignClient {
@RequestLine("GET /simple/{id}")
public User findById(@Param("id") long id);
}
FeignClient 配置类
@Configuration
public class Configuration1 {
/**
* 该注解默认契约用的是@RequestLine注解;
* RequestLine 如果不知名POST还是GET 就会报 Method get not annotated with HTTP method type (ex. GET, POST)。
* 如果要用@RequestMapping 注解怎么办,就要注入该契约,这时Fegin会使用SpringMvcContract契约
*/
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}
7. Hystrix
Hystrix翻译为豪猪,全省是刺,起到保护作用。
7.1 雪崩效应
微服务之间相互调用,关系错综复杂。如果一个服务A -> B, B->C。当C服务反应慢或占用连接不释放时,会引起B服务、A服务占用的连接也不能释放,服务积压,必然引起雪崩效应。
处理方法
1) 超时机制
2) 服务降级
服务接口调用超时,发生错误时,终止掉服务接口的调用,改为调用本地方法。
3) 熔断机制
为了解决服务的高并发,一旦达到规定的请求,进行服务熔断
4) 隔离机制
每个服务有自己的线程池,如果A,B服务。如果A线程池满,阻塞不会影响B线程的正常运行。
5) 限流机制
1) 存在问题
当服务提供者响应非常慢,那么消费者对提供者的请求就会被强制等待,直到服务返回。在高负载情况下,如果不做任何处理,这种问题很可能造成所有处理用户请求的线程被耗竭,而不能响应用户的进一步请求。
2) 解决方案
n 超时机制
通过网络请求其它服务时,都必须设置超时。正常情况下,一个远程调用一般都在几十毫秒内返回。当依赖的服务不可用,或者因为网络原因,响应时间会变得很长。而通常情况一下,一次远程调用对应一个线程/进程,如果响应太慢,那么这个线程/进程就得不到释放。而线程/进程都对应了系统资源,如果大量的线程/进程得不到释放,并且越积越多,服务资源就会被耗尽,从而导致资深服务不可用。所以必须为每个请求设置超时。
n 断路器模式
当依赖的服务有大量请求超时时,再让新的请求去访问已经没有多大意义,只会无谓的消耗系统资源。譬如设置超时时长为1秒,如果短时间内有大量的请求(如50)个在1秒内都得不到响应,就往往意味者异常,此时就没有必要让更多的请求去访问这个依赖,我们应该使用断路器避免资源浪费。
7.2 Hystrix作用
线程隔离、服务降级
7.2.1 线程隔离原理
为每个服务分配一定的线程池。比如给服务A分配有10个线程的线程池,当访问A服务的时候,由A服务线程池中的一个线程为其服务,如果服务阻塞,只会阻塞A服务线程池中的一个线程。
Hystrix为每个依赖服务调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,加速失败判定时间。
用户的请求将不再直接访问服务,而是通过线程池中空闲的线程来访问服务,如果线程池已满,或者请求超时则会进行降级处理;用户的请求故障时,不会被阻塞,更不会无休止的等待,至少可以看到一个执行结果(例如返回友好信息)。
触发Hystrix服务降级的情况:
² 线程池已满
² 请求超时
7.2.2 服务降级
7.3 Hystrix集成
7.3.1 Maven依赖
springcloud1.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
springcloud2.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
7.3.2 启用Hystrix
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class ConsumerMovieRibbonWithHystrixApp {
public static void main(String[] args) {
//SpringApplication.run(ConsumerMovieRibbonApp.class, args);
// 启动消费者,注意这里不能用 SpringApplication.run(...) 启动,否则使用 ribbon 会找不到服务提供者实例(或 Unknown Host)
new SpringApplicationBuilder(ConsumerMovieRibbonWithHystrixApp.class).web(true).run(args);
}
}
7.3.3 服务降级
7.3.3.1 方法级别实现
/**
* 获取用户以及用户订单信息
* 使用了 Hystrix 进行服务降级
*/
@GetMapping("/getUserWithOrder/{id}")
@HystrixCommand(/* fallbackMethod = "getUserWithOrderFail" */
commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="2000")
}
)
public Response getUserWithOrder(@PathVariable Long id) {
UserDTO userDTO = new UserDTO();
userDTO.setId(1L);
userDTO.setUsername("测试用户");
String orderId = "1";
String url = "http://DEMO-SPRINGCLOUD-2-PROVIDER-ORDER/demo-springcloud-2/provider-order/order/getOrderById/" + orderId;
Object response = restTemplate.getForObject(url, Object.class);
System.out.println("response = " + response);
return DataResult.ofSuccess(userDTO);
}
Hystrix方法级别参数配置
commandProperties = {
@HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds", value="2000")
}
配置hystrix参数,可以在 HystrixCommandProperties 类中查找具体都有哪些配置参数。
7.3.3.2 类级别实现
@RestController
@RequestMapping("/demo-springcloud-2/provider-user/user")
@DefaultProperties(defaultFallback = "defaultFallbackMethod")
public class UserController {
/**
* 获取用户以及用户订单信息
* 使用了 Hystrix 进行服务降级
*/
@GetMapping("/getUserWithOrder/{id}")
@HystrixCommand
public Response getUserWithOrder(@PathVariable Long id) {
UserDTO userDTO = new UserDTO();
userDTO.setId(1L);
userDTO.setUsername("测试用户");
String orderId = "1";
String url = "http://DEMO-SPRINGCLOUD-2-PROVIDER-ORDER/demo-springcloud-2/provider-order/order/getOrderById/" + orderId;
Object response = restTemplate.getForObject(url, Object.class);
System.out.println("response = " + response);
return DataResult.ofSuccess(userDTO);
}
public Response defaultFallbackMethod() {
return Result.ofFail();
}
}
7.3.3.3 服务降级配置
默认值对所有的配置
hystrix:
command:
default: // default可以换成具体的服务或方法,就只对具体服务或方法有用
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 # 服务调用超时时长
hystrix.command.default为Hystrix配置的头,
execution.isolation.thread.timeoutInMilliseconds 为一个配置项的key,1000为值。
具体的配置项可以在HystrixCommandProperties 类中查找。
7.3.4 服务熔断
服务熔断,也叫断路器,英文单词为CircuitBreaker。
7.3.4.1 熔断原理
在分布式系统中,服务调用方可以自己判断某些服务反应慢或者存在大量超时的情况是,能够主动熔断,防止整个系统被拖垮。
熔断器又3中状态:
l Closed 熔断器关闭。请求可以通过。
l Open 熔断器打开。请求不能通过。默认最近20次请求,50%都出现超时,就会将断路器打开,此时当用户再次访问该接口时会快速返回失败。
l Half Open 熔断器半打开。当熔断器打开后,进入5秒休眠窗时期,在5秒之后会进入半开状态,此时会放一定的请求通过,来测试请求是否正常,如果请求依然失败则重新进入打开状态,再次进入5秒休眠时间。如果访问正常,熔断器进入关闭状态。
7.3.4.2 服务熔断配置
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000 # 服务调用超时时长
circuitBreaker: # 断路器
enabled: true # 是否开启断路器,默认true
requestVolumeThreshold: 20 # 断路器阈值,默认20
sleepWindowInMilliseconds: 5000 # 休眠时间窗,默认值5000(5秒)
errorThresholdPercentage: 50 # 超时/失败百分比,默认50(50%)
hystrix.command.default为Hystrix配置的头,
circuitBreaker.enabled 为一个配置项的key,true为值。
具体的配置项可以在HystrixCommandProperties 类中查找。
这些参数也可以配置在方法级别(同方法级别服务降级的配置)
7.3.5 Feign 中使用 Hystrix
- application.yml 配置
feign:
hystrix:
enabled: true
- UserFeignClient类
@FeignClient(value = "demo-springcloud-provider", fallback = UserFeignClientFallBack.class)
public interface UserFeignClient {
@RequestMapping(value = "/userservice/queryuserbyid", method = RequestMethod.GET)
public Map<String, Object> queryUserById(@RequestParam("userId") Integer userId,
@RequestParam("username") String username);
}
- UserFeignClientFallBack类
@Component
public class UserFeignClientFallBack implements UserFeignClient {
@Override
public Map<String, Object> queryUserById(Integer userId, String username) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("retCode", "1001");
result.put("retMsg", "服务调用超时,请稍后重试");
return result;
}
}
7.4 Hystrix参数配置
7.4.1 降级策略
² 默认的降级策略
查看com.netflix.hystrix.HystrixCommandProperties获取默认的配置
² 隔离策略execution.isolation.strategy 有两种:
1) Thread 线程池隔离(默认)
2) Semaphore 信号量:适用于接口并发量高的情况,如每秒千次调用,导致线程开销过高,通常只适用于非网络调用,执行速度快
² execution.isolation.thread.timeoutInMilliseconds 超时时间,默认1000
² execution.timeout.enabled 是否开启超时限制
execution.isolation.semaphore.maxConcurrentRequests 隔离策略为信号量的时候,如果达到最大并发数后,后续请求会被拒绝,默认是10
官方文档:
https://github.com/Netflix/Hystrix/wiki/Configuration#execution.isolation.strategy
接口超时时间配置:
hystrix:
threadpool:
default:
coreSize: 10 #并发执行的最大线程数,默认10
command:
paas-file:
execution:
isolation:
thread:
timeoutInMilliseconds: 3600000
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 8000 #命令执行超时时间,默认1000ms
hystrix.command.default和hystrix.threadpool.default中的default为默认CommandKey
Command Properties
Execution相关的属性的配置:
hystrix.command.default.execution.isolation.strategy 隔离策略,默认是Thread, 可选Thread|Semaphore
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds 命令执行超时时间,默认1000ms
hystrix.command.default.execution.timeout.enabled 执行是否启用超时,默认启用true
hystrix.command.default.execution.isolation.thread.interruptOnTimeout 发生超时是是否中断,默认true
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests 最大并发请求数,默认10,该参数当使用ExecutionIsolationStrategy.SEMAPHORE策略时才有效。如果达到最大并发请求数,请求会被拒绝。理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。
semaphore应该占整个容器(tomcat)的线程池的一小部分。
Fallback相关的属性
这些参数可以应用于Hystrix的THREAD和SEMAPHORE策略
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests 如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10
hystrix.command.default.fallback.enabled 当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true
Circuit Breaker相关的属性
hystrix.command.default.circuitBreaker.enabled 用来跟踪circuit的健康性,如果未达标则让request短路。默认true
hystrix.command.default.circuitBreaker.requestVolumeThreshold 一个rolling window内最小的请求数。如果设为20,那么当一个rolling window的时间内(比如说1个rolling window是10秒)收到19个请求,即使19个请求都失败,也不会触发circuit break。默认20
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds 触发短路的时间值,当该值设为5000时,则当触发circuit break后的5000毫秒内都会拒绝request,也就是5000毫秒后才会关闭circuit。默认5000
hystrix.command.default.circuitBreaker.errorThresholdPercentage错误比率阀值,如果错误率>=该值,circuit会被打开,并短路所有请求触发fallback。默认50
hystrix.command.default.circuitBreaker.forceOpen 强制打开熔断器,如果打开这个开关,那么拒绝所有request,默认false
hystrix.command.default.circuitBreaker.forceClosed 强制关闭熔断器 如果这个开关打开,circuit将一直关闭且忽略circuitBreaker.errorThresholdPercentage
Metrics相关参数
hystrix.command.default.metrics.rollingStats.timeInMilliseconds 设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000
hystrix.command.default.metrics.rollingStats.numBuckets 设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10
hystrix.command.default.metrics.rollingPercentile.enabled 执行时是否enable指标的计算和跟踪,默认true
hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds 设置rolling percentile window的时间,默认60000
hystrix.command.default.metrics.rollingPercentile.numBuckets 设置rolling percentile window的numberBuckets。逻辑同上。默认6
hystrix.command.default.metrics.rollingPercentile.bucketSize 如果bucket size=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds 记录health 快照(用来统计成功和错误绿)的间隔,默认500ms
Request Context 相关参数
hystrix.command.default.requestCache.enabled 默认true,需要重载getCacheKey(),返回null时不缓存
hystrix.command.default.requestLog.enabled 记录日志到HystrixRequestLog,默认true
Collapser Properties 相关参数
hystrix.collapser.default.maxRequestsInBatch 单次批处理的最大请求数,达到该数量触发批处理,默认Integer.MAX_VALUE
hystrix.collapser.default.timerDelayInMilliseconds 触发批处理的延迟,也可以为创建批处理的时间+该值,默认10
hystrix.collapser.default.requestCache.enabled 是否对HystrixCollapser.execute() and HystrixCollapser.queue()的cache,默认true
ThreadPool 相关参数
线程数默认值10适用于大部分情况(有时可以设置得更小),如果需要设置得更大,那有个基本得公式可以follow:
requests per second at peak when healthy × 99th percentile latency in seconds + some breathing room
每秒最大支撑的请求数 (99%平均响应时间 + 缓存值)
比如:每秒能处理1000个请求,99%的请求响应时间是60ms,那么公式是:
(0.060+0.012)
基本得原则时保持线程池尽可能小,他主要是为了释放压力,防止资源被阻塞。
当一切都是正常的时候,线程池一般仅会有1到2个线程激活来提供服务
hystrix.threadpool.default.coreSize 并发执行的最大线程数,默认10
hystrix.threadpool.default.maxQueueSize BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
hystrix.threadpool.default.queueSizeRejectionThreshold 即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。因为maxQueueSize不能被动态修改,这个参数将允许我们动态设置该值。if maxQueueSize == -1,该字段将不起作用
hystrix.threadpool.default.keepAliveTimeMinutes 如果corePoolSize和maxPoolSize设成一样(默认实现)该设置无效。如果通过plugin(https://github.com/Netflix/Hystrix/wiki/Plugins)使用自定义实现,该设置才有用,默认1.
hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds 线程池统计指标的时间,默认10000
hystrix.threadpool.default.metrics.rollingStats.numBuckets 将rolling window划分为n个buckets,默认10
7.5 Hystrix 仪表盘
1) Hystrix Dashboard 依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
2) 启动类中添加 @EnableHystrixDashboard
3) 配置文件中增加
management:
endpoints:
web:
exposure:
include: "*"
4) 访问http://localhost:1031/hystrix
5) 访问http://localhost:1031/hystrix.stream
在中间的输入框输入:http://localhost:1031/hystrix.stream后点击Monitor Stream进入到Hystrix Dashboard 主页,不过这个时候一直时Loading状态,调用一个接口后可以看到如下界面:
圆圈表示流量, 流量越大,圆圈越大
Success 0|0 Timeout
Short-Circuited 0|0 Rejected
Bad Request 0|0 Failure
0.0% Error %
Host: 0.0/s 请求速率
Cluster:0.0/s
Circuit Closed 断路器的状态
Hosts 1 90th 0ms
Median 0ms 99th 0ms
Mean 0ms 99.5th 0ms
配置文件类
spring-configuration-metadata.json
采集到数据通过Server-Send Events 推送到前端。
它基于 HTTP 协议,目前除了 IE/Edge,其他浏览器都支持。
SSE 是单向通道,只能服务器向浏览器发送,因为流信息本质上就是下载。如果浏览器向服务器发送信息,就变成了另一次 HTTP 请求
8. Turbine
8.1.1 pom.xml中集成Turbine
SpringCloud-1.x版本
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-turbine</artifactId>
</dependency>
SpringCloud-2.x版本
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
8.1.2 启动类中集成
@SpringBootApplication
@EnableTurbine
public class TurbineApp {
public static void main(String[] args) {
SpringApplication.run(TurbineApp.class, args);
}
}
localhost:1101/turbine.stream?cluster=DEMO-SPRINGCLOUD-CONSUMER
9. Zuul
API网关服务请求路由、组合已经协议转换。客户端的所有请求都首先经过API网关,然后由它请求路由到合适微服务。它可以在Web协议(如Http协议与WebSocket协议)与内部使用的非Web协议之间友好转换。
l API网关的优点
认证、解决跨域、服务流量控制
l API网关的缺点
9.1 Zuul作用
电影《捉鬼敢死队》中的怪物Zuul在纽约引发巨大骚乱。在微服务中,Zuul就是守门的大BOSS,一夫当关,万夫莫开!
Zuul是Netflix的基于JVM的路由器和服务端的负载均衡器。提供代理、过滤、限流、路由功能。
n Zuul运行机制
pre、routing、post、error为zuul的几个阶段,而不是四个过滤器。每个阶段都有多个过滤器。
源码分析:
ZuulServlet extends HttpServlet, 在他的service方法中:
@Override
public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
try {
init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
// Marks this request as having passed through the "Zuul engine", as opposed to servlets
// explicitly bound in web.xml, for which requests will not have the same data attached
RequestContext context = RequestContext.getCurrentContext();
context.setZuulEngineRan();
try {
preRoute();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
route();
} catch (ZuulException e) {
error(e);
postRoute();
return;
}
try {
postRoute();
} catch (ZuulException e) {
error(e);
return;
}
} catch (Throwable e) {
error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
} finally {
RequestContext.getCurrentContext().unset();
}
}
9.2 集成Zuul
9.2.1 maven依赖
springcloud1.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
springcloud2.x依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
9.2.2 Zuul配置
server:
port: 1040
spring:
application:
name: ics-gateway-zuul
eureka:
client:
service-url:
defaultZone: http://www.kkwrite.com:8761/eureka
instance:
prefer-ip-address: true
zuul:
host:
connect-timeout-millis: 2000 # 定义连接超时时间 2 秒
socket-timeout-millis: 1000 # 定义socket连接超时时长 1 秒
max-total-connections: 200 # 定义http链接池大小
max-per-route-connections: 20 # 每个host的最大连接数
ribbon-isolation-strategy: semaphore # 限流配置
semaphore:
max-semaphore: 100 # 最大信号量
#ignored-services: '*' # 服务内部调用的服务,可以忽略
prefix: /api # 全局前缀
routes:
provider-user: # 路由ID
path: /provider-user/** # 匹配路径
serviceId: demo-springcloud-2-provider-user # 服务ID
#url: http://localhost:15001 # 服务匹配可以是服务ID,也可以是 url。通常使用服务ID,带有负载功能
provider-order: # 路由ID
path: /provider-order/**
serviceId: demo-springcloud-2-provider-order
路由配置可以简化为:
routes:
demo-springcloud-2-provider-user: /provider-user/**
备注:
如果配置了ribbon-isolation-strategy, 再配置hystrix就会不起作用
对于微服务ID为 ics-provider 的微服务配置访问路径为 /myusers/,users为唯一标识符。/* 只匹配一个级别,/**可以匹配多层。
Zuul默认会给每个微服务创建一个默认路由,默认路由的路由ID为微服务名。可以用ignored-services来关闭默认的路由配置。
ignored-services: # 配置忽略的微服务
- demo-springcloud-2-provider-order
9.2.3 启动Zuul
@SpringBootApplication
@EnableZuulProxy
public class IcsZuulApp {
public static void main(String[] args) {
SpringApplication.run(IcsZuulApp.class, args);
}
}
@EnableZuulProxy是一个组合注解
@EnableZuulProxy 是 @EnableZuulServer 的增强版
9.2.4 通过Zuul代理访问
9.2.4.1 原服务访问
http://localhost:1051/userservice/queryuserbyid?userId=1&username=wk
9.2.4.2 Zuul代理访问
http://localhost:1041/my-provider/userservice/queryuserbyid?userId=1&username=wk
9.2.4.3 消费者UserCtrl类
@RestController
@RequestMapping("/userctrl")
public class UserCtrl {
// zuul 代理地址前缀
private final String proxyPrefix = "http://demo-springcloud-zuul/my-provider/userservice";
// 服务地址前缀
private final String prefix = "http://demo-springcloud-provider/userservice";
@Autowired
private RestTemplate restTemplate;
@GetMapping("/qryuserbyid")
public Map<String, Object> qryUserById(Integer userId, String username) {
String url = prefix + "/queryuserbyid?userId=" + userId + "&username=" + username;
Map<String, Object> result = restTemplate.getForObject(url, Map.class);
return result;
}
@GetMapping("/qryuserbyid2")
public Map<String, Object> qryUserById2(Integer userId, String username) {
String url = proxyPrefix + "/queryuserbyid?userId=" + userId + "&username=" + username;
Map<String, Object> result = restTemplate.getForObject(url, Map.class);
return result;
}
}
9.3 Zuul配置
9.3.1 Zuul的路由配置
9.3.1.1 简单路由
9.3.1.2 跳转路由
9.4 Zuul过滤器
9.4.1 ZuulFilter
ZuulFilter是过滤器的顶级父类:
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
abstract public String filterType();
abstract public int filterOrder();
public boolean shouldFilter();
public Object run() throws ZuulException;
}
9.4.2 自定义参数过滤器
public class ParamFilter extends ZuulFilter {
private Logger logger = Logger.getLogger(ParamFilter.class);
@Override
public Object run() {
logger.info("[ run ] ParamFilter.run() 过滤器开始执行。");
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
Object accessToken = request.getParameter("token");
if (accessToken != null) {
return null;
}
logger.error("必须传入 token 参数!");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
try {
ctx.getResponse().getWriter().write("必须传入 token 参数!");
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
@Override
public boolean shouldFilter() {
return true; // 是否执行该过滤器,此处为true,说明需要过滤
}
@Override
public int filterOrder() {
return 0; // 优先级为0,数字越大,优先级越低
}
/**
* pre:可以在请求被路由之前调用
* route:在路由请求时候被调用
* post:在route和error过滤器之后被调用
* error:处理请求时发生错误时被调用
*/
@Override
public String filterType() {
return "pre"; // 前置过滤器
}
}
在主类中开启过滤器
/**
* 在主类中开启 ParamFilter
*/
@Bean
public ParamFilter paramFilter() {
return new ParamFilter();
}
9.5 负载均衡和熔断
hystrix: # Zuul 默认集成了 hystrix 和 ribbon,所有策略都做默认配置,熔断超时只有1s,很容易触发,因此我们手动配置
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 6000
ribbon:
ConnectionTimeout: 500
ReadTimeout: 2000
MaxAutoRetries: 2 # 读取超时重试次数
MaxAutoRetriesNextServer: 1 # 读取超时重拾读取其它机器的次数
注意:ribbon超时时长的真实值是:
ribbonTimeout = (ConnectionTimeout + ReadTimeout)* (MaxAutoRetries + 1)*(MaxAutoRetriesNextServer + 1)
具体代码在:org.springframework.cloud.netflix.zuul.filters.route.support .AbstractRibbonCommand
10. Gateway
10.1 什么是Gateway
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
相关概念:
Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
Filter(过滤器):这是org.springframework.cloud.gateway.filter.GatewayFilter的实例,我们可以使用它修改请求和响应。
工作流程:
客户端向 Spring Cloud Gateway 发出请求。如果 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Spring Cloud Gateway 的特征:
l 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
l 动态路由
l Predicates 和 Filters 作用于特定路由
l 集成 Hystrix 断路器
l 集成 Spring Cloud DiscoveryClient
l 易于编写的 Predicates 和 Filters
l 限流
l 路径重写
10.2 集成gateway
10.2.1 Maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
注意springcloud gateway使用的web框架为webflux,和springMVC不兼容。引入的限流组件是hystrix。redis底层不再使用jedis,而是lettuce。
Spring Cloud Gateway 是使用 netty+webflux 实现因此不需要再引入 web 模块。
10.2.2 gateway配置
server:
port: 8082
spring:
application:
name: demo-springcloud-2-gateway
cloud:
gateway:
routes:
- id: provider-user
uri: http://localhost:15001
predicates:
- Path=/provider-user/**
filters:
- StripPrefix=1 # StripPrefix=1 表示过滤器是去掉一个路径,即 /provider-user/** 去掉 /provider-user 变成 /**
id:我们自定义的路由 ID,保持唯一
uri:目标服务地址
predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
filters:过滤规则
上面这段配置的意思是,配置了一个 id 为 provider-user 的路由规则,当访问地址 http://localhost:8082/provider-user时会自动转发到地址:http:// localhost:15001/spring-cloud。但因有 StripPrefix=1, 所以会把路径中的第1个部分去掉,变成localhost:15001
10.2.3 代码实现路由
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("path_route", r -> r.path("/about")
.uri("http://ityouknow.com"))
.build();
}
上面配置了一个 id 为 path_route 的路由,当访问地址http://localhost:8080/about时会自动转发到地址:http://www.ityouknow.com/about。
10.2.4 路由规则
Spring Cloud Gateway 是通过 Spring WebFlux 的 HandlerMapping 做为底层支持来匹配到转发路由,Spring Cloud Gateway 内置了很多 Predicates 工厂,这些 Predicates 工厂通过不同的 HTTP 请求参数来匹配,多个 Predicates 工厂可以组合使用。
Predicate 来源于 Java 8,是 Java 8 中引入的一个函数,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。可以用于接口请求参数校验、判断新老数据是否有变化需要进行更新操作。
在 Spring Cloud Gateway 中 Spring 利用 Predicate 的特性实现了各种路由匹配规则,有通过 Header、请求参数等不同的条件来进行作为条件匹配到对应的路由。下图总结了 Spring Cloud 内置的几种 Predicate 的实现。
1) 通过时间匹配
2) 通过 Cookie 匹配
3) 通过 Header 属性匹配
4) 通过 Host 匹配
5) 通过请求方式匹配
6) 通过请求路径匹配
7) 通过请求参数匹配
8) 通过请求 ip 地址进行匹配
9) 组合使用