利用saltstack的event实现自己的功能

saltstack的master上minion连接较多,下面这个程序可以分析哪些minion任务执行成功,哪些执行失败以及哪些没有返回。

脚本说明:

一、最先打印出本次任务的job id、command name以及其它相关信息,然后是本次任务的执行流程和结果,这和我们单独执行这个命令是一致的。最后程序会打印出所有未成功的任务和未返回的任务,并且重新执行一遍。 这里要说明的是,因为没有查看对应的情景,对于失败任务的排判断做的不好,另外minion未连接我也归为任务未返回,并且会再执行一遍,实际上如果是minion未连接,则不应该执行。

二、 程序我们先派生子进程去执行salt命令,再salt命令执行完毕后,我们的程序会对其中失败的和未返回的minion任务二次执行

三、编写脚本

import salt.utils.event
import re
import signal, time
import sys
import os
def single_handler(target):
    os.execl('/usr/bin/salt', 'salt', target, 'state.sls', 'os')

def handler(num1, num2):
    #signal.signal(signal.SIGCLD,signal.SIG_IGN)
    print 'We are in signal handler'
    print 'Job Not Ret: '+str(record[jid])
    print ' Job Failed: '+str(failedrecord[jid])
    print 'all done...'
    for item in failedrecord[jid]:
        #print item
        try:
           pid  = os.fork()
           if pid == 0:
              single_handler(item)
        except OSError:
           print 'we exec. '+ item +' error!'
    for item in record[jid]:
        #print item
        try:
           print 'fork ok ' + item
           pid = os.fork()
           if pid == 0 :
              single_handler(item)
        except OSError:
           print 'we exec. '+item + ' error!' 
    sys.stdout.flush()
    os._exit(0)



fd = open('/tmp/record', 'w+')
#sys.stdout = fd
#sys.stderr = fd

signal.signal(signal.SIGCLD, handler)

#fd = open('/var/log/record', 'w+')
os.dup2(fd.fileno(), sys.stdout.fileno())
os.dup2(fd.fileno(), sys.stderr.fileno())

#sys.stdout = fd
#sys.stderr = fd


try:
   pid = os.fork()
   if pid == 0:
      time.sleep(2)
      try:
         os.execl('/usr/bin/salt', 'salt', '*', 'state.sls', 'os')
      except OSError:
         print 'exec error!'
         os._exit(1)
except OSError:
   print 'first fork error!'
   os._exit(1)
event = salt.utils.event.MasterEvent('/var/run/salt/master')
flag=False
reg=re.compile('salt/job/([0-9]+)/new')
reg1=reg
#a process to exec. command, but will sleep some time
#another process listen the event
#if we use this method, we can filter the event through func. name
record={}
failedrecord={}
jid = 0


#try:
for eachevent in event.iter_events(tag='salt/job',full=True):
    eachevent=dict(eachevent)
    result = reg.findall(eachevent['tag'])
    if not flag and result:
       flag = True
       jid = result[0]
       print "   job_id: " + jid
       print "  Command: " + dict(eachevent['data'])['fun'] + ' ' + str(dict(eachevent['data'])['arg'])
       print "    RunAs: " + dict(eachevent['data'])['user'] 
       print "exec_time: " + dict(eachevent['data'])['_stamp'] 
       print "host_list: " + str(dict(eachevent['data'])['minions'])
       sys.stdout.flush()
       record[jid]=eachevent['data']['minions']
       failedrecord[jid]=[]
       reg1 = re.compile('salt/job/'+jid+'/ret/([0-9.]+)')
    else:
       result = reg1.findall(eachevent['tag'])
       if result:
          record[jid].remove(result[0])
          if not dict(eachevent['data'])['success']:
             failedrecord[jid].append(result[0])
#except:
#   print 'we in except'
"""
   print 'Job Not Ret: '+str(record[jid])
   print ' Job Failed: '+str(failedrecord[jid])
   for item in failedrecord[jid]:
       os.system('salt '+ str(item) + ' state.sls os')
   for item in record[jid]:
       os.system('salt '+ str(item) + ' state.sls os')
   os._exit(0)
"""

执行结果:

   job_id: 20151208025319005896
  Command: state.sls ['os']
    RunAs: root
exec_time: 2015-12-08T02:53:19.006284
host_list: ['172.18.1.212', '172.18.1.214', '172.18.1.213', '172.18.1.211']
172.18.1.213:
----------
          ID: configfilecopy
    Function: file.managed
        Name: /root/node3
      Result: True
     Comment: File /root/node3 is in the correct state
     Started: 02:53:19.314015
    Duration: 13.033 ms
     Changes:   
----------
          ID: commonfile
    Function: file.managed
        Name: /root/commonfile
      Result: True
     Comment: File /root/commonfile is in the correct state
     Started: 02:53:19.327173
    Duration: 1.993 ms
     Changes:   

Summary
------------
Succeeded: 2
Failed:    0
------------
Total states run:     2
172.18.1.212:
----------
          ID: configfilecopy
    Function: file.managed
        Name: /root/node2
      Result: True
     Comment: File /root/node2 is in the correct state
     Started: 02:53:19.337325
    Duration: 8.327 ms
     Changes:   
----------
          ID: commonfile
    Function: file.managed
        Name: /root/commonfile
      Result: True
     Comment: File /root/commonfile is in the correct state
     Started: 02:53:19.345787
    Duration: 1.996 ms
     Changes:   

Summary
------------
Succeeded: 2
Failed:    0
------------
Total states run:     2
172.18.1.211:
----------
          ID: configfilecopy
    Function: file.managed
        Name: /root/node1
      Result: True
     Comment: File /root/node1 is in the correct state
     Started: 02:53:19.345017
    Duration: 12.741 ms
     Changes:   
----------
          ID: commonfile
    Function: file.managed
        Name: /root/commonfile
      Result: True
     Comment: File /root/commonfile is in the correct state
     Started: 02:53:19.357873
    Duration: 1.948 ms
     Changes:   

Summary
------------
Succeeded: 2
Failed:    0
------------
Total states run:     2
172.18.1.214:
    Minion did not return. [Not connected]
We are in signal handler
Job Not Ret: ['172.18.1.214']
 Job Failed: []
all done...
fork ok 172.18.1.214
172.18.1.214:
    Minion did not return. [Not connected]

saltstack之salt event事件用法

event是一个本地的ZeroMQ PUB Interface,event是一个开放的系统,用于发送信息通知salt或其他的操作系统。每个event都有一个标签。事件标签允许快速制定过滤事件。除了标签之外,每个事件都有一个数据结构。这个数据结构是一个dict类型,其中包含关于事件的信息。

作用:用于监控salt-master执行结果。

一、监听salt event事件脚本

1.环境准备

节点 IP

  • salt-master 192.168.56.41
  • salt-minion 192.168.56.42

2.配置好salt-key连接,再在salt-master节点上面开两个窗口测试。

#salt-master窗口01

[root@salt ~]# salt '*' test.ping
salt-minion:
    True
[root@salt ~]# salt '*' test.ping
salt-minion:
    True

#salt-master窗口02

#编写脚本
[root@salt-minion ~]# cat salt_monitor_event.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import salt.utils.event
event = salt.utils.event.MasterEvent('/var/run/salt/master')
for eachevent in event.iter_events(full=True):
    print eachevent
    print "---------"

#授权
[root@salt-minion ~]# chmod +x salt_monitor_event.py

#执行结果
[root@salt ~]# python salt_monitor_event.py
{u'tag': '20180624070339744384', u'data': {u'_stamp': u'2018-06-23T23:03:39.745401', u'minions': [u'salt-minion']}}
---------
{u'tag': 'salt/job/20180624070339744384/new', u'data': {u'tgt_type': u'glob', u'jid': u'20180624070339744384', u'tgt': u'*', u'missing': [], u'_stamp': u'2018-06-23T23:03:39.745714', u'user': u'root', u'arg': [], u'fun': u'test.ping', u'minions': [u'salt-minion']}}
---------
{u'tag': 'salt/job/20180624070339744384/ret/salt-minion', u'data': {u'fun_args': [], u'jid': u'20180624070339744384', u'return': True, u'retcode': 0, u'success': True, u'cmd': u'_return', u'_stamp': u'2018-06-23T23:03:39.783037', u'fun': u'test.ping', u'id': u'salt-minion'}}
---------
{u'tag': '20180624070341195901', u'data': {u'_stamp': u'2018-06-23T23:03:41.196632', u'minions': [u'salt-minion']}}
---------
{u'tag': 'salt/job/20180624070341195901/new', u'data': {u'tgt_type': u'glob', u'jid': u'20180624070341195901', u'tgt': u'*', u'missing': [], u'_stamp': u'2018-06-23T23:03:41.196926', u'user': u'root', u'arg': [], u'fun': u'test.ping', u'minions': [u'salt-minion']}}
---------
{u'tag': 'salt/job/20180624070341195901/ret/salt-minion', u'data': {u'fun_args': [], u'jid': u'20180624070341195901', u'return': True, u'retcode': 0, u'success': True, u'cmd': u'_return', u'_stamp': u'2018-06-23T23:03:41.234596', u'fun': u'test.ping', u'id': u'salt-minion'}}
---------
{u'tag': '20180624070347154023', u'data': {u'_stamp': u'2018-06-23T23:03:47.154591', u'minions': [u'salt-minion']}}
---------
{u'tag': 'salt/job/20180624070347154023/new', u'data': {u'tgt_type': u'glob', u'jid': u'20180624070347154023', u'tgt': u'*', u'missing': [], u'_stamp': u'2018-06-23T23:03:47.154990', u'user': u'root', u'arg': [], u'fun': u'test.ping', u'minions': [u'salt-minion']}}
---------
{u'tag': 'salt/job/20180624070347154023/ret/salt-minion', u'data': {u'fun_args': [], u'jid': u'20180624070347154023', u'return': True, u'retcode': 0, u'success': True, u'cmd': u'_return', u'_stamp': u'2018-06-23T23:03:47.191617', u'fun': u'test.ping', u'id': u'salt-minion'}} 

二、saltStack的event接口通过mysql数据库接收SaltStack批量管理日志

作用:在master上直接将返回结果写入mysql

1.编写自定义return脚本

vim salt_event_to_mysql.py


#!/bin/env python
#coding=utf8
# Import python libs
import json
# Import salt modules
import salt.config
import salt.utils.event
# Import third part libs
import MySQLdb
__opts__ = salt.config.client_config('/etc/salt/master')
#create MySQL connect
#conn = MySQLdb.connect(host=__opts__['mysql.host'],user=__opts__['mysql.user'],passwd=__opts__['mysql.pass'],db=__opts__['mysql.db'],port=__opts__['mysql.port'])


conn = MySQLdb.connect(host='192.168.3.87',user='salt',passwd='salt',db='salt',port=3306)
cursor = conn.cursor()
# Listen Salt Master Event System
event = salt.utils.event.MasterEvent(__opts__['sock_dir'])
for eachevent in event.iter_events(full=True):
    ret = eachevent['data']
    if "salt/job/" in eachevent['tag']:
        #Return Event
        if ret.has_key('id') and ret.has_key('return'):
            #Ignore saltutil.find_job event
            if ret['fun'] == "saltutil.find_job":
                continue
            sql = '''INSERT INTO `salt_returns`
                (`fun`,`jid`,`return`,`id`,`success`,`full_ret` )
                VALUES (%s,%s,%s,%s,%s,%s)'''
            cursor.execute(sql,(ret['fun'],ret['jid'],
                                json.dumps(ret['return']),ret['id'],
                                ret['success'],json.dumps(ret)))
            cursor.execute("COMMIT")
    # Other Event
    else:
        pass

保存退出

注意:

MySQLdb.connect(host=__opts__['mysql.host'],user=__opts__['mysql.user'],passwd=__opts__['mysql.pass'],db=__opts__['mysql.db'],port=__opts__['mysql.port'])

要换成自己的实际数据库地址、数据库用户、密码,如:

conn = MySQLdb.connect(host='192.168.3.87',user='salt',passwd='salt',db='salt',port=3306)

2.修改master的配置文件

vim /etc/salt/master


mysql.host: '192.168.3.87' # mysql服务器的IP地址
mysql.user: 'salt' # mysql数据库的用户名,需要跟后面授权的用户名一致
mysql.pass: 'salt' # mysql数据库的密码,需要跟后面授权的密码一致
mysql.db: 'salt' # mysql数据库的名称
mysql.port: 3306 # 使用端口为3306


mysql.host: '192.168.3.87' # mysql服务器的IP地址
mysql.user: 'salt' # mysql数据库的用户名,需要跟后面授权的用户名一致
mysql.pass: 'salt' # mysql数据库的密码,需要跟后面授权的密码一致
mysql.db: 'salt' # mysql数据库的名称
mysql.port: 3306 # 使用端口为3306

保存退出

3.在master上安装MySQL-python

yum -y install MySQL-python

创建数据库

CREATE DATABASE `salt`DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; 
USE `salt`; 


DROP TABLE IF EXISTS `jids`; 
CREATE TABLE `jids` 
(`jid` varchar(255) NOT NULL,`load` mediumtext NOT NULL,UNIQUE KEY `jid` (`jid`) ) 
ENGINE=InnoDB DEFAULT CHARSET=utf8; 




DROP TABLE IF EXISTS `salt_returns`; 
CREATE TABLE `salt_returns` 
(`fun` varchar(50) NOT NULL,`jid` varchar(255) NOT NULL,`return` mediumtext NOT NULL,`id` varchar(255) NOT NULL,`success` varchar(10) NOT NULL,`full_ret` mediumtext NOT NULL,KEY `id` (`id`),KEY `jid` (`jid`),KEY `fun` (`fun`) ) 
ENGINE=InnoDB DEFAULT CHARSET=utf8;

授权

GRANT ALL PRIVILEGES ON salt.* to 'salt'@'%' identified by 'salt';
flush privileges;

4.在master的后台执行自定义return脚本

