ES 的分布式架构原理能说一下么(ES 是如何实现分布式的啊)?
ES 写入数据的工作原理是什么啊?ES 查询数据的工作原理是什么啊?底层的 Lucene 介绍一下呗?倒排索引了解吗?
ES 在数据量很大的情况下(数十亿级别)如何提高查询效率啊?
ES 生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?
在项目中缓存是如何使用的?缓存如果使用不当会造成什么后果?
Redis 和 Memcached 有什么区别?Redis 的线程模型是什么?为什么单线程的 Redis 比多线程的 Memcached 效率要高得多?
Redis 都有哪些数据类型?分别在哪些场景下使用比较合适?
Redis 的过期策略都有哪些?手写一下 LRU 代码实现?
如何保证 Redis 高并发、高可用?Redis 的主从复制原理能介绍一下么?Redis 的哨兵原理能介绍一下么?
Redis 的持久化有哪几种方式?不同的持久化机制都有什么优缺点?持久化机制具体底层是如何实现的?
Redis 集群模式的工作原理能说一下么?在集群模式下,Redis 的 key 是如何寻址的?分布式寻址都有哪些算法?了解一致性 hash 算法吗?如何动态增加和删除一个节点?
了解什么是 Redis 的雪崩、穿透和击穿?Redis 崩溃之后会怎么样?系统该如何应对这种情况?如何处理 Redis 的穿透?
如何保证缓存与数据库的双写一致性?
Redis 的并发竞争问题是什么?如何解决这个问题?了解 Redis 事务的 CAS 方案吗?
生产环境中的 Redis 是怎么部署的?
为什么要分库分表(设计高并发系统的时候,数据库层面该如何设计)?用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
现在有一个未分库分表的系统,未来要分库分表,如何设计才可以让系统从未分库分表动态切换到分库分表上?
如何设计可以动态扩容缩容的分库分表方案?
分库分表之后,id 主键如何处理?
如何实现 MySQL 的读写分离?MySQL 主从复制原理是啥?如何解决 MySQL 主从同步的延时问题?
高并发系统
如何设计一个高并发系统?
现在流行的微服务体系结构正在改变我们构建应用程序的方式,从单一的单体服务转变为越来越小的可单独部署的服务(称为
微服务
),共同构成了我们的应用程序。当进行一个业务时不可避免就会存在多个服务之间调用,假如一个服务 A 要访问在另一台服务器部署的服务 B,那么前提是服务 A 要知道服务 B 所在机器的 IP 地址和服务对应的端口,最简单的方式就是让服务 A 自己去维护一份服务 B 的配置(包含 IP 地址和端口等信息),但是这种方式有几个明显的缺点:随着我们调用服务数量的增加,配置文件该如何维护;缺乏灵活性,如果服务 B 改变 IP 地址或者端口,服务 A 也要修改相应的文件配置;还有一个就是进行服务的动态扩容或缩小不方便。
一个比较好的解决方案就是
服务发现(Service Discovery)
。它抽象出来了一个注册中心,当一个新的服务上线时,它会将自己的 IP 和端口注册到注册中心去,会对注册的服务进行定期的心跳检测,当发现服务状态异常时将其从注册中心剔除下线。服务 A 只要从注册中心中获取服务 B 的信息即可,即使当服务 B 的 IP 或者端口变更了,服务 A 也无需修改,从一定程度上解耦了服务。服务发现目前业界有很多开源的实现,比如
apache
的
zookeeper
、
Netflix
的
eureka
、
hashicorp
的
consul
、
CoreOS
的
etcd
。
Eureka 是什么
Eureka
在
GitHub
上对其的定义为
Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.
At Netflix, Eureka is used for the following purposes apart from playing a critical part in mid-tier load balancing.
Eureka
是由
Netflix
公司开源,采用的是 Client / Server 模式进行设计,基于 http 协议和使用 Restful Api 开发的服务注册与发现组件,提供了完整的服务注册和服务发现,可以和
Spring Cloud
无缝集成。其中 Server 端扮演着服务注册中心的角色,主要是为 Client 端提供服务注册和发现等功能,维护着 Client 端的服务注册信息,同时定期心跳检测已注册的服务当不可用时将服务剔除下线,Client 端可以通过 Server 端获取自身所依赖服务的注册信息,从而完成服务间的调用。遗憾的是从其官方的
github wiki
可以发现,2.0 版本已经不再开源。但是不影响我们对其进行深入了解,毕竟服务注册、服务发现相对来说还是比较基础和通用的,其它开源实现框架的思想也是想通的。
服务注册中心(Eureka Server)
我们在项目中引入
Eureka Server
的相关依赖,然后在启动类加上注解
@EnableEurekaServer
,就可以将其作为注册中心,启动服务后访问页面如下:
我们继续添加两个模块
service-provider
,
service-consumer
,然后在启动类加上注解
@EnableEurekaClient
并指定注册中心地址为我们刚刚启动的
Eureka Server
,再次访问可以看到两个服务都已经注册进来了。
Demo
仓库地址:https://github.com/mghio/depth-in-springcloud
可以看到
Eureka
的使用非常简单,只需要添加几个注解和配置就实现了服务注册和服务发现,接下来我们看看它是如何实现这些功能的。
服务注册(Register)
注册中心提供了服务注册接口,用于当有新的服务启动后进行调用来实现服务注册,或者心跳检测到服务状态异常时,变更对应服务的状态。服务注册就是发送一个
POST
请求带上当前实例信息到类
ApplicationResource
的
addInstance
方法进行服务注册。
可以看到方法调用了类
PeerAwareInstanceRegistryImpl
的
register
方法,该方法主要分为两步:
调用父类
AbstractInstanceRegistry
的
register
方法把当前服务注册到注册中心
调用
replicateToPeers
方法使用异步的方式向其它的
Eureka Server
节点同步服务注册信息
服务注册信息保存在一个嵌套的
map
中,它的结构如下:
第一层
map
的
key
是应用名称(对应
Demo
里的
SERVICE-PROVIDER
),第二层
map
的
key
是应用对应的实例名称(对应
Demo
里的
mghio-mbp:service-provider:9999
),一个应用可以有多个实例,主要调用流程如下图所示:
服务续约(Renew)
服务续约会由服务提供者(比如
Demo
中的
service-provider
)定期调用,类似于心跳,用来告知注册中心
Eureka Server
自己的状态,避免被
Eureka Server
认为服务时效将其剔除下线。服务续约就是发送一个
PUT
请求带上当前实例信息到类
InstanceResource
的
renewLease
方法进行服务续约操作。
进入到
PeerAwareInstanceRegistryImpl
的
renew
方法可以看到,服务续约步骤大体上和服务注册一致,先更新当前
Eureka Server
节点的状态,服务续约成功后再用异步的方式同步状态到其它
Eureka Server
节上,主要调用流程如下图所示:
服务下线(Cancel)
当服务提供者(比如
Demo
中的
service-provider
)停止服务时,会发送请求告知注册中心
Eureka Server
进行服务剔除下线操作,防止服务消费者从注册中心调用到不存在的服务。服务下线就是发送一个
DELETE
请求带上当前实例信息到类
InstanceResource
的
cancelLease
方法进行服务剔除下线操作。
进入到
PeerAwareInstanceRegistryImpl
的
cancel
方法可以看到,服务续约步骤大体上和服务注册一致,先在当前
Eureka Server
节点剔除下线该服务,服务下线成功后再用异步的方式同步状态到其它
Eureka Server
节上,主要调用流程如下图所示:
服务剔除(Eviction)
服务剔除是注册中心
Eureka Server
在启动时就启动一个守护线程
evictionTimer
来定期(默认为
60
秒)执行检测服务的,判断标准就是超过一定时间没有进行
Renew
的服务,默认的失效时间是
90
秒,也就是说当一个已注册的服务在
90
秒内没有向注册中心
Eureka Server
进行服务续约(Renew),就会被从注册中心剔除下线。失效时间可以通过配置
eureka.instance.leaseExpirationDurationInSeconds
进行修改,定期执行检测服务可以通过配置
eureka.server.evictionIntervalTimerInMs
进行修改,主要调用流程如下图所示:
服务提供者(Service Provider)
对于服务提供方(比如
Demo
中的
service-provider
服务)来说,主要有三大类操作,分别为
服务注册(Register)
、
服务续约(Renew)
、
服务下线(Cancel)
,接下来看看这三个操作是如何实现的。
服务注册(Register)
一个服务要对外提供服务,首先要在注册中心
Eureka Server
进行服务相关信息注册,能进行这一步的前提是你要配置
eureka.client.register-with-eureka=true
,这个默认值为
true
,注册中心不需要把自己注册到注册中心去,把这个配置设为
false
,这个调用比较简单,主要调用流程如下图所示:
服务续约(Renew)
服务续约是由服务提供者方定期(默认为
30
秒)发起心跳的,主要是用来告知注册中心
Eureka Server
自己状态是正常的还活着,可以通过配置
eureka.instance.lease-renewal-interval-in-seconds
来修改,当然服务续约的前提是要配置
eureka.client.register-with-eureka=true
,将该服务注册到注册中心中去,主要调用流程如下图所示:
服务下线(Cancel)
当服务提供者方服务停止时,要发送
DELETE
请求告知注册中心
Eureka Server
自己已经下线,好让注册中心将自己剔除下线,防止服务消费方从注册中心获取到不可用的服务。这个过程实现比较简单,在类
DiscoveryClient
的
shutdown
方法加上注解
@PreDestroy
,当服务停止时会自动触发服务剔除下线,执行服务下线逻辑,主要调用流程如下图所示:
服务消费者(Service Consumer)
这里的服务消费者如果不需要被其它服务调用的话,其实只会涉及到两个操作,分别是从注册中心
获取服务列表(Fetch)
和
更新服务列表(Update)
。如果同时也需要注册到注册中心对外提供服务的话,那么剩下的过程和上文提到的服务提供者是一致的,这里不再阐述,接下来看看这两个操作是如何实现的。
获取服务列表(Fetch)
服务消费者方启动之后首先肯定是要先从注册中心
Eureka Server
获取到可用的服务列表同时本地也会缓存一份。这个获取服务列表的操作是在服务启动后
DiscoverClient
类实例化的时候执行的。
可以看出,能发生这个获取服务列表的操作前提是要保证配置了
eureka.client.fetch-registry=true
,该配置的默认值为
true
,主要调用流程如下图所示:
更新服务列表(Update)
由上面的
获取服务列表(Fetch)
操作过程可知,本地也会缓存一份,所以这里需要定期的去到注册中心
Eureka Server
获取服务的最新配置,然后比较更新本地缓存,这个更新的间隔时间可以通过配置
eureka.client.registry-fetch-interval-seconds
修改,默认为
30
秒,能进行这一步更新服务列表的前提是你要配置
eureka.client.register-with-eureka=true
,这个默认值为
true
。主要调用流程如下图所示:
工作中项目使用的是
Spring Cloud
技术栈,它有一套非常完善的开源代码来整合
Eureka
,使用起来非常方便。之前都是直接加注解和修改几个配置属性一气呵成的,没有深入了解过源码实现,本文主要是阐述了服务注册、服务发现等相关过程和实现方式,对
Eureka
服务发现组件有了更近一步的了解。
Netflix Eureka
Service Discovery in a Microservices Architecture