文章

狂写了几天PBFT。

虽然再写一遍PBFT是所有编程工作中最没意义的一种了,但是在当下这个很久没动手写过代码,也丧失了很多能写出代码的信心的节骨眼,写一个让自己满意的BFT协议实验平台大概是恢复信心最好的选择。

打破心魔还是有必要的,就算已经焦头烂额了。

不过我也不敢说这次写的代码就比以往所有设计都要好。这次的代码至今都没有什么大规模的范型,让我感到有点奇怪。但是我已经不会因为这个而怀疑自己了。

我已经在「不去纠结是否在以最泛用的方式写代码」上面取得了很大的进步,在我自己都不知道是什么时候。

在实现网络层的时候,我写了很多直接操作TCP连接的代码,然后提取出一读一写两段通用逻辑。其中写的那段长这样

1
2
3
4
5
6
7
8
9
10
11
async fn write_message<W: AsyncWrite + Unpin>(
    message: impl Encode,
    egresses: impl IntoIterator<Item = W>,
    encode_bytes: &mut [u8],
) -> anyhow::Result<()> {
    let len = bincode::encode_into_slice(message, encode_bytes, bincode::config::standard())?;
    for mut egress in egresses {
        egress.write_all(&encode_bytes[..len]).await?
    }
    Ok(())
}

好像是这个日志网站第一次贴正经代码段落。

一段虽然有三个范型参数但是根据使用场景非常定制化的逻辑。如果让我提前想清楚设计再写,带一个encode_bytes缓冲区参数或是W上挂一个Unpin约束大概无论如何也不会被认为是最佳实践。然而,如果先写出了所有的使用例,再从重复代码段落中提取公因数,那么把可复用段落设计成这样就变得很显然了。

另外,这段能发送任意impl Encode的逻辑,现在还在PBFT的网络模块里。我先做了一个只能支持PBFT的网络模块,这在以往是很难想象的。然而,这也使得我在实现PBFT的过程中可以随心所欲的定制化运行时功能,而不用试图把协议乱七八糟的行为都塞进一套规规矩矩但是用起来很不方便的「通用」运行时接口里去。我会在接下来写HotStuff的时候重复PBFT的很多代码,然后再进行一次更高层次、更大规模的抽取可复用段落,以此类推。

我愿称之为汉诺塔开发模式。


写了几天以后终于想歇一下了。继续看论文吧。

Aksoy, Remzi Can, and Manos Kapritsos. “Aegean: replication beyond the client-server model.” Proceedings of the 27th ACM Symposium on Operating Systems Principles. 2019.

在微服务的语境下探讨复制协议。状态机复制的状态转移不再是一个本地副作用而是可能会作为复制的客户端发起请求,制造外部副作用。很有趣的问题设定,但是总觉得有更简单的办法。

本文由作者按照 CC BY 4.0 进行授权