python salt_event_to_mysql.py &

5.开一个新的master终端进行测试

salt '*' test.ping

6.在mysql上看是否已经将数据写入数据库

mysql -uroot -p

输入密码之后进入mysql数据库

use salt
show tables;
select * from salt_returns G
如果出现如下结果表示插入成功:
mysql> select * from salt_returns G
*************************** 1. row ***************************
     fun: test.ping
     jid: 20160807111832766142
  return: true
      id: 192.168.3.108
 success: 1
full_ret: {"fun_args": [], "jid": "20160807111832766142", "return": true, "retcode": 0, "success": true, "cmd": "_return", "_stamp": "2016-08-07T03:18:32.950841", "fun": "test.ping", "id": "192.168.3.108"}
*************************** 2. row ***************************
     fun: test.ping
     jid: 20160807111832766142
  return: true
      id: minion_client01.DHCP
 success: 1
full_ret: {"fun_args": [], "jid": "20160807111832766142", "return": true, "retcode": 0, "success": true, "cmd": "_return", "_stamp": "2016-08-07T03:18:32.953034", "fun": "test.ping", "id": "minion_client01.DHCP"}

Saltstack 安装配置详解

下面这篇文章主要介绍另外一个运维自动化工具 Saltstack 。

一、简介

Saltstack 比 Puppet 出来晚几年,是基于Python 开发的,也是基于 C/S 架构,服务端 master 和客户端 minions ;Saltstack 和 Puppet 很像,可以说 Saltstatck 整合了 Puppet 和 Chef 的功能,更加强大,更适合大规模批量管理服务器,并且它比 Puppet 更容易配置。
三大功能: 远程命令执行,配置管理(服务,文件,cron,用户,组),云管理。
支持系统:大多数都支持,windows 上不支持安装 master。

二、安装配置

1、准备工作

准备两台机器,这两台机器都关闭 selinux,清空 iptables 规则并保存。

master:192.168.0.109
slaver:192.168.0.110

2、编辑 hosts 文件

两台都设置,若机器太多,可以通过搭建 DNS,则不用在每台机器上设置这个

# vim /etc/hosts
192.168.0.109  master.test.com
192.168.0.110  slaver.test.com

3、设置 hostname

在 master 上

[iyunv@master ~]# vim /etc/sysconfig/network
HOSTNAME=master.test.com

在 slaver 上

[iyunv@slaver ~]# vim /etc/sysconfig/network
HOSTNAME=slaver.test.com

4、安装

1)服务端安装

[iyunv@master ~]# yum install -y epel-release
[iyunv@master ~]# yum install -y salt-master salt-minion

2)客户端安装

[iyunv@slaver ~]# yum install -y epel-release
[iyunv@slaver ~]# yum install -y salt-minion

5、配置

服务端和客户端都要配置 master

# vim /etc/salt/minion                   //在第16行添加,冒号后有一个空格
master: 192.168.0.109

6、启动服务

1)服务端

[iyunv@master ~]# /etc/init.d/salt-master start
Starting salt-master daemon:                               [确定]
[iyunv@master ~]# /etc/init.d/salt-minion start
Starting salt-minion daemon:                               [确定]

2)客户端

[iyunv@slaver ~]# /etc/init.d/salt-minion start
Starting salt-minion daemon:                               [确定]

三、配置认证

1)在服务端上操作

[iyunv@master ~]# salt-key -a  slaver.test.com
[iyunv@master ~]# salt-key -a  master.test.com
[iyunv@master ~]# salt-key

未分类

说明:-a :accept ,-A:accept-all,-d:delete,-D:delete-all。可以使用 salt-key 命令查看到已经签名的客户端。此时我们在客户端的 /etc/salt/pki/minion 目录下面会多出一个minion_master.pub 文件。

2)测试验证

示例1: salt ‘*’ test.ping //检测通讯是否正常,也可以指定其中一个 ‘slaver.test.com’

未分类

示例2: salt ‘*’ cmd.run ‘df -h’ //远程执行命令

未分类

说明: 这里的 * 必须是在 master 上已经被接受过的客户端,可以通过 salt-key 查到,通常是我们已经设定的 id 值。关于这部分内容,它支持通配、列表以及正则。 比如两台客户端 web10、web11, 那我们可以写成 salt ‘web*’ salt ‘web1[02]’ salt -L ‘web10,web11’ salt -E ‘web(10|11)’ 等形式,使用列表,即多个机器用逗号分隔,而且需要加-L,使用正则必须要带-E选项。 它还支持 grains 和 pillar,分别加 -G 和 -I 选项,下面会介绍到。

四、grains 和 pillar

下面来介绍 grains 和 pillar

1、grains

grains 是在 minion(客户端)启动时收集到的一些信息,比如操作系统类型、网卡ip等。
使用命令:

[iyunv@master ~]# salt 'slaver.test.com' grains.ls            //列出所有的 grains 项目名字
[iyunv@master ~]# salt 'slaver.test.com' grains.items      //列出所有的 grains 项目名以及值

grains的信息并不是动态的,并不会时时变更,它只是在 minion 启动时收集到的。grains 也可以做配置管理。
下面我们来自定义 grains

1)客户端上配置

[iyunv@slaver ~]# vim /etc/salt/grains               //添加如下,注意冒号后有空格
role: nginx
env: test
myname: tpp

或者

[iyunv@slaver ~]# vim /etc/salt/minion            //在最下面添加或更改
grains:
  role:
    - nginx
  env:
    - test
  myname:
    - tpp

重启minion服务

[iyunv@slaver ~]# /etc/init.d/salt-minion restart

2)服务端获取 grains

[iyunv@master ~]# salt 'slaver.test.com' grains.item role env myname       //列出多个

未分类

[iyunv@master ~]# salt 'slaver.test.com' grains.get myname                    //列出单个

未分类

注意:grains 在远程执行命令时很方便。我们可以按照 grains 的一些指标来操作。比如把所有的 web 服务器的 grains 的 role 设置为 nginx,那这样我们就可以批量对 nginx 的服务器进行操作了:

[iyunv@master ~]# salt -G role:nginx cmd.run 'hostname'
[iyunv@master ~]# salt -G os:CentOS cmd.run 'hostname'

2、pillar

pillar 和 grains 不一样,是在 master 上定义的,并且是针对 minion 定义的一些信息。像一些比较重要的数据(密码)可以存在 pillar 里,还可以定义变量等。
查看指定minion的 pillar 值:

[iyunv@master ~]# salt 'slaver.test.com' pillar.items

1)服务端自定义配置 pillar

[iyunv@master ~]# vim /etc/salt/master                    //找

到如下内容,去掉#号

pillar_roots:
  base:
    - /srv/pillar
[iyunv@master ~]# mkdir /srv/pillar
[iyunv@master ~]# vim /srv/pillar/test.sls                   //自定义配置文件,内容如下
conf: /etc/123.conf
myname: tpp
[iyunv@master ~]# vim /srv/pillar/top.sls                   //总入口文件,内容如下
base:
  'slaver.test.com':
    - test

重启master

[iyunv@master ~]# /etc/init.d/salt-master restart

注意:当更改完 pillar 配置文件后,我们可以通过刷新 pillar 配置来获取新的 pillar 状态:

[iyunv@master ~]# salt '*' saltutil.refresh_pillar

2)验证:

[iyunv@master ~]# salt 'slaver.test.com' pillar.items

未分类

[iyunv@master ~]# salt 'slaver.test.com' pillar.item conf
[iyunv@master ~]# salt 'slaver.test.com' pillar.item myname

未分类

pillar 同样可以用来作为 salt 的匹配对象。比如:

[iyunv@master ~]# salt -I 'conf:/etc/123.conf' test.ping
[iyunv@master ~]# salt -I 'conf:/etc/123.conf' cmd.run 'w'

未分类

五、配置管理安装Apache

下面进行的演示是远程通过 yum 方式安装 Apache。步骤如下:

1、配置

[iyunv@master ~]# vim /etc/salt/master        //打开如下内容的注释

file_roots:
  base:
    - /srv/salt

注意:环境: base、dev(开发环境)、test(测试环境)、prod(生产环境)。

[iyunv@master ~]# mkdir /srv/salt
[iyunv@master ~]# vim /srv/salt/top.sls
base:
  'slaver.test.com':
    - apache

注意:若换成 ‘*’,则表示在所有的客户端执行 apache 模块。

[iyunv@master ~]# vim /srv/salt/apache.sls
apache-service:
  pkg.installed:
    - names:                //如果只有一个服务,那么就可以写成 –name: httpd 不用再换一行
      - httpd
      - httpd-devel
  service.running:
    - name: httpd
    - enable: True

注意:apache-service 是自定义的 id 名。pkg.installed 为包安装函数,下面是要安装的包的名字。service.running 也是一个函数,来保证指定的服务启动,enable 表示开机启动。

2、重启服务

[iyunv@master ~]# /etc/init.d/salt-master restart

3、执行命令

[iyunv@master ~]# salt 'slaver.test.com' state.highstate         //执行时间比较长,因为要安装httpd

未分类

未分类

未分类

如上图所示,说明 Apache 远程安装已成功。

六、文件目录管理

1、文件管理

1)服务端配置

接着编辑之前的 top.sls 文件

[iyunv@master ~]# vim /srv/salt/top.sls              //修改为如下
base:
  'slaver.test.com':
    - filetest

新建 filetest.sls 文件

[iyunv@master ~]# vim /srv/salt/filetest.sls
file-test:
  file.managed:
    - name: /tmp/filetest.txt
    - source: salt://test/123/1.txt
    - user: root
    - group: root
    - mode: 644

注意:第一行的 file-test 为自定的名字,表示该配置段的名字,可以在别的配置段中引用它;source指定文件从哪里拷贝,这里的 test 目录相当于是 /srv/salt/test 目录;name指定远程客户端要生成的文件。

新建所要测试的源文件

[iyunv@master ~]# mkdir -p /srv/salt/test/123/
[iyunv@master ~]# vim /srv/salt/test/123/1.txt
msiyuetian.blog.iyunv.com

执行命令:

[iyunv@master ~]# salt 'slaver.test.com' state.highstate

未分类

2)客户端验证

未分类

2、目录管理

1)服务端配置

接着编辑之前的 top.sls 文件

[iyunv@master ~]# vim /srv/salt/top.sls              //修改为如下
base:
  'slaver.test.com':
    - filedir

新建 filedir.sls 文件

[iyunv@master ~]# vim /srv/salt/filedir.sls
file-dir:
  file.recurse:
    - name: /tmp/testdir
    - source: salt://test1/234
    - user: root
    - file_mode: 644
    - dir_mode: 755
    - mkdir: True
    - clean: True

注意:clean: True 源删除文件或目录,目标也会跟着删除,否则不会删除。可以默认设置为 False。

新建所要测试的源目录

[iyunv@master ~]# mkdir -p /srv/salt/test1/234
[iyunv@master ~]# vim /srv/salt/test1/234/2.txt
msiyuetian.blog.iyunv.com

执行命令:

[iyunv@master ~]# salt 'slaver.test.com' state.highstate

未分类

2)客户端验证

未分类

3)测试增删功能

在服务端新建 mydir 目录以及 testdir.add 文件,删除 2.txt 文件:

[iyunv@master ~]# mkdir /srv/salt/test1/234/mydir
[iyunv@master ~]# touch /srv/salt/test1/234/mydir/111.txt
[iyunv@master ~]# touch /srv/salt/test1/234/testdir.add
[iyunv@master ~]# rm -rf /srv/salt/test1/234/2.txt

执行命令:

[iyunv@master ~]# salt 'slaver.test.com' state.highstate

客户端验证

未分类

注意:由上图可知,成功在客户端 /tmp/testdir/ 目录下创建了 mydir 目录以及 testdir.add 文件,并删除 2.txt 文件。这里值得注意的是要成功创建 mydir 目录,前提是 mydir 目录下要有文件,如这里的111.txt 文件,如若没有,客户端是不会创建 mydir 目录的。

七、远程执行

前面提到远程执行命令 test.ping,cmd.run,点前面的是模块,点后面的是函数;这样总归是不太规范化,下面详细介绍怎么远程执行命令和脚本。

1、远程执行命令

1)服务端配置

接着编辑之前的 top.sls 文件

[iyunv@master ~]# vim /srv/salt/top.sls              //修改为如下
base:
  'slaver.test.com':
    - cmdtest

新建 cmdtest.sls 文件

[iyunv@master ~]# vim /srv/salt/cmdtest.sls
cmd-test:  
  cmd.run:
    - onlyif: test -f /tmp/123.txt
    - names:
      - touch /tmp/cmdtest.txt
      - mkdir /tmp/cmdtest
    - user: root

注意:条件 onlyif 表示若 /tmp/123.txt 文件存在,则执行后面的命令;可以使用 unless,两者正好相反。

执行命令:

[iyunv@master ~]# salt 'slaver.test.com' state.highstate

2)客户端验证

未分类

2、远程执行脚本

1)服务端配置

接着编辑之前的 top.sls 文件

[iyunv@master ~]# vim /srv/salt/top.sls              //修改为如下
base:
  'slaver.test.com':
    - shelltest

新建 shelltest.sls 文件

[iyunv@master ~]# vim /srv/salt/shelltest.sls
shell-test:
  cmd.script:
    - source: salt://test/1.sh
    - user: root

新建 1.sh 脚本文件

[iyunv@master ~]# vim /srv/salt/test/1.sh
#!/bin/bash
touch /tmp/shelltest.txt
if [ -d /tmp/shelltest ]
then
    rm -rf /tmp/shelltest
else
    mkdir /tmp/shelltest
fi

执行命令:

[iyunv@master ~]# salt 'slaver.test.com' state.highstate

2)客户端验证

未分类

注意:通过上面的例子,我们实现了远程执行脚本;如果我们想一键远程安装 LAMP 或者 LNMP,那么只需把本例中的 1.sh 脚本替换成 一键安装的脚本就行。

八、管理任务计划

