一、前言

Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。

本篇介绍 Spring Cloud 入门系列中的 Eureka,实现快速入门。

二、简单介绍

Eureka 是 Netflix 的子模块,它是一个基于 REST 的服务,用于定位服务,以实现云端中间层服务发现和故障转移。

服务注册和发现对于微服务架构而言,是非常重要的。有了服务发现和注册,只需要使用服务的标识符就可以访问到服务,而不需要修改服务调用的配置文件。该功能类似于 Dubbo 的注册中心,比如 Zookeeper。

Eureka 采用了 CS 的设计架构。Eureka Server 作为服务注册功能的服务端,它是服务注册中心。而系统中其他微服务则使用 Eureka 的客户端连接到 Eureka Server 并维持心跳连接。

其运行原理如下图:

由图可知,Eureka 的运行原理和 Dubbo 大同小异, Eureka 包含两个组件: Eureka Server 和 Eureka Client。

Eureka Server 提供服务的注册服务。各个服务节点启动后会在 Eureka Server 中注册服务,Eureka Server 中的服务注册表会存储所有可用的服务节点信息。

Eureka Client 是一个 Java 客户端,用于简化 Eureka Server 的交互,客户端同时也具备一个内置的、使用轮询负载算法的负载均衡器。在应用启动后,向 Eureka Server 发送心跳(默认周期 30 秒)。如果 Eureka Server 在多个心跳周期内没有接收到某个节点的心跳,Eureka Server 会从服务注册表中将该服务节点信息移除。

三、Eureka服务端搭建

基于sprinboot基础项目进行搭建。

3.1 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    <properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</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>

注意:Spring Boot 与 SpringCloud 有版本兼容关系,如果引用版本不对应,项目启动会报错。

3.2 修改application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
server:
port: 9000

spring:
application:
name: Eureka-Server

eureka:
instance:
hostname: wno704 # eureka 实例名称
instance-id: eureka-server
prefer-ip-address: off
client:
register-with-eureka: true # 是否向注册中心注册自己
fetch-registry: false # 是否检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 注册中心访问地址

上面配置了服务的端口为8080,剩下几个为Eureka配置:

eureka.instance.hostname指定了Eureka服务端的IP;
eureka.client.register-with-eureka表示是否将服务注册到Eureka服务端,由于自身就是Eureka服务端,所以设置为false;
eureka.client.fetch-registry表示是否从Eureka服务端获取服务信息,因为这里只搭建了一个Eureka服务端,并不需要从别的Eureka服务端同步服务信息,所以这里设置为false;
eureka.client.serviceUrl.defaultZone指定Eureka服务端的地址,默认值为http://localhost:8761/eureka。

3.3 Eureka配置

下面我们总结一下在Eureka中常用的配置选项及代表的含义:

配置含义默认值
eureka.client.enabled是否启用Eureka Clienttrue
eureka.client.register-with-eureka表示是否将自己注册到Eureka Servertrue
eureka.client.fetch-registry表示是否从Eureka Server获取注册的服务信息true
eureka.client.serviceUrl.defaultZone配置Eureka Server地址,用于注册服务和获取服务http://localhost:8761/eureka
eureka.client.registry-fetch-interval-seconds默认值为30秒,即每30秒去Eureka Server上获取服务并缓存30
eureka.instance.lease-renewal-interval-in-seconds向Eureka Server发送心跳的间隔时间,单位为秒,用于服务续约30
eureka.instance.lease-expiration-duration-in-seconds定义服务失效时间,即Eureka Server检测到Eureka Client木有心跳后(客户端意外下线)多少秒将其剔除90
eureka.server.enable-self-preservation用于开启Eureka Server自我保护功能true
eureka.client.instance-info-replication-interval-seconds更新实例信息的变化到Eureka服务端的间隔时间,单位为秒30
eureka.client.eureka-service-url-poll-interval-seconds轮询Eureka服务端地址更改的间隔时间,单位为秒。300
eureka.instance.prefer-ip-address表示使用IP进行配置为不是域名false
eureka.client.healthcheck.enabled默认Erueka Server是通过心跳来检测Eureka Client的健康状况的,通过置为true改变Eeureka Server对客户端健康检测的方式,改用Actuator的/health端点来检测。false

