0%

性能测试的重要性

因为最近用到的性能测试工具比较多,例如fio, wrk。所以难免有一些感慨。
计算机是一个严格的科学工具,里边没有黑魔法,里边所有的东西都是可以测量的。什么是科学?科学具有可证伪性,可检验性。有个有趣的段子:

1
2
你要是推翻西医里的理论,那是可以拿诺奖的。
你要是推翻中医里的理论。那后果...

回忆之前在课本里学到了很多概念,例如时间复杂度分析,学的时候觉得很枯燥,彷佛用不上。平时自己写的小程序,在自己的个人电脑上也都是瞬间执行结束的。根本体会不到时间复杂度的重要性。直到我在leetcode上遇到1G的数据量时,我才真正的体会到了时间复杂度的重要。

1
2
3
4
>>> log(10**9,2)
29.897352853986263
>>> 10**9 / log(10**9,2)
33447777.29599791

不知道,你有没有对这个计算结果吃惊呢?
有的时候我就在想:是计算机太快了,绝大多数普通人是感受不到毫秒级的延迟的。当然更重要的是好多人也不会遇到这样的问题,毕竟能有百万并发的场景是少数大厂。既然遇不到这样的问题,那么所有的知识,就会变得枯燥。
Latency Numbers Every Programmer Should Know

anyway所有的东西都是可以测试的,可以通过数学去计算,度量的。去分析瓶颈在哪里,看Htop, 火焰图iostat,最后分析讨论该怎么优化。
记得用成熟的工具去测试,否则盲目的人肉测试是不科学的。

Http性能测试

无意中发现了一个非常simple的http-server, 还是那句话,http的本质是确定格式的文本读写。

1
wrk -t12 -c400 -d30s http://127.0.0.1:7890

结果

1
2
3
4
5
6
7
8
Running 30s test @ http://127.0.0.1:7890
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 800.64us 3.95ms 202.80ms 99.83%
Req/Sec 47.56k 5.41k 98.40k 89.10%
17068381 requests in 30.10s, 1.24GB read
Requests/sec: 567065.38
Transfer/sec: 42.18MB

Htop

对比一下之前的常用的Flask

1
2
3
4
5
6
7
(venv) [root@node1 demo-flask]# python demo.py
* Serving Flask app 'demo' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: off
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit
1
2
3
4
5
6
7
8
9
10
[root@node1 wrk]# ./wrk -t12 -c400 -d30s http://127.0.0.1:5000
Running 30s test @ http://127.0.0.1:5000
12 threads and 400 connections
Thread Stats Avg Stdev Max +/- Stdev
Latency 284.32ms 178.98ms 1.96s 81.05%
Req/Sec 71.11 42.46 292.00 73.87%
24828 requests in 30.03s, 3.93MB read
Socket errors: connect 0, read 0, write 0, timeout 131
Requests/sec: 826.80
Transfer/sec: 134.03KB