1、建立 cron

1)服务端配置

编辑 top.sls 文件

[iyunv@master ~]# vim /srv/salt/top.sls              //修改为如下
base:
  'slaver.test.com':
    - crontest

编辑 crontest.sls 文件

[iyunv@master ~]# vim /srv/salt/crontest.sls
cron-test:
  cron.present:
    - name: /bin/touch /tmp/111.txt
    - user: root
    - minute: '*'
    - hour: 20
    - daymonth: 1-10
    - month: '3,5'
    - dayweek: '*'

注意,*需要用单引号引起来。当然我们还可以使用 file.managed 模块来管理 cron,因为系统的 cron都是以配置文件的形式存在的。

执行命令:

[iyunv@master ~]# salt 'slaver.test.com' state.highstate

2)客户端验证

未分类

2、删除 cron

1)服务端配置

我们只需修改 crontest.sls 文件

[iyunv@master ~]# vim /srv/salt/crontest.sls

把 cron.present: 改成 cron.absent:
注意:两者不能共存,要想删除一个 cron,那之前的 present 就得替换掉或者删除掉。
执行命令:

[iyunv@master ~]# salt 'slaver.test.com' state.highstate

未分类

2)客户端验证

[iyunv@slaver ~]# crontab -l           //可查看到该任务计划已删除

九、Saltstack 常用命令

1、拷贝文件到客户端

[iyunv@master ~]# salt 'slaver.test.com' cp.get_file salt://apache.sls /tmp/cp.txt
slaver.test.com:
    /tmp/cp.txt

2、拷贝目录到客户端

[iyunv@master ~]# salt 'slaver.test.com' cp.get_dir salt://test /tmp
slaver.test.com:
    - /tmp/test/1.sh
    - /tmp/test/123/1.txt

未分类

3、显示存活的客户端

[iyunv@master ~]# salt-run manage.up

未分类

4、命令下执行服务端的脚本

[iyunv@master ~]# vim /srv/salt/test/shell.sh

#! /bin/bash
echo "msiyuetian.blog.iyunv.com" > /tmp/shell.txt
[iyunv@master ~]# salt 'slaver.test.com' cmd.script salt://test/shell.sh

客户端查看

未分类

rsync基本用法

Top
NSD SERVICES DAY05

案例1:rsync基本用法
案例2:rsync+SSH同步
案例3:使用inotifywait工具
案例4:配置Web镜像同步

一、案例1:rsync基本用法

1.1 问题

本例要求掌握远程同步的基本操作,使用rsync命令完成下列任务:

将目录 /boot 同步到目录 /todir 下
将目录 /boot 下的文档同步到目录 /todir 下
在目录 /boot 下新增文件 a.txt,删除 /todir 下的子目录 grub2,再次同步使 /todir 与 /boot 一致
验证 -a、-n、-v、--delete 选项的含义

1.2 方案

本地同步操作:

rsync [选项...] 本地目录1 本地目录2
rsync [选项...] 本地目录1/ 本地目录2

rsync同步工具的常用选项:

-n:测试同步过程,不做实际修改
--delete:删除目标文件夹内多余的文档
-a:归档模式,相当于-rlptgoD
-v:显示详细操作信息
-z:传输过程中启用压缩/解压

1.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:rsync同步基本操作

1)将目录 /boot 同步到目录 /todir 下

[root@svr7 ~]# ls  -l  /todir                  //同步前
ls: 无法访问/todir: 没有那个文件或目录
[root@svr7 ~]# rsync  -a  /boot  /todir          //将目录1作为目录2的子目录
[root@svr7 ~]# ls  -l  /todir                  //检查同步结果
总用量 4
dr-xr-xr-x. 4 root root 4096 11月 30 18:50 boot

2)将目录 /boot 下的文档同步到目录 /todir 下

[root@svr7 ~]# rm  -rf  /todir                  //清理掉目录2
[root@svr7 ~]# rsync  -a  /boot/  /todir          //将目录1下的文档同步到目录2下
[root@svr7 ~]# ls  -l  /todir                  //检查同步结果
总用量 126708
-rw-r--r--. 1 root root   126426 10月 30 2015 config-3.10.0-327.el7.x86_64
drwxr-xr-x. 2 root root     4096 11月 30 18:50 extlinux
drwx------. 6 root root      104 12月  9 09:58 grub2
.. ..

3)同步效果测试

在目录/boot下新增文件a.txt,删除/todir下的子目录 grub2:

[root@svr7 ~]# touch  /boot/a.txt
[root@svr7 ~]# rm  -rf  /todir/grub2/ 

现在目录/boot和/todir目录下的内容已经不一致了:

[root@svr7 ~]# ls  -ld  /boot/a.txt  /todir/a.txt
ls: 无法访问/todir/a.txt: 没有那个文件或目录
-rw-r--r--. 1 root root 0 1月  11 21:09 /boot/a.txt
[root@svr7 ~]# ls  -ld  /boot/grub2  /todir/grub2
ls: 无法访问/todir/grub2: 没有那个文件或目录
drwx------. 6 root root 104 12月  9 09:58 /boot/grub2

再次同步使/todir与/boot一致:

[root@svr7 ~]# rsync  -a  /boot/  /todir/

确认同步结果:

[root@svr7 ~]# ls  -ld  /boot/a.txt  /todir/a.txt
-rw-r--r--. 1 root root 0 1月  11 21:09 /boot/a.txt
-rw-r--r--. 1 root root 0 1月  11 21:09 /todir/a.txt
[root@svr7 ~]# ls  -ld  /boot/grub2  /todir/grub2
drwx------. 6 root root 104 12月  9 09:58 /boot/grub2
drwx------. 6 root root 104 12月  9 09:58 /todir/grub2

步骤二:验证 -a、-v、-n、–delete 选项的含义

1)验证-a选项

当目录1包含文件夹时,若缺少-a或-r选项则文件夹会被忽略:

[root@svr7 ~]# rsync  /home  /testa
skipping directory home
[root@svr7 ~]# ls  -ld  /testa
ls: 无法访问/testa: 没有那个文件或目录

添加-a后才会执行同步:


[root@svr7 ~]# rsync -a /home/ /testa [root@svr7 ~]# ls -ld /testa drwxr-xr-x. 4 root root 31 1月 6 17:33 /testa

类似的情况,当目录1中的数据出现权限、归属、修改时间等变化时,若文件内容不变默认不会同步,若希望目录2也同步这些变化,也需要-a选项。

2)验证-v选项

创建测试目录及文档:

[root@svr7 ~]# mkdir  /fdir
[root@svr7 ~]# touch  /fdir/1.txt

添加-v选项时,可以看到操作细节信息,比如第一次同步时:

[root@svr7 ~]# rsync  -av  /fdir/  /tdir
sending incremental file list
created directory /tdir
./
1.txt                                 //传输文档列表
sent 82 bytes  received 34 bytes  232.00 bytes/sec
total size is 0  speedup is 0.00

在目录/fdir/添加文件2.txt,再次跟踪同步信息:

[root@svr7 ~]# touch  /fdir/2.txt
sending incremental file list
./
2.txt                                 //传输文档列表
sent 100 bytes  received 34 bytes  268.00 bytes/sec
total size is 0  speedup is 0.00

确认目录1和目录2的内容已经一致:

[root@svr7 ~]# ls  /fdir/  /tdir/
/fdir/:
1.txt  2.txt
/tdir/:
1.txt  2.txt

再次跟踪同步信息,已经无需传输文件:

[root@svr7 ~]# rsync  -av  /fdir/  /tdir
sending incremental file list
sent 58 bytes  received 12 bytes  140.00 bytes/sec
total size is 0  speedup is 0.00

3)验证-n选项

将-n、-v选项合用,可以模拟同步过程,显示需要做哪些操作(但并不真的同步)。

在目录/fdir下新建文件3.txt,测试同步操作:

[root@svr7 ~]# touch  /fdir/3.txt
[root@svr7 ~]# rsync  -avn  /fdir/  /tdir/
sending incremental file list
./
3.txt                                          //提示同步时会传输哪些文件
sent 78 bytes  received 18 bytes  192.00 bytes/sec
total size is 0  speedup is 0.00 (DRY RUN)
[root@svr7 ~]# ls  -l  /tdir/3.txt                 //但实际并未真的同步
ls: 无法访问/tdir/3.txt: 没有那个文件或目录

去掉-n选项才会真正同步:

[root@svr7 ~]# rsync  -av  /fdir/  /tdir/
sending incremental file list
./
3.txt
sent 114 bytes  received 34 bytes  296.00 bytes/sec
total size is 0  speedup is 0.00
[root@svr7 ~]# ls  -l  /tdir/3.txt
-rw-r--r--. 1 root root 0 1月  11 21:46 /tdir/3.txt

4)验证–delete选项

rsync同步操作默认只是将目录1的数据同步到目录2,但如果目录2存在多余的文件却并不会去除,除非添加—delete选项。

在目录/fdir、/tdir已经完成同步后,删除/tdir/2.txt文件,再次同步:

[root@svr7 ~]# rm  -rf  /fdir/2.txt 
[root@svr7 ~]# rsync  -a  /fdir/  /tdir/

检查发现目标文件夹/tdir下的2.txt文件还在:

[root@svr7 ~]# ls  /fdir/  /tdir/
/fdir/:
1.txt  3.txt
/tdir/:
1.txt  2.txt  3.txt

这种情况下添加–delete选项再次执行同步,两个目录的内容就一致了:

[root@svr7 ~]# rsync  -a  --delete  /fdir/  /tdir/
[root@svr7 ~]# ls  /fdir/  /tdir/
/fdir/:
1.txt  3.txt
/tdir/:
1.txt  3.txt

二、案例2:rsync+SSH同步

2.1 问题

本例要求掌握rsync与远程SSH资源的同步操作,使用rsync命令访问远程主机svr7,完成下列任务:

查看远程主机的 / 目录下有哪些子目录
从远程主机下载 /etc/passwd 文件到当前目录
将远程主机的 /boot/ 目录同步为本地的 /fromssh
将本机的 /etc 目录同步到远程主机的 /opt/下

2.2 方案

列出 SSH 服务端资源

rsync user@host:远程目录/

rsync+SSH远程同步操作:

rsync [...] user@host:远程目录 本地目录
rsync [...] 本地目录 user@host:远程目录

2.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:列出远程主机的SSH资源

查看远程主机svr7的/目录下有哪些子目录:

[root@pc207 ~]# rsync  [email protected]:/
[email protected]'s password:                           //验证对方的密码
dr-xr-xr-x        4096 2016/12/15 10:39:34 .
lrwxrwxrwx           7 2016/12/07 09:21:50 bin
lrwxrwxrwx           7 2016/12/07 09:21:50 lib
lrwxrwxrwx           9 2016/12/07 09:21:50 lib64
lrwxrwxrwx           8 2016/12/07 09:21:50 sbin
dr-xr-xr-x        4096 2016/12/07 11:25:29 boot
drwxr-xr-x           6 2016/12/07 09:21:14 data
drwxr-xr-x        3200 2016/12/15 10:46:15 dev
drwxr-xr-x        8192 2016/12/20 17:01:02 etc

步骤二:rsync+SSH同步操作

1)从远程主机svr7下载/etc/passwd文件到当前目录

[root@pc207 ~]# rsync  [email protected]:/etc/passwd  ./
[email protected]'s password:                           //验证对方的密码
[root@pc207 ~]# cat  passwd                             //检查同步结果
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
.. ..

2)将远程主机svr7的/boot/目录同步为本地的/fromssh

[root@pc207 ~]# rsync  -a  [email protected]:/boot/  /fromssh
[email protected]'s password:                           //验证对方的密码
[root@pc207 ~]# ls  /fromssh/                             //检查同步结果
config-3.10.0-327.el7.x86_64
extlinux
grub2
initramfs-0-rescue-a19921505cc7e19d20dfcd5cea7d8aa2.img
initramfs-3.10.0-327.el7.x86_64.img
initramfs-3.10.0-327.el7.x86_64kdump.img
.. ..

3)将本机的/etc目录同步到远程主机svr7的/opt/下

确认目录大小:

[root@pc207 ~]# du  -sh  /etc
35M    /etc

上行同步到远程主机svr7上:

[root@pc207 ~]# rsync  -a  /etc  [email protected]:/opt/
[email protected]'s password:

在远程主机上检查同步结果:

[root@svr7 ~]# du  -sh  /opt/etc
35M    /opt/etc

三、案例3:使用inotifywait工具

3.1 问题

本例要求安装inotify-tools工具,并针对文件夹 /opt 启用 inotifywait 监控,完成下列任务:

当此目录下出现新建、修改、更改权限、删除文件等事件时能给出提示
验证上述监控事件的效果

3.2 方案

inotifywait监控操作:

inotifywait [选项] 目标文件夹

inotifywait常用命令选项:

-m,持续监控(捕获一个事件后不退出)
-r,递归监控、包括子目录及文件
-q,减少屏幕输出信息
-e,指定监视的 modify、move、create、delete、attrib 等事件类别

3.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:安装inotify-tools软件包

1)解包

[root@svr7 ~]# tar  xf  inotify-tools-3.13.tar.gz  -C  /usr/src/

2)配置

[root@svr7 ~]# cd  /usr/src/inotify-tools-3.13/
[root@svr7 inotify-tools-3.13]# ./configure

3)编译

[root@svr7 inotify-tools-3.13]# make

4)安装

[root@svr7 inotify-tools-3.13]# make

5)检查安装结果(inotifywait程序可用)

[root@svr7 ~]# inotifywait --help
inotifywait 3.13
Wait for a particular event on a file or set of files.
Usage: inotifywait [ options ] file1 [ file2 ] [ file3 ] [ ... ]
Options:
    -h|--help         Show this help text.
.. ..

步骤二:测试inotifywait监控

1)开启监控任务,置入后台

[root@svr7 ~]# inotifywait  -mrq  -e  create,modify,move,attrib,delete /opt &
[1] 55564

