redis usage

登录

1
$ redis-cli -h <host> -p <port>

查询

集群信息

1
2
3
4
5
6
# 集群信息
> cluster info
# 节点信息
> cluster nodes
# 内存信息
> info memory

导出数据

redis-cli

redis-shake

1
2
./redis-shake.linux -type=dump -conf=redis-shake.conf

redis-shake.conf 配置参考这里

搭建集群

1
2
3
4
5
6
nohup redis-server --cluster-enabled yes --port 6379 --cluster-config-file nodes1.conf &
nohup redis-server --cluster-enabled yes --port 6378 --cluster-config-file nodes2.conf &
nohup redis-server --cluster-enabled yes --port 6377 --cluster-config-file nodes3.conf &

sleep 3 # 等服务节点起来
redis-cli --cluster create 127.0.0.1:6379 127.0.0.1:6378 127.0.0.1:6377

nodes1.conf、nodes1.conf、nodes1.conf 可以不存在,运行后 redis 自动创建。不同的 redis 实例,需要绑定不同的配置文件,默认为 node.conf。

c++ 最佳实践

工程

工具

网络

服务治理

配置中心

定义全局变量

1
2
3
4
5
6
7
8
9
// global.h
namespace {
extern int i;
}

// global.cpp
namespace {
int i = 1024;
}

随机数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// random_util.h
#include "random"
#include "climits"
namespace utils {
extern std::random_device gRandomDev;
extern std::mt19937 gRandomGenerator;
extern std::uniform_int_distribution<std::mt19937::result_type> gRandomDist; // (0, INT_MAX)

uint32_t RandomInt(const int &min = 0, const int &max = 0);
}

// random_util.cpp
#include "random_util.h"
namespace ranker::utils {
std::random_device gRandomDev;
std::mt19937 gRandomGenerator(gRandomDev());
std::uniform_int_distribution<std::mt19937::result_type> gRandomDist(0, INT_MAX);

uint32_t RandomInt(const int &min, const int &max) {
if (min == max && min == 0) {
return (uint32_t) gRandomDist(gRandomGenerator);
} else {
std::uniform_int_distribution<std::mt19937::result_type> dist(min, max);
return (uint32_t) dist(gRandomGenerator);
}
}
}

/*
满足泊松分布的随机数
std::poisson_distribution<> d{/*mean=*/4};
d(gRandomGenerator);
*/

c++ - holes

容易 Core Dump 的情形

容器重新分配内存导致的非法内存访问

1
2
3
4
5
6
7
// 定义结构
struct Biz {...};
// 定义容器
std::vector<Biz> bizes;
bizes.emplace_back(); // 添加一个元素
auto p_biz = &bizes.back(); // 保存指针
bizes.emplace_back(); // 添加一个元素. 该操作可能会使指针 p_biz 失效, 因为内存重新分配

模板

类似需要在编译期展开的代码实现,不能放在 cpp 文件中,而应该放在 header 中

静态变量初始化

1
2
3
4
5
6
7
8
// s.h
#ifndef S_H
#define S_H
class S {
static int v;
};
int S::v = 0;
#endif

上面的代码在链接时会报错。声明保护(gragma once 或使用 ifndef 判断)可以解决单个编译单元(单个源文件)的重复声明错误,但不能避免多个编译单元在链接时的重复定义错误。要解决这个问题,需要把静态变量的声明和初始化放在不同的文件里面。

1
2
3
4
5
6
7
8
9
10
// s.h
#ifndef S_H
#define S_H
class S {
static int v;
};
#endif

// s.cpp
int S::v = 0;

undefined reference to

  • 未连接库

  • 编译库的编译器版本不一致,ABI 不兼容

    • 尤其注意,多个库之间使用的 gcc/++ 不一致
  • find_package 的顺序,在某些情况下也有可能会导致这个问题

    • 被依赖先调用 find_package

编译 C 库使用 C++ 语法

1
gcc -lstdc++ code.c

c++ - perf

工具

gprof、perf、gperftools、valgrind。

Perf

perf 工具默认生成记录文件 perf.data,读写路径是 pwd。

安装

1
2
3
4
5
# ubuntu
sudo apt install linux-tools -y

# centos
sudo yum install perf -y

工具

top

系统性能分析工具。

参数

1
2
-e 指定 event,多个 event 用 , 分隔
-s 指定分类聚合列, 默认是函数,还有 comm(命令)、pid 等

使用

1
2
3
4
# 所有进程
$ perf top
# 指定进程
$ perf top -p <pid>

stat

运行命令 / 指定 pid,并收集性能统计数据。

record

运行命令 / 指定 pid,并收集分析数据至 perf.data。

1
2
3
4
5
# 指定 pid
$ pid=$(pgrep <program-name>)

# 查看报告
$ perf report --show-total-period

report

读取 perf.data,展示分析结果。

1
2
3
4
5
$ perf report 

# 参数
--show-total-period 展示总耗时列
-a 统计系统范围内数据

diff

对比两个 perf.data 内容的区别,使用场景比如对比改动前后的变化。

archive

用来打包 perf.data 中使用到的环境数据,用于离线分析。

1
2
3
4
5
6
# 打包
$ perf archieve

# 分析
$ tar xvf perf.data.tar.bz2 -C ~/.debug
$

使用

1
2
3
4
5
6
7
8
9
10
11
12
# 查看系统全部耗时
$ perf top

# pid
$ pid=$(pgrep <program-name>)

