【微服务实战】从零构建SpringCloud应用:注册中心、负载均衡与服务调用

张开发
2026/4/7 10:26:52 15 分钟阅读

分享文章

【微服务实战】从零构建SpringCloud应用:注册中心、负载均衡与服务调用
1. 微服务架构入门为什么需要注册中心第一次接触微服务架构时我最大的困惑就是明明用HTTP请求就能直接调用其他服务为什么还要搞这么复杂的注册中心直到实际项目中出现服务实例频繁上下线、IP地址动态变化的情况才真正理解注册中心的必要性。想象一下这样的场景你的电商系统有订单服务、支付服务、库存服务等十几个模块。当支付服务需要扩容时新实例的IP地址会动态分配。如果没有注册中心订单服务里硬编码的支付服务地址就会失效。这就是服务注册与发现机制要解决的核心问题。SpringCloud提供了三种主流注册中心方案EurekaNetflix开源最经典的SpringCloud注册中心ConsulHashiCorp公司开发支持健康检查、KV存储等丰富功能ZookeeperApache项目在分布式协调领域广泛应用我在实际项目中更推荐新手从Eureka开始它的学习曲线最平缓。虽然官方已经停止维护但在中小型项目中完全够用。当需要更强大的服务网格功能时再考虑Consul或Nacos这类新方案。2. 环境搭建五分钟快速启动Eureka服务2.1 创建父工程首先用Spring Initializr创建Maven父工程关键配置如下!-- pom.xml -- packagingpom/packaging modules moduleeureka-server/module modulepayment-service/module moduleorder-service/module /modules dependencyManagement dependencies dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-dependencies/artifactId version2021.0.3/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement2.2 Eureka服务端配置创建eureka-server子模块添加核心依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-netflix-eureka-server/artifactId /dependency配置文件application.yml的黄金三要素server: port: 8761 eureka: instance: hostname: localhost client: register-with-eureka: false # 不注册自己 fetch-registry: false # 不抓取注册表 service-url: defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/启动类只需一个注解SpringBootApplication EnableEurekaServer public class EurekaServerApplication { public static void main(String[] args) { SpringApplication.run(EurekaServerApplication.class, args); } }启动后访问http://localhost:8761你会看到Eureka的仪表盘。虽然现在空空如也但接下来我们就要注册服务了。3. 服务注册与发现实战3.1 支付服务注册在payment-service模块中添加客户端依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-netflix-eureka-client/artifactId /dependency配置文件需要特别注意instance-id的命名规范spring: application: name: payment-service eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: instance-id: ${spring.application.name}:${random.value} prefer-ip-address: true启动类添加注解SpringBootApplication EnableEurekaClient public class PaymentApplication { public static void main(String[] args) { SpringApplication.run(PaymentApplication.class, args); } }启动后刷新Eureka页面就能看到PAYMENT-SERVICE已经注册成功。这里有个实用技巧通过配置eureka.instance.instance-id可以自定义实例显示名称方便在集群环境下区分不同节点。3.2 服务发现机制有时我们需要动态获取服务实例信息这时可以用DiscoveryClientRestController RequestMapping(/discovery) public class DiscoveryController { Autowired private DiscoveryClient discoveryClient; GetMapping(/services) public ListString listServices() { return discoveryClient.getServices(); } GetMapping(/instances/{serviceId}) public ListServiceInstance getInstances( PathVariable String serviceId) { return discoveryClient.getInstances(serviceId); } }这个接口特别适合做健康检查或服务监控。我在实际项目中经常用它来实现自定义的熔断策略比如当某个服务的实例数低于阈值时触发告警。4. 客户端负载均衡Ribbon深度解析4.1 RestTemplate整合Ribbon在order-service中配置负载均衡的RestTemplateConfiguration public class RestTemplateConfig { Bean LoadBalanced // 关键注解 public RestTemplate restTemplate() { return new RestTemplateBuilder() .setConnectTimeout(Duration.ofSeconds(3)) .setReadTimeout(Duration.ofSeconds(5)) .build(); } }使用时直接通过服务名调用RestController RequestMapping(/orders) public class OrderController { Autowired private RestTemplate restTemplate; GetMapping(/pay/{orderId}) public String payOrder(PathVariable String orderId) { // 注意这里的PAYMENT-SERVICE就是注册中心的服务名 return restTemplate.getForObject( http://PAYMENT-SERVICE/payments/process/{orderId}, String.class, orderId); } }4.2 自定义负载均衡策略Ribbon默认使用轮询算法我们可以通过配置切换Configuration RibbonClient(name PAYMENT-SERVICE, configuration RandomRuleConfig.class) public class RibbonConfiguration { } // 随机规则配置 public class RandomRuleConfig { Bean public IRule ribbonRule() { return new RandomRule(); } }实际项目中遇到过的一个坑如果服务启动较慢随机策略可能导致大量请求落到尚未就绪的实例上。这时可以考虑使用WeightedResponseTimeRule它会根据响应时间动态调整权重。5. 声明式服务调用OpenFeign最佳实践5.1 Feign基础使用添加依赖dependency groupIdorg.springframework.cloud/groupId artifactIdspring-cloud-starter-openfeign/artifactId /dependency定义接口式客户端FeignClient(name payment-service) public interface PaymentClient { GetMapping(/payments/{orderId}) PaymentInfo getPaymentInfo(PathVariable String orderId); PostMapping(/payments) PaymentResult createPayment(RequestBody PaymentRequest request); }启动类添加注解SpringBootApplication EnableFeignClients public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }5.2 Feign高级配置自定义编解码器和拦截器Configuration public class FeignConfig { Bean public Encoder feignEncoder() { return new JacksonEncoder(); } Bean public RequestInterceptor authInterceptor() { return template - { String token SecurityContext.getToken(); template.header(Authorization, Bearer token); }; } }日志配置application.ymllogging: level: com.example.order.feign: DEBUG feign: client: config: default: loggerLevel: FULL connectTimeout: 5000 readTimeout: 10000在微服务调试阶段建议开启FULL级别的日志可以清晰看到请求/响应的完整信息。生产环境记得调整为BASIC或NONE。6. 注册中心选型对比根据三年微服务实战经验我整理了三者的核心差异特性EurekaConsulZookeeper一致性协议APCPCP健康检查客户端心跳服务端主动检查会话维持管理界面内置功能丰富需第三方工具配置中心功能不支持支持通过ZNode实现学习成本低中高适合场景中小型项目需要服务网格的项目分布式协调对于刚开始接触微服务开发的团队我的建议是先用Eureka快速搭建原型等业务规模扩大后再考虑迁移到Consul或Nacos。曾经有个项目在初期直接上Zookeeper结果30%的开发时间都花在解决ZK的异常问题上得不偿失。

更多文章