安装saltstack-web管理界面

1、安装salt-master、salt-minion和salt-api

$ sudo yum install epel-release -y

$ sudo yum install salt-master salt-minion salt-api -y


# 配置教程请看 文章 。
# 启动并设置开机启动服务

$ sudo systemctl  start salt-master
$ sudo systemctl  enable salt-master

$ sudo systemctl  start salt-minion
$ sudo systemctl  enable salt-minion

$ sudo systemctl  start salt-api
$ sudo systemctl  enable salt-api

2、安装halite及其依赖文件

$ sudo yum install python-pip -y && sudo pip install --upgrade pip 
$ sudo pip install -U halite
$ sudo pip install cherrypy
$ sudo pip install paste

$ sudo yum install python-devel gcc -y 
$ sudo pip install gevent 
$ sudo pip install pyopenssl

3、在配置文件master

$ sudo vim /etc/salt/master


external_auth:
  pam:
    testuser:            //此用户设置为系统在用的用户
      - .*
      - '@runner'

halite:
  level: 'debug'
  server: 'cherrypy'
  host: '0.0.0.0'
  port: '8080'
  cors: False
  tls: True
  certpath: '/etc/pki/tls/certs/localhost.crt'
  keypath: '/etc/pki/tls/certs/localhost.key'
  pempath: '/etc/pki/tls/certs/localhost.pem'

4、运行命令

$ sudo salt-call tls.create_self_signed_cert tls

5、启动并设置自启动服务

$ sudo systemctl restart salt-master
$ sudo systemctl restart salt-minion
$ sudo systemctl restart salt-api

6、网页打开访问地址

https://IP:8080

未分类

rsync软件服务利用ansible实现一键化部署

首先创建一个脚本文件 /server/tools/peizhi.sh

cat  /server/tools/peizhi.sh
cat >>/etc/rsyncd.conf<<EOF
#luo
##paichu.sh##

uid = rsync
gid = rsync
use chroot = no
max connections = 200
timeout = 300
pid file = /var/run/rsyncd.pid
lock file = /var/run/rsync.lock
log file = /var/log/rsyncd.log
ignore errors
read only = false
list = false
hosts allow = 172.16.1.0/24
hosts deny = 0.0.0.0/32
auth users = rsync_backup
secrets file = /etc/rsync.password
[backup]
comment = "backup dir by oldboy"
path = /backup
EOF

一键化剧本

[root@m01 tools]# cat rsyncpiliang.yml 
- hosts: 172.16.1.141 #服务端
tasks:
- name: yum 
shell: yum install -y rsync #安装rsync
- name: yunm
shell: yum -y install sshpass #安装密钥的软件
- name: chuangjianmulu
shell: mkdir -p /server/tools/ #创建所在配置的文件
- name: peizhi
copy: src=/server/tools/peizhi.sh dest=/server/tools/peizhi.sh
- name: yunxing 
script: /server/tools/peizhi.sh #运行脚本
- name: guanliyunhu
shell: useradd -s /sbin/nologin -M rsync #创建rsync虚拟用户
- name: anquanwenjian
shell: echo "rsync_backup:oldboy123" >/etc/rsync.password #设置密码文件
- name: quanxian
shell: chmod 600 /etc/rsync.password #给予权限
- name: beifenmulu
shell: mkdir -p /backup && chown -R rsync.rsync /backup
- name: qidong
shell: rsync --daemon #运行rsync
- hosts: 172.16.1.108
tasks:
- name: yum
shell: yum install -y rsync # 客户端
- name: chuangjianmulu
shell: mkdir -p /server/tools/
- name: mimawenjian
shell: echo "oldboy123" >/etc/rsync.password && chmod 600 /etc/rsync.password
- hosts: 172.16.1.131
tasks:
- name: yum
shell: yum install -y rsync #客户端 
- name: chuangjianmulu
shell: mkdir -p /server/tools/
- name: mimawenjian
shell: echo "oldboy123" >/etc/rsync.password && chmod 600 /etc/rsync.password

PHP性能调优,PHP慢日志—善用php-fpm的慢执行日志slow log,分析php性能问题

众所周知,MySQL有slow query log,根据慢查询日志,我们可以知道那些sql语句有性能问题。作为mysql的好搭档,php也有这样的功能。如果你使用php-fpm来管理php的话,你可以通过如下选项开启。
PHP 5.3.3 之前设置如下:

<value name="request_slowlog_timeout">5s</value>
<value name="slowlog">logs/php-fpm-slowlog.log</value>

PHP 5.3.3 之后设置以下如下:

request_slowlog_timeout = 5s
slowlog = /usr/local/php/log/php-fpm-slowlog.log

说明:

request_slowlog_timeout 是脚本超过多长时间 就可以记录到日志文件
slowlog 是日志文件的路径

开启后,如果有脚本执行超过指定的时间,就会在指定的日志文件中写入类似如下的信息:

[19-Dec-2013 16:54:49] [pool www] pid 18575
script_filename = /home/web/htdocs/sandbox_canglong/test/tt.php
[0x0000000003a00dc8] curl_exec() /home/web/htdocs/sandbox_canglong/test/tt.php:2
[0x0000000003a00cd0] exfilter_curl_get() /home/web/htdocs/sandbox_canglong/test/tt.php:6

日志说明:

  • script_filename 是入口文件
  • curl_exec() : 说明是执行这个方法的时候超过执行时间的。
  • exfilter_curl_get() :说明调用curl_exec()的方法是exfilter_curl_get() 。
  • 每行冒号后面的数字是行号。