2)测试/opt/目录下的新建、修改、改名、更改权限、删除文件等事件的响应消息

观察新建文件时的监控信息:

[root@svr7 ~]# touch  /opt/a.txt
/opt/ CREATE a.txt
/opt/ ATTRIB a.txt

观察修改文件内容时的监控信息:

[root@svr7 ~]# echo  Hello  >  /opt/a.txt
[root@svr7 ~]# /opt/ MODIFY a.txt
/opt/ MODIFY a.txt

观察将文件改名时的监控信息:

[root@svr7 ~]# mv  /opt/a.txt  /opt/b.txt
/opt/ MOVED_FROM a.txt
/opt/ MOVED_TO b.txt

观察修改文件权限时的监控信息:

[root@svr7 ~]# chmod  600  /opt/b.txt
/opt/ ATTRIB b.txt

观察删除文件时的监控信息:

[root@svr7 ~]# rm  -rf  /opt/b.txt 
/opt/ DELETE b.txt

3)停止监控任务

[root@svr7 ~]# kill  -9  %1
[1]+  已杀死          inotifywait -mr -e create,modify,move,attrib,delete /opt

四、案例4:配置Web镜像同步

4.1 问题

本例要求为两台Web服务器svr7、pc207的网页文档目录配置镜像同步,主要基于inotifywait监控技术实现实时触发操作,需要完成下列任务:

以 svr7 为发起方,原始目录为 /var/www/html/
以 pc207 为同步目标,基于SSH免密验证
编写 inotify+rsync 同步脚本,验证实时同步效果

4.2 方案

inotifywait与rsync的结合,主要思路:

while  inotifywait监控操作
do
        需要执行的rsync同步操作
done

4.3 步骤

实现此案例需要按照如下步骤进行。

步骤一:为主机svr7、pc207部署同步目录

双方的目录均为/var/www/html/,如果安装了httpd,此目录会自动出现。

1)确认svr7的目录内容

[root@svr7 ~]# yum  -y  install  httpd
.. ..
[root@svr7 ~]# ls  /var/www/html/                     //向目录下提供一些测试文件
libreoffice

2)确认pc207的目录内容

[root@pc207 ~]# yum  -y  install  httpd
.. ..
[root@pc207 ~]# ls   /var/www/html                 //初始目录无数据
[root@pc207 ~]# 

步骤二:为svr7配置到pc207的SSH密钥对验证,实现免密码交互

1)检查当前用户是否已经有可用的SSH密钥对文件

[root@svr7 ~]# ls  ~/.ssh/id_*
/root/.ssh/id_rsa  /root/.ssh/id_rsa.pub

如果找不到id_rsa、id_rsa.pub密钥对文件,则需要执行下列操作创建:

[root@svr7 ~]# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa):      //按回车,确认存放位置
Enter passphrase (empty for no passphrase):       //按回车,确认不要密码
Enter same passphrase again:                      //再次按回车,确认
Your identification has been saved in /root/.ssh/id_rsa. 
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
00:a7:cb:2d:9d:b8:8a:df:f5:ff:5b:ed:bd:04:10:fe root@svr7
The key's randomart image is:
+--[ RSA 2048]----+
|    . .    .     |
|     +    . .    |
|    . .    o     |
|   . = o    o    |
|    = + S    E   |
|     o        .. |
|    . .       ...|
| . o . .     ....|
|..o .   ....o. .+|
+-----------------+

2)将当前用户的SSH公钥部署到远程主机

[root@svr7 ~]# ssh-copy-id  [email protected]
The authenticity of host '192.168.4.207 (192.168.4.207)' can't be established.
ECDSA key fingerprint is d3:16:2c:9a:9d:91:28:c8:74:9c:af:2d:04:82:c9:66.
Are you sure you want to continue connecting (yes/no)? yes         //首次连yes确认
[email protected]'s password:                      //验证对方的密码
Number of key(s) added: 1
Now try logging into the machine, with:   "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.

3)验证免密码登录效果

[root@svr7 ~]# ssh  [email protected]
Last login: Fri Jan 13 09:52:08 2017 from 192.168.4.110
[root@pc207 ~]#                                      //确认已免密码连入远程主机
[root@pc207 ~]# exit                                  //退出SSH登录环境
登出
Connection to 192.168.4.207 closed.
[root@svr7 ~]#                                     //已反对原客户机

步骤三:编写镜像同步脚本并测试效果

1)编写脚本文件/root/isync.sh

[root@svr7 ~]# vim  /root/isync.sh
#!/bin/bash
FROM_DIR="/var/www/html/"      
RSYNC_CMD="rsync  -az  --delete  $FROM_DIR  [email protected]:/var/www/html" 
while  inotifywait  -rqq  -e  modify,move,create,delete,attrib  $FROM_DIR 
do
    $RSYNC_CMD
done  &
[root@svr7 ~]# chmod  +x  /root/isync.sh  

2)运行脚本

[root@svr7 ~]# /root/isync.sh
[root@svr7 ~]# pgrep  -l  inotify                      //确认任务在运行
56494 inotifywait

3)测试同步效果

在svr7上向/var/www/html/目录下添加一个测试网页(触发同步):

[root@svr7 ~]# touch  /var/www/html/a.txt
[root@svr7 ~]# ls  /var/www/html/
a.txt  libreoffice

在pc207上检查/var/www/html/目录,内容应该已经与svr7上的同名目录一致:

[root@pc207 ~]# ls   /var/www/html
a.txt  libreoffice

4)结束测试后,在svr7上停止监控任务

[root@svr7 ~]# pkill  -9  inotify
[root@svr7 ~]# pgrep  -l  inotify                     //确认已没有监控任务
[root@svr7 ~]#

数据科学20个最好的Python库

Python 在解决数据科学任务和挑战方面继续处于领先地位。去年,我们曾发表一篇博客文章 Top 15 Python Libraries for Data Science in 2017,概述了当时业已证明最有帮助的Python库。今年,我们扩展了这个清单,增加了新的 Python 库,并重新审视了去年已经讨论过的 Python 库,重点关注了这一年来的更新。
我们的选择实际上包含了 20 多个库,因为其中一些库是相互替代的,可以解决相同的问题。因此,我们将它们放在同一个分组。

核心库和统计数据

1. NumPy (Commits: 17911, Contributors: 641)

官网:http://www.numpy.org/

NumPy 是科学应用程序库的主要软件包之一,用于处理大型多维数组和矩阵,它大量的高级数学函数集合和实现方法使得这些对象执行操作成为可能。

2. SciPy (Commits: 19150, Contributors: 608)

官网:https://scipy.org/scipylib/

科学计算的另一个核心库是 SciPy。它基于 NumPy,其功能也因此得到了扩展。SciPy 主数据结构又是一个多维数组,由 Numpy 实现。这个软件包包含了帮助解决线性代数、概率论、积分计算和许多其他任务的工具。此外,SciPy 还封装了许多新的 BLAS 和 LAPACK 函数。

3. Pandas (Commits: 17144, Contributors: 1165)

官网:https://pandas.pydata.org/

Pandas 是一个 Python 库,提供高级的数据结构和各种各样的分析工具。这个软件包的主要特点是能够将相当复杂的数据操作转换为一两个命令。Pandas包含许多用于分组、过滤和组合数据的内置方法,以及时间序列功能。

4. StatsModels (Commits: 10067, Contributors: 153)

官网:http://www.statsmodels.org/devel/

Statsmodels 是一个 Python 模块,它为统计数据分析提供了许多机会,例如统计模型估计、执行统计测试等。在它的帮助下,你可以实现许多机器学习方法并探索不同的绘图可能性。
Python 库不断发展,不断丰富新的机遇。因此,今年出现了时间序列的改进和新的计数模型,即 GeneralizedPoisson、零膨胀模型(zero inflated models)和 NegativeBinomialP,以及新的多元方法:因子分析、多元方差分析以及方差分析中的重复测量。

可视化

5. Matplotlib (Commits: 25747, Contributors: 725)

官网:https://matplotlib.org/index.html

Matplotlib 是一个用于创建二维图和图形的底层库。藉由它的帮助,你可以构建各种不同的图标,从直方图和散点图到费笛卡尔坐标图。此外,有许多流行的绘图库被设计为与matplotlib结合使用。

未分类

6. Seaborn (Commits: 2044, Contributors: 83)

官网:https://seaborn.pydata.org/

Seaborn 本质上是一个基于 matplotlib 库的高级 API。它包含更适合处理图表的默认设置。此外,还有丰富的可视化库,包括一些复杂类型,如时间序列、联合分布图(jointplots)和小提琴图(violin diagrams)。

未分类

7. Plotly (Commits: 2906, Contributors: 48)

官网:https://plot.ly/python/

Plotly 是一个流行的库,它可以让你轻松构建复杂的图形。该软件包适用于交互式 Web 应用程,可实现轮廓图、三元图和三维图等视觉效果。

8. Bokeh (Commits: 16983, Contributors: 294)

官网:https://bokeh.pydata.org/en/latest/
Bokeh 库使用 JavaScript 小部件在浏览器中创建交互式和可缩放的可视化。该库提供了多种图表集合,样式可能性(styling possibilities),链接图、添加小部件和定义回调等形式的交互能力,以及许多更有用的特性。

未分类

9. Pydot (Commits: 169, Contributors: 12)

官网:https://pypi.org/project/pydot/

Pydot 是一个用于生成复杂的定向图和无向图的库。它是用纯 Python 编写的Graphviz 接口。在它的帮助下,可以显示图形的结构,这在构建神经网络和基于决策树的算法时经常用到。

未分类

机器学习

10. Scikit-learn (Commits: 22753, Contributors: 1084)

官网:http://scikit-learn.org/stable/

这个基于 NumPy 和 SciPy 的 Python 模块是处理数据的最佳库之一。它为许多标准的机器学习和数据挖掘任务提供算法,如聚类、回归、分类、降维和模型选择。

利用 Data Science School 提高你的技能

Data Science School:http://datascience-school.com/

11. XGBoost / LightGBM / CatBoost (Commits: 3277 / 1083 / 1509, Contributors: 280 / 79 / 61)

官网:http://xgboost.readthedocs.io/en/latest/http://lightgbm.readthedocs.io/en/latest/Python-Intro.htmlhttps://github.com/catboost/catboost

梯度增强算法是最流行的机器学习算法之一,它是建立一个不断改进的基本模型,即决策树。因此,为了快速、方便地实现这个方法而设计了专门库。就是说,我们认为 XGBoost、LightGBM 和 CatBoost 值得特别关注。它们都是解决常见问题的竞争者,并且使用方式几乎相同。这些库提供了高度优化的、可扩展的、快速的梯度增强实现,这使得它们在数据科学家和 Kaggle 竞争对手中非常流行,因为在这些算法的帮助下赢得了许多比赛。

12. Eli5 (Commits: 922, Contributors: 6)

官网:https://eli5.readthedocs.io/en/latest/

通常情况下,机器学习模型预测的结果并不完全清楚,这正是 Eli5 帮助应对的挑战。它是一个用于可视化和调试机器学习模型并逐步跟踪算法工作的软件包,为 scikit-learn、XGBoost、LightGBM、lightning 和 sklearn-crfsuite 库提供支持,并为每个库执行不同的任务。

深度学习

13. TensorFlow (Commits: 33339, Contributors: 1469)

官网:https://www.tensorflow.org/

TensorFlow 是一个流行的深度学习和机器学习框架,由 Google Brain 开发。它提供了使用具有多个数据集的人工神经网络的能力。在最流行的 TensorFlow应用中有目标识别、语音识别等。在常规的 TensorFlow 上也有不同的 leyer-helper,如 tflearn、tf-slim、skflow 等。

14. PyTorch (Commits: 11306, Contributors: 635)

官网:https://pytorch.org/

PyTorch 是一个大型框架,它允许使用 GPU 加速执行张量计算,创建动态计算图并自动计算梯度。在此之上,PyTorch 为解决与神经网络相关的应用程序提供了丰富的 API。该库基于 Torch,是用 C 实现的开源深度学习库。

15. Keras (Commits: 4539, Contributors: 671)

官网:https://keras.io/

Keras 是一个用于处理神经网络的高级库,运行在 TensorFlow、Theano 之上,现在由于新版本的发布,还可以使用 CNTK 和 MxNet 作为后端。它简化了许多特定的任务,并且大大减少了单调代码的数量。然而,它可能不适合某些复杂的任务。

分布式深度学习

16. Dist-keras / elephas / spark-deep-learning (Commits: 1125 / 170 / 67, Contributors: 5 / 13 / 11)

官网:http://joerihermans.com/work/distributed-
keras/https://pypi.org/project/elephas/https://databricks.github.io/spark-deep-learning/site/index.html

随着越来越多的用例需要花费大量的精力和时间,深度学习问题变得越来越重要。然而,使用像 Apache Spark 这样的分布式计算系统,处理如此多的数据要容易得多,这再次扩展了深入学习的可能性。因此,dist-keras、elephas 和 spark-deep-learning 都在迅速流行和发展,而且很难挑出一个库,因为它们都是为解决共同的任务而设计的。这些包允许你在 Apache Spark 的帮助下直接训练基于 Keras 库的神经网络。Spark-deep-learning 还提供了使用 Python 神经网络创建管道的工具。

自然语言处理

17. NLTK (Commits: 13041, Contributors: 236)

官网:https://www.nltk.org/

NLTK 是一组库,一个用于自然语言处理的完整平台。在 NLTK 的帮助下,你可以以各种方式处理和分析文本,对文本进行标记和标记,提取信息等。NLTK 也用于原型设计和建立研究系统。

18. SpaCy (Commits: 8623, Contributors: 215)

官网:https://spacy.io/

SpaCy 是一个具有优秀示例、API 文档和演示应用程序的自然语言处理库。这个库是用 Cython 语言编写的,Cython 是 Python 的 C 扩展。它支持近 30 种语言,提供了简单的深度学习集成,保证了健壮性和高准确率。SpaCy 的另一个重要特性是专为整个文档处理设计的体系结构,无须将文档分解成短语。

