4 Star 18 Fork 2

Anur / kanashi

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
贡献代码
同步代码
取消
提示: 由于 Git 不支持空文件夾,创建文件夹后会生成空的 .keep 文件
Loading...
README
MIT

Kanashi 0.0.1-Alpha


Kanashi 是一款基于 kotlin 开发的分布式 NoSQL 内存数据库,支持事务。确切的来说,旨在作者加深学习分布式写出来的玩具,并不对标现有的成熟数据库。Kanashi 基于 netty 来进行通信,基于 raft 实现了选举,可以说是内置了一个粗糙的 NameServer。日志同步相关功能则是由 Kafka 改造而来。此外,Kanashi 还支持事务,目前仅支持 Repeatable read 的隔离级别。

在不严谨的性能测试中:

  • 由服务端本地的日志写入,连同数据引擎对日志的解析,可达到 30W 左右的 TPS( Intel(R) i7-8700 + SAMSUNG 860 EVO)。
  • 由客户端对服务器单机进行调用,则可达到 6000 TPS。
  • 而如若客户端对服务器集群(3台机器),则只能达到每秒 10 次左右请求。

并不是集群之间协调的效率太差,由于日志沿用了 Kakfa 的 Pull 模型,又由于每个操作需要得到节点半数同意才可真正提交、落盘、告知提交状态。由日志同步,到汇报进度,再到集群提交,最后到 Leader 进行标记提交。整个状态的流转,对于客户端是一个线性的过程。

但是对于多客户端来说,是可以互相不受干扰并行的。我们有理由相信,在多客户端的情况下,集群的整体性能将更好。当然这也充分反映了整套为了保证数据较强一致性流程的不合理,以及客户端设计不合理等问题。

额外的话:

Kanashi 的前身由是作者的另一个框架 Hanabi 改进而来,改善了许多在 Hanabi 中设计不合理的地方。

目前整套架子已经比较完善,实际上可以很快将项目改造成其他类型的框架,如 RPC 框架、分布式调度框架等。


一、Quick Start

1.1 服务端

配置服务端,服务端的配置位于 kanashi-server 模块下的 kanashi.properties:

#
# set the server name, caution: server name must be configured in client.addr
#
server.name=kanashi.1
#
# addr config composed with client.addr.{serverName}={host}:{port}
#
client.addr.kanashi.1=127.0.0.1:11001
client.addr.kanashi.2=127.0.0.1:11002
client.addr.kanashi.3=127.0.0.1:11003

可根据 client.addr.{serverName}={host}:{port} 来配置集群信息,目前暂不支持动态节点伸缩。

server.name 则声明本机为哪个节点。但不一定要写在配置文件中,可在启动时指定 server_name:kanashi.1 来声明本节点。

pic1-server

1.2 客户端

客户端目前及其粗糙,可通过引入 kanashi-client 或者直接在 kanashi-client 上启动。客户端需要在 kanashi-client 模块下的 application.properties 配置如下,只需要配置任意一台机器:

kanashi.host=127.0.0.1
kanashi.port=11001
kanashi.reSendBackOffMs=30

通过 KanashiStrTemplate 来发送指令到服务器:


@Autowired
private KanashiStrTemplate kanashiStrTemplate;

@Test
public void test() {

    kanashiStrTemplate.delete("Anur");
    assert (kanashiStrTemplate.get("Anur") == null);

    kanashiStrTemplate.set("Anur", "1");
    assert (kanashiStrTemplate.get("Anur")
                              .equals("1"));

    kanashiStrTemplate.setIf("Anur", "2", "2");
    assert (kanashiStrTemplate.get("Anur")
                              .equals("1"));
}

具体可参照测试用例 TestWithNonTrxTestWithTrxTestWritingAmountOfMsg

二、项目介绍

Kanashi 大可由以下六大模块组成:选举模块、日志模块、IOC模块、数据引擎模块、业务模块、配置模块。

1.1 选举模块

选举模块是框架中较为成熟的模块,基于 raft 实现,raft 的核心就在于几个定时器:成为 Candidate 并发起新世代操作的任务、选举(拉票)任务、心跳任务。在选举阶段,整个集群将不可用,直到在新的世代(Epoch,项目里成为Generation),选出唯一的 Leader 后,才由监听器触发通知其他模块。

1.2 日志模块

日志模块是由 Kakfa 改造而来,使用磁盘顺序写来刷盘,一条日志由 LogItem 来表示,刷盘时则由 偏移量信息 + LogItem 组成一条磁盘日志。一定大小(可在配置中指定)的一系列日志将落盘为 .log 文件,文件以第一条日志的偏移量命名。

每个 .log 文件都有一个与之对应的 .index 索引文件。索引文件采用稀疏索引,每落盘一定大小的日志会记录其在文件中的相对位置(position)。

在集群同意提交某进度时,所有早于此进度的日志都将刷盘。还未获取到此进度的服务可通过 Fetch 请求进行同步,同步消息会采用零复制技术直接将磁盘日志发送到网络中。

1.3 IOC模块

项目实现了简单的注入:

/**
 * Created by Anur IjuoKaruKas on 2019/7/8
 *
 * 选举相关的元数据信息都保存在这里
 */
@NigateBean
class ElectionMetaService {

    @NigateInject
    private lateinit var inetSocketAddressConfiguration: InetSocketAddressConfiguration

    @NigateInject
    private lateinit var logService: LogService

    @NigateInject
    private lateinit var kanashiListenerService: NigateListenerService

