Spring Cloud构建微服务架构:服务消费者
🎈 选型
| 方式 | 类型 | 协议 | 是否声明式 | 负载均衡 | 注册中心集成 | 适用场景 |
|---|---|---|---|---|---|---|
LoadBalancerClient | 底层 API | HTTP | ❌ | ✅(手动选实例) | ✅ | 需精细控制选例逻辑 |
RestTemplate(Ribbon) | 模板类 | HTTP | ❌ | ✅(自动) | ✅ | 简单 HTTP 调用(已不推荐新项目使用) |
@FeignClient | 声明式客户端 | HTTP | ✅ | ✅(自动) | ✅ | 接口化开发、HTTP 服务间调用 |
@DubboReference | RPC 代理 | TCP(二进制) | ✅ | ✅(自动) | ✅ | 高性能、低延迟、强一致内部调用 |
✅一句话选型建议:
- 写 REST API → 选
@FeignClient;- 写核心链路/高并发内部服务 → 选
@DubboReference;- 学原理或特殊调度 → 用
LoadBalancerClient;RestTemplate + Ribbon仅用于兼容老系统。
🎈 使用LoadBalancerClient
1、服务端和客户端配置
--✅**服务端`application.yml`**(以`user-service` 为例):spring:application:name:user-serviceserver:port:8081# 若对接 Eureka(推荐)eureka:client:service-url:defaultZone:http://localhost:8761/eureka/--✅**客户端`application.yml`**(调用方):spring:application:name:order-serviceserver:port:8080# 同样注册到同一注册中心(如 Eureka),才能发现 user-serviceeureka:client:service-url:defaultZone:http://localhost:8761/eureka/2、服务端代码
@RestController@RequestMapping("/api")publicclassUserController{@GetMapping("/user/{id}")publicMap<String,Object>getUser(@PathVariableLongid){returnMap.of("id",id,"name","Charlie","age",28,"timestamp",System.currentTimeMillis());}}3、客户端代码
@ServicepublicclassUserService{privatefinalLoadBalancerClientloadBalancerClient;privatefinalRestTemplaterestTemplate;publicUserService(LoadBalancerClientloadBalancerClient,RestTemplaterestTemplate){this.loadBalancerClient=loadBalancerClient;this.restTemplate=restTemplate;}publicMap<String,Object>fetchUser(Longid){// ① 通过服务名获取一个可用实例ServiceInstanceinstance=loadBalancerClient.choose("user-service");if(instance==null){thrownewIllegalStateException("No instances available for user-service");}// ② 拼接完整请求 URL(http://host:port/path)Stringurl=String.format("http://%s:%d/api/user/%d",instance.getHost(),instance.getPort(),id);// ③ 发起同步 HTTP GET 请求returnrestTemplate.getForObject(url,Map.class);}}@ConfigurationpublicclassRestTemplateConfig{@BeanpublicRestTemplaterestTemplate(){returnnewRestTemplate();}}🎈使用RestTemplate(Ribbon版)
1、服务端和客户端配置
--✅**服务端`application.yml`**(`user-service`,注册到Eureka):spring:application:name:user-serviceserver:port:8081eureka:client:service-url:defaultZone:http://localhost:8761/eureka/instance:prefer-ip-address:true--✅**客户端`application.yml`**(`d-eureka-consumer-ribbon`):spring:application:name:d-eureka-consumer-ribbonserver:port:8080eureka:client:service-url:defaultZone:http://localhost:8761/eureka/2、服务端代码
@RestController@RequestMapping("/api")publicclassUserController{@GetMapping("/user/{id}")publicMap<String,Object>getUser(@PathVariableLongid){returnMap.of("id",id,"name","David","age",32,"from","d-eureka-consumer-ribbon demo");}}3、客户端代码
💡 原理说明:
@LoadBalanced使RestTemplate被RibbonInterceptor拦截,将http://user-service/...中的user-service解析为 Eureka 中注册的多个实例,并按IRule(如RoundRobinRule)选择一个发起真实 HTTP 请求。
@ConfigurationpublicclassRestTemplateConfig{@Bean@LoadBalanced// 关键:开启 Ribbon 负载均衡拦截publicRestTemplaterestTemplate(){returnnewRestTemplate();}}@ServicepublicclassUserService{privatefinalRestTemplaterestTemplate;publicUserService(RestTemplaterestTemplate){this.restTemplate=restTemplate;}publicMap<String,Object>fetchUser(Longid){// ✅ 直接使用服务名 "user-service"(非 IP),Ribbon 自动解析并负载均衡Stringurl="http://user-service/api/user/{id}";returnrestTemplate.getForObject(url,Map.class,id);}}@RestController@RequestMapping("/consumer")publicclassConsumerController{privatefinalUserServiceuserService;publicConsumerController(UserServiceuserService){this.userService=userService;}@GetMapping("/user/{id}")publicMap<String,Object>getUser(@PathVariableLongid){returnuserService.fetchUser(id);}}🎈使用@FeignClient(Feign版)
1、服务端和客户端配置
--✅**服务端`application.yml`**(同前,`user-service`):spring:application:name:user-serviceserver:port:8081eureka:client:service-url:defaultZone:http://localhost:8761/eureka/instance:prefer-ip-address:true--✅**客户端`application.yml`**(`d-eureka-consumer-feign`):spring:application:name:d-eureka-consumer-feignserver:port:8080eureka:client:service-url:defaultZone:http://localhost:8761/eureka/2、服务端代码
@RestController@RequestMapping("/api")publicclassUserController{@GetMapping("/user/{id}")publicMap<String,Object>getUser(@PathVariableLongid){returnMap.of("id",id,"name","Eve","age",27,"from","d-eureka-consumer-feign demo");}}3、客户端代码
💡 原理说明:
@FeignClient接口在运行时被动态代理,Feign 自动整合 Ribbon(旧版)或 Spring Cloud LoadBalancer(新版)实现服务发现 + 负载均衡,并通过Contract解析@GetMapping、@PathVariable等注解生成 HTTP 请求。
@SpringBootApplication@EnableFeignClients// ← 必须添加!扫描 @FeignClient 接口@EnableDiscoveryClientpublicclassDEurekaConsumerFeignApplication{publicstaticvoidmain(String[]args){SpringApplication.run(DEurekaConsumerFeignApplication.class,args);}}@FeignClient(name="user-service")// ← 服务名,自动从 Eureka 查找publicinterfaceUserClient{@GetMapping("/api/user/{id}")Map<String,Object>getUser(@PathVariable("id")Longid);}@ServicepublicclassUserService{privatefinalUserClientuserClient;publicUserService(UserClientuserClient){this.userClient=userClient;}publicMap<String,Object>fetchUser(Longid){returnuserClient.getUser(id);// ← 一行代码完成远程调用!}}@RestController@RequestMapping("/consumer")publicclassConsumerController{privatefinalUserServiceuserService;publicConsumerController(UserServiceuserService){this.userService=userService;}@GetMapping("/user/{id}")publicMap<String,Object>getUser(@PathVariableLongid){returnuserService.fetchUser(id);}}🎈使用@DubboReference(Dubbo版)
1、服务端和客户端配置
✅**服务端`application.yml`**(`user-service-dubbo-nacos`):spring:application:name:user-service-dubbo-nacoscloud:nacos:discovery:server-addr:localhost:8848# ← Nacos 服务地址dubbo:protocol:name:dubboport:20880registry:address:nacos://localhost:8848# ← 关键:Nacos 协议注册中心scan:base-packages:com.example.demo.service.implserver:port:8081--✅**客户端`application.yml`**(`dubbo-consumer-nacos`):spring:application:name:dubbo-consumer-nacoscloud:nacos:discovery:server-addr:localhost:8848dubbo:registry:address:nacos://localhost:8848# ← 同样使用 nacos:// 协议server:port:8080✅ 前提:本地启动 Nacos Server(https://nacos.io),默认地址
http://localhost:8848,账号密码均为nacos。
2、服务端代码
--✅**服务接口(共享)**:// com.example.demo.service.UserService.javapublicinterfaceUserService{Map<String,Object>getUser(Longid);}--✅**服务实现类(`@DubboService`+自动注册到Nacos)**:importorg.apache.dubbo.config.annotation.DubboService;importorg.springframework.stereotype.Service;@DubboService// ← 自动向 Nacos 注册为 dubbo 服务(含接口名、版本、分组等元数据)publicclassUserServiceImplimplementsUserService{@OverridepublicMap<String,Object>getUser(Longid){returnMap.of("id",id,"name","Grace","age",29,"registry","nacos","timestamp",System.currentTimeMillis());}}✅ 启动后,服务将注册到 Nacos 控制台【服务管理】→
providers:com.example.demo.service.UserService,支持健康检查与元数据展示。
3、客户端代码
--✅**消费方注入(`@DubboReference`,自动从Nacos发现)**:importorg.apache.dubbo.config.annotation.DubboReference;importorg.springframework.stereotype.Service;@ServicepublicclassUserService{// ← 关键:Dubbo 自动从 Nacos 拉取 provider 列表,创建 RPC 代理// 如果 不能直接引入,需要定义完全一样的@DubboReferenceprivatecom.example.demo.service.UserServiceuserService;publicMap<String,Object>fetchUser(Longid){returnuserService.getUser(id);// ← 无感知远程调用(非 HTTP,是高性能 RPC)}}--✅**Controller示例(可选)**:@RestController@RequestMapping("/consumer")publicclassConsumerController{privatefinalUserServiceuserService;publicConsumerController(UserServiceuserService){this.userService=userService;}@GetMapping("/user/{id}")publicMap<String,Object>getUser(@PathVariableLongid){returnuserService.fetchUser(id);}}💡 补充说明:Dubbo 通过
NacosServiceDiscovery与 Nacos 交互;服务发现采用长轮询 + 缓存机制,支持秒级故障剔除;默认负载均衡策略为RandomLoadBalance。
