Akawa

ETY001的博客

一周三次出块都失败了,也不知道服务器到底抽了什么风,于是索性就重装系统吧,顺便把 zram 也折腾下。重做系统的时候没有再选择 ubuntu16.04,而是选择了我最熟悉的 Archlinux。重做很快就结束了,登陆系统看了下,还是 Arch 精简干练,即使把所有需要的软件都装完,内存依然占用很少。

由于我还是打算在 docker 里面跑见证人,所以先安装 docker,执行 pacman -S docker 搞定。

然后安装一个轻量的管理 docker 界面 portainer

1
2
# docker volume create portainer_data
# docker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --name portainer --restart always -v portainer_data:/data portainer/portainer

安装好,直接浏览器访问 IP:9000 ,配置上用户名和密码,选择本地模式,搞定。

接下来就是做个文件的 swap

1
2
3
4
# fallocate -l 50G /swapfile
# mkswap /swapfile
# swapon /swapfile
# echo '/swapfile none swap sw 0 0' | tee -a /etc/fstab

然后安装 zramswap,由于 zramswap 是在 AUR 里,所以先安装一下 makepkg 所要用到的编译环境

1
# pacman -S base-devel

然后新建一个普通用户,并切换到该用户的家目录,然后下载 zramswap 的安装包(在 https://aur.archlinux.org/packages/zramswap/ 这里能找到下载地址),解压后编译安装

1
2
3
4
$ wget https://aur.archlinux.org/cgit/aur.git/snapshot/zramswap.tar.gz
$ tar xvf zramswap.tar.gz
$ cd zramswap
$ makepkg -si

安装好以后,确认下 zram 模块是否加载,如果没有加载的话,加载下

1
2
# lsmod | grep zram
# modprobe zram

启动 zramswap 并设置为开机自启动,

1
2
# systemctl start zramswap
# systemctl enable zramswap

检查是否正常

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
# zramctl
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram9 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram8 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram7 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram6 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram5 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram4 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram3 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram2 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram1 lz4 1006.3M 4K 69B 4K 10 [SWAP]
/dev/zram0 lz4 1006.3M 4K 69B 4K 10 [SWAP]

# swapon
NAME TYPE SIZE USED PRIO
/swapfile file 50G 0B -2
/dev/zram0 partition 1006.3M 0B 100
/dev/zram1 partition 1006.3M 0B 100
/dev/zram2 partition 1006.3M 0B 100
/dev/zram3 partition 1006.3M 0B 100
/dev/zram4 partition 1006.3M 0B 100
/dev/zram5 partition 1006.3M 0B 100
/dev/zram6 partition 1006.3M 0B 100
/dev/zram7 partition 1006.3M 0B 100
/dev/zram8 partition 1006.3M 0B 100
/dev/zram9 partition 1006.3M 0B 100

发现 zramswap 只分配了10G内存,于是去查了下他的启动文件 /usr/lib/systemd/system/zramswap.service,发现最终执行的文件是 /usr/lib/systemd/scripts/zramctrl,打印下 zramctrl 的内容,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# cat /usr/lib/systemd/scripts/zramctrl 
#!/bin/sh

start() {
exec awk -v ZRAM_SIZE=$ZRAM_SIZE -v ZRAM_PARM="$(modinfo zram | grep -E -o '(num_devices|zram_num_devices)')" '

FILENAME == "/proc/cpuinfo" && ($1 == "processor" || $1 == "Processor") {
cpucount++
next
}
FILENAME == "/proc/meminfo" && $1 == "MemTotal:" {
if (ZRAM_SIZE == "")
ZRAM_SIZE = 20
mem_total = int( (0 + $2) * 1024 * ( ZRAM_SIZE/100 ) )
next
}

....以下代码省略

发现需要设置一个 $ZRAM_SIZE 的环境变量,否则默认20%。在 /usr/lib/systemd/system/zramswap.service 中增加环境变量,

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Zram-based swap (compressed RAM block devices)

[Service]
Type=oneshot
Environment="ZRAM_SIZE=100" # 这里是增加的环境变量
ExecStart=/usr/lib/systemd/scripts/zramctrl start
ExecStop=/usr/lib/systemd/scripts/zramctrl stop
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

重载配置文件并重启 zramswap

1
2
# systemctl daemon-reload
# systemctl restart zramswap

再次查看 zram 状态,发现已经搞定。

1
2
3
4
5
6
7
8
9
10
11
12
# zramctl
NAME ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram9 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram8 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram7 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram6 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram5 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram4 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram3 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram2 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram1 lz4 4.9G 4K 69B 4K 10 [SWAP]
/dev/zram0 lz4 4.9G 4K 69B 4K 10 [SWAP]

接下来就是去部署 @someguy123 的见证人镜像了,这个在之前写过教程,就不再细说了,这是链接 https://steemit.com/cn/@ety001/5xtjgz

看目前下载离线区块数据的速度应该不是网络卡导致的丢块,难道是之前配置的 shared-file-size 有问题?等部署完见证人运行起来再观察下看看还会不会频繁的丢块了吧。。。

25天前填完 LightDB 的坑以后,就开始研究 alt 留下的那个 btsbots代码 了。之前也有团队在修复这套代码 ,但是考虑到这么重要的一个设施,还是把核心技术握在自己手里比较好,要不然哪天那个团队歇菜了,又要凉。

代码断断续续的看了将近一个月了,总的感受就是崩溃。

从我开始参加工作开始,接的活都是半路加入救火的,没有人指导,只有代码,基本上是靠分析输出数据和看代码来逆向梳理需求的。但是这次修复 btsbots 的难度加大了,因为不会 *C++*,只能对着各种输出数据去逆向推理 btsbots 的代码到底想要做什么,为什么这么做。所以等这个 btsbots 修好,还是要找时间去学 C++ 了。

目前并没有什么实质的代码修改,还停留在分析代码的阶段,该文就是基于目前我对于 btsbots 的理解进行的总结。

最简单的做市需要的数据无非就是当前账户内各种币剩多少,交易对是否满足下单要求,是否有订单需要取消。btsbots 的思路其实很简单,就是基于 1.11.x 这个 operation history object 来把链上的所有操作 replay 一遍,然后得到所有用户的 balance 和所有的交易对深度数据。

btsbots 项目使用 python 来完成从链上取数据并 replay 的操作,然后存入 MongoDB,最后基于 Meteor 开发了应用界面来读取 MongoDB 中的数据并展示,用户可以设置自己的做市逻辑并在浏览器本地完成下单和取消订单的签名操作。

思路很明确,但是比较让人恶心的就是数据和需求的分析了。

目前几乎所有的时间都在分析 scripts 目录下的 python 代码的逻辑,因为这一块之前在试跑的时候,数据一直有问题。

scripts 目录下主要就是 correct-balance.py , monitor.py, statistics.py 这三个文件。

这三个文件中,monitor.pystatistics.py 这两个的目的我是最明确的了,correct-balance.py 的目的一直很糊涂。

之前忘记在哪里看到的部署文档说,第一次运行 btsbots 的时候,要先运行 correct-balance.py 进行 balanceorder 的初始化。然而看 correct-balance.py 的代码中,只是去获取 1.8.x 这一种类型的订单的数据和 2.5.xbalance 数据。在代码的头部,你需要指定 x 的上限,如下图

然后循环获取从 1.8.01.8.x 获取订单数据并插入数据库,从 2.5.02.5.x 获取 balance 数据并插入数据库。完成这两步以后,会再遍历一遍已经插入数据库的订单数据,然后去更新数据库里已经存下来的 balance,如图:

这里我就有疑问了!!!

首先是为什么第一次获取数据只获取 1.8.x 这一种类型的订单,而在下面的 add_b_from_o 函数中却有 1.4.x1.7.x 的处理逻辑?

其次就是获取到的 2.5.x 是最新的 balance 数据,而拿着订单中的 amount 在最新 balance 上加减是不是错误的?我通过自己手动从 API 获取到的 2.5.x 的数据来看,拿到的这些 balance 数据都是刨除掉未成交订单额度后的,而在 btsbots 中却又在 2.5.x 的数据的基础上去刨除订单额度貌似是不对的,但是现在我又不敢确定,因为我不确定 1.4.x, 1.7.x, 1.8.x这些数据是代表的是未成交的订单,还是所有的订单。

最后就是感觉这里处理 balance 的方法跟 monitor.py 的处理方法是冲突的,这里先按下不表。我们先去看 monitor.py 的代码。

monitor.py 中,思路很简单,就是获取 1.11.x,然后根据各种不同的 operation type 来分别处理数据,最后把数据更新进数据库。这个脚本里的疑问也是有不少。

首先是怎么触发的问题,先看下图的代码,

其中 63 行是我补上的代码。与第 63 行类似的代码其实在这个类初始化的时候已经执行过了,这个在这里可以看到。在 python-bts 库里,默认是把 clear_filter 置为 false 了,这意味着只有你发送了 get_object 的指令,服务端才会推送你刚才获取的那个 object 的变动。

在没有我补上的 63 行的代码之前,执行 monitor.py 其实是没有任何数据进数据库的,因为所有的插入数据库的操作都在 onOperation 里面,而 onOperation 的触发是在用户订阅的 1.11.xobject 有变动的时候才会触发,而程序代码从开始运行,就没有 get_object 的操作,所以服务端并不知道你想要获取哪个 object 的变动信息。加上 63 行后,把 clear_filter 置为 true,那么服务端就会推送所有的 object 的变动信息了。

其次就是语言方面的问题了,如下图

由于我写 python 的时间不久,这里并不明白加锁的意义。如果加锁是为了保证共享资源的修改顺序的话,为啥还要用协程来异步?这里我还需要再多看看这方面的文章去了解下协程中的锁的目的。

然后这里第 41 行的代码在执行的时候也有问题。在这个类的父类中,看到作者最早是把 op_id_begin 置为 1.11.0 的,在从数据库中取不到数据的时候(也就是说第一次运行的时候),但是现在注释掉了,改为了置为 None,这就导致这里的代码如果按照作者原先的逻辑,就是从当前程序执行的时刻起,开始数据的同步。这也是之前那个团队修复的 btsbots 为何不支持老账户的原因。这里我改回到 1.11.0 后,运行程序跑了一天多后,发现有报错,大致意思就是插入的数据的索引有重复导致插入失败。这个问题现在还没有去深究原因。

最后为解决的难点就是这个 monitor.pyoperation 的处理了。先看下面两个截图感受下

第一个截图中,是初始化了所有类型的 op,从 btsbots 的代码里看,截止到这份代码的最后修改时间,一共有 44 个类型,这里比较蛋疼的就是之后的 bitshares-core 中是否有再新增类型吗?由于我看不懂 **C++**,所以这里就会不确定。这 44 个类型里,有些没有手续费的类型,就去掉了没有处理,因为这个对于 balance 不产生影响,剩下的类型由 16 个 handler 处理,这里就有疑问了。大致扫过这些 handler,里面也有对数据库中的 balance 的操作,那么这里通过 1.11.xbalance 进行的操作与 correct-balance.py 文件中,通过 1.8.xbalance 的操作会不会有冲突?这个问题现在我一直在里面绕着出不来。现在的打算是,最好能确认 correct-balance.py 的意义,然后把这个文件中的逻辑整合进 monitor.py 中。

目前还没有深入看 statistics.py 这个文件,这个文件的主要作用就是通过 1.2.9952 发布的喂价来更新数据库里的交易对的交易价格,另外就是统计一下交易数据。

纵观这三个文件,还有个疑问就是三个文件都在获取 global properties,且都更新数据库,那么这里面到底会不会有问题有冲突,这个还没有细看。

以上就是现有的收获了。接下来有两个方向,一个是继续看所有的 handler,把具体的处理逻辑都看完,然后再修复,另一个方向就是跳过细看 handler,直接去看看那个索引冲突的问题,强行把程序跑起来看看。

最后再附上一份我搜集的 object id

ID Object Type
1.1.x base object
1.2.x account object
1.3.x asset object
1.4.x force settlement object
1.5.x committee member object
1.6.x witness object
1.7.x limit order object
1.8.x call order object
1.9.x custom object
1.10.x proposal object
1.11.x operation history object
1.12.x withdraw permission object
1.13.x vesting balance object
1.14.x worker object
1.15.x balance object
2.0.x global_property_object
2.1.x dynamic_global_property_object
2.3.x asset_dynamic_data
2.4.x asset_bitasset_data
2.5.x account_balance_object
2.6.x account_statistics_object
2.7.x transaction_object
2.8.x block_summary_object
2.9.x account_transaction_history_object
2.10.x blinded_balance_object
2.11.x chain_property_object
2.12.x witness_schedule_object
2.13.x budget_record_object
2.14.x special_authority_object

最近在使用 Python 重写 LightDB 的第二层的数据转换程序,因为之前 PHP 版的转换程序效率太低了(主要是设计思路的问题)。使用 Python 换了个思路来实现。

现在发现了 PyMysql 在多线程下工作异常,示例代码大致如下:

执行效果如下:

通过搜索引擎找到这个 Issue (https://github.com/PyMySQL/PyMySQL/issues/422),在 7 楼有提到,

1
2
3
See https://www.python.org/dev/peps/pep-0249/#threadsafety

PyMySQL's threadsafety is 1.

解决方案,在这个 Issue 中也提到了,就是一个 process/thread 中开一个 mysql 的连接。

Oh my god !!!!!

这显然并不好!

另外,@oflyhigh O哥给找到了一个小哥写的库,https://github.com/jkklee/pymysql-connpool ,这个库的目的就是实现一个 Mysql connection pool 来解决多线程的问题。准备尝试下这个库。感谢 O哥。

之前在文章中提到过通过PHP捕获信号,以优雅的关闭程序,保证程序能够完成数据写入后才关闭。由于目前程序是通过 Docker 进行部署的,因此需要看下 Docker 容器的关闭方式。

目前 Docker 容器关闭可以通过两条命令,一条是 docker stop 一条是 docker kill

先看 docker stop

1
2
3
4
5
6
7
8
9
~/ > docker stop -h
Flag shorthand -h has been deprecated, please use --help

Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]

Stop one or more running containers

Options:
-t, --time int Seconds to wait for stop before killing it (default 10)

docker stop 命令执行的时候,会先向容器中PID为1的进程发送系统信号SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认的10秒,会继续发送SIGKILL的系统信号强行kill掉进程。在容器中的应用程序,可以选择忽略和不处理SIGTERM信号,不过一旦达到超时时间,程序就会被系统强行kill掉,因为SIGKILL信号是直接发往系统内核的,应用程序没有机会去处理它。在使用docker stop命令的时候,我们唯一能控制的是超时时间。

目前 SteemLightDB 使用 docker stop -t 60 lightdb-transfer 来停止程序,60秒的超时时间足够了。

另外一个就是 docker kill

1
2
3
4
5
6
7
8
9
~/ > docker kill -h
Flag shorthand -h has been deprecated, please use --help

Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]

Kill one or more running containers

Options:
-s, --signal string Signal to send to the container (default "KILL")

docker kill 的好处就是可以使用自定义的信号。默认信号是 SIGKILL,我们可以通过使用 -s 参数,使用指定信号。比如在我的程序中,我有对 SIGINT 信号进行处理,因此使用 docker kill -s SIGINT lightdb-transfer 也可以优雅的停止容器。

Over!!!

在部署 LightDB 第二阶段的代码的时候,需要基于 alpine 构建 PHP 镜像,结果发现 iconv 扩展有缺陷,具体表现见 https://github.com/docker-library/php/issues/240

通过该 issue 中提到的那个 hack 方法可以临时解决

1
2
RUN apk add --no-cache --repository http://dl-3.alpinelinux.org/alpine/edge/testing gnu-libiconv
ENV LD_PRELOAD /usr/lib/preloadable_libiconv.so

但是并不是完美解决,虽然 iconv 函数可用了,但是扩展使用的库版本依旧是 unknown,并且在使用 yetanotherape/diff-match-patch 的时候,依旧有报错无法使用。( yetanotherape/diff-match-patch 在我之前的 这篇文章 里有介绍 )

基本上导致 iconv 有问题的原因,可能是 alpine 使用的是 musl ,目前基于 musl 的 iconv 库可能本身就有些问题,具体问题不详。

最终不得不改用 ubuntu 18.04 来构建,可参考我的 构建文件。使用 ubuntu 构建的最大问题就是最终的镜像包过大,目前我的这个封装,镜像包大约300多兆,之前基于 alpine 构建的话,大小在100M以内。

为了让 LightDB 在退出的时候更加安全,因此考虑加入信号处理。目前 LightDB 在主循环中,先获取区块数据,再遍历区块数据进行加工,最后入库,这几个步骤中,最不希望发生的事情就是,在加工数据最后入库的时候,程序被中断。

因此加入信号处理,对人为执行 ctrl + c 的时候,把程序中断放到获取数据阶段或者入库结束之后。

以下是示例代码

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
<?php
//使用ticks需要PHP 4.3.0以上版本
// declare(ticks = 1);

class a {
public function __construct() {
pcntl_signal(SIGTERM, array($this, 'handle'));
pcntl_signal(SIGINT, array($this, 'handle'));
}

public function handle($signo) {
var_dump($signo);
exit();
}

public function start() {
while (1) {
echo time()."\n";
sleep(1);
echo "11111111\n";
pcntl_signal_dispatch();
echo "22222222\n";
}
}

}

$b = new a();
$b->start();

PHP处理信号的方法是,把程序收到的信号入队列,然后再从队列里取信号处理。这样在使用 pcntl_signal 方法的时候,需要加上 declare(ticks = 1); 或者 pcntl_signal_dispatch();,二者添加一个即可。declare(ticks = 1); 的作用是,让 PHP 每执行一行代码就去触发下从队列取信号的操作。pcntl_signal_dispatch(); 的作用则是,主动去处理队列里的信号。如果这两句不添加一个,那么最终的执行效果是,你触发了信号,但是程序只是对信号进行入队列的操作,没有程序来执行队列中的信号。

另外一个需要注意的就是,pcntl_signal 的第二个参数的使用。网上多是面向过程的使用方法,直接写要绑定的回调函数的名字即可,PHP文档中也只是有面向过程的使用方法。经过搜索,才找到绑定对象中的函数的方法,就是上面示例代码的方法。

Over!

LightDB 开发过程中,如何把已修改的文章更新到原有文章上,是个很重要的功能。

Steem 对于大篇幅的文章修改,使用的是 patch 的方式,把增量信息存入到区块链上,这样可以很大程度上减少空间开销,但是原始数据对人类不友好。因此在最终给人阅读前,需要由程序把所有变动的 patch 都依次打上才行。

经过调研,发现 Steem 使用的是 Google 的 DiffMatchPatch 这个库,但是遗憾的是,没有针对 PHP 的封装。不过最终我也还是找到一个 PHP 的库,https://github.com/yetanotherape/diff-match-patch

这个库的使用非常的简单,直接上示例

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
<?php
require __DIR__ . '/vendor/autoload.php';
use DiffMatchPatch\DiffMatchPatch;
$dmp = new DiffMatchPatch();

$old = "[SteemLightDB](https://github.com/ety001/steem-lightdb) (以下简称 LightDB ) 是一个基于 Steem 链的 MySQL 数据服务。

在有 SBDS 的情况下,为啥还要在弄一个 SteemLightDB 呢?

由于之前搭建过 SBDS 服务,发现里面有很多的问题,其中稳定性、数据原始性、数据容量这三个问题比较突出。

* SBDS 的稳定性直接依赖于其取数据的节点的稳定性;
* SBDS 的数据太原始,加工度不够,如果应用开发者需要一些数据之间的关系,需要开发者获取后自己加工,增加了开发者的开发工作量和难度;
* SBDS 占用空间太大,对于穷人来说,服务器费用开销太大。";


$patches = "@@ -32,11 +32,8 @@
om/e
-ty0
01/s
@@ -90,17 +90,16 @@
%E6%95%B0%E6%8D%AE%E6%9C%8D%E5%8A%A1%E3%80%82%0A%0A
-%E5%9C%A8
%E6%9C%89 SBDS %E7%9A%84
";



$new_content = "[SteemLightDB](https://github.com/e01/steem-lightdb) (以下简称 LightDB ) 是一个基于 Steem 链的 MySQL 数据服务。

有 SBDS 的情况下,为啥还要在弄一个 SteemLightDB 呢?

由于之前搭建过 SBDS 服务,发现里面有很多的问题,其中稳定性、数据原始性、数据容量这三个问题比较突出。

* SBDS 的稳定性直接依赖于其取数据的节点的稳定性;
* SBDS 的数据太原始,加工度不够,如果应用开发者需要一些数据之间的关系,需要开发者获取后自己加工,增加了开发者的开发工作量和难度;
* SBDS 占用空间太大,对于穷人来说,服务器费用开销太大。";



$patches = $dmp->patch_fromText($patches);
$res = $dmp->patch_apply($patches, $old);
var_dump($res);
var_dump($new_content);

这里需要注意一点,就是 patch 中数据的格式。之前一直卡在这里调不出来的原因就是 steemd.com 中提供的 patch 数据格式有问题。就像我上面给出的示例代码中的 patch ,其中有些行前面是有空格的,而在 steemd.com 提供数据中,这些空格是消失了的。。。

除此之外,还有一个坑,就是 Steem 会针对文章长短来采取是否使用 patch。。。也就是说如果你的文章就一句话,如果你编辑修好提交了,那么就不会用 patch,而是直接把你新修改的内容直接提交保存。。。具体多长我也不清楚,我没有看源码,但是有一点可以肯定就是这个操作让人很蛋疼。目前我的解决方案是使用 try catch 来区分是否使用了 patch。

1
2
3
4
5
6
try {
$patches = $dmp->patch_fromText($patches);
$res = $dmp->patch_apply($patches, $old);
} catch(\Exception $e) {
$res = $patches;
}

虽然目前看貌似没有什么问题,但是感觉实现的很不优雅。

OVER!

SteemLightDB 的用户表设计,
之前使用的是 Many To Many self-referencing 的方法来实现的用户关注关系的数据库实现。

昨天发现在关注数据中,还有一个 what 字段我给疏忽了,这个字段表明是“关注”还是“屏蔽”,
于是想要在 join table 中加一个字段。但是看了 Doctrine 的文档后,发现并没有相关的方法,
按照 Doctrine 的意思是,ManyToMany 的 join table 的目的是为了连接两个表,
因此只会存储两个被连接表的外键字段,如果想要增加 extra column,需要自己手动建立 join table,
实现 OneToMany <=> ManyToOne 的关系。

Over!

真的是刚爬出一个坑,又跳到一个新坑。LightDBTransfer 组件使用 Symfony4 框架,感觉 Symfony3 出来都没有多久,就又出来4了。本来想使用框架来节约开发时间的,结果又额外增加了时间。

Symfony 的最新文档里,给出的在 Console 中使用数据库的最佳实践方案是,把数据库的调用封装进 Service ,然后通过容器注入,来实现最终的数据库调用。

下面是代码示例:

src/Service/ExampleManager.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
namespace App\Service;

use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Example;

class ExampleManager
{
private $em;

public function __construct(
EntityManagerInterface $em
)
{
$this->em = $em;
}

public function findSomething($id)
{
return $this->em->getRepository(Example::class)->find($id);
}
}

src/Command/ExampleRunCommand.php

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
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;

use App\Service\ExampleManager;

class TransferRunCommand extends Command
{
protected static $defaultName = 'example:run';
private $example_manager;

public function __construct(
ExampleManager $example_manager,
)
{
parent::__construct();
$this->example_manager = $example_manager;
}

protected function configure()
{
$this
->setDescription('run the example shell')
;
}

protected function execute(InputInterface $input, OutputInterface $output)
{
$result = $this->example_manager->findSomething(1);
}
}

Over!!!

昨天新购置了一台大硬盘服务器,为了完成 Steem LightDB 也是拼了。今天机房那边完成了服务器的部署,登陆后发现原本 2 X 1T 的硬盘,只挂了一块,如图:

一看是通过 LVM 完成的硬盘管理。并且在 /dev/sda 这块硬盘上划出了243M做 /boot。还有两个 /dev/mapper/vg-* 的 Logical Volume。

再看下目前硬盘的各个分区以及 LVM 是怎么配置的。(注意:一定要仔细看下现有LVM的配置,我刚开始由于粗心,一直误认为PV是做在了sda上,以为sdb是空硬盘啊,差点酿成大错!!!坑爹的配置,第一次见sda上只放一个引导区的。。。

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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
[root@lightdb ~]# fdisk -l

Disk /dev/sdb: 1000.2 GB, 1000204886016 bytes, 1953525168 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000e0ad8

Device Boot Start End Blocks Id System
/dev/sdb1 2048 1953523711 976760832 8e Linux LVM

Disk /dev/sda: 1000.2 GB, 1000204886016 bytes, 1953525168 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x00040a3c

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 514047 256000 83 Linux

Disk /dev/mapper/vg-root: 990.7 GB, 990665244672 bytes, 1934893056 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/vg-swap: 8455 MB, 8455716864 bytes, 16515072 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes


Disk /dev/mapper/vg-tmp: 1073 MB, 1073741824 bytes, 2097152 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes



### 一开始粗心没看好这里!!!也是太长时间不摸LVM了。。。

[root@lightdb ~]# pvdisplay
--- Physical volume ---
PV Name /dev/sdb1
VG Name vg
PV Size 931.51 GiB / not usable 4.00 MiB
Allocatable yes
PE Size 4.00 MiB
Total PE 238466
Free PE 1
Allocated PE 238465
PV UUID 3MQezN-W53d-oj6q-I3GK-9GdP-j4Yj-hag2Dj





[root@lightdb ~]# vgdisplay
--- Volume group ---
VG Name vg
System ID
Format lvm2
Metadata Areas 1
Metadata Sequence No 4
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 3
Open LV 3
Max PV 0
Cur PV 1
Act PV 1
VG Size <931.51 GiB
PE Size 4.00 MiB
Total PE 238466
Alloc PE / Size 238465 / 931.50 GiB
Free PE / Size 1 / 4.00 MiB
VG UUID dzuyJa-LoIn-ZUAY-BvEN-X13J-EYkc-daFgy5






[root@lightdb ~]# lvdisplay
--- Logical volume ---
LV Path /dev/vg/swap
LV Name swap
VG Name vg
LV UUID qNuaiE-j4mA-5dZJ-RsZZ-YqKX-4zSm-P2vWg8
LV Write Access read/write
LV Creation host, time lightdb, 2018-05-12 15:20:38 -0400
LV Status available
# open 2
LV Size <7.88 GiB
Current LE 2016
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:1

--- Logical volume ---
LV Path /dev/vg/tmp
LV Name tmp
VG Name vg
LV UUID oaS1B0-eSu3-s7B4-L2Dl-EUAs-wQxU-Ueohsl
LV Write Access read/write
LV Creation host, time lightdb, 2018-05-12 15:20:39 -0400
LV Status available
# open 1
LV Size 1.00 GiB
Current LE 256
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:2

--- Logical volume ---
LV Path /dev/vg/root
LV Name root
VG Name vg
LV UUID wqdC7u-9sg4-ekEH-dwR5-kBpy-PdN0-TXVcN0
LV Write Access read/write
LV Creation host, time lightdb, 2018-05-12 15:20:40 -0400
LV Status available
# open 1
LV Size <922.63 GiB
Current LE 236193
Segments 1
Allocation inherit
Read ahead sectors auto
- currently set to 256
Block device 253:0

/dev/sda 这个硬盘上创建新的分区,使用剩余的全部空间

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
[root@lightdb ~]# fdisk /dev/sda
Welcome to fdisk (util-linux 2.23.2).

Changes will remain in memory only, until you decide to write them.
Be careful before using the write command.


Command (m for help): t
Partition number (1,2, default 2): 2
Hex code (type L to list all codes): 8e
Changed type of partition 'Linux' to 'Linux LVM'

Command (m for help): p

Disk /dev/sda: 1000.2 GB, 1000204886016 bytes, 1953525168 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x00040a3c

Device Boot Start End Blocks Id System
/dev/sda1 * 2048 514047 256000 83 Linux
/dev/sda2 514048 1953525167 976505560 8e Linux LVM

Command (m for help): w
The partition table has been altered!

/dev/sda2 做成 PV

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
[root@lightdb ~]# pvcreate /dev/sda2 
Physical volume "/dev/sda2" successfully created.


[root@lightdb ~]# pvdisplay
--- Physical volume ---
PV Name /dev/sdb1
VG Name vg
PV Size 931.51 GiB / not usable 4.00 MiB
Allocatable yes
PE Size 4.00 MiB
Total PE 238466
Free PE 1
Allocated PE 238465
PV UUID 3MQezN-W53d-oj6q-I3GK-9GdP-j4Yj-hag2Dj

"/dev/sda2" is a new physical volume of "<931.27 GiB"
--- NEW Physical volume ---
PV Name /dev/sda2
VG Name
PV Size <931.27 GiB
Allocatable NO
PE Size 0
Total PE 0
Free PE 0
Allocated PE 0
PV UUID gGrU32-2AW0-10Sv-upON-cXEE-hqC9-fe3X76

给 VG 扩容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@lightdb ~]# vgextend vg /dev/sda2 
Volume group "vg" successfully extended

[root@lightdb ~]# vgdisplay
--- Volume group ---
VG Name vg
System ID
Format lvm2
Metadata Areas 2
Metadata Sequence No 6
VG Access read/write
VG Status resizable
MAX LV 0
Cur LV 3
Open LV 3
Max PV 0
Cur PV 2
Act PV 2
VG Size <1.82 TiB
PE Size 4.00 MiB
Total PE 476870
Alloc PE / Size 476870 / <1.82 TiB
Free PE / Size 0 / 0
VG UUID dzuyJa-LoIn-ZUAY-BvEN-X13J-EYkc-daFgy5

/dev/mapper/vg-root 这个 LV 扩容

1
2
3
4
[root@lightdb ~]# lvextend -l +100%FREE /dev/vg/root
Size of logical volume vg/root changed from <922.63 GiB (236193 extents) to 1.81 TiB (474598 extents).
Logical volume vg/root successfully resized.

刷新文件系统

1
2
3
4
5
[root@lightdb ~]# resize2fs /dev/vg/root
resize2fs 1.42.9 (28-Dec-2013)
Filesystem at /dev/vg/root is mounted on /; on-line resizing required
old_desc_blocks = 116, new_desc_blocks = 232
The filesystem on /dev/vg/root is now 485988352 blocks long.

查看下是否成功

1
2
3
4
5
6
7
8
9
10
[root@lightdb ~]# df -hP
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/vg-root 1.8T 1.2G 1.7T 1% /
devtmpfs 7.8G 0 7.8G 0% /dev
tmpfs 7.8G 0 7.8G 0% /dev/shm
tmpfs 7.8G 8.6M 7.8G 1% /run
tmpfs 7.8G 0 7.8G 0% /sys/fs/cgroup
/dev/sda1 243M 148M 83M 65% /boot
/dev/mapper/vg-tmp 976M 2.6M 907M 1% /tmp
tmpfs 1.6G 0 1.6G 0% /run/user/0

根目录已经是1.8T,扩容成功!

重启服务器看了下,也没有什么异常,OVER!


扩展阅读

简单说下什么是 LVM

LVMLogical Volume Manager 的简称。可以用来在物理硬盘上创建虚拟的卷,使服务器的硬盘动态扩容变的更加轻松。

其中这里面涉及到以下几个常用概念:

  • 物理卷(PV, Physical Volume)
    物理卷就是指磁盘,磁盘分区或从逻辑上和磁盘分区具有同样功能的设备(如RAID),是LVM的基本存储逻辑块,但和基本的物理存储介质(如分区、磁盘等)比较,却包含有和LVM相关的管理参数。当前LVM允许你在每个物理卷上保存这个物理卷的0至2份元数据拷贝.默认为1,保存在设备的开始处.为2时,在设备结束处保存第二份备份.

  • 卷组(VG, Volume Group)
    LVM卷组类似于非LVM系统中的物理硬盘,其由物理卷组成。能在卷组上创建一个或多个“LVM分区”(逻辑卷),LVM卷组由一个或多个物理卷组成。

  • 逻辑卷(LV, Logical Volume)
    LVM的逻辑卷类似于非LVM系统中的硬盘分区,在逻辑卷之上能建立文件系统(比如/home或/usr等)。

其实可以简单的理解为:PV相当于是传统硬盘的扇区,VG相当于是传统硬盘,LV是在VG上划分的逻辑分区。

Over!

0%