spring cloud实践
Posted ac_dao_di
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring cloud实践相关的知识,希望对你有一定的参考价值。
1. 前言
spring cloud是一个企业级的基于spring boot的微服务解决方案,生态非常庞大,拥有许多微服务治理组件,一开始netflix贡献了大量的套件,随着netflix组件逐渐不维护,spring cloud开始与其解耦,发展了自己的套件,例如spring cloud gateway等。后来国内的大厂也贡献了自己生态的解决方案,例如alibaba spring cloud。
注意spring cloud和spring boot的区别:
维度 | spring cloud | spring boot |
---|---|---|
定位 | spring cloud是一套完整的微服务解决方案,很多基本API都封装好了,需要各个组件去适配(实现)它,例如DiscoveryClient组件的实现者可以是consul/nacos/euraka等,更适合全技术栈使用spring cloud的情形。 | spring boot是自动化配置而已,简化配置,更方便开发者适配自己的技术栈,但是功能还是和原生的一样。 |
配置方式 | 一般用bootstrap.yamlj进行配置,会先启动一个spring cloud容器 | 一般用application.yaml进行配置 |
使用场景 | 比较适合新搭建企业,开箱即用,快速全栈使用spring cloud生态 | 对于非spring cloud技术栈的传统企业(例如使用dubbo),迁移spring boot可以简化配置 |
spring cloud vs dubbo对比:均具备微服务治理功能
比较维度 | spring cloud | dubbo |
---|---|---|
传输协议 | rest http | dubbo/triple |
负载均衡 | ribbon | round roubin/ random等 |
注册中心 | consul/eureka | zookeeper |
集群失败策略 | ribbon failover | failover/failsafe/failback |
路由 | gateway/zuul | router 模块 |
API | 声明式客户端feign client | 纯接口API |
本文基于spring boot 2.1.7.RELEASE + spring cloud Greenwich.SR6进行实验,代码仓库见:springCloudDemo。选型时需要注意版本兼容,spring boot版本 + spring cloud版本对应关系:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
。版本搭配错误可能导致奇怪的问题。涉及到的组件如下:
微服务组件 | spring cloud | 本文使用 |
---|---|---|
链路追踪 | skywalking/slegeth | ✖️ |
注册中心 | consul/eureka | consul |
配置中心 | spring cloud config | nacos |
网关 | spring cloud gateway/ zuul | ✖️ |
熔断限流 | hystrix/sentinel | ✖️ |
RPC | feign client(声明式客户端)+ ribbon(负载均衡) | feign client(声明式客户端)+ ribbon(负载均衡) |
一张图总结springCloudDemo架构:
2. 安装consul
安装教程见:https://www.consul.io/downloads
consul 开发模式服务端启动
consul agent -server -ui -dev
启动成功日志:
==> Starting Consul agent...
Version: '1.12.3'
Node ID: 'd72c42e1-9bc0-07b5-a71e-5fe2f6e55579'
Node name: 'mokas-MacBook-Pro-3.local'
Datacenter: 'dc1' (Segment: '<all>')
Server: true (Bootstrap: false)
Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: -1, gRPC: 8502, DNS: 8600)
Cluster Addr: 127.0.0.1 (LAN: 8301, WAN: 8302)
Encrypt: Gossip: false, TLS-Outgoing: false, TLS-Incoming: false, Auto-Encrypt-TLS: false
==> Log data will now stream in as it occurs:
访问页面: http://localhost:8500/ui
3. nacos安装
注意mysql8,需要设置时区,jdbc url要正确。已部署到云主机上:101.43.195.208:8848
https://blog.csdn.net/u010312671/article/details/105930774
https://blog.csdn.net/LeslieTsai2019/article/details/118759956
启动nacos:
sudo /opt/nacos/bin/startup.sh
访问nacos:http://101.43.195.208:8848/nacos
4. 定义FeignClient API
核心是指定serviceId,serviceId为注册到consul上的serviceName。微服务的唯一标识。
@FeignClient("spring-cloud-provider")
public interface UserServiceFeignClient
@RequestMapping("/getByName")
User getByName(@RequestParam String name);
5. 定义consumer
5.1 开启feign client扫描
使用注解EnableFeignClients,自动将上面的feign API,生成jdk代理,并注入到spring context作为bean,原理在5.5进行说明。
@EnableFeignClients(basePackages = "com.jessin.practice.spring.cloud.api")
@SpringBootApplication
public class ConsumerApplication
public static void main(String[] args)
SpringApplication.run(ConsumerApplication.class, args);
5.2 集成nacos
引进依赖:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
在bootstrap.yaml中配置nacos,由于是spring cloud,配置必须放到bootstrap.yaml中,在spring cloud容器启动时就已经拿到远程变量数据,方便后续子容器注入。
spring:
cloud:
nacos:
config:
# nacos-spring-cloud配置方式,file-extension会添加到dataId中,
# dataId默认是$spring.application.name.$file-extension
server-addr: 101.43.195.208:8848
file-extension: properties
注意:这是spring cloud的配置方式,nacos spring boot的方式与这不同。不要同时使用spring cloud和spring boot配置nacos,会配置两次连接,而且会有类冲突。
nacos上的spring-cloud-consumer.properties上的变量为:
之后,可以通过@Value(“$mykey”)注入nacos上配置的变量,同时通过监听nacosConfigManager,可以得到配置变更的事件回调。
- RefreshScope 表示该bean在environment有变化时,会动态刷新,重新注入依赖。 nacos跟spring cloud集成的话,配置文件在bootstrap.yaml,变量会自动注入到environment中,因而可以使用@Value,spring cloud才有RefreshScope这个注解,表示可以动态刷新,会重新构建environment
@Configuration
@RefreshScope
@Slf4j
public class NacosCloudService implements InitializingBean
@Value("$spring.application.name")
private String appName;
// nacos注册进变量,自动refresh
@Value("$useLocalCache:false")
private boolean useLocalCache;
@Value("$myKey:112")
private int myKey;
@Autowired
private NacosConfigManager nacosConfigManager;
@Autowired
private NacosConfigProperties configProperties;
public boolean getSwitch()
return useLocalCache;
public int getMyKey()
return myKey;
@Override
public void afterPropertiesSet() throws Exception
// nacos spring cloud回调方式或者监听event
nacosConfigManager.getConfigService().addListener(appName + ".properties", configProperties.getGroup(),
new Listener()
@Override
public Executor getExecutor()
return null;
/**
* 接收整个配置文件的配置
* @param configInfo
*/
@Override
public void receiveConfigInfo(String configInfo)
log.info("收到spring cloud配置变更消息:", configInfo);
);
5.3 注册consul
这里将spring.application.name配置到bootstrap.yaml中,作为consul服务名,consul注册中心为本地consul。需要注意的是spring cloud服务注册,使用spring boot actuator做健康检查,所以需要引进spring-boot-actuator这个jar,默认开放health这个endpoint。另外,默认情况下server.port和management.port是同一个,且management的路径会基于servlet.path作为其前缀,再加上/actuator,需要保证向consul注册的健康检查url是正确的,可以调通的,否则会服务发现不会会报no provider。
bootstrap.yaml配置:
spring:
cloud:
consul:
discovery:
#注册到 Consul 的服务名称,后期客户端会根据这个名称来进行服务调用
serviceName: $spring.application.name
prefer-ip-address: true
ip-address: localhost
#consul注册中心地址
host: localhost
port: 8500
application:
name: spring-cloud-consumer
application.yaml配置:
server:
port: 9999
spring:
mvc:
servlet:
# 不能配置为/practice,否则健康检查不通过,服务发现不了
path: /
loadOnStartup: 1
throw-exception-if-no-handler-found: true
# actuator单独使用一个端口,避免与业务端口共用一个path
#management:
# server:
# port: 19999
5.4 ribbon配置,异常重试:
在bootstrap.yaml中添加如下配置,底层依然是http连接池。在provider超时时,会进行重试。运行时异常500,错误码不在范围内,不会进行重试。
#全局
ribbon:
#每台重试一次,不包括第一次
MaxAutoRetries: 0
#重试2台机器,不包括第一台,总共重试(MaxAutoRetries + 1) * (MaxAutoRetriesNextServer + 1)
MaxAutoRetriesNextServer: 2
#每次http请求的超时时间
ReadTimeout: 2000
ConnectTimeout: 2000
#如果对方返回404,抛出异常,以允许重试
retryableStatusCodes: 404,405,406
#所有异常都可以重试,否则只有get请求能重试
OkToRetryOnAllOperations: true
5.5 调用FeignClient
直接使用@Resource FeignClient API 即可调用
@RestController
@Slf4j
public class ConfigController
@Resource
private NacosCloudService nacosCloudService;
@Resource
private UserServiceFeignClient userServiceFeignClient;
/**
* http://localhost:9991/getUseLocalCache
*
* 修改值:
*
* curl -X POST "http://101.43.195.208:8848/nacos/v1/cs/configs?dataId=spring-cloud-consumer.properties&group=DEFAULT_GROUP&content=useLocalCache=true"
*
* @return
*/
@RequestMapping("/getUseLocalCache")
public boolean getUseLocalCache()
return nacosCloudService.getSwitch();
/**
* http://localhost:9991/getKey
*
* 修改值:
*
* curl -X POST "http://101.43.195.208:8848/nacos/v1/cs/configs?dataId=spring-cloud-consumer.properties&group=DEFAULT_GROUP&content=useLocalCache=true"
*
* @return
*/
@RequestMapping("/getKey")
public int getKey()
// spring cloud
return nacosCloudService.getMyKey();
/**
* http://localhost:9991/getUserByName?name=xiaoming
* @param name
* @return
*/
@RequestMapping("/getUserByName")
public User getUserByName(String name)
log.info("name is , myKey:", name, nacosCloudService.getMyKey());
return userServiceFeignClient.getByName(name);
consumer feign client调用基本原理:
a. feign client会使用jdk动态代理,代理逻辑见feign.ReflectiveFeign.FeignInvocationHandler#FeignInvocationHandler。对应的methodHandler为SynchronousMethodHandler。ReflectiveFeign#newInstance构建动态代理。
b. 代理逻辑最终会调用LoadBalancerFeignClient#execute()
feign.ReflectiveFeign.FeignInvocationHandler#invoke
-> feign.SynchronousMethodHandler#invoke
-> 执行RequestInteceptor
-> org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient#execute
-> RetryableFeignLoadBalancer#executeWithLoadBalancer
->com.netflix.client.AbstractLoadBalancerAwareClient#executeWithLoadBalancer(S, com.netflix.client.config.IClientConfig),利用LoadBalancerCommand进行处理,rxjava,会利用LoadBalancer进行服务发现和重试
->org.springframework.cloud.openfeign.ribbon.RetryableFeignLoadBalancer#execute,底层会调用RetryTemplate,可以对同一台服务实例进行重试。
RetryableFeginLoadBalancer#execute,底层封装了RetryTemplate,进行重试。doWithRetry->apache httpClient。均使用LoadBalancer选择服务实例,第一次使用外层设置的server,当失败时,底层可以通过lb重新选择一台服务实例。第一次:com.netflix.loadbalancer.reactive.LoadBalancerCommand#selectServer,重试见org.springframework.cloud.netflix.ribbon.RibbonLoadBalancedRetryPolicy#registerThrowable。注意,当没有服务示例可用时,会抛出异常,见com.netflix.loadbalancer.LoadBalancerContext#getServerFromLoadBalancer。
6. 定义provider
跟consumer很类似,spring.application.name改为:spring-cloud-provider。同时controller实现FeignClient API,其他的参考仓库:github仓库
@RestController
@Slf4j
public class HelloController implements UserServiceFeignClient
/**
* http://localhost:9999/getByName?name=xiaoming
* @param name
* @return
*/
@RequestMapping("/getByName")
@Override
public User getByName(String name)
log.info("provider 实现,name is ", name);
User user = new User();
user.setName(name);
user.setAge(18);
return user;
7. 测试
- 开启配置中心
本实例使用的是nacos,已经部署在云端,无需配置
- 开启注册中心
使用consul,则需要手动启动
consul agent -server -ui -dev
访问页面:
http://localhost:8500/ui
- 开启两个provider
provider1:
java -Dserver.port=9999 -jar provider/target/provider-0.0.1-SNAPSHOT.jar
provider2:
java -Dserver.port=9998 -jar provider/target/provider-0.0.1-SNAPSHOT.jar
可以看到会往consul上注册服务,且注册了actuator的健康检测连接,consul每10秒会调用这个链接检测存活。
- 开启一个consumer,并调用接口测试consumer
java -Dserver.port=9991 -jar consumer/target/consumer-0.0.1-SNAPSHOT.jar
通过consul ui可以看到已经启动了3个instance:
http://localhost:8500/ui/dc1/services
访问页面:
http://localhost:9991/getUserByName?name=xiaoming
consumer:
调用了provider2:
再次访问,则调用provider1:说明底层使用roundrobin负载均衡
- nacos测试:
nacos上的值:
调用http://localhost:9991/getKey,得到myKey的值:
修改myKey:
curl -X POST "http://101.43.195.208:8848/nacos/v1/cs/configs?dataId=spring-cloud-consumer.properties&group=DEFAULT_GROUP&content=myKey=123"
可以看到整个文件内容都被替换了,原来的useLocalCache也没有值了。
再次getKey:
6. 异常测试:
(1) 超时测试,会调用三次provider:
http://localhost:9991/timeout?timeout=2
consumer:
provider1:
provider2:
(2) 运行时异常,500,不重试
http://localhost:9991/fail?name=xiaoming
consumer:
provider2:
以上是关于spring cloud实践的主要内容,如果未能解决你的问题,请参考以下文章
苏宁Spring Cloud微服务脚手架工具vole实践分享
爱油科技基于Docker和Spring Cloud的微服务实践
Spring Cloud开发实践: 基于Consul和Spring Cloud 2021.0的演示项目
Spring Cloud 学习——7. Spring Cloud Config