nfs客户端启动报错rpc.mountd: svc_tli_create: could not open connection for tcp6

# /etc/init.d/nfs start
Starting NFS services:                                     [  OK  ]
Starting NFS mountd: rpc.mountd: svc_tli_create: could not open connection for udp6
rpc.mountd: svc_tli_create: could not open connection for tcp6
rpc.mountd: svc_tli_create: could not open connection for udp6
rpc.mountd: svc_tli_create: could not open connection for tcp6
rpc.mountd: svc_tli_create: could not open connection for udp6
rpc.mountd: svc_tli_create: could not open connection for tcp6
                                                           [FAILED]
Starting NFS daemon: rpc.nfsd: writing fd to kernel failed: errno 111 (Connection refused)
rpc.nfsd: address family inet6 not supported by protocol TCP
rpc.nfsd: unable to set any sockets for nfsd
                                                           [FAILED]

根据启动提示可以获知,inet6地址族不被支持,原因是当前主机没有加载ipv6的模块,可以重新加载一遍ipv6模块解决这个问题。

由于我的系统不需要ipv6的支持,所以还可以通过下面的操作,取消NFS的ipv6调用。

# cat /etc/netconfig
#
# The network configuration file. This file is currently only used in
# conjunction with the TI-RPC code in the libtirpc library.
#
# Entries consist of:
#
#       <network_id> <semantics> <flags> <protofamily> <protoname> 
#               <device> <nametoaddr_libs>
#
# The <device> and <nametoaddr_libs> fields are always empty in this
# implementation.
#
udp        tpi_clts      v     inet     udp     -       -
tcp        tpi_cots_ord  v     inet     tcp     -       -
udp6       tpi_clts      v     inet6    udp     -       -
tcp6       tpi_cots_ord  v     inet6    tcp     -       -
rawip      tpi_raw       -     inet      -      -       -
local      tpi_cots_ord  -     loopback  -      -       -
unix       tpi_cots_ord  -     loopback  -      -       -
注掉:udp6,tcp6后重启nfs

还是报错:

# /etc/init.d/nfs start
Starting NFS services:                                     [  OK  ]
Starting NFS mountd:                                       [FAILED]
Starting NFS daemon: rpc.nfsd: writing fd to kernel failed: errno 111 (Connection refused)
rpc.nfsd: unable to set any sockets for nfsd
                                                           [FAILED]

还没启动rpcbin

# /etc/init.d/rpc
rpcbind     rpcgssd     rpcidmapd   rpcsvcgssd 
[root@208LBT /]# /etc/init.d/rpcbind start
Starting rpcbind:                                          [  OK  ]
# /etc/init.d/nfs start
Starting NFS services:                                     [  OK  ]
Starting NFS mountd:                                       [  OK  ]
Starting NFS daemon:                                       [  OK  ]
Starting RPC idmapd:                                       [  OK  ]

MySQL备份工具Xtrabackup锁问题

从XtraBackup的备份过程可以看出,XtraBackup可以实现Innodb表的无锁备份,但是一个数据库中,即使所有的业务表都是innodb表,但是还存在一些MySQL系统库下的user表等,均是myisam表(MySQL 8.0均替换为InnoDB),同时备份过程需要获取Binlog文件名和位置,也要保证表定义文件的一致性,所以从整个实例的角度,即使用XtraBackup还是有一段时间需要执行Flush table with read lock全局锁的,会对用户访问产生影响,同时由于Flush table with read lock的一些特殊性,如果稍不注意,可能会对用户访问数据库产生致命影响。

MySQL官网文档对Flush tables with read lock的解释:

Closes all open tables and locks all tables for all databases with a global read lock.

If the thread that is doing FLUSH TABLES has a lock on some tables, it will first close the locked tables, then wait until all other threads have also closed them, and then reopen them and get the locks. After this it will give other threads a chance to open the same tables.

从上面的这段话,我们可以得到两个结论:

  • Flush tables with read lock会上一个实例级别的全局锁,该锁与Lock tables或者Select for update等互斥,即如果前面有上述锁,会导致Flush tables with read lock阻塞。

  • Flush tables with read lock首先需要关闭所有的表,然后再打开所有的表。如果有线程正在扫描表,Flush tables with read lock会被阻塞,一直等到该表允许被关闭为止。如果有一个select count(*)的慢查询,会阻塞Flush tables with read lock。

可能大家认为Flush tables with read lock仅仅是一把读锁,即使阻塞了也不会影响正常的读,但是事实不是这个样子的。Peter Zaitsev 在文献【1】中提到了Flush tables with read lock的潜在风险。概括的讲,如果Flush table with read lock执行完毕,成功获取到了全局实例锁,后续的快照读和S锁的读是没有问题的,只是阻塞DDL、写;但是如果一旦因为表无法关闭或者因为其他的锁导致无法正常获取到表锁使得Flush table with read lock阻塞,这个后果将是灾难性的,所有的读,无论是快照读,还是S锁或者X锁的读,均会被阻塞,因为Flush table with read lock需要关闭表,这点是需要所有数据库运维人员警惕的,我们的数据库也因此导致了服务的长时间不可用。

XtraBackup从1.4到2.1.3均存在这样的隐患,在2.1.4的Release Notes中我们找到了相关描述:

Percona XtraBackup has introduced additional options to handle the locking during the FLUSH TABLES WITHREAD LOCK.These options can be used to minimize the amount of the time when MySQL operates in the read-only mode.

显然,Percona已经意识到了这个问题的严重性,提供了两种解决问题的思路:

1、设置超时时间

XtraBackup设置一个超时时间,避免无限期的等待。Xtrabackup提供了一下参数实现该功能:

  • –lock-wait-timeout=SECONDS :一旦Flush table with read lock被阻塞超过预定时间,则XtraBackup出错返回退出,该值默认为0,也就是说一旦阻塞,立即返回失败。

  • –lock-wait-query-type=all|update :该参数允许用户指定,哪类的SQL语句是需要Flush table with read lock等待的,同时用户可以通过–lock-wait-threshold=SECONDS设置等待的时间,如果不在query-type指定的类型范围内或者超过了wait-threshold指定的时间,XtraBackup均返回错误。如果指定update类型,则UPDATE/ALTER/REPLACE /INSERT 均会等待,ALL表示所有的SQL语句。

2、kill其他阻塞线程

Kill掉所有阻塞Flush table with read lock的线程:

  • –kill-long-queries-timeout=SECONDS :参数允许用户指定了超过该阈值时间的查询会被Kill,同时也允许用户指定Kill SQL语句的类型。

  • –kill-long-query-type=all|select :默认值为ALL,如果选择Select,只有Select语句会被Kill,如果Flush table with read lock是被Update语句阻塞,则XtraBackup不会处理。

数据库运维人员在备份数据库时,应选择正确的XtraBackup版本规避该问题。同时,个人在使用XtraBackup在Slave做备份时,还碰到跟SQL线程产生死锁的情况。MariaDB并行复制,死锁信息如下:

mysql> show processlist;
+---------+-------------+----------------------+--------------------+-------------+---------+--------------------------------------------------------------------------------+-----------------------------+----------+
| Id      | User        | Host                 | db                 | Command     | Time    | State                                                                          | Info                        | Progress |
+---------+-------------+----------------------+--------------------+-------------+---------+--------------------------------------------------------------------------------+-----------------------------+----------+
| 3335182 | system user |                      | NULL               | Connect     | 9556202 | Waiting for master to send event                                               | NULL                        |    0.000 |
| 3335183 | system user |                      | NULL               | Connect     |   15622 | Waiting for prior transaction to start commit before starting next transaction | NULL                        |    0.000 |
| 3335184 | system user |                      | NULL               | Connect     |   15622 | Waiting for global read lock                                                   | NULL                        |    0.000 |
| 3335185 | system user |                      | NULL               | Connect     |   14920 | Waiting for prior transaction to commit                                        | NULL                        |    0.000 |
| 3335195 | system user |                      | NULL               | Connect     |   15622 | Waiting for prior transaction to start commit before starting next transaction | NULL                        |    0.000 |
| 3335196 | system user |                      | NULL               | Connect     |   14920 | Waiting for global read lock                                                   | NULL                        |    0.000 |
| 3335197 | system user |                      | NULL               | Connect     |   14920 | Waiting for prior transaction to start commit before starting next transaction | NULL                        |    0.000 |
| 3335198 | system user |                      | NULL               | Connect     |   14920 | Waiting for prior transaction to start commit before starting next transaction | NULL                        |    0.000 |
| 3335199 | system user |                      | NULL               | Connect     |   21335 | Waiting for room in worker thread event queue                                  | NULL                        |    0.000 |
| 4525735 | backupuser  | localhost            | NULL               | Query       |   14920 | Waiting for commit lock                                                        | FLUSH TABLES WITH READ LOCK |    0.000 |
+---------+-------------+----------------------+--------------------+-------------+---------+--------------------------------------------------------------------------------+-----------------------------+----------+
26 rows in set (0.01 sec)

