一、服务发现简介
1、服务发现产生的背景
先看下边一段代码,传统中我们习惯于这样操作,可能变动信息,如ip,端口提取到配置文件中,方便修改。 然而这种方式是存在很多局限性的。
- 适用场景局限:若服务提供者的地址发生了变化,它的消费者也会跟着变动,并重新发布,显然不可取的。
- 无法动态伸缩:生产环境,同一服务一般是多实例部署,实现容灾及负载均衡,根据需要动态增减节点, 传统的这种做法是无法适应这种要求的。
@Autowired
private RestTemplate restTemplate;
@Value("${user.phoneServiceUrl}")
private String phoneServiceUrl;
@GetMapping("/user/{id}")
public User queryPhoneInfo(@PathVariable("id") long id) {
return restTemplate.getForObject(phoneServiceUrl+id, User.class);
}
面对以上问题服务发现便应运而生
2、服务发现带来的好处
服务发现组件提供一种能力——即使服务提供方的信息发生变化,服务消费者也无需跟着修改。在微服务架构中,服务发现组件是一个非常关键的组件。使用服务发现组件的架构图如下所示
服务提供者、服务消费者、服务发现组件三者之间的关系大致如下:
- 各个微服务在启动时,将自己的网络地址等信息注册到服务发现组件中,服务发现组件就储存了这些信息。
- 服务消费者可从服务发现组件查询服务提供者的网络地址,并用该地址去调用服务提供者提供的接口
- 各个微服务与服务发现组件使用一定的机制(如发送心跳)通信。服务发现组件达到某个指定的时间与某个服务实例无法通信,便会注销该实例。
- 当服务提供者地址发生改变时候,会重新注册到服务发现组件,而服务消费者是去服务发现组件中获取服务提供者的网络地址,无需手动修改地址。
综上服务发现组件应具备以下功能
服务注册表:它是服务发现组件的核心,存储着各个微服务注册来的信息,如服务名称、地址、端口等信息。服务注册表提供对API的查询与管理功能,查询API可用于查询可用的微服务实例,管理API可用于服务的注册和注销。
服务注册与服务发现:服务注册是指微服务在启动时将自己的信息注册到服务发现组件上的过程,服务发现是指查询可用微服务列表及其网络地址的机制。
服务检查 :服务发现组件会使用一定的机制定期检测已注册的服务,如发现某服务长时间无法访问,就会从服务注册表中移除该实例。
二、Eureka简介
Euraka是Netflix公司开源的服务发现组件,是一个基于REST的服务,用于定位服务,包含Server和Client两部分。SpringCloud将它集成在子项目Spring Cloud Netflix中,从而实现微服务的注册与发现。
三、Eureka工作原理
下图再现了Eureka官方的架构图,该图比较详细地展示了Eureka的工作原理。
- App Service:相当于服务提供者
- App Client:相当于服务消费者
- Make Remote Call:可理解成调用RESTful API的行为
- us-east-1c、us-east-1d、us-east-1e都是zone,都属于us-east-1这个region
Eureka包含两个组件:Eureka Server和Eureka Client
Eureka Server提供服务发现能力,各个微服务启动时会向Eureka Server注册自己的信息(服务名、IP、端口等),Eureka Server便存储了这个信息。
Eureka Client 是一个Java客户端,用于简化与Eureka的交互。
每个微服务启动后, 会每个一定时间(默认30s)向Eureka Server 发送心,让其知道自己扔健康存活。如果Eureka Server在一定时间内(默认90s)没有收到某个微服务实例的心跳,Eureka Server就会注销该实例。
默认情况下,Eureka Server也是Eureka Client,多个Eureka Server之间通过复制方式,来实现服务注册表中的数据同步。
Eureka Client会缓存服务注册表中的信息, 无须每次都请求Eureka Server查询,缓解了Eureka Server的压力;即使Eureka Server所有节点都宕机,服务消费者依然可以查询自己缓存中的信息找到去调用服务提供者
四、Eureka的自我保护机制
有时候我们会看到以下警告信息,这个Eureka web页面输出的警告。默认情况下Eureka Server在90s内没有收到某个微服务实例的心跳,Euraka Server将会注销该实例。但是当网络出现短暂分区故障,微服务与网络之间无法正常通信,Eureka Server可能会注销无法正常通信的服务,导致服务不可用。
THE SELF PRESERVATION MODE IS TURNED OFF.THIS MAY NOT PROTECT INSTANCE EXPIRY IN CASE OF NETWORK/OTHER PROBLEMS
Euraka 通过“自我保护机制”来解决这个问题,当Eureka Server在短时间内丢失过多客户端时候,有可能是发生了网络分区故障,那么Eureka Server就会进入自我保护模式。一旦进入该模式Eureka Server就会保护服务注册表中的信息,不在删除。当网络故障恢复时,Eureka Server会自动退出自我保护模式。默认开启自我保护,可以通过设置eureka.server.enable-self-preservation=false禁用自我保护模式
五、服务注册到Eureka
新建以下三个模块,举例说明Eureka的注册与发现
microservice-cloud-parent parent pom
microservice-discover-eureka Server注册中心
microservice-provider-order 服务提供者
microservice-consumer-pay 服务消费者
1、microservice-cloud-parent
在pom.xml中添加Eureka依赖,子模块pom.xml继承父模块,不再展示
<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.ultradata.task</groupId>
<artifactId>microservice-cloud-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/>
</parent>
<modules>
<module>microservice-discover-eureka</module>
<module>microservice-provider-order</module>
<module>microservice-consumer-pay</module>
</modules>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--开启安全认证时需要配置,不开启无需配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Cairo-SR2</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>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
</repository>
</repositories>
</project>
2、microservice-discover-eureka
(1)application.yml中添加以下配置
server:
port: 8761
spring:
security:
user:
name: eureka
password: eureka
application:
name: microservice-discover-eureka
eureka:
instance:
prefer-ip-address: true
hostname: localhost
client:
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
fetch-registry: false
register-with-eureka: false
- eureka.client.register-with-eureka,表示是否将自己注册到Eureka Server,默认值是true,本身就是Eureka Server,无须注册,故而这里设置为false。
- eureka.client.fetch-registry,表示是否从获取信息,默认true,单节点Eureka Server不需要获取其他Eureka Server信息来同步,这里设置为false.
- eureka.client.service-url.defaultZone,设置Eureka Server的交互地址,查询与注册服务都需要依赖这个地址。默认值http://localhost:8761/eureka/,多个地址用逗号分隔。
- spring.application.name服务注册到Eureka Server的名称。
- euraka.instance.prefer-ip-address,是否将微服务所在操作系统的hostname注册到Eureka Server。默认值false,这里设置为true,表示将微服务所在机Ip注册到Eureka Server。
(2)Eureka Server启动类,@EnableEurekaServer注解声明该服务是Eureka Server
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class EurekaDiscoveryApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(EurekaDiscoveryApplication.class, args);
}
}
3、microservice-provider-order
(1)提供者application.yml配置
spring:
profiles:
active: dev
application:
name: microservice-provider-order
# 注册中心配置
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://eureka:eureka@localhost:8761/eureka/
(2)提供者启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class);
}
}
@EnableDiscoveryClient声明式Eureka Client,也可以用组合注解@SpringCloudApplication代替@SpringBootApplication、@EnableDiscoveryClient
4、microservice-consumer-pay
(1)消费者application.yml配置
spring:
profiles:
active: dev
application:
name: microservice-consumer-pay
# 注册中心配置
eureka:
instance:
prefer-ip-address: true
client:
service-url:
defaultZone: http://eureka:eureka@localhost:8761/eureka/
(2)消费者启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class);
}
}
按次序启动microservice-discover-eureka、microservice-provider-order、microservice-consumer-pay。provider启动时向注册中心注册信息。consumer去注册中心查询信息就可以调用provider
六、Eureka高可用机制
1、单节点的弊端
前边的Eureka Server只有一个,不适合在生产环境中使用。Eureka Client会定期连接Eureka Server,获取服务注册表中信息,更新本地缓存。在没有更新的周期内,服务消费远程Api时总是使用本地缓存数据,即使远程Eureka Server发生宕机也不影响调用。但是Eureka Server宕机的同时,恰好某些服务挂掉,Eureka Client本地缓存的信息并不会被更新,服务消费者仍然认为要调取的远程服务可用,导致调用出错,从而影响系统的正常使用。因此,实际生产环境需要部署一个高可用的Eureka Server集群。
2、高可用机制
Eureka集群是通过部署多个Eureka Server实例,实例之间并通过相互注册的方式实现高可用。Eureka Server相互之间会增量的同步信息,保证每个Server的数据的一致性。Eureka Server之间相互注册是默认行为,eureka.client.register-with-eureka,表示此服务是否向Eureka Server注册,以被其他服务发现。默认值是true,去注册。eureka.client.fetch-registry表示是否从Eureka Server注册表获取eureka信息,默认值true,去获取。
3、高可用双节点配置
由前边的介绍可知,只要去掉application.yml中eureka.client.register-with-eureka和eureka.client.fetch-registry删除即可,即设置成默认值true即可,让Eureka Server之间相互注册。
分别在ip为78、79两个节点配置hosts
192.168.40.78 eureka01
192.168.40.79 eureka02
高可用开启身份认证时Finchley.SR2版本还要进行安全配置
@EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().ignoringAntMatchers("/eureka/**");
super.configure(http);
}
}
microservice-discover-eureka的配置如下:
spring:
security:
user:
name: eureka
password: eureka
application:
name: microservice-discover-eureka
---
spring:
profiles: eureka01
server:
port: 8761
eureka:
instance:
prefer-ip-address: true
hostname: eureka01
client:
service-url:
defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@eureka02:8762/eureka/
---
spring:
profiles: eureka02
server:
port: 8761
eureka:
instance:
prefer-ip-address: true
hostname: eureka02
client:
service-url:
defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@eureka01:8761/eureka/
用连接字符“---”将application.yml分为三段,第一段没有指定spring.profiles值,会对所有profiles生效,第二段和第三段分别指定了spring.profiles的值, 启动时候通过spring.profiles.active指定值就可以读取指定值所在的那段配置。分别在两台节点上启动指定配置即可。
java -jar microservice-discover-eureka-0.0.1-SNAPSHOT.jar spring.profiles.active=eureka01
java -jar microservice-discover-eureka-0.0.1-SNAPSHOT.jar spring.profiles.active=eureka02
启动第一个Eureka Server时候会java.net.ConnectException: Connection refused: connect报错,因为第二个server还没有启动起来,启动另一个Eureka Server后就好了
访http://eureka01:8761,同样http://eureka02:8761也是如此
改变生产和消费微服务的application.yml配置如下
eureka:
client:
service-url:
defaultZone: http://eureka01:8761/eureka/,http://eureka02:8761/eureka/
microservice-provider-order和microservice-consumer-pay的配置可以注册一个Eureka Server,也可以都注册,即使只注册一个节点,Eureka Server之间是会信息同步的。集群模式时,不过还是建议配置注册多个Eureka Server节点。eureka.client.service-url.defaultZone配置地址,多个地址用逗号分隔。
参考:《Spring Cloud与Docker微服务架构实战》周立