一、先白话白话
昨天咱把user-service和order-service都登记到Nacos了,就像俩人都在社区服务中心登了记。但是光登记不中啊,得能互相找人、互相帮忙!
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
实际场景:
order-service(订单服务)想知道是哪个用户下单了,得问user-service(用户服务):
- 以前:order-service得知道user-service住哪(IP:端口),自己写HTTP请求
- 现在:order-service直接喊一声“user-service!”,Nacos负责找地址,OpenFeign负责传话
OpenFeign就是个翻译官+跑腿的:
- 你写个接口,说想调user-service的某个方法
- OpenFeign帮你生成具体实现
- 帮你找到user-service在哪
- 帮你发请求、收响应
- 出问题了还帮你重试
二、为啥不用原生的RestTemplate?
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
RestTemplate也能调用,但是:
// RestTemplate方式(老式写法)
String url = "http://localhost:8081/user/" + userId;
User user = restTemplate.getForObject(url, User.class);
问题:
- 得自己拼URL,麻烦!
- 得自己处理异常,麻烦!
- 负载均衡得自己写,麻烦!
- 改个地址得到处找,麻烦!
OpenFeign写法:
// Feign方式(新式写法)
User user = userClient.getUserById(userId);
优点:
- 跟调本地方法一样简单
- 自动负载均衡
- 自动重试
- 配置统一管理
三、开整!让order-service调user-service
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
准备工作
1. 先完善user-service
在user-service里加个正经接口:
// User.java(先简单点,明天连数据库)
public class User {
private Integer id;
private String name;
private String address;
private String favoriteFood;
// 构造方法、getter、setter省略...
}
// UserController.java
@RestController
@RequestMapping("/user")
public class UserController {
// 模拟数据库
private Map<Integer, User> userMap = new HashMap<>();
public UserController() {
// 初始化几个用户
userMap.put(1, new User(1, "张三", "郑州", "烩面"));
userMap.put(2, new User(2, "李四", "洛阳", "水席"));
userMap.put(3, new User(3, "王五", "开封", "灌汤包"));
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Integer id) {
User user = userMap.get(id);
if (user == null) {
throw new RuntimeException("用户不存在!");
}
return user;
}
@GetMapping("/test")
public String test() {
return "user-service正常工作中...";
}
}
启动user-service,访问http://localhost:8081/user/1看看:
{
"id": 1,
"name": "张三",
"address": "郑州",
"favoriteFood": "烩面"
}
中了!
步骤1:给order-service加OpenFeign依赖
在order-service的pom.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>
步骤2:启动类加注解
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 这个注解是重点!开启Feign
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
步骤3:创建Feign客户端接口
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
新建feign包,里面创建UserClient.java:
@FeignClient(name = "user-service") // 指定要调用的服务名
public interface UserClient {
/**
* 调用user-service的GET /user/{id}接口
* 跟写本地Controller一样,只是注解变@GetMapping
*/
@GetMapping("/user/{id}")
User getUserById(@PathVariable("id") Integer id);
/**
* 调用user-service的GET /user/test接口
*/
@GetMapping("/user/test")
String test();
}
注意:
@FeignClient(name = "user-service")里的name必须跟Nacos里注册的服务名一致- 方法上的注解(
@GetMapping)要跟被调用方一致 - 方法签名(参数、返回值)也要一致
步骤4:写个Controller测试
在order-service里:
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private UserClient userClient; // 注入Feign客户端
/**
* 根据订单ID获取订单信息,顺便获取用户信息
*/
@GetMapping("/{orderId}")
public Map<String, Object> getOrderWithUser(@PathVariable Integer orderId) {
Map<String, Object> result = new HashMap<>();
// 1. 模拟订单信息
result.put("orderId", orderId);
result.put("orderName", "河南特产大礼包");
result.put("status", "已发货");
result.put("address", "河南省郑州市金水区");
// 2. 调用user-service获取用户信息(关键!)
// 假设订单1对应用户1,订单2对应用户2...
Integer userId = orderId;
try {
User user = userClient.getUserById(userId);
result.put("user", user);
} catch (Exception e) {
result.put("user", "获取用户信息失败:" + e.getMessage());
}
return result;
}
/**
* 测试user-service是否正常
*/
@GetMapping("/test/user")
public String testUserService() {
return userClient.test();
}
}
步骤5:测试一下
-
启动所有服务:
- Nacos(8848端口)
- user-service(8081端口)
- order-service(8082端口)
-
访问
http://localhost:8082/order/test/user应该返回:user-service正常工作中... -
访问
http://localhost:8082/order/1应该返回:
{
"orderId": 1,
"orderName": "河南特产大礼包",
"status": "已发货",
"address": "河南省郑州市金水区",
"user": {
"id": 1,
"name": "张三",
"address": "郑州",
"favoriteFood": "烩面"
}
}
得劲! order-service成功调用了user-service!
四、Feign高级配置(稍微了解一下)
1. 超时配置(默认等1秒,太短)
在order-service的application.yml里:
feign:
client:
config:
default: # 全局配置
connect-timeout: 5000 # 连接超时5秒
read-timeout: 5000 # 读取超时5秒
user-service: # 针对user-service的配置
connect-timeout: 3000
read-timeout: 3000
# 或者用ribbon(Feign底层用ribbon)
ribbon:
ConnectTimeout: 5000
ReadTimeout: 5000
2. 开启日志(调试时用)
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
logging:
level:
com.example.order.feign.UserClient: DEBUG # 你的Feign客户端包名
在配置类里:
@Configuration
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; // 详细日志
}
}
日志会显示:
[UserClient#getUserById] ---> GET http://user-service/user/1 HTTP/1.1
[UserClient#getUserById] <--- HTTP/1.1 200 OK (15ms)
3. 失败重试
spring:
cloud:
loadbalancer:
retry:
enabled: true # 开启重试
五、负载均衡(Load Balancing)
啥是负载均衡?
假设user-service有3个实例:
- 实例1:8081端口
- 实例2:8083端口
- 实例3:8084端口
OpenFeign会自动把请求均匀分配给这三个实例,不会让一个实例累死。
测试负载均衡
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
-
启动多个user-service实例:
- 改端口号启动就行
- IDEA里复制配置,改
server.port - 启动8081、8083、8084三个实例
-
在Nacos里能看到3个实例
-
多次访问
http://localhost:8082/order/test/user- 看每个实例的控制台日志
- 请求会被均匀分配
得劲! 不用改代码,自动负载均衡!
六、实际开发中的最佳实践
1. 统一返回结果
// 通用的返回类
public class Result<T> {
private Integer code;
private String message;
private T data;
// ...
}
// Feign客户端返回Result
@FeignClient(name = "user-service")
public interface UserClient {
@GetMapping("/user/{id}")
Result<User> getUserById(@PathVariable Integer id);
}
2. 统一异常处理
// 创建Fallback类(服务降级)
@Component
public class UserClientFallback implements UserClient {
@Override
public User getUserById(Integer id) {
// user-service挂了,返回兜底数据
return new User(id, "默认用户", "河南", "胡辣汤");
}
@Override
public String test() {
return "user-service暂时不可用";
}
}
// Feign客户端指定Fallback
@FeignClient(name = "user-service", fallback = UserClientFallback.class)
public interface UserClient {
// ...
}
3. 放在公共模块
把Feign客户端接口放到一个公共模块,谁想用谁引用。
七、今儿个总结
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
学会了啥?
- ✅ OpenFeign是干啥的(翻译官+跑腿的)
- ✅ 创建Feign客户端接口(
@FeignClient) - ✅ 调用远程服务(跟调本地方法一样)
- ✅ 配置超时、日志
- ✅ 负载均衡(自动的!)
关键点
- 服务名要对:
@FeignClient(name = "user-service")里的name必须跟Nacos里一样 - 注解要一致:方法上的注解要跟被调用方一致
- 依赖要全:OpenFeign + LoadBalancer
- 启动类要加注解:
@EnableFeignClients
八、常见问题
1. 报错:UnknownHostException: user-service
- 检查Nacos里user-service注册上了没
- 检查
@FeignClient的name写对没 - 检查order-service连上Nacos没
2. 报错:Connection refused
- 检查user-service启动了没
- 检查端口号对不
- 等一会儿再试(服务刚启动可能需要时间)
3. 调用返回null
- 检查返回值类型对不
- 检查被调用方接口路径对不
- 开日志看看具体请求和响应
4. 超时问题
- 默认1秒太短,调大点
- 网络不好也会超时
九、明儿个学啥?
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
明天咱学服务熔断降级!
- user-service挂了咋办?
- order-service一直等,等到天荒地老?
- 用Sentinel做熔断降级
- 跟电路保险丝一样,过载就跳闸!
明天咱让服务更坚强,一个挂了不影响别的!🚀