Steins;Lab

  • 项目
  • 折腾
  • 笔记
  • 图册
  • 杂谈
  • 文章索引 - 博主自荐博文
  • 关于/留言
Steins;Lab
某团的自留研究所
  1. 首页
  2. 学习笔记
  3. 正文

使用 braft 构建应用,应该关注哪些指标?

2024年5月12日 3040点热度 1人点赞 2条评论

braft 是一个较多人使用的 C++ raft 框架。开发者基于其抽象接口实现自己的业务逻辑,方便实现 raft 高可用的服务。本文从 metrics 入手,梳理开发者应该持续关注哪些监控变量。以其为线索,阅读源码探究其实现原理。力争做到心里有谱,不惧异常。


(题图由 StableDiffusionXL 生成)

Table of Contents

  • 0 背景
  • 1 概览
  • 2 state
    • STATE_FOLLOWER
    • STATE_LEADER
    • STATE_TRANSFERRING
    • STATE_CANDIDATE
    • STATE_ERROR
    • STATE_UNINITIALIZED, STATE_SHUTTING, STATE_SHUTDOWN
  • 3 term
  • 4 configuration 相关: conf_index / peers / changing_conf / stage
    • configuration & peers 管理接口
    • chaning_conf / stage
  • 5 log_manager 相关 index
  • 6 ballot_box 相关 index
    • Q: apply 与 commit
  • 7 state_machine 相关
  • 8 snapshot 相关
  • 9 Replicator 相关
  • 小结

0 背景

raft 算法和 braft 框架的解析,网络上已经有众多优秀的文章。项目 example 下也提供了完整的示例,服务可参考实现自己的业务逻辑。
相比快速实现一个基本可用服务,在长尾运行中应对各类异常情况更具挑战性。因此,本文主要回答以下问题:

  • braft 中,已经暴露了哪些监控 metrics 变量?
  • 每个变量对应 raft 背后的实现、以及为什么它很重要,需要监控?
  • 根据源码和实验,特定异常情况下,braft 框架表现出哪些行为?

braft 提供了 2 种观测的方法。

  1. 自行获取 node status
    • 更加偏向于 node 最基本的状态,包括 Follower / Leader, commit log Index 等
  2. 内置 bvar 暴露的 metrics
    • 更多地是每个模块内部的详细状态,比如 snapshot store 写入速度, apply log 速率等

而这些监控指标,正是我们入手了解 braft 内部实现的好线索。

本文梳理源码版本 v1.1.2。

1 概览

braft 要求用户将自己服务逻辑实现在 node 类中,比如实现其中的 on_apply, on_leader_start 等接口,本文不再赘述。
其中 node 提供了 get_status 方法,用于获取 raft node 内部状态。

class NodeImpl 
        : public butil::RefCountedThreadSafe<NodeImpl> {
...
public:
...
    void describe(std::ostream& os, bool use_html);

    // Get the internal status of this node, the information is mostly the same as we
    // see from the website, which is generated by |describe| actually.
    void get_status(NodeStatus* status);
...

}

NodeStatus 的定义如下:

// Status of Node
struct NodeStatus {
    ...

    State state;
    PeerId peer_id;
    PeerId leader_id;
    bool readonly;
    int64_t term;

    int64_t committed_index;
    int64_t known_applied_index;

    int64_t pending_index;
    int64_t pending_queue_size;

    int64_t applying_index;
    int64_t first_index;
    int64_t last_index;
    int64_t disk_index;

    PeerStatusMap stable_followers;
    PeerStatusMap unstable_followers;
};

braft 会自动注册到 brpc 服务中,用户可以访问 http://brpc_server_addr:port/raft_stat 直接获取状态。不过相比于程序中的 get_status,直接访问调用的是 node.describe,相比单独的 NodeStatus 更加详细一些。

比如:

