微服务-网络模型与服务通信方式openfein

通信方式

网络模型

OSI的七层模型

1. 应用层
  • 作用 :为应用程序提供网络服务,定义应用间通信的协议规范
  • 前后端例子 :前端浏览器(如 Chrome)通过HTTP 协议 向后端服务器发起接口请求(如获取用户信息的<font style="color:rgb(0, 0, 0);">GET /api/user</font>);后端通过同样的协议返回 JSON 格式的用户数据。常见协议还有 HTTPS(加密的 HTTP)、FTP(文件传输)等。
2. 表示层
  • 作用 :负责数据的格式转换、加密 / 解密、压缩 / 解压缩,确保两端应用能识别相同格式的数据。
  • 前后端例子 :前端将用户输入的中文姓名、密码等数据,通过JSON 格式封装(转换为标准数据格式);若涉及敏感信息(如密码),还会通过 ** 加密算法(如 MD5)** 处理后再传输;后端收到后解密、解析 JSON 格式,得到原始数据。
3. 会话层
  • 作用:建立、管理和维护应用间的通信会话(如长连接、会话同步)。
  • 前后端例子 :用户登录后,后端会生成一个会话 ID(Session ID) 并通过 Cookie 传给前端;后续前端每次调用接口时,都会携带这个 Session ID,会话层负责 "识别这个用户的会话是否有效",管理前后端的通信连接(比如超时断开后重新建立)。

在OSI五层模型中,把前三层归类为应用层

4. 传输层
  • 作用:建立端到端的可靠 / 不可靠数据传输,处理差错控制、流量控制。
  • 前后端例子 :前端调用后端接口时,数据通过TCP 协议 传输(确保数据完整、有序到达);若前端是移动端 App,也可能用UDP 协议(如实时聊天场景,追求速度牺牲部分可靠性)。传输层的 "端口号" 用于区分后端的不同服务(如后端 HTTP 服务监听 80 端口)。
5. 网络层
  • 作用:通过 IP 地址进行寻址,选择数据传输的路由(即 "走哪条路能到后端服务器")。
  • 前后端例子 :前端设备(如你的电脑)通过IP 地址 (如后端服务器的公网 IP <font style="color:rgb(0, 0, 0);">111.222.333.444</font>)确定目标位置;网络层的路由器会根据 IP 路由表,把数据从你的网络 "转发" 到后端服务器所在的网络。
6. 数据链路层
  • 作用:负责相邻设备间的链路管理、数据帧的封装 / 解封装,使用 MAC 地址进行介质访问。
  • 前后端例子 :你的电脑通过网卡(物理设备)的MAC 地址,在局域网内与路由器通信;数据链路层会把网络层的 IP 数据包封装成 "数据帧",通过网线(或无线信号)传输到路由器,同时处理链路中的差错(如数据帧损坏重传)。
7. 物理层
  • 作用:定义物理介质的电气特性、传输速率等,负责比特流的实际传输(最底层的硬件传输)。
  • 前后端例子 :你的电脑通过网线(双绞线)无线电磁波(WiFi),将数据的电信号 / 光信号传输到路由器;。常见物理设备有网线、网卡、中继器等


后端服务器也通过物理介质接收这些信号 为什么需要通过物理介质 现在的wifi不是无线的吗 比如我使用的是阿里云服务器和我的电脑

1. 物理层的核心:"比特流的物理传输" 必须依赖介质

物理层的作用是传输原始的比特流(0 和 1 的电信号、光信号等) ,无论 "有线" 还是 "无线",都需要物理介质 来承载这些信号:

  • 对于有线网络 (如网线、光纤):通过铜导线的电信号、光纤的光信号来传输比特流,"网线、光纤" 就是物理介质。
  • 对于无线网络(WiFi) :看似 "无线",但实际上是通过电磁波(无线电波) 作为物理介质来传输信号的。你的电脑通过无线网卡发射 / 接收电磁波,路由器也通过电磁波转发信号 ------电磁波就是 WiFi 的 "物理介质" ,它是客观存在的物理载体。

