springcloud
项目的架构
单体结构
优点
开发简单
部署简单
维护简单
成本低
缺点
随着用户量增多,负载越来越高,负载均衡只能横向扩展
业务越来越复杂之后,导致框架结构越来越复杂,需求变动改动较大
随着数据增多/业务增多,可能导致war包/jar包体积越来越大。
优化
优化
横向增加服务器,让单台服务器变成多台机器的集群
垂直拆分模块,降低耦合度
数据库缓存等技术,压力也会增大,也要横向扩展
总结
适合小型创业公司
一个公司产品的最初产品,后续再重构优化
适合用户量较少的项目
一个单体架构的项目就足以支持公司的所有业务功能
例如:用户量只有十几人的xxx管理系统,xxxAPP内容管理系统,xxx政务系统
微服务框架
优点
可扩展性强
独立部署
开发比较灵活
复杂度可控
每个微服务都可以有自己的数据库
容错性高、高可用
比较适合大型企业/大型项目
缺点
故障排查困难,每次请求,都可能是一个请求链,涉及到多个微服务,每个微服务的日志可能独立存储,排查bug困难
服务监控困难
分布式的复杂性,调用其他的服务器上的服务,网络出错概率增加,延迟增高,连接超时情况增加
服务的互相依赖,导致修改接口/服务其他模块报错
运维成本增高
服务器成本增高
总结
适合成本预算充足的大型项目
适合高并发的互联网项目
适合用户体量较大的项目
不适合小型项目
微服务注册中心
认识微服务注册中心
什么是注册中心?
注册中心是微服务架构中最基础也是最重要的组件。
注册中心本质上是为了解耦微服务
注册中心主要用于提供服务的发现与注册
相当于微服务之间的通讯录,记录着所有微服务的地址
微服务之间的调用是通过注册中心来相互寻找。
为什么需要注册中心?
微服务是分布式的
微服务的数量和地址是动态变化的
所以需要引入一个额外的组件,来管理微服务
常见的注册中心
Eureka Nacos Consul Zookeeper
CAP理论
CAP理论是分布式系统中一个很重要的理论,它描述的是一个分布式系统最多只能满足CAP中的两个条件,不可能同时满足三个条件
C(Consistency):这里指的是强一致性。保证在一定时间内,集群中的各个节点会达到较强的一致性,同时,为了达到这一点,一般会牺牲一点响应时间。而放弃C也不意味着放弃一致性,而是放弃强一致性。允许系统内有一定的数据不一致情况的存在
A (Avalibility):可用性。意味着系统一直处于可用状态。个别节点的故障不会影响整个服务的运作
P(Partition Tolerance):分区容忍性。当系统出现网络分区等情况时,依然能对外提供服务。想到达到这一点,一般来说会把数据复制到多个分区里,来提高分区容忍性。这个一般是不会被抛弃的
对比注册中心
Eureka | Nacos | Consul | Zookeeper | |
---|---|---|---|---|
CAP | AP | CP/AP | CP | CP |
雪崩保护 | 有 | 有 | 无 | 无 |
创建方式 | 内部项目 | 外部程序 | 外部程序 | 外部程序 |
版本状态 | 停止升级 | 版本迭代 | 版本迭代 | 版本迭代 |
文档 | 英文 | 中文 | 英文 | 英文 |
SpringCloud集成 | 支持 | 支持 | 支持 | 不支持 |
Dubbo集成 | 支持 | 不支持 | 不支持 | 支持 |
Eureka服务端
创建Eureka服务端
Eureka的服务端是在项目内部创建
<!--EurekaServer依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
修改启动类
修改配置文件
server:
port: 8761
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka
访问Eureka服务监控网页
-
启动服务
注意:在启动的过程中会报Connection refused和Cannot execute request on any known server等错误信息,这里暂时不需要关注,这是由于Eureka的心跳机制所导致的问题,由于当前服务还没有启动成功,Eureka会在项目启动成功之前访问配置文件中配置的服务地址,所以会报出不可访问的错误。当当前服务端启动成功之后,会陆续出现心跳的日志,服务器这时候才真正注册上。
-
访问localhost:8761,出现以下页面则表示启动成功
服务监控网页说明
- System Status(注册中心基本信息)
Environment | 指定环境,默认为test,可以不改 |
---|---|
Data center | 数据中心 |
Current time | 当前系统时间 |
Uptime | 已运行时长 |
Lease expiration enabled | 是否启用租约过期。自我保护机制关闭时,该值默认是true,自我保护机制开启之后为false |
Renews threshold | server 期望在每分钟中收到的心跳次数 |
Renews (last min) | 上一分钟内收到的心跳次数 |
- DS Replicas(注册中心基本信息)
Instances currently registered with Eureka | 当前已注册到注册中心的服务 |
---|---|
- General Info (当前服务器基本信息)
total-avail-memory | 总共可用的内存 |
---|---|
environment | 环境名称,默认test |
num-of-cpus | CPU个数 |
current-memory-usage | 当前已经使用内存的百分比 |
server-uptime | 服务在线时间 |
registered-replicas | 相邻集群复制节点 |
unavailable-replicas | 不可用的集群复制节点 |
available-replicas | 可用的相邻集群复制节点 |
- Instance Info(当前实例基本信息)
ipAddr | 实例ip |
---|---|
status | 实例状态 |
监控网页上的红字提醒
Spring Eureka 服务注册中心在三种情况下会出现红色加粗的字体提示:
1)自我保护机制开启时(enable-self-preservation: true):
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
2)自我保护机制关闭时(enable-self-preservation: false):
RENEWALS ARE LESSER THAN THE THRESHOLD. THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
3)自我保护机制关闭了,但是一分钟内的续约数没有达到85%,可能发生了网络分区,会有如下提示
THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS.
Eureka的一些常用配置
eureka.instance.hostname
修改当前主机名称。在后续的高可用中需要用到
eureka.instance.appname
修改当前实例名称。实例名称用作在监控页面显示
eureka.client.registerWithEureka(或eureka.client.register-with-eureka)
是否注册自身到Eureka服务器。如果是单台且当前应用本身就是服务器,则可以把值设置为false
eureka.client.fetchRegistry(或eureka.client.fetch-registry)
是否从Eureka服务器获取注册信息。如果是单台且当前应用就是服务器,则可以把值设置为false
spring.application.name
声明当前应用名称,可作为监控网页的显示
创建高可用Eureka服务端
集群配置文件
假设我们有两台Eureka服务器,那么我们只需要将这两台服务器的注册中心地址填对方即可完成两两注册。为了方便测试,我们使用在同一个项目中采用多段配置的方式。配置文件如下所示
- Eureka Server 1
eureka:
instance:
appname: server
server:
#关闭自我保护
enable-self-preservation: false
#设置超时节点清理的间隔时间
eviction-interval-timer-in-ms: 10000
---
spring:
#配置别名
profiles: eurekaS1
eureka:
client:
service-url:
#注册到其他服务群,多个以逗号隔开
defaultZone: http://192.168.2.201:8762/eureka
server:
port: 8761
---
spring:
profiles: eurekaS2
eureka:
client:
service-url:
defaultZone: http://192.168.2.201:8761/eureka
server:
port: 8762
—表示多段配置,配置的应用可以在启动服务时启用(–spring.profiles.active=profiles的值。 如–spring.profiles.active=eurekaS1)。
- 在高可用环境中客户端的配置
在服务端高可用的情况下,客户端需要同时指向多台服务器,保证高可用性,避免某台节点故障之后导致不可访问的情况。
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka,http://127.0.0.1:8762/eureka
spring:
application:
name: client
客户端
Eureka客户端
- 创建Eureka客户端
<!--EurekaClint依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 修改启动类
- 修改配置文件
server:
port: 8081
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:8761/eureka
- 测试
在监控页面能看见两个启动的客户端,已经是测试成功了。
模拟服务注册与基本调用
- 创建测试项目
为了方便测试,我们可以创建一个项目包含多个module子项目来进行,具体操作如下:
- 创建一个SpringBoot项目,引入Web和Eureka Discovery,删掉项目中的src目录,将当前项目作为父项目。然后根据当前项目创建两个module子项目(SpringBoot的项目创建方式),分别是order-system和user-system。这两个项目不需要引入Web和Eureka Discovery。
- 修改父项目的pom文件,增加modules,引入当前项目的子项目
- 分别修改两个子项目的pom文件,将子项目的parent信息指向父项目
a. groupId、artifactId和version从父项目的pom拷贝
b.
c.如果没有个性化的配置的话,properties、dependencies、dependencyManagement和build配置信息都可以删除,引用父pom文件中的配置。
- 配置完成后启动两个子项目,验证是否可以成功启动并访问。
这里启动了一个client-order作为请求端
启动一个client-user作为被请求端
下面是user端的代码
@RestController
public class UserController {
@GetMapping("/query/{uid}")
public UserModel query(@PathVariable Integer uid){
UserModel userModel = new UserModel(uid,"这是用户模块");
return userModel;
}
}
下面是order端的请求代码
@RestController
public class OrderController {
@Resource
private EurekaClient eurekaClient;
@GetMapping("/query/user/{uid}")
public UserModel get(@PathVariable Integer uid) {
Application application = eurekaClient.getApplication("client-user");
//获取实例名为client-user的客户端列表,如果启动多个实例,则返回多个。
//必须已经启动实例client-user
List<InstanceInfo> instanceList = application.getInstances();
//获得第一个客户端 TODO:非空判断
InstanceInfo instanceInfo = instanceList.get(0);
String host = instanceInfo.getHostName();//ip地址
int port = instanceInfo.getPort();//端口号
String url = "http://"+host+":"+port+"/query/"+uid;//拼接请求地址
RestTemplate restTemplate = new RestTemplate();
UserModel userModel =restTemplate.getForObject(url,UserModel.class);
return userModel;
}
}
可以在访问order页面的时候获取user的数据,就是成功了。
客户端的常见问题
当客户端关闭或掉线之后,服务端监控页面仍然还显示?
基于CAP原则,Eureka只保证了AP(高可用),也就是Eureka某个节点在不可用时,会自动将请求转发至下一个节点,并不会将当前节点下线,而是等待恢复(这是考虑到了集群环境中网络延迟、通信堵塞等因素)。所以在监控页面我们仍然还是会看到连接请求。所以Eureka的哲学是 宁可放过一个,也不错杀一千。
如何让掉线的连接过期后自动下线呢?
将Server端的自我保护机制关闭,并修改清理间隔时间
eureka.server.enable-self-preservation=false (关闭自我保护)
eureka.server.eviction-interval-timer-in-ms=30*1000(单位毫秒)
调整客户端的租约更新时间间隔和到期时间
eureka.instance.lease-renewal-interval-in-seconds=10(更新时间间隔)
eureka.instance.lease-expiration-duration-in-seconds=30(到期时间)
Consul客户端
- Consul能做什么
服务注册发现
Consul客户能够注册一个服务,比如api或mysql,其他客户可以在Consul上查询一个指定服务的提供者。Consul提供DNS和HTTP的服务发现接口。
健康检查
Consul可以灵活的使用脚本等来检测注册在其上的服务是否可用,不健康的服务Consul也能够灵活处理,比如提供服务的主机内存使用超过90%,我们可以配置让Consul不要把这样的服务提供给服务调用者。
key/value存储
这个功能和etcd有些类似,可以通过HTTP API方便地使用。
多数据中心支持
Consul支持开箱即用的多数据中心支持,这意味着用户不用建立额外的抽象层让业务扩展到各个区域
- 官网下载
Spring Cloud Consul地址:https://spring.io/projects/spring-cloud-consul
启动和关闭指令
## 启动命令
consul agent -dev
##创建run.bat 放入启动命令指令 保存双击即可启动
## 关闭命令
consul leave
- 访问地址
说明服务端已经启动成功
-
SpringCloudConsul
- 创建客户端项目
也可以在springboot项目中手动导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!--监控可选 可以不加 不加不影响代码运行 consul监控页面会有个错误-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
- 修改配置文件
server:
port: 8088
spring:
application:
name: consul-user
cloud:
consul:
host: 127.0.0.1
port: 8500
discovery:
health-check-url: http://127.0.0.1:8500
-
修改启动类
-
测试
启动客户端之后,访问consul服务端网址,服务列表,可以看见客户端接入。
这里启动了一个consul-order作为请求端
启动一个consul-user作为被请求端
下面是order端的请求代码
@RestController public class OrderController { @Autowired private DiscoveryClient discoveryClient; @Autowired RestTemplate restTemplate; @GetMapping("/query/user/{uid}") public UserModel get(@PathVariable Integer uid) { //获取实例名为consul-user的客户端列表,如果启动多个实例,则返回多个。 //必须已经启动实例consul-user List<ServiceInstance> instanceList = discoveryClient.getInstances("consul-user"); //获得第一个客户端 ServiceInstance serviceInstance = instanceList.get(0); String host = serviceInstance.getHost();//ip地址 int port = serviceInstance.getPort();//端口号 String url = "http://"+host+":"+port+"/query/"+uid;//拼接请求地址 UserModel userModel =restTemplate.getForObject(url,UserModel.class); return userModel; } }
可以在访问order页面的时候获取user的数据,就是成功了。
Nacos客户端
- Nacos官网
- 目录结构
- Nacos启动
startup -m standalone 或者 直接双击
启动成功之后,可以看见nacos端口号是8848
访问本地Nacos
http://127.0.0.1:8848/nacos
- SpringCloudAlibaba+Nacos
也可以在SpringCloud项目中,直接加入依赖
<!--2.2.1 版本-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
- 修改配置文件
server:
port: 8066
spring:
application:
name: nacos-order
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
#这里的server-addr 端口号如果是80也不可以省略,如果是8848可以省略,可能报错
-
修改启动类
-
测试
启动客户端之后,访问nacose服务端网址,服务列表,可以看见客户端接入。
这里启动了一个nacos -order作为请求端
启动一个nacos -user作为被请求端
下面是order端的请求代码
@RestController
public class OrderController {
@Autowired
private DiscoveryClient discoveryClient;
@Autowired
RestTemplate restTemplate;
@GetMapping("/query/user/{uid}")
public UserModel get(@PathVariable Integer uid) {
//获取实例名为nacos-client-user的客户端列表,如果启动多个实例,则返回多个。
//必须已经启动实例nacos-client-user
List<ServiceInstance> instanceList = discoveryClient.getInstances("nacos-client-user");
//获得第一个客户端
ServiceInstance serviceInstance = instanceList.get(0);
String host = serviceInstance.getHost();//ip地址
int port = serviceInstance.getPort();//端口号
String url = "http://"+host+":"+port+"/query/"+uid;//拼接请求地址
UserModel userModel =restTemplate.getForObject(url,UserModel.class);
return userModel;
可以在访问order页面的时候获取user的数据,就是成功了。
Spring Cloud OpenFeign
Spring Cloud OpenFeign 基于Netflix Feign 实现的,并且实现了声明式的Web服务客户端定义方式,它使得web服务客户端更容易编写。为了更方便的与Spring 组件集成,Spring Cloud还为OpenFeign提供了SpringMVC的支持。当它配合Eureka和Ribbon可以很方便的实现客户端负载均衡。
所谓客户端负载均衡就是,客户端在启动时会拉取目前可用的服务,然后通过随机、轮询的机制在客户端对访问进行分流。
相较于我们在Eureka中的客户端调用的案例中,使用HttpClient调用方式,OpenFeign更简单明了,无论是配置还是使用起来都很方便。
OpenFeign环境要求
由于Spring Cloud OpenFeign是基于Netflix Feign实现的,所以它也与Spring Cloud Eureka一样都属于Netflix套件。版本要求也与Spring Cloud Eureka一样。
创建OpenFeign
引入OpenFeign
按照官方文档的描述,只需要在pom中引入openfeign的starter即可换成相关依赖引入。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
创建OpenFeign客户端
在SpringBoot的启动类上加入@EnableFeignClients注解即可完成OpenFeign的使用声明。
@SpringBootApplication
@EnableEurekaClient
@EnableFeignClients
public class OrderSystemApplication {
public static void main(String[] args) {
SpringApplication.run(OrderSystemApplication.class, args);
}
}
使用测试
- 被调用模块的handler
@RestController
public class UserHandler {
@GetMapping("query/{uid}")
public UserModel query(@PathVariable Integer uid){
UserModel userModel = new UserModel();
userModel.setUid(uid);
userModel.setUname();
return userModel;
}
}
- 调用模块创建一个接口
// 对应yml配置文件中被调用模块的application name,name不允许出现特殊字符
@FeignClient("userclient")
public interface UserClient {
//GetMapping("访问的全路径,不是方法上面的路径,要加上类上的路径")
//返回类型要对应 名字不需要
@GetMapping("query/{uid}")
UserModel getUserInfo(@PathVariable("uid") Integer uid);
}
- 调用模块像使用接口一样进行调用
@RestController
public class OrderHandler {
@Resource
private UserClient userClient;
//直接调用UserClient 像接口一样
@GetMapping("query/feign/{oid}/{uid}")
public OrderModel query(@PathVariable Integer oid, @PathVariable Integer uid){
UserModel userModel = userClient.getUserInfo(uid);
OrderModel orderModel = new OrderModel();
orderModel.setOid(oid);
orderModel.setUserModel(userModel);
return orderModel;
}
}
12345678910111213141516
OpenFeign的基本配置
- OpenFeign的基本配置信息在FeignClientProperties类中,所有的配置都是基于config的成员变量进行设置。具体可配置属性可参考FeignClientProperties类中的内部类FeignClientConfiguration中的成员属性。
- 超时配置建议在所有OpenFeign中进行配置。
feign:
client:
config:
default:
connectTimeout: 1000
readTimeout: 6000
##超时配置建议在所有OpenFeign中进行配置
微服务中的公共类
在微服务中,每一个服务都是单独存在的个体,各自治理。虽然是独立的个体,但是多多少少还会有一些对应的交集,比如实体类和各种工具类。那么在这样的情况下,我们应该怎么去设计整体架构呢?
在当前多模块的测试项目中,我们可以采取两种方案来解决问题:
1、将实体类和工具类拷贝到每个服务模块中。
2、创建一个公共模块,然后让每一个服务模块来引入当前公共模块。
上述两种方案中,很明显第二种更加便于维护及扩展。下面来介绍如何创建公共模块以及引用
创建公共模块
目录结构
common模块由于只负责管理公共类,并不需要容器和启动类,所以当前模块是使用maven创建的Java项目
pom文件配置说明
common 的pom文件
跟其他服务的pom一样,需要指向共同的父级,并指定当前的artifactId。
父级pom文件
在父级pom文件中同样需要将common加入到当前项目的子模块。同时为了方便下属其他子模块的引用,需要将common加入到公共依赖中。
打包公共模块
配置完所有的信息之后,只需要将公共模块打包即可在其他项目中引用。目前做测试使用,只需要使用maven install 将common打包到本地即可。
Spring Cloud Ribbon
Ribbon是一个客户端负载均衡器,在Feign中实际上也默认使用了Ribbon来做负载,且该功能是默认启用的。客户端负载实际上跟Nginx类似,都是讲访问请求转发到其他服务器。它通过Eureka注册中心的Application名称来获取服务器列表,然后在调用时通过负载策略来访问具体的服务器地址。
Ribbon环境要求
OpenFeign默认集成Ribbon,不需要额外的环境要求
测试Ribbon
为了方便测试,按照之前的项目架构,我们在Order系统中使用OpenFeign调用了User系统的一个接口,这里可以将User系统启动两个实例,并让User系统中的接口返回数据不同,来测试Ribbon的负载。创建多实例的方式很简单,可以修改Controller打两个返回数据不同的Jar,在开发中这样的方式肯定会很麻烦。下面介绍IDEA中运行多个实例的方式。
创建多实例
先将User系统中原有的UserController返回值改为1,另一个实例我们将这个返回值改为2,在测试时可以很方便的甄别负载的效果。
当运行实例中出现刚刚创建的实例时,就表示成功了。
启动完毕之后就有两个用户实例,分别是8080和8081端口。分别访问这两个端口下的UserController确保两个返回值不同之后,再访问Order服务接口,查看Order服务通过Feign调用的结果。
多次访问之后,会发现这个值是来回变动的,也就是说,Ribbon的负载默认采取的是轮询的机制。
修改Ribbon的负载策略
Ribbon除了默认提供的轮询策略以外,还有很多其他的负载策略供开发者选择。
策略类名 | 策略描述 |
---|---|
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server(active connections 超过配置的阈值) |
BestAvailableRule | 选择一个最小的并发请求的Server |
RandomRule | 随机选择一个Server |
RetryRule | 重试查找选择一个可用的Server |
RoundRobinRule | 轮询选择Server(默认策略) |
WeightedResponseTimeRule | 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 |
ZoneAvoidanceRule | 复合判断。判断区域是否可用,并过滤掉区域中的连接过多的Server |
配置负载策略也比较简单,只需要在yml中声明负载需要引用的类即可。
USERSERVICE:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
USERSERVICE是指在FeignClient中连接的服务名,负载策略是按照服务名单独来进行配置的
Spring Cloud Hystrix
Netflix提供了一个叫Hystrix的类库,它实现了断路器模式。在微服务架构中,通常一个微服务会调用多个其他的微服务。当某一个环节的微服务调用失败后,它将会导致上一层服务失败,服务访问越大则失败率越高,而这一连串的失败就是雪崩效益,下层失败导致上层所有服务崩溃。
断路器的作用就是,当发现某一服务调用失败后,告知调用者该接口失败,从而避免调用者服务资源消耗。而这样的处理就是我们经常会在微博以及淘宝上经常看到的繁忙页面或相关提示,这实际上就是做了服务熔断。
Hystrix环境要求
与其他netflix组件版本一致
创建Hystrix服务
官方架构图示意: 当API调用远程服务,而远程服务报错时,Hystrix将执行fallback方法来返回给api调用。
加入pom依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
创建Hystrix服务
启动类依赖
-
加入Hystrix注解
在order服务中直接加入**@EnableCircuitBreaker** 来启用Hystrix。
-
使用SpringCloudApplication
也可以直接使用**@SpringCloudApplication** 注解来代替**@SpringBootApplication**、@EnableDiscoveryClient和**@EnableCircuitBreaker**注解
修改服务调用方(Order服务)
使用**@HystrixCommand**注解标记在需要熔断的方法上,使用fallbackMethod声明当接口熔断时需要调用的方法。该方法可以返回一个默认提示文案或者默认对象,且该方法的入参参数列表必须与接口方法的参数列表保持一致。
修改服务提供方(User服务)
修改User服务的UserController,在对外开放的接口中加入抛出错误和模拟超时的线程睡眠。
抛出错误是为了测试服务调用方的服务降级。
线程睡眠是为了测试服务调用时的请求超时。
测试结果
当我们访问order服务时,无论是超时还是抛出异常,服务端都会响应在fallback中返回的对象。
配置扩展
基本示例
Hystrix的配置信息可以通过@HystrixCommand的commandProperties属性来进行配置。
具体可配属性在HystrixCommandProperties类中。
默认配置都在该类的
HystrixCommandProperties(HystrixCommandKey key, HystrixCommandProperties.Setter builder, String propertyPrefix)构造方法中完成。
属性详解
commandProperties是配置Hystrix配置的属性,该属性的值为@HystrixProperty类型的数组。@HystrixProperty中配置属性名和属性值即可完成属性配置
超时配置
Hystrix默认超时时间是1秒钟,当远程服务调用超过1秒则会判定该远程服务调用失败。
execution.isolation.thread.timeoutInMilliseconds:连接超时时间,单位毫秒,默认为1秒。
熔断机制设置
Hystrinx的熔断由三个参数的设置共同完成。
circuitBreaker.requestVolumeThreshold:默认值20,表示10秒内20个请求为一个轮回。
circuitBreaker.errorThresholdPercentage:默认值是50,表示50%的错误率。
circuitBreaker.sleepWindowInMilliseconds:默认值是5000,表示熔断后,拒绝所有请求5秒。
- 示例
配置详解
10秒内10个请求中错误率百分之60以上,则触发熔断,熔断后拒绝请求10秒钟。
注意 : Hystrix在熔断期间会对服务进行一次请求测试,如果服务能够正常访问,则会重新关闭熔断。
使用类全局配置
按照上诉配置方式,若一个类中存在多个接口需要进行熔断配置,则需要在每一个方法上加入注解和属性配置,这样非常麻烦。Hystrix也提供了类全局配置来方便开发者进行参数设置。使用**@DefaultProperties**即可完成全局声明。
当当前类中某个方法报错时,会默认按照defaultFallback属性指定的方法来执行(该方法参数列表必须为空)。
注意:@DefaultProperties 和 @HystrixCommand可以共存,后者的优先级大于前者。
Openfeign集成
Openfeign组件中默认是由集成Hystrix的,只需要在配置文件中开启即可使用Hystrix的功能。使用集成功能不需要像手动创建Hystrix服务那样繁琐,使用@DefaultProperties 和 @HystrixCommand注解,而是直接在@FeignClient上标记Hystrix的fallback,在yml中对Hystrix进行配置即可。
开启OpenFeign的Hystrix
-
OpenFeign中的Hystrix功能是默认关闭的。需要在配置文件中声明开启才可使用。
-
在yml配置文件中增加开启Hystrix的配置。
feign:
#开启feign的hystrix
hystrix:
enabled: true
配置FeignClient
FeignClient注解上有两个fallback参数,
分别是:fallback和fallbackFactory。
这两个参数的值类型都是class,class的实现的就是fallback相关的内容。
FeignClient的fallback
- 创建fallback类
自定义一个fallback类,这里以order系统中的userClient类为例,创建的fallback类,类名规范一般为XXXFallback,且XXXFallback类必须实现它需要接管的feignClient接口。
该类必须使用**@Component**注解将类注入到Spring容器中,否则Hystrix无法发现该类则熔断时会报错。
- 修改FeignClien
在服务调用端(Order)中修改FeignClient,加入fallback属性,并赋值定义好的fallback类。
测试user服务异常
当异常的时候,此时会发现并没有像之前创建的示例一样,出现抛出错误的情况。这是因为在OpenFeign中集成的fallback是实现client接口的,我们在fallback中当前是默认返回的**null**,而这个时候其实,hystrix已经在运行了。只是OpenFeign的hystrix是返回空或者自定义一个服务异常时协定的某个对象。
所以,在开发大型的项目,我们会提供一个公共的数据model来进行数据装载这样会更加利于后续的判断。所有的Controller都必须基于该model进行数据返回,
测试user服务超时
在**@HystrixCommand**中可以进行超时时间配置,在OpenFeign集成Hystrix环境中可以通过yml对超时做额外的配置。
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
注意:在使用OpenFeign+Hystrix测试请求超时时,务必确保OpenFeign请求超时相关配置正确
FeignClient的fallbackFactory
- 创建fallbackFactory类
fallbackFactory类需要实现FallbackFactory接口和当前实现的具体类型,fallbackFactory的作用与fallback类似,只是前者会获取client端的报错信息。
- 修改FeignClient
Sentinle
了解Sentinel
- Sentinel是什么
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。
Sentinel 是面向分布式服务架构的流量控制组件,
主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
- Sentinel特性
丰富的应用场景
Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控
Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况
广泛的开源生态
Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
完善的 SPI 扩展点
Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
- Sentinel的基本组成
核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,可以结合springcloud一起使用
控制台(Dashboard):控制台主要负责管理推送规则、监控、集群限流分配管理等
启动
就是启动jar包的指令
java -jar sentinel-dashboard-1.7.2.jar
##默认端口8080
访问控制平台
配置客户端
加入依赖
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
修改配置文件
spring:
application:
name: nacos-order
cloud:
sentinel:
transport:
port: 8719
dashboard: 127.0.0.1:8080
项目启动成功之后,请求一次页面,才能在管理页面看见对应的项目
Spring Cloud Config
Spring Cloud Config的基本原理
-
将所有的配置文件保存至版本管理工具
-
将配置中心的远程配置地址设置为版本控制地址,配置中心会从版本控制中拉取对应的配置文件并加载至本地。
-
将配置中心注册到Eureka完成服务注册。配置中心注册到注册中心之后,即可完成配置中心的高可用。
-
将所有的微服务配置改为链接Eureka发现配置中心,拉取指定配置中心的版本的配置文件。
-
修改微服务的配置文件为启动预加载。
创建 Config 服务端
使用向导创建项目
加入svn依赖
由于配置中心需要连接版本控制工具,所以当前项目需要加入SVN相关的依赖支持。
<dependency>
<groupId>org.tmatesoft.svnkit</groupId>
<artifactId>svnkit</artifactId>
<version>1.9.3</version>
</dependency>
上传配置文件至版本控制工具
将其他客户端的配置文件上传至版本控制工具,这里以svn举例。
- svn文件夹说明
svn作为版本协同工具,拥有创建多个分支的功能,多个分支标记着多个版本环境。用于在线服务回滚。
#url可作为config连接的地址
#分支版本可在config端连接时指定,默认是trunk目录
- 分支文件夹中的文件说明
分支文件夹下存放所有的客户端的配置文件,这里存放了三个版本的文件。分别是userService.yml、userService-dev.yml(开发环境)、userService-pro.yml(生产环境)。
这三个文件又有各自不同的作用域。其中两个通过名称很好理解。
userService-dev.yml 用于开发测试环境,后缀dev可以随意自定义。
userService-pro.yml 用于正式生产环境,后缀pro 可以随意自定义。
userService.yml 用来存放不同环境的公共配置信息(可省略)。
关于这几个文件的用法,在后续配置使用中会继续分析。
给启动类加入注解
控制中心需要实现高可用,除了SpringBoot规范中的开启配置中心服务注解以外,还需要加入Eureka客户端连接的注解
修改配置文件
属性详解:
spring.cloud.config.server.svn 配置svn相关的属性
uri :svn地址
username : svn账户
password :svn密码
basedir :下载的配置文件本地存放路径
default-label :引用分支,默认为trunk。若需要使用其他的分支需要声明属性并指定值
spring.profiles.active = subversion 指定配置中心使用svn
访问测试
-
userService.yml(存放微服务的注册中心配置信息,不带后缀的文件,config是当做公共配置使用的,无法直接访问。)
-
userService-dev.yml(开发环境)
-
userService-pro.yml(生产环境)
注意:这里只是为了测试才将两个文件的应用名设置为不同,在后续真正开发的过程中,一定要将所有的相同的服务设置为相同的应用名。
-
访问userService-dev.yml(访问开发环境配置文件,config会将公共配置文件同样引入进来。)
-
访问userService-pro.yml(生产环境)
服务端高可用
Config服务端高可用比较简单,只需要启动多台config服务实例即可完成集群高可用。分流操作由客户端去Eureka发现Config服务,并拉取配置时分配具体服务器。具体可以参考客户端拉取配置时,客户端打印的Fetching config from server at :配置中心地址。
具体实现在ConfigServicePropertySourceLocator.getRemoteEnvironment(…)方法中。
客户端连接 Config
引入pom依赖
在原有的Eureka依赖下,加入Spring Cloud Config相关依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
修改配置文件
spring:
application:
name: orderService
cloud:
config:
discovery:
service-id: eros-config
enabled: true
profile: dev
label: release
spring.application.name : 应用名(注意,在连接了配置中心的情况下,这里指配置中心的文件名)
spring.cloud.config.discovery.enabled : 开启配置中心。(true为开启,false为不开启,默认为false)
spring.cloud.config.discovery.service-id :配置中心在Eureka注册中心的名称
spring.cloud.config.profile :引用配置再配置中心的后缀。
也就是说当前配置中由spring.application.name+ spring.cloud.config.profile组成了文件名(当前示例的结果为 :userService-dev)
spring.cloud.config.label :引用配置的分支名称
修改配置文件
bootdtrap.yml
系统级配置,最先执行
application.yml
开发级配置,当系统初始化之后才会执行
- 将application.yml名称改为bootdtrap.yml
- 启动类启动
Spring Cloud Zuul
Spring Cloud Zuul 是一套边缘服务,它能实现动态路由、监控、负载和流量管理等功能。简单来说,Zuul就是服务应用端的一套负载均衡器。它是由一个核心ZuulServlet和一些列的过滤器组成
pre:前置过滤器。在请求被路由之前调用
routing: 路由过滤器。在前置调用完成之后,路由请求时被调用
error:处理请求时发生错误时被调用
post:在routing和error过滤器之后被调用
Origin Server :当前调用的服务
#Zuul统一了不同模块的接口地址 用户直接 Zuul地址+接口地址 即可调用不同模块的接口
创建Zuul服务
Spring项目向导
Pom文件
Zuul是单独的一个服务,需要重新创建一个新项目,除了加入Zuul相关依赖以外,还需要加入Config客户端和Eureka客户端依赖。
Zuul可以通过Config拉取配置文件,并注册至Eureka直接完成高可用,这个过程不需要任何配置信息。
创建Zuul服务端
@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class ErosZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ErosZuulApplication.class, args);
}
}
//Zuul服务加入@EnableZuulProxy注解即可
配置信息
eureka:
client:
service-url:
defaultZone: http://192.168.12.239:8761/eureka,http://192.168.12.239:8762/eureka
spring:
application:
name: api-geteway
cloud:
config:
discovery:
service-id: eros-config
enabled: true
profile: dev
label: release
server:
port:80
与配置中心客户端连接一样,连接配置中心,起名为api-config,同时在版本控制工具中上传对应的配置文件。配置文件可以参考Config示例创建同样的三个配置文件,并命名为api-config.yml、api-config-dev.yml、api-config-pro.yml。
在当前版本中,启动完Zuul服务直接访问zuul服务并加上需要访问的ServiceId(注册在Eureka的Application名)和请求路径即可完成转发。
从测试结果中可以看到,通过zuul服务地址+服务实例+接口地址 就可以完成请求转发的功能。整个过程如下图所示:
当用户访问网关时,Zuul网关会将Eureka上所有可用服务列表拉取下来,根据用户访问路径先匹配应用ID,然后将请求路径转发给对应的应用服务
Zuul服务的一些常用配置
配置路径转发的几种方式
Zuul启动后可以通过Zuul服务地址+实例名称和请求路径,对注册中心的服务进行访问,Zuul也提供自定义映射的功能,通过本方式可以间接的隐藏服务实例名称。当然原始的实例名称仍然可以访问
- 通过指定serviceId的方式自定义路径(完整版)
user可以随便制定,只为区别节点,并没有实际意义
path 需要映射的路径
serviceId 映射路径需要访问的实例名称
- 通过指定serviceId的方式自定义路径(简写版)
通过实例名:映射路径 可以简洁的声明实例名和映射路径
- 通过指定url的方式自定义路径
path 请求路径与上述其他定义一致,
url 请求路径需要转发的连接
通配符详解
通配符 | 说明 | 通配符示例 | 通配符示例说明 |
---|---|---|---|
? | 匹配任意单个字符 | /user/? | 可以匹配/user/后面拼接的任意一个字符的路径。如:/user/a |
* | 匹配任意数量的字符 | /user/* | 可以匹配/user/后面拼接的任意多个字符的路径。如:/user/123 |
** | 匹配任意数量的字符,支持多级目录 | /user/** | 可以匹配/user/后面拼接的任意多级目录路径。如:/user/photo/123 |
禁用服务
- 禁用所有默认映射的服务
为了防止用户直接通过服务ID访问接口,可以通过zuul.ignored-services通配符来禁用直接通过Eureka注册中心的服务名访问。如果需要单独限制,可以使用数组的方式声明对应的服务ID。如下图
连接及超时配置
Zuul的连接数,超时等常见配置在ZuulProperties的内部类Host类中。超时配置有两种情况:
- 当zuul使用了服务注册中心时,会有ribbon组件负责连接。开发者需要设置ribbon.ReadTimeout 和ribbon.SocketTimeout两项配置属性
- 当使用了zuul节点和url映射时,需要配置
zuul.host.connect-timeout-millis和zuul.host.socket-timeout-milli属性
-
连接超时配置
-
Ribbon超时设置
自定义过滤器
在Zuul的架构图中可以看到,zuul中由四层核心过滤器,分别是pre(前置)、routing(路由)、post(后置)和error(异常)过滤器。开发者一般可以通过前置过滤器完成用户权限校验,后置过滤器完成响应结果过滤等功能。主要的过滤器一共有10个,但是对应的过滤器都有自己的执行前置条件,所以,并不会因为过滤器过多而影响到性能。
内置过滤器基本介绍(了解)
Filter名称 | Filter 的作用 |
---|---|
Pre 前置过滤器 | |
ServletDetectionFilter | 判定当前请求是通过SpringMVC还是ZuulServlet来执行的 |
Servlet30WrapperFilter | 将request对象进行二次包装 |
FormBodyWrapperFilter | 文件上传时,对文件对象封装的过滤器 |
DebugFilter | 调试过滤器,激活调试日志 |
PreDecorationFilter | 查找路由 |
Routing过滤器 | |
RibbonRoutingFilter | 对通过serviceId路由访问的请求进行负载访问 |
SimpleHostRoutingFilter | 对通过url路由访问的请求进行直接访问 |
SendForwardFilter | 对请求上下文中的forward.do参数进行处理请求 |
Post过滤器 | |
SendErrorFilter | 在上下文中出现错误时处理 |
SendResponseFilter | 使用当前上下文中的参数对访问端进行响应 |
自定义过滤器简介
@Component
public class SimpleFilter extends ZuulFilter {
@Override
public String filterType() {
//定义当前过滤器类型。具体过滤器类型详见org.springframework.cloud.netflix.zuul.filters.support.FilterConstants
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//当前过滤器的顺序,值越小越靠前
return FilterConstants.PRE_DECORATION_FILTER_ORDER - 1;
}
@Override
public boolean shouldFilter() {
//true/false表示是否执行run方法
return true;
}
@Override
public Object run() throws ZuulException {
//过滤器的具体业务
return null;
}
}
自定义的Filter需要继承ZuulFilter父类,并添加@Component注解
ZuulFilter中定义了四个方法需要重写。
1.filterType方法 : 返回当前过滤器类型。包含有pre、routing、post、error类型过滤器。具体可以参考FilterConstants类中的*_type常量定义
2..filterOrder : 返回当前过滤器的运行顺序,值越小,运行优先级越高。值定义在FilterConstants类中的_order常量。前置过滤器官方demo给的建议是运行在PreDecorationFilter过滤器之前。后置过滤器官方demo给的建议是运行在SendResponseFilter之前
3.shouldFilter : 返回当前过滤器是否要执行run方法。无特殊情况时,一般返回true即可。
4.run:过滤器的主要逻辑业务方法。该方法可直接返回null即可。不需要返回其他值。
1.1.1.2. 自定义pre前置过滤器
@Component
public class SessionFilter extends ZuulFilter {
@Override
public String filterType() {
//定义当前过滤器类型。具体过滤器类型详见org.springframework.cloud.netflix.zuul.filters.support.FilterConstants
return FilterConstants.PRE_TYPE;
}
@Override
public int filterOrder() {
//当前过滤器的顺序,值越小越靠前
return FilterConstants.PRE_DECORATION_FILTER_ORDER -1;
}
@Override
public boolean shouldFilter() {
//true/false表示是否执行run方法
return true;
}
@Override
public Object run() throws ZuulException {
//zuul内置RequestContext对象,可获取当前请求的上下文
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String uid = request.getParameter("uid");
if(StringUtils.isEmpty(uid)){
//终止后续过滤器的执行
requestContext.setSendZuulResponse(false);
//设置响应码
requestContext.setResponseStatusCode(HttpStatus.SC_BAD_REQUEST);
requestContext.getResponse().setContentType("text/html;charset=utf-8");
requestContext.setResponseBody("无权限访问~");
}else{
//redis中查找用户id对应的token是否存在,判定当前用户是否有权限。
}
return null;
}
}
# filterType() 返回值定义为**FilterConstants.****PRE_TYPE**类型,将当前过滤器定义为前置过滤器
# filterOrder()返回值按照官方的建议,filterOrder取值为 FilterConstants.PRE_DECORATION_FILTER_ORDER -1
# run() 主要负责过滤器的主业务逻辑
RequestContext 是Zuul内置的对象,通过该对象可以获取当前请求的Request和Response对象。
requestContext.setSendZuulResponse(false); 当设置为false时将会中断后续的过滤器执行
requestContext.setResponseStatusCode(HttpStatus.SC_BAD_REQUEST); 当设置了SendZuulResponse为false时,需要设置当前响应的状态码
requestContext.setResponseBody("error context"); 设置当前错误信息的响应文本
1.1.1.3. 自定义其他类型过滤器
除了创建pre前置过滤器以外,还可以按照FilterConstants类中的 *_type常量定义filterType方法的返回值来定义具体过滤器类型。同时定义filterOrder的返回值来定义过滤器执行顺序。