微服务学习
有商品服务,订单服务。
首先在父项依赖中统一依赖版本。
一 、服务治理 Nacos Discovery
默认8848端口,用于实现各个微服务的自动化注册与发现。
①写nacos依赖
②在配置文件中添加Nacos服务的地址
spring:
application:
name: xxx #告诉nacos我的名字
cloud:
nacos:
discovery:
server-addr: localhost:8848 #找到nacos的地址
二、远程调用负载均衡LoadBalancer
分为服务端负载均衡和客户端负载均衡
①写入依赖
②注解@LoadBalanced
③负载均衡策略
1.RandomLoadBalancer-随机分配策略
2.RoundRobinLoadBalancer-轮询分配策略(默认)
三、远程调用Feign(默认集成了负载均衡)
①依赖
②在启动类OrderServer.java上添加Fegin的扫描注解
@EnableFeignClients
③在shop-order-server项目中新增接口ProductFeignApi
/**
- name属性:访问远端服务器的名字
- fallback属性:当远端服务访问失败时,本地的错误处理类
*/
@FeignClient(name = "product",fallback = ProductFeignFallback.class)
public interface ProductFeignApi {
@GetMapping("/product/get/{pid}")
TShopProduct get(@PathVariable("pid") Long pid);
}
④访问失败处理类
/**
-
本地容错类,当Feign接口请求远端服务器失败时,就会调用该类获取兜底数据
*/
@Component
public class ProductFeignFallback implements ProductFeignApi {@Override
public TShopProduct get(Long pid) {
TShopProduct product = new TShopProduct();
product.setPid(-1L);
product.setPname("商品错误");
product.setPprice(0.0D);
product.setStock(0);return product;}
}
⑤远程调用方法
//远程调用商品微服务,查询商品信息
Product product = productFeignApi.findByPid(productId);
四、服务熔断降级 Sentinel!!!!重点
规则默认放在内存中,如果想要进行持久化需要写配置
sentinel可视化控制台的端口号默认为:8080
sentinel与服务之间的交互端口号默认为:8719
sentinel中有两种资源:
1.在sentinel中,Controller中的接口默认被识别成sentinel的资源
2.使用@SentinelResource注解也可以声明资源,注解括号里的名字是资源名
常见容错方案:隔离机制、超时机制、限流机制、熔断机制、降级机制
①Sentinel依赖
②修改配置文件
spring:
cloud:
sentinel:
transport:
port: 9999 #跟控制台交流的端口,随意指定一个未使用的端口即可
dashboard: localhost:8080 # 指定控制台服务的地址
sentinel规则种类:流控规则、降级规则、热点规则、系统规则、授权规则
4.1 流控规则
流控模式:直接、关联、链路
直接:谁超过阈值范围就给谁限流
关联:比如资源让行,自己超过阈值,则让其他限流
实际案例:付费网站,普通用户给vip用户让行
链路:访问同一个资源,不同的用户走不同的链路,例如vip1走vip1链路、vip2走vip2链路、普通用户走普通用户链路
流控效果:快速失败、Warm Up、排队等待
快速失败(默认):直接抛出500异常或显示Blocked by Sentinel(flow limiting)
Warm up:经过预热时长最终会达到阈值数,开始的阈值数为设置的阈值数的三分之一,如果在此期间达到阈值也会抛出异常
实际场景:(可理解为机械的最大性能)例如服务器刚启动性能还没有完全发挥,先让它预热
排队等待:如果超出阈值,则会进行等待,如果等待时间用完还不能进去,则一样抛出异常
实际场景(用得少,仅适用于小网站,如果大一点的则会雪崩):网站用户量稳定假设只有2000,每年只增长100,服务器能承受的数量为2200,当超过2200则可以让超过的用户访问的慢一点,但是如果买了一个新的服务器,则需要20年才可以勉强用满
4.2 降级规则
根据服务器的状况来进行降级,流控规则是根据访问的线程数来进行管理。
①慢调用比例
RT:最大响应时间
最小请求数:自己设置
实际场景:tomcat每秒可以请求千八百个请求,如果只来了10个请求,还有问题,只能说明是网络或者硬件问题,不是服务器高并发问题
熔断时长:
服务器默认是熔断关闭状态,当慢调用比例达到阈值会变成熔断开启状态,则接下来的熔断时长内的请求会自动被熔断。经过熔断时长后熔断器会进入半熔断状态,若接下来的一个请求响应的时间小于设置的慢调用RT则会结束熔断,否则会再次熔断
②异常比例
③异常数
单位时间内,异常数超过阈值
4.3 热点规则
用来限定热点的
实际场景:如果在商品网站中大家都来抢购热点商品,但是不能因为这个,让不来抢购热点商品的用户感受到卡顿
这里要注意!!!,在Controller中写接口的时候,要在接口上写上@SentinelResource("资源名")注解
参数索引:参数在的下标位置
单机阈值:几秒钟中最多访问几次
参数例外项是用来设置特殊商品的,例如热点商品
4.4 授权规则
①要写一个请求解析器,用来解析请求的目标来源
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
@Component
public class RemoteOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String fromsource = httpServletRequest.getHeader("fromsource");
if (fromsource == null || "".equals(fromsource)){
fromsource = "error";
}
return fromsource;
}
}
②接口
@RequestMapping("/auth1")
public String auth1(String serviceName){
log.info("应用:{},访问接口",serviceName);
return "auth1";
}
③新增授权规则
④访问测试
4.5 系统规则
针对整个服务器层面,不针对指定的接口
一般实际开发中,用的多的是Load自适应和入口QPS
4.6 Sentinel 自定义返回异常
分为一下几种异常:
FlowException 限流异常
DegradeException 降级异常
ParamFlowException 参数限流异常(热点异常)
AuthorityException 授权异常
SystemBlockException 系统负载异常(系统异常)
4.6.1异常处理类(只捕获Sentinel的异常)
/**
- 能解决在触发sentinel规则后,出现'block by sentinel(Flow limiting)'的异常;直接抛出500的异常解决不了
- 只捕获sentinel的异常
/
@Component
public class ShopBlockExceptionHandler implements BlockExceptionHandler {
/*-
当发生异常就会进入这个方法
*/
@Override
public void handle(HttpServletRequest req, HttpServletResponse resp, BlockException e) throws Exception {Map<String,Object> map = new HashMap<>();
if (e instanceof FlowException){
// 限流异常
map.put("code",-1);
map.put("msg","限流异常");
}else if (e instanceof DegradeException){
// 降级异常
map.put("code",-2);
map.put("msg","降级异常");
}else if (e instanceof ParamFlowException){
// 热点异常
map.put("code",-3);
map.put("msg","热点异常");
}else if (e instanceof AuthorityException){
// 授权异常
map.put("code",-4);
map.put("msg","授权异常");
}else if (e instanceof SystemBlockException){
// 系统异常
map.put("code",-5);
map.put("msg","系统异常");
}String jsonString = JSON.toJSONString(map);
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().write(jsonString);
}
}
-
4.6.2统一异常处理(可以捕获所有的异常)
/**
-
统一异常处理,可以捕获所有的异常
*/
@RestControllerAdvice
public class MyExceptionHandler{@ExceptionHandler(BlockException.class)
public Object handleBlockException(BlockException e){
Map<String,Object> map = new HashMap<>();
if (e instanceof FlowException){
// 限流异常
map.put("code",-1);
map.put("msg","限流异常");
}else if (e instanceof DegradeException){
// 降级异常
map.put("code",-2);
map.put("msg","降级异常");
}else if (e instanceof ParamFlowException){
// 热点异常
map.put("code",-3);
map.put("msg","热点异常");
}else if (e instanceof AuthorityException){
// 授权异常
map.put("code",-4);
map.put("msg","授权异常");
}else if (e instanceof SystemBlockException){
// 系统异常
map.put("code",-5);
map.put("msg","系统异常");
}
return map;
}
}
4.7@SentinelResource的使用
可用于:
1.通过不同接口访问同一个资源
2.热点规则,在接口上写上这个注解
主要参数:
value:设置资源名称
可以为空,此时资源名为包名.类名.方法名
blockHandler/blockHandlerClass和fallback/fallbackClass用于指定异常的处理,但是一般不会这样用,我们基本都是用同一异常来进行异常处理
blockHander用于处理Sentinel发生的异常,而fallback用于处理其他的异常
exceptionsToIgnore:忽略某些异常
4.8 Feign整合Sentinel
之前写的代码思路都是正向请求,意思是我们请求的东西一定会被请求到。
现在我们如果请求不到某些东西,则要用Sentinel进行降级处理(fallback)。
4.8.1依赖
feign:
sentinel:
enabled: true
AI运行代码
java
之所以是写这个依赖,是因为现在的框架集成度很高,feign可以用别的来进行熔断处理不一定是sentinel,所以要把他开启
4.8.2创建容错类
/**
-
本地容错类,当Feign接口请求远端服务器失败时,就会调用该类获取兜底数据
*/
@Component
public class ProductFeignFallback implements ProductFeignApi {@Override
public TShopProduct get(Long pid) {
TShopProduct product = new TShopProduct();
product.setPid(-1L);
product.setPname("商品错误");
product.setPprice(0.0D);
product.setStock(0);return product;}
}
4.8.3在feign接口中定义容错类
/**
-
name属性:访问远端服务器的名字
-
fallback属性:当远端服务访问失败时,本地的错误处理类
*/
@FeignClient(name = "product",fallback = ProductFeignFallback.class)
public interface ProductFeignApi {@GetMapping("/product/get/{pid}")
TShopProduct get(@PathVariable("pid") Long pid);
}
有一种情况面试会问:当前项目中出现这么一个问题,A服务访问B服务第一次访问成功,第二次失败,以此类推
分析:一会请求成功,一会请求失败,说明有多台服务器,失败的情况就是访问到了不好用的服务器(网络问题、重编译了、下线了)
答:因为负载均衡,其中有某一个服务器是挂了的情况
五、 服务网关Gateway
Gateway网关不是基于tomcat体系的,而是基于Netty服务器
Gateway也可以负载均衡
网关能找到服务器,则说明Gateway是nacos中的一个客户端
5.1 集成服务网关
①需要导入3个依赖
②启动类没有什么好说的了
③配置文件
server:
port: 9000
spring:
application:
name: gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848
gateway:
discovery:
locator:
enabled: true # 让gateway可以发现nacos中的微服务
gateway是springCloud团队开发的,nacos是alibaba开发的,代码不是互通的,所以要写spring.cloud.gateway.discovery.locator.enabled=true这个配置
这样网关就配置好了,接下来进行测试:
之前访问 localhost:8091/sentinel2
现在访问 localhost:9000/order/sentinel2 访问的是一个内容,其中9000是网关的端口号,order是要请求的服务名
5.2 自定义网关路由
如果用默认的网关路由会出现以下问题:
暴露服务名,不安全
服务名太长,输入地址不方便
编写自定义路由,只需要在application.yml中添加新的路由规则:
spring:
cloud:
gateway:
routes: # 一个网关中有多个路由,注意不要写成route
- id: product_route
uri: lb://product # 这个要写微服务的名字
predicates:
- Path=/p/** # 只有满足这个路径,才会走网关
filters:
- StripPrefix=1 # 跳过一级路径,也就是p
- id: order_route
uri: lb://order
predicates:
- Path=/o/**
filters:
- StripPrefix=1
注意还有一个重要参数order,值越小越先进行匹配
执行流程:
5.3 过滤器 Filter
过滤器就是在请求的传递过程中,对请求和响应做一些手脚。
在Gateway中,Filter的生命周期只有两个:“pre”和“post”。
PRE:这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTPHeader、收集统计信息和指标、将响应从微服务发送给客户端等。
在Gateway中,Filter的作用范围分为两种:
GatewayFilter:应用到单个路由或者一个分组的路由上。
GlobalFilter:应用到所有的路由上。
5.4 局部路由过滤器统计耗时
在配置路由规则中的filters下,加入- show=true
@Component
public class TimeGatewayFilterFactory extends AbstractGatewayFilterFactory<TimeGatewayFilterFactory.MyConfig> {
public TimeGatewayFilterFactory(){
super(MyConfig.class);
}
@Override
public List<String> shortcutFieldOrder() {
// Stream.of("a", "str", "b").toList();
List
return collect;
}
// 加工过滤器的方法
@Override
public GatewayFilter apply(MyConfig config) {
// 生成的过滤器,并返回
return new GatewayFilter() {
/**
* 过滤器的过滤方法
* 参数1:相当于tomcat容器体系下,request和response对象的整合
* 参数2:过滤器链
*/
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (!config.show){ // 如果配置中设置为false,直接放行,不需要统计耗时
return chain.filter(exchange);
}
// pre过滤器
long begin = System.currentTimeMillis();
return chain.filter(exchange).then(Mono.fromRunnable(()->{
// post过滤器
long end = System.currentTimeMillis();
// 发请求的客户端域名
String ip = exchange.getRequest().getRemoteAddress().getHostName();
// 请求的资源地址
String path = exchange.getRequest().getURI().getPath();
StringBuffer stringBuffer = new StringBuffer();
stringBuffer.append(ip).append("访问接口").append(path).append("耗时").append(end-begin).append("毫秒");
System.err.println(stringBuffer.toString());
}));
}
};
}
// 用于存放配置文件中的参数
@Data
public static class MyConfig{
private boolean show;
}
}
5.5 全局路由过滤器
@Component
public class ShopAuthFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// System.err.println("全局路由过滤器被执行了");
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
String token = request.getQueryParams().getFirst("token");
if (StringUtils.isBlank(token)) { // 传递的token是null,"",或者空白符
response.setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}else {
// 验证token合法性
// 如果token不合法,返回401
}
return chain.filter(exchange); // 放行
}
}
5.6 集成Sentinel实现网关限流
①依赖
②添加配置
spring:
cloud:
sentinel:
transport:
port: 9999
dashboard: localhost:8080
AI运行代码
③配置完成后进行限流操作
5.7 API分组
是方法层面上的限流,更精细
一共有3种匹配模式:精确、前缀、正则
现在我们定义了如下的接口地址
@RestController
@RequestMapping("/v1")
public class TestController {
@RequestMapping("/test1")
public String test1() {
return "test1";
}
@RequestMapping("/test2")
public String test2() {
return "test2";
}
@RequestMapping("/test3/test")
public String test3() {
return "test3";
}
}
5.7.1 精准匹配
1.在API管理中新建API分组,匹配模式选择精准匹配,匹配串写请求URL地址
2.在流控规则中,API类型中选择API分组,然后在API名称中选择我们刚刚定义的V1限流
3.此时上面三个请求中,只有 /o/v1/test1会被限流
5.7.2 前缀匹配
然后在增加流控规则,此时/o/v1/**路径下的所有请求都会被流控
注意: 如果路径为/*表示匹配一级路径,如果路径为/**表示多级路径
5.7.3 正则匹配
5.8 修改限流默认返回格式
@Configuration
public class ShopGatewayConfig {
// 不用主动调用
@PostConstruct
public void ShopGatewayExceptionHandle(){
BlockRequestHandler blockRequestHandler = new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
Map<String,Object> map = new HashMap<>();
map.put("code",-100);
map.put("message","网关路由被限流");
return ServerResponse.status(HttpStatus.OK) // 状态
.contentType(MediaType.APPLICATION_JSON) // JSON类型
.body(BodyInserters.fromValue(map)); //用Body插入器插入map
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
六、链路追踪 Sleuth&Zipkin
Sleuth是用来与@Slf4j配合打日志的
6.1 集成链路追踪组件Sleuth
导入依赖
6.2 日志参数解释
日志格式:
[order,c323c72e7009c077,fba72d9c65745e60,true]
1、第一个值,spring.application.name的值
2、第二个值,c323c72e7009c077 ,sleuth生成的一个ID,叫Trace ID,用来标识一条请求链路,一条请求链路中包含一个Trace ID,多个Span ID
3、第三个值,fba72d9c65745e60,spanID 基本的工作单元,获取元数据,如发送一个http
4、第四个值:true,是否要将该信息输出到zipkin服务中来收集和展示。
可以根据时间判断哪个日志先执行的
6.3 Zipkin+Sleuth整合
①导入依赖
②加入配置
spring:
zipkin:
base-url: http://127.0.0.1:9411/ #zipkin server的请求地址
discoveryClientEnabled: false #让nacos把它当成一个URL,而不要当做服务名
sleuth:
sampler:
probability: 1.0 #采样的百分比
七、配置中心 Nacos Config
微服务架构下关于配置文件的一些问题:
1.配置文件相对分散。在一个微服务架构下,配置文件会随着微服务的增多变的越来越多,而且分散在各个微服务中,不好统一配置和管理。
2.配置文件无法区分环境。微服务项目可能会有多个环境,例如:测试环境、预发布环境、生产环境。每一个环境所使用的配置理论上都是不同的,一旦需要修改,就需要我们去各个微服务下手动维护,这比较困难。
3. 配置文件无法实时更新。我们修改了配置文件之后,必须重新启动微服务才能使配置生效,这对一个正在运行的项目来说是非常不友好的。
基于上面这些问题,我们就需要配置中心的加入来解决这些问题
配置中心的思路是:
首先把项目中各种配置全部都放到一个集中的地方进行统一管理,并提供一套标准的接口。
当各个服务需要获取配置的时候,就来配置中心的接口拉取自己的配置。
当配置中心中的各种参数有更新的时候,也能通知到各个服务实时的过来同步最新的信息,使之动态更新。
当加入了服务配置中心之后,我们的系统架构图会变成下面这样:
7.1 Nacos Config入门
1.导入依赖
2.在微服务中添加config的配置
注意:不能使用原来的 application.yml 作为配置文件,而是新建一个 bootstrap.yml 作为配置文件
spring:
application:
name: product
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos中心地址
file-extension: yml # 配置文件格式
profiles:
active: dev # 环境标识
3.在nacos中添加配置,然后把商品微服务application.yml配置复制到配置内容中.
4.注释本地的application.yam中的内容, 启动程序进行测试
7.2 配置动态刷新
只需一个注解@RefreshScope
1.在nacos中的product-service-dev.yaml配置项中添加下面配置:
appConfig:
name: product
- 在商品微服务中新增NacosConfigControlller.java
@RestController
@RefreshScope // 配置动态刷新
public class NacosConfigController {
@Value("${appConfig.name}")
private String appConfigName;
@RequestMapping("/nacosConfig1")
public String nacosConfig(){
return "远程信息:"+appConfigName;
}
}
7.3 配置共享
同一个微服务的不同环境之间共享配置
①环境不同
同一个product微服务可能有不同环境,例如开发环境、测试环境。
把开发环境和测试环境的配置相同的部分抽取出来,放到product.yml中。
把独有的配置放到自己的配置文件中,开发环境独有的server.port=8091放到product-dev.yml中,测试环境独有的server.port=8099放到product-test.yml中。
当程序运行时,会先读取product.yml文件覆盖到本地的配置文件中,然后再读取相应环境的配置文件再进行覆盖。
②端口号不同
product微服务进行了集群,端口分别为8081、8082、8083。
也是先抽取相同的配置放到product.yml中,然后再将不同的配置分别放到product-dev8081.yml、product-dev8082.yml、product-dev8083.yml中。
然后通过idea来配置,
不同微服务中间共享配置
用于网站外联
- 在nacos中定义一个DataID为global-config.yaml的配置,用于所有微服务共享
globalConfig: global
- 修改bootstrap.yaml
spring:
application:
name: product
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848 #nacos中心地址
file-extension: yml # 配置文件格式
shared-configs:
- data-id: global-config.yaml # 配置要引入的配置
refresh: true
profiles:
active: test # 环境标识
7.4Sentinel规则持久化
1.导入依赖
2.修改bootstrap.yml文件,增加datasource的配置
spring:
sentinel:
datasource: # sentinel数据源
ds1: # 自定义连接名
nacos:
server-addr: localhost:8848 # nacos服务地址
data-id: sentinel-consumer-ds1 # nacos的dataId
group-id: DEFAULT_GROUP # 默认分组
data-type: json # 数据类型 json类型
rule-type: flow # flow表示流控规则
rule-type有五种的流控规则
authority (授权规则)
degrade (降级规则)
flow (流控规则)
param (热点规则)
system (系统规则)
3. 在Nacos的控制面板新建 sentinel-consumer-ds1 的DataId,具体配置内容如下
[
{
"resource":"/sentinel2", // 资源名
"limitApp":"default", // 默认来源
"grade": 1, // QPS
"count": 2, // 一秒2请求
"strategy": 0, // 直接流控
"controlBehavior": 0, // 快速失败
"clusterMode": false // 是不是集群
}
]
- 然后访问/sentinel2,可以看到在Sentinel的管控台中看到流控规则了.
浙公网安备 33010602011771号