SpringCloud下的微服务应用技术(结尾篇)

2024-03-30 19:20

本文主要是介绍SpringCloud下的微服务应用技术(结尾篇),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

六. Feign远程调用

6.1 替代RestTemplate

RestTemplate调用问题:代码可读性差,参数复杂且URL难维护。

Feign是一个声明式的HTTP客户端,官方地址:GitHub - OpenFeign/feign: Feign makes writing java http clients easier

它可以解决上述提到的问题。

STEP1:首先,在orderservice中引入依赖

<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

 

STEP2:在启动类添加@EnableFeignClients注解

STEP3:创建一个调用接口

// clients/UserClient@FeignClient("userservice")
public interface UserClient {@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);
}

 

STEP4:编写业务代码

@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;@Autowiredprivate UserClient userClient;public Order queryOrderById(Long orderId){// 1.查询订单Order order = orderMapper.findById(orderId);// 2.利用RestTemplate发起HTTP请求,查询用户User user = userClient.findById(order.getUserId());// 3.封装user到orderorder.setUser(user);// 4.返回return order;}
}

 

对比一下之前的代码,使用Feign方式更简洁,编程体验也更统一。

6.2 自定义配置

类型作用说明
feign.Logger.Level修改日志级别包含四种不同的级别:NONE、BASIC、HEADERS、FULL
feign.codec.Decoder响应结果的解析器http远程调用的结果做解析,例如解析JSON字符串为Java对象
feign.codec.Encoder请求参数编码将请求参数编码,便于通过http请求发送
feign. Contract支持的注解格式默认是SpringMVC的注解
feign. Retryer失败重试机制请求失败的重试机制,默认是没有,不过会使用Ribbon的重试

方式一:配置文件方式

  1. 全局

    feign:client:config:default:loggerLevel: FULL

  2. 针对服务

    feign:  client:config: userservice: # 针对某个微服务的配置loggerLevel: FULL #  日志级别 

方式二:代码方式

先定义一个Bean对象:

public class DefaultFeignConfiguration  {@Beanpublic Logger.Level feignLogLevel(){return Logger.Level.BASIC; // 日志级别为BASIC}
}

 

全局生效,需要在启动类注解中添加:

@EnableFeignClients(defaultConfiguration = DefaultFeignConfiguration.class)

 

局部生效,需要找到对应的Client,例如:

@FeignClient(value = "userservice", configuration = DefaultFeignConfiguration.class) 

 

6.3 性能优化

Feign底层发起HTTP请求,依赖于其它的框架,底层客户端实现包括:

URLConnection:默认实现,不支持连接池

Apache HttpClient :支持连接池

OKHttp:支持连接池

因此,要提高Feign的性能,最重要的一点就是使用连接池。同时,建议使用BASIC日志级别(太多日志影响性能)。

使用 Apache HttpClient

1)引入依赖

<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-httpclient</artifactId>
</dependency>

 

2)配置连接池

feign:httpclient:enabled: truemax-connections: 200 # 最大连接数max-connections-per-route: 50 # 每个路径最大连接数

 

除了上面的参数,还有很多参数可以配置,例如存活时间等。

6.4 最佳实践

先来看看原来的代码。

Feign客户端:

@FeignClient("userservice")
public interface UserClient{@GetMapping("/user/{id}")User findById(@PathVariable("id") Long id);
}

 

被调用的服务Controller:

@GetMapping("/user/{id}")
public User queryById(@PathVariable("id") Long id){return userservice.queryById(id);
}

 

有没有什么方法简化代码呢?答案就是:继承或抽取(根据需求选择,没有十全十美的方案)。

方式一(继承):给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。

Feign最佳实现-继承

优点:可以规范接口,实现面向契约编程和代码共享

缺点:会造成服务提供者和消费者的代码紧耦合,且参数列表的注解不会被继承,Controller需要再次声明方法、参数列表和注解。

方式二(抽取):将Feign的Client抽取为独立模块,并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

Feign最佳实践-抽取

