或许你也发现了,Linux的磁盘休眠调度有一些小问题,即使设置了休眠参数,过了一段时间后机械硬盘还是不会休眠 QAQ

虽然搜索引擎给出的答案是利用hdparm设置磁盘休眠参数,例如sudo hdparm -S 600 /dev/sdX,但是实际操作过后,你很有可能会发现并没有效果,过了设置的时间,硬盘还是很欢快的在转呀转。。。。。。
但是,当你执行hdparm -y /dev/sda后,硬盘sda真的就进入standby模式了,也就是休眠停转了!
这就说明实际上hdparm是可以控制硬盘进入休眠的,不过不知道因为什么玄学原因,导致不能触发休眠条件。

获取硬盘的活跃状态

首先,在确定一块硬盘是不是要被休眠之前,我们要参考这段时间这个硬盘的I/O吞吐量,不然给正在进行I/O负载下达休眠命令,硬盘会先停转又起转,这是在谋杀硬盘!
iostat是一个可以检测硬盘当前I/O吞吐量的软件,可以通过软件包管理器安装到系统,例如yum install iostat,它将会是检测过程中的得力助手。
装好iostat后,你可以运行iostat查看硬盘使用的情况:

[chocola@Kagura-Nana-Server ~]$ iostat
Linux 4.18.0-513.18.1.el8_9.x86_64 (Kagura-Nana-Server)         2024年03月07日  _x86_64_        (4 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          12.72    0.03    5.60    1.25    0.00   80.39

Device             tps    kB_read/s    kB_wrtn/s    kB_read    kB_wrtn
sdb               1.36        72.77       515.77   69239718  490735104
sda               1.43        73.06       516.63   69513181  491549676
sde               9.02        96.45       405.26   91764005  385587245
sdc               1.36        72.27       515.41   68761436  490393888
sdf               0.13         5.45        78.91    5182342   75077936
sdd               1.35        71.62       514.72   68146499  489737476
md127             5.29       235.52      2055.79  224085481 1956003528

我们要利用的,就是kB_readkB_wrtn这两列数据,分别对应的是总读取量总写入量
我们可以通过这两个参数一段时间后的数值前后对比,判断硬盘的活跃状态。
如果一段时间内总读取量和总写入量没有变化,那就说明这块硬盘这段时间内没有被使用,那就可以操作它休眠,我个人喜好是设置这个时间间隔为20分钟

通过Python获取并处理得到各个硬盘活跃状态

我们要实现根据活跃状态让硬盘进入休眠,首先是要获取并分析硬盘的活跃状态,对此,我选择用python来实现这个功能,因为大部分Linux发行版都带有python环境,而且python的功能多,语法相对简单。
首先是构建一个函数,用于获取系统硬盘的I/O信息:

import subprocess
import os
import time


def get_io():
    result = subprocess.run("iostat",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8",timeout=1) #获取命令行执行后的结果
    lines = result.stdout.split('\n') #根据每一行的内容,把结果转换成列表
    lines = lines[6:-3] #抛弃掉不需要的部分,我们只需要中间的数据

    extracted_values = []
    for item in lines:
    # 分割字符串以空格为分隔符

        parts = item.split()

    # 提取设备名称(第一个值)

        device_name = parts[0]

    # 提取最后两个数字值,对应的是总读取量和总写入量

        try:

            last_two_numbers = [float(parts[-2]), float(parts[-1])]

        except ValueError:

        # 如果最后两个值不是数字,跳过这个项

            continue

    # 将提取的值添加到列表中

        extracted_values.append((device_name, last_two_numbers))


    return extracted_values

这样,我们就得到了一个硬盘状态的列表,大概是这个样子:

[root@Kagura-Nana-Server ~]# python3 test.py 
[('sdb', [70170310.0, 490735104.0]), ('sda', [70445044.0, 491549688.0]), ('sde', [91788724.0, 386117289.0]), ('sdc', [69691744.0, 490393900.0]), ('sdf', [5182342.0, 75077936.0]), ('sdd', [69076806.0, 489737476.0])]

用Python写出判断硬盘前后读写状态的函数,符合条件后休眠磁盘

我们需要对这个函数传入前后的读写量,并且进行对比,如果没有变化就休眠硬盘。
注意!在实际部署中,即使没有其他程序使用硬盘,一段时间后也会有微弱的读取量,所以偏差的阈值可以调得大一点。

import subprocess
import os
import time
def shutdown_hdd(dev_io,dev_io_per):
    count=0  #dev_io [('sdb', [read:666582.0, write27640924.0]), ('sdd', [661874.0, 27562704.0])]
    for dev in dev_io:
        read_ex = int(dev_io[count][1][0])-int(dev_io_per[count][1][0]) #读取总数之差,这个可以不等于0,但是不能设置得阈值太大,不然会让正在使用的硬盘休眠
        if (dev_io[count][1][1] == dev_io_per[count][1][1]) and  read_ex <=6 :#判断读写前后的区别,是否符合休眠条件
            os.system("hdparm -y /dev/{}".format(dev_io[count][0]))#根据所得的硬盘名称休眠磁盘
            print("STOP",dev_io[count][0])

        count=count+1

缝合起来 缝合怪是吧?

接下来写一个循环,中间设置一个时间间隔,向操作休眠的函数传递前后硬盘的I/O数值,就完事了:

import subprocess
import os
import time


def get_io():
    result = subprocess.run("iostat",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE,encoding="utf-8",timeout=1) #获取命令行执行后的结果
    lines = result.stdout.split('\n') #根据每一行的内容,把结果转换成列表
    lines = lines[6:-3] #抛弃掉不需要的部分,我们只需要中间的数据

    extracted_values = []
    for item in lines:
    # 分割字符串以空格为分隔符

        parts = item.split()

    # 提取设备名称(第一个值)

        device_name = parts[0]

    # 提取最后两个数字值,对应的是总读取量和总写入量

        try:

            last_two_numbers = [float(parts[-2]), float(parts[-1])]

        except ValueError:

        # 如果最后两个值不是数字,跳过这个项

            continue

    # 将提取的值添加到列表中

        extracted_values.append((device_name, last_two_numbers))


    return extracted_values

import subprocess
import os
import time
def shutdown_hdd(dev_io,dev_io_per):
    count=0  #dev_io [('sdb', [read:666582.0, write27640924.0]), ('sdd', [661874.0, 27562704.0])]
    for dev in dev_io:
        read_ex = int(dev_io[count][1][0])-int(dev_io_per[count][1][0]) #读取总数之差,这个可以不等于0,但是不能设置得阈值太大,不然会让正在使用的硬盘休眠
        if (dev_io[count][1][1] == dev_io_per[count][1][1]) and  read_ex <=6 :#判断读写前后的区别,是否符合休眠条件
            os.system("hdparm -y /dev/{}".format(dev_io[count][0]))#根据所得的硬盘名称休眠磁盘
            print("STOP",dev_io[count][0])

        count=count+1

while True: #设置循环,定期将前后对比数据发给控制休眠的函数
    current_io=get_io() #获取I/O数据
    time.sleep(1200) #循环延迟的时间间隔,单位是秒
    per_io=current_io #把之前的数值赋给新的变量
    current_io=get_io() #获取新的I/O数据
    shutdown_hdd(current_io,per_io) #传递给函数

但是要注意,这个脚本需要root来执行

设置系统服务(systemd)来运行这个脚本

把脚本放到/root/stop_hdd.py,然后在systemd的配置文件目录创建一个systemd的配置文件:

nano /usr/lib/systemd/system/hdd-sleep.service

配置文件的内容如下:

[Unit]
Description=Set HDD To sleep

[Service]
WorkingDirectory=/root
ExecStart=/usr/bin/python3 stop_hdd.py
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s QUIT $MAINPID
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"

[Install]
WantedBy=multi-user.target

F2保存文件,然后开启这个服务:

systemctl enable --now hdd-sleep

然后运行systemctl status hdd-sleep查看服务状态,如果是这样子,那就说明成功了:

[root@Kagura-Nana-Server ~]# systemctl status hdd-sleep
● hdd-sleep.service - Set HDD To sleep
   Loaded: loaded (/usr/lib/systemd/system/hdd-sleep.service; enabled; vendor preset: disabled)
   Active: active (running) since Wed 2024-02-28 00:12:52 CST; 1 weeks 1 days ago
  Process: 276924 ExecStop=/bin/kill -s QUIT $MAINPID (code=exited, status=0/SUCCESS)
 Main PID: 276927 (python3)
    Tasks: 1 (limit: 75872)
   Memory: 5.0M
   CGroup: /system.slice/hdd-sleep.service
           └─276927 /usr/bin/python3 stop_hdd.py

3月 07 21:55:31 Kagura-Nana-Server python3[1100021]: /dev/sdd:
3月 07 21:55:31 Kagura-Nana-Server python3[1100021]:  issuing standby command
3月 07 22:05:31 Kagura-Nana-Server python3[1103152]: /dev/sdf:
3月 07 22:05:31 Kagura-Nana-Server python3[1103152]:  issuing standby command
3月 07 22:15:31 Kagura-Nana-Server python3[1103283]: /dev/sdf:
3月 07 22:15:31 Kagura-Nana-Server python3[1103283]:  issuing standby command
3月 07 22:25:31 Kagura-Nana-Server python3[1104170]: /dev/sdf:
3月 07 22:25:31 Kagura-Nana-Server python3[1104170]:  issuing standby command
3月 07 22:35:31 Kagura-Nana-Server python3[1104302]: /dev/sdf:
3月 07 22:35:31 Kagura-Nana-Server python3[1104302]:  issuing standby command

至此,你的服务器就可以根据你设置的时间休眠硬盘了。