通信方式
网络模型
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 的物理介质) 传输信号。
- 家里的路由器 → 运营商机房 :通过网线(双绞线,有线物理介质) 传输信号。
- 运营商机房 → 阿里云服务器机房 :通过光纤(有线物理介质,长距离传输用光纤更稳定) 传输信号。
- 阿里云服务器机房内部 :服务器通过网线 连接到机房的交换机、路由器等设备,最终把信号传输到目标服务器的网卡上。
简言之,"无线" 只是传输方式的表象 ,其底层仍然依赖 "电磁波" 这种物理介质;而互联网的长距离传输(比如你到阿里云服务器),大多还是靠 "网线、光纤" 这些有线物理介质来保证稳定性和传输效率。物理层的 "物理介质" 是比特流传输的基础载体 ,缺一不可~

问题

- ① 当需要跨语言、跨平台通信,或对服务间耦合度要求低时,采用 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
对服务之间发起请求做负载均衡

使用场景
- 服务注册:订单服务、库存服务(多实例)把自己的 IP 和端口注册到注册中心。
- 服务发现 :订单服务 A 从注册中心获取 "库存服务" 的所有实例列表,本地缓存一份。
- 负载均衡:Ribbon 从库存服务实例列表中,按策略(如轮询、随机)选一个实例。
- 服务调用:通过 RestTemplate/openfein 向选中的库存服务实例发起 HTTP 请求。
实战
一、方式1:DiscoveryClient + 自实现负载均衡
流程
- 服务注册到Nacos
- 消费者通过
DiscoveryClient
获取服务实例列表 - 手动实现负载均衡选择实例
- 调用选中实例接口
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
流程
- 服务注册到Nacos
- 消费者通过
LoadBalancerClient
自动获取并选择实例(内置策略) - 调用选中实例接口
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
流程
- 服务注册到Nacos
- 消费者使用带
@LoadBalanced
的RestTemplate
- 直接通过服务名调用,框架自动完成负载均衡和实例选择
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
- 服务端流程 :启动服务端,暴露
<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
- 消费端流程 :
- 启动消费端,通过
<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 请求发送、响应解析" 等操作。
- 启动消费端,通过
- 参数传递与返回值 :
- 基本类型、对象、数组、集合等参数通过 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 自动解析为字符串或自定义对象。
- 基本类型、对象、数组、集合等参数通过 SpringMVC 注解(
- 超时配置 :在
<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);
}
}