0%

备份命令

1
mongodump -h <ip>:<port> -d <数据库名> -u root -p pass -o <备份目录> [--gzip]

例如

1
2
TRADE_DATE=`date "+%Y%m%d"`
mongodump -h 127.0.0.1:27017 -d ftresearch -u root -p pass -o /var/lib/mongodb/backup/$TRADE_DATE --gzip

其中--gzip表示压缩备份文件,对于压缩

还原备份

还原前我们先启动一个新的MongoDB的容器,把容器的27017端口映射到宿主机的47017。

1
docker run -dit -p 47017:27017 --name=mongo_test -e MONGO_INITDB_ROOT_USERNAME=root -e MONGO_INITDB_ROOT_PASSWORD=pass mongo:3.6.2

执行还原命令

1
mongorestore -h <ip>:<port> -d <数据库名> -u root -p pass <备份目录> --drop --gzip

例如

1
mongorestore -h 127.0.0.1:47017 -u root -p pass /var/lib/mongodb/backup/20230709 [--drop] [--gzip]

其中--drop的意思是如果要还原的数据库已存在,则先删除。
--gzip表示还原的是压缩备份,如果备份时没加--gzip参数则还原时也不加。

在NTP(Network Time Protocol)中,ntpdate是一个命令行工具,用于将系统时钟与网络上的NTP服务器进行同步。它可以通过向NTP服务器发送请求并根据响应调整本地时钟来实现时间同步。

CentOS上通常自带ntpdate,如果找不到ntpdate命令可以通过下面命令安装:

1
yum install -y ntpdate

Ubuntu上通常没有自带ntpdate,可以通过下面命令安装:

1
apt install ntpdate

常用的同步命令格式

1
ntpdate [-u] <server>

-u 参数表示使用非特权(unprivileged)模式进行时间同步。当以非特权模式运行时,ntpdate不需要超级用户权限来执行同步操作。这对于普通用户来说很有用,因为他们通常没有足够的权限来使用特权模式。

通过使用 -u 参数,ntpdate会使用一个高位端口(1024以上)来发送NTP请求,而不是使用标准的NTP端口(123号端口)。在大多数操作系统中,只有特权用户才能使用低位端口,因此非特权模式是普通用户进行时间同步的常见选择。

常用的ntp服务器

1
2
3
4
5
cn.pool.ntp.org  中国开源免费NTP服务器
ntp1.aliyun.com 阿里云NTP服务器
ntp2.aliyun.com 阿里云NTP服务器
time1.aliyun.com 阿里云NTP服务器
time2.aliyun.com 阿里云NTP服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Screen {
public:
template<typename... Arg>
void print(int x, int y, const char* fmt, Arg&&... arg) {
move_to(x, y);
printf(fmt, forward<Arg>(arg)...);
}

void move_to(int x, int y) {
printf("\033[%d;%dH", x+1, y+1); // 移动光标
}

void hide_cursor() {
printf("\033[?25l"); // 隐藏光标
}

void show_cursor() {
printf("\033[?25h"); // 显示光标
}

void clear() {
printf("\033[2J\n"); // 清屏
}
};

BusyWaitableCondition是忙寻版的可等待条件

WaitableCondition是睡眠版的可等待条件

两者实现相同功能,只是忙寻版会占用一个cpu core,而睡眠版会陷入睡眠在需要时才被唤醒继续执行。

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

#include <iostream>
#include <atomic>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <thread>
#include <future>

using namespace std;

class BusyWaitableCondition {
public:
void Wait() {
while(!_flag);
}

void Notify() {
_flag = true;
}

void Reset() {
_flag = false;
}

private:
std::atomic<bool> _flag = false;
};

class WaitableCondition {
public:
void Wait() {
std::unique_lock<std::mutex> lock(_mutex);
if(!_flag) {
_cv.wait(lock, [this](){ return _flag; });
}
}

void Notify() {
do {
std::lock_guard<std::mutex> lock(_mutex);
_flag = true;
} while(false);
_cv.notify_all();
}

void Reset() {
std::lock_guard<std::mutex> lock(_mutex);
_flag = false;
}

private:
std::mutex _mutex;
std::condition_variable _cv;
bool _flag = false;
};