Eureka还有许多别的配置,具体可以参考EurekaClientConfigBean,EurekaServerConfigBean和EurekaInstanceConfigBean配置类的源码。

3.4 修改启动类

在启动类上添加@EnableEurekaServer注解,表明这是一个Eureka服务端

1
2
3
4
5
6
7
8
9
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {

public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}

}

至此,准备工作完成,启动项目完成后,浏览器访问 http://localhost:9000 ,查看 Eureka 服务监控界面,如下图:

通过该网址可以查看注册中心注册服务的相关信息。当前还没有服务注册,因此没有服务信息。

补充http://localhost:9000 是 Eureka 监管界面访问地址,而 http://localhost:9000/eureka/ Eureka 注册服务的地址。

四、Eureka服务集群搭建

4.1 基础系统配置

由于使用一台机器,使用两个名称还需要修改 C:\Windows\System32\drivers\etc 下的 host 文件,添加如下配置:

127.0.0.1 localhost eureka01 eureka02

4.2 application-xxx.yml

在上面Eureka服务新建 application-eureka01.yml,application-eureka02.yml,具体配置内容如下:
application-eureka01.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 9001

spring:
application:
name: Eureka-Server

eureka:
instance:
hostname: eureka01
instance-id: eureka-server1
prefer-ip-address: off
client:
register-with-eureka: true
fetch-registry: false
service-url:
defaultZone: http://eureka02:9002/eureka/
server:
enable-self-preservation: false

application-eureka02.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
port: 9002

spring:
application:
name: Eureka-Server

eureka:
instance:
hostname: eureka02
instance-id: eureka-server2
prefer-ip-address: off
client:
register-with-eureka: true
fetch-registry: false
service-url:
defaultZone: http://eureka01:9001/eureka/
server:
enable-self-preservation: false

4.3 新增启动参数

服务名字实例名称实例ID端口服务启动参数
EurekaServerApplication-eureka01-9001Eureka-Servereureka-server19001spring.profiles.active=eureka01
EurekaServerApplication-eureka02-9002Eureka-Servereureka-server29002spring.profiles.active=eureka02

如下图所示:

4.4 启动两个服务

访问 http://localhost:9001/ ,

http://localhost:9002/

五、客户端搭建

基于springboot基础搭建

5.1 依赖引入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    <properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>

5.2 修改application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 8764

spring:
application:
name: User-Server

eureka:
instance:
instance-id: user-server
prefer-ip-address: off
client:
register-with-eureka: true # 是否向注册中心注册自己
fetch-registry: true # 是否检索服务
service-url:
defaultZone: http://eureka01:9001/eureka/,http://eureka02:9002/eureka/

如果eureka-server是单节点

defaultZone: http://127.0.0.1:8761/eureka/

eureka.client.register-with-eureka和eureka.client.fetch-registry上面已经解释了其意思,虽然这两个配置的默认值就是true,但这里还是显式配置下,使Eureka客户端的功能更为直观(即向服务端注册服务并定时从服务端获取服务缓存到本地)。

5.3 修改启动类

在启动类上添加@EnableEurekaClient注解,表明这是一个Eureka客户端

1
2
3
4
5
6
7
8
9
@EnableEurekaClient
@SpringBootApplication
public class UserServerApplication {

public static void main(String[] args) {
SpringApplication.run(UserServerApplication.class, args);
}

}

至此,准备工作完成,启动项目完成后,浏览器访问 Eureka 监管界面,查看 Eureka 服务监控界面,如下图:

5.4 日志分析