开启后,在错误日志文件中也有相关记录。如下:

[19-Dec-2013 15:55:37] WARNING: [pool www] child 18575, script '/home/web/htdocs/sandbox_canglong/test/tt.php' (request: "GET /test/tt.php") executing too slow (1.006222 sec), logging
[19-Dec-2013 15:55:37] NOTICE: child 18575 stopped for tracing
[19-Dec-2013 15:55:37] NOTICE: about to trace 18575
[19-Dec-2013 15:55:37] NOTICE: finished trace of 18575

升级PHP 7.1并启用PHP-FPM

一个多月前把系统升级到了Debian Buster,同时Apache也升级到了2.4.27,然后就发现HTTP/2没法用了,因为那时忙别的事情,也就没去管它。

今天闲着无事,就翻了翻文档,才发现在Apache 2.4.27中,Apache MPM (Multi-Processing Module) prefork取消了对HTTP/2的支持。因此,准备随即切换到Apache MPM event。

但随之而来的一个新问题就是,Apache服务器下,PHP所使用的mod_php模块只能支持prefork,因此,同时要将PHP切换到php-fpm以在FastCGI模式下运行PHP。然后想想,不如同时把PHP升级到7.1版本算了。

首先要做的,就是卸载PHP 7.0,虽然直接安装也行,但我还是选择了卸载,反正留着也没用。如果有安装phpMyAdmin,因为依赖被卸载,所以也会连带被卸载,这里直接选择卸载重装。

apt-get purge phpmyadmin php7.0-gd php7.0-xml php7.0 php-pear php7.0-mysql php7.0-common
apt-get autoremove

然后重新安装PHP 7.1,可以一步安装完所有包,也可以分开安装。这里为了写得清楚一点,分开写。但需要注意的是,如果一步安装PHP 7.1基本包的同时安装php7.1-fpm,默认就不会安装包含了mod_php模块的libapache2-mod-php7.1,如果你要用mod_php,要么分开两步装,要么别装php7.1-fpm。

首先安装LAMP需求的包。

apt-get install php7.1 php-pear php7.1-mysql

然后是WordPress需求的包。

apt-get install php7.1-gd php7.1-xml

然后是php-fpm。

apt-get install php7.1-fpm

最后装回phpMyAdmin,需要注意的是,phpMyAdmin默认安装的需求包是php7.0-mbstring,在PHP 7.1下会提示缺少mbstring,这个问题困扰了好久!最后发现,直接选择安装php7.1-mbstring就解决了!

apt-get install phpmyadmin php7.1-mbstring

安装完成之后重新配置以下phpMyAdmin就行。可以参考这里:https://www.prinice.org/2016/06/29/33/

之后就是启用PHP-FPM了。

首先关闭Apache服务器。

service apache2 stop

再执行以下命令。

a2enmod proxy_fcgi setenvif
a2enconf php7.1-fpm
a2dismod php7.1
a2dismod mpm_prefork
a2enmod mpm_event

最后重新启动Apache,这样就大功告成了,很简单。然后测试一下,我遇到了些问题,最后发现是自己写的.htaccess里面有一些PHP语句需要更新,这方面注意一下。

nginx使用DDNS域名作为反向代理的upstream

0x01 前言

我配置所有的服务都喜欢在前面套一个nginx,然后加上SSL以保安全。在这过程中有一种情况比较特殊,就是upstream使用DDNS进行解析。

正好前两天发现有位V友也问了这个问题,在解答之后我将这个问题记录在这里,V站链接如下:

https://www.v2ex.com/t/387350#reply15

这种使用方式也挺常见的,例如后端放在家里或其他没有固定IP的网络中,又需要将这个服务暴露在公网,可是不希望每次访问都输入端口号,这时候就需要反向代理。

0x02 BUG

nginx的反向代理需要配置一个upstream,而upstream支持众多协议,这里以http协议为例:

[root@web conf.d]# cat zabbix.t.com.ngx.conf 
server {

    listen                  80;
    server_name             zabbix.t.com;

    root                    /usr/local/services_data/html/zabbix.t.com/public_html/;

    access_log              /usr/local/services_data/html/zabbix.t.com/logs/ngx_access.log;

    location / {

        index               index.php index.html;
        proxy_pass          http://127.0.0.1:8080;

    }

}

以上是我家zabbix作为后端,nginx作为前端的配置文件,其中proxy_pass使用http协议访问127.0.0.1:8080这个后端地址。

以上配置一般不会出现问题,如果后端的IP地址出现了变更,那么就需要手动修改nginx的配置文件并reload。

如果proxy_pass使用了域名,如下:

proxy_pass http://zabbix-upstream.t.com:8080;

而且这个域名使用DDNS动态解析的方式,就会有一个问题:

nginx只会在nignx启动的时候解析域名并缓存,如果域名的解析变了,nignx也不会有任何动作。

这样会导致这个服务再也无法访问。

0x03 DEBUG

解决方法很简单,只需要将proxy_pass部分做一些改造:

server { 
... 
resolver [DNS IP 地址] valid=5s; 
set $[变量名] "http://ddns.domain.com:4430"; 
proxy_pass $[变量名]; 
... 
}

例如:

server { 
... 
resolver 114.114.114.114 valid=5s; 
set $upstream "http://ddns.domain.com:4430"; 
proxy_pass $upstream; 
... 
}

这里用了set变量这种方式,使用set这种指定变量的方式有一个特点:每次访问都会出发 {} 符号内的set。

这也就是说,每次访问都对运行一遍set。

而上面的proxy_pass配置会强制每一次访问都进行解析,同时使用resolver参数指定DNS和TTL。

resolver参数所指定的TTL优先级最高,可以覆盖DNS的TTL。如果DNS默认的TTL为60s,那么上面的配置会将TTL修改为5s。

这里还有个需要注意的地方,如果不是必要,不要再server段设定set,这在大流量的情况下会引发性能下降,如果可以,将set放置在location段会更好。

0x04 结语

问题解决,如果不是使用DDNS解析的域名作为upstream,请不要使用上述方法配置proxy_pass。另外要尽量减少set变量。

Nginx用rsyslog转发日志的一些小坑

我们从Nginx的代码中可以看到,给syslog发通知的时候,tag后面跟了2个字符 冒号+空格,所以阿里云这里的文档完全是坑爹

if $syslogtag == 'nginx' then @@10.101.166.173:11111;ALI_LOG_FMT

这里判断的是$syslogtag,也就是这种配置

access_log syslog:server=ip:port,facility=local7,tag=nginx,severity=info combined;

这边配置tag为 nginx,而 $syslogtag 不会是 nginx,而是 nginx+冒号+空格

查了rsyslog文档,$syslogtag 的行为本来就是程序自己定的,(),真正需要的是 这个 $programname

所以这样的配置是可以work的:

cat /etc/rsyslog.d/nginx.conf

input(type="imuxsock" Socket="/var/log/nginx.sock" CreatePath="on")
# $template ALI_LOG_FMT,"2.3 streamlog_tag %timegenerated:::date-unixtimestamp% %fromhost-ip% %pri-text% %app-name% %syslogtag% %msg:::drop-last-lf%n"
template(name="ALI_LOG_FMT" type="string" string="2.3 streamlog_tag %timegenerated:::date-unixtimestamp% %fromhost-ip% %pri-text% %app-name% %syslogtag% %msg:::drop-last-lf%n")
if $programname == 'nginx' then @@127.0.0.1:11111;ALI_LOG_FMT
& ~

注意,最后的那个 $ ~ 表示被上条规则匹配的日志不在发送到别的地方,因为nginx的默认的facility 是 local7 和boot.log是一样的,会造成发送日志的同时 也写入到boot.log ,造成boot.log巨大,这也是一个坑点。

另外其实,可以自己配置nginx的日志格式 满足ilogtail的采集规则,这样可以少走一次rsysylog。这里的 streamlog_tag 对应阿里云的syslog采集日志的时候的log tag.

nfs软件服务利用ansible实现一键化部署

[root@m01 tools]# cat nfspeizhi.sh
cat >>/etc/exports<<EOF 
/data 172.16.1.0/24(rw,sync)
EOF
- hosts: 172.16.1.131 #服务端
tasks:
- name: yum 
shell: yum install -y nfs-utils rpcbind #安装nfs
- name: yunm
shell: yum -y install sshpass #安装密钥的软件
- name: chuangjianmulu
shell: mkdir -p /server/tools/ #创建所在配置的文件
- name: peizhi
copy: src=/server/tools/nfspeizhi.sh dest=/server/tools/nfspeizhi.sh
- name: yunxingjiaoben 
script: /server/tools/nfspeizhi.sh #运行脚本
- name: guanlimulu
shell: mkdir -p /data && chown -R nfsnobody.nfsnobody /data
- name: qidong 
shell: /etc/init.d/rpcbind start #启动
- name: kaijiqidong
shell: chkconfig rpcbind on && chkconfig nfs on
#设置密码文件
- hosts: 172.16.1.108
tasks:
- name: yum
shell: yum install -y nfs-utils rpcbind # 客户端
# - name: gui
# shell: umount -f /mnt
- name: guazai
shell: mount -t nfs 172.16.1.131:/data /mnt
- hosts: 172.16.1.141
tasks:
- name: yumh
shell: yum install -y nfs-utils rpcbind #客户端 
# - name: guih
# shell: umount -f /mnt 
- name: guazaih
shell: mount -t nfs 172.16.1.131:/data /mnt

未分类

一个不可思议的MySQL慢查分析与解决

一、前言

开发需要定期的删除表里一定时间以前的数据,SQL如下

mysql > delete from testtable WHERE biz_date <= '2017-08-21 00:00:00'  AND status = 2  limit 500G

前段时间在优化的时候,已经在相应的查询条件上加上了索引

KEY `idx_bizdate_st` (`biz_date`,`status`)

但是实际执行的SQL依然非常慢,为什么呢,我们来一步步分析验证下

二、分析

表上的字段既然都有索引,那么按照之前的文章分析,是两个字段都可以走上索引的。如果有疑问,请参考文章 10分钟让你明白MySQL是如何利用索引的

既然能够利用索引,表的总大小也就是200M左右,那么为什么形成了慢查呢?

我们查看执行计划,去掉limit 后,发现他选择了走全表扫描。

mysql > desc  select * from testtable   WHERE biz_date <= '2017-08-21 00:00:00';
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
| id | select_type | table     | type | possible_keys  | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | testtable | ALL  | idx_bizdate_st | NULL | NULL    | NULL | 980626 | Using where |
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)

-- 只查询biz_date
-- 关键点:rows:980626;type:ALL 

mysql > desc  select * from testtable   WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
| id | select_type | table     | type | possible_keys  | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | testtable | ALL  | idx_bizdate_st | NULL | NULL    | NULL | 980632 | Using where |
+----+-------------+-----------+------+----------------+------+---------+------+--------+-------------+
1 row in set (0.00 sec)

-- 查询biz_date + status 
-- 关键点:rows:980632;type:ALL  


mysql > desc  select * from testtable   WHERE biz_date <= '2017-08-21 00:00:00' and status = 2 limit 100;
+----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
| id | select_type | table     | type  | possible_keys  | key            | key_len | ref  | rows   | Extra                 |
+----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
|  1 | SIMPLE      | testtable | range | idx_bizdate_st | idx_bizdate_st | 6       | NULL | 490319 | Using index condition |
+----+-------------+-----------+-------+----------------+----------------+---------+------+--------+-----------------------+
1 row in set (0.00 sec)

-- 查询biz_date + status+ limit 
-- 关键点:rows:490319;  

mysql > select count(*)  from testtable   WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (0.34 sec)


mysql > select count(*)  from testtable   WHERE biz_date <= '2017-08-21 00:00:00';
+----------+
| count(*) |
+----------+
|   970183 |
+----------+
1 row in set (0.33 sec)


mysql > select count(*)  from testtable;
+----------+
| count(*) |
+----------+
|   991421 |
+----------+
1 row in set (0.19 sec)


mysql > select distinct biz_status from whwtestbuffer;
+------------+
| biz_status |
+------------+
|          1 |
|          2 |
|          4 |
+------------+

通过以上查询,我们可以发现如下几点问题:

  • 通过 biz_date 预估出来的行数 和 biz_date + status=2 预估出来的行数几乎一样,为98w。
  • 实际查询表 biz_date + status=2 一条记录都没有。
  • 整表数据量达到了99万,MySQL发现通过索引扫描需要98w行(预估)

因此,MySQL通过统计信息预估的时候,发现需要扫描的索引行数几乎占到了整个表,放弃了使用索引,选择了走全表扫描。

那是不是他的统计信息有问题呢?我们重新收集了下表统计信息,发现执行计划的预估行数还是一样,猜测只能根据组合索引的第一个字段进行预估(待确定)

那我们试下直接强制让他走索引呢?

mysql > select * from testtable   WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
Empty set (0.79 sec)

mysql > select * from testtable force index(idx_bizdate_st)  WHERE biz_date <= '2017-08-21 00:00:00' and status = 2;
Empty set (0.16 sec)

我们发现,强制指定索引后,查询耗时和没有强制索引比较,的确执行速度快了很多,因为没有强制索引是全表扫描嘛!但是!依然非常慢!

那么还有什么办法去优化这个本来应该很快的查询呢?

大家应该都听说过要选择性好的字段放在组合索引的最前面?
是的,相对于status字段,biz_date 的选择性更加不错,那组合索引本身已经没有好调整了

那,能不能让他不要扫描索引的那么多范围呢?之前的索引模型中也说过,MySQL是通过索引去确定一个扫描范围,如果能够定位到尽可能小的范围,那是不是速度上会快很多呢?

并且业务逻辑上是定期删除一定日期之前的数据。所以逻辑上来说,每次删除都是只删除一天的数据,直接让SQL扫描一天的范围。那么我们就可以改写SQL啦!

mysql > select * from testtable WHERE biz_date >= '2017-08-20 00:00:00' and biz_date <= '2017-08-21 00:00:00' and status = 2;
Empty set (0.00 sec)

mysql > desc select * from testtable WHERE biz_date >= '2017-08-20 00:00:00' and biz_date <= '2017-08-21 00:00:00' and status = 2;
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
| id | select_type | table            | type  | possible_keys  | key            | key_len | ref  | rows | Extra                 |
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
|  1 | SIMPLE      | testtable        | range | idx_bizdate_st | idx_bizdate_st | 6       | NULL |  789 | Using index condition |
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
1 row in set (0.00 sec)

-- rows降低了很多,乖乖的走了索引

mysql > desc select * from testtable WHERE biz_date >= '2017-08-20 00:00:00' and biz_date <= '2017-08-21 00:00:00' ;
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
| id | select_type | table            | type  | possible_keys  | key            | key_len | ref  | rows | Extra                 |
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
|  1 | SIMPLE      | testtable        | range | idx_bizdate_st | idx_bizdate_st | 5       | NULL | 1318 | Using index condition |
+----+-------------+------------------+-------+----------------+----------------+---------+------+------+-----------------------+
1 row in set (0.00 sec)

-- 即使没有status,也是肯定走索引啦

三、小结

这个问题,我原本打算用hint,强制让他走索引,但是实际上强制走索引的执行时间并不能带来满意的效果。结合业务逻辑,来优化SQL,是最好的方式,也是终极法宝,一定要好好利用。不了解业务的DBA,不是一个好DBA… 继续去补业务知识去了。。

mariadb galera集群配置

最近在看一些关于数据库的资料,从最开始的mysql的主从复制到mysql的双主+heartbeat实现mysql的高可用再到mysql+drbd+heartbeat实现底层数据同步的双主高可用再到mysql_mmm+amoeba实现双主多从的高可用和负载均衡以及读写分离,再到后来发现mysql自从被Oracle收购后已经越来越走向了封闭,更新也不如以前频繁,并且新版的mysql已经不支持GPL协议了。。。感觉mysql已经在Oracle手中渐渐没落了。。。后来发现了一个更好的替代方案那就是mariadb的galera实现多主负载均衡,于是动手实验搭建了一把mariadb galbra实验环境,发现效果确实不错,果断以后要慢慢退出mysql,使用mariadb来做存储了。

关于mariadb的产生,由于08年sun收购了mysql之后,mysql中的一批高管就离开了mysql项目出来创业,而09年sun被oracle收购后又有一批mysql老员工离开了,这两批人后来就创立了一个新的公司,继续开发开源的关系数据库,于是mariadb就这样诞生了。并且这两年mariadb的发展越来越迅猛,很多大公司都开始抛弃mysql转向mariadb了。包括Redheat 7以及Centos 7都抛弃了mysql转而投奔了mariadb。好了闲扯了这么多废话,给各位看官们大概讲解了一下mariadb的前世今生,具体的各位可以百度一下更详细的资料,下面来分析一下以上各种数据库集群的优缺点已经我为何选择mariadb galera来实现数据库集群。

首先,mysql主从复制由于是使用的binlog日志实现同步的,主和从之间的数据同步是异步的,在面对大并发量的数据读写时存在时延性的问题,可能导致数据不同步现象。但是该场景可以适用于少量写入数据,大量查询数据场景中,使用mysql的主+多从还是个不错的方案。

接着是mysql+heartbeat实现双主复制,其原理也是使用mysql的binlog来实现数据同步的,只不过在配置中设置了两台数据库服务器互为对方的主,然后通过heartbeat来实现双主的高可用,但是这个存在一个问题就是永远只有一个mysql服务器在工作,另一个一直处于热备状态,浪费了服务器资源,适用于对核心数据提供高可用的场景中,并且能提供的并发量也不是很大,况且还有可能存在脑裂的问题。

再然后就是mysql+drbd+heartbeat实现双主数据库的高可用了,其数据同步的原理是使用drbd在磁盘上面划取一块磁盘专门用来做数据存储,通过DRBD来实现两台服务器的数据同步,该同步过程是在底层数据块实现的,效率比使用mysql的binlog日志同步更加高效,个人感觉比mysql的binlog同步数据要更好,但是假如随着数据量的越来越大,预先划定的DRBD同步磁盘区域不够用怎么办?这也是一个问题,而且这个方案也存在脑裂的问题。

然后发现了一个比较好的数据库集群高可用方案,那就是mysql_mmm+amoeba实现双主多从的高可用和负载均衡以及读写分离。mysql_mmm是mysql的一套组件,通过使用虚拟IP的方式,动态的将读写分离到不同的服务器上面去执行,并且假如某台服务器挂掉了,虚拟IP将自动跳转到其他正常服务器上面继续提供服务。而amoeba提供的服务是将所有mysql_mmm产生的虚拟IP转换成一个IP,对前端业务提供服务接口,从而在前端业务看来,mysql集群是透明的,不需要对前端程序进行修改就能实现数据库的负载均衡,读写分离。这里既然提到了amoeba就不得不提mysql的一个组件mysql_HAproxy,mysql_HAproxy这套组件是mysql官方退出的一套实现mysql集群的读写分离套件,但是mysql官方都不建议企业中使用该套件,因为该套件还不稳定,其实现读写分离的功能是通过一个配置文件balance.lua来实现的,在面对大并发量数据的时候不稳定,并且假如后端某台数据库服务器挂掉了,HAproxy无法检测到,从而出现将数据转发到挂掉的数据库上面,相对比来讲mysql_mmm+amoeba的组合方式比HAproxy的方式要好。但是在看mysql_mmm+amoeba的资料的时候发现其salve是和其中的某一台maste同步数据的,这样就产生了一个问题,那就是假如这台master服务器挂掉了,slave将无法继续同步数据了,需要手动将slave同步的master服务器切换到另外一台,这样就产生了时延性,影响业务的正常应用。

最后,mariadb galera实现数据库的多主模式,该模式是通过在写数据的时候,确保数据写入到所有服务器中之后才认为该写入操作成功,所以其能够基本保持数据的一致性以及数据操作的原子性。并且其不存在mysql主从中的不能在从上面写入数据的原则,在所有服务器上面都可以写入数据,其会自动同步到所有服务器中。当然,不能只谈mariadb galera的好处不谈它的缺点,它的缺点就是其写入数据的性能是由集群中最差的一台服务器来决定的,所以在生产环境中需要尽量保持集群中的所有服务器软硬件配置一样,从而避免所谓的木桶原理影响性能。还有就是mariadb galera只能使用innodb存储引擎,而不能使用其他存储引擎,并且不支持锁表操作。对于mariadb galera的局限性可以参考以下连接: 官网解释

好了,以上就是我对于数据库集群的一些理解,里面的一些不对之处还请各位大神轻喷,前面说了一大堆文字理论,下面我们要进入正题了,那就是怎样搭建mariadb galera环境。

最开始,我本来想直接去官网下载mariadb galera的源码包来安装的,但是不知道为什么,最近官网的所有包都不能下载,于是在网上找资料,发现可以使用yum安装,于是找到了yum安装的源地址:http://yum.mariadb.org/ 配置如下:

[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.0.20/centos6-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

但是,因为这是一个境外站点,yum下载速度好慢,老是失败,于是逼得没办法,只能把这个站中的10.0.20/centos6-amd64/目录整个下载下来,放到本地做成本地源来使用了,下面贴出我做的本地源的配置:

[root@test3 my.cnf.d]# tail -15 /etc/yum.repos.d/Centos-Local.repo 
name=CentOS-6 - Local
baseurl=http://192.168.1.160/base/
gpgcheck=0
enabled=1
failovermethod=priority
[c6-epel]
name=CentOS-6 - Local
baseurl=http://192.168.1.160/epel/$basearch/
gpgcheck=0
enabled=1
[mariadb]
name=mariadb_galera
baseurl=http://192.168.1.160/mariadb_galera/centos6X86_64/
gpgcheck=0
enabled=1

其中的192.168.1.160是我的局域网yum源服务器。 好了,配置好yum源后开始安装了,我这里准备了4台服务器,分别是:

192.168.1.161 nd1
192.168.1.162 nd2
192.168.1.163 nd3
192.168.1.164 nd4

分别在四台服务器上面使用命令安装套件:

yum -y install MariaDB-Galera-server MariaDB-client rsync galera

安装好后,启动数据库,使用命令service mysql start启动,启动之后需要对数据库进行安全加固,删除一些不需要的账户,这里就不进行赘述了。然后需要添加一个同步数据的用户,

在四台服务器上进入数据库中实行下面的命令:

grant all privileges on *.* to 'wsrep_sst-user'@'192.168.1.%' identified by 'password';

flush privileges;

之后退出数据库,创建配置文件:

[root@test2 ~]# cat /etc/my.cnf.d/galera.cnf
[server]
query_cache_size=0
binlog_format=ROW
default_storage_engine=innodb
innodb_autoinc_lock_mode=2
wsrep_provider=/usr/lib64/galera/libgalera_smm.so
#wsrep_cluster_address="gcomm://192.168.1.162,192.168.1.163,192.168.1.164"
wsrep_cluster_address=gcomm://
wsrep_cluster_name='example_cluster'
wsrep_node_address='192.168.1.161'
wsrep_node_name='nd1'
wsrep_sst_method=rsync
#wsrep_sst_method=xtrabackup
wsrep_sst_auth=wsrep_sst-user:password

上面是nd1的配置文件,其中需要注意的地方有: wsrep_cluster_address=gcomm://这条命令,gcomm://是一个特殊的参数,在启动第一台数据库时需要使用这个参数来启动,否则会启动失败,后面的节点使用wsrep_cluster_address=”gcomm://192.168.1.162,192.168.1.163,192.168.1.164″这个参数来启动数据库。当第一台数据库需要重启时需要切换到wsrep_cluster_address=”gcomm://192.168.1.162,192.168.1.163,192.168.1.164″这个参数来启动才能加入到集群中。这样讲有点抽象,换一种方式来讲就是最开始启动集群的第一台服务器时将wsrep_cluster_address=”gcomm://192.168.1.162,192.168.1.163,192.168.1.164″这一行注释掉,使用下面那个参数来启动,后面的节点注释掉wsrep_cluster_address=gcomm://这个参数启动数据库,加入到集群中来。当集群全部起来以后万一第一台数据库要重启时将下面那条参数注释掉,开启上面那条参数。这样就能加入集群中来。至于wsrep_sst_auth=wsrep_sst-user:password这个参数就是我们之前设定的用来同步的用户名和密码。

另外,需要注意的是防火墙需要开启TCP的3306端口和TCP4567端口开启,否则集群将不能实现,这里为了方便直接清空了防火墙iptables -F,也可以使用下面的命令来使防火墙开启相应端口

iptables -A INPUT -i eth0 -p tcp --dport 3306 -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --dport 4567 -j ACCEPT

配置好了参数之后,最先重启nd1节点service mysql restart,接下来重启nd2-4节点,之后就可以进数据库中查询同步状态了,可以使用下面的命令查询数据库同步状态:

MariaDB [(none)]> show global status like  'wsrep%';
+------------------------------+-----------------------------------------------------------------------------+
| Variable_name                | Value                                                                       |
+------------------------------+-----------------------------------------------------------------------------+
| wsrep_local_state_uuid       | 0381ab0f-a3aa-11e4-9737-be607495053f                                        |
| wsrep_protocol_version       | 7                                                                           |
| wsrep_last_committed         | 2                                                                           |
| wsrep_replicated             | 0                                                                           |
| wsrep_replicated_bytes       | 0                                                                           |
| wsrep_repl_keys              | 0                                                                           |
| wsrep_repl_keys_bytes        | 0                                                                           |
| wsrep_repl_data_bytes        | 0                                                                           |
| wsrep_repl_other_bytes       | 0                                                                           |
| wsrep_received               | 4                                                                           |
| wsrep_received_bytes         | 948                                                                         |
| wsrep_local_commits          | 0                                                                           |
| wsrep_local_cert_failures    | 0                                                                           |
| wsrep_local_replays          | 0                                                                           |
| wsrep_local_send_queue       | 0                                                                           |
| wsrep_local_send_queue_max   | 1                                                                           |
| wsrep_local_send_queue_min   | 0                                                                           |
| wsrep_local_send_queue_avg   | 0.000000                                                                    |
| wsrep_local_recv_queue       | 0                                                                           |
| wsrep_local_recv_queue_max   | 1                                                                           |
| wsrep_local_recv_queue_min   | 0                                                                           |
| wsrep_local_recv_queue_avg   | 0.000000                                                                    |
| wsrep_local_cached_downto    | 18446744073709551615                                                        |
| wsrep_flow_control_paused_ns | 0                                                                           |
| wsrep_flow_control_paused    | 0.000000                                                                    |
| wsrep_flow_control_sent      | 0                                                                           |
| wsrep_flow_control_recv      | 0                                                                           |
| wsrep_cert_deps_distance     | 0.000000                                                                    |
| wsrep_apply_oooe             | 0.000000                                                                    |
| wsrep_apply_oool             | 0.000000                                                                    |
| wsrep_apply_window           | 0.000000                                                                    |
| wsrep_commit_oooe            | 0.000000                                                                    |
| wsrep_commit_oool            | 0.000000                                                                    |
| wsrep_commit_window          | 0.000000                                                                    |
| wsrep_local_state            | 4                                                                           |
| wsrep_local_state_comment    | Synced                                                                      |
| wsrep_cert_index_size        | 0                                                                           |
| wsrep_causal_reads           | 0                                                                           |
| wsrep_cert_interval          | 0.000000                                                                    |
| wsrep_incoming_addresses     | 192.168.1.164:3306,192.168.1.162:3306,192.168.1.161:3306,192.168.1.163:3306 |
| wsrep_evs_delayed            |                                                                             |
| wsrep_evs_evict_list         |                                                                             |
| wsrep_evs_repl_latency       | 0/0/0/0/0                                                                   |
| wsrep_evs_state              | OPERATIONAL                                                                 |
| wsrep_gcomm_uuid             | c009d89b-a3b6-11e4-9e41-a2518f032b8a                                        |
| wsrep_cluster_conf_id        | 6                                                                           |
| wsrep_cluster_size           | 4                                                                           |
| wsrep_cluster_state_uuid     | 0381ab0f-a3aa-11e4-9737-be607495053f                                        |
| wsrep_cluster_status         | Primary                                                                     |
| wsrep_connected              | ON                                                                          |
| wsrep_local_bf_aborts        | 0                                                                           |
| wsrep_local_index            | 2                                                                           |
| wsrep_provider_name          | Galera                                                                      |
| wsrep_provider_vendor        | Codership Oy <[email protected]>                                           |
| wsrep_provider_version       | 25.3.9(r3385)                                                               |
| wsrep_ready                  | ON                                                                          |
| wsrep_thread_count           | 2                                                                           |
+------------------------------+-----------------------------------------------------------------------------+
57 rows in set (0.00 sec)

其中可以看到wsrep_incoming_addresses中已经列出来了所有的四台服务器都同步了,还有wsrep_connected和wsrep_ready都是ON状态。这样就可以确定数据库已经同步了,接下来创建一个数据库测试是否真的能同步数据。

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.01 sec)
MariaDB [(none)]> create database huxianglin;
Query OK, 1 row affected (0.00 sec)