int main() {
WaitableCondition c1;
auto fut1 = std::async(std::launch::async, [&]{
c1.Wait(); // 等待条件1满足才继续执行
cout << "Hello 1" << endl;
});
auto fut2 = std::async(std::launch::async, [&]{
c1.Wait(); // 等待条件1满足才继续执行
cout << "Hello 2" << endl;
});

this_thread::sleep_for(chrono::seconds(1));
cout << "1" << endl;
cout << "2" << endl;
cout << "3" << endl;
cout << "4" << endl;
cout << "5" << endl;
c1.Notify(); // 通知条件1满足了

return 0;
}

上面的例子中有定义Reset方法,但是没有演示,它的作用是将条件设置为否,以便可以再次等待,然后可以重新触发。

如果只需要等待一个一次性事件而不需要Reset方法那么可以用std::promise+std::future进行更简单的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class WaitableEvent {
public:
WaitableEvent(): _fut(_promise.get_future()) {

}

void Wait() const {
_fut.wait();
}

void Notify() {
_promise.set_value();
}

private:
std::promise<void> _promise;
std::future<void> _fut;
};

sscanf是C语言库函数,作用是从字符串中进行格式化解析。

例如在读文本文件时先用fgets把整行读入buffer再用sscanf进一步解析buffer的内容。

头文件

1
#include <stdio.h>
1
#include <cstdio>

函数原型

1
int sscanf(const char *str, const char *format, ...);

参数

  • str – 以’\0’结尾的字符串,解析的数据源。
  • format – 格式化字符串,描述解析的规则
  • args… – 对应format参数,提供需要读取的变量的地址

返回值

返回读取到的参数的个数。

当没有读入任何数据时,可能是因为走到了字符串的结尾,也可能是因为当前将要读入的字符串无法转换为指定的格式。

如果因为读到了字符串结尾则返回EOF,即-1,如果是因为其他原因没有读入任何参数,则返回0。

样例

1
2
3
4
5
6
int i;
float f;
double ff;
char buf[128];
char ch;
sscanf("123 3.14 2.71828 \t\r\n Hello x", "%d %f %lf %s %c", &i, &f, &ff, buf, &ch);

说明

%d表示读取一个整数

%f表示读取一个浮点数

%lf表示读取一个双精度浮点

%s表示读取一个字符串

%c 表示读取一个字符

