一、先白话白话
昨天咱把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方式(老式写法)Stringurl="http://localhost:8081/user/"+userId;Useruser=restTemplate.getForObject(url,User.class);问题:
- 得自己拼URL,麻烦!
- 得自己处理异常,麻烦!
- 负载均衡得自己写,麻烦!
- 改个地址得到处找,麻烦!
OpenFeign写法:
// Feign方式(新式写法)Useruser=userClient.getUserById(userId);优点:
- 跟调本地方法一样简单
- 自动负载均衡
- 自动重试
- 配置统一管理
三、开整!让order-service调user-service
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
准备工作
1. 先完善user-service
在user-service里加个正经接口:
// User.java(先简单点,明天连数据库)publicclassUser{privateIntegerid;privateStringname;privateStringaddress;privateStringfavoriteFood;// 构造方法、getter、setter省略...}// UserController.java@RestController@RequestMapping("/user")publicclassUserController{// 模拟数据库privateMap<Integer,User>userMap=newHashMap<>();publicUserController(){// 初始化几个用户userMap.put(1,newUser(1,"张三","郑州","烩面"));userMap.put(2,newUser(2,"李四","洛阳","水席"));userMap.put(3,newUser(3,"王五","开封","灌汤包"));}@GetMapping("/{id}")publicUsergetUserById(@PathVariableIntegerid){Useruser=userMap.get(id);if(user==null){thrownewRuntimeException("用户不存在!");}returnuser;}@GetMapping("/test")publicStringtest(){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// 这个注解是重点!开启FeignpublicclassOrderServiceApplication{publicstaticvoidmain(String[]args){SpringApplication.run(OrderServiceApplication.class,args);}}步骤3:创建Feign客户端接口
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
新建feign包,里面创建UserClient.java:
@FeignClient(name="user-service")// 指定要调用的服务名publicinterfaceUserClient{/** * 调用user-service的GET /user/{id}接口 * 跟写本地Controller一样,只是注解变@GetMapping */@GetMapping("/user/{id}")UsergetUserById(@PathVariable("id")Integerid);/** * 调用user-service的GET /user/test接口 */@GetMapping("/user/test")Stringtest();}注意:
@FeignClient(name = "user-service")里的name必须跟Nacos里注册的服务名一致- 方法上的注解(
@GetMapping)要跟被调用方一致 - 方法签名(参数、返回值)也要一致
步骤4:写个Controller测试
在order-service里:
@RestController@RequestMapping("/order")publicclassOrderController{@AutowiredprivateUserClientuserClient;// 注入Feign客户端/** * 根据订单ID获取订单信息,顺便获取用户信息 */@GetMapping("/{orderId}")publicMap<String,Object>getOrderWithUser(@PathVariableIntegerorderId){Map<String,Object>result=newHashMap<>();// 1. 模拟订单信息result.put("orderId",orderId);result.put("orderName","河南特产大礼包");result.put("status","已发货");result.put("address","河南省郑州市金水区");// 2. 调用user-service获取用户信息(关键!)// 假设订单1对应用户1,订单2对应用户2...IntegeruserId=orderId;try{Useruser=userClient.getUserById(userId);result.put("user",user);}catch(Exceptione){result.put("user","获取用户信息失败:"+e.getMessage());}returnresult;}/** * 测试user-service是否正常 */@GetMapping("/test/user")publicStringtestUserService(){returnuserClient.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:3000read-timeout:3000# 或者用ribbon(Feign底层用ribbon)ribbon:ConnectTimeout:5000ReadTimeout:50002. 开启日志(调试时用)
零基础全栈开发Java微服务版本实战-后端-前端-运维-实战企业级三个实战项目
资源获取:关注公众号: 小坏说Java ,获取本文所有示例代码、配置模板及导出工具。
logging:level:com.example.order.feign.UserClient:DEBUG# 你的Feign客户端包名在配置类里:
@ConfigurationpublicclassFeignConfig{@BeanpublicLogger.LevelfeignLoggerLevel(){returnLogger.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. 统一返回结果
// 通用的返回类publicclassResult<T>{privateIntegercode;privateStringmessage;privateTdata;// ...}// Feign客户端返回Result@FeignClient(name="user-service")publicinterfaceUserClient{@GetMapping("/user/{id}")Result<User>getUserById(@PathVariableIntegerid);}2. 统一异常处理
// 创建Fallback类(服务降级)@ComponentpublicclassUserClientFallbackimplementsUserClient{@OverridepublicUsergetUserById(Integerid){// user-service挂了,返回兜底数据returnnewUser(id,"默认用户","河南","胡辣汤");}@OverridepublicStringtest(){return"user-service暂时不可用";}}// Feign客户端指定Fallback@FeignClient(name="user-service",fallback=UserClientFallback.class)publicinterfaceUserClient{// ...}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做熔断降级
- 跟电路保险丝一样,过载就跳闸!
明天咱让服务更坚强,一个挂了不影响别的!🚀