简介
摘自百度百科:
SpringCloud是一系列框架的有序集合。它利用SpringBoot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用SpringBoot的开发风格做到一键启动和部署。SpringCloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过SpringBoot风格进行再封装屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。
反正SpringCloud是一个用来做微服务的框架,主要是基于SpringBoot来完成的,相关的微服务框架还有比较出名的Dubbo和DubboX。关于微服务是用来做什么的,有什么好处自行百度。
SpringCloud是Spring全家桶中的一员,所以SpringCloud对于Spring的其他组件都有着很好的支持。
SpringCloud主要采用的Eureka,Dubbo主要采用的是zookeeper。
上代码
provider 服务提供者
直接新建一个SpringBoot项目,由于是为了示例,本次示例中不使用数据库,只引入了spring-boot-starter-web
项目及其简单,只暴露了一个接口,用于测试,yml文件只配置了端口号为7900
consumer 服务消费者
同样新建一个SpringBoot项目,使其调用consumer的接口,同样只引入了spring-boot-starter-web
,yml文件只是修改了端口号为7910。
这里解释一下RestTemplates
这个类,关于这个类,这个老哥的解释我觉得非常完整了,RestTemplates
在SpringCloud中,消费者通过这个类访问生产者,@bean
注解是为了实例化这个类,实例化之后通过@AutoWired
注解引入,将其交给Spring进行管理。
分别启动两个类,分别访问localhost:7900/provider/demo
和http://localhost:7910/consumer/demo
应该都能得到ProviderDemo这个结果,说明consumer成功调用了provider中的方法。
问题:
这样的编码方式是将接口(http://localhost:7900/provider/demo
)硬编码在代码中,但是项目发布之后,ip地址必然是变动的。而且,硬编码的方式肯定是无法实现负载均衡的,就是说如果同时启动多个provider服务,这种硬编码方式是无法根据负载均衡策略去调用服务的。
解决方法:
在Dubbo中使用的ZooKeeper作为服务注册与发现的容器,在Springcloud中使用的是Eureka作为服务注册与发现的容器。
Eureka
同样创建一个SPringBoot项目,下面是pom.xml
文件,我是在idea中直接添加spring-cloud-starter-netflix-eureka-server
这个依赖的。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.edu.nuaa</groupId>
<artifactId>eureka-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>eureka-demo</name>
<description>eureka-demo</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
然后在Springboot的启动类上添加@EnableEurekaServer
或者@EnableDiscoveryClient
,这两个注解的注解的区别主要是:
@EnableEurekaServer
是基于 spring-cloud-netflix依赖,只能为eureka作用,是专门给Eureka用的 @EnableDiscoveryClient
是基于 spring-cloud-commons依赖,并且在classpath中实现,是给比如zookeeper、consul使用的,
旧版本的@EnableEurekaServer
的源码上面也是也是有@EnableDiscoveryClient
注解的。
本示例是使用Eureka的,故推荐使用@EnableEurekaServer
@SpringBootApplication
@EnableEurekaServer
public class EurekaDemoApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaDemoApplication.class, args);
}
}
然后在yml文件中配置如下信息:
server:
port: 8761
eureka:
client:
register-with-eureka: false
fetch-registry: false
service-url:
defaultZone: http://localhost:8761/eureka/
这个端口号官网设置成8761,这里也设置成8761;
`register-with-eureka: false
这个的默认值为true,设置为true不会对使用不会有很大的影响,但是在启动的时候会保下面的错误:
was unable to refresh its cache! status = Cannot execute request on any known server
是因为启动的时候自己注册了自己而引起的冲突
defaultZone配置eureka的地址,这个如果有多个注册中心,则用逗号隔开。
为了服务注册中心的安全考虑,很多时候我们都会为服务注册中心加入安全认证。通常与SpringSecurity整合,主要是SpringSecurity的内容,这里不做讨论(主要是我自己尝试的时候失败了,啊哈哈哈)。
配置信息完成之后,启动eureka项目,访问localhost:8761
便可以得到下面的界面,目前上面两个写的provider和consumer还没有注册到该注册中心:
关于Eureka配置的含义及其默认值主要参考下表:
服务注册类配置
省略了eureka.client前缀
参数名 | 说明 | 默认值 |
enabled | 启用Eureka客户端 | true |
registryFetchIntervalSeconds | 从Eureka服务端获取注册信息的间隔时间,单位为秒 | 30 |
instanceInfoReplicationIntervalSeconds | 更新实例信息的变化到Eureka服务端的间隔时间,单位为秒 | 30 |
initialInstanceInfoReplicationIntervalSeconds | 初始化实例信息到Eureka服务端的间隔时间,单位为秒 | 40 |
eurekaServiceUrlPollIntervalSeconds | 轮询Eureka服务端地址更改的间隔时间,单位为秒。当我们与Spring CLoud Config整合,动态刷新Eureka的serviceURL地址时需要关注该参数 | 300 |
eurekaServerReadTimeoutSeconds | 读取Eureka Server信息的超时时间,单位为秒 | 8 |
eurekaServerConnectTimeoutSeconds | 链接Eureka Server的超时时间,单位为秒 | 5 |
eurekaServerTotalConnections | 从Eureka客户端到所有Eureka服务端的连接总数 | 200 |
eurekaServerTotalConnectionsPerHost | 从Eureka客户端到每个Eureka服务端主机的连接总数 | 50 |
eurekaConnectionIdleTimeoutSeconds | Eureka服务端连接的空闲关闭时间,单位为秒 | 30 |
heartbeatExecutorThreadPoolSize | 心跳连接池的初始化线程数 | 2 |
heartbeatExecutorExponentialBackOffBound | 心跳超时重试延迟时间的最大乘数值 | 10 |
cacheRefreshExecutorThreadPoolSize | 缓存刷新线程池的初始化线程数 | 2 |
cacheRefreshExecutorExponentialBackOffBound | 缓存刷新重试延迟时间的最大乘数值 | 10 |
useDnsForFetchingServiceUrls | 使用DNS来获取Eureka服务端的serviceUrl | false |
registerWithEureka | 是否要将自身的实例信息注册到Eureka服务端 | true |
preferSameZoneEureka | 是否偏好使用处于相同Zone的Eureka服务端 | true |
filterOnlyUpInstances | 获取实例时是否过滤,仅保留UP状态的实例 | true |
fetchRegistry | 是否从Eureka服务端获取注册信息 | true |
服务实例类配置
eureka.instance.instanceId
为实例的id,可以自定义,默认值为
${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance_id:${server.port}}
省略了eureka.instance为前缀
参数名 | 说明 | 默认值 |
preferIpAddress | 是否优先使用IP地址作为主机名的标识 | false |
leaseRenewalIntervalInSeconds | Eureka客户端向服务端发送心跳的时间间隔,单位为秒 | 30 |
leaseExpirationDurationInSeconds | Eureka服务端在收到最后一次心跳之后等待的时间上限,单位为秒。超过该时间之后服务端会将该服务实例从服务清单中剔除,从而禁止服务调用请求被发送到该实例上 | 90 |
nonSecurePort | 非安全的通信端口号 | 80 |
securePort | 安全的通信端口号 | 443 |
nonSecurePortEnabled | 是否启用非安全的通信端口号 | true |
securePortEnabled | 是否启用安全的通信端口号 | |
appname | 服务名,默认取spring.application.name的配置值,如果没有则为unknown | |
hostname | 主机名,不配置的时候讲根据操作系统的主机名来获取 |
注册服务到Eureka
回到之前写的provider-demo项目中,在项目中添加下面这个依赖,注意version:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>RELEASE</version>
</dependency>
在之前的SpringCloud版本中,还有出现过下面的依赖,但是目前最新的版本是上面这个,亲测,在本示例中两个都是可以的。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
<version>RELEASE</version>
</dependency>
然后在项目的启动类上添加@EnableEurekaClient
注解
@SpringBootApplication
@EnableEurekaClient
public class ProviderDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderDemoApplication.class, args);
}
}
从依赖的名字spring-cloud-starter-netflix-eureka-client
的名字就知道这个依赖是用来做eureka的客户端,然后注册服务到eureka上的。然后在项目的启动类上的添加的注解为@EnableEurekaClient
回到上文中会发现搭建eureka注册中心的时候会发现引入的包是spring-cloud-starter-netflix-eureka-server
,在项目的启动类上的添加的注解为@EnableEurekaServer
还是很好记忆和理解的。
最后在yml文件中添加上eureka注册中心的地址并可以了:
server:
port: 7900
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
分别启动一下eureka和provider项目,然后访问一下localhost:8761
便会得到下面的页面:
可以看到provider的服务已经注册上eureka上了。
发现application的名称为UNKNOWN,可以在yml文件中添加下面内容修改名字:
spring:
application:
name: provider-demo
重启一下服务便可以看到名字,这里就不贴图片了。
有可能会遇到下面的红色字体:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
这个主要是eureka的自我保护的心跳机制导致的,即如果真实的服务已经Down掉,但在注册中心界面服务却一直存在,且显示为UP状态。解决方法可以添加下面内容,上面有eureka的配置信息中解释含义:
eureka:
client:
serviceUrl:
defaultZone: http://localhost:8761/eureka/
instance:
lease-renewal-interval-in-seconds: 1
lease-expiration-duration-in-seconds: 2
服务监控
对于服务的众多信息可能都需要监控和监听,SpringCloud主要采用的是下面这个依赖对其实现监听的:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
添加这个依赖之后重启服务会发现console中多了下图这几个端点:
/actuator/health和/actuator/info以及/actuator这三个就是actuator提供的端点,注意以前的版本是没有/actuator前缀的,2.0以后的版本都加了/actuator前缀。可以访问localhost:7900/actuator
尝试看监听的都是一些什么内容
如果要开启全部的监听端点,可以在yml文件中加入下面的配置信息:
management:
endpoints:
web:
exposure:
include: "*"
重启服务便可以看到所有的端点都开启用了。
主要的端点的含义可以参考下面的表格:
端点 | 描述 |
actuator | 为其他端点提供“发现页面”。要求Spring HATEOAS在classpath路径上。 |
auditevents | 陈列当前应用程序的审计事件信息。 |
autoconfig | 展示自动配置信息并且显示所有自动配置候选人以及他们“被不被”应用的原因。 |
beans | 显示应用程序中所有Spring bean的完整列表。 |
configprops | 显示所有配置信息。 |
dump | dump所有线程。 |
env | 陈列所有的环境变量。 |
flyway | Shows any Flyway database migrations that have been applied. |
health | 显示应用程序运行状况信息 |
info | 显示应用信息。 |
loggers | 显示和修改应用程序中的loggers配置。 |
liquibase | 显示已经应用的任何Liquibase数据库迁移。 |
metrics | 显示当前应用程序的“指标”信息。 |
mappings | 显示所有@RequestMapping的url整理列表。 |
shutdown | 关闭应用(默认情况下不启用)。 |
trace | 显示跟踪信息(默认最后100个HTTP请求)。 |
使用同样的方法把consumer-demo一起注册到Eureka上。
ribbon
上文只是将服务注册到eureka上,但是consumer还是硬编码调用,前文也有提到这种硬编码方式肯定是不合理的,一来服务上线之后,IP地址肯定是变动的, 再则,采用硬编码的方式是无法实现负载均衡的。
ribbon便是一个用来做负载均衡的组件。
点进spring-cloud-starter-netflix-eureka-client
依赖,会发现,这个依赖中已经添加了spring-cloud-starter-netflix-ribbon
,故在项目中可以直接使用ribbon,不用重新添加依赖。
在加载restTemplates的方法上添加@LoadBalanced
注解,使其具有负载均衡的能力,然后将硬编码的ip地址换成服务提供者的应用名字(application.name
属性的值),修改之后controller便变成了下面的样子,其他地方不做修改。
@RestController
public class ConsumerController {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/consumer/demo")
public String ConsumerDemo(){
return this.restTemplate.getForObject("http://provider-demo:7900/provider/demo", String.class);
}
}
重启一下Eureka,provider-demo,consumer-demo,分别访问localhost:7900/provider/demo
和http://localhost:7910/consumer/demo
应该都能得到ProviderDemo 这个结果,说明consumer成功调用了provider中的方法。便解决了不采用硬编码的方式,使得consumer-demo调用provider-demo接口。
ribbon默认的负载均衡策略是轮询的。测试一下:
首先启动多个provider-demo项目,我这里分别用的接口是7900和7901两个接口,可以在Eureka中看一下provider的信息:
然后在consumer-demo项目的controller中添加下面的内容,代码应该很容易看懂就是模拟loadBalancerClient
客户端来访问名字为provider-demo
的服务接口,然后输出访问的接口端口号。
@Autowired
private LoadBalancerClient loadBalancerClient;
@RequestMapping("/consumer/getInterfaceInfo")
public void getInterfaceInfo(){
ServiceInstance choose = loadBalancerClient.choose("provider-demo");
System.out.println(choose.getPort());
}
然后分别访问一下多次访问localhost:7910//consumer/getInterfaceInfo
这个URL,可以看到控制台输出的信息在下面,很容易看出是轮询。
7901
7900
7901
7900
7901
7900
7901
7900
7901
7900
7901
ribbon有多种修改负载均衡的策略,可以通过代码,也可以通过配置文件,个人觉得配置文件的方法比较方便,其他的方法可以自行百度:
在consumer-demo的application.yml
文件中添加下面的内容,这里使用的是随机的策略,该测试就不贴图了,反正最后结果是随机的:
#服务提供者的名字
provider-demo:
ribbon:
# 所要采用的策略
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
ribbon所有的策略可以参照下表:
策略名 | 策略描述 | 实现说明 |
BestAvailableRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server |
AvailabilityFilteringRule | 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态 |
WeightedResponseTimeRule | 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 | 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成status时,使用roubine策略选择server。 |
RetryRule | 对选定的负载均衡策略机上重试机制。 | 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server |
RoundRobinRule | roundRobin方式轮询选择server | 轮询index,选择index对应位置的server |
RandomRule | 随机选择一个server | 在index上随机,选择index对应位置的server |
ZoneAvoidanceRule | 复合判断server所在区域的性能和server的可用性选择server | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。 |