微服务-01

一、导入项目

  1. 执行cd /rootdocker compose down命令,删除同目录下,可能存在相同的项目;
  2. 安装mysql
    将mysql目录(配置文件和初始化脚本)复制加载到虚拟机中------执行命令1(创建通用网络)------执行命令2(安装mysql,创建mysql容器)------执行命令3(查看mysql容器)------
bash 复制代码
#命令1
docker network create yue-net


# 命令2
docker run -d \
  --name mysql \
  -p 3306:3306 \
  -e TZ=Asia/Shanghai \
  -e MYSQL_ROOT_PASSWORD=123456 \
  -v /home/yueyue/mysql/data:/var/lib/mysql \
  -v /home/yueyue/mysql/conf:/etc/mysql/conf.d \
  -v /home/yueyue/mysql/init:/docker-entrypoint-initdb.d \
  --network yue \
  mysql
  
#命令3
docker ps
  1. Java后端代码

    将后端项目代码复制到工作空间------使用idea打开------按下ALT+8键打开services窗口,新增一个启动项------点击Run Configuration Type------鼠标向下滚动,找到Spring Boot------点击启动运行或者Debug运行------给启动项做简单配置(XXXApplication右键点击------弹出窗口------Edit Configuration------Active profiles对应写local值)

    浏览器访问:http://localhost:8080/hi

  2. 前端代码

    将前端项目代码复制到工作空间(非中文、不包含特殊字符的目录)------进入项目目录,利用cmd启动------执行命令

PowerShell 复制代码
# 启动nginx
start nginx.exe
# 停止
nginx.exe -s stop
# 重新加载配置
nginx.exe -s reload
# 重启
nginx.exe -s restart

特别注意:

nginx.exe 不要双击启动,而是打开cmd窗口,通过命令行启动。停止的时候也一样要是用命令停止。如果启动失败不要重复启动,而是查看logs目录中的error.log日志,查看是否是端口冲突。如果是端口冲突则自行修改端口解决。

浏览器访问:http://localhost:18080/

二、认识微服务

2.1 单体架构

单体架构(monolithic structure): 整个项目中的所有功能模块都在一个工程中开发,项目部署时需要对所有模块一起编译、打包;项目的架构设计、开发模式都非常简单;
优点:

当项目规模较小时,该模式上手快,部署、运维都方便;
缺点:

  • 团队协作成本高: 所有人改同一个代码库,频繁的代码合并极易产生冲突,解决冲突会耗费大量时间
  • 系统发布效率低: 每次修改无论大小都要重新构建、测试并部署整个应用,导致发布流程冗长且易出错。
  • 系统可用性差: 某个模块(如秒杀)占用大量资源,可能导致整个系统崩溃,拖垮其他无关模块。

2.2 微服务

微服务架构: 首先是服务化,就是将单体架构中的功能模块从单体应用中拆分出来,独立部署为多个服务。同时要满足下面的一些特点:

  • 单一职责: 一个微服务负责一部分业务功能,并且其核心数据不依赖于其它模块;
  • 团队自治: 每个微服务都有自己独立的开发、测试、发布、运维人员,团队人员规模不超过10人;
  • 服务自治: 每个微服务都独立打包部署,访问自己独立的数据库。并且要做好服务隔离,避免对其它服务产生影响;

解决单体架构存在的问题:

  • 团队协作成本高?
    • 由于服务拆分,每个服务代码量大大减少,参与开发的后台人员在1~3名,协作成本大大降低
  • 系统发布效率低?
    • 每个服务都是独立部署,当有某个服务有代码变更时,只需要打包部署该服务即可
  • 系统可用性差?
    • 每个服务独立部署,并且做好服务隔离,使用自己的服务器资源,不会影响到其它服务。

分布式 就是服务拆分的过程 ,其实微服务架构正式分布式架构的一种最佳实践的方案

微服务架构虽然能解决单体架构的各种问题,但在拆分的过程中,还会面临很多其它问题:

  • 如果出现跨服务的业务该如何处理?
  • 页面请求到底该访问哪个服务?
  • 如何实现各个服务之间的服务隔离?

