Skip to main content

3-分布式KV缓存系统

1.项目介绍

项目介绍:一个分布式的基于内存K-V 存储系统,基于 CS 架构,可灵活部署多个分布式服务器节点,为客户端提供增删查改操作,并支持单一节点故障迁移功能。由一个中心路由节点与多个slave存储节点成。

1.中心节点具有客户端路由,服务节点哈希环注册,故障监听迁移,LRU高频数据缓存等功能,为客户端提供服务。

2.每个存储节点使用单线程,使用跳表实现数据存储,包含自己的数据与上一个节点备份数据。

3.支持单一节点故障数据迁移,中心节点监听故障并通知存储节点故障迁移。

4.灵活增删节点,允许动态增删存储节点,实现了存储节点优雅停机与自动迁移

5.存储节点通过阻塞队列,使用一个异步线程进行主从同步。

6.服务器通信网络库:重写并简化 muduo 网络库。采用了Reactor模型,one loop peer thead 的模式。通过 epoll +线程池,使用多线程和事件循环机制实现高并发网络库。

项目背景: 之前在学习分布式和缓存以及redis的时候,尝试想着使用另外一种方式实现一下。同时锻炼自己的编码能力以及对加深分布式缓存、跳表、一致性哈希、以及底层网络库的认识。

项目收获:熟悉了 muduo网络库的底层实现原理以及网络编程;加深了对跳表、一致性哈希以及分布式KV 缓存的理解。

2.中心路由节点

  1. 客户端请求的节点,请求数据会映射到哈希环中,中心节点会进行完成数据的查询,返回给客户端。
  2. 中心节点还起到监听集群状态和数据迁移的作用。通过定时(10)秒对各个slave节点发送通知,如果超过3次未收到回应就会被认定为主观下线,从哈希环上移除注册节点,这段时间内的数据操作会直接打到备份节点中。每一个节点的上一个节点也同时与此节点建立心跳,当上一个节点维持的心跳也消失的时候,会被主观下线,中心节点同时完成数据迁移的工作。
  3. 中心节点本地会通过LRU记录缓存一些高频访问数据,用于快速返回,而无需查找slave节点。

3.slave存储节点

  1. slave节点在建立连接时会注册到中心节点的哈希环上,并与中心节点维持一个长连接。

  2. 通过跳表实现的基于内存的分布式 K-V 缓存系统(为什么用跳表实现?原因是方便对数据进行哈希范围查询方便迁移数据)

  3. slave存储节点命令使用的单线程的处理模式,避免因多线程问题导致访问跳表出现线程安全问题。

  4. slave节点会分为两个跳表,一个用来存储自己的数据,一个用来存储上一个节点的备份数据。

4.单一节点故障节点数据迁移

  1. 通过心跳机制检测节点情况,中心节点维持心跳机制,若重复3次没有收到心跳,则主观会认为该节点挂掉,移除哈希环数据。当前一个节点与它的心跳消失时,回给中心节点发送请求,标记为客观下线。同时会进行数据迁移。而在这期间打到该slave节点的请求都会被中心节点路由到他的备份节点。从而对备份节点执行相应操作。

  2. 如果重复3次没有收到心跳,则认为该slave节点已经挂掉。则从中心节点将其移除,执行数据的迁移操作。而在数据迁移过程中的打进来的请求会被路由到备份节点中。

    image-20240717222512206

image-20240717224245257

5.动态新增slave节点

​ slave节点新增首先会向中心节点发送连接请求。连接成功后,会进行节点间的数据自动迁移。数据迁移完成后,中心节点会将slave节点的数据以及连接注册到哈希环中。

6.分布式缓存的一致性问题

因为采用的是哈希环映射架构,一致性问题只会在节点与备份节点中存在,只需在缓存时同时操作节点与备份节点的数据。并通过判断是否同时操作成功来保证数据一致性。

操作不成功的原因主要有两个:网络延迟、服务器挂掉。

主从同步使用异步的日志复制来解决主从同步问题,当主节点接收到消息,会与从节点建立连接,将接收到的日志复制给备份节点。

7.优雅停机

采用特定的Stop命令,发送给中心节点,中心节点会注销哈希环上的节点数据,进行主动数据迁移

8.服务器通信网络库

Reactor 方式,one loop peer thead 的模式,驱动的事件回调的 epoll + 线程池面向对象,通过多线程和事件循环机制实现 高并发处理。

EventLoop为核心的事件处理类

Channel:为了对关注的以及发生的读写事件和对应fd的封装。以及保存handler。

Poller:多路事件分发器的核心IO复用模块,使用epoll多路复用监听。通过poll函数监听活跃事件。返回活跃事件的Channel 列表。

EventLoop:事件循环处理类,调用poll函数,得到活跃事件。执行活跃事件对应的channel对应的handler方法。通过eventfd方式进行唤醒。同时执行异步队列里面的方法。

EventLoopThreadPool:基于EventLoop的线程池,保存固定数量的线程,每一个线程中会有一个EventLoop事件循环。

TcpConnection为核心的连接类

Acceptor:主线程中用来监听新的连接的类,复制建立新的连接。建立连接后调用Callback函数,建立连接。

TcpConnection:在Acceptor建立监听之后,调用回调函数进行连接的建立。保存有对应的EventLoop,封装的Channel,为用户提供写入接口。

TcpServer:做线程配置,启动服务,建立连接的功能。其包含Acceptor以及多个TcpConnection连接还有一个主eventLoop。还有一个EventLoopThreadPool线程池。

启动流程:

  1. TcpServer 启动线程池(线程池会启动线程以及开启eventLoop事件监听),线程池处理的是读写回调。主线程对Acceptor开启监听,监听读事件,也就是新的连接。
  2. 当有新的连接来,会触发Acceptor的读事件新连接到来的回调,会通过轮询的方式,选择一个eventLoop,同时构造一个TcpConnection连接,设置读写回调函数。
  3. 通过调用TcpConnection的connectEstablished,在poller中注册新连接Channel读事件。
  4. 当有读事件发生,对应的eventLoop会对读事件进行处理。
  5. TcpConnection也提供对外了写的接口,在回调函数中,通过处理指定的信息,实现对客户端写入数据。