# 记录数据
$ perf record -e cpu-clock -F 99 -p $pid -g -- sleep 10 # 记录 10 秒数据

# 展示记录数据的分析结果
$ perf report --show-total-period
$ perf report --show-total-period$ -i <perf.data> # 指定记录文件

火焰图

使用 这里 生成火焰图。

1
2
3
4
5
# 下载工具
$ git clone https://github.com/brendangregg/FlameGraph

# 生成火焰图
$ sudo perf script | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

参考

问题

1

性能分析

指标

  • 通量(Throughput,或吞吐量)
  • 时延(Latency)

通量

性能瓶颈排查

  • 处理器占用率

    • 使用 top 命令查看
    • 如果处理器占用率未满,排查网络、任务调度、IO 空洞问题
    • 如果处理器占满,使用 perf、ftrace 等工具进一步排查
  • 流控(网卡)

    • 查看网卡丢包,可以通过增加缓冲区降低丢包率

      1
      2
      3
      4
      5
      6
      7
      8
      wlan0     Link encap:以太网  硬件地址 7c:7a:91:xx:xx:xx  
      inet 地址:192.168.0.103 广播:192.168.0.255 掩码:255.255.255.0
      inet6 地址: fe80::7e7a:91ff:fefe:5a1a/64 Scope:Link
      UP BROADCAST RUNNING MULTICAST MTU:1500 跃点数:1
      接收数据包:113832 错误:0 丢弃:0 过载:0 帧数:0
      发送数据包:81183 错误:0 丢弃:0 过载:0 载波:0
      碰撞:0 发送队列长度:1000
      接收字节:47850861 (47.8 MB) 发送字节:18914031 (18.9 MB)
  • 任务调度缺陷

    • 线程数远小于核心数,不能充分利用多核
  • IO 空洞

    • 同步等待 IO 操作,可以通过异步解决

参考

性能分析 - dstat

安装

1
2
# centos
sudo yum install -y dstat

命令

1
$ dstat

参数

默认参数为 -cdngy

1
2
3
4
5
6
-c cpu 信息
-C cpu 信息,指定核心,如 -C 0,2,4
-d disk 信息
-n net 信息
-g page 信息
-y system 信息

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# cpu 相关
usr 用户消耗 cpu 时间占比
sys 系统消耗 cpu 时间占比
idl cpu 空闲时间占比
wai io 等待消耗的 cpu 占比
hiq 硬中断
siq 软中断

# system 相关
int 中断次数
csw 上下文切换次数

# disk
read 读取磁盘总数
write 写入磁盘总数

# net
recv 网络设备结束数据总数
send 网络设备发送数据总数

# page 相关(系统分页活动)
in 换入次数
out 换出次数

性能分析 - vmstat

安装

1
# centos

命令

1
$ vmstat <period> <count>

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
r 运行队列,分配到 CPU 的进程数
b 堵塞进程数
swpd 使用虚拟内存
free 空闲物理内存
buff 缓冲占用内存
cache 缓存占用内存
si 每秒从磁盘读取虚拟内存大小
so 每秒写入磁盘虚拟内存大小
bi 每秒从块设备接受块数量
bo 每秒发送的块数量
in 每秒 cpu 中断次数,包括时间中断
cs 每秒上下文切换次数
us 用户 cpu 时间
sy 系统 cpu 时间
id 空闲 cpu 时间
wa 等待 io cpu 时间
st 从虚拟机窃取时间

c++ - segmentation fault

原因

  • 访问空指针
  • 访问不可访问的内存
    • 访问不可访问的变量 / 已经不在可访问作用域的变量,即便 gdb 调试可以展示变量值

案例

1
2
3
4
5
6
# 1. 访问已经回收的临时变量
使用 seastar 时,调用返回 future 方法的方法,参数包含局部变量。在执行 then 回调时,局部变量已经不可访问(使用 gdb 调试仍可展示数值)。
解决方法是使用 seastar::do_with(std::move(v), [](V &v) { ... })

# 2. 未加 return 值导致 std::function 析构
定义的方法返回值类型不为 void,但是没有 return 语句,定义 std::function 对象,在析构时报 segmentation fault

Lambda

shared_ptr

shared_ptr 引用导致的内存非法访问

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A {
public:
int a;
};

std::function<void()> fun() {
std::shared_ptr<A> sa = std::make_shared<A>();
sa->a = 2;
auto f = [&] {
std::cout << "sa-> " << sa->a << std::endl;
sa->a = 1;
};
return f;
}

void test() {
std::cout << "----- in test -----" << std::endl;
fun()();
std::cout << "----- out test -----" << std::endl;
}

调用

1
2
3
4
int main() {
test();
return 0;
}

输出

1
2
3
4
----- in test -----
sa-> 281314120

Process finished with exit code 138 (interrupted by signal 10: SIGBUS)

原因

lambda 表达式使用引用访问局部变量,在 fun() 返回 lambda 表达式对象时,局部变量 sa 已被释放,在 test 中执行 lambda 表达式时会访问已被释放的内存。

解决

使用拷贝替换引用,增加引用计数。

1
2
3
4
5
6
7
8
9
std::function<void()> fun() {
std::shared_ptr<A> sa = std::make_shared<A>();
sa->a = 2;
auto f = [&, sa] {
std::cout << "sa-> " << sa->a << std::endl;
sa->a = 1;
};
return f;
}

输出

1
2
3
4
5
----- in test -----
sa-> 2
----- out test -----

Process finished with exit code 0