MySQL sysbench基准测试

一、基准测试

参考《高性能 MySQL》第二章。

二、Sysbench

sysbench 是开源的跨平台多线程基准测试工具,主要用于测试各种不同系统参数下的 CPU/内存/线程/IO/数据库等方面的性能,数据库目前支持 MySQL/Oracle/PostgreSQL。具体的参数设置,应根据实际环境来进行必要调整。

与之前版本相比,sysbench 最新的 0.5 版本,可以使用脚本来决定测试语句,比之前在代码里写死测试更加方便用户修改和使用,不需要去修改源程序,只需要修改相应的 lua 脚本,即可定制不同的测试用例,在数据库负载测试中,这样可以对 SQL 语句进行更有针对性的测试。

1、下载安装

Linu 自带版本大多为 0.4.12,最新版本可以从 Launchpad 下载安装步骤如下:

./autogen.sh
./configure
make && sudo make install

sysbench 依赖 mysql-dev 包的支持,如果 mysql 没有安装在默认位置,执行./configure 时需要配置–with-mysql-includes 和 –with-mysql-lib。具体参看源码包中 README 文档。

安装完成后可以查看版本信息。

sysbench --version

2、使用说明

简要说明 sysbench 的使用方法,侧重对数据库的测试。具体用法参考 sysbench –help。

2.1 命令格式:

Sysbench [general-options]… –test=<test-name> [test-options]… command

通用选项(general-options):

  • –num-threads=N 指定要使用的线程

  • –report-interval=N 每隔 N 秒打印统计信息

  • –rand-XXX 随机分布相关配置

内建测试项目(test-option):

  • fileio – File I/O test
  • cpu – CPU performance test
  • memory – Memory functions speed test
  • threads – Threads subsystem performance test
  • mutex – Mutex performance test

oltp,从 0.5 开始不再设置单独的选项,可以直接通过 Lua 脚本文件进行测试,兼容之前 oltp 的所有选项。

以上所有的项目都可以通过 sysbench-0.5sysbenchtests 下的测试脚本进行测试。

2.2 测试项目选项(test-option)

各种内建测试项目的选项可以通过命令 sysbench –test= help 查看。

2.3 命令(command):

sysbench 做压力测试的时候分 3 个阶段:prepare(准备测试数据); run(运行压力测试); cleanup(清理测试数据)。

3、内建测试说明

3.1 CPU

sysbench 采用寻找最大素数的方式来测试 CPU 的性能。

sysbench --test=cpu  --cpu-max-prime=1000 run

3.2 Fileio

Sysbench 的 I/O 测试和 InnoDB 的 I/O 模式非常类似。

sysbench --test=fileio –file_num=40 –-file-total-size=80G --file-test-mode=rndrw prepare
sysbench --test=fileio –file_num=40 –-file-total-size=80G --file-test-mode=rndrw run
sysbench --test=fileio –file_num=40 –-file-total-size=80G --file-test-mode=rndrw cleanup

3.3 Memory

待完善

3.4 Threads

待完善

3.5 Mutex

待完善

3.6 OLTP

Sysbench 0.5 中的 oltp.lua 提供了一个比之前版本中的 oltp 模式更为真实的场景来进行数据库的基准测试。和之前 oltp 模式中的单个表场景相比,0.5 通过 Lua 脚本可以对多个表进行工作测试。oltp.lua 可以理解原先 oltp 模式中的大多数选项。

所有的测试脚本位于/sysbench-0.5/sysbench/test/下,db 目录下是数据库测试项目,其中 common.lua 并非测试文件,是用于 prepare 和 cleanup。 oltp.lua 文件用于测试事务性能,其中 thread_init 函数来初始化每个线程的参数,初始化工作调用了 common.lua 中的 set_vars()函数,来初始化 oltp 的相关参数。

阶段 1: 连接数据库服务器

每次执行基准测试,不管是 prepare 还是 run,如果不是使用默认值的话,都应该指定如何连接数据库。默认值如下:

未分类

默认的数据库 sbtest,sysbench 不会自动创建该数据库。所以如果你要用过的话要首先。

阶段 2:Prepare

如果使用默认值,首先要创建测试所用的表。创建方式有两种: oltp.lua (串行) 和 parallel_prepare.lua (并行)。

未分类

针对 database driver 还需要指明以下参数:

未分类

创建表:

oltp.lua 中提供的 –oltp-tables-count 指明了表的数量。默认的表名是 sbtest。如果制定了 oltp-tables-count,则在表名后加数字,例如 sbtest1, sbtest2, .. sbtest[oltp-tables-count],注意,此种情况下不会创建 sbtest 表。

通过选项 –oltp-secondary 可以在每个表上使用第二索引来替代主键。也就是说通过 KEY xid (ID) 而不是 PRIMARY KEY (ID) 来创建表。这个选项将会使 InnoDB 为每个表创建内部 6-byte 的索引。同样可以使用选项 –oltp-auto-in 将 id 字段设为递增。

未分类

创建表 SQL 语句示例如下:

CREATE TABLE `sbtest101` (
     `id` int(10) unsigned NOT NULL auto_increment,                            
     `k` int(10) unsigned NOT NULL default '0',
     `c` char(120) NOT NULL default '',
     `pad` char(60) NOT NULL default '',
     PRIMARY KEY  (`id`),
     KEY `k` (`k`));

Parallel.lua(并行) 创建:

./sysbench --test=tests/db/parallel_prepare.lua --mysql-user=USER --mysql-password=SECRET --oltp-tables-count=64 --num-threads=8 run

注意:oltp-tables-count 应该是 num-threads 的整数倍。

oltp.lua(串行)创建:

./sysbench --test=tests/db/oltp.lua --mysql-user=USER --mysql-password=SECRET --mysql-table-engine=myisam --oltp-table-size=1000000 --oltp-tables-count=64 --mysql-socket=/tmp/mysql.sock prepare

阶段 3:Run

准备好测试环境之后就可以使用 oltp.lua 执行一系列的测试了,测试使用的线程数量通过选项 –num-threads 来指定。每个线程通过随机产生小于或者等于 oltp-tables-count 的数字来选择一个表。

随机取样分布通过选项 –oltp-dist-type 来进行设置,该选项默认值是 special。Special 分布还和另外两个参数有关:–oltp-dist-pct,用来指定要特殊对待的记录的百分比,–oltp-dist-res 指定这些记录的概率。例如,对 1000 行记录进行 1000 次查询,–oltp-dist-pct=1 and –oltp-dist-res=50 结果,开始 10 条记录(1% of 1000),每条记录选中五十次,总共 500 次,剩余的查询将会从 990 条记录中均匀采样。

选中表之后,就会执行相应的测试。他们将会打包为一个事务(transaction)传递给数据库服务器(除非使用 myisam 引擎,这样先会锁住表)。也可以单线程运行 oltp 的子集,例如 oltp_simple.lua, select.lua, insert.lua, delete.lua, update_index.lua, update_non_index.lua

未分类

  • SELECT tests
    Select 还可以分为点选择测试(Point Select tests)和范围测试(Ranges tests)。

    • 点测试
      选项 oltp-point-selects 单次事务中点选择测试的查询次数。 每次测试,通过制定的分布来随机产生一个小于或者等于表大小(oltp-table-size)的数字,然后执行下面的查询语句。 SELECT c FROM sbtestXXX WHERE id=N

    • 范围测试
      通过变量 oltp-range-size 可以制定要查询的范围(不大于表大小)

    • 简单范围测试
      选项 oltp-simple-ranges 单次事务中范围选择测试的查询次数。 每次通过指定的分布来产生一个不大于 oltp-talbe-size 的整数 N,然后通过选项设置 oltp-range-size 设置整数 M,然后执行如下查询: SELECT c FROM sbtest WHERE id BETWEEN N AND M

  • 范围求和(Sum in ranges)
    选项 oltp_sum_ranges 单次事务中范围选择测试的查询次数。查询语句: SELECT SUM(K) FROM sbtest WHERE id BETWEEN N and M

  • 范围排序(Order in ranges)
    选项 oltp_order_ranges 单次事务中范围选择测试的查询次数。查询语句: SELECT c FROM sbtest WHERE id between N and M ORDER BY c

  • 范围去重(Distincts in ranges)
    选项 oltp-distinct-ranges 单次事务中范围选择测试的查询次数。查询语句:

SELECT DISTINCT c FROM sbtest WHERE id BETWEEN N and M ORDER BY c

未分类

  • UPDATE tests

只要没有指定 oltp-read-only=on 就能进行更新测试。

未分类

1、index_update.lua

选项 oltp_index_updates 单次事务中范围选择测试的查询次数。查询语句:

UPDATE sbtest SET k=k+1 WHERE id=N

2、non_index_update.lua

选项 oltp-non-index-updates 单次事务中范围选择测试的查询次数。C 为随机产生的字符串,查询语句:

UPDATE sbtest SET c=C WHERE id=N
  • DELETE test

只要没有指定 oltp-read-only=on 就能进行更新测试。通过执行分布产生一个不大于 oltp-table-siez 的数字 N,执行语句:

DELETE FROM sbtest WHERE id=N

  • INSERT test

只要没有指定 oltp-read-only=on 就能进行更新测试。通过执行分布产生一个不大于 oltp-table-siez 的数字 N,执行语句:

INSERT INTO sbtest (id, k, c, pad) VALUES N, K, C, PAD

  • 使用举例:

使用 5 个线程在 25 个 table 上进行默认测试:

./sysbench --mysql-user=USER --mysql-password=SECRET --test=tests/db/oltp.lua --oltp-tables-count=25 --num-threads=5 run

使用 10 个线程在 100 个 table 上进行 select 测试,10 个点测试和值为 1000 的范围测试:

./sysbench --mysql-user=USER --mysql-password=SECRET --test=tests/db/select.lua --oltp-tables-count=100 --num-threads=10
--oltp-point-selects=100 --oltp-range-size=1000 run

阶段 4:清理(cleanup)

可以通过清理操作来返回到准备的阶段。必须提供链接数据库服务器的选项和创建的表的数量。

./sysbench --test=tests/db/oltp.lua --mysql-user=USER --mysql-password=SECRET --oltp-tables-count=64 cleanup

使用MySQL Sniffer或PacketBeat来实时审计Mysql语句

生产环境中可以使用工具实时审计Mysql流量。

介绍两种方式:

  • MySQL Sniffer
  • PacketBeat

MySQL Sniffer

MySQL Sniffer 是一个基于 MySQL 协议的抓包工具,实时抓取 MySQLServer 端或 Client 端请求,并格式化输出。输出内容包括访问时间、访问用户、来源 IP、访问 Database、命令耗时、返回数据行数、执行语句等。有批量抓取多个端口,后台运行,日志分割等多种使用方式,操作便捷,输出友好。

安装依赖:

yum install glib2-devel libpcap-devel libnet-devel

项目下载地址:

https://github.com/Qihoo360/mysql-sniffer

安装步骤:

cd mysql-sniffer

mkdir proj

cd proj

cmake ../

make

cd bin/

参数如下:

[root@server120 bin]# ./mysql-sniffer -h

Usage ./mysql-sniffer [-d] -i eth0 -p 3306,3307,3308 -l /var/log/mysql-sniffer/ -e stderr

         [-d] -i eth0 -r 3000-4000

         -d daemon mode.

         -s how often to split the log file(minute, eg. 1440). if less than 0, split log everyday

         -i interface. Default to eth0

         -p port, default to 3306. Multiple ports should be splited by ','. eg. 3306,3307

            this option has no effect when -f is set.

         -r port range, Don't use -r and -p at the same time

         -l query log DIRECTORY. Make sure that the directory is accessible. Default to stdout.

         -e error log FILENAME or 'stderr'. if set to /dev/null, runtime error will not be recorded

         -f filename. use pcap file instead capturing the network interface

         -w white list. dont capture the port. Multiple ports should be splited by ','.

         -t truncation length. truncate long query if it's longer than specified length. Less than 0 means no truncation

         -n keeping tcp stream count, if not set, default is 65536. if active tcp count is larger than the specified count, mysql-sniffer will remove the oldest one

测试:

[root@server120 bin]# ./mysql-sniffer -i lo -p 3306

2017-08-16 13:56:04        root        127.0.0.1        NULL                0ms                1  select @@version_comment limit 1

2017-08-16 14:01:56        root        127.0.0.1        NULL                0ms                1  SELECT DATABASE()

2017-08-16 14:01:56        root        127.0.0.1        mysql               0ms                0  use mysql

2017-08-16 14:01:56        root        127.0.0.1        mysql               0ms                5  show databases

2017-08-16 14:01:56        root        127.0.0.1        mysql               0ms               23  show tables

2017-08-16 14:02:04        root        127.0.0.1        mysql               0ms                8  select * from user

输出格式为:时间,访问用户,来源 IP,访问 Database,命令耗时,返回数据行数,执行语句。

保存日志可以用filebeat采集:

[root@server120 bin]# ./mysql-sniffer -i eth0 -p 3306 -l /tmp/
[root@server120 tmp]# head -n 5 3306.log 
2017-08-16 14:04:58  root    192.168.190.201     NULL             0ms             0  SET NAMES utf8
2017-08-16 14:04:58  root    192.168.190.201     NULL             0ms             2  SHOW VARIABLES LIKE 'lower_case_%'
2017-08-16 14:04:58  root    192.168.190.201     NULL             0ms             1  SHOW VARIABLES LIKE 'profiling'
2017-08-16 14:04:58  root    192.168.190.201     NULL             0ms             5  SHOW DATABASES
2017-08-16 14:05:20  root    192.168.190.201     NULL             0ms             0  SET NAMES utf8

-l 指定日志输出路径,日志文件将以 port.log 命名。

需要注意的是:

只能抓取新建的链接,如果是之前创建的链接将获取不到用户名和库名,并有一定几率丢包。

PacketBeat

Packeybeat可以部署在:

  • Mysql服务端
  • 镜像DB服务器上游交换机流量到服务器
yum -y install libpcap
./packetbeat -c packetbeat.yml

packetbeat.yml为配置文件
packetbeat.template.json为mapping文件

测试:

mysql> select host,user from mysql.user;
+-----------+------+
| host      | user |
+-----------+------+
| %         | root |
| %         | soc  |
| 127.0.0.1 | root |
| localhost |      |
| localhost | soc  |
+-----------+------+
5 rows in set (0.00 sec)

输出到Elasticsearch内容如下:

未分类

  • query:执行的SQL语句
  • num_fields:返回的字段数
  • num_rows:查询结果行数

KVM虚拟化平台部署及命令行管理虚拟机教程

KVM是Kernel Virtual Machine的简写,目前Red Hat只支持在64位的RHEL5.4及以上的系统运行KVM,同时硬件需要支持VT技术。KVM的前身是QEMU,2008年被Red Hat公司收购并获得一项hypervisor技术,不过Red Hat的KVM被认为是将成为未来Linux hypervisor的主流,准确来说,KVM仅仅是Linux内核的一个模块。管理和创建完整的KVM虚拟机,需要更多的辅助工具。

案例:搭建KVM虚拟化平台

一、案例分析

1、案例概述

公司部分Linux服务器利用率不高,为充分利用这些Linux服务器,可以部署KVM,在物理机上运行多个业务系统。例如,在运行Nginx的服务器上部署KVM,然后在虚拟机上运行Tomcat

2、案例前置知识点

KVM自Linux 2.6.20版本后就直接整合到Linux内核,它依托CPU和虚拟化指令集(如Inter-VT、AMD-V)实现高性能的虚拟化支持。由于与Linux内核高度整合,因此在性能、安全性、兼容性、稳定性上都有很好的表现

未分类

图上简单描述了KVM虚拟化架构,在KVM环境中运行的每个虚拟化操作系统都将表现为单个独立的系统进程。因此它可以很方便地与Linux系统中的安全模块进行整合(SElinux),可以灵活地实现资源的管理及分配

3、案例环境

采用CentOS 6.6 x86_64,开启CPU虚拟化支持

二、案例实施

1、安装方式

(1)最简单的安装方法就是在安装系统的时候,选择桌面安装,然后选择虚拟化选项

未分类

安装桌面

未分类

安装虚拟化平台

(2)在已有系统基础上,安装KVM所需软件

yum  -y  groupinstall  "Desktop"                  //安装GNOME桌面环境
    yum  -y  install  qemu-kvm.x86_64                  //KVM模块
    yum  -y  install  qemu-kvm-tools.x86_64            //KVM调试工具,可不安装

    yum  -y  install  python-virtinst.noarch          //python组件,记录创建VM时的xml文件

    yum  -y  install  qemu-img.x86_64                  //qemu组件,创建磁盘、启动虚拟机等

    yum  -y  install  bridge-utils.x86_64              //网络支持工具

    yum  -y  install  libvirt                          //虚拟机管理工具

    yum  -y  install  virt-manager                    //图形界面管理虚拟机

    yum  -y  install  libguestfs*                      //virt-cat等命令的支持软件包

