SpringCloud核心
基于HTTP协议,这就是它和Dubbo的最本质的区别。Dubbo的核心是基于RPC。
SpringCloud组件
注册中心:Eurake
客户端负载均衡:Ribbon
声明式远程方法调用:Feign
服务降级、熔断:Hystrix
网关:Zuul
虽然这个博客涉及到的大部分都是停更的技术,但是停更不停用,还是有很多的企业在用的。
这几个也是比较经典的技术,肯定不会白学
本博客涉及到的源码地址:https://gitee.com/rabbityyhh/spring-cloud-parent
对应版本:
在spring的官网就有springcloud的全面的介绍,具体的版本问题大家可以在官网里面看
测试环境
创建maven父工程spring-cloud-parent
,导入springcloud所需要的依赖,cloud的版本与boot的版本要对应,在官网上可查
打包方式为pom就行
<dependencyManagement>
<dependencies>
<!--spring cloud Hoxton.SR11-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR11</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring boot 2.3.10-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.10.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
创建第一个maven module
spring-cloud-common
建立实体类
(把getter setter 无参有参 tostring那一套整上 也可以用Lombok 都随意自己喜欢啥弄啥 太长这里就不写了)
public class entity {
private Integer empId;
private String empName;
private Double empSalary;
}
创建服务提供者maven module
spring-cloud-provider
这个module打包的方式设置jar 因为后来这个module要以springboot的方式跑起来
pom中导入common还有spring boot web
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.yh.spring.cloud</groupId>
<artifactId>spring-cloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
在yml文件里把这个module的端口号改为1000
server:
port: 1000
创建一个主启动类
@SpringBootApplication
public class AtYHMainType {
public static void main(String[] args){
SpringApplication.run(AtYHMainType.class,args);
}
}
新建一个handlerEmployeeHandler
查看环境是否搭建成功
@RestController
public class EmployeeHandler {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote(){
return new Employee(555,"nbclass",555.55 );
}
}
成功就是这样的
整个module的结构
创建消费者maven module
spring-cloud-consumer
pom中的依赖跟服务提供者一样 这里省略
主启动类也是跟服务提供者一样的 这里省略
创建config 得到RestTemplate SpringCloudConfig
@Configuration
public class SpringCloudConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
创建handler
@RestController
public class HumanResourceHandler {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/get/employee")
public Employee getEmployeeRemote(){
//1.声明远程微服务的主机地址加端口号
String host = "http://localhost:1000";
//2.声明具体要调用的功能的URL地址
String url = "/provider/get/employee/remote";
//3.通过RestTemplate调用原称为服务
return restTemplate.getForObject(host + url, Employee.class);
}
}
然后把消费者的端口号设为4000
就可以跑起来测试了 为什么要设置为4000 因为还有俩提供者用于测试
server:
port: 4000
成功以后是这样的
Eureka
创建Eureka注册中心工程
继续创建maven module
spring-cloud-eureka
在pom中导入eureka必要的依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
创建主启动类需要注意一点
- 就是这个@EnableEurekaServer注解是必须加的 没有它就不能导入Eureka相关的Bean
@EnableEurekaServer
@SpringBootApplication
public class AtYHMainType {
public static void main(String[] args){
SpringApplication.run(AtYHMainType.class,args);
}
}
在yaml配置文件中 这回不关要写端口号了,还得写点eurake自己的配置
server:
port: 5000
eureka:
instance:
hostname: localhost # 配置当前Eureka服务的主机地址
client:
register-with-eureka: false # 当前服务本身就是注册中心,不必“自己注册自己”
fetch-registry: false # 当前服务本身就是注册中心,不必“从注册中心取回信息”
service-url: # 客户端(指consumer、provider)访问当前注册中心时使用的地址
defaultZone: http://${eureka.instance.hostname}/${server.port}/eureka
配置之后就可以访问Eureka注册服务中心的主页了
将provider注册到eureka
在provider中添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置yaml 必须要指定应用名称,每一个微服务工程都要设置一个应用名称 不然没法使用Eureka的功能
spring:
application:
name: yh-provider # 指定当前微服务名称,以便将来通过微服务名称调用当前微服务时能够找到
eureka:
client:
service-url: # 配置当前微服务作为Eureka客户端访问Eureka服务器端时使用的地址
defaultZone: http://localhost:5000/eureka
注册成功之后可以在Eurake注册中心里看到
consumer访问provider时使用微服务名称代替localhost:1000
首先在consumer中替换主机地址端口号
//1.声明远程微服务的主机地址加端口号
// String host = "http://localhost:1000";
// 将远程微服务调用地址从“IP地址+端口号”改成“微服务名称”
String host = "http://yh-provider";
在consumer工程加入如下依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在yml中加入配置
server:
port: 4000
spring:
application:
name: yh-consumer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:5000/eureka/
之后在SpringCloudConfig
中的方法中加入@LoadBalance
注解,让RestTemplate
有负载均衡的功能,通过调用Ribbon
访问Provider
集群
可以改一下com.yh.spring.cloud.handler.HumanResourceHandler
中的@RequestMapping("/consumer/ribbon/get/employee")
代码看着更明确点
Ribbon
provider以集群方式启动修改provider的handler方法
@RestController
public class EmployeeHandler {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote(HttpServletRequest request){
// 获取当前服务的端口号
int serverPort = request.getServerPort();
return new Employee(555,"nbclass "+serverPort,555.55 );
}
}
首先启动Eurake中心 然后启动provide:1000 2000 3000
这样就有三个实例了
再然后我们启动消费者consumer 点击刷新就可以得到轮询负载均衡的 效果
Feign
使用Feign实现远程方法声明式调用
在common module中导入feign的依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
</dependencies>
在common中创建一个接口 实现远程调用的接口方法
//这个注解表示当前接口和一个Provider对应,
// 注解中value属性指定要调用的Provider的微服务名称
@FeignClient(value = "yh-provider")
public interface EmployeeRemoteService {
//远程调用的接口方法,
// 要求RequestMapping注解映射的地址一致
//要求方法声明一致
// 用来获取请求参数的@RequestParam、@PathVariable、@RequestBody不能省略,两边一致
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote();
}
创建新的maven module
spring-cloud-feign-consumer
导入所需要的一些个依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.yh.spring.cloud</groupId>
<artifactId>spring-cloud-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
</dependencies>
之后在主启动类当中加入@EnableFeignClients
注解来启用Feign客户端功能
创建handler方法
@RestController
public class FeignHumanResourceHandler {
// 装配调用远程微服务的接口,后面向调用本地方法一样直接使用
@Autowired
private EmployeeRemoteService employeeRemoteService;
@RequestMapping("/feign/consumer/get/emp")
public Employee getEmployeeRemote(){
return employeeRemoteService.getEmployeeRemote();
}
}
编写yaml配置文件
server:
port: 7000
spring:
application:
name: yh-feign-consumer
eureka:
client:
serviceUrl:
defaultZone: http://localhost:5000/eureka/
都完事之后可以去跑一下 成功的话是可以拿到数据的
Hystrix
简介
hystrix,框架,提供了高可用相关的各种各样的功能,然后确保说在hystrix的保护下,整个系统可以长期处于高可用的状态,100%。
高可用系统架构:
资源隔离、限流、熔断、降级、运维监控
资源隔离:让你的系统里,某一块东西,在故障的情况下,不会耗尽系统所有的资源,比如线程资源。
限流:高并发的流量涌入进来,比如说突然间一秒钟100万QPS,废掉了,10万QPS进入系统,其他90万QPS被拒绝了。
熔断:系统后端的一些依赖,出了一些故障,比如说mysql挂掉了,每次请求都是报错的,熔断了,后续的请求过来直接不接收了,拒绝访问,10分钟之后再尝试去看看mysql恢复没有
降级:mysql挂了,系统发现了,自动降级,从内存里存的少量数据中,去提取一些数据出来
测试服务熔断
在服务提供者provider里添加相关的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
之后在著启动类中加入@EnableCircuitBreaker
注解,使用这个注解开启断路器功能
handler
在handler中写一个测试方法
// @HystrixCommand注解指定当前方法出问题时调用的备份方法(使用fallbackMethod属性指定)
@HystrixCommand(fallbackMethod = "getEmpWithCircuitBreakerBackup")
@RequestMapping("/provider/get/emp/with/circuit/breaker")
public ResultEntity<Employee> getEmpWithCircuitBreaker(@RequestParam("signal") String signal) throws InterruptedException {
if("quick-bang".equals(signal)) {
throw new RuntimeException();
}
if("slow-bang".equals(signal)) {
Thread.sleep(5000);
}
return ResultEntity.successWithData(new Employee(666, "empName666", 666.66));
}
public ResultEntity<Employee> getEmpWithCircuitBreakerBackup(@RequestParam("signal") String signal) {
String message = "方法执行出现问题,执行断路 signal="+signal;
return ResultEntity.failed(message);
}
执行成功可以看到
测试hystrix降级
首先在common module中加入hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
消费者降级 在common module中的MyFalBackFactory
中添加消费者服务降级功能
实现FallbackFactory接口时要传入@FeignClient注解的接口类型
在create方法中返回@FeignClient注解标记的接口类型的对象,当Provider调用失败后,会执行这个对象对应方法
@Component
public class MyFallBackFactory implements FallbackFactory<EmployeeRemoteService> {
@Override
public EmployeeRemoteService create(final Throwable cause) {
return new EmployeeRemoteService() {
@Override
public Employee getEmployeeRemote() {
return null;
}
@Override
public List<Employee> getEmpListRemote(String keyword) {
return null;
}
@Override
public ResultEntity<Employee> getEmpWithCircuitBreaker(String signal) {
return ResultEntity.failed(cause.getMessage());
}
};
}
}
在EmployeeRemoteService接口中的@FeignClient注解中加入注解fallbackFactory属性指定provider不可用时提供备用方案的工厂类型
在feign的handler中添加调用方法
@RequestMapping("/feign/consumer/test/fallback")
public ResultEntity<Employee> testFallBack(@RequestParam("signal") String signal){
return employeeRemoteService.getEmpWithCircuitBreaker(signal);
}
之后就可以在Feign的yaml文件中开启hystrix
feign:
hystrix:
enabled: true
之后先启动7000 4000
当启动1000 后 服务重新连接
监控
在provider中添加依赖并修改他的yaml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
endpoints:
web:
exposure:
include: hystrix.stream
创建新的maven module 用于监控
pom
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
</dependencies>
主启动类
//启用仪表盘监控的功能
@EnableHystrixDashboard
@SpringBootApplication
public class AtYHMainDashboard {
public static void main(String[] args){
SpringApplication.run(AtYHMainDashboard.class,args);
}
}
yaml配置文件
server:
port: 8000
spring:
application:
name: yh-dashboard
之后就可以启动访问 8000
Zuul网关
网关有以下几个作用:
- 统一入口:未全部为服务提供一个唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性。
- 鉴权校验:识别每个请求的权限,拒绝不符合要求的请求。
- 动态路由:动态的将请求路由到不同的后端集群中。
- 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。
创建zuul工程
创建maven module spring-cloud-zuul
导入需要的依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
配置主启动类和yaml
server:
port: 9000
spring:
application:
name: zuul-gateway
eureka:
client:
serviceUrl:
defaultZone: http://localhost:5000/eureka/
@EnableZuulProxy
@SpringBootApplication
public class AtYhMainZuul {
public static void main(String[] args){
SpringApplication.run(AtYhMainZuul.class,args);
}
}
访问测试
通过zuul访问服务的,URL地址默认格式为:http://zuulHostIp:port/要访问的服务名称/服务中的URL
服务名称:properties配置文件中的spring.application.name。
服务的URL:就是对应的服务对外提供的URL路径监听。
使用指定地址代替微服务名称
zuul:
ignored-services: '*'
prefix: /maomi #前缀
routes:
employee:
service-id: yh-feign-consumer
path: /zuul-emp/**
之后可以重启一下zuul项目
成功拿到数据
ZuulFilter
编写MyZuulFilter
//加到IOC容器里面
@Component
public class MyZuulFilter extends ZuulFilter {
Logger logger = LoggerFactory.getLogger(MyZuulFilter.class);
//判断当前请求是否要过滤
//要 :继续执行run方法 return true
// 不要 :直接放行 false
public boolean shouldFilter() {
// 获取RequestContext对象
RequestContext requestContext = RequestContext.getCurrentContext();
// 获取Request对象
HttpServletRequest request = requestContext.getRequest();
// 判断当前请求参数是否为signal=hello
String parameter = request.getParameter("signal");
return "hello".equals(parameter);
}
public Object run() throws ZuulException {
logger.info("当前请求要进行过滤,run()方法执行了");
// Current implementation ignores it.
// 当前实现会忽略这个方法的返回值,所以返回null,不做特殊处理
return null;
}
public String filterType() {
// 返回当前过滤器的类型,决定当前过滤器在什么时候执行
// pre表示在目标微服务前执行
String filterType = "pre";
return filterType;
}
public int filterOrder() {
return 0;
}
}
SpringCloud小结
整体流程
springboot和springcloud的关系
springboot是基础springcloud要基于springcloud开发