加上WSGI

  • 四进程
    1
    2
    3
    4
    5
    6
    7
    8
    (venv) [root@node1 demo-flask]# gunicorn -w 4 -k gevent demo:app -b :7889
    [2024-03-13 08:49:25 +0000] [220624] [INFO] Starting gunicorn 21.2.0
    [2024-03-13 08:49:25 +0000] [220624] [INFO] Listening at: http://0.0.0.0:7889 (220624)
    [2024-03-13 08:49:25 +0000] [220624] [INFO] Using worker: gevent
    [2024-03-13 08:49:25 +0000] [220627] [INFO] Booting worker with pid: 220627
    [2024-03-13 08:49:25 +0000] [220628] [INFO] Booting worker with pid: 220628
    [2024-03-13 08:49:25 +0000] [220629] [INFO] Booting worker with pid: 220629
    [2024-03-13 08:49:26 +0000] [220630] [INFO] Booting worker with pid: 220630
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@node1 wrk]# ./wrk -t12 -c400 -d30s http://127.0.0.1:7889
    Running 30s test @ http://127.0.0.1:7889
    12 threads and 400 connections
    Thread Stats Avg Stdev Max +/- Stdev
    Latency 24.68ms 147.52ms 2.00s 96.66%
    Req/Sec 3.44k 3.16k 11.86k 53.42%
    353945 requests in 30.04s, 57.72MB read
    Socket errors: connect 0, read 0, write 0, timeout 502
    Requests/sec: 11782.63
    Transfer/sec: 1.92MB
  • 八进程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@node1 wrk]# ./wrk -t12 -c400 -d30s http://127.0.0.1:7889
    Running 30s test @ http://127.0.0.1:7889
    12 threads and 400 connections
    Thread Stats Avg Stdev Max +/- Stdev
    Latency 13.29ms 107.98ms 2.00s 98.02%
    Req/Sec 4.49k 2.79k 17.94k 71.28%
    709308 requests in 30.04s, 115.67MB read
    Socket errors: connect 0, read 11, write 0, timeout 445
    Requests/sec: 23612.98
    Transfer/sec: 3.85MB
  • 十六进程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    [root@node1 wrk]# ./wrk -t12 -c400 -d30s http://127.0.0.1:7889
    Running 30s test @ http://127.0.0.1:7889
    12 threads and 400 connections
    Thread Stats Avg Stdev Max +/- Stdev
    Latency 86.79ms 283.99ms 2.00s 91.56%
    Req/Sec 4.73k 3.01k 28.13k 67.26%
    1318602 requests in 30.10s, 215.04MB read
    Socket errors: connect 0, read 0, write 0, timeout 1732
    Requests/sec: 43807.48
    Transfer/sec: 7.14MB
    总结

一:提交代码流程

  1. 同步最新代码
    1
    2
    git fetch
    git merge --ff-only
  2. 创建新分支 version_{bug-id}
    1
    git checkout -b 8.2-PBT-1234
  3. 修改代码…
  4. 添加
  5. 提交
    1
    git commit
  6. 推送远程仓库
    1
    git push origin 8.2-PBT-1234
  7. code review
  8. 删除分支
    1
    git branch -D 8.2-PBT-1234
  9. 同步其他分支子模块更新后,主模块需要更新添加子模块
    1
    git add iso-sources/kylin-v10

二: 特殊处理

模块管理

1
2
vim .gitmodules  # view git modules
git submodule update --init bt-ganesha/src/nfs-ganesha # 拉取子模块的代码

修改commit日志

1
git commit --amend

强推

1
git push origin 8.2-PBT-5703 --force

合并提交

1
git rebase -i HEAD~2 # 记得选s.

提交后测试不通过

1
git rebase origin/virtualstor-8.3

cherry-pick

1
2
git cherry-pick <commitHash>
git cherry-pick <branch>

同步主分支

1
2
3
git fetch
git rebase origin/scaler/8.3/12.2.13/master
git push origin PBT-5804 --force

回退到指定位置

1
git reset --hard hash

主项目合并子项目

1
2
3
4
5
6
7
8
9
10
11
12
13
cd ./主项目路径
git fetch
git merge --ff-only
git submodule update # 非常重要
cd ./子项目路径
git checkout 子项目的分支
git fetch
git merge --ff-only
git add 子项目
git commit # [子项目名] message, for example: [ezs3-ceph] add s3 tier related
commit
git checkout -b 8.2-s3-tier-fix-v3
git push origin 8.2-s3-tier-fix-v3

优雅切割

  • 默认空格
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <string>
    #include <sstream>
    #include <iostream>

    using namespace std;

    int main() {
    istringstream iss("capiTalIze tHe titLe");
    string s;
    while (iss >> s) {
    std::cout << "size = "<< s.size() << " " << s << std::endl;
    }
    return 0;
    }
  • 指定字符
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    #include <string>
    #include <sstream>
    #include <iostream>

    using namespace std;

    int main() {
    istringstream iss("capiTalIze, tHe, titLe");
    string s;
    while (getline(iss, s, ',')) {
    std::cout << "size = "<< s.size() << " " << s << std::endl;
    }
    return 0;
    }
  • 优雅format
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #include <string>
    #include <sstream>
    #include <iostream>

    using namespace std;

    int main() {
    istringstream iss("1 2 3");
    int i, j, k;
    iss >> i >> j >> k;
    cout << "i=" << i << ", j=" << j << ", k=" << k << endl;
    return 0;
    }

下载代码

code。关于版本,我这里参考的是cppreference, 例如:通过阅读文档,可以知道:完全支持cpp17, 至少需要 gcc-7.0

1
2
3
wget https://ftp.gnu.org/gnu/gcc/gcc-13.2.0/gcc-13.2.0.tar.gz
tar -xzf gcc-13.2.0.tar.gz
cd gcc-13.2.0

编译和安装

1
2
3
4
5
mkdir build
cd build
../configure --prefix=/usr/local/gcc-13.2.0/build --enable-languages=c,c++ --disable-multilib
make
make install
  • --prefix=[PATH]: 这个参数非常重要,确定了gcc的安装路径。就是执行make install后,gcc的安装路径。
  • 当执行完make install后,才会生成完整的gcc。

使用

  • 配置环境变量
    1
    2
    export CC=/usr/local/gcc-13.2.0/build/bin/gcc
    export CXX=/usr/local/gcc-13.2.0/build/bin/g++
  • 写入CMake
    1
    2
    set(CMAKE_C_COMPILER /usr/local/gcc-13.2.0/build/bin/gcc)
    set(CMAKE_CXX_COMPILER /usr/local/gcc-13.2.0/build/bin/g++)
    参考how-to-specify-new-gcc-path-for-cmake

命令

1
2
3
4
perf record -F 99 -p [pid] -g -- sleep 60
perf script > out.perf
./stackcollapse-perf.pl out.perf > out.folded
./flamegraph.pl out.folded > out.svg

命令的解释:

  • perf record: 启动perf的记录模式,用于收集性能数据。

  • -F 99: 设置记录事件的采样频率。在这里,设置为99,表示每秒进行99次采样。采样频率的选择依赖于需要捕获的性能信息以及对性能开销的容忍度。

  • -p 13204: 指定要监视的进程的PID(进程ID)。在这个例子中,监视的是PID为13204的进程。

  • -g: 启用调用图跟踪,收集函数调用堆栈信息。这对于分析程序的性能瓶颈和理解函数之间的调用关系非常有用。

  • --: 表示后面的参数是要执行的命令。在这里,是sleep 30,即使perf工具在后台运行,记录了30秒的性能数据。

  • perf script > out.perf: 将收集到的数据转换为可读的文本格式。

  • ./stackcollapse-perf.pl out.perf > out.folded: 生成stack。

  • ./flamegraph.pl out.folded > out.svg: 生成火焰图

perf 做了什么事

可以简单的理解perf是一个采样工具,对CPU正在执行的函数,以及当前的函数堆栈(stack), 进行采样。
perf是怎么做这件事的呢? 我猜测是通过读取/proc/[pid]下的内容,进行统计,监控。

FlameGraph是解释型语言Perl写的。所以无需安装,只需要下载源代码,就可以执行。

参考

其实写这篇博客的时候,已经是2024年啦~
今年有无数次想写博客的冲动,但是因为拖延症,所以没有写。

今年对我来说是:时间过得飞快的一年。或许六月份是一个节点,六月份之前是一种生活,六月份之后又是一种生活。六月份之前,稳定的部门聚餐,团建。四月份的时候还去了重庆。重庆是一个非常繁华,非常热闹的城市。我也玩得很开心~ 自己一个人去了酒吧,看到了很多光鲜亮丽的小姐姐,一对对的情侣,还有几个听到《晴天》就被唱哭的路人朋友。不得不感慨,原来酒吧里有这么多真实的,单纯的人啊。
十八梯~ 十八水~ 十点半的飞机,它不下来~

Bigtera是一个非常强的技术团队,我也很荣幸能够加入这样的团队,自己能够做一点微薄贡献。从前端的vue,到后端的flask,这些开发任务都可以熟练地完成。当然最核心的ceph,我也侥幸做过一点贡献。

