一、Spring Cloud基本组件
注册中心: Eureka<br />
负载均衡: Ribbon<br />
声明式调用远程方法: Feign<br />
熔断、降级、监控: Hystrix<br />
网关: Zuul
原理:
各个微服务的提供者(provider)将各自注册入Eureka注册中心,可以指定一个模块,导入Feign的相关依赖,专门用于编写对应各个提供者的接口,通过Feign,将抽象方法与微服务的具体方法联系起来。而微服务的使用者(consumer)也注册入Eureka,并依赖前面的Feign接口模块,这样可以通过自动装配各个接口,来调用接口的方法,而这些方法就是在微服务中被实现的,并且可以直接在使用者工程中使用;
Ribbon提供了客户端负载均衡的功能,但是并不需要开发时主动配置,就有默认的配置。
Hystrix的作用则是给provider提供熔断机制(在provider中提供方法的备用方法)、给consumer提供降级功能(在consumer中给调用远程方法处增加备用方法,通常是在调用失败时,在方法中匿名实现接口,使用自己实现的接口的方法),并且提供了监控provider的功能(Hystrix Dashboard);
Zuul的作用则是给外部提供一个统一的入口,如浏览器等访问时,就可以通过Zuul提供的入口,进行相对简单的访问。
二、各服务具体配置内容
此处Spring Boot版本: 2.3.3RELEASE
Spring Cloud版本:Hoxton.SR8
在parent工程管理依赖信息
<dependencyManagement>
<dependencies>
<!-- 导入SpringCloud需要的依赖信息 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- SpringBoot依赖信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.3.3.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Eureka
服务端:
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
主启动类
@EnableEurekaServer
@SpringBootApplication
public class CrowdMainApp {
public static void main(String[] args) {
SpringApplication.run(CrowdMainApp.class, args);
}
}
application.yml
server: # 服务的端口号
port: 1000
spring: # Spring的应用名
application:
name: crowd-eureka
eureka: # Eureka的服务端设置hostname,客户端通过hostname与端口访问
instance:
hostname: localhost
client:
fetch-registry: false # 自己就是注册中心,因此不注册自己
register-with-eureka: false # 自己就是注册中心,不需要从注册中心取回信息
service-url: # 客户端访问Eureka时,使用的地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
客户端:
依赖
<!-- eureka客户端依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
yml配置:
server:
port: 2000
spring:
application:
name: crowd-mysql # application.name会显示在Eureka的页面
eureka:
client:
service-url:
defaultZone: http://localhost:1000/eureka/
注意:低版本的Spring Cloud可能需要在主启动类上加@EnableEurekaClient或@EnableDiscoveryClient注解。
Feign
Feign的依赖spring-cloud-starter-openfeign一般会加入到一个通用的接口模块,其他消费者项目依赖该模块,以得到这些接口,来调用接口对应的远程方法。
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
统一接口
远程调用方法的接口示例:
@FeignClient("crowd-mysql")
public interface MySQLRemoteService {
@RequestMapping("/get/member/by/login/acct/remote")
public ResultEntity<MemberPO> getMemberPOByLoginAcctRemote(@RequestParam("loginacct") String loginacct);
}
@RequestMapping中的url必须与远程方法的@RequestMapping中的url相同;方法的声明也必须完全相同,其中参数前的@RequestParam、@RequestBody等,也必须保持一致
远程方法
@RestController
public class MemberProviderHandler {
@Autowired
MemberService memberService;
@RequestMapping("/get/member/by/login/acct/remote")
public ResultEntity<MemberPO> getMemberPOByLoginAcctRemote(@RequestParam("loginacct") String loginacct){
try {
MemberPO memberPO = memberService.getMemberPOByLoginAcct(loginacct);
return ResultEntity.successWithData(memberPO);
} catch (Exception e){
e.printStackTrace();
return ResultEntity.failed(e.getMessage());
}
}
}
Consumer
在Consumer中想要调用接口模块中对应的远程方法,则必须在主启动类上加入@EnableFeignClients注解开启Feign客户端功能。
前提也需要引入前面接口项目的依赖
@EnableFeignClients
@SpringBootApplication
public class CrowdMainApp {
public static void main(String[] args) {
SpringApplication.run(CrowdMainApp.class, args);
}
}
Hystrix
Hystrix提供了服务提供端的熔断功能、服务使用端的降级功能以及对提供端的监控功能。
熔断:
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
主启动类设置开启断路器功能
// 开启断路器功能
@EnableCircuitBreaker
@SpringBootApplication
public class SpringCloudStudyProviderApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudStudyProviderApplication.class, args);
}
}
handler层的方法中添加一个备用方法,作为熔断时的备用:
触发异常、长时间未响应等情况都会触发服务端熔断
@RestController
public class EmployeeHandler {
// @HystrixCommand表示出现问题时,使用fallbackMethod的属性值的方法名的方法作为备用方法
@HystrixCommand(fallbackMethod = "getEmpBackup")
@RequestMapping("/provider/circuit/breaker/get/emp")
public ResultEntity<Employee> getEmp(@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(99,"FallBack",2888.88));
}
// getEmp熔断时的备用方法
public ResultEntity<Employee> getEmpBackup(@RequestParam("signal") String signal) {
return ResultEntity.failed("熔断生效-因为signal:" + signal);
}
}
降级:
以下的对接口的设置发生在接口模块,而不是consumer模块
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
通过一个自己实现的FallbackFactory接口实现类,定义降级操作
// 通过实现FallbackFactory接口,给出consumer端的备用方案(降级)
// 自定义的FallbackFactory必须通过@Component加入IOC容器,
// 否则在对应接口的@FeignClient注解中通过fallbackFactory属性设置时会找不到该类
@Component
// 通过FallbackFactory的泛型指出是对谁进行降级操作
public class MyFallbackFactory implements FallbackFactory<EmployeeRemoteService> {
@Override
public EmployeeRemoteService create(Throwable throwable) {
return new EmployeeRemoteService() {
// 不想进行降级操作就直接返回null
@Override
public Employee getEmployeeRemoteToFeign(String keyword) {
return null;
}
// 实现的该方法,就是在原本service中该方法不可达时,备用调用的方法
@Override
public ResultEntity<Employee> getEmp(String signal) throws InterruptedException {
return ResultEntity.failed("执行降级" + throwable.getMessage());
}
};
}
}
EmployeeService使用FallbackFactory:
// value指定eureka中的application name,fallbackFactory指定代表备用方案的FallbackFactory实现类
@FeignClient(value = "fall-provider" , fallbackFactory = MyFallbackFactory.class)
public interface EmployeeRemoteService {
@RequestMapping("/provider/feign/get/employee/remote")
public Employee getEmployeeRemoteToFeign(@RequestParam("keyword") String keyword);
@RequestMapping("/provider/circuit/breaker/get/emp")
public ResultEntity<Employee> getEmp(@RequestParam("signal") String signal) throws InterruptedException;
}
consumer模块的操作:
在application.yml文件中设置开启hystrix:
feign:
hystrix:
enabled: true
监控
监控仪表盘模块:
依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
主启动类开启仪表盘功能
@EnableHystrixDashboard
@SpringBootApplication
public class MyDashboardMainApp {
public static void main(String[] args) {
SpringApplication.run(MyDashboardMainApp.class, args);
}
}
application.yml
server:
port: 8000
spring:
application:
name: fall-dashboard
hystrix:
dashboard:
proxy-stream-allow-list: localhost # 设置允许监控的列表,不设置正确的话会在监控页面出现无法连接的提示
被监控的模块:
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml加入:
management:
endpoints:
web:
exposure:
include: hystrix.stream
通过Hystrix Dashboard工程的端口访问监控首页
如 : http://localhost:8000/hystrix
在页面中通过http://url:port/actutor/hystrix.stream,访问对应url和port的监控页面
Zuul
Zuul网关与Eureka整合,将自身注册入Eureka,并通过application-name管理其中注册的服务等,让外界可以直接通过Zuul访问各项微服务。
依赖
<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>
application.yml
server:
port: 9000
spring:
application:
name: fall-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/
zuul:
routes:
employee: # 设置“/zuul-emp/”来代替微服务的名字,“employee”是自定义的,可以使用其他的名字
serviceId: fall-feign-consumer
path: /zuul-emp/**
ignored-services: # 设置不能通过“fall-feign-consumer”(微服务的名字)来访问,只能通过包装后的path
- fall-feign-consumer
# ignored-services: '*' :用于忽视所有微服务的名称
主启动类
// 启动Zuul代理功能
@EnableZuulProxy
@SpringBootApplication
public class MyZuulMainApp {
public static void main(String[] args) {
SpringApplication.run(MyZuulMainApp.class,args);
}
}
此时用户就可以通过:
http://localhost:9000/application-name/requestMapping的url
来访问
ZuulFilter
Zuul中提供的一个过滤器,可以通过ZuulFilter进行登录检测等。
需要继承ZuulFilter,并通过@Component加入IOC容器
@Component
// 继承ZuulFilter,并放入IOC容器,通过该类可通过网关进行过滤
public class MyZuulFilter extends ZuulFilter {
Logger logger = LoggerFactory.getLogger(MyZuulFilter.class);
@Override
public String filterType() {
// filterType用pre返回,表示在目标微服务之前执行过滤操作
String filterType = "pre";
return filterType;
}
@Override
public int filterOrder() {
return 0;
}
/***
*
* @return true: 表示需要过率; false:表示直接放行
*/
@Override
public boolean shouldFilter() {
// 获取当前的RequestContext对象
RequestContext context = RequestContext.getCurrentContext();
// 通过RequestContext对象得到当前请求对象
HttpServletRequest request = context.getRequest();
// 判断
String signal = request.getParameter("signal");
// signal=="hello"时,进入过滤
return "hello".equals(signal);
}
@Override
public Object run() throws ZuulException {
logger.info("signal==hello,进入过滤!");
System.err.println("signal==hello,进入过滤!");
// 官方文档指出当前实现会忽略该返回值
// 因此直接返回null即可
return null;
}
}