2.3 SpringCloud

微服务拆分 以后碰到的各种问题都有对应的解决方案和微服务组件,而SpringCloud框架可以说是目前Java领域最全面的微服务组件的集合了。

而且SpringCloud依托于SpringBoot的自动装配能力,大大降低了其项目搭建、组件使用的成本。对于没有自研微服务组件能力的中小型企业,使用SpringCloud全家桶来实现微服务开发可以说是最合适的选择了!
SpringCloud官网https://spring.io/projects/spring-cloud#overview
Alibaba的微服务产品SpringCloudAlibaba目前也成为了SpringCloud组件中的一员,我们课堂中也会使用其中的部分组件

三、微服务拆分

3.1 服务拆分原则

服务拆分要考虑的几个问题:

  • 什么时候拆?
  • 怎么拆?

3.1.1 什么时候拆

对于大多数小型项目来说,一般是先采用单体架构 ,随着用户规模扩大、业务复杂后再逐渐拆分为微服务架构 。这样初期成本会比较低,可以快速试错。但是,这么做的问题就在于后期做服务拆分时,可能会遇到很多代码耦合带来的问题,拆分比较困难(前易后难 ) 。

而对于一些大型项目,在立项之初目的就很明确,为了长远考虑,在架构设计时就直接选择微服务架构。虽然前期投入较多,但后期就少了拆分服务的烦恼(前难后易

3.1.2 怎么拆

服务拆分目标:

微服务拆分时粒度要小,这其实是拆分的目标。具体可以从两个角度来分析:

  • 高内聚:每个微服务的职责要尽量单一,包含的业务相互关联度高、完整度高。
  • 低耦合:每个微服务的功能要相对独立,尽量减少对其它微服务的依赖,或者依赖接口的稳定性要强

高内聚 首先是单一职责 ,但不能说一个微服务就一个接口,而是要保证微服务内部业务的完整性为前提。目标是当我们要修改某个业务时,最好就只修改当前微服务,这样变更的成本更低。

一旦微服务做到了高内聚,那么服务之间的耦合度自然就降低了。

服务拆分方式:

  • 纵向拆分
    • 纵向拆分,就是按照项目的功能模块来拆分。如商城项目中,有用户管理功能、订单管理功能、购物车功能、商品管理功能、支付功能等,按照功能模块将他们拆分成一个个服务,这种拆分模式可以尽可能提高服务的内聚性;
  • 横向拆分
    • 横向拆分,是看各个功能模块之间有没有公共的业务部分,如果有将其抽取出来作为通用服务。如用户登录是需要发送消息通知,记录风控数据,下单时也要发送短信,记录风控数据。因此消息发送、风控数据记录就是通用的业务功能,因此可以将他们分别抽取为公共服务:消息中心服务、风控管理服务,这种拆分模式可以提高业务的复用性,避免重复开发,通用业务一般接口稳定性较强,也不会使服务之间过分耦合;

商品服务与购物车服务拆分案例

3.1.3 服务调用

服务拆分之后,不可避免的会出现跨微服务的业务,此时微服务之间就需要进行远程调用,微服务之间的远程调用被称为RPC,即远程过程调用。
跨服务远程调用 (RPC,即R emote P roduce Call)实现方式:

  • 基于Http协议方式不关系服务提供者的具体技术实现,只要对外暴露Http接口即可,更符合未服务的需要;
  • 基于Dubbo协议

Java发送http请求可以使用Spring提供的RestTemplate,使用的基本步骤:

  • 注册RestTemplate到Spring容器;
  • 调用RestTemplate的API发送请求,常见方法有:
    • getForObject:发送Get请求并返回指定类型对象;
    • PostForObject:发送Post请求并返回指定类型对象;
    • put:发送PUT请求;
    • delete:发送Delete请求;
    • exchange:发送任意类型请求,返回ResponseEntity;
  1. RestTemplate
    RestTemplate通常用作共享组件,它的配置不支持并发修改,因此它的配置通常是在启动时准备的。
  2. 远程调用
    定义一个配置类,将RestTemplate注册为一个Bean
java 复制代码
/**
 * 将RestTemplate注册为一个Bean
 */
@Configuration
public class RemoteCallConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

远程调用

java 复制代码
private void handleCartItems(List<CartVO> vos) {
        // TODO 1.获取商品id
        Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
        // 2.查询商品
        // List<ItemDTO> items = itemService.queryItemByIds(itemIds);
        // 2.1.利用RestTemplate发起http请求,得到http的响应
        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, ","))  //请求参数
        );
        // 2.2.解析响应
        if (!response.getStatusCode().is2xxSuccessful()) {
            // 查询失败,直接结束
            return;
        }
        List<ItemDTO> items = response.getBody();
        if (CollUtils.isEmpty(items)) {
            return;
        }
        // 3.转为 id 到 item的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (CartVO v : vos) {
            ItemDTO item = itemMap.get(v.getItemId());
            if (item == null) {
                continue;
            }
            v.setNewPrice(item.getPrice());
            v.setStatus(item.getStatus());
            v.setStock(item.getStock());
        }
    }