[Region_0]
peer_id: 127.0.0.1:30000:0
state: LEADER
readonly: 0
term: 12
conf_index: 279754448
peers: 127.0.0.1:30000:0 127.0.0.1:30001:0 127.0.0.1:30002:0
changing_conf: NO    stage: STAGE_NONE
election_timer: timeout(5000ms) STOPPED
vote_timer: timeout(6000ms) STOPPED
stepdown_timer: timeout(5000ms) SCHEDULING(in 3749ms)
snapshot_timer: timeout(30000ms) SCHEDULING(in 17529ms)
storage: [279752493, 279755268]
disk_index: 279755268
known_applied_index: 279755268
last_log_id: (index=279755268,term=12)
state_machine: Idle
last_committed_index: 279755268
last_snapshot_index: 279754448
last_snapshot_term: 12
snapshot_status: IDLE
replicator_6597069766661@127.0.0.1:30001:0: next_index=279714316  flying_append_entries_size=0 blocking consecutive_error_times=41 hc=15897 ac=2676 ic=1355
replicator_7709466296321@127.0.0.1:30002:0: next_index=279755269  flying_append_entries_size=0 idle hc=15897 ac=529862 ic=0

2 state

state 是 raft node 状态机最最基本的状态,相信读过 raft 论文的开发者都不陌生。除了我们的业务逻辑,raft 状态也是个有限状态机:

enum State {
    STATE_LEADER = 1,
    STATE_TRANSFERRING = 2,
    STATE_CANDIDATE = 3,
    STATE_FOLLOWER = 4,
    STATE_ERROR = 5,
    STATE_UNINITIALIZED = 6,
    STATE_SHUTTING = 7,
    STATE_SHUTDOWN = 8,
    STATE_END,
};

STATE_FOLLOWER

根据源码,有 2 种情况会流转进入。

  1. node init, node 初始化
    • 初始化时,做完各项前置检查后,会先将 state 设置为 FOLLOWER。
  2. leader step down, leader 下台

梳理一下所有 leader 下台的原因:

情景 code msg 说明
node init Status::OK() ok 节点初始化时,会触发一次下台,做必要的内部 ctx, timer 更新
stepdown_timer ERAFTTIMEDOUT Majority of the group dies leader 上台后,开启 stepdown timer 定期检查 check_dead_nodes。连接超时超过节点半数,则主动下台
reset_peers ESETPEER Set peer from empty configuration 或 Raft node set peer normally" 通过接口强行 reset_peers 后,会调用 step_down, 做必要的 ctx 和 timer 更新
shutdown ESHUTDOWN Raft node is going to quit 自行调用 shutdown 时下台
handle_timeout_now EHIGHERTERMREQUEST Raft node receives higher term request 接收到其他节点的立即超时 rpc,且请求中 term 大于自身 term,主动下台
on_error EBADNODE Raft node(leader or candidate) is in error leader 在 snapshot 操作和用户 fsm 发生错误时,主动下台。并设置为 ERROR 状态。(注:若 Follower 发生错误,也会停止 follower,转为 ERROR 状态)
vote_timeout ERAFTTIMEDOUT Fail to get quorum vote-granted 开启 FLAGS_raft_step_down_when_vote_timedout 时, vote 超时会自动下台。默认为 true。
handle_request_vote_response EHIGHERTERMRESPONSE Raft node receives higher term request_vote_response 处理 vote resp 时发现 term 更大
handle_pre_vote_response EHIGHERTERMRESPONSE Raft node receives higher term pre_vote_response 处理 pre-vote resp 时发现 term 更大
handle_request_vote_request EHIGHERTERMREQUEST Raft node receives higher term request_vote_request 接收 vote resp 时发现 term 更大
handle_request_vote_request EVOTEFORCANDIDATE Raft node votes for some candidate tep down to restart election_timer 接收 vote resp 时发现投票给了其他 candidate
handle_install_snapshot_request / handle_append_entries_request ENEWLEADER Raft node receives message from new leader with higher term. 或 Candidate receives message from new leader with the same term 或 Follower receives message from new leader with the same term 接收 append entries 和 install snapshot 时发现了更高 term 的 leader。可能是网络分区后更新的 leader 发过来的。
handle_append_entries_request / handle_append_entries_request ELEADERCONFLICT More than one leader in the same term 处理 append entries 时发现同一 term 有不同的 leader
rpc_returned / heartbeat_returned / timeout_now_returned EHIGHERTERMRESPONSE Leader receives higher term timeout_now_response(rpc/heartbeat) from peer:%s 回包发现产生了更新的 term,直接设置成更高的 term increase_term_to,下台
remove peer ELEADERREMOVED This node was removed 管理节点移除,且该节点为 leader
lease expired ERAFTTIMEDOUT Leader lease expired leader 令牌过期

当然,所有的状态流转都离不开 raft + 一些优化。了解所有可能的情况有助于我们应对复杂的网络分区故障。