这是在节点1上面进行的操作

MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.00 sec)
MariaDB [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| huxianglin         |
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
5 rows in set (0.00 sec)

这是在节点2上面前后查询结果的对比,从中我们可以看出数据已经同步到节点2了。

至此mariadb galera集群实验完成了,至于mariadb galera的仲裁人节点配置可以使用命令来实现

garbd -a gcomm://192.168.1.100:4567 -g my_wsrep_cluster -d

注释:参数说明: -d:以daemon模式运行 -a:集群地址 -g: 集群名称

在多主机Docker网络中运行XtraDB Cluster

译者前言

Percona 所维护的XtraDB 是mysql的一个分支,使用了性能比innodb更加出色的xtrodb驱动,XtraDB-Cluster产品,是其集群化的方案,方案内容,请自行google。最近XtraDB-Cluster的5.7版本的推出,跟上了mysql主分支的脚步,更加吸引mysql爱好者转移到Percona上来。)
Percona-XtraDB-Cluster-certification-1

译文如下

以下,我将阐述一下,怎么样在多主机Docker网络中运行Percona XtraDB Cluster。
随着我们的Pecrona XtraDB Cluster 5.7 beta版本的发布,我们亦决定提供Pecrona XtraDB Cluster 5.6 和 Pecrona XtraDB Cluster 5.7 的镜像文件。
启动一个单点的Percona XtraDB Cluster 是很方便的,基本和Percona Server的镜像相同。唯一的不同就是需要指定CLUSTER_NAME环境变量. 启动容器的命令如下

docker run -d -p 3306:3306
    -e MYSQL_ROOT_PASSWORD= Theistareyk
    -e CLUSTER_NAME= Theistareykjarbunga
    -e XTRABACKUP_PASSWORD=Theistare
    percona/percona-xtradb-cluster

你应该会注意到我们提供了可选的参数 XTRABACKUP_PASSWORD,这个参数是用户xtradbbackup@localhost 执行xtrabackup-SST 同步的用户密码。
运行单点的Percona XtraDB Cluster 需要迎合Cluster启动所需要的 CLUSTER_NAME 参数,单点其实也无所谓这个。在我们提供的镜像中,需要解决以下任务:

  1. 运行在多主机的环境(多主机环境上一般跑Docker Swarm 和Kubernetes)

  2. 如我们所需的,在某个Cluster中启动多个节点

  3. 在服务发现的服务端口上,注册所有的节点,这样,所有的客户端就能知道有多少个节点以及他们运行的状态

  4. 集成 ProxySQL

让我们一个一个看。

随着Docker网络协议的完善,可以使用多主机环境部署Percona XtraDB了。 最近的Docker 版本带来了网络overlay驱动,我们将使用这个特性建立一个虚拟网络。安装和启动Docker 的overlay 网络超出了本问的范围,这里给出个链接,感兴趣的人可以看下这个非常好的介绍资料了解下这个虚拟网络是如何运行的。

