爲 zabbix 的 mysql 進行表分區

一. 線上 zabbix 數據庫的總數據量達到了180G,慢查詢日誌都是由於 history, history_uint 這兩個表引起的

mysql> selecttable_name,(data_length+index_length)/1024/1024 as total_mb, table_rows frominformation_schema.tables where table_name='history';
+------------+----------------+------------+
| table_name | total_mb       | table_rows |
+------------+----------------+------------+
| history   | 38281.28125000 |  225689376 |
+------------+----------------+------------+
1 row in set (0.00 sec)
 
mysql> selecttable_name,(data_length+index_length)/1024/1024 as total_mb, table_rows frominformation_schema.tables where table_name='history_uint';
+--------------+----------------+------------+
| table_name   | total_mb       | table_rows |
+--------------+----------------+------------+
| history_uint | 43551.34375000 |  189510565 |
+--------------+----------------+------------+
1 row in set (0.05 sec)

決定對history, history_uint 進行表分區,來降低 mysql 的壓力


注:在進行 mysql 分區時先在zabbix前端將 housekeeper 的功能關閉,關閉方法:

Administration --> General --> (右側下拉框) Housekeeping


二. 分區方法,zabbix 官方有 https://www.zabbix.org/wiki/Docs/howto/mysql_partition 

介紹,不過他們是使用存儲過程(函數)來完成的,這裏使用腳本來完成。

 

這裏線上環境是的情況是 mysql 雙主,把兩個 mysql 的複製線程都停掉,然後在 backup 上執行腳本,執行 OK 之後,讓 primary 去同步backup的數據

 

腳本實現的功能:

  1. history 重命名爲history_bak

  2. 根據分區粒度,分區的開始時間,分區的數量生成要分區的 sql 語句,建立新的分區的history

  3. 生成從 history_bak 裏抽取數據導入history 表的insert 語句,將舊錶的數據導入新表,生成的建表語句也是根據分區粒度來進行的

  4. 執行建表語句,創建history 爲分區表

 

5. 執行 update_history_1.sh 腳本將舊錶數據導入新表,這裏因數據大小不一致,時間可能不同,腳本如下

[root@zabbix_server zabbix_partitions]# cat partition.sh
#!/bin/bash
# 
export PATH=/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/root/bin

# day * 24 * 60 * 60
# 這個變量決定的是分區的粒度,3 代表每3天一個分區
part_interval=$[1*24*60*60]
# 這個變量決定的是分區的總數量,比如 part_interval 爲3,這裏爲60,那麼時間跨度將是180天
# ,分區數量有上限,別超過1024個
part_count=480
# 這個變量表示你的分區表要從哪一天開始,這個值最好是觀察一下,history 裏面最小的 clock 值
# ,然後轉換爲相關的時間
part_begin=20160209

part_times=$(date -d "${part_begin}" +%s)
#yet_date=$(date -d ${part_begin} +%Y-%m-%d)

# 生成分區表的字符串
part_string=$(
    for i in `seq 1 ${part_count}`;do
          # 根據 part_times 這個時間戳生成一個類似 20160901 的可讀性好的日期名
        part_name=$(date -d @${part_times} +%Y%m%d)

          # 同上,這個是在 UNIX_TIMESTAMP() 函數中要用的名字
        yet_date=$(date -d @${part_times} +%Y-%m-%d)

        if [ $i == $part_count ];then
            echo "partition p${part_name} values less than(UNIX_TIMESTAMP(\"${yet_date} 00:00:00\"))"   
        else
            echo "partition p${part_name} values less than(UNIX_TIMESTAMP(\"${yet_date} 00:00:00\")),"   
        fi 

          # 開始的時間 加上 間隔時間,生成新的時間戳
        part_times=$[part_times+part_interval]
    done
)
# 這裏如果你不願意去查一下你數據裏最早的時間,那麼就把第一個變量打開,第二個註釋掉,這樣他自己去查
#history_start=`mysql -Bse 'select clock from zabbix.history order by clock limit 1;'`
history_start=1455003811
#echo $history_start
now_date=$(date +%s)
insert_string=''
state=true
> /tmp/insert_zabbix.txt
while $state;do
    history_para=$[history_start+part_interval]
    diff_time=$[now_date-history_para]
    if [ ${diff_time} -ge ${part_interval} ];then
    
        insert_string="insert into zabbix.history select * from zabbix.history_bak where clock between ${history_start} and ${history_para};"
        echo "${insert_string}" >> /tmp/insert_zabbix.txt
    else
        insert_string="insert into zabbix.history select * from zabbix.history_bak where clock >= ${history_start};"
        echo "${insert_string}" >> /tmp/insert_zabbix.txt
        state=false
    fi
    history_start=$[history_para+1]
