SpringCloud之服务注册中心
前言
之前springcloud的家族如下图所示,因为微服务的引入,以及流量的增加,导致了微服务出现了一下的一些技术,主要分为服务注册与发现
,服务调用
,服务熔断
,服务网关
,服务配置
,而这些对应的落地的技术就是下图了。
而现在由于部分功能的停更,其中Eureka官宣2.x版本不再开源,所以部分厂就要么使用下面的zookeeper,consul,以及阿里的nacos
,以及服务调用中不推荐使用Ribbon以及Feign,而推荐使用LoadBalancer以及OpenFeign,服务降级中的豪猪哥Hystrix已经停止开发,官网推荐使用reilience4j,而国内则推荐使用阿里的sentinel,服务网关Zuul则是公司内部问题,准备出的Zuul2迟迟未上线,所以推荐使用Spring自己出的gateway,服务配置则不推荐使用Config了,推荐使用Nacos
,服务总线,也从Bus转换到Nacos
。
CAP原理
- C(一致性):所有的节点上的数据时刻保持同步
- A(可用性):每个请求都能接受到一个响应,无论响应成功或失败
- P(分区容错):系统应该能持续提供服务,即使系统内部有消息丢失(分区)
高可用、数据一致是很多系统设计的目标,但是分区又是不可避免的事情:
- CA without P:如果不要求P(不允许分区),则C(强一致性)和A(可用性)是可以保证的。但其实分区不是你想不想的问题,而是始终会存在,因此CA的系统更多的是允许分区后各子系统依然保持CA。
- CP without A:如果不要求A(可用),相当于每个请求都需要在Server之间强一致,而P(分区)会导致同步时间无限延长,如此CP也是可以保证的。很多传统的数据库分布式事务都属于这种模式。
- AP wihtout C:要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。现在众多的NoSQL都属于此类。
服务注册中心
本节内容则主要讲解服务注册中心中的Eureka,Zookeeper,Consul,Nacos如何配置开发,以及他们之间的区别,如果选择他们。
Eureka
新建一个工程,在工程下面建立一个子模块,EurekaMain7001,其中pom如下:
其中加入spring-cloud-starter-netflix-eureka-server包。
<?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>springcloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-eureka-server7001</artifactId>
<dependencies>
<!--eureka-server-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
<!--boot web actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--一般通用配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</project>
启动类如下:
主要加上@EnableEurekaServer注解。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
/** * ${description} * * @author Fjj * @date 2020/3/15 12:02 */
@SpringBootApplication
@EnableEurekaServer
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class, args);
}
}
application.yml文件如下:
主要配置端口,实例名,以及service-url。
server:
port: 7001
eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名称
client:
register-with-eureka: false #false表示不向注册中心注册自己。
fetch-registry: false #false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
service-url:
#集群指向其它eureka
# defaultZone: http://eureka7002.com:7002/eureka/
#单机就是7001自己
defaultZone: http://eureka7001.com:7001/eureka/
配置完成之后就可以启动了:
会出现如下界面:
启动完成空白一片,接下来写我们的服务:(这边只演示注册服务,不进行服务调用)
首先创建服务提供者payment8001
下面是主启动类:
主要是加上@EnableEurekaClient以及@EnableDiscoveryClient注解二选一即可,后者可以用于所有的服务注册与发现,前者只能用于Eureka的服务注册与发现。
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
/** * ${description} * * @author Fjj * @date 2020/3/9 12:32 */
@SpringBootApplication
//@EnableEurekaClient
@EnableDiscoveryClient
public class PaymentMain01 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain01.class, args);
}
}
pom文件需要加上如下:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
application.yml文件如下:
主要配置端口号,服务名称,eureka注册地址eureka-url和发送心跳时间间隔,等待时间间隔。
server:
port: 8001
spring:
application:
name: cloud-payment-service #服务名称
datasource:
type: com.alibaba.druid.pool.DruidDataSource #当前数据源操作类型
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://locahost:3306/cloud2020?characterEncoding=utf8&useSSL=false&useUnicode=true
username: root
password: 111111
eureka:
client:
#表示向注册中心注册自己 默认为true
register-with-eureka: true
#是否从EurekaServer抓取已有的注册信息,默认为true,单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetch-registry: true
service-url:
# 入驻地址
defaultZone: http://localhost:7001/eureka/
#集群版
# defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
#服务名称
instance:
instance-id: payment8001
#访问路径可以显示IP地址
prefer-ip-address: true
#Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
# lease-renewal-interval-in-seconds: 1
#Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
# lease-expiration-duration-in-seconds: 2
mybatis:
mapper-locations: classpath:mapper/*.xml type-aliases-package: com.atguigu.springcloud.entities #所有entity别名所在包
接下来启动PaymentMain01主启动类:
可以看到我们的微服务实例已经注册到了Eureka上面了,上面一排红色的字是Eureka的自我保护模式:自我保护模式:一个微服务不可用了,Eureka不会立马清理,依旧会对该服务的信息进行保存
(AP高可用,分区容错),我们的微服务可能并不是挂了,而是网络问题分区问题,导致Eureka接受不到服务的心跳包,而此服务确实正常的,所以Eureka为了高可用不会将服务立马删除。
可以使用eureka.server.enable-self-preservation=false来禁用自我保护模式。
将Eureka工程加上eureka.server.eviction-interval-timer-in-ms
以及服务端加上: lease-renewal-interval-in-seconds: 1 #Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-expiration-duration-in-seconds: 2 #Eureka服务端在收到最后一次心跳后等待时间上限,单位为秒(默认是90秒),超时将剔除服务
然后关闭服务,就会发现很快服务就会在Eureka上消失。
下图是eureka的工作流程图:
- 服务注册(register):Eureka Client会通过发送REST请求的方式向Eureka Server注册自己的服务,提供自身的元数据,比如ip地址、端口、运行状况指标的url、主页地址等信息。Eureka Server接收到注册请求后,就会把这些元数据信息存储在一个双层的Map中。
- 服务续约(renew):在服务注册后,Eureka Client会维护一个心跳来持续通知Eureka Server,说明服务一直处于可用状态,防止被剔除。Eureka Client在默认的情况下会每隔30秒(eureka.instance.leaseRenewallIntervalInSeconds)发送一次心跳来进行服务续约。
- 服务同步(replicate):Eureka Server之间会互相进行注册,构建Eureka Server集群,不同Eureka Server之间会进行服务同步,用来保证服务信息的一致性。
- 获取服务(get registry):服务消费者(Eureka Client)在启动的时候,会发送一个REST请求给Eureka Server,获取上面注册的服务清单,并且缓存在Eureka Client本地,默认缓存30秒(eureka.client.registryFetchIntervalSeconds)。同时,为了性能虑,Eureka Server也会维护一份只读的服务清单缓存,该缓存每隔30秒更新一次。
- 服务调用:服务消费者在获取到服务清单后,就可以根据清单中的服务列表信息,查找到其他服务的地址,从而进行远程调用。Eureka有Region和Zone的概念,一个Region可以包含多个Zone,在进行服务调用时,优先访问处于同一个Zone中的服务提供者。
- 服务下线(cancel):当Eureka Client需要关闭或重启时,就不希望在这个时间段内再有请求进来,所以,就需要提前先发送REST请求给Eureka Server,告诉Eureka Server自己要下线了,Eureka Server在收到请求后,就会把该服务状态置为下线(DOWN),并把该下线事件传播出去。
- 服务剔除(evict):有时候,服务实例可能会因为网络故障等原因导致不能提供服务,而此时该实例也没有发送请求给Eureka Server来进行服务下线,所以,还需要有服务剔除的机制。Eureka Server在启动的时候会创建一个定时任务,每隔一段时间(默认60秒),从当前服务清单中把超时没有续约(默认90秒,eureka.instance.leaseExpirationDurationInSeconds)的服务剔除。
- 自我保护:既然Eureka Server会定时剔除超时没有续约的服务,那就有可能出现一种场景,网络一段时间内发生了异常,所有的服务都没能够进行续约,Eureka Server就把所有的服务都剔除了,这样显然不太合理。所以,就有了自我保护机制,当短时间内,统计续约失败的比例,如果达到一定阈值,则会触发自我保护的机制,在该机制下,Eureka Server不会剔除任何的微服务,等到正常后,再退出自我保护机制。自我保护开关(eureka.server.enable-self-preservation: false)
Zookeeper
新建一个工程PaymentMain8004:
pom文件:
这边注意zookeeper的版本问题。
<?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>springcloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-zookeeper-payment8111</artifactId>
<dependencies>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${ project.version}</version>
</dependency>
<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<!--先排除自带的zookeeper3.5.3-->
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
一目了然,服务名称,以及zookeeper注册地址。
#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
application:
name: cloud-provider-payment
cloud:
zookeeper:
connect-string: localhost:2181
然后主启动类:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient //该注解用于向使用consul或者zookeeper作为注册中心时注册服务
public class PaymentMain8004
{
public static void main(String[] args) {
SpringApplication.run(PaymentMain8004.class, args);
}
}
启动:
zkCli.cmd -server 127.0.0.1:2181
进入到zk里面,可以查看到刚刚注册进去的节点:
每个服务注册进zookeeper都会生成一个流水号,可以根据这个流水号查看到节点信息。
接下来我们停止服务,在zookeeper上面一个心跳时间之后会发现服务消失了,也就是说,zookeeper并不像eureka那样有自我保护机制不立即清理,这也就是CP(一致性,分区容错)
。
Consul
官网下载
下载完,解压,使用下面命令启动
consul agent -dev
然后打开localhost:8500
老规矩新建一个module:
pom:
主要引入 spring-cloud-starter-consul-discovery 包
<?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>springcloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-consul-provider8009</artifactId>
<dependencies>
<!--SpringCloud consul-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
yml:
其实由其他的配置风格,大概能猜到consul的配置文件应该怎么配
###consul服务端口号
server:
port: 8006
spring:
application:
name: consul-provider-payment
####consul注册中心地址
cloud:
consul:
host: localhost
port: 8500
discovery:
#hostname: 127.0.0.1
service-name: ${ spring.application.name}
主启动类:
package com.atguigu.cloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class Cloud8006 {
public static void main(String[] args) {
SpringApplication.run(Cloud8006.class, args);
}
}
启动之后就可以看到consul上注册的实例:
接着将springboot程序停止:
很快在页面上 服务就×了,
consul是CP模式,一致性和分区容错性。
Nacos
阿里的nacos是集大成者,充当服务注册中心以及配置服务,并且界面清晰支持CP和AP的切换。
下载nacos,nacos文档,以及demo
运行startup.cmd就可以很轻松的启动一个nacos,默认8848端口。
接下来老规矩新建一个工程:
主启动类加上@EnableDiscoveryClient,用于服务发现
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@SpringBootApplication
@EnableDiscoveryClient
public class NacosProvider {
public static void main(String[] args) {
SpringApplication.run(NacosProvider.class, args);
}
}
pom文件主要加上spring-cloud-starter-alibaba-nacos-discovery
<?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>springcloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-nacos-payment9001</artifactId>
<dependencies>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringBoot整合Web组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
yaml文件:
其中包括注册地址:本地nacos地址,以及暴露所有端点
server:
port: 9001
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
然后写一个控制类就可以了:
package com.atguigu.springcloud.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/** * ${description} * * @author Fjj * @date 2020/4/7 15:40 */
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
@GetMapping(value = "/payment/nacos/{id}")
public String getPayment(@PathVariable("id") Integer id) {
return "nacos registty serverPort " + serverPort + "\t id:" + id;
}
}
接着启动这个工程:
访问 nacos的界面 http://localhost:8848/nacos/index.html
可以看到刚的nacos-payment-provider已经注册到nacos上面了,可以看到nacos比Eureka和zookeeper能够更清晰的看到服务的信息。
下面是nacos的整个流程图:
有兴趣可以结合源码看看。
下面是一些注册中心的比较:
一般来说,如果不需要存储服务级别的信息且服务实例是通过nacos-client注册,并能够保持心跳上报,那么就可以选择AP模式。当前主流的服务如Spring Cloud 和 Dubbo服务,都适用于AP模式,AP模式为了服务的可能性而减弱了一致性,因此AP模式下只支持注册临时实例。
如果需要在服务级别编辑或者存储配置信息,那么CP是必须的,K8S服务和DNS服务则适用于CP模式。CP模式下支持注册持久化实例,此时则是以Raft协议为集群运行模式,该模式下注册实例之前必须先注册服务,如果服务不存在,则会返回错误。
只有Nacos是支持CP和AP之间的切换的
curl -X PUT `$NACOS_SERVER:8848/nacos/v1/ns/operator/switches?entry=serverMode&value=CP'
还没有评论,来说两句吧...