Tips:
on_leader_stop 中有个 status 传入,err 中有很详细的原因解释,别忘了记录。

STATE_LEADER

根据源码,有 2 种情况会流转进入。

  1. node became leader
  2. transfer leader timeout
    • transfer leader 超时,如果 term 未改变且仍为 STATE_TRANSFERRING,会尝试调用 on_leader_start 并恢复 STATE_LEADER 状态

STATE_TRANSFERRING

调用 NodeImpl::transfer_leadership_to 后进入,直到成功选出新 leader 或者超时。

STATE_CANDIDATE

节点选举自己时 elect_self。

STATE_ERROR

一般是用户业务状态机报错时,或者 log/ load snapshot 发现存储有错误的时候,有以下枚举

enum ErrorType : int {
  ERROR_TYPE_NONE = 0,
  ERROR_TYPE_LOG = 1,
  ERROR_TYPE_STABLE = 2,
  ERROR_TYPE_SNAPSHOT = 3,
  ERROR_TYPE_STATE_MACHINE = 4
};

STATE_UNINITIALIZED, STATE_SHUTTING, STATE_SHUTDOWN

顾名思义

3 term

该部分即 raft 论文中 term。不再赘述。

4 configuration 相关: conf_index / peers / changing_conf / stage

示例输出:

conf_index: 279754448
peers: 127.0.0.1:30000:0 127.0.0.1:30001:0 127.0.0.1:30002:0
changing_conf: NO    stage: STAGE_NONE

configuration & peers 管理接口

我们先来明确 Configuration 这个术语: 即 raft 组的所有节点集合。

// A set of peers.
class Configuration {
    ......
    std::set<PeerId> _peers;

};

而整个 raft 组的 config,是以 raft 日志的形式存储、也要以日志的形式 apply。我们进行 raft 节点管理和变更时,也需要取得 raft 组的共识。源码可以看到,ConfigurationEntry 实际上也是一个 LogEntry。

struct ConfigurationEntry {
    LogId id;
    Configuration conf;
    Configuration old_conf;
    ......
    // ConfigurationEntry 实际上也是一个 LogEntry
    ConfigurationEntry(const LogEntry& entry) {
        id = entry.id;
        conf = *(entry.peers);
        if (entry.old_peers) {
            old_conf = *(entry.old_peers);
        }
    }
    ......
};

影响到 conf 的变更,主要是 node 管理接口的调用。关于节点变更,官方文档解释得非常详细且权威。应用开发者提供运维文档时非常有必要参考。
braft 官方文档 - server 篇

以下接口可以对正常的 raft 组执行变更。

// Add a new peer to the raft group. done->Run() would be invoked after this
// operation finishes, describing the detailed result.
void add_peer(const PeerId& peer, Closure* done);

// Remove the peer from the raft group. done->Run() would be invoked after
// this operation finishes, describing the detailed result.
void remove_peer(const PeerId& peer, Closure* done);

// Gracefully change the configuration of the raft group to |new_peers| , done->Run()
// would be invoked after this operation finishes, describing the detailed
// result.
void change_peers(const Configuration& new_peers, Closure* done);

以下接口变更是强制性的,可能会破坏数据一致性。仅在紧急时飞线使用。

// Reset the configuration of this node individually, without any repliation
// to other peers before this node beomes the leader. This function is
// supposed to be inovoked when the majority of the replication group are
// dead and you'd like to revive the service in the consideration of
// availability.
// Notice that neither consistency nor consensus are guaranteed in this
// case, BE CAREFULE when dealing with this method.
butil::Status reset_peers(const Configuration& new_peers);

chaning_conf / stage

此两项状态只会在节点为 leader 时输出。config stage 指的是节点 conf 变换的各个阶段,枚举如下。除了 STAGE_NONE,都视为 changing_conf=true。

enum Stage {
    // Don't change the order if you are not sure about the usage
    STAGE_NONE = 0,
    STAGE_CATCHING_UP = 1,
    STAGE_JOINT = 2,
    STAGE_STABLE = 3,
};

官方文档 [1] 对节点变更配置提供了接口和描述。
https://github.com/baidu/braft/blob/master/docs/cn/server.md

追赶阶段: 如果新的节点配置相对于当前有新增的一个或者多个节点,leader对应的Replicator, 向把最新的snapshot再这个这些中安装,然后开始同步之后的日志。等到所有的新节点数据都追的差不多,就开始进入一下一阶段。

