JAVA面试题分享一百七十九:Nacos的配置中心和注册中心是如何进行数据存储的?

一、一致性存储

配置中心最为关键的就是如何去做好存储,一般我们存储就两种方式, 要么全内存存储,能保证性能非常高,但是维护不同机器内存一致性复杂度比较高,还有一种就是使用数据库,内存里面不维护任何状态,每一台机器都可以进行写入操作,这个复杂度比较低,不需要考虑一致性的问题,但是由于所有的读写都会走数据库所以性能就不能保证。在Nacos中对这两种存储方式做了一些改进,实现了既保证了性能又保证了复杂度一致性。

在Nacos1.3之后提供了mysql 和 raft + derby两种存储方式,接下来介绍一一介绍一下这两种存储方式。

1、mysql + 异步全量通知

nacos最开始提供的就是mysql的方式,所有机器都可以进行读写,没有主备之分,如下图所示:

数据的读写都是直接走Mysql,具体的代码在ExternalStoragePersistServiceImpl中,其中直接使用的JdbcTemplate,这里就不详细把代码展现出来介绍了,有兴趣的可以直接去这里看。

如果只是使用mysql,有同学会提出问题,只使用mysql如何才能保证数据库性能不会成为瓶颈呢?最简单的方法就是使用高配置的Mysql,用钱给我干上去,很明显这个不是很靠谱,只适用于土豪玩家。那么怎么去做这种优化呢?一般做业务的同学通常会在Mysql前面放一层缓存层,比如redis,memcached等等。

在Nacos中同样的也使用了缓存这个概念帮助我们缓解数据库压力。但是和普通的缓存稍微有点不同: 在ConfigService中有一个HashMap缓存了所有Config的元数据(MD5,类型这些数据)

但是对于具体存储的值我们不会直接放在内存,而是存储到了本地磁盘,这么做的好处是因为我们的config所配置的值我们不能保证他的大小,如果每个config的值都很大,那么我们的内存必然会不足,这个时候Nacos和Apollo 两个开源中间件给出两种解法:

  • Apollo的做法是使用一个guavaCache,使用淘汰策略将不经常使用的进行淘汰。
  • Nacos的做法是全量缓存元数据,具体的值存储到磁盘空间,采用分离存储的方式,nacos采用这种方法,如果只是访问元数据那么全量内存即可,不会像Apollo一样可能会遇到淘汰的原因,访问数据库。

Dump

Nacos使用的是全量缓存元数据到内存,具体的值存储到磁盘空间,但是会存在一个问题,那就是当一台机器的数据发生变更,其他机器的内存怎么变更呢?这就需要我们的全量异步通知,在每一次修改数据的时候都会发送一个ConfigDataChage事件,然后本机接受并进行处理,然后发送这个变更消息到其他的所有机器上。

其他机器收到这个变更通知之后,会进行一次dump操作:

会先查询元数据中的MD5,MD5其实也是根据我们配置中的值算出来的,所以能进行快速判断这一次时候发生了值的变更,如果发生变更,我们就将这个值存储到磁盘上。

如果我们这个机器是新启动的,这个时候其实就不会存在任何缓存以及dump文件,那么DumpService会遍历数据库的所有数据,全量的都缓存到机器上,以便我们使用。

2、raft + derby

Nacos在1.3.0之后提供了一个新的存储模式,那就是使用raft协议保证数据一致性,使用apache derby进行内嵌的数据存储。提供这种方式的目的是减少用户维护mysql数据库集群的成本,并且简化了集群部署的成本,部署Nacos的时候直接打包Nacos镜像就好,不需要再单独部署一套数据库。

在Nacos中使用的是sofa-jraft,这个是蚂蚁开源的一个java版本高性能的raft实现,不熟悉raft的同学可以阅读以下raft的论文,了解过raft的同学应该都知道raft非常强化Leader的概念:

  • 系统中必须存在且同一时刻只能有一个 leader,只有 leader 可以接受 clients 发过来的请求
  • Leader 负责主动与所有 followers 通信,负责将’提案’发送给所有 followers,同时收集多数派的 followers 应答
  • Leader 还需向所有 followers 主动发送心跳维持领导地位

我们发现所有的事情都和leader相关,那么我们的性能必定被限制在leader上面,所以在Nacos中选择了对raft本身有大量优化的sofa-jraft,在sofa-jraft中做了如下的优化:

  • 批量化:批量化操作是很多系统的一个优化策略,在jraft中同样的也采用了批量化操作,通过disruptor 的 MPSC 模型批量消费,实现了下面的一些批量操作,提升了很多的性能:

    • 批量提交 task
    • 批量网络发送
    • 本地 IO batch 写入
    • 批量应用到状态机
  • pipeline复制: pipeline是一种管道技术,帮助我们不再和以前请求-响应模型一样,他可以持续往管道中放入请求,过程中而不需要等待请求的回复,在最后再一并读取结果即可。在jraft中开启pipeline性能会提升30%。

  • 并行化:leader持久化log和发送Log到follower是并行的,发送到不同的follower也是并行的。

  • 线性读:在raft协议中,读请求会按照 Log 处理,通过 Log 复制和状态机执行来得到读结果,然后再把结果返回给 Client。这种办法的缺点是需要 Log 存储、复制,这样会带来刷盘开销、存储开销、网络开销,因此在读操作很多的场景下对性能影响很大。在Sofajrat中进行了ReadIndex,Lease Read优化,让所有的读都可以在本地执行,这个对性能的提升特别大。

Apache Derby也是一个Java编写的轻量级数据库,Nacos通过这样的设计其实是构建了一个轻量级的分布式数据库,在每一台的机器上都会有一个保存数据的数据库,然后通过raft协议保证所有机器数据的一致性。

内嵌数据库的方式并不比Mysql的方式更好,在性能上Mysql那种方式因为存了很多缓存,并且content也保存到磁盘上,读取的时候基本不会走库,所以Mysql的方式其实更好,但是内嵌数据库的方式在运维部署的方式上是非常占优的。这里如何取舍需要用户自己进行一个选择

二、总结

Nacos的配置中心和注册中心的数据存储方式如下:

  1. 配置中心:

    • 配置中心的数据存储在外置数据源(关系型数据库)和内嵌存储数据源(Apache Derby)中。当配置文件数量较少时,可以使用内嵌的轻量级基于Derby的分布式关系型存储来解决。在集群模式下,如果需要高可用数据库集群作为支撑,成本会比较大。设计目标是期望Nacos存在两种数据存储模式,一种是现在的方式,数据存储在外置数据源(关系型数据库);第二种方式是内嵌存储数据源(Apache Derby)。
  2. 注册中心:

    • 注册到Nacos的服务实例信息是存放在Map数据结构的内存中,目前Nacos使用了ConcurrentSkipListMap。因为Nacos对服务的存储是共享的,所以会存在多个线程并发访问的安全问题,而ConcurrentSkipListMap比较适合解决此类问题,这主要是依靠它的“跳表”存取算法,只需局部加锁,所以吞吐率比较好,这是Nacos选择它的主要原因。Nacos主要存放了服务所在机器的IP地址和端口号、空间ID、分组Group、服务名字等信息。

以上是关于Nacos配置中心和注册中心数据存储的介绍,供您参考。

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

之乎者也·

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值