说明:关于SpringCloud系列的文章中的代码都在码云上面
地址:https://gitee.com/zh_0209_java/springcloud-alibaba.git
CAP原则
CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency
(一致性)、 Availability
(可用性)、Partition tolerance
(分区容错性),三者不可得兼。
一致性(C):在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
可用性(A):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
分区容忍性(P):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。
CAP原则的精髓就是要么AP,要么CP,要么AC,但是不存在CAP。如果在某个分布式系统中数据无副本, 那么系统必然满足强一致性条件, 因为只有独一数据,不会出现数据不一致的情况,此时C和P两要素具备,但是如果系统发生了网络分区状况或者宕机,必然导致某些数据不可以访问,此时可用性条件就不能被满足,即在此情况下获得了CP系统,但是CAP不可同时满足 [1]
Eureka: 服务注册于发现
-
什么是Eureka?
Netflix在设计Eureka时,遵循的就是AP原则.
Eureka是SpringCloud的核心模块之一,Eureka是一个基于RestFul的服务,用于定位服务,以实现云端中间层服务发现和故障转移,服务注册与发现对与微服务来说是非常重要的,有了服务发现与注册,只需要使用服务的标识符,就可以访问到服务,而不需要修改服务调用的配置文件了,功能类十余Dubbo的注册中心,比如Zookeeper; -
原理讲解
Eureka的基本架构- Eureka采用了C/S的架构设计,EurekaServer作为服务注册功能的服务器,他是服务注册中心
- 而系统中的其他微服务,使用Eureka的客户端连接到EurekaServer并维持心跳连接,这样系统的维护人员就可以通过EurekaServer来监控系统中各个微服务是否正常运行,SpringCloud的一些其他模块(比如Zuul)就可以通过EurekaServer来发现系统中的其他微服务,并执行相关的逻辑;
- Eureka包含两个组件:Eureka Server 和 Eureka Client.
- Eureka Client 是一个Java客户端,用于简化EurekaServer的交互,客户端同时也具备一个内置的,使用轮询负载算法的负载均衡器。在应用启动后,将会向EurekaServer发送心跳(默认周期为30秒)。如果EurekaServer在多个心跳周期内没有接收到某个节点的心跳,EurekaServer将会从服务注册表中吧这个服务节点移除掉(默认周期为90秒)
-
三大角色
- Eureua Server :提供服务的注册与发现。类似Zookeeper
- Service Provider: 将自身服务注册到Eureka中,从而使消费方能够找到。
- Service Consumer: 服务消费方从Eureka中获取服务列表,从而找到消费服务
左图为Eureka系统架构, 右图为Dubbo 的架构
Eureka的两个组件:Eureka Server 和 Eureka Client
Eureka Server
提供服务注册服务
各个微服务节点通过配置启动后,会在EurekaServer中进行注册,这样EurekaServer中的服务注册表中将会存储所有可用服务节点的信息,服务节点的信息可以在界面中直观看到
EurekaClient 通过注册中心进行访问
EurekaClient 是一个Java客户端,用于简化Eureka Server的交互,客户端同时也具备一个内置的,使用轮询(round-robin)负载算法的负载均衡器。在应用启动后,将会向Eureka Server发送心跳(默认周期为30秒)。如果Eureka Server在多个心跳周期内没有接受到某个节点的心跳,EurekaServer 将会从服务注册表中把这个服务节点移除(默认90秒)
部署Eureka Server
- 新建eureka server项目,添加pom
<!--引入eureka Server依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
- 修改配置文件
#===== eureka server服务端配置 ========
eureka:
instance:
hostname: localhost # eureka服务端的实例名称
client:
# false表示不向注册中心注册自己,因为自己本身就是注册中心
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
- 启动类上添加主键
@EnableEurekaServer
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableEurekaServer // 开启eureka server
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class,args);
}
}
- 启动
eurekaServer
,访问测试
部署Eureka Client
- 在服务提供者项目上添加pom
<!--引入eureka Client依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- 修改配置文件
# ============ eureka client ===========
eureka:
client:
# 表示是否将自己注册进eurekaServer,默认为true
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true.单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 本机入住eurekaServer 地址
defaultZone: http://localhost:7001/eureka
- 启动类上添加
@EnableEurekaClient
注解
@SpringBootApplication
@EnableEurekaClient
@MapperScan("com.zh.springcloud.mapper")
public class PaymentApplication {
public static void main(String[] args) {
SpringApplication.run(PaymentApplication.class,args);
}
}
- 启动访问注册中心地址
Eureka集群
描述:其实就是相互注册,相互守望。来实现服务的高可用和容错性
cloud-eureka-server7001
的配置文件
server:
port: 7001
#===== eureka server服务端配置 ========
eureka:
instance:
hostname: localhost # eureka服务端的实例名称
server:
enable-self-preservation: false #测试时关闭自我保护机制,保证不可用服务及时踢出
eviction-interval-timer-in-ms: 4000 #清理间隔(单位毫秒,默认是60*1000)
client:
# false表示不向注册中心注册自己,因为自己本身就是注册中心
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://192.254.112.10:7002/eureka/
# ======== 数据库连接 ============
spring:
application:
name: cloud-payment-service
datasource:
url: jdbc:mysql://localhost:3306/zh_springcloud?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowMutiQueries=true
#root
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
#mybatis plus 设置
mybatis-plus:
mapper-locations: classpath*:mapper/*Mapper.xml
global-config:
# 关闭MP3.0自带的banner
banner: false
db-config:
#主键类型 0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
id-type: 3
# 默认数据库表下划线命名
table-underline: true
# 逻辑已删除值(默认为 1)
logic-delete-value: 1
# 逻辑未删除值(默认为 0)
logic-not-delete-value: 0
configuration:
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
call-setters-on-nulls: true
cloud-eureka-server7002
的配置文件
server:
port: 7002
#===== eureka server服务端配置 ========
eureka:
instance:
hostname: 10.10.0.42 # eureka服务端的实例名称
server:
enable-self-preservation: false #测试时关闭自我保护机制,保证不可用服务及时踢出
eviction-interval-timer-in-ms: 4000 #清理间隔(单位毫秒,默认是60*1000)
client:
# false表示不向注册中心注册自己,因为自己本身就是注册中心
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://192.254.112.10:7001/eureka/
# ======== 数据库连接 ============
spring:
application:
name: cloud-payment-service
datasource:
url: jdbc:mysql://localhost:3306/zh_springcloud?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowMutiQueries=true
#root
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
#mybatis plus 设置
mybatis-plus:
mapper-locations: classpath*:mapper/*Mapper.xml
global-config:
# 关闭MP3.0自带的banner
banner: false
db-config:
#主键类型 0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
id-type: 3
# 默认数据库表下划线命名
table-underline: true
# 逻辑已删除值(默认为 1)
logic-delete-value: 1
# 逻辑未删除值(默认为 0)
logic-not-delete-value: 0
configuration:
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
call-setters-on-nulls: true
注意:相互注册就是在 defaultZone 中注册对方eureka服务器地址,如果是多个,使用 ’ , ’ 分隔。
主机服务名修改
现在的控制台显示
可以看到显示的本机localhost, 我们修改成我们自定义的
server:
port: 8001
spring:
application:
name: cloud-payment-service
datasource:
url: jdbc:mysql://localhost:3306/zh_springcloud?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowMutiQueries=true
#root
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
#mybatis plus 设置
mybatis-plus:
mapper-locations: classpath*:mapper/*Mapper.xml
global-config:
# 关闭MP3.0自带的banner
banner: false
db-config:
#主键类型 0:"数据库ID自增",1:"该类型为未设置主键类型", 2:"用户输入ID",3:"全局唯一ID (数字类型唯一ID)", 4:"全局唯一ID UUID",5:"字符串全局唯一ID (idWorker 的字符串表示)";
id-type: 3
# 默认数据库表下划线命名
table-underline: true
# 逻辑已删除值(默认为 1)
logic-delete-value: 1
# 逻辑未删除值(默认为 0)
logic-not-delete-value: 0
configuration:
# 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 返回类型为Map,显示null对应的字段
call-setters-on-nulls: true
# ============ eureka client ===========
eureka:
# 设置控制台显示的服务路径
instance:
instance-id: payment8001
client:
# 表示是否将自己注册进eurekaServer,默认为true
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true.单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 本机入住eurekaServer 地址
# defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka # 集群版
新增了
instance:
instance-id: payment8001
@EnableDiscoveryClient
作用:它和@EnableEurekaClient
在功能上是一致的,都是写在启动类上,开启服务注册发现功能。
不同的是,当注册中心不一样时,像:eureka,consul,zookeeper,时,@EnableDiscoveryClient可以使用。
而@EnableEurekaClient只能注册eureka。
DiscoveryClient
作用:可以查看注册服务的信息
package com.zh.springcloud.controller;
import com.zh.springcloud.common.Result;
import com.zh.springcloud.entity.Payment;
import com.zh.springcloud.service.PaymentService;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @Description:
* @ClassName PaymentController
* @date: 2021.06.08 15:50
* @Author: zhanghang
*/
@RequestMapping("payment")
@RestController
@Slf4j
public class PaymentController {
@Autowired
private PaymentService paymentService;
@Autowired
private DiscoveryClient discoveryClient;
@Value("${server.port}")
private String serverPort;
@PostMapping("save")
@ApiOperation(value = "添加订单")
public Result<?> savePayment(@RequestBody Payment payment){
log.info("===========插入==========serverPort:"+serverPort);
return paymentService.savePayment(payment);
}
@GetMapping("/{id}")
@ApiOperation(value = "获取订单")
public Result<?> getPaymentById(@PathVariable Long id){
Result<Object> result = new Result<>();
log.info("===========查询==========serverPort:"+serverPort);
result.setResult(paymentService.getPaymentById(id));
return result;
}
@GetMapping("/discovery")
@ApiOperation(value = "获取discoveryClient信息")
public Result<?> getDiscoveryClient(){
Result<Object> result = new Result<>();
List<String> services = discoveryClient.getServices();
for (String element : services){
log.info("===========element======:"+element);
}
List<ServiceInstance> instances = discoveryClient.getInstances("CLOUD-PAYMENT-SERVICE");
for (ServiceInstance instance : instances){
log.info(instance.getInstanceId()+"\t"+instance.getHost()+"\t"+instance.getPort()+"\t"+instance.getUri());
}
result.setResult(discoveryClient);
return result;
}
}
访问 /discovery
自我保护机制
自我保护机制时一种应对网络异常的安全保护措施。他的架构哲学是宁可同时保留所有微服务(健康的微服务和不健康的微服务都会保留)也不盲目注销任何可能健康的微服务。使用自我保护模式。可以让Eureka集群更加健壮,稳定,所以Eureka采用的是CAP中的AP原则。
eureka默认是在90秒内没有收到来自客户端的心跳,eureka就会将客户端信息移除,但是同时间没有接收到大量的客户端心跳,他就会采取自我保护机制。
关闭自我保护机制
- 在EurekaServer端yml中增加配置
#===== eureka server服务端配置 ========
eureka:
instance:
hostname: localhost # eureka服务端的实例名称
server:
enable-self-preservation: false #测试时关闭自我保护机制,保证不可用服务及时踢出
eviction-interval-timer-in-ms: 2000 #清理间隔(单位毫秒,默认是60*1000)
client:
# false表示不向注册中心注册自己,因为自己本身就是注册中心
register-with-eureka: false
# false表示自己端就是注册中心,我的职责就是维护服务实例,并不需要去检索服务
fetch-registry: false
service-url:
# 设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://10.10.0.42:7002/eureka/
- 在EurekaClient端yml修改配置
eureka:
# 设置控制台显示的服务路径
instance:
instance-id: payment8001
prefer-ip-address: true # 访问地址可以显示ip
# Eureka客户端向服务端发送心跳的时间间隔,单位为秒(默认是30秒)
lease-renewal-interval-in-seconds: 1
# Eureka 服务端在收到最后一次心跳后等待时间上线,单位为秒(默认是90秒),超时将剔除服务
lease-expiration-duration-in-seconds: 2
client:
# 表示是否将自己注册进eurekaServer,默认为true
register-with-eureka: true
# 是否从EurekaServer抓取已有的注册信息,默认为true.单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
# 本机入住eurekaServer 地址
# defaultZone: http://localhost:7001/eureka # 单机版
defaultZone: http://localhost:7001/eureka,http://localhost:7002/eureka # 集群版
- 启动测试
可以看到自我保护机制是关闭的,在启动客户端
可以看到已经注册上,然后将客户端关闭,等待两秒,在刷新页面
发现服务被及时剔除,没有使用自我保护机制