文章目录
SpringCloud学习
Eureka注册中心基础搭建
还有一个集中配置中心(很方便和关键)
Netflix Eureka
服务中心,云端服务发现,一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。这个可是springcloud最牛鼻的小弟,服务中心,任何小弟需要其它小弟支持什么都需要从这里来拿,同样的你有什么独门武功的都赶紧过报道,方便以后其它小弟来调用;它的好处是你不需要直接找各种什么小弟支持,只需要到服务中心来领取,也不需要知道提供支持的其它小弟在哪里,还是几个小弟来支持的,反正拿来用就行,服务中心来保证稳定性和质量。
Netflix Ribbon
Ribbon是一个客户端负载均衡组件,帮我们实现后端服务节点动态扩容,而不影响调用方。
Ribbon的使用方法
Eureka和Feign中已经默认集成了Ribbon,
如果项目中引入了Eureka,通过在RestTemplate上添加@LoadBalanced;
如果用的是Feign声明式REST客户端,feign默认就已经帮我们开启了ribbon负载均衡能力。
搭建工程过程:pom——yml——Application——应用层(业务代码)
1. *父工程依赖pom文件(SpringCloud-parent)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cxp</groupId>
<artifactId>springcloud-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<!-- 定义SpringBoot依赖版本 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 注意:SpringCloud最新的Greenwich版本是基于SpringBoot2.1.x(Greenwich)版本构建的
所以这里不支持SpringBoot2.2.x版本
具体SpringBoot与SpringCloud版本对应关系参见:https://spring.io/projects/spring-cloud页面最下方的Release Trains
-->
<!--<version>2.2.1.RELEASE</version>-->
<!-- 定义SpringCloud依赖版本 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2. *SpringCloud-eureka-server(服务者)
配置文件:
server:
port: 8761
eureka:
instance:
prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名
#instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
hostname: cloud.study.com
client:
# 是否将当前应用注册到eureka中
registerWithEureka: true
# 是否从eureka注册中心中拉取服务提供者列表
fetchRegistry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
spring:
application:
name: eureka-server
拓展:*jdk1.8配置( pom.xml)
<!-- 配置maven编码字符、编译版本 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
3. *springcloud-eureka-server-cloud 模块(高可用的服务者)
spring:
application:
name: eureka-server
eureka:
instance:
prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名
#instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
hostname: cloud.study.com
client:
# 是否将当前应用注册到eureka中
registerWithEureka: true
# 是否从eureka注册中心中拉取服务提供者列表
fetchRegistry: true
service-url:
defaultZone: http://cloud02.study.com:8762/eureka/,http://cloud03.study.com:8763/eureka/
server:
port: 8761
---
spring:
profiles: cloud02
server:
port: 8762
eureka:
instance:
hostname: cloud02.study.com
client:
service-url:
defaultZone: http://cloud.study.com:8761/eureka/,http://cloud03.study.com:8763/eureka/
---
spring:
profiles: cloud03
server:
port: 8763
eureka:
instance:
hostname: cloud03.study.com
client:
service-url:
defaultZone: http://cloud.study.com:8761/eureka/,http://cloud02.study.com:8762/eureka/
注:这个cloud需要一一都启动起来:才不会报错
4. *Provider(cloud提供者)
配置文件;
server:
port: 8765
eureka:
instance:
prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名
#instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
hostname: cloud.study.com
client:
# 是否将当前应用注册到eureka中
registerWithEureka: true
# 是否从eureka注册中心中拉取服务提供者列表
fetchRegistry: false
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
spring:
application:
name: eureka-server
5. *Consumer(cloud消费者)
yml文件:
spring:
application:
name: service-consumer
eureka:
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${eureka.client.eureka-server-port}/eureka/
eureka-server-port: 8765
instance:
prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名
instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
hostname: cloud.study.com
server:
port: 8082
学习理解:
微服务 Rpc和Rest协议
接口调用通常包含两个部分,序列化和通信协议。常见的序列化协议包括json、xml、hession、protobuf、thrift、text、bytes等;通信比较流行的是http、soap、websockect,RPC通常基于TCP实现,常用框架例如dubbo,netty、mina、thrift
- Rest:严格意义上说接口很规范,操作对象即为资源,对资源的四种操作(post、get、put、delete),并且参数都放在URL上,但是不严格的说Http+json、Http+xml,常见的http api都可以称为Rest接口。
- Rpc:我们常说的远程方法调用,就是像调用本地方法一样调用远程方法,通信协议大多采用二进制方式
Eureka的角色
Eureka 是 Netflix 开源的服务注册发现组件,服务端通过 REST 协议暴露服务,提供应用服务的注册和发现的功能。
所有的Eureka服务都被称为实例(instance)。Eureka服务又分为两类角色:Eureka Server和Eureka Client
Eureka-Client又分为Application Provider 和Application Consumer
Application Provider :服务提供者,内嵌 Eureka-Client ,它向 Eureka-Server 注册自身服务、续约、下线等操作
Application Consumer :服务消费者,内嵌 Eureka-Client ,它从 Eureka-Server 获取服务列表,分为全量获取和增量。
遇到的问题:
1. *两个注解
开启应用的Eureka Server功能
在应用启动类上加 @EnableEurekaServer注解开启应用的Eureka Client功能
在应用启动类上加 @EnableEurekaClient注解
- @EnableDiscoveryClient 注解
这是SpringCloud通过的一个注解,所有的服务发现中心组件都可以使用(客户端和服务端使用的注解一样)
Feign声明式REST客户端
依赖eureka和Ribbon
Feign底层会根据我们配置的注解属性拼接得到最终访问的URI,根据接口中定义的方法的参数和返回值将请求参数、返回结果处理好,这些都是Feign通过帮我们生成代理实现类的方式完成的。
实现过程:
还是需要三方配合,那好处在哪里
过程:
- (外部路线)首先消费者的控制层——提供者的控制层(返回responseInfo)——返回页面(页面展示数据)
- 其次消费者的控制层通过(接口注解以及@RequestMapping("/calc"))——访问提供者的控制层(通过处理得到数据可以看成是消费者层级的接口实现)——回到消费者控制层返回页面数据
- 最后server端默默的做着他们之间的桥梁。
消费者控制层
@Controller
@RequestMapping("/calc")
public class CalcController {
@Autowired
private CalcServiceFeign calcServiceFeign;
@RequestMapping("/add")
public String add(@RequestParam int num1, @RequestParam int num2, Model model) {
ResponseInfo responseInfo = calcServiceFeign.add(num1, num2);
Map data = (Map) responseInfo.getData();
model.addAttribute("num1", num1);
model.addAttribute("num2", num2);
model.addAttribute("result", data.get("result"));
return "index";
}
}
消费者的Feign(接口)
//提供者名字(provider)
@FeignClient(name = "SERVICE-PROVIDER")
@RequestMapping("/calc")
public interface CalcServiceFeign {
@GetMapping("/add/{num1}/{num2}")
ResponseInfo add(@PathVariable("num1") int num1,@PathVariable("num2") int num2);
}
提供者的控制层
@RestController
@RequestMapping("/calc")
public class CalcController {
@Autowired
private CalcServiceImpl calcService;
@RequestMapping("/add/{num1}/{num2}")
public ResponseInfo add(@PathVariable int num1, @PathVariable int num2) {
int result = calcService.add(num1, num2);
Map<String, Object> data = new HashMap<String, Object>();
data.put("num1", num1);
data.put("num2", num2);
data.put("operator", "+");
data.put("result", result);
return new ResponseInfo(200, "请求成功", data);
}
}
提供者的实现类
@Service
public class CalcServiceImpl {
@Autowired
private Registration registration;
public int add(int num1, int num2) {
String serviceInfo = registration.getHost() + "-" + registration.getInstanceId();
return num1 + num2;
}
}
server端默默的做着连接桥梁工作。
Hystrix 服务熔断、降级、限流、隔离
消费者模块两种方式
Ribbon: RestTemplate
Feign。分别做降级处理、步骤如下:
pom文件依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 添加SpringBoot Actuator端点监控依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
完整配置文件:
spring:
application:
name: service-consumer-hystrix
server:
port: 8082
eureka:
instance:
prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名
instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
hostname: cloud.study.com
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${eureka.client.eureka-server-port}/eureka/
eureka-server-port: 8765
#hystris的配置
hystrix:
command:
calc_add_command:
##对应@HystrixCommand注解的commandKey属性,指定特定熔断器的属性(一般建议在代码中通过注解配置),此处换成default可修改全局属性。
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 #设置熔断器判定超时的时间,超过此时间的请求会执行降级逻辑,默认1s
circuitBreaker:
requestVolumeThreshold: 2 #设置熔断阈值,在熔断统计窗口期内,错误请求(超时、异常)次数达到阈值就会触发熔断,执行降级逻辑,默认20
sleepWindowInMilliseconds: 10000 #设置熔断器多久进入半开状态,然后再次尝试确定熔断器是否应再次关闭,默认5s
errorThresholdPercentage: 50 #设置在熔断统计窗口期内,错误请求达到百分之多少触发熔断,默认50
metrics:
rollingStats:
timeInMilliseconds: 5000 #熔断度量窗口期时间, 默认10s
#hystris.stream的配置
management:
server:
port: 8092
endpoints:
web:
base-path: /actuator
exposure:
include: "*"
通过Properties修改指定HystrixCommand的参数
hystrix:
command:
hystrixCommandName (如:calc_add_command): #对应@HystrixCommand注解的commandKey属性,指定特定熔断器的属性(一般建议在代码中通过注解配置),此处换成default可修改全局属性。
execution:
isolation:
thread:
timeoutInMilliseconds: 5000 #设置熔断器判定超时的时间,超过此时间的请求会执行降级逻辑,默认1s
circuitBreaker:
requestVolumeThreshold: 2 #设置熔断阈值,在熔断统计窗口期内,错误请求(超时、异常)次数达到阈值就会触发熔断,执行降级逻辑,默认20
sleepWindowInMilliseconds: 10000 #设置熔断器多久进入半开状态,然后再次尝试确定熔断器是否应再次关闭,默认5s
errorThresholdPercentage: 50 #设置在熔断统计窗口期内,错误请求达到百分之多少触发熔断,默认50
metrics:
rollingStats:
timeInMilliseconds: 5000 #熔断度量窗口期时间, 默认10s
启动类(Ribbon方式):
@SpringBootApplication
@EnableEurekaClient
//@EnableHystrix /*不是此注解*/
@EnableCircuitBreaker //启用断路器
public class ConsumerHystrixApplication {
@Bean
@LoadBalanced //(Ribbon)开启负载均衡
public RestTemplate restTemplate(){
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(ConsumerHystrixApplication.class,args);
}
}
成功配置、添加hystrix的fallback方法
*在需要的方法上边添加注解
@HystrixCommand(
fallbackMethod = "addFallback",
commandKey = "calc_add_command"
)
//服务正常通过的方法。
public String add(){}
/**
* 定义add方法的降级方法,当add方法调用远程服务出现问题,发生熔断时,hystrix会自动放弃调用原来的服务,转而调用降级方法
* @param num1
* @param num2
* @param model
* @return
*/
public String addFallback(@RequestParam int num1, @RequestParam int num2, Model model) {
model.addAttribute("num1", num1);
model.addAttribute("num2", num2);
model.addAttribute("result", "-1");
return "index";
}
模拟处理时间(provider——impl):
// 只是模拟处理时间长的服务调用
Random random = new Random();
int sleepTime = random.nextInt(3000);
log.info("休眠时间:" + sleepTime+"ms.");
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
Feign方式
配置文件也不同;(多个Feign的配置)
spring:
application:
name: service-consumer-feign-with-hystrix
server:
port: 8082
eureka:
instance:
prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名
instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
hostname: cloud.study.com
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${eureka.client.eureka-server-port}/eureka/
eureka-server-port: 8765
hystrix:
command:
default:
# calc_add_command:
##对应@HystrixCommand注解的commandKey属性,指定特定熔断器的属性(一般建议在代码中通过注解配置),此处换成default可修改全局属性。
execution:
isolation:
thread:
timeoutInMilliseconds: 2000 #设置熔断器判定超时的时间,超过此时间的请求会执行降级逻辑,默认1s
circuitBreaker:
requestVolumeThreshold: 2 #设置熔断阈值,在熔断统计窗口期内,错误请求(超时、异常)次数达到阈值就会触发熔断,执行降级逻辑,默认20
sleepWindowInMilliseconds: 10000 #设置熔断器多久进入半开状态,然后再次尝试确定熔断器是否应再次关闭,默认5s
errorThresholdPercentage: 50 #设置在熔断统计窗口期内,错误请求达到百分之多少触发熔断,默认50
metrics:
rollingStats:
timeInMilliseconds: 5000 #熔断度量窗口期时间, 默认10s
#hystris.stream的配置
management:
server:
port: 8092
endpoints:
web:
base-path: /actuator
exposure:
include: "*"
feign:
client:
config:
#calc-service-provider: 这一级可以写具体的微服务名称或者default,default代表全局配置,影响所有微服务的调用
default:
connectTimeout: 1000
readTimeout: 2000
hystrix:
enabled: true
pom文件主要依赖(全)
<!-- 引入feign依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- SpringCloud 格林威治版本中需要单独将hystrix依赖也添加上 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<!-- 引入eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入eureka依赖会自动将ribbon依赖一并引入,我们不需要自己单独添加 -->
<!--<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
</dependencies>
成功配置、添加hystrix的fallback方法
(改动地方:一个注解一个实现类(fallback))
//提供者名字(provider)
@FeignClient(name = "SERVICE-PROVIDER",fallback = CalcClientFallback.class)
@RequestMapping("/calc")
public interface CalcServiceFeign {
@GetMapping("/add/{num1}/{num2}")
ResponseInfo add(@PathVariable("num1") int num1, @PathVariable("num2") int num2);
}
有个坑(错误信息:)
There is already ‘calcClientFallback’ bean method
@Component
@RequestMapping("/fallback") //此处只是为了避免重复(实现接口的时候和RestMapping一并继承了)
public class CalcClientFallback implements CalcServiceFeign{
public ResponseInfo add(int num1, int num2) {
Map data = new HashMap();
data.put("num1", num1);
data.put("num2", num2);
data.put("result", -1);
ResponseInfo responseInfo = new ResponseInfo(203, "降级结果", data);
return responseInfo;
}
}
Feign和Ribbon(RestTemplate方式)
- 配置文件不同
Feign多了它自己的配置
feign:
client:
config:
# calc-service-provider: 这一级可以写具体的微服务名称或者default,default代表全局配置,影响所有微服务的调用
default:
connectTimeout: 1000
readTimeout: 2000
hystrix:
enabled: true
-
熔断的回调方式不同
(RestTemplate 放在消费者的控制层复写方法加注解建立联系):[目录](*在需要的方法上边添加注解)
Feign是通过接口添加另一个实现类也是通过注解建立联系:【目录】(上高亮处)
监控
基于spring-boot-starter-actuator的一项可视化图表工具
消费者工程添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Dashboard、turbine
1. Hystrix Dashboard
Hystrix Dashboard是Hystrix给我们提供的实时查看单机熔断情况的工具。需要配合SpringBoot Actuator使用。
Hystrix Dashboard使用方法
Hystrix Dashboard就是一个独立的图表工具,与业务没有任何关联。我们可以单独创建一个工程,引入dashboard的包
- 需要的依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
-
启动类注解:@EnableHystrixDashboard
-
浏览器中访问:http://localhost:9000/hystrix 就可以打开dashboard首页了
dashboard地址和turbine地址说明:
http://localhost:8092/actuator/hystrix.stream
ping的数据供dashboard收集成图表工具
把上述地址放到搜索框里面得到以下现象
上条地址的图表数据来源如下:
springboot 提供的监控数据
首页访问信息(基于actuator的hystrix.stream)
访问地址:http://localhost:8092/actuator/
后面可拼的其他路径:
beans、mappings、info、health。。。等
#hystris.stream的配置
management:
server:
port: 8092
endpoints:
web:
base-path: /actuator
#默认是health、info
exposure:
include: "*"
昨天还有一个知识点没练习完
2. Turbine(聚合服务)
- DashBoard、turbine、hystrix stream三者之间的关系
在复杂的分布式系统中,相同服务的节点经常需要部署上百甚至上千个,很多时候,运维人员希望能够把相同服务的节点状态以一个整体集群的形式展现出来,这样可以更好的把握整个系统的状态。 为此,Netflix提供了一个开源项目(Turbine)来提供把多个hystrix.stream的内容聚合为一个数据源供Dashboard展示。
Turbine与Dashboard、Hystrix EventStream的关系
Turbine的使用方法
-
引入Turbine依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-turbine</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
-
配置Turbine要监控的服务
server: port: 9005 #turbine 配置@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ turbine: app-config: calc-service-consumer-hystrix,calc-service-feign-consumer-with-hystrix cluster-name-expression: "'default'" aggregator: cluster-config: default #eureka配置Turbine需要从Eureka中获取服务列表,然后遍历每个服务每个节点上的hystrix.stream数据,然后汇聚到一起 eureka: instance: prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名 instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}} hostname: node1.john.com client: service-url: defaultZone: http://${eureka.instance.hostname}:${eureka.client.eureka-server-port}/eureka/ eureka-server-port: 8761 #hystrix.stream的配置@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ management: endpoints: web: exposure: include: "*"
turbine.appConfig
:配置Eureka中的serviceId列表,表明监控哪些服务turbine.aggregator.clusterConfig
:指定聚合哪些集群,多个使用”,”分割,默认为default。可使用http…turbine.streamcluster={clusterConfig之一}访问turbine.clusterNameExpression
:- clusterNameExpression指定集群名称,默认表达式appName;此时:turbine.aggregator.clusterConfig需要配置想要监控的应用名称;
- 当clusterNameExpression default时,turbine.aggregator.clusterConfig可以不写,因为默认就是default;
- 当clusterNameExpression metadata[‘cluster’]时,假设想要监控的应用配置了eureka.instance.metadata-map.cluster ABC,则需要配置,同时turbine.aggregator.clusterConfig ABC
- 启动类:
@SpringBootApplication @EnableTurbine public class TurbineConsumerApplication { public static void main(String[] args) { SpringApplication.run(TurbineConsumerApplication.class,args); } }
-
访问turbine.stream
在浏览器中访问http://localhost:9005/turbine.stream 就可以看到配置到Turbine上的所有微服务产生的流式事件数据 -
在Hystrix Dashboard中查看所有、指定微服务的实时熔断监控。
将上述地址输入到Hystrix Dashboard中即可查看所有微服务的监控情况,或者加上cluster=[clusterName] 参数,只看某个微服务的情况 -
在dashboard中查看聚合图表数据地址:http://localhost:9005/turbine.stream ?cluster=default
Zuul(Netflix) - 统一网关、地址路由
主要用途:
1. 反向代理
2. 统一权限校验
3. 和hystrix的整合
设置整体的熔断。
Zuul介绍
(在前端调用后台的时候加一层工程(看似就访问了一个端口服务,也就不存在跨域问题))
Zuul是Netflx开源的微服务网关。可以和Eureka、Ribbon、Hystrix配合使用,一个主要的功能就是可以将后端众多的微服务屏蔽、整合,对前端提供一套统一的服务(有点像是后端的Facade)。
反向代理用法:
-
添加依赖
<dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> </dependencies>
-
配置文件
server: port: 9100 spring: application: name: zuul-server eureka: client: service-url: defaultZone: http://${eureka.instance.hostname}:${eureka.client.eureka-server-port}/eureka/ eureka-server-port: 8765 instance: prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名 instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}} hostname: cloud.study.com management: endpoints: web: exposure: include: "*" zuul: routes: # 这种方式配置路由规则:前面的key是微服务名称,后面是匹配路径(更安全) #service-provider: /calcApi/** # 这种方式配置路由规则:第一级的key可以随便取(路由名称),下面可以配置更多key、value(相比上面一种配置更强大) calc-proxy: serviceId: service-provider # 有效的微服务名称 path: /calc/** # 访问路径 strip-prefix: false # 是否在网关层面消耗掉指定服务的路由规则前缀 # taotao-rest-proxy: # serviceId: taotao-rest # path: /rest/** taotao-rest-proxy-forward: # 使用forward本地转发(就是将匹配到路由规则的请求,转发到网关本地应用中去处理) path: /rest-f/** url: forward:/rest # 比如浏览器访问:http://localhost:9100/api/rest-f/content/getall/89 # 会被转发到:http://localhost:9100/rest/content/getall/89 ignored-services: "*" # 忽略所有未显示配置路由规则的微服务 prefix: /api strip-prefix: true # 是否在网关层面消耗掉全局前缀 #淘淘商城测试 # 使用Zuul代理未接入Eureka的传统服务 taotao-rest: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList #如果不配置ServerList类型为ConfigurationBasedServerList的话就需要禁用ribbon的eureka支持 ConnectTimeout: 500 ReadTimeout: 2000 listOfServers: http://localhost:8081 #和hystrix不同 (配置超时) service-provider: #微服务名称 ribbon: ReadTimeout: 2000 ConnectTimeout: 500
-
启动类
@SpringBootApplication @EnableZuulProxy @EnableEurekaClient public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class,args); } }
重点主要在它的application.yml文件中的配置。
Zuul的访问超时控制
通过ServiceId代理的请求
由于通过ServiceId的方式代理的请求(包括手动配置的Ribbon 服务和自动服务发现的服务)会走Ribbon和Hystrix,所以我们要控制超时就包括Ribbon请求的超时和Hystrix的访问超时。
test-service:
ribbon:
ReadTimeout:100
ConnectTimeout:500
MaxAutoRetries:1
MaxAutoRetriesNextServer:1
上面这段配置通过Zuul访问test-service服务时,Ribbon的ConnectTimeout是500ms,ReadTimeout是100ms,重试次数为1次; Zuul会根据ribbon设置的超时时间和重试次数,自动设置Hystrix的超时时间(上面这段配置, Zuul会把Hystrix的超时时间设置为:(100+500)*2=1200ms
当然,我们也可以通过添加下面的配置显示指定Hystrix参数
hystrix:
command:
service-calc-ribbon:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
这里如果不显示设置,Zuul会根据ribbon设置的ConnectionTimeout和ReadTimeout自动确定Hystrix的超时时间(所以一般可以通过配置ribbon的超时时间来控制hystrix超时),由于ribbon默认重试次数是1,所以Hystrix的超时时间会被设置为(500+2000)*2=5000
Zuul的核心——Filter
Filter是Zuul的核心,用来实现对外服务的控制。Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,
Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
Zuul中默认实现的Filter
类型 | 顺序 | 过滤器 | 功能 |
---|---|---|---|
pre | -3 | ServletDetectionFilter | 标记处理Servlet的类型 |
pre | -2 | Servlet30WrapperFilter | 包装HttpServletRequest请求 |
pre | -1 | FormBodyWrapperFilter | 包装请求体 |
route | 1 | DebugFilter | 标记调试标志 |
route | 5 | PreDecorationFilter | 处理请求上下文供后续使用 |
route | 10 | RibbonRoutingFilter | serviceId请求转发 |
route | 100 | SimpleHostRoutingFilter | url请求转发 |
route | 500 | SendForwardFilter | forward请求转发 |
post | 0 | SendErrorFilter | 处理有错误的请求响应 |
post | 1000 | SendResponseFilter | 处理正常的请求响应 |
禁用指定的Filter
zuul:
FormBodyWrapperFilter:
pre:
disable: true
自定义Filter示例
我们假设有这样一个场景,因为服务网关应对的是外部的所有请求,为了避免产生安全隐患,我们需要对请求做一定的限制,比如请求中含有Token便让请求继续往下走,如果请求不带Token就直接返回并给出提示。
-
首先自定义一个Filter,在run()方法中验证参数是否含有Token。
/** * 自定义网关过滤器 */ @Component public class TokenFilter extends ZuulFilter { /** * 过滤器类型 * @return */ public String filterType() { return "pre"; } /** * 过滤器顺序 * @return */ public int filterOrder() { return 2; } /** * 是否开启过滤 * @return */ public boolean shouldFilter() { return true; } /** * 过滤器中要执行的具体逻辑 * @return * @throws ZuulException */ public Object run() throws ZuulException { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest req = ctx.getRequest(); String token = req.getHeader("token"); if(token == null) { // 不对其进行路由,直接返回错误 ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(403); ctx.setResponseBody("forbidden"); return null; } // 检查token if(token.startsWith("abc")) { ctx.setSendZuulResponse(true); ctx.setResponseStatusCode(200); return null; } // 不对其进行路由,直接返回错误 ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(403); ctx.setResponseBody("invalid token"); return null; } }
- 将TokenFilter加入到请求拦截队列,在启动类中添加以下代码
@Bean public TokenFilter tokenFilter() { return new TokenFilter(); }
通过上面这例子我们可以看出,我们可以使用“PRE”类型的Filter做很多的验证工作,在实际使用中我们可以结合shiro、oauth2.0等技术去做鉴权、验证。
配置中心(基于git仓库)
1. 解决重复配置
2. 基于web的加密解密
3.热更新配置(动态)
单台服务端的更新方式。
基于Springcloud bus (rabbitmq。。。或 kafka方式的更新配置
解决重复配置
需要依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<!-- 添加SpringCloud Bus amqp依赖,用于bus通知各个微服务刷新配置 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
配置文件:
#这个文件不是必要的使用bootstrap.yml
server:
port: 7000
spring:
application:
name: config-server
#主要有关配置。
cloud:
config:
server:
git:
uri: https://gitee.com/checkChen/Springcloud-config-server.git
search-paths: test_repo #精确到文件夹
#也可以是本地地址
#uri: file://${user.home}/work/test_repo
# username: xxx
# password: xxx
#基于客户端配置消息中间件
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
encrypt:
key: lanou3g # 配置一个自己的秘钥,用于对称加密算法加密
management:
endpoints:
web:
exposure:
include: bus-refresh
启动类:
@SpringBootApplication
@EnableConfigServer
public class ConfigApplication {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication.class,args);
}
}
模拟查看匹配到的配置文件(后面地址是模糊查询也可以很具体。)
http://localhost:8888/app/dev
全都不匹配的话有个默认的application.properties/yml
客户端使用springcloud-config:
server配置完成接下来consumer使用:
首先配置文件:
bootstrap.yml是云配置优先级高于application.yml
所需依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--基于普通consumer新添加依赖-->
<!-- 配置中心客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!-- 为了使用/refresh端点动态刷新配置,需要添加actuator依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
</dependencies>
配置文件
application.yml:
spring:
application:
name: service-config-consumer
#git上已经配置
#eureka:
# client:
# service-url:
# defaultZone: http://${eureka.instance.hostname}:${eureka.client.eureka-server-port}/eureka/
# eureka-server-port: 8765
# instance:
# prefer-ip-address: true #在某些情况下,Eureka优先使用IP地址而不是主机名。设置为true,当应用程序向eureka注册时,它将使用其IP地址而不是其主机名
# instance-id: ${spring.application.name}:${spring.application.instance_id:${server.port}}
# hostname: cloud.study.com
server:
port: 8082
management:
endpoints:
web:
exposure:
include: bus-refresh #或者"*"
bootstrap.yml:
spring:
cloud:
config:
uri: http://localhost:7000 #config-server文件地址
name: config-calc-consumer
# profile: dev
# 拉取的二级路径profile
配置映射参数
在SpringCloudConfig中,
-
{application}对应客户端配置的“spring.application.name”参数(如果指定了spring.cloud.config.name则以其为准。);
-
{profile} 对应客户端配置的“spring.profiles.active”参数(如果指定了spring.cloud.config.profile则以其为准。);
-
{label}对应版本库中的branch(或tag)名称,取决于客户端的“spring.cloud.config.label”参数决定。
如果分支名称中包含“/”,label配置中应该使用“(_)”替换,如foot/dev配置为foot(_)dev。
git仓库加密解密
命令:id_rsa.pub公钥、id_rsa秘钥(公钥的钥匙)
$ cd ~/.ssh/
$ ls
id_rsa id_rsa.pub known_hosts
$ cat id_rsa
配置加密解密
(数据库如:用户名密码)
方法:
使用命令行(参考SpringCloud笔记之微服务配置中心Config.pdf)
使用postman:http://localhost:7000/encrypt
对称加密
配置文件config-server加密、在客户端接收的发送回来的信息是解密后的。
encrypt:
key: lanou3g # 配置一个自己的秘钥,用于对称加密算法加密
#加密后的串提交git仓库。(.yml文件必须加引号、.properties文件必须不加引号。)
password:
'{cipher}fda4hu54sd7a7df78sadf9aga7d6g'
遇到的问题:非法的key需要jce
在SpringCloud中实现RSA对称加密需要一些前提条件,首先要给jdk/jre配置JCE安装JCE加密可扩展。替换两个文件
非对称加密
参见官网或笔记
动态更新配置:
单台客户端动态更新
由于SpringCloud Config-Server端在每次客户端拉取配置时都会从远程仓库拉取最新的配置,所以Config- Server端的配置其实已经更新了, 我们要做的是让客户端应用更新配置,如果是单台客户端的话,我们直接通过SpringBoot提供的小下文断电中的:”/refresh“ 断电就可以了。具体步骤如下:
- 加入SpringBoot actuator依赖
- 在需要支持动态刷新配置的类中添加@RefreshScope注解
- 访问http://localhost:port/refresh,即可刷新配置。(不支持get请求、可以使用postman)
但是工作量很大、容易遗漏、要做到同时让所有客户端都刷新配置很麻烦。基于此SpringCould Config Server给我们提供了一种更便利的方式、基于Spring Cloud Bus的方式充当中间人通知所有客户端(配置变更相关的客户端)更新配置。
多台客户端动态更新
使用工具,环境搭建:
erlang_otp_win64_21.3.exe
rabbitmq-server-3.7.22.exe
安装之后修改rabbitmq_server-3.7.22\etc下的rabbitmq.config.example文件。
登录密码
启动和访问地址
http://localhost:15672
RabbitMQ 端口
- 4369 (epmd), 25672 (Erlang distribution)
- 5672, 5671 (AMQP 0-9-1 without and with TLS)
- 15672 (if management plugin is enabled)
- 61613, 61614 (if STOMP is enabled)
- 1883, 8883 (if MQTT is enabled)
安装步骤:
Windows安装ribbitmq步骤:
https://blog.csdn.net/qq_38931949/article/details/95513014
https://www.rabbitmq.com/install-windows.html(官方说明)
配置中心加密错误: https://bbs.csdn.net/topics/392514733
解决办法: https://blog.csdn.net/qq_36827957/article/details/81777333
SpringCloud Config Server端配置
-
添加bus依赖
<!-- 添加SpringCloud Bus amqp依赖,用于bus通知各个微服务刷新配置 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-bus-amqp</artifactId> </dependency>
这里我们用的是rabbitmq,所以添加的是amqp依赖、如果消息中间件用的是kafka的话就换成bus-kafka依赖。
-
配置消息中间件
#基于客户端配置消息中间件 rabbitmq: host: 127.0.0.1 port: 5672 username: guest password: guest
-
当配置修改完成,并且push到远程仓库之后,执行下面的请求。
新版地址不同有变化。
旧版本:http://localhost:7000/bus/refresh
新版本:http://localhost:7000/actuator/bus-refresh
必须要配置如下server - config端
management: endpoints: web: exposure: include: bus-refresh #或者"*"
访问Config Server 的actuator/bus-refresh端点,触发Config 的检查更新。(必须post请求)
SpringCloud -client端
重复 1.添加bus依赖 2. 配置消息中间件
课堂知识:
查看可调用的服务(可以配个虚假的就是不和服务名保持一致(提高安全性)):http://localhost:9100/actuator/routes (方式如下)
理解如下:
此地址用来查看使用zuul代理服务(如:taotao-rest(只是服务的名字))的访问路径地址 配合(属性strip-prefix: true # 是否在网关层面消耗掉全局前缀即path:/api——最简化通过zuul的访问地址)
# 这种方式配置路由规则:前面的key是微服务名称,后面是匹配路径(更安全)
service-provider: /calcApi/**
# 这种方式配置路由规则:第一级的key可以随便取(路由名称),下面可以配置更多key、value(相比上面一种配置更强大)
calc-proxy:
serviceId: service-provider # 有效的微服务名称
path: /calc/** # 访问路径
strip-prefix: false # 是否在网关层面消耗掉指定服务的路由规则前缀
ignored-services: "*" # 忽略所有未显示配置路由规则的微服务
遇到的问题
- 所需工具:
- 安装步骤:
Windows安装步骤:
https://blog.csdn.net/qq_38931949/article/details/95513014
https://www.rabbitmq.com/install-windows.html(官方说明)
配置中心加密错误: https://bbs.csdn.net/topics/392514733
解决办法: https://blog.csdn.net/qq_36827957/article/details/81777333
-
小知识
iml是 intellij idea的工程配置文件,里面是当前 project 的一些配置信息。.Idea 存放项目的配置信息,包括历史记录,版本控制信息等。
-
通过配置代理服务
# 使用Zuul代理未接入Eureka的传统服务 taotao-rest: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList #如果不配置ServerList类型为ConfigurationBasedServerList的话就需要禁用ribbon的eureka支持 ConnectTimeout: 500 ReadTimeout: 2000 listOfServers: http://localhost:8081
-
属性:(通过)
zuul: routes: microserver-calc: /calcs/** #指定的服务下 prefix: /api strip-prefix: true # 是否在网关层面消耗掉全局前缀即/api
-
测试forward的路径
@RestController //@RequestMapping("/rest") public class TestController { //@RequestMapping("/content/getall/{id}") @RequestMapping("/**") public void handlerRequest(HttpServletRequest req) { StringBuffer requestURL = req.getRequestURL(); //得到完整路径forward后的地址。 System.out.println(requestURL.toString()); } }
-
本地转发(forward);地址栏没变forward只是把结果带到页面:在地址栏再拼路径
zuul: routes: route-name: path: /path-a/** url: forward:/path-b taotao-rest-proxy-forward: # 使用forward本地转发(就是将匹配到路由规则的请求,转发到网关本地应用中去处理) path: /rest-f/** url: forward:/rest # 比如浏览器访问:http://localhost:9100/api/rest-f/content/getall/89 # 会被转发到:http://localhost:9100/rest/content/getall/89
-
nginx代理很重要!(实现集群)
Author:CheckChen