一、安装
yum install mariadb-server mariadb
二、启动服务
systemctl start mariadb
三、配置大小写敏感问题、和字符为utf8
vim /etc/my.cnf
添加下列内容:
yum install mariadb-server mariadb
systemctl start mariadb
vim /etc/my.cnf
添加下列内容:
记得MySQL Group Replicatioin 刚开始的时候,MySQL界很是轰动,等待了多年,终于有了官方的这个高可用解决方案。你要说还有一些方案补充,比如MySQL Cluster,MySQL Proxy,这些的使用率个人感觉还是不高,也就是经受的考验还不够,原因有很多,就不赘述了。
不久,我和一个MySQL DBA有了下面的一个基本对话。
我: MySQL GR GA之后,里面的自动切换功能确实很赞,能够做到读写分离,原本MHA的方案现在MGR也可以做了。
MySQL DBA:如果数据库发生了故障,这个自动切换的过程,其实对于应用不是透明的,因为读写节点相当于漂移到了另外一台服务器上,除非再做个中间件。
我:单纯MGR目前还做不了这个,它目前只是保证数据库层面的这种切换和高可用。
MySQL DBA:所以说MGR的企业级应用还是需要一些辅助,这样才算是一个完整的解决方案。
不久,MySQL InnoDB Cluster推出,我觉得这个方案想比原来的MGR更进一步,说实话,我很看好这个方案,尽管目前愿意真正去用的用户确实不多。
如果你看一下官方的这个架构图,就会发现,MGR本身就是Innodb Cluster的一部分,还有两个组件,MySQL Shell,MySQL Router,这三板斧就是InnoDB Cluster的一个核心组件,而正如我之前所说,可以看到MySQL的一个格局和定位,他正在很努力去解决以前诟病的问题。
要安装InnoDB Cluster,环境的一个基本要求就是Python,我看了下,很多默认的系统版本是2.6,而它的最低要求是2.7及以上,所以还是需要提前准备下这个部分。
如果你的系统是Python 2.6版本的,可以考虑升级到2.7,参考如下的方法。
下载安装包,部署
wget http://python.org/ftp/python/2.7/Python-2.7.tar.bz2 --no-check-certificate
./configure
make all
make install
make clean
make distclean
查看Python的版本
# /usr/local/bin/python2.7 -V
Python 2.7
做基本的环境设置,替换旧的Python
mv /usr/bin/python /usr/bin/python2.6
ln -s /usr/local/bin/python2.7 /usr/bin/python
搭建InnoDB Cluster显而易见需要多台服务器,而如果在一台服务器上练习测试,也是全然没有问题,如果想更快更方便的测试模拟,还可以使用sandbox来做,首先你得有sandbox,接着InnoDB Cluster的三大组件是MGR,MySQL Shell,MySQL Router,所以你可以从官网直接下载下来。
然后我们开启安装之旅。
使用MySQL Shell的命令mysqlsh开始部署,创建一个端口为3310的实例
mysql-js> dba.deploySandboxInstance(3310)
A new MySQL sandbox instance will be created on this host in
/root/mysql-sandboxes/3310
输入密码之后,一个3310端口的MySQL服务就启动了。
Please enter a MySQL root password for the new instance:
Deploying new MySQL instance...
Instance localhost:3310 successfully deployed and started.
Use shell.connect('root@localhost:3310'); to connect to the instance.
接着创建另外两个节点 3320,3330
dba.deploySandboxInstance(3320)
dba.deploySandboxInstance(3330)
我们切换到3310的MySQL实例,准备开始创建Cluster
mysql-js> connect root@localhost:3310
Creating a Session to 'root@localhost:3310'
Enter password:
Closing old connection...
Classic Session successfully established. No default schema selected.
定义一个Cluster变量,节点1就开启了Cluster创建之旅,可以从下面的信息看出,至少需要3个节点
mysql-js> var cluster = dba.createCluster('testCluster')
A new InnoDB cluster will be created on instance 'root@localhost:3310'.
Creating InnoDB cluster 'testCluster' on 'root@localhost:3310'...
Adding Seed Instance...
Cluster successfully created. Use Cluster.addInstance() to add MySQL instances.
At least 3 instances are needed for the cluster to be able to withstand up to one server failure.
接着把另外两个节点加入进来,先加入端口为3320的节点
mysql-js> cluster.addInstance('root@localhost:3320')
A new instance will be added to the InnoDB cluster. Depending on the amount of
data on the cluster this might take from a few seconds to several hours.
Please provide the password for 'root@localhost:3320':
Adding instance to the cluster ...加入端口为3330的节点,日志和节点2相似。
mysql-js> cluster.addInstance('root@localhost:3330')
这个时候Cluster就创建好了。
这个时候,我们再配置一下MySQL Router,创建个软链接,保证能够正常调用。
# ln -s /home/innodb_cluster/mysql-router-2.1.3-linux-glibc2.12-x86-64bit/bin/mysqlrouter /usr/bin/mysqlroute
# which mysqlroute
/usr/bin/mysqlroute
配置MySQL Router的启动节点为端口3310的实例
# mysqlrouter –bootstrap root@localhost:3310 –user=mysql
这个时候还是要输入密码,成功之后,这个绑定就打通了。
Please enter MySQL password for root:
Bootstrapping system MySQL Router instance...
MySQL Router has now been configured for the InnoDB cluster 'testCluster'.
The following connection information can be used to connect to the cluster.
Classic MySQL protocol connections to cluster 'testCluster':
- Read/Write Connections: localhost:6446
- Read/Only Connections: localhost:6447
X protocol connections to cluster 'testCluster':
- Read/Write Connections: localhost:64460
- Read/Only Connections: localhost:64470
可以从上面的日志看出来,分配的读写端口是6446,只读端口是6447,还有x协议连接的端口为64460,64470
启动MySQL Router
# mysqlrouter &
[1] 2913
如果对MySQL Router还有些疑问,可以看看安装目录下,会生成下面的配置文件,我们就看里面的.conf文件,里面的一部分内容如下:
[routing:testCluster_default_rw]
bind_address=0.0.0.0
bind_port=6446
destinations=metadata-cache://testCluster/default?role=PRIMARY
mode=read-write
protocol=classic
我们尝试使用6446来连接登录,这个时候就通过MySQL Shell开启了连接入口,MySQL Router做了转接,连接到了里面的读写节点3310
# mysqlsh --uri root@localhost:6446
Creating a Session to 'root@localhost:6446'
Enter password:
Classic Session successfully established. No default schema selected.
Welcome to MySQL Shell 1.0.9
切换到sql模式,查看端口就知道是哪个节点了。
mysql-js> sql
Switching to SQL mode... Commands end with ;
mysql-sql> select @@port;
+--------+
| @@port |
+--------+
| 3310 |
+--------+
1 row in set (0.00 sec)
如果切换为脚本模式查看实例的状态,可以使用里面定义的API来做,输出都是JSON串。
mysql-js> dba.configureLocalInstance('[email protected]:3310')
Please provide the password for '[email protected]:3310':
Detected as sandbox instance.
Validating MySQL configuration file at: /root/mysql-sandboxes/3310/my.cnf
Validating instance...
The instance '127.0.0.1:3310' is valid for Cluster usage
You can now use it in an InnoDB Cluster.
{
"status": "ok"
}如果查看Cluster的信息,可以看到下面的读写节点,只读节点的状态信息
mysql-js> dba.getCluster()
<Cluster:testCluster>
得到Cluster的信息
var cluster = dba.getCluster()
mysql-js> cluster.status()
{
"clusterName": "testCluster",
"defaultReplicaSet": {
"name": "default",
"primary": "localhost:3310",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"localhost:3310": {
"address": "localhost:3310",
"mode": "R/W",
"readReplicas": {},
"role": "HA",
"status": "ONLINE"
},
"localhost:3320": {
"address": "localhost:3320",
"mode": "R/O",
"readReplicas": {},
"role": "HA",
"status": "ONLINE"
},
"localhost:3330": {
"address": "localhost:3330",
"mode": "R/O",
"readReplicas": {},
"role": "HA",
"status": "ONLINE"
}
}
}
}
也可以使用describe得到一些基本的信息
mysql-js> cluster.describe();
{
"clusterName": "testCluster",
"defaultReplicaSet": {
"instances": [
{
"host": "localhost:3310",
"label": "localhost:3310",
"role": "HA"
},
{
"host": "localhost:3320",
"label": "localhost:3320",
"role": "HA"
},
{
"host": "localhost:3330",
"label": "localhost:3330",
"role": "HA"
}
],
"name": "default"
}
}
当然光看不练还是假把式,我们切换一下,看看好使不?
模拟一个节点出现问题,可以使用killSandboxInstance方法。
mysql-js> dba.killSandboxInstance(3310)
The MySQL sandbox instance on this host in
/root/mysql-sandboxes/3310 will be killed
Killing MySQL instance...
Instance localhost:3310 successfully killed.
节点被清理了,没有任何进程存在。
# ps -ef|grep mysql|grep 3310
#
我们还是使用6446的端口来统一连接,这个时候就切换到了端口3320的MySQL服务
# mysqlsh --uri root@localhost:6446
mysql-js> sql
Switching to SQL mode... Commands end with ;
mysql-sql> select @@port;
+--------+
| @@port |
+--------+
| 3320 |
+--------+
1 row in set (0.00 sec)
所以切换的部分没有问题,我们再次把“迷失”的节点启动起来。
# mysqlsh --uri root@localhost:6446
mysql-js> dba.startSandboxInstance(3310)
The MySQL sandbox instance on this host in
/root/mysql-sandboxes/3310 will be started
Starting MySQL instance...
Instance localhost:3310 successfully started.
这个时候再次查看Cluster的状态,3320就是主了,3310就是只读节点了。
mysql-js> dba.getCluster()
<Cluster:testCluster>
把节点2纳入到Cluster中
mysql-js> cluster.rejoinInstance('root@localhost:3310')
Rejoining the instance to the InnoDB cluster. Depending on the original
problem that made the instance unavailable, the rejoin operation might not be
successful and further manual steps will be needed to fix the underlying
problem.
Please monitor the output of the rejoin operation and take necessary action if
the instance cannot rejoin.
Please provide the password for 'root@localhost:3310':
Rejoining instance to the cluster ...
The instance 'root@localhost:3310' was successfully rejoined on the cluster.
The instance 'localhost:3310' was successfully added to the MySQL Cluster.
mysql-js>
可以想象如果是一个生产系统,这么多的日志,这个过程真是让人纠结。
最后来一个切换后的Cluster状态
mysql-js> cluster.status()
{
"clusterName": "testCluster",
"defaultReplicaSet": {
"name": "default",
"primary": "localhost:3320",
"status": "OK",
"statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
"topology": {
"localhost:3310": {
"address": "localhost:3310",
"mode": "R/O",
"readReplicas": {},
"role": "HA",
"status": "ONLINE"
},
"localhost:3320": {
"address": "localhost:3320",
"mode": "R/W",
"readReplicas": {},
"role": "HA",
"status": "ONLINE"
},
"localhost:3330": {
"address": "localhost:3330",
"mode": "R/O",
"readReplicas": {},
"role": "HA",
"status": "ONLINE"
}
}
}
}
前段时间用了下zabbix+percona的插件监控MySql,感觉不错,内置了190多个监控项,40多个图形展示。对MySql的监控比较全面,自己导入模版之后稍微根据自己的需求进行修改就可以使用。
https://www.percona.com/doc/percona-monitoring-plugins/1.1/zabbix/index.html
部署前提:你已经在MySql数据库添加安装好了zabbix-agent环境,并且在zabbix-server端添加agent
rpm -ivh http://www.percona.com/downloads/percona-release/redhat/0.1-3/percona-release-0.1-3.noarch.rpm
Percona的监控插件是php编写的。通过php连接MySql来获取相关数据,所以需要你在本地安装php环境和php-MySql环境
yum -y install php php-mysql
yum -y install percona-zabbix-templates
安装完成生成如下文件
# rpm -ql percona-zabbix-templates
/var/lib/zabbix/percona
/var/lib/zabbix/percona/scripts
/var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh
/var/lib/zabbix/percona/scripts/ss_get_mysql_stats.php
/var/lib/zabbix/percona/templates
/var/lib/zabbix/percona/templates/userparameter_percona_mysql.conf
/var/lib/zabbix/percona/templates/zabbix_agent_template_percona_mysql_server_ht_2.0.9-sver1.1.6.xml
可以看到他的组成部分有一个shell脚本,一个php脚本,一个zabbix的配置文件,和一个Zabbix 模版的xml。下面我们需要引用配置文件,并在Zabbix上来导入该模版。
cp /var/lib/zabbix/percona/templates/userparameter_percona_mysql.conf /etc/zabbix/zabbix_agentd.d/
授权监控用户
MariaDB [(none)]> grant select,process,replication client on *.* to zabbix@'192.168.10.31' identified by 'zabbixpass';
MariaDB [(none)]> flush privileges;
修改php文件
vim /var/lib/zabbix/percona/scripts/ss_get_mysql_stats.php
$mysql_user = 'zabbix';
$mysql_pass = 'zabbixpass';
修改监控脚本HOST选项,为MySql服务器IP地址
# vim /var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh
HOST=192.168.10.31
# /var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh gg
405647
将/var/lib/zabbix/percona/templates/zabbix_agent_template_percona_mysql_server_ht_2.0.9-sver1.1.6.xml下载到本地,然后在zabbix上导入
下面你就可以给你的MySQL数据库指定监控模板了,由于篇幅原因就不在截图。
因为Zabbix自带的MySQL监控没有提供可以直接使用的Key,所以一般不采用,业界的同学们都使用Percona Monitoring Plugins 监控MySQL的方式。
percona监控zabbix的脚本是使用php写的,所以需要准备好php运行环境,这里直接用yum安装就可以满足要求了:
$ yum install zabbix-agent php php-mysql
$ php -v
PHP 7.0.9 (cli) (built: Jul 20 2016 18:08:08) ( NTS )
$ mysql --version
mysql Ver 14.14 Distrib 5.5.51, for Linux (x86_64) using readline 5.1
$ yum install https://www.percona.com/downloads/percona-monitoring-plugins/1.1.6/percona-zabbix-templates-1.1.6-1.noarch.rpm
也可配置Percona yum仓库后安装,建议配置percona的yum源,因为作为一个DBA你肯定会用到percona toolkit套件。
$ yum install https://www.percona.com/redir/downloads/percona-release/redhat/latest/percona-release-0.1-3.noarch.rpm
$ yum install percona-zabbix-templates
$ rpm -ql percona-zabbix-templates
# 监控获取MySQL状态的;
/var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh
# 配置连接数据库用户名密码的,用shell来调用PHP;
/var/lib/zabbix/percona/scripts/ss_get_mysql_stats.php
# Zabbix-agent端监控MySQL的配置文件;
/var/lib/zabbix/percona/templates/userparameter_percona_mysql.conf
# Zabbix模板文件;
/var/lib/zabbix/percona/templates/zabbix_agent_template_percona_mysql_server_ht_2.0.9-sver1.1.6.xml
把配置文件复制到zabbix-agent目录下。
$ cp /var/lib/zabbix/percona/templates/userparameter_percona_mysql.conf /etc/zabbix/zabbix_agentd.d/
默认zabbix-agent配置文件已经把/etc/zabbix/zabbix_agentd.d/目录include进去了。
$ grep Include /etc/zabbix/zabbix_agentd.conf | grep -v "^#"
Include=/etc/zabbix/zabbix_agentd.d/
# CentOS6
$ service zabbix-agent restart
# CentOS7
$ systemctl restart zabbix-agent
首先如果你要监控的MySQL不是默认的端口3306(是就忽略此步骤),则需要修改/var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh脚本,小心被坑。修改端口号码文件:
# 这里要先建好这个文件,否则会报rm权限错误;
$ touch /tmp/localhost-mysql_cacti_stats.txt:3307
$ chown -R zabbix.zabbix /tmp/localhost-mysql_cacti_stats.txt:3307
# 修改端口脚本;
$ vim /var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh
CACHEFILE="/tmp/$HOST-mysql_cacti_stats.txt "
换成:
CACHEFILE="/tmp/$HOST-mysql_zabbix_stats.txt:3307"
TIMEFLM=`stat -c %Y /tmp/$HOST-mysql_cacti_stats.txt`
换成:
TIMEFLM=`stat -c %Y /tmp/$HOST-mysql_zabbix_stats.txt:3307`
如果你的MySQL是编译安装的,执行客户端命令需要绝对路径的话,你可能还需要更改脚本中的执行命令语句:
RES=`HOME=~zabbix mysql -e 'SHOW SLAVE STATUSG' | egrep '(Slave_IO_Running|Slave_SQL_Running):' | awk -F: '{print $2}' | tr 'n' ','`
换成:
RES=`/usr/local/mysql/bin/mysql -e 'SHOW SLAVE STATUSG' | egrep '(Slave_IO_Running|Slave_SQL_Running):' | awk -F: '{print $2}' | tr 'n' ','`
(创建监控MySQL的用户和密码。
mysql> GRANT SELECT,PROCESS,SUPER,REPLICATION CLIENT ON *.* TO 'zabbixmoniter'@'localhost' IDENTIFIED BY "123456";
mysql> flush privileges;
然后PHP脚本连接MySQL的用户名和密码。
$ cat /var/lib/zabbix/percona/scripts/ss_get_mysql_stats.php
$mysql_user = 'zabbixmoniter';
$mysql_pass = '123456';
$mysql_port = 3306;
测试脚本
$ bash /var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh gg
908
先去percona官网:https://www.percona.com/downloads/percona-monitoring-plugins/
下载Percona Monitoring Plugins最新tar.gz源码包。解压里面有cacti、nagios、zabbix不同监控模块,找到zabbix/templates/目录,然后通过Zabbix Web界面 (Configuration -> Templates -> Import) 导入XML模板,注意要另外选择上Screens。最后配置主机关联上Percona MySQL Server Template模板即可。
针对Zabbix server 3.0导入模板文件时可能会报错:
Invalid tag “/zabbix_export/date”:“YYYY-MM-DDThh:mm:ssZ” is expected.
经过核查,这个原因是模版错误,是因为percona官方上1.1.6还是zabbix2.0的版本模版,所以需要跟新上来匹配zabbix3.0的模版规范,而且这个zabbix模版还是直接copy的cacti模版,比较粗糙,用more查看就可以看到里面都是cacti的内容,可见percona官网对zabbix也不是很重视嘛,至少不如cacti那样重视的。
解决:网上有人在原来的zabbix_agent_template_percona_mysql_server_ht_2.0.9-sver1.1.6_1.xml之上进行修改,改完后就可以import导入进去了,(下载地址:http://jaminzhang.github.io/soft-conf/Zabbix/zbx_percona_mysql_template.xml)。
模板导入进去之后,接下来要做的就是对主机进行添加模板了,当然这属于zabbix的知识了,自行看博客zabbix章节。percona这套MySQL监控项很多,需要好好细看才能理解。
先在mysql slave的agent里面需要check下看是否生效:
$ sh /var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh running-slave
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: NO)
0
所以需要配置下访问mysql的一些基本参数:
$ cat /etc/my.cnf
[mysql]
user=zabbixmoniter
password=ys_ipowerlong0418
socket=/usr/local/mysql/mysql.sock
再check试试,就可以看到了:
$ sh /var/lib/zabbix/percona/scripts/get_mysql_stats_wrapper.sh running-slave
1
经常会遇到一些连接数超标或者mysql链接出现异常(比如表被锁等)导致的报错,此时直接重启不仅影响线上的业务而且指标不治本。
我们处理方式一般是不重启服务的前提下处理异常的表列,同时排查是服务器问题、mysql参数配置问题还是程序、或表本身设计的问题。
比如这里连接数:
[root@21yunwei /]# mysql -uroot -p123456 -e "use 21yunwei;show processlist;"
mysql: [Warning] Using a password on the command line interface can be insecure.
+----+------+-----------+----------+---------+------+----------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+----------+---------+------+----------+------------------+
| 27 | root | localhost | 21yunwei | Query | 0 | starting | show processlist |
+----+------+-----------+----------+---------+------+----------+------------------+
show processlist查看方式:
mysqladmin -uroot -p123456 processlist;
mysql -uroot -p123456 -e "show processlist;"
说明:
如下信息取于酷喃博客:
这个命令中最关键的就是state列,mysql列出的状态主要有以下几种:
大部分状态对应很快的操作,只要有一个线程保持同一个状态好几秒钟,那么可能是有问题发生了,需要检查一下。
参考资料:
mysql手册所有状态说明,链接如下:http://dev.mysql.com/doc/refman/5.0/en/general-thread-states.html
mysql show processlist详解 http://coolnull.com/1230.html
数据库事务有四种隔离级别:
上面这样的教科书式定义第一次接触事务隔离概念的朋友看了可能会一脸懵逼,下面我们就通过具体的实例来解释四个隔离级别。
首先我们创建一个user表:
CREATE TABLE user (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE `uniq_name` USING BTREE (name)
) ENGINE=`InnoDB` AUTO_INCREMENT=10 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
我们先将事务的隔离级别设置为read committed:
mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set (0.00 sec)
在下面我们开了两个终端分别用来模拟事务一和事务二,p.s: 操作一和操作二的意思是按照时间顺序来执行的。
事务1
mysql> start transaction; # 操作1
Query OK, 0 rows affected (0.00 sec)
mysql> insert into user(name) values('ziwenxie'); # 操作3
Query OK, 1 row affected (0.05 sec)
事务2
mysql> start transaction; # 操作2
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user; # 操作4
+----+----------+
| id | name |
+----+----------+
| 10 | ziwenxie |
+----+----------+
1 row in set (0.00 sec)
从上面的执行结果可以和清晰的看出来,在read uncommited级别下面我们在事务一中可能会读取到事务二中没有commit的数据,这就是脏读。
通过设置隔离级别为committed可以解决上面的脏读问题。
mysql> set session transaction isolation level read committed;
事务一
mysql> start transaction; # 操作一
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user; # 操作三
+----+----------+
| id | name |
+----+----------+
| 10 | ziwenxie |
+----+----------+
1 row in set (0.00 sec)
mysql> select * from user; # 操作五,操作四的修改并没有影响到事务一
+----+----------+
| id | name |
+----+----------+
| 10 | ziwenxie |
+----+----------+
1 row in set (0.00 sec)
mysql> select * from user; # 操作七
+----+------+
| id | name |
+----+------+
| 10 | lisi |
+----+------+
1 row in set (0.00 sec)
mysql> commit; # 操作八
Query OK, 0 rows affected (0.00 sec)
事务二
mysql> start transaction; # 操作二
Query OK, 0 rows affected (0.00 sec)
mysql> update user set name='lisi' where id=10; # 操作四
Query OK, 1 row affected (0.06 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit; # 操作六
Query OK, 0 rows affected (0.08 sec)
虽然脏读的问题解决了,但是注意在事务一的操作七中,事务二在操作六commit后会造成事务一在同一个transaction中两次读取到的数据不同,这就是不可重复读问题,使用第三个事务隔离级别repeatable read可以解决这个问题。
MySQL的Innodb存储引擎默认的事务隔离级别就是可重复读隔离级别,所以我们不用进行多余的设置。
事务一
mysql> start tansactoin; # 操作一
mysql> select * from user; # 操作五
+----+----------+
| id | name |
+----+----------+
| 10 | ziwenxie |
+----+----------+
1 row in set (0.00 sec)
mysql> commit; # 操作六
Query OK, 0 rows affected (0.00 sec)
mysql> select * from user; # 操作七
+----+------+
| id | name |
+----+------+
| 10 | lisi |
+----+------+
1 row in set (0.00 sec)
事务二
mysql> start tansactoin; # 操作二
mysql> update user set name='lisi' where id=10; # 操作三
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> commit; # 操作四
在事务一的操作五中我们并没有读取到事务二在操作三中的update,只有在commit之后才能读到更新后的数据。
实际上RR级别是可能产生幻读,InnoDB引擎官方称中利用MVCC多版本并发控制解决了这个问题,下面我们验证一下Innodb真的解决了幻读了么?
为了方便展示,我修改了一下上面的user表:
mysql> alter table user add salary int(11);
Query OK, 0 rows affected (0.51 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> delete from user;
Query OK, 1 rows affected (0.07 sec)
mysql> insert into user(name, salary) value('ziwenxie', 88888888);
Query OK, 1 row affected (0.07 sec)
mysql> select * from user;
+----+----------+----------+
| id | name | salary |
+----+----------+----------+
| 10 | ziwenxie | 88888888 |
+----+----------+----------+
1 row in set (0.00 sec)
事务一
mysql> start transaction; # 操作一
Query OK, 0 rows affected (0.00 sec)
mysql> update user set salary='4444'; # 操作六,竟然影响了两行,不是说解决了幻读么?
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0
mysql> select * from user; # 操作七, Innodb并没有完全解决幻读
+----+----------+--------+
| id | name | salary |
+----+----------+--------+
| 10 | ziwenxie | 4444 |
| 11 | zhangsan | 4444 |
+----+----------+--------+
2 rows in set (0.00 sec)
mysql> commit; # 操作八
Query OK, 0 rows affected (0.04 sec)
事务二
mysql> start transaction; # 操作二
Query OK, 0 rows affected (0.00 sec)
mysql> insert into user(name, salary) value('zhangsan', '666666'); # 操作四
Query OK, 1 row affected (0.00 sec)
mysql> commit; # 操作五
Query OK, 0 rows affected (0.04 sec)
从上面的例子可以看出,Innodb并没有如官方所说解决幻读,不过上面这样的场景中也不是很常见不用过多的担心。
所有事务串行执行,最高隔离级别,不会出现幻读性能会很差,实际开发中很少使用到。
在数据库服务器上安装包:
yum –y install mariadb-server mariadb-devel
systemctl start mariadb.service
systemctl enable mariadb
在FTP服务器上安装包pam_mysql
centos7:无对应rpm包,需手动编译安装
yum -y groupinstall “Development Tools”
yum -y install mariadb-devel pam-devel vsftpd
下载pam_mysql-0.7RC1.tar.gz
ftp://172.16.0.1/pub/Sources/sources/pam/
tar xvfpam_mysql-0.7RC1.tar.gz
cd pam_mysql-0.7RC1/
./configure –with-mysql=/usr –with-pam=/usr –with-pam-mods-dir=/lib64/security
make
make install
示例创建为vsftpd数据库
mysql> CREATE DATABASE vsftpd;
mysql> SHOW DATABASES;
ftp服务和mysql不在同一主机:
mysql> GRANT SELECT ON vsftpd.* TO
vsftpd@’172.16.%.%’ IDENTIFIED BY ‘magedu’;
ftp服务和mysql在同一主机:
mysql> GRANT SELECT ON vsftpd.* TO
vsftpd@localhost IDENTIFIED BY ‘magedu’;
mysql> GRANT SELECT ON vsftpd.* TO
vsftpd@’127.0.0.1′ IDENTIFIED BY ‘magedu’;
mysql> FLUSH PRIVILEGES;
mysql> USE vsftpd;
Mysql> SHOW TABLES;
mysql> CREATE TABLE users ( id INT AUTO_INCREMENT NOT NULL PRIMARY KEY,name CHAR(50) BINARY NOT NULL,password CHAR(48) BINARY NOT NULL);
mysql>DESC users;
mysql -uvsftpd -h 172.16.200.200 -pmagedu
mysql> SHOW DATABASES;
根据需要添加所需要的用户,为了安全应该使用PASSWORD函数加密其密码后存储
mysql>DESC users;
mysql> INSERT INTO users(name,password) values(‘wang’,password(‘magedu’));
mysql> INSERT INTO users(name,password) values(‘mage’,password(‘magedu’));
mysql> SELECT * FROM users;
vi /etc/pam.d/vsftpd.mysql
添加如下两行
auth required pam_mysql.so user=vsftpd passwd=magedu host=mysqlserver db=vsftpd table=users usercolumn=name passwdcolumn=password crypt=2
account required pam_mysql.so user=vsftpd passwd=magedu host=mysqlserver db=vsftpd table=users usercolumn=name passwdcolumn=password crypt=2
注意:参考README文档,选择正确的加密方式crypt是加密方式,0表示不加密,1表示crypt(3)加密,2表示使用mysql password()函数加密,3表示md5加密,4表示sha1加密
配置字段说明
使其适应mysql认证
建立虚拟用户映射的系统用户及对应的目录
useradd -s /sbin/nologin -d /var/ftproot vuser
chmod 555 /var/ftproot centos7 需除去ftp根目录的写权限
mkdir /var/ftproot/{upload,pub}
setfacl –m u:vuser:rwx /var/ftproot/upload
确保/etc/vsftpd.conf中已经启用了以下选项
anonymous_enable=YES
添加下面两项
guest_enable=YES
guest_username=vuser
修改下面一项,原系统用户无法登录
pam_service_name=vsftpd.mysql
service vsftpd start;systemctl start vsftpd
chkconfig vsftpd on;systemctl enable vsftpd
查看端口开启情况
netstat -tnlp |grep :21
restorecon -R /lib64/security
setsebool -P ftpd_connect_db 1
setsebool -P ftp_home_dir 1
chcon -R -t public_content_rw_t /var/ftproot/
tail /var/log/secure
vsftpd可以在配置文件目录中为每个用户提供单独的配置文件以定义其ftp服务访问权限,每个虚拟用户的配置文件名同虚拟用户的用户名。配置文件目录可以是任意未使用目录,只需要在vsftpd.conf指定其路径及名称即可
vim /etc/vsftpd/vsftpd.conf
添加如下选项
user_config_dir=/etc/vsftpd/vusers_config
并为虚拟用户提供配置文件
mkdir /etc/vsftpd/vusers_config/
cd /etc/vsftpd/vusers_config/
touch wangmage
虚拟用户对vsftpd服务的访问权限是通过匿名用户的相关指令进行的。如果需要让用户wang具有上传文件的权限,可以修改/etc/vsftpd/vusers_config/wang文件,在里面添加如下选项并设置为YES即可,只读则设为NO
注意:需确保对应的映射用户对于文件系统有写权限
anon_upload_enable={YES|NO}
anon_mkdir_write_enable={YES|NO}
anon_other_write_enable={YES|NO}
MySQL是一个强大的开源数据库。随着MySQL上的应用越来越多,MySQL逐渐遇到了瓶颈。这里提供 101 条优化 MySQL 的建议。有些技巧适合特定的安装环境,但是思路是相通的。我已经将它们分成了几类以帮助你理解。
1、有足够的物理内存,能将整个InnoDB文件加载到内存里 —— 如果访问的文件在内存里,而不是在磁盘上,InnoDB会快很多。
2、全力避免 Swap 操作 — 交换(swapping)是从磁盘读取数据,所以会很慢。
3、使用电池供电的RAM(Battery-Backed RAM)。
4、使用一个高级磁盘阵列 — 最好是 RAID10 或者更高。
5、避免使用RAID5 — 和校验需要确保完整性,开销很高。
6、将你的操作系统和数据分开,不仅仅是逻辑上要分开,物理上也要分开 — 操作系统的读写开销会影响数据库的性能。
7、将临时文件和复制日志与数据文件分开 — 后台的写操作影响数据库从磁盘文件的读写操作。
8、更多的磁盘空间等于更高的速度。
9、磁盘速度越快越好。
10、SAS优于SATA。
11、小磁盘的速度比大磁盘的更快,尤其是在 RAID 中。
12、使用电池供电的缓存 RAID(Battery-Backed Cache RAID)控制器。
13、避免使用软磁盘阵列。
14、考虑使用固态IO卡(不是磁盘)来作为数据分区 — 几乎对所有量级数据,这种卡能够支持 2 GBps 的写操作。
15、在 Linux 系统上,设置 swappiness 的值为0 — 没有理由在数据库服务器上缓存文件,这种方式在Web服务器或桌面应用中用的更多。
16、尽可能使用 noatime 和 nodirtime 来挂载文件系统 — 没有必要为每次访问来更新文件的修改时间。
17、使用 XFS 文件系统 — 一个比ext3更快的、更小的文件系统,拥有更多的日志选项,同时,MySQL在ext3上存在双缓冲区的问题。
18、优化你的 XFS 文件系统日志和缓冲区参数 – -为了获取最大的性能基准。
19、在Linux系统中,使用 NOOP 或 DEADLINE IO 调度器 — CFQ 和 ANTICIPATORY 调度器已经被证明比 NOOP 和 DEADLINE 慢。
20、使用 64 位操作系统 — 有更多的内存能用于寻址和 MySQL 使用。
21、将不用的包和后台程序从服务器上删除 — 减少资源占用。
22、将使用 MySQL 的 host 和 MySQL自身的 host 都配置在一个 host 文件中 — 这样没有 DNS 查找。
23、永远不要强制杀死一个MySQL进程 — 你将损坏数据库,并运行备份。
24、让你的服务器只服务于MySQL — 后台处理程序和其他服务会占用数据库的 CPU 时间。
25、使用 innodb_flush_method=O_DIRECT 来避免写的时候出现双缓冲区。
26、避免使用 O_DIRECT 和 EXT3 文件系统 — 这会把所有写入的东西序列化。
27、分配足够 innodb_buffer_pool_size ,来将整个InnoDB 文件加载到内存 — 减少从磁盘上读。
28、不要让 innodb_log_file_size 太大,这样能够更快,也有更多的磁盘空间 — 经常刷新有利降低发生故障时的恢复时间。
29、不要同时使用 innodb_thread_concurrency 和 thread_concurrency 变量 — 这两个值不能兼容。
30、为 max_connections 指定一个小的值 — 太多的连接将耗尽你的RAM,导致整个MySQL服务器被锁定。
31、保持 thread_cache 在一个相对较高的数值,大约是 16 — 防止打开连接时候速度下降。
32、使用 skip-name-resolve — 移除 DNS 查找。
33、如果你的查询重复率比较高,并且你的数据不是经常改变,请使用查询缓存 — 但是,在经常改变的数据上使用查询缓存会对性能有负面影响。
34、增加 temp_table_size — 防止磁盘写。
35、增加 max_heap_table_size — 防止磁盘写。
36、不要将 sort_buffer_size 的值设置的太高 — 可能导致连接很快耗尽所有内存。
37、监控 key_read_requests 和 key_reads,以便确定 key_buffer 的值 — key 的读需求应该比 key_reads 的值更高,否则使用 key_buffer 就没有效率了。
38、设置 innodb_flush_log_at_trx_commit = 0 可以提高性能,但是保持默认值(1)的话,能保证数据的完整性,也能保证复制不会滞后。
39、有一个测试环境,便于测试你的配置,可以经常重启,不会影响生产环境。
MySQL Schema 优化:
40、保证你的数据库的整洁性。
41、归档老数据 — 删除查询中检索或返回的多余的行
42、在数据上加上索引。
43、不要过度使用索引,评估你的查询。
44、压缩 text 和 blob 数据类型 — 为了节省空间,减少从磁盘读数据。
45、UTF 8 和 UTF16 比 latin1 慢。
46、有节制的使用触发器。
47、保持数据最小量的冗余 — 不要复制没必要的数据.
48、使用链接表,而不是扩展行。
49、注意你的数据类型,尽可能的使用最小的。
50、如果其他数据需要经常需要查询,而 blob/text 不需要,则将 blob/text 数据域其他数据分离。
51、经常检查和优化表。
52、经常做重写 InnoDB 表的优化。
53、有时,增加列时,先删除索引,之后在加上索引会更快。
54、为不同的需求选择不同的存储引擎。
55、日志表或审计表使用ARCHIVE存储引擎 — 写的效率更高。
56、将 session 数据存储在 memcache 中,而不是 MySQL 中 — memcache 可以设置自动过期,防止MySQL对临时数据高成本的读写操作。
57、如果字符串的长度是可变的,则使用VARCHAR代替CHAR — 节约空间,因为CHAR是固定长度,而VARCHAR不是(utf8 不受这个影响)。
58、逐步对 schema 做修改 — 一个小的变化将产生的巨大的影响。
59、在开发环境测试所有 schema 变动,而不是在生产环境的镜像上去做。
60、不要随意改变你的配置文件,这可能产生非常大的影响。
61、有时候,少量的配置会更好。
62、质疑使用通用的MySQL配置文件。
63、使用慢查询日志,找出执行慢的查询。
64、使用 EXPLAIN 来决定查询功能是否合适。
65、经常测试你的查询,看是否需要做性能优化 — 性能可能会随着时间的变化而变化。
66、避免在整个表上使用count(*) ,它可能会将整个表锁住。
67、保持查询一致,这样后续类似的查询就能使用查询缓存了。
68、如果合适,用 GROUP BY 代替 DISTINCT。
69、在 WHERE、GROUP BY 和 ORDER BY 的列上加上索引。
70、保证索引简单,不要在同一列上加多个索引。
71、有时,MySQL 会选择错误的索引,这种情况使用 USE INDEX。
72、使用 SQL_MODE=STRICT 来检查问题。
73、索引字段少于5个时,UNION 操作用 LIMIT,而不是 OR。
74、使用 INSERT ON DUPLICATE KEY 或 INSERT IGNORE 来代替 UPDATE,避免 UPDATE 前需要先 SELECT。
75、使用索引字段和 ORDER BY 来代替 MAX。
76、避免使用 ORDER BY RAND()。
77、LIMIT M,N 在特定场景下会降低查询效率,有节制使用。
78、使用 UNION 来代替 WHERE 子句中的子查询。
79、对 UPDATE 来说,使用 SHARE MODE 来防止排他锁。
80、重启 MySQL 时,记得预热数据库,确保将数据加载到内存,提高查询效率。
81、使用 DROP TABLE ,然后再 CREATE TABLE ,而不是 DELETE FROM ,以删除表中所有数据。
82、最小化你要查询的数据,只获取你需要的数据,通常来说不要使用 *。
83、考虑持久连接,而不是多次建立连接,已减少资源的消耗。
84、基准查询,包括服务器的负载,有时一个简单的查询会影响其他的查询。
85、当服务器的负载增加时,使用SHOW PROCESSLIST来查看慢的/有问题的查询。
86、在存有生产环境数据副本的开发环境中,测试所有可疑的查询。
87、在二级复制服务器上进行备份。
88、备份过程中停止数据的复制,以防止出现数据依赖和外键约束的不一致。
89、彻底停止MySQL之后,再从数据文件进行备份。
90、如果使用MySQL dump进行备份,请同时备份二进制日志 — 确保复制过程不被中断。
91、不要信任 LVM 快照的备份 — 可能会创建不一致的数据,将来会因此产生问题。
92、为每个表做一个备份,这样更容易实现单表的恢复 — 如果数据与其他表是相互独立的。
93、使用 mysqldump 时,指定 -opt 参数。
94、备份前检测和优化表。
95、临时禁用外键约束,来提高导入的速度。
96、临时禁用唯一性检查,来提高导入的速度。
97、每次备份完后,计算数据库/表数据和索引的大小,监控其增长。
98、使用定时任务(cron)脚本,来监控从库复制的错误和延迟。
99、定期备份数据。
100、定期测试备份的数据。
这是同事分享给我的。安装数据库也是一门学问,千万不要被安装的简单性而忽略一些细节。针对于 Win os 服务器而言,MySQL 的安装版可以选则三种不同的服务器类型:
Server Machine(服务器)
为 MySQL 分配一定比例的系统资源
Dedicated MySQL Server Machine(专用MySQL服务器)
为 MySQL 分配所有的系统资源
如果你跟我一样不幸,不仅选择了 Win os 做服务器系统,还选择了 Developer Machine(开发机器),兄弟抱一个,不要哭,重装。发生这些上述不幸的原因已经不重要,需要做的是必须切换成 Dedicated MySQL Server Machine(专用MySQL服务器)。
重装切换之后,你会发现,之前安装的一定是假的 MySQL。
主键或者唯一键可以用作某条记录的唯一标志符。主键生成有两种方式:
自增方式有个重要因素是“步长”,也就是则增的幅度,在单机模式下,一般步长为1。若是在分布式数据库系统下,步长设为节点的数量,这样一来,就可以避免主键重复的情况。建议预估好节点的数量,步长不可小于节点数。
UUID 可以更有效地避免自增主键带来的烦恼,但是它也有不足之处:
针对上述 UUID 的缺点,推特开发了“雪花算法”,并开源。其中心思想是利用时间戳、数据中心码、机器码、序列号组成有规则的 UUID,使其有序降低性能消耗。
我在项目中使用了“自增+步长2”,因为使用了主从,虽然不是分布式,但是双数据源也是两个节点,采用这种方式保险一些。
更多内容请移步我的其他博文:数据库自增长主键与-UUID (http://zhoupq.com/%E6%95%B0%E6%8D%AE%E5%BA%93%E8%87%AA%E5%A2%9E%E9%95%BF%E4%B8%BB%E9%94%AE%E4%B8%8E-UUID/)
推荐 MySQL 使用自增ID主键和UUID 作为主键的优劣比较详细过程(从百万到千万表记录测试) (http://blog.csdn.net/mchdba/article/details/52336203)
我一直秉承杀鸡就用杀鸡刀,宰牛采用宰牛刀的原则。一个“tab_NAME” 的数据类型非要整一个 “VARCHAR(500)”,这是浪费,过多的长度分配会造成空间占用太多,最终造成性能下降。有同事说我杞人忧天,一个库才二十几张表,即使每个字段都设成500,也不过如此嘛。从短期的结果上来看,结果没有受到明显影响。但是别忘了,我们是来解决问题的,如果因为我们的操作违反了约定,造成严重后果,那么我们将背负罪过。“量身定做”的好处不言而喻,列的长度亦是如此。
尽量不将列设为 NULL,从业务角度上看,NULL 是错误的,试想,既然是 NULL,哪有何必存在这个列呢?反之,既然存在这个列,那么 NULL 便失去了意义。让我设计表的时候,我都会给列设置一个初始值,“tab_UPDATETIME” 就设置为 “CURRENT_TIMESTAMP”,“tab_STATUS” 就设置为 “1”或者“0”。
从开发维护的角度看,如果不确定列是否为 NULL,那么在 SQL 中,就必须加上 “AND tab_NAME != NULL AND tab_NAME != ‘’”,很容易被忽略,代码越多,出错的概率就越大。
好的索引是一颗仙丹,可以让迟缓的查询得到质的提升,否则,就是一碗毒药。
索引我做了三点优化:
索引不是越多越好,因为生成索引需要时间,而且索引占表物理空间。表一大,查询速度多少会受影响。我亲眼看到同事建好表时候,无微不至地为每一个字段都建了索引,或者为每一个条件都建了索引,形如条件“a=1 and b =2 and c=3 and d=4”,为其建了“a”、“b”、“c”、“d”四个索引。浪费!低效!这种情况,应当使用“最左前缀索引”。
在生成联合索引时会碰到最左前缀索引,什么是最左前缀索引呢?就是在联合索引中,从最左边的索引开始匹配,直到遇上“like”、“>”、“>=”、“<”、“<=”等范围匹配时停止,即使后面有“=”都不再匹配。
简单举例:现有字段“a”、“b”、“c”、“d”组成的联合索引“abcd”,SQL 条件部分为:
a=1 and b =2 and c=3 and d=4
a=1 and b =2 and d=4 and c=3
a=1 and b =2 and c>=3 and d=4
1 用到索引为“abcd”,2 用到的索引为“abd”, (2 同 1)3 用到的索引为“abc”。条件的顺序很重要。跟自拍一样,脸大的站后面。
抱歉,上述第二点同第一点,同样用到的索引为“abcd”。
利用 EXPLAIN 工具分析:
// 建表语句略,已知创建了组合索引 (abcd)
mysql> EXPLAIN SELECT * FROM test t WHERE t.a = 'q' AND t.b = 'w' AND t.c = 'e' AND t.d = 'f';
+----+-------------+-------+------+---------------+------+---------+-------------------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------------------------+------+--------------------------+
| 1 | SIMPLE | t | ref | name | name | 360 | const,const,const,const | 1 | Using where; Using index |
+----+-------------+-------+------+---------------+------+---------+-------------------------+------+--------------------------+
1 row in set
mysql> EXPLAIN SELECT * FROM test t WHERE t.a = 'q' AND t.b = 'w' AND t.d = 'f' AND t.c = 'e';
+----+-------------+-------+------+---------------+------+---------+-------------------------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------------------------+------+--------------------------+
| 1 | SIMPLE | t | ref | name | name | 360 | const,const,const,const | 1 | Using where; Using index |
+----+-------------+-------+------+---------------+------+---------+-------------------------+------+--------------------------+
1 row in set
前缀索引是针对某个字段而言,我们知道 MySQL 中,有一个全文索引,“BOLD”、“TEXT” 是不能建全文索引。想想也能理解,这么长的全文索引得占多少空间,这显然是不现实的。最好的办法是为其创建“左前缀索引”,只取字符串的前面一小段作为索引,具体取多少,决定于多长的字符可以尽可能多的确定唯一记录。“varchar”同样受用。
更多内容请移步我的其他博文:MySQL 高性能索引之前缀索引 (http://zhoupq.com/MySQL-%E9%AB%98%E6%80%A7%E8%83%BD%E7%B4%A2%E5%BC%95%E4%B9%8B%E5%89%8D%E7%BC%80%E7%B4%A2%E5%BC%95/)
不管是 Heibernate 在代码中拼 SQL,还是 MyBatis 在 Mapper.xml 中写 SQL,由于数据库范式的规范,导致为完成某项查询,必须联合多表查询。一个 LEFT JOIN 很常见,三四个 LEFT JOIN 呢?
多个 LEFT JOIN 肯定不行,即使有索引,也很容易造成全表扫描,为了减少该情况发生的概率,我一般会采取两种方法:
衡量一个 DBA 的水平有多高,得看他反范式能力有多强。
—— 知乎
比如我要根据 A表 的日期,关联 B表,统计出每个日期下某个属性的数量。我可以在A表中添加一列,用来存储“数量”,虽然违反了范式,但是性能上得到了提升。我觉得这是一笔划算的买卖。
规范化是为了技术服务,而技术是为业务服务。规范化也就是套路,能保证不出错,但是并不能解决特殊问题,特殊问题还需要特殊处理。
当需要联合三张表以上时,轻微的反范式已经不适用了,推荐用临时表,或者物化视图,但是 MySQL 的物化视图实现起来比较困难。事实上,我用的就是临时表,将四张表的部分数据抽离出来,保存在一张临时表中,制定一个“计划”,每天凌晨会自动更新。
好处
缺点
以上是仅针对数据库做的优化,至于缓存(一级缓存、二级缓存),那属于持久层框架的职责,不在此文记录范围之内。
mysql 自行安装
Lua 相关包
LuaJIT (下载 http://luajit.org/download.html)
ngx_devel_kit (下载 https://github.com/simpl/ngx_devel_kit/tags)
ngx_lua (下载 https://github.com/openresty/lua-nginx-module/tags)
pcre (下载 https://sourceforge.net/projects/pcre/files/pcre/)
lua_mysql (下载 https://github.com/openresty/lua-resty-mysql)
tar zxf LuaJIT-2.0.5.tar.gz
cd LuaJIT-2.0.5
make
make install PREFIX=/home/myself/lj2
tar -zxvf pcre-8.32.tar.gz
cd pcre-8.32
make
make install
export LUAJIT_LIB=/path/to/luajit/lib
export LUAJIT_INC=/path/to/luajit/include/luajit-2.1
./configure --prefix=/opt/nginx
--with-ld-opt="-Wl,-rpath,/path/to/luajit-or-lua/lib"
--add-module=/path/to/ngx_devel_kit
--add-module=/path/to/lua-nginx-module
make
make install
user root;
worker_processes 2;
events {
worker_connections 1024;
}
http{
lua_package_path "/home/oicq/guomm/nginx_lua/lua-resty-mysql-master/lib/?.lua;;"; --重要
lua_shared_dict logs 10m;
init_worker_by_lua_block {
local delay = 10
function put_log_into_mysql(premature)
local mysql = require "resty.mysql"
local db, err = mysql:new()
if not db then
ngx.log(ngx.ERR,"failed to instantiate mysql: ", err)
return
end
db:set_timeout(1000)
local ok, err, errcode, sqlstate = db:connect{
host = "xxx",
port = 3306,
database = "database_name",
user = "username",
password = "password",
charset = "utf8",
}
if not ok then
ngx.log(ngx.ERR,"failed to connect: ", err, ": ", errcode, " ", sqlstate)
return
end
-- get data from shared dict and put them into mysql
local key = "logs"
local vals = ""
local temp_val = ngx.shared.logs:lpop(key)
while (temp_val ~= nil)
do
vals = vals .. ",".. temp_val
temp_val = ngx.shared.logs:lpop(key)
end
if vals ~= "" then
vals = string.sub(vals, 2,-1)
local command = ("insert into es_visit_record(access_ip,server_ip,access_time,run_time,es_response_time,request_body_byte,run_state,url,post_data) values "..vals)
ngx.log(ngx.ERR,"command is ",command)
local res, err, errcode, sqlstate = db:query(command)
if not res then
ngx.log(ngx.ERR,"insert error: ", err, ": ", errcode, ": ", sqlstate, ".")
return
end
end
local ok, err = db:close()
if not ok then
ngx.log(ngx.ERR,"failed to close: ", err)
return
end
-- decycle call timer to run put_log_into_mysql method, just like crontab
local ok, err = ngx.timer.at(delay, put_log_into_mysql);
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
return
end
end
local ok, err = ngx.timer.at(delay, put_log_into_mysql)
if not ok then
ngx.log(ngx.ERR, "failed to create timer: ", err)
return
end
}
upstream elasticsearch_servers {
server xxx max_fails=3 fail_timeout=30s;
server xxx max_fails=3 fail_timeout=30s;
server xx max_fails=3 fail_timeout=30s;
}
log_format porxy '$remote_addr,$upstream_addr,[$time_local],$request,$request_body,$status,$body_bytes_sent,$request_time,$upstream_response_time';
server {
listen 9202;
location / {
proxy_pass http://elasticsearch_servers;
log_by_lua_block{
local currentTime = os.date("%Y-%m-%d %H:%M:%S", os.time())
currentTime = """ .. currentTime .. """
local req_body = '-'
if ngx.var.request_body then
req_body = ngx.var.request_body
req_body = string.gsub(req_body,"n","")
--req_body = string.gsub(req_body,"t","")
end
req_body = """ .. req_body .. """
local req_status = 0
if ngx.var.status then
req_status = ngx.var.status
end
local req_time = 0
if ngx.var.request_time then
req_time = ngx.var.request_time
end
local req_req = """ .. ngx.var.request .. """
local remote_addr = """ .. ngx.var.http_x_forwarded_for .. """
local server_addr = """ .. ngx.var.upstream_addr .. """
local myparams = ("("..remote_addr..",".. server_addr..","..currentTime..","..ngx.var.request_time .. ",".. ngx.var.upstream_response_time..","..ngx.var.body_bytes_sent..","..ngx.var.status..","..req_req..","..req_body..")")
local key = "logs"
local len,err = ngx.shared.logs:rpush(key, myparams)
if err then
ngx.log(ngx.ERR,"failed to put log vals into shared dict")
return
end
}
}
access_log logs/es_access.log porxy;
}
}
本配置主要解决Nginx向MySQL中实时插入日志的问题。
1、刚开始的时候看了Nginx和mysql的连接模块。比如说nginx-mysql-module,可以连接mysql。但是插入日志时遇到问题,我们知道nginx的执行过程先是location解析并重写阶段,然后是访问权限控制阶段,接着是内容生成阶段,最后是日志记录阶段。mysql访问阶段属于内容生成阶段,所以代理运行的时间和状态,mysql都无法获取的到。因此,这种通过nginx直连mysql的方式无法达到我们的要求。
2、通过lua脚本在日志生成阶段获取信息,然后将数据插入mysql。nginx有一个限制,无法在log阶段访问socket即无法访问mysql,所以无法在log阶段直接将数据存入mysql。但是可以通过运行包含mysql操作的shell脚本来解决这个问题。但是这个方法有两个弊端:
不向mysql插入数据,整个时间的消耗大约在0.02-0.04s之间。而向mysql插入数据后,整个时间消耗大约在0.4-0.9之间,消耗的时间是原来的10倍。
3、通过lua + ngx.time.at + lua_mysql + lua.share.dict 解决问题。整个过程如下所示:
在日志生成阶段,将数据封装并存入nginx的内存共享区。
不但访问Mysql的Mysql用户需要有操作对应数据库的权限,还需要调用Mysql命令的用户具有访问mysql的权限。授权命令如下:
GRANT ALL PRIVILEGES ON *.* to root@xxx IDENTIFIED BY 'password';
总的来说,Mysql的数据库对应三种编码。Mysql客户端显示数据的编码,连接Mysql用的编码(即数据存入mysql时,数据的编码),Mysql存储用的编码(字段,表,数据库三种格式可能不同)。不管Mysql存储用的编码是什么,只要Mysql客户端显示数据的编码和连接Mysql用的编码相同,数据就能通过mysql客户端正确显示。