(3)验证。重启系统后,查看CPU是否支持虚拟化,对于Inter服务器可以 通过一下命令查看,只要有输出就说明CPU支持虚拟化;AMD服务器可用cat /proc/cpuinfo | grep smv命令查看

[root@localhost ~]# cat  /proc/cpuinfo  |  grep  vmx
flags: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc up arch_perfmon pebs bts xtopology tsc_reliable nonstop_tsc aperfmperf unfair_spinlock pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm ida arat epb xsaveopt pln pts dts tpr_shadow vnmi ept vpid fsgsbase bmi1 avx2 smep bmi2 invpcid

检查KVM模块是否安装:

[root@localhost ~]# lsmod  |  grep  kvm
kvm_intel              55496  0 
kvm                  337772  1 kvm_intel

2、设置KVM网络

宿主服务器安装完成KVM,首先要设定网络,在libvirt中运行KVM网络有两种方法:NAT和Bridge,默认是NAT

关于两种模式的说明:

(1)用户模式,即NAT方式,这种方式是默认网络,数据包由NAT方式通过主机的接口进行传送,可以访问外网,但是无法从外部访问虚拟机网络

(2)桥接模式,这种模式允许虚拟机像一台独立的主机一样拥有网络,外部的机器可以直接访问到虚拟机内部,但需要网卡支持,一般有线网卡都支持

这里以Bridge(桥接)为例

[root@localhost ~]# vim /etc/sysconfig/network-scripts/ifcfg-eth0 
DEVICE=eth0
HWADDR=00:0C:29:4C:0C:24
TYPE=Ethernet
ONBOOT=yes
NM_CONTROLLED=no
BOOTPROTO=none
BRIDGE="br0"

[root@localhost ~]# vim /etc/sysconfig/network-scripts/ifcfg-br0
DEVICE=br0
TYPE=Bridge
ONBOOT=yes
NM_CONTROLLED=no
BOOTPROTO=dhcp

重启network服务

[root@localhost ~]# service network restart
正在关闭接口 br0:                                    [确定]
正在关闭接口 eth0:                                  [确定]
关闭环回接口:                                        [确定]
弹出环回接口:                                        [确定]
弹出界面 eth0:                                      [确定]
弹出界面 br0: 
正在决定 br0 的 IP 信息...完成。                      [确定]

确认IP地址信息

[root@localhost ~]# ifconfig 
br0      Link encap:Ethernet  HWaddr 00:0C:29:4C:0C:24  
          inet addr:192.168.0.106  Bcast:192.168.0.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fe4c:c24/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:70 errors:0 dropped:0 overruns:0 frame:0
          TX packets:25 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:69660 (68.0 KiB)  TX bytes:3066 (2.9 KiB)