2. 以 "你用阿里云服务器和电脑" 为例,完整链路的物理介质拆解

当你在电脑上通过 WiFi 调用阿里云服务器的接口时,整个流程的物理介质是 "分段接力" 的:

  • 你的电脑 → 家里的路由器 :通过电磁波(WiFi 的物理介质) 传输信号。
  • 家里的路由器 → 运营商机房 :通过网线(双绞线,有线物理介质) 传输信号。
  • 运营商机房 → 阿里云服务器机房 :通过光纤(有线物理介质,长距离传输用光纤更稳定) 传输信号。
  • 阿里云服务器机房内部 :服务器通过网线 连接到机房的交换机、路由器等设备,最终把信号传输到目标服务器的网卡上。

简言之,"无线" 只是传输方式的表象 ,其底层仍然依赖 "电磁波" 这种物理介质;而互联网的长距离传输(比如你到阿里云服务器),大多还是靠 "网线、光纤" 这些有线物理介质来保证稳定性和传输效率。物理层的 "物理介质" 是比特流传输的基础载体 ,缺一不可~
![](https://i-blog.csdnimg.cn/img_convert/4a68ac6cf4e970711cd0cde669305443.png)

问题

  • 当需要跨语言、跨平台通信,或对服务间耦合度要求低时,采用 HTTP 方式。HTTP 是应用层协议,不限制语言和技术栈,适合多语言异构系统或对外暴露 API 的场景。
  • 当服务间是同语言技术栈,且对通信性能、调用体验(如像本地方法调用)要求高时,采用 RPC 方式。RPC 更贴近 "进程间调用" 的体验,性能损耗小,适合内部服务的高频、低延迟通信。
  • Spring Cloud 倾向于 HTTP 方式(基于 RESTful 风格的 HTTP 通信,如 Feign 组件),因为它能很好地支持 Spring 生态的微服务架构,且兼容多语言场景的扩展性需求。

RestTemplate

RestTemplate 是 Spring 框架提供的HTTP 请求工具类,从 Spring 3.0 开始支持,用于简化 Java 代码中对 RESTful 服务的调用。它封装了 Apache HttpClient 等底层 HTTP 客户端的繁琐操作,提供了 GET、POST、PUT、DELETE 等 REST 风格请求的模板方法,让开发者能更便捷地发起 HTTP 通信(方便调用其他服务)。

作用

  • 简化 HTTP 请求流程:无需手动处理连接、请求头、响应解析等细节,通过简洁的 API 即可完成 RESTful 接口调用。
  • 统一 REST 操作模板:封装了 REST 风格的常用操作(如资源查询、新增、修改、删除),让代码更规范、易维护。

使用场景

  • 微服务间 HTTP 通信:在 Spring 生态的微服务架构中,服务间通过 RESTful 接口交互时,用 RestTemplate 可快速实现跨服务调用(如订单服务调用用户服务的接口)。
  • 调用外部 REST 接口:当需要调用第三方平台的 RESTful API(如支付接口、天气接口)时,RestTemplate 能便捷地发起请求并解析响应。
  • 内部系统的 REST 交互:企业内部系统间基于 REST 风格的接口通信,可通过 RestTemplate 简化开发,提升效率。

缺点:单独的RestTemplate 不能负载均衡,得手写配置,麻烦且不便于服务切换。

Ribbon

对服务之间发起请求做负载均衡

使用场景

  1. 服务注册:订单服务、库存服务(多实例)把自己的 IP 和端口注册到注册中心。
  2. 服务发现 :订单服务 A 从注册中心获取 "库存服务" 的所有实例列表,本地缓存一份。
  3. 负载均衡:Ribbon 从库存服务实例列表中,按策略(如轮询、随机)选一个实例。
  4. 服务调用:通过 RestTemplate/openfein 向选中的库存服务实例发起 HTTP 请求。

实战

一、方式1:DiscoveryClient + 自实现负载均衡
流程
  1. 服务注册到Nacos
  2. 消费者通过DiscoveryClient获取服务实例列表
  3. 手动实现负载均衡选择实例
  4. 调用选中实例接口
YAML配置(消费者)
yaml 复制代码
server:
  port: 8080  # 消费者端口

spring:
  application:
    name: ribbon-consumer  # 消费者服务名
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  # Nacos注册中心地址
核心代码
java 复制代码
@RestController
public class ConsumerController {
    // 注入服务发现客户端,用于从注册中心获取服务实例信息
    @Autowired
    private DiscoveryClient discoveryClient;
    
    // 注入普通的RestTemplate(未添加@LoadBalanced注解)
    // 仅用于发送HTTP请求,不具备自动负载均衡能力
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/call")
    public String callService() {
        // 1. 通过服务名从注册中心获取所有可用实例列表
        // 服务名"ribbon-producer"对应所有application.name为该值的服务实例
        // 返回结果例如:[192.168.1.10:9001, 192.168.1.11:9002, 127.0.0.1:9003]
        List<ServiceInstance> instances = discoveryClient.getInstances("ribbon-producer");
        
        // 2. 手动实现随机负载均衡策略
        // 创建随机数生成器,范围是实例列表的大小(0到列表长度-1)
        int randomIndex = new Random().nextInt(instances.size());
        // 根据随机索引从列表中选择一个服务实例
        ServiceInstance selectedInstance = instances.get(randomIndex);
        
        // 3. 拼接完整请求地址并调用服务
        // getUri()方法返回实例的基础地址(如http://192.168.1.10:9001)
        // 拼接具体接口路径(如/provider/hello)形成完整URL
        String requestUrl = selectedInstance.getUri() + "/provider/hello";
        
        // 使用RestTemplate发送GET请求,接收返回的字符串结果
        // 这里实际调用的是选中实例的接口(如http://192.168.1.10:9001/provider/hello)
        String result = restTemplate.getForObject(requestUrl, String.class);
        
        return "调用结果:" + result + "(来自实例:" + selectedInstance.getHost() + ":" + selectedInstance.getPort() + ")";
    }
}

// RestTemplate配置类:用于创建并注册RestTemplate实例到Spring容器
@Configuration
public class RestTemplateConfig {
    // 注册一个普通的RestTemplate Bean
    // 不添加@LoadBalanced注解,因此不具备自动负载均衡能力
    // 只能通过完整URL(包含IP和端口)调用服务
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
    
二、方式2:LoadBalancerClient + RestTemplate
流程
  1. 服务注册到Nacos
  2. 消费者通过LoadBalancerClient自动获取并选择实例(内置策略)
  3. 调用选中实例接口
YAML配置(消费者)
yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: ribbon-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    # 可选:配置负载均衡策略(默认轮询)
    loadbalancer:
      ribbon:
        enabled: true  # 启用Ribbon负载均衡(旧版Spring Cloud)
核心代码
java 复制代码
@RestController
public class ConsumerController {
    // 注入负载均衡客户端,用于自动获取服务实例并实现负载均衡
    // 内部集成了Ribbon的负载均衡策略(默认轮询)
    @Autowired
    private LoadBalancerClient loadBalancerClient;
    
    // 注入普通的RestTemplate(未添加@LoadBalanced注解)
    // 仅负责发送HTTP请求,不处理负载均衡逻辑
    @Autowired
    private RestTemplate restTemplate;

    @GetMapping("/call")
    public String callService() {
        // 1. 通过服务名自动选择一个服务实例(内置负载均衡策略)
        // 方法内部会查询注册中心获取"ribbon-producer"的所有实例
        // 并根据默认的轮询策略(可配置)选择一个健康实例
        ServiceInstance selectedInstance = loadBalancerClient.choose("ribbon-producer");
        
        // 2. 拼接完整请求地址并调用服务
        // 从选中实例中获取基础URL(如http://192.168.1.11:9002)
        String baseUrl = selectedInstance.getUri().toString();
        // 拼接接口路径,形成完整请求地址
        String requestUrl = baseUrl + "/provider/hello";
        
        // 使用RestTemplate发送GET请求,获取接口返回结果
        String result = restTemplate.getForObject(requestUrl, String.class);
        
        return "调用结果:" + result + "(来自实例:" + selectedInstance.getHost() + ":" + selectedInstance.getPort() + ")";
    }
}

// RestTemplate配置类
@Configuration
public class RestTemplateConfig {
    // 注册普通RestTemplate Bean(无@LoadBalanced注解)
    // 负载均衡逻辑由LoadBalancerClient单独处理,与RestTemplate解耦
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
    
三、方式3:@LoadBalanced + RestTemplate
流程
  1. 服务注册到Nacos
  2. 消费者使用带@LoadBalancedRestTemplate
  3. 直接通过服务名调用,框架自动完成负载均衡和实例选择
YAML配置(消费者)
yaml 复制代码
server:
  port: 8080

spring:
  application:
    name: ribbon-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    # 配置负载均衡策略(针对特定服务)
    loadbalancer:
      client:
        config:
          ribbon-producer:  # 服务名
            # 可选策略:RandomLoadBalancer(随机)、RoundRobinLoadBalancer(轮询)
            type: RandomLoadBalancer
核心代码
java 复制代码
@RestController
public class ConsumerController {
    // 注入带有@LoadBalanced注解的RestTemplate
    // 该实例已被增强,具备自动负载均衡能力
    @Autowired
    private RestTemplate loadBalancedRestTemplate;

    @GetMapping("/call")
    public String callService() {
        // 1. 直接使用服务名拼接请求路径
        // "ribbon-producer"是目标服务的application.name
        // RestTemplate会自动将服务名转换为具体实例的IP:端口
        String requestUrl = "http://ribbon-producer/provider/hello";
        
        // 2. 发起请求并获取结果(内部自动完成负载均衡)
        // 底层流程:
        // - 解析服务名"ribbon-producer"
        // - 从注册中心获取该服务的所有实例
        // - 根据配置的负载均衡策略(如随机、轮询)选择一个实例
        // - 替换服务名为实例的实际IP:端口,发起HTTP请求
        String result = loadBalancedRestTemplate.getForObject(requestUrl, String.class);
        
        return "调用结果:" + result;
    }
}

// RestTemplate配置类
@Configuration
public class RestTemplateConfig {
    // 注册带有@LoadBalanced注解的RestTemplate
    // 该注解会触发Spring的自动配置,为RestTemplate添加负载均衡拦截器
    // 使得RestTemplate能够识别服务名并自动完成实例选择
    @LoadBalanced
    @Bean
    public RestTemplate loadBalancedRestTemplate() {
        return new RestTemplate();
    }
}
    
服务提供者provider的配置
yaml 复制代码
# 服务提供者YAML(多实例启动时修改port后继续启动即可得到同一服务的多个实例(即可以去做负载均衡))
server:
  port: 9001  # 实例1端口,另一实例可设为9002

spring:
  application:
    name: ribbon-producer  # 服务名,所有实例保持一致
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848

不同的负载均衡策略

OpenFeign-服务之间调用

OpenFeign 是 Spring Cloud 的声明式伪 HTTP 客户端,基于注解简化微服务间 HTTP 调用,默认集成 Ribbon 实现负载均衡,让开发者能像调用本地方法一样调用远程服务接口。

实战

一、服务端(Producer):暴露可被调用的接口

:::color4

  1. 服务端流程 :启动服务端,暴露<font style="color:rgba(0, 0, 0, 0.85) !important;">/api/param/xxx</font>系列接口,若配置了 Nacos 则注册到注册中心。

:::

1. 依赖配置(pom.xml)

xml

xml 复制代码
<dependencies>
    <!-- Spring Boot 基础依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Nacos 服务注册(可选,若需要注册到注册中心) -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>
2. YAML 配置(application.yml)

yaml

yaml 复制代码
server:
  port: 8001  # 服务端端口
spring:
  application:
    name: openfeign-producer  # 服务名,消费端通过该名称调用
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  # Nacos 注册中心地址(若使用)
3. Controller:定义可被调用的接口

java

运行

java 复制代码
import org.springframework.web.bind.annotation.*;

@RestController
public class ProducerController {

    // ① 基本类型参数:接收单个ID
    @GetMapping("/api/param/basic/{id}")
    public String basicParam(@PathVariable Long id) {
        return "基本类型参数:ID = " + id;
    }

    // ② 对象类型参数:接收User对象
    @PostMapping("/api/param/object")
    public String objectParam(@RequestBody User user) {
        return "对象类型参数:User = " + user;
    }

    // ③ 数组类型参数:接收ID数组
    @GetMapping("/api/param/array")
    public String arrayParam(@RequestParam Long[] ids) {
        StringBuilder sb = new StringBuilder("数组类型参数:");
        for (Long id : ids) {
            sb.append(id).append(", ");
        }
        return sb.toString();
    }

    // ④ 集合类型参数:接收ID列表
    @GetMapping("/api/param/list")
    public String listParam(@RequestParam List<Long> ids) {
        StringBuilder sb = new StringBuilder("集合类型参数:");
        for (Long id : ids) {
            sb.append(id).append(", ");
        }
        return sb.toString();
    }

    // 用于对象参数的实体类
    static class User {
        private Long id;
        private String name;
        // 省略getter/setter
        @Override
        public String toString() {
            return "User{id=" + id + ", name='" + name + "'}";
        }
    }
}
二、消费端(Consumer):通过 OpenFeign 调用服务端

:::color4

  1. 消费端流程
    • 启动消费端,通过<font style="color:rgb(0, 0, 0);">@EnableFeignClients</font>开启 OpenFeign 功能;
    • 定义<font style="color:rgb(0, 0, 0);">ProducerFeignClient</font>接口,声明要调用的服务端接口及参数规则;
    • 在 Controller 中注入<font style="color:rgb(0, 0, 0);">ProducerFeignClient</font>,像调用本地方法一样发起远程请求;
    • OpenFeign 自动完成 "服务发现(若用 Nacos)、负载均衡、HTTP 请求发送、响应解析" 等操作。
  2. 参数传递与返回值
    • 基本类型、对象、数组、集合等参数通过 SpringMVC 注解(<font style="color:rgb(0, 0, 0);">@PathVariable</font><font style="color:rgb(0, 0, 0);">@RequestBody</font><font style="color:rgb(0, 0, 0);">@RequestParam</font>)声明,与服务端接口一一对应;
    • 返回值由 OpenFeign 自动解析为字符串或自定义对象。
  3. 超时配置 :在<font style="color:rgba(0, 0, 0, 0.85) !important;">application.yml</font>中通过<font style="color:rgba(0, 0, 0, 0.85) !important;">feign.client.config</font>配置指定服务的超时时间,避免因服务端响应慢导致请求卡住。

:::

1. 依赖配置(pom.xml)

xml

xml 复制代码
<dependencies>
    <!-- Spring Boot 基础依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- OpenFeign 核心依赖 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <!-- Nacos 服务发现(可选,若服务端注册到Nacos) -->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
</dependencies>
2. YAML 配置(application.yml)

yaml

yaml 复制代码
server:
  port: 8002  # 消费端端口
spring:
  application:
    name: openfeign-consumer
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848  # Nacos 地址(若使用)
# OpenFeign 超时配置
feign:
  client:
    config:
      openfeign-producer:  # 针对服务端服务名的配置
        connectTimeout: 5000  # 连接超时(毫秒)
        readTimeout: 5000     # 读取超时(毫秒)
3. OpenFeign 客户端接口:方便controller直接调用
java 复制代码
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;

// 指定要调用的服务名:即这里会根据服务名去nacos找对应的实例ip地址
@FeignClient(name = "openfeign-producer")
public interface ProducerFeignClient {

    // ① 调用基本类型参数接口
    @GetMapping("/api/param/basic/{id}")
    String basicParam(@PathVariable("id") Long id);
    
    // 使用springmvc的相关注解,使得跟http调用方式类似,即这里会去
    // 组装成 http://openfeign-producer/api/param/basic/{id} 去调用另一个服务

    // ② 调用对象类型参数接口
    @PostMapping("/api/param/object")
    String objectParam(@RequestBody ProducerController.User user);

    // ③ 调用数组类型参数接口
    @GetMapping("/api/param/array")
    String arrayParam(@RequestParam("ids") Long[] ids);

    // ④ 调用集合类型参数接口
    @GetMapping("/api/param/list")
    String listParam(@RequestParam("ids") List<Long> ids);
}
4. 消费端 Controller:触发 OpenFeign 调用

java

运行

java 复制代码
import org.springframework.web.bind.annotation.*;
import java.util.Arrays;
import java.util.List;

@RestController
public class ConsumerController {

    private final ProducerFeignClient producerFeignClient;

    public ConsumerController(ProducerFeignClient producerFeignClient) {
        this.producerFeignClient = producerFeignClient;
    }

    // 测试基本类型参数传递
    @GetMapping("/call/basic/{id}")
    public String callBasic(@PathVariable Long id) {
        // 直接调用Feign接口,像本地方法一样,实际调用另一个服务的流程已经在ProducerFeignClient接口定义好了
        return producerFeignClient.basicParam(id);

        // 这里如果是写成:String result=producerFeignClient.basicParam(id); 则会得到返回结果
    }

    // 测试对象类型参数传递
    @PostMapping("/call/object")
    public String callObject(@RequestBody ProducerController.User user) {
        return producerFeignClient.objectParam(user);
    }

    // 测试数组类型参数传递
    @GetMapping("/call/array")
    public String callArray() {
        Long[] ids = {1L, 2L, 3L};
        return producerFeignClient.arrayParam(ids);
    }

    // 测试集合类型参数传递
    @GetMapping("/call/list")
    public String callList() {
        List<Long> ids = Arrays.asList(4L, 5L, 6L);
        return producerFeignClient.listParam(ids);
    }
}
5. 启动类:开启 OpenFeign 功能
java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients  // 开启OpenFeign客户端功能
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}
相关推荐
2301_809815252 小时前
网络协议——UDP&TCP协议
网络·网络协议·udp
喂完待续3 小时前
【序列晋升】38 Spring Data MongoDB 的统一数据访问范式与实践
java·spring·spring cloud·big data·序列晋升
Yeats_Liao3 小时前
Java网络编程(一):从BIO到NIO的技术演进
java·网络·nio
Jabes.yang3 小时前
互联网大厂Java面试:从Spring到Kafka的技术挑战
spring boot·spring cloud·eureka·kafka·mybatis·jpa·java面试
ISACA中国3 小时前
《第四届数字信任大会》精彩观点:腾讯经验-人工智能安全风险之应对与实践|从十大风险到企业级防护架构
人工智能·安全·架构·漏洞案例·大模型越狱·企业级防护
GIS数据转换器4 小时前
2025无人机在低空物流中的应用实践
大数据·网络·人工智能·安全·无人机
YC运维4 小时前
LNMP架构(分离部署)PHP与数据库交互示例
数据库·架构·php
无小道5 小时前
了解交换机,集线器,中继器,路由器
网络
2501_920047035 小时前
docker-容器网络类型
网络·docker·容器