一、前言
Spring Cloud 是一系列框架的有序集合。它利用 Spring Boot 的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot 的开发风格做到一键启动和部署。
SpringCloud体系介绍:官方地址
二、简单介绍
Eureka是Netflix的一个子模块,也是核心模块之一。Eureka是一个基于 REST的服务,用于定位服务,以实现云端中间层服务发现和故障转移。
服务注册和发现对于微服务架构来说是非常重要的。有了服务发现和注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件。该功能类似于 Dubbo 的注册中心,比如 Zookeeper。
Eureka 采用了C-S的设计架构。Eureka Server作为服务注册功能的服务端,它是服务注册中心。而系统中的其他微服务,使用Eureka的客户端连接到Eureka Server并维持心跳连接。这样系统的维护人员就可以通过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会从服务注册表中把这个服务节点移除(默认90s)。
Eureka三大角色:
- Eureka Server:提供服务注册和发现
- Service Provider:服务提供方将自身服务注册到Eureka,从而使服务消费方能够找到该服务
- Service Consumer:服务消费方从Eureka获取注册服务列表,从而能够消费服务
Eureka 2.x闭源后选择: 参考一 参考二
三、什么是微服务的注册中心
注册中心:是微服务架构中的“通讯录”,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到注册中心,当一个服务需要调用其它服务时,就会到注册中心里去找服务的地址,然后进行调用。核心是通过服务注册表管理服务,通过心跳机制动态维护服务
服务提供者provider: 服务启动的时候会向注册中心上报自己的网络信息
服务消费者consumer: 服务启动的时候会向注册中心上报自己的网络信息,拉取服务提供者provider的相关网络信息
为什么要用注册中心:微服务应用和机器越来越多,调用方需要知道接口的网络地址,如果靠配置文件的方式去控制网络地址,对于动态新增机器,维护带来很大问题
主流的注册中心:zookeeper、Eureka、consul、etcd 等
四、CAP理论
CAP定理:指的是在一个分布式系统中,Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),三者不可同时获得。
- 一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(所有节点在同一时间的数据完全一致,越多节点,数据同步越耗时)
- 可用性(A):负载过大后,集群整体是否还能响应客户端的读写请求。(服务一直可用,而且是正常响应时间)
- 分区容错性(P):分区容忍性,就是高可用性,一个节点崩了,并不影响其它的节点(100个节点,挂了几个,不影响服务,越多机器越好)
CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容错性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡
五、分布式系统CAP原理常见面试题和注册中心的选择
- CA 满足的情况下,P不能满足的原因:数据同步(C)需要时间,也要正常的时间内响应(A),那么机器数量就要少,所以P就不满足
- CP 满足的情况下,A不能满足的原因:数据同步(C)需要时间,机器数量多(P),但是同步数据需要时间,所以不能再正常时间内响应,所以A就不满足
- AP 满足的情况下,C不能满足的原因:机器数量多(P),正常的时间内响应(A),那么数据就不能及时同步到其他节点,所以C不满足
注册中心选择:
- Zookeeper:CP设计,保证了一致性,集群搭建的时候,某个节点失效,则会进行选举行的leader,或者半数以上节点不可用,则无法提供服务,因此可用性没法满足
- Eureka:AP原则,无主从节点,一个节点挂了,自动切换其他节点可以使用,去中心化
结论:分布式系统中P,肯定要满足,所以只能在CA中二选一
没有最好的选择,最好的选择是根据业务场景来进行架构设计
如果要求一致性,则选择zookeeper,如金融行业
如果要去可用性,则Eureka,如电商系统
六、搭建注册中心(Eureka 服务端)
官方文档
maven仓库地址配置
6.1、添加依赖
父工程依赖
<packaging>pom</packaging>
<modules>
<module>../microservicecloud-api</module>
<module>../microservicecloud-eureka-7001</module>
<module>../microservicecloud-provider-dept-8001</module>
</modules>
<properties>
<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
<spring-boot.version>2.1.0.RELEASE</spring-boot.version>
<mybatis.version>1.3.0</mybatis.version>
<lombok.version>1.16.18</lombok.version>
<druid.version>1.1.10</druid.version>
</properties>
<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>
<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.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<finalName>microservicecloud</finalName>
<resources>
<resource>
<!-- 允许访问src/main/resources下的内容 -->
<directory>src/main/resources</directory>
<!-- 过滤开启 -->
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<delimiters>
<!-- 在$符号开始$符号结尾的src/main/resources下的内容都可访问 -->
<delimit>$</delimit>
</delimiters>
</configuration>
</plugin>
</plugins>
</build>
公共工程依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
公共依赖实体类
@AllArgsConstructor
@NoArgsConstructor
@Data
@Accessors(chain = true)
@SuppressWarnings("serial")
public class Dept implements Serializable {
private static final long serialVersionUID = 8003851260429627546L;
private Long deptno;
private String dname;
private String db_source;
public Dept(String dname){
super();
this.dname=dname;
}
}
Eureka服务注册端依赖
<!-- Eureka服务端 -->
<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</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
注意:Spring Boot 与 SpringCloud 有版本兼容关系,如果引用版本不对应,项目启动会报错。
6.2、application.yml参数配置
server:
port: 7001
eureka:
instance:
# eureka服务端的实例名称
hostname: 127.0.0.1
client:
# 声明自己是个服务端
register-with-eureka: false # false表示不向注册中心注册自己
fetch-registry: false # 是否检索服务,false表示自己端就是注册中心,不检索服务
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 注册中心访问地址
#server:
#enable-self-preservation: false # 关闭自我保护机制,不推荐
#eviction-interval-timer-in-ms: 5000 # 每隔5s扫描服务列表,移除失效服务
6.3、开启注册中心功能
在启动类上添加 @EnableEurekaServer注解。
@SpringBootApplication
@EnableEurekaServer//todo Eureka服务器端启动类,接受其它微服务注册进来
public class EurekaServer7001_App {
public static void main(String[] args) {
SpringApplication.run(EurekaServer7001_App.class,args);
}
}
准备工作完成,启动项目完成后,浏览器访问 http://127.0.0.1:7001 ,查看Eureka 服务监控界面,如下图:
注意:http://localhost:7001 是Eureka监管界面访问地址,而http://localhost:7001/eureka/Eureka注册服务的地址
七、实战演练
官方文档
了解Eureka的环境搭建后,我们需要进行实战直观的感受Eureka 的真正作用,这样才能清楚掌握和学习Eureka
7.1、服务提供者(Eureka客户端)
1、添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.bilemon.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- 热部署依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- mysql数据库依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- druid连接池依赖 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
</dependency>
<!-- Eureka客户端 -->
<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-actuator</artifactId>
</dependency>
<!-- 分布式配置中心组件 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
2、配置参数
server:
port: 8001
mybatis:
config-location: classpath:mybatis/mybatis-config.xml # mybatis配置文件所在路径
type-aliases-package: com.bjlemon.springcloud.entities # 所有Entity所在包
mapper-locations:
- classpath:mybatis/mapper/**/*.xml # mapper映射文件的位置
spring:
application:
name: microservicecloud-dept # 服务的名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource # 当前数据源操作类型
driver-class-name: com.mysql.cj.jdbc.Driver # mysql驱动包
url: jdbc:mysql://localhost:3306/cloudDB01?serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf-8 # 数据库名称
username: root
password: root
dbcp2:
min-idle: 5 # 数据库连接池的最小维持连接数
initial-size: 5 # 初始化连接数
max-total: 5 # 最大连接数
max-wait-millis: 200 # 等待连接获取的最大超时时间
#指定注册中心地址
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:7001/eureka/ # 客户端注册进eureka服务列表内,注册中心访问地址
instance:
instance-id: microservicecloud-dept8001 # 自定义服务名称信息
prefer-ip-address: true # 访问路径可以显示IP地址
# 服务名称信息链接访问的内容,需要在父工程配置访问权限
info:
app.name: bjlemon-microservicecloud
company.name: www.bjlemon.com
build.artifactId: ${project.artifactId}
build.version: ${project.version}
注意:http://localhost:7001/eureka/ 就是第六节中配置的注册中心的地址。
3、服务接口
public interface DeptService {
boolean add(Dept dept);
Dept get(Long id);
List<Dept> list();
}
4、Controller层
@RestController
public class DeptController {
@Autowired
private DeptService deptService;
@RequestMapping(value="/dept/add",method = RequestMethod.POST)
public boolean add(@RequestBody Dept dept){
return deptService.add(dept);
}
@RequestMapping(value="/dept/get/{id}",method = RequestMethod.GET)
public Dept get(@PathVariable("id")Long id){
return deptService.get(id);
}
@RequestMapping(value="/dept/list",method = RequestMethod.GET)
public List<Dept> list(){
return deptService.list();
}
}
注意:该 controller 是给web使用的内部服务,不是给浏览器端调用的。
5、开启服务注册功能
在启动类上添加 @EnableEurekaClient 注解(也可以不加自动注册)
@SpringBootApplication
@EnableEurekaClient //本服务启动后会自动注册进eureka server中
public class DeptProvider8001_App {
public static void main(String[] args) {
SpringApplication.run(DeptProvider8001_App.class,args);
}
}
启动项目完成后,浏览器访问 http://localhost:7001 查看Eureka服务监控界面 ,如下图:
注意:Eureka默认是开启保护模式的。当 EurekaServer 在短时间内丢失过多客户端时(可能发生了网络故障),Eureka Server 将进入自我保护模式。进入该模式后,EurekaServer 会保护服务注册表中的信息不被删除。当网络故障恢复后,EurekaServer 会自动退出自我保护模式
7.2、Eureka服务消费者,与浏览器端交互(Eureka客户端)
1、添加依赖
<dependency>
<groupId>com.bilemon.springcloud</groupId>
<artifactId>microservicecloud-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>springloaded</artifactId>
<version>1.2.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- Eureka客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、配置参数
server:
port: 80
eureka:
client:
register-with-eureka: false # 不向注册中心注册自己
fetch-registry: true # 是否检索服务,默认就是true检索
service-url:
defaultZone: http://127.0.0.1:7001/eureka/ #注册中心访问地址
4、配置信息
@Configuration
public class ConfigRest {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
5、Controller层
@RestController
public class DeptControllerConsumer {
private String uri="http://localhost:8001";
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient client;
@PostConstruct
public void initUri(){
List<ServiceInstance> list=this.client.getInstances("MICROSERVICECLOUD-DEPT");
if(list!=null && list.size()>0){
for(ServiceInstance serviceInstance:list){
System.out.println(serviceInstance.getUri());
System.out.println(serviceInstance.getUri()!=null);
System.out.println("".equals(serviceInstance.getUri()));
if(serviceInstance.getUri()!=null && !"".equals(serviceInstance.getUri())){
uri=serviceInstance.getUri().toString();
break;
}
}
}
}
@RequestMapping(value = "/consumer/dept/add")
public boolean add(Dept dept){
return restTemplate.postForObject(uri+"/dept/add",dept,Boolean.class);
}
@RequestMapping(value = "/consumer/dept/get/{id}")
public Dept get(@PathVariable("id")Long id){
return restTemplate.getForObject(uri+"/dept/get/"+id, Dept.class);
}
@RequestMapping(value = "/consumer/dept/list")
public List<Dept> list(){
return restTemplate.getForObject(uri+"/dept/list",List.class);
}
6、开启服务发现功能
在启动类上添加 @EnableDiscoveryClient 注解
@SpringBootApplication
@EnableEurekaClient
public class DeptConsumer80_App {
public static void main(String[] args) {
SpringApplication.run(DeptConsumer80_App.class,args);
}
}
使用浏览器访问http://192.168.30.25/consumer/dept/list,运行结果如下:
八、Eureka 集群
Eureka 作为注册中心,保存了系统服务的相关信息,如果注册中心挂掉,那么系统就瘫痪了。因此,对 Eureka 做集群实现高可用是必不可少的。
本次测试使用一台机器部署Eureka集群,通过名字和端口区分不同的eureka服务。
1、由于使用一台机器做集群,需要修改 C:\Windows\System32\drivers\etc 下的 host 文件,添加如下配置:
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 eureka7003.com
2、application.yml文件需要进行如下修改:
server:
port: 7001
eureka:
instance:
#hostname: 127.0.0.1 # 单机hostname配置,eureka服务端的实例名称
hostname: eureka7001.com # 集群hostname配置,eureka服务端的实例名称
client:
register-with-eureka: false # todo false表示不向注册中心注册自己
fetch-registry: false # todo false表示自己端就是注册中心
service-url:
#defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ # 单机defaultZone配置
defaultZone: http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # 集群配置
#server:
#enable-self-preservation: false # 关闭自我保护机制
#eviction-interval-timer-in-ms: 5000 # 每隔5s扫描服务列表,移除失效服务
三个Eureka服务实例的配置文件修改方式类似,将名称和端口以及注册中心地址进行修改即可。
3、服务提供者将 eureka.client.service-url.defaultZone 改成集群的 url 即可。
eureka:
client:
service-url:
#defaultZone: http://127.0.0.1:7001/eureka/
defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/ # 客户端注册进eureka服务列表内
启动效果如下图: