[
  {
    "path": "README.md",
    "content": "# 运维实践指南\n\n[![GitHub stars](https://img.shields.io/github/stars/meetbill/op_practice_book.svg?style=social&label=Star)](https://github.com/meetbill/op_practice_book/stargazers)\n[![GitHub forks](https://img.shields.io/github/forks/meetbill/op_practice_book.svg?style=social&label=Fork)](https://github.com/meetbill/op_practice_book/fork)\n[![GitHub watchers](https://img.shields.io/github/watchers/meetbill/op_practice_book.svg?style=social&label=Watch)](https://github.com/meetbill/op_practice_book/watchers)\n\n![Screenshot](./images/ops.jpg)\n\n## 阅读本书\n\n> * [网上阅读(github)](https://github.com/meetbill/op_practice_book/blob/master/SUMMARY.md)\n> * [下载本书(pdf)](https://www.gitbook.com/download/pdf/book/billwang139967/op_practice_book)\n\n## 相关内容\n\n> * [文档规范](./standard.md)\n> * [wiki](https://github.com/meetbill/op_practice_book/wiki)\n> * [相关程序下载](https://github.com/meetbill/op_practice_code)\n> * [点击进行反馈](https://github.com/meetbill/op_practice_book/issues)\n\n## 参加步骤\n\n* 在 GitHub 上 `fork` 到自己的仓库，然后 `clone` 到本地，并设置用户信息。\n```\n$ git clone https://github.com/meetbill/op_practice_book.git\n$ cd op_practice_book\n$ git config user.name \"yourname\"\n$ git config user.email \"your email\"\n```\n* 修改代码后提交，并推送到自己的仓库。\n```\n$ #do some change on the content\n$ git commit -am \"Fix issue #1: change helo to hello\"\n$ git push\n```\n* 在 GitHub 网站上提交 pull request。\n* 定期使用项目仓库内容更新自己仓库内容。\n```\n$ git remote add upstream https://github.com/meetbill/op_practice_book.git\n$ git fetch upstream\n$ git checkout master\n$ git rebase upstream/master\n$ git push -f origin master\n```\n\n## 小额捐款\n\n如果觉得 `op_practice_book` 对您有帮助，可以请笔者喝杯咖啡（支付宝）\n\n![Screenshot](images/5.jpg)\n\n## Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/meetbill/op_practice_book.svg)](https://starchart.cc/meetbill/op_practice_book)\n"
  },
  {
    "path": "SUMMARY.md",
    "content": "# Summary\n\n* [前言](doc/README.md)\n* [Linux 篇](doc/Linux/README.md)\n    * [Linux 基础](doc/Linux/base.md)\n    * [Linux 工具](doc/Linux/tools.md)\n    * [Linux 安全](doc/Linux/safety.md)\n    * [Linux 优化](doc/Linux/optimize.md)\n    * [脚本编程 (shell)](doc/Linux/shell.md)\n    * [常见服务架设](doc/Linux/service.md)\n    * [常用问题处理](doc/Linux/op.md)\n* [数据库及缓存篇](doc/db/README.md)\n    * [MySQL](doc/db/mysql.md)\n    * [MongoDB](doc/db/mongodb.md)\n    * [Redis](doc/db/redis.md)\n    * [MemCache](doc/db/memcache.md)\n* [Web 篇](doc/web/README.md)\n    * [Web 基础](doc/web/web_base.md)\n    * [Nginx](doc/web/nginx.md)\n    * [Django](doc/web/django.md)\n    * [Butterfly](doc/web/butterfly.md)\n* [监控篇](doc/monitor/README.md)\n    * [Zabbix](doc/monitor/zabbix.md)\n    * [Monit](doc/monitor/monit.md)\n* [存储篇](doc/store/README.md)\n    * [磁盘及 RAID](doc/store/RAID.md)\n    * [DAS/SAN/NAS](doc/store/store.md)\n    * [GFS](doc/store/gfs.md)\n    * [GlusterFS](doc/store/glusterfs.md)\n    * [Ceph](doc/store/ceph.md)\n    * [MooseFS](doc/store/moosefs.md)\n* [物理机，云服务及虚拟化篇](doc/cloud/README.md)\n    * [物理机](doc/cloud/physical_machine.md)\n    * [AWS](doc/cloud/aws.md)\n    * [阿里云](doc/cloud/aliyun.md)\n    * [KVM](doc/cloud/kvm.md)\n    * [Docker](doc/cloud/docker.md)\n    * [OpenStack](doc/cloud/openstack.md)\n    * [K8s](doc/cloud/k8s.md)\n* [集群应用篇](doc/HA/README.md)\n    * [负载均衡](doc/HA/lb.md)\n    * [LVS](doc/HA/lvs.md)\n    * [高可用的 LVS 负载均衡集群](doc/HA/keepalived.md)\n    * [ZooKeeper](./doc/cluster/zookeeper.md)\n* [其他篇](doc/other/README.md)\n    * [Windows 下服务](doc/other/windows.md)\n"
  },
  {
    "path": "doc/HA/README.md",
    "content": "#  集群应用篇\n\n> * 负载均衡\n> * LVS\n> * 高可用的 LVS 负载均衡集群\n"
  },
  {
    "path": "doc/HA/keepalived.md",
    "content": "## Keepalived 使用\n\n<!-- vim-markdown-toc GFM -->\n* [1 Keepalived 介绍及安装](#1-keepalived-介绍及安装)\n    * [1.1 介绍](#11-介绍)\n        * [1.1.1 LVS 和 Keepalived 的关系](#111-lvs-和-keepalived-的关系)\n    * [1.2 安装](#12-安装)\n    * [1.3 使用](#13-使用)\n* [2 Keepalived 配置相关](#2-keepalived-配置相关)\n    * [2.1 global defs 区域](#21-global-defs-区域)\n    * [2.2 vrrp script 区域](#22-vrrp-script-区域)\n    * [2.3 VRRPD 配置](#23-vrrpd-配置)\n        * [2.3.1 VRRP Sync Groups](#231-vrrp-sync-groups)\n        * [2.3.2 VRRP 实例配置](#232-vrrp-实例配置)\n    * [2.4 LVS 配置](#24-lvs-配置)\n* [3 Keepalived 工作原理](#3-keepalived-工作原理)\n    * [3.1 VRRP 工作流程](#31-vrrp-工作流程)\n    * [3.2 MASTER 和 BACKUP 节点的优先级如何调整？](#32-master-和-backup-节点的优先级如何调整)\n    * [3.3 ARP 查询处理](#33-arp-查询处理)\n    * [3.4 虚拟 IP 地址和 MAC 地址](#34-虚拟-ip-地址和-mac-地址)\n    * [3.5  Keepalived 进程](#35--keepalived-进程)\n    * [3.6 Keepalived 健康检查方式](#36-keepalived-健康检查方式)\n* [4 Keepalived 场景应用](#4-keepalived-场景应用)\n    * [4.1 Keepalived 主从切换](#41-keepalived-主从切换)\n    * [4.2 Keepalived 仅做 HA 时的配置](#42-keepalived-仅做-ha-时的配置)\n* [5 其他配置](#5-其他配置)\n    * [5.1 重定向 Keepalived 输出日志](#51-重定向-keepalived-输出日志)\n    * [5.2 只用 VRRP 模块](#52-只用-vrrp-模块)\n* [6 常见问题](#6-常见问题)\n    * [6.1 virtual_router_id 冲突](#61-virtual_router_id-冲突)\n    * [6.2 VIP 无法访问](#62-vip-无法访问)\n        * [6.2.1 VIP 被抢占](#621-vip-被抢占)\n        * [6.2.2 网关的 ARP 缓存没有刷新](#622-网关的-arp-缓存没有刷新)\n\n<!-- vim-markdown-toc -->\n# 1 Keepalived 介绍及安装\n\n## 1.1 介绍\n\nKeepalived 是一个基于 VRRP 协议来实现的 WEB 服务高可用方案，其功能类似于 [heartbeat], 可以利用其来避免单点故障。一个 WEB 服务至少会有 2 台服务器运行 Keepalived，一台为主服务器（MASTER），一台为备份服务器（BACKUP），但是对外表现为一个虚拟 IP，主服务器会发送特定的消息给备份服务器，当备份服务器收不到这个消息的时候，即主服务器宕机的时候，备份服务器就会接管虚拟 IP，继续提供服务，从而保证了高可用性。\n\n\t \t+---------VIP(192.168.0.3)----------+\n\t\t|                                   |\n\t    |                                   |\n\tserver(MASTER) <----keepalived----> server(BACKUP)\n\t(192.168.0.1)                       (192.168.0.2)\n\n### 1.1.1 LVS 和 Keepalived 的关系\n\nLVS 可以不依赖 Keepalived 而进行分发请求，但是想让负载调度器动态监控真实服务器心跳 需要写很复杂的代码。而 Keepalived 正是一个通过简单配置就能满足请求分发、心跳检测、集群管理的好工具\n\n## 1.2 安装\n\n编译安装：\n\n\t$ wget http://www.keepalived.org/software/keepalived-1.2.2.tar.gz</a>\n\t$ tar -zxvf keepalived-1.2.2.tar.gz\n\t$ cd keepalived-1.2.2\n\t$ ./configure --prefix=/usr/local/keepalived\n\t$ make && make install\n\n拷贝需要的文件：\n\n\t$ cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/keepalived\n\t$ cp /usr/local/keepalived/sbin/keepalived /usr/sbin/\n\t$ cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/\n\t$ mkdir -p /etc/keepalived/\n\t$ cp /usr/local/etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf\n\n`/etc/keepalived/keepalived.conf`是默认配置文件\n\n## 1.3 使用\n\n\t$ /etc/init.d/keepalived start | restart | stop\n\n当启动了 Keepalived 之后，通过`ifconfig`是看不到 VIP 的，但是通过`ip a`命令是可以看到的。 当 MASTER 宕机，BACKUP 升级为 MASTER，这些 VRRP_Instance 状态的切换都可以在`/var/log/message`中进行记录。\n\n# 2 Keepalived 配置相关\n\nKeepalived 只有一个配置文件 /etc/keepalived/keepalived.conf，里面主要包括以下几个配置区域，分别是 global\\_defs、static\\_ipaddress、static\\_routes、vrrp_script、vrrp\\_instance 和 virtual\\_server。\n\n## 2.1 global defs 区域\n\n主要是配置故障发生时的通知对象以及机器标识\n\n```\nglobal_de_s {\n    notification_email {\n        a@abc.com\n        b@abc.com\n        ...\n    }\n    notification_email_from alert@abc.com\n    smtp_server smtp.abc.com\n    smtp_connect_timeout 30\n    enable_traps\n    router_id host163\n}\n```\n\n* notification_email 故障发生时给谁发邮件通知。\n\n* notification_email_from 通知邮件从哪个地址发出。\n\n* smpt_server 通知邮件的 smtp 地址。\n\n* smtp_connect_timeout 连接 smtp 服务器的超时时间。\n\n* enable_traps 开启 SNMP 陷阱（[Simple Network Management Protocol][snmp]）。\n\n* router_id 标识本节点的字条串，通常为 hostname，但不一定非得是 hostname。故障发生时，邮件通知会用到。\n\n## 2.2 vrrp script 区域\n\n用来做健康检查的，当时检查失败时会将`vrrp_instance`的`priority`减少相应的值。\n\n```\nvrrp_script chk_http_port {\n    script \"</dev/tcp/127.0.0.1/80\"\n    interval 1\n    weight -10\n}\n```\n\n以上意思是如果`script`中的指令执行失败，那么相应的`vrrp_instance`的优先级会减少 10 个点。\n\n## 2.3 VRRPD 配置\n\n在 [VRRP] 协议中，有两组重要的概念：\n\n> * VRRP 路由器和虚拟路由器\n> * 主控路由器和备份路由器\n\nVRRP 路由器是指运行 VRRP 的路由器，是物理实体，虚拟路由器是指 VRRP 协议创建的，是逻辑概念。一组 VRRP 路由器协同工作，共同构成一台虚拟路由器。该虚拟路由器对外表现为一个具有唯一固定 IP 地址和 MAC 地址的逻辑路由器。处于同一个 VRRP 组中的路由器具有两种互斥的角色：\n\n主控路由器和备份路由器，一个 VRRP 组中有且只有一台处于主控角色的路由器，可以有一个或者多个处于备份角色的路由器。VRRP 协议使用选择策略从路由器组中选出一台作为主控，负责 ARP 相应和转发 IP 数据包，组中的其它路由器作为备份的角色处于待命状态。当由于某种原因主控路由器发生故障时，备份路由器能在几秒钟的时延后升级为主路由器。由于此切换非常迅速而且不用改变 IP 地址和 MAC 地址，故对终端使用者系统是透明的。\n\nVRRPD 配置包括两部分\n\n> * VRRP 同步组 (synchroization group)\n> * VRRP 实例 (VRRP Instance)\n\n### 2.3.1 VRRP Sync Groups\n\nvrrp_rsync_group 用来定义 vrrp_intance 组，使得这个组内成员动作一致。举个例子来说明一下其功能：\n\n两个 vrrp_instance 同属于一个 vrrp_rsync_group，那么其中一个 vrrp_instance 发生故障切换时，另一个 vrrp_instance 也会跟着切换（即使这个 instance 没有发生故障）。\n\neg: 机器有两个网段，一个内网一个外网，每个网段开启一个 VRRP 实例，假设 VRRP 配置为检查内网，那么当外网出现问题时，VRRPD 认为自己仍然健康，那么不会发送 Master 和 Backup 的切换，从而导致了问题。Sync group 就是为了解决这个问题。可以将两个实例都放到一个 Sync group，这样，group 里面任何一个实例出现问题都会发生切换\n\n```\nvrrp_sync_group VG_1 {\n    group {\n        inside_network   # name of vrrp_instance (below)\n        outside_network  # One for each moveable IP.\n        ...\n    }\n    notify_master /path/to_master.sh\n    notify_backup /path/to_backup.sh\n    notify_fault \"/path/fault.sh VG_1\"\n    notify /path/notify.sh\n    smtp_alert\n}\n```\n* notify_master/backup/fault 分别表示切换为主 / 备 / 出错时所执行的脚本。\n\n* notify 表示任何一状态切换时都会调用该脚本，并且该脚本在以上三个脚本执行完成之后进行调用，Keepalived 会自动传递三个参数（$1 = \"GROUP\"|\"INSTANCE\"，$2 = name of group or instance，$3 = target state of transition(MASTER/BACKUP/FAULT)）。\n\n* smtp_alert 表示是否开启邮件通知（用全局区域的邮件设置来发通知）。\n\n### 2.3.2 VRRP 实例配置\n\nvrrp_instance 用来定义对外提供服务的 VIP 区域及其相关属性。\n\n```\n\nvrrp_instance VI_1 {\n    state MASTER\n    interface eth0\n    use_vmac <VMAC_INTERFACE>\n    dont_track_primary\n    track_interface {\n        eth0\n        eth1\n    }\n    mcast_src_ip <IPADDR>\n    lvs_sync_daemon_interface eth1\n    garp_master_delay 10\n    virtual_router_id 1\n    priority 100\n    advert_int 1\n    authentication {\n        auth_type PASS\n        auth_pass 12345678\n    }\n    virtual_ipaddress {\n        10.210.214.253/24 brd 10.210.214.255 dev eth0\n        192.168.1.11/24 brd 192.168.1.255 dev eth1\n    }\n\n    virtual_routes {\n        172.16.0.0/12 via 10.210.214.1\n        192.168.1.0/24 via 192.168.1.1 dev eth1\n        default via 202.102.152.1\n    }\n\n    track_script {\n        chk_http_port\n    }\n\n    nopreempt\n    preempt_delay 300\n    debug\n    notify_master <STRING>|<QUOTED-STRING>\n    notify_backup <STRING>|<QUOTED-STRING>\n    notify_fault <STRING>|<QUOTED-STRING>\n    notify <STRING>|<QUOTED-STRING>\n    smtp_alert\n}\n```\n\n* state 可以是 MASTER 或 BACKUP，不过当其他节点 Keepalived 启动时会将 priority 比较大的节点选举为 MASTER，因此该项其实没有实质用途。\n\n* interface 节点固有 IP（非 VIP）的网卡，用来发 VRRP 包。\n\n* use_vmac 是否使用 VRRP 的虚拟 MAC 地址。\n\n* dont_track_primary 忽略 VRRP 网卡错误。（默认未设置）\n\n* track_interface 监控以下网卡，如果任何一个不通就会切换到 FALT 状态。（可选项）\n\n* mcast_src_ip 修改 vrrp 组播包的源地址，默认源地址为 master 的 IP。（由于是组播，因此即使修改了源地址，该 master 还是能收到回应的）\n\n* lvs_sync_daemon_interface 绑定 lvs syncd 的网卡。\n\n* garp_master_delay 当切为主状态后多久更新 ARP 缓存，默认 5 秒。\n\n* virtual_router_id 取值在 0-255 之间，用来区分多个 instance 的 VRRP 组播。\n\n注意： 同一网段中 virtual_router_id 的值不能重复，否则会出错，相关错误信息如下。\n```\nKeepalived_vrrp[27120]: ip address associated with VRID not present in received packet :\none or more VIP associated with VRID mismatch actual MASTER advert\nbogus VRRP packet received on eth1 !!!\nreceive an invalid ip number count associated with VRID!\nVRRP_Instance(xxx) ignoring received advertisment...\n```\n\n可以用这条命令来查看该网络中所存在的 vrid：`tcpdump -nn -i any net 224.0.0.0/8`\n\n* priority 用来选举 master 的，要成为 master，那么这个选项的值 [最好高于其他机器 50 个点][priority_more_than_50]，该项 [取值范围][priority] 是 1-255（在此范围之外会被识别成默认值 100）。\n\n* advert_int 发 VRRP 包的时间间隔，即多久进行一次 master 选举（可以认为是健康查检时间间隔）。\n\n* authentication 认证区域，认证类型有 PASS 和 HA（IPSEC），推荐使用 PASS（密码只识别前 8 位）。\n\n* virtual_ipaddress VIP，不解释了。\n\n* virtual_routes 虚拟路由，当 IP 漂过来之后需要添加的路由信息。\n\n* virtual_ipaddress_excluded 发送的 VRRP 包里不包含的 IP 地址，为减少回应 VRRP 包的个数。在网卡上绑定的 IP 地址比较多的时候用。\n\n* nopreempt 允许一个 priority 比较低的节点作为 master，即使有 priority 更高的节点启动。\n\n首先 nopreemt 必须在 state 为 BACKUP 的节点上才生效（因为是 BACKUP 节点决定是否来成为 MASTER 的），其次要实现类似于关闭 auto failback 的功能需要将所有节点的 state 都设置为 BACKUP，或者将 master 节点的 priority 设置的比 BACKUP 低。我个人推荐使用将所有节点的 state 都设置成 BACKUP 并且都加上 nopreempt 选项，这样就完成了关于 autofailback 功能，当想手动将某节点切换为 MASTER 时只需去掉该节点的 nopreempt 选项并且将 priority 改的比其他节点大，然后重新加载配置文件即可（等 MASTER 切过来之后再将配置文件改回去再 reload 一下）。\n\n当使用`track_script`时可以不用加`nopreempt`，只需要加上`preempt_delay 5`，这里的间隔时间要大于`vrrp_script`中定义的时长。\n\n* preempt_delay master 启动多久之后进行接管资源（VIP/Route 信息等），并提是没有`nopreempt`选项。\n\n## 2.4 LVS 配置\n\nvirtual_server_group 一般在超大型的 LVS 中用到，一般 LVS 用不过这东西，因此不多说。\n\n```\nvirtual_server IP Port {\n    delay_loop <INT>\n    lb_algo rr|wrr|lc|wlc|lblc|sh|dh\n    lb_kind NAT|DR|TUN\n    persistence_timeout <INT>\n    persistence_granularity <NETMASK>\n    protocol TCP\n    ha_suspend\n    virtualhost <STRING>\n    alpha\n    omega\n    quorum <INT>\n    hysteresis <INT>\n    quorum_up <STRING>|<QUOTED-STRING>\n    quorum_down <STRING>|<QUOTED-STRING>\n    sorry_server <IPADDR> <PORT>\n    real_server <IPADDR> <PORT> {\n        weight <INT>\n        inhibit_on_failure\n        notify_up <STRING>|<QUOTED-STRING>\n        notify_down <STRING>|<QUOTED-STRING>\n        # HTTP_GET|SSL_GET|TCP_CHECK|SMTP_CHECK|MISC_CHECK\n        HTTP_GET|SSL_GET {\n            url {\n                path <STRING>\n                # Digest computed with genhash\n                digest <STRING>\n                status_code <INT>\n            }\n            connect_port <PORT>\n            connect_timeout <INT>\n            nb_get_retry <INT>\n            delay_before_retry <INT>\n        }\n    }\n}\n```\n\n* delay_loop 延迟轮询时间（单位秒）。\n\n* lb_algo 后端调试算法（load balancing algorithm）。\n\n* lb_kind LVS 调度类型 [NAT][nat]/[DR][dr]/[TUN][tun]。\n\n* virtualhost 用来给 HTTP_GET 和 SSL_GET 配置请求 header 的。\n\n* sorry_server 当所有 real server 宕掉时，sorry server 顶替。\n\n* real_server 真正提供服务的服务器。\n\n* weight 权重。\n\n* notify_up/down 当 real server 宕掉或启动时执行的脚本。\n\n* 健康检查的方式，N 多种方式。\n\n* path 请求 real serserver 上的路径。\n\n* digest/status_code 分别表示用 genhash 算出的结果和 http 状态码。\n\n* connect_port 健康检查，如果端口通则认为服务器正常。\n\n* connect_timeout,nb_get_retry,delay_before_retry 分别表示超时时长、重试次数，下次重试的时间延迟。\n\n其他选项暂时不作说明。\n\n\n# 3 Keepalived 工作原理\n\nKeepalived 是以 VRRP 协议为实现基础的，VRRP 全称 Virtual Router Redundancy Protocol，即***虚拟路由冗余协议***。\n\n虚拟路由冗余协议，可以认为是实现路由器高可用的协议，即将 N 台提供相同功能的路由器组成一个路由器组，这个组里面有一个 master 和多个 backup，master 上面有一个对外提供服务的 VIP（该路由器所在局域网内其他机器的默认路由为该 VIP），master 会发组播，当 backup 收不到 vrrp 包时就认为 master 宕掉了，这时就需要根据 VRRP 的优先级***vrrp_priority***来选举一个 backup 当 master***select_master***。这样的话就可以保证路由器的高可用了。\n\nKeepalived 主要有三个模块，分别是 core、check 和 vrrp。core 模块为 Keepalived 的核心，负责主进程的启动、维护以及全局配置文件的加载和解析。check 负责健康检查，包括常见的各种检查方式。vrrp 模块是来实现 VRRP 协议的。\n```\n                           +-------------+\n\t\t\t\t\t\t   |   uplink    |\n\t\t\t\t\t\t   +-------------+\n\t\t\t\t\t\t\t     |\n\t\t\t\t\t\t\t     +\n\t\t                     keep|alived\n                    \t\t 192.168.0.3\n                        \t+-------------+\n                        \t| virtualIP   |\n                        \t+-------------+\n   \t        \t 192.168.0.1 主   |  192.168.0.2\n\t        \t+--------------+ | +--------------+\n\t        \t|LVS+Keepalived|---|LVS+Keepalived|\n\t        \t+--------------+   +--------------+\n\t\t\t  +------------------+------------------+\n\t\t\t  | \t\t\t\t |                  |\n\t\t+-------------+    +-------------+    +-------------+\n\t\t|   web01     |    |   web02     |    |   web03     |\n\t\t+-------------+    +-------------+    +-------------+\n```\n\n## 3.1 VRRP 工作流程\n\n(1). 初始化\n\n路由器启动时，如果路由器的优先级是 255（最高优先级，路由器拥有路由器地址）, 要发送 VRRP 通告信息，并发送广播 ARP 信息通告路由器 IP 地址\n对应的 MAC 地址为路由虚拟 MAC, 设置通告信息定时器准备定时发送 VRRP 通告信息，转为 MASTER 状态；否则进入 BACKUP 状态，设置定时器检查\n定时检查是否收到 MASTER 的通告信息。\n\n(2).Master\n\n    设置定时通告定时器；\n\n    用 VRRP 虚拟 MAC 地址响应路由器 IP 地址的 ARP 请求；\n\n    转发目的 MAC 是 VRRP 虚拟 MAC 的数据包；\n\n    如果是虚拟路由器 IP 的拥有者，将接受目的地址是虚拟路由器 IP 的数据包，否则丢弃；\n\n    当收到 shutdown 的事件时删除定时通告定时器，发送优先权级为 0 的通告包，转初始化状态；\n\n    如果定时通告定时器超时时，发送 VRRP 通告信息；\n\n    收到 VRRP 通告信息时，如果优先权为 0, 发送 VRRP 通告信息；否则判断数据的优先级是否高于本机，或相等而且实际 IP 地址大于本地实际 IP, 设置定时通告定时器，复位主机超时定时器，转 BACKUP 状态；否则的话，丢弃该通告包；\n\n(3).Backup\n\n    设置主机超时定时器；\n\n    不能响应针对虚拟路由器 IP 的 ARP 请求信息；\n\n    丢弃所有目的 MAC 地址是虚拟路由器 MAC 地址的数据包；\n\n    不接受目的是虚拟路由器 IP 的所有数据包；\n\n    当收到 shutdown 的事件时删除主机超时定时器，转初始化状态；\n\n    主机超时定时器超时的时候，发送 VRRP 通告信息，广播 ARP 地址信息，转 MASTER 状态；\n\n    收到 VRRP 通告信息时，如果优先权为 0, 表示进入 MASTER 选举；否则判断数据的优先级是否高于本机，如果高的话承认 MASTER 有效，复位主机超时定时器；否则的话，丢弃该通告包；\n\n\n## 3.2 MASTER 和 BACKUP 节点的优先级如何调整？\n\n首先，每个节点有一个初始优先级，由配置文件中的 priority 配置项指定，MASTER 节点的 priority 应比 BAKCUP 高。\n运行过程中 Keepalived 根据 vrrp_script 的 weight 设定，增加或减小节点优先级。规则如下：\n\n1. 当 weight > 0 时，vrrp_script script 脚本执行返回 0（成功）时优先级为 priority + weight, 否则为 priority.\n当 BACKUP 发现自己的优先级大于 MASTER 通告的优先级时，进行主从切换。\n\n2. 当 weight < 0 时，vrrp_script script 脚本执行返回非 0（失败）时优先级为 priority + weight, 否则为 priority.\n当 BACKUP 发现自己的优先级大于 MASTER 通告的优先级时，进行主从切换。\n\n3. 当两个节点的优先级相同时，以节点发送 VRRP 通告的 IP 作为比较对象，IP 较大者为 MASTER.\n\n以上文中的配置为例：\n\n    HOST1: 192.168.0.1, priority=91, MASTER(default)\n    HOST2: 192.168.0.2, priority=90, BACKUP\n    VIP: 192.168.0.3 weight = 2\n\n抓包命令：tcpdump -nn vrrp\n\n## 3.3 ARP 查询处理\n\n当内部主机通过 ARP 查询虚拟路由器 IP 地址对应的 MAC 地址时，MASTER 路由器回复的 MAC 地址为虚拟的 VRRP 的 MAC 地址，而不是实际网卡的\nMAC 地址，这样在路由器切换时让内网机器觉察不到；而在路由器重新启动时，不能主动发送本机网卡的实际 MAC 地址。如果虚拟路由器开启的 ARP\n代理 (proxy_arp) 功能，代理的 ARP 回应也回应 VRRP 虚拟 MAC 地址；\n\n## 3.4 虚拟 IP 地址和 MAC 地址\n\nVRRP 组（备份组）中的虚拟路由器对外表现为唯一的虚拟 MAC 地址，地址格式为 00-00-5E-00-01-[VRID], VRID 为 VRRP 组的编号，范围是 0~255.\n\n## 3.5  Keepalived 进程\n\nKeepalived 主要有三个模块，分别是 core、check 和 vrrp。\n\n> * core 模块为 Keepalived 的核心，负责主进程的启动、维护以及全局配置文件的加载和解析。\n> * check 负责健康检查，包括常见的各种检查方式。\n> * vrrp  模块是来实现 VRRP 协议的。\n\n## 3.6 Keepalived 健康检查方式\n\nKeepalived 对后端 realserver 的健康检查方式主要有以下几种：\n\n***TCP_CHECK***\n\n工作在第 4 层，Keepalived 向后端服务器发起一个 tcp 连接请求，如果后端服务器没有响应或者超时，那么这个后端将从服务器中移除\n\n***HTTP_GET***\n\n工作在第 5 层，通过向指定的 URL 执行 http 请求，将得到的结果比对（经检验此种方法在多个实体服务器只能检测到第一个，故不可行）\n\n***SSL_GET***\n\n与 HTTP_GET 类似\n***MISC_CHECK***\n\n用脚本来检测，脚本如果带有参数，需要将脚本和参数放入到双引号内。脚本的返回值需要为：\n\n> * 0-------------- 检测成功\n> * 1-------------- 检测失败，将从服务器池中移除\n> * 2~255-------- 检测成功；如果有设置 misc_dynamic，权重自动调整为退出码 -2，如果退出码为 200，权重自动调整为 198=200-2\n\n# 4 Keepalived 场景应用\n\n## 4.1 Keepalived 主从切换\n\n主从切换比较让人蛋疼，需要将 backup 配置文件的 priority 选项的值调整的比 master 高 50 个点，然后 reload 配置文件就可以切换了。当时你也可以将 master 的 Keepalived 停止，这样也可以进行主从切换。\n\n## 4.2 Keepalived 仅做 HA 时的配置\n\n请看该文档同级目录下的配置文件示例。\n\n用 tcpdump 命令来捕获的结果如下：\n\n```\n17:20:07.919419 IP 192.168.1.1 > 224.0.0.18: VRRPv2, Advertisement, vrid 1, prio 200, authtype simple, intvl 1s, length 20\n```\n# 5 其他配置\n\n## 5.1 重定向 Keepalived 输出日志\n\n(1) 修改 /etc/sysconfig/keepalived\n\n把 KEEPALIVED_OPTIONS=\"-D\" 修改为 KEEPALIVED_OPTIONS=\"-D -d -S 0\"\n\n其中 -S 指定 syslog 的 facility\"\n\n同时创建 /var/log/keepalived 目录\n\n```\n#mkdir /var/log/keepalived\n```\n\n(2) 在 /etc/rsyslog.conf 中添加\n```\n# keepalived -S 0\nlocal0.*                    /var/log/keepalived/keepalived.log\n```\n\n(3) 重启 Rsyslog 和 Keepalived 服务\n```\n#/etc/init.d/rsyslog restart\n#/etc/init.d/Keepalived restart\n```\n## 5.2 只用 VRRP 模块\n\n假如不使用 LVS 的话，即无需加载 ip_vs 模块（注；不装 ipvsadm 的话，直接启动 Keepalived 的话，会因为没有 ip_vs 模块而一直在日志中输出错误日志）\n\n修改 /etc/sysconfig/keepalived\n\n把 KEEPALIVED_OPTIONS=\"-D\" 修改为 KEEPALIVED_OPTIONS=\"-D -P\"\n\n# 6 常见问题\n\n## 6.1 virtual_router_id 冲突\n\nKeepalived 日志提示\n```\n[Time] [Hostname] Keepalived_vrrp: ip address associated with VRID not present in received packet : [VIP]\n[Time] [Hostname] Keepalived_vrrp: one or more VIP associated with VRID mismatch actual MASTER advert\n[Time] [Hostname] Keepalived_vrrp: bogus VRRP packet received on eth0 !!!\n[Time] [Hostname] Keepalived_vrrp: VRRP_Instance(web_1) Dropping received VRRP packet...\n```\n解决方法\n```\n同一集群的 Keepalived 的主、备机的 virtual_router_id 必须相同，取值 0-255\n但是同一内网中不应有相同 virtual_router_id 的集群\n修改 virtual_router_id 就可以了\n```\n\n## 6.2 VIP 无法访问\n\n### 6.2.1 VIP 被抢占\n\n```\narping -I eth0 VIP\n```\n查看是否输出两个不同的 Mac 地址，如果是则地址被占用\n\n### 6.2.2 网关的 ARP 缓存没有刷新\n\n```\narping -I eth0 -c 5 -s VIP GATEWAY\n```\n"
  },
  {
    "path": "doc/HA/lb.md",
    "content": "﻿## 负载均衡\n\n<!-- vim-markdown-toc GFM -->\n* [解决的问题](#解决的问题)\n* [网络层次上的负载均衡](#网络层次上的负载均衡)\n    * [四层负载均衡](#四层负载均衡)\n    * [七层负载均衡](#七层负载均衡)\n* [负载均衡算法](#负载均衡算法)\n\n<!-- vim-markdown-toc -->\n\n## 解决的问题\n\n* 能够将大规模并发访问和数据流量分发到多台内部服务器上，减少用户的等待时间；\n* 当有重负载的计算请求时，能够将请求分解成多个任务，并将这些任务分配到内部多个计算服务器上，收集处理内部计算服务器的处理结果，汇总结果并返回给用户；\n* 负载均衡能够大大提高系统的处理能力、提高系统灵活性；\n* 高可用：当某服务器出现故障时，不影响其它服务器和用户的运行和使用；\n* 当后端某个服务器出现故障时，能够将该服务器从服务列表中删除，当服务器恢复时，再将该服务器加入到列表中；\n* 可伸缩：能够不影响其它服务器和用户的情况下进行扩容；\n\n负载均衡的本质是数据包的转发，即如何将数据包转发到负载最小的服务器上去。最常用的硬件方案有 F5，软件方案有 LVS+Keepalived。\n\n##  网络层次上的负载均衡\n\n* 二层负载均衡，是通过一个虚拟的 MAC 地址接收请求，然后再分配到真实的 MAC 地址；\n* 三层负载均衡，是通过一个虚拟的 IP 地址接收请求，然后分配到真实的 IP 地址；\n* 四层负载均衡，是通过一个虚拟 IP+ 端口进行接收，然后分配到真实的服务器；\n* 七层负载均衡，是通过一个虚拟的主机名或 URL 接收请求，然后分配到真实的服务器。可以根据 URL，浏览器类别，语言等，将请求发给不同的内部服务器。\n\n### 四层负载均衡\n\n* 首先会配置 frontend 的 IP:PORT 与 backend 的 IP:PORT 映射关系。当有客户端请求到来时，会根据映射关系，将请求转发到 backend 的服务器上去。\n* 工作在 L4 层的负载均衡器，不需要对客户端的数据包内容进行解析。如 SYN 包到来时，负载均衡器只需要选择一个最佳的内部服务器，将 SYN 包中的 dst IP:PORT 替换为内部服务器的 IP:PORT，并直接转发给该内部服务器即可。对有些部署，可能还需要修改 source IP:PORT，这样负载均衡器可以收到内部服务器返回的包。\n\n![lb4](./../../images/HA/lb4.png)\n\n### 七层负载均衡\n\nL7 层负载均衡，是应用层的负载均衡。\n\n负载均衡器需要先和客户端建立连接 (TCP 三次握手），接收客户端发过来的报文，然后根据报文特定字段的内容，来选择内部服务器。L7 层负载均衡器，是一个代理服务器，需要与客户端和内部服务器间都建立连接。\n一般来说，L7 层负载均衡的处理能力，低于 L4 层。\n\n![lb7](./../../images/HA/lb7.png)\n\n优点：\n- 能够更好的拓展内部网络。如：能够将使用英语的和使用汉语的客户端请求，发送到不同的内部服务器。\n- 能够将对图片的请求，发送到图片服务器，同时图片服务器可以加缓存。\n- 能够提前过滤掉一些非法的请求和无用的数据包，而不用将这些请求发送到内部服务器，减轻内部服务器的压力。\n\n缺点：\n- 速度上，不如 L4 层快\n\n## 负载均衡算法\n- 轮循（Round Robin）：将每次请求，轮流的分配给内部服务器。当内部服务器的软硬件配置相当时，比较适合。\n- 权重轮循（Weighted Round Robin）：根据内部服务器的配置不同，给每台服务器一个权重。如服务器 A 的权值被设计成 1，B 的权值是 3，C 的权值是 2，客户请求依次发给 [ABBBCC]。权重大的，分配到的任务就多。\n- 随机（Random）：将请求随机分配给内部中的多个服务器。\n- 权重随机（Weighted Random）：此种均衡算法类似于权重轮循算法，不过在处理请求分担时是个随机选择的过程。\n- 响应速度（Response Time）：负载均衡器与内部服务器建立连接，并定时向内部服务器发 ping 包（或者其他包也行）。根据各服务器的响应速度，决定将用户请求发给哪台内部服务器。该均衡算法能够较好的反应内部服务器运行状况。\n- 连接数（Connections）：有些内部服务器可能直接与客户端建立连接。这时可以询问内部服务器当前的用户连接数，并将新的用户请求发送给连接数最少的内部服务器。比较适合长连接。\n- 处理能力（Processing　Capacity）：将内部服务器的 CPU/ 内存 /IO/ 当前负载，根据一定的算法换成负载值，并定时上报给负载均衡器。负载均衡器每次将用户请求转发给当前 Load 值最低的内部服务器。\n- DNS 轮询：DNS 也可以用来做负载均衡。网络上，客户端一般通过域名来找到服务器的 IP 地址，DNS 服务器在接收客户端查询时，按顺序将服务器的 IP 地址返回给客户端，来达到均衡的目的。比较适合全局负载均衡。\n- Hash：将访问用户的 IP 地址进行 hash，根据 hash 的结果来决定将该用户定向到哪台后端服务器。\n\n\n"
  },
  {
    "path": "doc/HA/lvs.md",
    "content": "﻿## LVS\n\n<!-- vim-markdown-toc GFM -->\n* [LVS 简介](#lvs-简介)\n* [数据包三种转发方式](#数据包三种转发方式)\n    * [NAT 网络地址翻译技术](#nat-网络地址翻译技术)\n    * [TUN IP 隧道技术](#tun-ip-隧道技术)\n    * [DR 直接转发](#dr-直接转发)\n* [配置脚本](#配置脚本)\n* [ipvsadm](#ipvsadm)\n* [tcpdump 抓包分析](#tcpdump-抓包分析)\n* [问题分析](#问题分析)\n\n<!-- vim-markdown-toc -->\n## LVS 简介\n\n[LVS](http://www.linuxvirtualserver.org/)(Linux Virtual Server)，早已加入到 Linux 内核中。\nLVS 使用的是 IP 负载均衡技术 (ipvs 模块实现）。LVS 安装在 DirectorServer(DS) 上，DS 根据配置信息虚拟出一个 ip(VIP)，同时根据配置信息，生成路由表，将用户的数据包按照一定的法则转发到后端 RealServer(RS) 上。\n\nLVS 进行 L4 层转发，且在内核中，速度极快。与 Keepalived 相比，缺少对内部服务器的健康检查，且存在单点故障。\nLVS 使用 ipvsadm 来配置 ipvs。\n\n数据包的转发，有三种方法：NAT/TUN/DR。\n\n| 模式 | 网络要求 | 是否需要 VIP | 端口映射 | DS 参与回包 | ARP 隔离 | 效率 |\n| ---- | ----------------------- | ----------- | -------- | ---------- | ------- | ---- |\n| NAT | 同一网段 | 不需要 | 支持 | 是 | 不需要  | 最慢 |\n| TUN | 可在同一物理网络或不同物理网络 | 需要 | - | 否 | - | 中等 |\n| DR | 同一物理网络 | 需要 | 不支持 | 否 | 需要 | 最高 |\n\n## 数据包三种转发方式\n\n### NAT 网络地址翻译技术\n\n![lvs_nat](./../../images/HA/lvs_nat.png)\n\nDS 和 RS，都在同一个网段，才能进行 NAT 模式转发。同时需要将 DS 的内部 IP 设置为内部网络的默认网关，RS 在回包时，直接发给内部网关（即 DS），由内部网关进行转发。\n用户通过 DS 的外部 IP 地址进行访问。\nNAT 模式下，是可以进行端口映射的。\n\n整个数据包流程如下：\n```sh\n假设用户 IP 为 10.15.62.204，使用端口 6356；\n用户 ->DS: src(10.15.62.204:6356) dst(10.15.144.71:80)\nDS 的外部 IP 收到数据包后，内核进行转发\nDS->RS2: src(10.15.62.204:6356) dst(192.168.1.12:10012)\nRS2 收到数据包，处理完毕后，将回包通过内部网关发出去\nRS2->DS: src(192.168.1.12:10012) dst(10.15.62.204:6356)\nDS 收到会包后，由内核转发给用户\nDS->用户：src(10.15.144.71:80) dst(10.15.62.204:6356)\n```\n\n### TUN IP 隧道技术\n\n![lvs_tun](./../../images/HA/lvs_tun.png)\n\n调度器采用 IP 隧道技术，将用户的请求转发到 RS，RS 直接将响应发给用户。\nTUN 模式下，RS 的回包，不需要经过 DS，而是直接发给客户端。\n\n### DR 直接转发\n\n![lvs_dr](./../../images/HA/lvs_dr.png)\n\nDS 通过改写请求报文的 MAC 地址，将请求发给 RS，RS 直接将响应发给用户。\n**DR 方式的效率最高**，但要求 DS 和 RS 在同一物理网络。\nDR 模式不支持端口映射；同时 RS 需要抑制关于 VIP 的 ARP 应答。\n\n整个数据包处理过程：\n```sh\n假设客户端 IP 10.15.62.204，使用端口 6356\n用户 ->DS: src(10.15.62.204:63565) dst(10.15.144.120:80)\nDS 收到包后，通过负载均衡算法，选择 RS2，改写包的目的 MAC 地址，将数据包发给 RS2\nDS->RS2: src(10.15.62.204:63565) dst(10.15.144.120:80)，目的 MAC 发生改变\nRS2 处理完毕后，将回包直接发给客户端\nRS2->用户：src(10.15.144.120:80) dst(10.15.62.204:63565)\n```\n从转发效率来讲，NAT 最差；TUN 多了 ip 隧道的处理，次之；DR 效率最高。\n\n## 配置脚本\n以 DR 模式为例\n```sh\n# 安装 LVS 机器（即 DirectorServer）脚本，lvs-DR.sh\n# 设置 VIP，并设置转发规则\n\n#!/bin/sh\n\nVIP=10.15.144.120\nRIP1=10.15.144.102\nRIP2=10.15.144.103\nRIP3=10.15.144.104\n\n. /etc/rc.d/init.d/functions\ncase \"$1\" in\n    start)\n        echo \" start LVS of Director Server\"\n        /sbin/ifconfig eth0:1 $VIP broadcast $VIP netmask 255.255.255.255 up # 添加虚拟设备 eth0:1 和虚拟 IP\n        /sbin/route add -host $VIP dev eth0:1\n        echo \"1\" >/proc/sys/net/ipv4/ip_forward # 允许转发\n\n        /sbin/ipvsadm -C   #Clear IPVS table\n        #set LVS\n        /sbin/ipvsadm -A -t $VIP:10087 -s wrr -p 60\n        /sbin/ipvsadm -a -t $VIP:10087 -r $RIP1 -g -w 2 # -g 为 DR 模式， -w 为权重\n        /sbin/ipvsadm -a -t $VIP:10087 -r $RIP2 -g -w 1\n        /sbin/ipvsadm -a -t $VIP:10087 -r $RIP3 -g -w 1\n\n        /sbin/ipvsadm  # 打印 ipvs 信息\n    ;;\n    stop)\n        echo \"close LVS Directorserver\"\n        echo \"0\" >/proc/sys/net/ipv4/ip_forward\n        /sbin/ipvsadm -C\n        /sbin/ifconfig eth0:1 down\n    ;;\n    *)\n        echo \"Usage: $0 {start|stop}\"\n        exit 1\nesac\n\n#-----------------------------------------------------------------------\n# 在 3 台 RealServer 上，配置 VIP，关闭对该 VIP 的 ARP 应答，所执行的脚本：rs-DR.sh\n#!/bin/bash\n\nVIP=10.15.144.120\n\n. /etc/rc.d/init.d/functions\ncase \"$1\" in\n    start)\n        echo \" Start LVS  of  Real Server\"\n        /sbin/ifconfig lo:0 $VIP netmask 255.255.255.255 broadcast $VIP up\n        /sbin/route add -host $VIP dev lo:0\n        # 忽略收到的 arp 广播\n        echo \"1\" >/proc/sys/net/ipv4/conf/lo/arp_ignore\n        # 封装数据包时，忽略源 ip(lvs 服务器 ip), 而是将 VIP 做为源 ip\n        echo \"2\" >/proc/sys/net/ipv4/conf/lo/arp_announce\n        echo \"1\" >/proc/sys/net/ipv4/conf/all/arp_ignore\n        echo \"2\" >/proc/sys/net/ipv4/conf/all/arp_announce\n        sysctl -p > /dev/null 2>&1\n        ;;\n    stop)\n        /sbin/ifconfig lo:0 down\n        echo \"close LVS Director server\"\n        route del $VIP > /dev/null 2>&1\n        echo \"0\" >/proc/sys/net/ipv4/conf/lo/arp_ignore\n        echo \"0\" >/proc/sys/net/ipv4/conf/lo/arp_announce\n        echo \"0\" >/proc/sys/net/ipv4/conf/all/arp_ignore\n        echo \"0\" >/proc/sys/net/ipv4/conf/all/arp_announce\n        ;;\n    *)\n        echo \"Usage: $0 {start|stop}\"\n        exit 1\nesac\n```\n在 DS 和 RS 上运行相应的脚本后，LVS 负载均衡系统就搭建完毕了。\n\n## ipvsadm\n\n利用 ipvs 管理工具 ipvsadm，查看 DS 内部情况\n```sh\n[root@10.15.144.71 lvs]# ipvsadm\nIP Virtual Server version 1.2.1 (size=4096)\nProt LocalAddress:Port Scheduler Flags\n  -> RemoteAddress:Port Forward Weight ActiveConn InActConn\nTCP 10.15.144.120:10087 wrr persistent 2\n  -> 10.15.144.102:10087 Route 2 0 4\n  -> 10.15.144.103:10087 Route 1 0 0\n  -> 10.15.144.104:10087 Route 1 0 0\n\n# 在 10.15.62.204 上进行测试，常用命令有：\nll@ll-rw:~$ wget http://10.15.144.120:10087/index.html\nll@ll-rw:~$ ab -n 100 -c 10 http://10.15.144.120:10087/index.html\n\n[root@10.15.144.71 lvs]# ipvsadm -L -c\nIPVS connection entries\npro expire state       source               virtual             destination\nTCP 00:22 FIN_WAIT 10.15.62.204:50937 10.15.144.120:10087 10.15.144.102:10087\nTCP 00:04 FIN_WAIT 10.15.62.204:50930 10.15.144.120:10087 10.15.144.102:10087\nTCP 01:50 FIN_WAIT 10.15.62.204:50959 10.15.144.120:10087 10.15.144.103:10087\nTCP 00:50 NONE 10.15.62.204:0 10.15.144.120:10087 10.15.144.103:10087\nTCP 00:22 NONE 10.15.62.204:0 10.15.144.120:65535 10.15.144.102:65535\nTCP 00:06 FIN_WAIT 10.15.62.204:50931 10.15.144.120:10087 10.15.144.102:10087\n\n# 关掉 RealServer 服务器中的服务，再次在 10.15.62.204 测试时，收到的错误信息\nll@ll-rw:~$ wget http://10.15.144.120:10087/index.html\n--2014-11-18 16:47:40-- http://10.15.144.120:10087/index.html\nConnecting to 10.15.144.120:10087... failed: No route to host.\n```\n当 DR 模式时，数据包是直接从 RS 返回到客户端的，所以在 RS 上也需要虚拟出设备和 IP（lo:0 10.15.14.120）。RS 直接利用该 IP 进行返回。\n同时，在同一子网内，有多个 Server 都拥有 10.15.14.120 这个 IP。当其它机器进行 ARP 查询时 (who has ip 10.15.1.120)，只能够由 DS 进行响应，其他 Server 不能够响应。这也是 RS 需要使用 echo \"0\" >/proc/sys/net/ipv4/conf/lo/arp_ignore 的原因。\n\n## tcpdump 抓包分析\n使用 tcpdump 在 DS 上抓包，可以看到从 User 来的数据包，直接转发给了 RS；RS 收到数据包后，也是直接回复给了 User，不需要再经过 DS 转发。下图是在 RS(10.15.14.102) 上抓包的截图：\n\n![tcpdump](./../../images/HA/tcpdump.png)\n\n## 问题分析\nLVS 存在单点故障。当 LVS 服务器挂掉了，整个系统就完了。\nLVS 不检测内部服务器的状态。当内部服务器挂掉时，仍然将请求发往该服务器。\n\n**LVS+Keepalived**解决方案：\n- 实现主备模式解决单点故障。\n- 内部服务器有问题时，将其从可用服务器列表中删除；当其恢复时，再将其加入到可用服务器列表。\n"
  },
  {
    "path": "doc/Linux/README.md",
    "content": "# Linux 篇\n\n> * Linux 基础\n> * Linux 工具\n> * Linux 安全\n> * Linux 优化\n> * 脚本编程 (shell)\n> * 常见服务架设\n> * 常用问题处理\n"
  },
  {
    "path": "doc/Linux/base.md",
    "content": "## Linux 基础\n\n<!-- vim-markdown-toc GFM -->\n\n* [安装](#安装)\n    * [安装准备](#安装准备)\n    * [安装 CentOS6.8](#安装-centos68)\n    * [系统安装后的配置](#系统安装后的配置)\n* [Bash 基础特性](#bash-基础特性)\n    * [命令历史](#命令历史)\n    * [命令补全](#命令补全)\n    * [路径补全](#路径补全)\n    * [命令行展开](#命令行展开)\n    * [命令的执行状态结果](#命令的执行状态结果)\n    * [命令别名](#命令别名)\n    * [通配符 glob](#通配符-glob)\n    * [bash 快捷键](#bash-快捷键)\n        * [编辑命令](#编辑命令)\n        * [重新执行命令](#重新执行命令)\n        * [控制命令](#控制命令)\n        * [Bang (!) 命令](#bang--命令)\n        * [友情提示](#友情提示)\n    * [bash 的 io 重定向及管道](#bash-的-io-重定向及管道)\n        * [I/O 重定向](#io-重定向)\n* [Linux 常用命令](#linux-常用命令)\n    * [系统](#系统)\n        * [系统信息](#系统信息)\n        * [关机](#关机)\n        * [监视和调试](#监视和调试)\n        * [公钥私钥](#公钥私钥)\n        * [其他](#其他)\n    * [资源](#资源)\n        * [磁盘空间](#磁盘空间)\n    * [文件及文本处理](#文件及文本处理)\n        * [文件和目录](#文件和目录)\n        * [文件搜索](#文件搜索)\n        * [文件的权限](#文件的权限)\n        * [文件的特殊属性](#文件的特殊属性)\n        * [查看文件内容](#查看文件内容)\n        * [文本处理](#文本处理)\n        * [字符设置和文件格式](#字符设置和文件格式)\n    * [挂载](#挂载)\n        * [挂载一个文件系统](#挂载一个文件系统)\n        * [光盘](#光盘)\n    * [用户管理](#用户管理)\n        * [用户和群组](#用户和群组)\n    * [包管理](#包管理)\n        * [打包和压缩文件](#打包和压缩文件)\n        * [RPM 包 \\(Fedora,RedHat and alike\\)](#rpm-包-fedoraredhat-and-alike)\n        * [YUM 软件工具 \\(Fedora,RedHat and alike\\)](#yum-软件工具-fedoraredhat-and-alike)\n        * [备份](#备份)\n    * [磁盘和分区](#磁盘和分区)\n        * [文件系统分析](#文件系统分析)\n        * [初始化一个文件系统](#初始化一个文件系统)\n        * [SWAP 文件系统](#swap-文件系统)\n    * [网络](#网络)\n        * [网络 \\(LAN / WiFi\\)](#网络-lan--wifi)\n        * [route 设置](#route-设置)\n            * [基本使用](#基本使用)\n            * [在 linux 下设置永久路由的方法](#在-linux-下设置永久路由的方法)\n        * [Microsoft windows 网络 \\(samba\\)](#microsoft-windows-网络-samba)\n        * [IPTABLES \\(firewall\\)](#iptables-firewall)\n* [Linux 简单管理](#linux-简单管理)\n    * [ssh](#ssh)\n        * [ssh 简介及基本操作](#ssh-简介及基本操作)\n            * [简介](#简介)\n            * [密钥](#密钥)\n            * [基于口令的安全验证通讯原理](#基于口令的安全验证通讯原理)\n            * [StrictHostKeyChecking 和 UserKnownHostsFile](#stricthostkeychecking-和-userknownhostsfile)\n        * [基于密匙的安全验证通讯原理](#基于密匙的安全验证通讯原理)\n        * [SSH 端口转发](#ssh-端口转发)\n            * [SSH 正向连接](#ssh-正向连接)\n            * [SSH 反向连接](#ssh-反向连接)\n            * [SSH 反向连接自动重连](#ssh-反向连接自动重连)\n        * [windows 下 xshell 使用](#windows-下-xshell-使用)\n    * [用户管理](#用户管理-1)\n        * [Linux 踢出其他正在 SSH 登陆用户](#linux-踢出其他正在-ssh-登陆用户)\n        * [使用脚本创建有 sudo 权限的用户](#使用脚本创建有-sudo-权限的用户)\n        * [无交互式修改用户密码](#无交互式修改用户密码)\n    * [网卡 bond](#网卡-bond)\n    * [其他设置](#其他设置)\n        * [时区及时间](#时区及时间)\n            * [UTC 和 GMT](#utc-和-gmt)\n            * [Linux 下调整时区及更新时间](#linux-下调整时区及更新时间)\n        * [登录提示信息](#登录提示信息)\n            * [修改登录前的提示信息](#修改登录前的提示信息)\n        * [修改登录成功后的信息](#修改登录成功后的信息)\n* [CentOS 7 vs CentOS 6 的不同](#centos-7-vs-centos-6-的不同)\n    * [运行相关](#运行相关)\n    * [网络](#网络-1)\n\n<!-- vim-markdown-toc -->\n\n# 安装\n\nCentOS 6.x\n\n## 安装准备\n    1. 下载安装镜像文件\n    http://www.centos.org   ->downloads->mirrors\n    http://mirrors.aliyun.com/centos/6.8/isos/x86_64/\n    http://mirrors.aliyun.com/centos/6.8/isos/i386/\n    主要下载 Centos-6.8-x86_64-bin-DVD1.iso 和 Centos-6.8-x86_64-bin-DVD2.iso\n\n## 安装 CentOS6.8\n\n***选择系统引导方式***\n\n    选择 install or upgrade an existing system\n\n***检查安装光盘介质***\n\n    选择：skip\n\n***选择安装过程语言***\n\n    选择：english\n\n***选择键盘布局***\n\n    选择：U.S.English\n\n***选择合适的物理设备***\n\n    选择：basic storage devices\n\n***初始化硬盘提示***\n\n    选择：yes ,discard and data\n\n***初始化主机名以及网络配置***\n\n    （1）. 为系统设置主机名  主机名为：meetbill\n    （2）. 配置网卡及连接网络（可选）\n\n***系统时钟及时区***\n\n    选择：Asia/Shanghai\n    取消：system clock uses UTC\n    然后：next\n\n***设置 root 口令***\n\n***磁盘分区类型选择与磁盘分区配置过程***\n\n(1) 选择系统安装磁盘空间类型\n\n    选择：create custom layout\n\n(2) 进入 'create custom layout'分区界面\n\n    可以 create （创建）,update（修改） ,delete（删除）等操作。\n\n(3) 按企业生产标准定制磁盘分区\n\n    选择：standard partition\n    1）. 创建引导分区，/boot 分区\n    mount point:/boot\n    file system type:ext4\n    size:200\n\n    2）. 创建 swap 交换分区\n    mount point :<not applicable>\n    file system type:swap\n    size:1024 （物理内存的 1-2 倍）\n    addtion size options : fixed size\n    force to be a primary partition\n\n    3). 创建 ( / ) 根分区\n    mount point :/\n    file system type : ext4\n    size : 剩余\n    addtion size options : fill to maximum allowable size  （根分区是最后一个分区，所以把剩余的空间都分配给根分区）\n    force to be a primary partition\n\n    4). 格式化警告\n    选择： format\n\n***系统安装包的选择与配置***\n\n(1) 启动引导设备的配置\n    系统默认使用 GRUB 作为启动加载器，引导程序默认在 MBR 下：\n    ```\n    install boot loader on /dev/sda ->change device\n    选择 master boot record -/dev/sda\n    [选择的是操作系统所在的那个设备，如 /dev/sda]\n\n    Boot Loader operation system list\n    列表中选择的是操作系统根目录 / 所在的分区，如 CentOS /dev/sda4\n\n    ```\n(2) 系统安装类型选择及自定义额外包组\n    系统默认是 desktop ，但是这里选择 minimal。\n    自定义安装包选择：customsize now\n    base system :\n        base\n    然后：next\n\n***开始安装 ->安装完成 ->reboot***\n\n## 系统安装后的配置\n\n***更新系统，打补丁到最新***\n\n    修改更新 yum 源：\n    cp  /etc /yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.ori\n    wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirros.163.com/.help/CentOS 6-Base-163.repo\n    ll /etc/pki/rpm-gpg/\n    rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY*\n    yum update -y\n\n    ps: 一般在首次安装时执行 yum update -y , 如果是在实际生产环境中，切记使用，以免导致异常。\n\n***安装额外的软件包***\n\n    yum install tree telnet dos2unix sysstat lrzsz nc nmap -y\n    yum grouplist    #查看包组列表\n    yum frouplist \"development Tools\"\n\n# Bash 基础特性\n## 命令历史\n(1)  使用命令：history\n\n(2)  环境变量：\n\n        a) HISTSIZE：命令历史缓冲区中记录的条数，默认为 1000；\n\n        b) HISTFILE：记录当前登录用户在 logout 时历史命令存放文件；\n\n        c) HISTFILESIZE：命令历史文件记录历史的条数，默认为 1000；\n(3)  操作命令历史：\n\n        a) history –d OFFSET 删除指定行的命令历史；\n\n        b) history –c 清空命令历史缓冲区中的命令；\n\n        c) history # 显示历史中最近的#条命令；\n\n        d) history –a 手动追加当前会话缓冲区中的命令至历史文件中；\n(4) 调用历史中的命令：\n\n        a) !# : 重复执行第#条命令；\n\n        b) !! : 重复执行上一条（最近一条命令；)\n\n        c) !string : 重复执行最近一次以指定字符串开头的命令；\n\n        d) 调用上一条命令的最后一个参数：\n\n             i. !$\n\n             ii. ESC, ．\n\n(5) 控制命令历史的记录方式：\n\n        环境变量：HISTCONTROL\n\n        三个值：\n\n            ignoredups：忽略重复的命令；所谓重复，一定是连续且完全相同，包括选项和参数；\n\n            ignorespace：忽略所有以空白开头的命令，不记录；\n\n            ignoreboth：忽略上述两项，既忽略重复的命令，也忽略空白开头的命令；\n\n- 修改环境变量的方式：\n\n         export 变量名 =“VALUE”\n\n         或： VARNAME=“VALUE” export VARNAME\n\n## 命令补全\n\n内部命令：直接通过 shell 补全；\n外部命令：bash 根据 PATH 环境变量定义的路径，自左而右地在每个路径搜寻以给定命令命名的文件，第一次找到即为要执行的命令；\n- Note: 在第一次通过 PATH 搜寻到命令后，会将其存入 hash 缓存中，下次使用不再搜寻 PATH，从 hash 中查找；\n\n```\n[root@sslinux ~]# hash\nhits command\n 1 /usr/sbin/ifconfig\n 1 /usr/bin/vim\n 1 /usr/bin/ls\n\n```\n\nTab 键补全：\n若用户给出的字符在命令搜索路径中有且仅有一条命令与之相匹配，则 Tab 键直接补全；\n\n若用户输入的字符在命令搜索路径中有多条命令与之相匹配，则再次 Tab 键可以将这些命令列出；\n\n## 路径补全\n\n以用户输入的字符串作为路径开头，并在其指定路径的上级目录下搜索以指定字符串开头的文件名；\n\n    如果唯一，则直接补全；\n\n    否则，再次 Tab，列出所有符合条件的路径及文件；\n\n## 命令行展开\n\n1）~ ：展开为用户的主目录；\n~~~shell\n[root@sslinux log]# pwd\n/var/log\n[root@sslinux log]# cd ~\n[root@sslinux ~]# pwd\n/root\n~~~\n2）~USERNAME ： 展开为指定用户的主目录；\n~~~shell\n[root@sslinux ~]# pwd\n/root\n[root@sslinux ~]# cd ~sslinux\n[root@sslinux sslinux]# pwd\n/home/sslinux\n~~~\n\n3） {} ： 可承载一个以逗号分隔的列表，并将其展开为多个路径；\n~~~shell\n[root@localhost test]# ls\n[root@localhost test]# mkdir -pv ./tmp/{a,b}/shell\nmkdir: created directory `./tmp'\nmkdir: created directory `./tmp/a'\nmkdir: created directory `./tmp/a/shell'\nmkdir: created directory `./tmp/b'\nmkdir: created directory `./tmp/b/shell'\n[root@localhost test]# mkdir -pv ./tmp/{tom,johnson}/hi\n[root@localhost test]# tree .\n\n└── tmp\n ├── a\n │ └── shell\n ├── b\n │ └── shell\n ├── johnson\n │ └── hi\n └── tom\n └── hi\n9 directories, 0 files\n~~~\n\n\n## 命令的执行状态结果\n表示命令是否成功执行；\n\nbash 使用特殊变量 $? 保存最近一条命令的执行状态结果；\n\n- 环境变量 $? 的取值：\n\n     0 ： 成功；\n\n     1-255：失败，1,127,255 为系统保留；\n\n-  程序执行有两类结果：\n\n     程序的返回值；程序自身执行的输出结果；\n\n     程序的执行状态结果；$?\n\n~~~shell\n[root@localhost test]# ls /etc/sysconfig/\n\n[root@localhost test]# echo $?\n\n0    #程序的执行状态结果；执行成功；\n\n[root@localhost test]# ls /etc/sysconfig/NNNN\n\nls: cannot access /etc/sysconfig/NNNN: No such file or directory    #程序自身的执行结果；\n\n[root@localhost test]# echo $?\n\n2    #执行失败；\n\n~~~\n\n## 命令别名\n- 通过 alias 命令实现：\n\na、alias ： 显示当前 shell 进程所有可用的命令别名；\n\nb、定义别名，格式为： alias NAME='VALUE'\n\n\t定义别名 NAME，其执行相当于执行命令 VALUE，VALUE 中可包含命令、选项以及参数；仅当前会话有效，不建议使用；\n\nc、通过修改配置文件定义命令别名：\n\n    当前用户：~/.bashrc\n    全局用户：/etc/bashrc\n\n- Bash 进程重新读取配置文件：\n~~~shell\n    source /path/to/config_file\n\n    . /path/to/config_file\n~~~\n- 撤销别名： unalias\n```\n    unalias [-a] name [name...]\n```\n- Note:\n\n    对于定义了别名的命令，要使用原命令，可通过、COMMAND 的方式使用；\n\n- Example:\n\n```\n[root@sslinux sslinux]# alias\nalias cp='cp -i'\nalias egrep='egrep --color=auto'\nalias fgrep='fgrep --color=auto'\nalias grep='grep --color=auto'\nalias l.='ls -d .* --color=auto'\nalias ll='ls -l --color=auto'\nalias ls='ls --color=auto'\nalias mv='mv -i'\nalias rm='rm -i'\nalias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'\n[root@sslinux sslinux]# grep alias /root/.bashrc\n### User specific aliases and functions\nalias rm='rm -i'\nalias cp='cp -i'\nalias mv='mv -i'\n```\n\n## 通配符 glob\n\nBash 中用于文件名\"通配\"\n\n- 通配符： *,?,[]\n\n    1) * 任意长度的任意字符；\n\n        a * b\n        ```\n        [root@sslinux sslinux]# ls -ld /etc/au*\n        drwxr-x---. 3 root root 41 Sep 3 22:05 /etc/audisp\n        drwxr-x---. 3 root root 79 Sep 3 22:09 /etc/audit\n        ```\n\n2)  ? 任意单个字符；\n\n       \t \ta?b\n\n~~~shell\n[root@sslinux sslinux]# ls -ld /etc/*d?t\ndrwxr-x---. 3 root root 79 Sep 3 22:09 /etc/audit\n~~~\n\n3)  []   匹配指定范围内的任意单个字符；\n\n        [0-9]    [a-z]   不区分大小写；\n        [admin]    可以是区间形式的，也可以是离散形式的；\n~~~shell\n[root@sslinux sslinux]# ls -ld /etc/[ab]*\ndrwxr-xr-x. 2 root root 4096 Sep 3 22:05 /etc/alternatives\ndrwxr-xr-x. 2 root root 33 Sep 3 22:04 /etc/avahi\ndrwxr-xr-x. 2 root root 33 Sep 3 22:04 /etc/bash_completion.d\n-rw-r--r--. 1 root root 2835 Oct 29 2014 /etc/bashrc\ndrwxr-xr-x. 2 root root 6 Mar 6 2015 /etc/binfmt.d\n~~~\n\n  4)   [^] 匹配指定范围以外的任意单个字符；\n\n```\n        [^0-9] : 单个非数字的任意字符；\n```\n- 专用字符结合：（表示一类字符中的单个）\n\n[:digit:] 任意单个数字，相当于 [0-9];\n\n[:lower:] 任意单个小写字母；\n\n[:upper:] 任意单个大写字母；\n\n[:alpha:] 任意单个大小写字母；\n\n[:alnum:] 任意单个数字或字母；\n\n[:space:] 任意空白字符；\n\n[:punct:] 任意单个特殊字符；\n\n- Note：\n\n在使用 [] 应用专用字符集合时，外层也需要嵌套 []。\n\nExample：\n\n```\n# ls -d /etc/*[[:digit:]]*[[:lower:]]\n```\n\n## bash 快捷键\n\n### 编辑命令\n- Ctrl + a ：移到命令行首\n- Ctrl + e ：移到命令行尾\n- Ctrl + f ：按字符前移（右向）\n- Ctrl + b ：按字符后移（左向）\n- Alt + f ：按单词前移（右向）\n- Alt + b ：按单词后移（左向）\n- Ctrl + xx：在命令行首和光标之间移动\n- Ctrl + u ：从光标处删除至命令行首\n- Ctrl + k ：从光标处删除至命令行尾\n- Ctrl + w ：从光标处删除至字首\n- Alt + d ：从光标处删除至字尾\n- Ctrl + d ：删除光标处的字符\n- Ctrl + h ：删除光标前的字符\n- Ctrl + y ：粘贴至光标后\n- Alt + c ：从光标处更改为首字母大写的单词\n- Alt + u ：从光标处更改为全部大写的单词\n- Alt + l ：从光标处更改为全部小写的单词\n- Ctrl + t ：交换光标处和之前的字符\n- Alt + t ：交换光标处和之前的单词\n- Alt + Backspace：与 Ctrl + w 相同类似，分隔符有些差别\n\n### 重新执行命令\n- Ctrl + r：逆向搜索命令历史\n- Ctrl + g：从历史搜索模式退出\n- Ctrl + p：历史中的上一条命令\n- Ctrl + n：历史中的下一条命令\n- Alt + .：使用上一条命令的最后一个参数\n\n### 控制命令\n- Ctrl + l：清屏\n- Ctrl + o：执行当前命令，并选择上一条命令\n- Ctrl + s：阻止屏幕输出\n- Ctrl + q：允许屏幕输出\n- Ctrl + c：终止命令\n- Ctrl + z：挂起命令\n\n### Bang (!) 命令\n- !!：执行上一条命令\n- !blah：执行最近的以 blah 开头的命令，如 !ls\n- !blah:p：仅打印输出，而不执行\n- !$：上一条命令的最后一个参数，与 Alt + . 相同\n- !$:p：打印输出 !$ 的内容\n- !*：上一条命令的所有参数\n- !*:p：打印输出 !* 的内容\n- ^blah：删除上一条命令中的 blah\n- ^blah^foo：将上一条命令中的 blah 替换为 foo\n- ^blah^foo^：将上一条命令中所有的 blah 都替换为 foo\n\n### 友情提示\n\n    以上介绍的大多数 Bash 快捷键仅当在 emacs 编辑模式时有效，\n\n    若你将 Bash 配置为 vi 编辑模式，那将遵循 vi 的按键绑定。\n\n    Bash 默认为 emacs 编辑模式。\n\n    如果你的 Bash 不在 emacs 编辑模式，可通过 set-o emacs 设置。\n\n    ^S、^Q、^C、^Z 是由终端设备处理的，可用 stty 命令设置。\n\n## bash 的 io 重定向及管道\n打开的文件都有一个 fd：file descriptor（文件描述符）\n\n    标准输入：keyboard，0\n\n    标准输出：monitor，1\n\n    标准错误输出：monitor，2\n\n### I/O 重定向\n\n- 输出重定向：\n\n     COMMAND > NEW_POS 覆盖重定向，目标文件中的原有内容会被清除；\n\n     COMMAND >> NEW_POS 追加重定向，新内容会被追加到目标文件尾部；\n\n- Note：\n\n    为了在输出重定向时防止覆盖原有文件，建议使用以下设置：\n\n    set –C ： 禁止将内容覆盖输出 (>) 至已有文件中，追加输出不受影响；\n\n    此时，若确定要将重定向的内容覆盖原有文件，可使用 >| 强制覆盖；\n\n- Example:\n~~~shell\n[root@localhost test1]# echo \"It's dangerous\" > ./result.txt #输出到文件；\n[root@localhost test1]# cat result.txt\nIt's dangerous\n[root@localhost test1]# set –C #禁止将内容覆盖输出到已有文件；\n[root@localhost test1]# echo \"It's very dangerous\" > ./result.txt\n-bash: ./result.txt: cannot overwrite existing file #提示不能覆盖已存在文件；\n[root@localhost test1]# echo \"It's very dangerous\" >| ./result.txt #强制覆盖\n[root@localhost test1]#\n[root@localhost test1]# set +C #取消禁止覆盖输出到已有文件；\n[root@localhost test1]# echo \"It's very dangerous\" > ./result.txt\n[root@localhost test1]#\n~~~\n\n- 错误输出：\n\n     2> : 覆盖重定向错误输出数据流；\n\n     2>> ：追加重定向错误输出数据流；\n~~~shell\n[root@localhost test1]# lss -l /etc/ 2> ./error.txt\n[root@localhost test1]# cat error.txt\n-bash: lss: command not found\n[root@localhost test1]# cat /etc/passwd.error 2>> ./error.txt\n[root@localhost test1]# cat error.txt\n-bash: lss: command not found\ncat: /etc/passwd.error: No such file or directory\n~~~\n\n将标准输出和标准错误输出各自重定向至不同位置：\n\n     COMMAND > /path/to/file.out 2> /path/to/error.out\n\n- Example:\n```\n# cat /etc/passwd > ./file.out 2> ./error.out\n```\n- 合并输出：\n\n     合并标准输出和错误输出为同一个数据流进行重定向；(PS:重定向命令是倒序操作的，如 > file 2>&1 是先执行 2>&1 然后执行 > file)\n\n         &> 合并覆盖重定向；\n\n         &>> 合并追加重定向；\n\n    格式为：\n\n         COMMAND > /path/to/file.out 2> &1\n\n         COMMAND >> /path/to/file.out 2>> &1\n\n    Example:\n~~~shell\n    [root@localhost test1]# ls -l /etc/ > ./file.out 2>&1\n    [root@localhost test1]# ls -l /etc/ &> file.out\n    [root@localhost test1]# ls -l /etcc/ &> file.out\n    [root@localhost test1]# cat file.out\n    ls: cannot access /etcc/: No such file or directory\n~~~\n\n- 输入重定向： <\n~~~shell\n     HERE Documentation：<<\n\n     # cat << EOF\n\n     # cat > /path/to/somefile << EOF\n~~~\n\nExample: 输入重定向，输入完成后显示内容到标准输出上；\n~~~shell\n[root@localhost test1]# cat << EOF\n> my name is kalaguiyin.\n> I'm a tibetan.\n> I come from Sichuan Provence.\n> EOF\nmy name is kalaguiyin.\nI'm a tibetan.\nI come from Sichuan Provence.\n~~~\n\nExample：从标准输入读取输入并重定向到文件。\n~~~shell\n[root@localhost test1]# cat > hello.txt << EOF\n> this is a test file.\n> 中华人民共和国。\n> EOF\n[root@localhost test1]# cat hello.txt\nthis is a test file.\n中华人民共和国。\n~~~\n\n- 管道：\n\n     COMMAND1 | COMMAND2 | COMMAND3 | …..\n\n     作用：前一个命令的执行结果将作为后一个命令执行的参数；\n\n     Note:\n\n            最后一个命令会在当前 shell 进程的子 shell 进程中执行；\n~~~shell\n[root@sslinux]# cat /etc/passwd | sort -t: -k3 -n | cut -d: -f1\nroot\nbin\ndaemon\nadm\nlp\npolkitd\nsslinux\n~~~\n# Linux 常用命令\n\n## 系统\n\n### 系统信息\n| 命令 | 说明 |\n|--------|--------|\n|\\# arch|显示机器的处理器架构|\n|\\# cal 2016|显示 2016 年的日历表|\n|\\# cat /proc/cpuinfo|查看 CPU 信息|\n|\\# cat /proc/interrupts|显示中断|\n|\\# cat /proc/meminfo|校验内存使用|\n|\\# cat /proc/swaps|显示哪些 swap 被使用|\n|\\# cat /proc/version|显示内核版本|\n|\\# cat /proc/net/dev|显示网络适配器及统计|\n|\\# cat /proc/mounts|显示已加载的文件系统|\n|\\# clock \\-w|将时间修改保存到 BIOS|\n|\\# date|显示系统日期|\n|\\# date 072308302016\\.00|设置日期和时间 \\- 月日时分年、. 秒|\n|\\# dmidecode \\-q|显示硬件系统部件 \\- \\(SMBIOS / DMI\\)|\n|\\# hdparm \\-i /dev/hda|罗列一个磁盘的架构特性|\n|\\# hdparm \\-tT /dev/sda|在磁盘上执行测试性读取操作|\n|\\# lspci \\-tv|罗列 PCI 设备|\n|\\# lsusb \\-tv|显示 USB 设备|\n|\\# uname \\-m|显示机器的处理器架构|\n|\\# uname \\-r|显示正在使用的内核版本|\n\n### 关机\n| 命令 | 说明 |\n|--------|--------|\n|\\# init 0|关闭系统|\n|\\# logout|注销|\n|\\# reboot|重启|\n|\\# shutdown \\-h now|关闭系统|\n|\\# shutdown \\-h 16:30 &|按预定时间关闭系统|\n|\\# shutdown \\-c|取消按预定时间关闭系统|\n|\\# shutdown \\-r now|重启|\n\n### 监视和调试\n| 命令 | 说明 |\n|--------|--------|\n|\\# free \\-m|以兆为单位罗列 RAM 状态|\n|\\# kill \\-9 process\\_id|强行关闭进程并结束它|\n|\\# kill \\-1 process\\_id|强制一个进程重载其配置|\n|\\# last reboot|显示重启历史|\n|\\# lsmod|罗列装载的内核模块|\n|\\# lsof \\-p process\\_id|罗列一个由进程打开的文件列表|\n|\\# lsof /home/user1|罗列所给系统路径中所打开的文件的列表|\n|\\# ps \\-eafw|罗列 linux 任务|\n|\\# ps \\-e \\-o pid,args \\-\\-forest|以分级的方式罗列 linux 任务|\n|\\# pstree|以树状图显示程序|\n|\\# smartctl \\-A /dev/hda|通过启用 SMART 监控硬盘设备的可靠性|\n|\\# smartctl \\-i /dev/hda|检查一个硬盘设备的 SMART 是否启用|\n|\\# strace \\-c ls >/dev/null|罗列系统 calls made 并用一个进程接收|\n|\\# strace \\-f \\-e open ls >/dev/null|罗列库调用|\n|\\# tail /var/log/dmesg|显示内核引导过程中的内部事件|\n|\\# tail /var/log/messages|显示系统事件|\n|\\# top|罗列使用 CPU 资源最多的 linux 任务|\n|\\# watch \\-n1 'cat /proc/interrupts'|罗列实时中断|\n\n### 公钥私钥\n| 命令 | 说明 |\n|--------|--------|\n| \\# ssh-keygen -t rsa -C \"邮箱地址\" | 产生公钥私钥对 |\n| \\# ssh-copy-id -i ~/.ssh/id_rsa.pub root@192.168.0.2  | 将本地机器的公钥复制到远程机器的 root 用户的 authorized_keys 文件中 |\n| \\# ssh-keygen -p -f ~/.ssh/id_rsa | 添加或修改 SSH-key 的私钥密码 |\n| \\# ssh-keygen -y -f ~/.ssh/id_rsa > id_rsa.pub | 从私钥中生成公钥 |\n\n### 其他\n| 命令 | 说明 |\n|--------|--------|\n|\\# alias hh='history'|为命令 history\\（历史、) 设置一个别名|\n|\\# gpg \\-c file1|用 GNU Privacy Guard 加密一个文件|\n|\\# gpg file1\\.gpg|用 GNU Privacy Guard 解密一个文件|\n|\\# ldd /usr/bin/ssh|显示 ssh 程序所依赖的共享库|\n|\\# man ping|罗列在线手册页（例如 ping 命令）|\n|\\# mkbootdisk \\-\\-device /dev/fd0 \\`uname \\-r\\`|创建一个引导软盘|\n|\\# wget \\-r www\\.example\\.com|下载一个完整的 web 站点|\n|\\# wget \\-c www\\.example\\.com/file\\.iso|以支持断点续传的方式下载一个文件|\n|\\# echo 'wget \\-c www\\.example\\.com/files\\.iso' &#124; at 09:00|在任何给定的时间开始一次下载|\n|\\# whatis \\.\\.\\.keyword|罗列该程序功能的说明|\n|\\# who \\-a|显示谁正登录在线，并打印出：系统最后引导的时间，关机进程，系统登录进程以及由 init 启动的进程，当前运行级和最后一次系统时钟的变化|\n\n## 资源\n\n### 磁盘空间\n| 命令 | 说明 |\n|--------|--------|\n|\\# df \\-h|显示已经挂载的分区列表|\n|\\# du \\-sh dir1|估算目录 'dir1' 已经使用的磁盘空间|\n|\\# du \\-sk \\* &#124; sort \\-rn|以容量大小为依据依次显示文件和目录的大小|\n|\\# ls \\-lSr &#124; more|以尺寸大小排列文件和目录|\n|\\# rpm \\-q \\-a \\-\\-qf '%10\\{SIZE\\}t%\\{NAME\\}n' &#124; sort \\-k1,1n|以大小为依据依次显示已安装的 rpm 包所使用的空间 \\(centos, redhat, fedora 类系统、)|\n\n\n## 文件及文本处理\n\n### 文件和目录\n| 命令 | 说明 |\n|--------|--------|\n|\\# cd /home|进入 '/home' 目录|\n|\\# cd \\.\\.|返回上一级目录|\n|\\# cd \\.\\./\\.\\.|返回上两级目录|\n|\\# cd|进入个人的主目录|\n|\\# cd ~user1|进入个人的主目录|\n|\\# cd \\-|返回上次所在的目录|\n|\\# cp file1 file2|复制一个文件|\n|\\# cp dir/\\* \\.|复制一个目录下的所有文件到当前工作目录|\n|\\# cp \\-a /tmp/dir1 \\.|复制一个目录到当前工作目录|\n|\\# cp \\-a dir1 dir2|复制一个目录|\n|\\# cp file file1|将 file 复制为 file1|\n|\\# iconv \\-l|列出已知的编码|\n|\\# iconv \\-f fromEncoding \\-t toEncoding inputFile > outputFile|改变字符的编码|\n|\\# find \\. \\-maxdepth 1 \\-name \\*\\.jpg \\-print \\-exec convert|batch resize files in the current directory and send them to a thumbnails directory (requires convert from Imagemagick)|\n|\\# ln \\-s file1 lnk1|创建一个指向文件或目录的软链接|\n|\\# ln file1 lnk1|创建一个指向文件或目录的物理链接|\n|\\# ls|查看目录中的文件|\n|\\# ls \\-F|查看目录中的文件|\n|\\# ls \\-l|显示文件和目录的详细资料|\n|\\# ls \\-a|显示隐藏文件|\n|\\# ls \\*\\[0\\-9\\]\\*|显示包含数字的文件名和目录名|\n|\\# lstree|显示文件和目录由根目录开始的树形结构|\n|\\# mkdir dir1|创建一个叫做 'dir1' 的目录|\n|\\# mkdir dir1 dir2|同时创建两个目录|\n|\\# mkdir \\-p /tmp/dir1/dir2|创建一个目录树|\n|\\# mv dir1 new\\_dir|重命名 / 移动 一个目录|\n|\\# pwd|显示工作路径|\n|\\# rm \\-f file1|删除一个叫做 'file1' 的文件|\n|\\# rm \\-rf dir1|删除一个叫做 'dir1' 的目录并同时删除其内容|\n|\\# rm \\-rf dir1 dir2|同时删除两个目录及它们的内容|\n|\\# rmdir dir1|删除一个叫做 'dir1' 的目录|\n|\\# touch \\-t 1607230000 file1|修改一个文件或目录的时间戳 \\- \\(YYMMDDhhmm\\)|\n|\\# tree|显示文件和目录由根目录开始的树形结构|\n\n### 文件搜索\n| 命令 | 说明 |\n|--------|--------|\n|\\# find / \\-name file1|从 '/' 开始进入根文件系统搜索文件和目录|\n|\\# find / \\-user user1|搜索属于用户 'user1' 的文件和目录|\n|\\# find /home/user1 \\-name \\\\\\*\\.bin|在目录 '/ home/user1' 中搜索带有'\\.bin' 结尾的文件|\n|\\# find /usr/bin \\-type f \\-atime \\+100|搜索在过去 100 天内未被使用过的执行文件|\n|\\# find /usr/bin \\-type f \\-mtime \\-10|搜索在 10 天内被创建或者修改过的文件|\n|\\# find / \\-name \\*\\.rpm \\-exec chmod 755 '\\{\\}' \\\\;|搜索以 '\\.rpm' 结尾的文件并定义其权限|\n|\\# find / \\-xdev \\-name \\\\\\*\\.rpm|搜索以 '\\.rpm' 结尾的文件，忽略光驱、捷盘等可移动设备|\n|\\# locate \\\\\\*\\.ps|寻找以 '\\.ps' 结尾的文件 \\- 先运行 'updatedb' 命令|\n|\\# whereis halt|显示一个二进制文件、源码或 man 的位置|\n|\\# which halt|显示一个二进制文件或可执行文件的完整路径|\n\n### 文件的权限\n| 命令 | 说明 |\n|--------|--------|\n|\\# chgrp group1 file1|改变文件的群组|\n|\\# chmod ugo\\+rwx directory1|设置目录的所有人、(u\\)、群组、(g\\) 以及其他人、(o\\) 以读、(r\\)、写、(w\\) 和执行、(x\\) 的权限|\n|\\# chmod go\\-rwx directory1|删除群组、(g\\) 与其他人、(o\\) 对目录的读写执行权限|\n|\\# chmod u\\+s /bin/file1|设置一个二进制文件的 SUID 位 \\- 运行该文件的用户也被赋予和所有者同样的权限|\n|\\# chmod u\\-s /bin/file1|禁用一个二进制文件的 SUID 位|\n|\\# chmod g\\+s /home/public|设置一个目录的 SGID 位 \\- 类似 SUID，不过这是针对目录的|\n|\\# chmod g\\-s /home/public|禁用一个目录的 SGID 位|\n|\\# chmod o\\+t /home/public|设置一个文件的 STIKY 位 \\- 只允许合法所有人删除文件|\n|\\# chmod o\\-t /home/public|禁用一个目录的 STIKY 位|\n|\\# chown user1 file1|改变一个文件的所有人属性|\n|\\# chown \\-R user1 directory1|改变一个目录的所有人属性并同时改变改目录下所有文件的属性|\n|\\# chown user1:group1 file1|改变一个文件的所有人和群组属性|\n|\\# find / \\-perm \\-u\\+s|罗列一个系统中所有使用了 SUID 控制的文件|\n|\\# ls \\-lh|显示权限|\n|\\# ls /tmp &#124; pr \\-T5 \\-W$COLUMNS|将终端划分成 5 栏显示|\n\n### 文件的特殊属性\n| 命令 | 说明 |\n|--------|--------|\n|\\# chattr \\+a file1|只允许以追加方式读写文件|\n|\\# chattr \\+c file1|允许这个文件能被内核自动压缩 / 解压|\n|\\# chattr \\+d file1|在进行文件系统备份时，dump 程序将忽略这个文件|\n|\\# chattr \\+i file1|设置成不可变的文件，不能被删除、修改、重命名或者链接|\n|\\# chattr \\+s file1|允许一个文件被安全地删除|\n|\\# chattr \\+S file1|一旦应用程序对这个文件执行了写操作，使系统立刻把修改的结果写到磁盘|\n|\\# chattr \\+u file1|若文件被删除，系统会允许你在以后恢复这个被删除的文件|\n|\\# lsattr|显示特殊的属性|\n\n### 查看文件内容\n| 命令 | 说明 |\n|--------|--------|\n|\\# cat file1|从第一个字节开始正向查看文件的内容|\n|\\# head \\-2 file1|查看一个文件的前两行|\n|\\# less file1|类似于 'more' 命令，但是它允许在文件中和正向操作一样的反向操作|\n|\\# more file1|查看一个长文件的内容|\n|\\# tac file1|从最后一行开始反向查看一个文件的内容|\n|\\# tail \\-2 file1|查看一个文件的最后两行|\n|\\# tail \\-f /var/log/messages|实时查看被添加到一个文件中的内容|\n\n### 文本处理\n| 命令 | 说明 |\n|--------|--------|\n|\\# cat example\\.txt &#124; awk 'NR%2==1'|删除 example\\.txt 文件中的所有偶数行|\n|\\# echo a b c &#124; awk '\\{print $1\\}'|查看一行第一栏|\n|\\# echo a b c &#124; awk '\\{print $1,$3\\}'|查看一行的第一和第三栏|\n|\\# cat \\-n file1|标示文件的行数|\n|\\# comm \\-1 file1 file2|比较两个文件的内容只删除 'file1' 所包含的内容|\n|\\# comm \\-2 file1 file2|比较两个文件的内容只删除 'file2' 所包含的内容|\n|\\# comm \\-3 file1 file2|比较两个文件的内容只删除两个文件共有的部分|\n|\\# diff file1 file2|找出两个文件内容的不同处|\n|\\# grep Aug /var/log/messages|在文件 '/var/log/messages'中查找关键词\"Aug\"|\n|\\# grep ^Aug /var/log/messages|在文件 '/var/log/messages'中查找以\"Aug\"开始的词汇|\n|\\# grep \\[0\\-9\\] /var/log/messages|选择 '/var/log/messages' 文件中所有包含数字的行|\n|\\# grep Aug \\-R /var/log/\\*|在目录 '/var/log' 及随后的目录中搜索字符串\"Aug\"|\n|\\# paste file1 file2|合并两个文件或两栏的内容|\n|\\# paste \\-d '\\+' file1 file2|合并两个文件或两栏的内容，中间用\"\\+\"区分|\n|\\# sdiff file1 file2|以对比的方式显示两个文件的不同|\n|\\# sed 's/string1/string2/g' example\\.txt|将 example\\.txt 文件中的 \"string1\" 替换成 \"string2\"|\n|\\# sed '/^$/d' example\\.txt|从 example\\.txt 文件中删除所有空白行|\n|\\# sed '/ \\*&#124;\\#/d; /^$/d' example\\.txt|去除文件 example\\.txt 中的注释与空行|\n|\\# sed \\-e '1d' exampe\\.txt|从文件 example\\.txt 中排除第一行|\n|\\# sed \\-n '/string1/p'|查看只包含词汇 \"string1\"的行|\n|\\# sed \\-e 's/ \\*$//' example\\.txt|删除每一行最后的空白字符|\n|\\# sed \\-e 's/string1//g' example\\.txt|从文档中只删除词汇 \"string1\" 并保留剩余全部|\n|\\# sed \\-n '1,5p' example\\.txt|显示文件 1 至 5 行的内容|\n|\\# sed \\-n '5p;5q' example\\.txt|显示 example\\.txt 文件的第 5 行内容|\n|\\# sed \\-e 's/00\\*/0/g' example\\.txt|用单个零替换多个零|\n|\\# sort file1 file2|排序两个文件的内容|\n|\\# sort file1 file2 &#124; uniq|取出两个文件的并集、（重复的行只保留一份、)|\n|\\# sort file1 file2 &#124; uniq \\-u|删除交集，留下其他的行|\n|\\# sort file1 file2 &#124; uniq \\-d|取出两个文件的交集、（只留下同时存在于两个文件中的文件、)|\n|\\# echo 'word' &#124; tr '\\[:lower:\\]' '\\[:upper:\\]'|合并上下单元格内容|\n\n### 字符设置和文件格式\n| 命令 | 说明 |\n|--------|--------|\n|\\# dos2unix filedos\\.txt fileunix\\.txt|将一个文本文件的格式从 MSDOS 转换成 UNIX|\n|\\# recode \\.\\.HTML < page\\.txt > page\\.html|将一个文本文件转换成 html|\n|\\# recode \\-l &#124; more|显示所有允许的转换格式|\n|\\# unix2dos fileunix\\.txt filedos\\.txt|将一个文本文件的格式从 UNIX 转换成 MSDOS|\n\n## 挂载\n\n### 挂载一个文件系统\n| 命令 | 说明 |\n|--------|--------|\n|\\# fuser \\-km /mnt/hda2|当设备繁忙时强制卸载|\n|\\# mount /dev/hda2 /mnt/hda2|挂载一个叫做 hda2 的盘 \\- 确保目录 '/mnt/hda2' 已经存在|\n|\\# mount /dev/fd0 /mnt/floppy|挂载一个软盘|\n|\\# mount /dev/cdrom /mnt/cdrom|挂载一个 cdrom 或 dvdrom|\n|\\# mount /dev/hdc /mnt/cdrecorder|挂载一个 cdrw 或 dvdrom|\n|\\# mount /dev/hdb /mnt/cdrecorder|挂载一个 cdrw 或 dvdrom|\n|\\# mount \\-o loop file\\.iso /mnt/cdrom|挂载一个文件或 ISO 镜像文件|\n|\\# mount \\-t vfat /dev/hda5 /mnt/hda5|挂载一个 Windows FAT32 文件系统|\n|\\# mount /dev/sda1 /mnt/usbdisk|挂载一个 U 盘或闪存设备|\n|\\# mount \\-t smbfs \\-o username=user,password=pass //WinClient/share /mnt/share|挂载一个 windows 网络共享|\n|\\# umount /dev/hda2|卸载一个叫做 hda2 的盘 \\- 先从挂载点 '/mnt/hda2' 退出|\n|\\# umount \\-n /mnt/hda2|运行卸载操作而不写入 /etc/mtab 文件、- 当文件为只读或当磁盘写满时非常有用|\n\n### 光盘\n| 命令 | 说明 |\n|--------|--------|\n|\\# cd\\-paranoia \\-B|从一个 CD 光盘转录音轨到 wav 文件中|\n|\\# cd\\-paranoia \\-\\-|从一个 CD 光盘转录音轨到 wav 文件中（参数、-3）|\n|\\# cdrecord \\-v gracetime=2 dev=/dev/cdrom \\-eject blank=fast \\-force|清空一个可复写的光盘内容|\n|\\# cdrecord \\-v dev=/dev/cdrom cd\\.iso|刻录一个 ISO 镜像文件|\n|\\# gzip \\-dc cd\\_iso\\.gz &#124; cdrecord dev=/dev/cdrom \\-|刻录一个压缩了的 ISO 镜像文件|\n|\\# cdrecord \\-\\-scanbus|扫描总线以识别 scsi 通道|\n|\\# dd if=/dev/hdc &#124; md5sum|校验一个设备的 md5sum 编码，例如一张 CD|\n|\\# mkisofs /dev/cdrom > cd\\.iso|在磁盘上创建一个光盘的 iso 镜像文件|\n|\\# mkisofs /dev/cdrom &#124; gzip > cd\\_iso\\.gz|在磁盘上创建一个压缩了的光盘 iso 镜像文件|\n|\\# mkisofs \\-J \\-allow\\-leading\\-dots \\-R \\-V|创建一个目录的 iso 镜像文件|\n|\\# mount \\-o loop cd\\.iso /mnt/iso|挂载一个 ISO 镜像文件|\n\n## 用户管理\n\n### 用户和群组\n| 命令 | 说明 |\n|--------|--------|\n|\\# chage \\-E 2016\\-12\\-31 user1|设置用户口令的失效期限|\n|\\# groupadd \\[group\\]|创建一个新用户组|\n|\\# groupdel \\[group\\]|删除一个用户组|\n|\\# groupmod \\-n moon sun|重命名一个用户组|\n|\\# grpck|检查 '/etc/passwd' 的文件格式和语法修正以及存在的群组|\n|\\# newgrp \\- \\[group\\]|登陆进一个新的群组以改变新创建文件的预设群组|\n|\\# passwd|修改口令|\n|\\# passwd user1|修改一个用户的口令 \\（只允许 root 执行、)|\n|\\# pwck|检查 '/etc/passwd' 的文件格式和语法修正以及存在的用户|\n|\\# useradd \\-c \"User Linux\" \\-g admin \\-d /home/user1 \\-s /bin/bash user1|创建一个属于 \"admin\" 用户组的用户|\n|\\# useradd user1|创建一个新用户|\n|\\# userdel \\-r user1|删除一个用户 \\( '\\-r' 排除主目录、)|\n|\\# usermod \\-c \"User FTP\" \\-g system \\-d /ftp/user1 \\-s /bin/nologin user1|修改用户属性|\n\n## 包管理\n\n### 打包和压缩文件\n| 命令 | 说明 |\n|--------|--------|\n|\\# bunzip2 file1\\.bz2|解压一个叫做 'file1\\.bz2'的文件|\n|\\# bzip2 file1|压缩一个叫做 'file1' 的文件|\n|\\# gunzip file1\\.gz|解压一个叫做 'file1\\.gz'的文件|\n|\\# gzip file1|压缩一个叫做 'file1'的文件|\n|\\# gzip \\-9 file1|最大程度压缩|\n|\\# rar a file1\\.rar test\\_file|创建一个叫做 'file1\\.rar' 的包|\n|\\# rar a file1\\.rar file1 file2 dir1|同时压缩 'file1', 'file2' 以及目录 'dir1'|\n|\\# rar x file1\\.rar|解压 rar 包|\n|\\# tar \\-cvf archive\\.tar file1|创建一个非压缩的 tarball|\n|\\# tar \\-cvf archive\\.tar file1 file2 dir1|创建一个包含了 'file1', 'file2' 以及 'dir1'的档案文件|\n|\\# tar \\-tf archive\\.tar|显示一个包中的内容|\n|\\# tar \\-xvf archive\\.tar|释放一个包|\n|\\# tar \\-xvf archive\\.tar \\-C /tmp|将压缩包释放到 /tmp 目录下|\n|\\# tar \\-cvfj archive\\.tar\\.bz2 dir1|创建一个 bzip2 格式的压缩包|\n|\\# tar \\-xvfj archive\\.tar\\.bz2|解压一个 bzip2 格式的压缩包|\n|\\# tar \\-cvfz archive\\.tar\\.gz dir1|创建一个 gzip 格式的压缩包|\n|\\# tar \\-xvfz archive\\.tar\\.gz|解压一个 gzip 格式的压缩包|\n|\\# unrar x file1\\.rar|解压 rar 包|\n|\\# unzip file1\\.zip|解压一个 zip 格式压缩包|\n|\\# zip file1\\.zip file1|创建一个 zip 格式的压缩包|\n|\\# zip \\-r file1\\.zip file1 file2 dir1|将几个文件和目录同时压缩成一个 zip 格式的压缩包|\n\n### RPM 包 \\(Fedora,RedHat and alike\\)\n| 命令 | 说明 |\n|--------|--------|\n|\\# rpm \\-ivh \\[package\\.rpm\\]|安装一个 rpm 包|\n|\\# rpm \\-ivh \\-\\-nodeeps \\[package\\.rpm\\]|安装一个 rpm 包而忽略依赖关系警告|\n|\\# rpm \\-U \\[package\\.rpm\\]|更新一个 rpm 包但不改变其配置文件|\n|\\# rpm \\-F \\[package\\.rpm\\]|更新一个确定已经安装的 rpm 包|\n|\\# rpm \\-e \\[package\\]|删除一个 rpm 包|\n|\\# rpm \\-qa|显示系统中所有已经安装的 rpm 包|\n|\\# rpm \\-qa &#124; grep httpd|显示所有名称中包含 \"httpd\" 字样的 rpm 包|\n|\\# rpm \\-qi \\[package\\]|获取一个已安装包的特殊信息|\n|\\# rpm \\-qg \"System Environment/Daemons\"|显示一个组件的 rpm 包|\n|\\# rpm \\-ql \\[package\\]|显示一个已经安装的 rpm 包提供的文件列表|\n|\\# rpm \\-qc \\[package\\]|显示一个已经安装的 rpm 包提供的配置文件列表|\n|\\# rpm \\-q \\[package\\] \\-\\-whatrequires|显示与一个 rpm 包存在依赖关系的列表|\n|\\# rpm \\-q \\[package\\] \\-\\-whatprovides|显示一个 rpm 包所占的体积|\n|\\# rpm \\-q \\[package\\] \\-\\-scripts|显示在安装 / 删除期间所执行的脚本 l|\n|\\# rpm \\-q \\[package\\] \\-\\-changelog|显示一个 rpm 包的修改历史|\n|\\# rpm \\-qf /etc/httpd/conf/httpd\\.conf|确认所给的文件由哪个 rpm 包所提供|\n|\\# rpm \\-qp \\[package\\.rpm\\] \\-l|显示由一个尚未安装的 rpm 包提供的文件列表|\n|\\# rpm \\-\\-import /media/cdrom/RPM\\-GPG\\-KEY|导入公钥数字证书|\n|\\# rpm \\-\\-checksig \\[package\\.rpm\\]|确认一个 rpm 包的完整性|\n|\\# rpm \\-qa gpg\\-pubkey|确认已安装的所有 rpm 包的完整性|\n|\\# rpm \\-V \\[package\\]|检查文件尺寸、 许可、类型、所有者、群组、MD5 检查以及最后修改时间|\n|\\# rpm \\-Va|检查系统中所有已安装的 rpm 包、- 小心使用|\n|\\# rpm \\-Vp \\[package\\.rpm\\]|确认一个 rpm 包还未安装|\n|\\# rpm \\-ivh /usr/src/redhat/RPMS/\\`arch\\`/\\[package\\.rpm\\]|从一个 rpm 源码安装一个构建好的包|\n|\\# rpm2cpio \\[package\\.rpm\\] &#124; cpio \\-\\-extract \\-\\-make\\-directories \\*bin\\*|从一个 rpm 包运行可执行文件|\n|\\# rpmbuild \\-\\-rebuild \\[package\\.src\\.rpm\\]|从一个 rpm 源码构建一个 rpm 包|\n\n### YUM 软件工具 \\(Fedora,RedHat and alike\\)\n| 命令 | 说明 |\n|--------|--------|\n|\\# yum \\-y install \\[package\\]|下载并安装一个 rpm 包|\n|\\# yum localinstall \\[package\\.rpm\\]|将安装一个 rpm 包，使用你自己的软件仓库为你解决所有依赖关系|\n|\\# yum \\-y update|更新当前系统中所有安装的 rpm 包|\n|\\# yum update \\[package\\]|更新一个 rpm 包|\n|\\# yum remove \\[package\\]|删除一个 rpm 包|\n|\\# yum list|列出当前系统中安装的所有包|\n|\\# yum repolist|显示可用的仓库|\n|\\# yum search \\[package\\]|在 rpm 仓库中搜寻软件包|\n|\\# yum clean \\[package\\]|清理 rpm 缓存删除下载的包|\n|\\# yum clean headers|删除所有头文件|\n|\\# yum clean all|删除所有缓存的包和头文件|\n\n\n\n### 备份\n| 命令 | 说明 |\n|--------|--------|\n|\\# find /var/log \\-name '\\*\\.log' &#124; tar cv \\-\\-files\\-from=\\- &#124; bzip2 > log\\.tar\\.bz2|查找所有以 '\\.log' 结尾的文件并做成一个 bzip 包|\n|\\# find /home/user1 \\-name '\\*\\.txt' &#124; xargs cp \\-av \\-\\-target\\-directory=/home/backup/ \\-\\-parents|从一个目录查找并复制所有以 '\\.txt' 结尾的文件到另一个目录|\n|\\# dd bs=1M if=/dev/hda &#124; gzip &#124; ssh user@ip\\_addr 'dd of=hda\\.gz'|通过 ssh 在远程主机上执行一次备份本地磁盘的操作|\n|\\# dd if=/dev/sda of=/tmp/file1|备份磁盘内容到一个文件|\n|\\# dd if=/dev/hda of=/dev/fd0 bs=512 count=1|做一个将 MBR \\(Master Boot Record\\) 内容复制到软盘的动作|\n|\\# dd if=/dev/fd0 of=/dev/hda bs=512 count=1|从已经保存到软盘的备份中恢复 MBR 内容|\n|\\# dump \\-0aj \\-f /tmp/home0\\.bak /home|制作一个 '/home' 目录的完整备份|\n|\\# dump \\-1aj \\-f /tmp/home0\\.bak /home|制作一个 '/home' 目录的交互式备份|\n|\\# restore \\-if /tmp/home0\\.bak|还原一个交互式备份|\n|\\# rsync \\-rogpav \\-\\-delete /home /tmp|同步两边的目录|\n|\\# rsync \\-rogpav \\-e ssh \\-\\-delete /home ip\\_address:/tmp|通过 SSH 通道 rsync|\n|\\# rsync \\-az \\-e ssh \\-\\-delete ip\\_addr:/home/public /home/local|通过 ssh 和压缩将一个远程目录同步到本地目录|\n|\\# rsync \\-az \\-e ssh \\-\\-delete /home/local ip\\_addr:/home/public|通过 ssh 和压缩将本地目录同步到远程目录|\n|\\# tar \\-Puf backup\\.tar /home/user|执行一次对 '/home/user' 目录的交互式备份操作|\n|\\# \\( cd /tmp/local/ && tar c \\. \\) &#124; ssh \\-C user@ip\\_addr 'cd /home/share/ && tar x \\-p'|通过 ssh 在远程目录中复制一个目录内容|\n|\\# \\( tar c /home \\) &#124; ssh \\-C user@ip\\_addr 'cd /home/backup\\-home && tar x \\-p'|通过 ssh 在远程目录中复制一个本地目录|\n|\\# tar cf \\- \\. &#124; \\(cd /tmp/backup ; tar xf \\- \\)|本地将一个目录复制到另一个地方，保留原有权限及链接|\n\n\n## 磁盘和分区\n\n### 文件系统分析\n| 命令 | 说明 |\n|--------|--------|\n|\\# badblocks \\-v /dev/hda1|检查磁盘 hda1 上的坏磁块|\n|\\# dosfsck /dev/hda1|修复 / 检查 hda1 磁盘上 dos 文件系统的完整性|\n|\\# e2fsck /dev/hda1|修复 / 检查 hda1 磁盘上 ext2 文件系统的完整性|\n|\\# e2fsck \\-j /dev/hda1|修复 / 检查 hda1 磁盘上 ext3 文件系统的完整性|\n|\\# fsck /dev/hda1|修复 / 检查 hda1 磁盘上 linux 文件系统的完整性|\n|\\# fsck\\.ext2 /dev/hda1|修复 / 检查 hda1 磁盘上 ext2 文件系统的完整性|\n|\\# fsck\\.ext3 /dev/hda1|修复 / 检查 hda1 磁盘上 ext3 文件系统的完整性|\n|\\# fsck\\.vfat /dev/hda1|修复 / 检查 hda1 磁盘上 fat 文件系统的完整性|\n|\\# fsck\\.msdos /dev/hda1|修复 / 检查 hda1 磁盘上 dos 文件系统的完整性|\n\n### 初始化一个文件系统\n| 命令 | 说明 |\n|--------|--------|\n|\\# fdformat \\-n /dev/fd0|格式化一个软盘|\n|\\# mke2fs /dev/hda1|在 hda1 分区创建一个 linux ext2 的文件系统|\n|\\# mke2fs \\-j /dev/hda1|在 hda1 分区创建一个 linux ext3\\（日志型、) 的文件系统|\n|\\# mkfs /dev/hda1|在 hda1 分区创建一个文件系统|\n|\\# mkfs \\-t vfat 32 \\-F /dev/hda1|创建一个 FAT32 文件系统|\n|\\# mkswap /dev/hda3|创建一个 swap 文件系统|\n\n### SWAP 文件系统\n| 命令 | 说明 |\n|--------|--------|\n|\\# mkswap /dev/hda3|创建一个 swap 文件系统|\n|\\# swapon /dev/hda3|启用一个新的 swap 文件系统|\n|\\# swapon /dev/hda2 /dev/hdb3|启用两个 swap 分区|\n\n## 网络\n\n### 网络 \\(LAN / WiFi\\)\n| 命令 | 说明 |\n|--------|--------|\n|\\# dhclient eth0|以 dhcp 模式启用 'eth0' 网络设备|\n|\\# ethtool eth0|显示网卡 'eth0' 的流量统计|\n|\\# host www\\.example\\.com|查找主机名以解析名称与 IP 地址及镜像|\n|\\# hostname|显示主机名|\n|\\# ifconfig eth0|显示一个以太网卡的配置|\n|\\# ifconfig eth0 192\\.168\\.1\\.1 netmask 255\\.255\\.255\\.0|控制 IP 地址|\n|\\# ifconfig eth0 promisc|设置 'eth0' 成混杂模式以嗅探数据包 \\(sniffing\\)|\n|\\# ifdown eth0|禁用一个 'eth0' 网络设备|\n|\\# ifup eth0|启用一个 'eth0' 网络设备|\n|\\# ip link show|显示所有网络设备的连接状态|\n|\\# iwconfig eth1|显示一个无线网卡的配置|\n|\\# iwlist scan|显示无线网络|\n|\\# mii\\-tool eth0|显示 'eth0'的连接状态|\n|\\# netstat \\-tup|显示所有启用的网络连接和它们的 PID|\n|\\# netstat \\-tupl|显示系统中所有监听的网络服务和它们的 PID|\n|\\# netstat \\-rn|显示路由表，类似于“route \\-n”命令|\n|\\# nslookup www\\.example\\.com|查找主机名以解析名称与 IP 地址及镜像|\n|\\# route \\-n|显示路由表|\n|\\# route add \\-net 0/0 gw IP\\_Gateway|控制预设网关|\n|\\# route add \\-net 192\\.168\\.0\\.0 netmask 255\\.255\\.0\\.0 gw 192\\.168\\.1\\.1|控制通向网络 '192\\.168\\.0\\.0/16' 的静态路由|\n|\\# route del 0/0 gw IP\\_gateway|删除静态路由|\n|\\# echo \"1\" > /proc/sys/net/ipv4/ip\\_forward|激活 IP 转发|\n|\\# tcpdump tcp port 80|显示所有 HTTP 回环|\n|\\# whois www\\.example\\.com|在 Whois 数据库中查找|\n\n### route 设置\n\n#### 基本使用\n\n添加到主机的路由（就是一个 IP 一个 IP 添加）\n\n```\n route add -host 146.148.149.202 dev eno16777984\n route add -host 146.148.149.202 gw 146.148.149.193\n```\n\n添加到网络的路由（批量）\n\n```\nroute add -net 146.148.149.0 netmask 255.255.255.0 dev eno16777984\nroute add -net 146.148.149.0 netmask 255.255.255.0 gw 146.148.149.193\n```\n\n简洁写法\n\n```\nroute add -net 146.148.150.0/24 dev eno16777984\nroute add -net 146.148.150.0/24 gw 146.148.150.193\n```\n\n添加默认网关\n\n```\nroute add default gw 146.148.149.193\n```\n\n删除主机路由：\n\n```\nroute del -host 146.148.149.202 dev eno16777984\n```\n\n删除网络路由：\n\n```\n route del -net 146.148.149.0 netmask 255.255.255.0\n route del -net 146.148.150.0/24\n```\n\n删除默认路由\n\n```\nroute del default gw 146.148.149.193\n```\n#### 在 linux 下设置永久路由的方法\n\n服务器启动时自动设置路由，第一想到的可能时 `rc.local`\n\n按照 linux 启动的顺序，rc.local 里面的内容是在 linux 所有服务都启动完毕，最后才被执行的，也就是说，这里面的内容是在 NFS 之后才被执行的，那也就是说在 NFS 启动的时候，服务器上的静态路由是没有被添加的，所以 NFS 挂载不能成功。\n\n/etc/sysconfig/static-routes\n```\nany net 192.168.3.0/24 gw 192.168.3.254\nany net 10.250.228.128 netmask 255.255.255.192 gw 10.250.228.129\n```\n使用 static-routes 的方法是最好的。无论重启系统和 service network restart 都会生效\n\nstatic-routes 文件又是什么呢，这个是 network 脚本 (/etc/init.d/network) 调用的，大致的程序如下\n\n```\nif [ -f /etc/sysconfig/static-routes  ]; then\n    grep \"^any\" /etc/sysconfig/static-routes | while read ignore args ; do\n        /sbin/route add -$args\n    done\nfi\n```\n从这段脚本可以看到，这个就是添加静态路由的方法，static-routes 的写法是\n\nany net 192.168.0.0/16 gw 网关 ip\n\n\n### Microsoft windows 网络 \\(samba\\)\n| 命令 | 说明 |\n|--------|--------|\n|\\# mount \\-t smbfs \\-o username=user,password=pass //WinClient/share /mnt/share|挂载一个 windows 网络共享|\n|\\# nbtscan ip\\_addr|netbios 名解析|\n|\\# nmblookup \\-A ip\\_addr|netbios 名解析|\n|\\# smbclient \\-L ip\\_addr/hostname|显示一台 windows 主机的远程共享|\n|\\# smbget \\-Rr smb://ip\\_addr/share|像 wget 一样能够通过 smb 从一台 windows 主机上下载文件|\n\n### IPTABLES \\(firewall\\)\n| 命令 | 说明 |\n|--------|--------|\n|\\# iptables \\-t filter \\-L|显示过滤表的所有链路|\n|\\# iptables \\-t nat \\-L|显示 nat 表的所有链路|\n|\\# iptables \\-t filter \\-F|以过滤表为依据清理所有规则|\n|\\# iptables \\-t nat \\-F|以 nat 表为依据清理所有规则|\n|\\# iptables \\-t filter \\-X|删除所有由用户创建的链路|\n|\\# iptables \\-t filter \\-A INPUT \\-p tcp \\-\\-dport telnet \\-j ACCEPT|允许 telnet 接入|\n|\\# iptables \\-t filter \\-A OUTPUT \\-p tcp \\-\\-dport http \\-j DROP|阻止 HTTP 连出|\n|\\# iptables \\-t filter \\-A FORWARD \\-p tcp \\-\\-dport pop3 \\-j ACCEPT|允许转发链路上的 POP3 连接|\n|\\# iptables \\-t filter \\-A INPUT \\-j LOG \\-\\-log\\-prefix|记录所有链路中被查封的包|\n|\\# iptables \\-t nat \\-A POSTROUTING \\-o eth0 \\-j MASQUERADE|设置一个 PAT \\（端口地址转换、) 在 eth0 掩盖发出包|\n|\\# iptables \\-t nat \\-A PREROUTING \\-d 192\\.168\\.0\\.1 \\-p tcp \\-m tcp \\-\\-dport 22 \\-j DNAT \\-\\-to\\-destination 10\\.0\\.0\\.2:22|将发往一个主机地址的包转向到其他主机|\n\n\n# Linux 简单管理\n##  ssh\n\n### ssh 简介及基本操作\n\n#### 简介\nSSH，全名 secure shell，其目的是用来从终端与远程机器交互，SSH 设计之处初，遵循了如下原则：\n\n  * 机器之间通讯的内容必须经过加密。\n  * 加密过程中，通过 public key 加密，private 解密。\n\n#### 密钥\nSSH 通讯的双方各自持有一个公钥私钥对，公钥对对方是可见的，私钥仅持有者可见，你可以通过\"ssh-keygen\"生成自己的公私钥，默认情况下，公私钥的存放路径如下：\n\n  * 公钥：$HOME/.ssh/id_rsa.pub\n  * 私钥：$HOME/.ssh/id_rsa\n\n#### 基于口令的安全验证通讯原理\n\n  建立通信通道的步骤如下：\n\n```\n  1. 远程主机将公钥发给用户 ---- 远程主机收到用户的登录请求，把自己的公钥发给客户端，客户端检查这个 public key 是否在自己的 $HOME/.ssh/known_hosts 中，如果没有，客户端会提示是否要把这个 public key 加入到 known_hosts 中。\n  2. 用户使用公钥加密密码 ------ 用户使用这个公钥，将登录密码加密后，发送回来\n  3. 远程主机使用私钥加密 ------ 远程主机用自己的私钥，解密登录密码，如果密码正确，就同意用户登录。\n  4. 客户端把 PUBLIC KEY(client), 发送给服务器。\n  5. 至此，到此为止双方彼此拥有了对方的公钥，开始双向加密解密。\n```\n\nPS：当网络中有另一台冒牌服务器冒充远程主机时，客户端的连接请求被服务器 B 拦截，服务器 B 将自己的公钥发送给客户端，客户端就会将密码加密后发送给冒牌服务器，冒牌服务器就可以拿自己的私钥获取到密码，然后为所欲为。因此当第一次链接远程主机时，在上述步骤中，会提示您当前远程主机的”公钥指纹”，以确认远程主机是否是正版的远程主机，如果选择继续后就可以输入密码进行登录了，当远程的主机接受以后，该台服务器的公钥就会保存到 ~/.ssh/known_hosts 文件中。\n\n#### StrictHostKeyChecking 和 UserKnownHostsFile\n\n> * 如何让连接新主机时，不进行公钥确认？\n>   * SSH 客户端的 StrictHostKeyChecking 配置指令，可以实现当第一次连接服务器时，自动接受新的公钥。\n>   * [例子:] ssh  -o StrictHostKeyChecking=no  192.168.0.110\n> * 如何防止远程主机公钥改变导致 SSH 连接失败\n>   * 将本地的 known_hosts 指向不同的文件，就不会造成公钥冲突导致的中断了,提示信息由公钥改变中断警告，变成了首次连接的提示。结合自动接收新的公钥\n>   * [例子:] ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null 192.168.0.110\n\n### 基于密匙的安全验证通讯原理\n\n这种方式你需要在当前用户家目录下为自己创建一对密匙，并把公匙放在需要登录的服务器上。当你要连接到服务器上时，客户端就会向服务器请求使用密匙进行安全验证。服务器收到请求之后，会在该服务器上你所请求登录的用户的家目录下寻找你的公匙，然后与你发送过来的公匙进行比较。如果两个密匙一致，服务器就用该公匙加密“质询”并把它发送给客户端。客户端收到“质询”之后用自己的私匙解密再把它发送给服务器。与第一种级别相比，第二种级别不需要在网络上传送口令。\n\nPS：简单来说，就是将客户端的公钥放到服务器上，那么客户端就可以免密码登录服务器了，那么客户端的公钥应该放到服务器上哪个地方呢？默认为你要登录的用户的家目录下的 .ssh 目录下的 authorized_keys 文件中（即：~/.ssh/authorized_keys）。\n我们的目标是：用户已经在主机 A 上登录为 a 用户，现在需要以不输入密码的方式以用户 b 登录到主机 B 上。\n\n可以把密钥理解成一把钥匙，公钥理解成这把钥匙对应的锁头，\n把锁头（公钥）放到想要控制的 server 上，锁住 server, 只有拥有钥匙（密钥）的人，\n才能打开锁头，进入 server 并控制\n而对于拥有这把钥匙的人，必需得知道钥匙本身的密码，才能使用这把钥匙（除非这把钥匙没设置密码）, 这样就可以防止钥匙被了配了（私钥被人复制）\n\n当然，这种例子只是方便理解罢了，\n拥有 root 密码的人当然是不会被锁住的，而且不一定只有一把锁（公钥）,\n但如果任何一把锁，被人用其对应的钥匙（私钥）打开了，server 就可以被那个人控制了\n所以说，只要你曾经知道 server 的 root 密码，并将有 root 身份的公钥放到上面，\n就可以用这个公钥对应的私钥\"打开\" server, 再以 root 的身分登录，即使现在 root 密码已经更改！\n\n步骤如下：\n\n  1. 以用户 a 登录到主机 A 上，生成一对公私钥。\n  2. 把主机 A 的公钥复制到主机 B 的 authorized_keys 中，可能需要输入 b@B 的密码。\n\n\t    ssh-copy-id -i ~/.ssh/id_dsa.pub b@B\n  3. 在 a@A 上以免密码方式登录到 b@B\n\n  \t\tssh b@B\n\ntips:\n\n   假如 B 机器修改端口后，将主机 A 上的公钥复制到 B 机的操作方法是（下面方法中双引号是必须的）：\n\n   ssh-copy-id \"-p 端口号 b@B\"\n\n### SSH 端口转发\n\nSSH 还同时提供了一个非常有用的功能，这就是端口转发。它能够将其他 TCP 端口的网络数据通过 SSH 链接来转发，并且自动提供了相应的加密及解密服务。这一过程有时也被叫做“隧道”(tunneling)，这是因为 SSH 为其他 TCP 链接提供了一个安全的通道来进行传输而得名。\n\nSSH 端口转发自然需要 SSH 连接，而 SSH 连接是有方向的，从 SSH Client 到 SSH Server 。\n而我们所要访问的应用也是有方向的，应用连接的方向也是从应用的 Client 端连接到应用的 Server 端。\n比如需要我们要访问 Internet 上的 Web 站点时，Http 应用的方向就是从我们自己这台主机 (Client) 到远处的 Web Server。\n\n> * SSH 连接和应用的连接这两个连接的方向一致，那我们就说它是本地转发。\n> * SSH 连接和应用的连接这两个连接的方向不同，那我们就说它是远程转发。\n\n#### SSH 正向连接\n\n正向连接就是 client 连上 server，然后把 server 能访问的机器地址和端口（当然也包括 server 自己）镜像到 client 的端口上。\n\n```\n何时使用本地 Tunnel？\n\n> * 比如说你在本地访问不了某个网络服务（如 www.google.com)，而有一台机器（如：xx.xx.xx.xx) 可以，那么你就可以通过这台机器的 ssh 服务来转发\n```\n使用方法\n```\nssh -L <local port>:<remote host>:<remote port> <SSH hostname>\nssh -L [客户端 IP 或省略]:[客户端端口]:[服务器能访问的 IP]:[服务器能访问的 IP 的端口] [登陆服务器的用户名 @服务器 IP] -p [服务器 ssh 服务端口（默认 22）]\n```\nssh  -L 1433:target_server:1433 user@ssh_host\n\n***windows 下使用本地转发 xshell***\n\n```\n(1)ssh 远程连接到 Linux\n(2) 打开代理设置面板，点击：view -> Tunneling Pane, 在弹出的窗口选择 Forwarding Rules\n(3) 在空白处右键：add。\n在弹出的 Forwarding Rule，\nType 选择\"Local(Outgoing)\";\nSource Host 使用默认的 localhost; Listen Port 添上端口 8888;\nDestination Host 使用默认的 localhost；Destination Port 添上 80;\n\nDestination Host 设置为 localhost 为要访问的机器，可以设置为登陆后的机器可以访问到的 IP\n```\n\n#### SSH 反向连接\n\n反向连接就是 client 连上 server，然后把 client 能访问的机器地址和端口（也包括 client 自己）镜像到 server 的端口上。\n\n```\n何时使用反向连接？\n\n比如当你下班回家后就访问不了公司内网的机器了，遇到这种情况可以事先在公司内网的机器上执行远程 Tunnel，连上一台公司外网的机器，等你下班回家后就可以通过公司外网的机器去访问公司内网的机器了。\n```\n使用方法\n```\nssh -R <remote port>:<local host>:<local port> <SSH hostname>\nssh -R [服务器 IP 或省略]:[服务器端口]:[客户端能访问的 IP]:[客户端能访问的 IP 的端口] [登陆服务器的用户名 @服务器 IP] -p [服务器 ssh 服务端口（默认 22）]\n```\n**外网机器 A 要控制 内网机器 B**\n\nA 主机：外网，ip：122.122.122.122，sshd 端口：2222（默认是 22)\n\nB 主机：内网，sshd 端口：2222（默认是 22)\n\n无论是外网主机 A，还是内网主机 B 都需要跑 ssh daemon\n\n***首先在内网机器 B 上执行***\n\n```\n    ssh -NfR 1234:localhost:2222 user1@122.122.122.122 -p 2222\n```\n\n这句话的意思是将 A 主机的 1234 端口和 B 主机的 2222 端口绑定，相当于远程端口映射 (Remote Port Forwarding)。\n\n***外网机器 A 会 listen 本地 1234 端口***\n\n```\n---- 外网机器 A sshd 会 listen 本地 1234 端口\n    #netstat -tanp | grep sshd\n    #Proto Recv-Q Send-Q   Local Address   Foreign Address   State       PID/Program name\n    #tcp     0      0      127.0.0.1:1234    0.0.0.0:*       LISTEN      4234/sshd\n\n---- 在外网机器 A 登录内网机器 B（非 root 用户的话，直接 user@localhost 即可）\n    #ssh user@localhost -p1234\n```\n#### SSH 反向连接自动重连\n\n上面的反向连接（Reverse Connection）不稳定，可能随时断开，需要内网主机 B 再次向外网 A 发起连接，这时需要个\"朋友\"帮你在内网 B 主机执行这条命令。可以使用 Autossh 或者 while 进行循环。\n\n(1) 在 B 机器上将 B 机器公钥放到外网机器 A 上（实现 B 机器自动登录到 A 机器）\n\n(2) 用 Autossh 或者 while 循环 保持 ssh 反向隧道一直连接，CentOS 需要使用 epel 源下载\n\n在 CentOS6 和 CentOS7 都可以执行下面的命令安装 epel 仓库\n\n**while**\n\n编写脚本写入如下内容\n\n```\n#!/bin/bash\n# 远程机器的 IP 和端口\nremote_ip=122.122.122.122\nremote_port=2222\n\nwhile [[ 1==1  ]]\ndo\n    ssh  -o ServerAliveInterval=15 -o ServerAliveCountMax=3 -N -R:1234:localhost:22 -p ${remote_port} root@${remote_ip}\n    sleep 3\ndone\n```\n执行脚本后，即可以通过登陆 122.122.122.122 机器访问本地 1234 端口进行访问此机器\n\n注;可以将此脚本放在后台中运行，并加到系统自启动程序中\n\n**autossh**\n\n```\n#yum -y install epel-release\n```\n安装号 autossh 后使用如下方法进行反向连接\n```\n#autossh -M 5678 -NfR 1234:localhost:2222 user1@122.122.122.122 -p2222\n```\n比之前的命令添加的一个 -M 5678 参数，负责通过 5678 端口监视连接状态，连接有问题时就会自动重连\n\n### windows 下 xshell 使用\n\n  * 私钥，在 Xshell 里也叫用户密钥\n  * 公钥，在 Xshell 里也叫主机密钥\n\n  利用 xshell 密钥管理服务器远程登录，\n\n  (1)Xshell 自带有用户密钥生成向导：点击菜单栏的工具 ->新建用户密钥生成向导\n  (2) 添加公钥 (Pubic Key) 到远程 Linux 服务器\n\n## 用户管理\n\n### Linux 踢出其他正在 SSH 登陆用户\n\n***查看系统在线用户***\n\n```\n[root@Linux ~]#w\n 20:40:18 up 1 day, 23 min,  4 users,  load average: 0.00, 0.00, 0.00\nUSER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT\nroot     tty1     -                Fri09   10:10m  0.34s  0.34s -bash\nroot     pts/2    192.168.31.124   10:30    4:39m  0.99s  0.99s -bash\nroot     pts/3    192.168.31.124   19:55    0.00s  0.07s  0.00s w\nroot     pts/4    192.168.31.124   19:55    4:52   0.16s  0.16s -bash\n```\n***查看当前自己占用终端***\n\n```\n[root@Linux ~]# who am i\nroot     pts/4        2016-10-30 19:55 (192.168.31.124)\n```\n***用 pkill 命令剔除对方***\n\n```\n[root@Linux ~]# pkill -kill -t pts/2\n[root@Linux ~]# w\n 20:44:15 up 1 day, 27 min,  3 users,  load average: 0.01, 0.03, 0.01\n USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT\n root     tty1     -                Fri09   10:14m  0.34s  0.34s -bash\n root     pts/3    192.168.31.124   19:55   51.00s  1.43s  1.35s vim base.md\n root     pts/4    192.168.31.124   19:55    0.00s  0.21s  0.00s w\n```\n如果最后查看还是没有干掉，建议加上 -9 强制杀死。\n[root@Linux ~]# pkill -9 -t pts/2\n\n### 使用脚本创建有 sudo 权限的用户\n\n```\ncat >./create_user.sh <<-'EOF'\n#!/bin/bash\narg=\"ceshi\"\nif id ${arg}\nthen\n    echo \"the username is exsit!\"\nelse\n    useradd $arg\n    echo \"ceshi_password\" | passwd --stdin $arg\n    echo \"${arg} ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/${arg}\nfi\nEOF\n```\n以上脚本会创建用户 `ceshi` 同时用户的密码为 `ceshi_password` ，并且此用户有 sudo 权限\n\n### 无交互式修改用户密码\n\n```\necho \"123456\" | passwd --stdin root\n```\n## 网卡 bond\n\nLinux 多网卡绑定\n\n网卡绑定 mode 共有七种 (0~6) bond0、bond1、bond2、bond3、bond4、bond5、bond6\n\n常用的有三种\n\n> * mode=0：平衡负载模式，有自动备援，但需要”Switch”支援及设定。\n> * mode=1：自动备援模式，其中一条线若断线，其他线路将会自动备援。\n> * mode=6：平衡负载模式，有自动备援，不必”Switch”支援及设定。\n\n需要说明的是如果想做成 mode 0 的负载均衡，仅仅设置这里 options bond0 miimon=100 mode=0 是不够的，与网卡相连的交换机必须做特殊配置（这两个端口应该采取聚合方式），因为做 bonding 的这两块网卡是使用同一个 MAC 地址。从原理分析一下（bond 运行在 mode 0 下）：\n\nmode 0 下 bond 所绑定的网卡的 IP 都被修改成相同的 mac 地址，如果这些网卡都被接在同一个交换机，那么交换机的 arp 表里这个 mac 地址对应的端口就有多 个，那么交换机接受到发往这个 mac 地址的包应该往哪个端口转发呢？正常情况下 mac 地址是全球唯一的，一个 mac 地址对应多个端口肯定使交换机迷惑了。所以 mode0 下的 bond 如果连接到交换机，交换机这几个端口应该采取聚合方式（cisco 称为 ethernetchannel，foundry 称为 portgroup），因为交换机做了聚合后，聚合下的几个端口也被捆绑成一个 mac 地址。我们的解 决办法是，两个网卡接入不同的交换机即可。\n\nmode6 模式下无需配置交换机，因为做 bonding 的这两块网卡是使用不同的 MAC 地址。\n\n## 其他设置\n\n\n### 时区及时间\n\n时区就是时间区域，主要是为了克服时间上的混乱，统一各地时间。地球上共有 24 个时区，东西各 12 个时区（东 12 与西 12 合二为一）。\n\n#### UTC 和 GMT\n\n时区通常写成`+0800`，有时也写成`GMT +0800`，其实这两个是相同的概念。\n\nGMT 是格林尼治标准时间（Greenwich Mean Time）。\n\nUTC 是协调世界时间（Universal Time Coordinated），又叫世界标准时间，其实就是`0000`时区的时间。\n\n#### Linux 下调整时区及更新时间\n\n修改系统时间\n\n```\n$ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime\n```\n\n修改 /etc/sysconfig/clock 文件，修改为：\n\n```\nZONE=\"Asia/Shanghai\"\nUTC=false\nARC=false\n```\n校对时间\n```\n$ntpdate cn.ntp.org.cn\n```\n设置硬件时间和系统时间一致并校准\n```\n$/sbin/hwclock --systohc\n```\n\n***定时同步时间设置***\n\n凌晨 5 点定时同步时间\n\n```\necho \"0 5 * * *  /usr/sbin/ntpdate cn.ntp.org.cn\" >> /var/spool/cron/root\n或者\necho \"0 5 * * *  /usr/sbin/ntpdate 133.100.11.8\" >> /var/spool/cron/root\n\n```\n### 登录提示信息\n\n#### 修改登录前的提示信息\n\n**(1) 系统级别的设置方法 /etc/issue**\n\n使用此方法时远程 ssh 连接的时候并不会显示\n\n```\n在登录系统输入用户名之前，可以看到上方有 WELCOME...... 之类的信息，这里会显示 LINUX 发行版本名称，内核版本号，日期，机器信息等等信息，\n\n首先打开 /etc/issue 文件，可以看到里面是这样一段\"Welcome to <LINUX 发行版本名称》-kernel 后接各项参数、\"\n\n参数的各项说明：\n\\r 显示 KERNEL 内核版本号；\n\\l 显示虚拟控制台号；\n\\d 显示当前日期；\n\\n 显示主机名；\n\\m 显示机器类型，即 CPU 架构，如 i386 等；\n\n可以显示所有必要的信息：\n\nWelcome to <LINUX 发行版本名称》-kernel \\r (\\l) \\d \\n \\m.\n```\n\n### 修改登录成功后的信息\n\nmotd(message of the day)\n\n修改登录成功后的提示信息在此文件中添加内容即可：/etc/motd\n\n如：\n```\n/////////////////////////////////////\n系统初始化配置提示\nxxxx\n\n应用联系人：xxxx 联系方式：xxxx\n/////////////////////////////////////\n```\n\n# CentOS 7 vs CentOS 6 的不同\n\n## 运行相关\n\n**桌面系统**\n\n    [CentOS6] GNOME 2.x\n    [CentOS7] GNOME 3.x（GNOME Shell）\n\n**文件系统**\n\n    [CentOS6] ext4\n    [CentOS7] xfs\n\n**内核版本**\n\n    [CentOS6] 2.6.x-x\n    [CentOS7] 3.10.x-x\n\n**启动加载器**\n\n    [CentOS6] GRUB Legacy (+efibootmgr)\n    [CentOS7] GRUB2\n\n**防火墙**\n\n    [CentOS6] iptables\n    [CentOS7] firewalld\n\n**默认数据库**\n\n    [CentOS6] MySQL\n    [CentOS7] MariaDB\n\n**文件结构**\n\n    [CentOS6] /bin, /sbin, /lib, and /lib64 在 / 下\n    [CentOS7] /bin, /sbin, /lib, and /lib64 移到 /usr 下\n\n**主机名**\n\n    [CentOS6] /etc/sysconfig/network  # 修改主机名时，修改此文件，同时在命令行执行 \"hostname 新主机名\"\n    [CentOS7] /etc/hostname\n\n**时间同步**\n\n    [CentOS6]\n    $ ntp\n    $ ntpq -p\n\n    [CentOS7]\n    $ chrony\n    $ chronyc sources\n\n**修改时区**\n\n    [CentOS6]\n    $ vim /etc/sysconfig/clock\n       ZONE=\"Asia/Shanghai\"\n       UTC=fales\n    $ sudo ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime\n\n    [CentOS7]\n    $ timedatectl set-timezone Asia/Shanghai\n    $ timedatectl status\n\n**修改语言**\n\n    [CentOS6]\n    $ vim /etc/sysconfig/i18n\n       LANG=\"en_US.utf8\"\n    $ /etc/sysconfig/i18n\n    $ locale\n\n    [CentOS7]\n    $ localectl set-locale LANG=en_US.utf8\n    $ localectl status\n\n**重启关闭**\n\n    1) 关闭\n    [CentOS6]\n    $ shutdown -h now\n\n    [CentOS7]\n    $ poweroff\n    $ systemctl poweroff\n\n    2) 重启\n    [CentOS6]\n    $ reboot\n    $ shutdown -r now\n\n    [CentOS7]\n    $ reboot\n    $ systemctl reboot\n\n    3) 单用户模式\n    [CentOS6]\n    $ init S\n\n    [CentOS7]\n    $ systemctl rescue\n\n    4) 启动模式\n    [CentOS6]\n    [GUICUI]\n    $ vim /etc/inittab\n      id:3:initdefault:\n    [CUIGUI]\n    $ startx\n\n    [CentOS7]\n    [GUICUI]\n    $ systemctl isolate multi-user.target\n    [CUIGUI]\n    $systemctl isolate graphical.target\n    默认\n    $ systemctl set-default graphical.target\n\n    [CentOS6]\n    $ chkconfig service_name on/off\n\n    [CentOS7]\n    $ systemctl enable service_name\n    $ systemctl disable service_name\n\n**服务一览**\n\n    [CentOS6]\n    $ chkconfig --list\n\n    [CentOS7]\n    $ systemctl list-unit-files\n    $ systemctl --type service\n\n**强制停止**\n\n    [CentOS6]\n    $ kill -9 <PID>\n\n    [CentOS7]\n    $ systemctl kill --signal=9 sshd\n\n## 网络\n\n**网络信息**\n\n    [CentOS6]\n    $ netstat\n    $ netstat -I\n    $ netstat -n\n\n    [CentOS7]\n    $ ip -s l\n    $ ss\n\n**IP 地址 MAC 地址**\n\n    [CentOS6]\n    $ ifconfig -a\n\n    [CentOS7]\n    $ ip address show\n\n**路由**\n\n    [CentOS6]\n    $ route -n\n    $ route -A inet6 -n\n\n    [CentOS7]\n    $ ip route show\n    $ ip -6 route show\n"
  },
  {
    "path": "doc/Linux/op.md",
    "content": "# 常用问题处理\n\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 系统配置](#1-系统配置)\n    * [1.1 Yum 安装安装包时提示证书过期](#11-yum-安装安装包时提示证书过期)\n    * [1.2 系统日志中的时间不准确](#12-系统日志中的时间不准确)\n    * [1.3  Linux 系统日志出现 hung_task_timeout_secs 和 blocked for more than 120 seconds](#13--linux-系统日志出现-hung_task_timeout_secs-和-blocked-for-more-than-120-seconds)\n        * [问题现象](#问题现象)\n        * [问题原因](#问题原因)\n        * [处理方法](#处理方法)\n        * [内核参数解释](#内核参数解释)\n* [2 磁盘](#2-磁盘)\n    * [2.1 lvm 变为 inactive 状态](#21-lvm-变为-inactive-状态)\n\n<!-- vim-markdown-toc -->\n\n## 1 系统配置\n### 1.1 Yum 安装安装包时提示证书过期\n\nyum 安装安装包时提示\"Peer's Certificate has expired\"\n\nhttps 的证书是有开始时间和失效时间的。因此本地时间要在这个证书的有效时间内。不过最好的方式，还是能够把时间进行同步。\n\n```\n# ntpdate pool.ntp.org\n```\n\n###  1.2 系统日志中的时间不准确\n\n重启下 rsyslog 服务\n\n```\n/etc/init.d/rsyslog restart\n\n```\n### 1.3  Linux 系统日志出现 hung_task_timeout_secs 和 blocked for more than 120 seconds\n#### 问题现象\n\nLinux 系统出现系统没有响应。 在 /var/log/message 日志中出现大量的类似如下错误信息：\n```\necho 0 > /proc/sys/kernel/hung_task_timeout_secs disables this message.\nblocked for more than 120 seconds\n```\n同时看监控时发现，服务器异常期间磁盘 io 比较高，cpu load 比较高\n\n#### 问题原因\n默认情况下， Linux 会最多使用 40% 的可用内存作为文件系统缓存。当超过这个阈值后，文件系统会把将缓存中的内存全部写入磁盘， 导致后续的 IO 请求都是同步的。\n将缓存写入磁盘时，有一个默认 120 秒的超时时间。 出现上面的问题的原因是  IO 子系统的处理速度不够快，不能在 120 秒将缓存中的数据全部写入磁盘。\nIO 系统响应缓慢，导致越来越多的请求堆积，最终系统内存全部被占用，导致系统失去响应。\n\n#### 处理方法\n根据应用程序情况，对 vm.dirty_ratio，vm.dirty_background_ratio 两个参数进行调优设置。 例如，推荐如下设置：\n```\n# sysctl -w vm.dirty_ratio=10\n# sysctl -w vm.dirty_background_ratio=5\n# sysctl -p\n```\n 如果系统永久生效，修改 /etc/sysctl.conf  文件。加入如下两行：\n```\n#vi /etc/sysctl.conf\nvm.dirty_background_ratio = 5\nvm.dirty_ratio = 10\n```\n重启系统生效。\n#### 内核参数解释\n\n> * vm.dirty_background_ratio: 这个参数指定了当文件系统缓存脏页数量达到系统内存百分之多少时（如 5%）就会触发 pdflush/flush/kdmflush 等后台回写进程运行，将一定缓存的脏页异步地刷入外存；\n> * vm.dirty_ratio: 而这个参数则指定了当文件系统缓存脏页数量达到系统内存百分之多少时（如 10%），系统不得不开始处理缓存脏页（因为此时脏页数量已经比较多，为了避免数据丢失需要将一定脏页刷入外存）；在此过程中很多应用进程可能会因为系统转而处理文件 IO 而阻塞。\n\n一般情况下，dirty_ratio 的触发条件不会达到，因为每次会先达到 vm.dirty_background_ratio 的条件，然后触发 flush 进程进行异步的回写操作，但是这一过程中应用进程仍然可以进行写操作，如果应用进程写入的量大于 flush 进程刷出的量，就会达到 vm.dirty_ratio 这个参数所设定的坎，此时操作系统会转入同步地处理脏页的过程，阻塞应用进程。\n\n\n## 2 磁盘\n### 2.1 lvm 变为 inactive 状态\n\nlvscan 查看 lvm 状态\n```\n[root@DB01 log]# lvscan\nACTIVE       '/dev/OraBack/backupone' [7.00 TB] inherit\nACTIVE       '/dev/OraBack/backuptwo' [7.00 TB] inherit\nACTIVE       '/dev/OraBack/backupthree' [1.00 TB] inherit\ninactive     '/dev/OraBack【vg 名字】/orcl' [3.00 TB] inherit\n```\n激活 VG\n```\n[root@DB01 log]# vgchange -ay OraBack\n4 logical volume(s) in volume group \"OraBack\" now active\n```\nlvscan 查看 lvm 状态\n```\n[root@DB01 log]# lvscan\nACTIVE            '/dev/OraBack/backupone' [7.00 TB] inherit\nACTIVE            '/dev/OraBack/backuptwo' [7.00 TB] inherit\nACTIVE            '/dev/OraBack/backupthree' [1.00 TB] inherit\nACTIVE            '/dev/OraBack/orcl' [3.00 TB] inherit\n```\n\n挂载\n\nmount -a\n\n\n"
  },
  {
    "path": "doc/Linux/optimize.md",
    "content": "# Linux 优化\n\n<!-- vim-markdown-toc GFM -->\n\n* [说明](#说明)\n    * [应用类型](#应用类型)\n    * [监测工具](#监测工具)\n        * [综合工具之 sar](#综合工具之-sar)\n        * [dstat](#dstat)\n* [Linux 性能监测 CPU 篇](#linux-性能监测-cpu-篇)\n    * [底线](#底线)\n    * [vmstat 命令](#vmstat-命令)\n    * [mpstat 命令](#mpstat-命令)\n    * [ps 命令](#ps-命令)\n* [Linux 性能监测 内存篇](#linux-性能监测-内存篇)\n    * [vmstat 命令](#vmstat-命令-1)\n* [Linux 性能监测 磁盘 IO 篇](#linux-性能监测-磁盘-io-篇)\n    * [内存页](#内存页)\n    * [缺页中断](#缺页中断)\n    * [File Buffer Cache](#file-buffer-cache)\n    * [页面类型](#页面类型)\n    * [IO's Per Seconds(OIPS)](#ios-per-secondsoips)\n    * [顺序 IO 和随机 IO](#顺序-io-和随机-io)\n    * [SWAP](#swap)\n        * [关掉 swap](#关掉-swap)\n* [Linux 性能监测 网络篇](#linux-性能监测-网络篇)\n    * [netperf](#netperf)\n    * [iperf](#iperf)\n    * [tcpdump 和 tcptrace](#tcpdump-和-tcptrace)\n* [ulimit 关于系统连接数的优化](#ulimit-关于系统连接数的优化)\n    * [修改方式](#修改方式)\n\n<!-- vim-markdown-toc -->\n## 说明\n\n系统优化是一项复杂、繁琐、长期的工作，优化前需要监测、采集、测试、评估，优化后也需要测试、采集、评估、监测，而且是一个长期和持续的过程，不是说现在又花了、测试了，以后就可以一劳永逸，而不是说书本上的优化就适合眼下正在运行的系统，不同的系统、不同的硬件、不用的应用优化的重点也不同、优化的方法也不同、优化的参数也不同。\n\n性能监测是系统优化过程中重要的一环，如果没有监测、不清楚性能瓶颈在哪里，怎么优化呢？所以`找到性能瓶颈`是性能监测的目的，也是系统优化的关键。\n\n系统由若干子系统构成，通常修改一个子系统有可能影响到另外一个子系统，甚至会导致整个系统不稳定、崩溃。\n\n所以说优化、监测、测试通常是连在一起的，而且是一个循环而且长期的过程，通常监测的子系统有以下这些：\n\n* CPU\n* Memory\n* IO\n* Network\n\n这些子系统互相依赖，了解这些子系统的特性，监测这些子系统的系能参数以及及时发现可能会出现的瓶颈对系统优化很有帮助\n\n本系列将按照 CPU、内存、磁盘 IO、网络这几个方面分别介绍\n\n### 应用类型\n\n不同的系统用途也不同，要找到性能瓶颈需要知道系统跑的是什么应用、有些什么特点，比如 web server 对系统的要求肯定和 file server 不一样，所以分清不同系统的应用类型很重要。\n\n通常应用可以分为两种类型：\n\n* IO 相关\n  * IO 相关的应用通常用来出来大量的数据，需要大量内存和存储，频繁 IO 操作读写数据\n  * 而对 CPU 的要求则较少，大部分时间 CPU 都在等待硬盘，比如，数据库服务器、文件服务器等\n* CPU 相关\n  * CPU 相关的应用需要使用大量 CPU\n  * 比如高并发的 web/mail 服务器、图像 / 视频处理、科学计算等都可视作 CPU 相关的应用\n\n### 监测工具\n\n我们只需要简单的工具就可以对 Linux 的性能进行监控，以下常用的工具：\n\n|     工具    |      简介                         | 备注|\n|:-:|---|---|\n|  top        |  查看进程活动状态以及一些系统状况 | |\n|  vmstat     |  查看系统状态、硬件和系统信息等   | |\n|  iostat     |  查看 CPU 负载、硬盘状况          | |\n|  sar        |  综合工具，查看系统状况           |（厂内默认安装）|\n|  mpstat     |  查看多处理器状况                 | |\n|  netstat    |  查看网络状况|日常工作中推荐使用 ss 命令以替代 netstat     |\n|  iptraf     |  实时网络状态监测|【推荐】 比如网卡打满时，查看哪个 port 流量比较高  |\n|  tcpdump    |  抓取网络数据包，详细分析         | |\n|  tcptrace   |  网络包分析工具                   | |\n|  netperf    |  网络带宽工具                     | |\n|  dstat      |  综合了 vmstat、iostat、ifstat、netstat 等多个信息  |(python) 厂内默认安装|\n\n#### 综合工具之 sar\n\n> 安装及简介\n```\nyum instal -y sysstat\n\nsysstat 工具包中包含两类工具：\n即时查看工具：iostat、mpstat、sar\n累计统计工具：sar\n\n也就是说，sar 具有这两种功能。因此，sar 是 sysstat 中的核心工具。\n\n为了实现 sar 的累计统计，系统必须周期地记录当时的信息，这是通过调用 /usr/lib64/sa/ 中的三个工具实现的：\n\n(1) sa1 ：收集并存储每天系统动态信息到一个二进制的文件中，用作 sadc 的前端程序\n(2) sa2 ：收集每天的系统活跃信息写入总结性的报告，用作 sar 的前端程序\n(3) sadc ：系统动态数据收集工具，收集的数据被写入一个二进制的文件中，它被用作 sar 工具的后端\n\n在 CentOS 系统的默认设置中，以如下的方式使用这三个工具：\n\n在守护进程 /etc/rc.d/init.d/sysstat 中使用 /usr/lib/sa/sadc -F -L - 命令创建当日记录文件，文件为 /var/log/sa/saDD，其中 DD 为当天的日期。当系统重新启动后，会向文件 /var/log/sa/saDD 输出类似 11:37:16 AM LINUX RESTART 这样的行信息。\n在 cron 任务 /etc/cron.d/sysstat 中每隔 10 分钟执行一次 /usr/lib/sa/sa1 1 1 命令，将信息写入文件 /var/log/sa/saDD\n在 cron 任务 /etc/cron.d/sysstat 中每天 23:53 执行一次 /usr/lib/sa/sa2 -A 命令，将当天的汇总信息写入文件 /var/log/sa/saDD\n您可以修改 /etc/cron.d/sysstat 以适合您的需要。\n\n另外，文件 /var/log/sa/saDD 为二进制文件，不能使用 more、less 等文本工具查看，必须用 sar 或 sadf 命令查看。\n \n```\n> 使用\n```\nsar -n DEV 网卡流量\nsar -q 系统负载\nsar -b 磁盘读写\nsar -f /var/log/sa/saxx 历史文件\n\nsar 每十分钟把系统的状态过滤一遍，并生产日志在 ls /var/log/sa\n\nsar -n DEV 1 10 查看网卡流量（没 1s 输出一次，总共输出 10 次，最后会将这十次的采集信息进行统计）\nrxpck/s   接收到的数据包  如果你要接收很多数据包，证明有人给你发送数据包 就是被攻击 几千是正常的，上万就不正常了\ntxpck/s   发送的数据包\n\nrxkB/s  数据量\nrxcmp/S 数据量\n\nsar -n DEV -f /var/log/sa/sa17  查看网卡流量并指定一个文件\nsar -q 1 10 查看系统负载\nsar -q -f /var/log/sa/sa17  查看当月 16 号的数据\n```\n\n> 过期日志自动清理\n```\n保留天数配置：/etc/sysconfig/sysstat\n执行文件    ：/usr/lib64/sa/sa2\n定时任务配置：/etc/cron.d/sysstat\n```\n如果没有定时过期清理，可以检查下定时任务中 \"53 23 * * * root /usr/lib64/sa/sa2 -A\" 是否为注释状态\n\n#### dstat\n\n> 常用命令\n```\n监控 CPU\\MEN\\磁盘IO 使用最多的进程: dstat --top-mem --top-io --top-cpu\n\ndstat -c --top-cpu -d --top-bio --top-latency --disk-util\n```\n\n> * dstat --top-cpu：显示最消耗 CPU 的进程\n> * dstat --top-cuptime：最消耗 CPU 时间的进程，以毫秒为单位\n> * dstat --top-io：显示消耗 io 最多的进程\n> * dstat --top-latency：显示哪个进程有最大的延迟\n> * dstat --top-mem:显示用内存最多的线程\n> * dstat --top-mem  --top-cpu:俩个一起使用也是OK的\n\n\n## Linux 性能监测 CPU 篇\n\n\nCPU 的占用主要取决于什么样的资源在 CPU 上面运行，比如拷贝一个文件通常占用较少的 CPU，因为大部分工作是由 DMA（Direct Memory Access）完成，只是在完成拷贝以后给一个中断让 CPU 知道拷贝已经完成；科学计算通常占用较多的 CPU，大部分计算工作都需要在 CPU 上完成，内存、硬盘等子系统只是做暂时的数据存储工作。\n\n要想监测和理解 CPU 的性能需要知道一些的操作系统基本知识，比如：中断、进程调度、进程上下文切换、可运行队列等。\n\n用一个例子来简单介绍一下这些概念和他们的关系，CPU 很无辜，是个任劳任怨的打工仔，每时每刻都有工作在做（进程、线程）并且自己有一张工作清单（可运行队列），由老板（进程调度）来决定他该干什么，他需要和老板沟通以便得到老板的想法并及时调整自己的工作（上下文切换），部分工作做完以后还需要及时向老板汇报（中断），所以打工仔（CPU）除了做自己该做的工作之外，还有大量时间和精力花在沟通和汇报上。\n\nCPU 也是一种硬件资源，和任何其他设备一样也需要驱动和管理程序才能使用，我们可以把内核的进程调度看作是 CPU 的管理程序，用来管理和分配 CPU 资源，合理安排进程抢占 CPU，并决定哪个进程该使用 CPU、哪个进程该等待。\n\n操作系统内核里的进程调度主要用来调度两类资源：进程（或线程）和中断，进程调度给不同的资源分配了不同的优先级，**优先级最高的是硬件中断，其次是内核（系统）进程，最后是用户进程**。\n\n每个 CPU 都维护这一个可运行队列，用来存放那些可运行的线程。线程要么在睡眠状态（blocked 正在等待 IO）、要么在可运行状态，如果 CPU 当前负载太高而新的请求不断，就会出现进程调度暂时应付不过来的情况，这个时候就不得不把线程暂时放到可运行队列中。\n\n本文是讨论的性能监测，上面淡了一堆都没提到性能，那么这些概念和性能监测有什么关系呢？关系重大！如果你是老板，你如何检查打工仔的效率（性能）呢？我们一般会通过以下这些信息来判断打工仔是否偷懒：\n\n* 打工仔接受和完成多少任务并向老板汇报了（中断）\n* 打工仔和老板沟通、写上每项工作的工作进度（上下文切换）\n* 打工仔的工作列表是不是都有排满（可运行队列）\n* 打工仔工作效率如何，是不是在偷懒（CPU 利用率）\n\n现在把打工仔换成 CPU，我们可以通过查看这些重要参数：**中断**、**上下文切换**、**可运行队列**、**CPU 利用率**来检测 CPU 的性能。\n\n### 底线\n\nLinux 性能监测：介绍提到了性能监测需要知道底线，那么监测 CPU 性能的底线是什么呢？\n\n通常我们期望我们的系统能达到以下目标：\n\n* **CPU 利用率**，如果 CPU 用 100% 的利用率，那么应该达到这样一个平衡：65%-70% User Time，30%-35% System Time，0%-5% Idle Time\n* **上下文切换**，上下文切换应该和 CPU 利用率联系起来看，如果能保持上面的 CPU 利用率平衡，大量的上下文切换是可以接受的\n* **可运行队列**，每个可运行队列不应该由超过 1-3 个线程（每处理器），比如：双处理器系统的可运行队列里不应该超过 6 个线程\n\n### vmstat 命令\n\nvmstat 是个查看系统整体性能的小工具，小巧，即使在很 heavy 的情况下也允许良好，并且可以用时间间隔采集得到连续的性能数据。\n\n参数介绍：\n\n* r，可运行队列的线程数，这些线程都是可运行状态，只不过 CPU 暂时不可用\n* b，被 blocked 的进程数，正在等待 IO 请求\n* in，被处理过的中断数\n* cs，系统上正在做上下文切换的数目\n* us，用户占用 CPU 的百分比\n* sys，内核和中断占用 CPU 的百分比\n* wa，所有可运行的线程被 blocked 以后都在等待 IO，这时候 CPU 空闲的百分比\n* id，CPU 完全空闲的百分比\n\n举两个现实中的例子来分析一下\n\n```\n$ vmstat 1\nprocs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------\n r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st\n 4  0    140 2915476 341288 3951700  0    0     0     0 1057  523 19 81  0  0  0\n 4  0    140 2915724 341296 3951700  0    0     0     0 1048  546 19 81  0  0  0\n 4  0    140 2915848 341296 3951700  0    0     0     0 1044  514 18 82  0  0  0\n 4  0    140 2915848 341296 3951700  0    0     0    24 1044  564 20 80  0  0  0\n 4  0    140 2915848 341296 3951700  0    0     0     0 1060  546 18 82  0  0  0\n\n```\n\n从上面的数据可以看出几点：\n\n1. interrupts(in) 非常高，context switch(cs) 比较低，说明这个 CPU 一直在不停的请求资源\n2. user time(us) 一直保持在 80% 以上，而且上下文切换较低 (cs)，说明某个进程可能一直霸占着 CPU\n* run queue(r) 刚好在 4 个\n\n```\n$ vmstat 1\nprocs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------\n r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st\n14  0    140 2904316 341912 3952308  0    0     0   460 1106 9593 36 64  1  0  0\n17  0    140 2903492 341912 3951780  0    0     0     0 1037 9614 35 65  1  0  0\n20  0    140 2902016 341912 3952000  0    0     0     0 1046 9739 35 64  1  0  0\n17  0    140 2903904 341912 3951888  0    0     0    76 1044 9879 37 63  0  0  0\n16  0    140 2904580 341912 3952108  0    0     0     0 1055 9808 34 65  1  0  0\n\n```\n\n从上面的数据可以看出几点：\n\n1. context switch(cs) 比 interrupts(in) 要高的多，说明内核不得不来回切换进程\n2. 进一步观察发现 system time(sy) 很高而 user time(us) 很低，而且加上高频度的上下文切换 (cs)，说明正在运行的应用程序调用了大量的系统调用\n3. run queue(r) 在 14 个线程以上，按照这个而是机器的硬件配置 (4 核），应该保持在 12 以内\n\n### mpstat 命令\n\nmpstat 和 vmstat 类似，不同的是 mpstat 可以输出多个处理器的数据，下面的输出显示 CPU1 和 CPU2 基本上没有派上用场，系统有足够的能力处理更多的任务\n\n```\n$ mpstat -P ALL 1\nLinux 2.6.18-164.el5 (vpsee) 11/13/2009\n\n02:24:33 PM  CPU   %user   %nice    %sys %iowait    %irq   %soft  %steal   %idle    intr/s\n02:24:34 PM  all    5.26    0.00    4.01   25.06    0.00    0.00    0.00   65.66   1446.00\n02:24:34 PM    0    7.00    0.00    8.00    0.00    0.00    0.00    0.00   85.00   1001.00\n02:24:34 PM    1   13.00    0.00    8.00    0.00    0.00    0.00    0.00   79.00    444.00\n02:24:34 PM    2    0.00    0.00    0.00  100.00    0.00    0.00    0.00    0.00      0.00\n02:24:34 PM    3    0.99    0.00    0.99    0.00    0.00    0.00    0.00   98.02      0.00\n```\n\n### ps 命令\n\n如何查看某个程序、进程占用了多少 CPU 资源呢？下面是 java 在一台 Linux 服务器上的运行情况，当前只有 2 个 java 进程\n\n```\n$ while :; do ps -eo pid,ni,pri,pcpu,psr,comm | grep 'java'; sleep 1; done\n PID  NI PRI %CPU PSR COMMAND\n 7252   0  24  3.2   3 java\n 9846   0  24  8.8   0 java\n 7252   0  24  3.2   2 java\n 9846   0  24  8.8   0 java\n 7252   0  24  3.2   2 java\n```\n\n## Linux 性能监测 内存篇\n\n这里讲到的`内存`包括**物理内存**和**虚拟内存**。虚拟内存 (Virtual Memory) 把计算机的内存空间扩展到硬盘，物理内存 (RAM) 和硬盘的一部分空间 (SWAP) 组合在一起作为虚拟内存为计算机提供了一个连续的虚拟内存空间，好处是我们拥有的内存`变多了`，可以运行更多、更大的程序，坏处是把部分硬盘当内存用，整体性能受到影响，硬盘读写速度要比内存慢几个数量级，并且 RAM 和 SWAP 之间的交换增加了系统的负担。\n\n在操作系统里，虚拟内存被分为页，在 x86 系统上每个页大小是 4KB。Linux 内核读写虚拟内存是以“页”为单位操作的，把内存转移到硬盘交换空间 (SWAP) 和从交换空间读取内存的时候都是按页来读写的。\n\n内存和 SWAP 的这种交互过程称为页面交换 (Paging)，值得注意的是 paging 和 swapping 是两个完全不同的概念，国内很多参考书把这两个概念混为一谈，swapping 也翻译为交换，在操作系统里是指把某程序完全交换到硬盘以腾出内存给新程序使用，和 paging 只交换程序的部分（页面）是两个不同的概念。春吹的 swapping 在现代操作系统中已经很难看到了，因为把整个程序交换到硬盘的办法既耗时又费力而且没必要，现代操作系统基本都是 paging 或者 paging/swapping 混合，swapping 最初是在 Unix system V 上实现的。\n\n虚拟内存管理是 Linux 内核里面最复杂的部分，要弄懂这部分内容可能需要一本书的讲解。这里只介绍和性能监测有关的两个内核进程：kswapd 和 pdflush。\n\n**kswapd daemon**用来检查 pages_high 和 pages_low，如果可用内存少于 pages_low，kswapd 就开始扫描并试图释放 32 个页面，并且重复扫描释放的过程知道可用内存大于 pages_high 为止。扫描的时候检查 3 件事：\n\n* 如果页面没有修改，把页放到可用内存列表里\n* 如果页面被文件系统修改，把页面内容写到磁盘上\n* 如果页面被修改了，但不是被文件系统修改的，把页面写到交换空间\n\n**pdflush daemon**用来同步文件相关的内存页面，把内存页面及时同步到硬盘上。比如打开一个文件，文件被导入到内存里，对文件修改并保存后，内核并不马上保存文件到硬盘，由 pdfush 决定什么时候把相应页面写到硬盘，这由一个内核参数 vm.dirty_background_ratio 来控制，比如下面的参数显示脏页面（dirty pages）达到所有内存页面 10% 的时候开始写入硬盘。\n\n```\n# /sbin/sysctl -n vm.dirty_background_ratio\n10\n```\n\n### vmstat 命令\n\n继续 vmstat 一些参数的介绍，上一篇 Linux 性能监测：CPU 介绍了 vmstat 的部分参数，这里介绍另外一部分。以下数据来自一个 256MB RAM，512MB SWAP 的 Xen VPS：\n\n```\n# vmstat 1\nprocs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------\n r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st\n 0  3 252696   2432    268   7148 3604 2368  3608  2372  288  288  0  0 21 78  1\n 0  2 253484   2216    228   7104 5368 2976  5372  3036  930  519  0  0  0 100  0\n 0  1 259252   2616    128   6148 19784 18712 19784 18712 3821 1853  0  1  3 95  1\n 1  2 260008   2188    144   6824 11824 2584 12664  2584 1347 1174 14  0  0 86  0\n 2  1 262140   2964    128   5852 24912 17304 24952 17304 4737 2341 86 10  0  0  4\n```\n*memory*\n\n* swpd，已使用的 SWAP 控件大小，KB 为单位\n* free，可用的物理内存大小，KB 为单位\n* buff，物理内存用来缓存读写操作的 buffer 大小，KB 为单位\n* cache，物理内存用来缓存进程地址空间的 cache 大小，KB 为单位\n\n*swap*\n\n* si，数据从 SWAP 读取到 RAM（swap in）的大小，KB 为单位\n* so，数据从 RAM 写到 SWAP（swap in）的大小，KB 为单位\n\n*io*\n\n* bi，磁盘块从文件系统或 SWAP 读取到 RAM（blocks in）的大小，block 为单位\n* bo，磁盘块从 RAM 写到文件系统或 SWAP（blocks out）的大小，block 为单位\n\n上面是一个频繁读写交换区的例子，可以观察到以下几点：\n\n* 物理可用内存 free 基本没有显著变化，swapd 逐步增加，说明最小可用的内存使用保持在 256MB X 10% = 2.56MB 左右，当脏数据达到 10% 的时候 (vm.dirty_background_ratio = 10) 就开始大量使用 swap\n* buff 稳步减少说明系统知道内存不够用了，kwapd 正在从 buff 那里借用部分内存\n* kswapd 持续把脏数据写到 swap 交换区 (so)，并且从 swapd 主键增加看出确实如此。根据上面将的 kswapd 扫描时检查的三件事，如果页面被修改了，但不是被文件系统修改的，把页面写到 swap，所以这里 swapd 持续增加\n\n## Linux 性能监测 磁盘 IO 篇\n\n磁盘通常是计算机最慢的子系统，也是最容易出现性能瓶颈的地方，因为磁盘离 CPU 最远而且 CPU 访问磁盘涉及到机械操作，比如转轴、寻轨等，访问硬盘和访问内存之间的速度差别是以数量级来计算的，就像 1 天和 1 分钟的差别一样，要监测 IO 性能，有必要了解一下基本原理和 Linux 是如何处理硬盘和内存之间的 IO 的。\n\n### 内存页\n\nMemory 介绍中提到了内存和硬盘之间的 IO 是以页为单位来进行的，在 Linux 系统上 1 页的大小为 4K。可以用下面命令查看系统默认的页面大小：\n\n```\n$getconf  PAGESIZE\n...\n4096\n...\n```\n\n### 缺页中断\n\nLinux 利用虚拟内存极大的扩展了程序地址空间，是的原来物理内存不能容下的程序也可以通过内存和硬盘之间的不断交换（把暂时不用的内存页交换到硬盘，把需要的内存页从硬盘读到内存）来赢得更多的内存，看起来就像物理内存被扩大一样。\n\n事实上这个过程对程序是完全透明的，程序完全不用理会自己哪一部分、什么时候被交换到内存，一切都在内核的虚拟内存管理来完成。\n\n当程序启动的时候，Linux 内核首先检查 CPU 的缓存和物理内存，如果数据已经在内存里就忽略，如果数据不再内存里就引起一个**缺页中断（Page Fault）**，然后从硬盘读取缺页，并把缺页缓存到物理内存中。\n\n缺页中断可分为主缺页中断（Major Page Fault）和次缺页中断（Minor Page Fault），要从磁盘读取数据而产生的中断是主缺页中断；数据已经读到内存并被缓存起来，从内存缓存区中而不是直接从硬盘中读取数据而产生的中断是次缺页中断。\n\n上面的内存缓存区起到了预读硬盘的作用，内核现在物理内存里寻找缺页，没有的话产生次缺页中断从内存缓存中找，如果还没有发现的话就从硬盘读取。很显然，把多于的内存拿出来做成内存缓存区有助于提高访问速度。\n\n这里还有一个**命中率**的问题，运气好的话如果每次缺页都能从内存缓存区读取的话将会极大提升性能。要提升命中率的一个简单的方法就是增大内存缓存区面积，缓存区越大预存的页面就越多，命中率也会越多。\n\n下面的 time 命令可以用来查看某程序第一次启动的时候产生了多少主缺页中断和次缺页中断：\n\n```\n$ /usr/bin/time -v date\n...\nMajor (requiring I/O) page faults: 1\nMinor (reclaiming a frame) page faults: 260\n...\n```\n\n### File Buffer Cache\n\n从上面的内存缓存区（也叫文件缓存区 File Buffer Cache) 读取页比从硬盘读取页要快的多，所以 Linux 内核希望能尽可能产生次却也中断（从文件缓存区读），并且能尽可能避免主缺页中断（从硬盘读），这样随着次缺页中断的增多，文件缓存区也逐步增大，直到系统只有少量可用物理内存的时候 Linux 才开始释放不用的页。\n\n我们运行 Linux 一段时间后会发现虽然系统上运行的程序不多，但是可用内存总是很少，这样给大家造成了 Linux 对内存管理很低效的假象，事实上 Linux 把哪些暂时不用的物理内存高效的利用起来做预存（内存缓存区）呢。下面打印的是一台 Sun 服务器上的物理内存和文件缓存区的情况：\n\n```\n$ cat /proc/meminfo\nMemTotal:      8182776 kB\nMemFree:       3053808 kB\nBuffers:        342704 kB\nCached:        3972748 kB\n```\n\n这台服务器总共有 8GB 物理内存（MemTotal），3GB 左右可用内存（MemFree），343MB 左右用来做磁盘缓存（Buffers），4GB 左右用来做文件缓存区（Cached），可见 Linux 真的用了很多物理内存做 Cache，而且这个缓存区还可以不断增长。\n\n### 页面类型\n\nLinux 中内存页面有三种类型：\n\n* Read Pages，只读页（或代码页），那些通过主缺页中断从硬盘读取的页面，包括不能修改的静态文件、可执行文件、库文件等。当内核需要它们的时候把它们读到内存中，当内存不足的时候，内核就释放它们到空闲列表，当程序再次需要它们的时候需要通过缺页中断再次读到内存\n* Dirty Pages，脏页，指那些在内存中被修改过的数据页，比如文本文件等。这些文件有 pdflush 负责同步到硬盘，内存不足的时候由 kswapd 和 pdflush 把数据写回硬盘并释放内存\n* Anonymous Pages，匿名页，那些属于某个进程但是又和任何文件无关联，不能被同步到硬盘上，内存不足的时候有 kswapd 负责将它们写到交换分区并释放内存\n\n### IO's Per Seconds(OIPS)\n\n每次磁盘 IO 请求都需要一定的时间，和访问内存比起来这个等待时间简直难以忍受。\n\n在一台 2001 年典型 1GHz PC 上，磁盘随机访问一个 word 需要 8000000 nanosec = 8 millisec，顺序访问一个 word 需要 200nanosec；而从内存访问一个 word 只需要 10 nanoses。（数据来自：Teach Yourself Programming in Ten Years）这个硬盘可以提供 125 次 IOPS（1000 ms / 8 ms）。\n\nIOPS：每秒 IO 的次数。\n\n### 顺序 IO 和随机 IO\n\nIO 分为顺序 IO 和随机 IO 两种，性能监测前需要弄清楚系统偏向顺序 IO 的应用还是随机 IO 的应用。\n\n随机 IO 是指同时顺序请求大量数据，比如数据库执行大量的查询、流媒体服务等，顺序 IO 可以同时很快的移动大量数据。可以这样来评估 IOPS 的性能，用每秒读写 IO 字节数除以每秒读写 IOPS 数，rkB/s 除以 r/s，wkB/s 除以 w/s。下面显示的是连续两秒的 IO 情况，可见每次 IO 写的数据是增加的（45060.00 / 99.00 = 455.15 KB per IO，54272.00 / 112.00 = 484.57 KB per IO）。\n\n相对随机 IO 而言，顺序 IO 更应该重视每次 IO 的吞吐能力（KB per IO）：\n\n```\n$ iostat -kx 1\navg-cpu:  %user   %nice %system %iowait  %steal   %idle\n           0.00    0.00    2.50   25.25    0.00   72.25\n\nDevice:  rrqm/s   wrqm/s   r/s   w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await  svctm  %util\nsdb       24.00 19995.00 29.00 99.00  4228.00 45060.00   770.12    45.01  539.65   7.80  99.80\n\navg-cpu:  %user   %nice %system %iowait  %steal   %idle\n           0.00    0.00    1.00   30.67    0.00   68.33\n\nDevice:  rrqm/s   wrqm/s   r/s   w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await  svctm  %util\nsdb        3.00 12235.00  3.00 112.00   768.00 54272.00   957.22   144.85  576.44   8.70 100.10\n```\n\n随机 IO 是指随机请求数据，其 IO 速度不依赖于数据的大小和排序，依赖于磁盘的每秒能 IO 的次数，比如 Web 服务、Mial 服务等每次请求的数据都很小，随机 IO 每次同时会有更多的请求数产生，所以磁盘的每秒能 IO 多少次是关键\n\n```\n$ iostat -kx 1\navg-cpu:  %user   %nice %system %iowait  %steal   %idle\n           1.75    0.00    0.75    0.25    0.00   97.26\n\nDevice:  rrqm/s   wrqm/s   r/s   w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await  svctm  %util\nsdb        0.00    52.00  0.00 57.00     0.00   436.00    15.30     0.03    0.54   0.23   1.30\n\navg-cpu:  %user   %nice %system %iowait  %steal   %idle\n           1.75    0.00    0.75    0.25    0.00   97.24\n\nDevice:  rrqm/s   wrqm/s   r/s   w/s    rkB/s    wkB/s avgrq-sz avgqu-sz   await  svctm  %util\nsdb        0.00    56.44  0.00 66.34     0.00   491.09    14.81     0.04    0.54   0.19   1.29\n```\n\n按照上面的公式得出：436.00 / 57.00 = 7.65KB per IO， 491.09 / 66.34 = 7.40 KB per IO ，与顺序 IO 比较发现，随机 IO 的 KB per IO 小到可以忽略不计，可见对于随机 IO 而言重要的是每秒能 IOPS 的次数，而不是每次 IO 的吞吐能力（KB per IO）\n\n### SWAP\n\n当系统没有足够物理内存来应付所有请求的时候就会用到 swap 设备，swap 设备可以是一个文件，也可以是磁盘分区。\n\n不过要小心的是，使用 swap 的代价非常大。如果系统没有物理内存可用，就会频繁 swapping，如果 swap 设备和程序正在访问的数据在同一个文件系统上，那会碰到严重的 IO 问题，最终导致整个系统迟缓，甚至崩溃。\n\nswap 设备和内存之间的 swapping 状况是判断 Linux 系统性能的重要参考，我们已经有很多工具可以用来监测 swap 和 swapping 的情况，比如：top、cat/proc/meminfo、vmstat 等：\n\n```\n$ cat /proc/meminfo\nMemTotal:      8182776 kB\nMemFree:       2125476 kB\nBuffers:        347952 kB\nCached:        4892024 kB\nSwapCached:        112 kB\n...\nSwapTotal:     4096564 kB\nSwapFree:      4096424 kB\n...\n```\n#### 关掉 swap\n\n```\n(1) 将 /etc/fstab 文件中所有设置为 swap 的设备关闭\n[root@meetbill ~]# swapoff -a\n\n(2) 设置开机不启动 swap\n将 /etc/fstab 中 swap 行注释掉\n```\n\n## Linux 性能监测 网络篇\n\n网络的监测是所有 Linux 子系统里面最复杂的，有太多的因素在里面，比如：延迟、阻塞、冲突、丢包等，更糟的是与 Linux 主机相连的路由器、交换机、无线信号都会影响到整体网络并且很难判断是因为 Linux 网络子系统的问题还是别的设备的问题，增加了监测和判断的复杂度。\n\n现在我们使用的所有网卡都称为自适应网卡，意思是说能根据网络上的不同网络设备导致的不同网络速度和工作模式进行自动调整。我们可以通过 ethtool 共苦；来查看网卡的配置和工作模式：\n\n```\n# /sbin/ethtool eth0\nSettings for eth0:\nSupported ports: [ TP ]\nSupported link modes:   10baseT/Half 10baseT/Full\n                        100baseT/Half 100baseT/Full\n                        1000baseT/Half 1000baseT/Full\nSupports auto-negotiation: Yes\nAdvertised link modes:  10baseT/Half 10baseT/Full\n                        100baseT/Half 100baseT/Full\n                        1000baseT/Half 1000baseT/Full\nAdvertised auto-negotiation: Yes\nSpeed: 100Mb/s\nDuplex: Full\nPort: Twisted Pair\nPHYAD: 1\nTransceiver: internal\nAuto-negotiation: on\nSupports Wake-on: g\nWake-on: g\nCurrent message level: 0x000000ff (255)\nLink detected: yes\n```\n\n上面给出的例子说明网卡有 10baseT，100baseT 和 1000baseT 三种选择，目前正在自适应为 100baseT（Speed：100MB/s）。可以通过 ethtool 工具强制网卡工作在 1000basseT 下：\n\n```\n# /sbin/ethtool -s eth0 speed 1000 duplex full autoneg off\niptraf\n```\n\n两台主机之间有网线（或无线）、路由器、交换机等设备，测试两台主机之间的网络性能的一个办法就是在这两个系统之间互发数据并统计结果，看看吞吐量、延迟、速率如何。\n\niptraf 就是一个很好的查看本机网络吞吐量的好工具，支持文字图形界面，很直观。下面图片显示在 100mbps 速率的网络下这个 Linux 系统的发送传输率有点慢，Outgoing rates 只有 66mbps：\n\n```\n# iptraf -d eth0\n```\n\n### netperf\n\nnetperf 运行在 client/server 模式下，比 iptraf 能更多样化的测试终端的吞吐量。先在服务器端启动 netserver：\n\n```\n# netserver\nStarting netserver at port 12865\nStarting netserver at hostname 0.0.0.0 port 12865 and family AF_UNSPEC\n```\n\n然后在客户端测试服务器，执行一次持续 10 秒的 TCP 测试：\n\n```\n# netperf -H 172.16.38.36 -l 10\nTCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 172.16.38.36 (172.16.38.36) port 0 AF_INET\nRecv   Send    Send\nSocket Socket  Message  Elapsed\nSize   Size    Size     Time     Throughput\nbytes  bytes   bytes    secs.    10^6bits/sec\n\n 87380  16384  16384    10.32      93.68\n```\n\n从上面输出可以看出，网络吞吐量在 94mbps 左右，对于 100mbps 的网络来说这个性能算的上很不错。\n\n上面的测试是在服务器和客户端位于同一个局域网，并且局域网是有线网的情况，你也可以试试不同结构、不同速率的网络，比如：网络之间中间多个路由器、客户端在 wi-fi、VPN 等情况。\n\nnetperf 还可以通过建立一个 TCP 连接并顺序地发送数据包来测试每秒有多少 TCP 请求和响应。下面的输出显示在 TCP requests 使用 2K 大小，responses 使用 32K 的情况下处理速率为每秒 243：\n\n```\n# netperf -t TCP_RR -H 172.16.38.36 -l 10 -- -r 2048,32768\nTCP REQUEST/RESPONSE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 172.16.38.36 (172.16.38.36) port 0 AF_INET\nLocal /Remote\nSocket Size   Request  Resp.   Elapsed  Trans.\nSend   Recv   Size     Size    Time     Rate\nbytes  Bytes  bytes    bytes   secs.    per sec\n\n16384  87380  2048     32768   10.00     243.03\n16384  87380\n```\n同时可以使用 netperf 持续发送数据包，通过 atop 查看网卡流量查看网络状态是否良好\n\n### iperf\n\niperf 和 netperf 运行方式类似，也是 server/client 模式，现在服务器端启动 iperf：\n\n```\n# iperf -s -D\n------------------------------------------------------------\nServer listening on TCP port 5001\nTCP window size: 85.3 KByte (default)\n------------------------------------------------------------\nRunning Iperf Server as a daemon\nThe Iperf daemon process ID : 5695\n```\n\n然后在客户端对服务器进行测试，客户端线连接到服务器端（172.16.38.36），并在 30 秒内每隔 5 秒对服务器和客户端之间的网络进行一次带宽测试和采样：\n\n```\n# iperf -c 172.16.38.36 -t 30 -i 5\n------------------------------------------------------------\nClient connecting to 172.16.38.36, TCP port 5001\nTCP window size: 16.0 KByte (default)\n------------------------------------------------------------\n[  3] local 172.16.39.100 port 49515 connected with 172.16.38.36 port 5001\n[ ID] Interval       Transfer     Bandwidth\n[  3]  0.0- 5.0 sec  58.8 MBytes  98.6 Mbits/sec\n[ ID] Interval       Transfer     Bandwidth\n[  3]  5.0-10.0 sec  55.0 MBytes  92.3 Mbits/sec\n[ ID] Interval       Transfer     Bandwidth\n[  3] 10.0-15.0 sec  55.1 MBytes  92.4 Mbits/sec\n[ ID] Interval       Transfer     Bandwidth\n[  3] 15.0-20.0 sec  55.9 MBytes  93.8 Mbits/sec\n[ ID] Interval       Transfer     Bandwidth\n[  3] 20.0-25.0 sec  55.4 MBytes  92.9 Mbits/sec\n[ ID] Interval       Transfer     Bandwidth\n[  3] 25.0-30.0 sec  55.3 MBytes  92.8 Mbits/sec\n[ ID] Interval       Transfer     Bandwidth\n[  3]  0.0-30.0 sec    335 MBytes  93.7 Mbits/sec\n```\n\n### tcpdump 和 tcptrace\n\ntcpdump 和 tcptrace 提供了一种更细致的分析方法，先用 tcpdump 按要求捕获数据包把结果输出到某一文件，然后再用 tcptrace 分析其文件格式。这个工具组合可以提供一些难以用其他工具发现的信息：\n\n```\n# /usr/sbin/tcpdump -w network.dmp\ntcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes\n511942 packets captured\n511942 packets received by filter\n0 packets dropped by kernel\n\n# tcptrace network.dmp\n1 arg remaining, starting with 'network.dmp'\nOstermann's tcptrace -- version 6.6.7 -- Thu Nov  4, 2004\n\n511677 packets seen, 511487 TCP packets traced\nelapsed wallclock time: 0:00:00.510291, 1002714 pkts/sec analyzed\ntrace file elapsed time: 0:02:35.836372\nTCP connection info:\n  1: zaber:54581 - boulder:111 (a2b)                   6>    5<  (complete)\n  2: zaber:833 - boulder:32774 (c2d)                   6>    5<  (complete)\n  3: zaber:pcanywherestat - 172.16.39.5:53086 (e2f)    2>    3<\n  4: zaber:716 - boulder:2049 (g2h)                  347>  257<\n  5: 172.16.39.100:58029 - zaber:12865 (i2j)           7>    5<  (complete)\n  6: 172.16.39.100:47592 - zaber:36814 (k2l)        255380> 255378<  (reset)\n  7: breakpoint:45510 - zaber:7012 (m2n)               9>    5<  (complete)\n  8: zaber:35813 - boulder:111 (o2p)                   6>    5<  (complete)\n  9: zaber:837 - boulder:32774 (q2r)                   6>    5<  (complete)\n 10: breakpoint:45511 - zaber:7012 (s2t)               9>    5<  (complete)\n 11: zaber:59362 - boulder:111 (u2v)                   6>    5<  (complete)\n 12: zaber:841 - boulder:32774 (w2x)                   6>    5<  (complete)\n 13: breakpoint:45512 - zaber:7012 (y2z)               9>    5<  (complete)\n```\n\ntcptrace 功能很强大，还可以通过过滤和布尔表达式来找出有问题的连接，比如，找出转播大于 100segments 的连接：\n\n```\n# tcptrace -f'rexmit_segs>100' network.dmp\n```\n\n如果发现连接 #10 有问题，可以查看关于这个连接的其他信息：\n\n```\n# tcptrace -o10 network.dmp\n```\n\n下面的命令使用 tcptrace 的 slice 模式，程序自动在当前目录创建了一个 slice.dat 文件，这个文件包含了每隔 15 秒的转播信息：\n\n```\n# tcptrace -xslice network.dmp\n\n# cat slice.dat\ndate                segs    bytes  rexsegs rexbytes      new   active\n--------------- -------- -------- -------- -------- -------- --------\n16:58:50.244708    85055  4513418        0        0        6        6\n16:59:05.244708   110921  5882896        0        0        0        2\n16:59:20.244708   126107  6697827        0        0        1        3\n16:59:35.244708   151719  8043597        0        0        0        2\n16:59:50.244708    37296  1980557        0        0        0        3\n17:00:05.244708       67     8828        0        0        2        3\n17:00:20.244708      149    22053        0        0        1        2\n17:00:35.244708       30     4080        0        0        0        1\n17:00:50.244708       39     5688        0        0        0        1\n17:01:05.244708       67     8828        0        0        2        3\n17:01:11.081080       37     4121        0        0        1        3\n```\n\n## ulimit 关于系统连接数的优化\n\nlinux 默认值 open files 和 max user processes 为 1024\n\n\\#ulimit -n\n\n1024\n\n\\#ulimit –u\n\n1024\n\n问题描述： 说明 server 只允许同时打开 1024 个文件，处理 1024 个用户进程\n\n使用 ulimit -a 可以查看当前系统的所有限制值，使用 ulimit -n 可以查看当前的最大打开文件数。\n\n新装的 linux 默认只有 1024 ，当作负载较大的服务器时，很容易遇到 error: too many open files 。因此，需要将其改大。\n\n解决方法：\n\n使用 ulimit –n 65535 可即时修改，但重启后就无效了。（注 ulimit -SHn 65535 等效 ulimit -n 65535 ，-S 指 soft ，-H 指 hard)\n\n### 修改方式\n\n有如下三种修改方式：\n\n1.  在 /etc/rc.local 中增加一行 ulimit -SHn 65535\n\n2.  在 /etc/profile 中增加一行 ulimit -SHn 65535\n\n3.  在 /etc/security/limits.conf 最后增加：\n\n    ```\n    * soft nofile 65535\n    * hard nofile 65535\n    * soft nproc 65535\n    * hard nproc 65535\n    ```\n\n具体使用哪种，在 CentOS 中使用第 1 种方式无效果，使用第 3 种方式有效果，而在 Debian 中使用第 2 种有效果\n\n\\# ulimit -n\n\n65535\n\n\\# ulimit -u\n\n65535\n\n备注：ulimit 命令本身就有分软硬设置，加 -H 就是硬，加 -S 就是软默认显示的是软限制\n\nsoft 限制指的是当前系统生效的设置值。 hard 限制值可以被普通用户降低。但是不能增加。 soft 限制不能设置的比 hard 限制更高。 只有 root 用户才能够增加 hard 限制值。\n\n```bash\n#!/bin/bash\n\nfile=/etc/security/limits.conf\n\nif grep '^* soft nofile' $file > /dev/null ;then\n    sed -i 's/^* soft nofile.*/* soft nofile 1024000/' $file\nelse\n    echo '* soft nofile 1024000' >> $file\nfi\n\nif grep '^* hard nofile' $file > /dev/null ;then\n    sed -i 's/^* hard nofile.*/* hard nofile 1024000/' $file\nelse\n    echo '* hard nofile 1024000' >> $file\nfi\n\nulimit -SHn 1024000\n```\n"
  },
  {
    "path": "doc/Linux/safety.md",
    "content": "## Linux 安全\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 禁止 ping](#1-禁止-ping)\n* [2 禁止密码登陆](#2-禁止密码登陆)\n* [3 ssh 防暴力破解及提高 ssh 安全](#3-ssh-防暴力破解及提高-ssh-安全)\n* [4 运维操作审计](#4-运维操作审计)\n* [5 双因子认证](#5-双因子认证)\n    * [5.1  安装及配置篇](#51--安装及配置篇)\n        * [5.1.1 环境](#511-环境)\n        * [5.1.2 查看系统时间](#512-查看系统时间)\n        * [5.1.3 安装 google authenticator](#513-安装-google-authenticator)\n        * [5.1.4 为 SSH 服务器用 Google 认证器](#514-为-ssh-服务器用-google-认证器)\n        * [5.1.5 生成验证密钥](#515-生成验证密钥)\n    * [5.2 使用](#52-使用)\n        * [5.2.1 在安卓设备上运行 Google 认证器](#521-在安卓设备上运行-google-认证器)\n        * [5.2.2 终端使用二次身份验证登陆](#522-终端使用二次身份验证登陆)\n    * [5.3 常见问题及注意点](#53-常见问题及注意点)\n        * [5.3.1 登陆失败](#531-登陆失败)\n        * [5.3.2 是否可以不同的用户使用不用密钥](#532-是否可以不同的用户使用不用密钥)\n        * [5.3.3 是否可以使用 ssh 密钥直接登陆](#533-是否可以使用-ssh-密钥直接登陆)\n    * [5.4 原理](#54-原理)\n        * [5.4.1 前世今生](#541-前世今生)\n        * [5.4.2 TOTP 中的特殊问题](#542-totp-中的特殊问题)\n* [6 iptables 命令](#6-iptables-命令)\n    * [6.1 iptables 是什么](#61-iptables-是什么)\n    * [6.2 iptables 示例](#62-iptables-示例)\n        * [6.2.1 filter 表 INPUT 链](#621-filter-表-input-链)\n        * [6.2.2 filter 表 OUTPUT 链](#622-filter-表-output-链)\n        * [6.2.3 filter 表的 FORWARD 链](#623-filter-表的-forward-链)\n    * [6.3 nat 表](#63-nat-表)\n        * [6.3.1 nat 表 PREROUTING 链](#631-nat-表-prerouting-链)\n        * [6.3.2 nat 表 POSTROUTING 链](#632-nat-表-postrouting-链)\n        * [6.3.3 nat 表做 HA 的实例](#633-nat-表做-ha-的实例)\n        * [6.3.4 nat 表为虚拟机做内外网联通](#634-nat-表为虚拟机做内外网联通)\n    * [6.4 iptables 管理命令](#64-iptables-管理命令)\n        * [6.4.1 查看 iptables 规则](#641-查看-iptables-规则)\n        * [6.4.2 清除 iptables 规则](#642-清除-iptables-规则)\n        * [6.4.3 保存 iptables 规则](#643-保存-iptables-规则)\n    * [6.5 常用操作](#65-常用操作)\n        * [6.5.1 使用 ip6tables 禁用 ipv6](#651-使用-ip6tables-禁用-ipv6)\n        * [6.5.2 配置 iptables 允许部分端口通行，其他全部阻止](#652-配置-iptables-允许部分端口通行其他全部阻止)\n        * [6.5.3 关闭某个端口外部访问](#653-关闭某个端口外部访问)\n\n<!-- vim-markdown-toc -->\n\n## 1 禁止 ping\n\n禁止系统响应任何从外部 / 内部来的 ping 请求攻击者一般首先通过 ping 命令检测此主机或者 IP 是否处于活动状态 ，如果能够 ping 通某个主机或者 IP，那么攻击者就认为此系统处于活动状态，继而进行攻击或破坏。如果没有人能 ping 通机器并收到响应，那么就可以大大增强服务器的安全性，linux 下可以执行如下设置，禁止 ping 请求：\n```\n[root@localhost ~]#echo \"1\"> /proc/sys/net/ipv4/icmp_echo_ignore_all\n```\n默认情况下\"icmp_echo_ignore_all\"的值为\"0\"，表示响应 ping 操作。\n\n可以加上面的一行命令到 /etc/rc.d/rc.local 文件中，以使每次系统重启后自动运行\n\n## 2 禁止密码登陆\n\n## 3 ssh 防暴力破解及提高 ssh 安全\n\n## 4 运维操作审计\n\n[添加运维操作审计工具](https://github.com/meetbill/shell_menu)\n\n## 5 双因子认证\n\n海上生明月，天涯共此时！\n\n### 5.1  安装及配置篇\n\n#### 5.1.1 环境\n\nserver：CentOS 6.5/CentOS 7.3\n\n#### 5.1.2 查看系统时间\n\n使用外网机器时，创建的时候有可能不是北京时间\n\n```\n[root@centos ~]#date\nSun Aug 14 23:18:41 EDT 2011\n[root@centos ~]# rm -rf /etc/localtime\n[root@centos ~]# ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/localtime\n```\n#### 5.1.3 安装 google authenticator\n\n安装 EPEL 源并安装 google_authenticator\n\n```\n#yum -y install epel-release\n#yum -y install google-authenticator\n```\n#### 5.1.4 为 SSH 服务器用 Google 认证器\n\n**配置 /etc/pam.d/sshd**\n\n```\n# CentOS 6.5 在\"auth       include      password-auth\"行前添加如下内容\n# CentOS 7 在\"auth       substack     password-auth\"行前添加如下内容\nauth       required pam_google_authenticator.so\n```\n即先 google 方式认证再 linux 密码认证\n\n**修改 SSH 服务配置 /etc/ssh/sshd_config**\n\nChallengeResponseAuthentication no->yes\n\n```\nsed -i 's#^ChallengeResponseAuthentication no#ChallengeResponseAuthentication yes#' /etc/ssh/sshd_config\n```\n**重启 SSH 服务**\n\n```\n# CentOS6\n#service sshd restart\n\n# CentOS7\n# systemctl restart sshd\n```\n**关掉 selinux**\n\n```\n# setenforce 0\n# 修改 /etc/selinux/config 文件 将 SELINUX=enforcing 改为 SELINUX=disabled\n```\n\n#### 5.1.5 生成验证密钥\n在 Linux 主机上登陆需要认证的用户运行 Google 认证器（我这是使用 root 用户演示的）\n```\n$google-authenticator\n```\n直接一路输入 yes 即可，询问内容如下，想了解的可以看下\n\n```\nDo you want me to update your \"~/.google_authenticator\" file (y/n):y\n应急码的保存路径\n\nDo you want to disallow multiple uses of the same authentication token?\nThis restricts you to one login about every 30s,\nbut it increases your chances to notice or even prevent man-in-the-middle attacks (y/n)\n是否禁止一个口令多用，自然也是答 y\n\nBy default, tokens are good for 30 seconds and in order to compensate for possible time-skew between the client and the server,\nwe allow an extra token before and after the current time. If you experience problems with poor time synchronization, you can increase the window from its default size of 1:30min to about 4min. Do you want to do so (y/n)\n问是否打开时间容错以防止客户端与服务器时间相差太大导致认证失败。\n这个可以根据实际情况来。如果一些 Android 平板电脑不怎么连网的，可以答 y 以防止时间错误导致认证失败。\n\nIf the computer that you are logging into isn't hardened against brute-force login attempts,\nyou can enable rate-limiting for the authentication module.\nBy default, this limits attackers to no more than 3 login attempts every 30s.Do you want to enable rate-limiting (y/n)\n选择是否打开尝试次数限制（防止暴力攻击），自然答 y\n```\n这里需要记住的是\n\n```\n$cat ~/.google_authenticator       手机密钥和应急码保存路径\n密钥\nYour emergency scratch codes are: 一些生成的 5 个应急码，每个应急码只能使用一次\n```\n### 5.2 使用\n\n#### 5.2.1 在安卓设备上运行 Google 认证器\n\n***安装 google 身份验证器***\n\n我的方法是在 UC 中搜索的 google 身份验证器进行的安装\n\n***输入密钥***\n\n选择\"Enter provided key\"选项，使用键盘输入账户名称和验证密钥\n\n#### 5.2.2 终端使用二次身份验证登陆\n\n***windows xshell***\n\n打开 xshell（其他终端类似），选择登陆主机的属性。设置登陆方法为 Keyboard Interactive\n\n登陆时输入用户名后，接着输入手机设备上的数字，然后输入密码\n\n***linux***\n\nlinux 下直接输入\n\n```\n#ssh 用户名 @IP\n```\n连接比较慢时可以修改本机的客户端配置文件 ssh_config，注意，不是 sshd_config\n\nGSSAPIAuthentication yes -->no\n\n```\n#sed -i 's#GSSAPIAuthentication yes#GSSAPIAuthentication no#' /etc/ssh/ssh_config\n\n```\n### 5.3 常见问题及注意点\n\n#### 5.3.1 登陆失败\n\n如果 SELinux 是打开状态，则会登陆失败，日志 /var/log/secret 中会有如下日志\n\n```\nJan  3 23:42:50 hostname sshd(pam_google_authenticator)[1654]: Failed to update secret file \"/home/username/.google_authenticator\"\nJan  3 23:42:50 hostname  sshd[1652]: error: PAM: Cannot make/remove an entry for the specified session for username from 192.168.0.5\n```\n#### 5.3.2 是否可以不同的用户使用不用密钥\n\n可以，只需要在不同的用户执行`google-authenticator`即可\n\n#### 5.3.3 是否可以使用 ssh 密钥直接登陆\n\n可以，根据以上方法操作，只限制密码登陆时需要二次认证\n\n### 5.4 原理\n\n 基于时间的一次性密码（Time-based One-time Password，简称 TOTP），只需要在手机上安装密码生成应用程序，就可以生成一个随着时间变化的一次性密码，用于帐户验证，而且这个应用程序不需要连接网络即可工作。仔细看了看这个方案的实现原理，发现挺有意思的。\n\n#### 5.4.1 前世今生\n\n***HOTP***\n\n Google 的两步验证算法源自另一种名为 HMAC-Based One-Time Password 的算法，简称 HOTP。HOTP 的工作原理如下：\n\n 客户端和服务器事先协商好一个密钥 K，用于一次性密码的生成过程，此密钥不被任何第三方所知道。此外，客户端和服务器各有一个计数器 C，并且事先将计数值同步。\n进行验证时，客户端对密钥和计数器的组合 (K,C) 使用 HMAC（Hash-based Message Authentication Code）算法计算一次性密码，公式如下：\n\n> HOTP(K,C) = Truncate(HMAC-SHA-1(K,C))\n\n上面采用了 HMAC-SHA-1，当然也可以使用 HMAC-MD5 等。HMAC 算法得出的值位数比较多，不方便用户输入，因此需要截断（Truncate）成为一组不太长十进制数（例如 6 位）。计算完成之后客户端计数器 C 计数值加 1。用户将这一组十进制数输入并且提交之后，服务器端同样的计算，并且与用户提交的数值比较，如果相同，则验证通过，服务器端将计数值 C 增加 1。如果不相同，则验证失败。\n\n这里的一个比较有趣的问题是，如果验证失败或者客户端不小心多进行了一次生成密码操作，那么服务器和客户端之间的计数器 C 将不再同步，因此需要有一个重新同步（Resynchronization）的机制。\n\n***TOTP***\n\n介绍完了 HOTP，Time-based One-time Password（TOTP）也就容易理解了。TOTP 将 HOTP 中的计数器 C 用当前时间 T 来替代，于是就得到了随着时间变化的一次性密码。非常有趣吧！\n\n一句话概括就是\n\n> 海上升明月，天涯共此时！\n\n#### 5.4.2 TOTP 中的特殊问题\n\n***时间 T 的选取 (30 秒作为时间片）***\n\n首先，时间 T 的值怎么选取？因为时间每时每刻都在变化，如果选择一个变化太快的 T（例如从某一时间点开始的秒数），那么用户来不及输入密码。如果选择一个变化太慢的 T（例如从某一时间点开始的小时数），那么第三方攻击者就有充足的时间去尝试所有可能的一次性密码（试想 6 位数字的一次性密码仅仅有 10^6 种组合），降低了密码的安全性。除此之外，变化太慢的 T 还会导致另一个问题。如果用户需要在短时间内两次登录账户，由于密码是一次性的不可重用，用户必须等到下一个一次性密码被生成时才能登录，这意味着最多需要等待 59 分 59 秒！这显然不可接受。综合以上考虑，\n\nGoogle 选择了 30 秒作为时间片，T 的数值为从 Unix epoch（1970 年 1 月 1 日 00:00:00）来经历的 30 秒的个数。\n\n***网络延时处理***\n\n第二个问题是，由于网络延时，用户输入延迟等因素，可能当服务器端接收到一次性密码时，T 的数值已经改变，这样就会导致服务器计算的一次性密码值与用户输入的不同，验证失败。解决这个问题个一个方法是，服务器计算当前时间片以及前面的 n 个时间片内的一次性密码值，只要其中有一个与用户输入的密码相同，则验证通过。当然，n 不能太大，否则会降低安全性。\n事实上，这个方法还有一个另外的功能。我们知道如果客户端和服务器的时钟有偏差，会造成与上面类似的问题，也就是客户端生成的密码和服务端生成的密码不一致。但是，如果服务器通过计算前 n 个时间片的密码并且成功验证之后，服务器就知道了客户端的时钟偏差。因此，下一次验证时，服务器就可以直接将偏差考虑在内进行计算，而不需要进行 n 次计算。\n\n以上就是 Google 两步验证的工作原理，推荐大家使用，这确实是保护帐户安全的利器。\n\n## 6 iptables 命令\n\n### 6.1 iptables 是什么\niptables 是与 Linux 内核集成的 IP 信息包过滤系统，该系统有利于在 Linux 系统上更好地控制 IP 信息包过滤和防火墙配置。\n\n### 6.2 iptables 示例\n#### 6.2.1 filter 表 INPUT 链\n怎么处理发往本机的包。\n\n```\n# iptables {-A|-D|-I} INPUT rule-specification\n# iptables -A INPUT -s 10.1.2.11 -p tcp --dport 80 -j DROP\n# iptables -A INPUT -s 10.1.2.11 -p tcp --dport 80 -j REJECT --reject-with tcp-reset\n# iptables -A INPUT -s 10.1.2.11 -p tcp --dport 80 -j ACCEPT\n```\n\n以上表示将从源地址 10.1.2.11 访问本机 80 端口的包丢弃（以 tcp-reset 方式拒绝和接受）。\n\n> * -s 表示源地址（--src,--source），其后面可以是一个 IP 地址（10.1.2.11）、一个 IP 网段（10.0.0.0/8）、几个 IP 或网段（192.168.1.11/32,10.0.0.0/8，添加完成之后其实是两条规则）。\n> * -d 表示目的地址（--dst,--destination），其后面可以是一个 IP 地址（10.1.2.11）、一个 IP 网段（10.0.0.0/8）、几个 IP 或网段（10.1.2.11/32,10.1.3.0/24，添加完成之后其实是两条规则）。\n> * -p 表示协议类型（--protocol），后面可以是 tcp, udp, udplite, icmp, esp, ah, sctp, all，其中 all 表示所有的协议。\n> * --sport 表示源端口（--source-port），后面可以是一个端口（80）、一系列端口（80:90，从 80 到 90 之间的所有端口），一般在 OUTPUT 链使用。\n> * --dport 表示目的端口（--destination-port），后面可以是一个端口（80）、一系列端口（80:90，从 80 到 90 之间的所有端口）。\n> * -j 表示 iptables 规则的目标（--jump），即一个符合目标的数据包来了之后怎么去处理它。常用的有 ACCEPT, DROP, REJECT, REDIRECT, LOG, DNAT, SNAT。\n>   * (“就好象骗子给你打电话，ACCEPT 是接收，drop 就是直接拒收，reject 的话，相当于你还给骗子回个电话。”)\n\n```\n# iptables -A INPUT -p tcp --dport 80 -j DROP\n# iptables -A INPUT -p tcp --dport 80:90 -j DROP\n# iptables -A INPUT -m multiport -p tcp --dports 80,8080 -j DROP\n```\n\n以上表示将所有访问本机 80 端口（80 和 90 之间的所有端口，80 和 8080 端口）的包丢弃。\n\n> * -m 匹配更多规则（--match），可以指定更多的 iptables 匹配扩展。可以是 tcp, udp, multiport, cpu, time, ttl 等，即你可以指定一个或多个端口，或者本机的一个 CPU 核心，或者某个时间段内的包。\n\n#### 6.2.2 filter 表 OUTPUT 链\n怎么处理本机向外发的包。\n\n```\n# iptables -A OUTPUT -p tcp --sport 80 -j DROP\n```\n\n以上这条规则意思是不允许访问本机 80 端口的包出去。即你可以向本机 80 端口发送请求包，但是本机回应给你的包会被该条规则丢弃。\n\nINPUT 链与 OUTPUT 链用法一样，但是表示的意思不同。\n\n#### 6.2.3 filter 表的 FORWARD 链\nFor packets being routed through the box（不知道怎么解释）。\n\n其用法与 INPUT 链和 OUTPUT 链类似。\n\n### 6.3 nat 表\nnat 表有三条链，分别是 PREROUTING, OUTPUT, POSTROUTING。\n\n#### 6.3.1 nat 表 PREROUTING 链\n修改发往本机的包。\n\n```\n# iptables -t nat -A PREROUTING -p tcp -d 202.102.152.23 --dport 80 -j DNAT --to-destination 10.67.15.23:8080\n# iptables -t nat -A PREROUTING -p tcp -d 202.102.152.23 -j DNAT --to-destination 10.67.15.23\n```\n\n以上这两条规则的意思是将发往 IP 地址 202.102.152.23 和端口 80 的包的目的地址修改为 10.67.15.23，目的端口修改为 8080。将发往 202.102.152.23 的其他非 80 端口的包目的地址修改为 10.67.15.23。第二条规则中的 -p tcp 是可选的，也可以指定其他协议。\n\n其实类似这样的规则一般在路由器上做，路由器上有个公网 IP（202.102.152.23），其中有个用户的内网 IP（10.67.15.23）想提供外网的 web 服务，而路由器又不想将公网 IP 地址绑定到用户机器上，因此就出来了以上的蛋疼规则。\n\n#### 6.3.2 nat 表 POSTROUTING 链\n修改本机向外发的包。\n\n```\n# iptables -t nat -A POSTROUTING -p tcp -s 10.67.15.23 --sport 8080 -j SNAT --to-source 202.102.152.23:80\n# iptables -t nat -A POSTROUTING -p tcp -s 10.67.15.23 -j SNAT --to-source 202.102.152.23\n```\n\n以上两条规则的意思是将从 IP 地址 10.67.15.23 和端口 8080 发出的包的源地址修改为 202.102.152.23，源端口修改为 80。将从 10.67.15.23 发出的非 80 端口的包的源地址修改为 202.102.152.23。\n\n这两条正好与以上两条 PREROUTING 共同完成了内网用户想提供外网服务的功能。\n\n其中的 --to-destination 和 --to-source 都可以缩写成 --to，在 DNAT 和 SNAT 中会分别被理解成 --to-destination 和 --to-source。\n\n注： 之所以将内网服务的端口和外网服务的端口写的不一致是因为二者其实真的可以不一致。另外，是否将 PREROUTNG 中的 -d 改为域名就可以使用一个公网 IP 为不同用户提供服务了呢？这个需要哥哥我稍后验证。\n\n#### 6.3.3 nat 表做 HA 的实例\n有两台服务器和三个 IP 地址，分别是 10.1.2.21, 10.1.2.22, 10.1.5.11。假设他们提供的是相同的 WEB 服务，现在想让他们做 HA，而 10.1.5.11 是他们的 VIP。\n\n* 10.1.2.21 这台的 NAT 规则如下：\n\n```\n# iptables -t nat -A PREROUTING -p tcp -d 10.1.2.11 --dport 80 -j DNAT --to-destination 10.1.2.21:80\n# iptables -t nat -A POSTROUTING -p tcp -s 10.1.2.21 --sport 80 -j SNAT --to-source 10.1.2.11:80\n```\n\n* 10.1.2.22 这台的 NAT 规则如下：\n\n```\n# iptables -t nat -A PREROUTING -p tcp -d 10.1.2.11 --dport 80 -j DNAT --to-destination 10.1.2.22:80\n# iptables -t nat -A POSTROUTING -p tcp -s 10.1.2.22 --sport 80 -j SNAT --to-source 10.1.2.11:80\n```\n\n默认可以认为 VIP 在 10.1.2.21 上挂着，那么当这台机器发生故障不能提供服务时，我们可以及时将 VIP 挂到 10.1.2.22 上，这样就可以保证服务不中断了。当然我们可以写一个简单的 SHELL 脚本来完成 VIP 的检测及挂载，方法非常简单。\n\n注： LVS 的实现中貌似有这么一项，还没有深入去研究 LVS。\n\n#### 6.3.4 nat 表为虚拟机做内外网联通\n\n宿主机内网 IP 是 10.67.15.183(eth1)，外网 IP 是 202.102.152.183(eth0)，内网网关是 10.67.15.1，其上面的虚拟机 IP 是 10.67.15.250(eth1)。\n\n目前虚拟机只能连接内网，其路由信息如下：\n\n```\n# ip r s\n10.67.15.0/24 dev eth1  proto kernel  scope link  src 10.67.15.250\n169.254.0.0/16 dev eth1  scope link  metric 1003\n192.168.0.0/16 via 10.67.15.1 dev eth1\n172.16.0.0/12 via 10.67.15.1 dev eth1\n10.0.0.0/8 via 10.67.15.1 dev eth1\ndefault via 10.67.15.1 dev eth1\n```\n\n若要以 NAT 方式实现该虚拟机即能连接公网又能连接内网，则该虚拟机路由需要改成以下：\n\n```\n# ip r s\n10.67.15.0/24 dev eth1  proto kernel  scope link  src 10.67.15.250\n169.254.0.0/16 dev eth1  scope link  metric 1003\n192.168.0.0/16 via 10.67.15.1 dev eth1\n172.16.0.0/12 via 10.67.15.1 dev eth1\n10.0.0.0/8 via 10.67.15.1 dev eth1\ndefault via 10.67.15.183 dev eth1\n```\n虚拟机连接内网的网关地址也可以写成宿主机内网 IP 地址。\n\n宿主机上面添加如下 NAT 规则：\n\n```\n# iptables -t nat -A POSTROUTING -s 10.67.15.250/32 -d 10.0.0.0/8 -j SNAT --to-source 10.67.15.250\n# iptables -t nat -A POSTROUTING -s 10.67.15.250/32 -d 172.16.0.0/12 -j SNAT --to-source 10.67.15.250\n# iptables -t nat -A POSTROUTING -s 10.67.15.250/32 -d 192.168.0.0/16 -j SNAT --to-source 10.67.15.250\n# iptables -t nat -A POSTROUTING -s 10.67.15.250/32 -j SNAT --to-source 202.102.152.183\n```\n\n以上四条规则的意思是将从源地址 10.67.15.250 发往内网机器上的数据包的源地址改为 10.67.15.250。将从源地址 10.67.15.250 发往公网机器上的数据包的源地址修改为 202.102.152.183。\n\n### 6.4 iptables 管理命令\n#### 6.4.1 查看 iptables 规则\n\n```\n# iptables -nL\n# iptables -n -L\n# iptables --numeric --list\n# iptables -S\n# iptables --list-rules\n# iptables -t nat -nL\n# iptables-save\n```\n> * -n 代表 --numeric，意思是 IP 和端口都以数字形式打印出来。否则会将 127.0.0.1:80 输出成 localhost:http。端口与服务的对应关系可以在 /etc/services 中查看。\n> * -L 代表 --list，列出 iptables 规则，默认列出 filter 链中的规则，可以用 -t 来指定列出哪个表中的规则。\n> * -t 代表 --tables，指定一个表。\n> * -S 代表 --list-rules，以原命令格式列出规则。\n\niptables-save 命令是以原命令格式列出所有规则，可以 -t 指定某个表。\n\n#### 6.4.2 清除 iptables 规则\n\n```\n# iptables -F\n# iptables --flush\n# iptables -F OUTPUT\n# iptables -t nat -F\n# iptables -t nat -F PREROUTING\n```\n\n> * -F 代表 --flush，清除规则，其后面可以跟着链名，默认是将指定表里所有的链规则都清除。\n>   * （警告：如果已经配置过默认规则为 deny 的环境，即 iptables -P INPUT DROP ，直接命令行执行 iptables -F 将使系统的所有网络访问中断，此坑已踩过）\n\n#### 6.4.3 保存 iptables 规则\n\n```\n# /etc/init.d/iptables save\n```\n\n该命令会将 iptables 规则保存到 /etc/sysconfig/iptables 文件里面，如果 iptable 有开机启动的话，开机时会自动将这些规则添加到机器上。\n\n### 6.5 常用操作\niptables 命令中的很多选项前面都可以加\"!\"，意思是“非”。如\"! -s 10.0.0.0/8\"表示除这个网段以外的源地址，\"! --dport 80\"表示除 80 以外的其他端口。\n\n#### 6.5.1 使用 ip6tables 禁用 ipv6\n\n目前 ipv6 不禁用会存在安全隐患，那我们就可以通过 ip6tables 禁用 ipv6，我们只要在 ip6tables 的 filter 表上的出入口以及转发做限定就行了。\n\n```\n[root@meetbill ~]# vim /etc/sysconfig/ip6tables\n*filter\n:INPUT ACCEPT [0:0]\n:FORWARD ACCEPT [0:0]\n:OUTPUT ACCEPT [0:0]\n\n# 添加这 3 条规则\n\n-A INPUT -j REJECT --reject-with icmp6-adm-prohibited\n-A FORWARD -j REJECT --reject-with icmp6-adm-prohibited\n-A OUTPUT -j REJECT --reject-with icmp6-adm-prohibited\nCOMMIT\n[root@meetbill ~]# /etc/init.d/ip6tables restart\n```\n可以通过 ifconfig 查看 ipv6 的地址\n```\n[root@meetbill ~]# ping6 -I eth0 fe80::20c:29ff:febc:8aab\n\n```\n#### 6.5.2 配置 iptables 允许部分端口通行，其他全部阻止\n\n将下列内容放到脚本中，然后执行脚本即可，此脚本可以重复执行\n```\n#!/bin/bash\niptables -F /* 清除所有规则 */\niptables -A INPUT -p tcp --dport 22 -j ACCEPT /*允许包从 22 端口进入*/\niptables -A OUTPUT -p tcp --sport 22 -m state --state ESTABLISHED -j ACCEPT /*允许从 22 端口进入的包返回*/\niptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT /*允许本机访问本机*/\niptables -A OUTPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT\niptables -A INPUT -p tcp -s 0/0 --dport 80 -j ACCEPT /*允许所有 IP 访问 80 端口*/\niptables -A OUTPUT -p tcp --sport 80 -m state --state ESTABLISHED -j ACCEPT\niptables -P INPUT DROP\niptables -P FORWARD DROP\niptables -P OUTPUT DROP\n#iptables-save > /etc/sysconfig/iptables /*保存配置*/\niptables -L /* 显示 iptables 列表 */\n```\n（警告：如果已经配置过默认规则为 deny 的环境，即 iptables -P INPUT DROP ，直接命令行执行 iptables -F 将使系统的所有网络访问中断，此坑已踩过）\n\n如何清除配置尼（-P 为默认规则）\n```\n#!/bin/bash\niptables -P INPUT ACCEPT\niptables -P FORWARD ACCEPT\niptables -P OUTPUT ACCEPT\niptables -F\n#iptables-save > /etc/sysconfig/iptables\niptables -L\n```\n#### 6.5.3 关闭某个端口外部访问\n\n场景: 设置 butterfly 服务外部机器无法访问，本机可正常访问\n\n> 封禁命令\n```\n#iptables -A INPUT -s 127.0.0.1 -d 127.0.0.1 -j ACCEPT\n#iptables -A INPUT -p tcp --dport 8585 -j DROP\n```\n> iptables -L INPUT --line-numbers\n```\nChain INPUT (policy ACCEPT)\nnum  target     prot opt source               destination\n1    ACCEPT     all  --  localhost            localhost\n2    DROP       tcp  --  anywhere             anywhere            tcp dpt:8585\n```\n\n> 解封命令\n```\niptables -D INPUT 2 （注意，这个 2 是行号，是iptables -L INPUT --line-numbers 所打印出来的行号）\niptables -D INPUT 1\n```\n"
  },
  {
    "path": "doc/Linux/service.md",
    "content": "# 常见服务架设\n\n<!-- vim-markdown-toc GFM -->\n\n* [NTP](#ntp)\n    * [简介](#简介)\n    * [ntpd](#ntpd)\n        * [NTP Server 安装配置](#ntp-server-安装配置)\n        * [配置选项说明](#配置选项说明)\n        * [相关命令](#相关命令)\n    * [chrony](#chrony)\n        * [chrony server](#chrony-server)\n        * [chrony client](#chrony-client)\n* [Cron](#cron)\n    * [Cron 基础](#cron-基础)\n        * [什么是 cron, crond, crontab](#什么是-cron-crond-crontab)\n        * [crontab 选项](#crontab-选项)\n        * [crontab 格式](#crontab-格式)\n    * [使用举例](#使用举例)\n* [rsync](#rsync)\n    * [rsync 基本介绍](#rsync-基本介绍)\n    * [rsync 工作场景](#rsync-工作场景)\n    * [使用方法](#使用方法)\n        * [rsync 选项](#rsync-选项)\n        * [常用选项](#常用选项)\n    * [一些命令](#一些命令)\n        * [常用命令](#常用命令)\n        * [ssh 端口非默认 22 同步](#ssh-端口非默认-22-同步)\n        * [ssh 自动接受公钥和修改 known_hosts 文件](#ssh-自动接受公钥和修改-known_hosts-文件)\n    * [inotify+rsync 实现实时文件同步](#inotifyrsync-实现实时文件同步)\n        * [存储数据异地灾备](#存储数据异地灾备)\n            * [需求背景](#需求背景)\n            * [架构](#架构)\n            * [脚本内容](#脚本内容)\n            * [原理](#原理)\n        * [常见问题](#常见问题)\n            * [对大磁盘进行 inotify 监听时出错](#对大磁盘进行-inotify-监听时出错)\n* [telnet-server](#telnet-server)\n    * [安装使用](#安装使用)\n    * [测试](#测试)\n* [ftp](#ftp)\n    * [ftp 简介](#ftp-简介)\n    * [安装配置](#安装配置)\n    * [设置 FTP 虚拟账号密码](#设置-ftp-虚拟账号密码)\n        * [修改 proftpd 配置文件（早期系统镜像的默认 FTP 配置路径为 /etc/proftpd.conf）](#修改-proftpd-配置文件早期系统镜像的默认-ftp-配置路径为-etcproftpdconf)\n        * [获取 ftpasswd 用于设置虚拟账号](#获取-ftpasswd-用于设置虚拟账号)\n        * [设置账号密码](#设置账号密码)\n        * [删除 ftpasswd](#删除-ftpasswd)\n        * [重启 FTP 服务（如启动失败，proftpd -t -d5 检查配置文件出错点）](#重启-ftp-服务如启动失败proftpd--t--d5-检查配置文件出错点)\n        * [wget 使用](#wget-使用)\n\n<!-- vim-markdown-toc -->\n# NTP\n## 简介\n\nNetwork Time Protocol-NTP 是用来使计算机时间同步化的一种协议，它可以使计算机对其服务器或时钟源（如石英钟，GPS 等等）做同步化，它可以提供高精准度的时间校正（LAN 上与标准间差小于 1 毫秒，WAN 上几十毫秒），且可使用加密确认的方式来防止恶毒的协议攻击。默认使用 `UDP 123 端口`\n\nNTP 提供准确时间，首先需要一个准确的 UTC 时间来源，NTP 获得 UTC 的时间来源可以从原子钟、天文台、卫星，也可从 Internet 上获取。时间服务器按照 NTP 服务器的等级传播，根据离外部 UTC 源的远近将所有服务器归入不用的层 (Stratum) 中。Stratum-1 在顶层由外部 UTC 接入，stratum-1 的时间服务器为整个系统的基础，Stratum 的总数限制在 15 以内。下图为 NTP 层次图：\n\n![Screenshot](../../images/linux_service/Network_Time_Protocol_servers_and_clients.png)\n\n## ntpd\n### NTP Server 安装配置\n\n关于 NTP 服务器的安装，根据不同版本安装方法也不同。REDHAT 系统则可以使用 yum 安装，Ubuntu 系列可以使用 `apt-get` 安装，这里不做具体的介绍，主要详细介绍配置文件的信息。\n\n对于 CentOS 过滤注释和空行后，NTP 配置文件内容如下\n\n```\n# grep -vE '^#|^$' /etc/ntp.conf\ndriftfile /var/lib/ntp/drift\n\n# 默认对所有 client 拒绝所有的操作\nrestrict default kod nomodify notrap nopeer noquery\nrestrict -6 default kod nomodify notrap nopeer noquery\n\n# 允许本机地址的一切操作\nrestrict 127.0.0.1\nrestrict -6 ::1\n\n# 允许其他机器连接\nrestrict default kod nomodify\n\nserver 0.centos.pool.ntp.org\nserver 1.centos.pool.ntp.org\nserver 2.centos.pool.ntp.org\nincludefile /etc/ntp/crypto/pw\nkeys /etc/ntp/keys\n```\n\n### 配置选项说明\n\n* `driftfile` 选项， 用来保存系统时钟频率偏差。 ntpd 程序使用它来自动地补偿时钟的自然漂移， 从而使时钟即使在切断了外来时源的情况下， 仍能保持相当的准确度。`无需更改`\n* `restrict` 语法为：restrict IP mask 掩码 参数\n    * IP 规定了允许或不允许访问的地址（此处若为 default，即为 0.0.0.0 所有 ip），配合掩码可以对某一网段进行限制。\n        * `ignore`:     关闭所有 NTP 服务\n        * `nomodiy`:    客户端不能修改服务端的时间，但可以作为客户端的校正服务器\n        * `notrust`:    拒绝没有通过认证的客户端\n        * `kod`:        kod 技术科阻止 \"Kiss of Death\" 包（一种 DOS 攻击）对服务器的破坏\n        * `nopeer`:     不与其它同一层的 NTP 服务器进行同步\n        * `noquery`:    不提供时间查询，即用户端不能使用 ntpq，ntpc 等命令来查询 ntp 服务器\n        * `notrap`:     不提供 trap 远端事件登陆的功能\n* `server [IP|FQDN|prefer]`指该服务器上层 NTP Server，使用 prefer 的优先级最高，没有使用 prefer 则按照配置文件顺序由高到低，默认情况下至少 15min 和上层 NTP 服务器进行时间校对\n* `fudge`:          可以指定本地 NTP Server 层，如 `fudge 127.0.0.1 stratum 9`\n* `broadcast 网段 子网掩码`:    指定 NTP 进行时间广播的网段，如`broadcast 192.168.1.255`\n* `logfile`:        可以指定 NTP Server 日志文件\n\n**bill 提醒**\n```\nrestrict 用于权限控制，server 用于设定上级时间服务器\n主要是这两个参数\n```\n\n几个与 NTP 相关的配置文件：` /usr/share/zoneinfo/`、`/etc/sysconfig/clock`、`/etc/localtime`\n\n* `/usr/share/zoneinfo/`:  存放时区文件目录\n* `/etc/sysconfig/clock`:  指定当前系统时区信息\n* `/etc/localtime`:        相应的时区文件\n\n如果需要修改当前时区，则可以从 /usr/share/zoneinfo/ 目录拷贝相应时区文件覆盖 /etc/localtime 并修改 /etc/sysconfig/clock 即可\n\n```\ncp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime\nsed -i 's:ZONE=.*:ZONE=\"Asia/Shanghai\":g' /etc/sysconfig/clock\n```\n\n### 相关命令\n\n`ntpstat` 查看同步状态\n\n```\n# ntpstat\nsynchronised to NTP server (192.168.0.18) at stratum 4\n   time correct to within 88 ms  \t# 表面时间校正 88ms\n   polling server every 1024 s\t\t# 每隔 1024s 更新一次\n```\n\n`ntpq` 列出上层状态\n\n```\n# ntpq -np\n   remote refid  st t when poll reach   delay   offset  jitter\n==============================================================\n* NTPD(IP)  IP   3  u  101 1024  377   14.268    0.998   0.143\n```\n\n输出说明：\n\n* `remote`:  NTP Server\n* `refid` :  参考的上层 ntp 地址\n* `st`    :  层次\n* `when`  :  上次更新时间距离现在时常\n* `poll`  :  下次更新时间\n* `reach` :  更新次数\n* `delay` :  延迟\n* `offset`:  时间补偿结果\n* `jitter`:  与 BIOS 硬件时间差异\n\n`ntpdate` 同步当前时间：`ntpdate NTP 服务器地址`\n\n## chrony\n\n使用安装命令安装 chrony 包即可\n\n### chrony server\n\n配置文件：/etc/chrony.conf\n\n对于 chrony server 来说，主要配置两项，上游的 ntp 服务器和对下游的权限\n\n> * 上游的 ntp 服务器\n>   * 有固定的 ntp 服务器或者可连互联网\n>     * 配置 `server 0.centos.pool.ntp.org iburst` 即可\n>   * 无外网环境使用本地的时间进行往下游同步\n>     * 配置 `local stratum 10`\n> * 对下游的权限\n>   * `allow 10.0.0.0/24` 对 10.0.0 网段开放\n>   * `allow 0/0` 对所有 IP 开放\n\n**bill 提醒**\n```\n(1) 无外网环境时，如果没有设置 local stratum 0，下游服务器显示的状态是不可达状态\n(2) 不加 allow 记录时，默认拒绝所有连接\n(3) chrony 端口为 udp 123\n```\n启动并设置开机自启\n```\n# systemctl enable chronyd.service\n# systemctl start chronyd.service\n```\n### chrony client\n\n对于 client 来说，只需要配置上游的服务器\n\n配置文件：/etc/chrony.conf\n\n添加 `server 上游服务器 IP/ 主机名 iburst`即可\n\n启动并设置开机自启\n```\n# systemctl enable chronyd.service\n# systemctl start chronyd.service\n```\n\n**查看同步状态**\n\n```\n#chronyc sources -v\n```\n上面命令会输出上游服务器的连接状态\n> * `* 正常`\n> * `? 不可达`\n\n# Cron\n## Cron 基础\n\n### 什么是 cron, crond, crontab\n\n> **cron** is the general name for the service that runs scheduled actions. **crond** is the name of the daemon that runs in the background and reads **crontab** files.\n\n简单理解：cron 是服务，crond 是守护进程， crontab 的 crond 的配置文件。\n\n### crontab 选项\n\n+ `crontab -e` : Edit your crontab file, or create one if it doesn't already exist. # 推荐使用命令新增计划任务 -- 语法检查\n+ `crontab -l` : Display your crontab file.\n+ `crontab -r` : Remove your crontab file. # 慎用\n+ `crontab -u user` : Used in conjunction with other options, this option allows you to modify or view the crontab file of user. When available, only administrators can use this option.\n\n### crontab 格式\n\n    minute(s) hour(s) day(s) month(s) weekday(s) command(s)\n\n```\n# Use the hash sign to prefix a comment\n# +—————- minute (0 – 59)\n# |  +————- hour (0 – 23)\n# |  |  +———- day of month (1 – 31)\n# |  |  |  +——- month (1 – 12)\n# |  |  |  |  +—- day of week (0 – 7) (Sunday=0 or 7)\n# |  |  |  |  |\n# *  *  *  *  *  command to be executed\n```\n\n## 使用举例\n\n使用命令 `crontab -e` 编辑 crontab 文件。\n\n(1) 在每天的 7 点同步服务器时间\n\n    0 7 * * * ntpdate 192.168.1.112\n\n\n(2) 每两个小时执行一次\n\n    0 */2 * * * echo \"2 minutes later\" >> /tmp/output.txt\n\n(3) 每周五早上十点写周报\n\n    0 10 * * * 5 /home/jerryzhang/update_weekly.py\n\n(4) 每天 6, 12, 18 点执行一次命令\n\n    0 6,12,18 * * *  /bin/echo hello\n\n(5) 每天 13, 14, 15, 16, 17 点执行一次命令\n\n    0 13-17 * * *  /bin/echo hello\n\n__注：__\n\n* 程序执行完毕，系统会给对应用户发送邮件，显示该程序执行内容，如果不想收到，可以重定向内容 `> /dev/null 2>&1`\n* 如果执行语句中有 `%` 号，需要使用反斜杠 '\\' 转义\n\n# rsync\n\n## rsync 基本介绍\n\n`rsync` 是类 unix 系统下的数据镜像备份工具，从软件的命名上就可以看出来了—— remote sync。它的特性如下：\n\n\n* 1、可以镜像保存整个目录树和文件系统\n* 2、可以很容易做到保持原来文件的权限、时间、软硬链接等等\n* 3、无须特殊权限即可安装\n* 4、优化的流程，文件传输效率高\n* 5、可以使用 rsh、ssh 等方式来传输文件，当然也可以通过直接的 socket 连接\n* 6、支持匿名传输\n\n在使用 rsync 进行远程同步时，可以使用两种方式：__远程 Shell 方式__（用户验证由 ssh 负责）和 __C/S 方式__（即客户连接远程 rsync 服务器，用户验证由 rsync 服务器负责）。\n\n无论本地同步目录还是远程同步数据，首次运行时将会把全部文件拷贝一次，以后再运行时将只拷贝有变化的文件（对于新文件）或文件的变化部分（对于原有文件）。\n\n## rsync 工作场景\n\n> * 两台服务器之间数据同步。\n> * 把所有客户服务器数据同步到备份服务器，生产场景集群架构服务器备份方案。\n> * rsync 结合 inotify 的功能做实时的数据同步。\n\n## 使用方法\n\nrsync 可以使用 ssh 和 C/S 方式进行传输文件，以下使用 ssh 方式\n\n```\nrsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST # 执行“推”操作\nor   rsync [OPTION]... [USER@]HOST:SRC [DEST]   # 执行“拉”操作\n```\n### rsync 选项\n\n```\nUsage: rsync [OPTION]... SRC [SRC]... DEST\n  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST\n  or   rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST\n  or   rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST\n  or   rsync [OPTION]... [USER@]HOST:SRC [DEST]\n  or   rsync [OPTION]... [USER@]HOST::SRC [DEST]\n  or   rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]\nThe ':' usages connect via remote shell, while '::' & 'rsync://' usages connect\nto an rsync daemon, and require SRC or DEST to start with a module name.\n```\n\n__注：__ 在指定复制源时，路径是否有最后的 “/” 有不同的含义，例如：\n\n* /data ：表示将整个 /data 目录复制到目标目录\n* /data/ ：表示将 /data/ 目录中的所有内容复制到目标目录\n\n### 常用选项\n\n* `-v` : Verbose (try -vv for more detailed information)            # 详细模式显示\n* `-e` \"ssh options\" : specify the ssh as remote shell              # 指定 ssh 作为远程 shell\n* `-a` : archive mode   # 归档模式，表示以递归方式传输文件，并保持所有文件属性，等于 -rlptgoD\n    * `-r`(--recursive) : 目录递归\n    * `-l`(--links) ：保留软链接\n    * `-p`(--perms) ：保留文件权限\n    * `-t`(--times) ：保留文件时间信息\n    * `-g`(--group) ：保留属组信息\n    * `-o`(--owner) ：保留文件属主信息\n    * `-D`(--devices) ：保留设备文件信息\n* `-z` : 压缩文件\n* `-h` : 以可读方式输出\n* `-H` : 复制硬链接\n* `-X` : 保留扩展属性\n* `-A` : 保留 ACL 属性\n* `-n` : 只测试输出而不正真执行命令，推荐使用，特别防止 `--delete` 误删除！\n* `--stats` : 输出文件传输的状态\n* `--progress` : 输出文件传输的进度\n* `––exclude=PATTERN` : 指定排除一个不需要传输的文件匹配模式\n* `––exclude-from=FILE` : 从 FILE 中读取排除规则\n* `––include=PATTERN` : 指定需要传输的文件匹配模式\n* `––include-from=FILE` : 从 FILE 中读取包含规则\n* `--numeric-ids` : 不映射 uid/gid 到 user/group 的名字\n* `-S, --sparse` : 对稀疏文件进行特殊处理以节省 DST 的空间（有空洞文件时使用）\n* `--delete` : 删除 DST 中 SRC 没有的文件，也就是所谓的镜像 [mirror] 备份\n* `-P` 等同于 `--partial` 保留那些因故没有完全传输的文件，以是加快随后的再次传输\n\n\n## 一些命令\n\n### 常用命令\n\n```\n#rsync -avzP --delete [SRC] [DEST]\n```\n\n__注：__ 日常传输时参数记不清楚时，只需要加 `-a` 参数即可，如果有稀疏文件，则添加 `-S` 选项可以提升传输性能。\n\n```\n[tips]\n稀疏文件（Sparse File）\n\n在 UNIX 文件操作中，文件位移量可以大于文件的当前长度，在这种情况下，对该文件的下一次写将延长该文件，并在文件中构成一个空洞。位于文件中但没有写过的字节都被设为 0。\n\n稀疏文件与其他普通文件基本相同，区别在于文件中的部分数据是全 0，且这部分数据不占用磁盘空间。\n下面是稀疏文件的创建与查看方法\n[root@Linux ceshi]# dd if=/dev/zero of=sparse-file bs=1 count=1 seek=1024k\n[root@Linux ceshi]#  ls -l sparse-file\n-rw-r--r-- 1 root root 1048577 6 月  19 10:20 sparse-file\n[root@Linux ceshi]# du -sh sparse-file\n4.0K    sparse-file\n[root@Linux ceshi]# cat sparse-file  >> meetbill_file\n[root@Linux ceshi]# du -sh meetbill_file\n1.1M    meetbill_file\n[root@Linux ceshi]# ll\n总用量 1032\n-rw-r--r-- 1 root root 1048577 6 月  19 10:21 meetbill_file\n-rw-r--r-- 1 root root 1048577 6 月  19 10:20 sparse-file\n[root@Linux ceshi]# ll -h\n总用量 1.1M\n-rw-r--r-- 1 root root 1.1M 6 月  19 10:21 meetbill_file\n-rw-r--r-- 1 root root 1.1M 6 月  19 10:20 sparse-file\n```\n\n### ssh 端口非默认 22 同步\n\n使用 ssh 方式传输时如果连接服务器 ssh 端口非标准，则需要通过 `-e` 选项指定：\n\n```\n#rsync -avzP --delete  -e \"ssh -p 22222\" [USER@]HOST:SRC [DEST]\n```\n### ssh 自动接受公钥和修改 known_hosts 文件\n\n```\n#rsync -a -e \"ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no\" [USER@]HOST:SRC [DEST]\n```\n\n## inotify+rsync 实现实时文件同步\n\n### 存储数据异地灾备\n\n#### 需求背景\n\n服务器文件需要实时同步，即使是轮询，也存在同步延迟，inotify 的出现让真正的实时成为了现实\n我们可以用 inotify 去监控文件系统的事件变化，一旦有我们期望的事件发生，就使用 rsync 进行冗余同步\n\n#### 架构\n\n\n| 用途        | IP           |\n| ------------- |:-------------:|\n| 服务端 A| 192.168.199.101 |\n| 服务器 B（备份服务器） | 192.168.199.102|\n\n```\n   +--------+          +-------------------+\n   |服务器 A |--------->|服务器 B（备份服务器）|\n   +--------+          +-------------------+\n\n  inotify+rsync             rsync\n\n```\n\n#### 脚本内容\n\n所有配置只需要在服务器 A 上配置即可\n\n(1) 安装 `inotify-tools`(yum -y install inotify-tools)\n\n(2) 配置服务器 A 使用秘钥登录服务器 B\n\n(3) 在服务器 A 上编写脚本，主要配置服务器 B 的机器 IP，登录用户，以及服务器器 A 的存储目录和存储数据异地灾备目录\n\n将此文件保存到 /opt/inotify_rsync.sh\n\n``` bash\n    #!/bin/bash\n    host=192.168.199.102\n    user=root\n    # 服务器存储目录\n    src='/tmp/src1/'\n    # 存储数据异地灾备目录\n    dest='/tmp/dest1'\n\n    inotifywait -mrq -e modify,attrib,moved_to,moved_from,move,move_self,create,delete,delete_self --timefmt='%d/%m/%y %H:%M' --format='%T %w%f %e' $src | while read chgeFile\n    do\n        rsync -avPz --delete $src $user@$host:$dest &>>./rsync.log\n    done\n```\n\n下载脚本\n\n```\n#curl -o inotify_rsync.sh https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/service/inotify_rsync.sh\n```\n\n(3) 启动异地灾备程序\n\n```\n    #nohup /bin/bash /opt/inotify_rsync.sh &  // 后台不挂断地运行命令\n    #echo \"nohup /bin/bash /opt/inotify_rsync.sh &\" >> /etc/rc.local // 设置 linux 服务器启动自动启动 nohup\n```\n\n#### 原理\n\n1. 使用 inotifywait 监控文件系统时间变化\n2. while 通过管道符接受内容，传给 read 命令\n3. read 读取到内容，则执行 rsync 程序\n\n### 常见问题\n\n#### 对大磁盘进行 inotify 监听时出错\n```\nFailed to watch /mnt/;upper limit on inotify watches reached!\nPlease increase the amount of inotify watches allowed per user via `/proc/sys/fs/inotify/max_user_watches’.`\n```\ncat 一下这个文件，默认值是 8192，echo 8192000 > /proc/sys/fs/inotify/max_user_watches 即可~\n\n# telnet-server\n\n## 安装使用\n\n```\n#curl -o telnet-server.tar.gz https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/service/telnet-server.tar.gz\n#tar -zxvf telnet-server.tar.gz\n#cd telnet-server*\n#sh start.sh\n```\n执行程序后有三项，执行第一项可以进行安装并启动 telnet-server，第二项会关闭 telnet-server 并将开机自动启动关闭\n\n## 测试\n\n需要测试 telnet 是否成功开启\n```\n#telnet localhost\n```\n输入用户名密码能登录成功。同时需要测试下其他机器远程 telnet 是否成功，如果不成功，那么很有可能是防火墙的问题\n\n```\n#iptables -I INPUT -p tcp --dport 23 -jACCEPT\n#service iptables save\n#service iptables restart\n```\n# ftp\n\n## ftp 简介\n\nftp 工作会启动两个通道：控制通道 ， 数据通道。在 ftp 协议中，控制连接均是由客户端发起的，而数据连接有两种模式：port 模式（主动模式）和 pasv 模式（被动模式）\n\n* **PORT 模式：**\n在客户端需要接收数据时，ftp_client （大于 1024 的随机端口） —> PORT 命令 —> ftp_server (21)  发送 PORT 命令，这个 PORT 命令包含了客户端是用什么端口来接收数据（大于 1024 的随机端口），在传送数据时， ftp_server 将通过自己的 TCP 20 端口和 PORT 中包含的端口建立新的连接来传送数据。\n\n* **PASV 模式：**\n传送数据时，ftp_client —> PASV 命令 —> ftp_server(21) 发送 PASV 命令时，ftp_server 自动打开一个 1024--5000 之间的随机端口并且通知 ftp_client 在这个端口上传送数据，然后客户端向指定的端口发出请求连接，建立一条数据链路进行数据传输。\n\n\n如果想对访问 FTP 的帐户给予更多的权限，可以用本地帐户来实现。但是，本地帐户默认情况下是可以登陆 Linux 系统的，这样对 Linux 系统来说是一个安全隐患。那么怎么能在灵活的赋予 FTP 用户权限的前提下，保证 FTP 服务器乃至整个 Linux 系统的安全呢？使用虚拟用户就是一种解决办法\n\n安装包\n> * vsftpd\n> * db4*\n\n## 安装配置\n\n```\n[root@meetbill ~]#curl -o ftptool.sh https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/service/ftptool.sh\n[root@meetbill ~]#chmod +x ftptool.sh\n[root@meetbill ~]#./ftptool.sh install_server\n[root@meetbill ~]#./ftptool.sh add_user\n[root@meetbill ~]#./ftptool.sh start\n```\n\n## 设置 FTP 虚拟账号密码\n\n### 修改 proftpd 配置文件（早期系统镜像的默认 FTP 配置路径为 /etc/proftpd.conf）\n\n```\n注释<Anonymous ~ftp> … </Anonymous>之间相关配置\n\n<Anonymous ~ftp> … </Anonymous>之外新增如下配置\nAuthOrder mod_auth_file.c\n\nAuthUserFile /etc/proftpd.passwd\n\nRequireValidShell off\n```\n\n\n### 获取 ftpasswd 用于设置虚拟账号\n\n拉取 proftpd 源码包中的 ftpasswd 文件\n```\ncd /usr/sbin;wget https://github.com/downloads/proftpd/proftpd.github.com/proftpd-1.3.4b.tar.gz -O proftpd-1.3.4b.tar.gz; tar -zxvf proftpd-1.3.4b.tar.gz; mv ./proftpd-1.3.4b/contrib/ftpasswd .;rm -rf proftpd-1.3.4b*\n```\n### 设置账号密码\n```\nftpasswd --file=/etc/proftpd.passwd --home=xxx --shell=/bin/false --name=xxx --uid=99 --gid=99 --passwd\n```\n配置说明\n\n> * --home=xxx 指定 ftp 用户登录后的根目录（eg. --home=/home）\n> * --name=xxx 指定 ftp 用户名\n> * --uid=99 --gid=99 指定账号关联对应系统用户和组\n> * wget 获取文件路径以–home 指定路径为基础进行拼接（eg. --home=/home; wget ftp://…/home/work => wget ftp://…/work ）\n\n> demo\n```\nid meetbill\nuid=500(meetbill) gid=501(meetbill) groups=501(meetbill)\n\nftpasswd --file=/etc/proftpd.passwd --home=/home/meetbill/ --shell=/bin/false --name=meetbill --uid=500 --gid=501 --passwd\n这里会输入两次密码\n```\n\n### 删除 ftpasswd\n```\ncd /usr/sbin; rm -f ftpasswd\n```\n### 重启 FTP 服务（如启动失败，proftpd -t -d5 检查配置文件出错点）\n```\nservice proftpd restart\n```\n\n### wget 使用\n```\nwget --ftp-user=meetbill  --ftp-password=xxxxxxxxx  ftp://xxxx/test_dif/test_file -O test_file\n\ntest_dif/test_file 在物理机上的绝对路径为 /home/meetbill/test_dif/test_file\n```\n"
  },
  {
    "path": "doc/Linux/shell.md",
    "content": "# Shell 基础及实例\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 shell 编程环境](#1-shell-编程环境)\n    * [1.1 编程基础知识](#11-编程基础知识)\n        * [1.1.1 程序编程风格](#111-程序编程风格)\n        * [1.1.2 程序的执行方式](#112-程序的执行方式)\n        * [1.1.3 shell 脚本](#113-shell-脚本)\n        * [1.1.4 运行脚本的两种方式](#114-运行脚本的两种方式)\n    * [1.2 Bash 编程](#12-bash-编程)\n    * [1.3 逻辑运算](#13-逻辑运算)\n* [2 bash 变量类型](#2-bash-变量类型)\n    * [2.1 强弱类型语言的区别](#21-强弱类型语言的区别)\n    * [2.2 Bash 中的变量](#22-bash-中的变量)\n        * [2.2.1 本地变量](#221-本地变量)\n        * [2.2.2 环境变量](#222-环境变量)\n        * [2.2.3 只读变量](#223-只读变量)\n        * [2.2.4 位置变量](#224-位置变量)\n    * [2.3 变量特殊用法](#23-变量特殊用法)\n        * [2.3.1 将多行结果赋值给变量](#231-将多行结果赋值给变量)\n        * [2.3.2 去除行结果后的特殊字符](#232-去除行结果后的特殊字符)\n* [3 bash 的配置文件](#3-bash-的配置文件)\n* [4 bash 中的算术运算符](#4-bash-中的算术运算符)\n* [5 条件测试](#5-条件测试)\n    * [Bash 的测试类型](#bash-的测试类型)\n    * [文件测试](#文件测试)\n* [6 bash 脚本编程之用户交互](#6-bash-脚本编程之用户交互)\n* [7 流程控制](#7-流程控制)\n    * [if 语句](#if-语句)\n    * [for 循环](#for-循环)\n        * [for 循环基础](#for-循环基础)\n        * [for 循环的特殊格式](#for-循环的特殊格式)\n    * [while 循环](#while-循环)\n        * [while 基础](#while-基础)\n        * [创建死循环](#创建死循环)\n        * [while 循环遍历文件的每一行](#while-循环遍历文件的每一行)\n        * [while 与 for 的区别](#while-与-for-的区别)\n            * [行读取](#行读取)\n            * [ssh 命令操作](#ssh-命令操作)\n    * [case 语句](#case-语句)\n* [8 函数](#8-函数)\n    * [函数基础](#函数基础)\n        * [Example 编写一个服务启动关闭脚本](#example-编写一个服务启动关闭脚本)\n    * [函数返回值](#函数返回值)\n        * [Example 求 N 的阶乘](#example-求-n-的阶乘)\n* [9 数组](#9-数组)\n    * [数组](#数组)\n        * [定义](#定义)\n    * [引用数组中的元素](#引用数组中的元素)\n* [10 bash 的字符串处理工具](#10-bash-的字符串处理工具)\n    * [字符串切片](#字符串切片)\n    * [基于模式取子串](#基于模式取子串)\n    * [查找替换](#查找替换)\n    * [查找并删除](#查找并删除)\n    * [字符大小写转换](#字符大小写转换)\n    * [变量赋值](#变量赋值)\n* [11 Bash 命令自动补全](#11-bash-命令自动补全)\n    * [11.1 内置补全命令](#111-内置补全命令)\n    * [11.2 编写脚本](#112-编写脚本)\n        * [11.2.1 支持主选项](#1121-支持主选项)\n        * [11.2.2 支持子选项](#1122-支持子选项)\n        * [11.2.3 安装补全脚本](#1123-安装补全脚本)\n* [12 常用实例](#12-常用实例)\n    * [12.1 推荐添加内容](#121-推荐添加内容)\n    * [12.2 脚本的配置文件](#122-脚本的配置文件)\n    * [12.3 ssh 登录相关](#123-ssh-登录相关)\n    * [12.4 ping 文件列表中所有主机](#124-ping-文件列表中所有主机)\n    * [12.5 shell 模板变量替换](#125-shell-模板变量替换)\n        * [应用场景](#应用场景)\n        * [使用方式](#使用方式)\n* [13 日常使用库](#13-日常使用库)\n\n<!-- vim-markdown-toc -->\n\n## 1 shell 编程环境\n### 1.1 编程基础知识\n#### 1.1.1 程序编程风格\n\n> * 过程式：以指令为中心，数据服务于指令；\n> * 对象式：以数据为中心，指令服务于数据；\n\nshell 程序，提供了编程能力，解释执行，shell 就是一解释器；\n\n#### 1.1.2 程序的执行方式\n 过程式编程的三种结构；\n> * 顺序执行\n> * 循环执行\n> * 选择执行\n\n#### 1.1.3 shell 脚本\n首行特定格式：\n`#!/bin/bash`\n\n#### 1.1.4 运行脚本的两种方式\n\n> * a、 给予执行权限，通过具体的文件路径指定文件执行；\n> * b、 直接运行解释器，将脚本作为解释器程序的参数运行；\n\nExample\n```\n    [root@localhost test1]# vim test.sh\n    [root@localhost test1]# bash test.sh\n    hello,girl\n    [root@localhost test1]# chmod +x test.sh\n    [root@localhost test1]# ./test.sh\n    hello,girl\n```\n\n### 1.2 Bash 编程\n\n> * bash 是弱类型编程，变量默认为字符型；\n> * 把所有要存储的数据统统当做字符进行存储；\n> * 变量不需要事先声明，可以在调用时直接赋值使用，参与运算会自动进行隐式类型转换；\n> * 不支持浮点数；\n\n### 1.3 逻辑运算\n\n> * 与：&& 同为 1 则为 1，否则为 0；\n> * 或：|| 同为 0 则为 0，否则为 1；\n> * 非：取反，!0 为 1，!1 为 0；\n> * 短路与运算：双目运算符前面的结果为 0，则结果一定为 0，后面的不执行；\n> * 短路或运算：双目运算符前面的结果为 1，则结果一定为 1，后面的不执行；\n\n## 2 bash 变量类型\n\n变量类型决定了变量的数据存储格式、存储空间大小以及变量能参与的运算种类；\n\n### 2.1 强弱类型语言的区别\n\n> * 强类型：定义变量时必须执行类型、参与运算必须符合类型要求；调用未声明变量会产生错误；\n> * 弱类型：无需指定类型，默认均为字符型；参与运算会自动进行隐式类型转换；变量无需事先定义即可直接调用；\n\n### 2.2 Bash 中的变量\n\n 根据变量的生效范围等标准划分：\n\n> * 本地变量：生效范围为当前 shell 进程；对当前 shell 之外的其他 shell 进程，包括当前 shell 的子 shell 进程均无效；\n> * 环境变量：生效范围为当前 shell 进程及其子进程；\n> * 局部变量：生效范围为当前 shell 进程中某代码片断（通常指函数）\n> * 位置变量：`$1, $2, ...` 来表示，用于让脚本在脚本代码中调用通过命令行传递给它的参数；\n> * 特殊变量：`$?, $0, $*, $@, $#`\n>   * `$$`: 代表所在命令的 PID（不常用）\n>   * `$!`: 代表最后执行的后台命令的 PID（不常用）\n>   * `$?`: 上一条命令的执行状态结果\n>   * `$0`: 命令本身\n>   * `$*`: 传递给脚本的所有参数，以一对双引号给出参数列表\n>   * `$@`: 传递给脚本的所有参数，将各个参数分别加双引号返回\n>   * `$#` : 传递给脚本的参数的个数；\n\n#### 2.2.1 本地变量\n变量赋值：name='VALUE'\n```\na) 在赋值时，VALUE 可以使用以下引用：\n\n【1】可以是直接字符串；name=\"username\"\n【2】变量引用：name=“$username”\n【3】命令引用：name=`COMMAND`,name=$(COMMAND)\n```\n变量引用：`${name}`, 花括号可省略：`$name`\n\n引号引用：\n\n> * `\" \"`： 弱引用，其中的变量引用会被替换为变量值；\n> * `' '`： 强引用，其中的变量不会被替换为变量值，而保持原字符串；\n\n查看所有已定义的变量： #set\n\n销毁变量： # unset name\n\n#### 2.2.2 环境变量\n```\n变量声明、赋值：\n   export name=VALUE\n   declare -x name=VALUE\n变量引用：\n   $name\n   ${name}\n显示所有环境变量：\n   export\n   env\n   printenv\n销毁环境变量：\n   unset name\n```\nBash 中内建的环境变量：\n> * PATH，SHELL，UID，HISTSIZE, HOME, PWD, OLD, HISTFILE, PS1\n\n#### 2.2.3 只读变量\n相当于常量，变量值不可变，不能再进行赋值运算；\n\n声明只读变量的格式：\n```\nreadonly name\ndeclare –r name\n```\n#### 2.2.4 位置变量\n在脚本代码中调用通过命令行传递给脚本的参数；\n```\n$1,$2,…. : 对应调用第 1、第 2 等参数；\n$0: 命令本身；\n$*: 传递给脚本的所有参数；\n$@: 传递给脚本的所有参数；\n$#: 传递给脚本的参数的个数；\n```\n### 2.3 变量特殊用法\n#### 2.3.1 将多行结果赋值给变量\n\n将多行结果赋值给变量时使用变量时需要注意\n\n如：ls_data=$(ls -l)\n\n> * 在 Mac 上直接输出 echo ${ls_data} 即正常结果\n> * 在 CentOS 上操作时\n>   * echo ${ls_data} 时为不换行内容，整体只有一行\n>   * echo \"${ls_data}\" 时内容输出正常\n\n比如要获取 redis 的 info 信息时，不加双引号输出时，输出的内容仅仅为多行结果的最后一行，加双引号输出时可以正常输出\n\n#### 2.3.2 去除行结果后的特殊字符\n\n如要去掉 `^M`\n\n`echo ${variable}|tr -d '\\r'`\n\n## 3 bash 的配置文件\n\n> * 全局配置：\n>   * /etc/profile\n>   * /etc/profile.d/*.sh\n>   * /etc/bashrc\n> * 用户配置：\n>   * ~/.bash_profile\n>   * ~/.bashrc\n\n## 4 bash 中的算术运算符\n`+，-，*，/，%，**`\n\n实现算术运算的方式：\n\n    (1) let var= 算术表达式\n    (2) var=$『算术表达式』\n    (3) var=$(（算术表达式）)\n    (4) var=$(expr arg1 arg2 arg3...)\n乘法符号在有些场景中需要转义；\n\n- bash 内建随机数生成器：$RANDOM\n\n- 增强型赋值：\n    +=，-=，*=，/=，%=\n\n    let varOPERvalue：\n\n        例如：letcount+=1\n- 自增，自减：\n\n    let var+=1\n\n        let var++\n\n    let var-=1\n\n        let var--\n\n## 5 条件测试\n判断某需求是否满足，需要有测试机制来实现；\n\n- Note：专用的测试表达式需要由测试命令辅助完成测试过程；\n\n- 测试命令：\n\n test EXPRESSION\n\n[ EXPRESSION ]\n\n[[ EXPRESSION ]]\n\n    Note: EXPRESSION 前后必须有空白字符，否则报错；\n\n### Bash 的测试类型\n\n- 数值测试：\n\n -gt : 是否大于； >\n\n -ge：是否大于等于； >=\n\n -eq：是否等于 ==\n\n -ne：是否不能于 !=\n\n -lt ：是否小于 <\n\n -le ：是否小于等于； <=\n\n- 字符串测试：\n\n\n> * `==` : 是否等于；\n> * `>` : 是否大于；\n> * `<` : 是否小于；\n> * `!=` : 是否不等于；\n> * `=~` : 左侧字符串是否能够被右侧的 PATTERN 所匹配；\n\n - Note：此表达式一般用于 [[ ]] 中；\n\n -z “STRING” : 测试字符串是否为空，空则为真，不空则为假；\n\n -n “STRING” ：测试字符串是否不空，不空则为真，空则为假；\n\nNote：在字符串比较时用到的操作数都应该使用引号；\n\n### 文件测试\n\n- (a) 存在性测试：\n\n    -a FILE\n\n    -e FILE：文件存在性测试，存在为真，否则为假；\n\n- (b) 存在性及类别测试\n\n     -b FILE ：是否存在且为块设备文件；\n\n     -c FILE ：是否存在且为字符设备文件；\n\n     -d FILE ：是否存在且为目录文件；\n\n     -f FILE ：是否存在且为普通文件；\n\n     -h FILE 或 –L FILE：是否存在且为符号链接文件；\n\n     -p FILE：是否存在且为命名管道文件；\n\n     -S FILE ：是否存在且为套接字文件；\n\n- (c) 文件权限测试：\n\n     -r FILE：是否存在且可读\n\n     -w FILE: 是否存在且可写\n\n     -x FILE: 是否存在且可执行\n\n- (d) 文件特殊权限测试：\n\n     -g FILE：是否存在且拥有 sgid 权限；\n\n     -u FILE：是否存在且拥有 suid 权限；\n\n     -k FILE：是否存在且拥有 sticky 权限；\n\n-  (e) 文件大小测试：\n\n     -s FILE：是否存在且非空；\n\n- (f) 文件是否打开：\n\n     -t fd：fd 表示文件描述符是否已经打开且与某终端相关\n\n     -N FILE：文件自从上一次被读取之后是否被修改过；\n\n     -O FILE：当前有效用户是否为文件属主；\n\n     -G FILE：当前有效用户是否为文件属组；\n\n- (g) 双目测试：\n\n    FILE1 –ef FILE2 ： FILE1 与 FILE2 是否指向同一个设备上的相同 inode；\n\n    FILE1 –nt FILE2 ：FILE 是否新于 FILE2；\n\n    FILE1 –ot FILE2 ：FILE1 是否旧于 FILE2；\n\n- (h) 组合测试条件：\n\n 完成逻辑运算：\n\n     第一种方式：\n\n             COMMAND1 && COMMAND2\n\n             COMMAND1 || COMMAND2\n\n             !COMMAND\n\n         eg：[ -e FILE ] && [ -r FILE ] 文件是否存在且是否有读权限；\n\n\n     第二种方式：\n\n             EXPRESSION1 –a EXPRESSION2\n\n             EXPRESSION1 –o EXPRESSION2\n\n             !EXPRESSION\n\n     必须使用测试命令进行；\n\n- Example\n```\n# [ -z \"$hostName\" -o \"$hostName\"==\"localhost.localdomain\" ] && hostname hostname_w\n# -z 判断 hostName 是否为空，-o 表示或者，即：hostName 为空或者值为 localhot.localdomain 的时候，使用 hostname 命令修改主机名；\n```\n\n- Example\n\n```\n[root@bill ~]# [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab\n#判断文件 /bin/cat 是否存在且是否有可执行权限，&& 是短路与，如果前面执行结果为真则使用 cat 命令#查看文件；\n#\n# /etc/fstab\n# Created by anaconda on Fri Jul 3 03:08:29 2015\n#\n# Accessible filesystems, by reference, are maintained under '/dev/disk'\n# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info\n#\n/dev/mapper/centos-root / xfs defaults 0 0\nUUID=3d04d82b-c52b-4184-8d64-1826db6e2eac /boot xfs defaults 0 0\n/dev/mapper/centos-home /home xfs defaults 0 0\n/dev/mapper/centos-swap swap swap defaults 0 0\n```\n- 快速查找选项定义的方法：\n\n man bash --> /^[[:space:]]*-f\n\n```\n# [ -f /bin/cat -a -x /bin/cat ] && cat /etc/fstab\n#如果文件存在且有执行权限，就用它查看文件内容；\n```\n\n## 6 bash 脚本编程之用户交互\n\n- read 命令：\n\n    read [option]… [name….]\n\n         -p “prompt” 提示符；\n\n         -t TIMEOUT 用户输入超时时间；\n\n- 检测脚本中的语法错误：\n\n         bash –n /path/to/some_script\n\n- 调试执行，查看执行流程：\n\n         bash –x /path/to/some_script\n\n- Example\n\n```\n#!/bin/bash\n#\n#Description: Test read command's grammer.\nread -t 50 -p \"Enter a disk special file:\" diskfile #将用户输入的内容赋值给 diskfile 变量\n[ -z \"$diskfile\" ] && echo \"Fool\" && exit 1\n#判断 diskfile 的值是否为空，如果为空则输出，并退出；\nif fdisk -l | grep \"^Disk $diskfile\" &> /dev/null;then\n fdisk -l $diskfile\nelse\n echo \"Wrong disk special file.\"\n exit 2\nfi\n```\n\n## 7 流程控制\n\n### if 语句\n\n```\nif 语句：\n     CONDITION：\n     bash 命令：\n```\n\n 用命令的执行状态结果：\n\n     成功：true，即执行状态结果值为 0 时；\n\n     失败：false，即执行状态结果为 1-255 时；\n\n 成功或失败的定义：取决于用到的命令；\n\n\n- 单分支 if：\n```\n if CONDITION；then\n     if-true（条件为真时的执行语句集合）\n fi\n```\n- Example\n```\n#!/bin/bash\n#if 单分支语句测试；\nif [ $UID -eq 0 ];then\n     echo \"It's amdinistrator.\"\nfi\n```\n\n-  双分支 if：\n```\n if CONDITION;then\n     if-true\n else\n     if-false\n fi\n```\n- Example\n\n```\n#!/bin/bash\n#\n#if 双分支语句测试；\nif [ $UID -eq 0 ];then\n echo \"It's administrator.\"\nelse\n echo \"It's Comman User.\"\nfi\n```\n\n- 多分支 if：\n```\n if CONDITION1；then\n     if-true\n elif CONDITION2;then\n     if-true\n elif CONDITION3;then\n     if-true\n ….\n else\n     all-false\n fi\n```\n逐条件进行判断，第一次遇为“真”条件时，执行其分支，而后结束；\n\n- Exmaple: 用户键入文件路径，脚本来判断文件类型；\n\n```\n#!/bin/bash\n#\n#if 语句多分支语句；\nread -t 20 -p \"Enter a file path:\" filename\n\nif [ -z \"$filename\" ];then #判断变量是否为空；\n echo \"Usage:Enter a file path.\"\n exit 2\nfi\n\nif [ ! -e $filename ];then #判断用户输入的文件是否存在；\n echo \"No such file.\"\n exit 3\nfi\n\nif [ -f $filename ];then #判断是否为普通文件；\n echo \"A common file.\"\nelif [ -d $filename ];then #判断是否为目录；\n echo \"A directory\"\nelif [ -L $filename ];then #判断是否为链接文件；\n echo \"A symbolic file.\"\nelse\n echo \"Other type.\"\nfi\n```\n\n### for 循环\n\n#### for 循环基础\n\n循环体：要执行的代码，可能要执行 n 遍；\n\n 循环需具备进入循环的条件和退出循环的条件；\n\n- for 循环：\n```\n for 变量名 in 列表；do\n     循环体\n done\n```\n\n- 执行机制：\n\n    依次将列表中的元素赋值给“变量”；每次赋值后即执行一次循环体；直到列表中的元素耗尽，循环结束；\n\n** Example 添加 10 个用户，用户名为：user1-user10：密码同用户名**\n\n```\n#!/bin/bash\n#\n#for 循环，使用列表；\n#添加 10 个用户，user1-user10\n\nif [ ! $UID -eq 0 ];then #判断执行脚本的是否为 root 用户，若不是则直接退出；\n\techo \"Only root can use this script.\"\n\texit 1\nfi\n\nfor i in {1..10};do\n\tif id user$i &> /dev/null;then\t#判断用户是否已经存在；\n\t\techo \"user$i exists\"\n\telse\n\t\tuseradd user$i\n\t\tif [ $? -eq 0 ];then   #判断前一条命令是否执行成功；\n\t\t\techo \"user$i\" | passwd --stdin user$i &> /dev/null\n\t\t\t#passwd 命令从标准输入获得命令，即管道前命令的执行结果；\n\t\tfi\n\tfi\ndone\n\n```\n\n- 列表生成方式：\n\n    (1) 直接给出列表 for i in {bill johnson rechard}\n\n    (2) 整数列表\n\n        (a) {start..end}\n\n        (b) $(seq [start [step]] end)\n\n    (3) 返回列表的命令\n\n        $(COMMAND) --> 如：$(ls /var)\n\n    (4) glob\n\n        /etc/rc.d/rc3.d/K*\n\n    (5) 变量引用\n\n        $@ , $*  -->所有向脚本传递的参数；\n\n**Example 判断某路径下所有文件的类型**\n\n```\n#!/bin/bash\n#\n#for 循环使用命令返回列表；\n\nfor file in $(ls /var);do #使用命令生成列表；\n if [ -f /var/$file ];then\n     echo \"Common file.\"\n elif [ -L /var/$file ];then\n     echo \"Symbolic file.\"\n elif [ -d /var/$file ];then\n     echo \"Directory.\"\n else\n     echo \"Other type\"\n fi\ndone\n```\n\n**Example 使用 for 循环统计关于 tcp 端口监听状态**\n\n```\n#!/bin/bash\n#\n#使用 for 循环过滤 netstat 命令中关于 tcp 的信息；\ndeclare -i estab=0\ndeclare -i listen=0\ndeclare -i other=0\n\nfor state in $( netstat -tan | grep \"^tcp\\>\" | awk '{print $NF}');do\n\n if [ \"$state\" == 'ESTABLISHED' ];then\n     let estab++\n elif [ \"$state\" == 'LISTEN' ];then\n     let listen++\n else\n     let other++\n fi\ndone\n\necho \"ESTABLISHED:$estab\"\necho \"LISTEN:$listen\"\necho \"Unknow:$other\"\n```\n\n#### for 循环的特殊格式\n```\n\tfor (（控制变量初始化；条件判断表达式；控制变量的修正表达式）)；do\n\t\t循环体\n\tdone\n```\n此种格式和 C 语言等的格式是一样一样的，只是多了一对括号；\n\n控制变量初始化：仅在运行到循环代码段时执行一次；\n\n控制变量的修正表达式：每轮循环结束会先进行控制变量修正运算，而后再在条件判断；\n\n**Example 求 100 以内所有正整数之和**\n\n```\n#!/bin/bash\n#\n#for 循环，类似 C 语言格式，求 100 以内正整数之和；\n\ndeclare -i sum=0\n\nfor ((i=1;i<=100;i++));do\n\tlet sum+=$i\ndone\n\necho \"Sum:$sum.\"\n\n```\n\n### while 循环\n#### while 基础\n语法：\n```\n while CONDITION；do\n     循环体\n done\n```\n```\nCONDITION：循环控制条件；进入循环之前，先做一次判断；\n每一次循环之后会再次做判断；条件为“true”，则执行一次循环；\n直到条件测试状态为“false”终止循环；\n\n因此：CONDTION 一般应该有循环控制变量；\n而此变量的值会在循环体不断地被修正，直到最终条件为 false，结束循环。\n```\n\n**Example 用 while 求 100 以内所有正整数之和**\n\n```\n#!/bin/bash\n#使用 while 求 100 以内正整数之和；\ndeclare -i sum=0\ndeclare -i i=1\nwhile [ $i -le 100 ];do\n let sum+=$i\n let i++\ndone\necho $i\necho \"Summary:$sum.\"\n```\n**Example 用 while 添加 10 个用户**\n\n```\n#!/bin/bash\n#\n#使用 while 循环添加 10 个用户\ndeclare -i i=1\ndeclare -i users=0\n\nwhile [ $i -le 10 ];do\n if ! id user$i &> /dev/null;then\n     useradd user$i\n     echo \"Add user: user$i\"\n     let users++\n fi\n let i++\ndone\necho \"Add $users users.\"\n\n```\n\n\n**Example 利用 RANDOM 生成 10 个随机数字，输出这 10 个数字，并显示其中的最大者和最小者**\n\n```\n#!/bin/bash\n#\n#利用 RANDOM 生成 10 个随机数，输出，并求最大值和最小值；\ndeclare -i max=0\ndeclare -i min=0\ndeclare -i i=1\n\nwhile [ $i -le 9 ];do\n\trand=$RANDOM\n\techo $rand\n\n\tif [ $i -eq 1 ];then\n\t\tmax=$rand\n\t\tmin=$rand\n\tfi\n\n\tif [ $rand -gt $max ];then\n\t\tmax=$rand\n\tfi\n\tif [ $rand -lt $min ];then\n\t\tmin=$rand\n\tfi\n\tlet i++\ndone\n\necho \"MAX:$max.\"\necho \"MIN:$min.\"\n\n```\n#### 创建死循环\n\n```\nwhile true;do\n\t循环体\ndone\n```\n\n```\nuntil false；do\n\t循环体\ndone\n\n```\n\n**Example 每隔 3 秒钟到系统上获取已经登录的用户信息；如果用户输入的用户名登录了，则记录于日志中，并退出**\n\n```\n#!/bin/bash\n#\n#用 while 造成死循环，在系统上每隔 3 秒判断一次用户输入的用户名是否登录；\nread -p \"Enter a user name:\" username\n\nwhile true;do\n\tif who | grep \"^$username\" &> /dev/null;then\n\t\tbreak\n\tfi\n\tsleep 3\ndone\n\necho \"$username logggen on.\" >> /tmp/user.log\n\n```\n\n#### while 循环遍历文件的每一行\n```\n\twhile read line;do\n\t\t循环体\n\tdone < /PATH/FROM/SOMEFILE\n```\n依次读取 /PATH/FROM/SOMEFILE 文件中的每一行，且将该行赋值给变量 line；\n\n另一种也很常见的用法【推荐】：\n```\ncommand | while read line\ndo\n\t...\ndone\n```\n**Example 依次读取 /etc/passwd 文件中的每一行，找出其 ID 号为偶数的所有用户，显示其用户名、ID 号及默认 shell**\n\n```\n#!/bin/bash\n#while 循环的特殊用法 读取指定文件的每一行并赋值给变量\n\nwhile read line;do\n\tif [ $[`echo $line | cut -d: -f3` % 2] -eq 0 ];then\n\t\techo -e -n \"username:`echo $line | cut -d: -f1`\\t\"\n\t\techo -e -n \"uid: `echo $line | cut -d: -f3`\\t\"\n\t\techo \"SHELL:`echo $line | cut -d: -f7`\"\n\tfi\ndone < /etc/passwd\n\n```\n#### while 与 for 的区别\n\n##### 行读取\n\n> * while 循环\n>   * 以行读取文件\n> * for 循环\n>   * 以空格和回车符分割读取文件，也就是碰到空格和回车，都会执行循环体，所以需要以行读取的话，就要把文件行中的空格转换成其他字符。\n\n##### ssh 命令操作\n\n> * for 循环\n>   * for line in $(cat $file) 在循环体中进行 ssh 命令操作可以依次执行\n> * while 循环\n>   * 循环体内有 ssh、scp、sshpass 的时候会执行一次循环就退出的情况，解决该问题方法有如下两种\n>     * a、使用`ssh -n \"command\"` ；\n>     * b、将 while 循环内加入 null 重定向，如 `ssh \"cmd\" < /dev/null` 将 ssh 的输入重定向输入。\n\n### case 语句\n```\ncase 变量引用 in\nPAT1)\n\t\t分支 1\n\t\t；\nPAT2）\n\t\t分支 2\n\t\t；\n….\n*)\n\t\t默认分支\n\t\t；\nesac\n```\n\ncase 支持 glob 风格的通配符：\n\n        *: 任意长度任意字符；\n        ?: 任意单个字符；\n        []：指定范围内的任意单个字符；\n\t    a|b: a 或 b\n\n** Example 使用 case 语句改写前一个练习 **\n\n```\n#!/bin/bash\n#\ncat << EOF\ncpu) show cpu information;\nmem) show memory information;\ndisk) show disk information;\nquit) quit\n============================\nEOF\nread -p \"Enter a option: \" option\nwhile [ \"$option\" != 'cpu' -a \"$option\" != 'mem' -a \"$option\" != 'disk' -a \"$option\" != 'quit' ]; do\n    read -p \"Wrong option, Enter again: \" option\ndone\n\ncase \"$option\" in\ncpu)\n\tlscpu\n\t;;\nmem)\n\tcat /proc/meminfo\n\t;;\ndisk)\n\tfdisk -l\n\t;;\n*)\n\techo \"Quit...\"\n\texit 0\n\t;;\nesac\n\n```\n\n\n## 8 函数\n### 函数基础\n\t函数的作用：\n\t过程式编程：为实现代码重用\n\t \t模块化编程\n\t \t结构化编程；\n\n- 语法一：\n```\nfunction f_name {\n\t…函数体…..\n}\n```\n\n- 语法二：\n```\nf_name() {\n\t…函数….\n}\n```\n\n- 函数调用：函数只有被调用才会执行：\n\n\t调用：给定函数名\n\n\t\t函数名出现的地方，会被自动替换为函数代码；\n\n- 函数的生命周期：被调用时创建，返回时终止；\n\n\treturn 命令返回自定义状态结果；\n\n\t\t0：成功\n\n\t\t1-255：失败\n\n**Example 通过函数，创建 10 个用户**\n\n```\n#!/bin/bash\n#\n#通过调用函数添加 10 个用户\n\nfunction adduser {\n\tif id $username &> /dev/null;then\n\t\techo \"$username exists.\"\n\t\treturn 1\n\telse\n\t\tuseradd $username\n\t\t[ $? -eq 0 ] && echo \"Add $username finished.\" && return 0\n\tfi\n}\n\nfor i in {1..10};do\n\tusername=myuser$i\n\tadduser\ndone\n\n```\n\n#### Example 编写一个服务启动关闭脚本\n\n```\n#!/bin/bash\n# chkconfig: - 88 12\n# description: test service script\nprog=$(basename $0)\nlockfile=/var/lock/subsys/$prog\nstart() {\n\tif [ -e $lockfile ];then\n\t\techo \"$prog is already running.\"\n\t\treturn 0\n\telse\n\t\ttouch $lockfile\n\t\t[ $? -eq 0 ] && echo \"Starting $prog finished.\"\n\tfi\n}\nstop() {\n\tif [ -e $lockfile ];then\n\t\trm -f $lockfile && echo \"Stop $prog ok.\"\n\telse\n\t\techo \"$prog is stopped yet.\"\n\tfi\n}\nstatus() {\n\tif [ -e $lockfile ];then\n\t\techo \"$prog is running.\"\n\telse\n\t\techo \"$prog is stopped.\"\n\tfi\n}\nusage() {\n\techo \"Usage:$prog {start | stop | restart | status}\"\n}\nif [ $# -lt 1 ];then\n\tusage\n\texit 1\nfi\n\ncase $1 in\nstart)\n\tstart\n\t;;\nstop)\n\tstop\n\t;;\nrestart)\n\tstop\n\tstart\n\t;;\nstatus)\n\tstatus\n\t;;\n*)\n\tusage\nesac\n```\n### 函数返回值\n函数的执行结果返回值：\n\n\t(1)\t使用 echo 或 print 命令进行输出；\n\n\t(2)\t函数体中调用命令的执行结果；\n\n函数的退出状态码：\n\n\t(1)\t默认取决于函数体中执行的最后一条命令的退出状态码；\n\n\t(2)\t自定义退出状态码；\n\n\t\t使用 return 关键字；\n\n函数可以接受参数：\n\n\t\t传递参数给函数：调用函数时，在函数名后面以空白分隔给定参数列表即可；\n\n例如：“testfunc arg1 arg2 …”\n\n\t在函数体当中，可使用 $1,$2,…. 调用这些参数；还可以使用 $@,$*,$#等特殊变量；\n\n```\n#!/bin/bash\n#\n#使用带参数的函数添加 10 个用户\n\nfunction adduser {\t\t\t#一个可接受参数的函数\n\tif [ $# -lt 1 ];then\n\t\treturn 2   # 2: no arguments\n\tfi\n\n\tif id $1 &> /dev/null;then\n\t\techo \"$1 exists.\"\n\t\treturn 1;\n\telse\n\t\tuseradd $1\n\t\t[ $? -eq 0 ] && echo \"Add $1 finished.\" && return 0\n\tfi\n}\n\nwhile true;do\t\t#死循环，直到输入的值为 quit 是退出；\n\tread -t 20 -p \"Please input your username(quit to cancel):\" username\n\tif [ $username == \"quit\" ];then\n\t\techo \"Quit.\"\n\t\texit 0\n\telse\n\t\tadduser $username\n\tfi\ndone\n```\n\n- 变量作用域：\n\n\t本地变量：当前 shell 进程，为了执行脚本会启动专用的 shell 进程；因此，本地变量的作用范围是当前 shell 脚本程序文件；\n\n\t局部变量：函数的生命周期：函数结束时变量被自动销毁；\n\n\t\t如果函数中有局部变量，其名称同本地变量无关；\n\n在函数中定义局部变量的方法：\n\n\t\tlocal NAME=VALUE\n\n函数递归：函数直接或间接调用自身；\n\n#### Example 求 N 的阶乘\n```\nN！=N（N-1）(N-2)….1\n\tN(N-1)!=N(N-1)(N-2)!\n```\n```\n#!/bin/bash\n#\n#利用函数递归求 N 的阶乘\n\nfact() {\n\tif [ $1 -eq 0 -o $1 -eq 1 ];then\n\t\techo 1\n\telse\n\t\techo $[$1*$(fact $[$1-1])]\n\tfi\n}\nfact 5\n\n```\n\n## 9 数组\n### 数组\n\n> * 变量：存储单个元素的内存空间；\n> * 数组：存储多个元素的连续的内存空间；\n>   * 索引：编号从 0 开始，属于数值索引；\n>   * 注意：索引页可支持使用自定义的格式，而不仅仅是数值格式；bash 的数组支持稀疏格式；\n\n#### 定义\n\n在 Shell 中，用括号来表示数组，数组元素用“空格”符号分割开。定义数组的一般形式为：\n```\narray_name=(value1 ... valuen)\n```\n例如：\n```\narray_name=(value0 value1 value2 value3)\n```\n或者\n```\narray_name=(\nvalue0\nvalue1\nvalue2\nvalue3\n)\n```\n还可以单独定义数组的各个分量：\n```\narray_name[0]=value0\narray_name[1]=value1\narray_name[2]=value2\n\n```\n\n**Example 生成 10 个随机数保存于数组中，并找出其最大值和最小值**\n\n```\n#!/bin/bash\n#\n#生成 10 个随机数保存于数组中；\n\ndeclare -a rand\ndeclare -i max=0\ndeclare -i min=0\n\nfor i in {0..9};do\n\trand[$i]=$RANDOM\n\tif [ $i -eq 1 ];then\n\t\tmax=${rand[$i]}\n\t\tmin=${rand[$i]}\n\tfi\n\techo ${rand[$i]}\n\t[ ${rand[$i]} -gt $max ] && max=${rand[$i]}\n\t[ ${rand[$i]} -lt $min ] && min=${rand[$i]}\ndone\n\necho \"Max:$max\"\necho \"Min:$min\"\n\n```\n\n**Example 定义一个数组，数组中的元素是 /var/log 目录下所有以.log 结尾的文件；要统计其下标为偶数的文件中的行数之和**\n\n```\n#!/bin/bash\n#\n#定义一个数组，数组中的元素是 /var/log 目录下所有以.log 结尾的文件；\n#要统计其下标为偶数的文件中的行数之和；\n\ndeclare -a files\nfiles=(/var/log/*.log)\ndeclare -i lines=0\n\nfor i in $(seq 0 $[${#files[*]}-1]);do\n\tif [ $[$i%2] -eq 0 ];then\n\t\tlet lines+=$(wc -l ${files[$i]} | cut -d' ' -f1)\n\tfi\ndone\n\necho \"Lines:$lines.\"\n\n```\n\n### 引用数组中的元素\n\n- 所有元素：${ARRAY[@]} , ${ARRAY[*]}\n\n- 数组切片：${ARRAY[@]:offset:number}\n\n\t\toffset: 要跳过的元素个数；\n\n\t\tnumber：要取出的元素个数，取偏移量之后的所有元素：${ARRAY[@]:offset};\n\n- 向数组中追加元素：ARRAY[${ARRAY[*]}]\n\n- 删除数组中的某元素：unset ARRAY[INDEX]\n\n- 关联数组：\n\tdeclare –A ARRAY_NAME\n\tARRAY_NAME=([index_name1]=’val1’ [index_name2]=’val2’ ….)\n\n```\n[root@bill scripts]# declare -a array\n[root@bill scripts]# #声明一个数组，不是必要的\n[root@bill scripts]# array=(0 1 2)\n[root@bill scripts]# array=([0]=0 [1]=1 [2]=v2)\n[root@bill scripts]# array[0]=5\n[root@bill scripts]# echo $array\n5\n```\n\n```\n[root@bill scripts]# #以空白作为分隔符拆分字符串为数组\n[root@bill scripts]# str=\"1 2 3\"\n[root@bill scripts]# array=($str)\n[root@bill scripts]# echo $array\n1\n```\n\n```\n[root@bill scripts]# #使用其他分隔符拆分字符串为数组，需指定 IFS\n[root@bill scripts]# IFS=: array=($PATH)\n[root@bill scripts]# echo $array\n/usr/local/sbin\n\n```\n\n- 引用数组元素：\n\t$array  ${array}  ${array[0]}  #第 0 个元素\n\n\t${array[n]}  #第 n 个元素（n 从 0 开始计算）\n\n- 引用整个数组：\n\t${array[*]}  ${array[@]}   这两种方式等同，会把数组展开。\n\n\t${array[*]}  表示把数组拼接在一起的整个字符串，如果作为参数传递，会把整个字符串作为一个参数。\n\n\t${array[@]}  如果作为参数传递，表示把数组中每个元素作为一个参数，数组有多少个元素，就会展开成多少个参数。\n\n- 计算数组元素长度；\n\n\t${#array[*]}\n\n\t${#array[@]}\n\n\t\t不是 ${#array}，\n\n\t\t因为它等同于 ${#array[0]}\n\n- 遍历数组：\n\tfor i in \"${array[@]}\";do\n\t\techo $i;\n\tdone\n\n## 10 bash 的字符串处理工具\n\n### 字符串切片\n- ${var:offset:number}\n\n\t取字符串最右侧的几个字符：${var: -lengh}\n\n\tNote：冒号后面必须有一空白字符；\n\n### 基于模式取子串\n- ${var#*word}：其中 word 可以是指定的任意字符；\n\n\t功能：自左而右，查找 var 变量所存储的字符串中，第一次出现的 word, 删除字符串开头至第一次出现 word 字符之间的所有字符；\n\n- ${var##*word}：其中 word 可以是指定的任意字符；\n\n\t功能：自左而右，查找 var 变量所存储的字符串中出现的 word，删除字符串开头至最后一次由 word 指定的字符之间的所有内容；\n\n**bash 基于模式取子串**\n\n```\n[root@bill scripts]# file=\"/var/log/messages\"\n[root@bill scripts]# echo ${file#*/}\nvar/log/messages\n[root@bill scripts]# echo ${file##*/}\nmessages\n\n```\n\n- ${var%word*}：其中 word 可以是指定的任意字符；\n\n\t功能：自右而左，查找 var 变量所存储的字符串中，第一次出现的 word, 删除字符串最后一个字符向左至第一次出现 word 字符之间的所有字符；\n\n- ${var%%word*}：其中 word 可以是指定的任意字符；\n\n\t功能：自右而左，查找 var 变量所存储的字符串中出现的 word,，只不过删除字符串最右侧的字符向左至最后一次出现 word 字符之间的所有字符；\n```\n[root@bill scripts]# file=\"/var/log/messages\" #从右至左，匹配‘/ ’\n[root@bill scripts]# echo ${file%/*}\n/var/log\n[root@bill scripts]# echo ${file%%/*}    # 双 % 匹配并删除后为空；\n[root@bill scripts]#\n```\n\n**Exampleurl=http://www.google.com:80, 分别取出协议和端口**\n\n```\n[root@bill scripts]# url=http://www.google.com:80\n[root@bill scripts]# echo ${url##*:}    #取端口号\n80\n[root@bill scripts]# echo ${url%%:*}   #取协议\nhttp\n[root@bill scripts]#\n```\n\n### 查找替换\n\t${var/pattern/substi}：查找 var 所表示的字符串中，第一次被 pattern 所匹配到的字符串，以 substi 替换之；\n\n\t${var//pattern/substi}: 查找 var 所表示的字符串中，所有能被 pattern 所匹配到的字符串，以 substi 替换之；\n\n\t${var/#pattern/substi}：查找 var 所表示的字符串中，行首被 pattern 所匹配到的字符串，以 substi 替换之；\n\n\t${var/%pattern/substi}：查找 var 所表示的字符串中，行尾被 pattern 所匹配到的字符串，以 substi 替换之；\n\n```\n[root@bill scripts]# var=$(head -n 1 /etc/passwd)\n[root@bill scripts]# echo $var\nroot x 0 0 root /root /bin/bash\n[root@bill scripts]# echo ${var/root/ROOT}  #替换第一次匹配到的 root 为 ROOT\nROOT x 0 0 root /root /bin/bash\n[root@bill scripts]# echo ${var//root/ROOT}  #替换所有匹配到的 root 为 ROOT\nROOT x 0 0 ROOT /ROOT /bin/bash\n\n```\n```\n[root@bill scripts]# useradd bash -s /bin/bash\n[root@bill scripts]# cat /etc/passwd | grep \"^bash.*bash$\"\nbash:x:1029:1029::/home/bash:/bin/bash\n[root@bill scripts]# line=$(cat /etc/passwd | grep \"^bash.*bash$\")\n[root@bill scripts]# echo $line\nbash x 1029 1029  /home/bash /bin/bash\n[root@bill scripts]# echo ${line/#bash/BASH}\t#替换行首的 bash 为 BASH\nBASH x 1029 1029  /home/bash /bin/bash\n[root@bill scripts]# echo ${line/%bash/BASH}\t#替换行尾的 bash 为 BASH\nbash x 1029 1029  /home/bash /bin/BASH\n```\n\n### 查找并删除\n\t${var/pattern}：查找 var 所表示的字符串中，删除第一次被 pattern 所匹配到的字符串\n\n\t${var//pattern}：查找 var 所表示的字符串中，删除所有被 pattern 所匹配到的字符串；\n\n\t${var/#pattern}：查找 var 所表示的字符串中，删除行首被 pattern 所匹配到的字符串；\n\n\t${var/%pattern}：查找 var 所表示的字符串中，删除行尾被 pattern 所匹配到的字符串；\n\n- Example\n```\n[root@bill scripts]# line=$(tail -n 1 /etc/passwd)\n[root@bill scripts]# echo $line\nbash x 1029 1029  /home/bash /bin/bash\n[root@bill scripts]# echo ${line/bash}  #查找并删除第一次匹配到的 bash\n x 1029 1029  /home/bash /bin/bash\n[root@bill scripts]# echo ${line//bash}\t#查找并删除所有匹配到的 bash\n x 1029 1029  /home/ /bin/\n[root@bill scripts]# echo ${line/#bash}\t#查找并删除匹配到的行首的 bash\n x 1029 1029  /home/bash /bin/bash\n[root@bill scripts]# echo ${line/%bash}   #查找并删除匹配到的行尾 bash\nbash x 1029 1029  /home/bash /bin/\n\n```\n\n### 字符大小写转换\n\t${var^^}：把 var 中的所有小写字母转换为大写；\n\n\t${var,,}：把 var 中的所有大写字母转换为小写；\n\n- Example\n\n```\n[root@bill scripts]# line=$(tail -n 1 /etc/fstab)\t\t#将文件最后一行的值赋值给变量\n[root@bill scripts]# echo ${line^^}\t\t#全部转换为大写后输出\n/DEV/MAPPER/CENTOS-SWAP SWAP                    SWAP    DEFAULTS        0 0\n[root@bill scripts]# line=`echo ${line^^}`\t#将转换为大写后的值在赋值给变量；\n[root@bill scripts]# echo $line\t\t\t#确认目前变量的值全为大写字母；\n/DEV/MAPPER/CENTOS-SWAP SWAP                    SWAP    DEFAULTS        0 0\n[root@bill scripts]# echo ${line,,}\t\t#全部转换为大写后输出；\n/dev/mapper/centos-swap swap                    swap    defaults        0 0\n\n```\n\n### 变量赋值\n\n\t${var:-value}：如果 var 为空或未设置，那么返回 value；否则，则返回 var 的值；\n\n\t${var:=value}：如果 var 为空或未设置，那么返回 value，并将 value 赋值给 var；否则，则返回 var 的值；\n\n- Example\n\n```\n[root@bill scripts]# echo $test\t\t#变量值为空；\n\n[root@bill scripts]# echo ${test:-helloworld}\t#  :- 仅返回设定值，不修改；\nhelloworld\n[root@bill scripts]# echo $test      #变量的值依然为空；\n\n[root@bill scripts]# echo ${test:=helloworld}   #  :=  返回设定值，并赋值给变量；\nhelloworld\n[root@bill scripts]# echo $test\t\t\t# 变量值已修改；\nhelloworld\n```\n## 11 Bash 命令自动补全\n\n### 11.1 内置补全命令\n\nBash 内置有两个补全命令，分别是 compgen 和 complete。compgen 命令根据不同的参数，生成匹配单词的候选补全列表，例如：\n```\n$ compgen -W 'hi hello how world' h\nhi\nhello\nhow\n```\ncompgen 最常用的选项是 -W，通过 -W 参数指定空格分隔的单词列表。h 即我们在命令行当前键入的单词，执行完后会输出候选的匹配列表，这里是以 h 开头的所有单词。\n\ncomplete 命令的参数有点类似 compgen，不过它的作用是说明命令如何进行补全，例如同样使用 -W 参数指定候选的单词列表：\n```\n$ complete -W 'word1 word2 word3 hello' foo\n$ foo w<Tab>\n$ foo word<Tab>\nword1  word2  word3\n```\n我们还可以通过 -F 参数指定一个补全函数：\n```\n$ complete -F _foo foo\n```\n现在键入 foo 命令后，会调用_foo 函数来生成补全的列表，完成补全的功能，这一点正是补全脚本实现的关键所在，我们会在后面介绍。\n\n补全相关的内置变量\n\n除了上面的两个补全命令外，Bash 还有几个内置的变量用来辅助补全功能，这里主要介绍其中三个：\n\n> * COMP_WORDS: 类型为数组，存放当前命令行中输入的所有单词；\n> * COMP_CWORD: 类型为整数，当前光标下输入的单词位于 COMP_WORDS 数组中的索引；\n> * COMPREPLY: 类型为数组，候选的补全结果；\n> * COMP_WORDBREAKS: 类型为字符串，表示单词之间的分隔符；\n> * COMP_LINE: 类型为字符串，表示当前的命令行输入；\n\n例如我们定义这样一个补全函数_foo：\n```\n$ function _foo()\n{\n     echo -e \"\\n\"\n     declare -p COMP_WORDS\n     declare -p COMP_CWORD\n     declare -p COMP_LINE\n     declare -p COMP_WORDBREAKS\n}\n$ complete -F _foo foo\n```\n假设我们在命令行下输入以下内容，再按下 Tab 键补全：\n```\n$ foo b\n\ndeclare -a COMP_WORDS='([0]=\"foo\" [1]=\"b\")'\ndeclare -- COMP_CWORD=\"1\"\ndeclare -- COMP_LINE=\"foo b\"\ndeclare -- COMP_WORDBREAKS=\"\n\\\"'><=;|&(:\"\n```\n对着上面的结果，我想应该比较容易理解这几个变量。当然正如我们之前据说，Bash-completion 包并非是必须的，补全功能是 Bash 自带的。\n\n### 11.2 编写脚本\n\n补全脚本分成两个部分：编写一个补全函数和使用 complete 命令应用补全函数。后者的难度几乎忽略不计，重点在如何写好补全函数。难点在，似乎网上很少与此相关的文档，但是事实上，Bash-completion 自带的补全脚本是最好的起点，可以挑几个简单的改改基本上就可以使用了。\n\n一般补全函数（假设这里依然为_foo) 都会定义以下两个变量：\n```\nlocal cur prev\n```\n其中 cur 表示当前光标下的单词，而 prev 则对应上一个单词：\n```\ncur=\"${COMP_WORDS[COMP_CWORD]}\"\nprev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n```\n#### 11.2.1 支持主选项\n\n初始化相应的变量后，我们需要定义补全行为，即输入什么的情况下补全什么内容，例如当输入 - 开头的选项的时候，我们将所有的选项作为候选的补全结果：\n```\nlocal opts=\"-h --help -f --file -o --output\"\n\nif [[ ${cur} == -* ]] ; then\n        COMPREPLY=( $(compgen -W \"${opts}\" -- ${cur}) )\n        return 0\nfi\n```\n不过再给 COMPREPLY 赋值之前，最好将它重置清空，避免被其它补全函数干扰。\n\n现在完整的补全函数是这样的：\n```\nfunction _foo() {\n    local cur prev opts\n\n    COMPREPLY=()\n\n    cur=\"${COMP_WORDS[COMP_CWORD]}\"\n    prev=\"${COMP_WORDS[COMP_CWORD-1]}\"\n    opts=\"-h --help -f --file -o --output\"\n\n    if [[ ${cur} == -* ]] ; then\n        COMPREPLY=( $(compgen -W \"${opts}\" -- ${cur}) )\n        return 0\n    fi\n}\n```\n现在在命令行下就可以对 foo 命令进行参数补全了：\n```\n$ complete -F _foo foo\n$ foo -\n-f        --file    -h        --help    -o        --output\n```\n\n#### 11.2.2 支持子选项\n当然，似乎我们这里的例子没有用到 prev 变量。用好 prev 变量可以让补全的结果更加完整，例如当输入 --file 之后，我们希望补全特殊的文件（假设以.sh 结尾的文件）：\n```\n    case \"${prev}\" in\n        -f|--file)\n            COMPREPLY=( $(compgen -o filenames -W \"`ls *.sh`\" -- ${cur}) )\n            ;;\n    esac\n```\n现在再执行 foo 命令，--file 参数的值也可以补全了：\n```\n$ foo --file<Tab>\na.sh b.sh c.sh\n```\n#### 11.2.3 安装补全脚本\n\n如果安装了 Bash-completion 包，可以将补全脚本放在 /etc/bash_completion.d 目录下，或者放到~/.bash_completion 文件中。\n如果没有安装 Bash-completion 包，可以把补全脚本放到~/.bashrc 或者其它能被 shell 加载的初始化文件中。\n## 12 常用实例\n### 12.1 推荐添加内容\n\n```\nset -u # 确保变量都被初始化\nset -e # 确保捕获所有非 0 状态\nset -o pipefail # 结合 -e 可以捕获管道后的异常状态\n```\n### 12.2 脚本的配置文件\n-\t(1) 定义文本文件，每行定义\"name=value\"\n-\t(2) 在脚本中 source 此文件即可\n\n```\n[root@bill scripts]# touch /tmp/config.test #创建配置文件；\n[root@bill scripts]# echo \"name=bill\" >> /tmp/config.test #在配置文件中定义变量；\n[root@bill scripts]# vim script_configureFile.sh\t\t#编写脚本，导入配置文件；如内容所示；\n[root@bill scripts]# bash script_configureFile.sh \t#脚本执行结果；\nbill\n[root@bill scripts]# cat script_configureFile.sh\n#!/bin/bash\n#\nsource /tmp/config.test\t\t#导入配置文件，脚本自身并未定义变量；\n\necho $name\t\t\t\t#引用的是配置文件中的变量 name\n```\n### 12.3 ssh 登录相关\n\n> * 可以使用 sshpass 进行直接传入密码\n> * 一定要加 -o StrictHostKeyChecking=no 否则如果是第一次访问对应的机器，会执行无效\n```\nssh_option=\"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 -o ConnectTimeout=3 -o ConnectionAttempts=3\"\nexport WSSH=\"./tools/sshpass -p ${PASSWD} ssh ${ssh_option}\"\nexport WSCP=\"./tools/sshpass -p ${PASSWD} scp ${ssh_option}\"\n```\n> * StrictHostKeyChecking=no        自动信任主机并添加到known_hosts文件\n> * UserKnownHostsFile=/dev/null    跳过检查 ~/.ssh/known_hosts 中的公钥操作，比如因为远端机器重装导致无法登录问题\n> * NumberOfPasswordPrompts=0       规避没有信任关系挂死的问题，当对应的机器需要输入密码时，会直接返回异常（异常返回码为 255），而不是阻塞在输入密码页面\n> * ConnectTimeout=3                连接超时时间，3秒\n> * ConnectionAttempts=3            连接失败后重试次数，3次\n\n### 12.4 ping 文件列表中所有主机\n```\n#!/bin/bash\nfile=$1\n\n[[ -z $file  ]] && exit 0\ncat $file| while read line;do\n    ping=`ping -c 1 $line|grep loss|awk '{print $6}'|awk -F \"%\" '{print $1}'`\n    if [ $ping -eq 100  ];then\n        echo ping $i fail\n    else\n        echo ping $i ok\n    fi\ndone\n```\n### 12.5 shell 模板变量替换\n\n#### 应用场景\n\n双引号是 json 的标准，如果一个服务的配置文件是 json 的，使用特定的配置（模板）生成对应的配置文件时。要是使用 shell，这样也可以做到：\n\n#### 使用方式\n\n模板文件\n```\n{\n   \"test_ip\" : ${test_ip},\n   \"test_port\" : ${test_port},\n}\n```\n脚本\n```\ntest_host=\"127.0.0.1\"\ntest_port=9099\n\ncontent=$(cat ./config_tpl)\ncontent_new=$(eval \"cat <<EOF\n$content\nEOF\")\n\necho $content_new\n```\n\n## 13 日常使用库\n\n```\nf_yellow='\\e[00;33m'\nf_red='\\e[00;31m'\nf_green='\\e[00;32m'\nf_reset='\\e[00;0m'\n\nfunction p_warn {\n    echo -e \"${f_yellow}[WRN]${f_reset} ${1}\"\n}\n\nfunction p_err {\n    echo -e \"${f_red}[ERR]${f_reset} ${1}\"\n}\n\nfunction p_ok {\n    echo -e \"${f_green}[OK ]${f_reset} ${1}\"\n}\n\nROOT_PATH=`S=\\`readlink \"$0\"\\`; [ -z \"$S\"  ] && S=$0; dirname $S`\ncd ${ROOT_PATH}\n\n\nssh_option=\"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o NumberOfPasswordPrompts=0 -o ConnectTimeout=3 -o ConnectionAttempts=3\"\n```\n"
  },
  {
    "path": "doc/Linux/tools.md",
    "content": "# Linux 工具篇\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 编程相关](#1-编程相关)\n    * [1.1 vim IDE 工具](#11-vim-ide-工具)\n    * [1.2 Git 分布式管理系统](#12-git-分布式管理系统)\n        * [1.2.1 Git 基础](#121-git-基础)\n        * [1.2.2 知识点](#122-知识点)\n        * [1.2.3 其他操作](#123-其他操作)\n            * [**解决 GitHub commit 次数过多.git 文件过大**](#解决-github-commit-次数过多git-文件过大)\n            * [**HTTP request failed**](#http-request-failed)\n            * [设置 Wiki 只能自己编写，其他人员只读](#设置-wiki-只能自己编写其他人员只读)\n            * [修改最后一次 commit 操作](#修改最后一次-commit-操作)\n        * [1.2.4 Git tips](#124-git-tips)\n* [2 运维相关](#2-运维相关)\n    * [2.1 sed](#21-sed)\n    * [2.2 awk](#22-awk)\n        * [2.2.1 基础知识](#221-基础知识)\n        * [2.2.2 脚本](#222-脚本)\n        * [2.2.3 运算与编程](#223-运算与编程)\n        * [2.2.4 AWK 中输出外部变量](#224-awk-中输出外部变量)\n        * [2.2.5 AWK if](#225-awk-if)\n        * [2.2.6 AWK 打印第 N 列后面的所有列](#226-awk-打印第-n-列后面的所有列)\n    * [2.3 find](#23-find)\n        * [2.3.1 linux 文件查找指定时间段的文件](#231-linux-文件查找指定时间段的文件)\n    * [2.4 grep](#24-grep)\n        * [2.4.1 grep 时出现错误 Binary file (standard input) matches](#241-grep-时出现错误-binary-file-standard-input-matches)\n    * [2.5 sshpass 命令行使用密码来进行远程操作](#25-sshpass-命令行使用密码来进行远程操作)\n* [3 系统相关](#3-系统相关)\n    * [3.1 screen](#31-screen)\n        * [3.1.1 screen 使用](#311-screen-使用)\n        * [3.1.2 开启 screen 状态栏](#312-开启-screen-状态栏)\n    * [3.2 dmesg](#32-dmesg)\n    * [3.3 日志切割之 Logrotate](#33-日志切割之-logrotate)\n        * [3.3.1 通用服务日志清理工具](#331-通用服务日志清理工具)\n    * [3.4 lsof](#34-lsof)\n        * [3.4.1 如何使用 lsof?](#341-如何使用-lsof)\n    * [3.4.2 lsof -p](#342-lsof--p)\n* [4 网络相关](#4-网络相关)\n    * [4.1 curl](#41-curl)\n        * [4.1.1 HTTP 请求](#411-http-请求)\n        * [4.1.2 curl 基础](#412-curl-基础)\n        * [4.1.3 curl 深入](#413-curl-深入)\n    * [4.2 tcpdump](#42-tcpdump)\n        * [4.2.1 tcp 三次握手和四次挥手](#421-tcp-三次握手和四次挥手)\n        * [4.2.2 tcpdump 使用](#422-tcpdump-使用)\n    * [4.3 nc](#43-nc)\n        * [4.3.1 语法](#431-语法)\n        * [4.3.2 TCP 端口扫描](#432-tcp-端口扫描)\n        * [4.3.3 扫描 UDP 端口](#433-扫描-udp-端口)\n* [5 日志相关操作](#5-日志相关操作)\n    * [5.1 截取某段时间内的日志](#51-截取某段时间内的日志)\n    * [5.2 处理日志文件中上下关联的两行](#52-处理日志文件中上下关联的两行)\n* [6 应用服务相关](#6-应用服务相关)\n    * [6.1 排查 java CPU 性能问题](#61-排查-java-cpu-性能问题)\n        * [6.1.1 用法](#611-用法)\n        * [6.1.2 示例](#612-示例)\n* [7 日常工具](#7-日常工具)\n    * [7.1 mget](#71-mget)\n\n<!-- vim-markdown-toc -->\n\n# 1 编程相关\n## 1.1 vim IDE 工具\n\n* [VIM 一键 IDE](https://github.com/meetbill/Vim)\n\n## 1.2 Git 分布式管理系统\n\n### 1.2.1 Git 基础\n\n**环境配置**\n\n+ `git config --global user.name your_name` : 设置你的用户名，提交会显示\n+ `git config --global user.email your_email` : 设置你的邮箱\n+ `git config core.quotepath false` : 解决中文文件名显示为数字问题\n\n**基本操作**\n\n+ `git init` : 初始化一个 git 仓库\n+ `git add <filename>` : 添加一个文件到 git 仓库中\n+ `git commit -m \"commit message\"`: 提交到本地\n+ `git push [remote-name] [branch-name]` : 把本地的提交记录推送到远端分支\n+ `git pull`: 更新仓库 `git pull` = `git fetch` + `git merge`\n+ `git checkout -- <file>` : 还原未暂存 (staged) 的文件\n+ `git reset HEAD <file>...` : 取消暂存，那么还原一个暂存文件，应该是先 `reset` 后 `checkout`\n+ `git stash` : 隐藏本地提交记录，恢复的时候 `git stash pop`。这样可以在本地和远程有冲突的情况下，更新其他文件\n\n**分支**\n\n+ `git branch <branch-name>` : 基于当前 commit 新建一个分支，但是不切换到新分支\n+ `git branch -r` : 查看远程的所有分支（常用）\n+ `git checkout -b <branch-name>` : 新建并切换分支\n+ `git checkout <branch-name>` : 切换分支（常用）\n+ `git branch -d <branch-name>` : 删除分支\n+ `git push origin <branch-name>` : 推送本地分支\n+ `git checkout -b <local-branch-name> origin/<origin-branch-name>` : 基于某个远程分支新建一个分支开发\n+ `git checkout --track origin/<origin-branch-name>` : 跟踪远程分支（创建跟踪远程分支，Git 在 `git push` 的时候不需要指定 `origin` 和 `branch-name` ，其实当我们 `clone` 一个 repo 到本地的时候，`master` 分支就是 origin/master 的跟踪分支，所以提交的时候直接 `git push`)。\n+ `git push origin :<origin-branch-name>` : 删除远程分支\n\n实践 --- 主分支 Master/ 开发分支 Develop\n```\n主分支只用来分布重大版本，日常开发应该在另一条分支上完成。我们把开发用的分支，叫做 Develop。\n\n# Git 创建 Develop 分支\ngit checkout -b develop master\n\n# 将 Develop 分支发布到 Master 分支\n\n# 切换到 Master 分支\ngit checkout master\n\n# 对 Develop 分支进行合并\ngit merge --no-ff develop\n上一条命令的 --no-ff 参数是什么意思。默认情况下，Git 执行\"快进式合并\"（fast-farward merge），会直接将 Master 分支指向 Develop 分支。\n\n# 删除本地分支\ngit branch -d develop\n```\n\n**标签**\n\n+ `git tag -a <tagname> -m <message>` : 创建一个标签（用 -a 指定标签名，-m 指定说明文字） 如 `git tag -a v1.0 -m \"version 1.0 released mitaka\"`\n+ `git tag` : 显示已有的标签\n+ `git show tagname`: 显示某个标签的详细信息\n+ `git push origin v1.0` push 到远端仓库 如`git push -u ${PWD##*/} master v1.0`\n+ `git checkout -b <tag-name>` : 基于某个 tag 创建一个新的分支\n\n**Git shortcuts/aliases**\n\n    git config --global alias.co checkout\n    git config --global alias.br branch\n    git config --global alias.ci commit\n    git config --global alias.st status\n\n### 1.2.2 知识点\n\n基本命令让你快速的上手使用 Git，知识点能让你更好的理解 Git。\n\n**文件的几种状态**\n\n+ untracked: 未被跟踪的，没有纳入 Git 版本控制，使用 `git add <filename>` 纳入版本控制\n+ unmodified: 未修改的，已经纳入版本控制，但是没有修改过的文件\n+ modified: 对纳入版本控制的文件做了修改，git 将标记为 modified\n+ staged: 暂存的文件，简单理解：暂存文件就是 add 之后，commit 之前的文件状态\n\n理解这几种文件状态对于理解 Git 是非常关键的（至少可以看懂一些错误提示了）。\n\n**快照和差异**\n\n详细可看：[Pro Git: Git 基础](http://iissnan.com/progit/html/zh/ch1_3.html) 中有讲到 *直接记录快照，而非差异比较*，这里只讲我个人的理解。\n\nGit 关心的是文件数据整体的变化，其他版本管理系统（以 svn 为例）关心的某个具体文件的*差异*。这个差异是好理解的，也就是两个版本具体文件的不同点，比如某一行的某个字符发生了改变。\n\nGit 不保存文件提交前后的差异，不变的文件不会发生任何改变，对于变化的文件，前后两次提交则保存两个文件。举个例子：\n\nSVN:\n\n1. 新建 3 个文件 a, b, c，做第一次提交 ->  `version1 : file_a file_b file_c`\n2. 修改文件 b， 做第二次提交（真正提交的是 修改后的文件 b 和修改前的 `file_b` 的 diff) -> `version2: diff_b_2_1`\n3. 当我要 checkout version2 的时候，实际上得到的是 `file_a file_b+diff_b_2_1 file_c`\n\nGit:\n\n1. 新建 3 个文件 a, b, c，做第一次提交 ->  `version1 : file_a file_b file_c`\n2. 修改文件 b （得到`file_b1`), 做第二次提交 -> `version2: file_a file_b1 file_c`\n3. 当我要用 version2 的时候，实际上得到的是 `file_a file_b1 file_c`\n\n上面的 `file_a file_b1 file_c` 就是 version2 的 *快照*。\n\n**Git 数据结构**\n\nGit 的核心数是很简单的，就是一个链表（或者一棵树更准确一些？无所谓了），一旦你理解了它的基本数据结构，再去看 Git，相信你有不同的感受。继续用上面的例子（所有的物理文件都对应一个 SHA-1 的值）\n\n当我们做第一次提交时，数据结构是这样的：\n\n\n    sha1_2_file_map:\n        28415f07ca9281d0ed86cdc766629fb4ea35ea38 => file_a\n        ed5cfa40b80da97b56698466d03ab126c5eec5a9 => file_b\n        1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39 => file_c\n\n    commit_26b985d269d3a617af4064489199c3e0d4791bb5:\n        base_info:\n            Auther: \"JerryZhang(chinajiezhang@gmail.com)\"\n            Date: \"Tue Jul 15 19:19:22 2014 +0800\"\n            commit_content: \"第一次提交\"\n        file_list:\n            [1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38\n            [2]: ed5cfa40b80da97b56698466d03ab126c5eec5a9\n            [3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39\n            pre_commit: null\n        next_commit: null\n\n当修改了 `file_b`, 再提交一次时，数据结构应该是这样的：\n\n    sha1_2_file_map:\n        28415f07ca9281d0ed86cdc766629fb4ea35ea38 => file_a\n        ed5cfa40b80da97b56698466d03ab126c5eec5a9 => file_b\n        1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39 => file_c\n        39015ba6f80eb9e7fdad3602ef2b1af0521eba89 => file_b1\n\n    commit_26b985d269d3a617af4064489199c3e0d4791bb5:\n        base_info:\n            Auther: \"JerryZhang(chinajiezhang@gmail.com)\"\n            Date: \"Tue Jul 15 19:19:22 2014 +0800\"\n            commit_content: \"第一次提交\"\n        file_list:\n            [1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38\n            [2]: ed5cfa40b80da97b56698466d03ab126c5eec5a9\n            [3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39\n        pre_commit: commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff\n        next_commit: null\n\n    commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff:\n        base_info:\n            Auther: \"JerryZhang(chinajiezhang@gmail.com)\"\n            Date: \"Tue Jul 15 22:19:22 2014 +0800\"\n            commit_content: \"更新文件 b\"\n        file_list:\n            [1]: 28415f07ca9281d0ed86cdc766629fb4ea35ea38\n            [2]: 39015ba6f80eb9e7fdad3602ef2b1af0521eba89\n            [3]: 1b5ca12a6cf11a9b89dbeee2e5431a1a98ea5e39\n        pre_commit: null\n        next_commit: commit_26b985d269d3a617af4064489199c3e0d4791bb5\n\n当提交完第二次的时候，执行 `git log`，实际上就是从 `commit_a08a57561b5c30b9c0bf33829349e14fad1f5cff` 开始遍历然后打印 `base_info` 而已。\n\n实际的 git 实际肯定要比上面的结构 (（的信息）的）要复杂的多，但是它的核心思想应该是就是，每一次提交就是一个新的结点。通过这个结点，我可以找到所有的快照文件。再思考一下，什么是分支？什么是 Tags，其实他们可能只是某次提交的引用而已（一个 `tag_head_node` 指向了某一次提交的 node)。再思考怎么回退一个版本呢？指针偏移！依次类推，上面的基本命令都可以得到一个合理的解释。\n\n**理解 git fetch 和 git pull 的差异**\n\n上面我们说过 `git pull` 等价于 `git fetch` 和 `git merge` 两条命令。当我们 `clone` 一个 repo 到本地时，就有了本地分支和远端分支的概念（假定我们只有一个主分支），本地分支是 `master`，远端分支是 `origin/master`。通过上面我们对 Git 数据结构的理解，`master` 和 `origin/master` 可以想成是指向最新 commit 结点的两个指针。刚 `clone` 下来的 repo，`master` 和 `origin/master` 指针指向同一个结点，我们在本地提交一次，`origin` 结点就更新一次，此时 `master` 和 `orgin/master` 就不再相同了。很有可能别人已经 commit 改 repo 很多次了，并且进行了提交。那么我们的本地的 `origin/master` 就不再是远程服务器上的最新的位置了。 `git fetch` 干的就是从服务器上同步服务器上最新的 `origin/master` 和一些服务器上新的记录 / 文件到本地。而 `git merge` 就是合并操作了（解决文件冲突）。`git push` 是把本地的 `origin/master` 和 `master` 指向相同的位置，并且推送到远程的服务器。\n\n### 1.2.3 其他操作\n\n#### **解决 GitHub commit 次数过多.git 文件过大**\n\n完全重建版本库\n\n```\n# rm -rf .git\n# git init\n# git add .\n# git commit -a -m \"[Update] 合并之前所有 commit\"\n# git remote add origin <your_github_repo_url>\n# git push -f -u origin master\n```\n#### **HTTP request failed**\n\n使用 git clone 失败\n\n```\n[root@localhost ~]# git clone https://github.com/meetbill/Vim.git\nInitialized empty Git repository in /root/Vim/.git/\nerror:  while accessing https://github.com/meetbill/Vim.git/info/refs\n\nfatal: HTTP request failed\n```\n解决方法\n```\n#git config --global http.sslVerify false\n\n```\n#### 设置 Wiki 只能自己编写，其他人员只读\n\n在项目中的设置中勾选`Restrict editing to collaborators only`\n\n#### 修改最后一次 commit 操作\n\n有时候我们提交完了才发现漏掉了几个文件没有加，或者提交信息写错了。想要撤消刚才的提交操作，可以使用 --amend 选项重新提交：\n```\n$ git commit -a -m 'initial commit'\n$ git add forgotten_file\n$ git commit --amend -m 'new commit'\n```\n\n### 1.2.4 Git tips\n\n> 命令简化\n```\ncd git_repo（替换为项目名字）\ngit remote add ${PWD##*/} git@github.com:meetbill/${PWD##*/}.git\ngit push -u ${PWD##*/} master\n```\n> 提升 git 使用体验\n\n> * [git 命令自动补全](https://github.com/meetbill/op_practice_code/tree/master/Linux/tools/git/git-completion)\n> * [命令行显示 git 信息](https://github.com/meetbill/op_practice_code/tree/master/Linux/tools/git/git-ps1)\n\n> 忽略特殊文件\n```\n.gitignore 写得有问题，需要找出来到底哪个规则写错了，可以用 git check-ignore 命令检查：\n\n$ git check-ignore -v App.class\n.gitignore:3:*.class\tApp.class\n\n.gitignore 的第 3 行规则忽略了该文件，于是我们就可以知道应该修订哪个规则。\n```\n\n\n# 2 运维相关\n## 2.1 sed\n\n## 2.2 awk\nAWK 是贝尔实验室 1977 年搞出来的文本处理工具。\n\n之所以叫 AWK 是因为其取了三位创始人 Alfred Aho，Peter Weinberger, 和 Brian Kernighan 的 Family Name 的首字符\n\n### 2.2.1 基础知识\n**分隔符**\n\n默认情况下， awk 使用空格当作分隔符。分割后的字符串可以使用 $1, $2 等访问。\n\n上面提到过，我们可以使用 -F 来指定分隔符。\nfs 如果是一个字符，可以直接跟在 -F 后面，比如使用冒号当作分隔符就是 -F: .\n如果分隔符比较复杂，就需要使用正则表达式来表示这个分隔符了。\n正则表达式需要使用引号引起来。\n比如使用‘ab’  当作分隔符，就是 -F 'ab' 了。\n使用 a 或 b 作为分隔符，就是 -F '\\[ab]' 了。\n关于正则表达式这里不多说了。\n\n**内建变量**\n\n```text\n$0\t当前记录（这个变量中存放着整个行的内容）\n$1~$n\t当前记录的第 n 个字段，字段间由 FS 分隔\nFS\t输入字段分隔符 默认是空格或 Tab\nNF\t当前记录中的字段个数，就是有多少列\nNR\t已经读出的记录数，就是行号，从 1 开始，如果有多个文件话，这个值也是不断累加中。\nFNR\t当前记录数，与 NR 不同的是，这个值会是各个文件自己的行号\nRS\t输入的记录分隔符， 默认为换行符\nOFS\t输出字段分隔符， 默认也是空格\nORS\t输出的记录分隔符，默认为换行符\nFILENAME\t当前输入文件的名字\n```\n\n**转义**\n\n一般字符在双引号之内就可以直接原样输出了。\n但是有部分转义字符，需要使用反斜杠转义才能正常输出。\n\n```\n\\\\   A literal backslash.\n\\a   The “alert” character; usually the ASCII BEL character.\n\\b   backspace.\n\\f   form-feed.\n\\n   newline.\n\\r   carriage return.\n\\t   horizontal tab.\n\\v   vertical tab.\n\\xhex digits\n\\c   The literal character c.\n```\n**模式**\n\n```\n~ 表示模式开始，与 == 相比不是精确比较\n/ / 中是模式\n! 模式取反\n```\n\n**单引号**\n\n当需要输出单引号时，直接转义发现会报错。\n由于 awk 脚本并不是直接执行，而是会先进行预处理，所以需要两次转义。\nawk 支持递归引号。单引号内可以输出转义的单引号，双引号内可以输出转义的双引号。\n\n比如需要输出单引号，则需要下面这样：\n\n```\n> awk 'BEGIN{print \"\\\"\"}'\n\"\n>  awk 'BEGIN{print \"'\\''\"}'\n'\n```\n\n当然，更简单的方式是使用十六进制来输出。\n\n```\nawk 'BEGIN{print \"\\x27\"}'\n```\n\n### 2.2.2 脚本\n\n```\nBEGIN{ 这里面放的是执行前的语句 }\nEND {这里面放的是处理完所有的行后要执行的语句 }\n{这里面放的是处理每一行时要执行的语句}\n```\n\n### 2.2.3 运算与编程\n\nawk 是弱类型语言，变量可以是串，也可以是数字，这依赖于实际情况。所有的数字都是浮点型。\n\n例如\n\n```\n//9\necho 5 4 | awk '{ print $1 + $2 }'\n\n//54\necho 5 4 | awk '{ print $1 $2 }'\n\n//\"5 4\"\necho 5 4 | awk '{ print $1, $2 }'\n\n0-1-2-3-4-5-6\necho 6 | awk '{ for (i=0; i<=$0; i++){ printf (i==0?i:\"-\"i); }printf \"\\n\";}'\n```\n\n**Example**\n\n假设我们有一个日期 2014/03/27, 我们想处理为 2014-03-27.\n我们可以使用下面的代码实现。\n\n```bash\necho \"2014/03/27\" | awk -F/  '{print $1\"-\"$2\"-\"$3}'\n```\n\n假设 处理的日期都在 date 文件里。\n我们可以导入文件来操作\n\n文件名 date\n\n```\n2014/03/27\n2014/03/28\n2014/03/29\n```\n\n命令\n```\nawk -F/ '{printf \"%s-%s-%s\\n\",$1,$2,$3}'  date\n```\n\n输出\n```text\n2014-03-27\n2014-03-28\n2014-03-29\n```\n\n**统计**\n\n```bash\nawk '{sum+=$5} END {print sum}'\n```\n\n### 2.2.4 AWK 中输出外部变量\n通过双引号内加个单引号将外部变量进行输出\n```\nwang=\"hello\"\necho meetbill | awk '{print \"'$wang' \" $1}'\n```\n### 2.2.5 AWK if\n\n必须用在{}中，且比较内容用 () 扩起来\n```\nawk -F: '{if($1~/mail/) print $1}'    /etc/passwd                           // 简写\nawk -F: '{if($1~/mail/) {print $1}}'  /etc/passwd                           // 全写\nawk -F: '{if($1~/mail/) {print $1} else {print $2}}' /etc/passwd            //if...else...\n```\n\n> * 条件表达式\n>   * `==   !=   >   >=`\n> * 逻辑运算符\n>   * `&& ||`\n如：查看使用了 CPU 0 核的进程\n```\n# ps -eF，其中 PSR 就是 (processor that process is currently assigned to.) 或者 ps -eo pid,command,args,psr\nps -eF |awk '{if($7==0) print $0}'\n```\n\n### 2.2.6 AWK 打印第 N 列后面的所有列\n```\nawk '{for(i=N+1;i<=NF;i++)printf $i \"  \";printf\"\\n\"}' file\n```\n## 2.3 find\n\n### 2.3.1 linux 文件查找指定时间段的文件\n\n```\ntouch -t 201710241800 t1\ntouch -t 201710252100 t2\n\n\n查找排序（先旧后新），结果写到文件\n\nfind ./ -type f -name \"*.aof\" -newer ./t1 ! -newer ./t2  |xargs ls -lrt  > /sdcard/amr/sort.txt\n```\n\n## 2.4 grep\n\n### 2.4.1 grep 时出现错误 Binary file (standard input) matches\n\n在使用 grep 命令时出现错误 Binary file (standard input) matches\n\n解决方法  加上 -a\n\n例如原本为 grep hello\n\n改为 grep -a hello\n\n## 2.5 sshpass 命令行使用密码来进行远程操作\n\nhttps://github.com/kevinburke/sshpass\n\n```\n例子：本地执行远程机器的命令：\n命令： sshpass -p xxx ssh root@192.168.11.11 \"ethtool eth0\"\n\n例子：从远程主机上拉取文件到本地\n命令： sshpass -p xxx scp root@host_ip:/home/test/t ./tmp/\n```\n\n# 3 系统相关\n## 3.1 screen\n\n现在很多时候我们的开发环境都已经部署到云端了，直接通过 SSH 来登录到云端服务器进行开发测试以及运行各种命令，一旦网络中断，通过 SSH 运行的命令也会退出，这个发让人发疯的。\n\n好在有 screen 命令，它可以解决这些问题。我使用 screen 命令已经有三年多的时间了，感觉还不错。\n\n### 3.1.1 screen 使用\n\n**新建一个 Screen Session**\n\n```\n$ screen -S screen_session_name\n```\n\n**将当前 Screen Session 放到后台**\n\n```\n$ CTRL + A + D\n```\n\n**唤起一个 Screen Session**\n\n```\n$ screen -r screen_session_name\n```\n\n**分享一个 Screen Session**\n\n```\n$ screen -x screen_session_name\n```\n\n通常你想和别人分享你在终端里的操作时可以用此命令。\n\n**终止一个 Screen Session**\n\n```\n$ exit\n$ CTRL + D\n```\n\n**查看一个 screen 里的输出**\n\n当你进入一个 screen 时你只能看到一屏内容，如果想看之前的内容可以如下：\n\n```\n$ Ctrl + a ESC\n```\n\n以上意思是进入 Copy mode，拷贝模式，然后你就可以像操作 VIM 一样查看 screen session 里的内容了。\n\n可以 Page Up 也可以 Page Down。\n\n### 3.1.2 开启 screen 状态栏\n\n```\n#curl -o screen.sh https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/tools/screen.sh\n#sh screen.sh\n```\n## 3.2 dmesg\n\n内核缓冲信息，在系统启动时，显示屏幕上的与硬件有关的信息\n\n> dmesg | grep -E 'error|fail'\n```\ndmesg - print or control the kernel ring buffer\ndmesg is used to examine or control the kernel ring buffer.\n\nThe default action is to read all messages from kernel ring buffer.\n```\n\n> eth1: Too much work at interrupt, IntrStatus=0x0001\n```\n一般类的提示\n\n某网卡的中断请求过多。如果只是偶尔出现一次可忽略\n但这条提示如果经常出现或是集中出现，那涉及到的可能性就比较多有可能需要进行处理了。可能性比较多，如网卡性能；服务器性能；网络攻击.. 等等。\n```\n\n> IPVS: incoming ICMP: failed checksum from 61.172.0.X!\n```\n一般类的提示\n\n服务器收到了一个校验和错误的 ICMP 数据包。这类的数据包有可能是非法产生的垃圾数据。但从目前来看服务器收到这样的数据非常多。一般都忽略。\n一般代理服务器在工作时会每秒钟转发几千个数据包。收到几个错误数据包不会影响正常的工作。\n```\n\n> NET: N messages suppressed.\n```\n一般类的提示\n\n服务器忽略了 N 个数据包。和上一条提示类似。服务器收到的数据包被认为是无用的垃圾数据数据。这类数据多是由攻击类的程序产生的。\n这条提示如果 N 比较小的时候可以忽略。但如果经常或是长时间出现 3 位数据以上的这类提示。就很有可能是服务器受到了垃圾数据类的带宽攻击了。\n与这条信息类似的还有。\n__ratelimit: N messages suppressed\n__ratelimit: N callbacks suppressed\n```\n\n> UDP: bad checksum. From 221.200.X.X:50279 to 218.62.X.X:1155 ulen 24\n> UDP: short packet: 218.2.X.X:3072 3640/217 to 222.168.X.X:57596\n> 218.26.131.X sent an invalid ICMP type 3, code 13 error to a broadcast: 0.1.0.4 on eth0\n```\n一般类的提示\n\n服务器收到了一个错误的数据包。分别为 UDP 校验和错误；过短的 UDP 数据包；一个错误的 ICMP 类型数据。这类信息一般情况下也是非法产生的。\n但一般问题不大可直接忽略。\n```\n\n> kernel: conntrack_ftp: partial 227 2205426703+13\n> FTP_NAT: partial packet 2635716056/20 in 2635716048/2635716075\n```\n一般类的提示\n\n服务器在维持一条 FTP 协议的连接时出错。这样的提示一般都可以直接忽略。\n```\n\n> NETDEV WATCHDOG: eth1: transmit timed out\n```\neth1: link down\neth1: link up, 10Mbps, half-duplex, lpa 0x0000\neth2: link up, 100Mbps, full-duplex, lpa 0x41E1\nsetting full-duplex based on MII #24 link partner capability of 45e1\n\n网络通信严重问题！\n\n这些提示是网络通信中出现严重问题时才会出现。故障基本和网络断线有关系。这几条提示分别代表的含意是 某块网卡传送数据超时；网卡连接 down; 网卡连接 up, 连接速率为 10/100Mbps, 全 / 半双功。\n这里写到的最后三行的提示比较类似。出现这类提示时必须注意网络连接状况进行处理！!!\n```\n\n> NIC Link is Up 100 Mbps Full Duplex\n```\n网络通信严重问题！\n\n情况和 kernel: eth1: link up,... 相同。指某块网卡适应的连接速率。一般认为没有说明哪个网卡 down, 只是连续出现网卡适应速率也是通信有问题。\n如果是网线正常的断接可以忽略这类的信息。\n```\n\n> eth0: Transmit timed out, status 0000, PHY status 786d, resetting...\n> eth0: Reset not complete yet. Trying harder.\n```\n网络通信严重问题！\n\n第一条提示 网卡关送数据失败。复位网卡。第二条提示 网卡复位不成功.... 这些提示都属于严重的通信问题。\n\n报警程序的提示\n0001 ##WMPCheckV001## 2005-04-13_10:10:01 Found .(ARP Spoofing sniffer)! IP:183 MAC:5\n0002 ##WMPCheckV001## 2005-04-07_01:53:32 Found .(MAC_incomplete)! IP:173 mac_incomplete:186\n0003 ##WMPCheckV001## 2005-04-17_16:25:11 Found .(HIGH_synsent)! totl:4271 SynSent:3490\n0004 ##WMPCheckV001## 20......\n这是由报警程序所引起的提示。详细的信息需要用报警程序的客户端进行实时接收。详细情况请查看\"告警模块和日志\".\n```\n\n> eth1: Promiscuous mode enabled.\n> device eth1 entered promiscuous mode\n> device eth1 left promiscuous mode\n```\n一般类的提示\n\n这几行提示指。某块网卡进入（离开）了混杂模式。一般来说混杂模式是当需要对通信进行抓包时才用到的。当使用维护或故障分析时会使用到（比如 consoletools 中的 countflow 命令）. 正常产生的这类提示可以忽略。\n如果在前台和远端都没有进行维护时出现这个提示倒是应该引起注意，但这种可能性不大。\n```\n\n> keyboard: unknown scancode e0 5e\n```\n基本无关\n\n键盘上接收到未定义的键值。如果经常出现。有可能是键盘有问题。linux 对于比较特殊的键或是组合键，有时也会出这样的提示。\n要看一下服务器的键盘是不是被压住了。其它情况一般忽略。\n```\n> uses obsolete (PF_INET,SOCK_PACKET)\n```\n基本无关\n\n系统内核调用了一部分功能模块，在第一次调入时会出现。一般情况与使用调试工具有关。可直接忽略。\n```\n\n> Neighbour table overflow.\n```\n网络通信故障\n\n出现这个提示。一般都是因为局域网内有部分计算机被病毒感染。情况严重时会影响通信。必须处理内部网通信不正常的计算机。\n```\n\n> eth1: Transmit error, Tx status register 82.\n```\nProbably a duplex mismatch. See Documentation/networking/vortex.txt\nFlags; bus-master 1, dirty 9994190(14) current 9994190(14)\nTransmit list 00000000 vs. f7171580.\n0: @f7171200 length 800001e6 status 000101e6\n1: @f7171240 length 8000008c status 0001008c\n\n这个提示是 3com 网卡特有的。感觉如果出现量不大的话也不会影响很严重。目前看维一的解决办法是更换服务器上的网卡。实在感觉 3com 的网卡有些问题...\n```\n\n> 服务器 CPU 工作温度过高\n```\nCPU0: Temperature above threshold\nCPU0: Running in modulated clock mode\n\n服务器系统严重故障\n\n服务器 CPU 工作温度过高。必须排除硬件故障。\n```\n\n> 磁盘故障\n```\nI/O error, dev hda, sector N\nI/O error, dev sda, sector N\nhda: dma_intr: status=0x51 { DriveReady SeekComplete Error }\nhda: dma_intr: error=0x40 { UncorrectableError }, LBAsect=811562, sector=811560\n\n服务器系统严重故障\n\n服务器系统磁盘存储卡操作失败。这样的问题一般不会使服务器直接停止工作，但会引起很多严重问题\n```\n\n## 3.3 日志切割之 Logrotate\n\nCentos 中 rsyslog 负责写入日志，logrotate 负责备份和删除旧日志\n\n> 定时任务\n```\n定时任务：/etc/cron.daily/logrotate\n定时任务执行的程序：/usr/sbin/logrotate /etc/logrotate.conf\n```\n> 配置\n```\n/etc/logrotate.conf  # 主配置文件\n/etc/logrotate.d     # 配置目录\n```\n\n备注：\n```\n当发现系统日志等没有轮转时，可以手动执行 \"/usr/sbin/logrotate /etc/logrotate.conf\" 查看下是否运行正常\n\n当配置文件中有异常的配置时，logrotate 无法正常工作（一个异常配置会影响所有使用 logrotate 进行管理日志的服务）\n```\n> 常见 logrotate 异常\n```\nerror: /etc/logrotate.conf:xx duplicate log entry for /var/log/xxx\n\n/etc/logrotate.conf:xx 行有重复配置项，将重复项清理下即可\n```\n### 3.3.1 通用服务日志清理工具\n\n如果是业务日志，也可以通过如下工具进行清理\n\n如下工具会启动单独进程进行管理日志\n\n[shell_logrotate](https://github.com/meetbill/linux_tools/tree/master/17_logrotate)\n\n## 3.4 lsof\n\nLsof 是遵从 Unix 哲学的典范，它只做一件事情，并且做的相当完美——它可以列出某个进程打开的所有文件信息。打开的文件可能是普通的文件，目录，NFS 文件，块文件，字符文件，共享库，常规管道，明明管道，符号链接，Socket 流，网络 Socket，UNIX 域 Socket，以及其它更多。因为 Unix 系统中几乎所有东西都是文件，你可以想象 lsof 该有多有用。\n\n### 3.4.1 如何使用 lsof?\n> 列出所有打开的文件  不带任何参数运行 lsof 会列出所有进程打开的所有文件。\n```\nlsof\n```\n\n> 找出谁在使用某个文件\n```\nlsof /path/to/file\n```\n\n> 列出某个用户打开的所有文件\n```\nlsof -u meetbill\n```\n\n> 查找某个程序打开的所有文件  -c 选项限定只列出以 apache 开头的进程打开的文件：\n```\nlsof -c apache\n```\n\n> 列出所有由某个 PID 对应的进程打开的文件  -p 选项让你可以使用进程 id 来过滤输出。\n```\nlsof -p 1\n```\n\n> 列出所有网络连接  lsof 的 -i 选项可以列出所有打开了网络套接字（TCP 和 UDP）的进程。\n```\nlsof -i\n```\n\n> 列出所有 TCP 网络连接  也可以为 -i 选项加上参数，比如 tcp，tcp 选项会强制 lsof 只列出打开 TCP sockets 的进程。\n```\nlsof -i tcp\n```\n\n> 找到使用某个端口的进程\n```\nlsof -i :25\n```\n\n> 找到某个用户的所有网络连接  使用 -a 将 -u 和 -i 选项组合可以让 lsof 列出某个用户的所有网络行为。\n```\nlsof -a -u hacker -i\n```\n\n> 列出所有 NFS（网络文件系统）文件  这个参数很好记，-N 就对应 NFS。\n```\nlsof -N\n```\n\n> 列出所有 UNIX 域 Socket 文件  这个选项也很好记，-U 就对应 UNIX。\n```\nlsof -U\n```\n\n> 列出所有对应某个组 id 的进程\n```\nlsof -g 1234\n```\n\n> 列出所有与某个描述符关联的文件\n```\nlsof -d 2\n```\n## 3.4.2 lsof -p\n\n> lsof -p 24406\n```\nCOMMAND     PID      USER   FD   TYPE     DEVICE     SIZE     NODE NAME\npython2.7 24406 wangbin34  cwd    DIR        8,8     4096 53002355 /home/users/meetbill/dev/butterfly\npython2.7 24406 wangbin34  rtd    DIR        8,2     4096        2 /\npython2.7 24406 wangbin34  txt    REG        8,8    10265 52711469 /bin/python2.7\npython2.7 24406 wangbin34  mem    REG        0,0                 0 [heap] (stat: No such file or directory)\npython2.7 24406 wangbin34  mem    REG        8,2    17624   246236 /lib64/libuuid.so.1.2\npython2.7 24406 wangbin34  mem    REG        8,2   105080   246045 /lib64/ld-2.3.4.so\npython2.7 24406 wangbin34  mem    REG        8,2  1493186   246040 /lib64/tls/libc-2.3.4.so\npython2.7 24406 wangbin34  mem    REG        8,2    17943   246057 /lib64/libdl-2.3.4.so\npython2.7 24406 wangbin34  mem    REG        8,2   613297   246035 /lib64/tls/libm-2.3.4.so\npython2.7 24406 wangbin34  mem    REG        8,2   106203   246042 /lib64/tls/libpthread-2.3.4.so\npython2.7 24406 wangbin34  mem    REG        8,2    17367   246060 /lib64/libutil-2.3.4.so\n...\npython2.7 24406 wangbin34    0r   CHR        1,3              4869 /dev/null\npython2.7 24406 wangbin34    1w   REG        8,8      319 52982340 /home/users/meetbill/dev/butterfly/__stdout\npython2.7 24406 wangbin34    2w   REG        8,8      319 52982340 /home/users/meetbill/dev/butterfly/__stdout\npython2.7 24406 wangbin34    3u  IPv4 3659770919               TCP :8021 (LISTEN)\npython2.7 24406 wangbin34    4w   REG        8,8    48845 53739748 /home/users/meetbill/dev/butterfly/logs/info.log\npython2.7 24406 wangbin34    5w   REG        8,8   205892 53739816 /home/users/meetbill/dev/butterfly/logs/common.log\npython2.7 24406 wangbin34    6w   REG        8,8      240 53739811 /home/users/meetbill/dev/butterfly/logs/common.log.wf\npython2.7 24406 wangbin34    7r   CHR        1,9              4439 /dev/urandom\npython2.7 24406 wangbin34    9w   REG        8,8    10005 53739790 /home/users/meetbill/dev/butterfly/logs/acc.log\n```\n\n> * COMMAND：进程的名称 \n> * PID：进程标识符\n> * USER：进程所有者\n> * FD：文件描述符，应用程序通过文件描述符识别该文件。如 cwd、txt 等\n>   * cwd 值表示应用程序的当前工作目录\n> * TYPE：文件类型，如 DIR、REG 等\n>   * 文件和目录分别称为 REG 和 DIR\n>   * CHR 表示字符；(fopen，打开文件）\n>   * BLK 表示块设备；\n>   * UNIX、FIFO 和 IPv4，分别表示 UNIX 域套接字、先进先出 (FIFO) 队列和网际协议 (IP) 套接字。\n> * DEVICE：指定磁盘的名称\n> * SIZE：文件的大小\n> * NODE：索引节点（文件在磁盘上的标识）\n> * NAME：打开文件的确切名称\n\n# 4 网络相关\n## 4.1 curl\n### 4.1.1 HTTP 请求\n\nGET 和 POST 是 HTTP 请求的两种基本方法，最直观的区别就是 GET 把参数包含在 URL 中，POST 通过 request body 传递参数。\n\n```\n    在万维网世界，TCP 就像汽车，我们用 TCP 来运输数据，它很可靠，\n从来不会发生丢件少件的现象。但是如果路上跑的全是看起来一模一样\n的汽车，那这个世界看起来是一团混乱，送急件的汽车可能被前面满载\n货物的汽车拦堵在路上，整个交通系统一定会瘫痪。为了避免这种情况\n发生，交通规则 HTTP 诞生了。HTTP 给汽车运输设定了好几个服务类别，\n有 GET, POST, PUT, DELETE 等等，HTTP 规定，当执行 GET 请求的时候，\n要给汽车贴上 GET 的标签（设置 method 为 GET)，而且要求把传送的数\n据放在车顶上 (url) 以方便记录。如果是 POST 请求，就要在车上贴上\nPOST 的标签，并把货物放在车厢里。\n\n    当然，你也可以在 GET 的时候往车厢内偷偷藏点货物，但是这是很不\n光彩；也可以在 POST 的时候在车顶上也放一些数据。\n```\n\n### 4.1.2 curl 基础\n在介绍前，我需要先做两点说明：\n\n1. 下面的例子中会使用 [httpbin.org](http://httpbin.org/) ，httpbin 提供客户端测试 http 请求的服务，非常好用，具体可以查看他的网站。\n2. 大部分没有使用缩写形式的参数，例如我使用 `--request` 而不是 `-X` ，这是为了好记忆。\n\n下面开始简单介绍几个命令：\n\n**get**\n\n> * curl protocol://address:port/url?args\n\n直接以个 GET 方式请求一个 url，输出返回内容：\n\n``` sh\ncurl httpbin.org\n```\n\n返回\n``` html\n<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv='content-type' value='text/html;charset=utf8'>\n  <meta name='generator' value='Ronn/v0.7.3 (http://github.com/rtomayko/ronn/tree/0.7.3)'>\n  <title>httpbin(1): HTTP Client Testing Service</title>\n  <style type='text/css' media='all'>\n  /* style: man */\n  body#manpage {margin:0}\n  .mp {max-width:100ex;padding:0 9ex 1ex 4ex}\n  .mp p,.mp pre,.mp ul,.mp ol,.mp dl {margin:0 0 20px 0}\n  .mp h2 {margin:10px 0 0 0}\n......\n```\n\n**post**\n\n> * curl --data \"args\" \"protocol://address:port/url\"\n>   * -d/--data <data>   HTTP POST 方式传送数据\n>　 * --data-ascii <data>  以 ascii 的方式 post 数据\n>   * --data-binary <data> 以二进制的方式 post 数据\n\n使用 `--request` 指定请求类型， `--data` 指定数据，例如：\n\n``` sh\ncurl httpbin.org/post --request POST --data \"name=keenwon&website=http://keenwon.com\"\n```\n\n返回：\n\n``` html\n{\n  \"args\": {},\n  \"data\": \"\",\n  \"files\": {},\n  \"form\": {\n    \"name\": \"tomshine\",\n    \"website\": \"http://tomshine.xyz\"\n  },\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"Content-Length\": \"41\",\n    \"Content-Type\": \"application/x-www-form-urlencoded\",\n    \"Host\": \"httpbin.org\",\n    \"User-Agent\": \"curl/7.43.0\"\n  },\n  \"json\": null,\n  \"origin\": \"121.35.209.62\",\n  \"url\": \"http://httpbin.org/post\"\n}\n```\n\n这个返回值是 httpbin 输出的，可以清晰的看出我们发送了什么数据，非常实用。\n\n**form 表单提交**\n\nform 表单提交使用 `--form`，使用 `@` 指定本地文件，例如我们提交一个表单，有字段 name 和文件 f：\n\n``` sh\ncurl httpbin.org/post --form \"name=tomshine\" --form \"f=@/Users/tomshine/test.txt\"\n```\n\n返回：\n\n``` json\n{\n  \"args\": {},\n  \"data\": \"\",\n  \"files\": {\n    \"f\": \"Hello Curl!\\n\"\n  },\n  \"form\": {\n    \"name\": \"tomshine\"\n  },\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"Content-Length\": \"296\",\n    \"Content-Type\": \"multipart/form-data; boundary=------------------------3bd3dc24dca6daf2\",\n    \"Host\": \"httpbin.org\",\n    \"User-Agent\": \"curl/7.43.0\"\n  },\n  \"json\": null,\n  \"origin\": \"112.95.153.98\",\n  \"url\": \"http://httpbin.org/post\"\n}\n```\n\n### 4.1.3 curl 深入\n**显示头信息**\n\n使用 `--include` 在输出中包含头信息，使用 `--head` 只返回头信息，例如：\n\n``` sh\ncurl httpbin.org/post --include --request POST --data \"name=tomshine\"\n```\n\n返回：\n\n``` html\nHTTP/1.1 200 OK\nServer: nginx\nDate: Sun, 18 Sep 2016 01:23:28 GMT\nContent-Type: application/json\nContent-Length: 363\nConnection: keep-alive\nAccess-Control-Allow-Origin: *\nAccess-Control-Allow-Credentials: true\n\n{\n  \"args\": {},\n  \"data\": \"\",\n  \"files\": {},\n  \"form\": {\n    \"name\": \"tomshine\"\n  },\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"Content-Length\": \"13\",\n    \"Content-Type\": \"application/x-www-form-urlencoded\",\n    \"Host\": \"httpbin.org\",\n    \"User-Agent\": \"curl/7.43.0\"\n  },\n  \"json\": null,\n  \"origin\": \"121.35.209.62\",\n  \"url\": \"http://httpbin.org/post\"\n}\n```\n\n再例如，只显示头信息的话：\n\n``` sh\ncurl httpbin.org --head\n```\n\n返回：\n\n``` html\nHTTP/1.1 200 OK\nServer: nginx\nDate: Sun, 18 Sep 2016 01:24:29 GMT\nContent-Type: text/html; charset=utf-8\nContent-Length: 12150\nConnection: keep-alive\nAccess-Control-Allow-Origin: *\nAccess-Control-Allow-Credentials: true\n```\n\n**详细显示通信过程**\n\n使用 `--verbose` 显示通信过程，例如：\n\n``` sh\ncurl httpbin.org/post --verbose --request POST --data \"name=tomshine\"\n```\n\n返回：\n\n``` html\n*   Trying 54.175.219.8...\n* Connected to httpbin.org (54.175.219.8) port 80 (#0)\n> POST /post HTTP/1.1\n> Host: httpbin.org\n> User-Agent: curl/7.43.0\n> Accept: */*\n> Content-Length: 13\n> Content-Type: application/x-www-form-urlencoded\n>\n* upload completely sent off: 13 out of 13 bytes\n< HTTP/1.1 200 OK\n< Server: nginx\n< Date: Sun, 18 Sep 2016 01:25:03 GMT\n< Content-Type: application/json\n< Content-Length: 363\n< Connection: keep-alive\n< Access-Control-Allow-Origin: *\n< Access-Control-Allow-Credentials: true\n<\n{\n  \"args\": {},\n  \"data\": \"\",\n  \"files\": {},\n  \"form\": {\n    \"name\": \"tomshine\"\n  },\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"Content-Length\": \"13\",\n    \"Content-Type\": \"application/x-www-form-urlencoded\",\n    \"Host\": \"httpbin.org\",\n    \"User-Agent\": \"curl/7.43.0\"\n  },\n  \"json\": null,\n  \"origin\": \"121.35.209.62\",\n  \"url\": \"http://httpbin.org/post\"\n}\n* Connection #0 to host httpbin.org left intact\n```\n\n**设置头信息**\n\n使用 `--header` 设置头信息，`httpbin.org/headers` 会显示请求的头信息，我们测试下：\n\n``` sh\ncurl httpbin.org/headers --header \"a:1\"\n```\n\n返回：\n\n``` html\n{\n  \"headers\": {\n    \"A\": \"1\",\n    \"Accept\": \"*/*\",\n    \"Host\": \"httpbin.org\",\n    \"User-Agent\": \"curl/7.43.0\"\n  }\n}\n```\n\n同样的，可以使用 `--header` 设置 `User-Agent` 等。\n\n**Referer 字段**\n\n设置 `Referer` 字段很简单，使用 `--referer` ，例如：\n\n``` sh\ncurl httpbin.org/headers --referer http://tomshine.xyz\n```\n\n返回：\n\n``` html\n{\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"Host\": \"httpbin.org\",\n    \"Referer\": \"http://tomshine.xyz\",\n    \"User-Agent\": \"curl/7.43.0\"\n  }\n}\n```\n\n**包含 cookie**\n\n使用 `--cookie` 来设置请求的 cookie，例如：\n\n``` sh\ncurl httpbin.org/headers --cookie \"name=tomshine;website=http://tomshine.xyz\"\n```\n\n返回：\n\n``` html\n{\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"Cookie\": \"name=tomshine;website=http://tomshine.xyz\",\n    \"Host\": \"httpbin.org\",\n    \"User-Agent\": \"curl/7.43.0\"\n  }\n}\n```\n\n**自动跳转**\n\n使用 `--location` 参数会跟随链接的跳转，例如：\n\n``` sh\ncurl httpbin.org/redirect/1 --location\n```\n\nhttpbin.org/redirect/1 会 302 跳转，所以返回：\n\n``` html\n{\n  \"args\": {},\n  \"headers\": {\n    \"Accept\": \"*/*\",\n    \"Host\": \"httpbin.org\",\n    \"User-Agent\": \"curl/7.43.0\"\n  },\n  \"origin\": \"121.35.209.62\",\n  \"url\": \"http://httpbin.org/get\"\n}\n```\n\n**http 认证**\n\n当页面需要认证时，可以使用 `--user` ，例如：\n\n``` sh\ncurl httpbin.org/basic-auth/tomshine/123456 --user tomshine:123456\n```\n\n返回：\n\n``` html\n{\n  \"authenticated\": true,\n  \"user\": \"tomshine\"\n}\n```\n\n\n\n## 4.2 tcpdump\n\n\n### 4.2.1 tcp 三次握手和四次挥手\n\n**三次握手**\n\nA 主动打开连接，B 被动打开连接\n\n```\n(1) 第一次握手：建立连接时，客户端 A 发送 SYN 包 (SYN=x) 到服务器 B，并进入 SYN_SEND 状态，等待服务器 B 确认。\n(2) 第二次握手：服务器 B 收到 SYN 包，必须确认客户 A 的 SYN(ACK=x+1)，同时自己也发送一个 SYN 包 (SYN=y)，即 SYN+ACK 包，此时服务器 B 进入 SYN_RECV 状态。\n(3) 第三次握手：客户端 A 收到服务器 B 的 SYN＋ACK 包，向服务器 B 发送确认包 ACK(ACK=y+1)，此包发送完毕，客户端 A 和服务器 B 进入 ESTABLISHED 状态，完成三次握手。\n完成三次握手，客户端与服务器开始传送数据。\n```\n\n为什么 A 还要发送一次确认呢？\n\n主要是为了防止已失效的连接请求报文段突然有传送到 B，因而产生错误。\n\n***正常情况***\n\nA 发出连接请求，但是因为连接请求报文丢失为未收到确认。于是 A 在重传一次连接请求，后来收到了确认，建立了连接。数据传输完毕后，就释放了连接。A 共发送两个连接请求报文段，其中第一个丢失第二个到达了 B。\n\n***异常情况***\nA 发出的第一个连接请求报文段并没有丢失，而是在某些网络节点长时间滞留，导致延误到连接释放之后才到达了 B，本来这是一个早已经失效的报文段，但是 B 收到此失效的连接请求报文段之后，误以为是 A 又发出一次新的连接请求，于是就向 A 发送确认报段，同意建立连接。假如不采用三次握手，那么只要 B 发出确认，新的连接就建立了。由于现在 A 并没有发出建立连接的请求，因此不会理睬 B 的确认，也不会向 B 发送数据，但 B 却以为新的运输连接已经建立了，并且一直等待 A 发来数据。B 的资源就这样白白的浪费了，\n采用三次握手的办法可以防止上述现象的发生。例如刚才的情况，A 不会向 B 的确认发出确认。B 由于接收不到确认，就知道 A 并没有要求建立连接。\n\n使用 tcpdump 来验证 TCP 的三次握手\n\n使用 ssh localhost 来连接主机，使用使用 tcpdump 来验证 TCP 的三次握手\n\n\n    [root@localhost apue]# tcpdump -i lo tcp -S\n    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode\n    listening on lo, link-type EN10MB (Ethernet), capture size 65535 bytes\n    1 15:08:03.039511 IP6 localhost.44910 > localhost.ssh: Flags [S], seq 3120401438, win 32752, options [mss 16376,sackOK,TS val 1319756 ecr 0,nop,wscale 7], length 0\n    2 15:08:03.039546 IP6 localhost.ssh > localhost.44910: Flags [S.], seq 404185237, ack 3120401439, win 32728, options [mss 16376,sackOK,TS val 1319756 ecr 1319756,nop,wscale 7], length 0\n    3 15:08:03.039576 IP6 localhost.44910 > localhost.ssh: Flags [.], ack 404185238, win 256, options [nop,nop,TS val 1319756 ecr 1319756], length 0\n    4 15:08:03.064809 IP6 localhost.ssh > localhost.44910: Flags [P.], seq 404185238:404185259, ack 3120401439, win 256, options [nop,nop,TS val 1319781 ecr 1319756], length 21\n    15:08:03.064944 IP6 localhost.44910 > localhost.ssh: Flags [.], ack 404185259, win 256, options [nop,nop,TS val 1319781 ecr 1319781], length 0\n\n\n第一行就是第一次握手，客户端向服务器发送 SYN 标志 (Flags [S])，其中 seq = 3120401438；\n第二行就是第二次握手，服务器向客户端进行 SYN+ACK 响应 (Flags [S.]), 其中 seq 404185237, ack 3120401439（是客户端的 seq+1 的值）\n第三行就是第三次握手，客户端向服务器进行 ACK 响应 (Flags [.]), 其中 ack 404185238（就是服务器的 seq+1 的值）。\n\n**四次挥手**\n\n通信传输结束后，通信的双方都可以释放连接，现在 A 和 B 都处于 ESTABLISHED 状态。\n\n由于 TCP 连接是全双工的，因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个 FIN 来终止这个方向的连接。收到一个 FIN 只意味着这一方向上没有数据流动，一个 TCP 连接在收到一个 FIN 后仍能发送数据。首先进行关闭的一方将执行主动关闭，而另一方执行被动关闭。\n\n(1) 客户端 A 发送一个 FIN 包，用来关闭客户 A 到服务器 B 的数据传送。序列号 seq = u，它等于前面已传送过的数据的最后一个字节的序号加 1. 这时 A 进入 FIN-WAIT-1（终止等待 1) 状态，等待 B 的确认。\n\n(2) 服务器 B 收到这个 FIN，它发回一个 ACK，确认序号是 ack = u +1。和 SYN 一样，一个 FIN 将占用一个序号。这个报文段自己的序号是 v，等于 B 前面已经传送过的数据的最后一个字节的序号加 1。然后 B 进程进入 CLOSE-WAIT（关闭等待）状态。TCP 服务器进程这时应通知高层应用进程，因而从 A 到 B 的这个方向的连接就释放了，这时的 TCP 连接处于半关闭 (half-close) 状态， 即 A 已经没有数据要发送了，但是 B 若发送数据，A 仍要接收，也就是说从 B 到 A 这个方向的连接并没有关闭，这个状态可能会持续一些时间。A 收到来到 B 的确认后就进入 FIN-WAIT-2（终止等待 2) 状态，等待 B 发出的连接释放报文段。\n\n(3) 若 B 已经没有要向 A 发送的数据，其应用进程就会通知 TCP 释放连接，这时 B 发出的连接释放报文段必须使 FIN = 1。现假定 B 的序号为 w（在半关闭状态 B 可能要发送一些数据）。B 还必须重复上次发送过的确认号 ack = u +1。这时 B 就进入了 LAST-ACK（最后确认）状态，等待 A 的确认。\n\n(4)A 在收到 B 的连接释放报文段后，必须对此发出确认。在确认报文段中把 ACK 置 1，确认号 ack = w + 1，而自己的序号是 seq = u + 1（根据 TCP 标准，前面发送过的 FIN 报文段要消耗一个序号），然后进入到 TIME-WAIT（时间等待）状态。\n\n***此时的 TCP 还没有完全的释放掉。必须经过时间等待计时器 (TIME-WAIT timer) 设置的时间 2MSL 后，A 才进入到 CLOSED 状态。***\n\n> MSL 叫做最长报文段寿命，它是任何报文段被丢弃前在网络内的最长时间。\n\n2MSL 也就是这个时间的两倍，RFC 建议设置为 2 分钟，但是 2 分钟可能太长了，因此 TCP 允许不同的实现使用更小的 MSL 值。\n\n因此从 A 进入到 TIME-WAIT 状态后，要经过 4 分钟才能进入 CLOSED 状态，此案开始建立下一个新的连接。当 A 撤销相应的传输控制块 TCP 后，就结束了 TCP 连接。\n\n使用 tcpdump 来验证 TCP 的四次挥手\n\n退出 ssh 连接的主机，使用使用 tcpdump 来验证 TCP 的四次挥手\n\n\n    15:14:58.836149 IP6 localhost.44911 > localhost.ssh: Flags [P.], seq 1823848744:1823848808, ack 3857143125, win 305, options [nop,nop,TS val 1735551 ecr 1735551], length 64\n    15:14:58.836201 IP6 localhost.44911 > localhost.ssh: Flags [F.], seq 1823848808, ack 3857143125, win 305, options [nop,nop,TS val 1735551 ecr 1735551], length 0\n    15:14:58.837850 IP6 localhost.ssh > localhost.44911: Flags [.], ack 1823848809, win 318, options [nop,nop,TS val 1735554 ecr 1735551], length 0\n    15:14:58.842130 IP6 localhost.ssh > localhost.44911: Flags [F.], seq 3857143125, ack 1823848809, win 318, options [nop,nop,TS val 1735559 ecr 1735551], length 0\n    15:14:58.842150 IP6 localhost.44911 > localhost.ssh: Flags [.], ack 3857143126, win 305, options [nop,nop,TS val 1735559 ecr 1735559], length 0\n\n***seq start:end 意思是初始序列号：结束序列号，end = start + length, 但是接受包的结束序号应该是 end - 1。***\n\n比如 start = 1，length = 3，那么真正的结束序号是 1+3-1 = 3, 因为开始序号也算在内的！\n\nseq 1823848744:1823848808 意思是初始序列号：结束序列号，其实后面那个就是前面那个加上包长度 length = 64，实际上结束序列号是没有使用的，真正使用的序号是开始序号到结束序号 -1，也就是 1823848807\n\n第一次挥手中客户端发送 FIN 即 [F.] seq u = 1823848808，也就是上次发送的数据的最后一个字节的序号加 1\n\n第二次挥手中服务器发送 ACK 即 [.] ack 1823848809 = u + 1\n\n第三次挥手中服务器发送 FIN 即 [F.] seq v = 3857143125， ack 1823848809 = u + 1\n\n第四次挥手中客户端发送 ACK 即 [.] ack 3857143126 = v + 1\n\n\n1. 默认情况下（不改变 socket 选项），当你调用 close 函数时，如果发送缓冲中还有数据，TCP 会继续把数据发送完。\n\n2. 发送了`FIN 只是表示这端不能继续发送数据（应用层不能再调用 send 发送）`，但是还可以接收数据。\n\n3. 应用层如何知道对方关闭？\n\n在最简单的阻塞模型中，当调用 recv 时，如果返回 0，则表示对方关闭，\n\n在这个时候通常的做法就是也调用 close，那么 TCP 层就发送 FIN，继续完成四次握手；\n\n***如果不调用 close，那么对方就会处于 FIN_WAIT_2 状态，而本端则会处于 CLOSE_WAIT 状态；***\n\n4. 在很多时候，TCP 连接的断开都会由 TCP 层自动进行，例如你 CTRL+C 终止你的程序，TCP 连接依然会正常关闭。\n\n### 4.2.2 tcpdump 使用\n\n**针对特定网口抓包 (-i 选项）**\n\n> tcpdump -i eth0\n\n**指定抓包端口**\n\n> tcpdump -i eth0 port 22\n\n**抓取特定目标 ip 和端口的包**\n\n> tcpdump -i eth0 dst 10.70.121.92 and port 22\n\n**增加抓包时间戳 (-tttt 选项）**\n\n> tcpdump -n -tttt -i eth0\n\n## 4.3 nc\n\nnc 检测端口更方便，同时批量进行检测端口的话是非常好的工具\n\n### 4.3.1 语法\n\nnc [-hlnruz][-g《网关...>][-G《指向器数目》][-i《延迟秒数》][-o《输出文件》][-p《通信端口》][-s《来源位址》][-v...][-w《超时秒数》][主机名称]『通信端口...]\n    ```\n    参数说明：\n    -g《网关》 设置路由器跃程通信网关，最丢哦可设置 8 个。\n    -G《指向器数目》 设置来源路由指向器，其数值为 4 的倍数。\n    -h 在线帮助。\n    -i《延迟秒数》 设置时间间隔，以便传送信息及扫描通信端口。\n    -l 使用监听模式，管控传入的资料。\n    -n 直接使用 IP 地址，而不通过域名服务器。\n    -o《输出文件》 指定文件名称，把往来传输的数据以 16 进制字码倾倒成该文件保存。\n    -p《通信端口》 设置本地主机使用的通信端口。\n    -r 乱数指定本地与远端主机的通信端口。\n    -s《来源位址》 设置本地主机送出数据包的 IP 地址。\n    -u 使用 UDP 传输协议。\n    -v 显示指令执行过程。\n    -w《超时秒数》 设置等待连线的时间。\n    -z 使用 0 输入 / 输出模式，只在扫描通信端口时使用。\n\n    ```\n### 4.3.2 TCP 端口扫描\n    ```\n    # nc -v -z -w2 10.20.144.145 1-100\n    Connection to 10.20.144.145 22 port [tcp/ssh] succeeded!\n    nc: connect to 10.20.144.145 port 23 (tcp) failed: Connection refused\n    nc: connect to 10.20.144.145 port 24 (tcp) failed: Connection refused\n    nc: connect to 10.20.144.145 port 25 (tcp) failed: Connection refused\n    ...\n    Connection to 10.20.144.145 80 port [tcp/http] succeeded!\n    ...\n    扫描 10.20.144.145 的端口 范围是 1-100\n    ```\n    不加 -v 时仅输出 succeeded 的结果\n\n### 4.3.3 扫描 UDP 端口\n    ```\n    # nc -u -z -w2 10.20.144.145 1-1000 // 扫描 10.20.144.145 的端口 范围是 1-1000\n    扫描指定端口\n    ```\n\n# 5 日志相关操作\n\n## 5.1 截取某段时间内的日志\n\n```\nsed -n '/2018-03-06 15:25:00/,/2018-03-06 15:30:00/p' access.log >25-30.log\n```\n## 5.2 处理日志文件中上下关联的两行\n\n```\nawk 'pattern { action  };pattern { action  };'\n```\n凡是被 {} 包裹的，就是 action, 凡是没有被{}包裹的，就是 pattern,\n\n文件 d.txt 如下内容\n```\nggg 1\nportals: 192.168.5.41:3260\nwerew 2\nportals: 192.168.5.43:3260\n```\n如何把文件 d.txt 内容变为如下内容\n\n```\nggg 192.168.5.41:3260\nwerew 192.168.5.43:3260\n```\n方法\n```\nawk '/port/{print a\" \"$2}{a=$1}' d.txt\n```\n处理第一行的时候，以 port 开头吗？很明显，不以 port 开头，所以那个 pattern 不匹配，action 不执行。但执行了后面的 a=$1\n\n处理第二行的时候，以 port 开头，打印出来 a 和本行 $2，再处理就是个循环过程。\n\n总之，编写模式匹配时候，匹配的模式为第二行中的内容\n\n# 6 应用服务相关\n\n## 6.1 排查 java CPU 性能问题\n\n[show-busy-java-threads.sh](https://github.com/meetbill/op_practice_code/blob/master/Linux/op/show-busy-java-threads.sh)\n```\ncurl -o show-busy-Java-threads.sh https://raw.githubusercontent.com/meetbill/op_practice_code/master/Linux/op/show-busy-java-threads.sh\n```\n\n用于快速排查`Java`的`CPU`性能问题 (`top us`值过高），自动查出运行的`Java`进程中消耗`CPU`多的线程，并打印出其线程栈，从而确定导致性能问题的方法调用。\n\nPS，如何操作可以参见 [@bluedavy](http://weibo.com/bluedavy) 的《分布式 Java 应用》的【5.1.1 cpu 消耗分析】一节，说得很详细：\n\n1. `top`命令找出有问题`Java`进程及线程`id`：\n    1. 开启线程显示模式\n    1. 按`CPU`使用率排序\n    1. 记下`Java`进程`id`及其`CPU`高的线程`id`\n1. 用进程`id`作为参数，`jstack`有问题的`Java`进程\n1. 手动转换线程`id`成十六进制（可以用`printf %x 1234`）\n1. 查找十六进制的线程`id`（可以用`grep`）\n1. 查看对应的线程栈\n\n查问题时，会要多次这样操作以确定问题，上面过程**太繁琐太慢了**。\n\n### 6.1.1 用法\n\n```bash\nshow-busy-java-threads.sh\n# 从 所有的 Java 进程中找出最消耗 CPU 的线程（缺省 5 个），打印出其线程栈。\n\nshow-busy-java-threads.sh -c 《要显示的线程栈数》\n\nshow-busy-java-threads.sh -c 《要显示的线程栈数》 -p 《指定的 Java Process>\n\n##############################\n# 注意：\n##############################\n# 如果 Java 进程的用户 与 执行脚本的当前用户 不同，则 jstack 不了这个 Java 进程。\n# 为了能切换到 Java 进程的用户，需要加 sudo 来执行，即可以解决：\nsudo show-busy-java-threads.sh\n```\n\n### 6.1.2 示例\n\n```bash\n$ show-busy-java-threads.sh\n[1] Busy(57.0%) thread(23355/0x5b3b) stack of java process(23269) under user(admin):\n\"pool-1-thread-1\" prio=10 tid=0x000000005b5c5000 nid=0x5b3b runnable [0x000000004062c000]\n   java.lang.Thread.State: RUNNABLE\n    at java.text.DateFormat.format(DateFormat.java:316)\n    at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)\n    at com.xxx.foo.shared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:127)\n    at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)\n    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)\n    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)\n    at java.lang.Thread.run(Thread.java:662)\n\n[2] Busy(26.1%) thread(24018/0x5dd2) stack of java process(23269) under user(admin):\n\"pool-1-thread-2\" prio=10 tid=0x000000005a968800 nid=0x5dd2 runnable [0x00000000420e9000]\n   java.lang.Thread.State: RUNNABLE\n    at java.util.Arrays.copyOf(Arrays.java:2882)\n    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)\n    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:572)\n    at java.lang.StringBuffer.append(StringBuffer.java:320)\n    - locked <0x00000007908d0030> (a java.lang.StringBuffer)\n    at java.text.SimpleDateFormat.format(SimpleDateFormat.java:890)\n    at java.text.SimpleDateFormat.format(SimpleDateFormat.java:869)\n    at java.text.DateFormat.format(DateFormat.java:316)\n    at com.xxx.foo.services.common.DateFormatUtil.format(DateFormatUtil.java:41)\n    at com.xxx.foo.shared.monitor.schedule.AppMonitorDataAvgScheduler.run(AppMonitorDataAvgScheduler.java:126)\n    at com.xxx.foo.services.common.utils.AliTimer$2.run(AliTimer.java:128)\n    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)\n    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)\n...\n```\n\n上面的线程栈可以看出，`CPU`消耗最高的 2 个线程都在执行`java.text.DateFormat.format`，业务代码对应的方法是`shared.monitor.schedule.AppMonitorDataAvgScheduler.run`。可以基本确定：\n\n- `AppMonitorDataAvgScheduler.run`调用`DateFormat.format`次数比较频繁。\n- `DateFormat.format`比较慢。（这个可以由`DateFormat.format`的实现确定。）\n\n多个执行几次`show-busy-java-threads.sh`，如果上面情况高概率出现，则可以确定上面的判定。\n\\# 因为调用越少代码执行越快，则出现在线程栈的概率就越低。\n\n分析`shared.monitor.schedule.AppMonitorDataAvgScheduler.run`实现逻辑和调用方式，以优化实现解决问题。\n\n- [oldratlee](https://github.com/oldratlee)\n- [silentforce](https://github.com/silentforce) 改进此脚本，增加对环境变量`JAVA_HOME`的判断。 #15\n- [liuyangc3](https://github.com/liuyangc3)\n    - 优化性能，通过`read -a`简化反复的`awk`操作 #51\n    - 发现并解决`jstack`非当前用户`Java`进程的问题 #50\n\n# 7 日常工具\n\n## 7.1 mget\n\n对文件生成 ftp 连接\n```\nif [ $# -lt 1 ];then\n    echo \"Usage: ./mget filename\"\n    exit 0\nfi\nif [ ! -d $1 ] && [ ! -f $1 ];then\n    echo \"path $1 invalid\"\n    exit 0\nfi\nm_path=`readlink -f $1`\n\nif [ -f $m_path ];then\n    file_name=`echo \"$m_path\" | awk -F \"/\" '{print $NF}'`\n    m_path=$m_path\" -O \"$file_name\nelif [ -d $m_path ];then\n    dir_depth=`echo \"$m_path\" | awk -F \"/\" '{print NF-2}'`\n    m_path=$m_path\" -r -nH --cut-dir=\"$dir_depth\nfi\n\nm_host=`hostname`\nm_host=\"wget ftp://\"${m_host}$m_path\necho $m_host\n```\n"
  },
  {
    "path": "doc/README.md",
    "content": "# 运维实践指南\n\n此笔记中记录着学习点滴，希望可以帮到更多的人。\n\n如果这其中有些是你正困惑的地方，那么此笔记也许能帮到你，如果有好的建议，[戳这](https://github.com/meetbill/op_practice_book/issues) 进行提交下建议\n\n"
  },
  {
    "path": "doc/cloud/README.md",
    "content": "# 物理机，云服务及虚拟化篇\n\n> * 物理机\n> * AWS\n> * 阿里云\n> * KVM\n> * Docker\n> * OpenStack\n> * K8s\n"
  },
  {
    "path": "doc/cloud/aliyun.md",
    "content": "# 阿里云\n\n<!-- vim-markdown-toc GFM -->\n* [1 访问控制 (RAM)](#1-访问控制-ram)\n    * [1.1 创建 ECS 管理员](#11-创建-ecs-管理员)\n* [2 ECS](#2-ecs)\n    * [2.1 使用 API 控制 ECS](#21-使用-api-控制-ecs)\n    * [2.2 克隆实例](#22-克隆实例)\n* [3 OSS](#3-oss)\n    * [3.1 创建一个 OSS](#31-创建一个-oss)\n        * [3.1.1 Bucket](#311-bucket)\n        * [3.1.2 访问策略（访问控制）](#312-访问策略访问控制)\n            * [需要一个 AccessKey](#需要一个-accesskey)\n    * [3.2 OSSFS - 将 OSS 挂载到本地文件系统工具](#32-ossfs---将-oss-挂载到本地文件系统工具)\n        * [3.2.1 简介](#321-简介)\n        * [3.2.2 功能](#322-功能)\n        * [3.2.3 安装](#323-安装)\n        * [3.2.4 运行](#324-运行)\n        * [3.2.5 常见问题处理](#325-常见问题处理)\n        * [3.2.6 局限性](#326-局限性)\n        * [3.2.6 相关链接](#326-相关链接)\n\n<!-- vim-markdown-toc -->\n\n# 1 访问控制 (RAM)\n\n## 1.1 创建 ECS 管理员\n\n**创建 ECS 管理员群组**\n\n点击访问控制 -->群组管理 -->新建群组\n\n在创建好的群组上点击授权，选择 AliyunECSFullAccess（管理云服务器服务 (ECS) 的权限） 和 AliyunBSSOrderAccess（在费用中心 (BSS) 查看订单、支付订单及取消订单的权限）\n\n**创建用户**\n\n点击用户管理 -->新建用户...->加入 ECS 管理员群组\n\n# 2 ECS\n\n## 2.1 使用 API 控制 ECS\n\n[工具及简单使用说明](https://github.com/meetbill/op_practice_code/tree/master/cloud/aliyun/ecs)\n\n## 2.2 克隆实例\n\n* 系统盘 ---- 通过创建自定义镜像的方式，创建一个自定义镜像，然后使用这个自定义镜像创建 ECS 即可。\n* 数据盘 ---- 对已经配置完成的数据盘进行打快照，然后在购买或者升级页面，添加磁盘的地方点：“用快照创建磁盘”，选择你要的快照即可。\n\n# 3 OSS\n\n## 3.1 创建一个 OSS\n\n### 3.1.1 Bucket\n\n创建好后需要用到的是 Bucket 名字和 endpoint\n\n### 3.1.2 访问策略（访问控制）\n\n#### 需要一个 AccessKey\n\n使用 RAM 步骤\n\n* (1) 自定义策略，使得策略只对新创建的 Bucket 有完全权限\n```\n{\n    \"Statement\": [\n        {\n            \"Action\": \"oss:*\",\n            \"Effect\": \"Allow\",\n            \"Resource\": [\n                \"acs:oss:*:*:my-oss\",\n                \"acs:oss:*:*:my-oss/*\"\n                      ]\n                }\n          ],\n    \"Version\": \"1\"\n}\n```\n* (2) 创建对应的群组，并授权对应的策略\n* (3) 创建用户，并将此用户加入此群组中\n* (4) 创建 AccessKey\n\n## 3.2 OSSFS - 将 OSS 挂载到本地文件系统工具\n\n### 3.2.1 简介\n\nossfs 能让您在 Linux/Mac OS X 系统中把 Aliyun OSS bucket 挂载到本地文件\n系统中，您能够便捷的通过本地文件系统操作 OSS 上的对象，实现数据的共享。\n\n### 3.2.2 功能\n\nossfs 基于 s3fs 构建，具有 s3fs 的全部功能。主要功能包括：\n\n* 支持 POSIX 文件系统的大部分功能，包括文件读写，目录，链接操作，权限，\n  uid/gid，以及扩展属性（extended attributes）\n* 通过 OSS 的 multipart 功能上传大文件。\n* MD5 校验保证数据完整性。\n\n### 3.2.3 安装\n\n**预编译的安装包**\n\n我们为常见的 linux 发行版制作了安装包：\n\n- Ubuntu-14.04\n- CentOS-7.0/6.5/5.11\n\n请从 [版本发布页面][releases] 选择对应的安装包下载安装，建议选择最新版本。\n\n- 对于 Ubuntu，安装命令为：\n\n```\nsudo apt-get update\nsudo apt-get install gdebi-core\nsudo gdebi your_ossfs_package\n```\n\n- 对于 CentOS6.5 及以上，安装命令为：\n\n```\nsudo yum localinstall your_ossfs_package\n```\n\n- 对于 CentOS5，安装命令为：\n\n```\nsudo yum localinstall your_ossfs_package --nogpgcheck\n```\n\n**源码安装**\n\n如果没有找到对应的安装包，您也可以自行编译安装。编译前请先安装下列依赖库：\n\nUbuntu 14.04:\n\n```\nsudo apt-get install automake autotools-dev g++ git libcurl4-gnutls-dev \\\n                     libfuse-dev libssl-dev libxml2-dev make pkg-config\n```\n\nCentOS 7.0:\n\n```\nsudo yum install automake gcc-c++ git libcurl-devel libxml2-devel \\\n                 fuse-devel make openssl-devel\n```\n\n然后您可以在 github 上下载源码并编译安装：\n\n```\ngit clone https://github.com/aliyun/ossfs.git\ncd ossfs\n./autogen.sh\n./configure\nmake\nsudo make install\n```\n\n### 3.2.4 运行\n\n设置 Bucket name, access key/id 信息，将其存放在 /etc/passwd-ossfs 文件中，\n注意这个文件的权限必须正确设置，建议设为 640。\n\n```\necho my-bucket:my-access-key-id:my-access-key-secret > /etc/passwd-ossfs\nchmod 640 /etc/passwd-ossfs\n```\n\n将 OSS Bucket mount 到指定目录\n\n```\nossfs my-bucket my-mount-point -ourl=my-oss-endpoint\n```\n**示例**\n\n将`my-bucket`这个 Bucket 挂载到`/tmp/ossfs`目录下，AccessKeyId 是`faint`，\nAccessKeySecret 是`123`，oss endpoint 是`http://oss-cn-hangzhou.aliyuncs.com`\n\n```\necho my-bucket:faint:123 > /etc/passwd-ossfs\nchmod 640 /etc/passwd-ossfs\nmkdir /tmp/ossfs\nossfs my-bucket /tmp/ossfs -ourl=http://oss-cn-hangzhou.aliyuncs.com\n```\n\n卸载 Bucket:\n\n```bash\numount /tmp/ossfs # root user\nfusermount -u /tmp/ossfs # non-root user\n```\n\n**常用设置**\n\n- 使用`ossfs --version`来查看当前版本，使用`ossfs -h`来查看可用的参数\n- 如果使用 ossfs 的机器是阿里云 ECS，可以使用内网域名来**避免流量收费**和\n  **提高速度**：\n\n        ossfs my-bucket /tmp/ossfs -ourl=http://oss-cn-hangzhou-internal.aliyuncs.com\n\n- 在 linux 系统中，[updatedb][updatedb] 会定期地扫描文件系统，如果不想\n  ossfs 的挂载目录被扫描，可参考 [FAQ][FAQ-updatedb] 设置跳过挂载目录\n- 如果你没有使用 [eCryptFs][ecryptfs] 等需要 [XATTR][xattr] 的文件系统，可\n  以通过添加`-o noxattr`参数来提升性能\n- ossfs 允许用户指定多组 bucket/access_key_id/access_key_secret 信息。当\n  有多组信息，写入 passwd-ossfs 的信息格式为：\n\n        bucket1:access_key_id1:access_key_secret1\n        bucket2:access_key_id2:access_key_secret2\n\n- 生产环境中推荐使用 [supervisor][supervisor] 来启动并监控 ossfs 进程，使\n  用方法见 [FAQ][faq-supervisor]\n\n**高级设置**\n\n- 可以添加`-f -d`参数来让 ossfs 运行在前台并输出 debug 日志\n- 可以使用`-o kernel_cache`参数让 ossfs 能够利用文件系统的 page cache，如\n  果你有多台机器挂载到同一个 bucket，并且要求强一致性，请**不要**使用此\n  选项\n\n### 3.2.5 常见问题处理\n\n遇到错误不要慌：) 按如下步骤进行排查：\n\n1. 如果有打印错误信息，尝试阅读并理解它\n2. 查看`/var/log/syslog`或者`/var/log/messages`中有无相关信息\n\n        grep 's3fs' /var/log/syslog\n        grep 'ossfs' /var/log/syslog\n\n3. 重新挂载 ossfs，打开 debug log：\n\n        ossfs ... -o dbglevel=debug -f -d > /tmp/fs.log 2>&1\n\n    然后重复你出错的操作，出错后将`/tmp/fs.log`保留，自己查看或者发给我\n\n[FAQ](https://github.com/aliyun/ossfs/wiki/FAQ)\n\n### 3.2.6 局限性\n\nossfs 提供的功能和性能和本地文件系统相比，具有一些局限性。具体包括：\n\n* 随机或者追加写文件会导致整个文件的重写。\n* 元数据操作，例如 list directory，性能较差，因为需要远程访问 oss 服务器。\n* 文件 / 文件夹的 rename 操作不是原子的。\n* 多个客户端挂载同一个 oss bucket 时，依赖用户自行协调各个客户端的行为。例如避免多个客户端写同一个文件等等。\n* 不支持 hard link。\n* 不适合用在高并发读 / 写的场景，这样会让系统的 load 升高\n\n\n### 3.2.6 相关链接\n\n* [ossfs wiki](https://github.com/aliyun/ossfs/wiki)\n* [s3fs](https://github.com/s3fs-fuse/s3fs-fuse) - 通过 fuse 接口，mount s3 bucket 到本地文件系统。\n\n\n[releases]: https://github.com/aliyun/ossfs/releases\n[updatedb]: http://linux.die.net/man/8/updatedb\n[faq-updatedb]: https://github.com/aliyun/ossfs/wiki/FAQ\n[ecryptfs]: http://ecryptfs.org/\n[xattr]: http://man7.org/linux/man-pages/man7/xattr.7.html\n[supervisor]: http://supervisord.org/\n[faq-supervisor]: https://github.com/aliyun/ossfs/wiki/FAQ#18\n"
  },
  {
    "path": "doc/cloud/aws.md",
    "content": "## AWS\n<!-- vim-markdown-toc GFM -->\n* [0 AWS 产品](#0-aws-产品)\n* [1 使用 AWS S3](#1-使用-aws-s3)\n    * [基本概念](#基本概念)\n    * [基本操作](#基本操作)\n        * [创建以及管理 Bucket](#创建以及管理-bucket)\n        * [操作文件](#操作文件)\n        * [同步本地文件、目录](#同步本地文件目录)\n        * [权限控制](#权限控制)\n    * [实际应用](#实际应用)\n        * [每天凌晨备份 Postgres 数据库](#每天凌晨备份-postgres-数据库)\n        * [将 S3 当作同步盘](#将-s3-当作同步盘)\n* [2 Amazon IAM（身份及访问管理）](#2-amazon-iam身份及访问管理)\n    * [存储桶策略示例](#存储桶策略示例)\n        * [创建对某个存储桶有所有权限实例](#创建对某个存储桶有所有权限实例)\n* [3 EC2](#3-ec2)\n    * [重启与停止以及终止之间的区别](#重启与停止以及终止之间的区别)\n    * [存储](#存储)\n* [4 AWS VPC](#4-aws-vpc)\n    * [VPC 中几个概念](#vpc-中几个概念)\n    * [VPC 规划](#vpc-规划)\n* [5 AWS 客户端](#5-aws-客户端)\n    * [AWS CLI](#aws-cli)\n        * [安装](#安装)\n        * [配置](#配置)\n            * [环境变量](#环境变量)\n        * [命令行参数](#命令行参数)\n        * [使用](#使用)\n    * [saws 工具](#saws-工具)\n        * [S3](#s3)\n    * [S3cmd](#s3cmd)\n        * [下载及配置](#下载及配置)\n        * [使用 S3cmd](#使用-s3cmd)\n\n<!-- vim-markdown-toc -->\n\n# 0 AWS 产品\n\n***计算***\n\n- Amazon EC2 `云中的虚拟服务器`\n- Auto Scaling\n- Amazon VPC `隔离的云资源`\n- Elastic Load Balancing\n\n***联网***\n\n- Amazon VPC `隔离的云资源`\n- AWS Direct Connect `AWS 的专用网络连接`\n- Amazon Route 53 `可扩展的域名系统 (DNS)`\n- Elastic Load Balancing\n\n***存储和内容分发***\n\n- Amazon S3 `可扩展的云存储`\n- Amazon Glacier `云中的低成本归档存储`\n- Amazon EBS `EC2 块存储卷`\n- AWS Storage Gateway `将内部 IT 环境与云存储相集成`\n- Amazon CloudFront `全球内容分发网络 (CDN)`\n- AWS Import/Export `大容量数据传输`\n\n***数据库***\n\n- Amazon RDS `适用于 MySQL、Oracle、SQL Server 和 PostgreSQL 的管理型关系数据库服务`\n- Amazon DynamoDB `快速、可预测、高可扩展的 NoSQL 数据存储`\n- Amazon Redshift `快速、功能强大的完全管理型 PB 级数据仓库服务`\n- Amazon ElastiCache `基于内存的缓存服务`\n\n***分析***\n\n- Amazon Kinesis `实时数据流处理`\n- Amazon Redshift `快速、功能强大的完全管理型 PB 级数据仓库服务`\n- Amazon EMR `托管的 Hadoop 框架`\n- AWS Data Pipeline `适用于周期性数据驱动工作流的编排服务`\n\n\n***应用程序服务***\n\n- Amazon CloudSearch `托管的搜索服务`\n- Amazon AppStream `低延迟应用程序流媒体传输`\n- Amazon SES `电子邮件发送服务`\n- Amazon SQS `消息队列服务`\n- Amazon SNS `推送通知服务`\n- Amazon SWF `用于协调应用程序组件的工作流服务`\n- Amazon FPS `基于 API 的付款服务`\n- Amazon Elastic Transcoder `易用型可扩展媒体转码`\n\n***部署与管理***\n\n- AWS Elastic Beanstalk `AWS 应用程序容器`\n- AWS OpsWorks `DevOps 应用程序管理服务`\n- AWS CloudFormation `AWS 资源创建模板`\n- AWS CloudTrail `用户活动与变更追踪`\n- Amazon CloudWatch `资源与应用程序监控`\n- AWS Identity and Access Management (IAM) `可配置的 AWS 访问控制`\n- AWS CloudHSM `有助于实现监管合规性的基于硬件的密钥存储`\n- AWS 管理控制台 `基于 Web 的用户界面`\n- AWS 命令行界面 `管理 AWS 服务的统一工具`\n\n***移动服务***\n\n- Amazon Cognito `用户身份和数据同步`\n- Amazon Mobile Analytics `快速、安全的应用程序使用情况分析`\n- Amazon SNS `跨越多种平台发送通知、更新和促销信息`\n- AWS 移动软件开发工具包 `快速轻松开发高质量移动应用程序`\n\n***应用程序***\n\n- Amazon WorkSpaces - 云中的虚拟桌面\n- Amazon Zocalo - 安全的企业文档存储和共享\n\n\n# 1 使用 AWS S3\n\nS3 是 Simple Storage Service 的缩写，是 AWS 提供的云存储服务，价格公道、服务稳定，因此被广泛应用在静态文件存储、内容备份、大数据分析领域。\n\n\n## 基本概念\n\n在使用 S3 前首先需要了解一些基本概念：\n\n- 对象：即文件。\n- Bucket：官方翻译为存储桶，是在网络存储服务中广泛使用的一个概念，通常用于区分文件所在区域，可以对比操作系统不同盘符来理解。\n- AWS CLI：AWS 提供的控制台工具，aws-cli 是其在 PyPI 上注册的名字，shell 中的命令为 ``aws``。\n- [AWS IAM](./aws_IAM.md)：即 Identity and Access Management，是 AWS 的身份认证服务，用于对用户身份及资源进行授权管理，需要配置号已授权的 AWS 认证信息才可以使用其服务。\n- S3 资源：S3 资源以 ``s3://bucket-name/`` 开始。\n\n\n## 基本操作\n\n### 创建以及管理 Bucket\n\n就像你首先需要有硬盘以及挂载盘符才能在本地操作文件一样，正式使用 S3\n提供服务前你同样需要准备好 Bucket：\n\n    aws s3 mb s3://bucket-name\n\nBucket 名称必须唯一，并且应符合 DNS 标准：可以包含小写字母、数字、连字符（-）和点号（.），\n只能以字母或数字开头和结尾，连字符或点号后不能跟点号。\n\n想要列出以创建的 Bucket 可以使用 ls 命令：\n\n```\n$ aws s3 ls\n       CreationTime Bucket\n       ------------ ------\n2015-11-11 11:11:11 my-bucket\n2015-11-11 11:11:11 my-bucket1\n```\n\n删除空 Bucket 可以使用 ``aws s3 rb s3://bucket-name``，非空 Bucket 需要加上 ``--force``\n命令，这一点对于管理文件对象也是一样。\n\n``mb``、``rb`` 命令分别是 ``MakeBucket`` 和 ``RemoveBucket`` 的缩写。\n\n### 操作文件\n\nS3 的服务基于文件对象，提供了类似 Linux 环境下的文件访问操作：\n\n```\naws s3 ls s3://bucket-name[/folder]/\naws s3 cp s3://bucket-name/file s3://bucket-name[/folder]/\naws s3 mv s3://bucket-name/file s3://bucket-name[/folder]/\naws s3 rm s3://bucket-name/file\naws s3 rm s3://bucket-name[/folder]/ --recursive\n```\n\n操作文件的方式基本上和 Linux 环境类似，``--recursive`` 用于递归调用，类似 Linux 命令的\n``-r`` 参数。\n\n### 同步本地文件、目录\n\nAWS CLI 还提供了一个本地文件同步命令 ``sync``：\n\n    aws s3 sync local_dir s3://my-bucket/MyFolder\n\n``sync`` 会递归地将本地文件复制到 S3 Bucket 中，如存在重名文件则覆盖处理。\n如果需要像同步盘一样删除已不存在的文件可以加上 ``--delete`` 命令。\n\n``sync`` 还可以通过 ``--exclude`` 和 ``--include`` 来指定不同步的目录，以及不同步的目录中的例外。\n\n### 权限控制\n\n默认情况下，所有 Amazon S3 资源都是私有的，包括存储桶、对象和相关子资源（例如，lifecycle 配置和 website 配置）。只有资源拥有者，即***创建该资源的 AWS 账户***可以访问该资源。资源拥有者可以选择通过编写访问策略授予他人访问权限\n\nAmazon S3 提供的访问策略\n\n> * 基于资源的策略 ------- 存储桶策略和访问控制列表 (ACL – 注：每个存储桶和对象都有关联的 ACL)\n> * 基于用户策略 ------ 使用 IAM 管理，IAM 用户必须拥有两种权限：一种权限来自其父账户，另一种权限来自要访问的资源的拥有者 AWS 账户。\n\n\n## 实际应用\n\n### 每天凌晨备份 Postgres 数据库\n\n```sh\npg_dump -Fc --no-owner  bxzz_exercise > /tmp/tmp.pgdump && aws s3 cp tmp.pgdump s3://filebackup/pg/$(date +%Y/%m/%d).pgdump\n```\n\ncrontab 设置：\n\n```\n0 2 * * * pg_dump -Fc --no-owner  bxzz_exercise > /tmp/tmp.pgdump && aws s3 cp tmp.pgdump s3://filebackup/pg/$(date +\\%Y/\\%m/\\%d).pgdump\n```\n\n### 将 S3 当作同步盘\n\n检测到文件变动时触发：\n\n    aws s3 sync . s3://my-bucket/MyFolder --exclude '*.txt' --include 'MyFile*.txt' --exclude 'MyFile?.txt'\n\n\n参考：[AWS S3 官方中文文档](http://docs.aws.amazon.com/zh_cn/cli/latest/userguide/using-s3-commands.html)\n\n\n# 2 Amazon IAM（身份及访问管理）\n\nIAM enables you to control who can do what in your AWS account.\n\n-\n## 存储桶策略示例\n\n创建存储桶后需要创建个 IAM 用户和关联下权限\n\n### 创建对某个存储桶有所有权限实例\n\n```\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"s3:ListAllMyBuckets\"\n            ],\n            \"Resource\": \"arn:aws-cn:s3:::*\"\n        },\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": \"s3:*\",\n            \"Resource\": [\n                \"arn:aws-cn:s3:::backet-name/*\"\n             ]\n        }\n   ]\n}\n```\n注：Amazon 资源名称 (ARN) 和 AWS 服务命名空间\n\narn:partition:service:region:account-id:resource\n\npartition: 资源所处的分区。对于标准 AWS 区域，分区是 aws。如果资源位于其他分区，则分区是 aws-partitionname。例如，位于 中国（北京） 区域的资源的分区为 aws-cn。\n\n\n# 3 EC2\n\n## 重启与停止以及终止之间的区别\n\n性能 | 重启| 停止 / 启动（仅限 Amazon EBS 支持的实例）|终止\n-----|------|------|------\n主机|实例保持在同一主机上运行|实例在新主机上运行|无\n私有和公有 IP 地址|这些地址保持不变|EC2-Classic：实例获得新的私有和公有 IP 地址 EC2-VPC：实例保留其私有 IP 地址。实例获取新的公有 IP 地址，除非它具有弹性 IP 地址 (EIP)（该地址在停止 / 启动过程中不更改）。|无\n弹性 IP 地址 (EIP)|EIP 保持与实例关联|EC2-Classic：EIP 不再与实例关联，EC2-VPC：EIP 保持与实例关联|EIP 不再与实例关联\n实例存储卷|数据保留|数据将擦除|数据将擦除\n根设备卷|卷将保留|卷将保留|默认情况下将删除卷\n记账功能|实例计费小时不更改|实例的状态一旦变为 stopping，就不再产生与该实例相关的费用。每次实例从 stopped 转换为 pending 时，我们都会启动新的实例计费小时|实例的状态一旦变为 shutting-down，就不再产生与该实例相关的费用\n\n## 存储\n> * Amazon EBS\n> * Amazon EC2 实例存储卷（停止实例时，会删除数据，只有部分实例有）\n> * Amazon S3\n\n![Screenshot](../../images/aws/architecture_storage.png)\n\n\n# 4 AWS VPC\n\n## VPC 中几个概念\n\n***VPC***\n\n* VPC 即 virtual private cloud，是个虚拟的局域网\n* AWS 云中的一个私有的、隔离的部分\n* 可自定义的虚拟网络拓扑\n\n***子网***\n\nVPC 是为了将你的所有服务与外界隔离开来，但是范围比较大，如果你的局域网内部还需要进一步的网络划分，那么需要设置子网。子网位于 VPC 内部。\n\n***路由器***\n\n这个页面上没有\n\n***路由表***\n\n路由表创建在 VPC 上，创建时需要选择一个对应的 VPC\n\n在 VPC 内创建的所有路由表都会包含一条到达该 VPC 的路由项，而且不能删除。可以在此基础上再添加新路由项，如 Internet 网关。\n\n主要功能是将消息从 VPC 内发到 VPC 外，不是子网间使用的\n\n***Internet 网关***\n\n如果要上网，Internet 网关是必须的，创建好后还要将其关联到路由表。点击做导航“路由表”，在右面的列表选中一项，在下方的路由选项卡中可以点击“编辑”添加 Internet 网关\n\n***安全组***\n\n安全组是入站规则与出站规则的集合。安全组同样是建立在 VPC 上的，创建时需要指定 VPC\n\n***VPC 的地区***\n\n* 区域\n    * 相互隔离的地区区域\n* 可用区 (AZ)\n    * 数据中心\n\nAWS 有 10 个区域、每个区域有多个可用区\n\n## VPC 规划\n\n* 考虑将来的扩展\n* VPC 可以从 /16 到 /28\n* CIDR 不可修改\n* 考虑将来是否需要与公司网络建立链接\n* 重复的 IP 地址空间 = 未来的痛苦\n\n# 5 AWS 客户端\n\n## AWS CLI\nAWS CLI 是 AWS 提供的命令行工具，使用 Python 开发支持 Python 2.6.5 以上绝大多数\nPython 版本。\n\n### 安装\n\n在 Unix/Linux 平台安装 AWS CLI 建议使用 pip：\n\n```sh\npip install aws-cli\n```\n\n注意这里是\"aws-cli\"而不是\"aws\"\n\n个人还推荐一个叫做 saws 的 aws-cli 封装包，提供了强大的命令补全功能：\n\n```sh\npip install saws\n```\n\n\n### 配置\n\n在使用 aws-cli 之前，你首先需要配置好个人身份信息以及偏好区域。\n配置个人身份信息前还需要注册 AWS ISM，并为自己的身份授予对应的权限。\n\nAWS 的配置文件分 config 和 credentials，默认存储在 ~/.aws 目录中，格式如下：\n\n```ini\n# ~/.aws/credentials\n[default]\naws_access_key_id=XXXXXXXXXXXXXXXXXXXX\naws_secret_access_key=XXXXXXXXXXXXXXXXXXXX\n[dev]\naws_access_key_id=XXXXXXXXXXXXXXXXXXXX\naws_secret_access_key=XXXXXXXXXXXXXXXXXXXX\n[s3]\naws_access_key_id=XXXXXXXXXXXXXXXXXXXX\naws_secret_access_key=XXXXXXXXXXXXXXXXXXXX\n```\n\n```ini\n# ~/.aws/config\n[default]\nregion=cn-north-1\noutput=json\n[user1]\nregion=us-west-2\noutput=text\n```\n\n参数解释：\n\n- ``aws_access_key_id``：AWS 访问密钥。\n- ``aws_secret_access_key``：AWS 私有密钥。\n- ``aws_session_token``：AWS 会话令牌。只有在使用临时安全证书时才需要会话令牌。\n- ``region``：AWS 区域。\n- ``output``：输出格式（json、text 或 table）\n\n\n#### 环境变量\n\nAWS CLI 支持以下变量：\n\n- ``AWS_ACCESS_KEY_ID``：AWS 访问密钥。\n- ``AWS_SECRET_ACCESS_KEY``：AWS 私有密钥。访问和私有密钥变量会覆盖证书和 config 文件中存储的证书。\n- ``AWS_SESSION_TOKEN``：会话令牌。只有在使用临时安全证书时才需要会话令牌。\n- ``AWS_DEFAULT_REGION``：AWS 区域。如果设置，此变量会覆盖正在使用的配置文件的默认区域。\n- ``AWS_DEFAULT_PROFILE``：要使用的 CLI 配置文件的名称。可以是存储在证书或 config 文件中的配置文件的名称，也可以是 default，后者使用默认配置文件。\n- ``AWS_CONFIG_FILE``：CLI config 文件的路径。\n\n\n### 命令行参数\n\n\n参考：[AWS 官方文档](http://docs.aws.amazon.com/zh_cn/cli/latest/userguide/cli-chap-getting-started.html)\n\n### 使用\n\n```\naws ec2 describe-instances --profile dev\naws ec2 describe-instances --profile default\naws s3api put-object --body /root/start.sh --bucket bucket-name --key \"start.sh\"  --profile s3\n```\n\n## saws 工具\n\nsaws 是 aws-cli 封装包\n\n### S3\n\n***上传文件***\n\n前提：AWS 的配置中的访问密钥对 S3 的某 bucket-name 有权限\n\n输入 saws 后输入\n\n```\nsaws>aws s3api put-object --body /root/start.sh --bucket bucket-name --key \"start.sh\"\n{\n    \"ETag\": \"\\\"2bdd5dd11b4273cfb0a807539325xxx\\\"\"\n}\n\n```\n\n***下载文件***\n\n前提：AWS 的配置中的访问密钥对 S3 的某 bucket-name 有权限\n\n输入 saws 后输入\n\n```\nsaws>aws s3api get-object --bucket bucket-name --key \"start.sh\" /root/start.sh2\"\n{\n    \"AcceptRanges\": \"bytes\",\n    \"ContentType\": \"binary/octet-stream\",\n    \"LastModified\": \"Thu, 13 Oct 2016 02:55:48 GMT\",\n    \"ContentLength\": 241,\n    \"ETag\": \"\\\"2bdd5dd11b4273cfb0a807539325xxx\\\"\",\n    \"Metadata\": {}\n}\n```\n\n## S3cmd\n### 下载及配置\n\n在 Linux 上 安装 s3 客户端\n\n[下载 s3cmd](https://raw.githubusercontent.com/meetbill/op_practice_code/master/cloud/aws/s3cmd-2.0.0.tar.gz)\n\n下载后解压进入到目录中\n\n连接 AWS S3 时可以通过`s3cmd --config`进行配置，`连接本地自有的存储可以配置如下`\n\n在当前用户的家目录下创建 .s3cfg 文件、（也是 s3cmd 默认配置文件路径、)，并填入以下内容：\n\n```\n[default]\naccess_key = XXXXXXXXXXXXXXXXXXXX\nsecret_key = XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\nhost_base = 10.20.144.2\nhost_bucket = 10.20.144.2:80/%(bucket)\nuse_https = False\n# 签名是 V2 的话设置为 True\nsignature_v2 = True\n```\n### 使用 S3cmd\n\n配置完 S3cmd 就可以通过它来使用对象存储了。\n\n对象存储中有两个非常重要的概念，bucket 和 object。object 对应需要存储的文件，而 bucket 作为 object 的存储空间。所以对象存储的操作主要涉及到的就是对 bucket 和 object 的操作。\n\n1. 操作 bucket\n\n * **列举 bucket**\n   ```\n   # s3cmd ls\n   ```\n\n   以列举当前的 bucket 为例：\n\n   ```\n   # s3cmd ls\n   2015-08-12 03:56  s3://test-bucket_1\n   ```\n\n * **创建 bucket**\n\n   ```\n   # s3cmd mb s3://BUCKET\n   ```\n\n   以创建名为 test\\_bucket\\_1 的 bucket 为例：\n\n   ```\n   # s3cmd mb s3://test-bucket_1\n   Bucket 's3://test-bucket_1/' created\n   ```\n\n * **删除 bucket**\n\n   ```\n   # s3cmd rb s3://BUCKET\n   ```\n\n   以删除我们刚创建的 test\\_bucket\\_1 为例：\n\n   ```\n   # s3cmd rb s3://test-bucket_1\n   Bucket 's3://test-bucket_1/' removed\n   ```\n\n2. 操作 object\n\n   需要在 S3 中存储的文件在对象存储中被称为 object。为了说明上传 object 的过程，首先我们来创建一个用来上传的文件 test.txt （也就是一个 object), 并写入 samplecontent 作为文件的内容：\n\n * **准备文件**\n   ```\n   # cat test.txt\n   samplecontent\n   ```\n\n * **创建 bucket**\n   ```\n   # s3cmd mb s3://test-bucket_2\n   Bucket 's3://test-bucket_2/' created\n   ```\n\n * **上传 object**\n\n   ```\n   s3cmd put FILE [FILE...] s3://BUCKET[/PREFIX]\n   ```\n\n   以上传刚创建的 test.txt 文件到 test\\_bucket\\_2 bucket 为例：\n\n   ```\n   # s3cmd put test.txt s3://test-bucket_2\n   WARNING: Module python-magic is not available. Guessing MIME types based on file extensions.\n   test.txt -> s3://test-bucket_2/test.txt  [1 of 1]\n   14 of 14   100% in    0s    20.59 kB/s\n   14 of 14   100% in   90s     0.16 B/s  done\n   ```\n\n * **列出 bucket 中 object**\n\n   ```\n   # s3cmd ls [s3://BUCKET[/PREFIX]]\n   ```\n\n   以列出当前 bucket 中的 object:\n\n   ```\n   # s3cmd ls s3://test-bucket_2\n   2015-08-12 04:22        14   s3://test-bucket_2/test.txt\n   ```\n\n * **下载 bucket 中 object**\n\n   ```\n   # s3cmd get s3://BUCKET/OBJECT LOCAL_FILE\n   ```\n\n   下载当前 bucket 中的文件 test.txt，并本地命名 localtest.txt：\n\n   ```\n   # s3cmd get s3://test-bucket-2/test.txt  localtest.txt\n   s3://test-bucket-2/test.txt -> localtest.txt  [1 of 1]\n   s3://test-bucket-2/test.txt -> localtest.txt  [1 of 1]\n   348 of 348   100% in    0s    10.58 kB/s  done\n   ```\n\n * **删除 bucket 中 object**\n\n   ```\n   # s3cmd del s3://BUCKET/FILENAME\n   ```\n\n   删除当前 bucket 中的 test.txt 对象：\n\n   ```\n   # s3cmd del s3://test-bucket-2/test.txt\n   File s3://test-bucket-2/test.txt delete\n   ```\n\n * **拷贝 bucket 中 object**\n\n   ```\n   # s3cmd cp s3://BUCKET1/OBJECT1 s3://BUCKET2[/OBJECT2]\n   ```\n\n   拷贝对象，从一个 bucket 到另一个 bucket：\n\n   ```\n   # s3cmd cp s3://test-bucket-2/test1.txt s3://test-bucket-1/test1.txt\n   WARNING: Retrying failed request: /test1.txt ()\n   WARNING: Waiting 3 sec...\n   File s3://test-bucket-2/test1.txt copied to s3://test-bucket-1/test1.tx\n   ```\n\n * **获取 object 信息**\n\n   ```\n   # s3cmd info s3://BUCKET/OBJECT\n   ```\n\n   获取当前 object 的信息：\n\n   ```\n   # s3cmd info s3://test-bucket-2/test1.txt\n   s3://test-bucket-2/test1.txt (object):\n   File size: 348\n   Last mod:  Fri, 14 Aug 2015 02:02:37 GMT\n   MIME type: text/plain\n   MD5 sum:   4b49d7dd076b0b71e0eda307388fac57\n   SSE:       NONE\n   ```\n\n其余使用请参见 S3cmd usage: [S3cmd 使用手册](http://s3tools.org/usage \"s3cmd 使用手册\")\n\n"
  },
  {
    "path": "doc/cloud/docker.md",
    "content": "# Docker\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 CentOS7 安装 Docker](#1-centos7-安装-docker)\n    * [1.1 准备](#11-准备)\n    * [1.2 安装 Docker](#12-安装-docker)\n        * [1.2.1 本地源安装](#121-本地源安装)\n        * [1.2.2 网络源安装](#122-网络源安装)\n    * [1.3 卸载 Docker](#13-卸载-docker)\n        * [1.3.1 列出安装的 Docker](#131-列出安装的-docker)\n        * [1.3.2 删除安装包](#132-删除安装包)\n        * [1.3.3 删除数据文件](#133-删除数据文件)\n    * [1.4 日志](#14-日志)\n* [2 Docker 基础](#2-docker-基础)\n    * [2.1 Docker 三大核心概念](#21-docker-三大核心概念)\n    * [2.2 Docker 镜像使用](#22-docker-镜像使用)\n        * [2.2.1 Docker tag](#221-docker-tag)\n        * [2.2.2 导入导出镜像](#222-导入导出镜像)\n    * [2.3 Docker 网络](#23-docker-网络)\n    * [2.4 私有仓库](#24-私有仓库)\n        * [2.4.1 环境准备](#241-环境准备)\n        * [2.4.2 搭建](#242-搭建)\n            * [搭建步骤](#搭建步骤)\n            * [常见问题](#常见问题)\n        * [2.4.3 在 docker 客户机验证](#243-在-docker-客户机验证)\n        * [2.4.4 客户机 config.json](#244-客户机-configjson)\n* [3 Dockerfile 最佳实践](#3-dockerfile-最佳实践)\n    * [3.1 Dockerfile 建议](#31-dockerfile-建议)\n    * [3.2 编写 Dockerfile](#32-编写-dockerfile)\n* [4 Docker 应用](#4-docker-应用)\n    * [4.1 MySQL](#41-mysql)\n* [5 其他](#5-其他)\n    * [5.1 CentOS 6.5 上安装 Docker](#51-centos-65-上安装-docker)\n    * [5.2 Alpine Linux](#52-alpine-linux)\n* [6 Docker 常见问题](#6-docker-常见问题)\n    * [6.1 Docker 容器故障致无法启动解决实例](#61-docker-容器故障致无法启动解决实例)\n    * [6.2 启动容器失败](#62-启动容器失败)\n    * [6.3 CentOS7 上运行容器挂载卷没有写入权限](#63-centos7-上运行容器挂载卷没有写入权限)\n    * [6.4 docker 修改 image 存储目录](#64-docker-修改-image-存储目录)\n* [7 原理](#7-原理)\n    * [7.1 Docker 背后的内核知识](#71-docker-背后的内核知识)\n* [8 API](#8-api)\n    * [8.1 API 使用前准备](#81-api-使用前准备)\n    * [8.2 操作 docker API](#82-操作-docker-api)\n        * [8.2.1 imasge 列表](#821-imasge-列表)\n        * [8.2.2 容器列表](#822-容器列表)\n        * [8.2.3 容器创建](#823-容器创建)\n            * [8.2.3.1 端口映射](#8231-端口映射)\n            * [8.2.3.2 磁盘映射](#8232-磁盘映射)\n            * [8.2.3.3 指定 Entrypoint](#8233-指定-entrypoint)\n        * [8.2.4 容器操作](#824-容器操作)\n\n<!-- vim-markdown-toc -->\n\n# 1 CentOS7 安装 Docker\n## 1.1 准备\nCentOS7 x86-64\n\n查看版本\n```\n#uname -r\n3.10.0-123.el7.x86_64\n```\n## 1.2 安装 Docker\n### 1.2.1 本地源安装\n\nCentOS 7.3 离线安装 Docker-ce(1703)\n\n```\n[root@meetbill ~]#curl -o docker_install.tar.gz https://raw.githubusercontent.com/meetbill/op_practice_code/master/cloud/docker/docker_install.tar.gz\n[root@meetbill ~]#tar -zxvf docker_install.tar.gz\n[root@meetbill ~]#cd docker_install\n[root@meetbill ~]#sh install.sh\n[root@meetbill ~]#systemctl start docker\n\n```\n### 1.2.2 网络源安装\n**添加 Docker 版本仓库**\n```\ncat >/etc/yum.repos.d/docker.repo <<-'EOF'\n[dockerrepo]\nname=Docker Repository\nbaseurl=https://yum.dockerproject.org/repo/main/centos/7\nenabled=1\ngpgcheck=1\ngpgkey=https://yum.dockerproject.org/gpg\n\n[docker-ce-stable]\nname=Docker CE Stable - $basearch\nbaseurl=https://download.docker.com/linux/centos/7/$basearch/stable\nenabled=1\ngpgcheck=1\ngpgkey=https://download.docker.com/linux/centos/gpg\nEOF\n```\n**安装 Docker**\n\ndocker 在 17 年 3 月份后，Docker 分成了企业版（EE）和社区版（CE），转向基于时间的 YY.MM 形式的版本控制方案，17.03 相当于 1.13.1 版本\n```\n#yum install docker-ce\n```\n安装旧版本 (1.12) 方法 `yum install docker-engine`\n\n**设置 Docker 开机自启动**\n```\n#systemctl enable docker.service\n```\n**启动 Docker daemon**\n```\n#systemctl start docker\n```\n**验证 Docker 安装是否成功**\n```\n#docker run --rm hello-world\n--------------------------------------------------- 以下是程序输出\nUnable to find image 'hello-world:latest' locally\nlatest: Pulling from library/hello-world\nc04b14da8d14: Pull complete\nDigest: sha256:0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9\nStatus: Downloaded newer image for hello-world:latest\n\nHello from Docker!\nThis message shows that your installation appears to be working correctly.\n\nTo generate this message, Docker took the following steps:\n   1. The Docker client contacted the Docker daemon.\n   2. The Docker daemon pulled the \"hello-world\" image from the Docker Hub.\n   3. The Docker daemon created a new container from that image which runs the\n      executable that produces the output you are currently reading.\n   4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.\n\nTo try something more ambitious, you can run an Ubuntu container with:\n$ docker run -it ubuntu bash\n\nShare images, automate workflows, and more with a free Docker Hub account:\n https://hub.docker.com\n\nFor more examples and ideas, visit:\n https://docs.docker.com/engine/userguide/\n```\n**创建 Docker 组**\n\n将 host 下的普通用户添加到 docker 组中后，可以不使用 sudo 即可执行 docker 程序（只是减少了每次使用 sudo 时输入密码的过程罢了，其实 docker 本身还是以 sudo 的权限在运行的。)\n```\nsudo usermod -aG docker your_username\n```\n**其他配置**\n\n设置 ipv4 转发 (CentOS 上需要配置），实践中发现 Ubuntu 和 SUSE 上无需配置\n\n查看\n```\n[root@meetbill ~]#sysctl net.ipv4.ip_forward\n```\n临时更改\n```\n[root@meetbill ~]#sysctl -w net.ipv4.ip_forward=1\n\n```\n永久更改\n```\n[root@meetbill ~]#echo \"net.ipv4.ip_forward=1\" >> /etc/sysctl.conf\n[root@meetbill ~]#sysctl -p\n[root@meetbill ~]#sysctl net.ipv4.ip_forward\n\n```\n## 1.3 卸载 Docker\n### 1.3.1 列出安装的 Docker\n```\nyum list installed | grep docker\n```\n### 1.3.2 删除安装包\n```\nsudo yum -y remove docker-engine.x86_64\n```\n### 1.3.3 删除数据文件\n```\nrm -rf /var/lib/docker\n```\n\n## 1.4 日志\n\nDocker daemon 日志的位置，根据系统不同各不相同。\n\n* Ubuntu - /var/log/upstart/docker.log\n* CentOS - /var/log/daemon.log | grep docker\n* Red Hat Enterprise Linux Server - /var/log/messages | grep docker\n\n# 2 Docker 基础\n## 2.1 Docker 三大核心概念\n- 镜像 Image\n镜像就是一个只读的模板。比如，一个镜像可以包含一个完整的 CentOS 系统，并且安装了 zabbix\n镜像可以用来创建 Docker 容器。\n其他人制作好镜像，我们可以拿过来轻松的使用。这就是吸引我的特性。\n- 容器 Container\nDocker 用容器来运行应用。容器是从镜像创建出来的实例（好有面向对象的感觉，类和对象），它可以被启动、开始、停止和删除。\n- 仓库 Repository\n个好理解了，就是放镜像的文件的场所。比如最大的公开仓库是 Docker Hub。\n\n## 2.2 Docker 镜像使用\n\n当运行容器时，使用的镜像如果在本地中不存在，docker 就会自动从 docker 镜像仓库中下载，默认是从 Docker Hub 公共镜像源下载。\n下面我们来学习：\n\n> * 管理和使用本地 Docker 主机镜像\n> * 拖取公共镜像源中的镜像\n> * 创建镜像\n\n### 2.2.1 Docker tag\n\ndocker tag : 标记本地镜像，将其归入某一仓库。\n\n**语法**\n```\ndocker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]\n```\n**实例**\n\n将镜像 Ubuntu:15.10 标记为 runoob/ubuntu:v3 镜像。\n```\nroot@runoob:~# docker tag ubuntu:15.10 runoob/ubuntu:v3\nroot@runoob:~# docker images   runoob/ubuntu:v3\nREPOSITORY          TAG                 IMAGE ID            CREATED             SIZE\nrunoob/ubuntu       v3                  4e3b13c8a266        3 months ago        136.3 MB\n```\n\n### 2.2.2 导入导出镜像\n\n导出 #docker save -o zabbix.tar meetbill/zabbix\n\n导入 #docker load -i zabbix.tar\n\n```\n注意：导出镜像时使用 imagesid 导出后，如下，导入镜像时 REPOSITORY 和 TAG 会为 <none>（我个人认为是一个 imagesid 可对应多组 REPOSITORY 和 TAG 的原因）\n#docker save -o zabbix.tar imagesid\n```\n\n## 2.3 Docker 网络\n\nDocker 的网络模式大致可以分成四种类型，在安装完 Docker 之后，宿主机上会创建三个网络，分别是 bridge 网络，host 网络，none 网络，可以使用 docker network ls 命令查看。\n\nbridge 方式（默认）、none 方式、host 方式、container 复用方式\n\n1、Bridge 方式： --network=bridge\n\n容器与 Host 网络是连通的：\neth0 实际上是 veth pair 的一端，另一端（vethb689485）连在 docker0 网桥上\n通过 Iptables 实现容器内访问外部网络\n\n2、None 方式： --network=none\n\n这样创建出来的容器完全没有网络，将网络创建的责任完全交给用户。可以实现更加灵活复杂的网络。\n另外这种容器可以可以通过 link 容器实现通信。\n\n3、Host 方式： --network=host\n\n容器和主机公用网络资源，使用宿主机的 IP 和端口\n这种方式是不安全的。如果在隔离良好的环境中（比如租户的虚拟机中）使用这种方式，问题不大。\n\n4、Container 复用方式： --network=container:name or id\n\n新创建的容器和已经存在的一个容器共享一个 IP 网络资源\n\n## 2.4 私有仓库\n### 2.4.1 环境准备\n\nip\n```\nrole\tip\ndocker 仓库机\t192.168.1.52\ndocker 客户机\t192.168.1.136\n```\n\n### 2.4.2 搭建\n\n#### 搭建步骤\n\n**(1) 搭建仓库 registry**\n```\ndocker pull regsity\n```\n**基于私有仓库镜像运行容器**\n```\n> docker run -d --name registry --restart always -p 5000:5000 -v  /data/registry:/var/lib/registry registry\n```\n**(2) 访问私有仓库**\n```\n>curl -X GET http://192.168.1.52:5000/v2/_catalog\n{\"repositories\":[]}   #私有仓库为空，没有提交新镜像到仓库中\n```\n**(3) 为基础镜像打个标签**\n\n根据 images 建立 tag,xxxxxxx 为某镜像 id 或 name\n\ndocker tag xxxxxxx 192.168.1.52:5000/zabbix\n\n**(4) 改 Docker 配置文件制定私有仓库 url**\n\n> echo '{ \"insecure-registries\":[\"192.168.1.52:5000\"] }' > /etc/docker/daemon.json\n> systemctl restart docker\n\nps:\n```\n此步因系统而异，有些是修改 /etc/sysconfig/docker 文件\n```\n\n**(5) 提交镜像到本地私有仓库中**\n\ndocker push 192.168.1.52:5000/zabbix\n\n**(6) 查看私有仓库是否存在对应的镜像**\n\nroot@localhost ~\n> `curl -X GET http://192.168.1.52:5000/v2/_catalog`\n```\n{\"repositories\":[\"zabbix\"]}\n```\n> curl -X GET http://192.168.1.52:5000/v2/zabbix/tags/list\n```\n{\"name\":\"zabbix\",\"tags\":[\"latest\"]}\n```\n#### 常见问题\n\n提交镜像到本地仓库时异常：\n\n> 提示非 https\n```\n启动项增加\n--insecure-registry  192.168.1.52:5000\n```\n\n> [received unexpected HTTP status: 500 Internal Server](https://github.com/docker/distribution-library-image/issues/89)\n```\n使用 registry:2.6.2 image\n```\n\n### 2.4.3 在 docker 客户机验证\n\n**(1) 修改 Docker 配置文件**\n\n```\necho '{ \"insecure-registries\":[\"192.168.1.52:5000\"] }' > /etc/docker/daemon.json\nsystemctl restart docker\n```\n**(2) 从私有仓库中下载已有的镜像**\n\n```\ndocker pull 192.168.1.52:5000/centos\n```\n至此，私有仓库已 OK\n\n### 2.4.4 客户机 config.json\n\n路径：~/.docker/config.json\n\nconfig.json 是用于存储 docker registry 的认证信息\n```\n\n{\n\t\"auths\": {\n\t\t\"harbor.xxx.com\": {\n\t\t\t\"auth\": \"xxx\"\n\t\t}\n\t}\n}\n\n```\nauth 后面的内容其实用户名密码的加密后的输出\n\n```\n// 加密\necho -n \"user:password\" | base64\n\n// 解密\necho -n \"xxx\" | base64 -d\n```\n\n> python\n```\n>>> import base64\n>>> base64.b64encode(\"user:password\")\n'dXNlcjpwYXNzd29yZA=='\n>>> base64.b64decode(\"dXNlcjpwYXNzd29yZA==\")\n'user:password'\n```\n\n\n\n# 3 Dockerfile 最佳实践\n\n## 3.1 Dockerfile 建议\n\n**1、挑选合适的基础镜像**\n\n基础镜像尽量选最小的镜像\n\n如果是对系统没有过深入学习的可使用比较成熟的基础镜像，如 `Ubuntu`,`CentOS` 等，因为基础镜像只需要下载一次即可共享，并不会造成太多的存储空间浪费。它的好处是这些镜像的生态比较完整，方便我们调试\n\n**2、优化 apt-get/yum 相关操作**\n\n将多个安装软件操作合并成一个，安装完成后使用 clean 清理一下\n\n**3、动静分离**\n\n经常变化的内容和基本不会变化的内容要分开，把不怎么变化的内容放在下层，创建出来不同基础镜像供上层使用。比如可以创建各种语言的基础镜像， `python2.7`、`python3.5`、`go1.7`、`java7`等等，这些镜像包含了最基本的语言库，每个组可以在上面继续构建应用级别的镜像。\n\n**4、最小原则：只安装必需的东西**\n\n很多人构建镜像时，会将可能用到的东西都打包到镜像中。必须要遏制这种想法，镜像中应该***只包含必需的东西***，任何可以有也可以没有的东西就不需要放在里面了。因为镜像的扩展很容易，而且运行容器的时候也很方便地对其进行修改。这样可以保证镜像尽可能的小，构建的时候尽可能的快，也保证未来的更快传输、更省网络资源。\n\n**5、使用更少的层**\n\n虽然看起来把不同的命令尽量分开来，写在多个命令中容易阅读和理解。但是这样会导致出现太多的镜像层，从而不好管理和分析镜像，而且镜像的层是有限的。尽量把内容相关的内容放到同一个层，使用换行符进行分割，这样可以进一步减小镜像大小，并且方便查看镜像历史。\n\n**6、减少每层的内容**\n\n尽管只安装必须的内容，在这个过程中也可能会产生额外的内容或者临时文件，我们要尽量让每层安装的东西保持最小。\n\t- 比如使用 `--no-install-recommends` 参数告诉 `apt-get` 不要安装推荐的软件包\n\t- 安装完软件包，清除 `/var/lib/apt/list/` 缓存\n\t- 删除中间文件：比如下载的压缩包，或者是只用了一次的软件包\n\t- 删除临时文件：如果命令产生了临时文件，也要及时删除\n\n**7、不要在 `Dockerfile` 中修改文件的权限**\n\n因为 `docker` 镜像是分层的，任何修改都会新增一个层，修改文件或者目录权限也是如此。如果修改大文件或者目录的权限，会把这些文件复制一份，这样很容易导致镜像很大。<br>\n解决方案也很简单，要么在添加到 `Dockerfile` 之前就把文件的权限和用户设置好，要么在容器启动脚本 (**entrypoint**) 中做些修改。\n\n**8、合理使用 ADD 命令**\n\n> * DD 命令和 COPY 命令在很大程度上功能是一样的，但是 COPY 语义更加直接。但是唯一例外的是 ADD 命令自带解压功能，如果需要拷贝并解压一个文件到镜像中，我们可以使用 ADD 命令，除此之外，推荐使用 COPY。<br>\n> * 如果是使用 ADD 命令来获取网络资源，是不推荐的。网络资源应该使用 RUN wget 或者 curl 命令来获取。\n\n总之，优先使用 COPY\n\n## 3.2 编写 Dockerfile\n\n> COPY\n```\n(1) 如果源路径是个文件，且目标路径是以 / 结尾， 则 docker 会把目标路径当作一个目录，会把源文件拷贝到该目录下。\n如果目标路径不存在，则会自动创建目标路径。\n\n(2) 如果源路径是个文件，且目标路径是不是以 / 结尾，则 docker 会把目标路径当作一个文件。\n如果目标路径不存在，会以目标路径为名创建一个文件，内容同源文件；\n如果目标文件是个存在的文件，会用源文件覆盖它，当然只是内容覆盖，文件名还是目标文件名。\n如果目标文件实际是个存在的目录，则会源文件拷贝到该目录下。 注意，这种情况下，最好显示的以 / 结尾，以避免混淆。\n\n(3) 如果源路径是个目录，且目标路径不存在，则 docker 会自动以目标路径创建一个目录，把源路径目录下的文件拷贝进来。\n如果目标路径是个已经存在的目录，则 docker 会把源路径目录下的文件拷贝到该目录下。\n```\n\n# 4 Docker 应用\n## 4.1 MySQL\n**(1) 拉取镜像**\n这里我们拉取官方的镜像，标签为 5.6\n\nmeetbill@Linux:~$ docker pull mysql:5.6\n\n等待下载完成后，我们就可以在本地镜像列表里查到 REPOSITORY 为 mysql, 标签为 5.6 的镜像。\n\n**(2) 使用 mysql 镜像**\n\n运行容器\n```\nmeetbill@Linux:~$mkdir mysql;cd mysql\nmeetbill@Linux:~/mysql$ docker run -d \\\n--restart always \\\n-p 3306:3306 \\\n--name mymysql \\\n-v $PWD/data:/var/lib/mysql \\\n-e MYSQL_ROOT_PASSWORD=123456  mysql:5.6\n```\n命令说明：\n> * -p 3306:3306：将容器的 3306 端口映射到主机的 3306 端口\n> * -v $PWD/data:/var/lib/mysql：将主机当前目录下的 data 目录挂载到容器的 /mysql_data\n> * -e MYSQL_ROOT_PASSWORD=123456：初始化 root 用户的密码\n\n**(3) 进入 mysql 容器**\n\n```\n$docker ps\n$docker exec -it 775c7c9ee1e1 /bin/bash\nor\n$docker exec -it mymysql /bin/bash\n```\n\n# 5 其他\n\n## 5.1 CentOS 6.5 上安装 Docker\n\n```\nrpm -ivh http://dl.Fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm\nrpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-EPEL-6\nyum -y install docker-io\n// 更新 device-mapper-libs\nyum install device-mapper-*\n/etc/init.d/docker start\n```\n**常见错误**\n```\n启动 docker 报错，错误 log：\nINFO[0000] Listening for HTTP on unix (/var/run/docker.sock)\nWARN[0000] You are running linux kernel version 2.6.32-431.el6.x86_64, which might be unstable running docker. Please upgrade your kernel to 3.10.0.\n\ndocker: relocation error: docker: symbol dm_task_get_info_with_deferred_remove, version Base not defined in file libdevmapper.so.1.02 with link time reference\n\n原因：是因为 libdevmapper 版本太旧，需要 update〖yum install device-mapper-*〗\n```\n\n## 5.2 Alpine Linux\nAlpine Linux 打出的包非常小\n\nAlpine Linux, 一个只有 5M 的 Docker 镜像\n\n\n# 6 Docker 常见问题\n\n## 6.1 Docker 容器故障致无法启动解决实例\ndocker zabbix-server 启动异常退出后，启动失败，解决的方法如下\n\n查找启动文件\n```\nroot@ubuntu:~#find / -name 'docker-zabbix'\n/xxxx/subvolumes/2086357831.../bin/docker-zabbix\n/xxxx/subvolumes/080fd911a6.../bin/docker-zabbix\n/xxxx/subvolumes/87bb2f9818...-init/bin/docker-zabbix\n/xxxx/87bb2f98185649304c505.../bin/docker-zabbix\n```\n修改配置文件进行调试（多输出一些信息进行判断和调试）\n\n## 6.2 启动容器失败\n提示如下\n```\nError response from daemon: driver failed programming external connectivity on endpoint zabbix (f76e6128eb80f9b9b2a50bc8642d7d9d25dc491b58fcccadcc700943487960bd):  (iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 10080 -j DNAT --to-destination 172.17.0.11:80 ! -i docker0: iptables: No chain/target/match by that name.\n (exit status 1))\nError: failed to start containers: zabbix\n```\n解决方法\n```\n重启 Docker\n#systemctl restart docker\n```\n\n## 6.3 CentOS7 上运行容器挂载卷没有写入权限\n\n在 CentOS7 中运行容器，发现挂载的本地目录在容器中没有执行权限，原因是 CentOS7 中的安全模块 selinux 把权限禁掉了，至少有以下三种方式解决挂载的目录没有权限的问题：\n\n1，在运行容器的时候，给容器加特权：\n\n示例：docker run -i -t --privileged=true -v /home/docs:/src waterchestnut/nodejs:0.12.0\n\n2，临时关闭 selinux：\n\n示例：su -c \"setenforce 0\"\n\n之后执行：docker run -i -t -v /home/docs:/src waterchestnut/nodejs:0.12.0\n\n注意：之后要记得重新开启 selinux，命令：su -c \"setenforce 1\"\n\n3，添加 selinux 规则，将要挂载的目录添加到白名单：\n\n示例：chcon -Rt svirt_sandbox_file_t /home/docs\n\n之后执行：docker run -i -t -v /home/docs:/src waterchestnut/nodejs:0.12.0\n\n## 6.4 docker 修改 image 存储目录\n\ndocker 安装好后默认 image 存储目录在 /var/lib/docker 目录下，但是通常这个目录挂载的空间很小，所以我们在安装好 docker 后要注意修改 image 存储目录\n\n> 查看当前目录\n```\n$ docker info\nContainers: 3\nImages: 33\nStorage Driver: devicemapper\n Pool Name: docker-8:33-60817411-pool\n Pool Blocksize: 65.54 kB\n Backing Filesystem: extfs\n Data file: /dev/loop2\n Metadata file: /dev/loop3\n Data Space Used: 984.4 MB\n Data Space Total: 107.4 GB\n Data Space Available: 106.4 GB\n Metadata Space Used: 1.987 MB\n Metadata Space Total: 2.147 GB\n Metadata Space Available: 2.145 GB\n Udev Sync Supported: true\n Deferred Removal Enabled: false\n Data loop file: /home/disk2/docker/devicemapper/devicemapper/data\n Metadata loop file: /home/disk2/docker/devicemapper/devicemapper/metadata\n Library Version: XXXXXX\nExecution Driver: native-0.2\nLogging Driver: json-file\nKernel Version: 3.10.0_3-0-0-26\nOperating System: <unknown>\nCPUs: 48\nTotal Memory: 125.5 GiB\nName: HOSTNAME\nID: XXXXX\n```\n当然我这个是修改后的，修改后为、/home/disk2/docker 目录下\n\n> 修改目录（配置文件 /etc/sysconfig/docker)\n```\n# /etc/sysconfig/docker\n#\n# Other arguments to pass to the docker daemon process\n# These will be parsed by the sysv initscript and appended\n# to the arguments list passed to docker -d\n\nother_args=\"--graph=/home/docker\"\nDOCKER_CERT_PATH=/etc/docker\n\n# Resolves: rhbz#1176302 (docker issue #407)\nDOCKER_NOWARN_KERNEL_VERSION=1\n\n# Location used for temporary files, such as those created by\n# # docker load and build operations. Default is /var/lib/docker/tmp\n# # Can be overriden by setting the following environment variable.\n# # DOCKER_TMPDIR=/var/tmp\n```\n在配置文件中将 other_args 赋值为你想存储的目录，注意一定要带引号\n\n> 重启 docker\n```\nservice docker restart\n```\n\n# 7 原理\n\n## 7.1 Docker 背后的内核知识\n\ndocker 容器的本质是宿主机上的一个进程。\n\nDocker 通过 namespace 实现了资源隔离，通过 cgroups 实现了资源限制，通过*写时复制机制（copy-on-write）*实现了高效的文件操作。\n\n> * Namespace：隔离技术的第一层，确保 Docker 容器内的进程看不到也影响不到 Docker 外部的进程。\n> * Control Groups：LXC 技术的关键组件，用于进行运行时的资源限制。\n> * UnionFS（文件系统）：容器的构件块，创建抽象层，从而实现 Docker 的轻量级和运行快速的特性\n\n# 8 API\n\n## 8.1 API 使用前准备\n```\nOS: Centos8.4\nDocker: 24.0.5\n```\n\n> /usr/lib/systemd/system/docker.service\n```\n[Unit]\nDescription=Docker Application Container Engine\nDocumentation=https://docs.docker.com\nAfter=network-online.target docker.socket firewalld.service containerd.service time-set.target\nWants=network-online.target containerd.service\nRequires=docker.socket\n\n[Service]\nType=notify\n# the default is not to use systemd for cgroups because the delegate issues still\n# exists and systemd currently does not support the cgroup feature set required\n# for containers run by docker\nExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock\nExecReload=/bin/kill -s HUP $MAINPID\nTimeoutStartSec=0\nRestartSec=2\nRestart=always\n\n# Note that StartLimit* options were moved from \"Service\" to \"Unit\" in systemd 229.\n# Both the old, and new location are accepted by systemd 229 and up, so using the old location\n# to make them work for either version of systemd.\nStartLimitBurst=3\n\n# Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230.\n# Both the old, and new name are accepted by systemd 230 and up, so using the old name to make\n# this option work for either version of systemd.\nStartLimitInterval=60s\n\n# Having non-zero Limit*s causes performance problems due to accounting overhead\n# in the kernel. We recommend using cgroups to do container-local accounting.\nLimitNOFILE=infinity\nLimitNPROC=infinity\nLimitCORE=infinity\n\n# Comment TasksMax if your systemd version does not support it.\n# Only systemd 226 and above support this option.\nTasksMax=infinity\n\n# set delegate yes so that systemd does not reset the cgroups of docker containers\nDelegate=yes\n\n# kill only the docker process, not all processes in the cgroup\nKillMode=process\nOOMScoreAdjust=-500\n\n[Install]\nWantedBy=multi-user.target\n```\n\n在 ExecStart=/usr/bin/dockerd 后面直接添加 -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock （注意端口 8088 自己随便定义，别跟当前的冲突即可）\n\n```\n$ systemctl daemon-reload\n$ systemctl restart docker\n```\n\n```\n$ curl -s http://127.0.0.1:4243/info | python2 -m json.tool\n```\n\n## 8.2 操作 docker API\n\n### 8.2.1 imasge 列表\n```\n$ curl -X GET http://127.0.0.1:4243/images/json\n```\n\n如\n```\n$ curl -s -X GET http://127.0.0.1:4243/images/json | python2 -m json.tool\n[\n    {\n        \"Containers\": -1,\n        \"Created\": 1693241633,\n        \"Id\": \"sha256:c631b267fd9ee8bfc3a10bf88d4346be67556a89eec7ca2bde969e0d29a70918\",\n        \"Labels\": null,\n        \"ParentId\": \"\",\n        \"RepoDigests\": [],\n        \"RepoTags\": [\n            \"butterfly:1.1.20.21\"\n        ],\n        \"SharedSize\": -1,\n        \"Size\": 76964781,\n        \"VirtualSize\": 76964781\n    }\n]\n```\n\n### 8.2.2 容器列表\n```\n$ docker run -d -p 8585:8585 --name butterfly_app butterfly:1.1.20.21\n\n$ curl -s -X GET http://127.0.0.1:4243/containers/json | python2 -m json.tool\n```\n\n> 如：\n```\n$ curl -s -X GET http://127.0.0.1:4243/containers/json | python2 -m json.tool\n[\n    {\n        \"Command\": \"sh /opt/butterfly/run.sh docker_start\",\n        \"Created\": 1693381603,\n        \"HostConfig\": {\n            \"NetworkMode\": \"default\"\n        },\n        \"Id\": \"ac14ff31c0b850123ba5182ad75b96b162cc883a892eb230c67ef284a270c24e\",\n        \"Image\": \"butterfly:1.1.20.21\",\n        \"ImageID\": \"sha256:c631b267fd9ee8bfc3a10bf88d4346be67556a89eec7ca2bde969e0d29a70918\",\n        \"Labels\": {},\n        \"Mounts\": [],\n        \"Names\": [\n            \"/butterfly_app\"\n        ],\n        \"NetworkSettings\": {\n            \"Networks\": {\n                \"bridge\": {\n                    \"Aliases\": null,\n                    \"DriverOpts\": null,\n                    \"EndpointID\": \"c0aba0aeb8aad2daeee8bc93a8fea4c9f88233b98e86d992d85fd0259771767b\",\n                    \"Gateway\": \"172.17.0.1\",\n                    \"GlobalIPv6Address\": \"\",\n                    \"GlobalIPv6PrefixLen\": 0,\n                    \"IPAMConfig\": null,\n                    \"IPAddress\": \"172.17.0.2\",\n                    \"IPPrefixLen\": 16,\n                    \"IPv6Gateway\": \"\",\n                    \"Links\": null,\n                    \"MacAddress\": \"02:42:ac:11:00:02\",\n                    \"NetworkID\": \"87b9faf1e682e8fdd66a6dae40aa61d27375e569022a9549d2c71ee206d2c8b1\"\n                }\n            }\n        },\n        \"Ports\": [\n            {\n                \"IP\": \"0.0.0.0\",\n                \"PrivatePort\": 8585,\n                \"PublicPort\": 8585,\n                \"Type\": \"tcp\"\n            },\n            {\n                \"IP\": \"::\",\n                \"PrivatePort\": 8585,\n                \"PublicPort\": 8585,\n                \"Type\": \"tcp\"\n            }\n        ],\n        \"State\": \"running\",\n        \"Status\": \"Up 47 seconds\"\n    }\n]\n```\n\n### 8.2.3 容器创建\n#### 8.2.3.1 端口映射\n\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\n    \"Image\": \"butterfly:1.1.20.21\",\n    \"ExposedPorts\": {\n        \"8585/tcp\": {}\n    },\n    \"HostConfig\": {\n        \"PortBindings\": {\n            \"8585/tcp\": [{\"HostPort\": \"8000\"}]\n        }\n    }\n}' http://127.0.0.1:4243/containers/create\n```\n\n> output\n```\n{\"Id\":\"6337ff5a53831eaa062ac07717ee44d62f681bfcc9c617ee8f9ee5908f21c264\",\"Warnings\":[]}\n```\n> 备注\n```\n$ docker ps -a\nCONTAINER ID   IMAGE                 COMMAND                  CREATED              STATUS             PORTS                                       NAMES\n6337ff5a5383   butterfly:1.1.20.21   \"sh /opt/butterfly/r…\"   About a minute ago   Created                                                        priceless_chebyshev\n\n\ncontainer 状态为 Created 状态，Docker 中的 created 状态表示该容器已被创建但还未启动。 您可以使用 docker start 命令来启动容器。\n此时的 PORTS 为空\n\n此时通过 docker inspect <container_id> 可以看到 {\"NetworkSettings\": {\"Ports\": {}}} 为 空 的状态\n```\n\n#### 8.2.3.2 磁盘映射\n\n```\n\"HostConfig\": {\n    \"Binds\": [\n        \"/mnt/container/docker_bind/xxx:/mnt/data\"\n    ]\n}\n```\n\n#### 8.2.3.3 指定 Entrypoint\n\n```\n// 启动 docker 内的 sshd\n\"Entrypoint\": [\"/bin/bash\", \"-c\", \"/usr/sbin/sshd && while true;do sleep 10;done\"]\n\n// Butterfly 启动\n\"Entrypoint\": [\"sh\", \"/opt/butterfly/run.sh\", \"docker_start\"]\n```\n\n### 8.2.4 容器操作\n```\n$ curl -X POST http://127.0.0.1:4243/containers/{id}/start      （注意这里是 POST 方法）\n$ curl -X POST http://127.0.0.1:4243/containers/{id}/stop       （注意这里是 POST 方法）\n$ curl -X POST http://127.0.0.1:4243/containers/{id}/restart    （注意这里是 POST 方法）\n```\n"
  },
  {
    "path": "doc/cloud/k8s.md",
    "content": "## K8s\n\n<!-- vim-markdown-toc GFM -->\n\n* [1 Kubernetes 概述](#1-kubernetes-概述)\n    * [1.1 简介](#11-简介)\n    * [1.2 特性](#12-特性)\n* [2 Kubernetes 设计架构](#2-kubernetes-设计架构)\n    * [2.1 官网体验教程](#21-官网体验教程)\n* [3 重要概念](#3-重要概念)\n* [4 使用 yaml 文件来部署应用实践](#4-使用-yaml-文件来部署应用实践)\n    * [4.1 扩容及缩容](#41-扩容及缩容)\n        * [创建 namespace](#创建-namespace)\n        * [创建应用](#创建应用)\n        * [扩容](#扩容)\n        * [创建 service 及访问 service](#创建-service-及访问-service)\n    * [4.2 版本更新](#42-版本更新)\n        * [push 新版本的 image](#push-新版本的-image)\n        * [更新 kube 的 image 版本](#更新-kube-的-image-版本)\n        * [查询版本](#查询版本)\n        * [检查是否符合预期](#检查是否符合预期)\n        * [回滚](#回滚)\n    * [4.3 小流量测试](#43-小流量测试)\n        * [创建 2 个 deployment](#创建-2-个-deployment)\n        * [创建访问节点](#创建访问节点)\n    * [4.4 自动扩容](#44-自动扩容)\n        * [清空之前环境](#清空之前环境)\n        * [创建 deployment](#创建-deployment)\n        * [设置自动扩容规则](#设置自动扩容规则)\n        * [设置 service](#设置-service)\n        * [压测服务](#压测服务)\n        * [删除自动扩容策略](#删除自动扩容策略)\n* [5 K8s 日常调试](#5-k8s-日常调试)\n    * [5.1 查看 Pod 生命周期的事件](#51-查看-pod-生命周期的事件)\n    * [5.2 查看资源 (CPU/Memory) 使用情况](#52-查看资源-cpumemory-使用情况)\n    * [5.3 如何摘下某个 Pod 进行 Debug](#53-如何摘下某个-pod-进行-debug)\n\n<!-- vim-markdown-toc -->\n## 1 Kubernetes 概述\n\n### 1.1 简介\nKubernetes 是一个开源的，用于管理云平台中多个主机上的容器化的应用，Kubernetes 的目标是让部署容器化的应用简单并且高效（powerful）,Kubernetes 提供了应用部署，规划，更新，维护的一种机制。\n\n### 1.2 特性\n\n> * 服务发现和负载均衡\n> * 自我修复 - 重新启动失败的容器\n> * 横向缩放 - 使用简单的命令或 UI，或者根据 CPU 的使用情况自动调整应用程序副本数\n> * 自动部署和回滚\n> * 密钥和配置管理 - 部署和更新密钥和应用程序配置，不会重新编译镜像，不会暴露\n> * 更多\n\n## 2 Kubernetes 设计架构\n\nKubernetes 集群包含有节点代理 kubelet 和 Master 组件 (APIs, scheduler, etc)，一切都基于分布式的存储系统。下面这张图是 Kubernetes 的架构图。\n\n![Screenshot](../../images/aws/k8s_arch.jpg)\n\nk8s 全景简图\n\n![Screenshot](../../images/aws/k8s_arch_simple.png)\n\n### 2.1 官网体验教程\n\nhttps://kubernetes.io/docs/tutorials/kubernetes-basics/\n\n官网通过如下 6 部分来实践如何玩转 k8s\n\n> * 创建 k8s 集群\n> * 部署应用\n> * 内部访问应用\n> * 外部访问应用\n> * Scale 应用\n> * 滚动更新\n\n## 3 重要概念\n\n> * Cluster : 计算，存储和网络资源的集合，K8s 利用这些资源运行各种基于容器的应用\n>   * 比如在百度 CCE 上首先需要创建个集群（购买 N 台 BCC 实例时，实际后台是创建了 N+3 实例，其中 3 台用于创建 Master 节点）\n> * Master : 主要负责调度，决定应用放在哪里运行\n> * Node ：Node 的职责是运行容器应用。\n>   * Node 由 Master 管理，Node 负责监控并汇报容器的状态\n>   * 根据 Master 的要求管理容器的生命周期\n> * Pod ：K8s 的最小工作单元。每个 Pod 包含一个或者多个容器。\n>   * Pod 中的容器会作为一个整体被 Master 调度到一个 Node 上运行\n> * Controller : K8s 通过 Controller 来管理 Pod，Controller 定义了 Pod 的部署特性：\n>   * Deployment: 记录版本更新信息，通过控制 replicaset，完成版本更新，回滚等操作\n>   * ReplicaSet: 控制 pods 数量\n>   * DaemonSet\n>   * StatefuleSet\n>   * Job\n> * Service：定义了外界访问一组 Pod 的方式\n>   * ClusterIP\n>   * NodePort\n>   * LoadBalancer\n> * Namespace\n>   * 多个用于或者项目可以使用同一个 cluster , 然后使用不同的 Namespace\n>   * --namespace=name 名\n\n## 4 使用 yaml 文件来部署应用实践\n\n### 4.1 扩容及缩容\n\n#### 创建 namespace\n\n> kubectl create -f namespace.yaml\n\nnamespace.yaml:\n```\napiVersion: v1\nkind: Namespace\nmetadata:\n  name: meetbill\n```\n> kubectl get deployments --namespace=meetbill\n\n空的\n\n#### 创建应用\n\n> kubectl create -f mypython.yaml\n\nmypython.yaml:\n```\napiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  name: mypython-deployment\n  namespace: meetbill\nspec:\n  replicas: 3\n  template:\n    metadata:\n      labels:\n        app: mypython\n        track: stable\n        version: 1.0.0\n    spec:\n      containers:\n        - name: mypython\n          image: \"hub.baidubce.com/xxxx/mypython:1.0.0\"\n          ports:\n            - name: http\n              containerPort: 8080\n```\n> * apiVersion 是当前配置格式的版本。\n> * kind 是要创建的资源类型，这里是 Deployment。\n> * metadata 是该资源的元数据，name 是必需的元数据项。\n> * spec 部分是该 Deployment 的规格说明。\n> * replicas 指明副本数量，默认为 1。\n> * template 定义 Pod 的模板，这是配置文件的重要部分。\n> * metadata 定义 Pod 的元数据，至少要定义一个 label。label 的 key 和 value 可以任意指定。\n> * spec 描述 Pod 的规格，此部分定义 Pod 中每一个容器的属性，name 和 image 是必需的。\n\n> kubectl get pods --namespace=meetbill 能看到实例是 3\n\n```\nNAME                                  READY     STATUS    RESTARTS   AGE\nmypython-deployment-b5f8f646d-b2dzs   1/1       Running   0          23s\nmypython-deployment-b5f8f646d-f7vwz   1/1       Running   0          23s\nmypython-deployment-b5f8f646d-vnqqm   1/1       Running   0          23s\n```\n\n#### 扩容\n\n> $kubectl scale deployments/mypython-deployment --replicas=4 --namespace=meetbill\n> $kubectl get pods  --namespace=meetbill 能看到实例已经变为 4 个\n\n#### 创建 service 及访问 service\n\n> kubectl create -f mypython-svc.yaml\n\nmypython-svc.yaml:\n```\napiVersion: v1\nkind: Service\nmetadata:\n  name: mypython-svc\n  namespace: meetbill\n  labels:\n    app: mypython\nspec:\n  ports:\n  - port: 8080\n    targetPort: 8080\n  type: NodePort\n  selector:\n    app: mypython\n```\n\n> export NODE_PORT=$(kubectl get services/mypython-svc -o go-template='{{(index .spec.ports 0).nodePort}}' --namespace=meetbill)\n\n获得 NODE_PORT\n\n> $while (true); do curl http://$VM_IP:$NODE_PORT; sleep 1;done\n\n重复的刷，能看到是多个实例的返回\n\n### 4.2 版本更新\n\n#### push 新版本的 image\n\n> $docker build -t hub.baidubce.com/xxxx/mypython:2.0.0 .\n> $docker push hub.baidubce.com/xxxx/mypython:2.0.0\n\n#### 更新 kube 的 image 版本\n\n> $kubectl set image deployments/mypython-deployment  mypython=hub.baidubce.com/xxxx/web-server:2.0 --namespace=meetbill\n\nmypython 为 Deployment 中 Pod 的 name 名\n\n#### 查询版本\n\n> $kubectl get pods --namespace=meetbill（能看到新的 pod 被创建，老 pod 被销毁）\n> $kubectl rollout status deployments/mypython-deployment --namespace=meetbill\n\n```\ndeployment \"mypython-deployment\" successfully rolled out\n```\n#### 检查是否符合预期\n\n> export NODE_PORT=$(kubectl get services/mypython-svc -o go-template='{{(index .spec.ports 0).nodePort}}' --namespace=meetbill)\n\n获得 NODE_PORT\n\n> $while (true); do curl http://$VM_IP:$NODE_PORT; sleep 1;done\n\n可以看到版本逐渐变为新版本\n\n#### 回滚\n\n> kubectl rollout undo deployment/mypython-deployment --namespace=meetbill 回滚\n\n### 4.3 小流量测试\n\n#### 创建 2 个 deployment\n> kubectl create -f deployments/mypython.yaml 创建 3 个 pod，运行 v1.0.0\n> kubectl create -f deployments/mypython-canary.yaml 创建一个 pod，运行 v2.0.0\n\ndeployments/mypython.yaml:\n```\napiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  name: mypython-deployment\n  namespace: meetbill\nspec:\n  replicas: 3\n  template:\n    metadata:\n      labels:\n        app: mypython\n        track: stable\n        version: 1.0.0\n    spec:\n      containers:\n        - name: mypython\n          image: \"hub.baidubce.com/xxxx/mypython:1.0.0\"\n          ports:\n            - name: http\n              containerPort: 8080\n\n```\ndeployments/mypython-canary.yaml:\n```\napiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  name: mypython-canary\n  namespace: meetbill\nspec:\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: mypython\n        track: canary\n        version: 2.0.0\n    spec:\n      containers:\n        - name: hello\n          image: \"hub.baidubce.com/xxxx/mypython:2.0.0\"\n          ports:\n            - name: http\n              containerPort: 8080\n```\n\n#### 创建访问节点\n> kubectl create -f services/mypython-svc.yaml\n\n通过 selector(app: mypython) 将两个版本的服务放在一个 service 中\n\nservices/mypython-svc.yaml:\n```\napiVersion: v1\nkind: Service\nmetadata:\n  name: mypython-svc\n  namespace: meetbill\n  labels:\n    app: mypython\nspec:\n  ports:\n  - port: 8080\n    targetPort: 8080\n  type: NodePort\n  selector:\n    app: mypython\n```\n### 4.4 自动扩容\n\n#### 清空之前环境\n\n删除 service\n> kubectl delete service -l app=mypython --namespace=meetbill\n\n删除 deployment\n> kubectl delete deployment mypython-deployment --namespace=meetbill\n\n#### 创建 deployment\n\n创建 deployment，包含一个有资源限制的 pod\n\n> kubectl create -f ./mypython-autoscale.yaml\n\n```\napiVersion: extensions/v1beta1\nkind: Deployment\nmetadata:\n  name: mypython-deployment\n  namespace: meetbill\nspec:\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app: mypython\n        track: stable\n        version: 1.0.0\n    spec:\n      containers:\n        - name: mypython\n          resources:\n              requests:\n                  cpu: \"300m\"\n                  memory: 1Gi\n              limits:\n                  cpu: \"500m\"\n                  memory: 2Gi\n          imagePullPolicy: Always\n          image: \"hub.baidubce.com/xxxx/mypython:autoscale\"\n          ports:\n            - name: http\n              containerPort: 8080\n```\n#### 设置自动扩容规则\n\n> kubectl autoscale deployment mypython-deployment --min=1 --max=3 --cpu-percent=2 --namespace=meetbill\n\n设置该 deployment 中 pod cpu 超过 2% 进行自动扩容，最多扩容 3 个\n\n#### 设置 service\n> kubectl create -f services/mypython-svc.yaml 创建一个 service，使得这个 pod 可以被访问到\n\n通过下面方式也可以获取到 service 的端口，如下 32410 即要访问的端口\n> kubectl get service --namespace=meetbill\n```\nNAME           TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE\nmypython-svc   NodePort   172.16.244.116   <none>        8080:32410/TCP   26s\n```\n\n#### 压测服务\n> ab -c 100 -n 100000 \"http://$VM_IP:$NODE_PORT/\"\n\n使用 ab 进行压测\n\n> watch -n 1 'kubectl top pod --namespace=meetbill'\n\n使用 kubectl top 命令查看 pod 资源利用状态，可以看到 cpu 超过 200m 时，自动扩容至 3 个 pod\n\n> kubectl get pod --namespace=meetbill\n```\nNAME                                  READY     STATUS    RESTARTS   AGE\nmypython-deployment-8dc88bc89-5kml8   1/1       Running   0          1m\nmypython-deployment-8dc88bc89-6m5tz   1/1       Running   0          1m\nmypython-deployment-8dc88bc89-rxmrd   1/1       Running   0          8m\n```\n\n#### 删除自动扩容策略\n\n> kubectl delete horizontalpodautoscaler.autoscaling/mypython-deployment --namespace=meetbill\n\n```\nhorizontalpodautoscaler.autoscaling \"mypython-deployment\" deleted\n```\n## 5 K8s 日常调试\n\n### 5.1 查看 Pod 生命周期的事件\n通过如下命令，看命令末尾 events 一节，查看 kubelet 给 APIServer 发送的 Pod 生命周期里发生的事件\n\n> kubectl describe pod podname\n\n### 5.2 查看资源 (CPU/Memory) 使用情况\n资源使用最多的节点\n> $ kubectl top nodes\n\n资源使用最多的 Pod\n> $ kubectl top pods\n\n### 5.3 如何摘下某个 Pod 进行 Debug\n使用 label 机制，对 Pod 进行标记。在 Service 定义中，我们添加 status: serving 字段。当需要摘下某个 Pod 做 Debug，而又不影响整个服务，可以：\n\n> $ kubectl get pods --selector=\"status=serving\"\n> $ kubectl label pods webserver-rc-lxag2 --overwrite status=debuging\n\n此时 kubelet 就会把这个 Pod 从 Service 的后端列表中删掉。等到 Debug 完，想恢复？再改回去就好了：\n\n> $ kubectl label pods webserver-rc-lxag2 --overwrite status=serving\n\n\n"
  },
  {
    "path": "doc/cloud/kvm.md",
    "content": "\n<!-- vim-markdown-toc GFM -->\n* [1 什么是 KVM](#1-什么是-kvm)\n* [2 安装 KVM](#2-安装-kvm)\n    * [2.1 系统要求](#21-系统要求)\n    * [2.2 安装 KVM 软件](#22-安装-kvm-软件)\n        * [2.2.1 确保正确加载 KVM 模块](#221-确保正确加载-kvm-模块)\n        * [2.2.2 检查 KVM 是否正确安装](#222-检查-kvm-是否正确安装)\n    * [2.3 配置网络](#23-配置网络)\n        * [2.3.1 默认网络 virbro](#231-默认网络-virbro)\n        * [2.3.2 桥接网络](#232-桥接网络)\n    * [2.4 配置 VNC](#24-配置-vnc)\n* [3 创建虚拟机](#3-创建虚拟机)\n    * [3.1 上传 ISO](#31-上传-iso)\n    * [3.2 创建 KVM 虚拟机的磁盘文件](#32-创建-kvm-虚拟机的磁盘文件)\n    * [3.3 启动虚拟机](#33-启动虚拟机)\n        * [3.3.1 启动虚拟机参数说明](#331-启动虚拟机参数说明)\n        * [3.3.2 bridge 网络模式启动虚拟机](#332-bridge-网络模式启动虚拟机)\n        * [3.3.3 nat 模式启动虚拟机](#333-nat-模式启动虚拟机)\n    * [3.4 连接虚拟机](#34-连接虚拟机)\n* [4 管理 KVM](#4-管理-kvm)\n    * [4.1 管理 KVM 上的虚拟机](#41-管理-kvm-上的虚拟机)\n\n<!-- vim-markdown-toc -->\n\n# 1 什么是 KVM\n\nKVM 是指基于 Linux 内核的虚拟机（Kernel-based Virtual Machine）。 2006 年 10 月，由以色列的 Qumranet 组织开发的一种新的“虚拟机”实现方案。 2007 年 2 月发布的 Linux 2.6.20 内核第一次包含了 KVM 。增加 KVM 到 Linux 内核是 Linux 发展的一个重要里程碑，这也是第一个整合到 Linux 主线内核的虚拟化技术。\n\nKVM 在标准的 Linux 内核中增加了虚拟技术，从而我们可以通过优化的内核来使用虚拟技术。在 KVM 模型中，每一个虚拟机都是一个由 Linux 调度程序管理的标准进程，你可以在用户空间启动客户机操作系统。一个普通的 Linux 进程有两种运行模式：内核和用户。 KVM 增加了第三种模式：客户模式（有自己的内核和用户模式）。\n\n一个典型的 KVM 安装包括以下部件：\n\n> * 一个管理虚拟硬件的设备驱动，这个驱动通过一个字符设备 /dev/kvm 导出它的功能。通过 /dev/kvm 每一个客户机拥有其自身的地址空间，这个地址空间与内核的地址空间相分离或与任何一个正运行着的客户机相分离。\n* 一个模拟硬件的用户空间部件，它是一个稍微改动过的 QEMU 进程。从客户机操作系统执行 I/O 会拥有 QEMU。QEMU 是一个平台虚拟化方案，它允许整个 PC 环境（包括磁盘、显示卡（图形卡）、网络设备）的虚拟化。任何客户机操作系统所发出的 I/O 请求都被拦截，并被路由到用户模式用以被 QEMU 过程模拟仿真。\n\n# 2 安装 KVM\n\n## 2.1 系统要求\n\nKVM 需要有 CPU 的支持 (Intel VT 或 AMD SVM)，在安装 KVM 之前检查一下 CPU 是否提供了虚拟技术的支持\n\n* 基于`Intel`处理器的系统，运行`grep vmx /proc/cpuinfo`查找 CPU flags 是否包括`vmx`关键词\n* 基于`AMD`处理器的系统，运行`grep svm /proc/cpuinfo`查找 CPU flags 是否包括`svm`关键词\n* 检查 BIOS，确保 BIOS 里开启`VT`选项\n\n**注：**\n\n* 一些厂商禁止了机器 BIOS 中的 VT 选项 , 这种方式下 VT 不能被重新打开\n* /proc/cpuinfo 仅从 Linux 2.6.15(Intel) 和 Linux 2.6.16(AMD) 开始显示虚拟化方面的信息。请使用 uname -r 命令查询您的内核版本。如有疑问，请联系硬件厂商\n\n```\negrep \"(vmx|svm)\" /proc/cpuinfo\nflags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt lahf_lm dts tpr_shadow vnmi flexpriority ept vpid\nflags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt lahf_lm dts tpr_shadow vnmi flexpriority ept vpid\nflags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt lahf_lm dts tpr_shadow vnmi flexpriority ept vpid\nflags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt lahf_lm dts tpr_shadow vnmi flexpriority ept vpid\nflags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt lahf_lm dts tpr_shadow vnmi flexpriority ept vpid\nflags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt lahf_lm dts tpr_shadow vnmi flexpriority ept vpid\nflags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt lahf_lm dts tpr_shadow vnmi flexpriority ept vpid\nflags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts rep_good xtopology nonstop_tsc aperfmperf pni dtes64 monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr pdcm dca sse4_1 sse4_2 popcnt lahf_lm dts tpr_shadow vnmi flexpriority ept vpid\n```\n\n## 2.2 安装 KVM 软件\n安装 KVM 模块、管理工具和 libvirt （一个创建虚拟机的工具）\n```\nyum install -y qemu-kvm libvirt virt-install virt-manager bridge-utils\n/etc/init.d/libvirtd start\nchkconfig libvirtd on\n```\n\n### 2.2.1 确保正确加载 KVM 模块\n```\nlsmod  | grep kvm\nkvm_intel              54285  0 \nkvm                   333172  1 kvm_intel\n```\n\n### 2.2.2 检查 KVM 是否正确安装\n```\nvirsh -c qemu:///system list\n Id    Name                           State\n----------------------------------------------------\n\n```\n如果这里是错误信息，说明安装出现问题\n\n\n## 2.3 配置网络\nKVM 上网有两种配置，一种是 default，它支持主机和虚拟机的互访，同时也支持虚拟机访问互联网，但不支持外界访问虚拟机，另外一种是 bridge 方式，可以使虚拟机成为网络中具有独立 IP 的主机。\n\n\n### 2.3.1 默认网络 virbro\n默认的网络连接是 virbr0，它的配置文件在 /var/lib/libvirt/network 目录下，默认配置为\n```\ncat /var/lib/libvirt/network/default.xml \n\n  default\n  77094b31-b7eb-46ca-930e-e0be9715a5ce\n\n```\n\n### 2.3.2 桥接网络\n\n配置桥接网卡，配置如下\n\n```\nmore /etc/sysconfig/network-scripts/ifcfg-\\*\n:::::::::::::: 新建文件\n/etc/sysconfig/network-scripts/ifcfg-br0\n::::::::::::::\nDEVICE=br0\nONBOOT=yes\nTYPE=Bridge\nBOOTPROTO=static\nIPADDR=192.168.39.20\nNETMASK=255.255.255.0\nGATEWAY=192.168.39.1\nDNS1=8.8.8.8\n:::::::::::::: 物理网卡\n/etc/sysconfig/network-scripts/ifcfg-em1\n::::::::::::::\nDEVICE=em1\nTYPE=Ethernet\nONBOOT=yes\nBOOTPROTO=static\nBRIDGE=br0\n::::::::::::::\n```\n## 2.4 配置 VNC\n\n**(1) 修改 VNC 服务端的配置文件**\n```\n[root@LINUX ~]# vim /etc/libvirt/qemu.conf  \nvnc_listen = \"0.0.0.0\"   第十二行，把 vnc_listen 前面的#号去掉。\n```\n**(2) 重启 libvirtd 和 messagebus 服务**\n```\n[root@LINUX ~]# /etc/init.d/libvirtd restart\nStopping libvirtd daemon:                                  [  OK  ]\nStarting libvirtd daemon: libvirtd: initialization failed  [FAILED]\n解决办法：\n[root@LINUX ~]# echo \"export LC_ALL=en_US.UTF-8\"  >>  /etc/profile\n[root@LINUX ~]# source /etc/profile\n[root@LINUX ~]# /etc/init.d/libvirtd restart\n[root@LINUX ~]# /etc/init.d/messagebus restart\n```\n# 3 创建虚拟机\n\nvirt-manager 是基于 libvirt 的图像化虚拟机管理软件，操作类似 vmware，不做详细介绍。\n\n* (1)Virt-manager 图形化模式安装\n* (2)Virt-install   命令模式安装【本文使用此方式】\n* (3)Virsh        XML 模板安装\n\n## 3.1 上传 ISO\n\n```\n[root@LINUX ~]# mkdir -p /home/iso\n[root@LINUX ~]# mkdir -p /home/kvm\n将 iso 拷贝到 /home/iso 目录\n```\n## 3.2 创建 KVM 虚拟机的磁盘文件\n\n本例创建的磁盘文件为 10G，实际使用中应注意下 /home 的空间，可以设置为 100G\n\n```\n[root@LINUX ~]# cd /home/kvm/\n[root@LINUX ~]# qemu-img create -f qcow2 -o preallocation=metadata kvm_mode.img 10G        \n```\n## 3.3 启动虚拟机\n\n### 3.3.1 启动虚拟机参数说明\n\nvirt-install 命令有许多选项，这些选项大体可分为下面几大类，同时对每类中的常用选项也做出简单说明。\n\n一般选项：指定虚拟机的名称、内存大小、VCPU 个数及特性等；\n\n> * -n NAME, --name=NAME：虚拟机名称，需全局惟一；\n> * -r MEMORY, --ram=MEMORY：虚拟机内在大小，单位为 MB；\n> * --vcpus=VCPUS[,maxvcpus=MAX][,sockets=#][,cores=#][,threads=#]：VCPU 个数及相关配置；\n> * --cpu=CPU：CPU 模式及特性，如 coreduo 等；可以使用 qemu-kvm -cpu ? 来获取支持的 CPU 模式；\n\n安装方法：指定安装方法、GuestOS 类型等；\n\n> * -c CDROM, --cdrom=CDROM：光盘安装介质；\n> * -l LOCATION, --location=LOCATION：安装源 URL，支持 FTP、HTTP 及 NFS 等，如 ftp://172.16.0.1/pub；\n> * --pxe：基于 PXE 完成安装；\n> * --livecd: 把光盘当作 LiveCD；\n> * --os-type=DISTRO_TYPE：操作系统类型，如 linux、unix 或 windows 等；\n> * --os-variant=DISTRO_VARIANT：某类型操作系统的变体，如 rhel5、fedora8 等；\n> * -x EXTRA, --extra-args=EXTRA：根据 --location 指定的方式安装 GuestOS 时，用于传递给内核的额外选项，例如指定 kickstart 文件的位置，--extra-args \"ks=http://172.16.0.1/class.cfg\"\n> * --boot=BOOTOPTS：指定安装过程完成后的配置选项，如指定引导设备次序、使用指定的而非安装的 kernel/initrd 来引导系统启动等 ；例如：\n> * --boot cdrom,hd,network：指定引导次序；\n> * --boot kernel=KERNEL,initrd=INITRD,kernel_args=”console=/dev/ttyS0”：指定启动系统的内核及 initrd 文件；\n\n存储配置：指定存储类型、位置及属性等；\n\n> * --disk=DISKOPTS：指定存储设备及其属性；格式为 --disk /some/storage/path,opt1=val1，opt2=val2 等；常用的选项有：\n>   * device：设备类型，如 cdrom、disk 或 floppy 等，默认为 disk；\n>   * bus：磁盘总结类型，其值可以为 ide、scsi、usb、virtio 或 xen；\n>   * perms：访问权限，如 rw、ro 或 sh（共享的可读写），默认为 rw；\n>   * size：新建磁盘映像的大小，单位为 GB；\n>   * cache：缓存模型，其值有 none、writethrouth（缓存读）及 writeback（缓存读写）；\n>   * format：磁盘映像格式，如 raw、qcow2、vmdk 等；\n>   * sparse：磁盘映像使用稀疏格式，即不立即分配指定大小的空间；\n> * --nodisks：不使用本地磁盘，在 LiveCD 模式中常用；\n\n网络配置：指定网络接口的网络类型及接口属性如 MAC 地址、驱动模式等；\n\n> *  -w NETWORK, --network=NETWORK,opt1=val1,opt2=val2：将虚拟机连入宿主机的网络中，其中 NETWORK 可以为：\n>   * bridge=BRIDGE：连接至名为“BRIDEG”的桥设备；\n>   * network=NAME：连接至名为“NAME”的网络；\n>   * 其它常用的选项还有：\n>     * model：GuestOS 中看到的网络设备型号，如 e1000、rtl8139 或 virtio 等；\n>     * mac：固定的 MAC 地址；省略此选项时将使用随机地址，但无论何种方式，对于 KVM 来说，其前三段必须为 52:54:00；\n> * --nonetworks：虚拟机不使用网络功能；\n\n图形配置：定义虚拟机显示功能相关的配置，如 VNC 相关配置；\n\n> * --graphics TYPE,opt1=val1,opt2=val2：指定图形显示相关的配置，此选项不会配置任何显示硬件（如显卡），而是仅指定虚拟机启动后对其进行访问的接口；\n>   * TYPE：指定显示类型，可以为 vnc、sdl、spice 或 none 等，默认为 vnc；\n>   * port：TYPE 为 vnc 或 spice 时其监听的端口；\n>   * listen：TYPE 为 vnc 或 spice 时所监听的 IP 地址，默认为 127.0.0.1，可以通过修改 /etc/libvirt/qemu.conf 定义新的默认值；\n>   * password：TYPE 为 vnc 或 spice 时，为远程访问监听的服务进指定认证密码；\n> * --noautoconsole：禁止自动连接至虚拟机的控制台；\n\n设备选项：指定文本控制台、声音设备、串行接口、并行接口、显示接口等；\n\n> * --serial=CHAROPTS：附加一个串行设备至当前虚拟机，根据设备类型的不同，可以使用不同的选项，格式为“--serial type,opt1=val1,opt2=val2,...”，例如：\n> * --serial pty：创建伪终端；\n> * --serial dev,path=HOSTPATH：附加主机设备至此虚拟机；\n> * --video=VIDEO：指定显卡设备模型，可用取值为 cirrus、vga、qxl 或 vmvga；\n\n### 3.3.2 bridge 网络模式启动虚拟机\n有独立 IP 时使用这种方式\n\n```\n[root@LINUX ~]# chmod -R 777 /etc/libvirt\n[root@LINUX ~]# chmod -R 777 /home/kvm\n[root@LINUX ~]#virt-install --name=kvm_test --ram 4096 --vcpus=4 \\\n       -f /home/kvm/kvm_mode.img --cdrom /home/iso/sucunOs_anydisk.iso \\\n       --graphics vnc,listen=0.0.0.0,port=7788, --network bridge=br0 \\\n       --force --autostart\n```\n### 3.3.3 nat 模式启动虚拟机\n\n没有独立 IP 时使用这种方式\n\n```\n[root@LINUX ~]# chmod -R 777 /etc/libvirt\n[root@LINUX ~]# chmod -R 777 /home/kvm\n[root@LINUX ~]#virt-install --name=kvm_test --ram 4096 --vcpus=4 \\\n       -f /home/kvm/kvm_mode.img --cdrom /home/iso/sucunOs_anydisk.iso \\\n       --graphics vnc,listen=0.0.0.0,port=7788,--network network=default \\\n       --force --autostart\n```\n## 3.4 连接虚拟机\n\n> * (1) 网上下载 VNC 客户端\n> * (2) 用 VNC 客户端连接并安装虚拟机的操作系统（VNC 连上之后，跟安装 Linux CentOS 6.5 系统一样，重新装一次）\n\n```\n点击 continue 是如果出现闪退的情况，请修改 Option->Expert->ColorLevel 的值为 full\n```\n\n# 4 管理 KVM\n\n## 4.1 管理 KVM 上的虚拟机\n\n* virsh list           #显示本地活动虚拟机\n* virsh list  --all    #显示本地所有的虚拟机（活动的 + 不活动的）\n* virsh start x        #启动名字为 x 的非活动虚拟机\n* virsh shutdown x     #正常关闭虚拟机\n* virsh dominfo x      #显示虚拟机的基本信息\n* virsh autostart x    #将 x 虚拟机设置为自动启动\n"
  },
  {
    "path": "doc/cloud/openstack.md",
    "content": "# OpenStack\n\n[OpenStack 实践](https://github.com/meetbill/openstack_install/wiki)\n"
  },
  {
    "path": "doc/cloud/physical_machine.md",
    "content": "## 物理机常见问题即处理方法\n\n<!-- vim-markdown-toc GFM -->\n* [开机无法启动](#开机无法启动)\n    * [提示无法找到系统盘](#提示无法找到系统盘)\n        * [场景](#场景)\n        * [重做引导项方法](#重做引导项方法)\n\n<!-- vim-markdown-toc -->\n\n## 开机无法启动\n\n### 提示无法找到系统盘\n\n#### 场景\n\n**现象**\n```\nReboot and select proper boot device or insert boot media in selected boot device and press a key\n```\n**可能原因**\n\n先排查硬件原因 ---> 再排查软件原因\n\n**硬件原因**\n\n> * RAID 卡（可以开机时查看 RAID 卡 能否识别到硬盘）\n> * 硬盘（硬盘状态灯是否正常）\n\n**软件原因**\n\n> * 开机启动项顺序\n> * RAID 卡设置的启动盘配置\n> * 引导项丢失\n\n#### 重做引导项方法\n\n> * 插入光盘进入救援模式（选择完英文等，选择 continue 进入救援模式）\n> * chroot /mnt/sysimage\n> * /sbin/grub-install /dev/sda(sda 是系统所在的设备）\n> * 多系统加引导项的话可以添加到 /boot/grub/grub.conf\n> * 重启服务器\n"
  },
  {
    "path": "doc/cluster/zookeeper.md",
    "content": "## ZooKeeper\n<!-- vim-markdown-toc GFM -->\n\n* [1 ZooKeeper 安装](#1-zookeeper-安装)\n    * [2.1 单机模式](#21-单机模式)\n    * [2.2 集群模式](#22-集群模式)\n* [2 客户端](#2-客户端)\n* [3 ZooKeeper 的简单操作](#3-zookeeper-的简单操作)\n    * [3.1 ls](#31-ls)\n    * [3.2 create](#32-create)\n    * [3.3 get](#33-get)\n    * [3.4 set](#34-set)\n    * [3.5 delete](#35-delete)\n\n<!-- vim-markdown-toc -->\n\n## 1 ZooKeeper 安装\nZooKeeper 的安装模式分为三种，分别为：单机模式（stand-alone）、集群模式和集群伪分布模式。ZooKeeper 单机模式的安装相对比较简单，如果第一次接触 ZooKeeper 的话，建议安装 ZooKeeper 单机模式或者集群伪分布模式。\n\nhttp://hadoop.apache.org/zookeeper/releases.html\n### 2.1 单机模式\n\n```bash\n[meetbill@meetbill_dev01 /opt]$ tar xvfz  zookeeper-3.4.10.tar.gz\n[meetbill@meetbill_dev01 /opt]$ ZOOKEEPER_HOME=/opt/zookeeper-3.4.10\n```\n配置：\n\n```bash\n# 添加一个 zoo.cfg 配置文件\n[meetbill@meetbill_dev01 ~]$ cd $ZOOKEEPER_HOME/conf\n[meetbill@meetbill_dev01 conf]$ cp zoo_sample.cfg zoo.cfg\n\n# 修改配置文件（zoo.cfg）\ndataDir=/home/hadoop/app/tmp/zk\n\n# 启动 zk\n[meetbill@meetbill_dev01 zookeeper]$ bin/zkServer.sh start\n```\n### 2.2 集群模式\n\n为了获得可靠的 ZooKeeper 服务，用户应该在一个集群上部署 ZooKeeper 。只要集群上大多数的 ZooKeeper 服务启动了，那么总的 ZooKeeper 服务将是可用的。另外，最好使用奇数台机器。 如果 zookeeper 拥有 5 台机器，那么它就能处理 2 台机器的故障了。\n\n之后的操作和单机模式的安装类似，我们同样需要对 JAVA 环境进行设置，下载最新的 ZooKeeper 稳定版本并配置相应的环境变量。不同之处在于每台机器上 conf/zoo.cfg 配置文件的参数设置，参考下面的配置：\n\n```\ntickTime=2000\ndataDir=/var/zookeeper/\nclientPort=2181\ninitLimit=5\nsyncLimit=2\nserver.1=zoo1:2888:3888\nserver.2=zoo2:2888:3888\nserver.3=zoo3:2888:3888\n```\n\nserver.id=host:port:port. 指示了不同的 ZooKeeper 服务器的自身标识，作为集群的一部分的机器应该知道 ensemble 中的其它机器。用户可以从“ server.id=host:port:port. ”中读取相关的信息。 在服务器的 data （ dataDir 参数所指定的目录）目录下创建一个文件名为 myid 的文件，这个文件中仅含有一行的内容，指定的是自身的 id 值。比如，服务器“ 1 ”应该在 myid 文件中写入“ 1 ”。这个 id 值必须是 ensemble 中唯一的，且大小在 1 到 255 之间。这一行配置中，第一个端口（ port ）是从（ follower ）机器连接到主（ leader ）机器的端口，第二个端口是用来进行 leader 选举的端口。在这个例子中，每台机器使用三个端口，分别是： clientPort ， 2181 ； port ， 2888 ； port ， 3888 。\n\n## 2 客户端\n```bash\n[meetbill@meetbill_dev01 ~]$ zkCli.sh\nConnecting to localhost:2181\n2018-07-21 01:11:38,350 [myid:] - INFO  [main:Environment@100] - Client\n...\n[zk: localhost:2181(CONNECTED) 0] ls /\n[zookeeper]\n```\n\n## 3 ZooKeeper 的简单操作\n\n### 3.1 ls\n使用 ls 命令来查看当前 ZooKeeper 中所包含的内容：\n```\n[zk: 10.77.20.23:2181(CONNECTED) 1] ls /\n[zookeeper]\n```\n### 3.2 create\n创建一个新的 znode ，使用 create /zk myData 。这个命令创建了一个新的 znode 节点“ zk ”以及与它关联的字符串：\n```\n[zk: 10.77.20.23:2181(CONNECTED) 2] create /zk myData\nCreated /zk\n```\n\n再次使用 ls 命令来查看现在 zookeeper 中所包含的内容：\n```\n[zk: 10.77.20.23:2181(CONNECTED) 3] ls /\n[zk, zookeeper]\n```\n\n此时看到， zk 节点已经被创建。\n\n### 3.3 get \n我们运行 get 命令来确认第二步中所创建的 znode 是否包含我们所创建的字符串：\n```\n[zk: 10.77.20.23:2181(CONNECTED) 4] get /zk\nmyData\nZxid = 0x40000000c\ntime = Tue Jan 18 18:48:39 CST 2011\nZxid = 0x40000000c\nmtime = Tue Jan 18 18:48:39 CST 2011\npZxid = 0x40000000c\ncversion = 0\ndataVersion = 0\naclVersion = 0\nephemeralOwner = 0x0\ndataLength = 6\nnumChildren = 0\n```\n\n### 3.4 set \n我们通过 set 命令来对 zk 所关联的字符串进行设置：\n```\n[zk: 10.77.20.23:2181(CONNECTED) 5] set /zk shenlan211314\ncZxid = 0x40000000c\nctime = Tue Jan 18 18:48:39 CST 2011\nmZxid = 0x40000000d\nmtime = Tue Jan 18 18:52:11 CST 2011\npZxid = 0x40000000c\ncversion = 0\ndataVersion = 1\naclVersion = 0\nephemeralOwner = 0x0\ndataLength = 13\nnumChildren = 0\n```\n\n### 3.5 delete\n下面我们将刚才创建的 znode 删除：\n```\n[zk: 10.77.20.23:2181(CONNECTED) 6] delete /zk\n```\n\n最后再次使用 ls 命令查看 ZooKeeper 所包含的内容：\n```\n[zk: 10.77.20.23:2181(CONNECTED) 7] ls /\n[zookeeper]\n```\n\n经过验证， zk 节点已经被删除。\n"
  },
  {
    "path": "doc/db/README.md",
    "content": "#  数据库篇\n\n> * MySQL\n> * MongoDB\n> * Redis\n\n"
  },
  {
    "path": "doc/db/memcache.md",
    "content": "## MemCache\r\n\r\n<!-- vim-markdown-toc GFM -->\r\n\r\n* [1 Memcache 使用](#1-memcache-使用)\r\n    * [1.1 telnet 访问](#11-telnet-访问)\r\n        * [telnet 写请求命令格式](#telnet-写请求命令格式)\r\n        * [telnet 读请求](#telnet-读请求)\r\n        * [telnet 查询 memcache 状态](#telnet-查询-memcache-状态)\r\n    * [1.2 flush_all](#12-flush_all)\r\n* [2 Case](#2-case)\r\n    * [2.1 MemCache 访问热点导致服务雪崩 case](#21-memcache-访问热点导致服务雪崩-case)\r\n    * [2.2 那我们如何防止这样的事故发生](#22-那我们如何防止这样的事故发生)\r\n        * [明确使用场景，防止滥用](#明确使用场景防止滥用)\r\n        * [不人为制造访问热点](#不人为制造访问热点)\r\n        * [实例拆分](#实例拆分)\r\n\r\n<!-- vim-markdown-toc -->\r\n# 1 Memcache 使用\r\n\r\nhttps://github.com/memcached/memcached/wiki/Commands\r\n\r\n## 1.1 telnet 访问\r\n### telnet 写请求命令格式\r\n```\r\n<Command name> <key> <flags> <exptime> <bytes>\\r\\n\r\n<data block> \\r\\n\r\n\r\n简单解释\r\n<Command name>：可以是 add，set，replace 等\r\n<key>：为 memcache key 键的名称，要求唯一\r\n<flags>：是一个 16 位的无符号整数（10 进制），该标志和需要存储的数据一起存储，并在客户端 get 数据时返回。客户可以将此标志用做特殊用途，此标志对服务器来说是透明的。\r\n<exptime>：过期的时间，单位为秒，设置为 0 表示永不过期。\r\n<bytes>：需要存储的字节数（不包含最后的“\\r\\n ”），可以为 0，表示空数据。\r\n\\r\\n：命令结尾标识符，在 telnet 界面输入命令时按回车键即可。\r\n<data block>：表示存储的数据内容，即 value。\r\n```\r\n\r\ntelnet 写命令响应\r\n\r\n> * Stored 表示存储成功\r\n> * not_stored：表示存储失败（命令正确，但操作不对）\r\n> * Error：表示命令错误\r\n\r\n### telnet 读请求\r\n\r\n```\r\nget key\r\n```\r\n\r\n### telnet 查询 memcache 状态\r\n\r\n```\r\nstats\r\n```\r\n## 1.2 flush_all\r\n\r\n执行 flush_all 时，Memcache 不会释放内存。\r\n\r\n它只是将 memcache 中所有对象的到期时间设置为当前时间。当获取一个键时，它将返回 null，对象将被清除。它使用懒惰的方法来 flush 所有对象。\r\n\r\n# 2 Case\r\n## 2.1 MemCache 访问热点导致服务雪崩 case\r\n\r\n15 年，某产品线发生了一起严重的丢失用户流量的事故，就这个 case 来谈谈 MemCache 使用不当的问题。\r\n\r\n他们的使用方法是这样的，在站点的主页上每次请求会首先请求一个单热点 key，value 大概在 250 KB 左右。\r\n\r\n以千兆网卡的容量计算，热点机器网卡容量极限为 400 QPS。若请求失败（cache 失效、访问超时等），PHP 会根据业务逻辑，再请求大约 600 个 cache 数据。\r\n\r\n然后重新构造首页的数据块。\r\n\r\n在当天下午 14 ~ 15 点左右，用户流量有自然增长，超过了 400 QPS，于是那台热点的机器单机网卡打满，大批量的首页请求获取热点 cache 失败。\r\n\r\nPHP 业务为了重新构造数据块，另外请求 600 个 key，导致所有的 cache 机器请求都上涨，网卡占用上涨。同时，由于请求 600 多次 cache 需要耗时过长，产生了很多的长耗时请求，这些请求占用 dbproxy 连接不释放，导致 DB 的连接数也打满。\r\n\r\n此时，其他请求大量失败，因此对于 memcache 调用减少，但是还是有相当量的首页请求仍然在请求单热点，导致单热点网卡依然处于打满情况，其他机器网卡有所下降。此时产品线无法提供正常服务，处于挂站状态。\r\n\r\n## 2.2 那我们如何防止这样的事故发生\r\n\r\n### 明确使用场景，防止滥用\r\n\r\n首先要确定一个需求是不是适合用 cache。大多数场景下，cache 里存储的都是几百个字节的小数据（如帖子列表、用户信息、图片元信息等），复杂的结构数据序列化之后一般也不会超过 2 KB。250 KB 的大数据块，如果是图片，应该塞到图片存储系统；如果是整个网页，那么展示时“实时渲染 VS 直接从 cache 取”这两种方案，还需商榷。\r\n\r\n### 不人为制造访问热点\r\n\r\nMemcache 的访问本身就具有一定的热点（比如某些书看的人多、一段时间内的热门话题等），在实际工程中，这些热点也是需要尽量被平均的。然而在这个 case 中，人为制造了热点，即，对同一个 key 的访问在每一个请求的关键路径上，这是一定需要避免的。\r\n\r\n### 实例拆分\r\n\r\n多个业务（如主页和文章列表等）使用同一个 Memcache 实例，某一个业务流量飙升（正常增长或隐藏的 bug 导致流量异常）就会导致其它所有的业务访问受影响，一挂挂全站。这时需要把比较重要的服务依赖的 cache 拆分成单独的实例，尽量减少互相影响，提升可用性。\r\n"
  },
  {
    "path": "doc/db/mongodb.md",
    "content": "## Mongodb\n<!-- vim-markdown-toc GFM -->\n\n* [1 安装](#1-安装)\n* [2 常见问题](#2-常见问题)\n    * [(1) 启动异常](#1-启动异常)\n* [3 Mongodb 备份](#3-mongodb-备份)\n* [4 原理](#4-原理)\n    * [4.1 选举机制](#41-选举机制)\n\n<!-- vim-markdown-toc -->\n## 1 安装\n配置\n```\nfork = true\nport = 27017\nquiet = true\ndbpath = data\nlogpath = mongod.log\nlogappend = true\njournal = true\n```\nmongod -f mongodb.conf\n\n## 2 常见问题\n### (1) 启动异常\n```\nFailed global initialization: BadValue Invalid or no user locale set. Please ensure LANG and/or LC_* environment variables are set correctly.\n```\n\n设置环境变量\n> export LC_ALL=C\n\n## 3 Mongodb 备份\nMongodb 用的是可以热备份的 mongodump 和对应恢复的 mongorestore, 在 linux 下面使用 shell 脚本写的定时备份，代码如下\n\n1. 定时备份\n\n```\n    #!/bin/bash\n    sourcepath='/usr'/bin   #mongodump 命令所在路径\n    targetpath='/var/lib/mongo/mongobak'   #备份存放位置\n    nowtime=$(date +%Y%m%d)\n\n    start()\n    {\n      ${sourcepath}/mongodump -u username -p password -d dbname --host 127.0.0.1 --port 27017 --out ${targetpath}/${nowtime}\n    }\n    execute()\n    {\n      start\n      if [ $? -eq 0 ]\n      then\n        echo \"back successfully!\"\n      else\n        echo \"back failure!\"\n      fi\n    }\n\n    if [ ! -d \"${targetpath}/${nowtime}/\" ]\n    then\n     mkdir ${targetpath}/${nowtime}\n    fi\n    execute\n    echo \"============== back end ${nowtime} ==============\"\n```\n<!--more-->\n2. 定时清除，保留 7 天的纪录\n\n```\n    #!/bin/bash\n    targetpath='/var/lib/mongo/mongobak'\n    nowtime=$(date -d '-7 days' \"+%Y%m%d\")\n    if [ -d \"${targetpath}/${nowtime}/\" ]\n    then\n      rm -rf \"${targetpath}/${nowtime}/\"\n      echo \"=======${targetpath}/${nowtime}/=== 删除完毕 ==\"\n    fi\n    echo \"===$nowtime ===\"\n```\n3. 服务器的时间要同步，同步的方法\n\n```\n  微软公司授时主机（美国）     time.windows.com\n  台警大授时中心（台湾）        asia.pool.ntp.org\n  中科院授时中心（西安）        210.72.145.44\n  网通授时中心（北京）           219.158.14.130\n  调用同步：  ntpdate asia.pool.ntp.org\n```\n4. 设置上面脚本权限和定时任务\n\n  权限：chmod +x filename\n  定时任务：crontab -e\n\n```\n  10 04 * * * /shell/mongobak.sh 1>/var/log/crontab_mongo_back.log &\n  10 02 * * * /shell/mongobakdelete.sh 1>/var/log/crontab_mongo_delete.log &\n```\n每天凌晨 4 点 10 开始进行备份，2 点 10 分删除旧的备份\n\n## 4 原理\n\n### 4.1 选举机制\nmongodb 副本集故障转移功能得益于它的选举机制。选举机制采用了 Bully 算法。\n"
  },
  {
    "path": "doc/db/mysql.md",
    "content": "# Mysql\n\n<!-- vim-markdown-toc GFM -->\n* [前言](#前言)\n    * [MySQL 引擎](#mysql-引擎)\n    * [查看下是否支持 InnoDB](#查看下是否支持-innodb)\n* [安装完 MySQL 后必须调整的 10 项配置](#安装完-mysql-后必须调整的-10-项配置)\n    * [写在开始前](#写在开始前)\n    * [基本配置](#基本配置)\n    * [InnoDB 配置](#innodb-配置)\n    * [其他设置](#其他设置)\n    * [总结](#总结)\n* [mysql 日志](#mysql-日志)\n    * [慢日志 (5.1.73)](#慢日志-5173)\n        * [配置](#配置)\n        * [查看变量](#查看变量)\n        * [测试](#测试)\n        * [日志查询](#日志查询)\n    * [清理 MySQL binlog](#清理-mysql-binlog)\n        * [概要](#概要)\n        * [相关基本参数](#相关基本参数)\n        * [清理方法](#清理方法)\n            * [手动清理](#手动清理)\n            * [自动清理](#自动清理)\n* [mysql 管理](#mysql-管理)\n    * [用户管理](#用户管理)\n        * [创建用户](#创建用户)\n        * [为用户授权](#为用户授权)\n        * [修改用户密码](#修改用户密码)\n    * [mysql 主从](#mysql-主从)\n        * [清除主从关系](#清除主从关系)\n* [mysql 工具](#mysql-工具)\n* [mysql 运维](#mysql-运维)\n    * [MySQL 忘记密码](#mysql-忘记密码)\n    * [mysql.sock 位置错误](#mysqlsock-位置错误)\n\n<!-- vim-markdown-toc -->\n\n# 前言\n## MySQL 引擎\n\n一种存储数据的技术。\n\n***并发控制***\n\n当多个连接对记录进行修改时保证数据的一致性和完整性。\n可以理解为同步与互斥，原理和操作系统的那部分知识一致。\n\n***事务处理***\n\n这也是数据库区别于文件系统的一点。保证数据的完整性。\n\n***外键***\n\n保证数据的一致性。\n\n***索引***\n\n对数据表中一列或多列值进行排序的一种结构。是进行快速定位的方法。\n\n***对比***\n\n* MyISAM: 适用于事务处理不多时；\n* InnoDB: 适用于事务处理比较多，且需要外键支持时；（锁颗粒为行锁，相对效率低）\n* Memory：将所有数据保存在 RAM 中，在需要快速查找引用和其他类似数据的环境下，可提供极快的访问。\n\n修改存储引擎，使用`SHOW CREATE TABLE table_name;`查看存储引擎类型。\n* 在配置文件中`-default-storage-engine = engine_name`\n* 创建数据表时`CREATE TABLE tbl_name() ENGINE = engine_name;`\n\n| 特点 | MyISAM | BDB | Memory | InnoDB | Archive |\n|------|:------:|:---:|:------:|:------:|:-------:|\n| 存储限制 | 没有 | 没有 |  有  | 64TB | 没有 |\n| 事务安全 |  -   | 支持 |  -   | 支持 |  -   |\n| 锁机制   | 表锁 | 页锁 | 表锁 | 行锁 | 行锁 |\n| B 树索引  | 支持 | 支持 | 支持 | 支持 |  -   |\n| 哈希索引 |  -   |  -   | 支持 | 支持 |  -   |\n| 全文索引 | 支持 |  -   |  -   |  -   |  -   |\n| 集群索引 |  -   |  -   |  -   | 支持 |  -   |\n| 数据缓存 |  -   |  -   | 支持 | 支持 |  -   |\n| 索引缓存 | 支持 |  -   | 支持 | 支持 |  -   |\n| 数据可压缩 |支持|  -   |  -   |  -   | 支持 |\n| 空间使用 |  低  |  低  | N/A  |  高  |非常低|\n| 内存使用 |  低  |  低  | 中等 |  高  |  低  |\n| 批量插入的速度|高| 高  |  高  |  低  |非常高|\n| 支持外键 |  -   |  -   |  -   | 支持 |  -   |\n\n## 查看下是否支持 InnoDB\n\n通过命令行进入 mysql 或者是通过 phpmyadmin 进行操作，执行如下命令：\n```\nSHOW variables like 'have_%';\n显示结果中会有如下 3 种可能的结果：\nhave_innodb YES\nhave_innodb NO\nhave_innodb DISABLED\n这 3 种结果分别对应：\n已经开启 InnoDB 引擎\n未安装 InnoDB 引擎\n未启用 InnoDB 引擎\n```\n针对第二种未安装，只需要安装即可；针对第三种未启用，则打开 MySQL 配置文件，找到 skip-innodb 项，将其改成#skip-innodb，之后重启 mysql 服务即可。\n\n# 安装完 MySQL 后必须调整的 10 项配置\n\n## 写在开始前\n\n即使是经验老道的人也会犯错，会引起很多麻烦。所以在盲目的运用这些推荐之前，请记住下面的内容：\n\n- 一次只改变一个设置！这是测试改变是否有益的唯一方法。\n- 大多数配置能在运行时使用 SET GLOBAL 改变。这是非常便捷的方法它能使你在出问题后快速撤销变更。但是，要永久生效你需要在配置文件里做出改动。\n- 一个变更即使重启了 MySQL 也没起作用？请确定你使用了正确的配置文件。请确定你把配置放在了正确的区域内（所有这篇文章提到的配置都属于 [mysqld])\n- 服务器在改动一个配置后启不来了：请确定你使用了正确的单位。例如，`innodb_buffer_pool_size`的单位是 MB 而`max_connection`是没有单位的。\n- 不要在一个配置文件里出现重复的配置项。如果你想追踪改动，请使用版本控制。\n- 不要用天真的计算方法，例如”现在我的服务器的内存是之前的 2 倍，所以我得把所有数值都改成之前的 2 倍“。\n\n## 基本配置\n\n你需要经常察看以下 3 个配置项。不然，可能很快就会出问题。\n\n**`innodb_buffer_pool_size`**: 这是你安装完 InnoDB 后第一个应该设置的选项。缓冲池是数据和索引缓存的地方：这个值越大越好，这能保证你在大多数的读取操作时使用的是内存而不是硬盘。典型的值是 5-6GB(8GB 内存），20-25GB(32GB 内存），100-120GB(128GB 内存）。\n\n**`innodb_log_file_size`**：这是 redo 日志的大小。redo 日志被用于确保写操作快速而可靠并且在崩溃时恢复。一直到 MySQL 5.1，它都难于调整，因为一方面你想让它更大来提高性能，另一方面你想让它更小来使得崩溃后更快恢复。幸运的是从 MySQL 5.5 之后，崩溃恢复的性能的到了很大提升，这样你就可以同时拥有较高的写入性能和崩溃恢复性能了。一直到 MySQL 5.5，redo 日志的总尺寸被限定在 4GB（默认可以有 2 个 log 文件）。这在 MySQL 5.6 里被提高。\n\n一开始就把`innodb_log_file_size`设置成 512M（这样有 1GB 的 redo 日志）会使你有充裕的写操作空间。如果你知道你的应用程序需要频繁的写入数据并且你使用的时 MySQL 5.6，你可以一开始就把它这是成 4G。\n\n**`max_connections`**: 如果你经常看到‘Too many connections’错误，是因为`max_connections`的值太低了。这非常常见因为应用程序没有正确的关闭数据库连接，你需要比默认的 151 连接数更大的值。`max_connection`值被设高了（例如 1000 或更高）之后一个主要缺陷是当服务器运行 1000 个或更高的活动事务时会变的没有响应。在应用程序里使用连接池或者在 MySQL 里使用 [进程池](http://www.mysqlperformanceblog.com/2014/01/23/percona-server-improve-scalability-percona-thread-pool/) 有助于解决这一问题。\n\n## InnoDB 配置\n\n从 MySQL 5.5 版本开始，InnoDB 就是默认的存储引擎并且它比任何其他存储引擎的使用都要多得多。那也是为什么它需要小心配置的原因。\n\n**`innodb_file_per_table`**：这项设置告知 InnoDB 是否需要将所有表的数据和索引存放在共享表空间里（`innodb_file_per_table` = OFF） 或者为每张表的数据单独放在一个.ibd 文件（`innodb_file_per_table` = ON）。每张表一个文件允许你在 drop、truncate 或者 rebuild 表时回收磁盘空间。这对于一些高级特性也是有必要的，比如数据压缩。但是它不会带来任何性能收益。你不想让每张表一个文件的主要场景是：有非常多的表（比如 10k+）。\n\nMySQL 5.6 中，这个属性默认值是 ON，因此大部分情况下你什么都不需要做。对于之前的版本你必须在加载数据之前将这个属性设置为 ON，因为它只对新创建的表有影响。\n\n**`innodb_flush_log_at_trx_commit`**：默认值为 1，表示 InnoDB 完全支持 ACID 特性。当你的主要关注点是数据安全的时候这个值是最合适的，比如在一个主节点上。但是对于磁盘（读写）速度较慢的系统，它会带来很巨大的开销，因为每次将改变 flush 到 redo 日志都需要额外的 fsyncs。将它的值设置为 2 会导致不太可靠（unreliable）因为提交的事务仅仅每秒才 flush 一次到 redo 日志，但对于一些场景是可以接受的，比如对于主节点的备份节点这个值是可以接受的。如果值为 0 速度就更快了，但在系统崩溃时可能丢失一些数据：只适用于备份节点。\n\n**`innodb_flush_method`**: 这项配置决定了数据和日志写入硬盘的方式。一般来说，如果你有硬件 RAID 控制器，并且其独立缓存采用 write-back 机制，并有着电池断电保护，那么应该设置配置为 O_DIRECT；否则，大多数情况下应将其设为 fdatasync（默认值）。sysbench 是一个可以帮助你决定这个选项的好工具。\n\n**`innodb_log_buffer_size`**: 这项配置决定了为尚未执行的事务分配的缓存。其默认值（1MB）一般来说已经够用了，但是如果你的事务中包含有二进制大对象或者大文本字段的话，这点缓存很快就会被填满并触发额外的 I/O 操作。看看`Innodb_log_waits`状态变量，如果它不是 0，增加`innodb_log_buffer_size`。\n\n\n## 其他设置\n\n**`query_cache_size`**: query cache（查询缓存）是一个众所周知的瓶颈，甚至在并发并不多的时候也是如此。 最佳选项是将其从一开始就停用，设置`query_cache_size` = 0（现在 MySQL 5.6 的默认值）并利用其他方法加速查询：优化索引、增加拷贝分散负载或者启用额外的缓存（比如 memcache 或 redis）。如果你已经为你的应用启用了 query cache 并且还没有发现任何问题，query cache 可能对你有用。这是如果你想停用它，那就得小心了。\n\n**`log_bin`**：如果你想让数据库服务器充当主节点的备份节点，那么开启二进制日志是必须的。如果这么做了之后，还别忘了设置`server_id`为一个唯一的值。就算只有一个服务器，如果你想做基于时间点的数据恢复，这（开启二进制日志）也是很有用的：从你最近的备份中恢复（全量备份），并应用二进制日志中的修改（增量备份）。二进制日志一旦创建就将永久保存。所以如果你不想让磁盘空间耗尽，你可以用 [PURGE BINARY LOGS](http://dev.mysql.com/doc/refman/5.6/en/purge-binary-logs.html) 来清除旧文件，或者设置`expire_logs_days`来指定过多少天日志将被自动清除。\n\n记录二进制日志不是没有开销的，所以如果你在一个非主节点的复制节点上不需要它的话，那么建议关闭这个选项。\n\n**`skip_name_resolve`**：当客户端连接数据库服务器时，服务器会进行主机名解析，并且当 DNS 很慢时，建立连接也会很慢。因此建议在启动服务器时关闭`skip_name_resolve`选项而不进行 DNS 查找。唯一的局限是之后`GRANT`语句中只能使用 IP 地址了，因此在添加这项设置到一个已有系统中必须格外小心。\n\n## 总结\n\n当然还有其他的设置可以起作用，取决于你的负载或硬件：在慢内存和快磁盘、高并发和写密集型负载情况下，你将需要特殊的调整。然而这里的目标是使得你可以快速地获得一个稳健的 MySQL 配置，而不用花费太多时间在调整一些无关紧要的 MySQL 设置或读文档找出哪些设置对你来说很重要上。\n\n# mysql 日志\n\nMySQL 服务器上一共有六种日志：错误日志，查询日志，慢查询日志，二进制日志，事务日志，中继日志\n\n## 慢日志 (5.1.73)\n\n慢查询日志，顾名思义就是记录执行比较慢查询的日志\n\n### 配置\n\n两种方法\n\n**修改配置（永久生效）**\n\n修改配置文件：```/etc/my.cnf```\n\n```ini\n[mysqld]\nslow_query_log = 1\nslow_query_log_file = /tmp/mysql_slow_queries.log\nlong_query_time = 10\n```\n配置后重启 mysql\n\n**修改变量（重启后失效）**\n\n`mysql> set global slow_query_log=1;`\n\n5.1.73 默认慢查询日志路径为：/var/run/mysqld/mysqld-slow.log\n\n### 查看变量\n\n**慢查询打开状态 (slow_query_log) 和日志位置 (slow_query_log_file)**\n\n`mysql> SHOW VARIABLES LIKE 'slow%';`\n\n```\n+---------------------------+----------------------------+\n| Variable_name             | Value                      |\n+---------------------------+----------------------------+\n| slow_launch_time          | 2                          |\n| slow_query_log            | ON                         |\n| slow_query_log_file       | /tmp/mysql_slow_queries.log|\n+---------------------------+----------------------------+\n3 rows in set (0.01 sec)\n```\n\n**慢查询时间**\n\n`mysql> SHOW VARIABLES LIKE 'long%';`\n\n```\n+---------------------------+-----------+\n| Variable_name             | Value     |\n+---------------------------+-----------+\n| long_query_time           | 10.000000 |\n+---------------------------+-----------+\n```\n### 测试\n\n登陆 mysql\n\n`mysql>select sleep(11);`\n\n查看日志\n\ntail -f /tmp/mysql_slow_queries.log\n\n### 日志查询\n\n列出记录次数最多的 10 个 sql 语句\n\n```\nmysqldumpslow -s c -t 10 /tmp/mysql_slow_queries.log\n```\n\n得到返回记录最多的 10 个 SQL。\n\n```\nmysqldumpslow -s r -t 10 /tmp/mysql_slow_queries.log\n```\n\n## 清理 MySQL binlog\n\n### 概要\n作为 master 的 Mysql 运行久了以后会在根目录中产生大量的 binlog 日志，如果不及时清理，会占用大量的磁盘空间，也会对数据库的正常运行带来隐患\n\n>之所以要开启 binlog 是因为，mysql 的主备复制是建立在 master 产生 binlog 的基础上\n\n### 相关基本参数\n\n**--log-bin[=base_name]**\n\nItem     | Format\n-------- | ---\nCommand-Line Format |--log-bin\nOption-File Format|log-bin\nSystem Variable Name|log_bin\nVariable Scope|Global\nDynamic Variable|No\nPermitted Values Type |file name\n\n> Enable binary logging. The server logs all statements that change data to the binary log, which is used for backup and replication.The option value, if given, is the basename for the log sequence. The server creates binary log files in sequence by adding a numeric suffix to the basename. It is recommended that you specify a basename (see Section C.5.8, “Known Issues in MySQL”, for the reason). Otherwise, MySQL uses host_name-bin as the basename.\n\n这是手册中关于 binlog 作用的两点描述\n\n>* For replication, the binary log on a master replication server provides a record of the data changes to be sent to slave servers. The master server sends the events contained in its binary log to its slaves, which execute those events to make the same data changes that were made on the master.\n>* Certain data recovery operations require use of the binary log. After a backup has been restored, the events in the binary log that were recorded after the backup was made are re-executed. These events bring databases up to date from the point of the backup\n\n\n**max_binlog_size**\n\nItem     | Format\n-------- | ---\nCommand-Line Format |--max_binlog_size=#\nOption-File Format|max\\_binlog\\_size\nSystem Variable Name|max\\_binlog\\_size\nVariable Scope|Global\nDynamic Variable|Yes\nPermitted Values Type |numeric\nDefault|1073741824\nRange|4096 .. 1073741824\n\n>If a write to the binary log causes the current log file size to exceed the value of this variable, the server rotates the binary logs (closes the current file and opens the next one). The minimum value is 4096bytes. The maximum and default value is 1GB.\n\n> **Note:** If max\\_relay\\_log\\_size is 0, the value of max\\_binlog\\_size applies to relay logs as well.\n\n\n**--log-bin-index[=file_name]**\n\nItem     | Format\n-------- | ---\nCommand-Line Format |--log-bin-index=name\nOption-File Format|log-bin-index\nSystem Variable Name|log_bin\nVariable Scope|Global\nDynamic Variable|No\nPermitted Values Type |file name\n\n>The index file for binary log file names. If you omit the file name,and if you did not specify one with --log-bin, MySQL uses host_name-bin.index as the file name.\n\n**expire_logs_days**\n\nItem     | Format\n-------- | ---\nCommand-Line Format |--expire\\_logs\\_days=#\nOption-File Format|expire\\_logs_days\nSystem Variable Name|expire\\_logs_days\nVariable Scope|Global\nDynamic Variable|Yes\nPermitted Values Type |numeric\nDefault|0\nRange|0 .. 99\n\n> The number of days for automatic binary log file removal. The default is 0, which means “no automatic removal.” Possible removals happen at startup and when the binary log is flushed. Log flushing occurs as indicated in Section 5.2, “MySQL Server Logs”.\n\n---\n\n### 清理方法\n\n#### 手动清理\n\n***使用 PURGE BINARY LOGS 进行清理***\n\n~~~\nmysql> help purge\nName: 'PURGE BINARY LOGS'\nDescription:\nSyntax:\nPURGE { BINARY | MASTER } LOGS\n    { TO 'log_name' | BEFORE datetime_expr }\n\nThe binary log is a set of files that contain information about data\nmodifications made by the MySQL server. The log consists of a set of\nbinary log files, plus an index file (see\nhttp://dev.mysql.com/doc/refman/5.1/en/binary-log.html).\n\nThe PURGE BINARY LOGS statement deletes all the binary log files listed\nin the log index file prior to the specified log file name or date.\nBINARY and MASTER are synonyms. Deleted log files also are removed from\nthe list recorded in the index file, so that the given log file becomes\nthe first in the list.\n\nThis statement has no effect if the server was not started with the\n--log-bin option to enable binary logging.\n\nURL: http://dev.mysql.com/doc/refman/5.1/en/purge-binary-logs.html\n\nExamples:\nPURGE BINARY LOGS TO 'mysql-bin.010';\nPURGE BINARY LOGS BEFORE '2008-04-02 22:46:26';\n\nmysql>\n~~~\n\n当 slave 正在复制时，这条命令也是安全的，如果尝试删除一个正在被读的日志文件，这个语句将什么事情也不做。\n\n但是如果一个 slave 没有连接上 master，而在 master 上删除了它还没读取的日志文件，一旦 salve 连接上 master，slave 将出错，无法正常复制。\n\n尽量遵循下面的流程，以确保安全删除日志文件：\n\n* 1. 在各个 slave 服务器上，使用**SHOW SLAVE STATUS**检查哪一个日志文件正在被读取\n* 2. 使用**SHOW BINARY LOGS**在 master 上获取一份日志文件列表\n* 3. 根据所有的 slaves 服务器，决定最早的那个日志文件是哪一个，这个就是目标文件，如果所有的 slaves 都更新完所有操作，这个日志文件就是列表中的最后一个\n* 4. 对所有即将删除的日志文件进行备份（这不是必要步骤，但是建议这么做）\n* 5. 使用**Purge**清理掉到目标日志的所有日志文件\n\n\n> **Note:** 当**.index**中列出的文件在系统中已经被各种原因移除（比如使用**rm**手动删除了）的情况下使用 **PURGE BINARY LOGS TO** 或 **PURGE BINARY LOGS BEFORE**会报错，解决办法是手动编辑**.index**文件，确保里面列出的文件在系统中真实存在，然后再次使用**PURGE BINARY LOGS**\n\n\n检查当前系统中的日志文件\n\n~~~\nmysql> show binary logs;\n+------------------+-----------+\n| Log_name         | File_size |\n+------------------+-----------+\n| mysql-bin.000001 |       125 |\n| mysql-bin.000002 |       125 |\n| mysql-bin.000003 |       125 |\n| mysql-bin.000004 |       125 |\n| mysql-bin.000005 |       125 |\n| mysql-bin.000006 |       125 |\n| mysql-bin.000007 |       125 |\n| mysql-bin.000008 |       125 |\n| mysql-bin.000009 |       125 |\n| mysql-bin.000010 |       125 |\n| mysql-bin.000011 |       125 |\n| mysql-bin.000012 |       125 |\n| mysql-bin.000013 |       125 |\n| mysql-bin.000014 |       125 |\n| mysql-bin.000015 |       125 |\n| mysql-bin.000016 |       106 |\n+------------------+-----------+\n16 rows in set (0.00 sec)\n\nmysql> \\! ls -l /var/lib/mysql\ntotal 20648\n-rw-rw----. 1 mysql mysql 10485760 Mar 18 17:32 ibdata1\n-rw-rw----. 1 mysql mysql  5242880 Mar 18 17:32 ib_logfile0\n-rw-rw----. 1 mysql mysql  5242880 Mar 18 17:32 ib_logfile1\n-rw-r-----. 1 mysql root     27513 Mar 19 00:27 localhost.localdomain.err\n-rw-r-----. 1 mysql root     36471 Apr  1 17:07 m2.err\n-rw-rw----. 1 mysql mysql        5 Apr  1 17:07 m2.pid\n-rw-rw----. 1 mysql mysql      125 Mar 31 22:27 m2-relay-bin.000001\n-rw-rw----. 1 mysql mysql      106 Apr  1 17:07 m2-relay-bin.000002\n-rw-rw----. 1 mysql mysql       44 Apr  1 17:07 m2-relay-bin.index\n-rw-rw----. 1 mysql mysql       68 Apr  1 17:07 master.info\ndrwx------. 2 mysql mysql     4096 Mar 31 19:28 mysql\n-rw-rw----. 1 mysql mysql      125 Mar 18 23:42 mysql-bin.000001\n-rw-rw----. 1 mysql mysql      125 Mar 19 00:27 mysql-bin.000002\n-rw-rw----. 1 mysql mysql      125 Mar 19 11:37 mysql-bin.000003\n-rw-rw----. 1 mysql mysql      125 Mar 19 15:03 mysql-bin.000004\n-rw-rw----. 1 mysql mysql      125 Mar 19 15:29 mysql-bin.000005\n-rw-rw----. 1 mysql mysql      125 Mar 19 15:56 mysql-bin.000006\n-rw-rw----. 1 mysql mysql      125 Mar 19 16:45 mysql-bin.000007\n-rw-rw----. 1 mysql mysql      125 Mar 19 17:27 mysql-bin.000008\n-rw-rw----. 1 mysql mysql      125 Mar 19 17:56 mysql-bin.000009\n-rw-rw----. 1 mysql mysql      125 Mar 19 19:06 mysql-bin.000010\n-rw-rw----. 1 mysql mysql      125 Mar 19 20:11 mysql-bin.000011\n-rw-rw----. 1 mysql mysql      125 Mar 20 02:33 mysql-bin.000012\n-rw-rw----. 1 mysql mysql      125 Mar 28 02:36 mysql-bin.000013\n-rw-rw----. 1 mysql mysql      125 Mar 28 06:08 mysql-bin.000014\n-rw-rw----. 1 mysql mysql      125 Mar 31 22:27 mysql-bin.000015\n-rw-rw----. 1 mysql mysql      106 Apr  1 17:07 mysql-bin.000016\n-rw-rw----. 1 mysql mysql      304 Apr  1 17:07 mysql-bin.index\nsrwxrwxrwx. 1 mysql mysql        0 Apr  1 17:07 mysql.sock\n-rw-rw----. 1 mysql mysql       60 Apr  1 17:07 relay-log.info\ndrwx------. 2 mysql mysql     4096 Mar 18 17:32 test\nmysql>\n~~~\n\n删除掉**mysql-bin.000004**之前的日志\n\n~~~\nmysql> purge binary logs to 'mysql-bin.000004';\nQuery OK, 0 rows affected (0.00 sec)\n\nmysql> show binary logs;\n+------------------+-----------+\n| Log_name         | File_size |\n+------------------+-----------+\n| mysql-bin.000004 |       125 |\n| mysql-bin.000005 |       125 |\n| mysql-bin.000006 |       125 |\n| mysql-bin.000007 |       125 |\n| mysql-bin.000008 |       125 |\n| mysql-bin.000009 |       125 |\n| mysql-bin.000010 |       125 |\n| mysql-bin.000011 |       125 |\n| mysql-bin.000012 |       125 |\n| mysql-bin.000013 |       125 |\n| mysql-bin.000014 |       125 |\n| mysql-bin.000015 |       125 |\n| mysql-bin.000016 |       106 |\n+------------------+-----------+\n13 rows in set (0.00 sec)\n\nmysql> \\! ls -l /var/lib/mysql\ntotal 20636\n-rw-rw----. 1 mysql mysql 10485760 Mar 18 17:32 ibdata1\n-rw-rw----. 1 mysql mysql  5242880 Mar 18 17:32 ib_logfile0\n-rw-rw----. 1 mysql mysql  5242880 Mar 18 17:32 ib_logfile1\n-rw-r-----. 1 mysql root     27513 Mar 19 00:27 localhost.localdomain.err\n-rw-r-----. 1 mysql root     36471 Apr  1 17:07 m2.err\n-rw-rw----. 1 mysql mysql        5 Apr  1 17:07 m2.pid\n-rw-rw----. 1 mysql mysql      125 Mar 31 22:27 m2-relay-bin.000001\n-rw-rw----. 1 mysql mysql      106 Apr  1 17:07 m2-relay-bin.000002\n-rw-rw----. 1 mysql mysql       44 Apr  1 17:07 m2-relay-bin.index\n-rw-rw----. 1 mysql mysql       68 Apr  1 17:07 master.info\ndrwx------. 2 mysql mysql     4096 Mar 31 19:28 mysql\n-rw-rw----. 1 mysql mysql      125 Mar 19 15:03 mysql-bin.000004\n-rw-rw----. 1 mysql mysql      125 Mar 19 15:29 mysql-bin.000005\n-rw-rw----. 1 mysql mysql      125 Mar 19 15:56 mysql-bin.000006\n-rw-rw----. 1 mysql mysql      125 Mar 19 16:45 mysql-bin.000007\n-rw-rw----. 1 mysql mysql      125 Mar 19 17:27 mysql-bin.000008\n-rw-rw----. 1 mysql mysql      125 Mar 19 17:56 mysql-bin.000009\n-rw-rw----. 1 mysql mysql      125 Mar 19 19:06 mysql-bin.000010\n-rw-rw----. 1 mysql mysql      125 Mar 19 20:11 mysql-bin.000011\n-rw-rw----. 1 mysql mysql      125 Mar 20 02:33 mysql-bin.000012\n-rw-rw----. 1 mysql mysql      125 Mar 28 02:36 mysql-bin.000013\n-rw-rw----. 1 mysql mysql      125 Mar 28 06:08 mysql-bin.000014\n-rw-rw----. 1 mysql mysql      125 Mar 31 22:27 mysql-bin.000015\n-rw-rw----. 1 mysql mysql      106 Apr  1 17:07 mysql-bin.000016\n-rw-rw----. 1 mysql mysql      247 Apr  1 19:47 mysql-bin.index\nsrwxrwxrwx. 1 mysql mysql        0 Apr  1 17:07 mysql.sock\n-rw-rw----. 1 mysql mysql       60 Apr  1 17:07 relay-log.info\ndrwx------. 2 mysql mysql     4096 Mar 18 17:32 test\nmysql>\n~~~\n\n查看 binlog 事件\n\n~~~\nmysql> show binlog events\\G\n*************************** 1. row ***************************\n   Log_name: mysql-bin.000004\n        Pos: 4\n Event_type: Format_desc\n  Server_id: 2\nEnd_log_pos: 106\n       Info: Server ver: 5.1.73-14.12-log, Binlog ver: 4\n*************************** 2. row ***************************\n   Log_name: mysql-bin.000004\n        Pos: 106\n Event_type: Stop\n  Server_id: 2\nEnd_log_pos: 125\n       Info:\n2 rows in set (0.03 sec)\n\nmysql>\n~~~\n\n根据时间来清理\n\n~~~\nmysql> purge master logs before '2015-03-19 15:56:00'\nQuery OK, 0 rows affected (0.01 sec)\n\nmysql> show binary logs;\n+------------------+-----------+\n| Log_name         | File_size |\n+------------------+-----------+\n| mysql-bin.000006 |       125 |\n| mysql-bin.000007 |       125 |\n| mysql-bin.000008 |       125 |\n| mysql-bin.000009 |       125 |\n| mysql-bin.000010 |       125 |\n| mysql-bin.000011 |       125 |\n| mysql-bin.000012 |       125 |\n| mysql-bin.000013 |       125 |\n| mysql-bin.000014 |       125 |\n| mysql-bin.000015 |       125 |\n| mysql-bin.000016 |       106 |\n+------------------+-----------+\n11 rows in set (0.00 sec)\n\nmysql>\n~~~\n\n清理 5 天之前的日志\n\n~~~\nmysql> select now();\n+---------------------+\n| now()               |\n+---------------------+\n| 2015-04-02 13:52:13 |\n+---------------------+\n1 row in set (0.00 sec)\n\nmysql> select date_sub(now(),interval 5 day);\n+--------------------------------+\n| date_sub(now(),interval 5 day) |\n+--------------------------------+\n| 2015-03-28 13:52:15            |\n+--------------------------------+\n1 row in set (0.00 sec)\n\nmysql> purge master logs before date_sub(now(),interval 5 day);\nQuery OK, 0 rows affected (0.01 sec)\n\nmysql> show binary logs;\n+------------------+-----------+\n| Log_name         | File_size |\n+------------------+-----------+\n| mysql-bin.000015 |       125 |\n| mysql-bin.000016 |       125 |\n| mysql-bin.000017 |       106 |\n+------------------+-----------+\n3 rows in set (0.00 sec)\n\nmysql> \\! ls -l *mysql-bin*\n-rw-rw----. 1 mysql mysql 125 Mar 31 22:27 mysql-bin.000015\n-rw-rw----. 1 mysql mysql 125 Apr  1 22:56 mysql-bin.000016\n-rw-rw----. 1 mysql mysql 106 Apr  2 10:35 mysql-bin.000017\n-rw-rw----. 1 mysql mysql  57 Apr  2 13:52 mysql-bin.index\nmysql> \\! cat mysql-bin.index\n./mysql-bin.000015\n./mysql-bin.000016\n./mysql-bin.000017\nmysql>\n~~~\n\n\n---\n\n***使用 RESET MASTER 进行清理***\n\n**RESET MASTER**会删除 index 文件中所有的 binary log，重新设置 binary log 为空，创建新的 binary log，这条语句适合在第一次 master 运行启动后，不太适合在生产环境中已经运行了好久的情况下。\n\n> **Note:** **RESET MASTER**和**PURGE BINARY LOGS**有以下两点不同\n>\n>* 1.**RESET MASTER**会移除掉 index 文件中的所有日志，然后只留下一个空的以**.000001**结尾的日志文件，但是**PURGE BINARY LOGS**不会重设后缀\n>* 2.**RESET MASTER**不是设计来用在有任何 slave 正在运行的情况下，但**PURGE BINARY LOGS**是设计来在此种情况下使用的，当 slave 的复制正在运行时使用**PURGE BINARY LOGS**也是安全的\n\n**RESET MASTER**在第一次设置 master 和 slave 的情况下非常有用，可以按照下面的步骤进行检查：\n\n* 1. 运行 master 和 slave, 开启复制\n* 2. 在 master 中进行一系列的插入测试\n* 3. 在 slave 中检查更新有无按预期同步\n* 4. 在 salve 上确认复制可以正常运行后，**RESET SLAVE** 然后 ** STOP SLAVE** ，然后确保所有不想要的数据在 slave 上已经清理\n* 5. 在 master 上执行**RESET MASTER**清除掉测试数据\n* 6. 在检查所有的不想要的测试数据和日志已经清理掉后，可以在 slave 上重新开启复制\n\n#### 自动清理\n\n可以使用**expire_logs_days**系统变量来设定日志过期时间，自动删除过期日志，如果环境中有复制，注意要设定合适的值，这个值要大于最坏情况下 slave 可能落后于 master 的天数。\n\n>自动清理操作会发生在系统开启和日志刷新的时候\n\n\n设定 5 天为日志过期时间\n\n~~~\nmysql> show variables like \"%expire%\";\n+------------------+-------+\n| Variable_name    | Value |\n+------------------+-------+\n| expire_logs_days | 0     |\n+------------------+-------+\n1 row in set (0.00 sec)\n\nmysql> set global expire_logs_days=5 ;\nQuery OK, 0 rows affected (0.00 sec)\n\nmysql> show variables like \"%expire%\";\n+------------------+-------+\n| Variable_name    | Value |\n+------------------+-------+\n| expire_logs_days | 5     |\n+------------------+-------+\n1 row in set (0.00 sec)\n\nmysql>\n~~~\n\n\n\n# mysql 管理\n\n## 用户管理\n\n### 创建用户\n\n  CREATE USER 'username'@'host' IDENTIFIED BY 'password';\n\n用户创建之后只能看到`information_schema`数据库，使用`show grants;`看到自己的权限为`GRANT USAGE ON *.* TO 'user'@'localhost'`;\n\n### 为用户授权\n使用***GRANT 命令***为用户授予数据库操作的权力，比如增删改查等。\n\n```\n> GRANT ALL PRIVILEGES ON *.* TO 'myuser'@'%' IDENTIFIED BY 'mypassword' WITH GRANT OPTION;  \n> FLUSH   PRIVILEGES; \n```\n其中 `%` 表示对所有主机开放，只对本机开放时可以写 `127.0.0.1`\n\n### 修改用户密码\nSET PASSWORD（推荐）,MySQL5.7.6 及以后：\n\n    SET PASSWORD [FOR user] = password_option\n    password_option: {\n        PASSWORD('auth_string') # 弃用\n      | 'auth_string'\n    }\n    # 修改自己的密码\n    SET PASSWORD = '123456';\n    # 修改一用户的密码\n    SET PASSWORD FOR 'user'@'34.23.44.32' = '123456';\n\nALTER（推荐）\n\n    ALTER USER 'user'@'localhost' IDENTIFIED BY 'auth_string';\n\n忘记 root 密码\n\n    # 正确关闭 MySQL 服务\n    $ mysqld stop\n    # 开启不验证登录\n    $ mysqld_safe --skip-grant-tables &\n    # 用上述其他方法修改密码\n    # 退出重启 MySQL\n\n    # 方法二：\n    1. 在 /etc/mysql/my.cnf 或者 /etc/my.cnf 中 [mysqld] 加入 `skip-grant-tales` 可以免除密码登陆。\n    2. `service mysqld start`启动 mysql 后，直接在终端输入`mysql`可以免密码直接登陆。\n    3. > flush privileges;\n    4. set password for root@localhost = 'new-pass';\n\n    在 MySQL 5.7 之后，Windows 使用解压缩安装 MySQL, 需要依次执行如下方便初始化：\n    mysqld --initialize-insecure # 删除其他所有用户，无密码初始化 MySQL root 用户，如果没有 insecure, 则会创建随机密码\n    mysqld -install # 安装服务\n    之后再不输入密码登录 root 用户，再设置密码。\n\n\nmysqladmin（不安全）, 在终端输入：\n\n    $ mysqladmin -u username -p password '123456'\n\nUPDATE user 表\n\n    UPDATE user SET password=password('123456') WHERE user='name' AND host='12.44.33.22';\n    FLUSH privileges;\n\n## mysql 主从\n\n### 清除主从关系\n\n一般情况下清除主从关系只需要做以下操作即可\n\n* 登陆 slave 数据库后执行 `stop slave`\n* 登陆 slave 数据库后执行 `RESET SLAVE`\n* 重启 slave 的 mysql 服务\n\n阅读下方内容了解更多清除主从关系的相关操作\n\n**RESET MASTER**\n\n删除所有 index file 中记录的所有 binlog 文件，将日志索引文件清空，创建一个新的日志文件，这个命令通常仅仅用于第一次用于搭建主从关系的时的主库，\n\n注意\n\nreset master 不同于 purge binary log 的两处地方\n\n> * reset master 将删除日志索引文件中记录的所有 binlog 文件，创建一个新的日志文件 起始值从 000001 开始，然而 purge binary log 命令并不会修改记录 binlog 的顺序的数值\n> * reset master 不能用于有任何 slave 正在运行的主从关系的主库。因为在 slave 运行时刻 reset master 命令不被支持，reset master 将 master 的 binlog 从 000001 开始记录，slave 记录的 master log 则是 reset master 时主库的最新的 binlog, 从库会报错无法找的指定的 binlog 文件。\n\n**RESET SLAVE**\n\n```\n使用 reset slave 之前必须使用 stop slave 命令将复制进程停止。\n\nreset slave 将使 slave 忘记主从复制关系的位置信息。该语句将被用于干净的启动，它删除 master.info 文件和 relay-log.info 文件以及所有的 relay log 文件并重新启用一个新的 relaylog 文件。\n\n在 5.1.73（后面的版本貌似也是） 的版本中 reset slave 并不会清理存储于内存中的复制信息比如  master host, master port, master user, or master password, 也就是说如果没有使用 change master 命令做重新定向，执行 start slave 还是会指向旧的 master 上面。当从库执行 reset slave 之后，将 mysqld 重启后复制参数将被重置。\n\n注 所有的 relay log 将被删除不管他们是否被 SQL thread 进程完全应用（这种情况发生于备库延迟以及在备库执行了 stop slave 命令）, 存储复制链接信息的 master.info 文件将被立即清除，如果 SQL thread 正在复制临时表的过程中，执行了 stop slave ，并且执行了 reset slave，这些被复制的临时表将被删除。\n```\n\n**RESET SLAVE  ALL**(5.6 后支持）\n\n在 5.6.3 版本以及以后 使用使用 RESET SLAVE ALL 来完全的清理复制连接参数信息。\n# mysql 工具\n\n* [mysql 日常定时备份](https://github.com/meetbill/mysql_tools)\n\n# mysql 运维\n\n## MySQL 忘记密码\n\n**现象**\n\n```\n#mysql -u root -p\n#就会出现：ERROR 1045 (28000): Access denied for user ''@'localhost' (using password: NO)\n```\n**解决方法**\n\n```\nl 关闭 mysql\n# service mysqld stop\n\n2. 屏蔽权限\n# mysqld_safe --skip-grant-table\n屏幕出现：\n161028 22:30:08 mysqld_safe Logging to '/var/log/mysqld.log'.\n161028 22:30:08 mysqld_safe Starting mysqld daemon with databases from /var/lib/mysql'\n\n3. 新开起一个终端输入\n#mysql -u root\nmysql> UPDATE user SET Password=PASSWORD('newpassword') where USER='root';\nmysql> FLUSH PRIVILEGES;// 记得要这句话，否则如果关闭先前的终端，又会出现原来的错误\nmysql> \\q\n#/etc/init.d/mysqld restart\n```\n## mysql.sock 位置错误\n\n日常迁移完数据库的存储路径后，client 登陆失败\n\n**解决方法**\n\n修改 /etc/my.cnf 配置文件\n```\n[client]\nsocket = /tmp/mysql.sock\n```\n"
  },
  {
    "path": "doc/db/redis.md",
    "content": "<!-- vim-markdown-toc GFM -->\n\n* [1 Redis](#1-redis)\n    * [1.1 持久化](#11-持久化)\n        * [1.1.1 AOF 重写机制](#111-aof-重写机制)\n    * [1.2 主从同步](#12-主从同步)\n        * [1.2.1 repl-timeout](#121-repl-timeout)\n        * [1.2.2 写入量太大超出 output-buffer](#122-写入量太大超出-output-buffer)\n        * [1.2.3 repl-backlog-size 太小导致失败](#123-repl-backlog-size-太小导致失败)\n        * [1.2.4 主库磁盘故障](#124-主库磁盘故障)\n            * [主库磁盘异常示例记录](#主库磁盘异常示例记录)\n    * [1.3 Redis bug](#13-redis-bug)\n        * [1.3.1 AOF 句柄泄露 bug](#131-aof-句柄泄露-bug)\n            * [表现](#表现)\n            * [分析](#分析)\n            * [解决](#解决)\n        * [1.3.2 在 AOF 文件 rewrite 期间如果设置 config set appendonly no，会导致 redis 进程一直死循环不间断触发 rewrite AOF](#132-在-aof-文件-rewrite-期间如果设置-config-set-appendonly-no会导致-redis-进程一直死循环不间断触发-rewrite-aof)\n            * [根因](#根因)\n        * [1.3.3 redis slots 迁移的时候，永不过期的 key 因为 ttl>0 而过期，导致迁移丢失数据](#133-redis-slots-迁移的时候永不过期的-key-因为-ttl0-而过期导致迁移丢失数据)\n            * [根因](#根因-1)\n        * [1.3.4 3.x 执行 exists 可以获取到，但 get 时则无法获取到数据](#134-3x-执行-exists-可以获取到但-get-时则无法获取到数据)\n            * [3.x exists 逻辑](#3x-exists-逻辑)\n            * [4.x exists 逻辑](#4x-exists-逻辑)\n        * [1.3.5 5.0 以前 redis 主从同步时因从库的 buffer 计算不准确导致主库大量的 key 被淘汰](#135-50-以前-redis-主从同步时因从库的-buffer-计算不准确导致主库大量的-key-被淘汰)\n    * [1.4 redis 日志及状态信息](#14-redis-日志及状态信息)\n        * [1.4.1 日常日志](#141-日常日志)\n        * [1.4.2 info](#142-info)\n    * [1.5 redis 协议说明](#15-redis-协议说明)\n        * [1.5.1 网络层](#151-网络层)\n        * [1.5.2 请求](#152-请求)\n        * [1.5.3 新的统一请求协议](#153-新的统一请求协议)\n        * [1.5.4 回复](#154-回复)\n        * [1.5.6 多批量回复中的 Nil 元素](#156-多批量回复中的-nil-元素)\n        * [1.5.7 多命令和管道](#157-多命令和管道)\n        * [1.5.8 旧协议发送命令](#158-旧协议发送命令)\n    * [1.6 Redis RDB 文件格式](#16-redis-rdb-文件格式)\n        * [1.6.1 解析 RDB 的高层算法](#161-解析-rdb-的高层算法)\n            * [魔术数](#魔术数)\n            * [RDB 版本号](#rdb-版本号)\n            * [数据库选择器](#数据库选择器)\n            * [键值对](#键值对)\n                * [键保存期限时间戳](#键保存期限时间戳)\n                * [值类型](#值类型)\n                * [键](#键)\n                * [值](#值)\n        * [1.6.2 长度编码](#162-长度编码)\n        * [1.6.3 字符串编码](#163-字符串编码)\n            * [长度前缀字符串](#长度前缀字符串)\n            * [整数作为字符串](#整数作为字符串)\n            * [压缩字符串](#压缩字符串)\n        * [1.6.4 List 编码](#164-list-编码)\n        * [1.6.5 Set 编码](#165-set-编码)\n        * [1.6.6 Sorted Set 编码](#166-sorted-set-编码)\n        * [1.6.7 Hash 编码](#167-hash-编码)\n        * [1.6.8 Zipmap 编码](#168-zipmap-编码)\n        * [1.6.9 Ziplist 编码](#169-ziplist-编码)\n        * [1.6.10 Intset 编码](#1610-intset-编码)\n        * [1.6.11 以 Ziplist 编码的 Sorted Set](#1611-以-ziplist-编码的-sorted-set)\n        * [1.6.12 Ziplist 编码的 Hashmap](#1612-ziplist-编码的-hashmap)\n            * [CRC32 校验和](#crc32-校验和)\n    * [1.7 Redis 内存](#17-redis-内存)\n        * [1.7.1 used_memmory](#171-used_memmory)\n        * [1.7.2 used_memmory 会大于 maxmemory 吗？](#172-used_memmory-会大于-maxmemory-吗)\n    * [1.8 骚操作](#18-骚操作)\n        * [1.8.1 Redis 关闭过期自动删除策略](#181-redis-关闭过期自动删除策略)\n* [2 Redis twemproxy 集群](#2-redis-twemproxy-集群)\n    * [2.1 Twemproxy 特性](#21-twemproxy-特性)\n    * [2.2 环境说明](#22-环境说明)\n    * [2.2 安装依赖](#22-安装依赖)\n    * [2.3 安装 Twemproxy](#23-安装-twemproxy)\n    * [2.4 配置 Twemproxy](#24-配置-twemproxy)\n    * [2.5 启动 Twemproxy](#25-启动-twemproxy)\n        * [2.5.1 启动命令详解](#251-启动命令详解)\n        * [2.5.2 启动](#252-启动)\n    * [2.6 查看状态](#26-查看状态)\n        * [2.6.1 状态参数](#261-状态参数)\n        * [2.6.2 状态实例](#262-状态实例)\n        * [2.6.3 获取 Twemproxy 状态](#263-获取-twemproxy-状态)\n    * [2.7 其他](#27-其他)\n        * [2.7.1 发送信号修改日志级别以及重新打开日志文件](#271-发送信号修改日志级别以及重新打开日志文件)\n* [3 redis cluster](#3-redis-cluster)\n    * [3.1 cluster 命令](#31-cluster-命令)\n    * [3.2 redis cluster 配置](#32-redis-cluster-配置)\n    * [3.3 redis cluster 状态](#33-redis-cluster-状态)\n    * [3.4 redis cluster 的 failover 机制](#34-redis-cluster-的-failover-机制)\n        * [3.4.1 故障 failover](#341-故障-failover)\n            * [探测阶段](#探测阶段)\n            * [准备阶段](#准备阶段)\n            * [执行阶段](#执行阶段)\n        * [3.4.2 人为 failover](#342-人为-failover)\n            * [缺省](#缺省)\n            * [force](#force)\n            * [takeover](#takeover)\n    * [3.5 弃用 redis cluster 的原因](#35-弃用-redis-cluster-的原因)\n        * [3.5.1 集群规模比较大时，容易出现 handshake 节点](#351-集群规模比较大时容易出现-handshake-节点)\n        * [3.5.2 网络问题导致某个 node 隔离后，在很长时间后， node 网络恢复，可能发生集群融合](#352-网络问题导致某个-node-隔离后在很长时间后-node-网络恢复可能发生集群融合)\n* [4 原理说明](#4-原理说明)\n    * [4.1 一致性 hash](#41-一致性-hash)\n        * [4.1.1 传统的取模方式](#411-传统的取模方式)\n        * [4.1.2 一致性哈希方式](#412-一致性哈希方式)\n        * [4.1.3 虚拟节点](#413-虚拟节点)\n    * [4.2 redis 过期数据存储方式以及删除方式](#42-redis-过期数据存储方式以及删除方式)\n        * [4.2.1 存储方式](#421-存储方式)\n        * [4.2.2 删除方式](#422-删除方式)\n            * [惰性删除](#惰性删除)\n            * [定期删除](#定期删除)\n        * [4.2.3 redis 主从删除过期 key 方式](#423-redis-主从删除过期-key-方式)\n        * [4.2.4 总结](#424-总结)\n    * [4.3 cluster 选举算法 Raft](#43-cluster-选举算法-raft)\n* [5 常见问题处理](#5-常见问题处理)\n    * [5.1 内核参数 overcommit](#51-内核参数-overcommit)\n        * [什么是 Overcommit 和 OOM](#什么是-overcommit-和-oom)\n    * [5.2 Redis CPU 100% 时分析](#52-redis-cpu-100-时分析)\n    * [5.3 Redis Cluster 集群出现 handshake 节点](#53-redis-cluster-集群出现-handshake-节点)\n* [6 数据迁移](#6-数据迁移)\n    * [6.1 目标](#61-目标)\n    * [6.2 怎么实现](#62-怎么实现)\n        * [6.2.1 方案](#621-方案)\n    * [6.3 问题](#63-问题)\n        * [6.3.1 aof 不是幂等的](#631-aof-不是幂等的)\n        * [6.3.2 切流量时的不一致](#632-切流量时的不一致)\n    * [6.4 实现](#64-实现)\n* [7 redis 服务准入](#7-redis-服务准入)\n    * [7.1 数据设计](#71-数据设计)\n        * [7.1.1 value 大小约束](#711-value-大小约束)\n        * [7.1.2 value 复杂度约束](#712-value-复杂度约束)\n        * [7.1.3 冷热数据设计约束](#713-冷热数据设计约束)\n        * [7.1.4 分片数据量约束](#714-分片数据量约束)\n    * [7.2 运行时约束](#72-运行时约束)\n        * [7.2.1 多 key 命令注意事项](#721-多-key-命令注意事项)\n        * [7.2.2 pipline 命令注意事项](#722-pipline-命令注意事项)\n        * [7.2.3 不支持的命令](#723-不支持的命令)\n    * [7.3 风险点](#73-风险点)\n        * [7.3.1 无协议保障的 io 流程](#731-无协议保障的-io-流程)\n        * [7.3.2 单线程带来的风险](#732-单线程带来的风险)\n        * [7.3.3 主从同步系数](#733-主从同步系数)\n\n<!-- vim-markdown-toc -->\n# 1 Redis\n\n## 1.1 持久化\n\n### 1.1.1 AOF 重写机制\n\nAOF 重写触发条件\n\nAOF 重写可以由用户通过调用 BGREWRITEAOF 手动触发。\n服务器在 AOF 功能开启的情况下，会维持以下三个变量：\n\n> * 记录当前 AOF 文件大小的变量 aof_current_size。\n> * 记录最后一次 AOF 重写之后，AOF 文件大小的变量 aof_rewrite_base_size。\n> * 增长百分比变量 aof_rewrite_perc。\n\n每次当 serverCron（服务器周期性操作函数，在 src/redis.c 中）函数执行时，它会检查以下条件是否全部满足，如果全部满足的话，就触发自动的 AOF 重写操作：\n\n> * 没有 BGSAVE 命令（RDB 持久化）/AOF 持久化在执行；\n> * 没有 BGREWRITEAOF 在进行；\n> * auto-aof-rewrite-percentage 参数不为 0\n> * 当前 AOF 文件大小要大于 server.aof_rewrite_min_size（默认为 1MB）\n> * 当前 AOF 文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比（在配置文件设置了 auto-aof-rewrite-percentage 参数，不设置默认为 100%）\n\n如果前面四个条件都满足，并且当前 AOF 文件大小比最后一次 AOF 重写时的大小要大于指定的百分比，那么触发自动 AOF 重写。\n\n源码如下：\n```\n /* Trigger an AOF rewrite if needed */\n        // 触发 BGREWRITEAOF\n         if (server.rdb_child_pid == -1 &&\n             server.aof_child_pid == -1 &&\n             server.aof_rewrite_perc &&\n             // AOF 文件的当前大小大于执行 BGREWRITEAOF 所需的最小大小\n             server.aof_current_size > server.aof_rewrite_min_size)\n         {\n            // 上一次完成 AOF 写入之后，AOF 文件的大小\n            long long base = server.aof_rewrite_base_size ?\n                            server.aof_rewrite_base_size : 1;\n\n            // AOF 文件当前的体积相对于 base 的体积的百分比\n            long long growth = (server.aof_current_size*100/base) - 100;\n\n            // 如果增长体积的百分比超过了 growth ，那么执行 BGREWRITEAOF\n            if (growth >= server.aof_rewrite_perc) {\n                redisLog(REDIS_NOTICE,\"Starting automatic rewriting of AOF on %lld%% growth\",growth);\n                // 执行 BGREWRITEAOF\n                rewriteAppendOnlyFileBackground();\n            }\n         }\n```\n## 1.2 主从同步\n\n主从同步相关参数\n\n> * repl-backlog-size: 增量重传 buf\n> * repl-timeout: 主动超时\n> * client-output-buffer-limit（和写入量有关）\n>   * 这个参数分为 3 部分，第二部分涉及 slave\n>   * slave 部分默认值：256M 64M 60 秒\n>   * output-buffer 缓冲区里放的是主库待同步给从库的操作数据。\n>   * 如果 output-buffer>256M 则从节点需要重新全同步，如果 256>output-buffer>64 且持续时间 60 秒，则从节点需要重新全同步。\n\n主从同步\n\n分别启动 master 和 slave 后，会自动启动同步\nslave 出现如下类似日志，则同步已完成：\n```\n[4611] 24 Aug 19:11:46.843 * MASTER <-> SLAVE sync started\n[4611] 24 Aug 19:11:46.844 * Non blocking connect for SYNC fired the event.\n[4611] 24 Aug 19:11:46.844 * Master replied to PING, replication can continue...\n[4611] 24 Aug 19:11:46.844 * Partial resynchronization not possible (no cached master)\n[4611] 24 Aug 19:11:46.844 * Full resync from master: 0629e2e6e79c13c21ff38b638b6009183140939a:1\n[4611] 24 Aug 19:13:55.662 * MASTER <-> SLAVE sync: receiving 5774276835 bytes from master\n[4611] 24 Aug 19:14:45.578 * MASTER <-> SLAVE sync: Flushing old data\n[4611] 24 Aug 19:16:57.509 * MASTER <-> SLAVE sync: Loading DB in memory\n[4611] 24 Aug 19:19:44.191 * MASTER <-> SLAVE sync: Finished with success\n```\n> 4.x 后的 redis 从库日志\n```\n13382:S 20 May 20:33:45.487 * SLAVE OF {master_ip}:{master_port} enabled (user request from 'id=3 addr={client_ip}:54054 fd=8 name= age=1 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=32768 obl=0 oll=0 omem=0 events=r cmd=slaveof')\n13382:S 20 May 20:33:45.802 * Connecting to MASTER {master_ip}:{master_port}\n13382:S 20 May 20:33:45.802 * MASTER <-> SLAVE sync started\n13382:S 20 May 20:33:45.802 * Non blocking connect for SYNC fired the event.\n13382:S 20 May 20:33:45.802 * start send sync cmd :PING        # 如果 master 没有返回异常，而是返回 pong，则说明 master 可用\n                                                               # 如果 Redis 设置了密码，slave 会发送 auth $masterauth 指令，进行鉴权。\n\n13382:S 20 May 20:33:45.802 * Master replied to PING, replication can continue...\n13382:S 20 May 20:33:45.802 * start send sync cmd :REPLCONF listening-port 2051    # 从库通过 replconf 发送自己的端口及 IP 给 master。\n\n13382:S 20 May 20:33:45.802 * start send sync cmd :REPLCONF capa eof capa psync2   # slave 通过 replconf 发送 capa eof capa psync2 进行复制版本校验\n\n13382:S 20 May 20:33:45.802 * Partial resynchronization(mem) not possible (no cached master)\n13382:S 20 May 20:33:45.802 * start send sync cmd :PSYNC ? -1                      # 从库接下来就通过 psync 将自己的复制 id、复制偏移发送给 master，正式开始准备数据同步\n\n13382:S 20 May 20:33:45.882 * Full resync from master: 3abf1447d61679bf3b83c1e3b8f6402446ab0d6b:0\n13382:S 20 May 20:34:02.574 * MASTER <-> SLAVE sync: receiving 736712380 bytes from master\n13382:S 20 May 20:34:04.306 * MASTER <-> SLAVE sync: Flushing old data\n13382:S 20 May 20:34:04.306 * MASTER <-> SLAVE sync: Loading DB in memory\n13382:S 20 May 20:34:11.706 * MASTER <-> SLAVE sync: Finished with success\n13382:S 20 May 20:34:13.118 * SLAVE OF would result into synchronization with the master we are already connected with. No operation performed.\n```\n### 1.2.1 repl-timeout\n若 slave 日志出现如下行：\n```\n# Timeout receiving bulk data from MASTER... If the problem persists try to set the    'repl-timeout' parameter in redis.conf to a larger value.\n```\n调整 slave 的 redis.conf 参数：\n```\nrepl-timeout 60  # 将数值设得更大\n如：config set repl-timeout 600\n```\n### 1.2.2 写入量太大超出 output-buffer\n\n若 slave 日志出现如下行：\n```\n# I/O error reading bulk count from MASTER: Resource temporarily unavailable\n# I/O error trying to sync with MASTER: connection lost\n```\n\n调整 master 分配给 slave client buffer：\n```\nclient-output-buffer-limit slave 256mb 64mb 60\n# 256mb 是一个硬性限制，当 output-buffer 的大小大于 256mb 之后就会断开连接\n# 64mb 60 是一个软限制，当 output-buffer 的大小大于 64mb 并且超过了 60 秒的时候就会断开连接\n# 或者全部设为 0，取消限制。\n\n如：config set client-output-buffer-limit \"slave 0 0 0\"\n```\n### 1.2.3 repl-backlog-size 太小导致失败\n\n当 master-slave 复制连接断开，server 端会释放连接相关的数据结构。replication buffer 中的数据也就丢失，当断开的 slave 重新连接上 master 的时候，slave 将会发送 psync 命令（包含复制的偏移量 offset），请求 partial resync。如果请求的 offset 不存在，那么执行全量的 sync 操作，相当于重新建立主从复制。\n\n> 主库日志\n```\n[16250] 26 Jul 22:59:13.921 # replication.c: 519 psync_offset:11198236363343 repl_backlog_off:11199960061969 repl_backlog_histlen:104857600\n[16250] 26 Jul 22:59:13.921 * replication.c: 526 Unable to partial resync with the slave for lack of backlog (Slave request was: 11198236363343).\n\nor\n\nUnable to partial resync with slave $slaveip:6379 for lack of backlog (Slave request was: 5974421660).\n```\n调整 repl-backlog-size 大小\n\n> 个人觉得\n```\nrepl-backlog-size[配置] > client-output-buffer-limit[slave 配置 2G]\n\n\n比如主从同步时，从库在加载 rdb 时，有大 key，或者增量数据大于 client-output-buffer-limit 时，主库主动与从库主动断开连接时\n\n从库在加载完 rdb 时，再请求增量数据时，可以从 repl-backlog 中找到数据\n```\n### 1.2.4 主库磁盘故障\n\n触发全量同步时，主库磁盘故障，主库 RDB 无法落盘，导致全量同步失败\n\n```\n* replication.c: 1646 Full resync from master: 1a0b22011aff6ea5d53710acff4ee32adde636ec:399255497708\n# replication.c: 1262 I/O error reading bulk count from MASTER: Operation now in progress\n```\n\n> repl-diskless-sync no #是否使用无盘复制 Diskless replication，默认是 no\n\n```\n对 master 进行操作\nconfig set repl-diskless-sync yes\n```\n#### 主库磁盘异常示例记录\n> 新增从库(4.0.10 版本), 从库日志\n```\n13475:S 13 May 05:51:08.134 * Connecting to MASTER {Master IP}:{Master port}\n13475:S 13 May 05:51:08.135 * MASTER <-> SLAVE sync started\n13475:S 13 May 05:51:08.135 * Non blocking connect for SYNC fired the event.\n13475:S 13 May 05:51:08.135 * start send sync cmd :PING\n\n13475:S 13 May 05:51:08.135 * Master replied to PING, replication can continue...\n13475:S 13 May 05:51:08.135 * start send sync cmd :REPLCONF listening-port 2019\n\n13475:S 13 May 05:51:08.135 * start send sync cmd :REPLCONF capa eof capa psync2\n\n13475:S 13 May 05:51:08.135 * Partial resynchronization(mem) not possible (no cached master)\n13475:S 13 May 05:51:08.135 * start send sync cmd :PSYNC ? -1\n\n13475:S 13 May 05:51:08.196 * Full resync from master: 7f0c1af873f9704ef0a55357ea8dc2a9bf89b7a9:0\n13475:S 13 May 05:51:08.399 # I/O error reading bulk count from MASTER: Resource temporarily unavailable\n```\n> 修改为无盘复制后从库日志\n```\n13475:S 13 May 06:37:56.103 * Connecting to MASTER {Master IP}:{Master port}\n13475:S 13 May 06:37:56.103 * MASTER <-> SLAVE sync started\n13475:S 13 May 06:37:56.103 * Non blocking connect for SYNC fired the event.\n13475:S 13 May 06:37:56.103 * start send sync cmd :PING\n\n13475:S 13 May 06:37:56.103 * Master replied to PING, replication can continue...\n13475:S 13 May 06:37:56.103 * start send sync cmd :REPLCONF listening-port 2019\n\n13475:S 13 May 06:37:56.103 * start send sync cmd :REPLCONF capa eof capa psync2\n\n13475:S 13 May 06:37:56.104 * Partial resynchronization(mem) not possible (no cached master)\n13475:S 13 May 06:37:56.104 * start send sync cmd :PSYNC ? -1\n\n13475:S 13 May 06:38:02.777 * Full resync from master: 7f0c1af873f9704ef0a55357ea8dc2a9bf89b7a9:659176439\n13475:S 13 May 06:38:02.846 * MASTER <-> SLAVE sync: receiving streamed RDB from master        // 从这里看已经是无盘复制模式\n13475:S 13 May 06:38:03.256 # I/O error trying to sync with MASTER: connection lost\n```\n\n//todo\n\n## 1.3 Redis bug\n\n### 1.3.1 AOF 句柄泄露 bug\n#### 表现\n日志中提示\n```\n* Residual parent diff successfully flushed to the rewritten AOF (329.83 MB)\n* Background AOF rewrite finished successfully\n* Starting automatic rewriting of AOF on 100% growth\n# Can't rewrite append only file in background: fork: Cannot allocate memory\n* Starting automatic rewriting of AOF on 100% growth\n# Can't rewrite append only file in background: fork: Cannot allocate memory\n* Starting automatic rewriting of AOF on 100% growth\n# Can't rewrite append only file in background: fork: Cannot allocate memory\n* Starting automatic rewriting of AOF on 100% growth\n# Error opening /setting AOF rewrite IPC pipes: Numerical result out of range\n* Starting automatic rewriting of AOF on 100% growth\n# Error opening /setting AOF rewrite IPC pipes: Numerical result out of range\n# Error registering fd event for the new client: Numerical result out of range (fd=10128)\n# Error registering fd event for the new client: Numerical result out of range (fd=10128)\n```\n使用 lsof 命令检查 fd 数，发现当时进程打开的 fd 数已经达到 10128 个，而其中大部分基本都是 pipe. 在 Redis 中，pipe 主要用于父子进程间通信，如 AOF 重写、基于 socket 的 RDB 持久化等场景。\n\n#### 分析\n\n**fd 限制**\n\n首先，我们定位到 client 连接报错的主要调用链为 networking.c/acceptCommonHandler => networking.c/createClient => ae.c/aeCreateFileEvent：\n```\nstatic void acceptCommonHandler(int fd, int flags, char *ip) {\n    client *c;\n    if ((c = createClient(fd)) == NULL) {\n        serverLog(LL_WARNING,\n            \"Error registering fd event for the new client: %s (fd=%d)\",\n            strerror(errno),fd);\n        close(fd); /* May be already closed, just ignore errors */\n        return;\n    }\n    //……\n}\nint aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,\n        aeFileProc *proc, void *clientData)\n{\n    if (fd >= eventLoop->setsize) {\n        errno = ERANGE;\n        return AE_ERR;\n    }\n//……\n}\n```\n而 eventLoop->setsize 则是在 server.c/initServer 中被初始化和设置的，大小为 maxclient+128 个。而我们 maxclient 采用 Redis 默认配置 10000 个，所以当 fd=10128 时就出错了。\n```\nserver.el = aeCreateEventLoop(server.maxclients+CONFIG_FDSET_INCR);\n\n```\n\n\n**aof 重写子进程启动失败为何不关闭 pipe**\n\naof 重写过程由 server.c/serverCron 定时时间事件处理函数触发，调用 aof.c/rewriteAppendOnlyFileBackground 启动 aof 重写子进程。在 rewriteAppendOnlyFileBackground 方法中我们注意到如果 fork 失败，过程就直接退出了。\n\n```\nint rewriteAppendOnlyFileBackground(void) {\n    //……\n    if (aofCreatePipes() != C_OK) return C_ERR; // 创建 pipe\n    //……\n    if ((childpid = fork()) == 0) {\n        /* Child */\n        //……\n    } else {\n        /* Parent */\n        // 子进程启动出错处理\n        if (childpid == -1) {\n            serverLog(LL_WARNING,\n                \"Can't rewrite append only file in background: fork: %s\",\n                strerror(errno)); // 最初内存不足正是这里打出的错误 log\n            return C_ERR;\n        }\n    //……\n    }\n}\n```\n而关闭 pipe 的方法，是在 server.c/serverCron => aof.c/backgroundRewriteDoneHandler 中发现 AOF 重写子进程退出后被调用：\n```\n//……\n    /* Check if a background saving or AOF rewrite in progress terminated. */\n    if (server.rdb_child_pid != -1 || server.aof_child_pid != -1 ||\n        ldbPendingChildren())\n    {\n        //……\n        // 任意子进程退出时执行\n        if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {\n            //……\n            if (pid == -1) {\n                serverLog(……);\n            } else if (pid == server.rdb_child_pid) {\n                backgroundSaveDoneHandler(exitcode,bysignal);\n            } else if (pid == server.aof_child_pid) { // 发现是 aof 重写子进程完成\n                backgroundRewriteDoneHandler(exitcode,bysignal); // 执行后续工作，包括关闭 pipe\n            }\n            //……\n        }\n    }\n//……\n```\n\n由此可见，如果 aof 重写子进程没有启动，则 pipe 将不会被关闭。而下次尝试启动 aof 重写时，又会调用 aof.c/aofCreatePipes 创建新的 pipe。\n\n#### 解决\n\n> * 2015 年就被两次在社区上报（参考 https://github.com/antirez/redis/issues/2857\n> * 2016 年有开发者提交代码修复此问题，直至 2017 年 2 月相关修复才被合入主干（参考 https://github.com/antirez/redis/pull/3408）\n> * 这只长寿的 bug 在 3.2.9 版本已修复\n\n### 1.3.2 在 AOF 文件 rewrite 期间如果设置 config set appendonly no，会导致 redis 进程一直死循环不间断触发 rewrite AOF\n\n此 BUG 在 4.0.7 版本修复 (2018.1 月）\n\nhttps://github.com/antirez/redis/commit/a18e4c964e9248008e0fba7efc1cad9ba9b8b1c3\n\n#### 根因\n\nredis 在 AOF rewrite 期间设置了 appendonly no，会 kill 子进程，设置 server.aof_fd = -1，但是并未更新 server.aof_rewrite_base_size。\n\n在 serverCron 中触发 AOF rewrite 时未判断当前 appendonly 是否为 yes，只判断了 server.aof_current_size 和 server.aof_rewrite_base_size 增长是否超过阈值\n\nAOF rewrite 重写完成后发现 server.aof_fd=-1 也未更新 server.aof_rewrite_base_size，导致 serverCron 中一直触发 AOF rewrite。\n\n### 1.3.3 redis slots 迁移的时候，永不过期的 key 因为 ttl>0 而过期，导致迁移丢失数据\n\n详细见博客 https://blog.csdn.net/doc_sgl/article/details/53825892\n\n对应 PR: https://github.com/antirez/redis/pull/3673/files\n\n在 4.0rc2 版本中进行修复\n\n#### 根因\n\n所有丢失 key 的 ttl 因为没有处理而使用了前一个 key 的 ttl！\n\n问题出在下面代码的 for 循环，对于不过期的 key,ttl 应该是 0，但是如果前面有过期的 key,ttl>0. 那么在下一个处理不过期 key 时，expireat=-1，不会进入 if，ttl 还是使用前一个 ttl，导致一个永不过期的 key 因为 ttl>0 而过期。\n\n```\n/* MIGRATE host port key dbid timeout [COPY | REPLACE]\n *\n * On in the multiple keys form:\n *\n * MIGRATE host port \"\" dbid timeout [COPY | REPLACE] KEYS key1 key2 ... keyN */\nvoid migrateCommand(client *c) {\n    long long ttl, expireat;\n    ttl = 0;\n    ...\n\n    /* Create RESTORE payload and generate the protocol to call the command. */\n    /*\n        问题出在这个 for 循环，对于不过期的 key,ttl 应该是 0，但是如果前面有过期的 key,ttl>0. 在处理不过期 key 时，expireat=-1，导致 ttl 还是使用前一个 ttl.\n        导致一个永不过期的 key 因为 ttl>0 而过期。\n    */\n    for (j = 0; j < num_keys; j++) {\n        /\n        expireat = getExpire(c->db,kv[j]);\n        if (expireat != -1) {\n            ttl = expireat-mstime();\n            if (ttl < 1) ttl = 1;\n        }\n        serverAssertWithInfo(c,NULL,rioWriteBulkCount(&cmd,'*',replace ? 5 : 4));\n        if (server.cluster_enabled)\n            serverAssertWithInfo(c,NULL,\n                rioWriteBulkString(&cmd,\"RESTORE-ASKING\",14));\n        else\n            serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,\"RESTORE\",7));\n        serverAssertWithInfo(c,NULL,sdsEncodedObject(kv[j]));\n        serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,kv[j]->ptr,\n                sdslen(kv[j]->ptr)));\n        serverAssertWithInfo(c,NULL,rioWriteBulkLongLong(&cmd,ttl));\n\n        /* Emit the payload argument, that is the serialized object using\n         * the DUMP format. */\n        createDumpPayload(&payload,ov[j]);\n        serverAssertWithInfo(c,NULL,\n            rioWriteBulkString(&cmd,payload.io.buffer.ptr,\n                               sdslen(payload.io.buffer.ptr)));\n        sdsfree(payload.io.buffer.ptr);\n\n        /* Add the REPLACE option to the RESTORE command if it was specified\n         * as a MIGRATE option. */\n        if (replace)\n            serverAssertWithInfo(c,NULL,rioWriteBulkString(&cmd,\"REPLACE\",7));\n    }\n```\n### 1.3.4 3.x 执行 exists 可以获取到，但 get 时则无法获取到数据\n\n#### 3.x exists 逻辑\n> exists\n```\nredis.c:    {\"exists\",existsCommand,-2,\"rF\",0,NULL,1,-1,1,0,0}\n```\n\n> db.c:void existsCommand\n```\n// EXISTS key1 key2 ... key_N.  Return value is the number of keys existing.\nvoid existsCommand(redisClient *c) {\n    long long count = 0;\n    int j;\n\n    for (j = 1; j < c->argc; j++) {\n        expireIfNeeded(c->db,c->argv[j]);\n        if (dbExists(c->db,c->argv[j])) count++;\n    }\n    addReplyLongLong(c,count);\n}\n```\n\n在从库上执行 exists 时，3.x 版本先执行的 expireIfNeeded ，在从库时不会进行主动淘汰，然后进行判断此 key 是否存在\n\n#### 4.x exists 逻辑\n\n> db.c:void existsCommand\n```\n/*/EXISTS key1 key2 ... key_N. Return value is the number of keys existing.\nvoid existsCommand(client *c) {\n    long long count = 0;\n    int j;\n\n    for (j = 1; j < c->argc; j++) {\n        if (lookupKeyRead(c->db,c->argv[j])) count++;\n    }\n    addReplyLongLong(c,count);\n}\n```\n\n> db.c:lookupKeyRead\n```\nrobj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {\n    robj *val;\n\n    if (expireIfNeeded(db,key) == 1) {\n        /* Key expired. If we are in the context of a master, expireIfNeeded()\n         * returns 0 only when the key does not exist at all, so it's safe\n         * to return NULL ASAP. */\n        if (server.masterhost == NULL) return NULL;\n\n        /* However if we are in the context of a slave, expireIfNeeded() will\n         * not really try to expire the key, it only returns information\n         * about the \"logical\" status of the key: key expiring is up to the\n         * master in order to have a consistent view of master's data set.\n         *\n         * However, if the command caller is not the master, and as additional\n         * safety measure, the command invoked is a read-only command, we can\n         * safely return NULL here, and provide a more consistent behavior\n         * to clients accessign expired values in a read-only fashion, that\n         * will say the key as non exisitng.\n         *\n         * Notably this covers GETs when slaves are used to scale reads. */\n        if (server.current_client &&\n            server.current_client != server.master &&\n            server.current_client->cmd &&\n            server.current_client->cmd->flags & CMD_READONLY)\n        {\n            return NULL;\n        }\n    }\n    val = lookupKey(db,key,flags);\n    if (val == NULL)\n        server.stat_keyspace_misses++;\n    else\n        server.stat_keyspace_hits++;\n    return val;\n}\n\n/* Like lookupKeyReadWithFlags(), but does not use any flag, which is the\n * common case. */\nrobj *lookupKeyRead(redisDb *db, robj *key) {\n    return lookupKeyReadWithFlags(db,key,LOOKUP_NONE);\n}\n```\n\n### 1.3.5 5.0 以前 redis 主从同步时因从库的 buffer 计算不准确导致主库大量的 key 被淘汰\n\nhttps://github.com/redis/redis/commit/bf680b6f8cdaee2c5588c5c8932a7f3b7fa70b15\n\n```\n当写入新数据时，会判断是否 used_memmory > maxmemory，这里会刨除从库的 client-output-buffer\n\n而 5.0 以前在计算从库 buffer 有误\n\n```\n比如需要刨除 1GB 空间，结果只是刨除了 500MB, 这个时候主库需要额外淘汰 500MB 数据，这 500MB 又会放到从库的 client-output-buffer ，形成了个恶性循环\n```\n\n```\n## 1.4 redis 日志及状态信息\n\n### 1.4.1 日常日志\n```\nDB 0: 1 keys (0 volatile) in 4 slots HT\n```\n> * Redis 中的 DB 是相互独立存在的，所以可以出现重复的 key。好处一是，对小型项目可以做如下设置： 1 号 DB 做开发，2 号 DB 做测试等等。\n>   * Redis Cluster 方案只允许使用 0 号数据库\n> * 0 volatile: 目前 0 号 DB 中没有 volatile key，volatile key 的意思是 过特定的时间就被 REDIS 自动删除，在做缓存时有用。\n> * 4 slots HT: 目前 0 号 DB 的 hash table 只有 4 个 slots(buckets)\n>   * //todo\n\n### 1.4.2 info\n\nredis键空间的状态监控\n\n> * 键个数 (keys): redis 实例包含的键个数\n> * 设置有生存时间的键个数 (keys_expires): 是纯缓存或业务的过期长，都建议对键设置 TTL; 避免业务的死键问题. （expires 字段）\n> * 估算设置生存时间键的平均寿命 (avg_ttl): redis 会抽样估算实例中设置TTL键的平均时长，单位毫秒。如果无 TTL 键或在 Slave 则 avg_ttl 一直为 0\n> * LRU淘汰的键个数 (evicted_keys): 因 used_memory 达到 maxmemory 限制，并设置有淘汰策略的实例；（对排查问题重要，可不设置告警）\n> * 过期淘汰的键个数 (expired_keys): 删除生存时间为 0 的键个数；包含主动删除和定期删除的个数。\n\n## 1.5 redis 协议说明\n\nRedis 的客户端和服务端之间采取了一种独立名为 RESP(REdis Serialization Protocol) 的协议\n\nRedis 协议在以下几点之间做出了折衷：\n\n> * 简单的实现\n> * 快速地被计算机解析\n> * 简单得可以能被人工解析\n\n注意：RESP 虽然是为 Redis 设计的，但是同样也可以用于其他 C/S 的软件。\n\n### 1.5.1 网络层\n\nRedis 在 TCP 端口 6379 上监听到来的连接，客户端连接到来时，Redis 服务器为此创建一个 TCP 连接。\n\n在客户端与服务器端之间传输的每个 Redis 命令或者数据都以、r\\n 结尾。\n\n### 1.5.2 请求\n\nRedis 接收由不同参数组成的命令。一旦收到命令，将会立刻被处理，并回复给客户端。\n\n### 1.5.3 新的统一请求协议\n\n新的统一协议已在 Redis 1.2 中引入，但是在 Redis 2.0 中，这就成为了与 Redis 服务器通讯的标准方式。\n\n在这个统一协议里，发送给 Redis 服务端的所有参数都是二进制安全的。以下是通用形式：\n\n```\n    *<number of arguments> CR LF\n    $<number of bytes of argument 1> CR LF\n    <argument data> CR LF\n    ...\n    $<number of bytes of argument N> CR LF\n    <argument data> CR LF\n```\n例子如下：\n```\n    *3\n    $3\n    SET\n    $5\n    mykey\n    $7\n    myvalue\n```\n上面的命令看上去像是单引号字符串，所以可以在查询中看到每个字节的准确值：\n\n```\n    \"*3\\r\\n$3\\r\\nSET\\r\\n$5\\r\\nmykey\\r\\n$7\\r\\nmyvalue\\r\\n\"\n```\n在 Redis 的回复中也使用这样的格式。批量回复时，这种格式用于每个参数 $6\\r\\nmydata\\r\\n。\n\n实际的统一请求协议是 Redis 用于返回列表项，并调用 Multi-bulk 回复。 仅仅是 N 个以以`*\\r\\n` 为前缀的不同批量回复，是紧随的参数（批量回复）数目。\n\n### 1.5.4 回复\nRedis 用不同的回复类型回复命令。它可能从服务器发送的第一个字节开始校验回复类型：\n```\n    用单行回复，回复的第一个字节将是“+”\n    错误消息，回复的第一个字节将是“-”\n    整型数字，回复的第一个字节将是“:”\n    批量回复，回复的第一个字节将是“$”\n    多个批量回复，回复的第一个字节将是“*”\n```\n通俗点讲，则如下\n\n> * (+) 表示一个正确的状态信息，具体信息是当前行 + 后面的字符。\n> * (-)  表示一个错误信息，具体信息是当前行－后面的字符。\n> * (`*`) 表示消息体总共有多少行，不包括当前行，`*`后面是具体的行数。\n> * ($) 表示下一行数据长度，不包括换行符长度 `\\r\\n`,$ 后面则是对应的长度的数据。\n> * (:) 表示返回一个数值，：后面是相应的数字节符。\n\n(1)Simple Strings\n\n状态回复（或者单行回复）以“+”开始以“\\r\\n”结尾的单行字符串形式。例如：\n```\n\"+OK\\r\\n\"\n\n127.0.0.1:6379> set name meetbill\n+OK\\r\\n  # 服务端实际返回\n-------------------\nOK       # redis-cli 客户端显示\n```\n客户端库将在“+”后面返回所有数据，正如上例中字符串“OK”一样。\n\n\n(2)Errors\n\n错误回复发送类似于状态回复。唯一的不同是第一个字节用“-”代替“+”。\n\n错误回复仅仅在一些意料之外的事情发生时发送，例如：如果你试图执行一个操作来应付错误的数据类型，或者如果命令不存在等等。所以当收到一个错误回复时，客户端将会出现一个异常。\n\n```\n127.0.0.1:6379> meetbill\n-ERR unknown command 'meetbill'\\r\\n  # 服务端实际返回，下同\n---\n(error) ERR unknown command 'meetbill'  # redis-cli 客户端显示，下同\n```\n\n\n(3)Integers\n\n这种回复类型只是用 CRLF 结尾字符串来表示整型，用一个字节的“：”作为前缀。例如：“：0\\r\\n”，或者“:1000\\r\\n”是整型回复。\n\n像 INCR 或者 LASTAVE 命令用整型回复作为实际回复值，此时对于返回的整型没有特殊的意思。它仅仅是为 INCR、LASTSAVE 的 UNIX 时间等增加数值。\n\n一些命令像 EXISTS 将为 true 返回 1，为 false 返回 0。\n\n其它命令像 SADD、SREM 和 SETNX 如果操作实际完成了的话将返回 1，否则返回 0。\n\n接下来的命令将回复一个整型回复：SETNX、DEL、EXISTS、INCR、INCRBY、DECR、DECRBY、DBSIZE、LASTSAVE、RENAMENX、MOVE、LLEN、SADD、SREM、SISMEMBER、SCARD。\n\n```\n27.0.0.1:6379> LPUSH info meetbill hello\n:2\\r\\n  # 服务端实际返回，下同\n---\n(integer) 2  # redis-cli 客户端显示，下同\n\n127.0.0.1:6379> LLEN info\n:2\\r\\n\n---\n(integer) 2\n\n127.0.0.1:6379> EXISTS info\n:1\\r\\n\n---\n(integer) 1\n\n127.0.0.1:6379> DEL info\n:1\\r\\n\n---\n(integer) 1\n\n127.0.0.1:6379> EXISTS info\n:0\\r\\n\n---\n(integer) 0\n```\n\n\n(4)Bulk Strings\n\n批量回复被服务器用于返回一个单二进制安全字符串。\n\n```\nC: GET mykey\nS: $6\\r\\nfoobar\\r\\n\n```\n服务器发送第一行回复，该行以“$”开始后面跟随实际要发送的字节数，随后是 CRLF，然后发送实际数据，随后是 2 个字节的额外数据用于最后的 CRLF。服务器发送的准确序列如下：\n\n```\n\"$6\\r\\nfoobar\\r\\n\"\n```\n如果请求的值不存在，批量回复将使用特殊的值 -1 来作为数据长度，例如：\n\n```\nC: GET nonexistingkey\nS: $-1\n```\n当请求的对象不存在时，客户端库 API 不会返回空字符串，而会返回空对象。例如：Ruby 库返回‘nil’，而 C 库返回 NULL（或者在回复的对象里设置指定的标志）等等。\n\n```\n127.0.0.1:6379> set site moelove.info\n+OK\\r\\n  # 服务端实际返回，下同\n---\nOK   # redis-cli 客户端显示，下同\n\n127.0.0.1:6379> get site\n$12\\r\\nmoelove.info\\r\\n\n---\n\"moelove.info\"\n\n127.0.0.1:6379> del site\n:1\\r\\n\n---\n(integer) 1\n\n127.0.0.1:6379> get site\n$-1\\r\\n\n---\n(nil)\n\n127.0.0.1:6379> set site ''\n+OK\\r\\n\n---\nOK\n\n127.0.0.1:6379> get site\n$0\\r\\n\\r\\n\n---\n\"\"\n```\n\n(5)Arrays\n\n像命令 LRNGE 需要返回多个值（列表的每个元素是一个值，而 LRANGE 需要返回多于一个单元素）。使用多批量写是有技巧的，用一个初始行作为前缀来指示多少个批量写紧随其后。\n\n批量回复的第一个字节总是`*`，例如：\n\n```\n    C: LRANGE mylist 0 3\n    s: *4\n    s: $3\n    s: foo\n    s: $3\n    s: bar\n    s: $5\n    s: Hello\n    s: $5\n    s: World\n```\n正如您可以看到的多批量回复是以完全相同的格式使用 Redis 统一协议将命令发送给服务器。\n\n服务器发送的第一行是`*4\\r\\n`，用于指定紧随着 4 个批量回复。然后传送每个批量写。\n\n如果指定的键不存在，则该键被认为是持有一个空的列表，且数值 0 被当作多批量计数值来发送，例如：\n\n```\n    C: LRANGE nokey 0 1\n    S: *0\n```\n当 BLPOP 命令超时时，它返回 nil 多批量回复。这种类型多批量回复的计数器是 -1，且值被当作 nil 来解释。例如：\n\n```\n    C: BLPOP key 1\n    S: *-1\n```\n当这种情况发生时，客户端库 API 将返回空 nil 对象，且不是一个空列表。这必须有别于空列表和错误条件（例如：BLPOP 命令的超时条件）。\n\n```\n    127.0.0.1:6379> LPUSH info TaoBeier moelove.info\n    :2\\r\\n   # 服务端实际返回，下同\n    ---\n    (integer) 2  # redis-cli 客户端显示，下同\n\n    127.0.0.1:6379> LRANGE info 0 -1\n    *2\\r\\n$12\\r\\nmoelove.info\\r\\n$8\\r\\nTaoBeier\\r\\n\n    ---\n    1) \"moelove.info\"\n    2) \"TaoBeier\"\n\n    127.0.0.1:6379> LPOP info\n    $12\\r\\nmoelove.info\\r\\n\n    ---\n    \"moelove.info\"\n\n    127.0.0.1:6379> LPOP info\n    $8\\r\\nTaoBeier\\r\\n\n    ---\n    \"TaoBeier\"\n\n    127.0.0.1:6379> LRANGE info 0 -1\n    *0\\r\\n\n    ---\n    (empty list or set)\n```\n\n### 1.5.6 多批量回复中的 Nil 元素\n多批量回复的单元素长度可能是 -1，为了发出信号这个元素被丢失且不是空字符串。这种情况发送在 SORT 命令时，此时使用 GET 模式选项且指定的键丢失。一个多批量回复包含一个空元素的例子如下：\n\n```\n    S: *3\n    S: $3\n    S: foo\n    S: $-1\n    S: $3\n    S: bar\n```\n第二个元素是空。客户端库返回如下：\n```\n    [\"foo\",nil,\"bar\"]\n```\n### 1.5.7 多命令和管道\n\n客户端能使用同样条件为了发出多个命令。管道用于支持多命令能够被客户端用单写操作来发送，它不需要为了发送下一条命令而读取服务器的回复。所有回复都能在最后被读出。\n\n通常 Redis 服务器和客户端拥有非常快速的连接，所以在客户端的实现中支持这个特性不是那么重要，如果一个应用需要在短时间内发出大量的命令，管道仍然会非常快。\n\n### 1.5.8 旧协议发送命令\n\n在统一请求协议出现前，Redis 用不同的协议发送命令，现在仍然支持，它简单通过手动 telnet。在这种协议中，有两种类型的命令：\n\n> * 内联命令：简单命令其参数用空格分割字符串。非二进制安全。\n> * 批量命令：批量命令准确如内联命令，但是最后的参数用特殊方式来处理用于保证最后参数二进制安全。 内联命令\n\n最简单的发送 Redis 命令的方式是通过内联命令。下面是一个使用内联命令聊天的服务器 / 客户端的例子（服务器聊天用 S: 开始，客户端聊天用 C: 开始）。\n\n```\nC: PING\nS: +PONG\n```\n下面是另外一个内联命令返回整数的例子：\n\n```\n    C: EXISTS somekey\n    S: :0\n```\n因为‘somekey’不存在，所以服务器返回‘:0’。\n\n注意：EXISTS 命令带有一个参数。参数用空格分割。\n\n批量命令\n\n一些命令当用内联命令发送时需要一种特殊的格式用于支持最后参数二进制安全。这种命令用最后参数作为“字节计数器”，然后发送批量数据（因为服务器知道读取多少个字节，所以是二进制安全的）。\n\n请看下面的例子：\n\n```\n    C: SET mykey 6\n    C: foobar\n    S: +OK\n```\n这条命令的最后一个参数是‘6’。这用于指定随后数据的字节数，即字符串“foobar”。注意：虽然这个字节流是以额外的两个 CRLF 字节结尾的。\n\n所有批量命令都是用这种准确的格式：用随后数据的字节数代替最后一个参数，紧跟着后面是组成参数本身的字节和 CRLF。为了更清楚程序，下面是通过客户端发送字符串的例子：\n\n```\n\"SET mykey 6\\r\\nfoobar\\r\\n\"\n```\nRedis 有一个内部列表，用于表示哪些命令是内联，哪些命令是批量，所以你不得不发送相应的命令。强烈建议使用新的统一请求协议来代替老的协议。\n\n## 1.6 Redis RDB 文件格式\n\n翻译自：\n<https://github.com/sripathikrishnan/redis-rdb-tools/wiki/Redis-RDB-Dump-File-Format>\n\n\n\n`Redis *.rdb` 文件是一个内存内存储的二进制表示法。这个二进制文件足以完全恢复 Redis 的状态。\n\nrdb 文件格式为快速读和写优化。LZF 压缩可以用来减少文件大小。通常，对象前面有它们的长度，这样，在读取对象之前，你可以准确地分配内存大小。\n\n\n为快速读 / 写优化意味着磁盘上的格式应该尽可能接近于在内存里的表示法。这种方式正是 rdb 文件采用的。\n导致的结果是，在不了解 Redis 在内存里表示数据的数据结构的情况下，你没法解析 rdb 文件。\n\n\n### 1.6.1 解析 RDB 的高层算法\n在高层层面看，RDB 文件有下面的格式：\n```\n----------------------------# RDB 是一个二进制文件。文件里没有新行或空格。\n52 45 44 49 53              # 魔术字符串 \"REDIS\"\n30 30 30 33                 # RDB 版本号，高位优先。在这种情况下，版本是 0003 = 3\n----------------------------\nFE 00                       # FE = code 指出数据库选择器。数据库号 = 00\n----------------------------# 键值对开始\nFD $unsigned int            # FD 指出 \"有效期限时间是秒为单位\". 在这之后，读取 4 字节无符号整数作为有效期限时间。\n$value-type                 # 1 字节标记指出值的类型 － set，map，sorted set 等。\n$string-encoded-key         # 键，编码为一个 redis 字符串。\n$encoded-value              # 值，编码取决于 $value-type.\n----------------------------\nFC $unsigned long           # FC 指出 \"有效期限时间是豪秒为单位\". 在这之后，读取 8 字节无符号长整数作为有效期限时间。\n$value-type                 # 1 字节标记指出值的类型 － set，map，sorted set 等。\n$string-encoded-key         # 键，编码为一个 redis 字符串。\n$encoded-value              # 值，编码取决于 $value-type.\n----------------------------\n$value-type                 # 这个键值对没有有效期限。$value_type 保证 != to FD, FC, FE and FF\n$string-encoded-key\n$encoded-value\n----------------------------\nFE $length-encoding         # 前一个数据库结束，下一个数据库开始。数据库号用长度编码读取。\n----------------------------\n...                         # 这个数据库的键值对，另外的数据库。\nFF                          ## RDB 文件结束指示器\n8 byte checksum             ## 整个文件的 CRC32 校验和。\n```\n\n#### 魔术数\n文件开始于魔术字符串 `REDIS`。这是一个快速明智的检查是否正在处理一个 redis rdb 文件。\n`52 45 44 49 53 # \"REDIS\" `\n\n\n#### RDB 版本号\n接下来 `4` 个字节存储了 rdb 格式的版本号。这 `4` 个字节解释为 ascii 字符，然后使用字符串到整数的转换法转换为一个整数。\n`00 00 00 03 # Version = 3`\n\n\n#### 数据库选择器\n一个 Redis 实例可以有多个数据库。\n单一字节 `0xFE` 标记数据库选择器的开始。在这个字节之后，一个可变长度的字段指出数据库序号。\n见“长度编码”章节来了解如何读取数据库序号。\n\n\n#### 键值对\n在数据库选择器之后，文件包含了一序列的键值对。\n\n每个键值对有 4 部分：\n>  1.  键保存期限时间戳。这是可选的。\n>  2.  一个字节标记值的类型。\n>  3.  键编码为 Redis 字符串。见“Redis 字符串编码”。\n>  4.  值根据值类型进行编码。见“Redis 值编码”。\n\n\n##### 键保存期限时间戳\n这个区块开始于一字节标记。值 `FD` 指出保存期限是以秒为单位指定。值 `FC` 指出有效期限是以毫秒为单位指定。\n\n如果时间指定为毫秒，接下来 `8` 个字节表示 unix 时间。这个数字是 unix 时间戳，精确到秒或毫秒，表示这个键的有效期限。\n\n数字如何编码见“Redis 长度编码”章节。\n\n在导入过程中，已经过期的键将必须丢弃。\n\n\n##### 值类型\n一个字节标记指示用于保存值的编码。\n\n>  1.  `0` ＝ “String 编码”\n>  2.  `1` ＝ “ List 编码”\n>  3.  `2` ＝ “Set 编码”\n>  4.  `3` ＝ “Sorted Set 编码”\n>  5.  `4` ＝ “Hash 编码”\n>  6.  `9` ＝ “Zipmap 编码”\n>  7.  `10` ＝ “Ziplist 编码”\n>  8.  `11` ＝ “IntSet 编码”\n>  9.  `12` ＝ “以 Ziplist 编码的 Sorted Set”\n>  10.  `13` ＝ “以 Ziplist 编码的 Hashmap” （在 rdb 版本 4 中引入）\n\n\n##### 键\n键简单地编码为 Redis 字符串。见“字符串编码”章节了解键如何被编码。\n\n\n##### 值\n值的编码取决于值类型标记。\n\n* 当值类型等于 `0`，值是简单字符串。\n* 当值类型是 `9`， `10`， `11` 或 `12` 中的一个，值被包装为字符串。读取字符串后，它必须进一步解析。\n* 当值类型是 `1`，`2`，`3` 或 `4` 中的一个，值是一序列字符串。这个序列字符串用于构造 list，set，sorted set 或 hashmap。\n\n\n### 1.6.2 长度编码\n长度编码用于存储流中接下来对象的长度。长度编码是一个可变字节编码，为尽可能少用字节而设计。\n\n这是长度编码如何工作：\n> 1.  从流中读取一个字节，最高 `2` bit 被读取。\n> 2.  如果开始 bit 是 `00` ，接下来 `6` bit 表示长度。\n> 3.  如果开始 bit 是 `01`，从流再读取额外一个字节。这组合的的 `14` bit 表示长度。\n> 4.  如果开始 bit 是 `10`，那么剩余的 `6`bit 丢弃，从流中读取额外的 `4` 字节，这 `4` 个字节表示长度。\n> 5.  如果开始 bit 是 `11`，那么接下来的对象是以特殊格式编码的。剩余 `6` bit 指示格式。这种编码通常用于把数字作为字符串存储或存储编码后的字符串。见字符串编码。\n\n作为这种编码的结果：\n>  1.  数字 `[0 - 63]` 可以在 `1` 个字节里存储\n>  2.  数字 `[0 - 16383]` 可以在 `2` 个字节里存储\n>  3.  数字 `[0 - (2^32 - 1)]` 可以在 `4` 个字节里存储\n\n\n### 1.6.3 字符串编码\nRedis 字符串是二进制安全的－－这意味着你可以在这里存储任何东西。它们没有任何特殊的字符串结束记号。\n最好认为 Redis 字符串是一个字节数组。\n\nRedis 里有三种类型的字符串：\n> 1.  长度前缀字符串。\n> 2.  一个 `8`，`16` 或 `32` bit 整数。\n> 3.  LZF 压缩的字符串。\n\n\n#### 长度前缀字符串\n长度前置字符串是很简单的。字符串字节的长度首先编码为“长度编码”，在这之后存储字符串的原始字节。\n\n\n#### 整数作为字符串\n首先读取“长度编码”块，特别是第一个 `2` bit 是 `11`。在这种情况下，读取剩余的 `6` bit。如果这 `6` bit 的值是：\n> 1.  `0` 表示接下来是 `8` bit 整数\n> 2.  `1` 表示接下来是 `16` bit 整数\n> 3.  `2` 表示接下来是 `32` bit 整数\n\n这些整数都是以 little endian 格式编码的。\n\n#### 压缩字符串\n首先读取“长度编码”，特别是第一个 `2` bit 是 `11`. 在这种情况下，读取剩余 `6` bit。如果这 `6` bit 值是 `3`，它表示接下来是一个压缩字符串。\n\n压缩字符串按如下读取：\n> 1.  从流中读取压缩后的长度 `clen`，按“长度编码”。\n> 2.  从流中读取未压缩长度，按“长度编码”。\n> 3.  接下来从流中读取 `clen` 个字节。\n> 4.  最后，这些字节按 LZF 算法解压。\n\n\n### 1.6.4 List 编码\n一个 Redis list 表示为一序列字符串。\n\n> 1.  首先，从流中读取 list 的大小： `size`，按“长度编码”。\n> 2.  然后，`size` 个字符串从流中读取，按“字符串编码”。\n> 3.  使用这些字符串重新构建 list。\n\n\n### 1.6.5 Set 编码\nSet 编码与 list 完全类似。\n\n\n### 1.6.6 Sorted Set 编码\n> 1.  首先，从流中读取 sorted set 大小 `size`，按“长度编码”\n> 2.  先后读取两个字符串作为 set 的元素和它的分值，作为一个元组。\n> 3.  一共读取 `size` 个上面的元组作为 sorted set 集合。\n\n\n### 1.6.7 Hash 编码\n> 1.  首先，从流中读取 hash 大小 `size`，按“长度编码”。\n> 2.  下一步，从流中读取 `2 * size` 个字符串，按“字符串编码”。\n> 3.  交替的字符串是键和值。\n> 4.  例如，`2 us washington india delhi` 表示 map `{\"us\" => \"washington\", \"india\" => \"dlhi\"}`。\n\n\n### 1.6.8 Zipmap 编码\n*注意：Zipmap 编码从 Redis 2.6 开始已弃用。小的的 hashmap 编码为 ziplist。*\n\nZipmap 是一个被序列化为一个字符串的 hashmap。本质上，键值对按顺序存储。在这种结构里查找一个键的复杂度是 `O(N)`。\n当键值对数量很少时，这个结构用于替代 dictionary。\n\n为解析 zipmap，首先用“字符串编码”从流读取一个字符串。这个字符串包装了 zipmap。字符串的内容表示了 zipmap。\n\n字符串里的 zipmap 结构如下： `<zmlen><len>\"foo\"<len><free>\"bar\"<len>\"hello\"<len><free>\"world\"<zmend>`\n\n1.  `zmlen` : `1` 字节长，保存 zipmap 的大小。如果大于等于 `254`，值不使用。将需要迭代整个 zipmap 来找出长度。\n2.  `len` : 后续字符串的长度，可以是键或值的。这个长度存储为 `1` 个或 `5` 个字节（与上面描述的“长度编码”不同）。\n            如果第一个字节位于 `0` 到 `252`，那么它是 zipmap 的长度。如果第一个字节是 `253`，读取下 `4` 个字节作为无符号整数来表示 zipmap 的长度。\n            `254` 和 `255` 对这个字段是非法的。\n3.  `free` : 总是 `1` 字节，指出值后面的空闲字节数。例如，如果键的值是 `America`，更新为 `USA` 后，将有 `4` 个空闲的字节。\n4.  `zmend` : 总是 `255`. 指出 `zipmap` 结束。\n\n*有效的例子*：\n`18 02 06 4d 4b 44 31 47 36 01 00 32 05 59 4e 4e 58 4b 04 00 46 37 54 49 ff ..`\n\n1.  从使用“字符串编码”开始解码。你会注意到 18 是字符串的长度。因此，我们将读取下 24 个字节，直到 ff。\n2.  现在，我们开始解析从  @02 06… @ 开始的字符串，使用 “Zipmap 编码”\n3.  02 是 hashmap 里条目的数量。\n4.  06 是下一个字符串的长度。因为长度小于 254, 我们不需要读取任何额外的字节\n5.  我们读取下 6 个字节  4d 4b 44 31 47 36 来得到键 “MKD1G6”\n6.  01 是下一个字符串的长度，这个字符串应当是值\n7.  00 是空闲字节的数量\n8.  读取下一个字节 0x32，得到值“2”\n9.   在这种情况下，空闲字节是 0，所以不需要跳过任何东西\n10.  05 是下一个字符串的长度，在这种情况下是键。\n11.  读取下 5 个字节 59 4e 4e 58 4b, 得到键 “YNNXK”\n12.  04 是下一个字符串的长度，这是一个值\n13.  00 是值后面的空闲字节数\n14.  读取下 4 个字节 46 37 54 49 来得到值 “F7TI”\n15.  最终，遇到 FF, 这表示这个 zipmap 的结束\n16. 因此，这个 zipmap 表示 hash {\"MKD1G6\" => \"2\", \"YNNXK\" => \"F7TI\"}\n\n\n### 1.6.9 Ziplist 编码\n一个 Ziplist 是一个序列化为一个字符串的 list。本质上，list 的元素按顺序地存储，借助于标记（`flag`）和偏移（`offset`）来达到高校地双向遍历 list。\n\n为解析一个 ziplist，首先从流中读取一个字符串，按“字符串编码”。这个字符串是 ziplist 的封装。这个字符串的内容表示了 ziplist。\n\n字符串里的 ziplist 的结构如下：<zlbytes><zltail><zllen><entry><entry><zlend>\n\n> 1.  `zlbytes` ：这是一个 `4` 字节无符号整数，表示 ziplist 的总字节数。这 `4` 字节是 little endian 格式－－最先出现的是最低有效位组。\n> 2.  `zltail`：这是一个 `4` 字节无符号整数，little endian 格式。它表示到 ziplist 的尾条目（tail entry）的偏移。\n> 3.  `zllen`：这是一个 `2` 字节无符号整数，little endian 格式。它表示 ziplist 的条目的数量\n> 4.  `entry`：一个条目表示 ziplist 的元素。细节在下面\n> 5.  `zlend`：总是等于 `255`。它表示 ziplist 的结束\n\nziplist 的每个条目有下面的格式：\n`<length-prev-entry><special-flag><raw-bytes-of-entry>`\n\n>  `length-prev-enty`： 这个字段存储上一个条目的长度，如果是第一个条目则是 0。这允许容易地进行反向遍历 list。这个长度存储为 1 或 5 个字节。\n     如果第一个字节小于等于 253，它被认为是长度，如果第一个字节是 254，接下来 4 个字节用于存储长度。4 字节按无符号整数读取。\n>\n>  `special-flag`：这个标记指出条目是字符串还是整数。它也指示字符串长度或整数的大小。这个标记的可变编码如下：\n>  >  1.  |00pppppp|  － 1 字节：字符串值长度小于等于 63 字节（6 bit）\n>  >  2.  |01pppppp|qqqqqqqq|  － 2 字节：字符串值长度小于等于 16383 字节（14 bit）\n>  >  3.  |10______|qqqqqqqq|rrrrrrrr|ssssssss|tttttttt|  － 5 字节：字符串值长度大于等于 16384 字节\n>  >  4.  |1100____|  － 读取后面 2 个字节作为 16 bit 有符号整数\n>  >  5.  |1101____|  － 读取后面 4 个字节作为 32 bit 有符号整数\n>  >  6.  |1110____|  － 读取后面 8 个字节作为 64 bit 有符号整数\n>  >  7.  |11110000|  － 读取后面 3 个字节作为 2 4bit 有符号整数\n>  >  8.  |11111110|  － 读取后面 1 个字节作为 8 bit 有符号整数\n>  >  9.  |1111xxxx|  － （当 xxxx 位于 0000 到 1101）直接 4 bit 整数。0 到 12 的无符号整数。被编码的实际值是从 1 到\n               13，因为 0000 和 1111 不能使用，所以应当从编码的 4bit 值里减去 1 来获得正确的值。\n>\n>  `Raw Bytes`：在 `special flag` 后，是原始字节。字节的数字由前面的 `special flag` 部分决定。\n>\n>  *举例*\n`23 23 00 00 00 1e 00 00 00 04 00 00 e0 ff ff ff ff ff ff ff 7f 0a d0 ff ff 00 00 06 c0 fc 3f 04 c0 3f 00 ff ... `\n  |           |           |     |                             |                 |           |           |\n\n>  >  1.  从使用“字符串编码”开始解码。23 是字符串的长度，然后读取 35 个字节直到 ff\n>  >  2.  使用“Ziplist 编码”解析开始于 23 00 00  ... 的字符串\n>  >  3.  前 4 个字节 23 00 00 00 表示 Ziplis 长度的字节总数。注意，这是 little endian 格式\n>  >  4.  接下来 4 个字节 1e 00 00 00 表示到尾条目的偏移。 0x1e = 30，这是一个基于 0 的偏移。\n>  >       0th position = 23, 1st position = 00 and so on. It follows that the last entry starts at 04 c0 3f 00 .. 。\n>  >  5.  接下来 2 个字节 04 00 表示 list 里条目的数量。\n>  >  6.  从现在开始，读取条目。\n>  >  7.  00 表示前一个条目的长度。0 表示这是第一个条目。\n>  >  8.  e0 是特殊标记，因为它开始于位模式 1110____，读取下 8 个字节作为整数。这是 list 的第一个条目。\n>  >  9.  现在开始读取第二个条目。\n>  >  10.  0a 是前一个条目的长度。10 字节 ＝ 1 字节 prev 长度 ＋ 1 字节特殊标记长度 ＋ 8 字节整数\n>  >  11.  d0 是特殊标记，因为它开始于位模式 1101____，读取下 4 个字节作为整数。这是 list 的第二个条目。\n>  >  12.  现在开始第二个条目。\n>  >  13.  06 是前一个条目的长度。 6 字节 ＝ 1 字节 prev 长度 ＋ 1 字节特殊标记 ＋ 4 字节整数。\n>  >  14.  c0 是特殊标记，因为它开始于位模式 1100____，读取下 2 个字节作为整数。这是 list 的第三个条目。\n>  >  15.  现在开始读取第四个条目。\n>  >  16.  04 是前一个题目的长度。\n>  >  17.  c0 指出是 2 字节整数。\n>  >  18.  读取下 2 个字节，作为第四个条目。\n>  >  19.  最终遇到 ff，这表明已经读取完 list 里的所有元素。\n>  >  20.  因此，ziplist 存储了值  [0×7fffffffffffffff, 65535, 16380, 63]。\n\n\n### 1.6.10 Intset 编码\n一个 Inset 是一个整数的二叉搜索树。这个二叉树在一个整数数组里实现。intset 用于当 set 的所有元素都是整数时。Inset 支持达 `64` 位的整数。\n作为一个优化，如果整数能用更少的字节表示，整数数组将由 `16` 位或 `32` 位整数构建。当一个新元素插入时，intset 实现在需要时将进行一次升级。\n\n因为 Intset 是二叉搜索树，set 里的数字总是有序的。\n\n一个 Intset 有一个 Set 的外部接口。\n\n为了解析 Inset，首先使用“字符串编码”从流中读取一个字符串。这个字符串包含了 Intset。这个字符串的内容表示了 Intset。\n\n在字符串里，Intset 有一个非常简单的布局： `<encoding><length-of-contents><contents>`\n\n>  1.  `encoding`：是一个 `32` 位无符号整数。它有 3 个可能的值 － `2`, `4` 或 `8`。它指出内容里存储的每个整数的字节大小。嗯，是的，这是浪费的－可以在 `2` bit 里存储这些信息。\n>\n>  2.  `length-of-contet`：是一个 `32` 位无符号整数，指出内容数组的长度。\n>\n>  3.  `contents`：是一个 `$length-of-content` 个字节的数组。它包含了二叉搜索树。\n\n*举例*\n`14 04 00 00 00 03 00 00 00 fc ff 00 00 fd ff 00 00 fe ff 00 00 ...`\n\n1.  使用“字符串编码”来开始。14 是字符串的长度，读取下 20 个字节直到 00.\n2.  现在，开始解析开始于 04 00 00 .... 的字符串。\n3.  前 4 个字节 04 00 00 00 是编码，因为它的值是 4，我们知道我们正在处理 32 位整数。\n4.  下 4 个字节 03 00 00 00 是内容的长度。这样，我们知道我们正在处理 3 个整数，每个 4 字节长。\n5.  从现在开始，我们以 4 个字节为一组读取，再把它转换为一个无符号整数。\n6.  这样，我们的 intset 看起来是这样的 － 0x0000FFFC, 0x0000FFFD, 0x0000FFFE。注意，这些整数是 little endian 格式的。首先出现的是最低有效位。\n\n\n### 1.6.11 以 Ziplist 编码的 Sorted Set\n以 ziplist 编码存储的 sorted list 跟上面描述的 Ziplist 很像。在 ziplist 里，sorted set 的每个元素后跟它的 score。\n\n*举例*\n `[‘Manchester City’, 1, ‘Manchester United’, 2, ‘Totenham’, 3] `\n\n如你所见 score 跟在每个元素后面。\n\n\n### 1.6.12 Ziplist 编码的 Hashmap\n在这里，hashmap 的键值对是作为连续的条目存储在 ziplist 里。\n\n注意：这是在 rdb 版本 4 引入，它废弃了在先前版本里使用的 zipmap。\n\n*举例*\n` {\"us\" => “washington”, “india” => \"delhi\"} `\n存储在 ziplist 里是： ` [“us”, “washington”, “india”, “delhi”]`\n\n\n#### CRC32 校验和\n从 RDB 版本 5 开始，一个 `8` 字节的 `CRC32` 校验和被加到文件结尾。可以通过  redis.conf 文件的一个参数来作废这个校验和。\n\n当校验和被作废时，这个字段将是 `0`。\n\n## 1.7 Redis 内存\n\n### 1.7.1 used_memmory\n\n```\n               /-------> 自身内存\n+------------+\n|used_memmory| --------> 对象内存\n+------------+\n               \\-------> 缓冲碎片（客户端缓冲，复制积压缓冲区，AOF 缓冲区）\n\n\nused_memmory_rss - used_memmory = 内存碎片\n```\n\nPS: 当集群容量满的时候，如果调整 repl-backlog-size , 会触发淘汰，导致业务请求阻塞 , :( 这个引发过 case\n\n### 1.7.2 used_memmory 会大于 maxmemory 吗？\n\n设置了 Maxmemory 的话，Redis 服务器每执行一个命令，都会检测内存，判断是否需要进行数据淘汰\n\n> 执行命令\n```\n/*src/redis.cprocessCommand*/\nint processCommand(redisClient *c) {\n        ......\n        // 内存超额\n        /* Handle the maxmemory directive.\n        **\n        First we try to free some memory if possible (if there are volatile\n        * keys in the dataset). If there are not the only thing we can do\n        * is returning an error. */\n        if (server.maxmemory) {\n                int retval = freeMemoryIfNeeded();\n        if ((c->cmd->flags & REDIS_CMD_DENYOOM) && retval == REDIS_ERR) {\n                flagTransaction(c);\n                addReply(c, shared.oomerr);\n                return REDIS_OK;\n        }\n    }\n    ......\n}\n```\n\n> freeMemoryIfNeeded 函数\n```\nint freeMemoryIfNeeded(void) {\n    size_t mem_used, mem_tofree, mem_freed;\n    int slaves = listLength(server.slaves);\n\n    /* Remove the size of slaves output buffers and AOF buffer from the\n     * count of used memory. */\n    // 计算出 Redis 目前占用的内存总数，但有两个方面的内存不会计算在内：\n    // 1）从服务器的输出缓冲区的内存\n    // 2）AOF 缓冲区的内存\n    mem_used = zmalloc_used_memory();\n    if (slaves) {\n        listIter li;\n        listNode *ln;\n\n        listRewind(server.slaves,&li);\n        while((ln = listNext(&li))) {\n            redisClient *slave = listNodeValue(ln);\n            unsigned long obuf_bytes = getClientOutputBufferMemoryUsage(slave);\n            if (obuf_bytes > mem_used)\n                mem_used = 0;\n            else\n                mem_used -= obuf_bytes;\n        }\n    }\n    if (server.aof_state != REDIS_AOF_OFF) {\n        mem_used -= sdslen(server.aof_buf);\n        mem_used -= aofRewriteBufferSize();\n    }\n\n    /* Check if we are over the memory limit. */\n    // 如果目前使用的内存大小比设置的 maxmemory 要小，那么无须执行进一步操作\n    if (mem_used <= server.maxmemory) return REDIS_OK;\n\n    // 如果占用内存比 maxmemory 要大，但是 maxmemory 策略为不淘汰，那么直接返回\n    if (server.maxmemory_policy == REDIS_MAXMEMORY_NO_EVICTION)\n        return REDIS_ERR; /* We need to free memory, but policy forbids. */\n\n    /* Compute how much memory we need to free. */\n    // 计算需要释放多少字节的内存\n    mem_tofree = mem_used - server.maxmemory;\n\n    // 初始化已释放内存的字节数为 0\n    mem_freed = 0;\n\n    // 根据 maxmemory 策略，\n    // 遍历字典，释放内存并记录被释放内存的字节数\n    while (mem_freed < mem_tofree) {\n        int j, k, keys_freed = 0;\n\n        // 遍历所有字典\n        for (j = 0; j < server.dbnum; j++) {\n            long bestval = 0; /* just to prevent warning */\n            sds bestkey = NULL;\n            dictEntry *de;\n            redisDb *db = server.db+j;\n            dict *dict;\n\n            if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||\n                server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM)\n            {\n                // 如果策略是 allkeys-lru 或者 allkeys-random\n                // 那么淘汰的目标为所有数据库键\n                dict = server.db[j].dict;\n            } else {\n                // 如果策略是 volatile-lru 、 volatile-random 或者 volatile-ttl\n                // 那么淘汰的目标为带过期时间的数据库键\n                dict = server.db[j].expires;\n            }\n\n            // 跳过空字典\n            if (dictSize(dict) == 0) continue;\n\n            /* volatile-random and allkeys-random policy */\n            // 如果使用的是随机策略，那么从目标字典中随机选出键\n            if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_RANDOM ||\n                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_RANDOM)\n            {\n                de = dictGetRandomKey(dict);\n                bestkey = dictGetKey(de);\n            }\n\n            /* volatile-lru and allkeys-lru policy */\n            // 如果使用的是 LRU 策略，\n            // 那么从一集 sample 键中选出 IDLE 时间最长的那个键\n            else if (server.maxmemory_policy == REDIS_MAXMEMORY_ALLKEYS_LRU ||\n                server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_LRU)\n            {\n                struct evictionPoolEntry *pool = db->eviction_pool;\n\n                while(bestkey == NULL) {\n                    // 随机取一集键值对\n                    evictionPoolPopulate(dict, db->dict, db->eviction_pool);\n                    /* Go backward from best to worst element to evict. */\n                    for (k = REDIS_EVICTION_POOL_SIZE-1; k >= 0; k--) {\n                        if (pool[k].key == NULL) continue;\n                        de = dictFind(dict,pool[k].key);\n\n                        /* Remove the entry from the pool. */\n                        sdsfree(pool[k].key);\n                        /* Shift all elements on its right to left. */\n                        memmove(pool+k,pool+k+1,\n                            sizeof(pool[0])*(REDIS_EVICTION_POOL_SIZE-k-1));\n                        /* Clear the element on the right which is empty\n                         * since we shifted one position to the left.  */\n                        pool[REDIS_EVICTION_POOL_SIZE-1].key = NULL;\n                        pool[REDIS_EVICTION_POOL_SIZE-1].idle = 0;\n\n                        /* If the key exists, is our pick. Otherwise it is\n                         * a ghost and we need to try the next element. */\n                        if (de) {\n                            bestkey = dictGetKey(de);\n                            break;\n                        } else {\n                            /* Ghost... */\n                            continue;\n                        }\n                    }\n                }\n            }\n\n            /* volatile-ttl */\n            // 策略为 volatile-ttl ，从一集 sample 键中选出过期时间距离当前时间最接近的键\n            else if (server.maxmemory_policy == REDIS_MAXMEMORY_VOLATILE_TTL) {\n                for (k = 0; k < server.maxmemory_samples; k++) {\n                    sds thiskey;\n                    long thisval;\n\n                    de = dictGetRandomKey(dict);\n                    thiskey = dictGetKey(de);\n                    thisval = (long) dictGetVal(de);\n\n                    /* Expire sooner (minor expire unix timestamp) is better\n                     * candidate for deletion */\n                    if (bestkey == NULL || thisval < bestval) {\n                        bestkey = thiskey;\n                        bestval = thisval;\n                    }\n                }\n            }\n\n            /* Finally remove the selected key. */\n            // 删除被选中的键\n            if (bestkey) {\n                long long delta;\n\n                robj *keyobj = createStringObject(bestkey,sdslen(bestkey));\n                propagateExpire(db,keyobj);\n                /* We compute the amount of memory freed by dbDelete() alone.\n                 * It is possible that actually the memory needed to propagate\n                 * the DEL in AOF and replication link is greater than the one\n                 * we are freeing removing the key, but we can't account for\n                 * that otherwise we would never exit the loop.\n                 *\n                 * AOF and Output buffer memory will be freed eventually so\n                 * we only care about memory used by the key space. */\n                // 计算删除键所释放的内存数量\n                delta = (long long) zmalloc_used_memory();\n                dbDelete(db,keyobj);\n                delta -= (long long) zmalloc_used_memory();\n                mem_freed += delta;\n\n                // 对淘汰键的计数器增一\n                server.stat_evictedkeys++;\n\n                notifyKeyspaceEvent(REDIS_NOTIFY_EVICTED, \"evicted\",\n                    keyobj, db->id);\n                decrRefCount(keyobj);\n                keys_freed++;\n\n                /* When the memory to free starts to be big enough, we may\n                 * start spending so much time here that is impossible to\n                 * deliver data to the slaves fast enough, so we force the\n                 * transmission here inside the loop. */\n                if (slaves) flushSlavesOutputBuffers();\n            }\n        }\n\n        if (!keys_freed) return REDIS_ERR; /* nothing to free... */\n    }\n\n    return REDIS_OK;\n}\n```\n\n## 1.8 骚操作\n\n### 1.8.1 Redis 关闭过期自动删除策略\n\n```\ndebug set-active-expire 0\n\n设置为 1 时恢复自动删除策略\n```\n\n# 2 Redis twemproxy 集群\n\n> * Nutcracker，又称 Twemproxy（读音：\"two-em-proxy\"）是支持 memcached 和 redis 协议的快速、轻量级代理；\n> * 它的建立旨在减少后端缓存服务器上的连接数量；\n> * 再结合管道技术（`pipelining*`）、及分片技术可以横向扩展分布式缓存架构；\n>   * Redis pipelining（流式批处理、管道技术）：将一系列请求连续发送到 Server 端，不必每次等待 Server 端的返回，而 Server 端会将请求放进一个有序的管道中，在执行完成后，会一次性将结果返回（解决 Client 端和 Server 端的网络延迟造成的请求延迟）\n\n## 2.1 Twemproxy 特性\n\ntwemproxy 的特性：\n\n> * 支持失败节点自动删除\n>   * 可以设置重新连接该节点的时间\n>   * 可以设置连接多少次之后删除该节点\n> * 支持设置 HashTag\n>   * 通过 HashTag 可以自己设定将两个 key 哈希到同一个实例上去\n> * 减少与 redis 的直接连接数\n>   * 保持与 redis 的长连接\n>   * 减少了客户端直接与服务器连接的连接数量\n> * 自动分片到后端多个 redis 实例上\n>   * 多种 hash 算法：md5、crc16、crc32 、crc32a、fnv1_64、fnv1a_64、fnv1_32、fnv1a_32、hsieh、murmur、jenkins\n> * 多种分片算法：ketama（一致性 hash 算法的一种实现）、modula、random\n>   * 可以设置后端实例的权重\n> * 避免单点问题\n>   * 可以平行部署多个代理层，通过 HAProxy 做负载均衡，将 redis 的读写分散到多个 twemproxy 上。\n> * 支持状态监控\n>   * 可设置状态监控 ip 和端口，访问 ip 和端口可以得到一个 json 格式的状态信息串\n>   * 可设置监控信息刷新间隔时间\n> * 使用 pipelining 处理请求和响应\n>   * 连接复用，内存复用\n>   * 将多个连接请求，组成 reids pipelining 统一向 redis 请求\n> * 并不是支持所有 redis 命令\n>   * 不支持 redis 的事务操作\n>   * 使用 SIDFF, SDIFFSTORE, SINTER, SINTERSTORE, SMOVE, SUNION and SUNIONSTORE 命令需要保证 key 都在同一个分片上。\n\n## 2.2 环境说明\n\n```\n4 台 redis 服务器\n10.10.10.4:6379   - 1\n10.10.10.5:6379   - 2\n```\n\n\n## 2.2 安装依赖\n\n安装 autoconf\ncentos 7 yum 安装既可， autoconf 版本必须 2.64 以上版本\n\n```\nyum -y install autoconf\n```\n\n## 2.3 安装 Twemproxy\n\n```\ngit clone https://github.com/twitter/twemproxy.git\nautoreconf -fvi          #生成 configure 文件\n./configure --prefix=/opt/local/twemproxy/ --enable-debug=log\nmake && make install\nmkdir -p /opt/local/twemproxy/{run,conf,logs}\nln -s /opt/local/twemproxy/sbin/nutcracker /usr/bin/\n```\n\n## 2.4 配置 Twemproxy\n\ncd /opt/local/twemproxy/conf/\n\nvi nutcracker.yml          #编辑配置文件\n\n```\nmeetbill:\n\n  listen: 10.10.10.4:6380                         #监听端口\n  hash: fnv1a_64                                  #key 值 hash 算法，默认 fnv1a_64\n  distribution: ketama                            #分布算法\n  #ketama 一致性 hash 算法；modula 非常简单，就是根据 key 值的 hash 值取模；random 随机分布\n  auto_eject_hosts: true                          #摘除后端故障节点\n  redis: true                                     #是否是 redis 缓存，默认是 false\n  timeout: 400                                    #代理与后端超时时间，毫秒\n  server_retry_timeout: 200000                    #摘除故障节点后重新连接的时间，毫秒\n  server_failure_limit: 1                         #故障多少次摘除\n  servers:\n   - 10.10.10.4:6379:1 server1\n   - 10.10.10.5:6379:1 server2\n```\n\n检查配置文件是否正确\n\n```\nnutcracker -t -c /opt/local/twemproxy/conf/nutcracker.yml\n```\n\n## 2.5 启动 Twemproxy\n\n### 2.5.1 启动命令详解\n```\nUsage: nutcracker [-?hVdDt] [-v verbosity level] [-o output file]\n[-c conf file] [-s stats port] [-a stats addr]\n[-i stats interval] [-p pid file] [-m mbuf size]\n参数    释义\n-h, –help   查看帮助文档，显示命令选项\n-V, –version    查看 nutcracker 版本\n-t, –test-conf  测试配置脚本的正确性\n-d, –daemonize  以守护进程运行\n-D, –describe-stats 打印状态描述\n-v, –verbosity=N    设置日志级别 (default: 5, min: 0, max: 11)\n-o, –output=S   设置日志输出路径，默认为标准错误输出 (default: stderr)\n-c, –conf-file=S    指定配置文件路径 (default: conf/nutcracker.yml)\n-s, –stats-port=N   设置状态监控端口，默认 22222 (default: 22222)\n-a, –stats-addr=S   设置状态监控 IP，默认 0.0.0.0 (default: 0.0.0.0)\n-i, –stats-interval=N   设置状态聚合间隔 (default: 30000 msec)\n-p, –pid-file=S 指定进程 pid 文件路径，默认关闭 (default: off)\n-m, –mbuf-size=N    设置 mbuf 块大小，以 bytes 单位 (default: 16384 bytes)\n```\n### 2.5.2 启动\n\n```\nnutcracker -d -c /opt/local/twemproxy/conf/nutcracker.yml -p /opt/local/twemproxy/run/redisproxy.pid -o /opt/local/twemproxy/logs/redisproxy.log\n```\n## 2.6 查看状态\n\n### 2.6.1 状态参数\n\n```\nnutcracker --describe-stats\nThis is nutcracker-0.2.4\n\npool stats:\n  client_eof          \"# eof on client connections\"\n  client_err          \"# errors on client connections\"\n  client_connections  \"# active client connections\"\n  server_ejects       \"# times backend server was ejected\"\n  forward_error       \"# times we encountered a forwarding error\"\n  fragments           \"# fragments created from a multi-vector request\"\n\nserver stats:\n  server_eof          \"# eof on server connections\"\n  server_err          \"# errors on server connections\"\n  server_timedout     \"# timeouts on server connections\"\n  server_connections  \"# active server connections\"\n  requests            \"# requests\"\n  request_bytes       \"total request bytes\"\n  responses           \"# respones\"\n  response_bytes      \"total response bytes\"\n  in_queue            \"# requests in incoming queue\"\n  in_queue_bytes      \"current request bytes in incoming queue\"\n  out_queue           \"# requests in outgoing queue\"\n  out_queue_bytes     \"current request bytes in outgoing queue\"\n```\n\n### 2.6.2 状态实例\n\n```\n#curl  -s http://127.0.0.1:22222|python -mjson.tool\n{\n    \"meetbill\": {                    # 配置名称\n        \"client_connections\": 0,     # 当前活跃的客户端连接数\n        \"client_eof\": 0,\n        \"client_err\": 2,             # 客户端连接错误次数\n        \"forward_error\": 0,          # 转发错误次数\n        \"fragments\": 0,\n        \"server_ejects\": 0           # 后端服务被踢出次数\n        \"server1\": {\n            \"in_queue\": 0,\n            \"in_queue_bytes\": 0,\n            \"out_queue\": 0,\n            \"out_queue_bytes\": 0,\n            \"request_bytes\": 0,      # 已请求字节数\n            \"requests\": 0,           # 已请求次数\n            \"response_bytes\": 0,     # 已相应字节数\n            \"responses\": 0,          # 已响应次数\n            \"server_connections\": 0, # 当前活跃的服务端连接数\n            \"server_eof\": 0,\n            \"server_err\": 0,         # 服务端连接错误次数\n            \"server_timedout\": 0     # 因连接超时的服务端错误次数\n        },\n        \"server2\": {\n            \"in_queue\": 0,\n            \"in_queue_bytes\": 0,\n            \"out_queue\": 0,\n            \"out_queue_bytes\": 0,\n            \"request_bytes\": 0,\n            \"requests\": 0,\n            \"response_bytes\": 0,\n            \"responses\": 0,\n            \"server_connections\": 0,\n            \"server_eof\": 0,\n            \"server_err\": 0,\n            \"server_timedout\": 0\n        },\n    },\n    \"service\": \"nutcracker\",\n    \"source\": \"meetbill\",    # 主机名\n    \"timestamp\": 1520780415, # 当前时间戳\n    \"uptime\": 3160,          # 服务已经启动的时间（单位：秒\n    \"version\": \"0.2.4\"\n}\n```\n### 2.6.3 获取 Twemproxy 状态\n\n使用 curl 获取 Twemproxy 状态时，如果后端的 redis 或者 memcache 过多，将会导致获取状态内容失败，这个是因为 proxy 的状态端口返回的不是 HTTP 数据包，可以进行如下解决方法\n\n> Python 程序\n```\ndef fetch_stats(ip, port):\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect((ip, port))\n    raw = \"\"\n    while True:\n        data = s.recv(1024)\n        if len(data) == 0:\n            break\n        raw += data\n    s.close()\n    stats = json.loads(raw)\n    return stats\n```\n\n> nc\n```\nnc ip stat_port\n```\n## 2.7 其他\n\n### 2.7.1 发送信号修改日志级别以及重新打开日志文件\n\n日志只有在编译安装的时候启用（ --enable-debug=log)，默认情况下日志写到 stderr. 可以使用 -o 或者 --output 命令指定输出文件，使用 -v 标记日志级别\n\n```\n# 提高日志级别（级别越高越详细）\nkill -SIGTTIN <pid>\n\n# 降低日志级别\nkill -SIGTTOU <pid>\n\n# 重新打开日志文件\nkill -SIGHUP <pid>\n```\n\n# 3 redis cluster\n\n## 3.1 cluster 命令\n\n> * 集群 (cluster)\n>   * cluster info                           打印集群的信息\n>   * cluster nodes                          列出集群当前已知的所有节点 (node)，以及这些节点的相关信息\n> * 节点 (node)\n>   * cluster meet <ip> <port>               将 ip 和 port 所指定的节点添加到集群当中，让它成为集群的一份子\n>   * cluster forget <node_id>               从集群中移除 node_id 指定的节点\n>   * cluster replicate <node_id>            将当前节点设置为 node_id 指定的节点的从节点\n>   * cluster saveconfig                     将节点的配置文件保存到硬盘里面\n>   * cluster slaves <node_id>               列出该 slave 节点的 master 节点\n>   * cluster set-config-epoch               强制设置 configEpoch\n> * 槽 (slot)\n>   * cluster addslots <slot> [slot ...]     将一个或多个槽 (slot) 指派 (assign) 给当前节点\n>   * cluster delslots <slot> [slot ...]     移除一个或多个槽对当前节点的指派\n>   * cluster flushslots                     移除指派给当前节点的所有槽，让当前节点变成一个没有指派任何槽的节点\n>   * cluster setslot <slot> node <node_id>        将槽 slot 指派给 node_id 指定的节点，如果槽已经指派给另一个节点，那么先让另一个节点删除该槽，然后再进行指派\n>   * cluster setslot <slot> migrating <node_id>   将本节点的槽 slot 迁移到 node_id 指定的节点中\n>   * cluster setslot <slot> importing <node_id>   从 node_id 指定的节点中导入槽 slot 到本节点\n>   * cluster setslot <slot> stable                取消对槽 slot 的导入 (import) 或者迁移 (migrate)\n> * 键 (key)\n>   * cluster keyslot <key>                        计算键 key 应该被放置在哪个槽上\n>   * cluster countkeysinslot <slot>               返回槽 slot 目前包含的键值对数量\n>   * cluster getkeysinslot <slot> <count>         返回 count 个 slot 槽中的键\n> * 其它\n>   * cluster myid                           返回节点的 ID\n>   * cluster slots                          返回节点负责的 slot\n>   * cluster reset                          重置集群，慎用\n\n## 3.2 redis cluster 配置\n```\ncluster-enabled yes\n```\n如果配置 yes 则开启集群功能，此 redis 实例作为集群的一个节点，否则，它是一个普通的单一的 redis 实例。\n\n```\ncluster-config-file nodes-6379.conf\n```\n虽然此配置的名字叫\"集群配置文件\"，但是此配置文件不能人工编辑，它是集群节点自动维护的文件，主要用于记录集群中有哪些节点、他们的状态以及一些持久化参数等，方便在重启时恢复这些状态。通常是在收到请求之后这个文件就会被更新。\n```\ncluster-node-timeout 15000\n```\n这是集群中的节点能够失联的最大时间，超过这个时间，该节点就会被认为故障。如果主节点超过这个时间还是不可达，则用它的从节点将启动故障迁移，升级成主节点。注意，任何一个节点在这个时间之内如果还是没有连上大部分的主节点，则此节点将停止接收任何请求。一般设置为 15 秒即可。\n```\ncluster-slave-validity-factor 10\n```\n如果设置成 0，则无论从节点与主节点失联多久，从节点都会尝试升级成主节点。如果设置成正数，则 cluster-node-timeout 乘以 cluster-slave-validity-factor 得到的时间，是从节点与主节点失联后，此从节点数据有效的最长时间，超过这个时间，从节点不会启动故障迁移。假设 cluster-node-timeout=5，cluster-slave-validity-factor=10，则如果从节点跟主节点失联超过 50 秒，此从节点不能成为主节点。注意，如果此参数配置为非 0，将可能出现由于某主节点失联却没有从节点能顶上的情况，从而导致集群不能正常工作，在这种情况下，只有等到原来的主节点重新回归到集群，集群才恢复运作。\n```\ncluster-migration-barrier 1\n```\n主节点需要的最小从节点数，只有达到这个数，主节点失败时，它从节点才会进行迁移。更详细介绍可以看本教程后面关于副本迁移到部分。\n```\ncluster-require-full-coverage yes\n```\n在部分 key 所在的节点不可用时，如果此参数设置为\"yes\"（默认值）, 则整个集群停止接受操作；如果此参数设置为”no”，则集群依然为可达节点上的 key 提供读操作。\n\n## 3.3 redis cluster 状态\n\n127.0.0.1:8001> cluster info\n> * cluster_state:ok\n>   * 如果当前 redis 发现有 failed 的 slots，默认为把自己 cluster_state 从 ok 个性为 fail, 写入命令会失败。如果设置 cluster-require-full-coverage 为 no, 则无此限制。\n> * cluster_slots_assigned:16384             #已分配的槽\n> * cluster_slots_ok:16384                   #槽的状态是 ok 的数目\n> * cluster_slots_pfail:0                    #可能失效的槽的数目\n> * cluster_slots_fail:0                     #已经失效的槽的数目\n> * cluster_known_nodes:6                    #集群中节点个数\n> * cluster_size:3                           #集群中设置的分片个数\n> * cluster_current_epoch:15                 #集群中的 currentEpoch 总是一致的，currentEpoch 越高，代表节点的配置或者操作越新，集群中最大的那个 node epoch\n> * cluster_my_epoch:12                      #当前节点的 config epoch，每个主节点都不同，一直递增，其表示某节点最后一次变成主节点或获取新 slot 所有权的逻辑时间。\n> * cluster_stats_messages_sent:270782059\n> * cluster_stats_messages_received:270732696\n\n\n```\n127.0.0.1:8001> cluster nodes\n25e8c9379c3db621da6ff8152684dc95dbe2e163 192.168.64.102:8002 master - 0 1490696025496 15 connected 5461-10922\nd777a98ff16901dffca53e509b78b65dd1394ce2 192.168.64.156:8001 slave 0b1f3dd6e53ba76b8664294af2b7f492dbf914ec 0 1490696027498 12 connected\n8e082ea9fe9d4c4fcca4fbe75ba3b77512b695ef 192.168.64.108:8000 master - 0 1490696025997 14 connected 0-5460\n0b1f3dd6e53ba76b8664294af2b7f492dbf914ec 192.168.64.170:8001 myself,master - 0 0 12 connected 10923-16383\neb8adb8c0c5715525997bdb3c2d5345e688d943f 192.168.64.101:8002 slave 25e8c9379c3db621da6ff8152684dc95dbe2e163 0 1490696027498 15 connected\n4000155a787ddab1e7f12584dabeab48a617fc46 192.168.67.54:8000 slave 8e082ea9fe9d4c4fcca4fbe75ba3b77512b695ef 0 1490696026497 14 connected\n```\n> * 节点 ID：例如 25e8c9379c3db621da6ff8152684dc95dbe2e163\n> * ip:port：节点的 ip 地址和端口号，例如 192.168.64.102:8002\n> * flags：节点的角色 (master,slave,myself) 以及状态 (pfail,fail)\n> * 如果节点是一个从节点的话，那么跟在 flags 之后的将是主节点的节点 ID，例如 192.168.64.156:8001 主节点的 ID 就是 0b1f3dd6e53ba76b8664294af2b7f492dbf914ec\n> * 集群最近一次向节点发送 ping 命令之后，过了多长时间还没接到回复\n> * 节点最近一次返回 pong 回复的时间\n> * 节点的配置纪元 (config epoch)\n> * 本节点的网络连接情况\n> * 节点目前包含的槽，例如 192.168.64.102:8002 目前包含的槽为 5461-10922\n\n\n## 3.4 redis cluster 的 failover 机制\n\nfailover 是 redis cluster 提供的容错机制，cluster 最核心的功能之一。failover 支持两种模式：\n> * 故障 failover：自动恢复集群的可用性\n> * 人为 failover：支持集群的可运维操作\n\n### 3.4.1 故障 failover\n\n故障 failover 表现在一个 master 分片故障后，slave 接管 master 的过程。\n\n分为如下 3 个阶段：\n> * 探测阶段\n> * 准备阶段\n> * 执行阶段\n\n#### 探测阶段\n集群中的所有分片通过 gossip 协议传递。探测步骤为：\n> * （1）在 cron 中非遍历 cluster nodes 做 ping 发送，随机从 5 个节点中选出最老 pong_recv 的节点发送 ping，再遍历节点中 pong_recv > timeout/2 的节点发送 ping。\n> * （2）再遍历每个节点从发出 ping 包后超时没有收到 pong 包的时间，超时将对应的分片设置为 pfail 状态，在跟其他节点的 gossip 包过程中，每个节点会带上被标记为 pfail 状态的包。\n> * （3）每个正常分片收到 ping 包后，统计集群中 maste 分片将故障节点设置为 pfail， 超过一半以上的节点设置为 pfail， 则将节点设置为 fail 状态。如果这个分片属于故障节点的 slave 节点，则主动广播故障节点为 fail 状态。\n\n#### 准备阶段\n\n在 cron 函数中，slave 节点获取到 master 节点状态为 fail，主动发起一次 failover 操作，该操作并不是立即执行，而是设计了多个限制：\n> * （1）过期的超时不执行。如何判断是够过期？\n>   * data_age = 当前时间点 - 上次 master 失联的时间点 - 超时时间\n>   * 如果 data_age > `master 到 slave 的 ping 间隔时间 + 超时时间*cluster_slave_validity_factor`， 则认为过期。cluster_slave_validity_factor 是一个配置项，cluster_slave_validity_factor 设置的越小越不容易触发 failover。\n> * （2）计算出一个延迟执行的时间 failover_auth_time， failover_auth_time = 当前时间 + 500ms + 0-500ms 的随机值 + 当前 slave 的 rank * 1s,  rank 按已同步的 offset 计算，offset 同步的越延迟，rank 值越大，该 slave 就越推迟触发 failover 的时间，以此来避免多个 slave 同时 failover。只有当前时间到 failover_auth_time 的时间点才会执行 failover。\n\n#### 执行阶段\n> * （1）将 currentEpoch 自增，再赋值给 failover_auth_epoch\n> * （2）向其他 master 分片发起 failover 投票，等待投票结果\n> * （3）其他 master 分片收到 CLUSTERMSG_TYPE_FAILOVER_AUTH_REQUEST 请求后，会判断是否符合以下情况：\n>   * epoch 必须 >= 所有集群视图的 master 节点的 epoch\n>   * 发起者是 slave\n>   * slave 的 master 已是 fail 状态\n>   * 在相同 epoch 内只投票一次\n>   * 在超时时间（cluster_node_timeout） * 2 的时间内只投票一次\n> * （4）其他 master 回复 CLUSTERMSG_TYPE_FAILOVER_AUTH_ACK，slave 端收到后做统计\n> * （5）在 cron 中判断统计超过一半以上 master 回复，开始执行 failover\n> * （6）标记自身节点为 master\n> * （7）清理复制链路\n> * （8）重置集群拓扑结构信息\n> * （9）向集群内所有节点广播\n\n### 3.4.2 人为 failover\n人为 failover 支持三种模式的 failover：缺省、force、takeover。\n\n#### 缺省\n```\n（1）由 salve 给 master 发送 CLUSTERMSG_TYPE_MFSTART\n（2）master 收到后设置 clients_pause_end_time = 当前时间 + 5s*2，clients_paused =1 , 客户端暂停所有请求，新建请求会被加到 block client list。\n（3）master 在 ping 包中带上 repl_offset 的信息\n（4）slave 检查 master 的 repl_offset，确认同步已完成\n（5）设置 mf_can_start = 1，在 cron 中开始正常的 failover 流程，不需要像故障 failover 设置推迟执行而是立即执行操作，而且其他 master 投票时不需要考虑 master 是否为 fail 状态。\n```\n日志：如下为主实例日志\n```\n5484:M 01 Apr 18:31:07.572 # Manual failover requested by slave db1e03f2158f48019cddd680764a17635b3901c5.\n5484:M 01 Apr 18:31:07.796 # Failover auth granted to db1e03f2158f48019cddd680764a17635b3901c5 for epoch 122\n5484:M 01 Apr 18:31:07.797 # Connection with slave 【slave1_ip:slave1_port】 lost.\n5484:M 01 Apr 18:32:08.509 # Disconnecting timedout slave: 【slave2_ip:slave2_port】\n5484:M 01 Apr 18:32:08.509 # Connection with slave 【slave2_ip:slave2_port】 lost.\n5484:M 01 Apr 18:32:08.509 # Disconnecting timedout slave: 【slave3_ip:slave3_port】\n5484:M 01 Apr 18:32:08.509 # Connection with slave 【slave3_ip:slave3_port】 lost.\n```\n#### force\n忽略主备同步的状态，设置 mf_can_start = 1，标记 failover 开始。\n\n#### takeover\n直接执行故障 failover 的第 6-9 步，忽略主备同步，忽略集群其他 master 的投票。\n\n## 3.5 弃用 redis cluster 的原因\n\n\n### 3.5.1 集群规模比较大时，容易出现 handshake 节点\n\n> redis cluster 增加节点简单，但是去掉一个节点确比较复杂，尤其是当集群规模特别大的时候\n```\n增加节点只需要 meet 即可\nforget 节点的时候需要全员进行 forget，这里注意了，如果集群是 3 地域及以上，需要汇总所有 redis 实例的，有漏掉的 redis 则 forget 失败，比如漏掉了单个地域等等\n\n汇总了所有 redis 实例就万事大吉了吗？no\n\n下发的 forget 命令不一定是成功的，比如对应的 node redis 连接数满了， node 正在加载 rdb 等，没接成功\n\n\n这个时候下掉节点，这个节点就有可能成为 handshake 节点\n```\n\n详细请看 [Cluster: How to remove a node in handshake state](https://github.com/antirez/redis/issues/2965)\n\n即未对所有的 redis 节点都发送 forget 命令，则可能产生 handshake 节点\n\n\n> 发现 handshake 节点\n```\ngrep -E 'noaddr|hand|fail'\n```\n### 3.5.2 网络问题导致某个 node 隔离后，在很长时间后， node 网络恢复，可能发生集群融合\n\n网络问题导致某个 node 隔离后，在很长时间后， node 网络恢复，但是 node 记录的节点（ip:port）已经因为实例迁移等，变成了其他集群，就会和两个集群握手，然后集群融合\n\n导致数据丢失\n\n# 4 原理说明\n\n## 4.1 一致性 hash\n\n### 4.1.1 传统的取模方式\n例如 10 条数据，3 个节点，如果按照取模的方式，那就是\n> * node a: 0,3,6,9\n> * node b: 1,4,7\n> * node c: 2,5,8\n\n当增加一个节点的时候，数据分布就变更为\n\n> * node a:0,4,8\n> * node b:1,5,9\n> * node c: 2,6\n> * node d: 3,7\n\n总结：数据 3,4,5,6,7,8,9 在增加节点的时候，都需要做搬迁，成本太高\n\n### 4.1.2 一致性哈希方式\n\n最关键的区别就是，对节点和数据，都做一次哈希运算，然后比较节点和数据的哈希值，数据取和节点最相近的节点做为存放节点。这样就保证当节点增加或者减少的时候，影响的数据最少。还是拿刚刚的例子，（用简单的字符串的 ascii 码做哈希 key）：\n\n十条数据，算出各自的哈希值\n\n> * 0：192\n> * 1：196\n> * 2：200\n> * 3：204\n> * 4：208\n> * 5：212\n> * 6：216\n> * 7：220\n> * 8：224\n> * 9：228\n\n有三个节点，算出各自的哈希值\n\n> * node a: 203\n> * node g: 209\n> * node z: 228\n\n这个时候比较两者的哈希值，如果大于 228，就归到前面的 203，相当于整个哈希值就是一个环，对应的映射结果：\n\n> * node a: 0,1,2\n> * node g: 3,4\n> * node z: 5,6,7,8,9\n\n这个时候加入 node n, 就可以算出 node n 的哈希值：\n\n> * node n: 216\n\n这个时候对应的数据就会做迁移：\n\n> * node a: 0,1,2\n> * node g: 3,4\n> * node n: 5,6\n> * node z: 7,8,9\n\n这个时候只有 5 和 6 需要做迁移\n\n### 4.1.3 虚拟节点\n\n另外，这个时候如果只算出三个哈希值，那再跟数据的哈希值比较的时候，很容易分得不均衡，因此就引入了虚拟节点的概念，通过把三个节点加上 ID 后缀等方式，每个节点算出 n 个哈希值，均匀的放在哈希环上，这样对于数据算出的哈希值，能够比较散列的分布（详见下面代码中的 replica）\n\n通过这种算法做数据分布，在增减节点的时候，可以大大减少数据的迁移规模。\n\n## 4.2 redis 过期数据存储方式以及删除方式\n\n当你通过 expire 或者 pexpire 命令，给某个键设置了过期时间，那么它在服务器是怎么存储的呢？到达过期时间后，又是怎么删除的呢？\n\n### 4.2.1 存储方式\n比如：\n```\nredis> EXPIRE book 5\n(integer) 1\n```\n首先我们知道，redis 维护了一个存储了所有的设置的 key->value 的字典。但是其实不止一个字典的。\n\n**redis 有一个包含过期事件的字典**\n\n每当有设置过期事件的 key 后，redis 会用当前的事件，加上过期的时间段，得到过期的标准时间，存储在 expires 字典中。\n\n![](./../../images/db/redis/key-expires-dict.png)\n\n从上图可以看出来，比如你给 book 设置过期事件，那么 expires 字典的 key 也为 book，值是当前的时间 +5s 后的 unix time。\n\n### 4.2.2 删除方式\n\n如果一个键已经过期了，那么 redis 的如果删除它呢？redis 采用了 2 种删除方式；\n\n#### 惰性删除\n\n惰性删除的原理是：放任键过期不管，但是每次从键空间获取键的时候，如果该键存在，再去 expires 字典判断这个键是不是过期。如果过期则返回空，并删除该键。过程如下：\n\n![](./../../images/db/redis/key-expires-delete.png)\n\n- 优点：惰性删除对 cpu 是友好的。保证在键必须删除的时候才会消耗 cpu\n- 缺点：惰性删除对内存特别不友好。虽然键过期，但是没有使用则一直存在内存中。\n\n#### 定期删除\nredis 架构中的时间事件，每隔一段时间后，在规定的时间内，会主动去检测 expires 字典中包含的 key 进行检测，发现过期的则删除。在 redis 的源码 redis.c/activeExpireCycle 函数中。\n下面分别是这个函数的源码与伪代码：\n\n\n```\nvoid  activeExpireCycle(int type) {\n    // 静态变量，用来累积函数连续执行时的数据\n    static  unsigned  int current_db =  0; /* Last DB tested. */\n    static  int timelimit_exit =  0; /* Time limit hit in previous call? */\n    static  long  long last_fast_cycle =  0; /* When last fast cycle ran. */\n\n    unsigned  int j, iteration =  0;\n    // 默认每次处理的数据库数量\n    unsigned  int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;\n    // 函数开始的时间\n    long  long start =  ustime(), timelimit;\n\n    // 快速模式\n    if (type == ACTIVE_EXPIRE_CYCLE_FAST) {\n        // 如果上次函数没有触发 timelimit_exit ，那么不执行处理\n        if (!timelimit_exit) return;\n        // 如果距离上次执行未够一定时间，那么不执行处理\n        if (start < last_fast_cycle + ACTIVE_EXPIRE_CYCLE_FAST_DURATION*2) return;\n        // 运行到这里，说明执行快速处理，记录当前时间\n        last_fast_cycle = start;\n    }\n\n    /*\n    * 一般情况下，函数只处理 REDIS_DBCRON_DBS_PER_CALL 个数据库，\n    * 除非：\n    * 当前数据库的数量小于 REDIS_DBCRON_DBS_PER_CALL\n    * 如果上次处理遇到了时间上限，那么这次需要对所有数据库进行扫描，\n    * 这可以避免过多的过期键占用空间\n    */\n    if (dbs_per_call > server.dbnum  || timelimit_exit)\n    dbs_per_call = server.dbnum;\n\n    // 函数处理的微秒时间上限\n    // ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC 默认为 25 ，也即是 25 % 的 CPU 时间\n    timelimit =  1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100;\n    timelimit_exit =  0;\n    if (timelimit <=  0) timelimit =  1;\n\n    // 如果是运行在快速模式之下\n    // 那么最多只能运行 FAST_DURATION 微秒\n    // 默认值为 1000 （微秒）\n    if (type == ACTIVE_EXPIRE_CYCLE_FAST)\n    timelimit = ACTIVE_EXPIRE_CYCLE_FAST_DURATION; /* in microseconds. */\n\n    // 遍历数据库\n    for (j =  0; j < dbs_per_call; j++) {\n        int expired;\n        // 指向要处理的数据库\n        redisDb *db = server.db+(current_db % server.dbnum);\n\n        // 为 DB 计数器加一，如果进入 do 循环之后因为超时而跳出\n        // 那么下次会直接从下个 DB 开始处理\n        current_db++;\n\n        do {\n            unsigned  long num, slots;\n            long  long now, ttl_sum;\n            int ttl_samples;\n\n            // 获取数据库中带过期时间的键的数量\n            // 如果该数量为 0 ，直接跳过这个数据库\n            if ((num =  dictSize(db->expires)) ==  0) {\n                db->avg_ttl  =  0;\n                break;\n            }\n            // 获取数据库中键值对的数量\n            slots =  dictSlots(db->expires);\n            // 当前时间\n            now =  mstime();\n\n            // 这个数据库的使用率低于 1% ，扫描起来太费力了（大部分都会 MISS）\n            // 跳过，等待字典收缩程序运行\n            if (num && slots > DICT_HT_INITIAL_SIZE &&\n            (num*100/slots <  1)) break;\n\n            // 已处理过期键计数器\n            expired =  0;\n            // 键的总 TTL 计数器\n            ttl_sum =  0;\n            // 总共处理的键计数器\n            ttl_samples =  0;\n\n            // 每次最多只能检查 LOOKUPS_PER_LOOP 个键\n            if (num > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP)\n            num = ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP;\n\n            // 开始遍历数据库\n            while (num--) {\n                dictEntry *de;\n                long  long ttl;\n\n                // 从 expires 中随机取出一个带过期时间的键\n                if ((de =  dictGetRandomKey(db->expires)) ==  NULL) break;\n                // 计算 TTL\n                ttl =  dictGetSignedIntegerVal(de)-now;\n                // 如果键已经过期，那么删除它，并将 expired 计数器增一\n                if (activeExpireCycleTryExpire(db,de,now)) expired++;\n                if (ttl <  0) ttl =  0;\n                // 累积键的 TTL\n                ttl_sum += ttl;\n                // 累积处理键的个数\n                ttl_samples++;\n            }\n\n            // 为这个数据库更新平均 TTL 统计数据\n            if (ttl_samples) {\n                // 计算当前平均值\n                long  long avg_ttl = ttl_sum/ttl_samples;\n                // 如果这是第一次设置数据库平均 TTL ，那么进行初始化\n                if (db->avg_ttl  ==  0) db->avg_ttl  = avg_ttl;\n                /* Smooth the value averaging with the previous one. */\n                // 取数据库的上次平均 TTL 和今次平均 TTL 的平均值\n                db->avg_ttl  = (db->avg_ttl+avg_ttl)/2;\n            }\n\n            // 我们不能用太长时间处理过期键，\n            // 所以这个函数执行一定时间之后就要返回\n\n            // 更新遍历次数\n            iteration++;\n\n            // 每遍历 16 次执行一次\n            if ((iteration &  0xf) ==  0  &&  /* check once every 16 iterations. */\n            (ustime()-start) > timelimit)\n            {\n                // 如果遍历次数正好是 16 的倍数\n                // 并且遍历的时间超过了 timelimit\n                // 那么断开 timelimit_exit\n                timelimit_exit =  1;\n            }\n\n            // 已经超时了，返回\n            if (timelimit_exit) return;\n\n            // 如果已删除的过期键占当前总数据库带过期时间的键数量的 25 %\n            // 那么不再遍历\n        } while (expired > ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4);\n    }\n}\n```\n**伪代码是：**\n```\n# 默认每次检测的数据库数量为 16\nDEFAULT_DB_NUMBERS = 16\n# 默认每次检测的键的数量最大为 20\nDEFAULT_KEY_NUMBERS = 20\n# 全局变量，记录当前检测的进度\ncurrent_db = 0\ndef activeExpireCycle():\n    # 初始化要检测的数据库数量\n    # 如果服务器的数据库数量小于 16，则以服务器的为准\n    if server.dbnumbers < DEFAULT_DB_NUMBERS:\n        db_numbers = server.dbnumbers\n    else\n        db_numbers = DEFAULT_DB_NUMBERS\n\n    # 遍历每次数据库\n    for i in range(db_numbers):\n        # 如果 current_db 的值等于服务器的数量，代表已经遍历全，则重新开始\n        if current_db = db_numbers:\n            current_db = 0\n\n        # 获取当前要处理的数据库\n        redisDb = server.db[current_db]\n\n        # 将数据库索引 +1，指向下一个数据库\n        current_db++\n\n        do\n            # 检测数据库中的键\n            for j in range(DEFAULT_KEY_NUMBERS):\n                # 如果数据库中没有过期键则跳过这个库\n                if redisDb.expires.size() == 0:break\n\n                # 随机获取一个带有过期事件的键\n                key_with_ttl = redisDb.expires.get_random_key()\n\n                # 检测键是不是过期了，如果过期则删除\n                if is_expired(key_with_ttl):\n                    delete_key(key_with_ttl)\n            # 已达到时间上限，则停止处理\n            if reach_time_limit(): retrun\n        while expired>ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP/4\n```\n对 activeExpireCycle 进行总结：\n- redis 默认 1s 调用 10 次，这个是 redis 的配置中的 hz 选项。hz 默认是 10，代表 1s 调用 10 次，每 100ms 调用一次。\n- hz 不能太大，太大的话，cpu 会花大量的时间消耗在判断过期的 key 上，对 cpu 不友好。但是如果你的 redis 过期数据过多，可以适当调大。\n- hz 不能太小，因为太小的话，一旦过期的 key 太多可能会过滤不完。\n- redis 执行定期删除函数，必须在一定时间内，超过该时间就 return。事件定义为`timelimit =  1000000*ACTIVE_EXPIRE_CYCLE_SLOW_TIME_PERC/server.hz/100` 可以看出该时间与 hz 成反比，hz 默认 10，timelimit 就为 25ms；hz 修改为 100，那么 timelimit 就为 2.5ms。\n- 抽取 20 个数据进行判断删除为一个轮训，每经过 16 个轮训才会去判断一次时间是不是超时。\n- 如果一个数据库，使用率低于 1%，则不去进行定期删除操作。\n- 如果对一个数据库，这次删除操作，已经删除了 25% 的过期 key，那么就跳过这个库。\n\n### 4.2.3 redis 主从删除过期 key 方式\n当 redis 主从模型下，从服务器的删除过期 key 的动作是由主服务器控制的。\n- 1、主服务器在惰性删除、客户端主动删除、定期删除一个 key 的时候，会向从服务器发送一个 del 的命令，告诉从服务器需要删除这个 key。\n\n![](./../../images/db/redis/key-expires-delete-master.png)\n\n- 2、从服务器在执行客户端读取 key 的时候，如果该 key 已经过期，也不会将该 key 删除，而是返回一个 null\n\n![](./../../images/db/redis/key-expires-delete-slave.png)\n\n- 3、从服务器只有在接收到主服务器的 del 命令才会将一个 key 进行删除。\n\n### 4.2.4 总结\n- 1、expires 字典的 key 指向数据库中的某个 key，而值记录了数据库中该 key 的过期时间，过期时间是一个以毫秒为单位的 unix 时间戳；\n- 2、redis 使用惰性删除和定期删除两种策略来删除过期的 key；惰性删除只会在碰到过期 key 才会删除；定期删除则每隔一段时间主动查找并删除过期键；\n- 3、当主服务器删除一个过期 key 后，会向所有的从服务器发送一条 del 命令，显式的删除过期 key；\n- 4、从服务器即使发现过期 key 也不会自作主张删除它，而是等待主服务器发送 del 命令，这种统一、中心化的过期 key 删除策略可以保证主从服务器的数据一致性。\n## 4.3 cluster 选举算法 Raft\n\n3 种状态：\n\n> * Leader（领袖）\n> * Follower（群众）\n> * Candidate（候选人）。\n\n规则：群众发起投票成为候选人，候选人得到大多数票至少 (n/2)+1，才能成为领导人，（自己可以投自己，当没有接受到请求节点的选票时，发起投票节点才能自己选自己），领导人负责处理所有与客户端交互，是数据唯一入口，协调指挥群众节点。\n\n选举过程：考虑最简单情况，abc 三个节点，每个节点只有一张票，当 N 个节点发出投票请求，其他节点必须投出自己的一票，不能弃票，最差的情况是每个人都有一票，那么随机设置一个 timeout 时间，就像加时赛一样，这时同时的概率大大降低，谁最先恢复过来，就向其他两个节点发出投票请求，获得大多数选票，成为领导人。选出 Leader 后，Leader 通过定期向所有 Follower 发送心跳信息维持其统治。若 Follower 一段时间未收到 Leader 的心跳则认为 Leader 可能已经挂了再次发起选主过程。\n\n\n# 5 常见问题处理\n\n## 5.1 内核参数 overcommit\n它是 内存分配策略，可选值：0、1、2。\n> * 0， 表示内核将检查是否有足够的可用内存供应用进程使用；如果有足够的可用内存，内存申请允许；否则，内存申请失败，并把错误返回给应用进程。\n> * 1， 表示内核允许分配所有的物理内存，而不管当前的内存状态如何。\n> * 2， 表示内核允许分配超过所有物理内存和交换空间总和的内存\n\n### 什么是 Overcommit 和 OOM\n\nLinux 对大部分申请内存的请求都回复\"yes\"，以便能跑更多更大的程序。因为申请内存后，并不会马上使用内存。这种技术叫做 Overcommit。当 linux 发现内存不足时，会发生 OOM killer(OOM=out-of-memory)。它会选择杀死一些进程（用户态进程，不是内核线程），以便释放内存。\n当 oom-killer 发生时，linux 会选择杀死哪些进程？选择进程的函数是 oom_badness 函数（在 mm/oom_kill.c 中），该函数会计算每个进程的点数 (0~1000)。点数越高，这个进程越有可能被杀死。每个进程的点数跟 oom_score_adj 有关，而且 oom_score_adj 可以被设置 (-1000 最低，1000 最高）。\n\n解决方法：\n    很简单，按提示的操作（将 vm.overcommit_memory 设为 1）即可：可以通过 ` cat /proc/sys/vm/overcommit_memory` 和 `sysctl -a | grep overcommit` 查看\n    有三种方式修改内核参数，但要有 root 权限：\n> * （1）编辑 /etc/sysctl.conf ，改 vm.overcommit_memory=1，然后 sysctl -p 使配置文件生效\n> * （2）sysctl vm.overcommit_memory=1\n> * （3）echo 1 > /proc/sys/vm/overcommit_memory\n\n## 5.2 Redis CPU 100% 时分析\n\n```\n$perf top -p 28764\n```\n## 5.3 Redis Cluster 集群出现 handshake 节点\n\n> 获取整个集群的 ip:port\n```\n不能有遗漏，否则如果对应的 node 还保存在 handshake 节点信息的话，会一直进行广播\n```\n> 对所有的 node 进行 ping 探测\n```\n检查集群中的 node 是否因为连接数满等情况导致无法连接\n```\n> 对说有 node 进行下发 forget 命令(可执行多次)\n```\n# Function\nfunction print_info() {\n    echo \"$(date +%Y%m%d-%H%M%S) $@\"\n}\n\nfunction redis_cluster_forget_fail() {\n    local ip_port=$1\n    local ip_=`echo ${ip_port} | awk -F':' '{print $1}'`\n    local port_=`echo ${ip_port} | awk -F':' '{print $2}'`\n\n    redis-cli -h ${ip_} -p ${port_} cluster nodes | sed 's/\\\\n/\\n/g' \\\n    | egrep -i 'hands|fail' | awk '{print $1}' \\\n    | while read line;do\n        print_info \"forget fail_or_hand redis-cli -h ${ip_} -p ${port_} cluster forget $line\"\n        redis-cli -h ${ip_} -p ${port_} cluster forget $line &\n    done\n}\n\nfor _ip_port in `cat ${list_ipport}`;do\n    redis_cluster_forget_fail ${_ip_port} &\ndone\n```\n> 关闭未关闭的 redis-cli\n```\n问题解决完后，可能还有很多 redis-cli 卡住\n\nps -ef | grep redis-cli | awk '{print $2}' | xargs kill -15\n```\n\n# 6 数据迁移\n\n## 6.1 目标\n\n从 A 集群热迁移到 B 集群\n\n## 6.2 怎么实现\n\nmysql 的主从同步是基于 binlog, redis 主从是一个 op buf, mongo 主从同步是 oplog.\n\nredis 里的 aof 就是类似 binlog, 记录每个操作的 log\n\n所以，我们可以利用 aof, 把它当作 binlog, 用于做迁移，分三步：\n\n> * 迁移基准数据\n> * 追增量\n> * 追上后，上层切流量。\n\nredis 的 aof 包含了基准数据和增量，所以我们只需要把旧集群中 redis 实例上的 aof 重放到新集群，重放追上时修改上层，把入口换为新集群即可。\n\n### 6.2.1 方案\n\n> 基于主从同步\n```\n模拟从库进行全量同步和后续的增量同步，全量同步时需要将 RDB 解析为 Redis 协议并写到新集群\n\n不足：\n(1) 迁移基准数据时，需要将 RDB 解析为 Redis 协议格式 （官方 hiredis 库解析大 key 时有 BUG, 超级慢），此块导致同步失败的概率极大\n(2) 需要自己实现 Redis 主从同步协议\n(3) 全量同步的 RDB 解析完成前，后面的增量数据会先放到主库的 output-buffer 中，当全量同步耗时长，或者业务写入量过大时，会导致 output-buffer 满而失败\n```\n> 基于 AOF\n```\n不足：\n(1) 需要关闭 AOF 重写，对磁盘空间有要求\n(2) 需要源集群开着 AOF 持久化，对集群性能有要求\n\n优势：\n直接通过读取 redis 自身的 AOF 文件写入目标集群，无需再次解析\n\n注意：\n(1) 迁移前记得 rewrite 下\n(2) 可以在集群的从库进行操作\n```\n## 6.3 问题\n### 6.3.1 aof 不是幂等的\n\naof 不像 binlog 那样可以重做 redolog, binlog 记录的操作是幂等 (idempotent) 的，意味着如果失败了，可以重做一次。\n\n这是因为 binlog 记录的是操作的结果，比如：\n```\nop                  log\n---------------------------\nset x 0             x = 0\nincr x              x = 1\nincr x              x = 2\n```\n但是 redis 的 aof 记录的是操作：\n```\nop                  log\n---------------------------\nset x 0             x = 0\nincr x              incr x\nincr x              incr x\n```\n这就是说，如果我们在重放 aof 的过程中出错（比如网络中断）:\n\n不能继续（不好找到上次同步到哪）,\n也不能重新重放一次，(incr 两次，值就错了）\n只能清除目标集群的数据重新迁移一次\n\n不过，好在 redis 单实例的 afo 数据都不大，一般 10G 左右，重放大约 20min 就能完成，出错的概率也很小。（这也是 redis 可以这样做，而其他持久存储比如 mysql, mongo 必须支持断点同步的原因）\n\n### 6.3.2 切流量时的不一致\n前面说的步骤是：\n\n> * 追 aof\n> * 追上后，切流量。\n追 aof 是一个动态的过程，追上后，新的写操作马上就来，所以这里追上的意思是说，新的写入马上会被消化掉。\n\n但是考虑这样一种场景：\n\n假设 client 上做对 x 做两次 set（一个机器上做两次，或者两个 app-server 上分别做）:\n```\nclient          old_cluster         new_cluster\n-----------------------------------------------\nset x 1(a)      set x 1（客户操作）\n-----------------------------------------------> 切流量到 new_cluster\nset x 2(b)                          set x 2 （客户操作）\n                                    set x 1 (b 操作被重放到 new_cluster)\n```\na 操作还没同步到 new_cluster, 流量就已经切到了 new_cluster, 这时候对同一个 key 的更新，会被老集群上的操作覆盖掉。\n\n解决：\n\n这个短暂的不一致，对多数业务，是能容忍的（很少有业务会高速更新同一个 key)\n如果非要达到一致，当追 aof 追上后，app-server 停写，等待彻底追上（此时老集群的 aof 不会有更新了）, 然后再切流量。\n\n## 6.4 实现\n\n[redis-replay-aof](https://github.com/meetbill/redis-replay-aof)\n\n# 7 redis 服务准入\n\n准入的目的是为了让大家，在合理的范围内使用 Redis 以维持 Redis 的稳定及性能。以良好的设计、常规的用法，规避 Redis 使用过程中可能遇到的问题，从而令 redis 能够更加稳定的发挥其服务特性。\n\n## 7.1 数据设计\n### 7.1.1 value 大小约束\n> 对于 string 类型 key，单 key 对应 value 大小不超过 10k\n\n> 对于复杂类型 key（hash、list、set、zset），单 key 对应 value 大小不超过 100k\n```\n单个 kv 过大对网卡及 cpu 造成较大消耗\n    redis 及 proxy 使用单线程 epoll 模型处理消息请求，在 kv 较小（100 字节内）情况下，单进程都可以承受 3 万以上 qps。但在 kv 较大情况下，qps 承压能力受网卡上限影响，同时大量数据在内存与网卡驱动之间进行复制，对 cpu 也有较大的消耗。\n    对于大 key 写请求，主要的压力在于主从复制使用的出口带宽，主节点下面带的从节点越多，出口带宽消耗越严重，同时主节点 cpu 消耗也越严重\n\n单个 kv 过大会增加运维成本\n    集群分片机制是一般是以 key 作为 hash 对象，当单个 kv 过大时，可能会出现 hash 不均的情况。hash 不均带来的后果是，一个集群下不同分片的内存使用量是有明显区别的，这对于运维过程中的资源预留规划是不利的\n    其次，单 key 过大在分片扩容场景下，可能造成数据迁移超时，导致扩容失败\n    单个 kv 过大，在执行删除操作时，会造成较长时间阻塞。\n    由于 redis 单线程运行的机制，一个操作阻塞主线程，会导致该时间段内所有请求都堆积在 tcp buffer 中，得不到及时的处理。如果较多大 kv 在短时间内密集的执行删除或其他耗时操作，会导致该 redis 响应时间明显升高，甚至超时\n\n设计建议：\n    合理构造 reids 数据结构及请求内容\n    经过网络的请求，应当仅进行满足当前需求的存取。\n    典型的优化 case 是：1 个大 json 存一个大 string，只关注 json 中某一个或某几个属性的读，也要读取全部 string；只修改 json 中一个属性，也要将整个 string 重新覆盖写。优化成 hash 后，可大大降低对网卡、cpu、内存容量的压力，同时当 hash key 个数较少（512 内），value 不是很大（64 字节），可以进行压缩，降低 redis 自身的数据结构开销。\n    尽量避免 key value 中重复的内容，比如 key 使用 id 进行索引话，value 中就可以不必再存放 id 字段。\n\n数据预热\n    若一个流程需要多次读取 redis 中相同内容，建议流程起始点一次读取，多次使用，尽量减少与 redis 交互，减轻后端压力\n```\n### 7.1.2 value 复杂度约束\n\n> 对于非 str 类型的复杂 key(hash、list、set、zset)，需注意控制单个复杂 key 下属 member 个数，单 key 下属 member 不超过 1000\n\n```\n复杂 key 的操作有很多种，主要可分为 O(1) 操作（如 hget）及非 O(1) 操作（hgetall、smembers 等）。\n当一个复杂 key 本身下属的 member 较多时，对其进行非 O(1) 操作可能会造成 redis 线程严重阻塞，从而导致请求堆积、超时。\n\n我们线上有过几次类似的 case，一个新上的流程，set 中慢慢的积累 member，然后不停的对其进行 smembers，导致 redis 进程的 cpu 缓慢上涨，最终上游超时情况越来越严重。\n因此，在设计上首先要尽量避免超大的复杂 key，其次，对于 member 数量可能会比较多的复杂 key，要严格审查其非 O(1) 操作。\n```\n### 7.1.3 冷热数据设计约束\n\n> 不建议在 redis 中存放 1 天以上不访问的数据，冷数据须考虑设置过期时间或使用 db 方式存储\n\n```\nredis 作为全内存数据库，使用其第一目的就是用成本换性能，内存存储成本比 ssd 及 hdd 都要高很多\n\n典型的服务器有 128G 内存，若算上持久化对内存的额外消耗，常规情况下只能提供约 80G 的使用容量，因此对 redis 的存储空间要格外的珍惜，设计上如果允许一个 key 进入内存长时间不使用，不做缓存超时，就会造成资源上的浪费。\n```\n### 7.1.4 分片数据量约束\n> 单分片不超过 12G\n```\n单分片数据量大，会影响持久化的效率，全量数据同步时间过长，同步成功率下降。并且单次持久化过程消耗的内存、时间上升。\n触发持久化时 fork 子进程耗时增加，导致主线程 io 阻塞时间增加。主线程 io 阻塞意味着 redis 处于不可用状态。目前超过 12G，单 fork 进程堵塞时间超过 3s 以上。\n单分片数据量大，会增加读数据时，hash 的时延。\n单分片负责的数据量越大，单进程负担的 io 压力也越大\n机器上 redis 业务是混布的，单分片数据量过大，会导致将机器内存使用率推向预警值，从而影响整个机器其他 redis 业务的使用。\n减小运维成本，单分片数据量小，跑 kv 分离，数据分析的效率会快很多。\n```\n\n\n## 7.2 运行时约束\n### 7.2.1 多 key 命令注意事项\n\n> 对于 mset、mget、del 的多 key 操作，建议每次批量操作小于 500 个 key\n```\nmset、mget、del 的多 key 操作，对于 proxy 会有额外的 cpu 消耗。\n    这三种特殊的操作，在后端做多分片时，proxy 需要将每个操作中的一批 key 按照后端分配规则，重组成 n 批 key 的组合，n 等于分片数量，然后分别将重组后的 n 个多 key 操作分片发给后端每一个分片；\n    回复消息时，也需要等待所有请求从后端回复回来，在 proxy 层进行结果 merge，再返回给上层。因此这种操作在 key 数量上升时，对 proxy 的 cpu 会造成额外的压力，\n    因此强烈建议控制批量操作的 key 数量，以及减少 mset、mget、del 等多 key 操作。\n```\n\n### 7.2.2 pipline 命令注意事项\n\n> 每个 pipeline 批次下 key 数量限制在 500 以内\n```\n一个 pipline 类型求情内容过多时，一次性打到 redis-proxy 时，会导致 proxy 申请内存数量暴涨，导致挤占同一物理机上混布的其他服务的资源，严重时会导致服务器重启。\n因此 pipline 类型请求需要严格限制单批次内的请求量。\n```\n\n### 7.2.3 不支持的命令\n\n```\n由于分片原理完全是依据 key 进行路由，因此引入分片后，有少量原生命令在 twemproxy 下无法支持：\n\n    不带 key 的命令，如 info、dbsize、keys、multi 等\n\n    阻塞连接的命令不支持，如 blpop 等\n\n    涉及多 key 联合操作命令不支持，如 zunionstor 等\n\n    script 脚本\n```\n\n## 7.3 风险点\n\n### 7.3.1 无协议保障的 io 流程\n提交指数据库承诺一条数据修改请求生效的时机\n\n常规系统中，提交之前有各种协议流程，能够保障提交后的一致性、持久性效果\n\n常规的保障协议包括：事务日志、持久化后提交、多副本（甚至全量副本）生效后再对用户端进行承诺的二阶段提交等\n\nredis 为了追求性能，并未使用上述任何协议\n\nredis 的提交仅仅是主节点内存提交，既不等待持久化，也不等待从节点，由此带来的风险包括：\n\n> * 1，内存提交后主节点立刻 down 掉，可能导致提交的数据被丢失\n> * 2，提交后立刻读从节点，可能读取到提交前的旧数据\n\n### 7.3.2 单线程带来的风险\n\nredis 使用单线程无锁设计，这样做的好处是能够最大限度的发挥单线程性能，避免争锁带来的额外开销\n\n缺陷是当一个请求操作比较重，耗时较长时，会将当前 redis 实例负责的所有请求都阻塞一小段时间，主线程阻塞甚至会影响主从探活\n\n因此线上命令需要严格控制，避免发生长时间线程阻塞的慢请求\n\n### 7.3.3 主从同步系数\n\n> 定义\n```\n同步系数 = 单分片已用空间（GB）* 写入流量(MB/s)\n```\n\n> 约束\n```\nRedis主库写入量大或者网络抖动时会出现主从同步中断的情况，此时从库会重新连接主库同步全量数据。\n主库同步全量数据给从库时需要先dump出当前内存的镜像发送给从库，同时还要承担分片的读写流量，这会导致主库负载过高。\n\n全量同步时请求处理耗时变长，触发业务超时，此时业务侧如果有重试风暴会使故障进一步恶化。\n主从同步时受内存buffer限制，如果在同步过程中写入量超过buffer时需要重新开始进行全量同步。经过实际测算，若使用量为5GB的实例，写入带宽超过10MB/s时主从同步无法追上；如果缓存数据量达到10GB时，写入带宽超过5MB/s时主从同步无法追上。\n\n因此业务侧需要保证：单分片已用空间（GB）* 写入流量(MB/s) < 50 的安全系数\n```\n\n"
  },
  {
    "path": "doc/monitor/README.md",
    "content": "# 监控篇\n    \n> * zabbix\n> * monit\n"
  },
  {
    "path": "doc/monitor/monit.md",
    "content": "## 目录\n\n<!-- vim-markdown-toc GFM -->\n* [概述](#概述)\n* [常用操作](#常用操作)\n    * [支持命令行的选项](#支持命令行的选项)\n    * [命令行参数](#命令行参数)\n* [配置文件](#配置文件)\n    * [日志](#日志)\n    * [守护进程模式](#守护进程模式)\n    * [Init 支持](#init-支持)\n    * [包含文件](#包含文件)\n* [管理工具](#管理工具)\n\n<!-- vim-markdown-toc -->\n\n### 概述\n\n**Monit** 是 Unix 系统中用于管理和监控进程、程序、文件、目录和文件系统的工具。使用 Monit 可以检测进程是否正常运行，如果异常可以自动重启服务以及报警，当然，也可以使用 Monit 检查文件和目录是否发生修改，例如时间戳、校验和以及文件大小的改变。\n\n> * 官方网址：http://mmonit.com/monit/\n> * 源代码包：http://mmonit.com/monit/dist/\n> * 二进制包：http://mmonit.com/monit/dist/binary/\n\n### 常用操作\n\n**Monit** 默认的配置文件是`~/.monitrc`，如果没有该文件，则使用`/etc/monitrc`文件。在启动 Monit 的时候，可以指定使用的配置文件：\n\n    $ monit -c /var/monit/monitrc\n\n在第一次启动 **monit** 的使用，可以使用如下命令测试配置文件（控制文件）是否正确\n\n    $ monit -t\n    $ Control file syntax OK\n\n如果配置文件没有问题的话，就可以使用`monit`命令启动 **monit** 了。\n\n    $ monit\n\n> 当启动 **monit** 的时候，可以使用命令行选项控制它的行为，命令行提供的选项优先于配置文件中的配置。\n\n#### 支持命令行的选项\n\n下列是 **monit** 支持的选项\n\n- **-c** 指定要使用的配置文件\n- **-d n** 每隔 n 秒以守护进程的方式运行 monit 一次，在配置文件中使用 [`set daemon`] 进行配置\n- **-g name** 设置用于`start`, `stop`, `restart`, `monitor`, `unmonitor`动作的组名\n- **-l logfile** 指定日志文件，[`set logfile`]\n- **-p pidfile** 在守护进程模式使用锁文件，配置文件中使用 [`set pidfile`] 指令\n- **-s statefile** 将状态信息写入到该文件，[`set statefile`]\n- **-I** 不要以后台模式运行（需要从 init 运行）\n- **-i** 打印 monit 的唯一 ID\n- **-r** 重置 monit 的唯一 ID\n- **-t** 检查配置文件语法是否正确\n- **-v** 详细模式，会输出针对信息\n- **-vv** 非常详细的模式，会打印出现错误的堆栈信息\n- **-H [filename]** 打印文件的 MD5 和 SHA1 哈希值 Print MD5 and SHA1，如果没有提供文件名，则为标准输入\n- **-V** 打印版本号\n- **-h** 打印帮助信息\n\n#### 命令行参数\n\n当 **Monit** 以守护进程运行的时候，可以使用下列的参数连接它的守护进程（默认是 TCP 的 127.0.0.1:2812）使其执行请求的操作。\n\n- `start all`\n\n    启动配置文件中列出的所有的服务并且监控它们，如果使用`-g`选项提供了组选项，则只对该组有效。\n\n- `start name`\n\n    启动指定名称的服务并对其监控，服务名为配置文件中配置的服务条目名称。\n\n- `stop all`\n\n    与`start all`相对。\n\n- `stop name`\n\n    与`start name`相对。\n\n- `restart all`\n\n    重启所有的 service\n\n- `restart name`\n\n    重启指定 service\n\n- `monitor all`\n\n    允许对配置文件中所有的服务进行监控\n\n- `monitor name`\n\n    允许对指定的 service 监控\n\n- `unmonitor all`\n\n    与`monitor all` 相对\n\n- `unmonitor name`\n\n    与`monitor name`相对\n\n- `status`\n\n    打印每个服务的状态信息\n\n- `reload`\n\n    重新初始化 Monitor 守护进程，守护进程将会重载配置文件以及日志文件\n\n- `quit`\n\n    关闭所有`monitor`进程\n\n- `validate`\n\n    检查所有配置文件中的服务，当 Monitor 以守护进程运行的时候，这是默认的动作\n\n- `procmatch regex`\n\n    对符合指定模式的进程进行简单测试，该命令接受正则表达式作为参数，并且显示出符合该模式的所有进程。\n\n### 配置文件\n\n**Monit** 的配置文件叫做`monitrc`文件。默认为`~/.monitrc`文件，如果该文件不存在，则尝试`/etc/monitrc`文件，然后是`@sysconfdir@/monitrc`，最后是`./monitrc`文件。\n\n> 这里所说的配置文件实际上就是控制文件（control file）\n\n**Monit** 使用它自己的领域语言 (DSL) 进行配置，配置文件包含一系列的服务条目和全局配置项。\n\n在语意上，配置文件包含以下三部分：\n\n- 全局 set 指令\n\n\t该指令以`set`开始，后面跟着配置项\n\n- 全局 include 指令\n\n\t该指令以`include`开头，后面是 glob 字符串，指定了要包含的配置文件位置\n\n- 一个或多个服务条目指令\n\n\t每一个服务条目包含关键字`check`，后面跟着服务类型。每一条后面都需要跟着一个唯一的服务标识名称。monit 使用这个名称来引用服务以及与用户进行交互。\n\n当前支持九种类型的`check`语句：\n\n1. `CHECK PROCESS <unique name> <PIDFILE <path> | MATCHING <regex>>`\n\n\t这里的`PIDFILE`是进程的 PID 文件的绝对路径，如果 PID 文件不存在，如果定义了进程的 start 方法的话，会调用该方法。\n\n\t`MATCHING`是可选的指定进程的方式，使用名称规则指定进程。\n\n2. `CHECK FILE <unique name> PATH <path>`\n\n\t检查文件是否存在，如果指定的文件不存在，则调用 start 方法。\n\n3. `CHECK FIFO <unique name> PATH <path>`\n4. `CHECK FILESYSTEM <unique name> PATH <path>`\n5. `CHECK DIRECTORY <unique name> PATH <path>`\n6. `CHECK HOST <unique name> ADDRESS <host address>`\n7. `CHECK SYSTEM <unique name>`\n8. `CHECK PROGRAM <unique name> PATH <executable file> [TIMEOUT <number> SECONDS]`\n9. `CHECK NETWORK <unique name> <ADDRESS <ipaddress> | INTERFACE <name>>`\n\n\n#### 日志\n\n**Monit** 将会使用日志文件记录运行状态以及错误消息，在配置文件中使用`set logfile`指令指定日志配置。\n\n如果希望使用自己的日志文件，使用下列指令：\n\n\tset logfile /var/log/monit.log\n\n如果要使用系统的 syslog 记录日志，使用下列指令：\n\n\tset logfile syslog\n\n如果不想开启日志功能，只需要注释掉该指令即可。\n\n日志文件的格式为：\n\n\t[date] priority : message\n\n例如：\n\n\t[CET Jan  5 18:49:29] info : 'mymachine' Monit started\n\n\n#### 守护进程模式\n\n    set daemon n (n 的单位是秒）\n\n指定 Monit 在后台轮询检查进程运行状态的时间。你可以使用命令行参数`-d`选项指定这个时间，当然，建议在配置文件中进行设置。\n\nMonit 应该总是以后台的守护进程模式运行，如果你不指定该选项或者是命令行的`-d`选项，则只会在运行 Monit 的时候对它监控的文件或者进程检查一次然后退出。\n\n#### Init 支持\n\n配置`set init`可以防止 monit 将自身转化为守护进程模式，它可以让前台进程运行。这需要从 init 运行 monit，另一种方式是使用 crontab 定时任务运行，当然这样的话你需要在运行前使用`monit -t`检查一下控制文件是否存在语法错误。\n\n要配置 monit 从 init 运行，可以在 monit 的配置文件中使用`set init`指令或者命令行中使用`-I`选项，以下是需要在`/etc/inittab`文件中增加的配置。\n\n    # Run Monit in standard run-levels\n    mo:2345:respawn:/usr/local/bin/monit -Ic /etc/monitrc\n\n> inittab 文件格式：`id:runlevels:action:process`\n> 该行配置是为 Monit 指定了 id 为 mo，运行级别 2-5 有效，`respawn`指明了无论进程是否已经运行，都对进程 restart\n\n在修改完 init 配置文件后，可以使用如下命令测试`/etc/inittab`文件并运行 Monit:\n\n\ttelinit q\n\n> `telinit q` 用于重载守护进程的配置，等价于`systemctl daemon-reload`\n\n对于没有`telinit`的系统，执行如下命令：\n\n\tkill -1 1\n\n如果 Monit 已经系统启动的时候运行对服务进行监控，在某些情况下，可能会出现竞争。也就是说如果一个服务启动的比较慢，Monit 会假设该服务没有运行并且可能会尝试启动该服务和报警，但是事实上该服务正在启动中或者已经在启动队列里了。\n\n#### 包含文件\n\n\tinclude globstring\n\n例如`include /etc/monit.d/*.cfg`会将`/etc/monit.d/`目录下所有的`.cfg`文件包含到配置文件中。\n\n### 管理工具\n\n[monit_manager](https://github.com/meetbill/monit_manager)\n\n------\n\n原文：[monit 官方文档 Version](https://mmonit.com/monit/documentation/)\n"
  },
  {
    "path": "doc/monitor/zabbix.md",
    "content": "# zabbix\n\n<!-- vim-markdown-toc GFM -->\n* [快速安装](#快速安装)\n* [zabbix 模板](#zabbix-模板)\n* [zabbix 管理工具](#zabbix-管理工具)\n\n<!-- vim-markdown-toc -->\n## 快速安装\n\n> [物理机上安装](https://github.com/meetbill/zabbix_install/wiki/install)\n> [docker部署](https://github.com/meetbill/zabbix_install/wiki/docker)\n\n## zabbix 模板\n\n[zabbix_templates](https://github.com/meetbill/zabbix_templates)\n\n## zabbix 管理工具\n\n[zabbix_manager](https://github.com/meetbill/zabbix_manager)\n"
  },
  {
    "path": "doc/other/README.md",
    "content": "# 其他篇\n\n> * windows下服务\n"
  },
  {
    "path": "doc/other/windows.md",
    "content": "# windows服务\n\n \n<!-- vim-markdown-toc GFM -->\n* [xp下建设VPN服务器](#xp下建设vpn服务器)\n\n<!-- vim-markdown-toc -->\n## xp下建设VPN服务器\n\n可以执行以下步骤完成VPN服务器部署\n\n* 1.打开网络连接\n* 2.点击网络任务重的\"创建一个新的连接\",\n* 3.[新建连接向导]，下一步\n    * [网络连接类型]--选择\"设置高级连接\"，点击下一步\n    * [高级连接选项]--选择\"接受传入的连接\"，点击下一步\n    * [传入的虚拟专用网(VPN)连接]--选择\"允许虚拟专用连接(A)\",点击下一步\n    * [用户权限]--点击\"添加\"，添加用户后，勾选上此用户，点击下一步\n    * [网络软件]--鼠标选中\"Internet协议后(TCP/IP)\",点击属性\n        * 选择制定TCP/IP地址，如从 192.168.199.2 到 192.168.199.10 点击确定\n    * 点击完成\n* 4 在内网中设置VPN服务器后，如果想让外网的服务器进行连接到此VPN服务器，可以端口转发下1723和1701两个端口\n"
  },
  {
    "path": "doc/store/RAID.md",
    "content": "# 磁盘及 RAID\n<!-- vim-markdown-toc GFM -->\n* [磁盘管理](#磁盘管理)\n    * [查看磁盘信息](#查看磁盘信息)\n        * [lsscsi](#lsscsi)\n        * [smartctl](#smartctl)\n            * [解释下各属性的含义](#解释下各属性的含义)\n            * [各个属性的含义](#各个属性的含义)\n            * [对于 SSD 硬盘，需要关注的几个参数](#对于-ssd-硬盘需要关注的几个参数)\n        * [MegaCli](#megacli)\n        * [LSIUtil](#lsiutil)\n        * [lsblk](#lsblk)\n    * [磁盘扩展](#磁盘扩展)\n        * [Linux 下 XFS 扩展](#linux-下-xfs-扩展)\n    * [测试硬盘](#测试硬盘)\n        * [dd 测试硬盘读写速度](#dd-测试硬盘读写速度)\n* [RAID](#raid)\n    * [RAID 分类](#raid-分类)\n        * [RAID 0(striped)](#raid-0striped)\n        * [RAID 1(mirroring)](#raid-1mirroring)\n        * [RAID 5 分布式奇偶校验的独立磁盘结构](#raid-5-分布式奇偶校验的独立磁盘结构)\n        * [RAID0+1/1+0](#raid0110)\n    * [MegaCli 管理工具](#megacli-管理工具)\n    * [RAID 日常运维](#raid-日常运维)\n        * [查看 RAID 信息](#查看-raid-信息)\n        * [更换硬盘时需要注意地方](#更换硬盘时需要注意地方)\n        * [RAID5 单块硬盘故障恢复方法](#raid5-单块硬盘故障恢复方法)\n\n<!-- vim-markdown-toc -->\n# 磁盘管理\n\n\n## 查看磁盘信息\n\n### lsscsi\n\n```\n--classic|-c      alternate output similar to 'cat /proc/scsi/scsi'\n--device|-d       show device node's major + minor numbers\n--generic|-g      show scsi generic device name\n--help|-h         this usage information\n--hosts|-H        lists scsi hosts rather than scsi devices\n--kname|-k        show kernel name instead of device node name\n--list|-L         additional information output one\n                  attribute=value per line\n--long|-l         additional information output\n--protection|-p   show data integrity (protection) information\n--sysfsroot=PATH|-y PATH    set sysfs mount point to PATH (def: /sys)\n--transport|-t    transport information for target or, if '--hosts'\n                  given, for initiator\n--verbose|-v      output path names where data is found\n--version|-V      output version string and exit\n```\n\n查看磁盘运行状态\n\n```\nlsscsi -l\n[0:0:0:0]    disk    ATA      ST2000LM003 HN-M 0007  -\n  state=running queue_depth=64 scsi_level=6 type=0 device_blocked=0 timeout=0\n[0:0:1:0]    disk    ATA      ST2000LM003 HN-M 0007  -\n  state=running queue_depth=64 scsi_level=6 type=0 device_blocked=0 timeout=0\n[0:0:2:0]    disk    ATA      ST2000LM003 HN-M 0007  /dev/sda\n  state=running queue_depth=64 scsi_level=6 type=0 device_blocked=0 timeout=30\n[0:0:3:0]    disk    ATA      ST2000LM003 HN-M 0007  /dev/sdb\n  state=running queue_depth=64 scsi_level=6 type=0 device_blocked=0 timeout=30\n[0:0:4:0]    disk    ATA      ST2000LM003 HN-M 0007  /dev/sdc\n  state=running queue_depth=64 scsi_level=6 type=0 device_blocked=0 timeout=30\n[0:0:5:0]    disk    ATA      ST2000LM003 HN-M 0007  /dev/sdd\n  state=running queue_depth=64 scsi_level=6 type=0 device_blocked=0 timeout=30\n[0:0:6:0]    disk    ATA      ST2000LM003 HN-M 0007  /dev/sde\n  state=running queue_depth=64 scsi_level=6 type=0 device_blocked=0 timeout=30\n[0:0:7:0]    disk    ATA      ST2000LM003 HN-M 0006  /dev/sdf\n  state=running queue_depth=64 scsi_level=6 type=0 device_blocked=0 timeout=30\n[0:1:0:0]    disk    LSILOGIC Logical Volume   3000  /dev/sdg\n  state=running queue_depth=64 scsi_level=3 type=0 device_blocked=0 timeout=30\n```\n\n### smartctl\n\nsmartctl 可以查看磁盘的 SN，WWN 等信息。还有是否有磁盘坏道的信息\n\n```\n$ smartctl -a -f brief /dev/sdb\n\n# 如果磁盘位于 RAID 下面，比如 megaraid，可以使用如下命令\n# smartctl -a -f brief -d megaraid,1 /dev/sdb\n\nsmartctl 5.43 2012-06-30 r3573 [x86_64-linux-3.12.21-1.el6.x86_64] (local build)\nCopyright (C) 2002-12 by Bruce Allen, http://smartmontools.sourceforge.net\n\n=== START OF INFORMATION SECTION ===\nDevice Model:     ST2000LM003 HN-M201RAD\nSerial Number:    S34RJ9EG109476\nLU WWN Device Id: 5 0004cf 20eeefc42\nFirmware Version: 2BC10007\nUser Capacity:    2,000,398,934,016 bytes [2.00 TB]\nSector Sizes:     512 bytes logical, 4096 bytes physical\nDevice is:        Not in smartctl database [for details use: -P showall]\nATA Version is:   8\nATA Standard is:  ATA-8-ACS revision 6\nLocal Time is:    Mon Jun 22 07:48:24 2015 UTC\nSMART support is: Available - device has SMART capability.\nSMART support is: Enabled\n\nVendor Specific SMART Attributes with Thresholds:\nID# ATTRIBUTE_NAME          FLAGS    VALUE WORST THRESH FAIL RAW_VALUE\n  1 Raw_Read_Error_Rate     POSR-K   91   91   051    -    11787\n  2 Throughput_Performance  -OS--K   252   252   000    -    0\n  3 Spin_Up_Time            PO---K   086   086   025    -    4319\n  4 Start_Stop_Count        -O--CK   100   100   000    -    16\n  5 Reallocated_Sector_Ct   PO--CK   252   252   010    -    0\n  7 Seek_Error_Rate         -OSR-K   252   252   051    -    0\n  8 Seek_Time_Performance   --S--K   252   252   015    -    0\n  9 Power_On_Hours          -O--CK   100   100   000    -    2277\n 10 Spin_Retry_Count        -O--CK   252   252   051    -    0\n 12 Power_Cycle_Count       -O--CK   100   100   000    -    34\n191 G-Sense_Error_Rate      -O---K   252   252   000    -    0\n192 Power-Off_Retract_Count -O---K   252   252   000    -    0\n194 Temperature_Celsius     -O----   064   064   000    -    22 (Min/Max 18/28)\n195 Hardware_ECC_Recovered  -O-RCK   100   100   000    -    0\n196 Reallocated_Event_Count -O--CK   252   252   000    -    0\n197 Current_Pending_Sector  -O--CK   100   100   000    -    11\n198 Offline_Uncorrectable   ----CK   252   252   000    -    0\n199 UDMA_CRC_Error_Count    -OS-CK   200   200   000    -    0\n200 Multi_Zone_Error_Rate   -O-R-K   100   100   000    -    3\n223 Load_Retry_Count        -O--CK   252   252   000    -    0\n225 Load_Cycle_Count        -O--CK   090   090   000    -    108289\n                            ||||||_ K auto-keep\n                            |||||__ C event count\n                            ||||___ R error rate\n                            |||____ S speed/performance\n                            ||_____ O updated online\n                            |______ P prefailure warning\n\nSMART Error Log Version: 1\nNo Errors Logged\n```\n\n#### 解释下各属性的含义\n\nID# ATTRIBUTE_NAME FLAG VALUE WORST THRESH TYPE UPDATED WHEN_FAILED RAW_VALUE\n\n> - **ID**    属性 ID，1~255\n> - **ATTRIBUTE_NAME**    属性名\n> - **FLAG**  表示这个属性携带的标记。使用 -f brief 可以打印\n> - **VALUE**     Normalized value, 取值范围 1 到 254. 越低表示越差。越高表示越好。(with 1 representing the worst case and 254 representing the best)。注意 wiki 上说的是 1 到 253. 这个值是硬盘厂商根据 RAW_VALUE 转换来的，smartmontools 工具不负责转换工作。\n> - **WORST**     表示 SMART 开启以来的，所有 Normalized values 的最低值。(which represents the lowest recorded normalized value.)\n> - **THRESH**    阈值，当 Normalized value 小于等于 THRESH 值时，表示这项指标已经 failed 了。注意这里提到，如果这个属性是 pre-failure 的，那么这项如果出现 Normalized value<=THRESH, 那么磁盘将马上 failed 掉\n> - **TYPE**      这里存在两种 TYPE 类型，Pre-failed 和 Old_age.\n    1. Pre-failed 类型的 Normalized value 可以用来预先知道磁盘是否要坏了。例如 Normalized value 接近 THRESH 时，就赶紧换硬盘吧。\n    2. Old_age 类型的 Normalized value 是指正常的使用损耗值，当 Normalized value 接近 THRESH 时，也需要注意，但是比 Pre-failed 要好一点。\n> - **UPDATED**   这个字段表示这个属性的值在什么情况下会被更新。一种是通常的操作和离线测试都更新 (Always), 另一种是只在离线测试的情况下更新 (Offline).\n> - **WHEN_FAILED**   这字段表示当前这个属性的状态 : failing_now(normalized_value <= THRESH), 或者 in_the_past(WORST <= THRESH), 或者 - , 正常 (normalized_value 以及 wrost >= THRESH).\n> - **RAW_VALUE** 表示这个属性的未转换前的 RAW 值，可能是计数，也可能是温度，也可能是其他的。\n注意 RAW_VALUE 转换成 Normalized value 是由厂商的 firmware 提供的，smartmontools 不提供转换。\n\n注意有个 FLAG 是 KEEP, 如果不带这个 FLAG 的属性，值将不会 KEEP 在磁盘中，可能出现 WORST 值被刷新的情况，例如这里的 ID=1 的值，已经 89 了，重新执行又变成 91 了，但是 WORST 的值并不是历史以来的最低 89。遇到这种情况的解决办法是找个地方存储这些值的历史值。\n\n因此监控磁盘的重点在哪里呢？\n严重情况从上到下 :\n\n> * 1. 最严重的情况 WHEN_FAILED = FAILING_NOW 并且 TYPE=Pre-failed, 表示现在这个属性已经出问题了。并且硬盘也已经 failed 了。\n> * 2. 次严重的情况 WHEN_FAILED = in_the_past 并且 TYPE=Pre-failed, 表示这个属性曾经出问题了。但是现在是正常的。\n> * 3. WHEN_FAILED = FAILING_NOW 并且 TYPE=Old_age, 表示现在这个属性已经出问题了。但是硬盘可能还没有 failed.\n> * 4. WHEN_FAILED = in_the_past 并且 TYPE=Old_age, 表示现在这个属性曾经出问题了。但是现在是正常的。\n\n为了避免这 4 种情况的发生。\n\n> * 1. 对于 UPDATE=Offline 的属性，应该让 smartd 定期进行测试 (smartd 还可以发邮件）. 或者 crontab 进行测试。\n> * 2. 应该时刻关注磁盘的 Normalized value 以及 WORST 的值是否接近 THRESH 的值了。当有值要接近 THRESH 了，提前更换硬盘。\n> * 3. 温度，有些磁盘对温度比较敏感，例如 PCI-E SSD 硬盘。如果温度过高可能就挂了。这里读取 RAW_VALUE 就比较可靠了。\n\n#### 各个属性的含义\n\n> * **read error rate** 错误读取率：记录读取数据错误次数（累计），非 0 值表示硬盘已经或者可能即将发生坏道\n> * **throughput performance** 磁盘吞吐量：平均吞吐性能（一般在进行了人工 Offline S.M.A.R.T. 测试以后才会有值。）；\n> * **spinup time** 主轴电机到达要求转速时间（毫秒 / 秒）；\n> * **start/stop count** 电机启动 / 停止次数（可以当作开机 / 关机次数，或者休眠后恢复，均增加一次计数。全新的硬盘应该小于 10）；\n> * **reallocated sectors count** 重分配扇区计数：硬盘生产过程中，有一部分扇区是保留的。当一些普通扇区读 / 写 / 验证错误，则重新映射到保留扇区，挂起该异常扇区，并增加计数。随着计数增加，io 性能骤降。如果数值不为 0，就需要密切关注硬盘健康状况；如果持续攀升，则硬盘已经损坏；如果重分配扇区数超过保留扇区数，将不可修复；\n> * **seek error rate** 寻道错误率：磁头定位错误一次，则技术增加一次。如果持续攀升，则可能是机械部分即将发生故障；\n> * **seek timer performance** 寻道时间：寻道所需要的时间，越短则读取数据越快，但是如果时间增加，则可能机械部分即将发生故障；\n> * **power-on time** 累计通电时间：指硬盘通电时间累计值。（单位：天 / 时 / 分 / 秒。休眠 / 挂起不计入？新购入的硬盘应小于 100hrs）；\n> * **spinup retry count** 电机启动失败计数：电机启动到指定转速失败的累计数值。如果失败，则可能是动力系统产生故障；\n> * **power cycle count** 电源开关计数：每次加电增加一次计数，新硬盘应小于 10 次；\n> * **g-sensor error rate** 坠落计数：异常加速度（例如坠落，抛掷）计数——磁头会立即回到 landing zone，并增加一次计数；\n> * **power-off retract count** 异常断电次数：磁头在断电前没有完全回到 landing zone 的次数，每次异常断电则增加一次计数；\n> * **load/unload cycle count** 磁头归位次数：指工作时，磁头每次回归 landing zone 的次数。（ps：流言说某个 linux 系统——不点名，在使用电池时候，会不断强制磁头归为，而磁头归位次数最大值约为 600k 次，所以认为 linux 会损坏硬盘，实际上不是这样的）；\n> * **temperature** 温度：没嘛好说的，硬盘温度而已，理论上比工作环境高不了几度。（sudo hddtemp /dev/sda）\n> * **reallocetion event count** 重映射扇区操作次数：上边的重映射扇区还记得吧？这个就是操作次数，成功的，失败的都计数。成功好说，也许硬盘有救，失败了，也许硬盘就要报废了；\n> * **current pending sector count** 待映射扇区数：出现异常的扇区数量，待被映射的扇区数量。 如果该异常扇区之后成功读写，则计数会减小，扇区也不会重新映射。读错误不会重新映射，只有写错误才会重新映射；\n> * **uncorrectable sector count** 不可修复扇区数：所有读 / 写错误计数，非 0 就证明有坏道，硬盘报废；\n\n#### 对于 SSD 硬盘，需要关注的几个参数\n\nSSD 磨损数据分析：\nSLC 的 SSD 可以擦除 10 万次，MLC 的 SSD 可以擦除 1 万次\n\n**Media Wearout Indicator**\n\n定义：表示 SSD 上 NAND 的擦写次数的程度，初始值为 100，随着擦写次数的增加，开始线性递减，递减速度按照擦写次数从 0 到最大的比例。一旦这个值降低到 1，就不再降了，同时表示 SSD 上面已经有 NAND 的擦写次数到达了最大次数。这个时候建议需要备份数据，以及更换 SSD。\n\n解释：直接反映了 SSD 的磨损程度，100 为初始值，0 为需要更换，有点类似游戏中的血点。\n\n结果：磨损 1 点\n\n**Re-allocated Sector Count**\n\n定义：出厂后产生的坏块个数，如果有坏块，从 1 开始增加，每 4 个坏块增加 1\n\n解释：坏块的数量间接反映了 SSD 盘的健康状态。\n\n结果：基本上都为 0\n\n**Host Writes Count**\n\n定义：主机系统对 SSD 的累计写入量，每写入 65536 个扇区 raw value 增加 1\n\n解释：SSD 的累计写入量，写入量越大，SSD 磨损情况越严重。每个扇区大小为 512bytes，65536 个扇区为 32MB\n\n结果：单块盘 40T\n\n**Timed Workload Media Wear**\n\n定义：表示在一定时间内，盘片的磨损比例，比 Media Wearout Indicator 更精确。\n\n解释：可以在测试前清零，然后测试某段时间内的磨损数据，这个值的 1 点相当于 Media Wearout Indicator 的 1/100，测试时间必须大于 60 分钟。另外两个相关的参数：Timed Workload Timer 表示单次测试时间，Timed Workload Host Read/Write Ratio 表示读写比例。\n\n**Available_Reservd_Space**\n\nSSD 上剩余的保留空间，初始值为 100，表示 100%，阀值为 10，递减到 10 表示保留空间已经不能再减少\n\n### MegaCli\n\n查看 media error, other error\n\n### LSIUtil\n\n查看磁盘的物理位置，error 检测\n\n### lsblk\n\n\n## 磁盘扩展\n\n### Linux 下 XFS 扩展\n\nXFS 是一个开源的（GPL）日志文件系统，最初由硅谷图形（SGI）开发，现在大多数的 Linux 发行版都支持。事实上，XFS 已被最新的 CentOS/RHEL 7 采用，成为其默认的文件系统。在其众多的特性中，包含了“在线调整大小”这一特性，使得现存的 XFS 文件系统在已经挂载的情况下可以进行扩展。\n\n扩展前\n```\n[root@meetbill ~]# xfs_info /mnt/\nmeta-data=/dev/sdb    isize=512    agcount=4, agsize=196608 blks\n         =            sectsz=512   attr=2, projid32bit=1\n         =            crc=1        finobt=0 spinodes=0\ndata     =            bsize=4096   blocks=786432, imaxpct=25\n         =            sunit=0      swidth=0 blks\nnaming   =version 2   bsize=4096   ascii-ci=0 ftype=1\nlog      =internal    bsize=4096   blocks=2560, version=2\n         =            sectsz=512   sunit=0 blks, lazy-count=1\nrealtime =none        extsz=4096   blocks=0, rtextents=0\n\n\n[root@meetbill ~]# df -h\nFilesystem           Size  Used Avail Use% Mounted on\n/dev/mapper/cl-root   17G  8.4G  8.7G  49% /\ndevtmpfs             902M     0  902M   0% /dev\ntmpfs                912M     0  912M   0% /dev/shm\ntmpfs                912M  8.7M  904M   1% /run\ntmpfs                912M     0  912M   0% /sys/fs/cgroup\n/dev/sda1           1014M  141M  874M  14% /boot\ntmpfs                183M     0  183M   0% /run/user/0\n/dev/sdb             3.0G   33M  3.0G   2% /mnt\n```\n将磁盘 (/dev/sdb) 进行扩展后，扩展磁盘的方式比如虚拟机对虚拟磁盘进行扩展或 isics 对存储进行扩展，磁盘扩展后，我们还需要对文件系统进行扩展 (/mnt)\n\n我们用到的是 `xfs_growfs` 命令\n```\n[root@meetbill ~]# xfs_growfs /mnt/\nmeta-data=/dev/sdb    isize=512    agcount=4, agsize=196608 blks\n         =            sectsz=512   attr=2, projid32bit=1\n         =            crc=1        finobt=0 spinodes=0\ndata     =            bsize=4096   blocks=786432, imaxpct=25\n         =            sunit=0      swidth=0 blks\nnaming   =version 2   bsize=4096   ascii-ci=0 ftype=1\nlog      =internal    bsize=4096   blocks=2560, version=2\n         =            sectsz=512   sunit=0 blks, lazy-count=1\nrealtime =none        extsz=4096   blocks=0, rtextents=0\ndata blocks changed from 786432 to 1310720\n```\n大功告成，如果`xfs_growfs` 不加任何参数，则会对指定挂载目录自动扩展 XFS 文件系统到最大的可用大小。`-D`参数可以设置为指定大小\n\n## 测试硬盘\n\n### dd 测试硬盘读写速度\n\n如何正确使用 dd 工具测试磁盘的 I/O 速度\n\n一般情况下，我们都是使用 dd 命令创建一个大文件来测试磁盘的读写速度。我们分析一下 dd 命令是如何工作的。\n```\ndd if=/dev/zero of=/xiaohan/test.iso bs=1024M count=1\n```\n测试显示的速度是 dd 命令将数据写入到内存缓冲区中的速度\n```\ndd if=/dev/zero of=/xiaohan/test.iso bs=1024M count=1;sync\n```\n测试显示的跟上一种情况是一样的，两个命令是先后执行的，当 sync 开始执行的时候，dd 命令已经将速度信息打印到了屏幕上，仍然无法显示从内存写硬盘时的真正速度。\n```\ndd if=/dev/zero of=/xiaohan/test.iso bs=1024M count=1 conv=fdatasync\n```\n这种情况加入这个参数后，dd 命令执行到最后会真正执行一次“同步 (sync)”操作，所以这时候你得到的是读取这 128M 数据到内存并写入到磁盘上所需的时间，这样算出来的时间才是比较符合实际的。\n```\ndd if=/dev/zero of=/xiaohan/test.iso bs=1024M count=1 oflag=dsync\n```\n这种情况下，dd 在执行时每次都会进行同步写入操作。也就是说，这条命令每次读取 1M 后就要先把这 1M 写入磁盘，然后再读取下面这 1M，一共重复 128 次。这可能是最慢的一种方式，基本上没有用到写缓存 (write cache)。\n\n总结：\n```\n建议使用测试写速度的方式为：\ndd if=/dev/zero of=/xiaohan/test.iso bs=1024M count=1 conv=fdatasync\n\n建议使用测试读速度的方式为：\ndd if=/xiaohan/test.iso of=/dev/zero bs=1024M count=1 iflag=direct\n*注：要正确测试磁盘读写能力，建议测试文件的大小要远远大于内存的容量！！！\n```\n\n# RAID\nRAID, Redundant Arrays of Inexpensive Disks, 容错式廉价磁盘阵列.RAID 的基本原理是把多个便宜的小磁盘组合到一起，成为一个磁盘组，使性能达到或超过一个容量巨大、价格昂贵的磁盘。目前 RAID 技术大致分为两种：基于硬件的 RAID 技术和基于软件的 RAID 技术。其中在 Linux 下通过自带的软件就能实现 RAID 功能，这样便可省去购买昂贵的硬件 RAID 控制器和附件就能极大地增强磁盘的 IO 性能和可靠性。由于是用软件去实现的 RAID 功能，所以它配置灵活、管理方便。同时使用软件 RAID，还可以实现将几个物理磁盘合并成一个更大的虚拟设备，从而达到性能改进和数据冗余的目的。当然基于硬件的 RAID 解决方案比基于软件 RAID 技术在使用性能和服务性能上稍胜一筹，具体表现在检测和修复多位错误的能力、错误磁盘自动检测和阵列重建等方面。\n\nRAID 动画演示\n\n[RAID 动画演示下载](https://raw.githubusercontent.com/meetbill/op_practice_code/master/store/RAID/raid.exe)\n\n## RAID 分类\n\n常用的 RAID RAID0/RAID1/RAID5/RAID10\n\n| 级别   |     优点      |     缺点|\n|------------|----------|---------|\n|RAID 0 | 存取速度最快 | 没有容错 |\n|RAID 1 | 完全容错 | 成本高 |\n|RAID 5 | 多任务可容错 | 写入时有 overhead |\n|RAID 0+1/RAID 10 | 速度快，完全容错，成本高 |\n\n\n### RAID 0(striped)\n\n![raid-0 图解](../../images/raid/raid-0.png)\n\nStriped 模式，把连续的数据分散懂啊多个磁盘上存取。速度快，但是没有冗余。\n\n### RAID 1(mirroring)\n\n![raid-1 图解](../../images/raid/raid-1.png)\n\nRAID 1 可以用于两个或 2xN 个磁盘，并使用 0 块或更多的备用磁盘，每次写数据时会同时写入镜像盘。这种阵列可靠性很高，但其有效容量减小到总容量的一半，同时这些磁盘的大小应该相等，否则总容量只具有最小磁盘的大小。\n\n### RAID 5 分布式奇偶校验的独立磁盘结构\n\n![raid-5 图解](../../images/raid/raid-5.png)\n\n### RAID0+1/1+0\n\nraid0 over raid1\n\n## MegaCli 管理工具\n\n[Megacli_tui](https://github.com/meetbill/megacli_tui)\n\n## RAID 日常运维\n### 查看 RAID 信息\n\n    dmesg | grep -i raid\n\n### 更换硬盘时需要注意地方\n\n    (1) 更换损坏硬盘前，必须查看阵列的当前状态，保证除损坏的硬盘外，其他硬盘处于正常的 ONLINE 状态\n    (2) 更换硬盘必须及时\n    (3) 更换的新硬盘必须是完好的\n    (4) 在阵列数据重建完成之前，不能插拔任何硬盘\n\n### RAID5 单块硬盘故障恢复方法\n\n    单个硬盘失效，我们通过热插拔拔下来再插上去。如热插拔没用在进入 RAID 配置界面，将该硬盘进行 ForceOnLine 操作。还可以通过更换其它硬盘插槽，切记不要打乱磁盘顺序。如果上面操作不能解决问题，尝试将该硬盘格式化后插入，然后使用 ReBuild 操作。在这过程中可能会遇到不能格式化现象，这是因为硬盘物理错误严重，应该更换硬盘后重建数据来解决问题。\n"
  },
  {
    "path": "doc/store/README.md",
    "content": "# 存储篇\n\n> * 磁盘及 RAID\n> * DAS/SAN/NAS\n> * gfs\n> * Glusterfs\n> * ceph\n> * moosefs\n"
  },
  {
    "path": "doc/store/ceph.md",
    "content": "## Ceph\n\n<!-- vim-markdown-toc GFM -->\n* [Ceph](#ceph)\n    * [Ceph 对象存储](#ceph-对象存储)\n    * [Ceph 块设备](#ceph-块设备)\n    * [Ceph 文件系统](#ceph-文件系统)\n    * [Ceph 架构](#ceph-架构)\n    * [Ceph 组件](#ceph-组件)\n    * [硬件配置](#硬件配置)\n        * [CPU](#cpu)\n        * [内存](#内存)\n        * [数据存储](#数据存储)\n        * [Ceph 网络](#ceph-网络)\n        * [最低硬件推荐（小型生产集群及开发集群）](#最低硬件推荐小型生产集群及开发集群)\n            * [Ceph-osd](#ceph-osd)\n            * [Ceph-mon](#ceph-mon)\n            * [Ceph-mds](#ceph-mds)\n        * [生产集群](#生产集群)\n            * [Dell PowerEdge R510](#dell-poweredge-r510)\n            * [Dell PowerEdge R515](#dell-poweredge-r515)\n    * [推荐操作系统](#推荐操作系统)\n        * [Ceph 依赖](#ceph-依赖)\n            * [Linux 内核](#linux-内核)\n        * [系统平台 (FIREFLY 0.80)](#系统平台-firefly-080)\n    * [数据流向](#数据流向)\n    * [数据复制](#数据复制)\n    * [数据重分布](#数据重分布)\n        * [影响因素](#影响因素)\n    * [Ceph 应用](#ceph-应用)\n    * [ceph 安装及应用](#ceph-安装及应用)\n\n<!-- vim-markdown-toc -->\n\n# Ceph\nCeph 提供了对象、块和文件存储功能。\n## Ceph 对象存储\n>* REST 风格的接口\n>* 与 S3 和 Swift 兼容的 API\n>* S3 风格的子域\n>* 统一的 S3/Swift 命名空间\n>* 用户管理\n>* 利用率跟踪\n>* 条带化对象\n>* 云解决方案集成\n>* 多站点部署\n>* 灾难恢复\n\n## Ceph 块设备\n>* 瘦接口支持\n>* 映像尺寸最大 16EB\n>* 条带化可定制\n>* 内存缓存\n>* 快照\n>* 写时复制克隆\n>* 支持内核级驱动\n>* 支持 KVM 和 libvirt\n>* 可作为云解决方案的后端\n>* 增量备份\n\n## Ceph 文件系统\n>* 与 POSIX 兼容的语义\n>* 元数据独立于数据\n>* 动态重均衡\n>* 子目录快照\n>* 可配置的条带化\n>* 有内核驱动支持\n>* 有用户空间驱动支持\n>* 可作为 NFS/CIFS 部署\n>* 可用于 Hadoop（取代 HDFS)\n\n## Ceph 架构\n**Rados**\n核心组件，提供高可靠、高可扩展、高性能的分布式对象存储架构，利用本地文件系统存储对象。\n\n**Client**\nRBD\nRadosgw\nLibrados\nCephfs\n\n![](../../images/store/ceph.png)\n\n## Ceph 组件\n最简的 Ceph 存储集群至少要一个监视器和两个 OSD 守护进程，只有运营 Ceph 文件系统时元数据服务器才是必需的。\n**OSD（对象存储守护进程）**\n存储数据，处理数据复制、恢复、回填、重均衡，并向监视器提供邻居的心跳信息。\n![](../../images/store/ceph-topo.jpg)\n\n**Monitor**\n维护着各种集群状态图，包括监视器图、OSD 图、归置组 (PG) 图和 CRUSH 图。\n**MDS（元数据服务器）**\n存储元数据。缓存和同步元数据，管理名字空间。\n## 硬件配置\n### CPU\n元数据服务器对 CPU 敏感，CPU 性能尽可能高。\nOSD 需要一定的处理能力，CPU 性能要求较高。\n### 内存\n元数据服务器和监视器对内存要求较高。\nOSD 对内存要求较低。\n恢复期间，占用内存较大。\n### 数据存储\n建议用容量大于 1TB 的硬盘。\n每个 OSD 守护进程占用一个驱动器。\n分别在单独的硬盘运行操作系统、OSD 数据和 OSD 日志。\nSSD 顺序读写性能很重要。\nSSD 可用于存储 OSD 的日志。\n### Ceph 网络\n建议每台服务器至少两个千兆网卡，分别用于公网（前端）和集群网络（后端）。集群网络用于处理有数据复制产生的额外负载，而且可用防止拒绝服务攻击。考虑部署万兆网络。\n\n![](../../images/store/ceph_network.png)\n\n### 最低硬件推荐（小型生产集群及开发集群）\n#### Ceph-osd\n**CPU:**\n>* 1x 64-bit AMD-64\n>* 1x 32-bit ARM dual-core or better\n>* 1x i386 dual-core\n\n**RAM:**\n~1GB for 1TB of storage per daemon\n**Volume Storage:**\n1x storage drive per daemon\n**Journal:**\n1x SSD partition per daemon (optional)\n**Network:**\n2x 1GB Ethernet NICs\n\n#### Ceph-mon\n**CPU:**\n>* 1x 64-bit AMD-64/i386\n>* 1x 32-bit ARM dual-core or better\n>* 1x i386 dual-core\n\n**RAM:**\n1GB per daemon\n**Disk Space:**\n10GB per daemon\n**Network:**\n2x 1GB Ethernet NICs\n\n#### Ceph-mds\n**CPU:**\n>* 1x 64-bit AMD-64 quad-core\n>* 1x 32-bit ARM quad-core\n>* 1x i386 quad-core\n\n**RAM:**\n1GB minimum per daemon\n**Disk Space:**\n1MB per daemon\n**Network:**\n2x 1GB Ethernet NICs\n### 生产集群\n#### Dell PowerEdge R510\n**CPU：**\n2x 64-bit quad-core Xeon CPUs\n**RAM:**\n16GB\n**Volume Storage:**\n8x 2TB drives.1 OS,7 Storage\n**Client Network:**\n2x 1GB Ethernet NICs\n**OSD Network:**\n2x 1GB Ethernet NICs\n**Mgmt. Network:**\n2x 1GB Ethernet NICs\n#### Dell PowerEdge R515\n**CPU：**\n1x hex-core Opteron CPU\n**RAM:**\n16GB\n**Volume Storage:**\n12x 3TB drives.Storage\n**OS Storage:**\n1x 500GB drive. Operating System.\n**Client Network:**\n2x 1GB Ethernet NICs\n**OSD Network:**\n2x 1GB Ethernet NICs\n**Mgmt. Network:**\n2x 1GB Ethernet NICs\n## 推荐操作系统\n### Ceph 依赖\n#### Linux 内核\n**Ceph 内核态客户端：**\n>* v3.16.3 or later (rbd deadlock regression in v3.16.[0-2])\n>* NOT v3.15.* (rbd deadlock regression)\n>* V3.14.*\n>* v3.6.6 or later in the v3.6 stable series\n>* v3.4.20 or later in the v3.4 stable series\n\n**btrfs:**\nv3.14 或更新\n### 系统平台 (FIREFLY 0.80)\n| Distro | Release | Code Name | Kernel | Notes | Testing |\n|--------|---------|-----------|--------|-------|---------|\n| Ubuntu | 12.04 | Precise Pangolin | linux-3.2.0 | 1,2 | B,I,C |\n| Ubuntu | 14.04 | Trusty Tahr | linux-3.13.0 |  | B,I,C |\n| Debian | 6.0 | Squeeze | linux-2.6.32 | 1,2,3 | B |\n| Debian | 7.0 | Wheezy | linux-3.2.0 | 1,2 | B |\n| CentOS | 6 | N/A | linux-2.6.32 | 1,2 | B,I |\n| RHEL | 6 |  | linux-2.6.32 | 1,2 | B,I,C |\n| RHEL | 7 |  | linux-3.10.0 |  | B,I,C |\n| Fedora | 19.0 | Schrodinger's Cat | linux-3.10.0 |  | B |\n| Fedora | 20.0 | Heisenbug | linux-3.14.0 |  | B |\n\nNote:\n\n>* 1: 默认内核 btrfs 版本较老，不推荐用于 ceph-osd 存储节点；要升级到推荐的内核，或者改用 xfs,ext4\n>* 2: 默认内核带的 Ceph 客户端较老，不推荐做内核空间客户端（内核 RBD 或 Ceph 文件系统），请升级到推荐内核。\n>* 3: 默认内核或已安装的 glibc 版本若不支持 syncfs(2) 系统调用，同一台机器上使用 xfs 或 ext4 的 ceph-osd 守护进程性能不会如愿。\n\n测试版：\n\n>* B: 我们持续地在这个平台上编译所有分支、做基本单元测试；也为这个平台构建可发布软件包。\n>* I: 我们在这个平台上做基本的安装和功能测试。\n>* C: 我们在这个平台上持续地做全面的功能、退化、压力测试，包括开发分支、预发布版本、正式发布版本。\n\n## 数据流向\nData-->obj-->PG-->Pool-->OSD\n\n![](../../images/store/Distributed-Object-Store.png)\n\n## 数据复制\n\n![](../../images/store/ceph_write.png)\n\n## 数据重分布\n### 影响因素\nOSD\nOSD weight\nOSD crush weight\n## Ceph 应用\n**RDB**\n为 Glance Cinder 提供镜像存储\n提供 Qemu/KVM 驱动支持\n支持 openstack 的虚拟机迁移\n\n**RGW**\n替换 swift\n网盘\n\n**Cephfs**\n提供共享的文件系统存储\n支持 openstack 的虚拟机迁移\n\n## ceph 安装及应用\n\n* [ceph 安装及应用](https://github.com/meetbill/ceph_practice)\n\nceph 安装及应用部分目前更新在 `ceph 实践` 的 wiki 中\n"
  },
  {
    "path": "doc/store/gfs.md",
    "content": "# GFS\n\n<!-- vim-markdown-toc GFM -->\n    * [分布式文件系统的要求](#分布式文件系统的要求)\n    * [GFS 基于的假设](#gfs-基于的假设)\n* [架构](#架构)\n    * [Chunk 大小](#chunk-大小)\n    * [Metadata](#metadata)\n    * [Operation Log](#operation-log)\n    * [容错机制](#容错机制)\n        * [Master 容错](#master-容错)\n    * [一致性模型](#一致性模型)\n    * [Lease 机制](#lease-机制)\n    * [版本号](#版本号)\n    * [负载均衡](#负载均衡)\n* [基本操作](#基本操作)\n    * [Read](#read)\n    * [Overwrite](#overwrite)\n    * [Record Append](#record-append)\n    * [Snapshot](#snapshot)\n    * [Delete](#delete)\n\n<!-- vim-markdown-toc -->\n\nGFS 作为一个分布式的文件系统，\n除了要满足一般的文件系统的需求之外，\n还根据一些特殊的应用场景（原文反复提到的`application workloads and technological environment`），\n来完成整个系统的设计。\n\n### 分布式文件系统的要求\n\n一般的分布式文件系统需要满足以下四个要求：\n\n* Performance：高性能，较低的响应时间，较高的吞吐量\n* Scalability: 易于扩展，可以简单地通过增加机器来增大容量\n* Reliability: 可靠性，系统尽量不出错误\n* Availability: 可用性，系统尽量保持可用\n\n（注：关于 reliability 和 availability 的区别，\n请参考 [这篇](http://unfolding-mirror.blogspot.com/2009/06/reliability-vs-availability.html)）\n\n### GFS 基于的假设\n\n基于对实际应用场景的研究，GFS 对它的使用场景做出了如下假设：\n\n1. GFS 运行在成千上万台便宜的机器上，这意味着节点的故障会经常发生。\n必须有一定的容错的机制来应对这些故障。\n\n2. 系统要存储的文件通常都比较大，每个文件大约 100MB 或者更大，\nGB 级别的文件也很常见。必须能够有效地处理这样的大文件，\n基于这样的大文件进行系统优化。\n\n3. workloads 的读操作主要有两种：\n\n    * 大规模的流式读取，通常一次读取数百 KB 的数据，\n    更常见的是一次读取 1MB 甚至更多的数据。\n    来自同一个 client 的连续操作通常是读取同一个文件中连续的一个区域。\n\n    * 小规模的随机读取，通常是在文件某个随机的位置读取\n    几个 KB 数据。\n    对于性能敏感的应用通常把一批随机读任务进行排序然后按照顺序批量读取，\n    这样能够避免在通过一个文件来回移动位置。（后面我们将看到，\n    这样能够减少获取 metadata 的次数，也就减少了和 master 的交互）\n\n\n4. workloads 的写操作主要由大规模的，顺序的 append 操作构成。\n一个文件一旦写好之后，就很少进行改动。因此随机的写操作是很少的，\n所以 GFS 主要针对于 append 进行优化。\n\n5. 系统必须有合理的机制来处理多个 client 并发写同一个文件的情况。\n文件经常被用于生产者 - 消费者队列，需要高效地处理多个 client 的竞争。\n正是基于这种特殊的应用场景，GFS 实现了一个无锁并发 append。\n\n6. 利用高带宽比低延迟更加重要。基于这个假设，\n可以把读写的任务分布到各个节点，\n尽量保证每个节点的负载均衡，\n尽管这样会造成一些请求的延迟。\n\n<!--more-->\n\n## 架构\n\n下面我们来具体看一下 GFS 的整个架构。\n\n可以看到 GFS 由三个不同的部分组成，分别是`master`，`client`, `chunkserver`。\n\n> * `master`负责管理整个系统（包括管理 metadata，垃圾回收等），一个系统只有一个`master`。\n> * `chunkserver`负责保存数据，一个系统有多个`chunkserver`。\n> * `client`负责接受应用程序的请求，通过请求`master`和`chunkserver`来完成读写等操作。\n\n由于系统只有一个`master`，`client`对`master`请求只涉及 metadata，数据的交互直接与`chunkserver`进行，这样减小了`master`的压力。\n\n一个文件由多个 chunk 组成，一个 chunk 会在多个`chunkserver`上存在多个 replica。\n对于新建文件，目录等操作，只是更改了 metadata，\n只需要和`master`交互就可以了。注意，与 linux 的文件系统不同，\n目录不再以一个 inode 的形式保存，也就是它不会作为 data 被保存在`chunkserver`。\n如果要读写文件的文件的内容，就需要`chunkserver`的参与，\n`client`根据需要操作文件的偏移量转化为相应的`chunk index`，\n向`master`发出请求，`master`根据文件名和`chunk index`，得到一个全局的`chunk handle`，\n一个 chunk 由唯一的一个`chunk handle`所标识，\n`master`返回这个`chunk handle`以及拥有这个 chunk 的`chunkserver`的位置。\n（不止一个，一个 chunk 有多个 replica，分布在不同的`chunkserver`。\n必要的时候，`master`可能会新建 chunk，\n并在`chunkserver`准备好了这个 chunk 的 replica 之后，才返回）\n`client`拿到`chunk handle`和`chunkserver`列表之后，\n先把这个信息用文件名和`chunk index`作为 key 缓存起来，\n然后对相应的`chunkserver`发出数据的读写请求。\n这只是一个大概的流程，对于具体的操作过程，下面会做分析。\n\n### Chunk 大小\n\nChunk 的大小是一个值得考虑的问题。在 GFS 中，chunk 的大小是 64MB。\n这比普通文件系统的 block 大小要大很多。\n在`chunkserver`上，一个 chunk 的 replica 保存成一个文件，\n这样，它只占用它所需要的空间，防止空间的浪费。\n\nChunk 拥有较大的大小由如下几个好处：\n\n* 它减少了`client`和`master`交互的次数。\n* 减少了网络的开销，由于一个客户端可能对同一个 chunk 进行操作，\n这样可以与`chunkserver`维护一个长 TCP 连接。\n* chunk 数目少了，metadata 的大小也就小了，这样节省了`master`的内存。\n\n大的 chunk size 也会带来一个问题，一个小文件可能就只占用一个 chunk，\n那么如果多个`client`同时操作这个文件的话，就会变成操作同一个 chunk，\n保存这个 chunk 的`chunkserver`就会称为一个 hotspot。\n这样的问题对于小的 chunk 并不存在，因为如果是小的 chunk 的话，\n一个文件拥有多个 chunk，操作同一个文件被分布到多个`chunkserver`.\n虽然在实践中，可以通过错开应用的启动的时间来减小同时操作一个文件的可能性。\n\n### Metadata\n\nGFS 的`master`保存三种 metadata：\n\n1. 文件和 chunk 的 namespace（命名空间） -- 整个文件系统的目录结构以及 chunk 基本信息\n2. 文件到 chunk 的映射\n3. 每一个 chunk 的具体位置\n\nmetadata 保存在内存中，可以很快地获取。\n前面两种 metadata 会通过 operation log 来持久化。\n第 3 种信息不用持久化，因为在`master`启动时，\n它会问`chunkserver`要 chunk 的位置信息。\n而且 chunk 的位置也会不断的变化，比如新的`chunkserver`加入。\n这些新的位置信息会通过日常的`HeartBeat`消息由`chunkserver`传给`master`。\n\n将 metadata 保存在内存中能够保证在`master`的日常处理中很快的获取 metadata，\n为了保证系统的正常运行，`master`必须定时地做一些维护工作，比如清除被删除的 chunk，\n转移或备份 chunk 等，这些操作都需要获取 metadata。\nmetadata 保存在内存中有一个不好的地方就是能保存的 metadata 受限于`master`的内存，\n不过足够大的 chunk size 和使用前缀压缩，能够保证 metadata 占用很少的空间。\n\n对 metadata 进行修改时，使用锁来控制并发。需要注意的是，对于目录，\n获取锁的方式和 linux 的文件系统有点不太一样。在目录下新建文件，\n只获取对这个目录的读锁，而对目录进行 snapshot，却对这个目录获取一个写锁。\n同时，如果涉及到某个文件，那么要获取所有它的所有上层目录的读锁。\n这样的锁有一个好的地方是可以在通过一个目录下同时新建两个文件而不会冲突，\n因为它们都是获得对这个目录的读锁。\n\n\n### Operation Log\n\nOperation log 用于持久化存储前两种 metadata，这样`master`启动时，\n能够根据 operation log 恢复 metadata。同时，可以通过 operation log 知道 metadata 修改的顺序，\n对于重现并发操作非常有帮助。因此，必须可靠地存储 operation log，\n只有当 operation log 已经存储好之后才向`client`返回。\n而且，operation log 不仅仅只保存在`master`的本地，而且在远程的机器上有备份，\n这样，即使`master`出现故障，也可以使用其他的机器做为`master`。\n\n从 operation log 恢复状态是一个比较耗时的过程，因此，使用 checkpoint 来减小 operation log 的大小。\n每次恢复时，从 checkpoint 开始恢复，只处理 checkpoint 只有的 operation log。\n在做 checkpoint 时，新开一个线程进行 checkpoint，原来的线程继续处理 metadata 的修改请求，\n此时把 operation log 保存在另外一个文件里。\n\n### 容错机制\n\n#### Master 容错\n\n通过操作日志加 checkpoint 的方式进行，并且有一台称为 \"Shadow Master\" 的实时热备。\n\n> * GFS Master 的修改操作总是先记录操作日志，然后修改内存。\n> * Master 会定期将内存中的数据以 checkpoint 文件的形式转存到磁盘中\n> * 实时热备，所有元数据修改操作都发送到实时热备才算成功。\n\n### 一致性模型\n\n关于一致性，先看几个定义，对于一个 file region，存在以下几个状态：\n\n* consistent。如果任何 replica, 包含的都是同样的 data。\n* defined。defined 一定是 consistent，而且能够看到一次修改造成的结果。\n* undefined。undefined 一定是 consistent，是多个修改混合在一块。举个例子，\n修改 a 想给文件添加 A1,A2，修改 b 想给文件添加 B1,B2，如果最后的结果是 A1,A2,B1,B2，\n那么就是 defined，如果是 A1,B1,A2,B2，就是 undefined。\n* inconsitent。对于不同的 replica，包含的是不同的 data。\n\n在 GFS 中，不同的修改可能会出现不同的状态。对于文件的 append 操作（是 GFS 中的主要写操作），\n通过放松一定的一致性，更好地支持并发，在下面的具体操作时再讲述具体的过程。\n\n### Lease 机制\n\n`master`通过 lease 机制把控制权交给`chunkserver`，当写一个 chunk 时，\n`master`指定一个包含这个 chunk 的 replica 的`chunkserver`作为`primary replica`，\n由它来控制对这个 chunk 的写操作。一个 lease 的过期时间是 60 秒，如果写操作没有完成，\n`primary replica`可以延长这个 lease。`primary replica`通过一个序列号控制对这个 chunk 的写的顺序，\n这样能够保证所有的 replica 都是按同样的顺序执行同样的操作，也就保证了一致性。\n\n### 版本号\n\n对于每一个 chunk 的修改，chunk 都会赋予一个新的版本号。\n这样，如果有的 replica 没有被正常的修改（比如修改的时候当前的`chunkserver`挂了）,\n那么这个 replica 就被`stale replica`，当`client`请求一个 chuck 时，`stale replica`会被`master`忽略，\n在`master`的定时管理过程中，会把`stale replica`删除。\n\n### 负载均衡\n\n为了尽量保证所有`chunkserver`都承受差不多的负载，\n`master`通过以下机制来完成：\n\n* 首先，在新建一个 chunk 或者是复制一个 chunk 的 replica 时，\n尽量保证负载均衡。\n* 当一个 chunk 的 replica 数量低于某个值时，尝试给这个 chuck 复制 replica\n* 扫描整个系统的分布情况，如果不够平衡，则通过移动一些 replica 来达到负责均衡的目的。\n\n注意，`master`不仅考虑了`chunkserver`的负载均衡，也考虑了机架的负载均衡。\n\n## 基本操作\n\n### Read\n\nRead 操作其实已经在上面的 Figure 1 中描述得很明白了，有如下几个过程：\n\n1. `client`根据 chunk size 的大小，把`(filename,byte offset)`转化为`(filename,chunk index)`,\n发送`(filename,chunk index)`给`master`\n\n2. `master` 返回`(chunk handle, 所有正常 replica 的位置）`,\n`client`以`(filename,chunk index)`作为 key 缓存这个信息\n\n3. `client`发`(chunk handle,byte range)`给其中一个`chunkserver`，通常是最近的一个。\n\n4. `chunkserver`返回 chunk data\n\n### Overwrite\n\n直接假设`client`已经知道了要写的 chunk，如 Figure 2，具体过程如下：\n\n1. `client`向`master`询问拥有这个 chunk 的 lease 的`primary replica`，如果当前没有`primary replica`，\n`master`把 lease 给其中的 replica\n2. `master`把`primary replica`的位置和其他的拥有这个 chunk 的 replica 的`chunkserver`（`secondary replica`）的位置返回，\n`client`缓存这个信息。\n3. `client`把数据以流水线的方式发送到所有的 replica，流水线是一种最高效利用的带宽的方法，\n每一个 replica 把数据用 LRU buffer 保存起来，并向`client`发送接受到的信息。\n4. `client`向`primary replica`发送 write 请求，`primary replica`根据请求的顺序赋予一个序列号\n5. `primary replica`根据序列号修改 replica 和请求其他的`secondary replica`修改 replica，\n这个统一的序列号保证了所有的 replica 都是按照统一的顺序来执行修改操作。\n6. 当所有的`secondary replica`修改完成之后，返回修改完成的信号给`primary replica`\n7. `primary replica`向`client`返回修改完成的信号，如果有任何的`secondary replica`修改失败，\n信息也会被发给`client`，`client`然后重新尝试修改，重新执行步骤 3-7。\n\n如果一个修改很大或者到了 chuck 的边界，那么 client 会把它分成两个写操作，\n这样就有可能发生在两个写操作之间有其他的写操作，所以这时会出现 undefined 的情况。\n\n### Record Append\n\nRecord Append 的过程相对于 Overwrite 的不同在于它的错误处理不同，\n当写操作没有成功时，`client`会尝试再次操作，由于它不知道 offset，\n所以只能再次 append，这就会导致在一些 replica 有重复的记录，\n而且不同的 replica 拥有不同的数据。\n\n为了应对这种情况的发生，应用程序必须通过一定的校验手段来确保数据的正确性，\n如果对于生产者 - 消费者队列，消费者可以通过唯一的 id 过滤掉重复的记录。\n\n### Snapshot\n\nSnapshot 是对文件或者一个目录的“快照”操作，快速地复制一个文件或者目录。\nGFS 使用*Copy-on-Write*实现 snapshot，首先`master`revoke 所有相关 chunk 的 lease，\n这样所有的修改文件的操作都需要和`master`联系，\n然后复制相关的 metadata，复制的文件跟原来的文件指向同样的 chunck，\n但是 chuck 的 reference count 大于 1。\n\n当有`client`需要写某个相关的 chunck C 时，`master`会发现它的 reference count 大于 1，\n`master`推迟回复给`client`，先新建一个`chunk handle`C'，\n然后让所有拥有 C 的 replica 的`chunkserver`在本地新建一个同样的 C‘的 replica，\n然后赋予 C’的一个 replica 一个 lease，把 C'返回给`client`用于修改。\n\n### Delete\n\n当`client`请求删除文件时，GFS 并不立即回收这个文件的空间。\n也就是说，文件相关的 metadata 还在，\n文件相关的 chunk 也没有从`chunkserver`上删除。\nGFS 只是简单的把文件删除的 operation log 记下，\n然后把文件重新命名为一个 hidden name， 里面包含了它的删除时间。\n在`master`的日常维护工作时，\n它会把删除时间删除时间超过 3 天的文件从 metadata 中删除，\n同时删除相应 chunk 的 metadata，\n这样这些 chunk 就变成了 orphan chunk，\n它们会在`chunkserver`和`master`进行`Heartbeat`交互时从`chunkserver`删除。\n\n这样推迟删除（原文叫垃圾回收）的好处有：\n\n* 对于分布式系统而言，要确保一个动作正确执行是很难的，\n所以如果当场要删除一个 chunk 的所有 replica 需要复杂的验错，重试。\n如果采用这种推迟删除的方法，只要 metadata 被正确的处理，最后的 replica 就一定会被删除，\n非常简单\n* 把这些删除操作放在`master`的日常处理中，可以使用批处理这些操作，\n平摊下来的开销就小了\n* 可以防止意外删除的可能，类似于回收站\n\n这样推迟删除的不好在于浪费空间，如果空间吃紧的话，`client`可以强制删除，\n或者指定某些目录下面的文件直接删除。\n"
  },
  {
    "path": "doc/store/glusterfs.md",
    "content": "# GlusterFS\n\n<!-- vim-markdown-toc GFM -->\n* [说明](#说明)\n    * [简介](#简介)\n    * [GlusterFS 在企业中的应用场景](#glusterfs-在企业中的应用场景)\n* [GlusterFS 安装](#glusterfs-安装)\n    * [环境说明](#环境说明)\n    * [安装 GlusterFS](#安装-glusterfs)\n    * [配置 GlusterFS](#配置-glusterfs)\n    * [volume 模式说明](#volume-模式说明)\n    * [创建 GlusterFS 磁盘](#创建-glusterfs-磁盘)\n    * [GlusterFS 客户端](#glusterfs-客户端)\n* [维护](#维护)\n\n<!-- vim-markdown-toc -->\n\n## 说明\n\n### 简介\n\n```\nGlusterFS 是 Scale-Out 存储解决方案 Gluster 的核心，它是一个开源的分布式文件系统， 具有强大的横向扩展能力，通过扩展能够支持数 PB 存储容量和处理数千客户端。\n\nGlusterFS 借助 TCP/IP 或 InfiniBand RDMA 网络将物理分布的存储资源聚集在一起，使用单一全局命名空间来管理数据。\n\nGlusterFS 基于可堆叠的用户空间设计，可为各种不同的数据负载提供优异的性能。\n\nGlusterFS 支持运行在任何 IP 网络上的标准应用程序的标准客户端，用户可以在全局统一的命名空间中使用 NFS/CIFS 等标准协议来访问应用数据。\n\nGlusterFS 使得用户可摆脱原有的独立、高成本的封闭存储系统，能够利用普通廉价的存储设备来部署可集中管理、横向扩展、虚拟化的存储池，存储容量可扩展至 TB/PB 级。\n\n目前 GlusterFS 已被 Red Hat 收购，它的官网是：https://www.gluster.org/\n```\n### GlusterFS 在企业中的应用场景\n\n理论和实践上分析，GlusterFS 目前主要适用于大文件存储场景，对于小文件尤其是海量小文件，存储效率和访问性能都表现不佳。建议存放文件大小大于 1MB\n\n## GlusterFS 安装\n\n### 环境说明\n\n> * CentOS 7\n> * GlusterFS\n\n3 台机器安装 GlusterFS 组成一个集群。\n\n```\n服务器：\n10.1.0.11\n10.1.0.12\n10.1.0.13\n\n配置 hosts\n\n10.1.0.11 manager\n10.1.0.12 node-1\n10.1.0.13 node-2\n\nclient:\n10.1.0.10 client\n```\n\n### 安装 GlusterFS\n\nCentOS 安装 GlusterFS 非常的简单\n\n在三个节点都安装 GlusterFS\n\n```\n# 安装 GlusterFS yum 源文件\n#yum install centos-release-gluster\n\n# 安装 GlusterFS 软件\nyum install -y glusterfs glusterfs-server glusterfs-fuse glusterfs-rdma\n```\n\n启动 GlusterFS\n\n```\n[root@manager ~]#systemctl start glusterd.service\n[root@manager ~]#systemctl enable glusterd.service\n[root@manager ~]#systemctl status glusterd.service\n● glusterd.service - GlusterFS, a clustered file-system server\n  Loaded: loaded (/usr/lib/systemd/system/glusterd.service; disabled; vendor preset: disabled)\n  Active: active (running) since 一 2017-02-27 17:55:56 CST; 11s ago\n Process: 5476 ExecStart=/usr/sbin/glusterd -p /var/run/glusterd.pid --log-level $LOG_LEVEL $GLUSTERD_OPTIONS (code=exited, status=0/SUCCESS)\nMain PID: 5477 (glusterd)\n Memory: 15.0M\n CGroup: /system.slice/glusterd.service\n        └─5477 /usr/sbin/glusterd -p /var/run/glusterd.pid --log-level INFO\n\n2 月 27 17:55:56 meetbill systemd[1]: Starting GlusterFS, a clustered file-system server...\n2 月 27 17:55:56 meetbill systemd[1]: Started GlusterFS, a clustered file-system server.\n```\n\n### 配置 GlusterFS\n\n\n在 manager 节点上配置，将 节点 加入到 集群中。\n\n```\n[root@manager ~]#gluster peer probe manager\npeer probe: success. Probe on localhost not needed\n\n[root@manager ~]#gluster peer probe node-1\npeer probe: success.\n\n[root@manager ~]#gluster peer probe node-2\npeer probe: success.\n```\n\n\n查看集群状态：\n\n```\n[root@manager ~]#gluster peer status\nNumber of Peers: 2\n\nHostname: node-1\nUuid: 41573e8b-eb00-4802-84f0-f923a2c7be79\nState: Peer in Cluster (Connected)\n\nHostname: node-2\nUuid: da068e0b-eada-4a50-94ff-623f630986d7\nState: Peer in Cluster (Connected)\n```\n\n\n创建数据存储目录：\n\n```\n[root@manager ~]#mkdir -p /opt/gluster/data\n[root@node-1 ~]# mkdir -p /opt/gluster/data\n[root@node-2 ~]# mkdir -p /opt/gluster/data\n```\n\n\n查看 volume 状态：\n\n```\n[root@manager ~]#gluster volume info\nNo volumes present\n```\n\n\n### volume 模式说明\n\n一、 默认模式，既 DHT, 也叫 分布卷：将文件已 hash 算法随机分布到 一台服务器节点中存储。\ngluster volume create test-volume server1:/exp1 server2:/exp2\n\n二、 复制模式，既 AFR, 创建 volume 时带 replica x 数量：将文件复制到 replica x 个节点中。\ngluster volume create test-volume replica 2 transport tcp server1:/exp1 server2:/exp2\n\n三、 条带模式，既 Striped, 创建 volume 时带 stripe x 数量： 将文件切割成数据块，分别存储到 stripe x 个节点中 ( 类似 raid 0 )。\ngluster volume create test-volume stripe 2 transport tcp server1:/exp1 server2:/exp2\n\n四、 分布式条带模式（组合型），最少需要 4 台服务器才能创建。 创建 volume 时 stripe 2 server = 4 个节点： 是 DHT 与 Striped 的组合型。\ngluster volume create test-volume stripe 2 transport tcp server1:/exp1 server2:/exp2 server3:/exp3 server4:/exp4\n\n五、 分布式复制模式（组合型）, 最少需要 4 台服务器才能创建。 创建 volume 时 replica 2 server = 4 个节点：是 DHT 与 AFR 的组合型。\ngluster volume create test-volume replica 2 transport tcp server1:/exp1 server2:/exp2　server3:/exp3 server4:/exp4\n\n六、 条带复制卷模式（组合型）, 最少需要 4 台服务器才能创建。 创建 volume 时 stripe 2 replica 2 server = 4 个节点： 是 Striped 与 AFR 的组合型。\ngluster volume create test-volume stripe 2 replica 2 transport tcp server1:/exp1 server2:/exp2 server3:/exp3 server4:/exp4\n\n七、 三种模式混合，至少需要 8 台 服务器才能创建。 stripe 2 replica 2 , 每 4 个节点 组成一个 组。\ngluster volume create test-volume stripe 2 replica 2 transport tcp server1:/exp1 server2:/exp2 server3:/exp3 server4:/exp4 server5:/exp5 server6:/exp6 server7:/exp7 server8:/exp8\n\n### 创建 GlusterFS 磁盘\n\n```\n[root@manager ~]#gluster volume create models replica 3 manager:/opt/gluster/data node-1:/opt/gluster/data node-2:/opt/gluster/data force\nvolume create: models: success: please start the volume to access data\n```\n\n再查看 volume 状态：\n\n```\n[root@manager ~]#gluster volume info\n\nVolume Name: models\nType: Replicate\nVolume ID: e539ff3b-2278-4f3f-a594-1f101eabbf1e\nStatus: Created\nNumber of Bricks: 1 x 3 = 3\nTransport-type: tcp\nBricks:\nBrick1: manager:/opt/gluster/data\nBrick2: node-1:/opt/gluster/data\nBrick3: node-2:/opt/gluster/data\nOptions Reconfigured:\nperformance.readdir-ahead: on\n```\n\n启动 models\n\n```\n[root@manager ~]#gluster volume start models\nvolume start: models: success\n```\n\n### GlusterFS 客户端\n\n部署 GlusterFS 客户端并 mount GlusterFS 文件系统\n\n```\n[root@client ~]#yum install -y glusterfs glusterfs-fuse\n[root@client ~]#mkdir -p /opt/gfsmnt\n[root@client ~]#mount -t glusterfs manager:models /opt/gfsmnt/\n```\n\n```\n[root@node-94 ~]#df -h\n文件系统 容量 已用 可用 已用 % 挂载点\n/dev/mapper/vg001-root 98G 1.2G 97G 2% /\ndevtmpfs 32G 0 32G 0% /dev\ntmpfs 32G 0 32G 0% /dev/shm\ntmpfs 32G 130M 32G 1% /run\ntmpfs 32G 0 32G 0% /sys/fs/cgroup\n/dev/mapper/vg001-opt 441G 71G 370G 17% /opt\n/dev/sda2 497M 153M 344M 31% /boot\ntmpfs 6.3G 0 6.3G 0% /run/user/0\nmanager:models 441G 18G 424G 4% /opt/gfsmnt\n```\n## 维护\n\n\n1. 查看 GlusterFS 中所有的 volume:\n\n```\n[root@manager ~]#gluster volume list\n```\n\n\n2. 删除 GlusterFS 磁盘：\n\n```\n[root@manager ~]#gluster volume stop models   #停止名字为 models 的磁盘\n[root@manager ~]#gluster volume delete models #删除名字为 models 的磁盘\n```\n\n注： 删除 磁盘 以后，必须删除 磁盘 ( /opt/gluster/data ) 中的 （ .glusterfs/ .trashcan/ ）目录。\n否则创建新 volume 相同的 磁盘 会出现文件 不分布，或者 类型 错乱 的问题。\n\n3. 卸载某个节点 GlusterFS 磁盘\n\n```\n[root@manager ~]#gluster peer detach node-2\n```\n\n4. 设置访问限制，按照每个 volume 来限制\n\n```\n[root@manager ~]#gluster volume set models auth.allow 10.6.0.*,10.7.0.*\n```\n\n5. 添加 GlusterFS 节点：\n\n```\n[root@manager ~]#gluster peer probe node-3\n[root@manager ~]#gluster volume add-brick models node-3:/opt/gluster/data\n```\n\n注：如果是复制卷或者条带卷，则每次添加的 Brick 数必须是 replica 或者 stripe 的整数倍\n\n6. 配置卷\n\n```\n[root@manager ~]# gluster volume set\n```\n\n7. 缩容 volume:\n\n先将数据迁移到其它可用的 Brick，迁移结束后才将该 Brick 移除：\n\n```\n[root@manager ~]#gluster volume remove-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data start\n```\n\n在执行了 start 之后，可以使用 status 命令查看移除进度：\n\n```\n[root@manager ~]#gluster volume remove-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data status\n```\n\n\n不进行数据迁移，直接删除该 Brick：\n\n```\n[root@manager ~]#gluster volume remove-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data commit\n```\n\n注意，如果是复制卷或者条带卷，则每次移除的 Brick 数必须是 replica 或者 stripe 的整数倍。\n\n扩容：\n\n```\ngluster volume add-brick models node-2:/opt/gluster/data\n```\n\n\n8. 修复命令：\n\n```\n[root@manager ~]#gluster volume replace-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data commit -force\n```\n\n9. 迁移 volume:\n\n```\n[root@manager ~]#gluster volume replace-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data start\n```\n\npause 为暂停迁移\n\n```\n[root@manager ~]#gluster volume replace-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data pause\n```\n\nabort 为终止迁移\n\n```\n[root@manager ~]#gluster volume replace-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data abort\n```\n\nstatus 查看迁移状态\n\n```\n[root@manager ~]#gluster volume replace-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data status\n```\n\n迁移结束后使用 commit 来生效\n\n```\n[root@manager ~]#gluster volume replace-brick models node-2:/opt/gluster/data node-3:/opt/gluster/data commit\n```\n\n10. 均衡 volume:\n\n```\n[root@manager ~]#gluster volume models lay-outstart\n[root@manager ~]#gluster volume models start\n[root@manager ~]#gluster volume models startforce\n[root@manager ~]#gluster volume models status\n[root@manager ~]#gluster volume models stop\n```\n"
  },
  {
    "path": "doc/store/moosefs.md",
    "content": "## MooseFS\n\n<!-- vim-markdown-toc GFM -->\n* [MooseFS 简介](#moosefs-简介)\n    * [功能特性](#功能特性)\n    * [架构原理](#架构原理)\n        * [关于分片](#关于分片)\n        * [容错／高可靠](#容错高可靠)\n        * [chunk 存储选择算法](#chunk-存储选择算法)\n* [安装及使用](#安装及使用)\n    * [配置要求](#配置要求)\n    * [安装](#安装)\n    * [使用方法](#使用方法)\n        * [挂载](#挂载)\n        * [设置冗余度](#设置冗余度)\n        * [设置 Trash time](#设置-trash-time)\n        * [使用快照](#使用快照)\n        * [启动顺序与停止顺序](#启动顺序与停止顺序)\n        * [数据恢复](#数据恢复)\n        * [Automated Failover](#automated-failover)\n* [日常问题及修复方法](#日常问题及修复方法)\n    * [Client 操作与修复](#client-操作与修复)\n        * [关于修复](#关于修复)\n    * [Chunker 的空间](#chunker-的空间)\n    * [快照 snapshot](#快照-snapshot)\n    * [mfsappendchunks](#mfsappendchunks)\n    * [回收站机制](#回收站机制)\n* [MFS 集群的维护](#mfs-集群的维护)\n    * [启动和停止 MFS 集群](#启动和停止-mfs-集群)\n    * [MFS chunkservers 的维护](#mfs-chunkservers-的维护)\n    * [MFS 元数据备份](#mfs-元数据备份)\n    * [MFS Master 的恢复](#mfs-master-的恢复)\n    * [从 MetaLogger 中恢复 Master](#从-metalogger-中恢复-master)\n\n<!-- vim-markdown-toc -->\n# MooseFS 简介\n\n本文介绍 `MooseFS` 架构原理，安装配置和使用方法。\n\n`MooseFS` 是一种容错的分布式文件系统。它将数据分散到多个物理位置（服务器），在用户看来是一个统一的资源。\n`MooseFS` 支持 `FUSE` 标准接口，能够无缝实现从本地文件的迁移。\n同时，`MooseFS` 提供比 `NFS` 更好的可运维性。\n\n## 功能特性\n\n对于标准的文件操作，`MooseFS` 表现与其它类 Unix 文件系统一致。\n\n支持的通用文件系统特性有：\n\n* 层次结构（目录数），是一种操作友好的文件系统。\n* 兼容 POSIX 文件属性\n* 支持特殊文件（块设备与字符设备，管道和套接字）\n* 符号链接（软链接）和硬链接。\n* 基于 IP 地址和（或）密码的访问权限控制\n\n`MooseFS` 独特的特性有：\n\n* 高可靠性（数据的多个副本存储在多个不同服务器上）\n* 容量动态扩展。只要增加新的机器／磁盘\n* 删除的文件保留一段时间（可配），像是文件系统的回收站。\n* 即使文件在被读写，也可以持续做文件快照。\n\n## 架构原理\n\nMooseFS 包含 4 个组件\n\n* 管理节点 master servers。支持单活。存储所有文件的元数据\n* 数据节点 chunk servers 数量不限。存储文件数据，相互同步文件。\n* 元数据备份服务器 metalogger server。数量不限。保存元数据变更日志，周期性的下载元数据文件。主节点失效时可以替代主节点。\n* 客户端。挂载使用文件系统。\n\n文件的读写流程可以根据以下图示来理解：\n\n![MooseFS Read Process](../../images/store/mfs-read-process.png \"MooseFS Read Process\")\n\n![MooseFS Write Process](../../images/store/mfs-write-process.png \"MooseFS Write Process\")\n\n### 关于分片\n\n文件数据分片（chunks）后保存，分片默认最大值为 64MiB。分片对应 chunkserver 中的文件。\n分片数据是版本化的，如果文件执行更新后，某台机器还有旧版本的数据，则会删除该机器上的旧版本文件，并同步到改文件的最新版本。\n\n### 容错／高可靠\n\n将文件分发多份到多个服务器中存储，以实现高可靠。通过设置单个文件的 `goal` 来指定文件应该可以保留的副本数。\n重要数据建议将 goal 设置大于 2；而将 goal 设为 1，则文件只在 1 台数据节点上保存\n\n### chunk 存储选择算法\n\n如果自己设计一套 chunkserver 选择算法，我们要达到哪些目标呢？\n\n> 1. 文件打散后尽量平均分布到各台 chunkserver 上\n> 2. 各台 chunkserver 上的 chunk 数量尽可能的平均\n> 3. 数据分发过程衡量系统负载，尽量把数据放在负载低的 chunkserver 上\n> 4. 数据分发过程是否应该衡量各台 chunkserver 的可用空间？\n> 5. 机架感应？\n\n回到 MFS 使用过程中会有一个疑问.chunkserver 的选择是怎么选择的. 怎么才能保证数据保存占用空间平衡甚至平均？这就是数据分布算法. 也正是分布式文件系统的核心容. 所以在此，转来一篇关于 MFS 的 chunk 存储选择算法的文章。\n\n* * *\n\n还记得 matocsserventry 结构中的 carry 字段么，这个字段就是分布算法的核心. 每台 chunkserver 会有自己的 carry 值，在选择 chunkserver 会将每台 chunkserver 按照 carry 从大到小做快速排序，优先选择 carry 值大的 chunkserver 来使用。\n\n在描述具体算法前，先介绍三个概念：\n\n> * allcnt:mfs 中可用的 chunkserver 的个数\n> * availcnt:mfs 中当前可以直接存储数据的 chunkserver 的个数\n> * demand: 当前文件的副本数目\n\n先说 allcnt, 可用的 chunkserver 要满足下面几个条件：\n\n> 1. chunkserver 是活着的\n> 2. chunkserver 的总空间大于 0\n> 3. chunkserver 的可用空间（总空间 - 使用空间）大于 1G\n\navailcnt 指的是 carry 值大于 1 的可用 chunkserver 的个数. 也就是在 allcnt 的约束条件上加一条 carry 值大于 1. 文件 1.txt 需要存储 2 个副本，但是 mfs 中仅仅有 1 台 chunkserver 可用，也就是```demand>allcnt```的时候，mfs 会自动减少文件的副本个数到 allcnt, 保证文件可以成功写入系统。\n\n关于 carry 有下面几个规则：\n\n> 1. 仅 carry 值大于 1 的 chunkserver 可以存储新数据\n> 2. 每台 chunkserver 存储新数据后其 carry 会减 1\n> 3. demand>availcnt 的时候，会递归的增加每台 chunkserver 的 carry 值，直到```demand<=availcnt```为止\n> 4. 每台 chunkserver 每次 carry 值的增加量等于当前 chunkserver 总空间除以最大的 chunkserver 总空间\n\n上面的规则比较复杂. 举个例子就更加清晰了。\n\n```\nchunkserver 1：totalspace:3.94G carry:0.463254\nchunkserver 2：totalspace:7.87G carry:0.885674\n```\n文件 1.txt 大小 1k,mfs 默认一个 chunk 大小为 64M, 所以仅仅需要一个 chunk 就够了. 此时 availcnt=0,demand=1, 所以需要增加 carry 值\n```\nchunkserver 1：carry=0.463254 + (3.94/7.87) = 0.463254 + 0.500005 = 0.963259\nchunkserver 2：carry=0.885674 + (7.87/7.87) = 0.885674 + 1.000000 = 1.885674\n```\n此时 availcnt=1,demand=1, 所以不需要增加 carry 值，对 chunkserver 按照 carry 从大到小排序结果为：```chunkserver 2 > chunkserver 1```, 文件 1.txt 的 chunk 会存储到 chunkserver 2 上，同时 chunkserver 2 的 carry 会减 1\n\n如下：\n```\nchunkserver 1：carry=0.963259\nchunkserver 2：carry=1.885674 – 1 = 0.885674\n```\n文件 2.txt 大小 1k,mfs 默认一个 chunk 大小为 64M, 所以仅仅需要一个 chunk 就够了. 此时 availcnt=0,demand=1. 所以需要增加 carry 值\n```\nchunkserver 1：carry=0.963259 + (3.94/7.87) = 0.963259 + 0.500005 = 1.463264\nchunkserver 2：carry=0.885674 + (7.87/7.87) = 0.885674 + 1.000000 = 1.885674\n```\n此时 availcnt=2,demand=1, 所以不需要增加 carry 值，对 chunkserver 按照 carry 从大到小排序结果为：```chunkserver 2 > chunkserver 1```, 文件 2.txt 的 chunk 会存储到 chunkserver 2 上，同时 chunkserver 2 的 carry 会减 1\n\n如下：\n```\nchunkserver 1：carry=1.463264\nchunkserver 2：carry=1.885674 – 1 = 0.885674\n```\n文件 3.txt 大小 1k,mfs 默认一个 chunk 大小为 64M, 所以仅仅需要一个 chunk 就够了. 此时 availcnt=1,demand=1, 所以不需要增加 carry 值. 对 chunkserver 按照 carry 从大到小排序结果为：```chunkserver 1 > chunkserver 2```, 文件 3.txt 的 chunk 会存储到 chunkserver 1 上，同时 chunkserver 1 的 carry 会减 1\n\n如下：\n```\nchunkserver 1：carry=1.463264 – 1 = 0.463264\nchunkserver 2：carry=0.885674\n```\n因为两台 chunkserver 的总空间大小不一致，根据算法总空间大的那台 chunkserver 会存储更多的新数据。\n\n记住：**仅仅和 chunkserver 的总空间有关系和可用空间没有任何关系**, 也就是说，当各台 chunkserver 总空间大小差不多的情况下，chunk 能更好的平均分布，否则 mfs 会更倾向于选择总空间大的机器来使用。\n\n* * *\n最后一个问题，当 mfs 刚刚启动的时候，carry 值是如果获得的？\n\n答案：随机产生，通过 rndu32() 这个函数，随机产生一个小于 1, 大于等于 0 的数。\n\n测试结果如下：\n```\nNov 23 01:01:25 sunwg mfsmaster[13175]: 192.168.0.159,0.594834\nNov 23 01:01:25 sunwg mfsmaster[13175]: 192.168.0.160,0.000000\nNov 23 01:03:58 sunwg mfsmaster[13187]: 192.168.0.159,0.516242\nNov 23 01:03:58 sunwg mfsmaster[13187]: 192.168.0.160,0.826559\nNov 23 01:04:17 sunwg mfsmaster[13192]: 192.168.0.159,0.123765\nNov 23 01:04:17 sunwg mfsmaster[13192]: 192.168.0.160,0.389592\n```\n\n# 安装及使用\n\n## 配置要求\n\n**管理节点** 是系统的核心，需要使用稳定性高的硬件设备，如冗余电源，ECC 内存，RAID1/RAID5/RAID10。\n根据文件数量的不同，也需要配置比较多的内存（一般来说，100 万个文件对应 300MiB 内存）。\n硬盘容量需要考虑文件数量和文件操作数量（一般来说，20GiB 磁盘可以保存 2500 万文件的元数据，或者 50 小时的文件操作日志）。\n管理节点如此重要，也需要根据情况做好安全设置。\n\n**元数据备份服务器** 只需要和管理节点有同样多的内存和磁盘来存储数据即可。\n\n**数据节点** 只需要保持足够的磁盘容量。\n\n## 安装\n\n在 CentOS 系统上安装。\n\n首先配置使用软件仓库。\n\n将 moosefs 仓库到 GPG KEY 加入本地软件包管理工具。\n\n    curl \"http://ppa.moosefs.com/RPM-GPG-KEY-MooseFS\" -o /etc/pki/rpm-gpg/RPM-GPG-KEY-MooseFS\n\n增加 MooseFS3.0 仓库配置项。\n\n    curl \"http://ppa.moosefs.com/MooseFS-3-el$(grep -o '[0-9]*' /etc/centos-release |head -1).repo\" -o /etc/yum.repos.d/MooseFS.repo\n\n使用以下命令来安装软件包：\n\n    # For Master Server:\n    yum install moosefs-master moosefs-cli moosefs-cgi moosefs-cgiserv\n\n    # For Chunkservers:\n    yum install moosefs-chunkserver\n\n    # For Metaloggers:\n    yum install moosefs-metalogger\n\n    # For Clients:\n    yum install moosefs-client\n\n\n启动文件系统\n\n    # To start process manually:\n    mfsmaster start\n    mfschunkserver start\n    # For sysv os family - EL5, EL6:\n    service moosefs-master start\n    service moosefs-chunkserver start\n    # For systemd os family - EL7:\n    systemctl start moosefs-master.service\n    systemctl start moosefs-chunkserver.service\n\n\n系统参数\n\n    # for master server\n    sysctl vm.overcommit_memory=1\n\n## 使用方法\n\n### 挂载\n\n    $ sudo mfsmount -H <mfs-master> [-P 9421] [-S /] [-o rw|ro] /mnt/mfs\n\n其中，\n`-H` / `-P` 代表 mfsmaster 的 IP 和端口；\n`-S` 挂载 MooseFS 中的路径；\n`-o rw` 或 `-o ro` 设置读写或只读模式；\n`/mnt/mfs` 为本地挂载路径。\n\n\n### 设置冗余度\n\n通过配置冗余度来保证出现失效时不丢失数据。\n冗余度为 N 时，能够在不超过 N-1 个 chunkserver 同时出现失效时不丢失数据。\n\n默认我们设置文件的冗余度为 2，即支持有 1 个 chunkserver 失败时不影响使用。\n\n调整数据冗余度 (goal)\n\n    $ sudo mfssetgoal -r 2 /mnt/mfs\n\n其中，\n`-r` 选项代表递归目录及子目录的文件。\n\n可以通过 `mfsgetgoal` 来读取当前的冗余度\n\n    $ sudo mfsgetgoal /mnt/mfs\n    /mnt/mfs 2\n\n可以通过 `mfscheckfile` 来读取特定文件的冗余度设置与生效情况\n\n    $ sudo mfscheckfile /mnt/mfs/testfile\n    /mnt/mfs/testfile:\n    2 copies: 1 chunks\n\n建议：\n\n*  最低设置为 2，保证不出现文件丢失；\n*  一般情况下设置为 3，应该是足够安全的；\n*  对于足够重要的数据，可以设置为 4 或者更高，但是不能超过 chunkserver 实例数量。\n\n### 设置 Trash time\n\n文件删除后会在 moosefs 的垃圾站中保留一段时间。\n通过 `mfsgettrashtime` 能读取过期时间的设置。\n\n### 使用快照\n\n使用 MooseFS 的一个好处是可以支持文件或目录的快照。\n我们知道 MooseFS 的分块都是版本化的，因此支持快照的方式保留一个文件的副本。\n在文件被修改前，这个副本并不会占用额外的空间。\n\n### 启动顺序与停止顺序\n\n启动顺序\n\n*  启动 mfsmaster\n*  启动 mfschunkserver\n*  启动 mfsmetalogger\n*  在 client 节点执行 mfsmount\n\n停止顺序\n\n*  在所有 client 节点执行 umount\n*  mfschunkserver stop\n*  mfsmetalogger stop\n*  mfsmaster stop\n\n### 数据恢复\n\n当出现 master 节点出现问题时，可以通过 `mfsmetarestore` 来恢复元数据。\n\n    mfsmetarestore -a -d /storage/mfsmaster\n\n如果 master 节点故障严重无法启动，可以利用 metalogger 节点的元数据备份来恢复。\n首先在选定的节点上按照 mfsmaster，使用之前 master 节点的相同配置；\n从备份设备或 metalogger 拷贝 `metadata.mfs.back` 文件到新 master 节点；\n从 metalogger 拷贝失败前元数据最新的 changelog 文件（`changelog.*.mfs`）；\n执行 `mfsmetarestore -a`。\n\n### Automated Failover\n\n生产环境使用 MooseFS 时，需要保证 master 节点的高可用。\n使用 `ucarp` 是一种比较成熟的方案。\n\n`ucarp` 类似于 `keepalived`，通过主备服务器间的健康检查来发现集群状态，并执行相应操作。\n\n# 日常问题及修复方法\n\n## Client 操作与修复\n客户端强制 `kill -9` 杀掉 `mfsmount` 进程，需要先 `umount` , 然后再 `mount` , 否则会提示：\n\n```\nfuse: bad mount point `/mnt/test/': Transport endpoint is not connected\nsee: /data/jingbo.li/mfs/bin/mfsmount -h for help\n```\n\n### 关于修复\n使用过程中遭遇 master 断电导致服务停止，可以使用 `mfsmetarestore -a` 修复才能启动，如果无法修复，使用 `metalogger` 上的备份日志进行恢复：`mfsmetarestore -m metadata.mfs.back -o metadata.mfs changelog_ml.*.mfs` , 但是此方法也不是万能的，但凡此过程 chunks 块出现问题，可以使用 `mfsfilerepair` 进行修复。\n\n`mfsfilerepair` 主要是处理坏文件的（如写操作引起的 I/O 错误）使文件能够部分可读. 作用如下：在丢失块的情况下使用 0 对丢失文件进行填充；在块的版本号不匹配时设置快的版本号为 master 上已知的能在 chunkerservers 找到的最高版本号；\n\n注意：\n\n> 因为在第二种情况的内容不匹配，可能发生在块具有相同的版本，建议进行文件的拷贝（而不是进行不快照！), 并删除原始文件再进行文件的修复. 恢复后会有文件丢失或损坏。\n\n* * *\n\n## Chunker 的空间\n查看 MooseFS 文件的使用情况，请使用 `mfsdirinfo` 命令，相当于 `df`.\n\n* * *\n\n## 快照 snapshot\n可以快照任何一个文件或目录，语法：`mfsmakesnapshot src dst` , 但是 src 和 dst 必须都属于 mfs 体系，即不能 mfs 体系中的文件快照到其他文件系统。\n\n* * *\n\n## mfsappendchunks\n追加 chunks 到一个文件，追加文件块到另一个文件. 如果目标文件不存在，则会创建一个空文件，然后继续将块进行追加。\n\n* * *\n\n## 回收站机制\n其实 MFS 有类似 windows 的回收站这种机制，当文件不小心删除了，不用担心，去回收站去找. 随时可以恢复. 当然，我所说的随时随地恢复要看你回收站的数据保存多长时间了（默认 24 小时）.\n\n* 首先挂载辅助系统\n\n单独安装或挂载 **MFSMETA** 文件系统，它包含目录 /trash （包含仍然可以被还原的删除文件的信息）和 `/trash/undel` （用于获取文件）, 用一个 `-m` 或 `-o mfsmeta` 的选项，这样可以挂接一个辅助的文件系统 MFSMETA, 这么做的目的是对于意外的从 MooseFS 卷上删除文件或者是为了释放磁盘空间而移动的文件而又此文件又过去了垃圾文件存放期的恢复。\n> 例如：\n\n        mfsmount -m /mnt/mfsmeta -H mfs1.com.org\n        或者\n        mfsmount -o mfsmeta -H mfs1.com.org /mnt/mfsmeta\n需要注意的是，如果要挂载 mfsmeta, 一定要在 mfsmaster 的 mfsexports.cfg 文件中加入如下条目：\\* . rw\n\n挂载后在 /mnt/mfsmeta 目录下分 reserved 和 trash 两个目录，trash 为已删除文件存放目录，删除时间根据 mfsgettrashtime 设置时间来自动删除。\n\n* 设置文件或目录的删除时间\n> 一个删除的文件能够存放在“ 垃圾箱”中的时间称为隔离时间，这个时间可以用 `mfsgettrashtime` 命令来查看：\n    默认时间为 86400, 即时间为 24 小时\n\n```\n[root@linux mnt]# mfsgettrashtime filename（某文件名）\nfilename: 86400\n```\n> 用 `mfssettrashtime` 命令来设置上面的这个有效时间，要注意的是，保存时间单位为秒。\n\n```\n[root@Linux mnt]# mfssettrashtime 60 filename\nfilename: 60\n```\n\n* 恢复删除的文件\n\n把删除的文件移到 /trash/undel 下，就可以恢复此文件. 在 MFSMETA 的目录里，除了 `trash` 和 `trash/undel` 两个目录，还有第三个目录 `reserved` , 该目录内有已经删除的文件，但却被其他用户一直打开着。\n在用户关闭了这些被打开的文件后，`reserved` 目录中的文件将被删除，文件的数据也将被立即删除. 此目录不能进行操作。\n\n\n\n# MFS 集群的维护\n\n## 启动和停止 MFS 集群\n\n***启动***\n\n最安全的启动 MooseFS 集群（避免任何读或写的错误数据或类似的问题）的方式是按照以下命令步骤：\n```\n(1) 启动 mfsmaster 进程\n(2) 启动所有的 mfschunkserver 进程\n(3) 启动 mfsmetalogger 进程（如果配置了 mfsmetalogger）\n(4) 当所有的 chunkservers 连接到 MooseFS master 后，任何数目的客户端可以利用 mfsmount 去挂接被 export 的文件系统。（可以通过检查 master 的日志或是 CGI 监视器来查看是否所有的 chunkserver 被连接）。\n```\n***停止***\n\n```\n(1) 在所有的客户端卸载 MooseFS 文件系统（用 umount 命令或者是其它等效的命令）\n(2) 用 mfschunkserver stop 命令停止 chunkserver 进程\n(3) 用 mfsmetalogger stop 命令停止 metalogger 进程\n(4) 用 mfsmaster stop 命令停止 master 进程\n```\n## MFS chunkservers 的维护\n若每个文件的 goal（目标）都不小于 2，并且没有 under-goal 文件（这些可以用 mfsgetgoal -r 和 mfsdirinfo 命令来检查），那么一个单一的 chunkserver 在任何时刻都可能做停止或者是重新启动。以后每当需要做停止或者是重新启动另一个 chunkserver 的时候，要确定之前的 chunkserver 被连接，而且要没有 under-goal chunks。\n\n## MFS 元数据备份\n\n通常元数据有两部分的数据：\n> + 主要元数据文件 metadata.mfs，当 mfsmaster 运行的时候会被命名为 metadata.mfs.back\n> + 元数据改变日志 changelog.*.mfs，存储了过去的 N 小时的文件改变（N 的数值是由 BACK_LOGS 参数设置的，参数的设置在 mfschunkserver.cfg 配置文件中）。\n主要的元数据文件需要定期备份，备份的频率取决于取决于多少小时 changelogs 储存。元数据 changelogs 实时的自动复制。\n\n## MFS Master 的恢复\n\n一旦 mfsmaster 崩溃（例如因为主机或电源失败），需要最后一个元数据日志 changelog 并入主要的 metadata 中。这个操作时通过 mfsmetarestore 工具做的，最简单的方法是：\n\n$/usr/local/mfs/bin/mfsmetarestore -a\n\n如果 master 数据被存储在 MooseFS 编译指定地点外的路径，则要利用 -d 参数指定使用，如：\n\n$/usr/local/mfs/bin/mfsmetarestore -a -d /opt/mfsmaster\n\n## 从 MetaLogger 中恢复 Master\n\n有些童鞋提到：如果 mfsmetarestore -a 无法修复，则使用 metalogger 也可能无法修复，暂时没遇到过这种情况，这里不暂不考虑。\n找回 metadata.mfs.back 文件，可以从备份中找，也可以中 metalogger 主机中找（如果启动了 metalogger 服务），然后把 metadata.mfs.back 放入 data 目录，一般为{prefix}/var/mfs\n从在 master 宕掉之前的任何运行 metalogger 服务的服务器上拷贝最后 metadata 文件，然后放入 mfsmaster 的数据目录。\n利用 mfsmetarestore 命令合并元数据 changelogs，可以用自动恢复模式 mfsmetarestore –a，也可以利用非自动化恢复模式\n$mfsmetarestore -m metadata.mfs.back -o metadata.mfs changelog_ml.*.mfs\n或：强制使用 metadata.mfs.back 创建 metadata.mfs，可以启动 master，但丢失的数据暂无法确定。\n"
  },
  {
    "path": "doc/store/store.md",
    "content": "# DAS/SAN/NAS\n\n目前常见的三种存储结构\n\n> * DAS：直连存储\n> * SAN：存储区域网\n> * NAS：网络附属存储\n\n<!-- vim-markdown-toc GFM -->\n* [DAS](#das)\n* [SAN](#san)\n* [NAS](#nas)\n    * [nfs(UNIX 和 UNIX 之间共享协议）](#nfsunix-和-unix-之间共享协议)\n        * [NFS 搭建](#nfs-搭建)\n        * [启动 NFS 服务端](#启动-nfs-服务端)\n        * [配置 NFS 服务端](#配置-nfs-服务端)\n        * [配置 Linux NFS 客户端](#配置-linux-nfs-客户端)\n        * [配置 Windows NFS 客户端](#配置-windows-nfs-客户端)\n        * [常见问题](#常见问题)\n            * [rpcbind 安装失败](#rpcbind-安装失败)\n            * [nfs 客户端挂载失败](#nfs-客户端挂载失败)\n            * [nfs 客户端无法 chown](#nfs-客户端无法-chown)\n    * [CIFS(UNIX 和 windows 间共享协议）](#cifsunix-和-windows-间共享协议)\n        * [给挂载共享文件夹指定 owner 和 group](#给挂载共享文件夹指定-owner-和-group)\n        * [给 mount 共享文件夹所在组的写权限](#给-mount-共享文件夹所在组的写权限)\n        * [永久挂载 Windows 共享](#永久挂载-windows-共享)\n\n<!-- vim-markdown-toc -->\n# DAS\n\nDAS :  Application --> File system --> Disk Storage\n\nDAS：直连式存储依赖服务器主机操作系统进行数据的 IO 读写和存储维护管理，数据备份和恢复要求占用服务器主机资源（包括 CPU、系统 IO 等）\n\n# SAN\n\nSAN :  Application --> File system --> Networking --> Disk Storage\n\nIPSAN 与 FCSAN\n\n# NAS\n\nNAS :  Application --> Networking --> File system --> Disk Storage\n\nNAS，网络附加存储，中心词\"存储\"，是的，它是一个存储设备。\n\nNAS 是一个设备。CIFS/NFS 是一种协议。可以在 NAS 上启用 CIFS/NFS 协议，这样，用户就能使用 CIFS/NFS 协议进行访问了。\n\n**一句话，CIFS 用于 UNIX 和 windows 间共享，而 NFS 用于 UNIX 和 UNIX 之间共享**\n\n## nfs(UNIX 和 UNIX 之间共享协议）\n\nNFS 的基本原则是“容许不同的客户端及服务端通过一组 RPC 分享相同的文件系统”，它是独立于操作系统，容许不同硬件及操作系统的系统共同进行文件的分享。\n\n### NFS 搭建\n\n> NFS 服务端部署环境准备\n\n服务器系统|角色|IP|\n----|----|----|\nCentOS6.6 x86_64|NFS 服务端（NFS-SERVER）|192.168.1.21|\nCentOS6.6 x86_64|NFS 客户端（NFS-CLIENT1）|192.168.1.22|\n\n服务器版本：6.x\n\n>  NFS 软件列表\n\n`NFS`可以被视为一个`RPC`程序，在启动任何一个`RPC`程序之前，需要做好端口的对应映射作用，这个映射工作就是由`rpcbind`服务来完成的，因此在提供`NFS`之前必须先启动`rpcbind`服务\n\n首先准备以下软件包\n* nfs-utils（NFS 服务主程序，包括 rpc.nfsd、rpc.mountd 两个 deamons 和相关文档说明及执行命令文件等）\n* rpcbind\n\n>  安装 NFS 软件包\n\n三台机器都需要安装`NFS`软件包，showmount 命令在`NFS`包中，客户端`NFS`服务不配置，不启动\n\n**安装 NFS 软件包**\n```\n[root@nfs-server ~]$ yum install nfs-utils rpcbind -y\n```\n\n>  NFS 服务器配置\n\n* `NFS`的常用目录\n\n| 目录路径|目录说明|\n|----|----|\n| /etc/exports | NFS 服务的主要配置文件|\n| /usr/sbin/exportfs | NFS 服务的管理命令|\n| /usr/sbin/showmount | 客户端的查看命令|\n| /var/lib/nfs/etab | 记录 NFS 分享出来的目录的完整权限设定值|\n| /var/lib/nfs/rtab | 记录连接的客户端信息|\n\n\n* `NFS`服务端的权限设置，`/etc/exports`文件配置格式中小括号中的参数\n\n| 参数名称 (*为重要参数）|参数用途|\n| ---- | ---- |\n|rw*|Read-write，表示可读写权限|\n|ro|Read-only，表示只读权限|\n|sync*|请求或写入数据时，数据同步写入到 NF SServer 中，（优点：数据安全不会丢，缺点：性能较差）|\n|async*|请求或写入数据时，先返回请求，再将数据写入到 NFSServer 中，异步写入数据|\n|no_root_squash|访问 NFS Server 共享目录的用户如果是 root 的话，它对共享目录具有 root 权限|\n|not_squash|访问 NFS Server 共享目录的用户如果是 root 的话，则它的权限，将被压缩成匿名用户|\n|all_squash*|不管访问 NFS Server 共享目录的身份如何，它的权限都被压缩成一个匿名用户，同事它的 UID、GID 都会变成 nfsnobody 账号身份|\n|anonuid*|匿名用户 ID|\n|anongid*|匿名组 ID|\n|insecure|允许客户端从大于 1024 的 TCP/IP 端口连 NFS 服务器|\n|secure|限制客户端只能从小于 1024 的 TCP/IP 端口连接 NFS 服务器（默认设置）|\n|wdelay|检查是否有相关的写操作，如果有则将这些写操作一起执行，这样可提高效率（默认设置）|\n|no_wdelay|若有写操作则立即执行（应与 sync 配置）|\n|subtree_check|若输出目录是一个子目录，则 NFS 服务器将检查其父目录的权限（默认设置）|\n|no_subtree_check|即使输出目录是一个子目录，NFS 服务器也不检查其父目录的权限，这样做可提高效率|\n\n### 启动 NFS 服务端\n\n    ```\n    # 启动 rpcbind 状态\n    [root@nfs-server ~]# /etc/init.d/rpcbind start\n    Starting rpcbind:                                   [  OK  ]\n\n    # 查看 rpcbind 状态\n    [root@nfs-server ~]# /etc/init.d/rpcbind status\n    rpcbind (pid  1826) is running...\n\n    # 查看 rpcbind 默认端口 111\n    [root@nfs-server ~]# lsof -i :111\n    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME\n    rpcbind 1826  rpc  6u IPv4 12657  0t0  UDP *:sunrpc\n    rpcbind 1826  rpc  8u IPv4 12660  0t0  TCP *:sunrpc (LISTEN)\n    rpcbind 1826  rpc  9u IPv6 12662  0t0  UDP *:sunrpc\n    rpcbind 1826  rpc 11u IPv6 12665  0t0  TCP *:sunrpc (LISTEN)\n\n    # 查看 rpcbind 服务端口\n    [root@nfs-server ~]# netstat -lntup|grep rpcbind\n    tcp  0 0 0.0.0.0:111  0.0.0.0:*        LISTEN   1826/rpcbind\n    tcp  0 0 :::111       :::*             LISTEN   1826/rpcbind\n    udp  0 0 0.0.0.0:729  0.0.0.0:*                 1826/rpcbind\n    udp  0 0 0.0.0.0:111  0.0.0.0:*                 1826/rpcbind\n    udp  0 0 :::729       :::*                      1826/rpcbind\n    udp  0 0 :::111       :::*                      1826/rpcbind\n\n    # 查看 rpcbind 开机是否自启动\n    [root@nfs-server ~]# chkconfig --list rpcbind\n    rpcbind    0:off   1:off   2:on   3:on  4:on   5:on    6:off\n\n    # 查看 nfs 端口信息（没有发现）\n    [root@nfs-server ~]# rpcinfo -p localhost\n       program vers proto   port  service\n        100000    4   tcp    111  portmapper\n        100000    3   tcp    111  portmapper\n        100000    2   tcp    111  portmapper\n        100000    4   udp    111  portmapper\n        100000    3   udp    111  portmapper\n        100000    2   udp    111  portmapper\n\n    # 启动 NFS 服务\n    [root@nfs-server ~]# /etc/init.d/nfs start\n    Starting NFS services:                             [  OK  ]\n    Starting NFS quotas:                               [  OK  ]\n    Starting NFS mountd:                               [  OK  ]\n    Starting NFS daemon:                               [  OK  ]\n    正在启动 RPC idmapd：                              [确定]\n\n    # 设置 nfs 开机自启动\n    [root@nfs-server ~]# chkconfig nfs on\n\n    # 查看 nfs 开机是否启动（已打开）\n\n    如何确定`rpcbind`服务一定在`NFS`服务之前启动？？？\n\n    # 无须调整，默认 rpcbind 开机顺序为 13，nfs 为 30\n    [root@nfs-server ~]# cat /etc/init.d/rpcbind|grep 'chkconfig'\n    # chkconfig: 2345 13 87（开机启动顺序 13）\n\n    [root@nfs-server ~]# cat /etc/init.d/nfs|grep 'chkconfig'\n    # chkconfig: - 30 60（开机启动顺序 30）\n    ```\n\n### 配置 NFS 服务端\n\nNFS 配置文件为 /etc/exports\n\n**配置格式**\n\n    ```\n    /etc/exports 配置文件格式\n    NFS 共享的目录 NFS 客户端地址 (arg1，arg2...)\n    NFS 共享的目录 NFS 客户端地址 1(arg1，arg2...) 客户端地址 2(arg1,arg2...)\n    ```\n如\n    ```\n     /home/share 192.168.102.15(rw,sync) *(ro)\n    ```\n\n**配置实例**\n\n    ```\n\n    # 创建共享目录\n    mkdir /data\n\n    # NFS 配置文件添加共享目录相关信息\n    cat >>/etc/exports<< EOF\n    ########nfs sync dir by zhangjie at 20150909########\n    /data  *(rw,sync,all_squash)\n    EOF\n\n    # NFS 平滑生效\n    /etc/init.d/nfs reload\n\n    # 查看共享记录\n    [root@nfs-server ~]# showmount -e localhost\n    Export list for localhost:\n    /data *\n\n    # 本机挂载测试\n    [root@nfs-server ~]# mount -t nfs 192.168.1.21:/data /mnt\n\n    # 查看是否已经挂载成功\n    [root@nfs-server ~]# df -h\n    Filesystem          Size  Used Avail Use% Mounted on\n    /dev/sda3            18G  1.6G   15G  10% /\n    tmpfs               491M     0  491M   0% /dev/shm\n    /dev/sda1           190M   61M  120M  34% /boot\n    192.168.0.1:/data   18G  1.6G   15G  10% /mnt\n\n    # 配置例子\n    /ceshi_test *(rw,sync,no_root_squash,nohide,no_root_squash,no_subtree_check,sync)\n    ```\n\n### 配置 Linux NFS 客户端\n    ```\n    # 启动 rpcbind 服务\n    [root@lamp01 ~]# /etc/init.d/rpcbind start\n    Starting rpcbind:                                   [  OK  ]\n\n    # 测试是否可以连接 NFS 服务器\n    [root@client ~]# showmount -e 192.168.1.21\n    Export list for 192.168.1.21:\n    /data *\n\n    # 挂载客户端 NFS 服务\n    [root@lamp01 ~]# mount -t nfs 192.168.1.21:/data /mnt\n\n    # 查看是否挂载成功\n    [root@lamp01 ~]# df -h\n    Filesystem          Size  Used Avail Use% Mounted on\n    /dev/sda3            18G  1.6G   15G  10% /\n    tmpfs               491M     0  491M   0% /dev/shm\n    /dev/sda1           190M   61M  120M  34% /boot\n    192.168.1.21:/data   18G  1.6G   15G  10% /mnt\n\n    # 查看 NFS 服务器完整参数配置（仔细看默认添加了很多参数，这里的 anonuid 用户、anongid 组）\n    [root@nfs-server /]# cat /var/lib/nfs/etab\n    /data   *(rw,sync,wdelay,hide,nocrossmnt,secure,root_squash,no_all_squash,no_subtree_check,secure_locks,acl,anonuid=65534,anongid=65534,sec=sys,rw,root_squash,no_all_squash)\n\n    # 查看用户组为 65534 的用户（nfsnobody 用户）\n    [root@nfs-server /]# grep '65534' /etc/passwd\n    nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin\n\n    # 更改目录所属用户、所属组\n    [root@nfs-server /]# chown -R nfsnobody.nfsnobody /data/\n\n    # 查看目录所属用户、所属组\n    [root@nfs-server /]# ls -ld /data/\n    drwxr-xr-x 2 nfsnobody nfsnobody 4096 9 月   8 07:16 /data/\n\n    > NFS 系统安全挂载\n\n    一般`NFS`服务器共享的只是普通的静态数据（图片、附件、视频等等），不需要执行 suid、exec 等权限，挂载的这个文件系统，只能作为存取至用，无法执行程序，对于客户端来讲增加了安全性，（如：很多木马篡改站点文件都是由上传入口上传的程序到存储目录，然后执行的）注意：非性能的参数越多，速度可能越慢\n\n    # 安全挂载参数（nosuid、noexec、nodev）\n    mount -t nfs nosuid,noexec,nodev,rw 192.168.1.21:/data /mnt\n\n    # 禁止更新目录及文件时间戳挂载（noatime、nodiratime）\n    mount -t nfs noatime,nodiratime 192.168.1.21:/data /mnt\n\n    # 安全加优化的挂载方式（nosuid、noexec、nodev、noatime、nodiratime、intr、rsize、wsize）\n    mount -t nfs -o nosuid,noexec,nodev,noatime,nodiratime,intr,rsize=131072,wsize=131072 192.168.1.21:/data /mnt\n\n    # 默认挂载方式（无）\n    mount -t nfs 192.168.24.7:/data /mnt\n    ```\n### 配置 Windows NFS 客户端\n\n    ```\n    启动 windos NFS 客户端服务：\n    1. 打开控制面板 ->程序 ->打开或关闭 windows 功能 ->NFS 客户端\n    勾选 NFS 客户端，即开启 windows NFS 客户端服务。\n\n    2.win+R->cmd\n    mount 192.168.1.21:/data X:\n    成功挂载，打开我的点脑，你即可在你网络位置看到 X: 盘了\n\n    X: 你挂载的网络文件盘 -- 注意，可能会与你的其他盘冲突，你可以随意更改\n\n    3. 取消挂载\n    直接在 我的电脑 里面鼠标点击取消映射网络驱动器 X:\n    或者：win+R->cmd\n    输入：umount X:\n    (umount -a 取消所有网络驱动器）\n    ```\n\n### 常见问题\n#### rpcbind 安装失败\n\nyum 安装时提示如下\n\n```\nerror: %pre(rpcbind-0.2.0-12.el6.x86_64) scriptlet failed, exit status 6\nError in PREIN scriptlet in rpm package rpcbind-0.2.0-12.el6.x86_64\nerror:   install: %pre scriptlet failed (2), skipping rpcbind-0.2.0-12.el6\nVerifying  : rpcbind-0.2.0-12.el6.x86_64                     1/1\nFailed:\n    rpcbind.x86_64 0:0.2.0-12.el6\n```\n\n因为通过 chattr +i 把 /etc/passwd /etc/group /etc/shadow /etc/gshadow 锁定了。\n\nchattr -i 解锁后，问题解决。\n\n#### nfs 客户端挂载失败\n\n现象：客户端 mount 失败，同时 rpcbind 服务是停止的，怎么都启不来\nmount 时提示如下：\n```\nmount.nfs: rpc.statd is not running but is required for remote locking.\nmount.nfs: Either use '-o nolock' to keep locks local, or start statd.\nmount.nfs: an incorrect mount option was specified\n```\n调试后发现，rpcbind 要求在 /etc/sysconfig/network 文件里写一个 NETWORKING=yes 才行，加上配置后，即可启动 rpcbind 服务\n\n#### nfs 客户端无法 chown\n\nnfs 常规配置后，客户端可以创建，删除，chmod；但无法修改属主和属组；\n\n解决方法：\n\n挂载时，加上 vers=3 即可，例：\n```\n#mount -t nfs -o vers=3 server:/share /mnt\n```\n## CIFS(UNIX 和 windows 间共享协议）\n\n在 Linux 上连接 windows 上 NAS 设备时，需要 cifs-utils 支持\n```\n#yum -y install cifs-utils\n```\n\n### 给挂载共享文件夹指定 owner 和 group\n\n在服务器部署的时候需要把文件夹设置在 windows 的共享文件上。在使用 mount 命令挂载到 linux 上后。文件路径和文件都是可以访问，但是不能写入，导致系统在上传文件的时候提示“权限不够，没有写权限”。用\"ls-l\"查看挂载文件的权限设置是 drwxr-xr-x, 很明显没有写权限。想当然使用 chmod 来更改文件夹权限，结果提示权限不够。root 和当前用户都不能正常修改权限。\n\n可以添加两个参数即可达到我们所要的效果：\n\n```\n#mount -t cifs -o username=\"***\",password=\"***\",gid=***,uid=**** //WindowsHost/sharefolder  /home/xxx/shared\n```\ngid 和 uid，可以使用 id username 来获得\n\n### 给 mount 共享文件夹所在组的写权限\n\n```\n#mount -t cifs -o username=\"Administrator\",password=\"PasswordForWindows\",uid=test_user,gid=test_user,dir_mode=0777 //192.168.1.2/test /mnt/\n```\n### 永久挂载 Windows 共享\n\n```\n#mount -t cifs -o username=\"***\",password=\"***\",gid=500,uid=500 //WindowsHost/sharefolder  /home/xxx/shared\n```\n如上挂载时，可写入 fstab 文件\n\n```\n//WindowsHost/sharefolder /home/xxx/shared cifs username=***,password=***,uid=500,gid=500 0 0\n```\n遗憾的是，此命令具有明显的安全问题，因为您必须在 /etc/fstab 条目中公开密码，而文件 /etc/fstab 通常可供系统上的每个用户读取。要解决此问题，可使用 credentials 挂载选项将用户名和密码放在指定的文本文件中。例如：\n\n```\n//WindowsHost/sharefolder /home/xxx/shared cifs credentials=/etc/cred.ceshi,ui500,gid=500 0 0\n```\n一个 credentials 文件的格式如下所示：\n```\nusername=***\npassword=MYPASSWORD\n```\n然后可使用以下命令，使 /etc/cred.ceshi 文件仅可供 root 用户（必须以其身份执行 mount 命令的用户）读取：\n```\n#chmod 600 /etc/cred.ceshi\n```\n"
  },
  {
    "path": "doc/web/README.md",
    "content": "# web篇\n\n> * web 基础\n> * nginx\n> * django\n> * butterfly 【推荐】\n"
  },
  {
    "path": "doc/web/butterfly.md",
    "content": "# butterfly\n\n<div align=center><img src=\"https://github.com/meetbill/butterfly/blob/master/images/butterfly.png\" width=\"350\"/></div>\n\n蝴蝶（轻量化 Web 框架）如同蝴蝶一样，此框架小而美，简单可依赖\n\n```\n    __          __  __            ______\n   / /_  __  __/ /_/ /____  _____/ __/ /_  __\n  / __ \\/ / / / __/ __/ _ \\/ ___/ /_/ / / / /\n / /_/ / /_/ / /_/ /_/  __/ /  / __/ / /_/ /\n/_.___/\\__,_/\\__/\\__/\\___/_/  /_/ /_/\\__, /\n                                    /____/\n```\n<!-- vim-markdown-toc GFM -->\n\n* [1 简介](#1-简介)\n    * [1.1 环境](#11-环境)\n    * [1.2 特性](#12-特性)\n* [2 五分钟 Butterfly 体验指南](#2-五分钟-butterfly-体验指南)\n    * [2.1 五分钟体验之部署 Butterfly（预计 1 分钟）](#21-五分钟体验之部署-butterfly预计-1-分钟)\n    * [2.2 五分钟体验之编写 handler （预计 3 分钟）](#22-五分钟体验之编写-handler-预计-3-分钟)\n    * [2.3 五分钟体验之调试 handler （预计 1 分钟）](#23-五分钟体验之调试-handler-预计-1-分钟)\n* [3 设计概述](#3-设计概述)\n    * [3.1 架构](#31-架构)\n    * [3.2 WSGI server 服务器模型](#32-wsgi-server-服务器模型)\n    * [3.3 WSGI App MVC 模型](#33-wsgi-app-mvc-模型)\n        * [3.3.1 Model 是核心](#331-model-是核心)\n        * [3.3.2 本框架中的 MVC](#332-本框架中的-mvc)\n    * [3.4 路由自动生成](#34-路由自动生成)\n* [4 使用手册](#4-使用手册)\n    * [4.1 手册传送门](#41-手册传送门)\n    * [4.2 举个栗子](#42-举个栗子)\n* [5 版本信息](#5-版本信息)\n\n<!-- vim-markdown-toc -->\n\n## 1 简介\n\n### 1.1 环境\n\n```\nenv:Python 2.7\n```\n### 1.2 特性\n\n```\n# GET 不带参数\nGET http://IP:PORT/{app}/{handler}\n\n# GET 带参数\nGET http://IP:PORT/{app}/{handler}?args1=value1\n\n# POST 参数时，数据类型需要是 application/json\n\n如：\ncurl -v \"http://127.0.0.1:8585/x/ping\"                                  ===> handlers/x/__init__.py:ping()\ncurl -v \"http://127.0.0.1:8585/x/hello?str_info=meetbill\"               ===> handlers/x/__init__.py:hello(str_info=meetbill)\ncurl -v  -d '{\"str_info\":\"meetbill\"}' http://127.0.0.1:8585/x/hello     ===> handlers/x/__init__.py:hello(str_info=meetbill)\n```\n\n> * 根据 handlers package 下 package 目录结构自动加载路由（目前不支持动态路由）\n>   * 只加载 handlers package 及其子 package 下符合条件的函数作为 handler\n> * 自定义 HTTP header\n> * Handler 的参数列表与 HTTP 请求参数保持一致，便于接口开发\n> * 自动对 HTTP 请求参数进行参数检查\n> * 请求的响应 Header 中包含请求的 reqid（会记录在日志中）, 便于进行 trace\n> * 简易方便的 DEBUG\n\n## 2 五分钟 Butterfly 体验指南\n\n### 2.1 五分钟体验之部署 Butterfly（预计 1 分钟）\n> 部署\n```\n$ wget https://github.com/meetbill/butterfly/archive/master.zip -O butterfly.zip\n$ unzip butterfly.zip\n$ cd butterfly-master/butterfly\n```\n> 配置端口 --- 默认 8585 端口，若无需修改可进入下一项启动\n```\nconf/config.py\n```\n> 启动\n```\n$ bash run.sh start\n```\n> 访问\n```\n$ curl \"http://127.0.0.1:8585/x/ping\"\n{\"stat\": \"OK\", \"randstr\": \"...\"}\n\n$ curl \"http://127.0.0.1:8585/x/hello?str_info=meetbill\"\n{\"stat\": \"OK\", \"str_info\": \"meetbill\"}\n```\n###  2.2 五分钟体验之编写 handler （预计 3 分钟）\n\n> 创建 app （handlers 目录下的子目录均为 app）, 直接拷贝个现有的 app 即可\n```\n$ cp -rf handlers/x handlers/test_app\n```\n> 新增 handler (app 目录下的`__init__.py` 中编写 handler 函数）\n```\n$ vim handlers/test_app/__init__.py\n\n新增如下内容：\n\n# ------------------------------ handler\n@funcattr.api\ndef test_handler(req, info):\n    return retstat.OK, {\"data\": info}\n```\n> 重启服务\n```\n$ bash run.sh restart\n```\n\n> 访问\n```\n$ curl \"http://127.0.0.1:8585/test_app/test_handler?info=helloworld\"\n{\"stat\": \"OK\", \"data\": \"helloworld\"}\n```\n\n###  2.3 五分钟体验之调试 handler （预计 1 分钟）\n\n假如编写的 handler 不符合预期的时候，可以通过 test_handler.py 进行调试\n\n> 调试刚才编写的 test_handler\n```\n$ python test_handler.py /test_app/test_handler helloworld\n... 此处会输出彩色的  DEBUG 信息\nSource path:... test_handler.py\n>>>>>|19:03:53.293076 4694912448-MainThread call        66             def main():\n------19:03:53.294108 4694912448-MainThread line        67                 return func(*args)\n    Source path:... /Users/meetbill/butterfly-master/butterfly/handlers/test_app/__init__.py\n    Starting var:.. req = <xlib.httpgateway.Request object at 0x10b4148d0>\n    Starting var:.. info = 'helloworld'\n    >>>>>|19:03:53.294402 4694912448-MainThread call        49 def test_handler(req, info):\n    ------19:03:53.294864 4694912448-MainThread line        50     return retstat.OK, {\"data\": info}\n    |<<<<<19:03:53.294989 4694912448-MainThread return      50     return retstat.OK, {\"data\": info}\n    Return value:.. ('OK', {'data': 'helloworld'})\nSource path:... test_handler.py\n|<<<<<19:03:53.295114 4694912448-MainThread return      67                 return func(*args)\nReturn value:.. ('OK', {'data': 'helloworld'})\nElapsed time: 00:00:00.002140\n=============================================================\n('OK', {'data': 'helloworld'})\n=============================================================\n```\n\n## 3 设计概述\n\n### 3.1 架构\n\n```\n         +---------------------------------------------------------+\n         |                       WEB brower                        |\n         +---------------------------------------------------------+\n             |                           ^       ^          ^\n             |                           |       |          |\n             |HTTP request               |       |          |\n             |                           |       |          |\n      -- +---V-----------------------------------------------------+\n    /    |                       HTTPServer                        |  WSGI server\n    |    |     +-------------------+ put +-------------------+     |\n    |    |     |ThreadPool(Queue) <+-----+ HTTPConnection    |     |\n    |    |     |+---------------+  |     | +---------------+ |     |\n    |    |     ||WorkerThread   |  |     | |HTTPRequest    | |     |\n    |    |     |+---------------+  |     | +---------------+ |     |\n    |    |     +-------------------+     +-------------------+     |\n    |    +---------------------------------------------------------+\n    |          /------------\\            ^       ^          ^\n    |         |   environ    |           |       |          |\n    |          \\------------/            |       |          |\n    |   .............|...................|.......|..........|.........WSGI\n    |                |                   |       |          |\n    |         +------V-------+           |       |          |\n    |         |      req     |           |       |          |\n    |         +--------------+           |       |          |\nButterfly            |                   |       |          |\n    |         +------V-------+           |       |          |\n    |         |apiname_getter|           |       |          |\n    |         +--------------+           |       |          |\n    |                |                   |       |          |\n    |       +--------V--------+ False +--+--+    |          |\n    |       |is_protocol_exist|------>| 400 |    |          |\n    |       +-----------------+       +-----+    |          |\n    |                |                           |          |\n    |                | (protocol_process)        |          |\n    |                V                           |          |\n    |       +-----------------+                  |          |\n    |       | protocol        |  Exception    +-----+       |\n    |       | +-------------+ |---------------| 500 |       |\n    |       | |/app1/handler| |               +-----+       |\n    |       | |/app2/handler| |               +----------------------------+\n    |       | +-------------+ |---------------|httpstatus, headers, content|\n    \\       +-----------------+               +----------------------------+\n     ---\n```\n\n### 3.2 WSGI server 服务器模型\n\n> 模型\n```\nThreadPool 线程池模型\n```\n> 核心流程\n```\n主线程不断执行 tick() 函数，tick() 用于接受新连接（使用 HTTPConnection 封装连接）并将其放入 ThreadPool 的队列\n\nThreadpool 管理的 WorkerThread 进行处理请求\n```\n> 主要的类\n```\n* HTTPServer      : HTTP 服务程序，基于 socket，监听在某个端口上，比如：localhost:8585\n* 线程池\n  * ThreadPool    : 线程池，有一个消息队列，所有的线程都等在这个消息队列上\n  * WorkerThread  : HTTP 请求的处理线程，它由 ThreadPool 统一管理，它等着 ThreadPool 消息队列中的消息\n* 主线程\n  * HTTPConnection: HTTP 连接，WorkerThread 的实际处理体\n  * HTTPRequest   : HTTP 请求，HTTP 请求和响应都由该类处理\n```\n### 3.3 WSGI App MVC 模型\n\n#### 3.3.1 Model 是核心\n\nModel，也就是模型，是对现实的反映和简化。**对问题的本质的描述就是 Model。解决问题就是给问题建立 Model。**\n\n当我们关注业务问题时，只有描述 “用户所关心的问题” 的代码才是 Model。当你的关注转移到其他问题时，Model 也会相应发生变化。\n\n**失去了解决特定问题这一语境，单谈 Model 没有意义。**\n\n可以说，View 和 Controller 是 Model 的一部分。\n\n> 为什么人们要单独把 View 和 Controller 跟 Model 分开呢？\n```\nView 和 Controller 是外在的东西，只有 Model 是本质的东西。\n外在的东西天天变化，但很少影响本质。把他们分开比较容易维护。\n```\n\n#### 3.3.2 本框架中的 MVC\n\n简单来说，Controller 和 View 分别是 Model 的 输入 和 输出。\n\n> * Model       : 也就是模型，是对现实的反映和简化\n> * View        : 也就是视图 / 视野，是你真正看到的，而非想象中的 Model\n> * Controller  : 也就是控制器，是你用来改变 Model 方式\n\nView 建议在 [butterfly-fe](https://github.com/meetbill/butterfly-fe) 实现，即前后端分离\n\n使用 butterfly 框架时主要是编写 handler\n```\n                                               handler\n                              +-----------------------------------------+\n    +-----------+ user action | +------------+  update  +-------------+ |\n    |   view    |-------------+>| controller |--------->|    model    | |\n    |           |<------------+-|            |<---------|             | |\n    +--+--------+  update     | +------------+   notify +-------------+ |\n                              +-----------------------------------------+\n```\n### 3.4 路由自动生成\n\n无需人为进行路由配置操作\n\n> 约定优于配置，配置优于实现\n```\n在 handlers 文件夹下的 package 中的 __init__.py 编写 handler controller 函数来完成 \"功能的抽象\"\ncontroller 函数是第一个参数为 \"req\" 的非私有函数\n```\n重新启动服务时，框架会自动加载 handlers 各 package  `__init__.py` 中符合条件的函数并生成路由\n\n> 条件（第一个参数为 \"req\" 的非私有函数）\n```\n(1) 私有函数不会被加载，即函数名是 \"_\" 开头\n(2) 类不会被加载，即 handler 中 controller 使用函数来完成 \"功能的抽象\"\n(3) 函数的第一个形参名需要是 \"req\"\n    调用此函数时的实参 req 是对 HTTP request environ 的封装\n```\n\n## 4 使用手册\n\n### 4.1 手册传送门\n\n* [Butterfly 手册](https://github.com/meetbill/butterfly/wiki)\n* [Butterfly 示例](https://github.com/meetbill/butterfly-examples)\n* [Butterfly 前端](https://github.com/meetbill/butterfly-fe)\n* [Butterfly nginx 配置](https://github.com/meetbill/butterfly-nginx)\n\n### 4.2 举个栗子\n\n> 厂内线上项目 ([接口认证方案](https://github.com/meetbill/butterfly/wiki/butterfly_cas)), 使用前后端分离 + 单点登录\n>  * 整体方案的后端接口认证使用 nginx auth_request 进行验证\n\n如下是 butterfly-fe（前端） + butterfly-auth（基于 butterfly 框架的后端接口认证） + app-backend(butterfly app) + butterfly-nginx(web 服务） 请求流程\n```\n               +--------------------------------------------------------------------------+\n               |                          butterfly-nginx                                 |\n               +--------------------------------------------------------------------------+\n                       |                     |                                      |\n                       V                     V                                      V\n               +-------------+    +---------------------+                      +----------+\n               |~* /static/  |    |= /auth/verification |                      |/         |\n               |= /index.html|    |= /butterfly_401     |                      |          |\n               |= /          |    |= /auth/ssologin     |                      |          |\n               +-------------+    +---------------------+                      +----------+\n                       |                     |                                       |\n                       V                     V                                       V\n+----------+       +------------+     +--------------+       +----------+     +-----------+\n|web browse|       |butterfly-fe|     |butterfly-auth|       |cas-server|     |app-backend|\n+----------+       +------------+     +--------------+       +----------+     +-----------+\n     |                    |                  |                     |                   |\n     +-------route------->|/                 |                     |                   |\n     |<-------page--------+/index.html       |                     |                   |\n     |                    |                  |                     |                   |\n     ==================================================================== not have token\n     |                    |                  |                     |                   |\n     +--V----------------request api-------------------------------------------------->|\n     |  +-sub request-header not have token->|(/auth/verification) |                   |\n     |<-code=401,targetURL=../auth/ssologin--+                     |                   |\n     |                    |                  |                     |                   |\n     +--window.location.herf=directurl------>|(/auth/ssologin)     |                   |\n     |<----code=302,Location=cas-server------+                     |                   |\n     |                    |                  |                     |                   |\n     +-----302 http://cas-server/login  login page --------------->|(/login)           |\n     |<-------------code=302,set Cookie TGT=xxx -------------------+                   |\n     |                    |                  |                     |                   |\n     +-----302 /auth/ssologin?ticket=xxx --->|                     |                   |\n     |                    |                  +-------check st----->|(/session/validate)|\n     |                    |                  |<-------st vaild-----+                   |\n     |<--code=302 set Cookie butterfly_token-+                     |                   |\n     |                    |                  |                     |                   |\n     +--302 / ----------->|                  |                     |                   |\n     |<-------page--------+/index.html       |                     |                   |\n     |                    |                  |                     |                   |\n     ======================================================================== have token\n     |                    |                  |                     |                   |\n     +---V----------------request api------------------------------------------------->|\n     |   +-sub request-header have token---->|/auth/verification   |                   |\n     |<-------------------response-----------------------------------------------------+\n```\n\n## 5 版本信息\n\n本项目的各版本信息和变更历史可以在[这里][changelog] 查看。\n\n"
  },
  {
    "path": "doc/web/django.md",
    "content": "# django\n\nenv\n\n```bash\n    python 2.6.6\n    django_1.4.22\n\n```\n\n##【目录】\n\n\n<!-- vim-markdown-toc GFM -->\n* [django 开始](#django-开始)\n* [使用 bootstrap](#使用-bootstrap)\n    * [settings](#settings)\n    * [bootstrap](#bootstrap)\n* [django 登陆](#django-登陆)\n    * [flow chart](#flow-chart)\n    * [detailed](#detailed)\n    * [django admin 密码重置](#django-admin-密码重置)\n* [django 输出到固定日志](#django-输出到固定日志)\n    * [将 log 封装成一个单独的 app](#将-log-封装成一个单独的-app)\n    * [编写 log 程序](#编写-log-程序)\n    * [在本项目其他应用中的 view.py 中调用 BLog](#在本项目其他应用中的-viewpy-中调用-blog)\n    * [测试](#测试)\n* [django FAQ](#django-faq)\n* [django 自用开发模板](#django-自用开发模板)\n\n<!-- vim-markdown-toc -->\n\n# django 开始\ndjango\n\nDjango 里是模型（Model）、模板 (Template) 和视图（Views）， Django 也被称为 MTV 框架 。在 MTV 开发模式中：\n* M 代表模型（Model），即数据存取层。 该层处理与数据相关的所有事务：如何存取、如何验证有效\n* T 代表模板 (Template)，即表现层。 该层处理与表现相关的决定：如何在页面或其他类型文档中进行显示。\n* V 代表视图（View），即业务逻辑层。 该层包含存取模型及调取恰当模板的相关逻辑。可以把它看作模型与模板之间的桥梁。\n\n![Screenshot](../../images/django/django.png)\n\n我个人理解：可以把 Template 看作是含有变量的字符串，View 调用模板时，就是将变量传给 Template 的字符串，并将页面显示出来，具体如何显示不是咱们要关心的事，咱们只需要将变量传递给 template 即可\n\n# 使用 bootstrap\ndjango\n## settings\n\n神奇的 Python 内部变量 __file__ , 该变量被自动设置为代码所在的 Python 模块文件名。\n```\nimport os.path\nTEMPLATE_DIRS = (\n    os.path.join(os.path.dirname(__file__), 'templates').replace('\\\\','/'),\n)\n```\nor\n\n```\nimport os.path\nSITE_ROOT = os.path.abspath(os.path.dirname(__file__))\n\nSTATIC_ROOT = os.path.join(SITE_ROOT,'static')\nSTATICFILES_DIRS = (\n    # Don't forget to use absolute paths, not relative paths.\n    (\"css\", os.path.join(STATIC_ROOT,'css')),\n    (\"js\", os.path.join(STATIC_ROOT,'js')),\n    (\"img\", os.path.join(STATIC_ROOT, 'img')),\n    (\"font\", os.path.join(STATIC_ROOT, 'font')),\n    (\"liger\", os.path.join(STATIC_ROOT, 'liger')),\n    (\"bootstrap3\", os.path.join(STATIC_ROOT, 'bootstrap3')),\n)\nTEMPLATE_DIRS = (\n    os.path.join(SITE_ROOT,'templates'),\n)\n```\n## bootstrap\nBootstrap 的使用一般有两种方法。一种是引用在线的 Bootstrap 的样式，一种是将 Bootstrap 下载到本地进行引用。\n使用本地的 Bootstrap\n\n下载 Bootstrap 到本地进行解压，解压完成，你将得到一个 Bootstrap 目录，结构如下：\n\n```\n[root@Linux bootstrap-3.3.5-dist]# tree\n.\n├── css\n│   ├── bootstrap.css\n│   ├── bootstrap.css.map\n│   ├── bootstrap.min.css\n│   ├── bootstrap-theme.css\n│   ├── bootstrap-theme.css.map\n│   └── bootstrap-theme.min.css\n├── fonts\n│   ├── glyphicons-halflings-regular.eot\n│   ├── glyphicons-halflings-regular.svg\n│   ├── glyphicons-halflings-regular.ttf\n│   ├── glyphicons-halflings-regular.woff\n│   └── glyphicons-halflings-regular.woff2\n└── js\n    ├── bootstrap.js\n    ├── bootstrap.min.js\n    └── npm.js\n```\n本地调用如下：\n```\n<!DOCTYPE html>\n<html lang=\"en\">\n    <head>\n        <meta charset=\"utf-8\">\n        <title>Hello Bootstrap</title>\n        <!-- Bootstrap core CSS -->\n        <link\n            href=\"./bootstrap-3.3.5-dist/css/bootstrap.min.css\" rel=\"stylesheet\">\n        <style type='text/css'>\n            body {\n                background-color: #CCC;\n            }\n        </style>\n    </head>\n    <body>\n        <h1>hello Bootstrap<h1>\n    </body>\n</html>\n```\n\n# django 登陆\n\n## flow chart\n```\n +---------+      +---------+       +------------+\n | url.py  |----->| view.py |------>| templates  |\n | (Login) |      | (Login) |       | login.html |\n +---------+      +---------+       +------------+\n\n 在登陆页输入账号密码后，通过认证函数进行认证，如果认证通过跳转到主页，否则重新登陆\n```\n\n## detailed\nurl.py\n```\n  url('^$','strap.view.LogIn'),\n  url('^login/$','strap.view.LogIn'),         //login\n  url('^index/$','strap.view.account_auth'),  //authentication\n  url('^showDashboard/$','strap.view.show'),  //Go to the home page\n\n```\nview.py\n```\n  from django.http import HttpResponseRedirect, HttpResponse\n  from django.shortcuts import render_to_response, render\n  from django.contrib import auth\n  import datetime\n  def index(request):\n      now = datetime.datetime.now()\n      return render(request,'index.html')\n\n  def LogIn(request):\n      if request.user is not None:\n          logout_view(request)\n      return render(request,'login.html')\n\n  def logout_view(request):\n      user = request.user\n      auth.logout(request)\n      # Redirect to a success page.\n      return HttpResponse(\"%s logged out!\" % user)\n\n  def account_auth(request):\n      username = request.POST.get('username')\n      password = request.POST.get('password')\n      tri_user = auth.authenticate(username=username,password=password)\n      if tri_user is not None:\n      auth.login(request,tri_user)\n      return HttpResponseRedirect('/showDashboard')\n      else:\n      return render_to_response('login.html',{'login_err':'Wrong username or password!'})\n\n  def show(request):\n      return render(request,'index.html')\n\n```\ntemplates\n```\n  <!DOCTYPE html>\n  <html lang=\"en\">\n    <head>\n      <meta charset=\"utf-8\">\n      <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n\n      <title>Admin</title>\n      <meta name=\"description\" content=\"\">\n      <meta name=\"author\" content=\"\">\n\n      <!-- http://davidbcalhoun.com/2010/viewport-metatag -->\n      <meta name=\"HandheldFriendly\" content=\"True\">\n      <meta name=\"MobileOptimized\" content=\"320\">\n      <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n\n      <!-- For Modern Browsers -->\n      <link rel=\"shortcut icon\" href=\"img/favicons/favicon.png\">\n      <!-- For everything else -->\n      <link rel=\"shortcut icon\" href=\"img/favicons/favicon.ico\">\n      <!-- For retina screens -->\n      <link rel=\"apple-touch-icon-precomposed\" sizes=\"114x114\" href=\"img/favicons/apple-touch-icon-retina.png\">\n      <!-- For iPad 1-->\n      <link rel=\"apple-touch-icon-precomposed\" sizes=\"72x72\" href=\"img/favicons/apple-touch-icon-ipad.png\">\n      <!-- For iPhone 3G, iPod Touch and Android -->\n      <link rel=\"apple-touch-icon-precomposed\" href=\"img/favicons/apple-touch-icon.png\">\n\n      <!-- iOS web-app metas -->\n      <meta name=\"apple-mobile-web-app-capable\" content=\"yes\">\n      <meta name=\"apple-mobile-web-app-status-bar-style\" content=\"black\">\n      <!-- Add your custom home screen title: -->\n      <meta name=\"apple-mobile-web-app-title\" content=\"Jarvis\">\n\n      <!-- Startup image for web apps -->\n      <link rel=\"apple-touch-startup-image\" href=\"img/splash/ipad-landscape.png\" media=\"screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:landscape)\">\n      <link rel=\"apple-touch-startup-image\" href=\"img/splash/ipad-portrait.png\" media=\"screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:portrait)\">\n      <link rel=\"apple-touch-startup-image\" href=\"img/splash/iphone.png\" media=\"screen and (max-device-width: 320px)\">\n\n      <link type=\"text/css\" rel=\"stylesheet\" href=\"/static/css/login.css\"></link>\n      <link type=\"text/css\" rel=\"stylesheet\" href=\"/static/bootstrap3/css/bootstrap.css\"></link>\n  </head>\n\n  <body class=\"eternity-form scroll-animations-activated\">\n  <section data-panel=\"sixth\" id=\"loginPage\" class=\"colorBg6 colorBg dark active\">\n          <div class=\"container\">\n          <div class=\"login-form-section\">\n              <br>\n              <br>\n              <br>\n\n              <div data-animation=\"bounceInLeft\" class=\"forgot-password-section animated bounceInLeft\">\n                  <div class=\"section-title\">\n                     <h3>Login</h3>\n                  </div>\n\n                  <div class=\"forgot-content\">\n                      <form method =\"post\" id=\"target\" action=\"/index/\">\n                          <div class=\"textbox-wrap\">\n                              <div class=\"input-group\">\n                                 <span class=\"input-group-addon \"><i\n                                      class=\"glyphicon glyphicon-user\"></i></span> <input type=\"text\"\n                                      placeholder=\"Username\" name=\"username\" class=\"form-control\" required=\"required\">\n                              </div>\n                          </div>\n                          <div class=\"textbox-wrap\">\n                              <div class=\"input-group\">\n                                 <span class=\"input-group-addon \"><i\n                                      class=\"glyphicon glyphicon-lock\"></i></span> <input type=\"password\"\n                                      placeholder=\"Password\" name=\"password\" class=\"form-control \"\n                                      required=\"required\">\n                              </div>\n                          </div>\n                          <div class=\"forget-form-action clearfix\">\n                             <button class=\"btn btn-success pull-right green-btn\"\n                                  type=\"submit\">\n                                  LogIn &nbsp; <i class=\"glyphicon glyphicon-chevron-right\"></i>\n                              </button>\n                          </div>\n                      </form>\n                  </div>\n              </div>\n          </div>\n          </div>\n      </section>\n      <script type=\"text/javascript\" src=\"/static/js/jquery-1.9.1.js\"></script>\n      <script type=\"text/javascript\" src=\"/static/bootstrap3/js/bootstrap.min.js\"></script>\n\n      <script type=\"text/javascript\">\n          $(function() {\n              $(\".form-control\").focus(function() {\n                  $(this).closest(\".textbox-wrap\").addClass(\"focused\");\n              }).blur(function() {\n                  $(this).closest(\".textbox-wrap\").removeClass(\"focused\");\n              });\n\n              if('{{login_err}}') {\n                  var html = '<div data-animation-delay=\".2s\" data-animation=\"fadeInRightBig\" class=\"login-form-links link1 animated fadeInRightBig\"\t\t\t\t\t\tstyle=\"animation-delay: 0.2s;\"><h4 class=\"blue\">{{login_err}}</h4></div>'\n                  $('div.login-form-section').append(html);\n              }\n          });\n      </script>\n  </body>\n  </html>\n```\n## django admin 密码重置\n\n```\npython manage.py shell\n\n\n然后获取你的用户名，并且重设密码：\nfrom django.contrib.auth.models import User\nuser = User.objects.get(username='admin')\nuser.set_password('new_password')\nuser.save()\n```\n这样就可以使用新密码进行登陆了\n\n\n# django 输出到固定日志\n\n## 将 log 封装成一个单独的 app\n```\n[root@Linux mysite]# django-admin.py startapp log\n[root@Linux mysite]# cd log\n[root@Linux log]# ls\n__init__.py  models.py  tests.py  views.py\n```\n\n## 编写 log 程序\n```\ncurl -o BLog.py https://raw.githubusercontent.com/meetbill/MyPythonLib/master/log_utils/BLog/BLog.py\n```\n## 在本项目其他应用中的 view.py 中调用 BLog\n```\nfrom django.shortcuts import render,render_to_response\n\nfrom log.BLog import Log\n# 是否在终端显示\ndebug=True\n# 日志文件\nlogpath = \"/tmp/test.log\"\n# 设置日志文件为 5Mb 时进行轮转，并且最多只保留个日志\nlogger = Log(logpath,level=\"debug\",is_console=debug, mbs=5, count=5)\n\n\ndef face(request):\n    logstr=\"########\"\n    logger.error(logstr)\n    logger.info(logstr)\n    logger.warn(logstr)\n    return render_to_response('register.html',{})\n```\n## 测试\n\n当在 view.py 中设置了终端显示时\n\n![Screenshot](./../../images/django/BLog.jpg)\n\n注：如果修改前 django 项目是运行的，那么当我们在程序中加入导入模块的程序时，需要重启下 django 应用，如果我们修改的程序不涉及导入模块部分，则不需要重启应用\n\n# django FAQ\ndjango\n(1)Django 表单提交出现 CSRF verification failed. Request aborted\n```\n  由于我们创建一个 POST 表单（它具有修改数据的作用），所以我们需要小心跨站点请求伪造。\n  谢天谢地，你不必太过担心，因为 Django 已经拥有一个用来防御它的非常容易使用的系统。\n  简而言之，所有针对内部 URL 的 POST 表单都应该使用{% csrf_token %}模板标签。\n  [templates-form]\n  {% csrf_token %}\n\n  [View]\n  return render_to_response(’polls/detail.html’, {’poll’: p},\n                    context_instance=RequestContext(request))\n```\n# django 自用开发模板\n\n[pine](https://github.com/meetbill/pine)\n"
  },
  {
    "path": "doc/web/nginx.md",
    "content": "# nginx\n\n<!-- vim-markdown-toc GFM -->\n\n* [功能](#功能)\n* [安装](#安装)\n    * [安装依赖](#安装依赖)\n    * [下载](#下载)\n    * [编译安装](#编译安装)\n* [nginx 服务架构](#nginx-服务架构)\n    * [模块化结构](#模块化结构)\n        * [模块化开发](#模块化开发)\n        * [nginx 的模块化结构](#nginx-的模块化结构)\n    * [nginx 的模块清单](#nginx-的模块清单)\n    * [nginx 的 web 请求处理机制](#nginx-的-web-请求处理机制)\n* [nginx 配置文件实例](#nginx-配置文件实例)\n* [nginx 服务器基础配置指令](#nginx-服务器基础配置指令)\n    * [nginx.conf 文件的结构](#nginxconf-文件的结构)\n    * [nginx 运行相关的 Global 部分](#nginx-运行相关的-global-部分)\n        * [配置运行 nginx 服务器用户](#配置运行-nginx-服务器用户)\n        * [配置允许生成的 worker process 数](#配置允许生成的-worker-process-数)\n        * [配置 nginx 进程 PID 存放路径](#配置-nginx-进程-pid-存放路径)\n        * [配置错误日志的存放路径](#配置错误日志的存放路径)\n        * [配置文件的引入](#配置文件的引入)\n    * [与用户的网络连接相关的 events](#与用户的网络连接相关的-events)\n        * [设置网络连接的序列化](#设置网络连接的序列化)\n        * [设置是否允许同时接收多个网络连接](#设置是否允许同时接收多个网络连接)\n        * [事件驱动模型的选择](#事件驱动模型的选择)\n        * [配置最大连接数](#配置最大连接数)\n    * [http](#http)\n        * [http Global 代理 - 缓存 - 日志 - 第三方模块配置](#http-global-代理---缓存---日志---第三方模块配置)\n            * [定义 MIME-Type](#定义-mime-type)\n            * [自定义服务日志](#自定义服务日志)\n            * [配置允许 sendfile 方式传输文件](#配置允许-sendfile-方式传输文件)\n            * [配置连接超时时间](#配置连接超时时间)\n            * [单连接请求数上限](#单连接请求数上限)\n        * [server](#server)\n            * [配置网络监听](#配置网络监听)\n            * [基于名称的虚拟主机配置](#基于名称的虚拟主机配置)\n            * [配置 https 证书](#配置-https-证书)\n            * [基于 IP 的虚拟主机配置](#基于-ip-的虚拟主机配置)\n            * [配置 location 块](#配置-location-块)\n            * [[root] 配置请求的根目录](#root-配置请求的根目录)\n            * [[alias] 更改 location 的 URI](#alias-更改-location-的-uri)\n            * [设置网站的默认首页](#设置网站的默认首页)\n            * [设置网站的错误页面](#设置网站的错误页面)\n            * [基于 IP 配置 nginx 的访问权限](#基于-ip-配置-nginx-的访问权限)\n            * [基于密码配置 nginx 的访问权限](#基于密码配置-nginx-的访问权限)\n* [应用](#应用)\n    * [架设简单文件服务器](#架设简单文件服务器)\n    * [nginx 正向代理](#nginx-正向代理)\n    * [nginx 服务器基础配置实例](#nginx-服务器基础配置实例)\n        * [测试 myServer1 的访问](#测试-myserver1-的访问)\n        * [测试 myServer2 的访问](#测试-myserver2-的访问)\n    * [使用缓存](#使用缓存)\n    * [使用 location 反向代理到已有网站](#使用-location-反向代理到已有网站)\n    * [其他](#其他)\n        * [ngx_http_sub_module 替换响应中内容](#ngx_http_sub_module-替换响应中内容)\n        * [配置 http 强制跳转 https](#配置-http-强制跳转-https)\n\n<!-- vim-markdown-toc -->\n# 功能\n\n> * 反向代理\n> * 负载均衡\n> * HTTP 服务器（动静分离）\n> * 正向代理\n\n# 安装\n\n## 安装依赖\n\n安装 nginx 之前，确保系统已经安装 gcc、openssl-devel、pcre-devel 和 zlib-devel 软件库\n\n* gcc 可以通过光盘直接选择安装\n* openssl-devel、zlib-devel 可以通过光盘直接选择安装，https 时使用\n* pcre-devel 安装 pcre 库是为了使 nginx 支持 HTTP Rewrite 模块（动静分离涉及此模块）\n\n## 下载\n\n> * [nginx 下载](http://nginx.org/en/download.html)\n> * [pcre](http://www.pcre.org/)\n\n## 编译安装\n\n通过上面的下载页下载最新的稳定版\n\n```\n#wget http://nginx.org/download/nginx-1.8.0.tar.gz\n#tar xzvf nginx-1.8.0.tar.gz\n#cd nginx-1.8.0\n#./configure --prefix=/opt/X_nginx/nginx --with-http_ssl_module\n#make && sudo make install\n```\n> * --prefix=/opt/X_nginx/nginx 安装目录\n> * --with-http_ssl_module 添加 https 支持\n\n> 编译时将 ssl 模块静态编译\n```\n./configure  --prefix=/opt/X_nginx/nginx \\\n             --with-openssl=../openssl-1.0.2l \\\n             --with-zlib=../zlib-1.2.11 \\\n             --with-pcre=../pcre-8.41 \\\n             --with-http_ssl_module\n```\n> 编译时添加 auth_request 模块（使用 auth_request 模块实现 nginx 端认证及鉴权控制）\n\n```\n./configure --with-pcre=../pcre-8.43 --with-http_auth_request_module\n```\n\n# nginx 服务架构\n\n## 模块化结构\n\n> nginx 服务器的开发`完全`遵循模块化设计思想\n\n### 模块化开发\n\n1. 单一职责原则，一个模块只负责一个功能\n2. 将程序分解，自顶向下，逐步求精\n3. 高内聚，低耦合\n\n### nginx 的模块化结构\n\n+ 核心模块：nginx 最基本最核心的服务，如进程管理、权限控制、日志记录；\n+ 标准 HTTP 模块：nginx 服务器的标准 HTTP 功能；\n+ 可选 HTTP 模块：处理特殊的 HTTP 请求\n+ 邮件服务模块：邮件服务\n+ 第三方模块：作为扩展，完成特殊功能\n\n## nginx 的模块清单\n\n+ 核心模块\n    - ngx_core\n    - ngx_errlog\n    - ngx_conf\n    - ngx_events\n    - ngx_event_core\n    - ngx_epll\n    - ngx_regex\n\n+ 标准 HTTP 模块\n    - ngx_http\n    - ngx_http_core             #配置端口，URI 分析，服务器相应错误处理，别名控制 (alias) 等\n    - ngx_http_log              #自定义 access 日志\n    - ngx_http_upstream         #定义一组服务器，可以接受来自 proxy, Fastcgi,Memcache 的重定向；主要用作负载均衡\n    - ngx_http_static\n    - ngx_http_autoindex        #自动生成目录列表\n    - ngx_http_index            #处理以`/`结尾的请求，如果没有找到 index 页，则看是否开启了`random_index`；如开启，则用之，否则用 autoindex\n    - ngx_http_auth_basic       #基于 http 的身份认证 (auth_basic)\n    - ngx_http_access           #基于 IP 地址的访问控制 (deny,allow)\n    - ngx_http_limit_conn       #限制来自客户端的连接的响应和处理速率\n    - ngx_http_limit_req        #限制来自客户端的请求的响应和处理速率\n    - ngx_http_geo\n    - ngx_http_map              #创建任意的键值对变量\n    - ngx_http_split_clients\n    - ngx_http_referer          #过滤 HTTP 头中 Referer 为空的对象\n    - ngx_http_rewrite          #通过正则表达式重定向请求\n    - ngx_http_proxy\n    - ngx_http_fastcgi          #支持 fastcgi\n    - ngx_http_uwsgi\n    - ngx_http_scgi\n    - ngx_http_memcached\n    - ngx_http_empty_gif        #从内存创建一个 1×1 的透明 gif 图片，可以快速调用\n    - ngx_http_browser          #解析 http 请求头部的 User-Agent 值\n    - ngx_http_charset          #指定网页编码\n    - ngx_http_upstream_ip_hash\n    - ngx_http_upstream_least_conn\n    - ngx_http_upstream_keepalive\n    - ngx_http_write_filter\n    - ngx_http_header_filter\n    - ngx_http_chunked_filter\n    - ngx_http_range_header\n    - ngx_http_gzip_filter\n    - ngx_http_postpone_filter\n    - ngx_http_ssi_filter\n    - ngx_http_charset_filter\n    - ngx_http_userid_filter\n    - ngx_http_headers_filter   #设置 http 响应头\n    - ngx_http_copy_filter\n    - ngx_http_range_body_filter\n    - ngx_http_not_modified_filter\n\n+ 可选 HTTP 模块\n    - ngx_http_addition         #在响应请求的页面开始或者结尾添加文本信息\n    - ngx_http_degradation      #在低内存的情况下允许服务器返回 444 或者 204 错误\n    - ngx_http_perl\n    - ngx_http_flv              #支持将 Flash 多媒体信息按照流文件传输，可以根据客户端指定的开始位置返回 Flash\n    - ngx_http_geoip            #支持解析基于 GeoIP 数据库的客户端请求\n    - ngx_google_perftools\n    - ngx_http_gzip             #gzip 压缩请求的响应\n    - ngx_http_gzip_static      #搜索并使用预压缩的以.gz 为后缀的文件代替一般文件响应客户端请求\n    - ngx_http_image_filter     #支持改变 png，jpeg，gif 图片的尺寸和旋转方向\n    - ngx_http_mp4              #支持.mp4,.m4v,.m4a 等多媒体信息按照流文件传输，常与 ngx_http_flv 一起使用\n    - ngx_http_random_index     #当收到 / 结尾的请求时，在指定目录下随机选择一个文件作为 index\n    - ngx_http_secure_link      #支持对请求链接的有效性检查\n    - ngx_http_ssl              #支持 https\n    - ngx_http_stub_status\n    - ngx_http_sub_module       #使用指定的字符串替换响应中的信息\n    - ngx_http_dav              #支持 HTTP 和 WebDAV 协议中的 PUT/DELETE/MKCOL/COPY/MOVE 方法\n    - ngx_http_xslt             #将 XML 响应信息使用 XSLT 进行转换\n\n+ 邮件服务模块\n    - ngx_mail_core\n    - ngx_mail_pop3\n    - ngx_mail_imap\n    - ngx_mail_smtp\n    - ngx_mail_auth_http\n    - ngx_mail_proxy\n    - ngx_mail_ssl\n\n+ 第三方模块\n    - echo-nginx-module         #支持在 nginx 配置文件中使用 echo/sleep/time/exec 等类 Shell 命令\n    - memc-nginx-module\n    - rds-json-nginx-module     #使 nginx 支持 json 数据的处理\n    - lua-nginx-module\n\n## nginx 的 web 请求处理机制\n\n作为服务器软件，必须具备并行处理多个客户端的请求的能力， 工作方式主要以下 3 种：\n\n+ 多进程 (Apache)\n    - 优点：设计和实现简单；子进程独立\n    - 缺点：生成一个子进程要内存复制，在资源和时间上造成额外开销\n+ 多线程 (IIS)\n    - 优点：开销小\n    - 缺点：开发者自己要对内存进行管理；线程之间会相互影响\n+ 异步方式 (nginx)\n\n经常说道异步非阻塞这个概念， 包含两层含义：\n\n通信模式：\n    + 同步：发送方发送完请求后，等待并接受对方的回应后，再发送下个请求\n    + 异步：发送方发送完请求后，不必等待，直接发送下个请求\n\n# nginx 配置文件实例\n\n```\n#定义 nginx 运行的用户和用户组\nuser www www;\n\n#nginx 进程数，建议设置为等于 CPU 总核心数。\nworker_processes 8;\n\n#nginx 默认没有开启利用多核 CPU, 通过增加 worker_cpu_affinity 配置参数来充分利用多核 CPU 以下是 8 核的配置参数\nworker_cpu_affinity 00000001 00000010 00000100 00001000 00010000 00100000 01000000 10000000;\n\n#全局错误日志定义类型，[ debug | info | notice | warn | error | crit ]\nerror_log /var/log/nginx/error.log info;\n\n#进程文件\npid /var/run/nginx.pid;\n\n#一个 nginx 进程打开的最多文件描述符数目，理论值应该是最多打开文件数（系统的值 ulimit -n）与 nginx 进程数相除，但是 nginx 分配请求并不均匀，所以建议与 ulimit -n 的值保持一致。\nworker_rlimit_nofile 65535;\n\n#工作模式与连接数上限\nevents\n{\n    #参考事件模型，use [ kqueue | rtsig | epoll | /dev/poll | select | poll ]; epoll 模型是 Linux 2.6 以上版本内核中的高性能网络 I/O 模型，如果跑在 FreeBSD 上面，就用 kqueue 模型。\n    #epoll 是多路复用 IO(I/O Multiplexing) 中的一种方式，但是仅用于 linux2.6 以上内核，可以大大提高 nginx 的性能\n    use epoll;\n\n    ############################################################################\n    #单个后台 worker process 进程的最大并发链接数\n    #事件模块指令，定义 nginx 每个进程最大连接数，默认 1024。最大客户连接数由 worker_processes 和 worker_connections 决定\n    #即 max_client=worker_processes*worker_connections, 在作为反向代理时：max_client=worker_processes*worker_connections / 4\n    worker_connections 65535;\n    ############################################################################\n}\n\n#设定 http 服务器\nhttp {\n    include mime.types; #文件扩展名与文件类型映射表\n    default_type application/octet-stream; #默认文件类型\n    #charset utf-8; #默认编码\n\n    server_names_hash_bucket_size 128; #服务器名字的 hash 表大小\n    client_header_buffer_size 32k; #上传文件大小限制\n    large_client_header_buffers 4 64k; #设定请求缓\n    client_max_body_size 8m; #设定请求缓\n    sendfile on; #开启高效文件传输模式，sendfile 指令指定 nginx 是否调用 sendfile 函数来输出文件，对于普通应用设为 on，如果用来进行下载等应用磁盘 IO 重负载应用，可设置为 off，以平衡磁盘与网络 I/O 处理速度，降低系统的负载。注意：如果图片显示不正常把这个改成 off。\n    autoindex on; #开启目录列表访问，合适下载服务器，默认关闭。\n    tcp_nopush on; #防止网络阻塞\n    tcp_nodelay on; #防止网络阻塞\n\n    ##连接客户端超时时间各种参数设置##\n    keepalive_timeout  120;          #单位是秒，客户端连接时时间，超时之后服务器端自动关闭该连接 如果 nginx 守护进程在这个等待的时间里，一直没有收到浏览发过来 http 请求，则关闭这个 http 连接\n    client_header_timeout 10;        #客户端请求头的超时时间\n    client_body_timeout 10;          #客户端请求主体超时时间\n    reset_timedout_connection on;    #告诉 nginx 关闭不响应的客户端连接。这将会释放那个客户端所占有的内存空间\n    send_timeout 10;                 #客户端响应超时时间，在两次客户端读取操作之间。如果在这段时间内，客户端没有读取任何数据，nginx 就会关闭连接\n    ################################\n\n    #FastCGI 相关参数是为了改善网站的性能：减少资源占用，提高访问速度。下面参数看字面意思都能理解。\n    fastcgi_connect_timeout 300;\n    fastcgi_send_timeout 300;\n    fastcgi_read_timeout 300;\n    fastcgi_buffer_size 64k;\n    fastcgi_buffers 4 64k;\n    fastcgi_busy_buffers_size 128k;\n    fastcgi_temp_file_write_size 128k;\n\n    ###作为代理缓存服务器设置#######\n    ###先写到 temp 再移动到 cache\n    #proxy_cache_path /var/tmp/nginx/proxy_cache levels=1:2 keys_zone=cache_one:512m inactive=10m max_size=64m;\n    ###以上 proxy_temp 和 proxy_cache 需要在同一个分区中\n    ###levels=1:2 表示缓存级别，表示缓存目录的第一级目录是 1 个字符，第二级目录是 2 个字符 keys_zone=cache_one:128m 缓存空间起名为 cache_one 大小为 512m\n    ###max_size=64m 表示单个文件超过 128m 就不缓存了  inactive=10m 表示缓存的数据，10 分钟内没有被访问过就删除\n    #########end####################\n\n    #####对传输文件压缩###########\n    #gzip 模块设置\n    gzip on; #开启 gzip 压缩输出\n    gzip_min_length 1k; #最小压缩文件大小\n    gzip_buffers 4 16k; #压缩缓冲区\n    gzip_http_version 1.0; #压缩版本（默认 1.1，前端如果是 squid2.5 请使用 1.0）\n    gzip_comp_level 2; #压缩等级，gzip 压缩比，1 为最小，处理最快；9 为压缩比最大，处理最慢，传输速度最快，也最消耗 CPU；\n    gzip_types text/plain application/x-javascript text/css application/xml;\n    #压缩类型，默认就已经包含 text/html，所以下面就不用再写了，写上去也不会有问题，但是会有一个 warn。\n    gzip_vary on;\n    ##############################\n\n    #limit_zone crawler $binary_remote_addr 10m; #开启限制 IP 连接数的时候需要使用\n\n    upstream blog.ha97.com {\n        #upstream 的负载均衡，weight 是权重，可以根据机器配置定义权重。weigth 参数表示权值，权值越高被分配到的几率越大。\n        server 192.168.80.121:80 weight=3;\n        server 192.168.80.122:80 weight=2;\n        server 192.168.80.123:80 weight=3;\n    }\n\n    #虚拟主机的配置\n    server {\n        #监听端口\n        listen 80;\n\n        #############https##################\n        #listen 443 ssl;\n        #ssl_certificate /opt/https/xxxxxx.crt;\n        #ssl_certificate_key /opt/https/xxxxxx.key;\n        #ssl_protocols SSLv3 TLSv1;\n        #ssl_ciphers HIGH:!ADH:!EXPORT57:RC4+RSA:+MEDIUM;\n        #ssl_prefer_server_ciphers on;\n        #ssl_session_cache shared:SSL:2m;\n        #ssl_session_timeout 5m;\n        ####################################end\n\n        #域名可以有多个，用空格隔开\n        server_name www.ha97.com ha97.com;\n        index index.html index.htm index.php;\n        root /data/www/ha97;\n        location ~ .*.(php|php5)?$ {\n            fastcgi_pass 127.0.0.1:9000;\n            fastcgi_index index.php;\n            include fastcgi.conf;\n        }\n\n        #图片缓存时间设置\n        location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$ {\n            expires 10d;\n        }\n\n        #JS 和 CSS 缓存时间设置\n        location ~ .*.(js|css)?$ {\n            expires 1h;\n        }\n\n        #日志格式设定\n        log_format access '$remote_addr - $remote_user [$time_local] \"$request\" ' '$status $body_bytes_sent \"$http_referer\" ' '\"$http_user_agent\" $http_x_forwarded_for';\n\n        #定义本虚拟主机的访问日志\n        access_log /var/log/nginx/ha97access.log access;\n\n        #对 \"/\" 启用反向代理\n        location / {\n            proxy_pass http://127.0.0.1:88;\n            proxy_redirect off;\n            proxy_set_header Host  $host:$server_port;  # 后端获取到客户端访问的实际地址\n            proxy_set_header X-Real-IP  $remote_addr;   # 后端的 Web 服务器可以通过 HTTP_X_REAL_IP 获取上一级 IP\n            proxy_set_header X-Real-PORT $remote_port;\n            # 后端的 Web 服务器可以通过 X-Forwarded-For 获取用户真实 IP，X-Forwarded-For 显示所有经过路径的 ip\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            #以下是一些反向代理的配置，可选。\n            proxy_set_header Host $host;\n            client_max_body_size 10m;                   # 允许客户端请求的最大单文件字节数\n            client_body_buffer_size 128k;               # 缓冲区代理缓冲用户端请求的最大字节数，\n\n            ##代理设置 以下设置是 nginx 和后端服务器之间通讯的设置##\n            proxy_connect_timeout 90;                   # nginx 跟后端服务器连接超时时间（代理连接超时）\n            proxy_send_timeout 90;                      # 后端服务器数据回传时间（代理发送超时）\n            proxy_read_timeout 90;                      # 连接成功后，后端服务器响应时间（代理接收超时）\n            proxy_buffering on;                         # 该指令开启从后端被代理服务器的响应内容缓冲 此参数开启后 proxy_buffers 和 proxy_busy_buffers_size 参数才会起作用\n            proxy_buffer_size 4k;                       # 设置代理服务器（nginx）保存用户头信息的缓冲区大小\n            proxy_buffers 4 32k;                        # proxy_buffers 缓冲区，网页平均在 32k 以下的设置\n            proxy_busy_buffers_size 64k;                # 高负荷下缓冲大小（proxy_buffers*2）\n            proxy_max_temp_file_size 2048m;             # 默认 1024m, 该指令用于设置当网页内容大于 proxy_buffers 时，临时文件大小的最大值。如果文件大于这个值，它将从 upstream 服务器同步地传递请求，而不是缓冲到磁盘\n            proxy_temp_file_write_size 512k;            # 这是当被代理服务器的响应过大时 nginx 一次性写入临时文件的数据量。\n            proxy_temp_path  /var/tmp/nginx/proxy_temp; # 定义缓冲存储目录，之前必须要先手动创建此目录\n            proxy_headers_hash_max_size 51200;\n            proxy_headers_hash_bucket_size 6400;\n            #######################################################\n        }\n\n        #设定查看 nginx 状态的地址\n        location /nginxStatus {\n            stub_status on;\n            access_log on;\n            auth_basic \"nginxStatus\";\n            auth_basic_user_file conf/htpasswd;\n            #htpasswd 文件的内容可以用 apache 提供的 htpasswd 工具来产生。\n        }\n\n        #本地动静分离反向代理配置\n        #所有 jsp 的页面均交由 tomcat 或 resin 处理\n        location ~ .(jsp|jspx|do)?$ {\n            proxy_set_header Host $host:$server_port;\n            proxy_set_header X-Real-IP $remote_addr;\n            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n            proxy_pass http://127.0.0.1:8080;\n        }\n\n        #所有静态文件由 nginx 直接读取不经过 tomcat 或 resin\n        location ~ .*.(htm|html|gif|jpg|jpeg|png|bmp|swf|ioc|rar|zip|txt|flv|mid|doc|ppt|pdf|xls|mp3|wma)$\n        { expires 15d; }\n\n        location ~ .*.(js|css)?$\n        { expires 1h; }\n    }\n}\n```\n\n# nginx 服务器基础配置指令\n\n## nginx.conf 文件的结构\n\n+ Global: nginx 运行相关\n+ events: 与用户的网络连接相关\n+ http\n    + http Global: 代理，缓存，日志，以及第三方模块的配置\n    + server\n        + server Global: 虚拟主机相关\n        + location: 地址定向，数据缓存，应答控制，以及第三方模块的配置\n\n> 所有的所有的所有的指令，都要以`;`结尾\n\n## nginx 运行相关的 Global 部分\n\n### 配置运行 nginx 服务器用户\n\nuser nobody nobody;\n\n### 配置允许生成的 worker process 数\n\nworker_processes auto;\nworker_processes 4;\n\n> 这个数字，跟电脑 CPU 核数要保持一致\n\n```\n# grep ^proces /proc/cpuinfo\nprocessor       : 0\nprocessor       : 1\nprocessor       : 2\nprocessor       : 3\n# grep ^proces /proc/cpuinfo | wc -l\n4\n```\n\n### 配置 nginx 进程 PID 存放路径\n\npid logs/nginx.pid;\n\n> 这里面保存的就是一个数字，nginx master 进程的进程号\n\n### 配置错误日志的存放路径\n\nerror_log logs/error.log;\nerror_log logs/error.log error;\n\n### 配置文件的引入\n\ninclude mime.types;\ninclude fastcgi_params;\ninclude ../../conf/*.conf;\n\n## 与用户的网络连接相关的 events\n\n### 设置网络连接的序列化\n\naccept_mutex on;\n\n> 对多个 nginx 进程接收连接进行序列化，防止多个进程对连接的争抢（惊群）\n\n### 设置是否允许同时接收多个网络连接\n\nmulti_accept off;\n\n### 事件驱动模型的选择\n\nuse select|poll|kqueue|epoll|rtsig|/dev/poll|eventport\n\n> 这个重点，后面再看\n\n### 配置最大连接数\n\nworker_connections 512;\n\n## http\n\n### http Global 代理 - 缓存 - 日志 - 第三方模块配置\n\n#### 定义 MIME-Type\n\ninclude mime.types;\ndefault_type application/octet-stream;\n\n#### 自定义服务日志\n\naccess_log logs/access.log main;\naccess_log off;\n\n#### 配置允许 sendfile 方式传输文件\n\nsendfile off;\n\nsendfile on;\nsendfile_max_chunk 128k;\n\n> nginx 每个 worker process 每次调用 sendfile() 传输的数据量的最大值\n\nRefer:\n\n+ [Linux kenel sendfile 如何提升性能](http://www.vpsee.com/2009/07/linux-sendfile-improve-performance/)\n+ [nginx sendifle tcp_nopush tcp_nodelay 参数解释](http://blog.csdn.net/zmj_88888888/article/details/9169227)\n\n#### 配置连接超时时间\n\n> 与用户建立连接后，nginx 可以保持这些连接一段时间，默认 75s\n> 下面的 65s 可以被 Mozilla/Konqueror 识别，是发给用户端的头部信息`Keep-Alive`值\n\nkeepalive_timeout 75s 65s;\n\n#### 单连接请求数上限\n\n> 和用户端建立连接后，用户通过此连接发送请求；这条指令用于设置请求的上限数\n\nkeepalive_requests 100;\n\n### server\n\n#### 配置网络监听\n\nlisten *:80 | *:8000; # 监听所有的 80 和 8000 端口\n\nlisten 192.168.1.10:8000;\nlisten 192.168.1.10;\nlisten 8000; # 等同于 listen *:8000;\nlisten 192.168.1.10 default_server backlog=511; # 该 ip 的连接请求默认由此虚拟主机处理；最多允许 1024 个网络连接同时处于挂起状态\n\n#### 基于名称的虚拟主机配置\n\nserver_name myserver.com www.myserver.com;\n\nserver_name *.myserver.com www.myserver.* myserver2.*; # 使用通配符\n\n> 不允许的情况： server_name www.ab*d.com; # `*`只允许出现在 www 和 com 的位置\n\nserver_name ~^www\\d+\\.myserver\\.com$; # 使用正则\n\n> nginx 的配置中，可以用正则的地方，都以`~`开头\n\n> from nginx~0.7.40 开始，server_name 中的正则支持 字符串捕获功能（capture）\n\nserver_name ~^www\\.(.+)\\.com$; # 当请求通过 www.myserver.com 请求时， myserver 就被记录到`$1`中，在本 server 的上下文中就可以使用\n\n如果一个名称 被多个虚拟主机的 server_name 匹配成功，那这个请求到底交给谁处理呢？看优先级：\n\n1. 准确匹配到 server_name\n2. 通配符在开始时匹配到 server_name\n3. 通配符在结尾时匹配到 server_name\n4. 正则表达式匹配 server_name\n5. 先到先得\n\n\n#### 配置 https 证书\n\n**原理**\n\nhttps 是在 http 和 TCP 中间加上一层加密层\n\n> * 浏览器向服务端发送消息时：本质上是浏览器（客户端）使用服务端的公钥来加密信息，服务端使用自己的私钥解密，\n> * 浏览器从服务端获取消息是：服务端使用自己私钥加密，浏览器（客户端）使用服务端的公钥来解密信息\n\n在这个过程中，需要保证服务端给浏览器的公钥不是假冒的。证明服务端公钥信息的机构是 CA（数字认证中心）\n\n可以理解为：如果想证明一个人的身份是真的，就得证明这个人的身份证是真的\n\n**数字证书**\n```\n数字证书相当于物理世界中的身份证，\n在网络中传递信息的双方互相不能见面，利用数字证书可确认双方身份，而不是他人冒充的。\n这个数字证书由信任的第三方，即认证中心使用自己的私钥对 A 的公钥加密，加密后文件就是网络上的身份证了，即数字证书\n```\n\n大致可以理解为如下\n\n```\n1. 服务端将自己的公钥和其他信息（服务端数字证书），请求数字认证中心签名，数字认证中心使用自己的私钥在证书里加密（只有数字认证中心的公钥才能解开）\n2. 服务端将自己的证书（证书里面包括服务端的公钥）给浏览器\n3. 浏览器的“证书管理器”中有“受信任的根证书颁发机构”列表，客户端在接收到响应后，会在这个列表里查看是否存在解开该服务器数字证书的公钥。有两种错误情况：如果公钥在这个列表里，但是解码后的内容不匹配，说明证书被冒用；如果公钥不在这个列表里，说明这张证书不是受信任的机构所颁发，他的真实性无法确定\n4. 如果一切都没问题，浏览器就可以使用服务器的公钥对信息内容进行加密，然后与服务器交换信息（已加密）\n\n+--------------+           +------------------+\n|    服务端    |---------->| 数字认证中心 (CA) |\n+------+-------+    1    X +------------------+\n       |                / /\n       |               / /\n       |              / /\n       |             / /\n       |2         3 / / 4\n       |           / /\n       |          / /\n       |         / /\n       X        / /\n+--------------+ /\n|    浏览器    |X\n+--------------+\n\n只要证书（证书里有服务端的公钥）是可信的，公钥就是可信的。\n```\n**证书格式**\n\nLinux 下的工具们通常使用 base64 编码的文本格式，相关常用后缀如下\n\n* 证书\n    * .crt\n    * .pem\n    * .cer(IIS 等一些平台下，则习惯用 cer 作为证书文件的扩展名，二进制证书）\n* 私钥：.key\n* 证书请求：.csr\n* 其他\n    * .keystore java 密钥库（包括证书和私钥）\n\n**制作证书**\n\n```\n1. 生成服务器端的私钥 (key 文件）\n$openssl genrsa  -out server.key 1024\n\n2. 生成服务器端证书签名请求文件 (csr 文件）;\n$ openssl req -new -key server.key -out server.csr\n\n...\nCountry Name:CN------------ 证书持有者所在国家\nState or Province Name:BJ-- 证书持有者所在州或省份（可省略不填）\nLocality Name:BJ----------- 证书持有者所在城市（可省略不填）\nOrganization Name:SC------- 证书持有者所属组织或公司\nOrganizational Unit Name:.- 证书持有者所属部门（可省略不填）\nCommon Name :ceshi.com----- 域名\nEmail Address:------------- 邮箱（可省略不填）\n\nA challenge password:------ 直接回车\nAn optional company name:-- 直接回车\n\n\n3. 生成证书文件 (crt 文件）\n$ openssl x509 -req -days 1000 -in server.csr -signkey server.key -out server.crt\n```\n以上生成 server.crt  server.key 文件即是用于 HTTPS 配置的证书和 key\n\n如果想查看证书里面的内容，可以通过 $openssl x509 -in server.crt -text -noout 查看\n\n**配置 nginx**\n\n在 nginx 的 server 区域内添加如下\n\n```\nlisten 443 ssl;\nssl_certificate /opt/https/server.crt;\nssl_certificate_key /opt/https/server.key;\nssl_protocols SSLv3 TLSv1;\nssl_ciphers HIGH:!ADH:!EXPORT57:RC4+RSA:+MEDIUM;\nssl_prefer_server_ciphers on;\nssl_session_cache shared:SSL:2m;\nssl_session_timeout 5m;\n```\n\n#### 基于 IP 的虚拟主机配置\n\n> 基于 IP 的虚拟主机，需要将网卡设置为同时能够监听多个 IP 地址\n\n```\nifconfig\n# 查看到本机 IP 地址为 192.168.1.30\nifconfig eth1:0 192.168.1.31 netmask 255.255.255.0 up\nifconfig eth1:1 192.168.1.32 netmask 255.255.255.0 up\nifconfig\n# 这时就看到 eth1 增加来 2 个别名， eth1:0 eth1:1\n\n# 如果需要机器重启后仍保持这两个虚拟的 IP\necho \"ifconfig eth1:0 192.168.1.31 netmask 255.255.255.0 up\" >> /etc/rc.local\necho \"ifconfig eth1:0 192.168.1.32 netmask 255.255.255.0 up\" >> /etc/rc.local\n```\n\n再来配置基于 IP 的虚拟主机\n\n```\nhttp {\n    ...\n    server {\n     listen 80;\n     server_name 192.168.1.31;\n     ...\n    }\n    server {\n     listen 80;\n     server_name 192.168.1.32;\n     ...\n    }\n}\n```\n\n#### 配置 location 块\n\n> location 块的配置，应该是最常用的了\n\nlocation [ = | ~ | ~* | ^~ ] uri {...}\n\n这里内容分 2 块，匹配方式和 uri， 其中 uri 又分为 标准 uri 和正则 uri\n\n先不考虑 那 4 种匹配方式\n\n1. nginx 首先会再 server 块的多个 location 中搜索是否有`标准 uri`和请求字符串匹配， 如果有，记录匹配度最高的一个；\n2. 然后，再用 location 块中的`正则 uri`和请求字符串匹配， 当第一个`正则 uri`匹配成功，即停止搜索， 并使用该 location 块处理请求；\n3. 如果，所有的`正则 uri`都匹配失败，就使用刚记录下的匹配度最高的一个`标准 uri`处理请求\n4. 如果都失败了，那就失败喽\n\n再看 4 种匹配方式：\n\n+ `=`:  用于`标准 uri`前，要求请求字符串与其严格匹配，成功则立即处理\n+ `^~`: 用于`标准 uri`前，并要求一旦匹配到，立即处理，不再去匹配其他的那些个`正则 uri`\n+ `~`:  用于`正则 uri`前，表示 uri 包含正则表达式， 并区分大小写\n+ `~*`: 用于`正则 uri`前， 表示 uri 包含正则表达式， 不区分大小写\n\n> `^~` 也是支持浏览器编码过的 URI 的匹配的哦， 如 `/html/%20/data` 可以成功匹配 `/html/ /data`\n\n#### [root] 配置请求的根目录\n\nWeb 服务器收到请求后，首先要在服务端指定的目录中寻找请求资源\n\n```\nroot /var/www;\n```\n**root 后跟的指定目录是上级目录**\n\n该上级目录下要含有和 location 后指定名称的同名目录才行，末尾“/”加不加无所谓\n```\nlocation /c/ {\n      root /a/\n}\n```\n访问站点 http://location/c 访问的就是 /a/c 目录下的站点信息。\n\n#### [alias] 更改 location 的 URI\n\n除了使用 root 指明处理请求的根目录，还可以使用 alias 改变 location 收到的 URI 的请求路径\n\n```\nlocation ~ ^/data/(.+\\.(htm|html))$ {\n    alias /locatinotest1/other/$1;\n}\n```\n**alias 后跟的指定目录是准确的，并且末尾必须加“/”，否则找不到文件**\n\n```\nlocation /c/ {\n      alias /a/\n}\n```\n访问站点 http://location/c 访问的就是 /a/ 目录下的站点信息。\n\n【注】一般情况下，在 location / 中配置 root，在 location /other 中配置 alias 是一个好习惯。\n\n#### 设置网站的默认首页\n\nindex 指令主要有 2 个作用：\n\n+ 对请求地址没有指明首页的，指定默认首页\n+ 对一个请求，根据请求内容而设置不同的首页，如下：\n\n```\nlocation ~ ^/data/(.+)/web/$ {\n    index index.$1.html index.htm;\n}\n```\n#### 设置网站的错误页面\n\nerror_page 404 /404.html;\nerror_page 403 /forbidden.html;\nerror_page 404 =301 /404.html;\n\n```\nlocation /404.html {\n    root /myserver/errorpages/;\n}\n```\n\n#### 基于 IP 配置 nginx 的访问权限\n\n```\nlocation / {\n    deny 192.168.1.1;\n    allow 192.168.1.0/24;\n    allow 192.168.1.2/24;\n    deny all;\n}\n```\n> 从 192.168.1.0 的用户时可以访问的，因为解析到 allow 那一行之后就停止解析了\n\n#### 基于密码配置 nginx 的访问权限\n\nauth_basic \"please login\";\nauth_basic_user_file /etc/nginx/conf/pass_file;\n\n> 这里的 file 必须使用绝对路径，使用相对路径无效\n\n```\n# /usr/local/apache2/bin/htpasswd -c -d pass_file user_name\n# 回车输入密码，-c 表示生成文件，-d 是以 crypt 加密。\n\nname1:password1\nname2:password2:comment\n```\n\n> 经过 basic auth 认证之后没有过期时间，直到该页面关闭；\n> 如果需要更多的控制，可以使用 HttpAuthDigestModule http://wiki.nginx.org/HttpAuthDigestModule\n\n\n# 应用\n## 架设简单文件服务器\n\n将 /data/public/ 目录下的文件通过 nginx 提供给外部访问\n```\n#mkdir /data/public/\n#chmod 777 /data/public/\n```\n```\nworker_processes 1;\nerror_log logs/error.log info;\nevents {\n    use epoll;\n}\nhttp {\n    server {\n        # 监听 8080 端口\n        listen 8080;\n        location /share/ {\n            # 打开自动列表功能，通常关闭\n            autoindex on;\n            # 将 /share/ 路径映射至 /data/public/，请保证 nginx 进程有权限访问 /data/public/\n            alias /data/public/;\n        }\n    }\n}\n```\n\n## nginx 正向代理\n\n -  正向代理指代理客户端访问服务器的一个中介服务器，代理的对象是客户端。正向代理就是代理服务器替客户端去访问目标服务器\n -  反向代理指代理后端服务器响应客户端请求的一个中介服务器，代理的对象是服务器。\n\n1. 配置\n\n代理服务器配置\n\nnginx.conf\n\n```\nserver{\n    resolver x.x.x.x;\n#       resolver 8.8.8.8;\n    listen 82;\n    location / {\n            proxy_pass http://$http_host$request_uri;\n    }\n    access_log  /data/httplogs/proxy-$host-aceess.log;\n}\n```\n\nlocation 保持原样即可，根据自己的配置更改 listen port 和 dnf 即 resolver\n验证：\n在需要访问外网的机器上执行以下操作之一即可：\n\n```\n1. export http_proxy=http://yourproxyaddress：proxyport（建议）\n2. vim ~/.bashrc\n    export http_proxy=http://yourproxyaddress：proxyport\n```\n\n2 不足\nnginx 不支持 CONNECT 方法，不像我们平时用的 GET 或者 POST，可以选用 apache 或 squid 作为代替方案。\n\n## nginx 服务器基础配置实例\n\n```\nuser nginx nginx;\n\nworker_processes 3;\n\nerror_log logs/error.log;\npid myweb/nginx.pid;\n\nevents {\n    use epoll;\n    worker_connections 1024;\n}\n\nhttp {\n    include mime.types;\n    default_type applicatioin/octet-stream;\n\n    sendfile on;\n\n    keepalive_timeout 65;\n\n    log_format access.log '$remote_addr [$time_local] \"$request\" \"$http_user_agent\"';\n\n    server {\n        listen 8081;\n        server_name myServer1;\n\n        access_log myweb/server1/log/access.log;\n        error_page 404 /404.html;\n\n        location /server1/location1 {\n            root myweb;\n            index index.svr1-loc1.htm;\n        }\n\n        location /server1/location2 {\n            root myweb;\n            index index.svr1-loc2.htm;\n        }\n    }\n\n    server {\n        listen 8082;\n        server_name 192.168.0.254;\n\n        auth_basic \"please Login:\";\n        auth_basic_user_file /opt/X_nginx/nginx/myweb/user_passwd;\n\n        access_log myweb/server2/log/access.log;\n        error_page 404 /404.html;\n\n        location /server2/location1 {\n            root myweb;\n            index index.svr2-loc1.htm;\n        }\n\n        location /svr2/loc2 {\n            alias myweb/server2/location2/;\n            index index.svr2-loc2.htm;\n        }\n\n        location = /404.html {\n            root myweb/;\n            index 404.html;\n        }\n    }\n}\n```\n\n```\n#./sbin/nginx -c conf/nginx02.conf\nnginx: [warn] the \"user\" directive makes sense only if the master process runs with super-user privileges, ignored in /opt/X_nginx/nginx/conf/nginx02.conf:1\n.\n├── 404.html\n├── server1\n│   ├── location1\n│   │   └── index.svr1-loc1.htm\n│   ├── location2\n│   │   └── index.svr1-loc2.htm\n│   └── log\n│       └── access.log\n└── server2\n    ├── location1\n    │   └── index.svr2-loc1.htm\n    ├── location2\n    │   └── index.svr2-loc2.htm\n    └── log\n        └── access.log\n\n8 directories, 7 files\n```\n### 测试 myServer1 的访问\n\n```\nhttp://myserver1:8081/server1/location1/\nthis is server1/location1/index.svr1-loc1.htm\n\nhttp://myserver1:8081/server1/location2/\nthis is server1/location1/index.svr1-loc2.htm\n```\n\n### 测试 myServer2 的访问\n```\nhttp://192.168.0.254:8082/server2/location1/\nthis is server2/location1/index.svr2-loc1.htm\n\nhttp://192.168.0.254:8082/svr2/loc2/\nthis is server2/location1/index.svr2-loc2.htm\n\nhttp://192.168.0.254:8082/server2/location2/\n404 404 404 404\n```\n## 使用缓存\n\n创建缓存目录\n\n```\nmkdir  /tmp/nginx_proxy_cache2\nchmod 777 /tmp/nginx_proxy_cache2\n```\n\n修改配置文件\n\n```\n# http 区域下添加缓存区配置\nproxy_cache_path /tmp/nginx_proxy_cache2 levels=1 keys_zone=cache_one:512m inactive=60s max_size=1000m;\n\n# server 区域下添加缓存配置\n#缓存相应的文件（静态文件）\nlocation ~ \\.(gif|jpg|png|htm|html|css|js|flv|ico|swf)(.*) {\n     proxy_pass http://IP: 端口；#如果没有缓存则通过 proxy_pass 转向请求\n     proxy_redirect off;\n     proxy_set_header Host $host;\n     proxy_cache cache_one;\n     proxy_cache_valid 200 302 1h;            #对不同的 HTTP 状态码设置不同的缓存时间，h 小时，d 天数\n     proxy_cache_valid 301 1d;\n     proxy_cache_valid any 1m;\n     expires 30d;\n}\n```\n\n## 使用 location 反向代理到已有网站\n\n```\nlocation ~/bianque/(.*)$ {\n        proxy_pass http://127.0.0.1:8888/$1/?$args;\n    }\n```\n> * 加内置变量 $args 是保障 nginx 正则捕获 get 请求时不丢失，如果只是 post 请求，`$args`是非必须的\n> * `$1` 取自正则表达式部分 () 里的内容\n\n## 其他\n\n### ngx_http_sub_module 替换响应中内容\n\n* ngx_http_sub_module nginx 用来替换响应内容的一个模块（应用：有些程序中写死了端口，可以通过此工具将页面中的端口替换为其他端口）\n\n### 配置 http 强制跳转 https\n\n在 nginx 配置文件中的 server 区域添加如下内容\n```\nif ($scheme = 'http') {\n    rewrite ^(.*)$ https://$host$uri;\n}\n```\n"
  },
  {
    "path": "doc/web/web_base.md",
    "content": "## web 基础\n<!-- vim-markdown-toc GFM -->\n\n* [1 一次完整的 HTTP 请求所经历的 7 个步骤](#1-一次完整的-http-请求所经历的-7-个步骤)\n* [2 HTTP 报文](#2-http-报文)\n    * [2.1 HTTP 请求报文解剖](#21-http-请求报文解剖)\n        * [HTTP 请求报文头属性](#http-请求报文头属性)\n        * [常见的 HTTP 请求报文头属性](#常见的-http-请求报文头属性)\n    * [2.2 HTTP 响应报文解剖](#22-http-响应报文解剖)\n        * [响应报文结构](#响应报文结构)\n        * [响应状态码](#响应状态码)\n        * [常见的 HTTP 响应报文头属性](#常见的-http-响应报文头属性)\n\n<!-- vim-markdown-toc -->\n\n## 1 一次完整的 HTTP 请求所经历的 7 个步骤\n\nHTTP 通信机制是在一次完整的 HTTP 通信过程中，Web 浏览器与 Web 服务器之间将完成下列 7 个步骤：\n\n> * (1) 建立 TCP 连接\n>   * 在 HTTP 工作开始之前，Web 浏览器首先要通过网络与 Web 服务器建立连接，该连接是通过 TCP 来完成的，该协议与 IP 协议共同构建 Internet，即著名的 TCP/IP 协议族，因此 Internet 又被称作是 TCP/IP 网络。HTTP 是比 TCP 更高层次的应用层协议，根据规则， 只有低层协议建立之后才能，才能进行更层协议的连接，因此，首先要建立 TCP 连接，一般 TCP 连接的端口号是 80。\n> * (2) Web 浏览器向 Web 服务器发送请求命令\n>   * 一旦建立了 TCP 连接，Web 浏览器就会向 Web 服务器发送请求命令。例如：GET/sample/hello.jsp HTTP/1.1。\n> * (3) Web 浏览器发送请求头信息\n>   * 浏览器发送其请求命令之后，还要以头信息的形式向 Web 服务器发送一些别的信息，之后浏览器发送了一空白行来通知服务器，它已经结束了该头信息的发送。\n> * (4) Web 服务器应答\n>   * 客户机向服务器发出请求后，服务器会客户机回送应答， HTTP/1.1 200 OK ，应答的第一部分是协议的版本号和应答状态码。\n> * (5) Web 服务器发送应答头信息\n>   * 正如客户端会随同请求发送关于自身的信息一样，服务器也会随同应答向用户发送关于它自己的数据及被请求的文档。\n> * (6) Web 服务器向浏览器发送数据\n>   * Web 服务器向浏览器发送头信息后，它会发送一个空白行来表示头信息的发送到此为结束，接着，它就以 Content-Type 应答头信息所描述的格式发送用户所请求的实际数据。\n> * Web 服务器关闭 TCP 连接\n>   * 一般情况下，一旦 Web 服务器向浏览器发送了请求数据，它就要关闭 TCP 连接，然后如果浏览器或者服务器在其头信息加入了这行代码：\n>   * Connection:keep-alive\n>   * TCP 连接在发送后将仍然保持打开状态，于是，浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间，还节约了网络带宽。\n\n## 2 HTTP 报文\n\nHTTP 报文是面向文本的，报文中的每一个字段都是一些 ASCII 码串，各个字段的长度是不确定的。HTTP 有两类报文：请求报文和响应报文。\n\n### 2.1 HTTP 请求报文解剖\n\nHTTP 请求报文由 3 部分组成（请求行 + 请求头 + 请求体）：\n\n![Screenshot](../../images/web/request_simple.png)\n\n下面是一个实际的请求报文：\n\n![Screenshot](../../images/web/request.png)\n\n> * (1) 是请求方法，GET 和 POST 是最常见的 HTTP 方法，除此以外还包括 DELETE、HEAD、OPTIONS、PUT、TRACE。不过，当前的大多数浏览器只支持 GET 和 POST，Spring 3.0 提供了一个 HiddenHttpMethodFilter，允许你通过“_method”的表单参数指定这些特殊的 HTTP 方法（实际上还是通过 POST 提交表单）。服务端配置了 HiddenHttpMethodFilter 后，Spring 会根据_method 参数指定的值模拟出相应的 HTTP 方法，这样，就可以使用这些 HTTP 方法对处理方法进行映射了。\n> * (2) 为请求对应的 URL 地址，它和报文头的 Host 属性组成完整的请求 URL，\n> * (3) 是协议名称及版本号。\n> * (4) 是 HTTP 的报文头，报文头包含若干个属性，格式为“属性名：属性值”，服务端据此获取客户端的信息。\n> * (5) 是报文体，它将一个页面表单中的组件值通过 param1=value1&param2=value2 的键值对形式编码成一个格式化串，它承载多个请求参数的数据。不但报文体可以传递请求参数，请求 URL 也可以通过类似于“/chapter15/user.html? param1=value1&param2=value2”的方式传递请求参数。\n\n对照上面的请求报文，我们把它进一步分解，你可以看到一幅更详细的结构图：\n\n![Screenshot](../../images/web/request2.png)\n\n#### HTTP 请求报文头属性\n\n报文头属性是什么东西呢？我们不妨以一个小故事来说明吧。\n```\n快到中午了，张三丰不想去食堂吃饭，于是打电话叫外卖：老板，我要一份『鱼香肉丝』，要 12：30 之前给我送过来哦，我在江湖湖公司研发部，叫张三丰。\n```\n这里，你要『鱼香肉丝』相当于 HTTP 报文体，而“12：30 之前送过来”，你叫“张三丰”等信息就相当于 HTTP 的报文头。它们是一些附属信息，帮忙你和饭店老板顺利完成这次交易。\n\n请求 HTTP 报文和响应 HTTP 报文都拥有若干个报文关属性，它们是为协助客户端及服务端交易的一些附属信息。\n\n\n#### 常见的 HTTP 请求报文头属性\n\n**Accept**\n\n请求报文可通过一个“Accept”报文头属性告诉服务端 客户端接受什么类型的响应。\n\n如下报文头相当于告诉服务端，俺客户端能够接受的响应类型仅为纯文本数据啊，你丫别发其它什么图片啊，视频啊过来，那样我会歇菜的~~~：\n```\nAccept:text/plain\n```\nAccept 属性的值可以为一个或多个 MIME 类型的值，关于 MIME 类型，大家请参考：http://en.wikipedia.org/wiki/MIME_type\n\n**Cookie**\n\n客户端的 Cookie 就是通过这个报文头属性传给服务端的哦！如下所示：\n```\nCookie: $Version=1; Skin=new;jsessionid=5F4771183629C9834F8382E23BE13C4C\n```\n\n服务端是怎么知道客户端的多个请求是隶属于一个 Session 呢？注意到后台的那个 jsessionid=5F4771183629C9834F8382E23BE13C4C 木有？原来就是通过 HTTP 请求报文头的 Cookie 属性的 jsessionid 的值关联起来的！（当然也可以通过重写 URL 的方式将会话 ID 附带在每个 URL 的后面哦）。\n\n**Referer**\n\n表示这个请求是从哪个 URL 过来的，假如你通过 google 搜索出一个商家的广告页面，你对这个广告页面感兴趣，鼠标一点发送一个请求报文到商家的网站，这个请求报文的 Referer 报文头属性值就是 http://www.google.com。\n```\n唐僧到了西天。\n如来问：侬是不是从东土大唐来啊？\n唐僧：厉害！你咋知道的！\n如来：呵呵，我偷看了你的 Referer...\n```\n**Cache-Control**\n\n对缓存进行控制，如一个请求希望响应返回的内容在客户端要被缓存一年，或不希望被缓存就可以通过这个报文头达到目的。\n\n如以下设置，相当于让服务端将对应请求返回的响应内容不要在客户端缓存：\n```\nCache-Control: no-cache\n```\n其它请求报文头属性\n\n参见：http://en.wikipedia.org/wiki/List_of_HTTP_header_fields\n\n### 2.2 HTTP 响应报文解剖\n\n#### 响应报文结构\n\nHTTP 的响应报文也由三部分组成（响应行 + 响应头 + 响应体）：\n\n![Screenshot](../../images/web/response_simple.png)\n\n以下是一个实际的 HTTP 响应报文：\n\n![Screenshot](../../images/web/response.png)\n\n\n> * (1) 报文协议及版本；\n> * (2) 状态码及状态描述；\n> * (3) 响应报文头，也是由多个属性组成；\n> * (4) 响应报文体，即我们真正要的“干货”。\n\n#### 响应状态码\n\n和请求报文相比，响应报文多了一个“响应状态码”，它以“清晰明确”的语言告诉客户端本次请求的处理结果。\n\nHTTP 的响应状态码由 5 段组成：\n\n> * 1xx 消息，一般是告诉客户端，请求已经收到了，正在处理，别急...\n> * 2xx 处理成功，一般表示：请求收悉、我明白你要的、请求已受理、已经处理完成等信息。\n> * 3xx 重定向到其它地方。它让客户端再发起一个请求以完成整个处理。\n> * 4xx 处理发生错误，责任在客户端，如客户端的请求一个不存在的资源，客户端未被授权，禁止访问等。\n> * 5xx 处理发生错误，责任在服务端，如服务端抛出异常，路由出错，HTTP 版本不支持等。\n\n\n以下是几个常见的状态码：\n\n> * 200 OK\n\n你最希望看到的，即处理成功！\n\n> * 303 See Other\n\n我把你 redirect 到其它的页面，目标的 URL 通过响应报文头的 Location 告诉你。\n```\n悟空：师傅给个桃吧，走了一天了\n唐僧：我哪有桃啊！去王母娘娘那找吧\n```\n> * 304 Not Modified\n\n告诉客户端，你请求的这个资源至你上次取得后，并没有更改，你直接用你本地的缓存吧，我很忙哦，你能不能少来烦我啊！\n\n> * 404 Not Found\n\n你最不希望看到的，即找不到页面。如你在 google 上找到一个页面，点击这个链接返回 404，表示这个页面已经被网站删除了，google 那边的记录只是美好的回忆。\n\n> * 500 Internal Server Error\n\n看到这个错误，你就应该查查服务端的日志了，肯定抛出了一堆异常，别睡了，起来改 BUG 去吧！\n\n其它的状态码参见：http://en.wikipedia.org/wiki/List_of_HTTP_status_codes\n\n有些响应码，Web 应用服务器会自动给生成。你可以通过 HttpServletResponse 的 API 设置状态码：\n\n#### 常见的 HTTP 响应报文头属性\n\n**Cache-Control**\n\n响应输出到客户端后，服务端通过该报文头属告诉客户端如何控制响应内容的缓存。\n\n下面，的设置让客户端对响应内容缓存 3600 秒，也即在 3600 秒内，如果客户再次访问该资源，直接从客户端的缓存中返回内容给客户，不要再从服务端获取（当然，这个功能是靠客户端实现的，服务端只是通过这个属性提示客户端“应该这么做”，做不做，还是决定于客户端，如果是自己宣称支持 HTTP 的客户端，则就应该这样实现）。\n\n```\nCache-Control: max-age=3600\n```\n\n**ETag** \n\n一个代表响应服务端资源（如页面）版本的报文头属性，如果某个服务端资源发生变化了，这个 ETag 就会相应发生变化。它是 Cache-Control 的有益补充，可以让客户端“更智能”地处理什么时候要从服务端取资源，什么时候可以直接从缓存中返回响应。\n\n关于 ETag 的说明，你可以参见：http://en.wikipedia.org/wiki/HTTP_ETag。\n\n**Location**\n\n我们在 JSP 中让页面 Redirect 到一个某个 A 页面中，其实是让客户端再发一个请求到 A 页面，这个需要 Redirect 到的 A 页面的 URL，其实就是通过响应报文头的 Location 属性告知客户端的，如下的报文头属性，将使客户端 redirect 到 iteye 的首页中：\n\n```\nLocation: http://www.iteye.com\n```\n\n\n**Set-Cookie**\n\n服务端可以设置客户端的 Cookie，其原理就是通过这个响应报文头属性实现的：\n\n```\nSet-Cookie: UserID=JohnDoe; Max-Age=3600; Version=1\n```\n其它 HTTP 响应报文头属性\n\n更多其它的 HTTP 响应头报文，参见：http://en.wikipedia.org/wiki/List_of_HTTP_header_fields\n\n"
  },
  {
    "path": "run_web.sh",
    "content": "#########################################################################\n# File Name: run_web.sh\n# Author: Bill\n# mail: \n# Created Time: 2016-10-12 13:17:27\n#########################################################################\n#!/bin/bash\n# 通过markdown文件生成html文件，并建立web service访问html\n\nHTML_DIR='html_tmp'\n\nPORT=8000\nif [ $# -ne 0 ]\nthen\n    if [[ $1 =~ ^[0-9]+$ ]]\n    then\n        PORT=$1\n        echo ${PORT}\n    fi\nfi\n\nCHECK=`rpm -qa | grep 'pandoc'| wc -l`\nif [[ w${CHECK} == w0 ]]\nthen\n    echo \"this system not have pandoc!\"\n    echo \"you must run:yum -y install pandoc\"\n    exit\nfi\n[[ -d \"${HTML_DIR}\" ]] && rm -rf ${HTML_DIR}\nmkdir -p ${HTML_DIR}\ncp -rf ./images ${HTML_DIR}\n\nfind . -name \"*.md\" | while read md_file\ndo\n    #echo ${md_file}\n    PATH_FILE=`dirname ${md_file}`\n    if [ ! -d \"${HTML_DIR}/${PATH_FILE}\" ]\n    then\n        mkdir -p ${HTML_DIR}/${PATH_FILE}\n    fi\n    file_name=$(echo ${md_file} | sed \"s/\\.md$//\")\n    pandoc -f markdown -t html --html5 --atx-headers ${md_file} > ${HTML_DIR}/${file_name}.html\n    sed -i \"s/.md/.html/g\" ${HTML_DIR}/${file_name}.html\ndone\n\ncd ${HTML_DIR}\necho \"http://localhost:${PORT}\"\npython -m SimpleHTTPServer ${PORT}\n"
  },
  {
    "path": "standard.md",
    "content": "# 文档规范\n<!-- vim-markdown-toc GFM -->\n* [1 工具说明](#1-工具说明)\n* [2 文档章节的划分](#2-文档章节的划分)\n    * [2.1 文章标题](#21-文章标题)\n    * [2.2 文章目录结构](#22-文章目录结构)\n* [3 各种信息](#3-各种信息)\n    * [3.1 信息说明](#31-信息说明)\n    * [3.2 三种提示信息的书写方法：](#32-三种提示信息的书写方法)\n* [4 内容相关](#4-内容相关)\n    * [4.1 需要高亮的内容如下：](#41-需要高亮的内容如下)\n    * [4.2 界面相关](#42-界面相关)\n    * [4.3 加粗或斜体](#43-加粗或斜体)\n    * [4.4 参见的书写](#44-参见的书写)\n    * [4.5 图片的插入](#45-图片的插入)\n    * [4.6 过程的书写](#46-过程的书写)\n    * [4.7 表格的书写](#47-表格的书写)\n\n<!-- vim-markdown-toc -->\n# 1 工具说明\n\n日常编写文档使用 [Vim](https://github.com/meetbill/Vim)，此程序还可以自动生成目录\n\n文章中的目录部分均由 Vim 自动更新和生成\n\n# 2 文档章节的划分\n\n## 2.1 文章标题\n\n原则上，不超过三级标题，如有需要，可以根据需要扩展。\n\n## 2.2 文章目录结构\n目录结构如下：\n\n```\n第1篇--第一章--1.1--1.1.1\n|        |      |---1.1.2\n|        |      |---1.2.3\n|        |-----1.2--1.2.1\n|        |      |---1.2.2\n|------第二章--2.1--2.1.1\n|        |      |---2.1.2\n|        |-----2.2--2.2.1\n|        |      |\n|        |-----2.3--2.3.1\n|------第三章--3.1--3.1.1\n```\n其中，每个文档中包含一个 **SUMMARY.md** 文件，将目录写入其中。\n\n按照如上的目录结构，每章一个文件\n\n> * **标题约定**:\n> * \n> * `# 一级标题`\n> * `## 二级标题`\n> * `### 三级标题`\n> * 以此类推。\n\n# 3 各种信息\n\n## 3.1 信息说明\n提示信息分为：注意、重要、警告。\n\n> * 注意：对目前任务的提示、捷径或者备选的解决方法。忽略提示不会造成负面后果，但可能会错过一个更省事的诀窍。\n> * 重要：重要框中的内容是那些容易错过的事情。配置更改只可用于当前会话，或者在应用更新前要重启的服务。忽略\"重要\"框中的内容不会造成数据丢失但可能会让您抓狂。\n> * 警告：警告是不应被忽略的。忽略警告信息很可能导致数据丢失。\n\n## 3.2 三种提示信息的书写方法：\n\n```\n> ###### 注意\n> 注意的内容\n```\n```\n> #### 重要\n> 重要的内容\n```\n```\n> ## 警告\n> 警告的内容\n```\n\n效果如下：\n\n> ###### 注意\n> 注意的内容\n\n> #### 重要\n> 重要的内容\n\n> ## 警告\n> 警告的内容\n\n\n# 4 内容相关\n\n## 4.1 需要高亮的内容如下：\n\n  * 所需要修改的配置文件，如：\n\n    修改配置文件 `/etc/nova/nova.conf`。\n\n    * 书写方法如下：\n    ```\n    修改配置文件 `/etc/nova/nova.conf`。\n    ```\n\n  * 所需要修改的字段，如：\n\n    修改配置文件中的 `auth_url`。\n\n    * 书写方法如下：\n\n    ```\n    修改配置文件中的 `auth_url`。\n    ```\n\n  * 要执行的命令，如：\n\n    执行命令 `nova list`。\n\n    或\n\n    执行如下命令：\n\n      ```\n      # nova list\n      ```\n\n    * 书写方法如下：\n\n      ```\n      执行命令 `nova list`。\n\n      或\n\n      执行如下命令：\n\n        ```\n        # nova list\n        ```\n      ```\n\n  * 代码，如：\n\n    代码如下：\n\n    ```python\n    # @file setup.py\n    from setuptools import setup\n\n    setup(\n        # Other keywords\n        entry_points={\n            'foo': [\n                'add = add:make',\n                'remove = remove:make',\n                'update = update:make',\n            ],\n        }\n    )\n    ```\n\n    * 书写方法如下：\n\n        ```\n        代码如下：\n\n            ```python\n            # @file setup.py\n            from setuptools import setup\n\n            setup(\n                # Other keywords\n                entry_points={\n                    'foo': [\n                        'add = add:make',\n                        'remove = remove:make',\n                        'update = update:make',\n                    ],\n                }\n            )\n            ```\n        ```\n\n## 4.2 界面相关\n\n  描述界面选项卡或按键时，使用【】，如：\n\n  ```\n  选择【项目】，点击【概况】选项卡，可以查看项目的概况信息。\n  ```\n\n## 4.3 加粗或斜体\n\n  * 加粗：某个命令的名称，如：\n\n    可以使用 **nova** 命令进行操作。（注意与上文的**执行命令**区分）\n\n    * 书写方法如下：\n\n      ```\n      可以使用 **nova** 命令进行操作。\n      ```\n\n  * 斜体：描述某个命令的参数或需要替换的字段时，如：\n\n    **nova** 命令的 *--debug* 参数用于......\n\n    将其中的 *NOVA_PASS* 替换为 nova 用户的密码。\n\n      * 书写方法如下：\n\n        ```\n        **nova** 命令的 *--debug* 参数用于......\n        ```\n        ```\n        将其中的 *NOVA_PASS* 替换为 nova 用户的密码。\n        ```\n\n  > **注**：其他时候可以根据需要加粗或写为斜体，另：加粗并斜体的书写方法为 `***--debug***`。\n\n## 4.4 参见的书写\n\n  有时需要一些参考内容，书写为：\n\n  **参见**\n\n  [Google](http://www.google.com)\n\n  * 书写方法如下：\n\n    ```\n    **参见**\n\n    [Google](http://www.google.com)\n    ```\n\n## 4.5 图片的插入\n\n  有时需要插入一些图片进行说明，书写为：\n\n  ```\n  ![图片名称](图片链接)\n\n  > **图片名称**\n\n  ```\n\n## 4.6 过程的书写\n\n  需要描述一些过程时，书写如下：\n\n  ```\n\n  > **过程**：过程名称\n\n  1. xxx(第一步)\n\n    1.1 xxx(第一步的第一个小步骤)\n\n  2. xxx(第二步)\n\n  3. xxx(第三步)\n\n  ```\n\n## 4.7 表格的书写\n\n  使用到表格时，书写如下：\n\n  ```\n  > **表格**：表格标题\n\n  |第一列|第二列|第三列|\n  |------|------|------|\n  | 内容 | 内容 | 内容 |\n\n  ```\n\n  效果如下：\n\n  > **表格**：表格标题\n\n  |第一列|第二列|第三列|\n  |------|------|------|\n  | 内容 | 内容 | 内容 |\n"
  }
]