项目场景:
项目场景:企业级数据库服务器,Centos7系统BIOS设置为performance,但服务器却工作在powersave状态。
原因分析:
Centos7系统在启动时,BIOS程序会将CPU设置为performance工作模式,但随后启动的/usr/sbin/tuned
工具会根据调优配置文件将CPU模式更改为"conservative"
或者”powersave”
模式,这两种模式都是牺牲性能保证低功耗的工作模式;
后面在/etc/rc.local
中增加sed -i '/cpupower frequency-set -g performance/d' /etc/rc.d/rc.local
,但是rc.local
的执行也在tuend
启动之前:
而Centos6系统没有安装tuned工具,因此没有这个问题。
解决方案:
- 可以将
tuned
调优工具关闭 但是性能问题无法保障 - 将tuned调优选择工作在
performance
下,虽说cpu频率和吞吐量有所保障,但是延时却不低,下面是performance
的配置文件内容:
#
# tuned configuration
#
[main]
summary=Broadly applicable tuning that provides excellent performance across a variety of common server workloads
[cpu]
governor=performance
energy_perf_bias=performance
min_perf_pct=100
[disk]
readahead=>4096
[sysctl]
kernel.sched_min_granularity_ns = 10000000
kernel.sched_wakeup_granularity_ns = 15000000
vm.dirty_ratio = 40
vm.dirty_background_ratio = 10
vm.swappiness=10
readahead
:磁盘预读字节数,适量增加readahead值可以减少磁盘IO,但是如果readahead值过大则会占据过多的系统内存,导致系统性能下降。
kernel.sched_min_granularity_ns
:CPU最小时间片,增大CPU时间片可以提高吞吐量(即单位时间内完成的进程数),因为时间片越大,发生的上下文切换次数和时间越少。但是过大的时间片会导致程序响应速度变慢,因为进程需要等待更多的时间获取时间片。
kernel.sched_wakeup_granularity_ns
:唤醒进程时间,理论上来说,wakeup值应该比min时间更小,以确保进程唤醒时候,不至于得不到CPU时间片而再次挂起。但是对于多任务系统,同时被唤醒的进程可能有很多,但是CPU时间片却只有一个,因此进程被短时间重复挂起是不可避免的。因此weakup和min 的值要视服务器吞吐量和延时情况确定。
vm.dirty_ratio
:和vm.dirty_bytes是相互的,vm.dirty_ratio表示系统写缓存占主内存的百分比,而vm.dirty_bytes表示写缓存的大小,单位是字节。两个参数在系统中只能有一个生效,具体可以使用sysctl -a | grep vm.dirty_ratio或sysctl -a | grep vm.dirty_bytes查看谁生效。
vm.dirty_background_ratio
:和vm.dirty_background_bytes是相互的,前者表示系统后台所能用的脏页占总内存的百分比,后者表示内存脏页的具体大小,单位是字节,和dirty_ratia类似,这两个参数也只能有一个生效,具体查看方法和上面类似。
vm.swappiness
:影响系统在内存分页的时候,是将数据交换到磁盘上还是尽量保存在内存中,如果swappiness=0,该值的范围是0-100,表示在系统内存不足的时候,系统更愿意使用交换区而不是在内存使用率达到极限的时候才使用交换区。当swappiness=0的时候,内核将不会主动使用交换区。swappiness=100的时候,内核尽可能的使用交换区。
内存分页
是内存管理机制当中的一种,是虚拟存储系统中将物理内存和磁盘空间组合使用的一种技术,它将物理内存划分成一个个大小相等的页(4K),将虚拟地址空间也划分成大小相等的页,使得虚拟内存的页对应于物理内存的页,从而实现虚拟内存技术。在实际运行过程中,程序要访问虚拟内存地址,系统会将对应的虚拟页面从磁盘空间加载到物理内存一个空白页中,然后将该页的物理地址返回给程序,并将物理页面映射到该程序的虚拟页面中,下次程序访问该虚拟内存时系统直接返回物理地址而不需要再次进行虚拟页面加载。如果是内核程序或设备驱动程序直接访问物理内存的时出现此物理内存被其他程序占用的情况,系统则会进行内存交换或者页面置换。将此页空出来给内核或驱动程序使用。
- 可以自定义一个profile供tuned工具使用,例如需要提高磁盘读写效率
[main]
summary=Broadly applicable tuning that provides excellent performance across a variety of common server workloads
[cpu]
governor=performance
energy_perf_bias=performance
min_perf_pct=100
[disk]
readahead>= 4096
disk_ioscheduler=deadline
fs_aio_max_nr=1048576
fs_file-max=2097152
vm.dirty_background_bytes=536870912
vm.dirty_bytes=2147483648
该配置文件增加readahead 值来提高磁盘读取性能 将 vm.dirty_background_bytes 和 vm.dirty_bytes 参数的值调整为较高的数值,以减少写操作
一个简单的系统调优工具-- mytuning
首先实现一个磁盘IO监控程序,获取系统磁盘读写io负载已经最大负载。
//monitor_disk.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
typedef struct {
char dev[32];
int old[11];
int new[11];
int max[11];
int diff[11];
} device;
typedef struct {
device devices[128];
int count;
} disk_monitor;
void update_stat(device* dev) {
char stat_file[128];
sprintf(stat_file, "/sys/block/%s/stat", dev->dev);
FILE* file = fopen(stat_file, "r");
char buffer[256];
fgets(buffer, sizeof(buffer), file);
fclose(file);
char* tok = strtok(buffer, " ");
for (int i = 0; i < 11; i++) {
dev->old[i] = dev->new[i];
dev->new[i] = atoi(tok);
tok = strtok(NULL, " ");
}
}
void update(disk_monitor* dm) {
for (int i = 0; i < dm->count; i++) {
update_stat(&dm->devices[i]);
for (int j = 0; j < 11; j++) {
dm->devices[i].diff[j] = dm->devices[i].new[j] - dm->devices[i].old[j];
if (dm->devices[i].diff[j] > dm->devices[i].max[j]) {
dm->devices[i].max[j] = dm->devices[i].diff[j];
}
}
}
}
void init(disk_monitor* dm) {
dm->count = 0;
DIR* dir = opendir("/sys/block");
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_name[0] == '.') {
continue;
}
char vendor_file[128];
sprintf(vendor_file, "/sys/block/%s/device/vendor", entry->d_name);
FILE* file = fopen(vendor_file, "r");
char buffer[128];
fgets(buffer, sizeof(buffer), file);
fclose(file);
if (strcmp(buffer, "ATA\n") == 0 || strcmp(buffer, "SCSI\n") == 0) {
device* dev = &dm->devices[dm->count];
strncpy(dev->dev, entry->d_name, sizeof(dev->dev));
memset(dev->old, 0, sizeof(dev->old));
memset(dev->new, 0, sizeof(dev->new));
memset(dev->max, 1, sizeof(dev->max));
memset(dev->diff, 0, sizeof(dev->diff));
update_stat(dev);
dm->count++;
}
}
closedir(dir);
}
void cleanup(disk_monitor* dm) {}
void get_load(disk_monitor* dm, char* buffer) {
update(dm);
int offset = sprintf(buffer, "{ \"DISK\": { ");
for (int i = 0; i < dm->count; i++) {
device* dev = &dm->devices[i];
offset += sprintf(buffer + offset, "\"%s\": { \"READ\": %f, \"WRITE\": %f }",
dev->dev, (float) dev->diff[1] / (float) dev->max[1],
(float) dev->diff[5] / (float) dev->max[5]);
if (i < dm->count - 1) {
offset += sprintf(buffer + offset, ", ");
/* plugin_disk.c 微调器 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Idle {
int read;
int write;
int level;
};
struct DiskTuning {
struct Idle *devidle;
int count;
};
void updateIdle(struct DiskTuning *diskTuning, char *dev, float read, float write) {
struct Idle *idle = &diskTuning->devidle[diskTuning->count++];
idle->level = 0;
idle->read = (read == 0.0) ? (idle->read + 1) : 0;
idle->write = (write == 0.0) ? (idle->write + 1) : 0;
}
void init(struct DiskTuning *diskTuning, int config) {
diskTuning->devidle = (struct Idle *)calloc(config, sizeof(struct Idle));
diskTuning->count = 0;
}
void cleanup(struct DiskTuning *diskTuning) {
for (int i = 0; i < diskTuning->count; i++) {
char command[50];
sprintf(command, "hdparm -S0 -B255 /dev/%d > /dev/null 2>&1", i);
system(command);
}
}
void setTuning(struct DiskTuning *diskTuning, float *load) {
int count = 0;
for (int i = 0; i < 100; i++) {
if (load[i] != 0.0) {
updateIdle(diskTuning, (char *)i, load[i], load[i+100]);
count++;
}
}
struct Idle *idle = diskTuning->devidle;
for (int i = 0; i < count; i++, idle++) {
if (idle->level == 0 && idle->read >= 30 && idle->write >= 30) {
idle->level = 1;
char command[50];
sprintf(command, "hdparm -Y -S60 -B1 /dev/%d > /dev/null 2>&1", i);
system(command);
}
if (idle->level > 0 && (idle->read == 0 || idle->write == 0)) {
idle->level = 0;
char command[50];
sprintf(command, "hdparm -S255 -B127 /dev/%d > /dev/null 2>&1", i);
system(command);
}
}
}
int main() {
struct DiskTuning diskTuning;
init(&diskTuning, 10);
float load[200] = {1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0
/*. tuning.c 程序入口 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <time.h>
#include <signal.h>
#include <unistd.h>
#include <dirent.h>
#include "config.h"
#define MAX_PLUGINS 50
struct plugin {
void *handle;
void (*init)(config_t *config);
void (*setTuning)(config_t *config, load_t *load);
void (*cleanup)();
};
struct tuned {
int interval;
struct plugin mp[MAX_PLUGINS];
struct plugin tp[MAX_PLUGINS];
};
void __initplugins__(const char *path, const char *module, struct plugin *store, config_t *config) {
DIR *dir;
struct dirent *ent;
char buf[512], *p;
int count = 0;
dir = opendir(path);
if (dir == NULL) {
fprintf(stderr, "Error: Failed to open directory %s\n", path);
exit(1);
}
while ((ent = readdir(dir)) != NULL) {
if (ent->d_type == DT_REG && strlen(ent->d_name) > 3 &&
strcmp(ent->d_name + strlen(ent->d_name) - 3, ".so") == 0) {
snprintf(buf, sizeof(buf), "%s/%s", path, ent->d_name);
p = strstr(ent->d_name, "_");
if (p != NULL) {
*p = '\0';
snprintf(buf, sizeof(buf), "%s/%s.so", path, ent->d_name);
store[count].handle = dlopen(buf, RTLD_NOW);
if (store[count].handle == NULL) {
fprintf(stderr, "Error: Failed to load plugin %s: %s\n", ent->d_name, dlerror());
exit(1);
}
store[count].init = dlsym(store[count].handle, "init");
store[count].setTuning = dlsym(store[count].handle, "setTuning");
store[count].cleanup = dlsym(store[count].handle, "cleanup");
if (store[count].init == NULL || store[count].setTuning == NULL || store[count].cleanup == NULL) {
fprintf(stderr, "Error: Invalid plugin %s\n", ent->d_name);
exit(1);
}
store[count].init(config);
count++;
if (count >= MAX_PLUGINS) {
fprintf(stderr, "Error: Too many plugins\n");
exit(1);
}
}
}
}
closedir(dir);
}
void cleanup(int signum) {
printf("Cleanup...\n");
for (int i = 0; i < MAX_PLUGINS; i++) {
if (tuned.mp[i].cleanup != NULL) {
tuned.mp[i].cleanup();
}
}
for (int i = 0; i < MAX_PLUGINS; i++) {
if (tuned.tp[i].cleanup != NULL) {
tuned.tp[i].cleanup();
}
}
exit(0);
}
int main(int argc, char *argv[]) {
config_t config;
load_t load;
signal(SIGTERM, cleanup);
if (argc != 2) {
fprintf(stderr, "Usage: %s <config file>\n", argv[0]);
exit(1);
}
if (config_read_file(&config
下面是一个简易的文件解析器:
#include <stdio.h>
#include <string.h>
#define MAX_LINE_LENGTH 100
#define MAX_KEY_LENGTH 50
#define MAX_VALUE_LENGTH 50
typedef struct {
char key[MAX_KEY_LENGTH];
char value[MAX_VALUE_LENGTH];
} ConfigItem;
int parse_config_file(const char *filename, ConfigItem *config_items, int max_items) {
FILE *file;
char line[MAX_LINE_LENGTH];
int count = 0;
file = fopen(filename, "r");
if (file == NULL) {
printf("Failed to open file.");
return 1;
}
while (fgets(line, MAX_LINE_LENGTH, file) != NULL) {
char key[MAX_KEY_LENGTH];
char value[MAX_VALUE_LENGTH];
if (sscanf(line, "%[^=]=%s", key, value) == 2) {
if (line[0] == '#') {
continue; // 如果该行以#开头,则跳过不进行处理
}
ConfigItem item;
strncpy(item.key, key, MAX_KEY_LENGTH);
strncpy(item.value, value, MAX_VALUE_LENGTH);
config_items[count++] = item;
if (count == max_items) {
break;
}
}
}
fclose(file);
return 0;
}
int main() {
char filename[] = "/Users/wyl/users/works/test.txt";
int max_item = 50;
ConfigItem config_items[max_item];
int index = 0;
if (!parse_config_file(filename, config_items, max_item)) {
while (config_items[index].key[0] != '\0') {
printf("%s=%s\n", config_items[index].key, config_items[index].value);
index++;
}
}
return 0;
}
未完待续