eth0      Link encap:Ethernet  HWaddr 00:0C:29:4C:0C:24  
          inet6 addr: fe80::20c:29ff:fe4c:c24/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:28413 errors:0 dropped:0 overruns:0 frame:0
          TX packets:20243 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:37239885 (35.5 MiB)  TX bytes:1683986 (1.6 MiB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:140 errors:0 dropped:0 overruns:0 frame:0
          TX packets:140 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:12096 (11.8 KiB)  TX bytes:12096 (11.8 KiB)

virbr0    Link encap:Ethernet  HWaddr 52:54:00:0B:01:73  
          inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:0 (0.0 b)  TX bytes:0 (0.0 b)

出现以上信息,说明网卡桥接成功了

3、KVM管理

[root@localhost ~]# virt-manager

virt-manager是基于libvirt的图像化虚拟机管理软件。请注意,不同的发行版上virt-manager的版本可能不同,图形界面和操作方法可能不同。本文使用了Centos 6企业版。创建KVM虚拟机最简单的方法是通过virt-manager接口。从控制台窗口启动这个工具,以root身份输入virt-manager命令

未分类

虚拟机管理界面

虚拟机管理步骤如下

(1)创建存储池,双击localhost(QEMU),选择“存储选项卡”,然后单击“+”按钮新建存储池。单击“前进”按钮,根据提示输入或浏览以设置存储目录,如/data_kvm/store,最后单击“完成”按钮即可

未分类

创建存储池

(2)以同样的操作创建一个镜像存储池,命名为linux_iso,目录为/data_kvm/iso即可。在安装操作系统时,我们把镜像上传到服务器目录/data_kvm/iso

未分类

创建镜像存储池

(3)创建存储卷,单击刚创建好的“linux”,单击对话框右下角的“新建卷”按钮建立一个存储卷,并设置最大容量与分配容量

未分类

创建存储卷

(4)单击“完成”按钮后,回到虚拟系统管理器。右击“localhost(QEMU)”,然后选择“新建”选项,在弹出的对话框中将虚拟机名称命令为“Centos-6.6”,然后单击“前进”按

未分类

新建虚拟机(1)

单击“浏览”按钮选择镜像文件,再选择操作系统类型及版本

未分类

新建虚拟机(2)

单击“前进”按钮,在对话框中适当分配内存和CPU资源,如1核CPU、512MB内存

未分类

新建虚拟机(3)

单击“前进”按钮,在对话框中勾选“立即分配整个磁盘”复选框,点选“管理的或者其他现有存储”单选按钮,单击“浏览”按钮选择文件,然后单击“前进”按钮

未分类

新建虚拟机(4)

在所示的对话框中勾选“在安装前自定义配置”复选框,单击“完成”按钮,弹出对话框

未分类

新建虚拟机(5)

未分类

新建虚拟机(6)

在“Overview”视图中,定位到“机器设置”,把机器设置-时钟偏移-改为“localtime”,单击“应用”按钮即可。定位到“Boot Options”,勾选“主机引导时启动虚拟机”复选框,这样在物理宿主机启动后,这个VM也会启动,最后单击“应用”按钮。如果要远程管理,需要在“显示VNC”中,将Keymap设置为“Copy Local Keymap”

未分类

新建虚拟机(7)

最后单击“Begin Installation”按钮即可,整个虚拟机配置过程完成。下面就是安装操作系统的工作,和平时安装Linux系统一样

未分类

CentOS安装界面

案例:使用KVM命令集管理虚拟机

一、案例分析

案例环境使用一台物理机器,一台服务器安装Centos 6.6的64位系统(即Stranded), Centos-6.6是在宿主机Stranded中安装的虚拟机

主机  操作系统    IP地址    主要软件
Stranded    Centos-6.6  x86_64  192.168.1.100   Xshell
Centos-6.6

Centos-6.6  x86_64  192.168.1.103   
Xmanager

二、案例实施

1、安装Linux虚拟机

安装过程同上一案例,使用Xshell远程控制Stranded主机

2、KVM基本功能管理

(1)查看命令帮助

[root@localhost ~]# virsh  -h
......      //省略输出内容

(2)查看KVM的配置文件存放目录(Centos-6.6.xml是虚拟机系统实例的配置文件)

[root@localhost ~]# ls  /etc/libvirt/qemu
autostart  Centos-6.6.xml  networks

(3)查看虚拟机状态

[root@localhost ~]# virsh  list  --all
 Id    名称                        状态
----------------------------------------------------
 2    Centos-6.6                  running

(4)虚拟机关机与开机

首先要确认acpid服务安装并运行

[root@localhost ~]# virsh  shutdown  Centos-6.6
[root@localhost ~]# virsh  start  Centos-6.6

(5)强制实例系统关闭电源

[root@localhost ~]# virsh  destroy  Centos-6.6

(6)通过配置文件启动虚拟机系统实例

[root@localhost ~]# virsh  create  /etc/libvirt/qemu/Centos-6.6.xml
[root@localhost ~]# virsh  list  --all
 Id    名称                        状态
----------------------------------------------------
 3    Centos-6.6                  running

(7)挂起虚拟机

[root@localhost ~]# virsh  suspend  Centos-6.6

查看虚拟机状态

[root@localhost ~]# virsh  list  --all
 Id    名称                        状态
----------------------------------------------------
 3    Centos-6.6                  暂停

(8)恢复虚拟机

[root@localhost ~]# virsh  resume  Centos-6.6

[root@localhost ~]# virsh  list  --all
 Id    名称                        状态
----------------------------------------------------
 3    Centos-6.6                  running

(9)配置虚拟机实例伴随宿主机自动启动

[root@localhost ~]# virsh  autostart  Centos-6.6

上述命令将创建/etc/libvirt/qemu/autostart/目录,目录内容为开机自动启动的系统

(10)导出虚拟机配置

[root@localhost ~]# virsh  dumpxml  Centos-6.6 >/etc/libvirt/qemu/Centos-02-6.6.xml

(11)虚拟机的删除与添加

删除虚拟机

[root@localhost ~]# virsh  shutdown Centos-6.6
[root@localhost ~]# virsh  undefine Centos-6.6

查看删除结果,Centos-6.6的配置文件被删除,但磁盘文件不会被删除

[root@localhost ~]# ls  /etc/libvirt/qemu
autostart  Centos-02-6.6.xml  networks

通过virsh list -all查看不到Centos-6.6的信息,说明此虚拟机被删除

[root@localhost ~]# virsh  list --all
 Id    名称                  状态
----------------------------------------------------

通过备份的配置文件重新定义虚拟机

[root@localhost ~]# cd  /etc/libvirt/qemu
[root@localhost qemu]# mv  Centos-02-6.6.xml  Centos-6.6.xml

重新定义虚拟机

[root@localhost qemu]# virsh  define Centos-6.6.xml

查看虚拟机信息

[root@localhost qemu]# virsh  list  --all
 Id    名称                        状态
----------------------------------------------------
 -    Centos-6.6                  关闭

(12)修改虚拟机配置信息(用来修改系统内存大小、磁盘文件等信息)

直接通过vim命令修改

[root@localhost ~]# vim  /etc/libvirt/qemu/Centos-6.6.xml

通过virsh命令修改

[root@localhost ~]# virsh  edit  Centos-6.6

3、KVM文件管理

通过文件管理可以直接查看、修改、复制虚拟机的内部文件。例如,当系统因为配置问题无法启动时,可以直接修改虚拟机的文件。虚拟机磁盘文件有raw与qcow2格式,KVM虚拟机默认使用raw格式,raw格式性能最好,速度最快,其缺点是不支持一些新的功能,如镜像、Zlib磁盘压缩、AES加密等,针对两种格式的文件有不同的工具可供选择。这里介绍本地YUM安装libguestfs-tools后产生的命令行工具(这个工具可以直接读取qcow2格式的磁盘文件,因此需要将raw格式的磁盘文件转换成qcow2的格式)

(1)转换raw格式磁盘至qcow2格式

查看当前磁盘格式

[root@localhost ~]# qemu-img  info  /data_kvm/store/linux_kvm.img 
image: /data_kvm/store/linux_kvm.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 10G

关闭虚拟机

[root@localhost ~]# virsh  shutdown  Centos-6.6

转换磁盘文件格式

[root@localhost ~]# qemu-img  convert  -f  raw  -O  qcow2  /data_kvm/store/linux_kvm.img  /data_kvm/store/linux_kvm.qcow2

(2)修改Centos-6.6的xml配置文件

[root@localhost ~]# virsh  edit  Centos-6.6
......            //省略部分内容
    <disk type='file' device='disk'>
      <driver name='qemu' type='qcow2' cache='none'/>
      <source file='/data_kvm/store/linux_kvm.qcow2'/>
      <target dev='vda' bus='virtio'/>
      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
    </disk>
......            //省略部分内容

(3)virt-cat命令,类似于cat命令

[root@localhost ~]# virt-cat  -a  /data_kvm/store/linux_kvm.qcow2  /etc/sysconfig/network
NETWORINT=yes
HOSTNAME=localhost.localdomain

(4)virt-edit命令,用于编辑文件,用法与vim基本一致

[root@localhost ~]# virt-edit  -a  /data_kvm/store/linux_kvm.qcow2  /etc/resolv.conf
nameserver  8.8.8.8

(5)virt-df命令用于查看虚拟机磁盘信息

[root@localhost  ~]# virt-df  -h  Centos-6.6
Filesystem                                    Size        Used      Available    Use%
Centos-6.6:/dev/sda1                          476M        32M        419M        7%
Centos-6.6:/dev/sdb1                          4.3G        4.3G          0          100%
Centos-6.6:/dev/VolGroup/lv_root              8.3G        620M        7.2G        8%

4、虚拟机克隆

(1)查看虚拟机状态

[root@localhost  ~]# virsh  list  --all
 Id    名称                        状态
----------------------------------------------------
 -    Centos-6.6                  关闭

(2)从Centos-6.6克隆Centos-02-6.6

[root@localhost  ~]# virt-clone  -o  Centos-6.6  -n  Centos-02-6.6  -f  /data_kvm/store/Centos-02-6.6.qcow2

(3)查看虚拟机状态

[root@localhost  ~]# virsh  list  --all
 Id    名称                      状态
----------------------------------------------------
 -    Centos-02-6.6              关闭
 -    Centos-6.6                关闭

(4)启动虚拟机

[root@localhost  ~]# virsh  start  Centos-02-6.6

5、虚拟机快照

KVM虚拟机要使用快照功能,磁盘格式必须是qcow2,之前已经将Centos-6.6的磁盘格式转换成了qcow2

下面介绍KVM虚拟机快照备份的过程

(1)对Centos-6.6创建快照

[root@localhost  ~]# virsh  snapshot-create  Centos-6.6
Domain snapshot 1440950172 created

(2)查看虚拟机快照版本信息

[root@localhost ~]# virsh  snapshot-current  Centos-6.6
<domainsnapshot>
  <name>1440950172</name>      //快照版本号
  <state>shutoff</state>
......            //省略部分内容

(3)查看快照信息

[root@localhost ~]# virsh  snapshot-list  Centos-6.6
 名称            Creation Time              状态
------------------------------------------------------------
 1440950172      2015-08-30 23:56:12  +0800  shutoff

(4)创建新快照

[root@localhost ~]# virsh  snapshot-create  Centos-6.6
Domain snapshot 1440950433 created

(5)查看快照信息

[root@localhost ~]# virsh  snapshot-list  Centos-6.6
 名称              Creation Time              状态
------------------------------------------------------------
 1440950172        2015-08-30 23:56:12  +0800  shutoff
 1440950433        2015-08-31 00:00:33  +0800  shutoff

(6)回复虚拟机状态至1440950172

[root@localhost ~]# virsh  snapshot-revert  Centos-6.6  1440950172

(7)查看虚拟机快照版本信息

[root@localhost ~]# virsh  snapshot-current  Centos-6.6
<domainsnapshot>
  <name>1440950172</name>              //快照版本号
  <state>shutoff</state>
......            //省略部分输出

(8)删除快照

[root@localhost ~]# virsh  snapshot-delete  Centos-6.6  1440950172
Domain snapshot 1440950172 deleted

如何实现MySQL的一次一密登录

背景介绍

在日常工作环境中, 开发或者测试人员经常需要连接测试库、线上库等查看表结构或数据来验证程序的功能. 实际上让 DBA 协助开发者查看信息会是特别繁琐且无趣的事情, 所以为了方便起见会将数据库的权限分发给开发或者测试人员. 不过长此以往下去有几件事情会让 DBA 深恶痛绝:

  • 账号会在开发者之间相互传递;
  • 为了方便开发者会以快捷命令的方式查看信息, 密码信息容易暴露;
  • 开发者忘记密码, DBA 可能需要重置以通知所有其他人员修改密码;

事实上, 上述几种情况是很难避免的, 只要有人工参与就会有这些潜在危险的隐患, 所以我们就需要提供一个相对方便记住的又能保证相对安全的方式供开发者使用. 下面则从不同层面简单的对这种方式进行描述.

管理主机

如果从系统层面来看, 我们建议最好把所有的开发者都集中到一台管理主机上登录, 只有开发者连接到这台机器上, 才能通过该机器连接测试库, 线上库等进行查询信息操作. 如下图所示:

     +------------+     ssh      +--------------+                +-----------+
     | developers |  ----------> | manager host |    --------->  | databases |
     +------------+              +--------------+                +-----------+

开发者通过 ssh 登录该主机, 这个步骤最好是以 key 的方式登录, 开发者的私钥最好设置密码; 在登录主机后, 开发者再连接后面的数据库, 不过这个步骤又回到了我们上述提到的三个问题, 只是发生的环境在我们可控的主机上, 而不是在开发者的层面.

另外如果可以的话, 建议在 manager host 主机中部署 google-authenticator-libpam, 让开发者一次一密以 keyboard interactive 的方式登录 manager host, 这样可以避免开发者私钥文件泄露引起的安全隐患(当然 pam 生成的安全字符串不能泄露)。

这种方式其实并没有本质上的改进, 只是将所有不稳定因素都限制到一台主机中, 在安全方面进步不少.

MySQL pam 插件

官方和 percona, mariadb 等分支版本都提供了 pam 或 auth_pam 插件, 我们可以基于此完成很多类似 ldap, 一次一密, 系统用户等方式登录 MySQL 数据库, 更多见 more.

这些插件确实为我们提供了很方便的方式来连接数据库, 但是它们都有一个共同的问题, 就是不支持远程连接. 如下图所示:

   +--------------+                +-----------+
   | manager host |    --------->  | databases |
   +--------------+                +-----------+

我们需要在数据库主机中开启 pam 插件以方便开发者登录数据库, 但是开发者并不能在 manager 主机中以 pam 的方式连接后面的数据库, 当然或许可以通过 ssh host -e “xxxxx” 的方式连接, 但是作为系统管理员或者 DBA 不大可能为所有数据库主机都开通相关的用户权限, 为每台数据库主机设置 pam 插件及建立相关用户也是特别繁琐的事情.

如果开发者访问的数据库很少, 可以考虑 pam 插件和 google authentication 相结合的方式供开发者访问. 这种方式同样解决不了上述提到的三个问题.

代理访问

我们也可以从中间件层面考虑这个问题, 简单描述则为中间件接收用户发送过来的用户名和密码进行校验, 如果通过则使用真实的数据库用户名和密码去和后端的数据库进行交互, 如下图所示:

             user/password                 mysql_user/pass
  +------+                     +-------+                     +--------------+
  | user |  ---------------->  | proxy | ------------------> | MySQL Server |
  +------+                     +-------+                     +--------------+

这里用户输入的用户名和密码最好是伪造的, password 应该具有既好记又比较安全的特点. 这里我们想到了 google authentication 的方式, 使用基于时间的 totp 方法动态生成用户输入的 password.

portproxy 则基于该方式实现开发者一次一密的访问数据库. 原理则比较简单, portproxy 劫持用户发送过来的用户名和密码信息, portproxy 默认以 user+totp 作为用户的默认密码, 如果校验成功, 则使用真实的用户密码构造 MySQL 的验证报文, 再发送到后端的 MySQL 数据库, 其流程大致如下:

             user/user+totp                   mysql_user/pass
  +------+                     +-----------+                     +--------------+
  | user |  ---------------->  | portproxy | ------------------> | MySQL Server |
  +------+                     +-----------+                     +--------------+

这种方式可以很容易的解决开发者遗忘密码的问题, 只要记住用户名及能够获取对应的 totp 6位数字即可连接数据库; 如果再加上管理机, 就能限制所有开发者在一台机器上操作, 也能比较方便的杜绝开发者互相传递数据库密码; 另外也可以在用户输入密码前封装一层, 只允许 tty 方式接收用户输入的密码, 这样就可以避免开发者以快捷方式连接数据库; 当然如果开发者足够厉害也是可以绕过我们的限制, 这种情况下也能解决上述的1, 3 两个问题.

portproxy 如何实现一次一密验证

事实上, portproxy 是解析了 mysql connection 的验证协议才实现了劫持的目的, 正常情况下, mysql 的连接建立过程如下:

未分类

client 和 server 三次握手完成后, server 开始给 client 发送初始的报文, 其中就包含了协议版本, MySQL Server 版本, 用户名, 连接 id 以及随机且固定长度的初始验证数据;

client 接收到 server 的初始报文后, 解析出验证的协议版本(MySQL 老的加密协议或者新的协议), 以及20位初始的随机验证数据. 通过 20位的数据和用户输入的密码经过下面的算法校验用户是否有效:

SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) )

