spring cloud 介绍
从 Spring Boot 到 Spring Cloud
Spring Cloud 具备一个天生的优势,因为它是 Spring 家庭的一员,而 Spring 在 Java EE 开发领域的强大地位,给 Spring Cloud 起到很好的推动作用。同时,Spring Cloud 所基于的 Spring Boot,已经成为 Java EE 领域中最流行的开发框架,用来简化 Spring 应用程序的框架搭建和开发过程。
在微服务架构中,我们将通过 Spring Boot 来开发单个微服务。同样作为 Spring 家族的新成员,Spring Boot 提供了令人兴奋的特性,这些特性主要体现在开发过程的简单化,包括支持快速构建项目、不依赖外部容器独立运行、开发部署效率高,以及与云平台天然集成等。而在微服务架构中,Spring Cloud 构建在 Spring Boot 之上,继承了 Spring Boot 的多项功能特性,使得开发微服务变得简单而高效。
在设计思想上,Spring Boot 充分利用约定优于配置(Convention over Configuration)的自动化配置机制。与传统的 Spring 应用程序相比, Spring Boot 在启动依赖项自动管理、简化部署并提供应用监控等方面对开发过程做了优化
Spring Cloud 中的核心组件
Spring Cloud Netflix 组件,其中集成了 Netflix OSS 的 Eureka 注册中心、Zuul 网关、Hystrix 熔断器等工具
服务治理的基本需求
在微服务架构中,任何一个服务既可以是服务的提供者,也可以是服务的消费者(调用者)。围绕服务消费者如何调用服务提供者这个问题,需要进行服务的治理。
服务治理最基本的需求是要支持服务的注册和发现,而且这个过程是自动的。
高可用,构建高可用集群,服务只要连接集群中的任何一台注册中心完成服务的注册和发现即可。单台服务器的宕机不影响服务的正常运行。
注册中心是一种服务器组件。
难度在于服务实例变更时,如何同步到服务的消费者?从架构设计上讲,状态变更管理可以采用发布-订阅模式,服务提供者可以根据服务定义发布服务。发布订阅采用监听机制或主动拉取的轮询策略。
服务治理实现工具
Consul
Zookeeper 采用监听机制
Netflix Eureka 采用轮询(默认同步频率为30
s)
基于Eureka构建注册中心
构建单个Eureka服务
一、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
EurekaServerApplication
package com.springhealth.eurekaserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
客户端配置
server:
port: 8761
eureka:
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://localhost:8761
server:
waitTimeInMsWhenSyncEmpty: 0
logging:
level:
com.netflix: WARN
org.springframework.web: WARN
com.tianyalan: INFO
构建Eureka服务器集群
peer awareness 模式
application-eureka1.yml
server:
port: 8761
eureka:
instance:
hostname: eureka1
client
serviceUrl
defaultZone: http:// eureka2:8762/eureka/
application-eureka2.yml
server:
port: 8762
eureka:
instance:
hostname: eureka2
client
serviceUrl
defaultZone: http://eureka1:8761/eureka/
Eureka服务器实现原理
多台Eureka相互注册形成集群,服务提供者可以向注册中心注册、续约、取消
服务消费者可以中注册中心中获取注册信息来进行调用。
显然对于一个注册中心而言,要理解它的设计理念,需要关注Eureka如何对服务注册信息的存储和管理的具体机制。
Eureka的服务存储和缓存处理
InstanceRegistry 接口的实现类 AbstractInstanceRegistry 中发现了 Eureka 用于保存注册信息的数据结构
private final ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>> registry =
new ConcurrentHashMap<String, Map<String, Lease<InstanceInfo>>>();
而对于 InstanceRegistry 本身,它也继承了 Eureka 中两个非常重要的接口,即LeaseManager 接口和 LookupService 接口
public interface LeaseManager<T> {
void register(T r, int leaseDuration, boolean isReplication);
boolean cancel(String appName, String id, boolean isReplication);
boolean renew(String appName, String id, boolean isReplication);
void evict();
}
显然 LeaseManager 做的事情就是 Eureka 注册中心模型中的服务注册、服务续约、服务取消和服务剔除等核心操作,关注于对服务注册过程的管理。而 LookupService 接口定义如下,关注于对应用程序与服务实例的管理:
public interface LookupService<T> {
Application getApplication(String appName);
Applications getApplications();
List<InstanceInfo> getInstancesById(String id);
InstanceInfo getNextServerFromEureka(String virtualHostname, boolean secure);
}
Eureka 客户端并理解其实现原理
一、引入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
UserApplication
package com.springhealth.user;
import org.springframework.boot.SpringApplication;
import org.springframework.cloud.client.SpringCloudApplication;
@SpringCloudApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class, args);
}
}
application.yml
server:
port: 8082
spring:
cloud:
config:
enabled: true
uri: http://localhost:8888
logging:
level:
com.netflix: WARN
org.springframework.web: WARN
com.tianyalan: INFO
eureka:
instance:
preferIpAddress: true
client:
registerWithEureka: true
fetchRegistry: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
serviceUrl.defaultZone 指定的就是 Eureka 服务器的地址。
获取注册到 Eureka 服务器上具体某一个服务实例的详细信息,我们可以访问如下地址:
http://<eureka-ip-port>:8761/eureka/apps/<APPID>
例如浏览器输入 http://localhost:8761/eureka/apps/userservice
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<application>
<name>USERSERVICE</name>
<instance>
<instanceId>192.168.1.11:userservice:8083</instanceId>
<hostName>192.168.1.11</hostName>
<app>USERSERVICE</app>
<ipAddr>192.168.1.11</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8083</port>
<securePort enabled="false">443</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1608016285007</registrationTimestamp>
<lastRenewalTimestamp>1608016464974</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1608016285007</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8083</management.port>
<jmx.port>50708</jmx.port>
</metadata>
<homePageUrl>http://192.168.1.11:8083/</homePageUrl>
<statusPageUrl>http://192.168.1.11:8083/actuator/info</statusPageUrl>
<healthCheckUrl>http://192.168.1.11:8083/actuator/health</healthCheckUrl>
<vipAddress>userservice</vipAddress>
<secureVipAddress>userservice</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1608016285007</lastUpdatedTimestamp>
<lastDirtyTimestamp>1608016284958</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
<instance>
<instanceId>192.168.1.11:userservice:8082</instanceId>
<hostName>192.168.1.11</hostName>
<app>USERSERVICE</app>
<ipAddr>192.168.1.11</ipAddr>
<status>UP</status>
<overriddenstatus>UNKNOWN</overriddenstatus>
<port enabled="true">8082</port>
<securePort enabled="false">443</securePort>
<countryId>1</countryId>
<dataCenterInfo class="com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo">
<name>MyOwn</name>
</dataCenterInfo>
<leaseInfo>
<renewalIntervalInSecs>30</renewalIntervalInSecs>
<durationInSecs>90</durationInSecs>
<registrationTimestamp>1608018298663</registrationTimestamp>
<lastRenewalTimestamp>1608019318854</lastRenewalTimestamp>
<evictionTimestamp>0</evictionTimestamp>
<serviceUpTimestamp>1608016335919</serviceUpTimestamp>
</leaseInfo>
<metadata>
<management.port>8082</management.port>
<jmx.port>51602</jmx.port>
</metadata>
<homePageUrl>http://192.168.1.11:8082/</homePageUrl>
<statusPageUrl>http://192.168.1.11:8082/actuator/info</statusPageUrl>
<healthCheckUrl>http://192.168.1.11:8082/actuator/health</healthCheckUrl>
<vipAddress>userservice</vipAddress>
<secureVipAddress>userservice</secureVipAddress>
<isCoordinatingDiscoveryServer>false</isCoordinatingDiscoveryServer>
<lastUpdatedTimestamp>1608018298663</lastUpdatedTimestamp>
<lastDirtyTimestamp>1608018298634</lastDirtyTimestamp>
<actionType>ADDED</actionType>
</instance>
</application>
Eureka 客户端基本原理
对于 Eureka 而言,微服务的提供者和消费者都是它的客户端,其中服务提供者关注服务注册、服务续约和服务下线等功能,而服务消费者关注于服务信息的获取。同时,对于服务消费者而言,为了提高服务获取的性能以及在注册中心不可用的情况下继续使用服务,一般都还会具有缓存机制。
boolean register() throws Throwable {
EurekaHttpResponse<Void> httpResponse;
try {
httpResponse = eurekaTransport.registrationClient.register(instanceInfo);
} catch (Exception e) {
throw e;
}
return httpResponse.getStatusCode() == 204;
}
上述 register 方法会在 InstanceInfoReplicator 类的 run 方法中进行执行。从操作流程上讲,上述代码的逻辑非常简单,即服务提供者先将自己注册到 Eureka 服务器中,然后根据返回的结果确定操作是否成功。显然,这里的重点代码是eurekaTransport.registrationClient.register(),DiscoveryClient 通过这行代码发起了远程请求。