Spring提供了一系列工具,可以帮助开发人员迅速搭建分布式系统中的公共组件。协调分布式环境中各个系统,为各类服务提供模板性配置。使用Spring Cloud, 开发人员可以搭建实现了这些样板的应用,并且在任何分布式环境下都能工作得非常好,小到笔记本电脑, 大到数据中心和云平台。
eureka注册中心
简介
Eureka是Netflix开发的服务发现框架,本身是一个基于REST的服务,主要用于定位运行在AWS域中的中间层服务,以达到负载均衡和中间层服务故障转移的目的。SpringCloud将它集成在其子项目spring-cloud-netflix中,以实现SpringCloud的服务发现功能。
Eureka包含两个组件:Eureka Server和Eureka Client。
Eureka Server
它提供了注册服务的功能,各个节点启动后,会在Eureka Server中进行注册,这样EurekaServer中的服务注册表中会将存储所有可用服务节点的信息,服务节点的信息可以在界面中直观的看到。
Eureka Client
它是一个Java客户端,用于简化与Eureka Server的交互,客户端同事也就是一个内置的、使用轮询(round-robin)负载算法的负载均衡器。
在应用启动后,将会向Eureka Server发送心跳,默认周期为30秒,如果Eureka Server在多个心跳周期内没有接受到某个节点的心跳,Eureka Server将会从服务注册表中把这个服务节点移除(默认90秒)。
Eureka Server之间通过复制的方式完成数据的同步,Eureka还提供了客户端缓存机制,即时所有的Eureka Server都挂掉,客户端依然可以利用缓存中的信息消费其他服务的API。综上,Eureka通过心跳检查、客户端缓存等机制,确保了系统的高可用性、灵活性和可伸缩性。
Eureka现已闭源,停止更新,处于维护阶段。不过目前已经很稳定,依旧可以使用。也可以使用Consul、zookeeper等替换
高可用环境搭建
Eureka Server 高可用环境需要部署两个Eureka server,它们互相向对方注册。如果在本机启动两个Eureka需要注意两个Eureka Server的端口要设置不一样,这里我们部署一个Eureka Server工程,将端口可配置,制作两个Eureka Server启动脚本,启动不同的端口,如下图:
1、在实际使用时Eureka Server至少部署两台服务器,实现高可用。
2、两台Eureka Server互相注册。
3、微服务需要连接两台Eureka Server注册,当其中一台Eureka死掉也不会影响服务的注册与发现。
4、微服务会定时向Eureka server发送心跳,报告自己的状态。
5、微服务从注册中心获取服务地址以RESTful方式发起远程调用。
最外层父工配置pom.xml
<?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>com.itheima</groupId>
<artifactId>springcloud-parent</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>springcloud-server</module>
<module>springcloud-provider</module>
<module>springcloud-consumer</module>
<module>springcloud-provider1</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring.boot.version>2.1.2</spring.boot.version>
<spring.cloud.version>Greenwich.RELEASE</spring.cloud.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<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>
</project>
eureka注册中心:
首先编写eureka注册中心,也就是服务端。
该注册中心继承于上面方父module:springcloud-parent
pom.xml
<?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">
<parent>
<artifactId>springcloud-parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jaxb.version>2.3.1</jaxb.version>
</properties>
<artifactId>springcloud-server</artifactId>
<dependencies>
<!-- 引入eureka server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
</dependencies>
</project>
配置文件:application.yml
server:
port: ${PORT:8080}
# springCloud应用都要指定application.name
spring:
application:
name: spring-eureka-server
# 默认情况下eureka server也是一个eureka client,必须指定一个server
eureka:
instance:
hostname: ${EUREKA_DOMAIN:eureka01}
server:
enable-self-preservation: false #是否开启自我保护模式
eviction-interval-timer-in-ms: 60000 #服务注册表清理间隔(单位毫秒,默认是60*1000)
client:
#register-with-eureka = false 和fetchRegistry=false 表明自己是一个eureka server
registerWithEureka: true #服务注册,是否将自己注册到Eureka服务中
fetchRegistry: true #服务发现,是否从Eureka中获取注册信息
serviceUrl: #Eureka客户端与Eureka服务端的交互地址,高可用状态配置对方的地址,单机状态配置自己(如果不配置则默认本机8761端口)
defaultZone: ${EUREKA_SERVER:http://eureka02:8081/eureka/}
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ //单个eureka注册中心,配置自己
启动类 Application.class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@SpringBootApplication
@EnableEurekaServer // 启用EurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
这里在本机启动两个Eureka需要注意两个Eureka Server的端口要设置不一样,这里我们部署一个Eureka Server工程,将端口可配置,制作两个Eureka Server启动脚本,启动不同的端口
-DPORT=8080 -DEUREKA_SERVER=
http://eureka02:8081/eureka/ -DEUREKA_DOMAIN=eureka01
-DPORT=8081 -DEUREKA_SERVER=
http://eureka02:8080/eureka/ -DEUREKA_DOMAIN=eureka02
启动服务后,浏览器访问http://localhost:8080或者http://localhost:8081就是eureka的控制台了,这里可以看到注册的所有应用:
服务提供者
provider:
这里需要引入依赖eureka-client
【不管是服务提供者还是消费者,在eureka眼里都是客户端】
:
pom.xml
<?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">
<parent>
<artifactId>springcloud-parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-provider</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 引入eureka client -->
<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>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
搭建provider集群时通过
spring.application.name===>
spring-eureka-provider相互调用
配置文件中需要指定应用名称和配置中心地址:
application.yml
server:
port: 8082
# 服务于服务之间相互调用一般是根据这个name,搭建provider集群时通过此调用
spring:
application:
name: spring-eureka-provider
eureka:
client:
registerWithEureka: true #服务注册开关
fetchRegistry: true #服务发现开关
serviceUrl: #Eureka客户端与Eureka服务端进行交互的地址,Eureka服务端为集群时,多个中间用逗号分隔
defaultZone: ${EUREKA_SERVER:http://localhost:8080/eureka/,http://localhost:8081/eureka/}
instance:
prefer-ip-address: true #将自己的ip地址注册到Eureka服务中
ip-address: ${IP_ADDRESS:127.0.0.1}
instance-id: ${spring.application.name}:${server.port} #指定实例id
编写启动类,顺便提供一个restAPI服务
启动类:ProviderApplication.class
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableEurekaClient
@RestController
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
}
@Value("${spring.application.name}")
String providerName;
@Value("${server.port}")
String port;
@GetMapping("/")
public String demo(String name) {
return String.format("hello %s ,this is %s and port is %s", name, providerName, port);
}
}
允许平行运行,修改server.port运行两个provider
在eureka控制台查看信息,发现注册了两个服务
服务消费者
consumer:
ribbon客户端负载均衡:
springcloud提倡微服务采用
rest http
的方式。消费者在注册中心中发现服务后,需要通过
负载均衡
进行调度,springcloud全家桶提供了两种服务调用方式,一种是
ribbon+restTemplate
,另一种是
feign
。其中
feign
底层使用了
ribbon
。本文介绍
ribbon
。
pom.xml
<?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">
<parent>
<artifactId>springcloud-parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入 netflix-ribbon -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.7.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
application.yml
server:
port: 8085
spring:
application:
name: springcloud-consumer
eureka:
client:
service-url:
defaultZone: ${EUREKA_SERVER:http://localhost:8080/eureka/,http://localhost:8081/eureka/}
ribbon:
MaxAutoRetries: 2 #最大重试次数,当Eureka中可以找到服务,但是服务连不上时将会重试
MaxAutoRetriesNextServer: 3 #切换实例的重试次数
OkToRetryOnAllOperations: false #对所有操作请求都进行重试,如果是get则可以,如果是post,put等操作没有实现幂等的情况下是很危险的,所以设置为false
ConnectTimeout: 5000 #请求连接的超时时间
ReadTimeout: 6000 #请求处理的超时时间
启动类 ConsumerApplication.class
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableEurekaClient
//@EnableDiscoveryClient // 启用服务发现组件 如果选用的注册中心是eureka,那么就推荐@EnableEurekaClient,如果是其他的注册中心,那么使用@EnableDiscoveryClient。
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
/**
* springcloud中提倡rest风格的微服务
* 向spring容器中注入RestTemplate
*
* 使用LoadBalanced表明这个restRemplate开启负载均衡的功能。
*/
@Bean
@LoadBalanced
RestTemplate restTemplate(){
return new RestTemplate();
}
}
启动类中通过注解启用了Eureka和服务发现组件,并且向spring容器中注入了开启了负载均衡功能的RestTemplate。
controller
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
@RestController
public class HelloController {
@Autowired
RestTemplate restTemplate;
@GetMapping("/")
public String sayHi(String name) {
return restTemplate.getForObject("http://SPRING-EUREKA-PROVIDER?name=" + name, String.class);
}
}
这里直接写的是服务名: SPRING-EUREKA-PROVIDER 。在ribbon中它会根据服务名来选择具体的服务实例,根据服务实例在请求的时候会用具体的url替换掉服务名。
启动服务,浏览器多次访问http://localhost:8085/?name=zhangsan,会发现结果会轮询出现 port 8081和port8082。这是因为我们服务提供者启动了两个,端口分别是8082和8083,通过ribbon进行了客户端负载均衡。
这时我们再看一下注册中心,里面展示了两个服务提供者,一个消费者
feign客户端负载均衡:
将robbin依赖修改为feign
<?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">
<parent>
<artifactId>springcloud-parent</artifactId>
<groupId>com.itheima</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>springcloud-consumer_feign</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--引入eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!--引入 feign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.7.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.1.2.RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
启动类开启feign服务
@SpringBootApplication
@EnableEurekaClient
//@EnableDiscoveryClient // 启用服务发现组件 如果选用的注册中心是eureka,那么就推荐@EnableEurekaClient,如果是其他的注册中心,那么使用@EnableDiscoveryClient。
@EnableFeignClients //开启Feign的功能
public class ConsumerFeignApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerFeignApplication.class, args);
}
}
feign采用这种编写方式,声明一个interface接口,开启Feign负载均衡
/**
* 通过FeignClient配置负载均衡,指定了服务提供者的名字
*/
@FeignClient(value = "spring-eureka-provider")
public interface FeignService {
//这里指定请求调用的rest URL
@RequestMapping(value = "/", method = RequestMethod.GET)
String sayHi(@RequestParam(value = "name") String name);
@RequestMapping(value = "/getname/{id}", method = RequestMethod.GET)
public String get(@PathVariable("id") long id);
}
编写controller调用服务
@RestController
public class HelloController {
// @Autowired
// RestTemplate restTemplate;
@Autowired
FeignService feignService;
@GetMapping("/")
public String sayHi(String name) {
// return restTemplate.getForObject("http://SPRING-EUREKA-PROVIDER?name=" + name, String.class);
return feignService.sayHi(name);
}
}
启动服务,浏览器访问同样会有两个port结果
http://localhost:8085/name=zhangsan
spring cloud的 Netflix 中提供了两个组件实现软负载均衡调用:ribbon 和 feign 。
Ribbon
是一个基于 HTTP 和 TCP 客户端 的负载均衡的工具。
它可以 在客户端 配置 RibbonServerList(服务端列表),使用 HttpClient 或 RestTemplate 模拟http请求,步骤相当繁琐。
Feign
Feign 是在 Ribbon的基础上进行了一次改进,是一个使用起来更加方便的 HTTP 客户端。
采用接口的方式, 只需要创建一个接口,然后在上面添加注解即可 ,将需要调用的其他服务的方法定义成抽象方法即可, 不需要自己构建http请求。
然后就像是调用自身工程的方法调用,而感觉不到是调用远程方法,使得编写 客户端变得非常容易。
类似于 mybatis 的 @Mapper注解 。
注意:spring-cloud-starter-feign 里面已经包含了 spring-cloud-starter-ribbon(Feign 中也使用了 Ribbon)
自我保护机制:
官方对于自我保护机制的定义:
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行。
自我保护机制的工作机制是如果在15分钟内超过85%的客户端节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制,此时会出现以下几种情况:
-
Eureka Server不再从注册列表中移除因为长时间没收到心跳而应该过期的服务。
-
Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用。
-
当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中。
因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况,而不会像ZK那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪。
关闭自我保护
服务端配置
eureka:
server:
# 测试时关闭自我保护机制,保证不可用服务及时踢出
enable-self-preservation: false
客户端配置
# 心跳检测检测与续约时间
# 测试时将值设置设置小些,保证服务关闭后注册中心能及时踢出服务
eureka:
instance:
lease-renewal-interval-in-seconds: 1 # 每间隔1s,向服务端发送一次心跳,证明自己依然”存活“
lease-expiration-duration-in-seconds: 2 # 告诉服务端,如果我2s之内没有给你发心跳,就代表我“死”了,将我踢出掉
这个只适用于测试环境,在生产环境不要关闭
##@EnableDiscoveryClient与@EnableEurekaClient
这两个注解非常类似,都是用作启用服务发现的
如果选用的注册中心是eureka,那么就推荐@EnableEurekaClient,如果是其他的注册中心,那么使用@EnableDiscoveryClient。