Linux 下使用命令获取硬盘信息

本文主要是一些和硬盘相关的命令,包括如何查看硬盘的型号,容量,还有硬盘上的分区情况,来详细了解本机硬盘的状态。

hdparm

如果想要在 Linux 下查看硬盘信息,可以使用命令 hdparm 。这个命令可以用来查看硬盘制造商,序列号等等有用信息。man hdparm 中告诉我, hdparm 命令是用来查看或者设置 SATA/IDE 设备参数的。

查看设备信息

假设本地有设备 /dev/sda 那么可以使用

hdparm -I /dev/sda

来查看该设备的信息

/dev/sda:

ATA device, with non-removable media
        Model Number:       Netac SSD  240G
        Serial Number:      5002B725438XXXX
        Firmware Revision:  O1217A
        Transport:          Serial, ATA8-AST, SATA 1.0a, SATA II Extensions, SATA Rev 2.5, SATA Rev 2.6, SATA Rev 3.0
Standards:
        Supported: 9 8 7 6 5
        Likely used: 9
Configuration:
        Logical         max     current
        cylinders       16383   16383
        heads           16      16
        sectors/track   63      63
        --
        CHS current addressable sectors:   16514064
        LBA    user addressable sectors:  268435455
        LBA48  user addressable sectors:  468862128
        Logical  Sector size:                   512 bytes
        Physical Sector size:                   512 bytes
        Logical Sector-0 offset:                  0 bytes
        device size with M = 1024*1024:      228936 MBytes
        device size with M = 1000*1000:      240057 MBytes (240 GB)
        cache/buffer size  = unknown
        Nominal Media Rotation Rate: Solid State Device
Capabilities:
        LBA, IORDY(can be disabled)
        Queue depth: 32
        Standby timer values: spec'd by Standard, no device specific minimum
        R/W multiple sector transfer: Max = 2   Current = 2
        DMA: mdma0 mdma1 mdma2 udma0 udma1 udma2 udma3 udma4 udma5 *udma6
             Cycle time: min=120ns recommended=120ns
        PIO: pio0 pio1 pio2 pio3 pio4
             Cycle time: no flow control=120ns  IORDY flow control=120ns
Commands/features:
        Enabled Supported:
           *    SMART feature set
                Security Mode feature set
           *    Power Management feature set
           *    Write cache
           *    Look-ahead
           *    Host Protected Area feature set
           *    WRITE_BUFFER command
           *    READ_BUFFER command
           *    NOP cmd
           *    DOWNLOAD_MICROCODE
                SET_MAX security extension
           *    48-bit Address feature set
           *    Device Configuration Overlay feature set
           *    Mandatory FLUSH_CACHE
           *    FLUSH_CACHE_EXT
           *    SMART error logging
           *    SMART self-test
           *    General Purpose Logging feature set
           *    WRITE_{DMA|MULTIPLE}_FUA_EXT
           *    {READ,WRITE}_DMA_EXT_GPL commands
           *    Segmented DOWNLOAD_MICROCODE
           *    Gen1 signaling speed (1.5Gb/s)
           *    Gen2 signaling speed (3.0Gb/s)
           *    Gen3 signaling speed (6.0Gb/s)
           *    Native Command Queueing (NCQ)
           *    Host-initiated interface power management
           *    Phy event counters
           *    READ_LOG_DMA_EXT equivalent to READ_LOG_EXT
           *    DMA Setup Auto-Activate optimization
                Device-initiated interface power management
           *    Software settings preservation
                Device Sleep (DEVSLP)
           *    SMART Command Transport (SCT) feature set
           *    SCT Write Same (AC2)
           *    SCT Features Control (AC4)
           *    SCT Data Tables (AC5)
           *    DOWNLOAD MICROCODE DMA command
           *    WRITE BUFFER DMA command
           *    READ BUFFER DMA command
           *    Data Set Management TRIM supported (limit 8 blocks)
           *    Deterministic read ZEROs after TRIM
Security:
        Master password revision code = 65534
                supported
        not     enabled
        not     locked
                frozen
        not     expired: security count
                supported: enhanced erase
        2min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
Device Sleep:
        DEVSLP Exit Timeout (DETO): 40 ms (drive)
        Minimum DEVSLP Assertion Time (MDAT): 31 ms (drive)
Checksum: correct

测试读取速度

hdparm 提供了一个简单的读速度测试参数

hdparm -Tt /dev/sda

结果

/dev/sda:
 Timing cached reads:   25572 MB in  2.00 seconds = 12798.56 MB/sec
 Timing buffered disk reads: 800 MB in  3.01 seconds = 266.08 MB/sec

能够看到 2 秒内读取了 25572M 缓存,而在 3 秒内从磁盘上物理读 800M 数据。

fdisk

fdisk 主要用来查看和修改硬盘分区表,它能够识别 GPT,MBR,BSD 等等分区表。设备可以被划分为一个或者若干逻辑磁盘,这些逻辑磁盘叫做分区。这些分区信息被包含在分区表 (partition table) 中,通常在硬盘的 sector 0 中保存。

设备名通常叫做 /dev/sda/dev/sdb 等等,设备的名字通常指整块硬盘,分区名字通常是设备名后面加上分区的序号,比如 /dev/sda1 表示的是第一块硬盘上的一个分区。详细的信息可以在 Linux kernel 文档 Documentation/devices.txt 文件中找到。

GPT

GPT 的全称是 GUID Partition Table,全局唯一标识分区表,指的是一个实体硬盘的分区表结构布局标准。1 GPT 使用 64 bit 逻辑块地址。

MBR

MBR 全称为 Master Boot Record,主引导扇区, DOS type。Sector 0 是被 4 个主分区 primary partition 描述占用的,逻辑分区 (Logical partition) 从序号 5 开始。

如果要查看硬盘的分区情况,可以使用 fdisk

fdisk -l

结果

Disk /dev/loop0: 81.7 MiB, 85692416 bytes, 167368 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/loop1: 81.7 MiB, 85639168 bytes, 167264 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/loop2: 81.6 MiB, 85549056 bytes, 167088 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/sda: 223.6 GiB, 240057409536 bytes, 468862128 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
Disklabel type: dos
Disk identifier: 0x5ad18deb

Device     Boot Start       End   Sectors   Size Id Type
/dev/sda1          63 468862127 468862065 223.6G 83 Linux


Disk /dev/sdb: 931.5 GiB, 1000204886016 bytes, 1953525168 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 4096 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes
Disklabel type: dos
Disk identifier: 0x29049925

Device     Boot      Start        End    Sectors   Size Id Type
/dev/sdb1  *            63  629153594  629153532   300G  7 HPFS/NTFS/exFAT
/dev/sdb2        629153656 1953523711 1324370056 631.5G  f W95 Ext'd (LBA)
/dev/sdb5        629153658 1153466999  524313342   250G  7 HPFS/NTFS/exFAT
/dev/sdb6       1153467063 1782588464  629121402   300G  7 HPFS/NTFS/exFAT
/dev/sdb7       1782589440 1798213631   15624192   7.5G 82 Linux swap / Solaris
/dev/sdb8       1798215680 1953523711  155308032  74.1G 83 Linux

Partition 1 does not start on physical sector boundary.
Partition 5 does not start on physical sector boundary.
Partition 6 does not start on physical sector boundary.