19. Gensim (Commits: 3603, Contributors: 273)

官网:https://radimrehurek.com/gensim/

Gensim 是一个用于健壮语义分析、主题建模和向量空间建模的 Python 库,构建在Numpy和Scipy之上。它提供了流行的NLP算法的实现,如 word2vec。尽管 gensim 有自己的 models.wrappers.fasttext实现,但 fasttext 库也可以用来高效学习词语表示。

数据采集

20. Scrapy (Commits: 6625, Contributors: 281)

官网:https://scrapy.org/

Scrapy 是一个用来创建网络爬虫,扫描网页和收集结构化数据的库。此外,Scrapy 可以从 API 中提取数据。由于该库的可扩展性和可移植性,使得它用起来非常方便。

结论

本文上述所列就是我们在 2018 年为数据科学领域中丰富的 Python 库集合。与上一年相比,一些新的现代库越来越受欢迎,而那些已经成为经典的数据科学任务的库也在不断改进。

下表显示了 GitHub 活动的详细统计数据:

未分类

Nginx+Tomcat 部署负载均衡集群

Nginx是一款轻量级的Web服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器。在Java的Web架构中,通常使用Tomcat和Nginx进行配合,Nginx作为反向代理服务器,可以对后台的Tomcat服务器负载均衡,也可以让Nginx处理静态页面的请求、Tomcat处理JSP页面请求达到动静分离的目的。

系统环境:

未分类

软件包百度下载: https://pan.baidu.com/share/init?surl=3fbIn0EuEcSVplRk4tRnAg&third=15 密码:6b7e

网站拓扑架构图:

未分类

开始部署

一、Tomcat服务器1、Tomcat服务器2

1.安装jdk并设置java环境

#安装jdk软件包
rpm -ivh jdk-10.0.1_linux-x64_bin.rpm

#添加jdk的环境变量,新建java.sh并写入以下内容
vim /etc/profile.d/java.sh

    export JAVA_HOME=/usr/java/jdk-10.0.1
    export PATH=$JAVA_HOME/bin:$PATH

#加载环境变量
source /etc/profile.d/java.sh

2.查看jdk版本信息

java -version

如下图表示jdk已经安装成功了

未分类

3.解压并安装tomcat

#解压缩软件包
tar zxvf apache-tomcat-8.5.11.tar.gz

#移动tomcat目录
mv apache-tomcat-8.5.11 /usr/local/tomcat8

4.启动tomcat服务

#启动服务
/usr/local/tomcat8/bin/startup.sh
  1. 默认tomcat运行在8080端口,检查服务是否成功启动
netstat -tunlp | grep 8080

未分类

6.创建站点目录

mkdir -p /web/webapp

7.为站点新建首页文件index.jsp

vim /web/webapp/index.jsp
#以为下index.jsp内容
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8" %>
<html>
<head>
<title>hello</title>
</head>
<body>
<% out.println("<h1>This is tomcat 1 web.</h1>"); %>
</body>
</html>

8.编辑tomcat主配置文件,以支持新站点:

vim /usr/local/tomcat8/conf/server.xml
#在<Host>标签内,新增第二行记录
<Context docBase="/web/webapp" path="" reloadable="false"></Context>

未分类

9.重启tomcat服务

#关闭服务
/usr/local/tomcat8/bin/shutdown.sh

#开启服务
/usr/local/tomcat8/bin/startup.sh

#关闭安全设置
setenforce 0

#关闭防火墙
systemctl stop firewalld.service

Tomcat服务器1 和 Tomcat服务器2 安装及配置等一样,只是测试网页内容有差异,具体网页内容显示如下图

Tomcat服务器1:

未分类

Tomcat服务器2:

未分类

二、Nginx服务器

1.安装依赖包(需要连接网络或者通过系统镜像ISO文件安装)

yum -y install pcre-devel zlib-devel gcc gcc-c++ make

2.创建管理用户nginx

useradd -M -s /sbin/nologin nginx

3.解压nginx,并进入解压后nginx目录

tar xzvf nginx-1.6.0.tar.gz -C /opt
cd /opt/nginx-1.6.0/

4.配置

./configure 
--prefix=/usr/local/nginx 
--user=nginx 
--group=nginx 
--with-http_stub_status_module //开启stub_status状态统计模块//

5.编译及安装

make && make install

6.nginx连接至系统命令区,方便命令使用

ln -s /usr/local/nginx/sbin/nginx /usr/local/sbin/

7.创建nginx管理脚本

vi /etc/init.d/nginx
#!/bin/bash
#chkconfig: - 99 20
#description: Nginx Service Control Script
PROG="/usr/local/nginx/sbin/nginx"
PIDF="/usr/local/nginx/logs/nginx.pid"
case "$1" in
start)
$PROG
;;
stop)
kill -s QUIT $(cat $PIDF)
;;
restart)
$0 stop
$0 start
;;
reload)
kill -s HUP $(cat $PIDF)
;;
*)
echo "Usage: $0 {start|stop|restart|reload}"
exit 1
esac
exit 0

8.为nginx赋予执行权限,并加入系统服务管理

chmod +x /etc/init.d/nginx
chkconfig --add nginx

三、配置nginx负载均衡集群

  1. 修改nginx配置文件
vim /usr/local/nginx/conf/nginx.conf
#http{}标签内添加以下命令
upstream tomcat_server {
server 192.168.100.6:8080 weight=1;
server 192.168.100.7:8080 weight=1;
}
#在location / {}标签内添加
location / {
root html;
index index.html index.htm;
proxy_pass http://tomcat_server; #通过proxy_pass方法进行代理至tomcat_server的服务器组,其中http://不能省略
}

未分类

2.检查nginx配置

nginx -t

3.重启nginx服务

killall -1 nginx
#关闭selinux
setenforce 0
#关闭防火墙
systemctl stop firewalld.service

测试

客户机访问Nginx服务器IP地址:http://192.168.100.25/,通过不断的刷新浏览器测试,可以看到哦由于权重相同,页面在两个tomcat站点反复切换,这样说明负载均衡集群搭建成功了。

未分类

未分类

记一次负载均衡+NFS博客站点搭建的总结

起因

  
原本是打算搭建个小博客站点做实验,突然想起之前遇到的一次负载均衡失效的经历,便打算做一次实验重现当初的情况并记录下来,防止日后再遇到类似的情况懵逼。
  

复现流程

架构体系

  原环境:前端依赖nginx做负载均衡的调度器,web端是httpd+php-fpm分离,分别处理动态和静态页面,后端mariadb数据库做数据存储,NFS服务器提供站点文件作出共享,防止,web端出现站点文件不一致的情况。
  模拟环境:nginx做负载均衡器,httpd和php-fpm共存在同一主机,NFS和mariadb分处2台主机,整个模拟一共使用5台主机。
  

软件环境与架构图

未分类
  
架构图

未分类
  

配置各个节点

配置nginx节点

  
1.安装nginx

yum install nginx -y

2.编辑nginx配置文件

vim /etc/nginx/conf.d/upstream.conf

#写入下面内容
#定义负载转发的后端ip
upstream www.douma.com {
  server 192.168.99.131:80;
  server 192.168.99.130:80;
}

#配置主配置信息
server {
  listen 80;
  server_name www.douma.com;
  access_log /var/log/douma-access.log;
  error_log /var/log/douma-error.log debug;
  #set your default location
  location / {
    proxy_pass http://www.douma.com;
  }
}

3.启动nginx

systemctl start nginx

未分类

配置mariadb

  
1.安装mariadb

#这里直接yum,新版的mariadb要到官网去配置新版的mariadb更新源
yum install mariadb -y
systemctl start mairadb

2.授权账号

MariaDB [(none)]> grant all on wp.* to wp_admin@'192.168.99.%' identified by 'admin';

MariaDB [(none)]> flush privileges;

3.建立wordpress数据库

MariaDB [(none)]> create database wp;

未分类

配置NFS

1.安装nfs管理工具,并且启动

# centos7,centos6要先启动rpcbind服务才能启动nfs-server
yum install nfs-utils -y
systemctl start nfs-server

2.编辑配置文件

vim /etc/exports
#写入想要共享的目录和允许挂载的机器和读写权限
/web/wordpress 192.168.99.0/24(rw)

3.创建目录并且放入wordpress站点文件

mkdir /web
cd /web
wget https://cn.wordpress.org/wordpress-4.9.4-zh_CN.tar.gz
tar -xvf wordpress-4.9.4-zh_CN.tar.gz 

4.设置acl权限,要给父附录权限,否则在wordpress在上传文件会失败

#由于httpd是yum安装的,所以uid号相同,所以设置一次就好
setfacl -R -m u:48:rwx /web

5.将nfs目录分享出去

#重读exports文件
exportfs -r
#查看分享出去的目录和权限
exportfs -v

6.编辑wordpress的配置文件

cd wordpress
mv wp-config.sample.php wp-config.php
vim  wp-config.php

未分类

配置web服务器

2台web都按照如下配置

1.安装httpd和php-fmp

yum install httpd php-fpm php-mysql -y
#由于未做分离所以可以直接启动,无需修改,如果分离,要注意修改配置文件允许远程访问和监听全部ip
systemctl start php-fpm

2.配置httpd

#自定义配置文件名字,但是目录和结尾要固定
 vim /etc/httpd/conf.d/php-fpm.conf 

#写入如下字段
#将php也作为默认页
DirectoryIndex index.php
#关闭代理请求
ProxyRequests Off
#设置允许代理的请求,这里未做分离所以直接代理到httpd目录就好
ProxyPassMatch ^/(.*.php)$ fcgi://127.0.0.1:9000/var/www/html/$1

未分类

3.安装nfs管理工具

yum install nfs-utils -y
#查看nfs服务器共享的文件夹
showmount -e 192.168.99.135

未分类

4.挂载目录

#这里有2中方法
#法一,编译fstabl文件
vim /etc/fstab
192.168.99.135:/web/wordpress /var/www/html/ nfs defaults 0 0

mount -a

未分类

#法二,直接命令挂载
mount 192.168.99.135:/web/wordpress /var/www/html/

5.启动httpd

systemctl start httpd

配置宿主机

  
由于没有做dns所以要修改宿主机的hosts文件才能实现基于域名的访问

C:WindowsSystem32driversetchosts

#添入如下字段
192.168.99.150  www.douma.com

安装wordpress

1.宿主机在浏览器打开www.douma.com
填写好对应信息

未分类

2.安装wordpress等待完成

未分类

3.重新打开www.douma.com

未分类

部署完成。
  

总结

  
整个部署基本是按照问题解决后的配置来进行安装的,所以负载均衡可以正常实现。下面将会列出刚开始错误的点和配置。
  

注意点

nginx反向代理的配置

未分类

未分类

上面2幅图分别是正确和错误的配置,不过正常来说2个配置都没问题,但是在这里并不行;错误配置是将请求192.168.99.150的请求转发到后端的2台httpd机器上,但是在这里同时会将http://backend 也转发到后端,而wordpress会在安装过程中记录下这个站点名称,并且之后的请求会直接请求http://backend

wordpress数据库的记录

1.下面是wordpress记录到数据库的内容
这里我使用了phpmyadmin这个web端的mysql管理工具

未分类

图中方框内记录的便是wordpress记录的站点地址,同时也会是以后请求的地址。

2.下面是使用调试窗口看到的请求,正常情况下下次请求会重新去请求记录的站点地址,可以重新通过nginx实现负载均衡。如果请求的站点地址不是域名而是上面nginx错误配置或者是某个web节点的地址的话,负载均衡会完全失效。

未分类

MySQL状态变量Aborted_connects与Aborted_clients浅析

关于MySQL的状态变量Aborted_clients & Aborted_connects分别代表的意义,以及哪些情况或因素会导致这些状态变量变化呢?下文通过实验测试来验证一下,首先我们来看看状态变量的描述:

Aborted Connect

Aborted Connect表示尝试连接到MySQL服务器失败的次数。这个状态变量可以结合host_cache表和其错误日志一起来分析问题。 引起这个状态变量激增的原因如下:

  1. 客户端没有权限但是尝试访问MySQL数据库。

  2. 客户端输入的密码有误。

  3. A connection packet does not contain the right information.

  4. 超过连接时间限制,主要是这个系统变量connect_timeout控制(mysql默认是10s,基本上,除非网络环境极端不好,一般不会超时。)

官方解释如下:

If a client is unable even to connect, the server increments the Aborted_connects status variable. Unsuccessful connection attempts can occur for the following reasons:

  • A client attempts to access a database but has no privileges for it.

  • A client uses an incorrect password.

  • A connection packet does not contain the right information.

  • It takes more than connect_timeout seconds to obtain a connect packet. See Section 5.1.7, “Server System Variables”.

Aborted Clients

Aborted Clients表示由于客户端没有正确关闭连接而中止的连接数。官方解释如下:

The number of connections that were aborted because the client died without closing the connection properly. See Section B.5.2.10, “Communication Errors and Aborted Connections”

当Aborted Clients增大的时候意味着有客户端成功建立连接,但是由于某些原因断开连接或者被终止了,这种情况一般发生在网络不稳定的环境中。主要的可能性有:

  1. 客户端程序在退出之前未调用mysql_close()正确关闭MySQL连接。

  2. 客户端休眠的时间超过了系统变量wait_timeout和interactive_timeout的值,导致连接被MySQL进程终止

  3. 客户端程序在数据传输过程中突然结束

官方文档B.5.2.10 Communication Errors and Aborted Connections的介绍如下:

If a client successfully connects but later disconnects improperly or is terminated, the server increments the Aborted_clients status variable, and logs an Aborted connection message to the error log. The cause can be any of the following:

  • The client program did not call mysql_close() before exiting.

  • The client had been sleeping more than wait_timeout or interactive_timeout seconds without issuing any requests to the server. See Section 5.1.7, “Server System Variables”.

  • The client program ended abruptly in the middle of a data transfer.

