SpringCloud核心
基于HTTP协议,这是它和Dubbo的最本质区别。Dubbo的核心是基于RPC。
Eureka:注册中心
Ribbon:客户端负载均衡
Feign:远程接口的声明式调用
Hystrix:服务的熔断、降级、监控
Zuul:网关
一、环境搭建
java 12 + SpringBoot 2.1.6.RELEASE + SpringCloud Greenwich.SR2
1. SpringCloud必须与SpringBoot配合使用。创建父工程,集中管理依赖,不必每个项目都重新导依赖。
<dependencies>
<!-- 导入 SpringCloud 需要使用的依赖信息 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR2</version>
<type>pom</type> <!-- import 依赖范围表示将 spring-cloud-dependencies 包中的依赖信息导入 -->
<scope>import</scope>
</dependency>
<!-- 导入 SpringBoot 需要使用的依赖信息 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.6.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
2. 创建provider项目(导入SpringBoot Web、配置端口号)
handler方法
@RestController
public class EmployeeHandler {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote() {
return new Employee(555, "tom555", 555.55);
}
}
3. 创建consumer项目(导入SpringBoot Web、配置端口号)
编写配置类,注入RestTemplate类。
@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.远程调用具体地址
String url="/provider/get/employee/remote";
// 3.调用远程方法
return restTemplate.getForObject(host+url,Employee.class);
}
}
4. 启动 provider 和 consumer 项目,测试。
5. 创建Eureka项目
导入Eureka依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
在SpringBoot启动类上,开启EurekaServer注解。
@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaApplication.class);
}
}
配置application.yml
server:
port: 5000
eureka:
instance:
hostname: localhost
client:
register-with-eureka: false #自己就是注册中心,不需要注册自己
fetch-registry: false #自己就是注册中心,不需要“从注册中心取回信息”
service-url: #客户端访问Eureka的地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
6. 启动Eureka项目,出现注册界面,配置成功。
如果出现启动错误 Type javax.xml.bind.JAXBContext not present
错误原因:java9+版本以后,JAXB默认没有加载
手动导入依赖。
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
7. 将微服务注册到Eureka
1)provider服务
在需要注册的客户端项目添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
配置application.yml
spring:
application:
name: crowdfunding-provider #指定当前项目作为微服务的名称
eureka:
client:
service-url: #配置当前微服务作为Eureka客户端访问Eureka服务端时使用的地址
defaultZone: http://localhost:5000/eureka/
启动项目,访问Eureka注册页面
2)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>
配置application.yml
spring:
application:
name: crowdfunding-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/
在配置类RestTemplate上添加负载均衡注解 @LoadBalanced ,将访问地址用微服务名称代替。String host = "http://springcloud-provider";
启动项目,可以看到两个服务均已被注册,并且访问成功。
8. 以集群的方式启动provider服务
修改handler方法,方便查看
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote(HttpServletRequest request) {
int port = request.getServerPort();
return new Employee(555, "tom555 "+port, 555.55);
}
修改端口号,分别以1000,2000,3000启动三个服务,注意:服务名必须一样。
9. 使用 Feign 实现远程方法声明式调用
common工程加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
编写接口方法
// 当前接口和一个provider对应,value指定调用微服务的名称
@FeignClient(value = "crowdfunding-provider")
public interface EmployeeRemoteService {
@RequestMapping("/provider/get/employee/remote")
public Employee getEmployeeRemote();
}
新建 Feign-consumer 工程,导入依赖
<dependency>
<groupId>com.spring.cloud</groupId>
<artifactId>springcloud-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类上开启 @EnableFeignClients 注解,配置application.yml,内容同consumer,端口号改为7000,编写handler方法。
6000端口号被系统占用
@RestController
public class HumanResourceHandler {
//自动导入,直接使用
@Autowired
private EmployeeRemoteService employeeRemoteService;
@RequestMapping("/feign/consumer/get/employee")
public Employee getEmployeeRemote(){
return employeeRemoteService.getEmployeeRemote();
}
}
对于需要参数传递的方法,最好加上 @RequestParam ,@RequestBody 等参数注解,特别是传递对象参数时。
二、Hystrix和Zuul
1. Hystrix
Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
1)熔断 ——服务端
provider项目导入Hystrix依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
在启动类上开启断路器功能 @EnableCircuitBreaker ,编写handler方法
// 指定断路备用方法
@HystrixCommand(fallbackMethod = "getEmployeeBreakBackUp")
@RequestMapping("/provider/get/employee/circuit")
public ResultEntity getEmployeeBreak(){ // ResultEntity自定义返回类
int i=10/0; // 抛出异常
return ResultEntity.successWithData(new Employee(666,"tom666",66.00));
}
// 备用方法
public ResultEntity getEmployeeBreakBackUp(){
return ResultEntity.failed("运行错误!");
}
2)降级——客户端
common工程导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
实现 FallbackFactory 接口,在 create 方法中返回自定义的备用方案
在 @FeignClient 中指定备用方案
@FeignClient(value="springcloud-provider", fallbackFactory=MyFallBackFactory.class)
在application.yml中加入
feign:
hystrix:
enabled: true
3)监控
在需要被监控的项目provider加入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
配置application.yml
management.endpoints.web.exposure.include: hystrix.stream
创建监控工程,导入依赖,在启动类上添加 @EnableHystrixDashboard 注解,启用 Hystrix 仪表盘功能,配置端口号和服务名
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
访问hystrix监控页面(http://localhost:8000/hystrix),输入监控工程地址(http://localhost:1000/actuator/hystrix.stream)
2. Zuul
创建springcloud-zuul工程,导入依赖,在主启动类上添加 @EnableZuulProxy 注解,开启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: springcloud-zuul
eureka:
client:
service-url:
defaultZone: http://localhost:5000/eureka/
zuul:
routes: #自定义路由规则的名称,以map形式存放
employee: #key
serviceId: springcloud-feign-consumer #微服务名称
path: /zuul-emp/** #代替微服务路径
#ignored-services: '*' #忽略自定义的服务器名称,让用户不能通过服务器名称访问,数据类型为数组,'*'表示忽略所有
#prefix: /cloud #给访问路径添加统一前缀
通过统一网关和别名访问
ZullFilter
自定义过滤器类,继承 ZuulFilter
@Component
public class MyZuulFilter extends ZuulFilter {
@Override
public String filterType() {
// 返回当前过滤器类型
// 可选类型包括:pre、route、post、static
String type="pre";
return type;
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
// 获取request对象
RequestContext context=RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
// 获取请求路径
String path = request.getServletPath();
System.out.println(path);
// true:表示应该过滤,下面继续执行 run()方法
// false:表示不过滤,直接放行
return true;
}
@Override
public Object run() throws ZuulException {
System.out.println("执行run方法");
return null;
}
}