SpringCloud
概念
微服务架构的演进思路
多服务应用->微服务应用(单服务应用+服务通信(restful))
问题:每个服务的访问路径需要硬编码
微服务应用+服务中心
问题:每次访问需要手动遍历,当同一服务存在多个实例时进一步出现负载均衡问题
多实例微服务应用+服务中心+负载均衡器
问题:
当某个服务故障时可能造成连锁反应,产生雪崩
所有服务都面向外部可用,缺少统一访问控制
如何采用类似rpc的接口调用风格
应用如此之多,每个应用都要单独编写一套配置,如何集中管理配置
等等
根据以上思路,微服务的核心组件呼之欲出:服务中心、负载均衡、熔断降级、服务网关、配置中心
核心组件
功能 | 介绍 | 组件 |
---|---|---|
服务中心 | 服务注册和服务发现 | ZooKeeper、Eureka、Nacos、Consul |
负载均衡 | 负载均衡和服务映射 | Ribbon、LoadBalancer |
熔断降级 | 服务熔断、服务降级、流量限制 | Hystrix、Resilience4j、Sentinel |
服务网关 | 服务映射、过滤控制 | Zuul、Gateway、Kong |
配置中心 | 集中配置、远端配置 | Config、Nacos、Consul |
接口风格 | 支持 RPC 风格调用 | Feign、OpenFeign |
事务管理 | 分布式事务协调 | Seata |
监控预警 | 实时监控系统指标,异常告警 | Prometheus 、Micrometer |
服务追踪 | 分布式链路追踪,定位请求延迟、性能瓶颈 | Zipkin、Jaeger |
服务中心
服务中心的核心本质是一个单独维护的服务列表,服务上线时通过心跳包机制进行把自身添加到服务列表中,服务自身也可以获取这个服务列表
实现集群比较复杂,各类型的服务中心基于不同策略(侧重点)采用不同的实现
负载均衡
负载均衡器的核心本质是根据服务名称service-id查询服务列表,获取对应的服务实例集合,根据某种均衡策略获取其中一个服务实例,返回这个实例对应的 ip 地址
熔断降级
熔断降级的核心本质是根据一定的策略对原始方法(类)进行代理,当发生意外时根据策略调用指定的方法或者触发指定的行为
服务网关
服务网关的核心本质是借助过滤器对请求进行拦截(其中会发生负载均衡)处理
配置中心
配置中心的核心本质是把分散的配置文件进行集中化、云端化管理
功能列表
功能 | 介绍 | 常见组件 |
---|---|---|
服务注册(Service Registration) | 微服务启动时向注册中心注册自身信息(如IP、端口等),以便其他服务发现并调用 | Eureka, Nacos, Consul, ZooKeeper |
服务发现(Service Discovery) | 客户端或网关根据服务名动态获取可用服务实例列表,实现去硬编码的服务调用 | Ribbon, Spring Cloud LoadBalancer, Envoy |
负载均衡(Load Balancing) | 在多个服务实例之间分配请求,提高系统吞吐量和可用性 | Ribbon, Spring Cloud Gateway, Nginx, HAProxy, Envoy |
限流 & 熔断(Rate Limiting & Circuit Breaking) | 控制流量防止过载,服务故障时快速失败避免雪崩 | Hystrix(已弃用), Resilience4j, Sentinel, Istio |
服务网关(API Gateway) | 统一入口,处理路由、鉴权、限流、日志记录等功能 | Spring Cloud Gateway, Zuul, Kong, Traefik |
配置管理(Configuration Management) | 集中管理各服务的配置信息,支持动态更新 | Spring Cloud Config, Nacos Config, Consul KV Store |
分布式事务(Distributed Transactions) | 跨服务操作保持数据一致性 | Seata, RocketMQ事务消息, Saga模式, TCC模式 |
服务追踪(Distributed Tracing) | 跟踪请求链路,定位性能瓶颈和故障点 | Zipkin, Jaeger, SkyWalking, Pinpoint |
监控与告警(Monitoring & Alerting) | 实时监控系统状态,异常时触发告警 | Prometheus + Grafana, ELK Stack, Zabbix |
认证与授权(Authentication & Authorization) | 控制访问权限,保障系统安全 | OAuth2, JWT, OpenID Connect, Keycloak, Auth0 |
消息队列(Message Queue) | 实现异步通信、解耦服务、削峰填谷 | Kafka, RabbitMQ, RocketMQ, ActiveMQ |
缓存(Caching) | 提高数据读取速度,降低后端压力 | Redis, Memcached, Caffeine, Hazelcast |
弹性设计(Resilience Design) | 构建具备容错、自动恢复能力的系统 | Retry, Timeout, Bulkhead, Fallback 模式 |
日志聚合(Log Aggregation) | 收集所有服务的日志,便于统一分析 | ELK Stack (Elasticsearch + Logstash + Kibana), Fluentd |
持续集成 / 持续部署(CI/CD) | 自动化构建、测试、部署流程,提升交付效率 | Jenkins, GitLab CI/CD, ArgoCD, Tekton |
容器编排(Container Orchestration) | 管理容器生命周期、调度、扩缩容 | Kubernetes, Docker Swarm, Mesos |
服务网格(Service Mesh) | 将网络通信、熔断、限流等交给基础设施层统一管理 | Istio, Linkerd, Consul Connect |
SpringCloud Netflix
功能 | 解决方案 | 组件 |
---|---|---|
服务注册与发现 | 提供服务注册中心,支持服务自动注册和发现 | Eureka |
负载均衡 | 客户端负载均衡器,支持多种负载均衡策略 | Ribbon |
声明式 REST 调用 | 简化 HTTP 请求调用,支持负载均衡 | Feign |
断路器(熔断) | 实现服务降级、限流、熔断等功能 | Hystrix (已停止维护,推荐使用 Resilience4j) |
API 网关 | 提供统一入口,支持路由、过滤等功能 | Zuul (已停止维护,推荐使用 Spring Cloud Gateway) |
配置管理 | 集中管理外部配置 | Spring Cloud Config |
分布式追踪 | 跟踪请求链路,分析性能瓶颈 | Sleuth + Zipkin |
消息总线 | 实现配置刷新通知等功能 | Spring Cloud Bus |
- 服务注册与发现:通过 Eureka Server 注册服务实例,客户端使用
@EnableDiscoveryClient
注解自动发现服务。- 负载均衡:Ribbon 默认集成在 Feign 中,通过
@FeignClient
自动实现负载均衡调用。- 声明式 REST 调用:使用
@FeignClient
定义接口,结合 Ribbon 实现负载均衡。- 断路器:使用
@HystrixCommand
注解定义熔断逻辑,或者使用 Resilience4j 替代 Hystrix。- API 网关:Zuul 或者 Spring Cloud Gateway 作为 API 网关,处理路由、过滤等功能。
- 配置管理:Spring Cloud Config Server 存储集中配置,客户端通过
@Value
或@ConfigurationProperties
注解获取配置。
SpringCloud Alibaba
功能 | 解决方案 | 组件 |
---|---|---|
服务注册与发现 | 提供服务注册中心,支持服务自动注册和发现 | Nacos |
负载均衡 | 客户端负载均衡器,支持多种负载均衡策略 | Spring Cloud LoadBalancer (默认) |
声明式 REST 调用 | 简化 HTTP 请求调用,支持负载均衡 | OpenFeign |
限流 & 熔断 | 实现服务降级、限流、熔断等功能 | Sentinel |
API 网关 | 提供统一入口,支持路由、过滤等功能 | Sentinel-Dubbo, Spring Cloud Gateway |
配置管理 | 集中管理外部配置 | Nacos Config |
分布式追踪 | 跟踪请求链路,分析性能瓶颈 | SkyWalking, Apache APISIX |
消息队列 | 支持异步通信、削峰填谷 | RocketMQ |
分布式事务 | 支持跨服务的数据一致性 | Seata |
- 服务注册与发现:通过 Nacos Server 注册服务实例,客户端使用
@EnableDiscoveryClient
注解自动发现服务。- 负载均衡:Spring Cloud LoadBalancer 作为默认负载均衡器,OpenFeign 集成 LoadBalancer 实现负载均衡调用。
- 声明式 REST 调用:使用
@FeignClient
定义接口,结合 LoadBalancer 实现负载均衡。- 限流 & 熔断:Sentinel 提供流量控制、熔断降级功能,使用
@SentinelResource
注解定义资源点,配置限流规则。- API 网关:可以使用 Spring Cloud Gateway 结合 Sentinel 实现网关层的限流和熔断保护。
- 配置管理:Nacos Config Server 存储集中配置,客户端通过
@RefreshScope
注解动态刷新配置。- 分布式追踪:SkyWalking 可以集成到 Spring Cloud Alibaba 中,用于跟踪请求链路。
- 消息队列:RocketMQ 作为高性能的消息队列,支持异步通信、削峰填谷。
SpringCloud NetFlix
创建项目
<dependencyManagement>
<dependencies>
<!-- spring-boot 依赖版本控制 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.1.4.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- spring-cloud 依赖版本控制 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Greenwich.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
多模块项目,后续模块均作为该项目的子模块
Eureka
自我保护机制
Eureka通过心跳包确定服务是否正常,但在网络故障时,有可能服务正常但无法接收服务实例的心跳,此时不应该立即注销该服务实例,而是保留服务等待网络恢复正常,这就是自我保护机制
假定存在100个注册的微服务,某一时刻A,只有80个心跳包成功响应,此时eureka会进入自我保护机制,即使连续多次都只有80个心跳包响应,也不会移除未响应的服务;某一时刻B,有90个心跳包响应,此时eureka退出自我保护机制,未响应的10个服务会根据上次响应时间和当前时间的差值判断是否清除,即如果这10个服务已经连续3次未响应就被清除(即自我保护期间的时间会被累积)
宁可保留所有服务,也不盲目注销任何服务
通过 eureka.server.enable-self-preservation=false 可以关闭自我保护机制
为什么说它是AP原则?
因为它基于自我保护机制和过期清理策略,不能严格保证服务列表中的服务全部都是最新可用状态,而是允许服务不可用,这避免了因网络分区故障造成的误清理,从而提高可用性,此外自我保护机制本身就是保证分区容错性的。
由于服务状态不是最新的(注册中心的服务状态和实际服务状态不一致),实际部分服务可能调用失败(自动重试机制在一定程度解决了这个问题),这牺牲了强一致性(随着时间推移网络故障恢复,实际最终还是一致的)
简单示例
server
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
配置文件
spring:
application:
# 应用(服务)的逻辑名称, 微服务架构中标识在注册中心的注册名称
name: eureka-server
server:
port: 7000
eureka:
instance:
# 应用(服务)通常不会单例部署, 而是同一服务具有多个实例, 该属性用于区分同一服务的不同实例
instance-id: eureka-server-single
# 当前服务的位置, 可以是localhost、ip、dns域名
hostname: localhost
client:
# 是否注册自己到服务中心, 此处本身就是注册中心, 因此是不把自己注册到自己
register-with-eureka: false
# 是否抓取服务注册列表, 通常注册中心本身不抓取, 而是服务从注册中心抓取
fetch-registry: false
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
// 作为 Eureka 的服务器, 即注册中心
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
provider
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
配置文件
spring:
application:
name: eureka-provider
server:
port: 8000
eureka:
instance:
instance-id: eureka-provider-single
client:
# 该服务作为纯粹的提供者, 无需获取服务列表
fetch-registry: false
service-url:
# 注册中心位置
defaultZone: http://localhost:7000/eureka/
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("/dc")
public String dc() {
return "serviceId: " + "eureka-provider" + ", instanceId: " + "eureka-provider-single";
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务
@EnableEurekaClient
public class EurekaProviderApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication.class, args);
}
}
consumer
载入依赖
同provider模块
配置文件
spring:
application:
name: eureka-consumer
server:
port: 9000
eureka:
instance:
instance-id: eureka-consumer-single
client:
# 该服务作为纯粹的消费者, 无需注册到注册中心
register-with-eureka: false
service-url:
# 注册中心位置
defaultZone: http://localhost:7000/eureka/
配置类
package xyz.ssydx.springcloud.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class MyController {
@Autowired
// 获取服务列表
private DiscoveryClient client;
@Autowired
// Restful
private RestTemplate restTemplate;
@RequestMapping("/dc")
public String dc() {
String serviceId = client.getServices().get(0);
ServiceInstance serviceInstance = client.getInstances(serviceId).get(0);
return restTemplate.getForObject(serviceInstance.getUri() + "/dc", String.class);
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务, 此处等价于 @EnableEurekaClient, 它同时也适用于其他注册中心
@EnableDiscoveryClient
public class EurekaConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerApplication.class, args);
}
}
思考
每次调用服务都要自行遍历服务列表选择对应的服务,进而获取对应的服务实例,再找到服务实例所在地址,最后才能访问,这很不方便,有没有更简单的方法?
生产环境中无论是注册中心还是服务实例都不是单一存在的,而是多个注册中心组成集群,多个服务实例组成多实例服务,从而保证服务的高可用性,此时调用方调用服务时势必要经过某种策略选择注册中心,进而又根据某种策略选择某个服务实例,这就是负载均衡,如何实现?
以上两个问题将由ribbon这一组件解决
Ribbon
核心作用是:根据服务名选择服务,并根据负载均衡算法选择服务实例,调用失败后尝试下一可用服务实例
即:服务发现、负载均衡、故障转移
什么时候主机名被视为服务名?
发起请求:
URL → http://host/path
↓
是否使用 @LoadBalanced 的客户端?
└─ 否 → 直接发起 HTTP 请求(不走服务发现)
└─ 是 → 继续判断
↓
host 是否是合法 IP 或域名?
└─ 是 → 直接发起 HTTP 请求(跳过服务发现)
└─ 否 → 继续判断
↓
尝试将 host 视为服务名,去服务注册中心查找实例:
└─ 找到多个实例 → 根据负载均衡策略选一个
└─ 找不到实例 → 抛出 UnknownHostException 或 ServiceInstanceNotFoundException
↓
替换 host 为真实 IP:PORT,发起最终 HTTP 请求
protocal://host[:port]/path/to?var1=value1&var2=value2 中的 host 判断
检查项 条件 是否跳过服务发现 host 是 IPv4 地址 如 192.168.1.10
✅ 跳过 host 是 IPv6 地址 如 [::1]
、fe80::1
✅ 跳过 host 是 localhost
或127.0.0.1
本地地址 ✅ 跳过 host 是域名(DNS 可解析) 如 www.example.com
✅ 跳过 host 是数字开头的字符串 如 123abc.com
(不合法域名)❌ 不跳过,尝试作为服务名处理
简单示例
server
在 C:Windows/System32/drivers/etc/host中添加
127.0.0.1 eureka7001.com 127.0.0.1 eureka7002.com 127.0.0.1 eureka7003.com
模拟多服务器环境
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
编写配置
spring:
application:
# 应用(服务)的逻辑名称, 微服务架构中标识在注册中心的注册名称
name: eureka-server
server:
port: 7001
eureka:
instance:
# 应用(服务)通常不会单例部署, 而是同一服务具有多个实例, 该属性用于区分同一服务的不同实例
instance-id: eureka-server-7001
# 当前服务的位置, 可以是localhost、ip、dns域名
hostname: eureka7001.com
client:
# 是否注册自己到服务中心, 此处本身就是注册中心, 因此是不把自己注册到自己
register-with-eureka: false
# 是否抓取服务注册列表, 通常注册中心本身不抓取, 而是服务从注册中心抓取
fetch-registry: false
# 注册中心位置, 目的是构建集群
service-url:
defaultZone: http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
// 作为 Eureka 的服务器, 即注册中心
@EnableEurekaServer
public class EurekaServerApplication_7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication_7001.class, args);
}
}
同理创建 eureka-server-7002 eureka-server-7003 两个服务器,注意修改对应的配置属性
provider
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
编写配置
spring:
application:
name: eureka-provider
server:
port: 8001
eureka:
instance:
instance-id: eureka-provider-8001
client:
# 该服务作为纯粹的提供者, 无需获取服务列表
fetch-registry: false
service-url:
# 注册中心位置
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("/dc")
public String dc() {
return "serviceId: " + "eureka-provider" + ", instanceId: " + "eureka-provider-8001";
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务
@EnableEurekaClient
public class EurekaProviderApplication_8001 {
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication_8001.class, args);
}
}
同理创建 eureka-provider-8002 eureka-provider-8003
consumer
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
编写配置
spring:
application:
name: eureka-consumer
server:
port: 9001
eureka:
instance:
instance-id: eureka-consumer-single
client:
# 该服务作为纯粹的消费者, 无需注册到注册中心
register-with-eureka: false
service-url:
# 注册中心位置
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
配置类
package xyz.ssydx.springcloud.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
// 使用随机替代默认的轮询
@Bean
public IRule iRule() {
return new RandomRule();
}
@Bean
// 启用路径映射和负载均衡
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class MyController {
@Autowired
// Restful
private RestTemplate restTemplate;
// 此处直接使用服务名, template在请求前会根据服务列表查询出服务名对应的服务实例, 进而查询到服务实例所在的地址
// 多实例情况会根据负载均衡策略进行实例的选择, 默认轮询, 可使用其他策略甚至自定义策略
private final String prefix = "http://eureka-provider";
@RequestMapping("/dc")
public String dc() {
return restTemplate.getForObject( prefix + "/dc", String.class);
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务, 此处等价于 @EnableEurekaClient, 它同时也适用于其他注册中心
@EnableDiscoveryClient
public class EurekaConsumerApplication_9001 {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerApplication_9001.class, args);
}
}
不难看出,ribbon 主要解决了 服务名称到服务地址的映射、负载均衡两个重要问题
注意:此处的负载均衡仅指服务提供者的负载均衡,注册中心的负载均衡默认只有轮询这一种策略
Feign
本质上 feign 依然是 eureka+ribbon, 只是它提供了一种类似 rpc 调用风格
当消费者调用该某接口方法时会自动映射为访问 指定服务下的指定端点
简单示例
server
使用 ribbon 的示例
provider
使用 ribbon 的示例
consumer
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- 等价 -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-openfeign</artifactId>-->
<!-- </dependency>-->
</dependencies>
编写配置
spring:
application:
name: eureka-consumer
server:
port: 9002
eureka:
instance:
instance-id: eureka-consumer-single
client:
# 该服务作为纯粹的消费者, 无需注册到注册中心
register-with-eureka: false
service-url:
# 注册中心位置
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
配置类
package xyz.ssydx.springcloud.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConfigBean {
// 使用随机替代默认的轮询
@Bean
public IRule iRule() {
return new RandomRule();
}
}
RPC风格接口
package xyz.ssydx.springcloud.rpc;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
// 指定服务名称
@FeignClient(name = "eureka-provider")
@Component
public interface MyRpc {
// 接口到路径的映射
@RequestMapping("/dc")
String interface_dc();
}
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.ssydx.springcloud.rpc.MyRpc;
@RestController
public class MyController {
@Autowired
private MyRpc myRpc;
@RequestMapping("/dc")
public String dc() {
return myRpc.interface_dc();
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务, 此处等价于 @EnableEurekaClient, 它同时也适用于其他注册中心
@EnableDiscoveryClient
// 扫描指定包下带有 @FeignClient 注解的接口, 便于进行动态代理, 从而实现 rpc 风格调用
@EnableFeignClients(basePackages = {"xyz.ssydx.springcloud.rpc"})
public class EurekaConsumerApplication_9002 {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerApplication_9002.class, args);
}
}
Hystrix
主要用于服务降级、服务熔断、实时监控
简单示例
server
使用 ribbon 的示例
provider
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
编写配置
spring:
application:
name: eureka-provider
server:
port: 8004
eureka:
instance:
instance-id: eureka-provider-8004
client:
# 该服务作为纯粹的提供者, 无需获取服务列表
fetch-registry: false
service-url:
# 注册中心位置
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
控制器
package xyz.ssydx.springcloud.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
// 提供者端降级
@RestController
public class MyController {
@RequestMapping("/dc1")
// 熔断机制的核心注解, 此处指定发生错误后的降级方法
@HystrixCommand(fallbackMethod = "fallback")
public String dc1() {
if (true) throw new RuntimeException("error");
return "serviceId: " + "eureka-provider" + ", instanceId: " + "eureka-provider-8004";
}
public String fallback() {
return "error";
}
@RequestMapping("/dc2")
public String dc2() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "serviceId: " + "eureka-provider" + ", instanceId: " + "eureka-provider-8004";
}
@RequestMapping("/dc3")
public String dc3() {
if (true) throw new RuntimeException("error");
return "serviceId: " + "eureka-provider" + ", instanceId: " + "eureka-provider-8004";
}
@RequestMapping("/dc4")
public String dc4() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
return "serviceId: " + "eureka-provider" + ", instanceId: " + "eureka-provider-8004";
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务
@EnableEurekaClient
// 启用熔断器
@EnableCircuitBreaker
public class EurekaProviderApplication_8004 {
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication_8004.class, args);
}
}
consumer
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-feign</artifactId>
<version>1.4.6.RELEASE</version>
</dependency>
<!-- 等价 -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-starter-openfeign</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
编写配置
spring:
application:
name: eureka-consumer
server:
port: 9003
eureka:
instance:
instance-id: eureka-consumer-single
client:
# 该服务作为纯粹的消费者, 无需注册到注册中心
register-with-eureka: false
service-url:
# 注册中心位置
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
配置类
package xyz.ssydx.springcloud.config;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.RandomRule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ConfigBean {
// 使用随机替代默认的轮询
@Bean
public IRule iRule() {
return new RandomRule();
}
}
RPC风格接口
package xyz.ssydx.springcloud.rpc;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
// 指定服务名称
@FeignClient(name = "eureka-provider")
@Component
public interface MyRpc {
// 接口到路径的映射
@RequestMapping("/dc1")
String interface_dc1();
// 接口到路径的映射
@RequestMapping("/dc2")
String interface_dc2();
// 接口到路径的映射
@RequestMapping("/dc3")
String interface_dc3();
// 接口到路径的映射
@RequestMapping("/dc4")
String interface_dc4();
}
控制器
package xyz.ssydx.springcloud.controller;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xyz.ssydx.springcloud.rpc.MyRpc;
// 消费者端降级
@RestController
// 全局降级方法(默认降级方法), 优先级低于方法层面的
@DefaultProperties(defaultFallback = "globalFallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public class MyController {
@Autowired
private MyRpc myRpc;
@RequestMapping("/dc1")
public String dc1() {
return myRpc.interface_dc1();
}
@RequestMapping("/dc2")
@HystrixCommand(fallbackMethod = "fallback", commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
})
public String dc2() {
return myRpc.interface_dc2();
}
public String fallback() {
return "timeout";
}
@RequestMapping("/dc3")
// 启用默认降级方法
@HystrixCommand
public String dc3() {
return myRpc.interface_dc3();
}
@RequestMapping("/dc4")
// 启用默认降级方法
@HystrixCommand
public String dc4() {
return myRpc.interface_dc4();
}
public String globalFallback() {
return "globalFallback";
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务, 此处等价于 @EnableEurekaClient, 它同时也适用于其他注册中心
@EnableDiscoveryClient
// 扫描指定包下带有 @FeignClient 注解的接口, 便于进行动态代理, 从而实现 rpc 风格调用
@EnableFeignClients(basePackages = {"xyz.ssydx.springcloud.rpc"})
// 启用熔断器
@EnableCircuitBreaker
public class EurekaConsumerApplication_9003 {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerApplication_9003.class, args);
}
}
Dashboard示例
TODO
Zuul
主要功能是路由和过滤
简单示例
在实际生产中都会多实例(集群)部署,此处进行简化
在实际生产中还会结合 Feign、Hystrix等相关组件
server
使用 ribbon 的示例
provider
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
编写配置
spring:
application:
name: eureka-provider
server:
port: 8006
eureka:
instance:
instance-id: eureka-provider-single
client:
# 该服务作为纯粹的提供者, 无需获取服务列表
fetch-registry: false
service-url:
# 注册中心位置
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@RequestMapping("/dc")
public String dc() {
return "serviceId: " + "eureka-provider" + ", instanceId: " + "eureka-provider-8006";
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务
@EnableEurekaClient
public class EurekaProviderApplication_8006 {
public static void main(String[] args) {
SpringApplication.run(EurekaProviderApplication_8006.class, args);
}
}
consumer-provider
载入依赖
同 provider
编写配置
spring:
application:
name: eureka-consumer-provider
server:
port: 9006
eureka:
instance:
instance-id: eureka-consumer-provider-single
client:
service-url:
# 注册中心位置
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
配置类
package xyz.ssydx.springcloud.config;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
@Configuration
public class ConfigBean {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class MyController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/dc")
public String dc() {
// 经过zuul
// zuul 被 template 解析为 http://localhost:11000
// 必须包含 /ssydx 前缀
// 由于 eureka-provider 服务名称被忽略, 只能通过 /provider/** 访问
return restTemplate.getForObject("http://zuul/ssydx/provider/dc/", String.class);
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务, 此处等价于 @EnableEurekaClient, 它同时也适用于其他注册中心
@EnableDiscoveryClient
public class EurekaConsumerProviderApplication_9006 {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerProviderApplication_9006.class, args);
}
}
consumer
载入依赖
同 provider
编写配置
spring:
application:
name: eureka-consumer
server:
port: 9005
eureka:
instance:
instance-id: eureka-consumer-single
client:
# 实际生产中, 通常所有微服务都会注册到服务中心, 保证受到网关的管理
register-with-eureka: true
service-url:
# 注册中心位置
defaultZone: http://eureka7001.com:7001/eureka/, http://eureka7002.com:7002/eureka/, http://eureka7003.com:7003/eureka/
配置类
同 consumer-provider
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class MyController {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/dc")
public String dc() {
// 未经过zuul
return restTemplate.getForObject("http://eureka-consumer-provider/dc/", String.class);
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
// 作为 Eureka 的客户端, 即服务, 此处等价于 @EnableEurekaClient, 它同时也适用于其他注册中心
@EnableDiscoveryClient
public class EurekaConsumerApplication_9005 {
public static void main(String[] args) {
SpringApplication.run(EurekaConsumerApplication_9005.class, args);
}
}
zuul
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
</dependencies>
编写配置
server:
port: 11000
spring:
application:
name: zuul
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
instance:
instance-id: zuul-11000
zuul:
# 路由组, 每个服务都可以进行一组映射
routes:
# 把 eureka-provider 服务映射到 /provider/**
# 即: 可以通过 localhost:11000/provider/** 访问 eureka-provider 服务
provider:
service-id: eureka-provider
path: /provider/**
# 拒绝通过服务名称访问, '*' 表示所有服务名称(''不可省略)
# 默认只要将 zuul 注册到 eureka 就可在 localhost:11000/eureka-provider/** 访问
# 注: 不能避免直接通过服务所在 ip 进行访问, 想要实现拒绝外部直接通过 ip 访问原始服务, 需要进行网络防火墙设置
# 注: 也不影响服务间通过服务名称调用
ignored-services: eureka-provider
# 给所有服务添加公共前缀
# 现在要通过 localhost:11000/ssydx/provider/** 进行访问
prefix: /ssydx
过滤器
package xyz.ssydx.springcloud.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class MyZuulFilter extends ZuulFilter {
// 过滤器类型, pre post error route
@Override
public String filterType() {
return "pre";
}
// 过滤器优先级
@Override
public int filterOrder() {
return 0;
}
// 是否启用过滤器
@Override
public boolean shouldFilter() {
return true;
}
// 过滤器的实际逻辑
@Override
public Object run() throws ZuulException {
// RequestContext 是一个本地线程
RequestContext context = RequestContext.getCurrentContext();
HttpServletRequest request = context.getRequest();
System.out.println("============>" + request.getRequestURI());
return null;
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
// 启用 zuul
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
测试
浏览器访问:
http://localhost:11000/ssydx/eureka-consumer/dc
zuul 应用的控制台会输出:
============>/ssydx/eureka-consumer/dc
============>/ssydx/provider/dc/
Config
配置中心,提供一个集中的远端的配置中心,避免分散配置应用
简单示例
仓库配置
git 及 github、gitee的具体使用此处不进行讲解
演示使用 gitee,网络更稳定,且 gitee 自带教程,上手简单
生成SSH密钥对->添加公钥到gitee->创建远程仓库->克隆仓库到本地
本地仓库中编写配置文件,文件名为config-client.yml,推送至远程仓库
spring:
application:
name: config-client
server:
port: 12002
eureka:
instance:
instance-id: config-client-default
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
---
spring:
profiles: dev
application:
name: config-client
server:
port: 12003
eureka:
instance:
instance-id: config-client-dev
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
---
spring:
profiles: test
application:
name: config-client
server:
port: 12004
eureka:
instance:
instance-id: config-client-test
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
实际你也可以仅在云端创建仓库,直接在云端编写配置文件
server
使用 ribbon 的示例
server
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
</dependencies>
编写配置
server:
port: 12001
spring:
application:
name: config-server
cloud:
config:
server:
git:
# 指定远程仓库的访问地址
uri: https://gitee.com/ssydx/springcloud-config.git
# 注册到 eureka 服务器
eureka:
instance:
instance-id: config-server-single
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.config.server.EnableConfigServer;
@SpringBootApplication
@EnableDiscoveryClient
// 作为配置文件服务器
@EnableConfigServer
public class ConfigApplication_12001 {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication_12001.class, args);
}
}
client
载入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<!-- 等价于 -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.cloud</groupId>-->
<!-- <artifactId>spring-cloud-config-client</artifactId>-->
<!-- </dependency>-->
</dependencies>
编写配置
此处使用的boostrap.yml 而不是 application.yml
两者不可混用
前者在应用上下文初始化之前就已经加载,常用于 SpringCloud 的远程配置
后者在应用上下文初始化之后才进行加载,用于常规配置
spring:
cloud:
config:
name: config-client
# 远端 config-client.yml 中存在 三个配置片段
# 第一个未指定 profile 名称, 会被视为 default; 另外两个分别是 dev, test
# 如果此处未指定 profile 名称, 自动使用 default 片段
# 如果指定, 则使用指定名称的片段
# profile: test
label: master
# 硬编码地址, 不推荐
# uri: http://localhost:12001
# 通过服务名称访问, 前提是开启配置服务器注册到 eureka
# 存在多个配置服务器时, 自动实现负载均衡
discovery:
enabled: true
service-id: config-server
eureka:
# 远端配置如果也配置了以下内容, 会进行同属性覆盖
# 即远端配置加载顺序晚于本地配置
instance:
instance-id: config-client-single
# 必须配置, 否则通过服务名称发现
client:
# 必须可抓取服务列表, 否则无法通过服务名称查询实际地址并进行负载均衡
fetch-registry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka,http://eureka7003.com:7003/eureka
控制器
package xyz.ssydx.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MyController {
@Value("${spring.application.name}")
private String appName;
@Value("${eureka.client.service-url.defaultZone}")
private String eurekaServer;
@Value("${server.port}")
private String port;
@RequestMapping("/dc")
public String dc() {
return "appName=" + appName + ", eurekaServer=" + eurekaServer + ", port=" + port;
}
}
主启动类
package xyz.ssydx.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class ConfigApplication_12002 {
public static void main(String[] args) {
SpringApplication.run(ConfigApplication_12002.class, args);
}
}
SpringCloud Alibaba
TODO
#java##spring##springboot##springcloud#本专栏包含Java、MySQL、JavaWeb、Spring、Redis、Docker等等,作为个人学习记录及知识总结,将长期进行更新! 有相关问题或错误指正欢迎交流沟通,邮箱ssydx@qq.com