Other reasons for problems with aborted connections or aborted clients:

  • The max_allowed_packet variable value is too small or queries require more memory than you have allocated for mysqld. See Section B.5.2.9, “Packet Too Large”.

Use of Ethernet protocol with Linux, both half and full duplex. Some Linux Ethernet drivers have this bug. You should test for this bug by transferring a huge file using FTP between the client and server machines. If a transfer goes in burst-pause-burst-pause mode, you are experiencing a Linux duplex syndrome. Switch the duplex mode for both your network card and hub/switch to either full duplex or to half duplex and test the results to determine the best setting.

  • A problem with the thread library that causes interrupts on reads.

  • Badly configured TCP/IP.

  • Faulty Ethernets, hubs, switches, cables, and so forth. This can be diagnosed properly only by replacing hardware.

如上介绍所示,有很多因素引起这些状态变量的值变化,那么我们来一个个分析、演示一下吧。首先,我们来测试一下导致Aborted Connect状态变量增加的可能因素

1、 客户端没有权限但是尝试访问MySQL数据库。

其实这里所说的没有权限,个人理解是:客户端使用没有授权的账号访问数据库 。打个比方,你尝试用账号kkk访问MySQL数据库,其实你也不知道数据库是否存在这个用户,实际上不存在这个用户。

实验对比测试前,先将状态变量清零。

mysql> flush status;
Query OK, 0 rows affected (0.01 sec)
mysql> show status like 'Abort%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Aborted_clients  | 0     |
| Aborted_connects | 0     |
+------------------+-------+
2 rows in set (0.01 sec)

mysql> 
mysql> select host,user from mysql.user;
+-------------------------------+-----------+
| host                          | user      |
+-------------------------------+-----------+
| %                             | mydba     |
| %                             | root      |
| %                             | test      |
| 127.0.0.1                     | root      |
| 192.168.%                     | mydbadmin |
| 192.168.103.18,192.168.103,22 | LimitIP   |
| ::1                           | root      |
| db-server.localdomain         | root      |
| localhost                     | backuser  |
| localhost                     | root      |
+-------------------------------+-----------+

在本机的SecureCRT的另外一个窗口,使用不存在的账号kkk访问MySQL后,你会发现状态变量Aborted_connects变为1了。

[root@DB-Server ~]# mysql -u kkk -p
Enter password:
ERROR 1045 (28000): Access denied for user 'kkk'@'localhost' (using password: YES)

未分类

也有可能,这个账号本身存在,但是只允许特定IP地址才能访问,实际环境中,可能是有人在进行尝试暴力破解。可能性非常多。我们来测试一下限制IP访问的情况

mysql> grant all on MyDB.* to mydbadmin@'10.20.%' identified by '123456';
Query OK, 0 rows affected (0.01 sec)

mysql> flush privileges;
Query OK, 0 rows affected (0.00 sec)

mysql>  show status like 'Abort%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Aborted_clients  | 0     |
| Aborted_connects | 0     |
+------------------+-------+
2 rows in set (0.00 sec)

如上所示,创建一个mydbadmin的行号,只允许10.20段的IP访问,然后我们从192.168段的IP访问MySQL数据库

# mysql -h 10.20.57.24 -u mydbadmin -p
Enter password:
ERROR 1045 (28000): Access denied for user 'mydbadmin'@'192.168.7.208' (using password: YES)

此时,状态变量Aborted_connects就变为1了。

未分类

2、 客户端输入的密码有误或者根本就是尝试各个密码。(A client uses an incorrect password)

如下所示,使用test账号访问MySQL数据,但是输入了一个错误密码

[root@DB-Server ~]# mysql -u test -p
Enter password:
ERROR 1045 (28000): Access denied for user 'test'@'localhost' (using password: YES)
[root@DB-Server ~]#

你检查状态变量Aborted_connects就会发现状态变量Aborted_connects变为2了。

mysql>  show status like 'Abort%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Aborted_clients  | 0     |
| Aborted_connects | 2     |
+------------------+-------+
2 rows in set (0.00 sec)

3: A connection packet does not contain the right information.

这个比较容易构造,可以对MySQL的端口进行端口测试(ping 端口),因为psping的包不包含正确的信息(right information),测试之前,先将状态变量清空。

mysql> flush status;
 Query OK, 0 rows affected (0.00 sec)
mysql> show status like 'abort%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Aborted_clients  | 0     |
| Aborted_connects | 0     |
+------------------+-------+
2 rows in set (0.00 sec)

在客户端对MySQL服务所在的主机进行端口连通性验证(psping)

未分类

如上所示,psping测试后,Aborted_connects变成了5,如果继续进行psping测试,那么这个状态变量就会继续增长。

未分类

另外,如果超过max_connect_error的限制后,某一个客户端持续访问MySQL,这个是否会引起状态变量Aborted_connects变化呢,实验测试的答案是不会。有兴趣的可以验证一下,很奇怪,网上有不少文章都说如果连接数满了,也会导致Aborted_connects状态变量增加,实际上这个是不会引起状态变量Aborted_connects变化的。

4、 超过连接时间限制,主要是这个参数connect_timeout控制(mysql默认是10s,基本上,除非网络环境极端不好,一般不会超时。)

首先在一台MySQL数据库服务器上执行下面命令,我们用Linux下的netem与tc命令模拟构造出复杂环境下的网络传输延时案例,延时11秒。

# tc qdisc add dev eth0 root netem delay 11000ms

在另外一台MySQL服务器ping这台MySLQ服务器,如下所示,你会看到网络时延为11秒

# ping 10.20.57.24
PING 10.20.57.24 (10.20.57.24) 56(84) bytes of data.
64 bytes from 10.20.57.24: icmp_seq=1 ttl=61 time=11001 ms
64 bytes from 10.20.57.24: icmp_seq=2 ttl=61 time=11001 ms
64 bytes from 10.20.57.24: icmp_seq=3 ttl=61 time=11001 ms
64 bytes from 10.20.57.24: icmp_seq=4 ttl=61 time=11001 ms
64 bytes from 10.20.57.24: icmp_seq=5 ttl=61 time=11001 ms

此时访问MySQL数据库,由于网络时延为11秒,超出了系统变量connect_timeout的10秒,就会出现下面错误,此时状态变量Aborted_connects的值变化!

# mysql -h 10.20.57.24 -u test -p
Enter password:
ERROR 2013 (HY000): Lost connection to MySQL server at 'reading authorization packet', system error: 0

那么如何区分状态变量Aborted Connect是那个引起的呢? 单从状态变量本身是无法区分的,但是可以结合performance_schema.host_cache来稍微做判别、甄别。

COUNT_NAMEINFO_PERMANENT_ERRORS IP到主机名称DNS解析期间的永久性错误数。
COUNT_AUTHENTICATION_ERRORS 验证失败导致的错误数量
SUM_CONNECT_ERRORS: 被视为“ 阻塞 ”的连接错误的数量 (根据max_connect_errors系统变量进行评估)。只有协议握手错误才会被计数,只有通过验证(HOST_VALIDATED = YES)的主机才会被计数

1、 客户端没有权限但是尝试访问MySQL数据库。

每次都会引起COUNT_AUTHENTICATION_ERRORS增1 ,第一次会引起COUNT_NAMEINFO_PERMANENT_ERRORS也增1

2、 客户端输入的密码有误

每次都会引起COUNT_AUTHENTICATION_ERRORS增1 ,第一次会引起COUNT_NAMEINFO_PERMANENT_ERRORS也增1

其实对于与1和2,两者无法判别,最简单有效的将系统变量log_warnings设置为2,然后分析、查看错误日志信息:

mysql> set global log_warnings=2;

Query OK, 0 rows affected (0.00 sec)



mysql> 

那么此时1和2都会记录到错误日志里面去,然后你就可以通过分析错误日志,结合状态变量Aborted Connect来分析, 如下测试案例所示:

2018-06-20 22:44:16 18026 [Warning] IP address '192.168.xxx.xxx' could not be resolved: Name or service not known
2018-06-20 22:44:16 18026 [Warning] Access denied for user 'kkkk'@'192.168.xxx.xxx' (using password: YES)
2018-06-20 22:45:18 18026 [Warning] Access denied for user 'test'@'192.168.xxx.xxx' (using password: YES)

3、 A connection packet does not contain the right information

每次引起COUNT_HANDSHAKE_ERRORS增1,
每次引起SUM_CONNECT_ERRORS增1

C:Users>psping 10.20.57.24:3306



PsPing v2.10 - PsPing - ping, latency, bandwidth measurement utility

Copyright (C) 2012-2016 Mark Russinovich

Sysinternals - www.sysinternals.com



TCP connect to 10.20.57.24:3306:

5 iterations (warmup 1) ping test:

Connecting to 10.20.57.24:3306 (warmup): from 192.168.103.34:55327: 1.93ms

Connecting to 10.20.57.24:3306: from 192.168.103.34:55328: 10.08ms

Connecting to 10.20.57.24:3306: from 192.168.103.34:55329: 3.35ms

Connecting to 10.20.57.24:3306: from 192.168.103.34:55330: 3.71ms

Connecting to 10.20.57.24:3306: from 192.168.103.34:55331: 2.32ms



TCP connect statistics for 10.20.57.24:3306:

  Sent = 4, Received = 4, Lost = 0 (0% loss),

  Minimum = 2.32ms, Maximum = 10.08ms, Average = 4.87ms

未分类

4、 超过连接时间限制

如果是超时引起,那么就会出现下面状况:

  • 每次引起SUM_CONNECT_ERRORS增1,
  • 每次引起COUNT_HANDSHAKE_ERRORS增1
  • 第一次会引起COUNT_NAMEINFO_PERMANENT_ERRORS增1

注意: 3与4不会写入错误日志,3与4的区别可以通过COUNT_NAMEINFO_PERMANENT_ERRORS的值来区别。

未分类

下面我们来实验测试一下状态变量Aborted Clients的变化因素,

1、 客户端程序在退出之前未调用mysql_close()正确关闭MySQL连接。

在实验前,使用flush status清理一下状态变量

mysql> flush status;
Query OK, 0 rows affected (0.00 sec)

mysql> show status like 'Abort%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Aborted_clients  | 0     |
| Aborted_connects | 0     |
+------------------+-------+
2 rows in set (0.00 sec)

mysql> 

写一个简单的Python测试脚本python_mysql.py,如下所示,将关闭数据库连接的地方dbcon.close注释掉,

import mysql.connector

try:

 dbcon=mysql.connector.connect(
 host='127.0.0.1',
 user='root' ,
 passwd='xxxxxxx',
 database='information_schema'
 )

 cursor= dbcon.cursor()
 sql_tex='select count(*) from MyDB.test'
 cursor.execute(sql_tex)
 dtlist= cursor.fetchall()
 print dtlist
except mysql.connector.Error as e:

  print('operation the sql fail!{0}'.format(e))

finally:

  cursor.close;
 # dbcon.close;

然后执行一下脚本,检查状态变量Aborted_clients,然后发现状态变量Aborted_clients的值增1了。

[root@DB-Server kerry]# python python_mysql.py
[(99999,)]

未分类

2、 客户端休眠的时间超过了系统变量wait_timeout和interactive_timeout的值,导致连接被MySQL进程终止

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

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

mysql> 

将全局系统变量interactive_timeout 和wait_timeout设置为4秒

mysql> set global interactive_timeout=4;
Query OK, 0 rows affected (0.00 sec)

mysql> set global wait_timeout=4;
Query OK, 0 rows affected (0.00 sec)

mysql> show status like 'Abort%';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Aborted_clients  | 0     |
| Aborted_connects | 0     |
+------------------+-------+
2 rows in set (0.00 sec)

然后在客户端连接到MySQL数据库,不做任何操作,过来4秒后,你去操作就会出现错误“ERROR 2013 (HY000): Lost connection to MySQL server during query”

# mysql -h 10.20.57.24 -u test -p
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or g.
Your MySQL connection id is 43
Server version: 5.6.20-enterprise-commercial-advanced-log MySQL Enterprise Server - Advanced Edition (Commercial)

Copyright (c) 2000, 2018, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

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

mysql> select current_user();
ERROR 2013 (HY000): Lost connection to MySQL server during query
mysql> 

在MySQL服务器你就会看到状态变量Aborted_clients变为1了。

mysql> show status like 'Abort%';

+------------------+-------+

| Variable_name    | Value |

+------------------+-------+

| Aborted_clients  | 1     |

| Aborted_connects | 0     |

+------------------+-------+