portproxy 就是通过三次握手后接收 server 发送的初始报文解析出我们需要的用户名和20位随机数据, 再将用户名+totp 作为默认的密码校验开发者是否输入正确的password(user+totp), 如果正确则重新使用真实的用户名和密码以及20位随机数据构造新的验证数据报文发送给 server, 通过后则连接建立完成, 开发者就可以正常访问数据库. 更多通信协议见 client-server-protocol.

以 arster 用户名为例进行以下操作:

# sys-google-totp -secret "OLENMTM3BTB36EUY"
otp message:
202340 (22 second(s) remaining)

# mysql -h 10.0.21.5 -P 33306 -u arster -p
......
......

Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.

mysql userread@[10.0.21.5:33306 (none)] > 
mysql userread@[10.0.21.5:33306 (none)] > quit

这里输入的密码就应该是 arster202340, totp 默认 30 秒变更一次, 开发者需要保证有足够的时间输入密码, 如果时间不够则重新执行 sys-google-totp 命令获取新的 6 位数字. 校验成功后, portproxy 则使用真实的 userread 用户重新构造数据报文并发送给后面的数据库.

总结

实际工作中, DBA 或系统管理员最烦的可能就是开发者忘记密码, 如果 DBA 也没有记录用户密码, 就只有重置这种方式, 最后再通知所有开发者进行修改. portproxy 的方式能够解决开发者忘记密码的问题, 稍加设置或封装就可以解决另外两个问题. 当然如果一些公司的devops做的足够好的话就可以不用考虑这些, 只需要保证开发者不乱传账号信息或者账号信息不被盗取就可以避免我们上述讨论的三个问题.

MySQL并发控制原理

并发即指在同一时刻,多个操作并行执行。MySQL对并发的处理主要应用了两种机制——是“锁”和“多版本控制”。

锁分为读锁和写锁两种,也称作共享锁和排他锁。

因为多个读操作同时进行是不会破坏数据的,所以读锁是共享的,多个读操作可以同时进行,互不干扰。

为了防止多个写操作共同执行破坏数据,写锁是排他的,一个写锁会阻塞其它的写锁和读锁,进而保证同一资源在任何时刻只有一个写操作在执行,并防止其它用户读取正在写入的该资源。

在锁粒度方面,MySQL包括表锁和行锁两种类型。锁的粒度越小,越有利于对数据库操作的并发执行。但是管理锁消耗的资源也会更多。如果系统花费大量的时间来管理锁,而不是存储数据,那么系统的性能也会受到影响。

表锁会锁定整张表,它是开销最小的策略。诸如ALTER TABLE之类的语句会使用表锁。

行锁最大程度的支持并发操作,同时也带来了最大的开销。InnoDB实现了行锁。

在MySql中并不只是用锁来维护并发控制。

事务的隔离级别

事务的概念在此不多介绍。我觉得,也可以将事务看成是并发中的一部分——事务包含了一组操作,事务和事务之间可以并行执行。事务和事务之间的并发也和普通的并发操作一样会共享相同的资源,这样并发执行的事务之间就会相互影响。根据事务之间影响程度的不同,提出了事务的隔离级别这个概念,分别是READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE。

READ UNCOMMITTED就是一个事务对共享数据的修改马上就能够被另一个事务感知到,其实也就是没有对修改操作做任何特殊处理。

SERIALIZABLE是通过加锁的方式强制事务串行执行,这样可以避免幻读。但这种方式会带来大量锁争用问题。

READ COMMITTED和REPEATABLE READ是基于MVCC的方式实现的。

MVCC多版本并发控制

MySql对于事务之间并发控制的实现并不是简单的使用行级锁。MySql在读操作时并不加锁,只有在写操作时才会对修改的资源加锁。

MVCC保存了数据资源在不同时间点上的多个快照。根据事务开始的时间不同,每个事务看到的数据快照版本是不一样的。

InnoDB中的MVCC实现:存储引擎全局维护了一个系统版本号,每开启一个新的事务,这个系统版本号就会递增。事务开始时刻的系统版本号,会作为这个事务本身的版本号。在每行记录中,存储引擎又在每行的后面保存两个隐藏的列,分别保存这一行的开始版本号和过期版本号。在REPEATABLE READ隔离级别下,MVCC的具体操作如下:

  • INSERT
    存储引擎为新插入的每一行保存当前的系统版本号作为这一行的开始版本号。

  • UPDATE
    存储引擎会新插入一行记录,当前的系统版本号就是新记录行的开始版本号。同时会将原来行的过期版本号设为当前的系统版本号。

  • DELETE
    存储引擎将删除的记录行的过期版本号设置为当前的系统版本号。

  • SELECT
    当读取记录时,存储引擎会选取满足下面两个条件的行作为读取结果。

    • 读取记录行的开始版本号必须早于当前事务的版本号。也就是说,在当前事务开始之前,这条记录已经存在。在事务开始之后才插入的行,事务不会看到。

    • 读取记录行的过期版本号必须晚于当前事务的版本号。也就是说,当前事务开始的时候,这条记录还没有过期。在事务开始之前就已经过期的数据行,该事务也不会看到。