done

echo "$part_string" > /tmp/part_string.txt
echo "${insert_string}" 

echo "backup history to history_bak;"
/usr/local/mysql/bin/mysql zabbix -e 'alter table history rename history_bak;'

echo "create partions table history"
/usr/local/mysql/bin/mysql zabbix -e "CREATE TABLE history (
  itemid bigint(20) unsigned NOT NULL,
  clock int(11) NOT NULL DEFAULT '0',
  value double(16,4) NOT NULL DEFAULT '0.0000',
  ns int(11) NOT NULL DEFAULT '0',
  KEY history_1 (itemid,clock)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 partition by range (clock)(${part_string});"


腳本執行後,已經將 history 更改爲 history_bak, 建立了新的帶分區的 history了。而且在 /tmp/下生成了兩個文件

[root@zabbix_server tmp]# ls -l
total 1572
-rw-r--r-- 1 root   root     34541 Dec 29 15:42 insert_zabbix.txt
-rw-r--r-- 1 root   root     36959 Dec 29 15:42 part_string.txt

insert_zabbix.txt 是要將數據導入新表的語句

[root@zabbix_server tmp]# tail -n 5 insert_zabbix.txt 
insert into zabbix.history select * from zabbix.history_bak where clock between 1482479329 and 1482565729;
insert into zabbix.history select * from zabbix.history_bak where clock between 1482565730 and 1482652130;
insert into zabbix.history select * from zabbix.history_bak where clock between 1482652131 and 1482738531;
insert into zabbix.history select * from zabbix.history_bak where clock between 1482738532 and 1482824932;
insert into zabbix.history select * from zabbix.history_bak where clock >= 1482824933;

part_string.txt 是建表時的 partition 的字段:

[root@zabbix_server tmp]# tail -n 5 part_string.txt 
partition p20170529 values less than(UNIX_TIMESTAMP("2017-05-29 00:00:00")),
partition p20170530 values less than(UNIX_TIMESTAMP("2017-05-30 00:00:00")),
partition p20170531 values less than(UNIX_TIMESTAMP("2017-05-31 00:00:00")),
partition p20170601 values less than(UNIX_TIMESTAMP("2017-06-01 00:00:00")),
partition p20170602 values less than(UNIX_TIMESTAMP("2017-06-02 00:00:00"))

這樣作是有個記錄,當你不確定時,可以先把腳本現在的 mysql 註釋掉,然後運行下腳本,觀察下是否OK,再來進行 mysql 的操作。


6.再看將數據導入新表的腳本

[root@zabbix_server zabbix_partitions]# cat update_history_1.sh 
#!/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
> /tmp/$0.out
tac /tmp/insert_zabbix.txt | while read line;do
    echo `date` >> /tmp/$0.out
    mysql -e "$line" &>> /tmp/$0.out
    echo "$line" >> /tmp/$0.out
    echo "sleeping 60 seconds ......" >> /tmp/$0.out
    sleep 60
done

  在我這裏因爲數據量大,如果執行全表導入的話,可能會出現中斷,中斷之後也很難確定是在哪裏斷掉的,所以將語句分開執行,從最後一條開始執行的話,還不影響平常前端的觀察。sleep 60 秒的作用可以在執行完某一條語句後中斷腳本,這樣下次再開始腳本,也知道從哪裏繼續。

  如果你的數據量不大,你完全可以自己執行一個全裏的語句,導過來就好了。


注: 腳本使用事項:

  1. 建立自己的my.cnf 配置文件供鏈接mysql使用,我這裏配置在 /root/.my.cnf裏:

[root@dockertest zabbix_partition]# cat /root/.my.cnf 
[client]
user=root
host=172.18.0.3
password=******