四、服务注册与发现

4.1 注册中心原理

在微服务远程调用的过程中,包括两个角色:

  • 服务提供者: 提供接口供其他微信服务访问,比如item-service;
  • 服务消费者: 调用其他微服务提供的接口,比如cart-service;

在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心 的概念。注册中心、服务提供者、服务消费者三者间关系如下:

流程如下:

  1. 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心;
  2. 调用者可以从注册中心订阅 想要的服务,获取服务对应的实例列表(1个服务可能多实例部署);
  3. 调用者自己对实例列表负载均衡,挑选一个实例;
  4. 调用者向该实例发起远程调用

当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?

  • 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求);
  • 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除;
  • 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表;
  • 注册中心服务列表变更时,会主动通知微服务,更新本地服务列表;

4.2 Nacos注册中心

目前开源的注册中心框架有很多,国内比较常见的有:

  • Eureka: Netflix公司出品,目前被集成在SpringCloud当中,一般用于Java应用;
  • Nacos: Alibaba公司出品,目前被集成在SpringCloudAlibaba中,一般用于Java应用;
  • Consul: HashiCorp公司出品,目前集成在SpringCloud中,不限制微服务语言;

Nacos 是国内产品,中文文档比较丰富,而且同时具备配置管理功能,Nacos官网

基于Docker部署Nacos注册中心

修改nacos文件夹中的 custom.env文件,MYSQL_SERVICE_HOST也就是mysql地址,需要修改为自己的虚拟机IP地址------将nacos目录上传到虚拟机/root目录------进入root目录,执行命令1

bash 复制代码
#命令1
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server:v2.1.0-slim

访问地址:http://192.168.19.128:8848/nacos/,注意将192.168.150.101替换为你自己的虚拟机IP地址,账号和密码均为nacos

4.3 服务注册

把服务(如item-service)注册到Nacos,步骤如下:

  • 引入依赖
  • 配置Nacos地址
  • 重启
  1. 添加依赖
    在需要注册的服务(如item-service)的pom.xml中添加依赖:
xml 复制代码
<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
  1. 配置Nacos
    在需要注册的服务(如item-service)的application.yml中添加nacos地址配置:
yaml 复制代码
spring:
  application:
    name: item-service # 服务名称
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848 # nacos地址,192.168.150.101修改成自己的虚拟机地址
  1. 启动服务实例
    为了测试一个服务多个实例的情况,我们再配置一个item-service的部署实例:

    然后配置启动项,注意重命名并且配置新的端口,避免冲突:
bash 复制代码
-Dserver.port=8083

重启item-service的两个实例:

访问nacos控制台,可以发现服务注册成功:

点击详情,可以查看到item-service服务的两个实例信息:

4.4 服务发现

