问题:在微服务中,每个项目是隔离开的,当有一个项目请求其他项目中的数据时,必须发起网络请求,本文即对此问题展开讨论。
1.使用restTemplate发送请求
//发送请求
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
"http://localhost:8081/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtil.join(itemIds, ","))
);
//解析响应体,判断是否成功发送
if (!response.getStatusCode().is2xxSuccessful()){
return;
}
List<ItemDTO> items = response.getBody();
在程序中注入类时,可以不用@Autowired注解,直接定义变量(必须用final修饰),在类上加上@RequiredArgsConstructor注解即可实现自动装配。
2.使用nacos实现远程调用
问题:restTemplate中代码中的路径是写死的,若一个服务有多个实例只能访问到一个,无法做到负载均衡。
1.注册中心原理
2.nacos注册中心
将nacos部署到docker中。
3.服务注册
服务在启动时将自己的信息提交到nacos,此为服务注册。
1.添加依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.在配置文件中中添加nacos地址配置:
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.145.129:8848 # nacos地址
操作完后就已经完成了服务的注册。
ps:当我们要启动一个项目的多个实例,需要在idea中按alt+8出现如下界面,点击复制配置:
如此配置,即可将同一项目的多个实例配置在不同的端口中,此配置优先级大于项目中的yaml配置文件。
4.服务发现
服务发现为服务的调用者去拉取别的服务的列表。
1.添加依赖
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.在配置文件中中添加nacos地址配置:
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.145.129:8848 # nacos地址
3.配置好后即可去调用别的服务
若一个服务有好几个实例,那么可以使用负载均衡的算法,从多个实例中挑选一个去访问。
常见的负载均衡算法有:
-
随机
-
轮询
-
IP的hash
-
最近最少访问
首先注入DiscoveryClient
private final DiscoveryClient discoveryClient;
调用nacos发起请求
//利用nacos发送请求访问,获取实例
List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
//使用随机的算法从实例中挑选一个实例
ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
// 2.查询商品
List<ItemDTO> items = itemService.queryItemByIds(itemIds);
//发送请求
ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
instance.getUri()+"/items?ids={ids}",
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<ItemDTO>>() {
},
Map.of("ids", CollUtil.join(itemIds, ","))
);
//解析响应体
if (!response.getStatusCode().is2xxSuccessful()){
return;
}
List<ItemDTO> items = response.getBody();
3.OpenFeign
问题:使用nacos和restTemplate发送请求操作仍然过于繁琐,因此需要使用openfeign。
OpenFeign是一个声明式的http客户端,是SpringCloud在Eureka公司开源的Feign基础上改造而来。官方地址:GitHub - OpenFeign/feign: Feign makes writing java http clients easier
其作用就是基于SpringMVC的常见注解,帮我们优雅的实现http请求的发送。
1.基本使用
1.导入依赖:
<!--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.在启动类上加上注解@EnableFeignClients
ps:当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。
当定义的FeignClient在另一个项目中,即使在项目中引入了pom文件,有这个类型,但是没有这个bean,因为在项目中spring扫描的包是本项目的文件,因此就接收不到这个bean。
有两种方式解决:
方式一:指定FeignClient所在包
@EnableFeignClients(basePackages = "com.hmall.api.clients")
方式二:指定FeignClient字节码
@EnableFeignClients(clients = {UserClient.class})
3.定义接口,编写代码
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByids(@RequestParam("ids") Collection<Long> ids);
}
-
@FeignClient("item-service")
:声明服务名称 -
@GetMapping
:声明请求方式 -
@GetMapping("/items")
:声明请求路径 -
@RequestParam("ids") Collection<Long> ids
:声明请求参数 -
List<ItemDTO>
:返回值类型
4.当项目需要发起请求时,即可导入ItemClient类调用方法实现。
2.连接池
问题:openfeign使用的HttpURLConnection效率太低。
OpenFeign其底层发起http请求,依赖于其它的框架。这些框架可以自己选择,包括以下三种:
HttpURLConnection:默认实现,不支持连接池
Apache HttpClient :支持连接池
OKHttp:支持连接池
此处使用okhttp替换默认的。
1.引入依赖:
<!--ok-http-->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
2.开启连接池功能
feign:
okhttp:
enabled: true # 开启OKHttp连接池支持
3.日志
OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:
NONE:不记录任何日志信息,这是默认值。
BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
由于Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
1.要自定义日志级别需要声明一个类型为Logger.Level的Bean,在其中定义日志级别:
public class defaultfeignconfig {
@Bean
public Logger.Level feignconfig(){
return Logger.Level.FULL;
}
}
2.1 局部配置
但此时这个Bean并未生效,要想配置某个FeignClient的日志,可以在@FeignClient注解中声明:
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
2.2 全局配置
如果想要全局配置,让所有FeignClient都按照这个日志配置,则需要在@EnableFeignClients注解中声明:
@EnableFeignClients(defaultConfiguration = myDefaultFeignConfig.class)