你也可以在 mysql 那裏添加用戶名密碼選項。


2. 更改 PATH 環境變量,和自己想要定義的分區選項,比如分區粒度,分區數量等等。最好是在自己的測試環境裏先搞搞再說。即使你測試過了,也在生產環境中先備份一下再執行操作,安全第一。


三. 分區效果

  我這裏在數據導入大部分之後,mysql 的慢查詢日誌已經沒有新的記錄了。磁盤的 IO 也有所下降,比之前下降了 20% 左右。畢竟纔剛剛導入。還有待後續觀察。

  分區完成後,還要定時的更新分區。你可以選擇手動的到時更新一下。當然這裏爲了方便,已經寫好了腳本:

[root@zabbix_server zabbix_partition]# cat update_partition.sh 
#!/bin/bash
export PATH=/usr/local/mysql/bin:/usr/lib64/qt-3.3/bin:/bin:/usr/local/sbin:/usr/bin:/bin:/usr/sbin:/sbin:/root/bin

# 保留分區的天數,以此爲基準刪除180天以前的分區
KeepPartDays=180
KeepPartSeconds=$[KeepPartDays*24*60*60]
# 當前時間
now_seconds=$(date +%s)
# 新分區的時間粒度
part_interval=$[1*24*60*60]
# 以當前時間爲基準的未用的新分區的數量
new_parts=90
NewPartSeconds=$[now_seconds+new_parts*part_interval]
# 需要來更新分區的表名
part_table=(history history_uint)
#out_file=/tmp/${0/.sh/.out}
out_file=/tmp/$(basename ${0/.sh/.out})
mysql='mysql --defaults-file=/root/zabbix_partitions/.my.cnf'
part_max() {
    ${mysql} information_schema -Bse "select MAX(PARTITION_DESCRIPTION) from partitions where table_name='$1' and table_schema='zabbix';"
}
part_min() {
    ${mysql} information_schema -Bse "select MIN(PARTITION_DESCRIPTION) from partitions where table_name='$1' and table_schema='zabbix';"
}
echo "$(date) ------------------------------" >> ${out_file}
for i in ${part_table[@]};do
    ### delete old partitions
    old_part_desc=$(part_min "${i}")
    old_keep_seconds=$[now_seconds-KeepPartSeconds]
    old_part_name=$(${mysql} information_schema -Bse "select partition_name from partitions where table_name='${i}' and table_schema='zabbix' and PARTITION_DESCRIPTION<${old_keep_seconds};")
    #echo "${i}:${old_part_name}"
    if [[ -z ${old_part_name} ]];then
        echo "old_part_name is empty;"
        exit 10
    else
        echo ${old_part_name}
        for j in ${old_part_name};do
            echo "`date +%Y%m%d%H%M%S`:delete $i:$j" >> ${out_file} 
            ${mysql} zabbix -e "alter table ${i} drop partition ${j};" &>> ${out_file}
        done
    fi
    ### add new parittions
    last_part_desc=$(part_max "${i}")
    diff_seconds=$[NewPartSeconds-last_part_desc]
    new_part_num=$[diff_seconds/part_interval]
    new_part_desc=$[last_part_desc+part_interval]
    for ((k=1;k<=${new_part_num};k++));do
        new_part_name=p$(date -d @${new_part_desc} +%Y%m%d)

        add_statement="partition ${new_part_name} values less than(${new_part_desc})"

        echo "`date +%Y%m%d%H%M%S`:${i}:${k} ${new_part_name}:${add_statement}" >> ${out_file}
        ${mysql} zabbix -e "alter table ${i} add partition (partition ${new_part_name} values less than(${new_part_desc}));" &>> ${out_file}
        new_part_desc=$[new_part_desc+part_interval]
    done 
done

寫到計劃任務裏:

# update zabbix table partition
00 02 * * * /bin/bash /root/zabbix_partition/update_partition.sh

   好了,分區搞定。zabbix可以放心的存儲數據了。當然這個問題還有更好的解決實現方法。只不過這裏如此實現了而已。所以記錄一下也是好的。  

   此文章會在我遇到問題時逐步更新。謝謝觀看!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章