例如,将UserClient、User、Feign的默认配置都抽取到一个feign-api包中,所有微服务引用该依赖包,即可直接使用。

优点:不用重复编写UserClient,降低代码量

缺点:假设只需要用到userservice的部分接口,但是使用这种方式会将接口全部引入,造成冗余

七. 网关

7.1 概述

不是所有人都可以调用微服务,我们需要网关(Gateway)作为微服务的统一入口,它的功能包括身份认证、权限校验、服务路由、负载均衡和请求限流等

网关架构

在SpringCloud中,网关的实现有两种:SpringCloudGatewayZuul

Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

7.2 搭建

STEP1:创建一个新的module,引入依赖:

<!--网关-->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--nacos服务发现依赖-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

 

STEP2:编写启动类:

@SpringBootApplication
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}

 

STEP3:配置网关举例:

server:port: 10010 # 网关端口
spring:application:name: gateway # 服务名称cloud:nacos:server-addr: 172.20.0.2:80 # nacos 服务地址discovery:namespace: 5b812fef-b156-4783-be29-c6ff749e38cd # devgateway:routes: # 网关路由配置- id: user-service # 路由id,自定义,只要唯一即可# uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称predicates: # 路由断言,也就是判断请求是否符合路由规则的条件- Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求- id: order-serviceuri: lb://orderservicepredicates:- Path=/order/**

 

访问 localhost:10010/user/1 即可查询id为1的用户信息,访问 localhost:10010/order/101 即可查询id为101的订单信息,实现了路由,整个流程如下图所示。

网关实现案例

7.3 断言工厂

在配置文件中写的断言规则是字符串,会被Predicate Factory读取和处理。除了Path匹配路径,还有很多个参数可以配置:

名称说明示例
After是某个时间点后的请求- After=2037-01-20T17:42:47.789-07:00[America/Denver]
Before是某个时间点之前的请求- Before=2031-04-13T15:14:47.433+08:00[Asia/Shanghai]
Between是某两个时间点之前的请求- Between=2037-01-20T17:42:47.789-07:00[America/Denver], 2037-01-21T17:42:47.789-07:00[America/Denver]
Cookie请求必须包含某些cookie- Cookie=chocolate, ch.p
Header请求必须包含某些header- Header=X-Request-Id, \d+
Host请求必须是访问某个host(域名)- Host=**.somehost.org,**.anotherhost.org
Method请求方式必须是指定方式- Method=GET,POST
Path请求路径必须符合指定规则- Path=/red/{segment},/blue/**
Query请求参数必须包含指定参数- Query=name, Jack 或者 - Query=name
RemoteAddr请求者的ip必须是指定范围- RemoteAddr=192.168.1.1/24
Weight权重处理

7.4 路由过滤器和默认过滤器

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理。

种类:Spring Cloud Gateway

以 AddRequestHeader 为例

目的:给所有进入userservice的请求加一个请求头。

在application.yml中配置如下:

spring:cloud:gateway:routes:- id: user-service uri: lb://userservice predicates: - Path=/user/** filters: # 过滤器- AddRequestHeader=Truth, 515code is freaking awesome! # 添加请求头

 

然后去userservice的controller中打印一下:

@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id, @RequestHeader(value = "Truth", required = false) String truth) {System.out.printf("truth: " + truth); // 打印Headerreturn userService.queryById(id);
}

 

默认过滤器(全局,配置实现)

配置default-filters字段即可,注意与routes同级。

7.5 全局过滤器(代码实现)

与上面提到的GatewayFilter作用一样,区别在于GatewayFilter使用配置文件来定义,处理逻辑固定。

GlobalFilter的逻辑需要代码实现,方式是实现GlobalFilter接口。

public interface GlobalFilter {/***  处理当前请求,有必要的话通过{@link GatewayFilterChain}将请求交给下一个过滤器处理** @param exchange 请求上下文,里面可以获取Request、Response等信息* @param chain 用来把请求委托给下一个过滤器 * @return {@code Mono<Void>} 返回标示当前过滤器业务结束*/Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain);
}

 

自定义:以实现Authorization过滤器为例,检查该字段是否为admin,是则通过,不是则过滤

// @Order(-1) // 优先级,默认2147483647,越小优先级越高
@Component
public class AuthorizeFilter implements GlobalFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// 1.获取请求参数ServerHttpRequest request = exchange.getRequest();MultiValueMap<String, String> params = request.getQueryParams();// 2.获取参数中的Authorization字段String auth = params.getFirst("Authorization");// 3.是否等于adminif ("admin".equals(auth)) {return chain.filter(exchange);}exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);return exchange.getResponse().setComplete();    // 拦截}@Overridepublic int getOrder() {return -1;  // 和注解一样效果}
}

 

这时候访问http://localhost:10010/order/101,页面会报401错误。访问http://localhost:10010/order/101?Authorization=admin则正常。

实际开发中,业务逻辑肯定会比上面的例子更复杂,例如读取Cookie/Session等。

7.6 过滤器执行顺序

之前已经学过,进入网关后会有三类过滤器:路由过滤器、默认过滤器(DefaultFilter)和全局过滤器(GlobalFilter)

请求路由后,会将当前路由过滤器和默认过滤器、全局过滤器合并到一个过滤器链(集合)中,排序后依次执行每个过滤器:

路由 <—> 默认过滤器 <—> 路由过滤器 <—> 全局过滤器 <—> 微服务

排序的规则:

  • 每一个过滤器都必须指定一个int类型的order值,order值越小,优先级越高,执行顺序越靠前
  • GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
  • 路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。
  • 当过滤器的order值一样时,会按照 默认过滤器 > 路由过滤器 > 全局过滤器 的顺序执行。

7.7 跨域问题

编写一个跨域请求来测试:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head>
<body>
这是跨域请求测试,请查看控制台。
</body>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>axios.get("http://localhost:10010/user/1?Authorization=admin").then(resp => console.log(resp.data)).catch(err => console.log(err))
</script>
</html>

 

进入控制台,可以查看到错误信息:index.html:1 Access to XMLHttpRequest at 'http://localhost:10010/user/1?Authorization=admin' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

采用CORS方案解决,通过配置即可实现:

YML

spring:cloud:gateway:# ......globalcors: # 全局的跨域处理add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题corsConfigurations:'[/**]':allowedOrigins: # 允许哪些网站的跨域请求 - "http://127.0.0.1:5500"allowedMethods: # 允许的跨域ajax的请求方式- "GET"- "POST"- "DELETE"- "PUT"- "OPTIONS"allowedHeaders: "*" # 允许在请求中携带的头信息allowCredentials: true # 是否允许携带cookiemaxAge: 360000 # 这次跨域检测的有效期(秒)

 

这篇关于SpringCloud下的微服务应用技术(结尾篇)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/862352

相关文章

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与

一文详解JavaScript中的fetch方法

《一文详解JavaScript中的fetch方法》fetch函数是一个用于在JavaScript中执行HTTP请求的现代API,它提供了一种更简洁、更强大的方式来处理网络请求,:本文主要介绍Jav... 目录前言什么是 fetch 方法基本语法简单的 GET 请求示例代码解释发送 POST 请求示例代码解释

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

Java利用docx4j+Freemarker生成word文档

《Java利用docx4j+Freemarker生成word文档》这篇文章主要为大家详细介绍了Java如何利用docx4j+Freemarker生成word文档,文中的示例代码讲解详细,感兴趣的小伙伴... 目录技术方案maven依赖创建模板文件实现代码技术方案Java 1.8 + docx4j + Fr

SpringBoot首笔交易慢问题排查与优化方案

《SpringBoot首笔交易慢问题排查与优化方案》在我们的微服务项目中,遇到这样的问题:应用启动后,第一笔交易响应耗时高达4、5秒,而后续请求均能在毫秒级完成,这不仅触发监控告警,也极大影响了用户体... 目录问题背景排查步骤1. 日志分析2. 性能工具定位优化方案:提前预热各种资源1. Flowable

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N