六月份之后,就是一种新的生活了。这个六个月的时间过得就像一天一样。

最大的成就就是只狼一周目通关了。战胜了一个个的boss,蝴蝶夫人,狮子猿,破解僧… 非常享受完美弹刀时,释放多巴胺的感觉。
英语的进步也很大,继续加油叭~

今年感悟最多地句子是:命运里的一切馈赠,早已在暗中标好了价格~

“缺爱”这个词是我经常用到的。最近几天发现我周围的朋友似乎也会提到这个词。讲 :”我们来这里,不就是为了找爱吗?”; 讲 :”我做爱不缺人,只是缺爱而已”。

那什么是爱呢?在思考这个问题的时候,我的脑海中浮现出了今年在超市遇到的一幕。在一大排摆满成人用品的货架前,一个帅哥开心地大声喊:”哇~宝贝,你看这里有好多好东西啊~“,然后一个美女缓缓走来,一脸娇羞和宠溺的语气说:”那你买嘛,那你买嘛~“。
虽然这一幕已经过去好久,但是我依然记得。我总是感觉这个帅哥的落落大方,真诚,真实,像小孩子一样的开心;美女的娇羞,宠溺; 都是现在这个社会难能可贵的。

因为现在社会的主流意识是偏压抑的,偏保守的。压抑,保守的原因,可能是生产力的束缚,如果一个人生产力不足,那他必然会选择压抑的,保守的态度处理问题。他需要得到主流意识的认可。选择压抑,保守的态度,至少可以保持自己现有的积累。

所以国内有梁山伯与祝英台,国外有罗密欧与朱丽叶,似乎道理都是一样。所以说”爱“是很难的。我缺的爱是更难的,我非常渴望构建一个更好的原生家庭,我们彼此之间自由,开放,把彼此放在最重要的位置。是开心,是宠溺。我们的精力用在让生活更精彩,更有意义,更有成长。

当然这些都很难。但是你也不要放弃,因为目前你的生活和积累,并不是很差。还有选择的机会。

王小波在黄金时代这样描述”那一天我二十一岁,在我一生的黄金时代。我有好多奢望。我想爱,想吃,还想在一瞬间变成天上半明半暗的云。后来我才知道,生活就是个缓慢受锤的过程,人一天天老下去,奢望也一天天消失,最后变得像挨了锤的牛一样。可是我过二十一岁生日时没有预见到这一点。我觉得自己会永远生猛下去,什么也锤不了我。“

加油叭~

下载源码

1
git clone https://github.com/facebook/rocksdb.git

编译 & 安装

1
2
make
make install

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <assert.h>
#include "rocksdb/db.h"

#include <string>
#include <iostream>

int main() {
rocksdb::DB* db;
rocksdb::Options options;
options.create_if_missing = true;
rocksdb::Status status =
rocksdb::DB::Open(options, "./db_dir", &db);

status = db->Put(rocksdb::WriteOptions(), "hello", "world"); //写
if (status.ok()) {
std::string k("hello");
std::string v;
db->Get(rocksdb::ReadOptions(), k, &v); //读
std::cout << v << std::endl;
}
assert(status.ok());
}

编译 & 链接

尝试:

1
g++ test.cc

报错:

1
2
3
4
/usr/local/include/rocksdb/wide_columns.h:51:21: error: ‘make_from_tuple’ is not a member of ‘std’; did you mean ‘make_tuple’?
51 | value_(std::make_from_tuple<Slice>(std::forward<VTuple>(value_tuple))) {
| ^~~~~~~~~~~~~~~
| make_tuple

解决方法:

1
g++ test.cc -std=c++17

报错:

1
undefined reference to `rocksdb::DB::Open(rocksdb::Options const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, rocksdb::DB**)'

解决方法:

1
g++ test.cc -std=c++17 -lrocksdb

报错:

1
undefined reference to `pthread_mutex_trylock'

解决方法:

1
g++ test.cc -std=c++17 -lrocksdb -lpthread

报错:

1
undefined reference to `dlopen'

解决方法:

1
g++ test.cc -std=c++17 -lrocksdb -lpthread -ldl

ok, 成功啦。啰啰嗦嗦地写了这么多,是为了加强自己对编译链接的理解。

遍历rocksdb

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
#include <cassert>
#include <rocksdb/db.h>
#include <iostream>
using namespace std;
int main(int argc, char** argv) {
rocksdb::DB* db; rocksdb::Options options;
options.create_if_missing = true; //打开数据库
rocksdb::Status status = rocksdb::DB::Open(options, "./omap", &db);
assert(status.ok()); //插入数据
// db->Put(rocksdb::WriteOptions(), "key1", "value1");
// db->Put(rocksdb::WriteOptions(), "key2", "value2");
// db->Put(rocksdb::WriteOptions(), "key3", "value3");
// db->Put(rocksdb::WriteOptions(), "key4", "value4");
// db->Put(rocksdb::WriteOptions(), "key5", "value5");
rocksdb::Iterator* it = db->NewIterator(rocksdb::ReadOptions()); //使用迭代器获取所有key和value
for (it->SeekToFirst(); it->Valid(); it->Next()) {
cout << "key is: " << it->key().ToString() << endl;
cout << "value is: " << hex << it->value().ToString() << endl;
cout << "++++++++++++++++++++" << endl;
}
assert(it->status().ok());
delete it; //关闭数据库
status= db->Close();
delete db;
}

背景

前几星期的晚上,有位朋友打电话问我一个问题。给定一个总价,和若干物品的单价。求出每个物品采购的数量,使得采购总和刚好等于给定的总价。
例如这样:

他说:“他算了很久,都没有合适的结果。”我说:“电话不要挂断,我很快就帮你算出来。” 于是乎,我打开pycharm,几行python代码就算出来了合适的结果。(很快啊)

他说:“计算机太牛了,比人的效率高太多了。这样的问题,我经常在工地上遇到,以后遇到了还找你算。”

开发需求

既然是经常遇到问题,那是不是应该开发一个合适的用户程序,让用户更加方便地使用起来呢。我总不能把我的代码直接丢给人家,让人家直接运行啊。代码的运行需要一个开发环境,总不能让人家配置一套python或者c++什么的开发环境吧。所以我们需要开发一个简单的GUI程序。
总结一下当前比较火的GUI开发方向。移动端(主要设备是手机),浏览器,PC桌面,小程序。其实我对浏览器的网页开发一直是非常感兴趣的,因为跨平台,有vue,react等开发框架。但是浏览器需要service,所以我放弃啦。ok那就移动端吧,但是我这位兄弟用的是苹果手机,我用的是小米手机。而且我也没有苹果电脑。所以我放弃啦。那就只能PC桌面啦,而且他身边也有电脑。

开发

其实GUI开发一直是我弱项,整天把自己的学习精力放在探索计算机里没有黑魔法的事情上了。但是也不影响,看看文档就可以快速上手了。
简单画了一个布局,添加几个组件。几个小时就可以搞定。
为了支持可以动态添加物品,所以还需要一个递归的算法。自从看了SICP,对递归的理解更深了。递归代码也可以轻松拿捏。写之后才发现这个问题和著名的背包问题非常类似。所以算法是很重要,很核心的。

打包命令

1
pyinstaller -F main.py

测试



ok,能用。

代码

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (QWidget, QPushButton, QLineEdit,
QInputDialog, QApplication, QLabel, QDialog, QDialogButtonBox, QVBoxLayout)
import sys
import time

f_ret = []
def f(total, price, ix, args = []):
p = price[ix - 1]
if ix == len(price):
if total % p == 0:
f_ret.append(args + [total // p])
return
for i in range(1, total // p):
f(total - p * i, price, ix + 1, args + [i])

class AnsDialog(QDialog):
def __init__(self, t, p):
super().__init__()
self.t = t
self.p = p
self.setWindowTitle("结果")

self.layout = QVBoxLayout()
for i in range(len(f_ret)):
ret = f_ret[i]
s = ""
for j in range(len(ret)):
s = s + str(ret[j]) + " * " + str(self.p[j]) + " + "
s = s[:-3] + ' = '
print(f"{s}{self.t}") # 输出到控制台窗口
message = QLabel(f"{s}{self.t}")
self.layout.addWidget(message)
self.setLayout(self.layout)


class Example(QWidget):

def __init__(self):
super().__init__()

self.initUI()

def initUI(self):
self.total_label = QLabel(self)
self.total_label.setText("总价")
self.total_label.move(20, 20)
self.total_price = QLineEdit(self)
self.total_price.move(50, 40)

self.unit_label = QLabel(self)
self.unit_label.setText("单价")
self.unit_label.move(20, 80)
self.unit_price_list = []
unit_price = QLineEdit(self)
unit_price.move(50, 110)
self.unit_price_list.append(unit_price)

self.add_btn = QPushButton('+', self)
self.add_btn.move(50, 80)
self.add_btn.clicked.connect(self.add_unit_price)

self.sub_btn = QPushButton('-', self)
self.sub_btn.move(120, 80)
self.sub_btn.clicked.connect(self.sub_unit_price)

self.calc_btn = QPushButton('计算', self)
self.calc_btn.move(200, 80)
self.calc_btn.clicked.connect(self.calc_price)

self.setGeometry(300, 300, 450, 350)
self.setWindowTitle('王工专用')
self.show()


def add_unit_price(self):
unit = self.unit_price_list[-1]
x, y = unit.x(), unit.y()

unit = QLineEdit(self)
unit.move(x, y + 25)
unit.show()
self.unit_price_list.append(unit)

def sub_unit_price(self):
if len(self.unit_price_list) > 1:
unit = self.unit_price_list.pop()
unit.close()
def calc_price(self):
total = int(self.total_price.text())
price = [i.text() for i in self.unit_price_list]
price = list(set(int(i) for i in price))
f_ret.clear()
f(total, price, 1)
dlg = AnsDialog(total, price)
dlg.exec()


def main():
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec())


if __name__ == '__main__':
main()

简介

创建rbd-pool, 创建image

CREATE A BLOCK DEVICE POOL

1
$ rbd pool init <pool-name>

CREATE A BLOCK DEVICE USER

这部分我的理解还不够,暂时略过。直接参考官方文档会更好。

CREATING A BLOCK DEVICE IMAGE

1
$ rbd create --size {megabytes} {pool-name}/{image-name}

实例

1
rbd create nrbd.img --size 50G --pool rbd-pool

(我好像更喜欢各种风格的命令)

LISTING BLOCK DEVICE IMAGES

1
2
3
4
5
[root@ceph01 etc]# rbd ls --pool rbd-pool
bar
nnrbd.img
nrbd.img
rbd.img
1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@ceph01 etc]# rbd info nnrbd.img --pool rbd-pool
rbd image 'nnrbd.img':
size 50 GiB in 12800 objects
order 22 (4 MiB objects)
snapshot_count: 0
id: cb6eaf54bbe4
block_name_prefix: rbd_data.cb6eaf54bbe4
format: 2
features: layering, exclusive-lock
op_features:
flags:
create_timestamp: Mon Feb 27 11:09:59 2023
access_timestamp: Mon Feb 27 11:09:59 2023
modify_timestamp: Mon Feb 27 11:09:59 2023

客户端挂载

参考这篇博客可以知道

1
cephadmin@ceph-deploy:~/ceph-cluster$ sudo scp ceph.conf ceph.client.admin.keyring root@192.168.1.180:/etc/ceph/

是需要把这些配置文件复制到客户端。
ok,我按照这样的操作执行啦。可是接下来的命令不能work。然后我就尝试了好多方法,去定位问题。我去看官方文档,但是似乎并没有合适的解决方法。
直到今天早上灵机一动。我想到了server-client通信的问题。所以我就恍然大悟啦。
既然要通信,那必须有网络(软件抽象),有网卡(硬件抽象)。那么ceph的通信是需要两个网络的,一个public ip,一个private ip。所以需要在客户端添加一个private ip.

ip配置

参考链接
查看网卡配置文件

1
2
3
[root@localhost etc]# cd /etc/sysconfig/network-scripts/
[root@localhost network-scripts]# cat ifcfg-
ifcfg-ens18 ifcfg-eth0 ifcfg-lo

重启网络

1
systemctl restart network

检查ceph连接状况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@localhost etc]# ceph -s
cluster:
id: ba39b878-7aa9-45c3-b9d9-dab4a59bce4f
health: HEALTH_OK

services:
mon: 1 daemons, quorum mon1 (age 3d)
mgr: mon1(active, since 3d)
osd: 6 osds: 6 up (since 3d), 6 in (since 3d)
rgw: 1 daemon active (1 hosts, 1 zones)

data:
pools: 10 pools, 241 pgs
objects: 1.51k objects, 4.7 GiB
usage: 605 GiB used, 4.1 TiB / 4.7 TiB avail
pgs: 241 active+clean

io:
client: 27 KiB/s rd, 31 op/s rd, 0 op/s wr

新的问题

下一步我打算下载fio。但是遇到了新的错误。

1
2
3
$ yum install fio
ndctl-libs-65-5.el7.x86_64.rpm FAILED
https://mirrors.tuna.tsinghua.edu.cn/centos/7/os/x86_64/Packages/ndctl-libs-65-5.el7.x86_64.rpm: [Errno 14] curl#6 - "Could not resolve host: mirrors.tuna.tsinghua.edu.cn; Unknown error"

这错误上周也有遇到过。当时尝试了很多的错误方法,首先我认为是镜像源的问题,于是尝试更换了阿里镜像源,网易镜像源。但是都不行。因为看到是curl报的错,我还认为是curl的问题。
但是今天这个场景我就想到了是DNS的问题,因为我新添加的网卡没有配置DNS地址。

尝试ping百度也不行

1
2
[root@localhost etc]# ping www.baidu.com
ping: www.baidu.com: Name or service not known

修复方法
参考 stackexchange,stackoverflow

1
2
3
4
5
6
7
8
9
10
[root@localhost etc]# cat resolv.conf
# Generated by NetworkManager
search domain.name
nameserver 8.8.8.8
nameserver 1.1.1.1
nameserver 1.0.0.1
[root@localhost etc]# systemctl restart NetworkManager
[root@localhost etc]# ping www.baidu.com
PING www.a.shifen.com (180.101.50.242) 56(84) bytes of data.
64 bytes from 180.101.50.242 (180.101.50.242): icmp_seq=1 ttl=53 time=2.46 ms

(其实我最初的想法是添加路由,但是好像不太行)

当你对一个东西很熟悉的时候,解决问题的灵感自然就来了。

参考链接

monitoring-linux-networking-state-using-netlink

python重写

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import struct
import socket
import os

nlmsghdr_format = "=IHHII"
ifinfomsg_format = "=BBHiII"
ifaddrmsg_format = "=BBBBI"
rtattr_format = "=HH"
nlmsghdr_size = 16
ifinfomsg_size = 16
ifaddrmsg_size = 8
rtattr_size = 4

RTM_NEWROUTE = 24
RTM_DELROUTE = 25
IFA_LOCAL = 2
IFLA_IFNAME = 3
IFLA_MAX = 51
RTMGRP_LINK = 1
RTMGRP_IPV4_IFADDR = 0x10
RTMGRP_IPV4_ROUTE = 0x40
IFF_UP = 0x1
IFF_RUNNING = 0x40
RTM_DELADDR = 21
RTM_DELLINK = 17
RTM_NEWLINK = 16
RTM_NEWADDR = 20


sock = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
sock.bind((os.getpid(), RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE))

def parseRtattr(rta, msg_len, ifla_rta_ix):
RTA_ALIGNTO = 4
RTA_ALIGN = lambda Length : (Length + RTA_ALIGNTO - 1) & ~(RTA_ALIGNTO - 1)
tb = [None for _ in range(IFLA_MAX + 1)]
point, size = 0, len(rta)
rta_len, rta_type = struct.unpack(rtattr_format, rta[point : rtattr_size])
while msg_len >= rtattr_size and rtattr_size <= rta_len <= msg_len and point + rtattr_size <= size:
rta_len, rta_type = struct.unpack(rtattr_format, rta[point : point + rtattr_size])
print("point is {}".format(point))
if rta_type <= IFLA_MAX:
print("rta_type is {}, rta_len is {}, rta is {}".format(rta_type, rta_len, ifla_rta_ix + point))
tb[rta_type] = ifla_rta_ix + point + rtattr_size
point += RTA_ALIGN(rta_len)
msg_len -= RTA_ALIGN(rta_len)

return tb


while True:
data = sock.recv(8192)
nlmsghdr = data[0:nlmsghdr_size]

msg_len, msg_type, flags, seq, pid = struct.unpack(nlmsghdr_format, nlmsghdr)
if msg_type == RTM_NEWROUTE or msg_type == RTM_DELROUTE:
print("Routing table was changed")
continue
# print("msg_len={}\t msg_type={}\t flags={}\t seq={}\t pid={}".format(msg_len, msg_type, flags, seq, pid))
# print(len(data))
# parser struct ifinfomsg
# unsigned really is a shorthand for unsigned int, and so defined in standard C
# https://stackoverflow.com/questions/1171839/what-is-the-unsigned-datatype
ifinfomsg = data[nlmsghdr_size : nlmsghdr_size + ifinfomsg_size]
ifi_family, __ifi_pad, ifi_type, ifi_index, ifi_flags, ifi_change = struct.unpack(ifinfomsg_format, ifinfomsg)
# print("ifi_family={}\t__ifi_pad={}\tifi_type={}\tifi_index={}\tifi_flags={}\tifi_change={}".format(ifi_family, __ifi_pad, ifi_type, ifi_index, ifi_flags, ifi_change))

ifla_rta = data[nlmsghdr_size + ifinfomsg_size :]
tb = parseRtattr(ifla_rta, msg_len, nlmsghdr_size + ifinfomsg_size)

if tb[IFLA_IFNAME]:
# may
for ix in range(tb[IFLA_IFNAME], len(data)):
if data[ix] == '\x00': break
ifName_format = "=" + "c" * (ix - tb[IFLA_IFNAME])
ifName = ''.join(struct.unpack(ifName_format, data[tb[IFLA_IFNAME] : ix]))
print("ifName is {}".format(ifName))
if ifi_flags & IFF_UP:
ifUpp = "UP"
else:
ifUpp = "DOWN"
print(ifUpp)
if ifi_flags & IFF_RUNNING:
ifRunn = "RUNNING"
else:
ifRunn = "NOT RUNNING"
print(ifRunn)
ifaddrmsg = data[nlmsghdr_size : nlmsghdr_size + ifaddrmsg_size]
ifa_family, ifa_prefixlen, ifa_flags, ifa_scope, ifa_index = struct.unpack(ifaddrmsg_format, ifaddrmsg)
ifa = data[nlmsghdr_size + ifaddrmsg_size :]
tba = parseRtattr(ifa, msg_len, nlmsghdr_size + ifaddrmsg_size)
if tba[IFA_LOCAL]:
for i in data[tba[IFA_LOCAL] : tba[IFA_LOCAL] + 4]:
print(ord(i))
ifAddress = '.'.join(str(i) for i in struct.unpack('=BBBB', data[tba[IFA_LOCAL] : tba[IFA_LOCAL] + 4 ]))
if msg_type == RTM_DELADDR:
print("Interface {}: address was removed".format(ifName))
pass
elif msg_type == RTM_DELLINK:
print("Network interface {} was removed".format(ifName))
pass
elif msg_type == RTM_NEWLINK:
print("New network interface {}, state: {} {}".format(ifName, ifUpp, ifRunn))
pass
elif msg_type == RTM_NEWADDR:
print("Interface {}: new address was assigned: {}".format(ifName, ifAddress))
pass

close(sock)

epoll的两种工作模式

LT(水平触发),ET(边缘触发)

LT模式: 支持阻塞和非阻塞套接字
ET模式: 只支持非阻塞套接字