服务的消费者要去nacos订阅服务,这个过程就是服务发现,步骤如下:

  • 引入依赖
  • 配置Nacos地址
  • 发现并调用服务
  1. 引入依赖
    服务发现除了要引入nacos依赖以外,由于还需要负载均衡,因此要引入SpringCloud提供的LoadBalancer依赖。
    消费者(如cart-service)中的pom.xml中添加下面的依赖:
xml 复制代码
<!--nacos 服务注册发现-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

可以发现,这里Nacos的依赖于服务注册时一致,这个依赖中同时包含了服务注册和发现的功能。因为任何一个微服务都可以调用别人,也可以被别人调用,即可以是调用者,也可以是提供者。

因此,等一会儿cart-service启动,同样会注册到Nacos

  1. 配置Nacos地址
    在cart-service的application.yml中添加nacos地址配置:
yaml 复制代码
spring:
  cloud:
    nacos:
      server-addr: 192.168.150.101:8848
  1. 发现并调用服务
    服务调用者cart-service就可以去订阅item-service服务了。不过item-service有多个实例,而真正发起调用时只需要知道一个实例的地址。
    因此,服务调用者必须利用负载均衡的算法,从多个实例中挑选一个去访问。常见的负载均衡算法有:
  • 随机
  • 轮询
  • IP的hash
  • 最近最少访问
  • ...
    这里我们可以选择最简单的随机负载均衡

服务发现需要用到一个工具,DiscoveryClient,SpringCloud已经帮我们自动装配,我们可以直接注入使用:

我们就可以对原来的远程调用做修改了,之前调用时我们需要写死服务提供者的IP和端口:

java 复制代码
private void handleCartItems(List<CartVO> vos) {
        // TODO 1.获取商品id
        Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
        // 2.查询商品
//        单体架构,本地调用
        // List<ItemDTO> items = itemService.queryItemByIds(itemIds);

//        RestTemplate远程调用
       /* // 2.1.利用RestTemplate发起http请求,得到http的响应
        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, ","))  //请求参数
        );*/


//     Nacos服务发现
//        2.1 发现item-service服务的实例列表
        List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
//        2.2 负载均衡,挑选一个实例
        ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
//        2.3 发送请求,查询商品
        ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
                instance.getUri() + "/items?ids={ids}",  //请求路径
                HttpMethod.GET, //请求方法
                null,  //请求实体
                new ParameterizedTypeReference<List<ItemDTO>>() {
                },  //返回值类型
                Map.of("ids", CollUtil.join(itemIds, ","))  //请求参数
        );
        // 2.2.解析响应
        if (!response.getStatusCode().is2xxSuccessful()) {
            // 查询失败,直接结束
            return;
        }
        List<ItemDTO> items = response.getBody();
        if (CollUtils.isEmpty(items)) {
            return;
        }
        // 3.转为 id 到 item的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (CartVO v : vos) {
            ItemDTO item = itemMap.get(v.getItemId());
            if (item == null) {
                continue;
            }
            v.setNewPrice(item.getPrice());
            v.setStatus(item.getStatus());
            v.setStock(item.getStock());
        }
    }

经过swagger测试,相关代码在服务拆分部分。

五、OpenFeign

OpenFeign 利用SpringMVC的相关注解来声明请求方式、请求路径、请求参数、返回值类型 4个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写;

5.1 快速入门

  1. 引入依赖
    在cart-service服务的pom.xml中引入OpenFeign的依赖和loadBalancer依赖:
xml 复制代码
  <!--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>
  1. 启用OpenFeign
    在cart-service的CartApplication启动类上添加注解,启动OpenFeign功能:
  2. 编写OpenFeign客户端
    在cart-service中,定义一个新的接口,编写Feign客户端:
java 复制代码
package com.hmall.cart.client;

import com.hmall.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.List;

@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> :返回值类型

OpenFeign就可以利用动态代理 帮我们实现这个方法,并且向http://item-service/items发送一个GET请求,携带ids为请求参数,并自动将返回值处理为List
只需要直接调用这个方法,即可实现远程调用

  1. 使用FeignClient
    在cart-service的com.hmall.cart.service.impl.CartServiceImpl中的代码,直接调用ItemClient的方法:
java 复制代码
private void handleCartItems(List<CartVO> vos) {
        // TODO 1.获取商品id
        Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
        // 2.查询商品
//        单体架构,本地调用
        // List<ItemDTO> items = itemService.queryItemByIds(itemIds);

//        RestTemplate远程调用
       /* // 2.1.利用RestTemplate发起http请求,得到http的响应
        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, ","))  //请求参数
        );*/


//     Nacos服务发现
/*//        2.1 发现item-service服务的实例列表
        List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
//        2.2 负载均衡,挑选一个实例
        ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
//        2.3 发送请求,查询商品
        ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
                instance.getUri() + "/items?ids={ids}",  //请求路径
                HttpMethod.GET, //请求方法
                null,  //请求实体
                new ParameterizedTypeReference<List<ItemDTO>>() {
                },  //返回值类型
                Map.of("ids", CollUtil.join(itemIds, ","))  //请求参数
        );
        // 2.2.解析响应
        if (!response.getStatusCode().is2xxSuccessful()) {
            // 查询失败,直接结束
            return;
        }
        List<ItemDTO> items = response.getBody();*/

//      OpenFeign
        List<ItemDTO> items = itemClient.queryItemByIds(itemIds);
        if (CollUtils.isEmpty(items)) {
            return;
        }
        // 3.转为 id 到 item的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (CartVO v : vos) {
            ItemDTO item = itemMap.get(v.getItemId());
            if (item == null) {
                continue;
            }
            v.setNewPrice(item.getPrice());
            v.setStatus(item.getStatus());
            v.setStock(item.getStock());
        }
    }

feign替我们完成了服务拉取、负载均衡、发送http请求的所有工作,是不是看起来优雅多了。

5.2 连接池

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

  • HttpURLConnection: 默认实现,不支持连接池
  • Apache HttpClient : 支持连接池
  • OKHttp: 支持连接池

通常会使用带有连接池的客户端来代替默认的HttpURLConnection,比如,我们使用OK Http

  1. 引入依赖
    在cart-service的pom.xml中引入依赖:
xml 复制代码
<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>
  1. 开启连接池
    在cart-service的application.yml配置文件中开启Feign的连接池功能:
yaml 复制代码
feign:
  okhttp:
    enabled: true # 开启OKHttp功能

重启服务,连接池就生效了。

  1. 验证

我们可以打断点验证连接池是否生效,在org.springframework.cloud.openfeign.loadbalancer.FeignBlockingLoadBalancerClient中的execute方法中打断点:

Debug方式启动cart-service,请求一次查询我的购物车方法,进入断点:

可以发现这里底层的实现已经改为OkHttpClient

5.3 最佳实践

不同的服务需要请求的接口是一样,如果在每个服务中重新编码,那样耦合性提高了。

  1. 思路分析

    避免重复编码的办法就是抽取:
    思路1: 抽取到微服务之外的公共module,抽取更加简单,工程结构也比较清晰,但缺点是整个项目耦合度偏高;
    思路2: 每个微服务自己抽取一个module,抽取相对麻烦,工程结构相对更复杂,但服务之间耦合度降低;

  2. 抽取Feign客户端

    在hmall下定义一个新的module,命名为hm-api:

    其依赖代码:

xml 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>hmall</artifactId>
        <groupId>com.heima</groupId>
        <version>1.0.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hm-api</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <!--open feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!-- load balancer-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-loadbalancer</artifactId>
        </dependency>
        <!-- swagger 注解依赖 -->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.6.6</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
</project>

把ItemDTO和ItemClient都拷贝过来,最终结构如下:

任何微服务要调用item-service中的接口,只需要引入hm-api模块依赖即可,无需自己编写Feign客户端了

  1. 扫描包
    我们在cart-service的pom.xml中引入hm-api模块