通过上面的描述,可以看到在存储引擎中,同一时刻存储了一个数据行的多个版本。每个事务会根据自己的版本号和每个数据行的开始及过期版本号选择读取合适的数据行。

MVCC只在READ COMMITTED和REPEATABLE READ这两个级别下工作。

使用MySQL binlog提取sql用法

本文只是简单的介绍mysql binlog基本用法,并不涉及到binlog的原理、格式等知识,如果需要了解这些高级的知识,请参见官方文档。

本文重点介绍–start-position和–stop-position参数的使用
–start-position的语法是

--start-position=N 

含义是从相对与二进制日志的第N偏移的事件开始读。 同理,–stop-position=N的介绍和–start-position类似。在默认的情况下, log-bin是关闭的,如下:

mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | OFF   |
+---------------+-------+
1 row in set (0.00 sec)

我们可以通过修改my.ini配置文件,在[mysqld] 下面添加 log-bin=日志名:

[mysqld]

# The TCP/IP Port the MySQL Server will listen on
port=3306

log-bin=mysql-bin

修改完成之后,我们需要重启mysql服务,然后再看下是否启动了binlog

mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | ON    |
+---------------+-------+
1 row in set (1.01 sec)

已经开启了binlog。然后我们创建一个数据库binlog

mysql> create database binlog;
Query OK, 1 row affected (0.00 sec)

mysql> use binlog;
Database changed

然后在binlog数据库下面创建表test,并依次进行如下操作。

mysql> create table test(
       id int auto_increment not null primary key, 
       val int,
       data varchar(20)
);
Query OK, 0 rows affected (0.01 sec)

mysql> insert into test(val, data) values (10, 'wu');
Query OK, 1 row affected (0.02 sec)

mysql> insert into test(val, data) values (20, 'yang');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test(val, data) values (20, 'ping');
Query OK, 1 row affected (0.01 sec)

mysql> flush logs;
Query OK, 0 rows affected (0.04 sec)

mysql> insert into test(val, data) values (40, 'hao');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test(val, data) values (50, 'iteblog');
Query OK, 1 row affected (0.01 sec)

mysql> delete from test where id between 4 and 5;
Query OK, 2 rows affected (0.01 sec)

mysql> insert into test(val, data) values (60, 'iteblog1');
Query OK, 1 row affected (0.02 sec)

mysql> flush logs;
Query OK, 0 rows affected (0.05 sec)

mysql> insert into test(val, data) values (70, 'ping123');
Query OK, 1 row affected (0.01 sec)

mysql> insert into test(val, data) values (80, 'ping163');
Query OK, 1 row affected (0.01 sec)

mysql> drop table test;
Query OK, 0 rows affected (0.01 sec)

mysql> drop database binlog;
Query OK, 0 rows affected (0.00 sec)

经过上述的操作,将会在本地数据库数据存放目录下面生成以下四个文件:

mysql-bin.000001
mysql-bin.000002
mysql-bin.000003
mysql-bin.index

*.index是索引文件,其他三个是binlog文件,我们可以用mysqlbinlog 工具来恢复数据。为了下面讲解的方便,我们先将binlog文件解析成txt文件,如下:

mysqlbinlog  datamysql-bin.000001 > E:/1.txt

mysqlbinlog  datamysql-bin.000002 > E:/2.txt

mysqlbinlog  datamysql-bin.000003 > E:/3.txt

通过这三个命令,可以在E盘下生成3个文件,里面分别记录了日志文件的内容,也就是用户操作的步骤。

下面开始恢复binlog日志到Mysql数据库,因为我们需要重做第一个日志文件的所有操作,所以这里只需要将第一个日志文件全恢复就行了。

mysqlbinlog  datamysql-bin.000001 | mysql -uroot -p123456

在第二个binlog里面我们进行了delete操作,我们并不想将delete的操作恢复到数据库,这样我们可以通过读取2.txt文件:

................................

/*!*/;
# at 653
#140902 16:07:43 server id 1  end_log_pos 759   Query   thread_id=1 exec_time=0 error_code=0
SET TIMESTAMP=1409645263/*!*/;
delete from test where id between 4 and 5
/*!*/;
# at 759
#140902 16:07:43 server id 1  end_log_pos 786   Xid = 175
COMMIT/*!*/;
................................

  
在这个文件中,我们可以看到DELETE的操作的起始位置是653,终止位置是759.那么我们只要重做第二个日志文件的开头到653的操作,然后再从759到末尾的操作,我们就可以把数据给恢复回来,而不会DELETE数据。所以执行两个命令

mysqlbinlog  datamysql-bin.000002 --stop-pos=653 | mysql -uroot -p123456

mysqlbinlog  datamysql-bin.000002 --start-pos=759 | mysql -uroot -p123456

mysqlbinlog  datamysql-bin.000003 --stop-pos=587 | mysql -uroot -p123456

好了,到这里,所有的数据全部恢复了,我们可以用下面语句查看到:

mysql> select * from test
+----+------+----------+
| id | val  | data     |
+----+------+----------+
|  1 |   10 | wu       |
|  2 |   20 | yang     |
|  3 |   20 | ping     |
|  4 |   40 | hao      |
|  5 |   50 | iteblog  |
|  6 |   60 | iteblog1 |
|  7 |   70 | ping123  |
|  8 |   80 | ping163  |
+----+------+----------+
8 rows in set (0.00 sec)

MySQL线程处于Waiting for table flush状态的分析

最近遇到一个案例,很多查询被阻塞没有返回结果,使用show processlist查看,发现不少MySQL线程处于Waiting for table flush状态,查询语句一直被阻塞,只能通过Kill进程来解决。那么我们先来看看Waiting for table flush的官方解释:

https://dev.mysql.com/doc/refman/5.6/en/general-thread-states.html

Waiting for table flush

The thread is executing FLUSH TABLES and is waiting for all threads to close their tables, or the thread got a notification that the underlying structure for a table has changed and it needs to reopen the table to get the new structure. However, to reopen the table, it must wait until all other threads have closed the table in question.
This notification takes place if another thread has used FLUSH TABLES or one of the following statements on the table in question: FLUSH TABLES tbl_name, ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE, or OPTIMIZE TABLE.

那么我们接下来模拟一下线程处于Waiting for table flush状态的情况,如所示:

在第一个会话连接(connection id=13)中,我们使用lock table 锁定表test。

mysql> use MyDB;
Database changed
mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|              13 |
+-----------------+
1 row in set (0.00 sec)

mysql> lock table test read;
Query OK, 0 rows affected (0.00 sec)

mysql> 

在第二个会话连接(connection id=17)中,我们执行flush table 或 flush table test 皆可。此时你会发现flush table处于阻塞状态。

mysql> use MyDB;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|              17 |
+-----------------+
1 row in set (0.00 sec)

mysql> flush table test;

未分类

在第三个会话/连接中,当你切换到MyDB时,就会提示“You can turn off this feature to get a quicker startup with -A” ,此时处于阻塞状态。此时你退出会话,使用参数-A登录数据库后,你如果查询test表,就会处于阻塞状态(当然查询其它表不会被阻塞)。如下所示:

mysql> use MyDB;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A


mysql> use MyDB;
Database changed
mysql> select * from test;

clip_image002

未分类

在第四个会话/连接,我们用show processlist查看到当前数据库所有连接线程状态,你会看到17、18都处于Waiting for table flush的状态。如下截图所示:

mysql> show processlist;
+----+------+-----------+------+---------+------+-------------------------+--------------------+
| Id | User | Host      | db   | Command | Time | State                   | Info               |
+----+------+-----------+------+---------+------+-------------------------+--------------------+
| 13 | root | localhost | MyDB | Sleep   |   90 |                         | NULL               |
| 14 | root | localhost | NULL | Query   |    0 | init                    | show processlist   |
| 17 | root | localhost | MyDB | Query   |   52 | Waiting for table flush | flush table test   |
| 18 | root | localhost | MyDB | Query   |    9 | Waiting for table flush | select * from test |
+----+------+-----------+------+---------+------+-------------------------+--------------------+
4 rows in set (0.00 sec)

mysql> 

未分类

mysql> show processlist;
+----+------+-----------+------+---------+------+-------------------------+--------------------+
| Id | User | Host      | db   | Command | Time | State                   | Info               |
+----+------+-----------+------+---------+------+-------------------------+--------------------+
| 13 | root | localhost | MyDB | Sleep   |   90 |                         | NULL               |
| 14 | root | localhost | NULL | Query   |    0 | init                    | show processlist   |
| 17 | root | localhost | MyDB | Query   |   52 | Waiting for table flush | flush table test   |
| 18 | root | localhost | MyDB | Query   |    9 | Waiting for table flush | select * from test |
+----+------+-----------+------+---------+------+-------------------------+--------------------+
4 rows in set (0.00 sec)

