0%

问题发现

背景

  • uwsgi version: 2.0.13.1
  • python version: 2.6.6
  • 采用 Nginx + uWSGI + Python 架构,均拥有ROOT权限

现象

Python执行os.mkdir操作,提示“Permission Denied”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Traceback (most recent call last):
File "/cs/nginx/uwsgi/main.py", line 24, in application
result = process().entry(environ, start_response)
File "/cs/nginx/uwsgi/rds/base_mng.py", line 90, in entry
self.process_put_request(env)
File "/cs/nginx/uwsgi/rds/init_rds.py", line 149, in process_put_request
result = self.do_put_request(env)
File "/cs/nginx/uwsgi/rds/init_rds.py", line 140, in do_put_request
err_code = op.do()
File "/cs/nginx/uwsgi/rds/init_rds.py", line 70, in do
if 0 == init_create_mp(mp):
File "/cs/nginx/uwsgi/rds/init/main.py", line 44, in init_create_mp
os.mkdir(mp,0755)
OSError: [Errno 13] Permission denied: '/aaab'

解决

Python脚本的权限,有uWSGI来决定,所以该错误肯定跟uWSGI有关,通过调整uwsgi.ini文件中的用户和权限相关配置发现,将cap = setuid,setgid去掉,可以正常创建目录。

但:具体原因不明,可能跟setuid和setgid的使用有关,有可能是个Bug

问题发现

现象

在nginx+uwsgi+python环境中,启动mysqld服务,发生服务无法正常启动现象(闪退)。而在shell终端中可以正常启动mysqld服务。说明“nginx+uwsgi+python”这个环境限制了某些资源,导致mysqld服务启动失败。

uwsgi错误日志(/var/log/uwsgi.log)

1
Starting MySQL. ERROR! The server quit without updating PID file (/store/xxx.pid)

mysql错误日志(/{mysqldatadir}/{hostname}.err)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
2016-09-22 16:21:41 28084 [Note] InnoDB: Using atomics to ref count buffer pool pages
2016-09-22 16:21:41 28084 [Note] InnoDB: The InnoDB memory heap is disabled
2016-09-22 16:21:41 28084 [Note] InnoDB: Mutexes and rw_locks use GCC atomic builtins
2016-09-22 16:21:41 28084 [Note] InnoDB: Memory barrier is not used
2016-09-22 16:21:41 28084 [Note] InnoDB: Compressed tables use zlib 1.2.3
2016-09-22 16:21:41 28084 [Note] InnoDB: Using CPU crc32 instructions
2016-09-22 16:21:41 28084 [Note] InnoDB: Initializing buffer pool, size = 1.0G
InnoDB: mmap(137363456 bytes) failed; errno 12
2016-09-22 16:21:41 28084 [ERROR] InnoDB: Cannot allocate memory for the buffer pool
2016-09-22 16:21:41 28084 [ERROR] Plugin 'InnoDB' init function returned error.
2016-09-22 16:21:41 28084 [ERROR] Plugin 'InnoDB' registration as a STORAGE ENGINE failed.
2016-09-22 16:21:41 28084 [ERROR] Unknown/unsupported storage engine: InnoDB
2016-09-22 16:21:41 28084 [ERROR] Aborting
2016-09-22 16:21:41 28084 [Note] Binlog end

分析

根据mysql错误日志提示,mysql在初始化InnoDB引擎时,申请“Buffer Pool”内存失败,此处的mmap是用来申请内存的(allocate申请内存时,小于128KB的在栈上进行分配内存,大于128K的,从堆上分配内存)。
使用shell终端正常启动mysqld服务,查看mysqld占用内存大小发现需要2GB左右的虚拟内存,如下所示:

1
2
3
[root@host]# ps aux | grep mysqld | grep -v grep
root 31635 0.0 0.1 11300 1520 ? S Sep22 0:00 /bin/sh /cs/mysql/bin/mysqld_safe --datadir=/store --pid-file=/store/zhoubo.pid
root 32165 0.0 25.5 2041620 259716 ? Sl Sep22 0:15 /cs/mysql/bin/mysqld --basedir=/cs/mysql --datadir=/store --plugin-dir=/cs/mysql/lib/plugin --user=root --log-error=/store/zhoubo.err --open-files-limit=65535 --pid-file=/store/zhoubo.pid --socket=/cs/mysql/data/mysql.sock --port=3306

