SpringCloud学习之路
1、使用IDEA搭建Eureka服务中心Server端启动
1.1、创建和配置注册中心Eureka
添加Eureka Server
第一步:创建项目
<?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.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yuange</groupId>
<artifactId>eureka_server</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka_server</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
第二步: 添加注解 @EnableEurekaServer
第三步:增加配置application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
#声明自己是个服务端
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
1.2、使用Eureka案例
创建product-service(使用的轮询)
server:
port: 8771
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service
启动多个节点
1、启动多个
2、指定端口号
1.3、负载均衡器Ribbon
RPC:
远程过程调用,像调用本地服务(方法)一样调用服务器的服务
支持同步、异步调用
客户端和服务器之间建立TCP连接,可以一次建立一个,也可以多个调用复用一次链接
PRC数据包小
protobuf
thrift
rpc:编解码,序列化,链接,丢包,协议
Rest(Http):
http请求,支持多种协议和功能
开发方便成本低
http数据包大
java开发:HttpClient,URLConnection
Ribbon(软负载均衡,在客户端上进行)
使用ribbon. (类似httpClient,URLConnection)
启动类增加注解
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
创建order-server
依赖
<?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.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yuange</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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>
第一种启动方式(核心代码)
@Service
public class ProductOrderServiceImpl implements ProductOrderService {
@Autowired
private RestTemplate restTemplate;
@Override
public ProductOrder save(int userId, int productId) {
//地址(服务名称/api),返回类型
Object obj = restTemplate.getForObject("http://product-service/api/v1/product/find?id="+productId, Object.class);
System.out.println(obj);
ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setUserId(userId);
productOrder.setTradeNo(UUID.randomUUID().toString());
return productOrder;
}
}
第二种启动方式
@Service
public class ProductOrderServiceImpl implements ProductOrderService {
// @Autowired
// private RestTemplate restTemplate;
@Autowired
private LoadBalancerClient loadBalancerClient;
@Override
public ProductOrder save(int userId, int productId) {
//第一种
//地址(服务名称/api),返回类型
//Map<String,Object> productMap = restTemplate.getForObject("http://product-service/api/v1/product/find?id="+productId, Map.class);
//第二种
ServiceInstance instance = loadBalancerClient.choose("product-service");
//地址,ip,端口号
String url=String.format("http://%s:%s/api/v1/product/find?id="+productId,instance.getHost(),instance.getPort());
RestTemplate restTemplate = new RestTemplate();
Map<String,Object> productMap = restTemplate.getForObject(url, Map.class);
ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setUserId(userId);
productOrder.setTradeNo(UUID.randomUUID().toString());
productOrder.setProductName(productMap.get("name").toString());
productOrder.setPrice(Integer.parseInt(productMap.get("price").toString()));
return productOrder;
}
}
自定义负载均衡,从轮询到随机
策略选择:
1、如果每个机器配置一样,则建议不修改策略 (推荐)
2、如果部分机器配置强,则可以改为 WeightedResponseTimeRule
server:
port: 8781
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: order-service
#自定义负载均衡策略(从轮询到随机)
product-service:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
1.4、负载均衡器Feign
包含了Feign的调用超时时间
Feign: 伪RPC客户端(本质还是用http)
官方文档: https://cloud.spring.io/spring-cloud-openfeign/
1、使用feign步骤讲解(新旧版本依赖名称不一样)
加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
启动类增加@EnableFeignClients
增加一个接口 并@FeignClient(name="product-service")
2、编码实战
3、注意点:
1、路径
2、Http方法必须对应
3、使用requestBody,应该使用@PostMapping
4、多个参数的时候,通过@RequestParam("id") int id)方式调用
第一步,引入依赖(新旧版本依赖名称不一样)
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
第二步,启动类增加@EnableFeignClients
第三步,增加一个接口 并@FeignClient(name=“product-service”)
@FeignClient(name = "product-service")
public interface ProductClient {
/**
* 通过id查找
* @param id
* @return
*/
@RequestMapping("/api/v1/product/find")
String findById(@RequestParam(value = "id") int id);
}
创建一个JsonUtils
public class JsonUtils {
private static final ObjectMapper objectMappper = new ObjectMapper();
/**
* json字符串转JsonNode对象的方法
*/
public static JsonNode str2JsonNode(String str){
try {
return objectMappper.readTree(str);
} catch (IOException e) {
return null;
}
}
}
第四步,调用
@Service
public class ProductOrderServiceImpl implements ProductOrderService {
@Autowired
private ProductClient productClient;
@Override
public ProductOrder save(int userId, int productId) {
//使用Feign创建
String response = productClient.findById(productId);
JsonNode jsonNode = JsonUtils.str2JsonNode(response);
ProductOrder productOrder = new ProductOrder();
productOrder.setCreateTime(new Date());
productOrder.setUserId(userId);
productOrder.setTradeNo(UUID.randomUUID().toString());
productOrder.setProductName(jsonNode.get("name").toString());
productOrder.setPrice(Integer.parseInt(jsonNode.get("price").toString()));
return productOrder;
}
}
注意点:
1、路径
2、Http方法必须对应
3、使用requestBody,应该使用@PostMapping
4、多个参数的时候,通过@RequestParam(“id”) int id)方式调用
1.5、Feign核心源码解读和服务调用方式ribbon和Feign选择
1、ribbon和feign两个的区别和选择
选择feign
默认集成了ribbon
写起来更加思路清晰和方便
采用注解方式进行配置,配置熔断等方式方便
2、超时配置
默认optons readtimeout是60,但是由于hystrix默认是1秒超时
#修改调用超时时间
feign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 2000
模拟接口响应慢,线程睡眠新的方式
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
2、降级熔断Hystrix实战
1、熔断:
保险丝,熔断服务,为了防止整个系统故障,包含子和下游服务
下单服务 -》商品服务
-》用户服务 (出现异常-》熔断)
2、降级:
抛弃一些非核心的接口和数据
旅行箱的例子:只带核心的物品,抛弃非核心的,等有条件的时候再去携带这些物品
3、熔断和降级互相交集
相同点:
1)从可用性和可靠性触发,为了防止系统崩溃
2)最终让用户体验到的是某些功能暂时不能用
不同点
1)服务熔断一般是下游服务故障导致的,而服务降级一般是从整体系统负荷考虑,由调用方控制
2.1、SpringCloud整合断路器的使用,用户服务异常情况(熔断)
第一步导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
第二步启动类上添加注解
@EnableCircuitBreaker
注解越来越多可以使用SpringCloudApplication注解
第三步在api方法上增加 @HystrixCommand(fallbackMethod = “saveOrderFail”)
注意编写fallback方法实现,方法签名一定要和api方法签名一致
核心代码
@RestController
@RequestMapping("api/v1/order")
public class OrderController {
@Autowired
private ProductOrderService productOrderService;
@RequestMapping("save")
@HystrixCommand(fallbackMethod = "saveOrderFail")
public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId) {
Map<String, Object> data = new HashMap<>();
data.put("code", 0);
data.put("data", productOrderService.save(userId, productId));
return data;
}
//注意方法签名一定要和api方法一致
private Object saveOrderFail(int userId, int productId) {
Map<String, Object> msg = new HashMap<>();
msg.put("code", -1);
msg.put("msg", "抢购人数太多,您被挤出来了,稍等重试");
return msg;
}
}
PRODUCT-SERVICE宕机后
补充: 修改maven仓库地址
pom.xml中修改
<repositories>
<repository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<layout>default</layout>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
2.2、SpringCloud整合断路器的使用,用户服务异常情况(降级)
1、feign结合Hystrix
1)开启feign支持hystrix (注意,一定要开启,旧版本默认支持,新版本默认关闭)
feign:
hystrix:
enabled: true
2)FeignClient(name="xxx", fallback=xxx.class ), class需要继承当前FeignClient的类
@FeignClient(name = "product-service",fallback = ProductClientFallback.class)
public interface ProductClient {
/**
* 通过id查找
* @param id
* @return
*/
@RequestMapping("/api/v1/product/find")
String findById(@RequestParam(value = "id") int id);
}
@Component
public class ProductClientFallback implements ProductClient {
@Override
public String findById(int id) {
System.out.println("feign 调用product-service findbyid 异常");
return null;
}
}
2.3、熔断降级服务异常报警通知
加入redis依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置redis链接信息
redis:
database: 0
host: 127.0.0.1
port: 6379
timeout: 2000
使用
核心代码
//监控报警(key-value形式)
String saveOrderKey = "save-order";
String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
//知道那个节点ip信息
final String ip = request.getRemoteAddr();
//发送信息要异步,开一个子线程(使用了lambda表达式)
new Thread(() -> {
if (StringUtils.isBlank(sendValue)) {
System.out.println("紧急短信,用户下单失败,请离开查找原因,ip地址是="+ip);
//发送一个http请求,调用短信服务 TODO (二十秒)
redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS);
} else {
System.out.println("已经发送过短信,20秒内不重复发送");
}
}).start();
举例
@RestController
@RequestMapping("api/v1/order")
public class OrderController {
@Autowired
private ProductOrderService productOrderService;
@Autowired
private StringRedisTemplate redisTemplate;
@RequestMapping("save")
@HystrixCommand(fallbackMethod = "saveOrderFail")
public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId, HttpServletRequest request) {
Map<String, Object> data = new HashMap<>();
data.put("code", 0);
data.put("data", productOrderService.save(userId, productId));
return data;
}
//注意方法签名一定要和api方法一致
private Object saveOrderFail(int userId, int productId, HttpServletRequest request) {
//监控报警(key-value形式)
String saveOrderKey = "save-order";
String sendValue = redisTemplate.opsForValue().get(saveOrderKey);
//知道那个节点ip信息
final String ip = request.getRemoteAddr();
//发送信息要异步,开一个子线程(使用了lambda表达式)
new Thread(() -> {
if (StringUtils.isBlank(sendValue)) {
System.out.println("紧急短信,用户下单失败,请离开查找原因,ip地址是="+ip);
//发送一个http请求,调用短信服务 TODO (二十秒)
redisTemplate.opsForValue().set(saveOrderKey, "save-order-fail", 20, TimeUnit.SECONDS);
} else {
System.out.println("已经发送过短信,20秒内不重复发送");
}
}).start();
Map<String, Object> msg = new HashMap<>();
msg.put("code", -1);
msg.put("msg", "抢购人数太多,您被挤出来了,稍等重试");
return msg;
}
}
2.4、Hystrix降级策略和调整超时时间
第一种注解形式设置commandProperties 属性(不友好)
@RequestMapping("save")
@HystrixCommand(fallbackMethod = "saveOrderFail", commandProperties = {
@HystrixProperty(name = "", value = "")
})
public Object save(@RequestParam("user_id") int userId, @RequestParam("product_id") int productId, HttpServletRequest request) {
Map<String, Object> data = new HashMap<>();
data.put("code", 0);
data.put("data", productOrderService.save(userId, productId));
return data;
}
第二种配置
这里是Hystrix的超时
#熔断器Hystrix超时时间调整
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 1000
#是否开启超时限制 (一定不要禁用)
hystrix:
command:
default:
execution:
timeout:
enabled: false
3、断路器Dashboard监控仪表盘
第一步,加入依赖。
<!--仪表盘监控依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
第二步,启动类型加上注解。
@EnableHystrixDashboard
第三步,配置文件增加endpoint。
#断路器Dashboard监控仪表盘(*代表暴露全部的监控信息)
management:
endpoints:
web:
exposure:
include: "*"
第四步,访问入口。
http://localhost:8781/hystrix
Hystrix Dashboard输入: http://localhost:8781/actuator/hystrix.stream
4、微服务网关zuul
拦截作用
什么是网关
API Gateway,是系统的唯一对外的入口,介于客户端和服务器端之间的中间层,处理非业务功能 提供路由请求、鉴权、监控、缓存、限流等功能
统一接入
智能路由
AB测试、灰度测试
负载均衡、容灾处理
日志埋点(类似Nignx日志)
流量监控
限流处理
服务降级
安全防护
鉴权处理
监控
机器网络隔离
主流的网关
zuul:是Netflix开源的微服务网关,和Eureka,Ribbon,Hystrix等组件配合使用,Zuul 2.0比1.0的性能提高很多
kong: 由Mashape公司开源的,基于Nginx的API gateway
nginx+lua:是一个高性能的HTTP和反向代理服务器,lua是脚本语言,让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
启动类加入注解 @EnableZuulProxy
4.1、基本配置
server:
port: 9000
#服务的名称
spring:
application:
name: api-gateway
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
默认访问规则
http://gateway:port/service-id/**
不使用网关访问
http://localhost:8781/api/v1/order/save?product_id=6&user_id=2
使用网关访问(网关端口号加服务)
http://localhost:9000/order-service/api/v1/order/save?product_id=6&user_id=2
4.2、自定义路由映射
给单独给服务配置
#自定义路由映射
zuul:
routes:
order-service: /apigateway/**
http://localhost:9000/order-service/api/v1/order/save?product_id=6&user_id=2
也可以
http://localhost:9000/apigateway/api/v1/order/save?product_id=6&user_id=2
4.3、忽略整个服务对外提供接口
拒绝访问商品服务
例如
http://localhost:9000/product-service/api/v1/product/find?id=1
server:
port: 9000
#服务的名称
spring:
application:
name: api-gateway
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#/order-service/api/v1/order/save?user_id=2&product_id=1
#自定义路由映射
zuul:
routes:
order-service: /apigateway/**
#忽略整个服务,对外提供接口
ignored-services: product-service
4.4、通过正则的方式忽略整个服务对外提供接口
server:
port: 9000
#服务的名称
spring:
application:
name: api-gateway
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#/order-service/api/v1/order/save?user_id=2&product_id=1
#自定义路由映射
zuul:
routes:
product-service: /apigateway1/**
order-service: /apigateway2/**
#统一入口为上面的配置,其他入口忽略
ignored-patterns: /*-service/**
#忽略整个服务,对外提供接口
#ignored-services: product-service
4.5、处理http请求头为空的问题
#自定义路由映射
zuul:
#处理http请求头为空的问题(如Cookies)
sensitive-headers:
其他笔记
过滤器执行顺序问题 ,过滤器的order值越小,越先执行
共享RequestContext,上下文对象
4.6、自定义Zuul过滤器实现登录鉴权
zuul流程
1、新建一个filter包
2、新建一个类,实现ZuulFilter,重写里面的方法
3、在类顶部加注解,@Component,让Spring扫描
核心代码
package com.yuange.apigateway.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* @author lichangyuan
* @create 2021-04-25 9:47
*/
@Component
public class LoginFilter extends ZuulFilter {
/**
* 过滤器类型前置通知
*
* @return
*/
@Override
public String filterType() {
return PRE_TYPE;
}
/**
* 过滤器顺序,越小越先执行
*
* @return
*/
@Override
public int filterOrder() {
return 4;
}
/**
* 过滤器是否生效
*
* @return
*/
@Override
public boolean shouldFilter() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
// System.out.println(request.getRequestURI());// /apigateway/product/api/v1/product/list
// System.out.println(request.getRequestURL());// http://localhost:9000/apigateway/product/api/v1/product/list
//ACL,一些权限校验逻辑
//如果返回true则进行拦截,执行业务逻辑
if ("/apigateway/order/api/v1/order/save".equalsIgnoreCase(request.getRequestURI())) {
return true;
} else if ("/apigateway/order/api/v1/order/list".equalsIgnoreCase(request.getRequestURI())) {
return true;
} else if ("/apigateway/order/api/v1/order/find".equalsIgnoreCase(request.getRequestURI())) {
return true;
}
return false;
}
/**
* 业务逻辑
*
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
//JWT
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
//token对象
String token = request.getHeader("token");
//如果token为空,则从参数中查找
if (StringUtils.isBlank((token))) {
token = request.getParameter("token");
}
//登录校验逻辑 根据公司情况自定义 JWT
if (StringUtils.isBlank(token)) {
//这个请求最终不会被zuul转发到后端服务器
requestContext.setSendZuulResponse(false);
//设置响应信息 HttpStatus.UNAUTHORIZED.value() 为401未经授权
requestContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
}
return null;
}
}
当不携带token时
当携带token时,则能正常访问。
4.7、高并发下接口限流
1、nginx层限流
2、网关层限流
package com.yuange.apigateway.filter;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.http.HttpStatus;
import javax.servlet.http.HttpServletRequest;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
/**
* 只给订单接口限流
*/
public class OederRateLimiterFilter extends ZuulFilter {
//每秒产生1000个令牌
private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
//默认最小-3
return -4;
}
@Override
public boolean shouldFilter() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest request = currentContext.getRequest();
//只对订单接口做限流
if ("/apigateway/order/api/v1/order/save".equalsIgnoreCase(request.getRequestURI())) {
return true;
}
return false;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
//如果没有拿到令牌
if (!RATE_LIMITER.tryAcquire()) {
//这个请求最终不会被zuul转发到后端服务器
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
}
return null;
}
}
4.8、Zuul微服务网关集群搭建
5、分布式链路追踪系统Sleuth和ZipKin
5.1、使用Sleuth
Sleuth是一个组件,专门用于记录链路数据的开源组件
加入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
自定义日志(由于第一次测试有,第二次测试没有说明日志级别问题)
核心代码
import com.yuange.product_server.domain.Product;
import com.yuange.product_server.service.ProductService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
/**
* @author lichangyuan
* @create 2021-04-19 14:57
*/
@Service
public class ProductServiceImpl implements ProductService {
private final Logger logger= LoggerFactory.getLogger(getClass());
@Override
public Product findById(int id) {
//打印日志
logger.info("service findById product");
return null;
}
}
[order-service,96f95a0dd81fe3ab,852ef4cfcdecabf3,false]
1、第一个值,spring.application.name的值
2、第二个值,96f95a0dd81fe3ab ,sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID
3、第三个值,852ef4cfcdecabf3、spanid 基本的工作单元,获取元数据,如发送一个http
4、第四个值:false,是否要将该信息输出到zipkin服务中来收集和展示。
5.2、可视化链路追踪系统ZipKin部署
大规模分布式系统的APM工具(Application Performance Management),基于Google Dapper的基础实现,和sleuth结合可以提供可视化web界面分析调用链路耗时情况
知识拓展:OpenTracing
OpenTracing 已进入 CNCF,正在为全球的分布式追踪,提供统一的概念和数据标准。
通过提供平台无关、厂商无关的 API,使得开发人员能够方便的添加(或更换)追踪系统的实现。
使用docker安装
docker run -d -p 9411:9411 openzipkin/zipkin
可视化web界面
http://192.168.8.128:9411/zipkin/
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>
此依赖包含了两个依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-sleuth-zipkin</artifactId>
</dependency>
配置
#服务的名称
spring:
application:
name: order-service
redis:
database: 0
host: 127.0.0.1
port: 6379
timeout: 2000
#zipkin服务所在地址
zipkin:
base-url: http://192.168.8.128:9411/
#配置采样百分比,开发环境可以设置为1,表示全部,生产就用默认
sleuth:
sampler:
probability: 1
6、微服务核心知识分布式配置中心Config
6.1、服务端
统一管理配置, 快速切换各个环境的配置
导入依赖和注册中心
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
启动类增加注解
@EnableConfigServer
配置
#服务的名称
spring:
application:
name: config-server
#git配置
cloud:
config:
server:
git:
uri: https://gitee.com/lichangyuan/config_cloud
username: 15888488307
password: l15888488307cy
#超时时间
timeout: 5
#分支
default-label: master
server:
port: 9100
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
访问路径(一定要有‘-’,根据前缀匹配)
http://localhost:9100/product-service.yml
访问方式(一定要注意语法,如果有问题,会出错)
多种访问路径,可以通过启动日志去查看
例子 http://localhost:9100/product-service.yml
/{name}-{profiles}.properties
/{name}-{profiles}.yml
/{name}-{profiles}.json
/{label}/{name}-{profiles}.yml
name 服务器名称
profile 环境名称,开发、测试、生产
lable 仓库分支、默认master分支
创建两个环境,两个分支。
server:
port: 8771
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service
#zipkin服务所在地址
zipkin:
base-url: http://192.168.8.128:9411/
#配置采样百分比,开发环境可以设置为1,表示全部,生产就用默认
sleuth:
sampler:
probability: 1
#这个为环境
env: dev
#这个为分支
branch: master
http://localhost:9100/master/product-service-dev.yml
6.2、客户端
依赖
<!--配置中心依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
配置,要把配置文件重命名为bootstrap.yml
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service
#指定从哪个配置中心读取
cloud:
config:
discovery:
service-id: CONFIG-SERVER
#开发发现的功能默认为false
enabled: true
#指定环境
profile: test
#建议用lable去区分环境,默认是lable是master分支
#label: test
最终使用的master分支test环境(默认lable由服务端决定)
生产环境部署常见问题,配置中心访问路径变化
#指定注册中心地址
eureka:
client:
serviceUrl:
#一般注册中心和配置中心不会在同一台服务器,而且不会对外公布,使用内网ip
defaultZone: http://localhost:8761/eureka/
instance:
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true
7、微服务消息总线Bus结合消息队列RabbitMQ
1、什么是消息
一个事件,需要广播或者单独传递给某个接口
2、为什么使用这个
配置更新了,但是其他系统不知道是否更新
安装步骤
1)拉取镜像:docker pull rabbitmq:management
2)查看当前镜像列表:docker images
3)删除指定镜像:docker rmi IMAGE_ID (如果需要强制删除加 -f)
4)创建容器
docker run -d --name="myrabbitmq" -p 5671:5671 -p 15672:15672 rabbitmq:management
-d: 后台运行容器,并返回容器ID
-p: 端口映射,格式为:主机(宿主)端口:容器端口
--name="rabbitmq": 为容器指定一个名称
RabbitMQ默认创建了一个 guest 用户,密码也是 guest, 如果访问不了记得查看防火墙,端口或者云服务器的安全组
管理后台:http://127.0.0.1:15672
服务器启动
5672是项目中连接rabbitmq的端口(我这里映射的是5672),15672是rabbitmq的web管理界面端口(我映射为15672)
rabbitmq: docker run -d -p 5672:5672 -p 15672:15672 rabbitmq:management
rabbitmq默认是5672,所以改为5672端口
导入依赖
<!--配置中心结合消息队列-->
<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>
各个服务的配置文件
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
#暴露全部的监控信息
management:
endpoints:
web:
exposure:
include: "*"
通过修改git上的配置再通过手动触发钩子函数(post方式访问)
http://localhost:8771/actuator/bus-refresh
会刷新服务配置(达到应用不重启,动态更新配置)
7.1、实战案例
git里面新增对应项目的配置文件,都要添加下面的配置
#服务的名称
spring:
rabbitmq:
host: 192.168.8.128
port: 5672
username: guest
password: guest
#暴露全部的监控信息
management:
endpoints:
web:
exposure:
include: "*"
依赖
<!--配置中心依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-client</artifactId>
</dependency>
<!--配置中心结合消息队列-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--Bus 消息总线的依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
修改application.properties为bootstrap.yml 并拷贝配置文件
#指定注册中心地址
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: order-service
#指定从哪个配置中心读取
cloud:
config:
discovery:
service-id: CONFIG-SERVER
enabled: true
#指定环境
profile: test
启动顺序从①注册中心到②配置中心到③对应的服务到④启动网关
8、安装Docker仓库
1、什么是Dokcer
百科:一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口;
使用go语言编写,在LCX(linux容器)基础上进行的封装
简单来说:
1)就是可以快速部署启动应用
2)实现虚拟化,完整资源隔离
3)一次编写,四处运行(有一定的限制,比如Docker是基于Linux 64bit的,无法在32bit的linux/Windows/unix环境下使用)
2、为什么要用
1、提供一次性的环境,假如需要安装Mysql,则需要安装很多依赖库、版本等,如果使用Docker则通过镜像就可以直接启动运行
2、快速动态扩容,使用docker部署了一个应用,可以制作成镜像,然后通过Dokcer快速启动
3、组建微服务架构,可以在一个机器上模拟出多个微服务,启动多个应用
4、更好的资源隔离和共享
一句话:开箱即用,快速部署,可移植性强,环境隔离
systemctl start docker #运行Docker守护进程
systemctl stop docker #停止Docker守护进程
systemctl restart docker #重启Docker守护进程
systemctl enable docker #设置Docker开机自启动
8.1、快速掌握Dokcer基础知识
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
1、概念:
Docker 镜像 - Docker images:
容器运行时的只读模板,操作系统+软件运行环境+用户程序
class User{
private String userName;
private int age;
}
Docker 容器 - Docker containers:
容器包含了某个应用运行所需要的全部环境
User user = new User()
Docker 仓库 - Docker registeries:
用来保存镜像,有公有和私有仓库,好比Maven的中央仓库和本地私服
镜像仓库:
(参考)配置国内镜像仓库:https://blog.csdn.net/zzy1078689276/article/details/77371782
对比面向对象的方式
Dokcer 里面的镜像 : Java里面的类 Class
Docker 里面的容器 : Java里面的对象 Object
通过类创建对象,通过镜像创建容器
4、Docker容器常见命令实战
简介:讲解Docker在云服务上的实际应用
1、 常用命令(安装部署好Dokcer后,执行的命令是docker开头),xxx是镜像名称
搜索镜像:docker search xxx
列出当前系统存在的镜像:docker images
拉取镜像:docker pull xxx
xxx是具体某个镜像名称(格式 REPOSITORY:TAG)
REPOSITORY:表示镜像的仓库源,TAG:镜像的标签
运行一个容器:docker run -d --name "xdclass_mq" -p 5672:5672 -p 15672:15672 rabbitmq:management
docker run - 运行一个容器
-d 后台运行
-p 端口映射
rabbitmq:management (格式 REPOSITORY:TAG),如果不指定tag,默认使用最新的
--name "xxx"
列举当前运行的容器:docker ps
检查容器内部信息:docker inspect 容器名称
删除镜像:docker rmi IMAGE_NAME
强制移除镜像不管是否有容器使用该镜像 增加 -f 参数,
停止某个容器:docker stop 容器名称
启动某个容器:docker start 容器名称
移除某个容器: docker rm 容器名称 (容器必须是停止状态)
9、SpringCloud和Docker整合部署
9.1、构建springboot应用
添加配置pom.xml
<properties>
<!--变量前缀-->
<docker.image.prefix>yuange</docker.image.prefix>
</properties>
<build>
<!--项目打包的名称-->
<finalName>docker-demo</finalName>
<plugins>
<!--一个插件,用于打包springboot应用成docker镜像-->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>dockerfile-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<!--指定打包成的镜像名称-->
<repository>${docker.image.prefix}/${project.artifactId}</repository>
<buildArgs>
<JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
</buildArgs>
</configuration>
</plugin>
</plugins>
</build>
配置讲解
Spotify 的 docker-maven-plugin 插件是用maven插件方式构建docker镜像的。
${project.build.finalName} 产出物名称,缺省为${project.artifactId}-${project.version}
9.2、打包SpringCloud镜像并上传私有仓库并部署
创建Dockerfile
FROM java:8
VOLUME /tmp
COPY *.jar app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
构建镜像(window的操作)
1.将springboot项目打成jar包
2.创建一个Dockerfile文件
3.将jar包和Dockerfile文件放到同一目录中
4.打成镜像(结尾有个点):
docker build -t docker-demo .
5.运行镜像
docker run -d -p 8080:8080 docker-demo
构建镜像(linux的操作)
mvn install dockerfile:build
10、其他
10.1、阿里云依赖
<repositories>
<repository>
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<layout>default</layout>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<enabled>true</enabled>
</releases>
</repository>
</repositories>
10.2、虚拟机环境开启
安装docker
打开docker
systemctl start docker
安装打开docker的rabbitmq
docker pull rabbitmq:management
docker run -d --name="myrabbitmq" -p 5671:5671 -p 15672:15672 rabbitmq:management
安装打开docker的zipkin
docker pull openzipkin/zipkin
docker run -d -p 9411:9411 openzipkin/zipkin
安装打开docker的nginx
docker pull nginx:latest
docker run --name nginx-test -p 8080:80 -d nginx
安装打开docker的MySQL(MYSQL_ROOT_PASSWORD=123456:设置 MySQL 服务 root 用户的密码。)
docker pull mysql:latest
docker run -itd --name mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 mysql
进入容器
docker exec -it 62349aa31687 /bin/bash
安装打开docker的redis(接着我们通过 redis-cli 连接测试使用 redis 服务)
命令说明:
redis-server --appendonly yes : 在容器执行redis-server启动命令,并打开redis持久化配置
docker pull redis:latest
docker run -d --name "yuange" -p 6379:6379 redis
docker run -itd --name redis-test -p 6379:6379 redis
访问redis容器里面,进行操作
docker exec -it 295058d2b92e redis-cli
#常用启动方式
docker run -d --name "yuange" -p 6379:6379 redis redis-server --appendonly yes
-d 后台运行镜像
-p 10240:8080 将镜像的8080端口映射到服务器的10240端口。
-p 10241:50000 将镜像的50000端口映射到服务器的10241端口
-v /var/jenkins_mount:/var/jenkins_mount /var/jenkins_home目录为容器jenkins工作目录,我们将硬盘上的一个目录挂载到这个位置,方便后续更新镜像后继续使用原来的工作目录。这里我们设置的就是上面我们创建的 /var/jenkins_mount目录
-v /etc/localtime:/etc/localtime让容器使用和服务器同样的时间设置。
–name myjenkins 给容器起一个别名
docker pull jenkins/jenkins
mkdir -p /var/jenkins_mount
chmod 777 /var/jenkins_mount
运行
docker run -d -p 10240:8080 -p 10241:50000 -v /var/jenkins_mount:/var/jenkins_home -v /etc/localtime:/etc/localtime --name myjenkins jenkins/jenkins
docker常用命令
docker ps [OPTIONS]
-a :显示所有的容器,包括未运行的。
docker start :启动一个或多个已经被停止的容器
docker stop :停止一个运行中的容器
docker restart :重启容器
强制删除容器 db01、db02:
docker rm -f db01 db02
删除所有已经停止的容器:
docker rm $(docker ps -a -q)
使用镜像nginx:latest以后台模式启动一个容器,并将容器的latest端口映射到主机的nginx端口。
docker run -P -d nginx:latest
10.3、生产环境部署常见问题,配置中心访问路径变化
#指定注册中心地址
eureka:
client:
serviceUrl:
#一般注册中心和配置中心不会在同一台服务器,而且不会对外公布,使用内网ip
defaultZone: http://localhost:8761/eureka/
instance:
instance-id: ${spring.cloud.client.ip-address}:${server.port}
prefer-ip-address: true