好了,等你overlay网络驱动装好,我们要在这上创建一个虚拟网络:

docker network create -d overlay cluster1_net

然后我们可以这样启动容器:

docker run -d -p 3306 --net=cluster1_net
 -e MYSQL_ROOT_PASSWORD=Theistareyk
 -e CLUSTER_NAME=cluster1
 ...
 -e XTRABACKUP_PASSWORD=Theistare
 percona/percona-xtradb-cluster

这玩意儿很cool,cool点在于,你随便在哪台服务器启动这个节点,只要是基于同一个网络的相同的 CLUSTER_NAME,他们都会自动的进行通信。
如果你处在单一Docker主机的环境中,譬如做个测试什么的,你还可以创建一个网桥网络,在一个单主机的环境中使用他。
好吧,以上这个脚本,怎么说呢,基本可以执行。问题在于每个新加入的节点需要知道运行的cluster的地址。
为了让实例知道这个地址,我们可以使用 CLUSTER_JOIN 这个变量,这个变量的值为某一个运行中的节点的ip地址,(如果是一个新的群,那么就是空)

在本例中,这个脚本应该像这样:

docker run -d -p 3306 --net=cluster1_net
 -e MYSQL_ROOT_PASSWORD=Theistareyk
 -e CLUSTER_NAME=cluster1
 -e CLUSTER_JOIN=10.0.5.5
 -e XTRABACKUP_PASSWORD=Theistare
 percona/percona-xtradb-cluster

手动追踪一个ip地址在我看来完全是一个额外的工作,尤其是要在一个动态的环境中起停一个节点的时候,真是有够麻烦的。所以我们决定使用一个发现服务。现在我们使用的是Etcd发现服务,当然,使用其他的发现服务也没啥问题,譬如Consul。

举个例子,当你在主机 10.20.2.4:2379上运行发现服务的时候,你可以这样启动节点:

docker run -d -p 3306 --net=cluster1_net
 -e MYSQL_ROOT_PASSWORD=Theistareyk
 -e CLUSTER_NAME=cluster1
 -e DISCOVERY_SERVICE=10.20.2.4:2379
 -e XTRABACKUP_PASSWORD=Theistare
 percona/percona-xtradb-cluster

这个节点就会自己把自己注册到发现服务中去,并且加入名为$CLUSTER_NAME的集群中。

下面是显示 CLUSTER_NAME 为 $CLUSTER_NAME 集群的一个简单的方法:

curl http://$ETCD_HOST/v2/keys/pxc-cluster/$CLUSTER_NAME/?recursive=true | jq
{
  "action": "get",
  "node": {
    "key": "/pxc-cluster/cluster4",
    "dir": true,
    "nodes": [
      {
        "key": "/pxc-cluster/cluster4/10.0.5.2",
        "dir": true,
        "nodes": [
          {
            "key": "/pxc-cluster/cluster4/10.0.5.2/ipaddr",
            "value": "10.0.5.2",
            "modifiedIndex": 19600,
            "createdIndex": 19600
          },
          {
            "key": "/pxc-cluster/cluster4/10.0.5.2/hostname",
            "value": "2af0a75ce0cb",
            "modifiedIndex": 19601,
            "createdIndex": 19601
          }
        ],
        "modifiedIndex": 19600,
        "createdIndex": 19600
      },
      {
        "key": "/pxc-cluster/cluster4/10.0.5.3",
        "dir": true,
        "nodes": [
          {
            "key": "/pxc-cluster/cluster4/10.0.5.3/ipaddr",
            "value": "10.0.5.3",
            "modifiedIndex": 26420,
            "createdIndex": 26420
          },
          {
            "key": "/pxc-cluster/cluster4/10.0.5.3/hostname",
            "value": "cfb29833f1d6",
            "modifiedIndex": 26421,
            "createdIndex": 26421
          }
        ],
        "modifiedIndex": 26420,
        "createdIndex": 26420
      }
    ],
    "modifiedIndex": 19600,
    "createdIndex": 19600
  }
}

用这个方法,你就可以在任意个Docker主机上启动任意多个数据库节点了。现在我们可以在数据库集群前端放置SQL Proxy了,这个下回再讨论了。

译者附

我在实施该XtraDB Cluster的时候,发现脚本有问题,同时发现该文的后方,亦有人发现相同的问题,现贴出该bug并给出解决办法:

发言人为Roma Cherepanov

他在启动节点的时候发现了一些error,我也发现了这些问题,导致容器一直启动不起来,之后经过调试,解决该问题,并在下方给出了答复

kevin:

在跑这个镜像的时候,我有相同的问题。

pxc-entry.sh脚本有些问题

line 125: (应该是125,之前数错了,帖子上面写的也错了☹)

i=$(curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/queue/$CLUSTER_NAME | jq -r '.node.nodes[].value')

应该是

i=(curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/queue/$CLUSTER_NAME | jq -r '.node.nodes[].value')

line 139:

i=$(curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/?quorum=true | jq -r '.node.nodes[]?.key' | awk -F'/' '{print $(NF)}')

应该是

i=(curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/?quorum=true | jq -r '.node.nodes[]?.key' | awk -F'/' '{print $(NF)}')

脚本要给i 赋值一个数组,那么shell的数组赋值形式应该是i=(a b c d) ,而不是i=$(a b c d) 不知道作者的脚本解释语言是啥,反正在我这里应该是这样的,改了之后就OK啦!