blog review 第七期
看tag知内容
一点私货:
以前格局小了,服务的概念很单一
上一次被启发是达哥的想法,rocksdb的compaction剥离出去由另一个服务去做。很印象深刻。这个想法的前提是文件底层是虚拟的文件系统(hdfs之类),可以做这种hack
最近看公司里的各种服务组件,发现和之前的做法不太一样
比如备份,以前的服务非常简单,就是db进程/对象存储/管控平台三方面交互
备份的速度,备份对当前服务的影响,都是不可观测的。完全不能动态可控的。db服务提供一个打快照的接口,文件准备好,随便管控平台来取,然后传到对象存储里
公司里的设计是引入一个backup服务夹在管控平台和对象存储之间
backup感知db服务的状态,另外无法拿磁盘的文件,需要复制,所以db服务提供拷贝接口,这也是没有用户态文件系统的缺陷吧
之前做数据迁移,是有个部门专门转协议接入的。各种解析然后转然后重新写,这种效率是非常低下的。原来是做redis的毕竟大部分在内存,可能没有dump完整的rdb,所以迁移还是在线形式的
现在做迁移工具不是一个部门,是一个组件,留管控面的接入之后,直接传二进制,一般来说,rocksdb的文件,直接覆盖就完事了。如果是别的文件,通过对象存储中转一下,改写成rocksdb的文件,然后再倒入。这样的效率要比转上层协议的导入要快的多,如果是同类集群的迁移,这个逻辑也能完美复用。原来的做法是做个全备,然后再加载。类似上面的backup服务
brpc的bvar是个很有意思的东西,简单的采集metric信息
TCP Fast Open? Not so fast
这有个很久很久的例子https://github.com/yuryu/tfoecho
注意,这里客户端用的是sendto,没用connect,在后面的修改 中加了TCP_FASTOPEN_CONNECT
所以说,上面这个tfoecho例子应该过时了。TODO:改写成新版
除了一些基本的socket配置TCP_FAST_OPEN,以及系统配置 net.ipv4.tcp.fastopen 之外
linux对于TFO比较保守,会有超时判定,所以要把net.ipv4.tcp_fastopen_blackhole_timeout_sec
sysctl 设置成 0.
minikeyvalue
用go实现的http kv todo:用c++重写
The Cache Replacement Problem
实现了各种cache并且压测,代码在这里,后面有机会用c++重写一下
其实现代的cache主要是抗scan污染,比如arc
这里有个arc cache的实现
Write a time-series database engine from scratch
https://github.com/nakabonne/tstorage
todo
Dropping cache didn’t drop cache
博主发现了内核最新的bug,一个多线程变量变动的问题,从cache释放入手,找到对应代码。挺细的
用 litmus 验证 x86 内存序
简单安装herdtools
#brew install opam
#opam init
opam install herdtools7
eval $(opam config env) # 执行这个加载到path不然找不到
具体操作看这里就行了http://diy.inria.fr/doc/litmus.html
高性能队列——Disruptor
几个c++实现
https://github.com/fsaintjacques/disruptor–/
https://github.com/Abc-Arbitrage/Disruptor-cpp
原理
https://leiyiming.com/2017/11/01/disruptor/
Tales From a Core File - Lessons from the Unix stdio ABI: 40 Years Later
讲了一些可以学习的细节,作者也是写os的
- APIs and ABIs api设计统一,结构体布局兼容保持abi
- The History of stdio 比如FILE这种不透明指针设计,以及padding的使用,避免false sharing 以及改动数组会破坏ABI,慎重
Concurrent programming: Two techniques to avoid shared state
就是拷贝修改和异步更新,没啥东西
Why NOT to Build a Time-Series Database
他们自己用redis和riak搭了个时序数据库,后来放弃了,很大的维护负担,还需要做迁移维护(S3等)
Dijkstra’s in Disguise
很多场景都是Dijkstra算法等延伸 图算法,Q-learning Physically-Based Rendering等等
A Flexible Reflection System in C++: Part 1
宏实现反射,具体原理就是注册结构信息
struct Node {
std::string key;
int value;
std::vector<Node> children;
REFLECT() // Enable reflection for this type
};
///
struct TypeDescriptor_Struct : TypeDescriptor {
struct Member {
const char* name;
size_t offset;
TypeDescriptor* type;
};
std::vector<Member> members;
TypeDescriptor_Struct(void (*init)(TypeDescriptor_Struct*)) : TypeDescriptor{nullptr, 0} {
init(this);
}
TypeDescriptor_Struct(const char* name, size_t size, const std::initializer_list<Member>& init) : TypeDescriptor{nullptr, 0}, members{init} {
}
virtual void dump(const void* obj, int indentLevel) const override {
std::cout << name << " {" << std::endl;
for (const Member& member : members) {
std::cout << std::string(4 * (indentLevel + 1), ' ') << member.name << " = ";
member.type->dump((char*) obj + member.offset, indentLevel + 1);
std::cout << std::endl;
}
std::cout << std::string(4 * indentLevel, ' ') << "}";
}
};
#define REFLECT() \
friend struct reflect::DefaultResolver; \
static reflect::TypeDescriptor_Struct Reflection; \
static void initReflection(reflect::TypeDescriptor_Struct*);
一个sparse set实现
#pragma once
#include <vector>
#include <type_traits>
template <typename T>
class SparseSet
{
static_assert(std::is_unsigned<T>::value, "SparseSet can only contain unsigned integers");
private:
std::vector<T> dense; //Dense set of elements
std::vector<T> sparse; //Map of elements to dense set indices
size_t size_ = 0; //Current size (number of elements)
size_t capacity_ = 0; //Current capacity (maximum value + 1)
public:
using iterator = typename std::vector<T>::const_iterator;
using const_iterator = typename std::vector<T>::const_iterator;
iterator begin() { return dense.begin(); }
const_iterator begin() const { return dense.begin(); }
iterator end() { return dense.begin() + size_; }
const_iterator end() const { return dense.begin() + size_; }
size_t size() const { return size_; }
size_t capacity() const { return capacity_; }
bool empty() const { return size_ == 0; }
void clear() { size_ = 0; }
void reserve(size_t u)
{
if (u > capacity_)
{
dense.resize(u, 0);
sparse.resize(u, 0);
capacity_ = u;
}
}
bool has(const T &val) const
{
return val < capacity_ &&
sparse[val] < size_ &&
dense[sparse[val]] == val;
}
void insert(const T &val)
{
if (!has(val))
{
if (val >= capacity_)
reserve(val + 1);
dense[size_] = val;
sparse[val] = size_;
++size_;
}
}
void erase(const T &val)
{
if (has(val))
{
dense[sparse[val]] = dense[size_ - 1];
sparse[dense[size_ - 1]] = sparse[val];
--size_;
}
}
};
这有个rust实现 https://github.com/bombela/sparseset
特定场景,key巨量不可接受,序列化其实保存成二进制其实还好
Memoize Commands or Bash Functions with Coprocs!
其实就是匿名管道小妙招,这种写法有点像go里的channel
这个命令bash4才支持,也就是2009年才支持, 比较新的linux应该都是4.2,应该是都支持的
#!/bin/bash
function rando-daemon {
declare -A cache
declare -a query
local IFS=$'\t'
while read -ra query; do
if ! [[ -v "cache[${query[*]}]" ]]; then
cache[${query[*]}]=$RANDOM
fi
printf '%s\n' "${cache[${query[*]}]}"
done
}
rando() {
local IFS=$'\t'
local resp
printf '%s\n' "$*" >&"${RANDO[1]}"
read -r resp <&"${RANDO[0]}"
printf '%s\n' "$resp"
}
coproc RANDO { rando-daemon; }
printf "%s\n" "$(rando butter bubbles)"
printf "%s\n" "$(rando butter bubbles)"
要留意coproc这个命令,对于记忆话shell变量会有点帮助,或者用到worker 模型会有点作用,或者cs模型,channel模型
这里引一下用法
COPROC基本语法
COPROC的基本语法适用于当只需要有一个coprocess的情况,它的语法为
coproc cmd [redirections]
这时与coprocess的输入/输出管道相连的句柄保存在
$COPROC
数组中,分别为${COPROC[1]}
和${COPROC[0]}
因此,你可以使用
echo $data >&"${COPROC[1]}"
来往coprocess中输入数据, 通过使用echo $data <&"${COPROC[0]}"
来读取coprocess的输出数据。COPROC的扩展语法
当需要创建多个coprocess时,你就需要使用coproc的扩展语法了,因为它允许你为coprocess命名。
coproc NAME {cmds} [redirections]
有没有觉得它跟bash中定义函数的语法
function NAME {cmds}
很类似?这时与coprocess的输入/输出管道相连的句柄保存在
$NAME
数组中,分别为${NAME[1]}
和${NAME[0]}
对应的,你可以使用
echo $data >&"${NAME[1]}"
来往coprocess中输入数据, 通过使用echo $data <&"${NAME[0]}"
来读取coprocess的输出数据。关闭不需要的管道
若coprocess只需要输入句柄或输出句柄,则可以使用
exec
来关闭不需要的文件句柄exec {NAME[0]}>&- exec {NAME[1]}>&-
注意事项
使用coprocess虽然方便,但要当心coprocess由于输出缓存而导致的卡死。
比如下面这个例子
coproc tr a b echo a >&"${COPROC[1]}" read var<&"${COPROC[0]}"
你的期望是第三句能够读出字符
b
作为var
的值, 然而实际上执行到第三句话时会卡死。 这是因为tr
命令缓存了输出的内容,而并未将其写到终端上来。因此创建coprocess时一定小心,只能使用那些不会缓存输出的命令。 也正因为此,coprocess的使用范围其实也很受限,真要用来跟其他进程做交互的话,还是推荐使用
expert
比较好。
Safer Bash: no unset
一个场景
set -e
MY_APP_FILES=/tmp/my-app
rm -rf $My_APP_FILES/
这里有个拼写错误,导致rm -rf /
了, set -e
没用,set -eu
能抓到这种错误提前退出,所以写脚本第二行放上set -eu
是个好习惯
Scaling Memcache at Facebook
facebook是如何管理memcache集群的
cache的优化策略
客户端角度
- 客户端路由
- 数据DAG,无依赖
- UDP
- 滑动窗口发送请求,进程级别,降低压力
服务端角度
- lease,降低热 key被反复修改造成cache失效压力
- cache不立即失效,存一回失效key,扛住nosuchkey压力
- cache池,区分频繁的和不频繁但重要的。LRU/LFU并不能代表业务的请求特征,不同的cache策略划分不同的组
- 压力太大就扩容,复制读副本,降低压力
- cache备机,提高抗压能力,及时剔除挂掉的节点
- cache集群,分组,提高抗压能力
- 如何整组的更新过期key,引入同步服务
- 聚合,效果更好 消息服务了属于是
- 没有版本很容易数据不对劲
- 集群间的一致性问题?
- 如何整组的更新过期key,引入同步服务
- 如何快速升热?
- cache组之间复制
- 单机,共享内存
https://fuzhe1989.github.io/2022/09/26/scaling-memcache-at-facebook/
降低延迟
mrouter接入+udp转发到memcache 降低tcp cost
memcache 客户端加上slide window 如果有大量的请求,超出的放到queue里,降低竞争进而降低延迟
降低压力
引入Lease 解决过期以及惊群,这里的鲸群指的是大量请求请求冷key,对请求加上lease,超过期限直接失败,让客户端重试
还有就是集群级别的管理了故障恢复之类的
A Deep dive into (implicit) Thread Local Storage
BetrFS: A Right-Optimized Write-Optimized File System
代码在这里https://github.com/oscarlab/betrfs
Analyzing Optimistic Concurrency Control Anomalies and Solutions
待读
https://danilafe.com/blog/00_compiler_intro/ 用c++写函数式语言
https://github.com/VictoriaMetrics/VictoriaMetrics 高性能的时序数据库,技术和clickhouse差不多
https://github.com/DigitalChinaOpenSource/TiDB-for-PostgreSQL
https://github.com/kelindar/column column存储,go写的
https://github.com/baidu/braft.git braft的文档值得读一下
braft todo
braft
https://wine93.gitbook.io/learn-raft/ch06/change_leader
curve https://my.oschina.net/u/4565392/blog/5519430 https://zhuanlan.zhihu.com/p/333884273 https://zhuanlan.zhihu.com/p/505366980 https://zhuanlan.zhihu.com/p/336674195 https://yriuns.github.io/2022/01/08/braft-in-action/
https://zhuanlan.zhihu.com/p/169840204
https://zhuanlan.zhihu.com/p/169904153
把braft 知乎文章看一遍
https://github.sheincorp.cn/baidu/braft/issues/ 332 有bug?
https://steinslab.io/archives/2605 metric https://luobuda.github.io/2023/01/07/Work-Review/ https://luobuda.github.io/2022/02/15/braft-snapshot%E5%AE%9E%E7%8E%B0/ https://blog.csdn.net/qq_35102066/article/details/122832550 问题,如果关闭周期性snapshot,什么时候truncate日志呢?无限膨胀?
https://zhuanlan.zhihu.com/p/690232462 https://zhuanlan.zhihu.com/p/639480562 https://github.com//kasshu/braft-docs/blob/master/cn/Braft%20detailed%20explanation.md
https://illx10000.github.io/2018/12/29/6.html brpc iobuf
https://github.com/afiodorov/radixmmap 用c++实现一下
https://github.com/stateright/stateright 一致性教研工具,原理是什么?