由此可以看出,“nginx+uwsgi+python”这个组合在其中某个环节对内存做了内存限制导致内存不能正常分配;nginx父进程为init,uwsgi父进程也为init,二者之间使用管道进行通信,所以不可能由nginx限制uwsgi进程资源。而uwsgi以fork形式调用python,fork过程中可以限制资源,python进程的资源被限制后再启动mysqld服务,就会导致mysqld分配内存失败,从而出现上述的错误现象。

解决

根据上诉分析,找到了问题的原因出在uwsgi对进程资源进行了限制,所以将uwsgi.ini中关于资源限制的配置去掉,即可解决。
在uwsgi.ini中去掉limit-as 256选项。

原理

uwsgi.ini中资源限制项包括limit-aslimit-nproclimit-port等,他们对进程资源的限制的访问获取都是通过系统接口getrlimt和setrlimit来实现的(POSIX API)。

1
2
3
4
5
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,struct rlimit *old_limit);

核心结构体

1
2
3
4
struct rlimit{
rlim_t rlim_cur; // soft limit
rlim_t rlim_max; // hard limit
}

rlim_cur

为soft limit是指内核所能支持的资源上限。比如对于RLIMIT_NOFILE(一个进程能打开的最大文件数,内核默认是1024),soft limit最大也只能达到1024。对于RLIMIT_CORE(core文件的大小,内核不做限制),soft limit最大能是unlimited。

rlim_max

为hard limit在资源中只是作为soft limit的上限。当你设置hard limit后,你以后设置的soft limit只能小于hard limit。要说明的是,hard limit只针对非特权进程,也就是进程的有效用户ID(effective user ID)不是0的进程。具有特权级别的进程(具有属性CAP_SYS_RESOURCE),soft limit则只有内核上限。

可限制资源列表

资源 描述
RLIMIT_AS 进程的最大虚拟内存空间,字节为单位
RLIMIT_CORE 内核转存文件的最大长度
RLIMIT_CPU 最大允许的CPU使用时间,秒为单位
RLIMIT_DATA 进程数据段的最大
RLIMIT_FSIZE 进程可建立的文件的最大长度
RLIMIT_LOCKS 进程可建立的锁和租赁的最大值
RLIMIT_MEMLOCK 进程可锁定在内存中的最大数据量,字节为单位
RLIMIT_MSGQUEUE 进程可为POSIX消息队列分配的最大字节数
RLIMIT_NICE 进程可通过setpriority() 或 nice()调用设置的最大完美值
RLIMIT_NOFILE 指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误
RLIMIT_NPROC 用户可拥有的最大进程数
RLIMIT_RTPRIO 进程可通过sched_setscheduler 和 sched_setparam设置的最大实时优先级
RLIMIT_SIGPENDING 用户可拥有的最大挂起信号数
RLIMIT_STACK 最大的进程堆栈,以字节为单位

参考及鸣谢

背景

使用python2.7

依赖库

  • gcc
    需要安装gcc编译器,用于编译_mysql.c

  • python
    安装python-devel解决找不到“Python.h”问题

  • mysql
    安装 mysql-community-devel

环境

OS

RHEL7.2

步骤

1、安装mysql,详细见mysql官网

2、启动mysql service

rhel7.2中,使用service mysqld start启动mysql服务。【status:查服务状态;stop:停止服务;restart:重启服务】

3、修改root密码

  • 首先使客户端登陆掉过密码检测,然后重启mysql服务
    echo "skip-grant-tables" >> /etc/my.cnf

  • 使用mysql -uroot -p登陆,然后修改mysql库中的user
    update user set password_expired="N" where user="root";
    update user set authentication_string=password("hnagroup") where user="root";

  • 最后删除/etc/my.cnf中的skip-grant-tables,去掉客户端跳过密码检测设置

概述

ceph-deploy官方认可的ceph部署工具。它不同于ansible、puppet。

架构

ceph-deploy-frame

各个模块

parser

主命令参数解析,子命令参数解析