追赶是为了避免新加入的节点数据和集群相差过远而影响集群的可用性. 并不会影响数据安全性.
在追赶阶段完成前, 只有leader知道这些新节点的存在,这个节点都不会被记入到集群的决策集合中,包括选主和日志提交的判定。追赶阶段任意节点失败,则这次节点变更就会被标记为失败。

联合选举阶段: leader会将旧节点配置和新节点配置写入Log, 在这个阶段之后直到下一个阶段之前,所有的选举和日志同步都需要在新老节点之间达到多数。 这里和标准算法有一点不同, 考虑到和之前实现的兼容性,如果这次只变更了一个节点, 则直接进入下一阶段。

新配置同步阶段: 当联合选举日志正式被新旧集群接受之后,leader将新节点配置写入log,之后所有的log和选举只需要在新集群中达成一致。 等待日志提交到新集群中的多数节点中之后, 正式完全节点变更。

清理阶段: leader会将多余的Replicator(如果有)关闭,特别如果当leader本身已经从节点配置中被移除,这时候leader会执行stepdown并且唤醒一个合适的节点触发选举。

值得注意的是,在追赶阶段,只有 leader 知道新节点的存在。

5 log_manager 相关 index

在整个 raft 组,index 是日志递增的序列号。通过 index 指标,我们可以明确当前的状态机情况,以及日志存储的范围。

raft_stat 的返回如下,由 log_manager 输出。

_log_manager->describe(os, use_html);
...
storage: [279752493, 279755268]
disk_index: 279755268
known_applied_index: 279755268
last_log_id: (index=279755268,term=12)

node status 结构体 index 包括:

LogManagerStatus log_manager_status;
_log_manager->get_status(&log_manager_status);
status->known_applied_index = log_manager_status.known_applied_index;
status->first_index = log_manager_status.first_index;
status->last_index = log_manager_status.last_index;
status->disk_index = log_manager_status.disk_index;

总结成表格如下

http 接口输出 node.state 结构体 注释
storage [first, last] first_index, last_index storage 指 raft log store。在我们初始化 node_options.log_uri 时指定。是当前日志存储的范围。该范围会随着 apply 和 snapshot 不断前进。包含内存和磁盘中的。
disk_index disk_index 磁盘上最新的 log。(log 可能在磁盘或者内存中)
known_applied_index known_applied_index 已知 apply 的 index
last_log_id 获取最新的 log id。注意,其会等待 disk 刷盘结束后才能获得。
last_index 最新的 log index,包括磁盘和内存中的。

6 ballot_box 相关 index

ballot_box 是用来管理投票的组件。

raft_stat 的返回如下,由 ballot_box 输出。

_ballot_box->describe(os, use_html);
...
os << "last_committed_index: " << _last_committed_index << newline;
os << "pending_index: " << _pending_index << newline;
os << "pending_queue_size: " << _pending_meta_queue.size() << newline;

node status 结构体对应内容

    BallotBoxStatus ballot_box_status;
    _ballot_box->get_status(&ballot_box_status);
    status->committed_index = ballot_box_status.committed_index;
    status->pending_index = ballot_box_status.pending_index;
    status->pending_queue_size = ballot_box_status.pending_queue_size;

对应的含义总结成表格:

http 接口输出 node.state 结构体 注释
last_committed_index last_committed_index 最新被 commit 的 log index
pending_index pending_index 通常为 last_committed_index + 1
pending_queue_size pending_queue_size 顾名思义 pending 队列长度

Q: apply 与 commit

在 Raft 论文 In Search of an Understandable Consensus Algorithm (Extended Version) 中,多次提到 commit 和 apply 两词。其中,commit (提交)或 committed (已提交)针对的是日志,即日志项被成功复制到集群中大多数节点后,日志项处于 committed 状态,;apply (应用)或 applied (已应用)针对的是状态机,即节点将日志项应用到状态机,真正改变节点变量的。[2]

7 state_machine 相关

由 FSMCaller 输出。

    _fsm_caller->describe(os, use_html);

在一个 node 中,执行各种回调实际上是由一个串行的 FSMCaller 执行的。
状态中的 state_machine 即标志着当前状态机的执行状态。

这也意味着,在我们应用层实现的各种回调中,一定是串行执行的。它们类似中断机制,一定不能长时间阻塞。一旦阻塞,将阻塞整个状态机的执行。

    switch (cur_task) {
    case IDLE:
        os << "Idle";
        break;
    case COMMITTED:
        os << "Applying log_index=" << applying_index;
        break;
    case SNAPSHOT_SAVE:
        os << "Saving snapshot";
        break;
    case SNAPSHOT_LOAD:
        os << "Loading snapshot";
        break;
    case ERROR:
        os << "Notifying error";
        break;
    case LEADER_STOP:
        os << "Notifying leader stop";
        break;
    case LEADER_START:
        os << "Notifying leader start";
        break;
    case START_FOLLOWING:
        os << "Notifying start following";
        break;
    case STOP_FOLLOWING:
        os << "Notifying stop following";
        break;
    }

8 snapshot 相关

http stat 输出

_snapshot_executor->describe(os, use_html);
...
os << "last_snapshot_index: " << last_snapshot_index << newline;
os << "last_snapshot_term: " << last_snapshot_term << newline;
if (m && is_loading_snapshot) {
    CHECK(!is_saving_snapshot);
    os << "snapshot_status: LOADING" << newline;
    os << "snapshot_from: " << request.uri() << newline;
    os << "snapshot_meta: " << meta.ShortDebugString();
} else if (m) {
    CHECK(!is_saving_snapshot);
    os << "snapshot_status: DOWNLOADING" << newline;
    os << "downloading_snapshot_from: " << request.uri() << newline;
    os << "downloading_snapshot_meta: " << request.meta().ShortDebugString();
} else if (is_saving_snapshot) {
    os << "snapshot_status: SAVING" << newline;
} else {
    os << "snapshot_status: IDLE" << newline;
}

node status

node status 未包含 snapshot 相关的监控

9 Replicator 相关

当 node 为 leader 时,可以在 http stat 中看到 replicator 状态。

源码的输出比较直观。

    os << "replicator_" << id << '@' << peer_id << ':';
    os << " next_index=" << next_index << ' ';
    os << " flying_append_entries_size=" << flying_append_entries_size << ' ';
    if (readonly_index != 0) {
        os << " readonly_index=" << readonly_index << ' ';
    }
    switch (st.st) {
    case IDLE:
        os << "idle";
        break;
    case BLOCKING:
        os << "blocking consecutive_error_times=" << consecutive_error_times;
        break;
    case APPENDING_ENTRIES:
        os << "appending [" << st.first_log_index << ", " << st.last_log_index << ']';
        break;
    case INSTALLING_SNAPSHOT:
        os << "installing snapshot {" << st.last_log_included
           << ", " << st.last_term_included  << '}';
        break;
    }
    os << " hc=" << heartbeat_counter << " ac=" << append_entries_counter << " ic=" << install_snapshot_counter << new_line;

小结

本文从一个使用者视角,通过源码总结了 braft 的观测指标。同时以它们为线索,探索了 raft 的一些基本原理和术语。

参考
[1] braft 文档 - https://github.com/baidu/braft/blob/master/docs/cn/server.md
[2] Raft 共识算法学习笔记 二:日志复制 - https://leehao.me/Raft-%E5%85%B1%E8%AF%86%E7%AE%97%E6%B3%95%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0-%E4%BA%8C%EF%BC%9A%E6%97%A5%E5%BF%97%E5%A4%8D%E5%88%B6/

相关

标签: braft raft 共识算法 分布式 学习笔记
最后更新:2024年5月12日

SPtuan

SPtuan 是一名普通的工程师,最大的愿望是度过平静的时光。 当前从事网络/CDN/对象存储研发。

点赞
< 上一篇
下一篇 >
0 0 votes
文章评分
Subscribe
Login
提醒
guest

guest

2 评论
最新
最旧 得票最多
Inline Feedbacks
View all comments
hust
hust
9 月 之前

赞

1
回复
buhen
buhen
9 月 之前

ping

0
回复

SPtuan