Disk /dev/sdc: 119.2 GiB, 128035676160 bytes, 250069680 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
Disklabel type: dos
Disk identifier: 0x326f11b9

Device     Boot     Start       End   Sectors   Size Id Type
/dev/sdc1  *           63 248346992 248346930 118.4G  7 HPFS/NTFS/exFAT
/dev/sdc2       248348672 250066943   1718272   839M 27 Hidden NTFS WinRE

dd

dd 工具是一个专业的测试工具,对测试结果不苛求可以用来做 IO 读写的简单评估。首先要了解两个特殊设备:

/dev/null 伪设备,回收站.写该文件不会产生IO
/dev/zero 伪设备,会产生空字符流,对它不会产生IO

dd 命令使用:

dd if=/dev/zero of=/tmp/test bs=1G count=1 oflag=dsync
  • if 用来设置dd命令读取的输入文件名
  • of dd 输出文件名
  • bs 设置dd命令读取的块大小
  • count dd 命令读取的块个数
  • oflag=dsync 使用同步I/O 去除 caching 影响

综上

测试硬盘写速度

sync; dd if=/dev/zero of=tempfile bs=1M count=1024; sync

测试磁盘读速度

dd if=tempfile of=/dev/null bs=1M count=1024

GUI

同样在 Linux 下也可以使用 GUI 图形化的工具来查看,搜索菜单 Disks,然后就能查看当前电脑安装的硬盘了。

  1. https://en.wikipedia.org/wiki/GUID_Partition_Table 


2018-03-24 linux , hard-drive , ssd

每天学习一个命令:ncdu 磁盘分析工具

最近想把机械硬盘换成 SSD,然后使用 du -h 查看了一下本地硬盘使用,发现用了180多G,想要清理一下废弃的大文件,然后就发现了这个非常好用的磁盘管理工具 ncdu。 Ubuntu 源下有这个软件,可以直接使用命令安装:

sudo apt install ncdu

使用