mysql> 
mysql> 
mysql> 
mysql> 
mysql> show open tables where in_use >=1;
+----------+-------+--------+-------------+
| Database | Table | In_use | Name_locked |
+----------+-------+--------+-------------+
| MyDB     | test  |      1 |           0 |
+----------+-------+--------+-------------+
1 row in set (0.00 sec)

mysql> kill 17;
Query OK, 0 rows affected (0.00 sec)

mysql> show processlist;
+----+------+-----------+------+---------+------+-------------------------+--------------------+
| Id | User | Host      | db   | Command | Time | State                   | Info               |
+----+------+-----------+------+---------+------+-------------------------+--------------------+
| 13 | root | localhost | MyDB | Sleep   |  442 |                         | NULL               |
| 14 | root | localhost | NULL | Query   |    0 | init                    | show processlist   |
| 18 | root | localhost | MyDB | Query   |  361 | Waiting for table flush | select * from test |
+----+------+-----------+------+---------+------+-------------------------+--------------------+
3 rows in set (0.00 sec)

mysql> kill 13;
Query OK, 0 rows affected (0.00 sec)

mysql> show processlist;
+----+------+-----------+------+---------+------+-------+------------------+
| Id | User | Host      | db   | Command | Time | State | Info             |
+----+------+-----------+------+---------+------+-------+------------------+
| 14 | root | localhost | NULL | Query   |    0 | init  | show processlist |
| 18 | root | localhost | MyDB | Sleep   |  427 |       | NULL             |
+----+------+-----------+------+---------+------+-------+------------------+
2 rows in set (0.00 sec)

mysql> 

未分类

注意:我们需要Kill线程13, Kill掉线程17是解决不了问题的。

生产环境中,很多时候可能不是lock table read引起的阻塞,而是由于慢查询,导致flush table一直无法关闭该表而一直处于等待状态,例如下面测试案例中,我使用同一张大表做笛卡尔积模拟一个慢查询,其它操作相同,如下所示,你会看到同样产生了Waiting for table flush

mysql> SELECT T.* FROM TEST1 T, TEST1 L;

未分类

另外,网上有个案例,mysqldump备份时,如果没有使用参数—single-transaction 或由于同时使用了flush-logs与—single-transaction两个参数也可能引起这样的等待场景,这个两个参数放在一起,会在开始dump数据之前先执行一个FLUSH TABLES操作。

解决方案:

出现Waiting for table flush时,我们一般需要找到那些表被lock住或那些慢查询导致flush table一直在等待而无法关闭该表。然后Kill掉对应的线程即可,但是如何精准定位是一个挑战,尤其是生产环境,你使用show processlist会看到大量的线程。让你眼花缭乱的,怎么一下子定位问题呢?

对于慢查询引起的其它线程处于Waiting for table flush状态的情形:

可以查看show processlist中Time值很大的线程。然后甄别确认后Kill掉,如上截图所示,会话连接14就是引起阻塞的源头SQL。有种规律就是这个线程的Time列值必定比被阻塞的线程要高。这个就能过滤很多记录。

对于lock table read引起的其它线程处于Waiting for table flush状态的情形:

对于实验中使用lock table read这种情况,这种会话可能处于Sleep状态,而且它也不会出现在show engine innodb status G命令的输出信息中。 即使show open tables where in_use >=1;能找到是那张表被lock住了,但是无法定位到具体的线程(连接),其实这个是一个头痛的问题。但是inntop这款利器就可以定位到,如下所示,线程17锁住了表test,在innotop里面就能定位到是线程17。所谓工欲善其事必先利其器!

未分类

未分类

另外,在官方文档中ALTER TABLE, RENAME TABLE, REPAIR TABLE, ANALYZE TABLE, or OPTIMIZE TABLE都能引起这类等待,下面也做了一些简单测试,如下所示:

Waiting for table flush的另外一个场景

会话连接(connection id=18)执行下面SQL语句,模拟一个慢查询SQL

mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|              18 |
+-----------------+
1 row in set (0.00 sec)

mysql> select name, sleep(64) from test;

会话连接(connection id=6)执行下面SQL语句,分析表test

mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|               6 |
+-----------------+
1 row in set (0.00 sec)
mysql> analyze table test;
+-----------+---------+----------+----------+
| Table     | Op      | Msg_type | Msg_text |
+-----------+---------+----------+----------+
| MyDB.test | analyze | status   | OK       |
+-----------+---------+----------+----------+
1 row in set (0.04 sec)

mysql> 

会话连接(connection id=8)执行下面SQL语句

mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|               8 |
+-----------------+
1 row in set (0.00 sec)

mysql> select * from test;

查看线程的状态,你会发现被阻塞的会话处于 Waiting for table flush状态。 因为当对表做了ANALYZE TABLE后,后台针对该表的查询需要等待,因为MySQL已经检测到该表内部变化,需要使用FLUSH TABLE关闭然后重新打开该表,所以当你查询该表时,就会处于 Waiting for table flush

mysql> show processlist;
+----+------+-----------+------+---------+------+-------------------------+----------------------------------+
| Id | User | Host      | db   | Command | Time | State                   | Info                             |
+----+------+-----------+------+---------+------+-------------------------+----------------------------------+
|  6 | root | localhost | MyDB | Sleep   |   22 |                         | NULL                             |
|  8 | root | localhost | MyDB | Query   |   14 | Waiting for table flush | select * from test               |
| 15 | root | localhost | NULL | Sleep   |    3 |                         | NULL                             |
| 16 | root | localhost | NULL | Query   |    0 | init                    | show processlist                 |
| 18 | root | localhost | MyDB | Query   |   46 | User sleep              | select name, sleep(64) from test |
+----+------+-----------+------+---------+------+-------------------------+----------------------------------+
5 rows in set (0.00 sec)

mysql> 

未分类

Waiting for table metadata lock

会话连接(connection id=17)执行下面SQL语句,模拟一个慢查询SQL

mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|              17 |
+-----------------+
1 row in set (0.00 sec)

mysql> select name, sleep(100) from test;

会话连接(connection id=6)执行下面SQL语句, 修改表结构操作

mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|               6 |
+-----------------+
1 row in set (0.00 sec)

mysql> alter table test add tname varchar(10); // rename table test to kkk 同样会引起Waiting for table metadata lock

会话连接(connection id=8)执行下面SQL语句,查询表test

mysql> select connection_id();
+-----------------+
| connection_id() |
+-----------------+
|               8 |
+-----------------+
1 row in set (0.00 sec)

mysql> select * from test;

查看线程的状态,你会发现被阻塞的会话处于 Waiting for table metadata lock状态。

mysql> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+----------------------------------------+
| Id | User | Host      | db   | Command | Time | State                           | Info                                   |
+----+------+-----------+------+---------+------+---------------------------------+----------------------------------------+
|  6 | root | localhost | MyDB | Query   |   19 | Waiting for table metadata lock | alter table test add tname varchar(10) |
|  8 | root | localhost | MyDB | Query   |    6 | Waiting for table metadata lock | select * from test                     |
| 15 | root | localhost | NULL | Sleep   |    8 |                                 | NULL                                   |
| 16 | root | localhost | NULL | Query   |    0 | init                            | show processlist                       |
| 17 | root | localhost | MyDB | Query   |   55 | User sleep                      | select name, sleep(100) from test      |
+----+------+-----------+------+---------+------+---------------------------------+----------------------------------------+
5 rows in set (0.00 sec)

mysql> 

未分类

Mongodb主从同步及问题处理

第一步:我们把mongodb部署多服务器上10.12.0.3和10.14.0.1。

第二步:启动10.12.0.3上的mongodb,把该数据库指定为主数据库

先启动主:

mongod --port 25019 --fork --logpath /var/log/mongo/mongdb1.log --dbpath /data/db/  --master

再启动从:

mongod --slave --source 10.12.0.3:25019 --dbpath /opt/product/mongodb/data

未分类

出现了syncing from host:10.12.0.3:25019说明已经从主数据库复制完成了。

常见问题处理:

问题一:从服务器执行同步,报错:errmsg: “not authorized on admin to execute command”

未分类

原因: 主启动携带了–auth .mongo默认是不鉴权。去掉后问题解决。

问题二:[replslave] –source 192.168.1.32:25019 != 19.168.1.30:25019 from local.sources collection

未分类

原因:在一开始的时候我们已经为slave指定了master的host和port,这个会插入到local.sources 这个集合的。所以,把master端口改成10000就可以了。