可以从控制台中看到注册成功的消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[  restartedMain] com.netflix.discovery.DiscoveryClient    : Registered Applications size is zero : true
[ restartedMain] com.netflix.discovery.DiscoveryClient : Application version is -1: true
[ restartedMain] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server
[ restartedMain] com.netflix.discovery.DiscoveryClient : The response status is 200
[ restartedMain] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30
[ restartedMain] c.n.discovery.InstanceInfoReplicator : InstanceInfoReplicator onDemand update allowed rate per min is 4
[ restartedMain] com.netflix.discovery.DiscoveryClient : Discovery Client initialized at timestamp 1599443104937 with initial instances count: 2
[ restartedMain] o.s.c.n.e.s.EurekaServiceRegistry : Registering application USER-SERVER with eureka with status UP
[ restartedMain] com.netflix.discovery.DiscoveryClient : Saw local status change event StatusChangeEvent [timestamp=1599443104940, current=UP, previous=STARTING]
[nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_USER-SERVER/user-server: registering service...
[ restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8764 (http) with context path ''
[ restartedMain] .s.c.n.e.s.EurekaAutoServiceRegistration : Updating port to 8764
[nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_USER-SERVER/user-server - registration status: 204
[ restartedMain] com.wno704.cloud.UserServerApplication : Started UserServerApplication in 10.779 seconds (JVM running for 12.431)

第3,4行输出表示已经成功从Eureka服务端获取到了服务;第5行表示发送心跳给Eureka服务端,续约(renew)服务;第8到11行表示已经成功将服务注册到了Eureka服务端。

六、项目实战

了解 Eureka 的环境搭建后,我们需要进行实战直观的感受 Eureka 的真正作用,这样才能清楚掌握和学习 Eureka 。

6.1 项目概况

我们再创建三个 Spring Boot 项目,一个名为 user-server ,用于提供接口服务,另一个名为 user-web,用于调用 user-server 接口获取数据与浏览器交互。在创建一个 common-api ,用户保存客户端和服务端共有的一些类。

项目名称服务实例端口描述
eureka-servereureka01/eureka029001/9002注册中心(Eureka 服务端集群)
common-api客户端和服务端共有的一些类
user-serveruser-server8764服务提供者(Eureka 客户端)
user-webuser-web8765服务消费者,与浏览器端交互(Eureka 客户端)

6.2 common-api 共享代码

6.2.1 引入依赖

1
2
3
4
5
6
7
8
9
10
        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

6.2.2 创建User实体

1
2
3
4
5
6
7
8
9
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class User {
int id;
String name;
String passwd;
}

6.2.2 配置RestTemplate

配置一个RestTemplate Bean,然后加上@LoadBalanced注解来开启负载均衡

1
2
3
4
5
6
7
8
@Configuration
public class RestConfiguration {
@Bean
@LoadBalanced
public RestTemplate getRestTemplate() {
return new RestTemplate();
}
}

6.3 user-server 服务提供者

6.3.2 引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
    <properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR8</spring-cloud.version>
<common-api.version>0.0.1-SNAPSHOT</common-api.version>
</properties>

<dependencies>
<dependency>
<groupId>com.wno704</groupId>
<artifactId>common-api</artifactId>
<version>${common-api.version}</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</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>

6.3.2 application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 8764

spring:
application:
name: User-Server

eureka:
instance:
instance-id: user-server
prefer-ip-address: off
client:
register-with-eureka: true # 是否向注册中心注册自己
fetch-registry: true # 是否检索服务
service-url:
defaultZone: http://eureka01:9001/eureka/,http://eureka02:9002/eureka/

6.3.3 创建service

UserService.java

1
2
3
public interface UserService {
public User getById(Integer id);
}

实现UserService
UserServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class UserServiceImpl implements UserService {

private static Map<Integer,User> map;
static {
map = new HashMap<>();
for (int i=1; i<10; i++) {
map.put(i, new User(i,"test" +i , "pwd" + i));
}
}

@Override
public User getById(Integer id) {
return map.get(id);
}
}

6.3.3 创建controller

UserController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
@RestController
@RequestMapping("/provider/user")
public class UserController {

@Autowired
private UserService userService;

@RequestMapping("/get/{id}")
public User get(@PathVariable("id") Integer id) {
return this.userService.getById(id);
}

}

6.3.4 修改启动类

在启动类上添加@EnableEurekaClient注解,表明这是一个Eureka客户端

1
2
3
4
5
6
7
8
9
@EnableEurekaClient
@SpringBootApplication
public class UserServerApplication {

public static void main(String[] args) {
SpringApplication.run(UserServerApplication.class, args);
}

}

6.4 user-web 服务消费者

6.4.1 依赖引入

同user-server。

6.4.2 配置application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
server:
port: 8765

spring:
application:
name: User-Web

eureka:
instance:
instance-id: user-web
prefer-ip-address: off
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://eureka01:9001/eureka/,http://eureka02:9002/eureka/

6.4.2 创建controller

UserController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private RestTemplate restTemplate;

@Autowired
private DiscoveryClient client;

@RequestMapping("get/{id}")
public User get(@PathVariable("id") Integer id) throws Exception {

List<ServiceInstance> list = this.client.getInstances("USER-SERVER");
String uri = "";
for (ServiceInstance instance : list) {
if (instance.getUri() != null && !"".equals(instance.getUri())) {
uri = instance.getUri().toString();
break;
}
}
return restTemplate.getForObject(uri + "/provider/user/get/" + id, User.class);
}

}

6.4.2 修改启动类

在启动类上添加@EnableEurekaClient注解,表明这是一个Eureka客户端.

6.4 测试

6.4.1 启动项目

1.先启动eureka-server集群模式,参考上面集群服务4.4。
2.启动user-server。
3.启动user-web。

6.4.2 测试

访问user-web: http://localhost:8765/user/get/5
结果如下:

至此,Eureka 的服务注册和发现演示完毕。

本系列的后续文章会基于该案例进行介绍和实战演练。

七、Eureka-Server添加认证

出于安全的考虑,我们可能会对Eureka服务端添加用户认证的功能。

7.1 引入依赖

1
2
3
4
        <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

7.2 修改application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
server:
port: 9000

spring:
application:
name: Eureka-Server
security:
user:
name: wno704
password: 19920503


eureka:
instance:
hostname: wno704 # eureka 实例名称
instance-id: eureka-server
prefer-ip-address: off
client:
register-with-eureka: true # 是否向注册中心注册自己
fetch-registry: false # 是否检索服务
service-url:
defaultZone: http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:${server.port}/eureka/ # 注册中心访问地址

7.3 增加安全认证类

springboot 2.0 以上版本springcloud的丢弃了配置:

1
2
3
security:
basic:
enabled: true

所以应该使用以下方式开启

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
}

}

注意:为了可以使用 http://${user}:${password}@${host}:${port}/eureka/ 这种方式登录,所以必须是httpBasic,如果是form方式,不能使用url格式登录.

7.4 客户端改造

Eureka服务端配置了密码之后,所有eureka.client.serviceUrl.defaultZone的配置也必须配置上用户名和密码,格式为:eureka.client.serviceUrl.defaultZone=http://${spring.security.user.name}:${spring.security.user.password}@${eureka.instance.hostname}:${server.port}/eureka/,如:

1
2
3
4
5
6
7
8
9
eureka:
instance:
instance-id: user-server
prefer-ip-address: off
client:
register-with-eureka: true # 是否向注册中心注册自己
fetch-registry: true # 是否检索服务
service-url:
defaultZone: http://wno704:19920503@127.0.0.1:9000/eureka/

7.5 测试

打开 http://localhost:9000/ 页面将弹出验证窗口,输入用户名和密码后即可访问。