终端下直接使用 ncdu ,然后工具就会直接扫描当前目录,并且按照文件及文件夹占用大小,从大到小排列,例如:

   22.2 GiB [#####     ] /Documents
   16.2 GiB [###       ] /.local
.  13.7 GiB [###       ] /.cache
   10.9 GiB [##        ] /.m2
   10.8 GiB [##        ] /Dropbox
    9.8 GiB [##        ] /Git
    3.2 GiB [          ] /.IntelliJIdea2016.3
    2.2 GiB [          ] /.pyenv
    1.4 GiB [          ] /.IdeaIC2016.3
    1.3 GiB [          ] /.wiznote

在界面中按下 ? 就会发现很多快捷键

┌───ncdu help─────────────────1:Keys───2:Format───3:About──┐
│                                                          │
│       up, k  Move cursor up                              │
│     down, j  Move cursor down                            │
│ right/enter  Open selected directory                     │
│  left, <, h  Open parent directory                       │
│           n  Sort by name (ascending/descending)         │
│           s  Sort by size (ascending/descending)         │
│           C  Sort by items (ascending/descending)        │
│           d  Delete selected file or directory           │
│           t  Toggle dirs before files when sorting       │
│           g  Show percentage and/or graph                │
│                        -- more --                        │
│                                         Press q to close │
└──────────────────────────────────────────────────────────┘

看到这个快捷键,就可以疯狂的使用d 来删除不再使用的大文件了。

最后开心的按下 q 退出。


2018-03-23 linux , ubuntu , ncdu , disk

使用 clonezilla 备份和恢复

Clonezilla 是一个分区和硬盘镜像和克隆的工具。Clonezilla 能够实现硬盘到硬盘,分区到分区的快速拷贝,在备份文件,克隆系统的应用中有着非常高的性能。使用起来也非常的方便,因此平时都保留着一个 8G U 盘制作的 Clonezilla 可以启动 U 盘。

Clonezilla is a partition and disk imaging/cloning program

下载地址: http://clonezilla.org/downloads.php

制作 U 盘启动

制作一个 bootable Clonezilla 非常简单,在官网现在 iso 镜像之后,使用镜像写入工具就可以制作可以启动的 Clonezilla。在 Windows 下可以使用 Universal usb installer,或者 unetbootin,Linux 下使用 etcher。几乎都是加载 iso,选择写入设备,写入的过程比较简单,就不展开具体教程了。

备份流程

使用制作好的 U 盘启动,需要调整 PC 启动顺序,然后进入 clonezilla 系统,在 Clonezilla 中可以选择硬盘到硬盘复制,分区到分区复制,还有网络的备份,SSH 的备份,这些不怎么常用就暂时省略,主要使用硬盘到硬盘的备份和分区到分区的备份。

在具体的使用过程中遇到了一系列的问题,比如 Clonezilla 中无法找到新加的磁盘,比如如何将大容量的分区拷贝到小容量的分区中。不过这些问题都一一得到了解决。下面就讲下这些问题的解决方案。

Clonezilla 无法找到新加的磁盘

在电脑上直接插上新的 SSD,有可能默认情况下新的 SSD 没有分区,磁盘上也没有新建的分区表。这个时候 Clonezilla 就无法在分区到分区的拷贝中找到新的磁盘的分区。因此需要使用 Live boot 的 Linux mint U 盘启动,在 U 盘启动的 Linux 中使用 GParted 来针对新的磁盘进行分区。一般情况下直接划分一个系统分区就足够了。

划分分区的时候可能会遇到 MBR(Master Boot Record) 和 GPT(GUID Partition Table) 这样两个分区表的名词,这两个都是硬盘分区表的格式,不过一老一新。

MBR 主引导记录,是传统的分区机制,MBR 支持 32 位或者 64 位系统,但是支持的分区数量有限,并且不支持超过 2T 的硬盘。

GPT 是全局唯一标识分区表,是一个新的分区机制,解决了 MBR 很多缺点。支持大于 2T 的磁盘,fdisk 命令最大只能建立 2T 分区,需要使用 parted 命令来创建大于 2T 的分区。GPT 向后兼容 MBR,必须在支持 UEFI 的硬件上才能使用。

可以使用

sudo parted /dev/sdb print

来查看 sdb 这块硬盘上的分区表。关于 parted 命令更多的介绍,可以参考这篇文章

如果使用 GUI,那么在 U 盘启动的 Linux 中使用 GParted 可以直接对硬盘进行分区,然后应用即可,当新硬盘有分区时,Clonezilla 就能够找到分区并将原先的分区克隆到新的分区中了。

将大容量 HDD 拷贝到小容量的 SSD

Clonezilla 只能够将小的分区复制到大的分区中,因此会面临一个大问题。比如有一块 1T 的机械硬盘,需要将其中的一个 500g 的分区克隆到 250G 的 SSD 中,那么就需要先将 500G 的分区缩小到 250G 以下。查看当前 500G 系统分区实际文件占用大小,实际使用量一定要小于 250G,删除无用文件将实际占用文件缩小到 250G 以下之后,可能需要一个可以启动的 Linux live CD,一般情况下我也会制作一个 Linux Mint 的可启动 U 盘,然后启动该 U 盘,在 Linux Mint 中,使用 GParted 调整需要克隆的分区大小,将分区调整到 250G 以下。搜索关键词 shrink partition。缩小分区操作可能因不同硬盘和文件占用而不同耗时,一般情况下也需要非常长时间的操作。

在缩小分区之后,就可以启动 Clonezilla 然后使用分区到分区的操作,将原先 HDD 上的系统分区拷贝到 SSD 的分区中。拷贝也是一个非常耗时的操作,等待拷贝完成,这样系统就在原来分区和 SSD 上各有一份拷贝了。

由于 Clonezilla 在拷贝时是原样复制,因此可能导致 Linux 分区的 UUID 也一模一样,因此需要根据这篇文章 来修改 Linux 分区的 UUID。

接下来的事情就是修改引导,让电脑启动到 SSD 中的系统。

这里就要推荐一个软件 boot-repair

sudo add-apt-repository ppa:yannubuntu/boot-repair
sudo apt-get update
sudo apt-get install -y boot-repair && boot-repair

需要在 Live boot 的 Linux mint 中启动 boot-repair 来修复确实的引导。然后开机启动新硬盘上的系统就 OK 了。

reference


2018-03-23 clonezilla , tutorial , backup , restore , system , ssd , linux

Trello 类似的工具和应用整理

Trello 是这一年来我发现的为数不多非常好用,并且一直用到现在“好”应用,然而上个礼拜在工作的时候收到一份邮件,是一个内部使用的效率工具,点开一看竟然发现是和 Trello 类似的一个内部规划和管理工具,那个时候就是萌生了写下这样一篇文章的想法。说实话经过一番搜索和调查整理之后发现 Trello 类似的项目真的很多,商业化的,非商业化的,开源的,闭源的,不过追根溯源看板这个概念也并不是 Trello 首创,这个概念本来就来自日本,已经经过多年的实践证明过了的。

Teambition

这是国内团队做的,看官网是主攻企业和大客户,为公司等等提供定制化服务。

我简单的使用了一下 Teambition 的个人版,主要的流程和 Trello 并没有什么大的差异,甚至大体功能都比不上 Trello,比如不能附加图片等等,不过界面很清爽,好的一点是他自带模板功能,Trello 早期让我无法适从的一点就是,进去以后光秃秃,虽然他也有模板,但是对于一个初用者,如果有不同种类的模板给予选择,肯定可以更好的使用起来,虽然熟悉之后可能自己手动根据自己的需求创建相应的项目要来的更加自由,但是初期模板确实给了一个清晰地方向,你可以用来管理 TODO,可以用来指定旅行计划,可以用来管理读书笔记,更加甚至给公司用可以用来管理 OKR,可以用来制定需求池,可以用来管理敏捷开发,用来管理项目进度等等等等。

teambition 模板

Screenshot from 2018-05-19 10-54-15

Screenshot from 2018-05-19 10-56-11

wekan

开源版本的 Trello

如果考虑到隐私问题,可以用来私有托管。并且在他们发布的 1.0 版本 中,增加了 Snap, Docker, VirtualBox, 等等快捷安装的方式。

如果想要在线尝试一下,可以访问这里:https://oasis.sandstorm.io/demo

Notion

单独写了一篇文章,开始的时候竟然漏掉了。

其他

下面几个也都大同小异,大家都主打团队协作工具,效率工具,看使用的方式吧,好的工具确实能够提高效率,但却也不能因为好的工具而浪费时间。

  • https://tower.im/
  • https://clickup.com
  • https://airtable.com

2018-03-19 trello , kanban , board , application

VPS 安全设置

以前也写过一篇文章叫做购买 VPS 之后需要做的事情 其中也提到了一些安全设置来确保 VPS 的安全性,但是那篇文章更多的集中于设置和配置。那这篇文章就集中总结归纳一下需要特别注意的安全问题。

保持系统更新

经常检查系统更新,尤其是出现重大安全问题时一定更新到最新的系统,以 Debian/Ubuntu/LinuxMint 为例

apt-get update
apt-get upgrade

SSH 端口和登录

SSH 默认使用 22 端口,我们和 VPS 打交道用的最多的就是这一个端口,修改 /etc/ssh/sshd_configPort 的设置,修改为其他端口,然后使用

ssh -p <the port you set> name@server.ip

来指定端口访问,虽然修改为非默认端口也避免不了被扫描出来,但概率要稍微低一点。

推荐使用公钥、私钥来登录 VPS,在本机 ssh-copy-id name@server.ip 将本地公钥拷贝 到远程 ~/.ssh/authorized_keys 文件中

禁止 root 账户 SSH 登录

限制 root 账户登录 SSH 同理,修改 /etc/ssh/sshd_configPermitRootLogin 值改为 no。注意之前先新建可用账户,然后再禁用 root 登录。

adduser [nickname_you_want]
adduser [nickname_you_want] sudo        # 或者 visudo

禁用 ping

不响应 ping,修改 /proc/sys/net/ipv4/icmp_echo_ignore_all 文件,0 为允许,1 为禁止

# 禁止 ping
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
# 允许 ping
echo 0 > /proc/sys/net/ipv4/icmp_echo_ignore_all

限制账号多重登录

编辑 /etc/security/limits.conf 添加配置:

*               hard    maxlogins       2

安装 fail2ban

Fail2ban 是一个能够保护 SSH 等常用端口暴力破解的工具

sudo apt install fail2ban

项目的配置地址在 /etc/fail2ban/ 目录下。其中可以找到一个 jail.conf 的配置文件,该文件可能在升级时被覆盖,所以可以拷贝一份 cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local 来编辑 local 文件,fail2ban 配置文件优先级:

  • /etc/fail2ban/jail.conf
  • /etc/fail2ban/jail.d/*.conf,按字母顺序排列
  • /etc/fail2ban/jail.local
  • /etc/fail2ban/jail.d/*.local,按字母顺序排列

编辑 /etc/fail2ban/jail.local

[DEFAULT]
# "ignoreip" can be an IP address, a CIDR mask or a DNS host. Fail2ban will not
# ban a host which matches an address in this list. Several addresses can be
# defined using space separator.
ignoreip = 127.0.0.1/8 123.45.67.89

# "bantime" is the number of seconds that a host is banned.
bantime  = 31536000            ; 1 year

# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds.
findtime = 600
maxretry = 3

更多的配置可以参考这篇文章

fail2ban 的日志可以在 /var/log/fail2ban.log 查看。

如何查看日志

当你发现服务器有异常请求时,如何查看服务器用户登录日志。首先查看当前服务器登录的用户

w

使用该命令可以查看当前连接在线的用户,然后使用

last

来查看过去一段时间的登录用户,包括登录用户名,登录 IP,登录时间,时长等等。如果发现异常等级即使处理。

然后检查 sudo less /var/log/auth.log 文件查看登录日志。

reference


2018-03-18 linux , vps , server , ssh , config , scan

使用Google出品的 cAdvisor 监控Docker容器

cAdvisor 可以对节点机器上的资源及容器进行实时监控和性能数据采集,包括CPU使用情况、内存使用情况、网络吞吐量及文件系统使用情况,cAdvisor集成在Kubelet中,当kubelet启动时会自动启动cAdvisor,即一个cAdvisor仅对一台Node机器进行监控。kubelet的启动参数 –cadvisor-port 可以定义cAdvisor对外提供服务的端口,默认为4194。

cAdvisor原生支持 Docker 容器,cAdvisor 容器是基于Google的 Imctfy 开发。cAdvisor 运行一个守护进程用来收集每一个容器的数据,cAdvisor 的数据可以使用一下方式获取:

  • cAdvisor 网页界面,cAdvisor 守护进程会暴露一个有好的UI界面用来展示手机的数据。显示的数据是实时的,可以用来debug
  • Influxdb Influxdb 是一个时序列数据库,cAdvisor 可以将监控的数据存储到 Influxdb 数据库中,以便于未来使用客户端来查询
  • Rest API cAdvisor 会暴露一个 RESTful API,使用该接口可以自己实现逻辑
  • Elasticsearch 在 pre release 的版本中,可以使用 Elasticsearch 来存储数据

更多的内容可以访问项目主页:http://github.com/google/cadvisor

使用方法

cAdvisor 有两种方法来运行,一种是以二进制可执行文件,另一种是以 Docker 容器运行。

Docker 容器运行

这里先介绍使用 Docker 容器运行的方法,使用docker容器运行cadvisor的方法如下:

docker run \
    --volume=/:/rootfs:ro \
    --volume=/var/run:/var/run:rw \
    --volume=/sys:/sys:ro \
    --volume=/var/lib/docker/:/var/lib/docker:ro \
    --publish=8080:8080 \
    --detach=true \
    --name=cadvisor \
    google/cadvisor:latest

运行之后,便可通过http://IP:8080来访问web界面。可以看到CPU的使用率、内存使用率、网络吞吐量以及磁盘空间利用率,点击界面顶部 docker 可以进入查看某个docker容器的详细信息。

注意,在 Ret Hat, CentOS, Fedora 等发行版上需要传递如下参数,因为 SELinux 加强了安全策略:

--privileged=true

设置为true之后,容器内的root才拥有真正的root权限,可以看到host上的设备,并且可以执行mount;否者容器内的root只是外部的一个普通用户权限。由于cadvisor需要通过socket访问docker守护进程,在CentOs和RHEL系统中需要这个这个选项。

--volume=/cgroup:/cgroup:ro 

对于CentOS和RHEL系统的某些版本(比如CentOS6),cgroup的层级挂在/cgroup目录,所以运行cadvisor时需要额外添加–volume=/cgroup:/cgroup:ro选项。

二进制运行

cAdvisor 项目在他的 release 页面发布了可执行的二进制,这些文件可以直接下载并执行

wget https://github.com/google/cadvisor/releases/download/v0.26.1/cadvisor
chmod 755 cadvisor
./cadvisor

默认情况下 cAdvisor 的网页端口为 8080,直接访问本地 http://localhost:8080 即可。

REST API

cadvisor还提供远程调用的REST API,详情可以参考如下文档:https://github.com/google/cadvisor/blob/master/docs/api.md

另外,github上还有提供了一个用Go语言实现的调用REST API的客户端:https://github.com/google/cadvisor/tree/master/client

reference


2018-03-18 docker , stat , monitor , google , open-source

Java 查漏补缺之 stream 中的 collect flatmap reduce 使用

之前一篇文章 介绍了 Java 8 中 stream 基本用法,这里主要说 collect,flatmap,map 这三个比较重要的方法使用。

基础数据结构

class Person {

	String name;
	int age;

	Person(String name, int age) {
		this.name = name;
		this.age = age;
	}

	@Override
	public String toString() {
		return name;
	}
}

collect

下面的例子覆盖了 collect 绝大部分的使用案例。

@Test
public void testCollectAdvance() {
	List<Person> persons =
		Arrays.asList(
			new Person("Max", 18),
			new Person("Peter", 23),
			new Person("Pamela", 23),
			new Person("David", 12));

	Supplier<Stream<Person>> supplier = () -> persons.stream();

	Set<Person> nameWithP = supplier.get()
		.filter(p -> p.name.startsWith("P"))
		.collect(Collectors.toSet());
	System.out.println(nameWithP);  // [Pamela, Peter]

	Map<Integer, List<Person>> groupByAge = supplier.get()
		.collect(Collectors.groupingBy(p -> p.age));
	System.out.println(groupByAge);

	Double averageAge = supplier.get()
		.collect(Collectors.averagingInt(p -> p.age));
	System.out.println(averageAge);

	IntSummaryStatistics ageSummary = supplier.get()
		.collect(Collectors.summarizingInt(p -> p.age));
	System.out.println(ageSummary);

	String phrase = supplier.get()
		.filter(p -> p.age >= 18)
		.map(p -> p.name)
		.collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));
	System.out.println(phrase);

	Map<Integer, String> map = supplier.get()
		.collect(Collectors.toMap(
			p -> p.age,
			p -> p.name,
			(name1, name2) -> name1 + ";" + name2
		));
	System.out.println(map);

	// 如果要实现自己的 collector
	Collector<Person, StringJoiner, String> personStringJoinerStringCollector = Collector.of(
		() -> new StringJoiner(" | ", "[ ", " ]"),          // supplier
		(j, p) -> j.add(p.name.toUpperCase()),  // accumulator
		(j1, j2) -> j1.merge(j2),               // combiner
		StringJoiner::toString                  // finisher
	);
	String personStr = supplier.get().collect(personStringJoinerStringCollector);
	System.out.println(personStr);
}

JDK 为我们实现了大部分常用的 Collector,都可以在 Collectors 类中查看。而如果我们要想实现自己的 Collector ,则需要提供四个实现,supplier,accumulator,combiner,finisher。

首先使用 Collector.of 这个静态方法来创建自定义 collector,这个静态方法需要上面提到的四个参数。

supplier 提供结果的容器

supplier 需要提供一个存放结果的容器,accumulator 的内容会存放在 supplier 中,比如上面例子中

() -> new StringJoiner(" | ")

accumulator 定义累加器

accumulator 将累加结果添加到 supplier 创建的结果容器中,该方法有两个参数,第一个参数为 supplier 提供的结果,另一个为流中的数据

(joiner, person) -> joiner.add(person.name.toUpperCase())

combiner 合并两个局部结果

在 sequential reduction 中上面两步已经足够,但是为了支持 parallel 需要提供 combiner, combiner 是定义两个结果如何合并的方法,在 parallel 的场景下,流会被分为多个部分计算,最后结果需要按照 combiner 中定义的方法来合并。

(j1, j2) -> j1.merge(j2)

finisher 结果处理

虽然之前定义了 StringJoiner 来存放结果,但其实我们需要的并不是 StringJoiner,而是一个 String,所以在结果返回的时候,我们可以将 StringJoiner map 到 String 来作为返回。

Collector.of 最后有一个可变参数 Characteristics ,这个参数有三个取值:

  • CONCURRENT 表明一个 result container 可以同时被多个 accumulator 使用
  • IDENTITY_FINISH 表明 finisher 方法是 identity function ,可以被省略
  • UNORDERED 表明 colletor 不依赖于元素的排序

更多关于 Collector 的内容可以参考 Java doc

FlatMap

之前的文章 已经讨论过将流中的对象通过 map 转成另外一种对象,但是 map 有一个限制每一个对象只能被 map 到另外一个对象,如果要将一个对象转变为多个对象,或者变成 none 呢?所以 FlatMap 就是做这个用途的。

引入基本数据结构

class Foo {

	String name;
	List<Bar> bars = new ArrayList<>();

	Foo(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Foo{" +
			"name='" + name + '\'' +
			", bars=" + bars +
			'}';
	}
}

class Bar {

	String name;

	Bar(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Bar{" +
			"name='" + name + '\'' +
			'}';
	}
}

然后填充一些数据

@Test
public void testFlatMapAdvanced() {
	List<Foo> foos = Lists.newArrayList();

	IntStream.range(1, 4).forEach(i -> foos.add(new Foo("Foo" + i)));
	foos.forEach(f -> IntStream.range(1, 4)
		.forEach(i -> f.bars.add(new Bar("Bar" + i + " <- " + f.name))));

	Supplier<Stream<Foo>> supplier = foos::stream;
	supplier.get()
		.flatMap(f -> f.bars.stream())
		.forEach(b -> System.out.println(b.name));

	List<Bar> list = supplier.get()
		.flatMap(f -> f.bars.stream())
		.collect(Collectors.toList());
	System.out.println(list);
}

FlatMap 将这个双层的数据结构拍扁,生成一个 List<Bar>

Reduce

Reduction 操作将流中的所有元素缩减到一个结果, Java 8 中支持三种方式的 reduce 操作。

reduce(BinaryOperator<T>)
reduce(T, BinaryOperator<T>)
reduce(U, BiFunction<U, ? super T, U>, BinaryOperator<U>)

第一种方法接受一个 BinaryOperator accumulator 方法,其实是一个两边类型相同的 BiFunction。BiFunction 和 Function 类似,但是接受两个参数。

List<Person> persons =
    Arrays.asList(
        new Person("Max", 18),
        new Person("Peter", 23),
        new Person("Pamela", 23),
        new Person("David", 12));

Supplier<Stream<Person>> supplier = persons::stream;
supplier.get().reduce((p1, p2) -> p1.age > p2.age ? p1 : p2)
    .ifPresent(System.out::println);

第二种方法接受两个参数一个 T,一个 BinaryOperator,比如说可以汇总四个 Person 到一个新的 Person

	Person finalPerson = supplier.get()
    .reduce(new Person("", 0), (p1, p2) -> {
        p1.age += p2.age;
        p1.name = p1.name.concat(p2.name);
        return p1;
    });
System.out.println(finalPerson);

第三种方法接受三个参数,一个 T,一个 BiFunction (accumulator),一个 BinaryOperator (combiner function),如果我们只想要所有 Person 的年龄总和,其实上面的例子中并不需要 name 的值,所以可以添加一个 BiFunction (累加器)

Integer totalAge = supplier.get()
    .reduce(
        0,
        (sum, p) -> {
            System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
            return sum += p.age;
        },
        (sum1, sum2) -> {
            System.out.format("combiner: sum1=%s; sum2=%s", sum1, sum2);
            return sum1 + sum2;
        }
    );
System.out.println(totalAge);

打印内容

accumulator: sum=0; person=Person{name='Max', age=18}
accumulator: sum=18; person=Person{name='Peter', age=23}
accumulator: sum=41; person=Person{name='Pamela', age=23}
accumulator: sum=64; person=Person{name='David', age=12}
76

通过打印的内容可以看到 accumulator 打印出了所有内容,sum 一直在累加,但是观察发现 combiner 根本没有做任何操作。这是因为我们创建的这个 stream 是一个串行的,而不是 parallelStream(),所以没有调用到 combiner。如果换成下面这种方式就能看到区别了。

Integer ageSum = persons
    .parallelStream()
    .reduce(0,
        (sum, p) -> {
            System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
            return sum += p.age;
        },
        (sum1, sum2) -> {
            System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
            return sum1 + sum2;
        });

// accumulator: sum=0; person=Pamela
// accumulator: sum=0; person=David
// accumulator: sum=0; person=Max
// accumulator: sum=0; person=Peter
// combiner: sum1=18; sum2=23
// combiner: sum1=23; sum2=12
// combiner: sum1=41; sum2=35

reference


2018-03-16 java , stream , java8 , collector

备份 Docker 镜像容器和数据

本意上想要了解一下 Docker 容器中 Volume 的备份,毕竟重要的数据都在 Volume 中。然后顺带看了一下 Docker 镜像,容器的备份,不过镜像和容器托管到 Docker Hub 上也算是备份了。

Volume 可以叫做 数据卷,可供一个或者多个容器使用:

  • 数据卷 可以在容器之间共享和重用
  • 对 数据卷 的修改会立马生效
  • 对 数据卷 的更新,不会影响镜像
  • 数据卷 默认会一直存在,即使容器被删除

镜像备份

这里说的备份指的是直接从本地备份镜像文件,可以使用 docker save 命令将镜像打包成 tar 文件,之后可以使用 docker load 命令来恢复。

容器备份

备份容器有不同的方法:

  • 通过 [docker commit] 命令来提交一个基于当前容器状态的新镜像
  • 使用 [docker export] 命令来将容器导出到系统文件并压缩成 tar,之后可以根据该 tar 文件使用 docker import 来创建新的镜像

需要注意的是所有的命令都只会备份容器 layered file system ,不包括 挂载的数据卷 Volumes

数据卷操作

Docker user guide 中有非常详细的知道,如何备份数据卷,这样就可以在新容器启动时使用备份好的数据。当备份 data volume 时,需要先关闭容器。

docker volume create my-vol          # 创建数据卷
docker volume ls                     # 查看所有数据卷
docker volume inspect my-vol         # 查看指定数据卷内容
docker run -d -P \
    --name web \
    # -v my-vol:/wepapp \
    --mount source=my-vol,target=/webapp \
    training/webapp \
    python app.py                   # 启动并挂载一个数据卷 使用 `--mount`
docker inspect web                  # 查看容器中 mount 信息
docker volume rm my-vol             # 移除数据卷

数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。

无主(dangling)的数据卷可能会占据很多空间,要清理请使用以下命令

docker volume prune

数据卷备份

比如在 docker compose 中定义了 volumes

volumes:
  db_data:

那么在启动 docker compose 之后会生成一个 DOCKER_COMPOSE_NAME 加上 VOLUME_NAME 的容器卷

[DOCKER_COMPOSE_NAME]_[VOLUME_NAME]

那么可以使用下面的命令来备份数据卷:

docker run --rm \ 
  --volume [DOCKER_COMPOSE_PREFIX]_[VOLUME_NAME]:/[TEMPORARY_DIRECTORY_TO_STORE_VOLUME_DATA] \
  --volume $(pwd):/[TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE] \
  alpine \
  tar cvf /[TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE]/[BACKUP_FILENAME].tar /[TEMPORARY_DIRECTORY_TO_STORE_VOLUME_DATA]

看清楚其中的临时 DATA 目录和 临时备份目录,执行该命令之后,在当前文件夹下就会产生 BACKUP_FILENAME.tar 这样的文件,里面包含数据卷中的内容。

那么就能够使用该命令来恢复数据卷数据

docker run --rm \ 
  --volume [DOCKER_COMPOSE_PREFIX]_[VOLUME_NAME]:/[TEMPORARY_DIRECTORY_STORING_EXTRACTED_BACKUP] \
  --volume $(pwd):/[TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE] \
  alpine \
  tar xvf /[TEMPORARY_DIRECTORY_TO_STORE_BACKUP_FILE]/[BACKUP_FILENAME].tar -C /[TEMPORARY_DIRECTORY_STORING_EXTRACTED_BACKUP] --strip 1

如果是数据库容器,比如 mysql 容器,备份数据可以使用如下方式

docker exec [CONTAINER_NAME] /usr/bin/mysqldump -u root --password=root [DATABASE] > backup.sql

然后使用下面的命令来恢复

cat backup.sql | docker exec -i [CONTAINER_NAME] /usr/bin/mysql -u root --password=root [DATABASE]

对于 docker compose 启动的多个容器,可能因为宿主机器变化而导致 docker 容器的id有变化,可能在回复数据之后,还需要对数据库连接的地址进行修改才能完整的恢复。

reference


2018-03-16 linux , docker , container , image , volume

树莓派中安装 Docker 及 docker compose

仅仅作为记录,为了不让树莓派吃灰。主要参考官网这篇文章

Docker 的好用程度已经不比多说,经过这两年的发展已经非常成熟,还记得一年前买的书已经跟不上Docker的发展了,所以这里还是推荐 Docker 的官方文档,要比市面上存在所有书籍要详细。不过要是想要了解 Docker 的内部技术还是有不少好书可以参考。跑偏了,回到正题。

安装

Docker 官方已经支持 Raspbian Jessie,所以可以直接安装:

curl -sSL https://get.docker.com | sh

Docker client 只能被 root 或者 docekr group 中的用户使用,所以将 pi 用户添加到 docker 用户组:

sudo usermod -aG docker pi

使用

如果拉取了 busybox 镜像,可能会发现工作不正常,这只是因为有些镜像是为了 PC 或者 x86_64 架构设计的,可能未来版本会修复,所以应该使用那些设计为了在 ARM 上运行的镜像,目前 Docker 团队维护了一小部分镜像,可以在arm32v6这个用户下找到。

可以使用 arm32v6/alpine 作为基础镜像,同样也可以使用 Resin.io 制作的镜像,该镜像被用在了当前 Docker 中,这是一个轻量版本的 Raspberry Jessie。

制作镜像

比如说想要制作一个在树莓派上能够跑的镜像,可以以 resion/rpi-raspbian 作为基础镜像

FROM resin/rpi-raspbian:latest
ENTRYPOINT []

RUN apt-get update && \
    apt-get -qy install curl ca-certificates

CMD ["curl", "https://docker.com"]

或者也可以

FROM arm32v6/alpine:3.5

RUN apk add --no-cache curl ca-certificates

CMD ["curl", "https://docker.com"]

build 命令

docker build -t curl_docker .
docker run curl_docker

如果不怎么使用 Raspberry Pi 连接显示器,或者不怎么使用 GPU,可以限制 gpu 内存的占用,修改 /boot/config.txt 添加下面一行:

gpu_mem=16

reference


2018-03-15 linux , respberry-pi , docker , docker-compose

使用 pt-online-schema-change 工具不锁表在线修改 MySQL 表结构

percona-toolkit 源自 Maatkit 和 Aspersa 工具,这两个工具是管理 MySQL 的最有名的工具,但 Maatkit 已经不维护了,全部归并到 percona-toolkit。Percona Toolkit 是一组高级的命令行工具,用来管理 MySQL 和系统任务,主要包括:

  • 验证主节点和复制数据的一致性
  • 有效的对记录行进行归档
  • 找出重复的索引
  • 总结 MySQL 服务器
  • 从日志和 tcpdump 中分析查询
  • 问题发生时收集重要的系统信息
  • 在线修改表结构

这里主要介绍在线修改表结构功能。

在运维 MySQL 数据库时,我们总会对数据表进行 ddl 变更,修改添加字段或者索引,对于 MySQL 而已,ddl 显然是一个令所有 MySQL dba 诟病的一个功能,因为在 MySQL 中在对表进行 ddl 时,会锁表,当表比较小,比如小于 1w 上时,对前端影响较小,当时遇到千万级别的表 就会影响前端应用对表的写操作。在 5.1 之前都是非常耗时耗力的,在 5.1 之后随着 Plugin innodb 的出现在线加索引的提高了很多,但是还会影响(时间缩短了); 不过 5.6 可以避免上面的情况。目前 InnoDB 引擎是通过以下步骤来进行 DDL 的 (注:mysql 版本查看:mysql -V) :

  • 按照原始表(original_table)的表结构和 DDL 语句,新建一个不可见的临时表(tmp_table)
  • 在原表上加 write lock,阻塞所有更新操作(insert、delete、update 等)
  • 执行 insert into tmp_table select * from original_table
  • rename original_table 和 tmp_table,最后 drop original_table
  • 释放 write lock。

我们可以看见在 InnoDB 执行 DDL 的时候,原表是只能读不能写的。为此 perconal 推出一个工具 pt-online-schema-change ,其特点是修改过程中不会造成读写阻塞。

工作原理

pt-online-schema-change 工作原理

  • 如果存在外键,根据 alter-foreign-keys-method 参数的值,检测外键相关的表,做相应设置的处理。没有使用 alter-foreign-keys-method 指定特定的值,该工具不予执行
  • 创建一个新的空表,其命名规则是:下划线 + 原表名 +_new—-_原表名_new
  • 根据 alter 语句,更新新表的表结构;
  • 创建触发器,用于记录从拷贝数据开始之后,对源数据表继续进行数据修改的操作记录下来,用于数据拷贝结束后,执行这些操作,保证数据不会丢失。如果表中已经定义了触发器这个工具就不能工作了。
  • 拷贝数据,从源数据表中拷贝数据到新表中。
  • 修改外键相关的子表,根据修改后的数据,修改外键关联的子表。
  • rename 源数据表为 old 表,把新表 rename 为源表名,其通过一个 RENAME TABLE 同时处理两个表,实现原子操作。(RENAME TABLE dbteamdb.user TO dbteamdb._user_old, dbteamdb._user_new TO dbteamdb.user)
  • 将 old 表删除、删除触发器。

下载安装

官网下载地址:

使用

pt-online-schema-change 使用

pt-online-schema-change [OPTIONS] DSN

OPTIONS 参数说明:

--user:
-u,连接的用户名
--password:
-p,连接的密码
--database:
-D,连接的数据库
--port
-P,连接数据库的端口
--host:
-h,连接的主机地址
--socket:
-S,连接的套接字文件
--ask-pass
隐式输入连接 MySQL 的密码
--charset
指定修改的字符集
--defaults-file
-F,读取配置文件
--alter:
结构变更语句,不需要 alter table 关键字。可以指定多个更改,用逗号分隔。如下场景,需要注意:
    不能用 RENAME 来重命名表。
    列不能通过先删除,再添加的方式进行重命名,不会将数据拷贝到新列。
    如果加入的列非空而且没有默认值,则工具会失败。即其不会为你设置一个默认值,必须显示指定。
    删除外键 (drop foreign key constrain_name) 时,需要指定名称_constraint_name,而不是原始的 constraint_name。
    如:CONSTRAINT `fk_foo` FOREIGN KEY (`foo_id`) REFERENCES `bar` (`foo_id`),需要指定:--alter "DROP FOREIGN KEY _fk_foo"
--alter-foreign-keys-method
如何把外键引用到新表?需要特殊处理带有外键约束的表,以保证它们可以应用到新表。当重命名表的时候,外键关系会带到重命名后的表上。
该工具有两种方法,可以自动找到子表,并修改约束关系。
auto: 在 rebuild_constraints 和 drop_swap 两种处理方式中选择一个。
rebuild_constraints:使用 ALTER TABLE 语句先删除外键约束,然后再添加。如果子表很大的话,会导致长时间的阻塞。
drop_swap: 执行 FOREIGN_KEY_CHECKS=0, 禁止外键约束,删除原表,再重命名新表。这种方式很快,也不会产生阻塞,但是有风险:
1, 在删除原表和重命名新表的短时间内,表是不存在的,程序会返回错误。
2, 如果重命名表出现错误,也不能回滚了。因为原表已经被删除。
none: 类似"drop_swap"的处理方式,但是它不删除原表,并且外键关系会随着重命名转到老表上面。
--[no]check-alter
默认 yes,语法解析。配合 --dry-run 和 --print 一起运行,来检查是否有问题(change column,drop primary key)。
--max-lag
默认 1s。每个 chunk 拷贝完成后,会查看所有复制 Slave 的延迟情况。要是延迟大于该值,则暂停复制数据,直到所有从的滞后小于这个值,使用 Seconds_Behind_Master。如果有任何从滞后超过此选项的值,则该工具将睡眠 --check-interval 指定的时间,再检查。如果从被停止,将会永远等待,直到从开始同步,并且延迟小于该值。如果指定 --check-slave-lag,该工具只检查该服务器的延迟,而不是所有服务器。
--check-slave-lag
指定一个从库的 DSN 连接地址,如果从库超过 --max-lag 参数设置的值,就会暂停操作。
--recursion-method
默认是 show processlist,发现从的方法,也可以是 host,但需要在从上指定 report_host,通过 show slave hosts 来找到,可以指定 none 来不检查 Slave。
METHOD       USES
===========  ==================
processlist  SHOW PROCESSLIST
hosts        SHOW SLAVE HOSTS
dsn=DSN      DSNs from a table
none         Do not find slaves
指定 none 则表示不在乎从的延迟。
--check-interval
默认是 1。--max-lag 检查的睡眠时间。

--[no]check-plan
默认 yes。检查查询执行计划的安全性。

--[no]check-replication-filters
默认 yes。如果工具检测到服务器选项中有任何复制相关的筛选,如指定 binlog_ignore_db 和 replicate_do_db 此类。发现有这样的筛选,工具会报错且退出。因为如果更新的表 Master 上存在,而 Slave 上不存在,会导致复制的失败。使用–no-check-replication-filters 选项来禁用该检查。

--[no]swap-tables
默认 yes。交换原始表和新表,除非你禁止 --[no]drop-old-table。

--[no]drop-triggers
默认 yes,删除原表上的触发器。 --no-drop-triggers 会强制开启 --no-drop-old-table 即:不删除触发器就会强制不删除原表。

--new-table-name
复制创建新表的名称,默认 %T_new。

--[no]drop-new-table
默认 yes。删除新表,如果复制组织表失败。

--[no]drop-old-table
默认 yes。复制数据完成重命名之后,删除原表。如果有错误则会保留原表。

--max-load
默认为 Threads_running=25。每个 chunk 拷贝完后,会检查 SHOW GLOBAL STATUS 的内容,检查指标是否超过了指定的阈值。如果超过,则先暂停。这里可以用逗号分隔,指定多个条件,每个条件格式: status 指标 =MAX_VALUE 或者 status 指标:MAX_VALUE。如果不指定 MAX_VALUE,那么工具会这只其为当前值的 120%。

--critical-load
默认为 Threads_running=50。用法基本与 --max-load 类似,如果不指定 MAX_VALUE,那么工具会这只其为当前值的 200%。如果超过指定值,则工具直接退出,而不是暂停。

--default-engine
默认情况下,新的表与原始表是相同的存储引擎,所以如果原来的表使用 InnoDB 的,那么新表将使用 InnoDB 的。在涉及复制某些情况下,很可能主从的存储引擎不一样。使用该选项会默认使用默认的存储引擎。

--set-vars
设置 MySQL 变量,多个用逗号分割。默认该工具设置的是: wait_timeout=10000 innodb_lock_wait_timeout=1 lock_wait_timeout=60

--chunk-size-limit
当需要复制的块远大于设置的 chunk-size 大小,就不复制。默认值是 4.0,一个没有主键或唯一索引的表,块大小就是不确定的。

--chunk-time
在 chunk-time 执行的时间内,动态调整 chunk-size 的大小,以适应服务器性能的变化,该参数设置为 0, 或者指定 chunk-size, 都可以禁止动态调整。

--chunk-size
指定块的大小,默认是 1000 行,可以添加 k,M,G 后缀。这个块的大小要尽量与 --chunk-time 匹配,如果明确指定这个选项,那么每个块就会指定行数的大小。

--[no]check-plan
默认 yes。为了安全,检查查询的执行计划。默认情况下,这个工具在执行查询之前会先 EXPLAIN, 以获取一次少量的数据,如果是不好的 EXPLAIN, 那么会获取一次大量的数据,这个工具会多次执行 EXPALIN, 如果 EXPLAIN 不同的结果,那么就会认为这个查询是不安全的。
--statistics
打印出内部事件的数目,可以看到复制数据插入的数目。
--dry-run
创建和修改新表,但不会创建触发器、复制数据、和替换原表。并不真正执行,可以看到生成的执行语句,了解其执行步骤与细节。--dry-run 与 --execute 必须指定一个,二者相互排斥。和 --print 配合最佳。
--execute
确定修改表,则指定该参数。真正执行。--dry-run 与 --execute 必须指定一个,二者相互排斥。
--print
打印 SQL 语句到标准输出。指定此选项可以让你看到该工具所执行的语句,和 --dry-run 配合最佳。
--progress
复制数据的时候打印进度报告,二部分组成:第一部分是百分比,第二部分是时间。
--quiet
-q,不把信息标准输出。

举例

例子一

建立测试表:

CREATE TABLE `online_table` (
  `id` int(11) primary key,
  `name` varchar(10) DEFAULT NULL,
  `age` int(11) DEFAULT NULL
) engine = innodb default charset utf8;

–-dry-run 不真实执行:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "ADD COLUMN content text" D=test,t=online_table --print --dry-run

–-execute 真实执行:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "ADD COLUMN content text" D=test,t=online_table --print --execute

查看表结构:

> describe online_table;
+---------+-------------+------+-----+---------+----------------+
| Field   | Type        | Null | Key | Default | Extra          |
+---------+-------------+------+-----+---------+----------------+
| id      | int(11)     | NO   | PRI | NULL    | auto_increment |
| name    | varchar(10) | YES  |     | NULL    |                |
| age     | int(11)     | YES  |     | NULL    |                |
| content | text        | YES  |     | NULL    |                |
+---------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)

注:上面的执行可能会出现异常:

Error setting innodb_lock_wait_timeout: DBD::mysql::db do failed: Variable ‘innodb_lock_wait_timeout’ is a read only variable [for Statement “SET SESSION innodb_lock_wait_timeout=1”]. The current value for innodb_lock_wait_timeout is 50. If the variable is read only (not dynamic), specify –set-vars innodb_lock_wait_timeout=50 to avoid this warning, else manually set the variable and restart MySQL. 根据提示(innodb_lock_wait_timeout 是静态参数),加上–set-vars innodb_lock_wait_timeout=50 即可:


pt-online-schema-change --user=root --passwor=root --host=localhost  --alter "ADD COLUMN content text" D=test,t=user --set-vars innodb_lock_wait_timeout=50 --print --execute

实例二(主从)

表结构

> describe tmp_test;
+-------+-------------+------+-----+---------+----------------+
| Field | Type        | Null | Key | Default | Extra          |
+-------+-------------+------+-----+---------+----------------+
| id    | int(11)     | NO   | PRI | NULL    | auto_increment |
| name  | varchar(10) | YES  |     | NULL    |                |
+-------+-------------+------+-----+---------+----------------+

添加字段:

pt-online-schema-change --user=root --host=127.0.0.1 --alter "ADD COLUMN content text" D=crm_production,t=tmp_test --ask-pass  --print --execute
Enter MySQL password:

报错,因为该工具在检测到服务器选项中有任何复制相关的筛选会退出,需要指定:–no-check-replication-filters

Replication filters are set on these hosts:
database2
replicate_do_db = crm_production
Please read the –check-replication-filters documentation to learn how to solve this problem. at /usr/local/bin/pt-online-schema-change line 8015, line 2.

加上参数:–no-check-replication-filters

pt-online-schema-change --user=root --host=127.0.0.1  --alter "ADD COLUMN content text" D=crm_production,t=tmp_test --ask-pass --no-check-replication-filters  --print --execute

除了 add column,也可以 modify column,drop column,对于 change column 则需要指定:–no-check-alter

实例三(外键)

测试表:

create table tt(
    id int not null auto_increment,
    name varchar(10),primary key(id)
) engine=innodb default charset utf8;

create table xx(
    id int not null auto_increment,
    tt_id int not null,
    name varchar(10),
    primary key(id)
) engine=innodb default charset utf8;

alter table xx add foreign key fk_xx_tt_id(tt_id) references tt(id);

添加字段:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1 --alter "ADD COLUMN content text" D=test,t=tt --no-check-replication-filters --print --execute

执行错误退出,提示需要指定:–alter-foreign-keys-method 参数来操作有外键的表。要是没有外键而加了参数的话会出现: No foreign keys reference test.xx; ignoring –alter-foreign-keys-method。

使用 –-alter-foreign-keys-method

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "ADD COLUMN content text" D=test,t=tt --no-check-replication-filters --alter-foreign-keys-method auto --print --execute

注:对可靠性要求不高可以用 auto 模式更新,要是可靠性要求高则需要用 rebuild_constraints 模式。即:

--alter-foreign-keys-method rebuild_constraints

综合实例

测试表:

CREATE TABLE `tmp_test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

增加字段:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "ADD COLUMN content text" D=test,t=tmp_test --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --print --execute

删除字段:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "DROP COLUMN content " D=test,t=tmp_test --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --quiet --execute

修改字段:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "MODIFY COLUMN age TINYINT NOT NULL DEFAULT 0" D=test,t=tmp_test --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --quiet --execute

改名:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "CHANGE COLUMN age address varchar(30)" D=test,t=tmp_test --no-check-alter --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --quiet --execut

索引:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "ADD INDEX idx_address(address)" D=test,t=tmp_test --no-check-alter --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --print --execute

删除索引:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "DROP INDEX idx_address" D=test,t=tmp_test --no-check-alter --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --print --execute

其他可选项:

–no-drop-old-table 参数: 上面的测试都是把原表删除了,要是不删除原表则: –no-drop-old-table,这样会让原表(_test_binlog_old)保留。

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "ADD COLUMN a text" D=test,t=test_binlog --no-check-replication-filters --no-drop-old-table --print --execute

–max-load 选项 要是在线上环境上添加字段,但又不想影响到服务,可以用参数:–max-load 去执行该工具,默认是 Threads_running=25,即当前有这么多线程在运行的时候就暂停数据的复制,等少于该值则继续复制数据到新表:

pt-online-schema-change --user=root --password=123456 --host=127.0.0.1  --alter "add INDEX idx_address(address)" D=test,t=tmp_test --no-check-alter --no-check-replication-filters --alter-foreign-keys-method=auto --recursion-method=none --max-load=Threads_running=2 --statistics --print --execute

暂停的时候标准输出里面会有: Pausing because Threads_running=2。等到运行的线程数小于给定的值,则就继续复制数据,直到完成。

总结

当业务量较大时,修改操作会等待没有数据修改后,执行最后的 rename 操作。因此,在修改表结构时,应该尽量选择在业务相对空闲时,至少修改表上的数据操作较低时,执行较为妥当。如果对外键表操作时,四种外键操作类型需要根据表的数据量和可靠程度,进行选择。处于可靠性的原因,尽量使用 rebuild_constraints 类型,如果没有可靠性要求,可以使用 auto 类型。

由于可能存在一定的风险,在操作之前,建议对数据表进行备份,可以使得操作更安全、可靠。使用该工具的前提是处理的表需要有主键或则唯一索引。当处理有外键的表时,需要加 –alter-foreign-keys-method 参数,值可以根据情况设置。当是主从环境,不在乎从的延迟,则需要加 –recursion-method=none 参数。当需要尽可能的对服务产生小的影响,则需要加上 –max-load 参数。


2018-03-15 linux , mysql , table , ddl , sql

电子书

最近文章

  • Vim 插件之全局搜索:ack.vim 这篇文章看开始陆陆续续记录一下用过的 Vim Plugin,虽然有些一直也在用但从没有好好整理过,正好这篇开一个计划吧。
  • 我可能要抛弃用了很多年的 Chrome 换用 Vivaldi 大概一两年前就听说了 Vivaldi 这样一款浏览器,它使用 Chromium 做内核,创始人是从 Opera 浏览器出来的,不满 Chrome 横扫浏览器市场,没有给 Opera 一点喘息的机会,很多 Opera 的特性在 Chrome 上都被摘掉了。所以他们就开始搞了这一个 Vivaldi 浏览器,我记得当时听到的第一个宣传语是,快,想来 Chrome 最初打开市场时的宣传语也是快,但是多少年过去了 Chrome 虽然自身保持非常干净,但是加上自定义的扩展,也变得越来越臃肿了,但倒是也在一个可以接受的范围内。两年前 Vivaldi 依靠这一条宣传没有吸引到我,但是我也下载尝试,并且这两年来一直存在我的电脑中。我记得之前在我看来最大的缺点便是不能同步数据,而这个功能也在之前的更新中被加上了。所以今天就非常值得拿出来讲一讲。
  • 使用 port knocking 隐藏 SSH daemon 端口 暴露在互联网上的服务器非常容易被恶意程序进行端口扫描,以前也整理过一篇 VPS 安全设置 的文章,但都是一些比较基础的设置,能够绕过一些简单的端口扫描,但是并不能从根本上解决端口扫描的问题。
  • 威联通折腾篇十四:迁移系统盘 当时安装系统的时候就直接插入了一块硬盘,安装在了第一块机械硬盘上面,虽然读写也没有遇到什么瓶颈,但是记录以做备份,可以用于将系统迁移到 SSD 上。
  • Intellij IDEA 支持 jQuery 在 设置中进行如下操作开启 jQuery 支持: