SpringCloud 运用(2)—— 跨服务调度
上一篇:SpringCloud 入门(1)—— nacos 注册中心-CSDN博客
1.RestTemplate 跨服务请求
RestTemplate
是 Spring 框架中的一个同步客户端,用于与 HTTP 服务进行交互。它简化了与 HTTP 服务器通信的过程,并且提供了对多种 HTTP 方法(如 GET、POST、PUT、DELETE 等)的支持,用于发送跨服务请求。
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
1.1 配置Bean
在 Spring Boot 2.0 及以上版本中,RestTemplate
不再自动配置,因此需要自己创建 RestTemplate
Bean
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RemoteCallConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
1.2 构造函数注入RestTemplate
spring不推荐使用@AutoWired注解,进行自动注入。我们可以自己书写构造函数注入
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
private RestTemplate restTemplate;
public CartServiceImpl(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
}
也可以通过lombok注解,自动生成构造函数,进行注入
@AllArgsConstructor全参构造注解,但使用该注解可能导致一些不需要通过构造传参的变量,也会生成构造函数
@RequiredArgsConstructor注解,只有通过final修饰值,才会生成构造函数。 因为通过final修饰后,必需在定义时进行赋初始值,或者通过构造函数初始化
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
private final RestTemplate restTemplate;
}
1.3 RestTemplate的使用
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",//请求路径
HttpMethod.GET,//请求方式
null,//请求实体
new ParameterizedTypeReference<List<ItemDTO>>() { },//返回值类型List<ItemDTO>
Map.of("ids", CollUtil.join(itemIds, ","))//请求参数,
);
//CollUtil.join(itemIds, ",")将集合转换成字符串,用逗号分隔。即集合123转化为字符串1,2,3。
//Map.of() "ids"是键,字符串1,2,3是值
我们可以看到http://localhost:8081/items中存在硬编码,这里我们可以使用上一篇学习到的nacos服务注册中心,将该微服务注册到nacos中,然后通过服务名发送请求。
当你通过 RestTemplate
发起请求时,Spring Cloud 提供了客户端负载均衡机制来决定具体发送到哪台计算机。默认的负载均衡策略是轮询(Round Robin)
这样如果该微服务在多台计算机都进行部署,并在nacos注册后,就可以实现负载均衡了
nacos注册中心地址教程:SpringCloud 入门(1)—— nacos 注册中心-CSDN博客
注册中心搭建完成后,使用构造函数将注入
@RequiredArgsConstructor
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart> implements ICartService {
private final RestTemplate restTemplate;
private final DiscoveryClient discoveryClient;//注册中心
}
默认情况下,采用轮询的方式进行负载均衡
// 发起请求时,直接使用服务名称
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://item-service/items?ids={ids}", // 使用服务名称而不是具体实例 URI
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {},
Map.of("ids", CollUtil.join(itemIds, ","))
);
指定服务实例方式为随机
// 查找 item-service 服务的实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
if (instances.isEmpty()) {
throw new RuntimeException("No instances available for item-service");
}
// 随机选择一个服务实例
Random random = new Random();
ServiceInstance instance = instances.get(random.nextInt(instances.size()));
// 发起请求,查询商品
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
instance.getUri() + "/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {},
Map.of("ids", CollUtil.join(itemIds, ","))
);
利用Nacos实现了服务的治理,利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了,下面介绍一款更简单的方法OpenFeign。
2.OpenFeign 跨服务请求
OpenFeign 是一个声明式的 Web 服务客户端,它使得编写 HTTP 客户端变得更加简单。它是 Netflix Feign 的增强版本,并且与 Spring Cloud 深度集成,允许开发者通过创建接口并用注解描述 HTTP 请求来定义和使用 RESTful 客户端。这简化了代码的编写,因为你不需要构建 URL、手动解析 JSON 或处理其他繁琐的任务,对跨服务请求进行简化了。
2.1 设计思路
为了避免重复编码,下面有两种抽取思路:
-
思路1:抽取到微服务之外的公共module(适用与聚合工程)
-
思路2:每个微服务自己抽取一个module
如图:
方案1抽取更加简单,工程结构也比较清晰,但缺点是整个项目耦合度偏高。(适用于maven聚合模块中使用)
方案2抽取相对麻烦,工程结构相对更复杂,但服务之间耦合度降低。
下面我们采用第一个思路,新建一名模板api模板,单独存放openFeign请求
2.2 导入依赖
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
2.3 编写Feign客户端
import com.heima.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Collection;
import java.util.List;
@FeignClient("item-service") //远程请求的服务
public interface ItemClient {
@GetMapping("/items")//请求的服务路径
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
2.4 启动OpenFeign功能
在需要发送跨服务请求(即:需要用到openFeign功能模块)的微服务的pom.xml中添加hm-api模块,
在启动类上添加注解@EnableFeignClients,开启openFeign功能 ,并且指明所在客户端的位置(类)
-
方式1:声明扫描包:
-
方式2:声明要用的API客户端
将客户端注入,发起请求
//注入
private final ItemClient itemClient;
//发起请求
List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
2.5 openFeign日志
默认情况下,openFeign请求中,后台是没有日志的,一旦出错,很难发现。
需要手动创建config包,配置日志类
import feign.Logger;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
}
在启动类中,开启日志,
全局生效:在@EnableFeignClients
中配置,针对所有FeignClient
生效。
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)
2.6 openFeign请求头
利用openFeign发送请求时,需要携带当前发起请求的用户信息。
这里我们将用户id放到请求头中,转发给另一个微服务。前端对后端发起的请求,交给网关处理,网关负责对jwt进行解析验证。网关验证完成后,才会转交给其他微服务。
后续更新网关处理方案.....
import com.hmall.common.utils.UserContext;
import feign.Logger;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.context.annotation.Bean;
public class DefaultFeignConfig {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.FULL;
}
@Bean
public RequestInterceptor userInfoRequestInterceptor(){
return new RequestInterceptor() {
@Override
public void apply(RequestTemplate template) {
// 获取登录用户
Long userId = UserContext.getUser();
if(userId == null) {
// 如果为空则直接跳过
return;
}
// 如果不为空则放入请求头中,传递给下游微服务
template.header("user-info", userId.toString());
}
};
}
}
2.7 openFeign连接池
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现
-
HttpURLConnection:默认实现,不支持连接池
-
Apache HttpClient :支持连接池
-
OKHttp:支持连接池
因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.
导入OKHttp依赖
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
application.yml
配置文件中开启Feign的连接池功能,重启服务,连接池就生效了。
feign:
okhttp:
enabled: true # 开启OKHttp功能
下一篇
SpringCloud 入门(3)—— Nacos配置中心-CSDN博客