    private val logger: Logger = LoggerFactory.getLogger(this::class.java)

    /**
     * 启动后将之前保存的 GAO 进度重新加载回内存
     */
    @NigatePostConstruct(dependsOn = "LogService")
    private fun init() {
        val initialGAO = logService.getInitialGAO()
        this.generation = initialGAO.generation
        this.offset = initialGAO.offset
    }
}

通过 @NigateBean 来将某个实例声明为 bean,我们可以在其他 bean 中,通过 @NigateInject 的方式将其注入,同时,也实现了诸如 PostConstructListener 等附加功能。

麻雀虽小,也算是五脏俱全了。这是此项目和 Hanabi 项目最大的区别。虽然 kotlin 可以很简单的去实现单例模式,但要实现一些其他的附加功能如事件监听,生命周期控制,将使得代码变得十分难以维护。

1.4 数据引擎模块与业务模块

数据引擎模块算是近期才开发的一个模块,整体模块的设计还算满意,封装比较合理,与其他模块基本是解耦的。

数据引擎模块可以看做是日志 LogItem 的消费者,查询和写入都采用了责任链模式进行设计。为了实现隔离性,我们将数据分别存储于“未提交部分”、”提交部分“以及“LSM部分”。

初期的设计是考虑将数据真正落盘,并构建成 LSM 树的,但这部分开发工作量还是蛮大的,暂且放缓,它需要考虑磁盘的编排,如extent、page、block 等等。另一方面,也要充分考虑性能,比如如何去防止无效的查询使用布隆过滤器等等。

事务方面使用了位图来总览事务的状态,也有利于对整个数据库事务进行“快照”。

业务模块基本就是对数据引擎模块的简单组合与调用:

StrApiTypeEnum.SELECT -> {
    engineDataQueryer.doQuery(engineExecutor, {
        if (it != null && !it.isDelete()) {
            engineExecutor.getEngineResult().setKanashiEntry(it)
        }
        engineExecutor.shotSuccess()
    }, false)
}

比较有意思的是事务阻塞控制:

fun acquire(trxId: Long, key: String, whatEverDo: () -> Unit) {
    // 创建或者拿到之前注册的事务,并注册持有此key的锁
    val trxHolder = trxHolderMap.compute(trxId) { _, th -> th ?: TrxHolder(trxId) }!!.also { it.holdKeys.add(key) }

    // 先去排队
    val waitQueue = lockKeeper.compute(key) { _, ll ->
        (ll ?: LinkedList()).also { if (!it.contains(trxId)) it.add(trxId) }
    }!!

    when (waitQueue.first()) {
        // 代表此键无锁
        trxId -> {
            whatEverDo.invoke()
            logger.trace("事务 $trxId 成功获取或重入位于键 $key 上的锁,并成功进行了操作")
        }

        // 代表有锁
        else -> {
            trxHolder.undoEvent.compute(key) { _, undoList ->
                (undoList ?: mutableListOf()).also { it.add(whatEverDo) }
            }
            logger.debug("事务 $trxId 无法获取位于键 $key 上的锁,将等待键上的前一个事务唤醒,且挂起需执行的操作")
        }
    }
}

代码采用了函数式的方式,将业务要进行的操作进行提取,如果获取到锁,则只需要简单调用此函数,否则将其放入映射中“等待通知”,并不涉及到真正的阻塞,这个设计很大程度避免了由于键锁带来的线程切换开销。

1.5 配置模块

配置模块的代码大概是一年多前就写了的,目前实际上很多可以配置的地方都是“魔数”直接写死,实现也比较简单,完善的优先度不高。

三、需要修复或解决的问题

  • 集群模式相对单机模式来说,在数据引擎这个模块不是过于稳定,缺少测试
  • 缺乏多客户端对集群模式的测试
  • 缺乏多客户端对单机模式的测试
  • 客户端在连接集群以及获取集群信息这块的设计比较生硬
  • 在不优雅关闭的情况下,有时候会出现日志文件读取失败的问题
  • 需要一个事务超时强制提交或回滚的机制
  • TrxHolder 事务快照可能有并发问题,出现的概率很低,比较难复现

四、其他

对项目有兴趣,或者想一起折腾这些乱七八糟事情的可以加群,目前基本没人:1035435027

kanashi

讨论Java相关技术可以加群:767271344

二维码

另外找工作中 = =,3年-Java后端,简历如下:

http://anur.ink/upload/2020/4/JAVA%E5%90%8E%E7%AB%AF-3%E5%B9%B4-%E7%BD%91%E7%BB%9C%E7%89%88-4ea872fb596f4553aa9f04cefcb65a84.pdf

MIT License Copyright (c) 2020 Anur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

简介

Kanashi 是一款基于 kotlin 开发的分布式 NoSQL 内存数据库,支持事务。确切的来说,旨在作者加深学习分布式写出来的玩具,并不对标现有的成熟数据库。Kanashi 基于 netty 来进行通信,基于 raft 实现了选举,可以说是内置了一个粗糙的 NameServer。日志同步相关功能则是由 Kafka 改造而来。此外,Kanashi 还支持事务,目前仅支持 Repeatable read 的隔离级别。 展开 收起
Kotlin 等 2 种语言
MIT
取消

发行版

暂无发行版

贡献者

全部

近期动态

加载更多
不能加载更多了
Kotlin
1
https://gitee.com/anur/kanashi.git
git@gitee.com:anur/kanashi.git
anur
kanashi
kanashi
master

搜索帮助

14c37bed 8189591 565d56ea 8189591