2 rows in set (0.00 sec

还有其他一些原因(客户端异常中断或查询超出了max_allowed_packet值)由于不方便构造,在此略过。另外,其实我们还可以通过tcpdump抓包工具来追踪分析。下面举个例子(这里

简单介绍一下tcpdump,后续文章再做展开分析)

在MySQL服务器使用tcpdump抓包

[root@DB-Server ~]# tcpdump -i eth0  port 3306  -s 1500 -w tcpdump.log

然后在另外一台MySQL服务器,使用不存在的账号或错误的密码访问MySQL数据库

# mysql -h 10.20.57.24 -u kkk  -p
Enter password:
ERROR 1045 (28000): Access denied for user 'kkk'@'192.168.7.208' (using password: YES)
# mysql -h 10.20.57.24 -u test -p
Enter password:
ERROR 1045 (28000): Access denied for user 'test'@'192.168.7.208' (using password: YES)
[root@GETLNX28 ~]#

执行完命令后,你可以使用CTRL + C结束抓包分析,然后查看分析。如下截图所示:

[root@DB-Server ~]# tcpdump -i eth0  port 3306  -s 1500 -w tcpdump.log
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 1500 bytes
28 packets captured
28 packets received by filter
0 packets dropped by kernel
[root@DB-Server ~]# strings tcpdump.log

未分类

未分类

分区规划及使用、LVM逻辑卷

分区规划

扇区的大小:默认 512 字节

• 识别硬盘 => 分区规划 => 格式化 => 挂载使用

一、分区规划

MBR分区模式 最大2TB空间

– 三种分区类型:主分区  扩展分区  逻辑分区

– 1~4个主分区,或者 3个主分区+1个扩展分区(n个逻辑分区)
– 最大支持容量为 2.2TB 的磁盘
– 扩展分区不能格式化
– 理论上:最多有4个主分区

/dev/sdc5 表示 scsi类型的磁盘,第三块,第一个逻辑分区

虚拟机Server

1.查看本机识别硬盘

[root@server0 ~]# lsblk

2.分区指令 fdisk

[root@server0 /]# fdisk /dev/vdb 
n 创建新的分区----->回车----->回车---->回车----->在last结束时 +2G
p 查看分区表
n 创建新的分区----->回车----->回车---->回车----->在last结束时 +2G
d 删除分区
w 保存并退出

[root@server0 ~]# lsblk 
[root@server0 ~]# ls /dev/vdb[1-2]

3.格式化文件系统

  • mkfs 工具集
  • mkfs.ext3 分区设备路径
  • mkfs.ext4 分区设备路径
  • mkfs.xfs 分区设备路径
  • mkfs.vfat -F 32 分区设备路径
[root@server0 ~]# blkid /dev/vdb1 #查看分区UUID及文件系统信息
[root@server0 ~]# mkfs.ext4 /dev/vdb1
[root@server0 ~]# blkid /dev/vdb1

[root@server0 ~]# blkid /dev/vdb2 #查看分区UUID及文件系统信息
[root@server0 ~]# mkfs.xfs /dev/vdb2
[root@server0 ~]# blkid /dev/vdb2

[root@server0 ~]# mkfs.xfs /dev/vdb2
mkfs.xfs: /dev/vdb2 appears to contain an existing filesystem (xfs).
mkfs.xfs: Use the -f option to force overwrite.
-f :强制格式化

4.挂载使用

[root@server0 ~]# mount /dev/vdb1 /mypart1
mount: 挂载点 /mypart1 不存在
[root@server0 ~]# mkdir /mypart1
[root@server0 ~]# mkdir /mypart2
[root@server0 ~]# mount /dev/vdb1 /mypart1
[root@server0 ~]# mount /dev/vdb2 /mypart2

[root@server0 ~]# df -h #查看所有 正在挂载使用 的分区的信息

################################################################

综合分区:

请划分 3个主分区,分别为2G
划分 2个逻辑分区,分别 为 1G

[root@server0 ~]# fdisk /dev/vdb
p 查看分区表
n 创建新的分区----->回车----->回车---->回车----->在last结束时 +2G
p 查看分区表
n 创建新的分区 
----->回车---->起始回车----->结束回车 将所有空间给扩展分区
p 查看分区表
n 创建新的分区----->起始回车------>结束+1G
n 创建新的分区----->起始回车------>结束+1G
d 删除分区
w 保存并退出

[root@server0 ~]# partprobe #刷新 新的分区
[root@server0 ~]# lsblk

[root@server0 ~]# ls /dev/vdb[1-6]

##################################################################

总结分区:

1.查看识别硬盘 lsblk
2.划分分区 fdisk
3.刷新新的分区 partprobe
4.格式化 mkfs.ext4
5.查看文件系统类型 blkid
6.挂载使用 mount
7.查看挂载使用 df -h
8.完成开机自动挂载 /etc/fstab
9.验证书写内容 mount -a

#################################################################

开机自动挂载分区

  • 配置文件 /etc/fstab 的记录格式
  • 设备路径 挂载点 类型 参数 备份标记 检测顺序

补充:vim 命令模式 按 o 另起一行进入 插入模式

[root@server0 ~]# blkid /dev/vdb1
[root@server0 ~]# vim /etc/fstab

/dev/vdb1 /mypart1 ext4 defaults 0 0

[root@server0 ~]# mount -a #检测/etc/fstab文件格式,并尝试挂载设备
[root@server0 ~]# df -h

################################################################

对于/dev/vdc进行综合分区:

划分3个主分区,分别为10G
划分2个逻辑分区,分别为10G

[root@server0 ~]# fdisk /dev/vdc
p 查看分区表
n 创建新的分区----->回车----->回车---->回车----->在last结束时 +10G #第一个主分区
p 查看分区表
.......
n 创建新的分区(扩展分区) 
----->回车---->起始回车----->结束回车 将所有空间给扩展分区
p 查看分区表
n 创建新的分区----->起始回车------>结束+10G #划分第一个逻辑分区
n 创建新的分区----->起始回车------>结束+10G #划分第二个逻辑分区
d 删除分区
w 保存并退出
[root@server0 ~]# lsblk #查看分区情况

################################################################

LVM逻辑卷

作用:    1. 可以整合分散的空间
         2. 逻辑卷可以容量扩大

将众多的物理卷(pv),组成卷组(vg),再从卷组中划分逻辑卷(lv),再去格式化逻辑卷,进行挂载

 面粉---------》大面团---------》小面团------》蒸--------》吃

 砖---------》大房子---------》打隔段---------》装修---------》入住

一、制作逻辑卷

1.创建卷组 : vgcreate 卷组的名字 组成卷组的成员

[root@server0 ~]# vgcreate myvg /dev/vdc1 /dev/vdc2

[root@server0 ~]# pvs #显示物理卷的信息
[root@server0 ~]# vgs #显示卷组的信息

2.创建逻辑卷 : lvcreate -n 逻辑卷的名字 -L 大小 卷组的名字

[root@server0 ~]# lvcreate -n vo -L 16G myvg

[root@server0 ~]# lvs #显示逻辑卷的信息
[root@server0 ~]# vgs #显示卷组的信息

3.使用逻辑卷

[root@server0 ~]# mkfs.ext4 /dev/myvg/vo 
[root@server0 ~]# blkid /dev/myvg/vo
[root@server0 ~]# mkdir /lvm

[root@server0 ~]# vim /etc/fstab

/dev/myvg/vo /lvm ext4 defaults 0 0

[root@server0 ~]# mount -a 
[root@server0 ~]# df -h

##############################################################

逻辑卷的扩大,支持线上操作

一、卷组有足够的剩余空间

1.空间的扩展

[root@server0 ~]# vgs
[root@server0 ~]# lvextend -L 18G /dev/myvg/vo 
[root@server0 ~]# lvs

2.文件系统的扩展

resize2fs : ext4文件系统扩展命令
xfs_growfs : xfs文件系统扩展命令

[root@server0 ~]# df -h
[root@server0 ~]# resize2fs /dev/myvg/vo 
[root@server0 ~]# df -h

二、卷组没有足够的剩余空间

1.扩展卷组空间

[root@server0 ~]# vgextend myvg /dev/vdc3
[root@server0 ~]# vgs

2.空间的扩展

[root@server0 ~]# vgs
[root@server0 ~]# lvextend -L 25G /dev/myvg/vo 
[root@server0 ~]# lvs

3.文件系统的扩展
resize2fs : ext4文件系统扩展命令
xfs_growfs : xfs文件系统扩展命令

[root@server0 ~]# df -h
[root@server0 ~]# resize2fs /dev/myvg/vo 
[root@server0 ~]# df -h

##################################################################

了解:逻辑卷的缩小,有风险

1.先缩减文件系统的大小,在缩减空间的大小

[root@server0 ~]# resize2fs /dev/myvg/vo 10G

[root@server0 ~]# umount /lvm/
[root@server0 ~]# resize2fs /dev/myvg/vo 10G

[root@server0 ~]# e2fsck -f /dev/myvg/vo
[root@server0 ~]# resize2fs /dev/myvg/vo 10G

[root@server0 ~]# lvreduce -L 10G /dev/myvg/vo 
WARNING: Reducing active logical volume to 10.00 GiB
THIS MAY DESTROY YOUR DATA (filesystem etc.)
Do you really want to reduce vo? [y/n]: y

[root@server0 ~]# df -h

ext4文件系统可以缩减

xfs文件系统不支持缩减

##################################################################

PE:卷组划分空间的单位

  • 创建卷组的时候设置PE大小
    • vgcreate -s PE大小 卷组名 空闲分区…
    • vgchange -s PE大小 卷组名
  • 创建逻辑卷的时候指定PE个数
    • lvcreate -l PE个数 -n 逻辑卷名 卷组名
[root@server0 ~]# vgdisplay myvg
[root@server0 ~]# vgchange -s 1M myvg

请划分一个逻辑卷名字lvtest01,大小为50个PE的总和

[root@server0 ~]# lvcreate -l 80 -n lvtest01 myvg

[root@server0 ~]# lvs

###################################################################

逻辑卷的删除

[root@server0 ~]# lvremove /dev/myvg/vo 
Logical volume myvg/vo contains a filesystem in use.

[root@server0 ~]# umount /lvm
[root@server0 ~]# lvremove /dev/myvg/vo 
Do you really want to remove active logical volume vo? [y/n]:y

[root@server0 ~]# lvs

[root@server0 ~]# vgremove myvg
[root@server0 ~]# vgs

[root@server0 ~]# pvremove /dev/vdc[1-3]
[root@server0 ~]# pvs

###############################################################

MySQL索引优化分析

为什么你写的sql查询慢?为什么你建的索引常失效?通过本章内容,你将学会MySQL性能下降的原因,索引的简介,索引创建的原则,explain命令的使用,以及explain输出字段的意义。助你了解索引,分析索引,使用索引,从而写出更高性能的sql语句。还在等啥子?撸起袖子就是干!

案例分析

我们先简单了解一下非关系型数据库和关系型数据库的区别。
MongoDB是NoSQL中的一种。NoSQL的全称是Not only SQL,非关系型数据库。它的特点是性能高,扩张性强,模式灵活,在高并发场景表现得尤为突出。但目前它还只是关系型数据库的补充,它在数据的一致性,数据的安全性,查询的复杂性问题上和关系型数据库还存在一定差距。
MySQL是关系性数据库中的一种,查询功能强,数据一致性高,数据安全性高,支持二级索引。但性能方面稍逊与MongoDB,特别是百万级别以上的数据,很容易出现查询慢的现象。这时候需要分析查询慢的原因,一般情况下是程序员sql写的烂,或者是没有键索引,或者是索引失效等原因导致的。
公司ERP系统数据库主要是MongoDB(最接近关系型数据的NoSQL),其次是Redis,MySQL只占很少的部分。现在又重新使用MySQL,归功于阿里巴巴的奇门系统和聚石塔系统。考虑到订单数量已经是百万级以上,对MySQL的性能分析也就显得格外重要。

我们先通过两个简单的例子来入门。后面会详细介绍各个参数的作用和意义。
说明:需要用到的sql已经放在了github上了,喜欢的同学可以点一下star,哈哈。https://github.com/ITDragonBlog/daydayup/tree/master/MySQL/

场景一:订单导入,通过交易号避免重复导单

业务逻辑:订单导入时,为了避免重复导单,一般会通过交易号去数据库中查询,判断该订单是否已经存在。

最基础的sql语句

mysql> select * from itdragon_order_list where transaction_id = "81X97310V32236260E";
+-------+--------------------+-------+------+----------+--------------+----------+------------------+-------------+-------------+------------+---------------------+
| id    | transaction_id     | gross | net  | stock_id | order_status | descript | finance_descript | create_type | order_level | input_user | input_date          |
+-------+--------------------+-------+------+----------+--------------+----------+------------------+-------------+-------------+------------+---------------------+
| 10000 | 81X97310V32236260E |   6.6 | 6.13 |        1 |           10 | ok       | ok               | auto        |           1 | itdragon   | 2017-08-18 17:01:49 |
+-------+--------------------+-------+------+----------+--------------+----------+------------------+-------------+-------------+------------+---------------------+

mysql> explain select * from itdragon_order_list where transaction_id = "81X97310V32236260E";
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table               | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | itdragon_order_list | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |    33.33 | Using where |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+-------------+

查询的本身没有任何问题,在线下的测试环境也没有任何问题。可是,功能一旦上线,查询慢的问题就迎面而来。几百上千万的订单,用全表扫描?啊?哼!
怎么知道该sql是全表扫描呢?通过explain命令可以清楚MySQL是如何处理sql语句的。打印的内容分别表示:
id : 查询序列号为1。
select_type : 查询类型是简单查询,简单的select语句没有union和子查询。
table : 表是 itdragon_order_list。
partitions : 没有分区。
type : 连接类型,all表示采用全表扫描的方式。
possible_keys : 可能用到索引为null。
key: 实际用到索引是null。
key_len : 索引长度当然也是null。
ref: 没有哪个列或者参数和key一起被使用。
Extra: 使用了where查询。
因为数据库中只有三条数据,所以rows和filtered的信息作用不大。这里需要重点了解的是type为ALL,全表扫描的性能是最差的,假设数据库中有几百万条数据,在没有索引的帮助下会异常卡顿。

初步优化:为transaction_id创建索引

mysql> create unique index idx_order_transaID on itdragon_order_list (transaction_id);
mysql> explain select * from itdragon_order_list where transaction_id = "81X97310V32236260E";
+----+-------------+---------------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-------+
| id | select_type | table               | partitions | type  | possible_keys      | key                | key_len | ref   | rows | filtered | Extra |
+----+-------------+---------------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | itdragon_order_list | NULL       | const | idx_order_transaID | idx_order_transaID | 453     | const |    1 |      100 | NULL  |
+----+-------------+---------------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-------+

这里创建的索引是唯一索引,而非普通索引。
唯一索引打印的type值是const。表示通过索引一次就可以找到。即找到值就结束扫描返回查询结果。
普通索引打印的type值是ref。表示非唯一性索引扫描。找到值还要继续扫描,直到将索引文件扫描完为止。(这里没有贴出代码)
显而易见,const的性能要远高于ref。并且根据业务逻辑来判断,创建唯一索引是合情合理的。

再次优化:覆盖索引

mysql> explain select transaction_id from itdragon_order_list where transaction_id = "81X97310V32236260E";
+----+-------------+---------------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-------------+
| id | select_type | table               | partitions | type  | possible_keys      | key                | key_len | ref   | rows | filtered | Extra       |
+----+-------------+---------------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | itdragon_order_list | NULL       | const | idx_order_transaID | idx_order_transaID | 453     | const |    1 |      100 | Using index |
+----+-------------+---------------------+------------+-------+--------------------+--------------------+---------+-------+------+----------+-------------+

这里将select * from 改为了 select transaction_id from 后
Extra 显示 Using index,表示该查询使用了覆盖索引,这是一个非常好的消息,说明该sql语句的性能很好。若提示的是Using filesort(使用内部排序)和Using temporary(使用临时表)则表明该sql需要立即优化了。
根据业务逻辑来的,查询结构返回transaction_id 是可以满足业务逻辑要求的。

场景二,订单管理页面,通过订单级别和订单录入时间排序

业务逻辑:优先处理订单级别高,录入时间长的订单。
既然是排序,首先想到的应该是order by, 还有一个可怕的 Using filesort 等着你。

最基础的sql语句

mysql> explain select * from itdragon_order_list order by order_level,input_date;
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table               | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | SIMPLE      | itdragon_order_list | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |      100 | Using filesort |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+----------------+

首先,采用全表扫描就不合理,还使用了文件排序Using filesort,更加拖慢了性能。
MySQL在4.1版本之前文件排序是采用双路排序的算法,由于两次扫描磁盘,I/O耗时太长。后优化成单路排序算法。其本质就是用空间换时间,但如果数据量太大,buffer的空间不足,会导致多次I/O的情况。其效果反而更差。与其找运维同事修改MySQL配置,还不如自己乖乖地建索引。

初步优化:为order_level,input_date 创建复合索引

mysql> create index idx_order_levelDate on itdragon_order_list (order_level,input_date);
mysql> explain select * from itdragon_order_list order by order_level,input_date;
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table               | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra          |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+----------------+
|  1 | SIMPLE      | itdragon_order_list | NULL       | ALL  | NULL          | NULL | NULL    | NULL |    3 |      100 | Using filesort |
+----+-------------+---------------------+------------+------+---------------+------+---------+------+------+----------+----------------+

创建复合索引后你会惊奇的发现,和没创建索引一样???都是全表扫描,都用到了文件排序。是索引失效?还是索引创建失败?我们试着看看下面打印情况

mysql> explain select order_level,input_date from itdragon_order_list order by order_level,input_date;
+----+-------------+---------------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
| id | select_type | table               | partitions | type  | possible_keys | key                 | key_len | ref  | rows | filtered | Extra       |
+----+-------------+---------------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | itdragon_order_list | NULL       | index | NULL          | idx_order_levelDate | 68      | NULL |    3 |      100 | Using index |
+----+-------------+---------------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------------+

将select * from 换成了 select order_level,input_date from 后。type从all升级为index,表示(full index scan)全索引文件扫描,Extra也显示使用了覆盖索引。可是不对啊!!!!检索虽然快了,但返回的内容只有order_level和input_date 两个字段,让业务同事怎么用?难道把每个字段都建一个复合索引?
MySQL没有这么笨,可以使用force index 强制指定索引。在原来的sql语句上修改 force index(idx_order_levelDate) 即可。

mysql> explain select * from itdragon_order_list force index(idx_order_levelDate) order by order_level,input_date;
+----+-------------+---------------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------+
| id | select_type | table               | partitions | type  | possible_keys | key                 | key_len | ref  | rows | filtered | Extra |
+----+-------------+---------------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------+
|  1 | SIMPLE      | itdragon_order_list | NULL       | index | NULL          | idx_order_levelDate | 68      | NULL |    3 |      100 | NULL  |
+----+-------------+---------------------+------------+-------+---------------+---------------------+---------+------+------+----------+-------+

再次优化:订单级别真的要排序么?
其实给订单级别排序意义并不大,给订单级别添加索引意义也不大。因为order_level的值可能只有,低,中,高,加急,这四种。对于这种重复且分布平均的字段,排序和加索引的作用不大。
我们能否先固定 order_level 的值,然后再给 input_date 排序?如果查询效果明显,是可以推荐业务同事使用该查询方式。

mysql> explain select * from itdragon_order_list where order_level=3 order by input_date;
+----+-------------+---------------------+------------+------+---------------------+---------------------+---------+-------+------+----------+-----------------------+
| id | select_type | table               | partitions | type | possible_keys       | key                 | key_len | ref   | rows | filtered | Extra                 |
+----+-------------+---------------------+------------+------+---------------------+---------------------+---------+-------+------+----------+-----------------------+
|  1 | SIMPLE      | itdragon_order_list | NULL       | ref  | idx_order_levelDate | idx_order_levelDate | 5       | const |    1 |      100 | Using index condition |
+----+-------------+---------------------+------------+------+---------------------+---------------------+---------+-------+------+----------+-----------------------+

和之前的sql比起来,type从index 升级为 ref(非唯一性索引扫描)。索引的长度从68变成了5,说明只用了一个索引。ref也是一个常量。Extra 为Using index condition 表示自动根据临界值,选择索引扫描还是全表扫描。总的来说性能远胜于之前的sql。

上面两个案例只是快速入门,我们需严记一点:优化是基于业务逻辑来的。绝对不能为了优化而擅自修改业务逻辑。如果能修改当然是最好的。

索引简介

官方定义:索引(Index) 是帮助MySQL高效获取数据的数据结构。
大家一定很好奇,索引为什么是一种数据结构,它又是怎么提高查询的速度?我们拿最常用的二叉树来分析索引的工作原理。看下面的图片:
未分类
创建索引的优势
1 提高数据的检索速度,降低数据库IO成本:使用索引的意义就是通过缩小表中需要查询的记录的数目从而加快搜索的速度。
2 降低数据排序的成本,降低CPU消耗:索引之所以查的快,是因为先将数据排好序,若该字段正好需要排序,则真好降低了排序的成本。

创建索引的劣势
1 占用存储空间:索引实际上也是一张表,记录了主键与索引字段,一般以索引文件的形式存储在磁盘上。
2 降低更新表的速度:表的数据发生了变化,对应的索引也需要一起变更,从而减低的更新速度。否则索引指向的物理数据可能不对,这也是索引失效的原因之一。
3 优质索引创建难:索引的创建并非一日之功,也并非一直不变。需要频繁根据用户的行为和具体的业务逻辑去创建最佳的索引。

索引分类

我们常说的索引一般指的是BTree(多路搜索树)结构组织的索引。其中还有聚合索引,次要索引,复合索引,前缀索引,唯一索引,统称索引,当然除了B+树外,还有哈希索引(hash index)等。

单值索引:一个索引只包含单个列,一个表可以有多个单列索引
唯一索引:索引列的值必须唯一,但允许有空值
复合索引:一个索引包含多个列,实际开发中推荐使用
实际开发中推荐使用复合索引,并且单表创建的索引个数建议不要超过五个

基本语法:
创建:

create [unique] index indexName on tableName (columnName...)
alter tableName add [unique] index [indexName] on (columnName...)

删除:

drop index [indexName] on tableName

查看:

show index from tableName

哪些情况需要建索引:
1 主键,唯一索引
2 经常用作查询条件的字段需要创建索引
3 经常需要排序、分组和统计的字段需要建立索引
4 查询中与其他表关联的字段,外键关系建立索引

哪些情况不要建索引:
1 表的记录太少,百万级以下的数据不需要创建索引
2 经常增删改的表不需要创建索引
3 数据重复且分布平均的字段不需要创建索引,如 true,false 之类。
4 频发更新的字段不适合创建索引
5 where条件里用不到的字段不需要创建索引

性能分析

MySQL 自身瓶颈

MySQL自身参见的性能问题有磁盘空间不足,磁盘I/O太大,服务器硬件性能低。
1 CPU:CPU 在饱和的时候一般发生在数据装入内存或从磁盘上读取数据时候
2 IO:磁盘I/O 瓶颈发生在装入数据远大于内存容量的时候
3 服务器硬件的性能瓶颈:top,free,iostat 和 vmstat来查看系统的性能状态

explain 分析sql语句

使用explain关键字可以模拟优化器执行sql查询语句,从而得知MySQL 是如何处理sql语句。

+----+-------------+-------+------------+------+---------------+-----+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+-----+---------+------+------+----------+-------+

id

select 查询的序列号,包含一组可以重复的数字,表示查询中执行sql语句的顺序。一般有三种情况:
第一种:id全部相同,sql的执行顺序是由上至下;
第二种:id全部不同,sql的执行顺序是根据id大的优先执行;
第三种:id既存在相同,又存在不同的。先根据id大的优先执行,再根据相同id从上至下的执行。

select_type

select 查询的类型,主要是用于区别普通查询,联合查询,嵌套的复杂查询
simple:简单的select 查询,查询中不包含子查询或者union
primary:查询中若包含任何复杂的子查询,最外层查询则被标记为primary
subquery:在select或where 列表中包含了子查询
derived:在from列表中包含的子查询被标记为derived(衍生)MySQL会递归执行这些子查询,把结果放在临时表里。
union:若第二个select出现在union之后,则被标记为union,若union包含在from子句的子查询中,外层select将被标记为:derived
union result:从union表获取结果的select

partitions

表所使用的分区,如果要统计十年公司订单的金额,可以把数据分为十个区,每一年代表一个区。这样可以大大的提高查询效率。

type

这是一个非常重要的参数,连接类型,常见的有:all , index , range , ref , eq_ref , const , system , null 八个级别。
性能从最优到最差的排序:system > const > eq_ref > ref > range > index > all
对java程序员来说,若保证查询至少达到range级别或者最好能达到ref则算是一个优秀而又负责的程序员。
all:(full table scan)全表扫描无疑是最差,若是百万千万级数据量,全表扫描会非常慢。
index:(full index scan)全索引文件扫描比all好很多,毕竟从索引树中找数据,比从全表中找数据要快。
range:只检索给定范围的行,使用索引来匹配行。范围缩小了,当然比全表扫描和全索引文件扫描要快。sql语句中一般会有between,in,>,< 等查询。
ref:非唯一性索引扫描,本质上也是一种索引访问,返回所有匹配某个单独值的行。比如查询公司所有属于研发团队的同事,匹配的结果是多个并非唯一值。
eq_ref:唯一性索引扫描,对于每个索引键,表中有一条记录与之匹配。比如查询公司的CEO,匹配的结果只可能是一条记录,
const:表示通过索引一次就可以找到,const用于比较primary key 或者unique索引。因为只匹配一行数据,所以很快,若将主键至于where列表中,MySQL就能将该查询转换为一个常量。
system:表只有一条记录(等于系统表),这是const类型的特列,平时不会出现,了解即可

possible_keys

显示查询语句可能用到的索引(一个或多个或为null),不一定被查询实际使用。仅供参考使用。

key

显示查询语句实际使用的索引。若为null,则表示没有使用索引。

key_len

显示索引中使用的字节数,可通过key_len计算查询中使用的索引长度。在不损失精确性的情况下索引长度越短越好。key_len 显示的值为索引字段的最可能长度,并非实际使用长度,即key_len是根据表定义计算而得,并不是通过表内检索出的。

ref

显示索引的哪一列或常量被用于查找索引列上的值。

rows

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数,值越大越不好。

extra

Using filesort: 说明MySQL会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。MySQL中无法利用索引完成的排序操作称为“文件排序” 。出现这个就要立刻优化sql。
Using temporary: 使用了临时表保存中间结果,MySQL在对查询结果排序时使用临时表。常见于排序 order by 和 分组查询 group by。 出现这个更要立刻优化sql。
Using index: 表示相应的select 操作中使用了覆盖索引(Covering index),避免访问了表的数据行,效果不错!如果同时出现Using where,表明索引被用来执行索引键值的查找。如果没有同时出现Using where,表示索引用来读取数据而非执行查找动作。
覆盖索引(Covering Index) :也叫索引覆盖,就是select 的数据列只用从索引中就能够取得,不必读取数据行,MySQL可以利用索引返回select 列表中的字段,而不必根据索引再次读取数据文件。
Using index condition: 在5.6版本后加入的新特性,优化器会在索引存在的情况下,通过符合RANGE范围的条数 和 总数的比例来选择是使用索引还是进行全表遍历。
Using where: 表明使用了where 过滤
Using join buffer: 表明使用了连接缓存
impossible where: where 语句的值总是false,不可用,不能用来获取任何元素
distinct: 优化distinct操作,在找到第一匹配的元组后即停止找同样值的动作。

filtered

一个百分比的值,和rows 列的值一起使用,可以估计出查询执行计划(QEP)中的前一个表的结果集,从而确定join操作的循环次数。小表驱动大表,减轻连接的次数。

通过explain的参数介绍,我们可以得知:
1 表的读取顺序(id)
2 数据读取操作的操作类型(type)
3 哪些索引被实际使用(key)
4 表之间的引用(ref)
5 每张表有多少行被优化器查询(rows)

性能下降的原因

从程序员的角度
1 查询语句写的不好
2 没建索引,索引建的不合理或索引失效
3 关联查询有太多的join
从服务器的角度
1 服务器磁盘空间不足
2 服务器调优配置参数设置不合理

总结

1 索引是排好序且快速查找的数据结构。其目的是为了提高查询的效率。
2 创建索引后,查询数据变快,但更新数据变慢。
3 性能下降的原因很可能是索引失效导致。
4 索引创建的原则,经常查询的字段适合创建索引,频繁需要更新的数据不适合创建索引。
5 索引字段频繁更新,或者表数据物理删除容易造成索引失效。
6 擅用 explain 分析sql语句
7 除了优化sql语句外,还可以优化表的设计。如尽量做成单表查询,减少表之间的关联。设计归档表等。

到这里,MySQL的索引优化分析就结束了,有什么不对的地方,大家可以提出来。如果觉得不错可以点一下推荐。