xml 复制代码
  <!--feign模块-->
  <dependency>
      <groupId>com.heima</groupId>
      <artifactId>hm-api</artifactId>
      <version>1.0.0</version>
  </dependency>

删除cart-service中原来的ItemDTO和ItemClient,重启项目。

ItemClient定义到了com.hmall.api.client包下,而cart-service的启动类定义在com.hmall.cart包下,扫描不到ItemClient,所以报错了,解决方法,在cart-service的启动类上添加声明即可,两种方式:
方式1:声明扫描包

方式2:声明要用的FeignClient

5.4 日志配置

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • NONE:不记录任何日志信息,这是默认值。
  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间
  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息
  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。
    Feign默认的日志级别就是NONE,所以默认我们看不到请求日志。
  1. 定义日志级别
    在hm-api模块下新建一个配置类,定义Feign的日志级别:
java 复制代码
package com.hmall.api.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;

public class DefaultFeignConfig {
    @Bean
    public Logger.Level feignLogLevel(){
        return Logger.Level.FULL;
    }
}
  1. 配置
    要让日志级别生效,还需要配置这个类。有两种方式:
  • 局部生效:在某个FeignClient中配置,只对当前FeignClient生效
java 复制代码
@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
  • 全局生效:在@EnableFeignClients中配置,针对所有FeignClient生效。
java 复制代码
@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

日志格式:

复制代码
17:35:32:148 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] ---> GET http://item-service/items?ids=100000006163 HTTP/1.1
17:35:32:148 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] ---> END HTTP (0-byte body)
17:35:32:278 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] <--- HTTP/1.1 200  (127ms)
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] connection: keep-alive
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] content-type: application/json
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] date: Fri, 26 May 2023 09:35:32 GMT
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] keep-alive: timeout=60
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] transfer-encoding: chunked
17:35:32:279 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] 
17:35:32:280 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] [{"id":100000006163,"name":"巴布豆(BOBDOG)柔薄悦动婴儿拉拉裤XXL码80片(15kg以上)","price":67100,"stock":10000,"image":"https://m.360buyimg.com/mobilecms/s720x720_jfs/t23998/350/2363990466/222391/a6e9581d/5b7cba5bN0c18fb4f.jpg!q70.jpg.webp","category":"拉拉裤","brand":"巴布豆","spec":"{}","sold":11,"commentCount":33343434,"isAD":false,"status":2}]
17:35:32:281 DEBUG 18620 --- [nio-8082-exec-1] com.hmall.api.client.ItemClient          : [ItemClient#queryItemByIds] <--- END HTTP (369-byte body)

以上源代码地址

相关推荐
LONGZETECH2 小时前
新能源汽车专业升级,仿真教学软件科学布局指南
人工智能·架构·汽车·汽车仿真教学软件·汽车故障诊断
帅次2 小时前
Android 高级工程师面试参考答案:架构设计、Jetpack 与 Compose
android·面试·职场和发展·架构·composer·jetpack
天涯明月19932 小时前
QClaw完全指南_AI代理网关架构与多代理管理实战
人工智能·架构·大模型·agent
陈天伟教授2 小时前
Gemma 4 模型-可变分辨率(令牌预算)
人工智能·安全·架构
zshs0002 小时前
重读《凤凰架构》,从分布式演进史看技术选型的本质
分布式·后端·架构
生活观察站2 小时前
地铁隧道5G工业专网规划:基于室内覆盖架构的Ranplan全场景解决方案
5g·架构
007张三丰4 小时前
系统架构设计师范文3:论基于架构的软件设计方法及应用(ABSD)
架构·软考·系统架构设计师·架构演化·论文高级·absd
Elastic 中国社区官方博客11 小时前
为 Elastic Cloud Serverless 和 Elasticsearch 引入统一的 API 密钥
大数据·运维·elasticsearch·搜索引擎·云原生·serverless
Agent手记11 小时前
制造业数字化升级:生产全流程企业级智能体落地解决方案 —— 基于LLM+超自动化全栈架构的智改数转深度实战
运维·ai·架构·自动化