cli

加载子命令模块,选择调用哪个子命令执行操作

new 子命令

创建集群,及 ceph.conf 配置文件

install 子命令

跟据节点的不通角色(mon、mds、osd、rgw等),安装相关的软件包

uninstall 子命令

卸载节点的ceph软件

purge 子命令

清除ceph安装包

purgedata子命令

清除ceph在该节点上产生的数据

mon 子命令

管理mon节点,添加、创建、删除等操作

gatherkeys子命令

收集ceph中的keyring

osd子命令

管理osd节点,准备、创建、激活等操作

disk子命令

管理硬盘,格式化硬盘分区等操作

mds子命令

管理mds节点,仅支持创建操作

forgetkeys子命令

删除“mon”、“client.admin”、“bootstrap-osd”、“bootstrap-mds”、“bootstrap-rgw”的keyring文件

config子命令

从(往)指定的节点获取(输出)集群配置信息

admin子命令

为指定的节点赋予admin角色

pkg子命令

包管理器,安装(卸载)指定的软件包

calamari子命令

rgw子命令

按配置文件中的配置启动rgw服务(建议使用CivetWeb)

repo子命令

管理节点repo信息,添加、删除repo操作

conf

ceph.conf集群配置文件读写、解析等支持类操作

host

按不同操作系统进行区别操作

lib

与系统相关的操作类库(如:连接管理、命令执行、文件操作等)

util

支持库(如:ssh连接管理、服务管理、RPM包管理、APT包管理、日志管理等)

概述

SimpleMessenger,继承了Messenger类,用于实现各个模块间传输命令及数据。

架构图

ceph-messenger-simple-frame

通讯协议

子模块

  • Pipes模块
    用于管理tcp网络链接,负责接受、发送数据。
  • Accepter模块
    仅用于接受外部请求链接,而不接收数据。将接收到的链接保存到Pipes模块中,然后由Pipes模块去收发数据。
  • DispatchQueue模块
    接收外部发送来的数据和命令,然后通过“dispatcher”或“fast dispatcher”模块,将数据和命令传送给各个dispatcher
  • dispatcher模块
    用于管理dispatchers,如OSD,MON,MDS等等各种需要通讯的client。
  • fast dispatcher模块
    用于管理 fast dispatchers

持久性

持久性是数据丢失的概率,可以用于度量一个存储系统的可靠性,俗称 “多少个9”。数据的放置(DataPlacement)决定了数据持久性,而Ceph的CRUSH MAP又决定了数据的放置,因此CRUSH MAP的设置决定了数据持久性。

数学模型

公式

P = Pr x M / C(R,N)

解释

P

P为丢失数据的概率,持久性,可用1-P来计算。

Pr

Pr = P1(any) x P2(any) x P3(any)

Pr为一年内R(ceph副本数)个OSD发生故障的概率。

  • P1(any)为一年内第一个OSD发生故障的概率
    硬盘在一定时间内的失败概率符合Possion(伯松)分布 Pn(入,t)(为lamda),由于我们不太容易直接计算任意一个OSD顺坏的概率,但可以计算出没有OSD出现故障的概率,再用1减去无OSD节点故障的概率,就得到了P1(any)
    入=FIT x NFIT=AFR/(24×365),AFR为硬盘年故障概率;
    t为一年的小时数,24x365

  • P2(any)为一个OSD恢复周期内第二个OSD发生故障的概率
    中的N为N-1
    t 为一个OSD恢复周期,OSD恢复周期 = 恢复数据量 / 恢复速度恢复数据量 = 硬盘容量 x 使用率恢复速度 = 每个OSD写速度 x 参与恢复的OSD数量

  • P3(any)为一个OSD恢复周期内第三个OSD发生故障的概率
    中的N为N-2
    t 为一个OSD恢复周期,算法同上

M

Copy Set个数,copy set上至少有一个PG的所有副本。
丢失数据必须是主副本数据同时丢失,数据不可恢复才算。而一个copy set,包含一个PG的所有主副本数据,所以一个copy set损坏(或丢失)导致至少一个PG的主副本数据丢失,数据不可恢复。

C(R,N)

