[toc]
| 单词 | 词性 | 音标 | 含义 |
|---|---|---|---|
| direct | adj. | /dəˈrekt/ | 直接的(客户端直接连服务地址,不经过注册中心) |
| remote | adj. | /rɪˈməʊt/ | 远程的(跨进程、跨机器发生的调用) |
| endpoint | n. | /ˈendpɔɪnt/ | 端点(一个可访问的接口地址,如 /user/1) |
| port | n. | /pɔːt/ | 端口(同一台机器上区分不同服务的编号) |
| hardcode | v. | /ˌhɑːdˈkəʊd/ | 硬编码(把地址、参数等直接写死在代码里) |
| serialize | v. | /ˈsɪəriəlaɪz/ | 序列化(对象与网络传输格式之间的相互转换) |
Lecture02-SpringCloud-微服务直连调用(RestTemplate)
目标:先不接注册中心,用最笨也最直观的方式——
IP + 端口 + RestTemplate——让两个 Spring Boot 应用互相调用一次。先亲手摸到"微服务之间的网络调用"是什么感觉,再回头理解它有什么毛病,为下一讲 Nacos 做铺垫。
1、先分清:本地调用 vs 远程调用
1、单体里的调用(不出 JVM)
单体应用里,用户、订单、商品都在同一个进程中,OrderService 想拿用户数据,直接调方法就行:
// 同一个 JVM 内,方法调用,不走网络
User user = userService.getUser(1L);
特点:快、简单,但前提是"大家住在一起"。
2、微服务里的调用(必须走网络)
微服务拆开之后,user-service 和 order-service 是两个独立进程(可能还在两台机器上),各有各的 JVM、各有各的端口:
┌───────────────┐ HTTP ┌───────────────┐
│ order-service │ ──────────────────▶ │ user-service │
│ (订单,8083) │ http://…:8081/user/1│ (用户,8081) │
└───────────────┘ └───────────────┘
两个独立进程(两个 JVM)
order-service 想拿用户数据,只能通过网络发一个 HTTP 请求过去——这就是"分布式调用",也是微服务最基本的动作。
一句话:跨进程 = 跨网络。微服务的第一课,就是学会"隔着网络调别人的接口"。
2、准备两个服务
在上一讲的 cloud-parent 父工程下建两个子模块。
1、user-service(服务提供者)
user-service/pom.xml 加 Web 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
user-service/src/main/resources/application.yml:
server:
port: 8081
spring:
application:
name: user-service
写一个查询用户的接口:
@RestController
public class UserController {
@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id) {
return "user-" + id + " @ user-service(8081)";
}
}
启动后能单独访问:http://localhost:8081/user/1
2、order-service(服务消费者)
同样加 spring-boot-starter-web 依赖,配置里注意端口不同:
server:
port: 8083
spring:
application:
name: order-service
RestTemplate 在 spring-web 里(spring-boot-starter-web 已包含),不需要额外加依赖。先把它声明成一个 Bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3、直连调用:把地址写死在代码里
order-service 直接按 http://IP:端口 去调 user-service:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class OrderController {
@Autowired
private RestTemplate restTemplate;
// user-service 的地址被"写死"在这里 —— 这就是最原始的做法
private static final String USER_SERVICE_URL = "http://localhost:8081";
@GetMapping("/order/call")
public String callUser(@RequestParam Long id) {
String url = USER_SERVICE_URL + "/user/" + id;
// getForObject:发 GET 请求,并把响应体转换成指定类型(这里是 String)
return restTemplate.getForObject(url, String.class);
}
}
跑起来的完整链路
- 启动
user-service(8081)和order-service(8083)。 - 浏览器访问:
http://localhost:8083/order/call?id=1 - 页面返回:
user-1 @ user-service(8081)
浏览器 ──▶ order-service(8083) ──HTTP──▶ user-service(8081)
◀────── "user-1 @ user-service(8081)" ──────
结论:两个服务确实是"隔着网络"在通信——你已经把一个微服务调起来了。
RestTemplate常用方法:getForObject(url, 返回类型)发 GET;postForObject(url, 请求体, 返回类型)发 POST;exchange(...)用于需要自定义请求头/方法的场景。
4、这种写法的痛点
跑通只是开始,把上面的代码拿去做真实项目,会立刻撞上四个问题:
| 问题 | 具体表现 | 后果 |
|---|---|---|
| ① 地址写死 | USER_SERVICE_URL 写死在代码里 | 换个 IP、改个端口就要改代码重新打包 |
| ② 无法负载均衡 | 只能指向一个实例 | 3 个 user-service 也只会打到这一个,白扩 |
| ③ 实例挂掉无感知 | 目标服务宕机,调用直接失败 | 没有自动切换、没有兜底 |
| ④ 多环境难维护 | 开发/测试/生产地址各不相同 | 到处改配置,容易漏、容易错 |
思考一下:既然
spring.application.name已经给服务起好名字了,为什么调用时还是得写 IP?因为"服务名"目前只是个名字,没有任何地方记录"这个名字对应哪些 IP:端口"。
谁来记这个"通讯录"?——注册中心。第 03 讲的 Nacos 就是来收编这些问题的。
5、课堂练习
- 在
cloud-parent下建好user-service、order-service,把上面这次直连调用跑通。 - 把
user-service的端口从8081改成8091,不改order-service的代码,再访问一次——观察报错,体会"地址写死"的代价。 - (思考)如果有 3 个
user-service实例同时运行,现在的写法能不能把请求分到 3 个实例上?为什么?
6、本讲小结
- 本地调用在同一 JVM 内走方法栈;远程调用必须跨网络,这是微服务的常态。
RestTemplate是 Spring 提供的 HTTP 客户端封装,getForObject(url, 返回类型)一行就能完成调用。- 直连调用虽然能跑通,但暴露了 地址写死、无法负载均衡、无感知宕机、多环境难维护 四个痛点。
- 解决思路:让服务启动时把自己的地址登记到"通讯录",调用方按服务名去查——这就是服务注册与发现。
7、本节验证
- 能说清"单体内部方法调用"与"微服务远程调用"的本质区别 ✅
- 会用
RestTemplate完成一次跨服务的 HTTP 调用(getForObject)✅ - 能说出 IP 直连写法的至少三条痛点 ✅
- 能解释"为什么已经有服务名了,调用还是得写 IP" ✅