1. 订单模块远程调用商品/用户模块
1.1 在订单模块pom.xml文件中添加依赖
<!--feign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
1.2 配置application.yml
spring:
application:
name: order-service
server:
port: 8201
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka,http://eureka2:2002/eureka
feign:
hystrix:
enabled: true
management:
endopoints:
include: hystrix.stream
1.3 添加启动类注解
- @EnableFeignClients
- @EnableCircuitBreaker
package cn.tedu.sp04;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableCircuitBreaker
@EnableFeignClients
public class Sp04OrderserviceApplication {
public static void main(String[] args) {
SpringApplication.run(Sp04OrderserviceApplication.class, args);
}
}
1.4 定义声明式接口及降级类
1.4.1 编辑ItemClientService
package cn.tedu.sp04.com.tedu.sp04.order.feign;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@FeignClient(name = "item-service", fallback = ItemClientServiceFB.class)
public interface ItemClientService {
@GetMapping("/{orderId}")
JsonResult<List<Item>> getItems(@PathVariable String orderId);
@PostMapping("decreaseNumber")
JsonResult decreaseNumber(@RequestBody List<Item> items);
}
1.4.2 编辑ItemclientServiceFB
package cn.tedu.sp04.com.tedu.sp04.order.feign;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.web.util.JsonResult;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
public class ItemClientServiceFB implements ItemClientService{
@Override
public JsonResult<List<Item>> getItems(String orderId) {
//模拟缓存数据
if(Math.random()<0.5){
List<Item> list = new ArrayList<>();
list.add(new Item(1, "缓存1", 1));
list.add(new Item(2, "缓存2", 2));
list.add(new Item(3, "缓存3", 3));
list.add(new Item(4, "缓存4", 4));
list.add(new Item(5, "缓存5", 5));
return JsonResult.ok().data(list);
}
//没有缓存数据则失败
return JsonResult.err().msg("获取商品信息失败");
}
@Override
public JsonResult decreaseNumber(List<Item> items) {
return JsonResult.err().msg("商品库存减少失败");
}
}
1.4.3 编辑UserClientService
package cn.tedu.sp04.com.tedu.sp04.order.feign;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = "user-service", fallback = UserClientServiceFB.class)
public interface UserClientService {
@GetMapping("/{userId}")
JsonResult<User> getUser(@PathVariable Integer userId);
@GetMapping("/{userId}/score")
JsonResult addScore(@PathVariable Integer userId,@RequestParam Integer score);
}
1.4.4 编辑UserClientServiceFB
package cn.tedu.sp04.com.tedu.sp04.order.feign;
import cn.tedu.sp01.pojo.User;
import cn.tedu.web.util.JsonResult;
import org.springframework.stereotype.Component;
@Component
public class UserClientServiceFB implements UserClientService{
@Override
public JsonResult<User> getUser(Integer userId) {
//模拟缓存数据
if(Math.random()<0.5) {
JsonResult.ok().data(new User(8,"缓存用户1","缓存密码1"));
}
return JsonResult.err().msg("获取用户信息失败");
}
@Override
public JsonResult addScore(Integer userId, Integer score) {
return JsonResult.err().msg("用户积分添加失败");
}
}
1.5 编辑OrderServiceImpl
package cn.tedu.sp04.com.tedu.sp04.order.service;
import cn.tedu.sp01.pojo.Item;
import cn.tedu.sp01.pojo.Order;
import cn.tedu.sp01.pojo.User;
import cn.tedu.sp01.service.OrderService;
import cn.tedu.sp04.com.tedu.sp04.order.feign.ItemClientService;
import cn.tedu.sp04.com.tedu.sp04.order.feign.UserClientService;
import cn.tedu.web.util.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@Slf4j
public class OrderServiceImpl implements OrderService {
@Autowired
private UserClientService userClientService;
@Autowired
private ItemClientService itemClientService;
@Override
public Order getOrder(String orderId) {
//TODO: 调用user-service获取用户信息
JsonResult<User> user = userClientService.getUser(7);
//TODO: 调用item-service获取商品信息
JsonResult<List<Item>> items = itemClientService.getItems(orderId);
Order order = new Order();
order.setId(orderId);
order.setUser(user.getData());
order.setItems(items.getData());
return order;
}
@Override
public void addOrder(Order order) {
//TODO: 调用item-service减少商品库存
itemClientService.decreaseNumber(order.getItems());
//TODO: 调用user-service增加用户积分
userClientService.addScore(7,100);
log.info("保存订单:"+order);
}
}
1.6 配置两台启动配置
- servert.port=8201
- servert.port=8202
1.7 测试
1.7.1 根据orderId,获取订单
http://localhost:8201/123abc
http://localhost:8202/123abc
1.7.2 保存订单
http://localhost:8201/
http://localhost:8202/
1.7.3 hystrix dashboard监控order service
访问 http://localhost:4001/hystrix,启动监控
http://localhost:8201/actuator/hystrix.stream
http://localhost:8202/actuator/hystrix.stream
2. Turbine
2.1 什么是Turbine
Turbine是一个聚合监控数据,负责汇集监控信息
2.2 搭建Turbine
2.2.1 创建module添加依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.teud</groupId>
<artifactId>sp10-turbine</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sp10-turbine</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<!--eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--turbine-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.2.2 配置application.yml
spring:
application:
name: turbine
server:
port: 5001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
turbine:
app-config: order-service
cluster-name-expression: new String("default")
2.2.3 配置启动类
添加注解:
- @EnableTurbine
- @EnableDiscoveryClient
package cn.tedu.sp10;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.turbine.EnableTurbine;
@SpringBootApplication
@EnableTurbine
@EnableDiscoveryClient
public class Sp10TurbineApplication {
public static void main(String[] args) {
SpringApplication.run(Sp10TurbineApplication.class, args);
}
}
2.2.4 启动测试
-
8201服务器产生监控数据:
http://localhost:8201/abc123
http://localhost:8201/ -
8202服务器产生监控数据:
http://localhost:8202/abc123
http://localhost:8202/ -
turbine 监控路径
http://localhost:5001/turbine.stream
-
在 hystrix dashboard 中填入turbine 监控路径,开启监控
http://localhost:4001/hystrix
http://localhost:5001/turbine.stream -
turbine聚合了order-service两台服务器的hystrix监控信息
3. Zuul api网关
- zuul API 网关是微服务系统统一的调用入口
- zuul 提供过滤器,对所有微服务提供统一的请求校验
3.1 搭建zuul
3.1.1 创建module项目添加依赖
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>sp11-zuul</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>sp11-zuul</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<!--zuul-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--eureka-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--commons-->
<dependency>
<groupId>cn.tedu</groupId>
<artifactId>sp01-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3.1.2 配置application.yml
- zuul 路由配置可以省略,缺省以服务 id 作为访问路径
string:
application:
name: zuul
server:
port: 3001
eureka:
client:
service-url:
defaultZone: http://eureka1:2001/eureka, http://eureka2:2002/eureka
zuul:
routes:
item-service: /item-service/**
user-service: /user-service/**
order-service: /order-service/**
3.1.2 启动类添加注解
- @EnableZuulProxy//实现统一校验,请求过滤
- @EnableDiscoveryClient
package cn.tedu.sp11;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
public class Sp11ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(Sp11ZuulApplication.class, args);
}
}
3.1.3 测试
- http://eureka1:2001
- http://localhost:3001/item-service/35
- http://localhost:3001/user-service/7
- http://localhost:3001/user-service/7/score?score=100
- http://localhost:3001/order-service/123abc
- http://localhost:3001/order-service/
- 使用postman,POST发送以下格式数据:http://localhost:3001/item-service/decreaseNumber
[{“id”:1, “name”:“abc”, “number”:23},{“id”:2, “name”:“def”, “number”:11}]
3.2 zuul实现统一校验,请求过滤
3.2.1 添加注解(已添加)
@EnableZuulProxy//实现统一校验,请求过滤
3.2.2 编辑过滤器类
package cn.tedu.sp11.filter;
import cn.tedu.web.util.JsonResult;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class AccessFilter extends ZuulFilter {
//指定过滤器类型, "pre","routes","post","error"
@Override
public String filterType() {
// return "pre";
return FilterConstants.PRE_TYPE;
}
//指定过滤器添加位置序号
@Override
public int filterOrder() {
return 6;
}
//判断针对当前这次请求,是否要执行过滤代码
//如果请求item-service,执行全选判断
@Override
public boolean shouldFilter() {
//获得正在调用的服务的服务id
RequestContext ctx = RequestContext.getCurrentContext();
String serviceId = (String)ctx.get(FilterConstants.SERVICE_ID_KEY);
return "item-service".equalsIgnoreCase(serviceId);
}
//过滤代码
@Override
public Object run() throws ZuulException {
// http://localhost:3001/item-service/35?token=1234
RequestContext ctx = RequestContext.getCurrentContext();
//获取request对象
HttpServletRequest req = ctx.getRequest();
//接受token参数
String token = req.getParameter("token");
//如果token参数不存在,则阻止继续调用,直接返回提示
if(StringUtils.isBlank(token)){
//此设置会组织请求被路由到后台微服务
ctx.setSendZuulResponse(false);
//向客户端的响应
String json = JsonResult.err().code(JsonResult.NOT_LOGIN).msg("not Login,没有登录").toString();
ctx.addZuulResponseHeader("Content-Type","application/json;charset=UTF-8");
ctx.setResponseBody(json);
}
return null;//返回任意数据都无效
}
}
3.2.3 demo测试
假设:
-
没有token参数不允许访问
http://localhost:3001/item-service/35
-
有token参数可以访问
http://localhost:3001/item-service/35?token=1234
3.3 zuul实现负载均衡和重试
3.3.1 实现负载均衡
zuul 已经集成了 ribbon,默认已经实现了负载均衡
3.3.2 实现重试(zuul不推荐)
3.2.2.1 添加依赖
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
3.2.2.2 编辑application.yml
#开启重试
retryable: true
3.4 zuul实现降级
3.4.1 启动hystrix(默认启动)
3.4.2 创建降级类
3.4.2.1 实现FallbackProvider接口
- ItemFB
package cn.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Component
public class ItemFB implements FallbackProvider {
//通过制定服务id,设置针对哪个服务进行降级
//如果返回 null / "*",表示针对所有服务都应用当前降级类
@Override
public String getRoute() {
return "item-service";
}
//用来封装降级响应的响应对象
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
String json = JsonResult.err().code(500).msg("调用商品服务失败").toString();
return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders h = new HttpHeaders();
h.add("Content-Type","application/json;charset=UTF-8");
return h;
}
};
}
}
- UserFB
package cn.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Component
public class UserFB implements FallbackProvider {
//通过制定服务id,设置针对哪个服务进行降级
//如果返回 null / "*",表示针对所有服务都应用当前降级类
@Override
public String getRoute() {
return "user-service";
}
//用来封装降级响应的响应对象
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
String json = JsonResult.err().code(500).msg("调用用户服务失败").toString();
return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders h = new HttpHeaders();
h.add("Content-Type","application/json;charset=UTF-8");
return h;
}
};
}
}
- OrderFB
package cn.tedu.sp11.fallback;
import cn.tedu.web.util.JsonResult;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
@Component
public class OrderFB implements FallbackProvider {
//通过制定服务id,设置针对哪个服务进行降级
//如果返回 null / "*",表示针对所有服务都应用当前降级类
@Override
public String getRoute() {
return "order-service";
}
//用来封装降级响应的响应对象
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse() {
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
String json = JsonResult.err().code(500).msg("调用订单服务失败").toString();
return new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8));
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders h = new HttpHeaders();
h.add("Content-Type","application/json;charset=UTF-8");
return h;
}
};
}
}
3.4.2.2 访问失败结果测试
http://localhost:3001/item-service/35?token=1234
3.4.3 配置暴露监控:
zuul依赖中包含actuator监控依赖,不需要添加依赖只需要在application.yml文件中添加下面内容:
#配置暴露监控数据
management:
endpoints:
web:
exposure:
include: hystrix.stream
启动服务,查看暴露的监控端点
http://localhost:3001/actuator
http://localhost:3001/actuator/hystrix.stream
3.4.4 查看监控数据
启动 sp08-hystrix-dashboard,填入 zuul 的监控端点路径,开启监控
http://localhost:4001/hystrix
填入监控端点:
http://localhost:3001/actuator/hystrix.stream
3.5 zuul+turbine 聚合监控
3.5.1 编辑sp10-turbine的application.yml
3.5.2 重启sp10测试
http://localhost:4001/hystrix
http://localhost:5001/turbine.stream
- 必须通过zuul网关访问后台服务才会产生监控数据
http://localhost:3001/item-service/35
http://localhost:3001/user-service/7
http://localhost:3001/user-service/7/score?score=100
http://localhost:3001/order-service/123abc
http://localhost:3001/order-service/
http://localhost:3001/item-service/decreaseNumber - 使用postman,POST发送以下格式数据:
[{“id”:1, “name”:“abc”, “number”:23},{“id”:2, “name”:“def”, “number”:11}]
3.6 zuul Feign区别
3.6.1 共同点
- ribbon
- hystirx
- 远程调用
3.6.2 不同点
3.6.2.1 zuul
- 部署在微服务系统的最前面
- 独立的服务
- 不推荐重试
- 会造成大面积服务器压力翻倍,重试功能越靠后越好
3.6.2.2 Feign
- 部署在微服务内部,业务模块之间调用
- 综合业务模块使用
- 不推荐hystrix
- 如果所有微服务都启动hystrix,会造成混乱
- 一般将hystrix往前加