N为OSD数量(一个OSD对应一个硬盘),R为副本数,C(R,N)为N个OSD中任意挑选R个OSD的组合数。

优化

综上所述,我们能调节的参数包括:

  • OSD恢复周期(recovery time)
    增加参与恢复的OSD数量可以缩短OSD恢复周期,从而降低丢失数据的概率
  • copy set个数
    减少copy set个数,可以降低丢失数据的概率

以上优化需要修改Crush map

使用prmc计算持久性

获取prmc

从github上下载prmc代码

1
git clone https://github.com/zhoubofsy/persistence_calculation.git

配置输入参数

修改config.py文件中的prmc字典

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
# 算法名称
name = 'prmc'

prmc = {
# 副本数
'replica_num' : 3,

# 集群OSD总数数量
'osd_num' : 14,

# AFR(硬盘年故障率)
'disk_afr' : 0.017,

# 磁盘平均容量(MB)
'disk_capacity' : 1000.0,

# 磁盘平均写速度(MB/s)
'disk_writerate' : 50.0,

# 磁盘使用率
'disk_usage' : 0.75,

# 一个Host中OSD的数量
'num_osd_in_host' : 4,

# 一个副本域的host数量
'num_host_in_replic_domain' : 1,

# rack数量
'num_rack_in_root' : 4,

# 副本域数量
'num_replic_domain_in_root' : 1
}

程序会根据name的设置,选择相关的算法,不同的算法会采用不同的配置参数,目前只支持prmc算法

执行计算

执行main.py进行计算持久性

1
./main.py

参考&鸣谢

OSD配置优化

Filestore

参数名 描述 默认值 推荐值
filestore max sync interval 从日志到数据盘最大同步间隔(秒) 5 15
filestore min sync interval 从日志到数据盘最小同步间隔(秒) 0.1 10
filestore queue max ops 最大未完成io数 50 25000
filestore queue max bytes 最大未完成io字节数(B) 100 << 20 10 << 20
filestore queue committing max ops 数据盘能够commit的操作数 500 5000
filestore queue committing max bytes 数据盘能够commit的最大字节数(B) 100 << 20 1000 << 20
filestore op threads 并发文件系统操作线程数 2 32
filestore fd cache size 对象文件句柄缓存大小 128 8192
filestore wbthrottle_xfs_bytes_start_flusher xfs文件系统开始执行回刷的脏数据 41943040 90 << 20
filestore wbthrottle_xfs_bytes_hard_limit xfs文件系统允许的最大脏数据 419430400 500 << 20

Journal

参数名 描述 默认值 推荐值
osd journal size OSD 日志大小(MB) 5120 20000
journal max write bytes 日志一次异步io的最大字节数(B) 10 << 20 1 << 30
journal max write entries 日志一次异步io的最大记录数 100 10000

Recovery

None

PG

PGs = (Total_number_of_OSD * 100) / max_replication_count
如果有15个OSD,副本数为3,根据公式计算PGs为500,最接近512,所以需要设置该pool的pg_numpgp_num都为512

1
2
ceph osd pool set {pool name} pg_num 512
ceph osd pool set {pool name} pgp_num 512

CRUSH Map

Todo…

Client

参数名 描述 默认值 推荐值
objecter inflight ops 客户端允许的最大未发送io请求数 1024 1 << 20
objecter inflight op bytes 客户端允许的最大未发送脏数据 100 << 20 10 << 30

Other

参数名 描述 默认值 推荐值
osd max write size OSD一次可写入的最大值(MB) 90 512
osd client message size 客户端允许在内存中的最大数据(B) 500 << 20 2 << 30
osd deep scrub stride 在Deep Scrub时允许读取的字节数(B) 512 << 10 128 << 10
osd op threads OSD进程操作的线程数 2 8
osd disk threads OSD恢复和Scrubbing时的线程数 1 4
osd map cache size OSD Map 的缓存(MB) 200 1024
ms_dispatch_throttle_bytes 控制DispatcherQueue队列深度大小 100 << 20 1 << 30

参考&鸣谢

目标

  • 优化OSD节点性能
  • 关闭OSD节点瓶颈项

优化项

CPU Status

将cpu status 设置为 performance,可通过BIOS设置,或在系统中进行如下配置,修改后需要重启生效

1
for CPUFREQ in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor; do [ -f $CPUFREQ ] || continue; echo -n performance > $CPUFREQ; done

Hyper-Threading(HT)

开启VT和HT,在BIOS中配置

NUMA

关闭NUMA,可通过BIOS关闭,也可在加载Kernel是设置
打开/etc/grub2-efi.cfg,修改kernel加载部分,修改后需要重启生效

1
linuxefi /vmlinuz-3.10.0-327.el7.x86_64 root=/dev/mapper/rhel-root ro crashkernel=auto rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet LANG=en_US.UTF-8 numa=off

关闭前

1
2
3
4
$ lscpu | grep -i numa
NUMA node(s): 2
NUMA node0 CPU(s): 0-5,12-17
NUMA node1 CPU(s): 6-11,18-23

关闭后

1
2
3
$ lscpu | grep -i numa
NUMA node(s): 1
NUMA node0 CPU(s): 0-23

Jumbo Frames

将cluster network 的网卡MTU设置为9000,修改后需要重启生效

1
echo "MTU=9000" | tee -a /etc/sysconfig/network-script/ifcfg-{网卡}

通过ifconfig查看mtu修改情况

1
2
3
4
5
6
7
8
9
$ ifconfig
ens5f0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 9000
inet 192.168.43.5 netmask 255.255.255.0 broadcast 192.168.43.255
inet6 fe80::92e2:baff:febf:3800 prefixlen 64 scopeid 0x20<link>
ether 90:e2:ba:bf:38:00 txqueuelen 1000 (Ethernet)
RX packets 5218040 bytes 661253942 (630.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 5170015 bytes 605082933 (577.0 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

SSD Trim

为减少SSD写放大,需要开启Trim。
判断SSD是否支持Trim

1
2
$ hdparm -I /dev/{SSD硬盘} | grep -i trim
* Data Set Management TRIM supported (limit 8 blocks)

出现 “TRIM supported” 表示支持Trim,否则表示不支持Trim指令.

在xfs文件系统中开启trim指令,需要挂载xfs文件系统时,option中增加discard

Scheduler

硬盘I/O调度机制配置,SSD 推荐采用“noop”,机械硬盘推荐采用“deadline”
修改/etc/udev/rules.d/60-schedulers.rules,增加如下:

1
2
3
# system default: set cfq scheduler for rotating disks
ACTION=="add|change",KERNEL=="sd[a-z]",ATTR{queue/rotational}=="1",ATTR{queue/scheduler}="deadline"
ACTION=="add|change",KERNEL=="sd[a-z]",ATTR{queue/rotational}=="0",ATTR{queue/scheduler}="noop"

rotational 为 “1” 表示机械硬盘,为“0”表示SSD

查看修改是否生效

1
cat /sys/block/{硬盘}/queue/scheduler

read_ahead_kb

设置硬盘预读大小为8k
修改/etc/udev/rules.d/60-schedulers.rules,增加如下:

1
ACTION=="add|change",KERNEL=="sd[b-z]",ATTR{queue/read_ahead_kb}="8192"

查看是否生效

1
cat /sys/block/{硬盘}/queue/read_ahead_kb

xfs option

xfs 挂载增加如下option:

rw 允许读写操作
noexec 不允许运行操作
nodev 不支持设备文件
noatime 不更新inode访问时间
nobarrier 关闭cache 回写机制

FD,core & PID

FD & core

修改 max open files 最大值,系统默认是1024,将其修改为131072
修改 core 值,当程序崩溃时,会输出core文件,便于分析错误原因
修改/etc/security/limits.conf文件,增加:

1
2
3
*   soft    core    10240
* soft nofile 131072
* hard nofile 131072

修改后,可通过ulimit -a查看。

PID

修改pid max值,打破系统运行进程数量瓶颈,将其设置为4194303(系统默认是 32768)
修改/etc/sysctl.conf文件,增加kernel.pid_max=4194303

cgroup

若集群搭建采用超融合方案(计算节点与存储节点混搭),使用cgroup将cpu与OSD进程绑定,减少cpu时间片切换次数,提高OSD相应速度

参考&鸣谢