一、没有使用注册中心搭建的分布式应用缺点:
1)我们的在调用的时候,请求的Ip地址和端口是硬编码的.
若此时,服务提供方(order)服务部署的机器换了端口或者是更换了部署机器的Ip,那么我们需要修改代码重新发布部署.
2) 假设我们的order服务压力过大,我们需要把order服务作为集群,那么意味着 order是多节点部署,比如原来的,我们只有一台服务器,现在有多台服务器,那么作为运维人员 需要在服务消费方进行手工维护一份注册表(容易出错)
3)有人马上回驳我说,我可以通过ng来做负载均衡,对,我首先认为这是可行的,当时微服务成百上千的服务,难道我们要那成百上千
nginx么?或者使用一个Ng 那么我们能想一下哪个ng的配置文件有多么复杂
3)服务发现原理初探,其实,服务发现机制非常简单,不妨用大家熟悉的MySQL(也可以用redis来做存储)来类比——只需一张表(图中的registry表)即可实现服务发现
应用启动时,自动往registry表中插入一条数据,数据包括服务名称、IP、端口等信息。
应用停止时,自动把自己在registry表中的数据的status设为 DOWN 服务调用,每次调用前,去mysql中去查询 select * from register where server_name=‘服务名称’ and status='UP’的记录,然后替换原来的调用路径地址
缺点: 上面这个毕竟是一个很简陋的服务注册中心
缺点1:若当前服务宕机,我们必须要把自己的对应的数据标记为down
缺点2:服务每次调用都要发送select 语句,mysql的压力很大
缺点3:若mysql(服务注册中心挂了,我们也应该提供服务调用)
真正的服务注册发现组件应该满足哪些条件才称为一个合格的服务发现注册组件
1)提供一个服务注册接口,其他微服务启动的时候,把自己的IP,端口都在服务端注册下来
2) 提供一个服务发现接口,也就是微服务可以通过service_id 来获取到 对应微服务的网络地址和端口
3)各个微服务与服务注册组件,使用心跳检查机制,若服务注册组件长时间和某微服务 没有通信,那么就应该剔除服务
4)客户端缓存,各个微服务将需要调用服务的地址缓存在本地,并使用一定机制更新(例如定时任务更新、事件推送更新等)。这样既能降低服务发现组件的压力,同 时,即使服务发现组件出问题,也不会影响到服务之间的调用
二:Eureka入门
2.1)搭建eureka服务端
三步骤:
①加依赖
<!-- eureka -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
<version>3.0.0</version>
<exclusions>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
②:写注解 在主启动类上 写上@EnableEurekaServer注解
@EnableEurekaServer
@SpringBootApplication
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
③:编写配置文件
eureka:
client:
#表示是否将自己注册到Eureka Server,默认为true,由于当前应用就是Eureka Server,故而设为false
register-with-eureka: false
# 表示是否从Eureka Server获取注册信息,默认为true,因为这是一个单点的Eureka Server,不需要同步其他的Eureka Server节点的数据,故而设为false
fetch-registry: false
#暴露给其他eureka client 的注册地址
service-url.defaultZone: http://localhost:9000/eureka/
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/yinz
driver-class-name: com.mysql.cj.jdbc.Driver
2.2)搭建eureka客户端
2.2.1)服务客户端 yinz-eureka-client
①:加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<version>3.0.0</version>
<exclusions>
<exclusion>
<artifactId>servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
②:加入注解@EnableDiscoveryClient
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClientApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaClientApplication.class, args);
}
}
③:编写配置文件
eureka:
client:
service-url:
#注册到eureka服务端的地址
defaultZone: http://localhost:9000/eureka/
instance:
#点击具体的微服务,右下角是否显示ip
prefer-ip-address: true
#显示微服务的名称
instance-id: eureka-client-9001
spring:
application:
#注册到eureka服务端的微服务名称
name: eureka-client
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/yinz
driver-class-name: com.mysql.cj.jdbc.Driver
我配置的是一个eureka server, 三个eureka client
client1配置:
server:
port: 9001
servlet:
context-path: /
eureka:
client:
service-url:
#注册到eureka服务端的地址
defaultZone: http://localhost:9000/eureka/
instance:
#点击具体的微服务,右下角是否显示ip
prefer-ip-address: true
#显示微服务的名称
instance-id: eureka-client1-9001
spring:
application:
#注册到eureka服务端的微服务名称
name: eureka-client1
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/yinz
driver-class-name: com.mysql.cj.jdbc.Driver
client2配置:
server:
port: 9003
servlet:
context-path: /
eureka:
client:
service-url:
#注册到eureka服务端的地址
defaultZone: http://localhost:9000/eureka/
instance:
#点击具体的微服务,右下角是否显示ip
prefer-ip-address: true
#显示微服务的名称
instance-id: eureka-client3-9003
spring:
application:
#注册到eureka服务端的微服务名称
name: eureka-client3
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/yinz
driver-class-name: com.mysql.cj.jdbc.Driver
client3配置:
server:
port: 9003
servlet:
context-path: /
eureka:
client:
service-url:
#注册到eureka服务端的地址
defaultZone: http://localhost:9000/eureka/
instance:
#点击具体的微服务,右下角是否显示ip
prefer-ip-address: true
#显示微服务的名称
instance-id: eureka-client3-9003
spring:
application:
#注册到eureka服务端的微服务名称
name: eureka-client3
datasource:
username: root
password: 123456
url: jdbc:mysql://127.0.0.1:3306/yinz
driver-class-name: com.mysql.cj.jdbc.Driver
client1启动类:
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClient1Application {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(EurekaClient1Application.class, args);
}
}
client2启动类:
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClient2Application {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(EurekaClient2Application.class, args);
}
}
client3启动类
@EnableDiscoveryClient
@SpringBootApplication
public class EurekaClient3Application {
@Bean
@LoadBalanced
RestTemplate restTemplate() {
return new RestTemplate();
}
public static void main(String[] args) {
SpringApplication.run(EurekaClient2Application.class, args);
}
}
client1的controller
@RestController
public class YinzController1 {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/eurekaClient1Test")
public String eurekaClient1Test() {
return "eurekaClient1Test方法";
}
@RequestMapping("/hello")
public String hello() {
return restTemplate.getForObject("http://EUREKA-CLIENT2/eurekaClient2Test", String.class);
}
}
client2的controller
@RestController
public class YinzController2 {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/eurekaClient1Test")
public String eurekaClient1Test() {
return "eurekaClient2Test方法";
}
@RequestMapping("/eurekaClient2Test")
public String eurekaClient2Test() {
return "eurekaClient2Test方法";
}
@RequestMapping("/hello")
public String hello() {
return restTemplate.getForObject("http://EUREKA-CLIENT1/eurekaClient1Test", String.class);
}
}
client3的controller
@RestController
public class YinzController3 {
@Autowired
private RestTemplate restTemplate;
@RequestMapping("/hello")
public String hello() {
return restTemplate.getForObject("http://EUREKA-CLIENT1/eurekaClient1Test", String.class);
}
}
访问Eureka Server地址http://localhost:9000/
spring:
application:
name: eureka-client1
cleint1 与 client2接口我spring.application.name相同都注册到了eureka-client1
访问client3的hello接口
3)Eureka 部署架构
3.1)首先,大家看到这个图 脑瓜子是不是嗡嗡嗡的?什么鬼 us-east-1c us-east-1d us-east-1e是什么东西??![在这里插入图片描述](https://img-blog.csdnimg.cn/20210226145919129.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2FsbGNvdmV0YWxsbG9zZQ==,size_16,color_FFFFFF,t_70)
我们首先需要了解一下 aws(amazon web service) 可以类比中国的阿里云服务器.
Region(us-east-1,) 可以类比于 阿里云的不同区域,我们在买阿里云服务器的时候,是不是需要你选择区域么?各个Region 之间的内网是不相通的
Availabitlity zone 可用区(us-east-1c us-east-1d us-east-1e) 可以理解为 我们 一个地区比如华南地区,不同城市的机房,内网是相通的
比如华南1 华东1 2 ,华北12345
Eureka Server提供服务发现的能力,各个微服务启动时,会向Eureka Server注册自己的信息(例如IP、端口、微服务名称等),Eureka Server会存储这些信息;
Eureka Client是一个Java客户端,用于简化与Eureka Server的交互(启动的时候,会向server注册自己的信息)
微服务启动后 renew,会周期性(默认30秒)地向Eureka Server发送心跳以续约自己的“租期”;
如果Eureka Server在一定时间内没有接收到某个微服务实例的心跳(最后一次续约时间开始计算),Eureka Server将会注销该实例(默认90秒);
默认情况下,Eureka Server同时也是Eureka Client。多个Eureka Server实例,互相之间通过增量复制的方式,来实现服务注册表中数据的同步。Eureka Server默认保证在90秒内,Eureka
Server集群内的所有实例中的数据达到一致(从这个架构来看,Eureka Server所有实例所处的角色都是对等的,没有类似Zookeeper、选举过程,也不存在主从,所有的节点都是主节点。Eureka官方将Eureka Server集群中的所有实例称为“对等体(peer)”)
Eureka Client会缓存服务注册表中的信息。这种方式有一定的优势——首先,微服务无需每次请求都查询Eureka Server,从而降低了Eureka Server的压力;其次,即使Eureka Server所有节点都宕掉,服务消费者依然可以使用缓存中的信息找到服务提供者并完成调用
3.2)Eureka配置的总要信息讲解
①:服务注册配置
eureka.client.register-with-eureka=true
该项配置说明,是否向注册中心注册自己,在非集群环境下设置为false,表示自己不注册自己
eureka.client.fetch-registry=true
该项配置说明,注册中心只要维护微服务实例清单,非集群环境下,不需要作检索服务,所有也设置为
②:服务续约配置
eureka.instance.lease-renewal-interval-in-seconds=30(默认)
该配置说明,服务提供者会维持一个心跳告诉eureka server 我还活着,这个就是一个心跳周期
eureka.instance.lease-expiration-duration-in-seconds=90(默认)
该配置说明,你的最后一次续约时间开始,往后推90s 还没接受到你的心跳,那么我就需要把你剔除.
③:获取服务配置 (前提,eureka.client.fetch-registry为true)
eureka.client.registry-fetch-interval-seconds=30
缓存在调用方的微服务实例清单刷新时间
4)eureka 的自我保护功能
默认情况下,若eureka server 在一段时间内(90s)没有接受到某个微服务实例的心跳,那么eureka server 就会剔除
该实例,当时由于网络分区故障,导致eureka server 和 服务之间无法通信,此时这个情况就变得很可怕—因为微服务是实例健康的
,本不应注销该实例.
那么通过eureka server 自我保护模式就起作用了,当eureka server节点短时间(15min是否低于85%)丢失过多客户端是(由于网络分区),那么该eureka server节点就会进入自我保护模式,eureka server 就会自动保护注册表中的微服务实例,不再删除该注册表中微服务的信息,等到网络恢复,eureka server 节点就会退出自我保护功能(宁可放过一千,不要错杀一个)
5)eureka 的高可用 搭建
5.1)9000 端口的工程配置
server:
port: 9000
eureka:
client:
#表示是否将自己注册到Eureka Server 表示9000的服务端需要向9100工程注册自己
register-with-eureka: true
# 表示是否从Eureka Server获取注册信息,默认为true,需要从9100上同步数据
fetch-registry: true
# 将自己注册到9100上去
service-url.defaultZone: http://www.eureka9100.com:9100/eureka/
5.2) 9100端口配置
server:
port: 9100
eureka:
client:
#表示是否将自己注册到Eureka Server 表示9000的服务端需要向9000工程注册自己
register-with-eureka: true
# 表示是否从Eureka Server获取注册信息,默认为true,需要从9000上同步数据
fetch-registry: true
# 将自己注册到9000上去
service-url.defaultZone: http://www.eureka9000.com:9000/eureka/
5.3)微服务提供者配置,需要向二个注册中心进行注册
eureka:
client:
service-url.defaultZone: http://www.eureka9000.com:9000/eureka/,http://www.eureka9001.com:9100/eureka/
6)安全配置 我们现在访问eureka server 直接访问 没有授权
6.1)导入安全配置 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
6.2)修改yml配置文件
# 注册的时候需要带入 用户名 密码 基本格式为 http:用户名:密码@ip:端口/eureka/
eureka:
client:
service-url.defaultZone: http://${spring.security.user.name}:${spring.security.user.name}@www.eureka9000.com:9000/eureka/
spring:
security:
basic:
#开启默认
enable: true
user:
name: root
password: 123456
## 客户端配置
# 注册地址修改为这个
eureka:
client:
service-url:
defaultZone: http://${security.login.username}:${security.login.pass}@www.eureka9000.com:9000/eureka/
三:CAP理论
1)分布式系统的三个指标
1:Consistency (持久性)
2:Availability(可用性)
3:Partition tolerance(分区容错性)
结论:这三个指标不可能同时做到。这个结论就叫做 CAP 定理。
戏说分区容错性 (Partition tolerance)
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败(由于网路原因)。比如,一台服务器放在北京,另一台服务器放在上海,这就是两个区,它们之间由于网络抖动原因导致不能通信.
上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。
一般来说,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。CAP 定理告诉我们,剩下的 C 和 A 无法同时做到。
Consistency(一致性)(各个时间上,各个服务器上的数据必须要一致的)
Consistency 中文叫做"一致性"。意思是,写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。
存在的问题:
用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。
解决方案:为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成 v1。
这样的话用户向G2发起读操作,也能得到V1
Availability 可用性
Availability 中文叫做"可用性",意思是只要收到用户的请求,服务器就必须给出回应。
用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。
为啥CAP 只能三选二
Consistency 和 Availability 的矛盾
一致性和可用性,为什么不可能同时成立?答案很简单,因为可能通信失败(即出现分区容错)。
如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性不。
如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。
综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。