SPtuan 是一名普通的工程师,最大的愿望是度过平静的时光。
当前从事网络/CDN/对象存储研发。

  • 0 背景
  • 1 概览
  • 2 state
    • STATE_FOLLOWER
    • STATE_LEADER
    • STATE_TRANSFERRING
    • STATE_CANDIDATE
    • STATE_ERROR
    • STATE_UNINITIALIZED, STATE_SHUTTING, STATE_SHUTDOWN
  • 3 term
  • 4 configuration 相关: conf_index / peers / changing_conf / stage
    • configuration & peers 管理接口
    • chaning_conf / stage
  • 5 log_manager 相关 index
  • 6 ballot_box 相关 index
    • Q: apply 与 commit
  • 7 state_machine 相关
  • 8 snapshot 相关
  • 9 Replicator 相关
  • 小结
分类
  • Uncategorized
  • 图册
  • 学习笔记
  • 库
  • 折腾
  • 杂谈
  • 瞎**扯
  • 碎碎念
  • 项目跟踪
最近评论
SPtuan 发布于 2 个月前(03月22日) 书签: 关于 disk-io 的经验, 异步/同步 io 系统设计的经验 https://you...
SPtuan 发布于 2 个月前(03月21日) 如果公司不是对外提供这些服务的,这种岗位都是 infra 部门,平均年龄确实会大一些。尤其构建和维护...
HUA 发布于 2 个月前(03月19日) 想请问博主对于国内CDN行业,以及CDN调度、DNS托管类服务相关岗位的看法,以及是否还推荐校招新人...
SPtuan 发布于 3 个月前(02月03日) 2025 注: 长辈对于只身去深圳的担忧,更多地来自于 80s/90s 治安情况。近几年了解了严打...
SPtuan 发布于 4 个月前(01月16日) 哈哈,100就100吧,新年快乐!
热门主题 & 页面
  • 全球互联网拓扑探索 (1) : 互联网是如何工作的
  • PYNQ上手体验:以目标检测应用为例
  • 使用 WSL2 + X11 转发 - 在 Windows10 中打造 GNU/Linux 学习生产环境
  • [实验]VPS搭建ss服务中转实现纯ipv6访问网络-校园网免流量
  • Intel Movidius Neural Compute Stick - 英特尔Movidius神经计算棒上手体验
归档
  • 2025 年 5 月
  • 2025 年 3 月
  • 2024 年 12 月
  • 2024 年 9 月
  • 2024 年 8 月
  • 2024 年 5 月
  • 2024 年 3 月
  • 2024 年 2 月
  • 2023 年 12 月
  • 2023 年 11 月
  • 2023 年 9 月
  • 2023 年 8 月
  • 2023 年 4 月
  • 2023 年 1 月
  • 2022 年 12 月
  • 2022 年 10 月
  • 2022 年 9 月
  • 2022 年 7 月
  • 2022 年 6 月
  • 2022 年 2 月
  • 2021 年 12 月
  • 2021 年 11 月
  • 2021 年 2 月
  • 2021 年 1 月
  • 2020 年 9 月
  • 2020 年 4 月
  • 2020 年 3 月
  • 2020 年 1 月
  • 2019 年 8 月
  • 2019 年 7 月
  • 2019 年 5 月
  • 2019 年 4 月
  • 2019 年 3 月
  • 2019 年 2 月
  • 2018 年 12 月
  • 2018 年 10 月
  • 2018 年 9 月
  • 2018 年 8 月
  • 2018 年 5 月
  • 2018 年 2 月
  • 2018 年 1 月
  • 2017 年 11 月
  • 2017 年 9 月
  • 2017 年 7 月
  • 2017 年 6 月
  • 2017 年 5 月
  • 2017 年 4 月
  • 2017 年 3 月
  • 2017 年 2 月
  • 2017 年 1 月
  • 2016 年 12 月
  • 2016 年 11 月
  • 2016 年 10 月
  • 2016 年 9 月
  • 2016 年 8 月
  • 2016 年 7 月
  • 2016 年 6 月
  • 2016 年 5 月
  • 2016 年 4 月
  • 2016 年 3 月
  • 2016 年 2 月
  • 2016 年 1 月
  • 2015 年 12 月
  • 2015 年 11 月
  • 2015 年 9 月

友情链接:

Blessing Studio hahaschool 绘枫和畅 魔法少女Fandy monsterx Clarke的博客 Luminous’ Home Shintaku's Blog
蓝黑的博客 haruhi.club Yida的博客 Bo2SS 涛叔 TangBao 同和君Hocassian

Steins;Lab 团子神社 zdfmc.net

steinslab.io built with ❤. Thanks for all 2015-2025.

Theme Kratos Made By Seaton Jiang

wpDiscuz