1. 微服务架构概念
概念:
- 对比单体架构:
- 单体架构开发速度慢,启动时间长,依赖庞大。
- 微服务架构易开发,理解和维护,独立的部署和启动,但存在分布式事务问题,服务治理问题。
- CAP理论:在一个分布式系统中,分区容错性必须满足,而一致性和可用性二选其一,无法共存:
- 分区容错性
Partition-tolerance:分布式系统必须保证分区容错性,即使某节点崩溃,整体也要正常对外提供服务,节点越多,分区容错越高。 - 一致性
Consistency:在响应结果之前先进行节点间完整的数据同步过程,即可保证数据一致性,但同步过程很耗时,可能会因为响应超时而报错(不满足A)。 - 可用性
Availability:异步执行节点之间的数据同步过程,以保证响应超时,此时服务高可用,但可能会因为来不及同步而导致响应的数据和其他节点不一致(不满足C)。
- 分区容错性
- 微服务核心概念:
- 网关
GATWAY:用于过滤掉所有不合法的路由以及转发路由。 - 配置中心:存放每个微服务的主配文件,支持动态更新,支持可视化界面,支持同时操作所有服务的配置文件。
- 链路追踪:分析调用链路节点耗时的一门技术:
- 如用户下单的链路:调用商品服务获取商品价格 -> 调用用户服务 -> 调用订单服务...,通过链路追踪分析链路上的哪个过程耗时比较久,以进行精准优化。
- 负载均衡:进行负载分发,如果集群中的A节点压力过大,则将需求分发到集群中的B节点。
- 熔断:保护自己,如果链路中的某个节点开了熔断保护(一般是不影响大局的节点才选择开启熔断保护),那么假设这个节点10次调用9次返回失败,,熔断机制会立即放弃这个节点,以保证整条链路的流畅性和速度,然后过一段时间后再重试这个节点。
- 网关
- 常见微服务框架:需要熟知consumer消费者,provider生产者的两种角色的概念:
- dubbo:zookeeper + dubbo + springboot
- dubbo官网
- 通信方式:rpc(远程方法调用)
- 注册中心:zookeeper/redis
- 配置中心:diamond(一款款阿里开源组件)
- 优点:采用rpc连接,速度快一些。
- 缺点:框架中的技术模块比较散,需要自己组装。
- springcloud:springcloud官网
- 通信方式:http长连接,restful
- 注册中心:eureka/consul
- 配置中心:config
- 断路器:hystrix
- 网关:zuul
- 分布式链路追踪系统:sleuth + zipkin
- 优点:框架中的技术模块是全家桶套装,整合度非常高。
- 缺点:http长连接,涉及多次握手,速度低一些。
- dubbo:zookeeper + dubbo + springboot
2. 注册中心Eureka
概念: 注册中心用表格的方式来管理和维护微服务集群,以解耦微服务之间的调用过程:
- 注册中心注册发现流程:
- A服务启动,向注册中心进行注册登记自己的节点网络信息,如IP,端口等,方法路由信息等。
- B服务启动,向注册中心进行注册登记自己的节点网络信息,如IP,端口等,方法路由信息等。
- 每个服务节点都定时向注册中心发送心跳检测,若长时间未发送,则表示该节点已挂。
- A服务向注册中心发送"调用B服务"的请求。
- 注册中心找到B服务,并将其网络信息返回给A服务。
- A服务调用B服务。
- 主流注册中心:zookeeper,eureka,consul等:
- zookeeper:CP设计,有主从结构,若主节点挂了,会进行内部投票选举新的主节点,此时不能对外提供服务,且如果半数以上节点不可用时,也无法对外提供服务,可见它无法满足可用性A,一般部分金融行业会选择。
- eureka:AP原则,无主从节点,一个节点挂了,会自动切换另一个节点,去中心化,以效率为主,但不保证数据一致性,一般部分电商系统会选择。
2.1 创建eureka服务端
概念: Eureka 是springcloud核心组件,Netflix旗下产品,2.x版本已闭源:
- EurekaServer:eureka服务端维护一个微服务集群列表。
- EurekaClient:每个微服务都会嵌入一个eureka客户端(Jar包),用于跟eureka服务端交互。
流程: 新建项目 springcloud2-eureka-server:
- 勾选依赖:
DevTools/Lombok/Web/Eureka Server。 - 启动类添加注解
@EnableEurekaServer以启用eureka。 - 主配添加:
server.port=8761:配置eureka服务端端口,默认8761。eureka.instance.hostname=localhost:配置eureka服务端IP。eureka.client.registerWithEureka=false:自己就是注册中心,不用注册自己。eureka.client.fetchRegistry=false:不去注册中心获取其他服务的地址。eureka.client.serviceUrl.defaultZone:配置微服务列表空间,可使用${}表达式动态获取值:http://${eureka.instance.hostname}:${server.port}/eureka/
- 启动项目并访问eureka服务端界面:
- cli:
http://localhost:8761/
- cli:
配置: /springcloud-eureka-server/
- pom:
pom.xml
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- yml:
application.yml
server:
port: 8761
eureka:
instance:
hostname: localhost #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
2.2 创建eureka客户端
流程: 新建项目 springcloud2-product-service 作为客户端:
- 勾选依赖:
DevTools/Lombok/Web/Eureka Discovery Client。 - 启动类添加注解
@EnableEurekaServer以启用eureka。 - 主配添加:
server.port=8770:配置商品微服务端口。eureka.instance.hostname=localhost:配置商品微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.application.name=product-service:给商品微服务起名。
- 开发控制类
controller.ProductController:- 开发商品全查控制方法和按主键查询商品的控制方法,数据模拟即可。
- 启动项目,观察eureka服务端界面的
Instances currently registered with Eureka信息:- cli:
http://localhost:8770/api/v1/product/select-all - cli:
http://localhost:8770/api/v1/product/find-by-id?id=1
- cli:
配置: /springcloud-product-server/
- pom :
pom.xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 配置:
application.yml
#给客户端起名
spring:
application:
name: product-service
#配置eureka客户端端口
server:
port: 8770
eureka:
#配置eureka客户端IP
instance:
hostname: localhost
#配置对应的eureka服务端地址
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
- src
ProductController
/**
* @author yap
*/
@RestController
@RequestMapping("/api/v1/product")
public class ProductController {
@RequestMapping("/select-all")
public Set<String> selectAll() {
Set<String> result = new HashSet<>();
result.add("手机");
result.add("电脑");
return result;
}
@RequestMapping("/select-by-id")
public String selectById(int id) {
return id == 1 ? "001-手机" : "002-电脑";
}
}
3.1 使用ribbon远程通信
流程: 新建项目 springcloud2-order-service-ribbon 作为客户端:
- 勾选依赖:
DevTools/Lombok/Web/Eureka Discovery Client - 启动类添加注解
@EnableDiscoveryClient以启用eureka。 - 主配添加:
server.port=8771:配置订单微服务端口。eureka.instance.hostname=localhost:配置订单微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.application.name=order-service-ribbon:给订单微服务起名。
- 开发配置类
config.RestTemplateConfig:- IOC
RestTemplate并额外标记@LoadBalanced以启用负载均衡:- 如果每个机器的硬件配置一样,则建议使用默认负载均衡策略。
- 如果多台机器的硬件配置不一样,则可将负载均衡策略改
WeightedResponseTimeRule。
- IOC
- 开发控制类
controller.OrderController:- 注入
RestTemplate类。 - 开发控制方法
insert():远程调用商品微服务的selectById()控制方法,其余订单数据模拟。
- 注入
- 启动项目,观察eureka服务端界面的
Instances currently registered with Eureka信息:- cli:
http://localhost:8771/api/v1/order/insert?product-id=1配置: /springcloud2-order-service-ribbon/
- cli:
- 配置:
application.yml
server:
port: 8771
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: order-service-ribbon
- src:
config
package com.yap.springcloud2orderserviceribbon.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author yap
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- src:
controller
package com.yap.springcloud2orderserviceribbon.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.*;
/**
* @author yap
*/
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
private RestTemplate restTemplate;
@Autowired
public OrderController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@RequestMapping("insert")
public Map<String, Object> insert(@RequestParam("product-id") String productId) {
String url = "http://product-service/api/v1/product/select-by-id?id=" + productId;
String product = restTemplate.getForObject(url, String.class);
Map<String, Object> order = new HashMap<>(3);
order.put("订单号", UUID.randomUUID().toString());
order.put("订单时间", new Date());
order.put("商品", product);
return order;
}
}
3.2 使用feign远程通信
概念: Feign 是一个伪RPC客户端,其本质仍是http调用,默认集成了ribbon,采用注解方式进行配置,配置熔断等方式方便,写起来更加思路清晰和方便。
流程: 新建项目 springcloud2-order-service-feign
- 勾选依赖:
DevTools/Lombok/Web/Eureka Discovery Client - 添加依赖:
spring-cloud-starter-openfeign - 主配添加:
server.port=8772:配置订单微服务端口。eureka.instance.hostname=localhost:配置订单微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.application.name=order-service-feign:给订单微服务起名。
- 启动类添加
@EnableFeignClients以启用Feign功能。 - 开发远程调用接口
ProductFeign.java对应商品微服务中的ProductController类:- 接口标记注解
@FeignClient(name = "product-service"):指定对应的微服务名。 - 仿写商品微服务中的控制方法:要求方法路由,方法签名,方法形参和请求方式等必须全部一致。
- 参数必须使用
@RequestParam()进行绑定。 - 不建议将
@RequestMapping提取到类上。
- 接口标记注解
- 开发控制类
controller.OrderController:- 注入
ProductFeign类。 - 开发控制方法
insert():直接调用ProductFeign接口中的selectById(),其余订单数据模拟。
- 注入
- 启动项目,观察eureka服务端界面的
Instances currently registered with Eureka信息:- cli:
http://localhost:8772/api/v1/order/insert?product-id=1配置: /springcloud2-order-service-feign/
- cli:
- pom:
pom.xml
<!--spring-cloud-starter-openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 配置:
application.yml
server:
port: 8772
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: order-service-feign
- src:
feign
package com.yap.springcloud2orderservicefeign.feign;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Set;
/**
* @author yap
*/
@FeignClient(name = "product-service")
public interface ProductFeign {
/**
* 查询全部商品
* @return 返回全部商品
*/
@RequestMapping("/api/v1/product/select-all")
Set<String> selectAll();
/**
* 根据主键查询商品信息
* @param id 主键
* @return 对应主键的商品信息
*/
@RequestMapping("/api/v1/product/select-by-id")
String selectById(@RequestParam("id") int id);
}
- src:
controller
package com.yap.springcloud2orderservicefeign.controller;
import com.yap.springcloud2orderservicefeign.feign.ProductFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author yap
*/
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
private ProductFeign productFeign;
@Autowired
public OrderController(ProductFeign productFeign){
this.productFeign = productFeign;
}
@RequestMapping("insert")
public Map<String, Object> insert(@RequestParam("product-id") int productId) {
String product = productFeign.selectById(productId);
Map<String, Object> order = new HashMap<>(3);
order.put("订单号", UUID.randomUUID().toString());
order.put("订单时间", new Date());
order.put("商品", product);
return order;
}
}
4. 熔断降级Hystrix
概念: Hystrix 豪猪断路器提供了当分布式系统发生高负载,高流量或网络异常情况下的熔断,隔离,Fallback兜底数据,Cache,监控等功能,以保证系统高可用,一般仅对不影响大局的服务开启熔断保护:
- 熔断:当某个下游服务发生超时,异常等错误时,立刻熔断该服务可以避免整个系统崩溃,过一段时间后再重试这个服务。
- 降级:主动抛弃一些非核心服务以暂时将更多资源分配给核心服务。
4.1 Hystrix配合ribbon
流程: 新建项目 springcloud2-order-service-ribbon-hystrix
- 勾选依赖:
DevTools/Lombok/Web/Eureka Discovery Client - 添加依赖:
spring-cloud-starter-netflix-hystrix - 主配添加:
server.port=8773:配置订单微服务端口。eureka.instance.hostname=localhost:配置订单微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.application.name=order-service-ribbon-hystrix:给订单微服务起名。
- 启动类添加
@EnableHystrix以启用熔断功能。 - 开发控制类
controller.OrderController:- 注入
RestTemplate类。 - 开发控制方法
insert():远程调用商品微服务的selectById()控制方法,其余订单数据模拟。 - 在控制方法
insert()上标记@HystrixCommand(fallbackMethod = "insertFall"),指定降级方法。 - 开发降级方法
insertFall():要求参数和返回值必须和控制方法一致,返回兜底数据,开发人员告警。
- 注入
- 测试:
- cli:
http://localhost:8773/api/v1/order/insert?product-id=1 - 停止商品服务以触发熔断,测试是否返回兜底数据。
- 重新启动商品服务,等一阵子,测试是否恢复功能。 源码:
- cli:
- pom:
pom.xml
<!--spring-cloud-starter-netflix-hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
<!--自定义版本-->
<version>2.2.6.RELEASE</version>
</dependency>
- 配置:
application.yml
server:
port: 8773
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: order-service-ribbon-hystrix
- src:
config
package com.yap.springcloud2orderserviceribbonhystrix.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* @author yap
*/
@Configuration
public class RestTemplateConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
- src:
controller
package com.yap.springcloud2orderserviceribbonhystrix.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author yap
*/
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
private RestTemplate restTemplate;
@Autowired
public OrderController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@HystrixCommand(fallbackMethod = "insertFall")
@RequestMapping("insert")
public Map<String, Object> insert(@RequestParam("product-id") String productId) {
String url = "http://product-service/api/v1/product/select-by-id?id=" + productId;
String product = restTemplate.getForObject(url, String.class);
Map<String, Object> order = new HashMap<>(3);
order.put("订单号", UUID.randomUUID().toString());
order.put("订单时间", new Date());
order.put("商品", product);
return order;
}
public Map<String, Object> insertFall(@RequestParam("product-id") String productId) {
Map<String, Object> order = new HashMap<>(3);
order.put("订单号", UUID.randomUUID().toString());
order.put("订单时间", new Date());
order.put("商品", "兜底数据");
System.out.println("发短信...");
return order;
}
}
4.2 Hystrix配合feign
流程: 新建项目 springcloud2-order-service-feign-hystrix
- springboot版本降为
2.3.9.RELEASE,springcloud版本降为Hoxton.SR10。 - 勾选依赖:
DevTools/Lombok/Web/Eureka Discovery Client/Hystrix[Maintenance] - 添加依赖:
spring-cloud-starter-openfeign - 主配添加:
server.port=8774:配置订单微服务端口。eureka.instance.hostname=localhost:配置订单微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.application.name=order-service-feign-hystrix:给订单微服务起名。feign.client.hystrix.enabled=true:启用熔断机制。feign.client.config.default.connectTimeout=2000:连接超时时间2秒。feign.client.config.default.readTimeout=2000:操作超时时间2秒。
- 启动类添加
@EnableHystrix以启用熔断功能。 - 启动类添加
@EnableFeignClients以启用Feign功能。 - 开发远程调用接口
ProductFeign对应商品微服务中的ProductController类:- 在
@FeignClient()中添加fallback,值为自定义降级类。- @FeignClient(name = "product-service", fallback = ProductFallBack.class)
- 在
- 创建降级类
ProcuctFallBack:- 实现
ProductFeign接口。 - 标记
@Component以被spring管理。
- 实现
- 开发控制类
controller.OrderController:- 注入
ProductFeign类。 - 开发控制方法
insert():远程调用商品微服务的selectById()控制方法,其余订单数据模拟。
- 注入
- 测试:
- cli:
http://localhost:8774/api/v1/order/insert?product-id=1 - 停止商品服务以触发熔断,测试是否返回兜底数据。
- 重新启动商品服务,等一阵子,测试是否恢复功能。 源码:
- cli:
- pom:
pom.xml
<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-hystrix</artifactId>
</dependency>
<!--spring-cloud-starter-openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
- 配置:
application.yml
server:
port: 8774
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: order-service-feign-hystrix
feign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 2000
hystrix:
enabled: true
- src:
ProductFeign
package com.yap.springcloud2orderservicefeignhystrix.feign;
import com.yap.springcloud2orderservicefeignhystrix.fallback.ProductFallBack;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Set;
/**
* @author yap
*/
@Qualifier("productFeign")
@FeignClient(name = "product-service", fallback = ProductFallBack.class)
public interface ProductFeign {
/**
* 查询全部商品
*
* @return 返回全部商品
*/
@RequestMapping("/api/v1/product/select-all")
Set<String> selectAll();
/**
* 根据主键查询商品信息
*
* @param id 主键
* @return 对应主键的商品信息
*/
@RequestMapping("/api/v1/product/select-by-id")
String selectById(@RequestParam("id") int id);
}
- src:
ProcuctFallBack
package com.yap.springcloud2orderservicefeignhystrix.fallback;
import com.yap.springcloud2orderservicefeignhystrix.feign.ProductFeign;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* @author yap
*/
@Component
public class ProductFallBack implements ProductFeign {
@Override
public Set<String> selectAll() {
Set<String> result = new HashSet<>();
result.add("熔断");
System.out.println("告警...");
return result;
}
@Override
public String selectById(int id) {
System.out.println("告警...");
return "熔断";
}
}
- src:
controller
package com.yap.springcloud2orderservicefeignhystrix.controller;
import com.yap.springcloud2orderservicefeignhystrix.feign.ProductFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author yap
*/
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
private ProductFeign productFeign;
@Autowired
public OrderController(@Qualifier("productFeign") ProductFeign productFeign){
this.productFeign = productFeign;
}
@RequestMapping("insert")
public Map<String, Object> insert(@RequestParam("product-id") int productId) {
String product = productFeign.selectById(productId);
Map<String, Object> order = new HashMap<>(3);
order.put("订单号", UUID.randomUUID().toString());
order.put("订单时间", new Date());
order.put("商品", product);
return order;
}
}
Hystrix降级策略和调整
- Hystrix熔断策略有两种:
- THREAD 线程池隔离 (默认)
- SEMAPHORE 信号量,适用于接口并发量高的情况,超过信号量的线程会被拒绝。
配置:设置策略(无提示)
# 设置hystrix策略为thread,且超时时间为4秒,默认1000毫秒
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 4000
# 设置hystrix策略为semaphore,且最大信号量为50,默认为10
hystrix:
command:
default:
execution:
isolation:
semaphore:
maxConcurrentRequests: 50
4.3 Dashboard监控仪表
概念: Dashboard 监控仪表盘通过SSE推送技术来监控服务请求状态:
流程: 新建项目 springcloud2-order-service-dashboard
- 勾选依赖:
DevTools/Lombok/Web/Eureka Discovery Client/OpenFeign/Hystrix[Maintenance] - 添加依赖:
spring-cloud-starter-netflix-hystrix-dashboardspring-boot-starter-actuator
- 启动类添加:
@EnableHystrixDashboard以启用监控仪表盘功能。@EnableFeignClients以启用Feign远程连接功能。@EnableHystrix以启用熔断功能。
- 主配添加:
server.port=8775:配置订单微服务端口。eureka.instance.hostname=localhost:配置订单微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.application.name=order-service-dashboard:给订单微服务起名。feign.client.hystrix.enabled=true:启用熔断机制。feign.client.config.default.connectTimeout=2000:连接超时时间2秒。feign.client.config.default.readTimeout=2000:操作超时时间2秒。hystrix.dashboard.proxy-stream-allow-list=localhost:设置dashboard监控IP地址为localhost。management.endpoints.web.exposure.include="*":对外暴露全部端点。
- 创建降级类
ProcuctFallBack:- 实现
ProductFeign接口。 - 标记
@Component以被spring管理。
- 实现
- 开发远程调用接口
ProductFeign对应商品微服务中的ProductController类:- 在
@FeignClient()中添加fallback,值为自定义降级类。
- 在
- 开发控制类
controller.OrderController:- 注入
ProductFeign类。 - 开发控制方法
insert():直接调用ProductFeign接口中的selectById()控制方法,其余订单数据模拟。
- 注入
- 测试:
- cli:
http://localhost:8775/hystrix - 在
Hystrix Dashboard栏输入http://localhost:8775/actuator/hystrix.stream - psm:
http://localhost:8775/api/v1/order/insert?product-id=2,使用runner循环发送1K次。 源码:
- cli:
- pom:
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
- 配置:
application.yml
server:
port: 8775
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: order-service-dashboard
feign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 2000
hystrix:
enabled: true
hystrix:
dashboard:
proxy-stream-allow-list: "localhost"
# a
management:
endpoints:
web:
exposure:
include: "*"
- src:
ProductFeign
package com.yap.springcloud2orderservicefeignhystrix.feign;
import com.yap.springcloud2orderservicefeignhystrix.fallback.ProductFallBack;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Set;
/**
* @author yap
*/
@Qualifier("productFeign")
@FeignClient(name = "product-service", fallback = ProductFallBack.class)
public interface ProductFeign {
/**
* 查询全部商品
*
* @return 返回全部商品
*/
@RequestMapping("/api/v1/product/select-all")
Set<String> selectAll();
/**
* 根据主键查询商品信息
*
* @param id 主键
* @return 对应主键的商品信息
*/
@RequestMapping("/api/v1/product/select-by-id")
String selectById(@RequestParam("id") int id);
}
- src:
ProcuctFallBack
package com.yap.springcloud2orderservicefeignhystrix.fallback;
import com.yap.springcloud2orderservicefeignhystrix.feign.ProductFeign;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* @author yap
*/
@Component
public class ProductFallBack implements ProductFeign {
@Override
public Set<String> selectAll() {
Set<String> result = new HashSet<>();
result.add("熔断");
System.out.println("告警...");
return result;
}
@Override
public String selectById(int id) {
System.out.println("告警...");
return "熔断";
}
}
- src:
controller
package com.yap.springcloud2orderservicefeignhystrix.controller;
import com.yap.springcloud2orderservicefeignhystrix.feign.ProductFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author yap
*/
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
private ProductFeign productFeign;
@Autowired
public OrderController(@Qualifier("productFeign") ProductFeign productFeign){
this.productFeign = productFeign;
}
@RequestMapping("insert")
public Map<String, Object> insert(@RequestParam("product-id") int productId) {
String product = productFeign.selectById(productId);
Map<String, Object> order = new HashMap<>(3);
order.put("订单号", UUID.randomUUID().toString());
order.put("订单时间", new Date());
order.put("商品", product);
return order;
}
}
5. 网关zuul
流程: 新建项目 springcloud2-order-service-zuul:
- springboot版本降为
2.3.9.RELEASE,springcloud版本降为Hoxton.SR10。 - 勾选依赖:
DevTools/Lombok/Web/Eureka Discovery Client/OpenFeign/Hystrix[Maintenance] - 启动类添加:
@EnableFeignClients以启用Feign远程连接功能。@EnableHystrix以启用熔断功能。
- 主配添加:
server.port=8776:配置订单微服务端口。eureka.instance.hostname=localhost:配置订单微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.application.name=order-service-zuul:给订单微服务起名。feign.client.hystrix.enabled=true:启用熔断机制。feign.client.config.default.connectTimeout=2000:连接超时时间2秒。feign.client.config.default.readTimeout=2000:操作超时时间2秒。
- 开发降级处理类
fallback.ProductFallBack:- 实现
ProductFeign接口。 - 标记
@Component以被spring管理。 - 重写商品微服务中
ProductController的控制方法,方法体开发降级处理,返回兜底数据。
- 实现
- 开发远程调用接口
feign.ProductFeign对应商品微服务中的ProductController类:- 在
@FeignClient()中添加fallback,值为自定义降级类。
- 在
- 开发控制类
controller.OrderController:- 注入
ProductFeign类。 - 开发控制方法
insert():直接调用ProductFeign接口中的selectById()控制方法,其余订单数据模拟。 - 开发控制方法
getCookie():在形参中注入HttpServletRequest实例,返回cookie内容。
- 注入
- 测试:
- cli:
http://localhost:8776/api/v1/order/insert?product-id=1 - cli:
http://localhost:8776/api/v1/order/get-cookie源码:
- cli:
- 配置:
application.yml
server:
port: 8777
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: order-service-zuul
feign:
client:
config:
default:
connectTimeout: 2000
readTimeout: 2000
hystrix:
enabled: true
- src:
ProductFeign
package com.yap.springcloud2orderservicefeignhystrix.feign;
import com.yap.springcloud2orderservicefeignhystrix.fallback.ProductFallBack;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Set;
/**
* @author yap
*/
@Qualifier("productFeign")
@FeignClient(name = "product-service", fallback = ProductFallBack.class)
public interface ProductFeign {
/**
* 查询全部商品
*
* @return 返回全部商品
*/
@RequestMapping("/api/v1/product/select-all")
Set<String> selectAll();
/**
* 根据主键查询商品信息
*
* @param id 主键
* @return 对应主键的商品信息
*/
@RequestMapping("/api/v1/product/select-by-id")
String selectById(@RequestParam("id") int id);
}
- src:
ProcuctFallBack
package com.yap.springcloud2orderservicefeignhystrix.fallback;
import com.yap.springcloud2orderservicefeignhystrix.feign.ProductFeign;
import org.springframework.stereotype.Component;
import java.util.HashSet;
import java.util.Set;
/**
* @author yap
*/
@Component
public class ProductFallBack implements ProductFeign {
@Override
public Set<String> selectAll() {
Set<String> result = new HashSet<>();
result.add("熔断");
System.out.println("告警...");
return result;
}
@Override
public String selectById(int id) {
System.out.println("告警...");
return "熔断";
}
}
- src:
controller
package com.yap.springcloud2orderservicefeignhystrix.controller;
import com.yap.springcloud2orderservicefeignhystrix.feign.ProductFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @author yap
*/
@RestController
@RequestMapping("/api/v1/order")
public class OrderController {
private ProductFeign productFeign;
@Autowired
public OrderController(@Qualifier("productFeign") ProductFeign productFeign){
this.productFeign = productFeign;
}
@RequestMapping("insert")
public Map<String, Object> insert(@RequestParam("product-id") int productId) {
String product = productFeign.selectById(productId);
Map<String, Object> order = new HashMap<>(3);
order.put("订单号", UUID.randomUUID().toString());
order.put("订单时间", new Date());
order.put("商品", product);
return order;
}
}
流程: 新建项目 springcloud2-zuul
- springboot版本降为
2.3.9.RELEASE,springcloud版本降为Hoxton.SR10。 - 勾选依赖:
Eureka Discovery Client/Zuul - 启动类添加:
@EnableZuulProxy以启用Zuul网关功能。
- 主配添加:
server.port=9000:配置网关微服务的端口号。eureka.instance.hostname=localhost:配置网关微服务的IP地址。spring.application.name=zuul:设置网关微服务名字。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
- 测试:启动服务,网关就开启了,在eureka服务端界面中可以查看到该9000服务,然后通过网关访问订单微服务:
- psm:
localhost:9000/order-service-zuul/api/v1/order/insert?product-id=1 - psm:
localhost:9000/order-service-zuul/api/v1/order/get-cookie:无法通过网关获取请求头信息。 - 开启多个网关节点以部署集群,可以在启动界面的
VM Options中添加-Dserver.port=9001后再启动一个新的网关。 源码:
- psm:
- 配置:
application.yml
server:
port: 9000
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
spring:
application:
name: zuul
隐私
zuul.routes.prefix=/zuul:设置自定义路由前缀。zuul.routes.order-service-zuul=/order/**:为order-service-zuul设置别名以保护隐私,全值需要拼前缀。zuul.routes.product-service=/product/**:为product-service设置别名以保护隐私,全值需要拼前缀。zuul.ignored-patterns=/*-service*/**:将指定的路由屏蔽,此时URL不可以直接访问。zuul.sensitive-headers::zuul网关默认屏蔽掉了请求头中的Cookie/Set-Cookie/Authorization,该值设置为空不屏蔽敏感头。
zuul:
routes:
prefix: /zuul
order-service-zuul: /order/**
product-service: /product/**
ignored-patterns: /*-service*/**
sensitive-headers:
网关过滤器鉴权
流程: 依赖 springcloud2-zuul:
- 开发一个鉴权过滤器:
- 继承
ZuulFilter类。 - 标记
@Component以被spring管理。 - 重写
FilterType():设置过滤器为前置过滤器。 - 重写
FilterOrder():返回值越小,优先级越高。 - 重写
shouldFilter():设置对下单接口order进行过滤:- 获取请求,判断uri路径中是否存在
/zuul/order,若存在,则需要被过滤。
- 获取请求,判断uri路径中是否存在
- 重写
run():被过滤后执行的业务处理:- 从请求头获取token,如果为空再尝试从请求参数中获取,若都为空,阻止向下运行,并响应 "认证失败"。
- 继承
- 测试:
- psm:
localhost:9000/zuul/order/api/v1/order/insert?product-id=1,不带token。 - psm:
localhost:9000/zuul/order/api/v1/order/insert?product-id=1,带token。 源码:
- psm:
package com.yap.springcloud2zuul.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author yap
*/
@Component
public class AuthFilter extends ZuulFilter {
/**
* 配置过滤器为前置过滤器还是后置过滤器
*
* @return FilterConstants.PRE_TYPE表示前置过滤器,FilterConstants.POST_TYPE表示后置过滤器
*/
@Override
public String filterType() {
// 设置本过滤器在请求分发之前执行
return FilterConstants.PRE_TYPE;
}
/**
* 配置过滤器执行顺序
*
* @return 值越小,优先级越高
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 配置哪些请求被过滤
*
* @return true 需要被过滤,false 直接放行
*/
@Override
public boolean shouldFilter() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest req = currentContext.getRequest();
String uri = req.getRequestURI();
// 若当前请求中包含 "/zuul/order",则需要被过滤
return uri.contains("/zuul/order");
}
/**
* 配置对所有过滤出来的请求做的操作
*
* @return 直接返回null即可
*/
@Override
public Object run() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest req = currentContext.getRequest();
String token = req.getHeader("token");
if (StringUtils.isBlank(token)) {
token = req.getParameter("token");
if (StringUtils.isBlank(token)) {
// 返回给调用方一个HTTP未认证状态
currentContext.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
currentContext.setResponseBody("token error...");
// 阻止分发请求
currentContext.setSendZuulResponse(false);
}
}
return null;
}
}
网关过滤器限流
概念: 限流的意义是保护下游服务:
- 令牌桶算法:每秒放置100个令牌,请求进来,尝试获取令牌,获取成功进入,失败拒绝。
流程: 依赖 springcloud2-zuul:
- 开发一个限流过滤器:
- 继承
ZuulFilter类。 - 标记
@Component以被spring管理。 - 重写
FilterType():设置过滤器为前置过滤器。 - 重写
FilterOrder():返回值越小,优先级越高。 - 重写
shouldFilter():设置对下单接口order进行过滤:- 获取请求,判断uri路径中是否存在
/zuul/order,若存在,则需要被过滤。
- 获取请求,判断uri路径中是否存在
- 重写
run():被过滤后执行的业务处理:- 设置令牌桶
RateLimiter属性,设置每秒产生1000个令牌。 - 每个请求尝试从令牌桶中获取令牌,若失败则阻止向下运行,阻止向下运行,并响应 "请求过多"。
- 设置令牌桶
- 继承
- 测试:压测。 源码:
package com.yap.springcloud2zuul.filter;
import com.google.common.util.concurrent.RateLimiter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
/**
* @author yap
*/
@Component
public class RequestLimitFilter extends ZuulFilter {
/**
* 新建一个令牌桶每秒产生1000个令牌
*/
private static final RateLimiter RATE_LIMITER = RateLimiter.create(1000);
/**
* 配置过滤器为前置过滤器还是后置过滤器
*
* @return FilterConstants.PRE_TYPE表示前置过滤器,FilterConstants.POST_TYPE表示后置过滤器
*/
@Override
public String filterType() {
// 设置本过滤器在请求分发之前执行
return FilterConstants.PRE_TYPE;
}
/**
* 配置过滤器执行顺序
*
* @return 值越小,优先级越高
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 配置哪些请求被过滤
*
* @return true 需要被过滤,false 直接放行
*/
@Override
public boolean shouldFilter() {
RequestContext currentContext = RequestContext.getCurrentContext();
HttpServletRequest req = currentContext.getRequest();
String uri = req.getRequestURI();
// 若当前请求中包含 "/zuul/order",则需要被过滤
return uri.contains("/zuul/order");
}
/**
* 配置对所有过滤出来的请求做的操作
*
* @return 直接返回null即可
*/
@Override
public Object run() {
RequestContext currentContext = RequestContext.getCurrentContext();
if (!RATE_LIMITER.tryAcquire()) {
// 返回给调用方一个请求过多状态
currentContext.setResponseStatusCode(HttpStatus.TOO_MANY_REQUESTS.value());
currentContext.setResponseBody("too many requests...");
// 阻止分发请求
currentContext.setSendZuulResponse(false);
}
return null;
}
}
配置中心config
概念: 配置中心可以统一管理微服务的配置,以及快速切换各个环境的配置:
- 配置中心服务端:负责连接github/gitee等云配置中心地址。
- 配置中心客户端:从服务端拉取配置。
流程: 创建gitee项目:
- 创建一个
product-service-test.yml文件:server.port=8777:端口号。env=test:当前为测试环境。branch=master:当前分支为master。
- 创建一个
product-service-dev.yml文件:server.port=8777:端口号。env=dev:当前为开发环境。branch=master:当前分支为master。
流程: 新建配置中心服务端项目 springcloud2-config-server:
- 勾选依赖:
Eureka Discovery Client/Config Server - 启动类添加:
@EnableConfigServer以启用config配置中心服务端。
- 主配添加:
server.port=9100:配置配置中心微服务端口。spring.application.name=gitee-config:给配置中心微服务起名。eureka.instance.hostname=localhost:配置配置中心微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.cloud.config.server.git.uri:值为gitee项目地址。spring.cloud.config.server.git.username:值为gitee账号,如springcloudconfig@163.com。spring.cloud.config.server.git.password:值为gitee密码,如config123456。spring.cloud.config.server.git.timeout:值为gitee连接超时时间,单位秒。spring.cloud.config.server.git.default-label:值为gitee库分支,默认master。
- 启动eureka配置中心,启动配置中心微服务:
- cli:
http://localhost:9100/master/product-service-test.yml:分支为master时可以省略。 - cli:
http://localhost:9100/master/product-service-dev.yml:分支为master时可以省略。 源码:
- cli:
- 配置:
application.yml
# config配置中心微服务的端口
server:
port: 9100
# config配置中心微服务的名字
spring:
application:
name: gitee-config
cloud:
config:
server:
git:
uri: https://gitee.com/yang-aopeng/product-service.git
username: xxxxxxxxxx@qq.com
password: xxxxxxxxxx
timeout: 5
default-label: master
# eureka注册中心
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
流程: 新建配置中心客户端项目 springcloud2-product-service-config:
- 勾选依赖:
DevTools/Lombok/Web/Eureka Discovery Client/Config Client。 - 主配添加:主配的名字必须修改为
bootstrap.yml:eureka.instance.hostname=localhost:配置商品微服务的IP。eureka.client.serviceUrl.defaultZone:配置对应的eureka服务端地址:http://localhost:8761/eureka/
spring.application.name=product-service-config:给商品微服务起名。spring.cloud.config.name=product-service:指定gitee上的配置文件的名字,无需添加环境和分支。spring.cloud.config.profile=test:指定gitee上的配置文件的环境。spring.cloud.config.label=master:指定gitee上的配置文件的分支。spring.cloud.config.uri=http://localhost:9100/:指定gitee上的配置文件的分支。
- 开发控制类
controller.ProductController:- 开发商品全查控制方法,数据模拟即可。
- 启动eureka注册中心,再启动配置中心,再启动本微服务:
- cli:
http://localhost:8777/api/v1/product/select-all源码:
- cli:
- 配置:
bootstrap.yml
#指定注册中心地址
eureka:
instance:
hostname: localhost
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
#服务的名称
spring:
application:
name: product-service-config
#指定从哪个配置中心读取
cloud:
config:
name: product-service
profile: dev
label: master
uri: http://localhost:9100/
- src:
ProductController
package com.yap.z25springcloud2productserviceconfig.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashSet;
import java.util.Set;
/**
* @author yap
*/
@RestController
@RequestMapping("/api/v1/product")
public class ProductController {
@RequestMapping("select-all")
public Set<String> selectAll() {
Set<String> result = new HashSet<>();
result.add("手机");
result.add("电脑");
return result;
}
}
alibaba sentinel
概念: 随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。 Sentinel特征:
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
Sentinel分为两个部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
- Sentinel分为:服务端和客户端
- 服务端有可视化界面,便于操作,功能十分强大和实用。
- 客户端如果是maven项目,需引入jar后,大家都知道,既然分为服务端和客户端,客户端必须配置连接服务端的地址,即可和服务端通信并完成限流功能。
流程: pom.xml中引入依赖
<!--spring-cloud-starter-alibaba-sentinel-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
主配添加:
- spring.application.name=frms
- spring.cloud.sentinel.transport.dashboard=localhost:8080
- spring.cloud.sentinel.transport.heartbeat-interval-ms=500 测试:
- 启动客户端服务,调用接口,可在服务端查看客户端信息
- 给selectUnitTreeList查询接口添加流控规则,它的QPS(每秒请求数)为1,如图: - 通过频繁调用selectUnitTreeList查询接口,通过监控可以看到如图信息:
- 降级规则配置
- 给selectUnitTreeList接口查询添加一个降级规则配置,如果QPS大于1,且平均响应时间大于20ms,则接口下来接口在2秒钟无法访问,之后自动恢复。