在format中空格表示跳过任意多的空白字符(空白字符包括' ', '\t','\r','\n'

%d %f %lf %s这些格式自带跳过空白字符即使前面没有空格。而%c不会自动跳过空白字符。

高级用法

指定读取字符串的长度

1
2
char buf[5];
sscanf("Hello", "%4s", buf);

我们的buf只有5个字节大小,考虑到sscanf读取的字符串会在结尾加上’\0’,所以我们最多读取4个自己,可以在%s中间加上希望读取的长度。

这同样适用于%d %f %lf仍然表示最多读取的字符串长度,只是会在读取后会将字符串转换成相应类型的数据。

丢弃读取内容

%后面加上*可以丢弃当前%表示的格式读取到的内容,即后面的参数列表不需要传入接收此项的数据地址。

1
2
char buf[5];
sscanf("Hello", "%*4s %s", buf);

丢弃了4个字符,再跳过任意多空白字符,然后读入一个字符串,所以这时读入的buf里只有"o"

读取指定的字符

通过%[]指定读取的字符,可逐个列出或通过-指定范围,也可以指定多个范围。

1
2
3
char buf1[128];
char buf2[128];
sscanf("helloWorld", "%[a-z]%s", buf1, buf2);

读取除指定字符外的字符

%[...]类似,通过%[^...]可以反向指定需要读取的字符,即除了什么字符以外的所有字符。

非常适合读取用某种字符分割的字符串。

1
2
3
char buf1[128];
char buf2[128];
sscanf("Hello,World", "%[^,]%s", buf1, buf2);

获取当前读取到的位置

通过在format中需要确定的位置插入%n获取

1
2
3
4
5
char buf1[128];
int n1;
char buf2[128];
int n2;
sscanf("Hello,World", "%[^,]%n%s%n", buf1, &n1, buf2, &n2);

n1 = 5, n2 = 11,注意%n实际上不是表示前一个%读入了多少字符,而是表示当前已经读到的位置,但是如果有需要我们可以通过这个参数算出每个参数读入了多少字符。

综合应用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const char* p = "Hello,World,123.45,, Hello world   ,ttt";
int n;
char buf[128] = {0};
int r;
while(*p != '\0' && (r = sscanf(p, "%127[^,]%n", buf, &n)) != EOF) {
if(r == 0) {
buf[0] = '\0';
n = 0;
}
printf("%s\n", buf);
p += n;
if(sscanf(p, "%*c%n", &n) == EOF) break;
p += n;
}

这里演示了读入以,分割的多字段的情况。其中有一点需要说明,while条件里的sscanf我们是期望读当前位置到下一个,前的字符串,而当r==0时,说明当前就指在下一个,上,此时读入了0个参数,而buf中会保留这之前的内容,所以将其手动置空,而n也同样没有被修改,所以我们也将其置0,以便后面的输出和移动可以用相同的代码统一处理。

通过这种方式sscanf完全可以替代strtok,而且避免了strtok使用了内部静态变量带来的线程安全问题。

CMakeList.txt中,LINK_DIRECTORIES和rpath-link都是用来指定链接库路径的,但是有区别,看来具体例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 设置库路径
LINK_DIRECTORIES(
${PROJECT_ROOT_DIR}/libs/kTrade/libs
${PROJECT_ROOT_DIR}/libs/kTrade/libs/ama_3_9_8 # (1)
)

# ...省略若干...

TARGET_LINK_LIBRARIES(AmaMD
dl Engine rt pthread
kTradeUtils
amaquote g3log
ama
-Wunused-function
-Wl,-rpath,'$ORIGIN'/libs/Ama,-rpath,'$ORIGIN'/libs,-rpath-link,${PROJECT_ROOT_DIR}/libs/kTrade/libs/ama_3_9_8 # (2)
)

在注释(1)处的目录下面有libama.so库和其依赖的若干.so
如果只加注释(1)处的路径而不加注释(2)处的-rpath-link参数,则会报错找不到通过ama间接依赖的一系列.so的问题,
如果只加注释(2)处的-rpath-link参数而不加注释(1)处的路径,则会报错找不到ama库本身的问题。

解决方案是要么两处都加上,要么只加(1)处的路径,但是需要在TARGET_LINK_LIBRARIES中写出所有通过ama间接依赖到的库名字,这样就都会通过(1)处的路径找到它们并使它们参与到链接中。
总之,直接链接的库只会通过(1)处指定的路径查找,而间接链接到的库只会通过(2)处指定的路径进行查找。

有时我们想查看C/C++代码对应的汇编代码,可以通过gcc/g++的编译选项-S来生成,但这样的代码可读性较差。

可以增加-fverbose-asm来增加可读性

1
g++ -O0 -o temp.s temp.cpp -std=c++17 -g -S -fverbose-asm

还有另一个方法,先生成目标文件再用objdump进行反汇编,我发现这样生成的汇编代码可读性更好。

1
g++ -O0 -o temp temp.cpp -std=c++17 -g && objdump -S -t -D temp > temp.s

之前写过在Python中监视卡死崩溃退出并打印卡死处的调用堆栈

在此记录一下C++的版本,不过没有在代码层面实现堆栈打印,可以通过core dump和gdb来查看崩溃时的堆栈

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
// WatchDog.h
#pragma once

#include <cstdint>
#include <mutex>
#include <thread>
#include <atomic>
#include <condition_variable>

class WatchDog {
public:
WatchDog(int timeout=10, bool echo=false); // seconds
~WatchDog() { stop(); }
void stop();
void kick();

private:
void dog();
void bark();

private:
const int _timeout;
const int _echo;
std::atomic<int64_t> _last_kicked_ts;

std::mutex _mutex;
bool _stopped; // protected by _mutex
std::condition_variable _cond; // protected by _mutex

std::thread _dog;
};

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
// WatchDog.cpp
#include "WatchDog.h"
#include <iostream>

using namespace std;

namespace {

int64_t get_gmtime_us(){
std::chrono::system_clock clock;
return std::chrono::duration_cast<std::chrono::microseconds>(
clock.now().time_since_epoch()).count();
}

} // namespace


WatchDog::WatchDog(int timeout, bool echo)
: _timeout(timeout)
, _echo(echo)
, _last_kicked_ts(get_gmtime_us())
, _stopped(false)
, _dog(&WatchDog::dog, this) {
}


void WatchDog::stop() {
do {
std::unique_lock<std::mutex> lock(_mutex);
_stopped = true;
_cond.notify_one(); // wake up the dog
} while(false);

try {
_dog.join();
}
catch (...) {
// it's ok, could already be dead
}
}

void WatchDog::kick() {
_last_kicked_ts = get_gmtime_us();
}

void WatchDog::dog() {
std::unique_lock<std::mutex> lock(_mutex);
while (true) {
if (_stopped) return;

int64_t ts = get_gmtime_us();
if (ts - _last_kicked_ts > _timeout * 1000000) {
bark();
}

if (_echo) {
std::cout << "Successful dog check"
<< " [ts] " << ts
<< " [last_kicked_ts] " << _last_kicked_ts << std::endl;
}

// wake up when notified, or every N seconds
int n = std::max(_timeout / 3, 1);
_cond.wait_for(lock, std::chrono::seconds(n));
}
}

void WatchDog::bark() {
if (_echo) {
std::cout << "\n!!!!! WATCH DOG FAILURE TRIGGERED !!!!!" << std::endl;
}
abort();
}

kTrade是一个交易柜台中间层,将不同券商提供的不同交易柜台封装成统一接口,以include+lib的方式提供统一的对接方案,交易系统只需要对接kTrade提供的统一接口,由kTrade处理各个柜台的区别。

QuoteApi

kTrade QuoteApi架构图

TradeApi

kTrade TradeApi架构图

iperf3用于测试两台主机间的带宽,主要参数如下:

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
-p, --port #,Server 端监听、Client 端连接的端口号; 
-f, --format [kmgKMG],报告中所用的数据单位,Kbits, Mbits, KBytes, Mbytes;
-i, --interval #,每次报告的间隔,单位为秒;
-F, --file name,测试所用文件的文件名。如果使用在 Client 端,发送该文件用作测试;如果使用在 Server 端,则是将数据写入该文件,而不是丢弃;
-A, --affinity n/n,m,设置 CPU 亲和力;
-B, --bind ,绑定指定的网卡接口;
-V, --verbose,运行时输出更多细节;
-J, --json,运行时以 JSON 格式输出结果;
--logfile f,输出到文件;
-d, --debug,以 debug 模式输出结果;
-v, --version,显示版本信息并退出;
-h, --help,显示帮助信息并退出。
Server 端参数:
-s, --server,以 Server 模式运行;
-D, --daemon,在后台以守护进程运行;
-I, --pidfile file,指定 pid 文件;
-1, --one-off,只接受 1 次来自 Client 端的测试,然后退出。
Client 端参数
-c, --client ,以 Client 模式运行,并指定 Server 端的地址;
-u, --udp,以 UDP 协议进行测试;
-b, --bandwidth #[KMG][/#],限制测试带宽。UDP 默认为 1Mbit/秒,TCP 默认无限制;
-t, --time #,以时间为测试结束条件进行测试,默认为 10 秒;
-n, --bytes #[KMG],以数据传输大小为测试结束条件进行测试;
-k, --blockcount #[KMG],以传输数据包数量为测试结束条件进行测试;
-l, --len #[KMG],读写缓冲区的长度,TCP 默认为 128K,UDP 默认为 8K;
--cport ,指定 Client 端运行所使用的 TCP 或 UDP 端口,默认为临时端口;
-P, --parallel #,测试数据流并发数量;
-R, --reverse,反向模式运行(Server 端发送,Client 端接收);
-w, --window #[KMG],设置套接字缓冲区大小,TCP 模式下为窗口大小;
-C, --congestion ,设置 TCP 拥塞控制算法(仅支持 Linux 和 FreeBSD );
-M, --set-mss #,设置 TCP/SCTP 最大分段长度(MSS,MTU 减 40 字节);
-N, --no-delay,设置 TCP/SCTP no delay,屏蔽 Nagle 算法;
-4, --version4,仅使用 IPv4;
-6, --version6,仅使用 IPv6;
-S, --tos N,设置 IP 服务类型(TOS,Type Of Service);
-L, --flowlabel N,设置 IPv6 流标签(仅支持 Linux);
-Z, --zerocopy,使用 “zero copy”(零拷贝)方法发送数据;
-O, --omit N,忽略前 n 秒的测试;
-T, --title str,设置每行测试结果的前缀;
--get-server-output,从 Server 端获取测试结果;
--udp-counters-64bit,在 UDP 测试包中使用 64 位计数器(防止计数器溢出)。

用例

服务端

1
iperf3 -s -p 30088

客户端

1
iperf3 -c server_ip -p 30088