MySQL 主从复制实践

MySQL 主从复制是一个通过自动将主库数据复制到从数据库的过程,使得用户可以轻松维护数据的多个副本。多副本不仅可以增强数据的安全性,通过实现读写分离还能提升数据库的负载能力。本文试图详尽地描述主从复制的过程。

本文使用的主机如下:

未分类

安装MySQL

这里简单提一下CentOS安装MySQL的过程,原因有二:

  1. CentOS7 发行版中的源默认为MariaDB
  2. MySQL 官方的安装文档有些晦涩,这部分内容方便笔者后续查看

如果读者对MySQL的安装非常了解,请跳过该部分内容 🙂

## 从官网 https://dev.mysql.com/downloads/repo/yum/ 下载相应系统对应MySQL版本的Yum源
## 这里可能让人疑惑的是没有显示标明CentOS应该下载哪个,Red Hat Enterprise Linux 的即可
## 另外一个可能疑惑的地方是只有57版本的repo packages, 其实它包含了该发行版可用的所有
## MySQL版本,只不过默认启用的版本为5.7,可使用`yum repolist all | grep mysql` 查看
[root@master ~]# wget https://dev.mysql.com/get/mysql57-community-release-el7-11.noarch.rpm

## 使用如下命令安装源
[root@master ~]# sudo rpm -Uvh mysql57-community-release-el7-11.noarch.rpm
## 或
[root@master ~]# sudo yum localinstall mysql57-community-release-el7-11.noarch.rpm

## 弃用5.7版本,启用5.6版本
[root@master ~]# sudo yum-config-manager --disable mysql57-community
[root@master ~]# sudo yum-config-manager --enable mysql56-community

## 查看启用的MySQL源
[root@master ~]# yum repolist enabled | grep "mysql.*-community.*"
!mysql-connectors-community/x86_64 MySQL Connectors Community                 42
!mysql-tools-community/x86_64      MySQL Tools Community                      55
!mysql56-community/x86_64          MySQL 5.6 Community Server                361

## 设置好源之后使用如下命令来安装MySQL
[root@master ~]# sudo yum install mysql-community-server
## 查看版本
[root@master ~]# mysqld -V
mysqld  Ver 5.6.38 for Linux on x86_64 (MySQL Community Server (GPL))

## 启动并设置开机启动
[root@master ~]# systemctl start mysqld.service
[root@master ~]# sudo systemctl enable mysqld.service

## Securing the MySQL Installation
## 在安装5.6版本时需要进行该操作,根据提示设置root密码,删除匿名用户等
## 5.7版本需要不同的操作,详见https://dev.mysql.com/doc/mysql-yum-repo-quick-guide/en/
[root@master ~]# mysql_secure_installation

以上是 MySQL 5.6 的安装过程,安装完成后往往还需要修改配置以获取较优的性能:

数据库配置

/etc/my.cnf

## [client] option group is read by all client programs provided in MySQL distributions (but not by mysqld)
[client]
default-character-set = utf8

## [mysql] option group apply specifically to mysql client program
[mysql]
# 更改默认字符集以免引发乱码
default-character-set = utf8

## [mysqld] option group apply specifically to mysqld server program
[mysqld]
# Typical values are 5-6GB (8GB RAM), 20-25GB (32GB RAM), 100-120GB (128GB RAM)
innodb_buffer_pool_size = 6G

# 客户端最大并发连接数量,default: 151
max_connections = 1000

# 在检查客户端连接时,不要解析主机名,只使用IP地址
# 该选项要求grant表中的所有主机值必须是IP地址或localhost
skip-name-resolve

# 在Windows或OS X系统中,文件系统不区分大小写
# 设置为1,表文件全部以小写命名
lower_case_table_names = 1

# Server允许发送和接收的最大消息包大小,default: 4MB
# 使用大的BLOB列或长字符串,需要增加该值,它应该和你要使用的最大BLOB一样大
max_allowed_packet = 20M

# 设置字符集为 utf8
character-set-server = utf8
# 每个客户端连接数据库之后首先执行的一条命令,也是为了查询到乱码
init_connect = 'SET NAMES utf8'
# 可以使用 `show collation;` 来查看每个字符集可用的排序规则
# `show variables like "%character%";show variables like "%collation%";` 来查看当前设置的字符集及排序规则
# ci => case insensitive
collation-server = utf8_unicode_ci

## omit other default options and option group
...

注:所有可配置的选项都可以通过相应的命令查看,如 mysqld –verbose –help、mysql –verbose –help。或查看手册 Server Option and Variable Reference、mysql Options。

Master/Slave Setup

回到正题。在讨论设置主从复制的细节之前,我们先简单了解一下 MySQL 是如何复制数据的,直观上,复制包括三个过程:

  1. 主节点将数据的变动记录到 binary log (这些记录被称作 binary log events)
  2. 从节点通过网络将主节点的 binary log events 复制到从节点的 relay log
  3. 从节点重放(replay) relay log 中的事件,将这些变动应用到从节点的数据上

下图显示了这一过程:

未分类

更细节的部分可翻阅《High Performance MySQL, 3rd Edition》第10章进行查看。

配置主节点

master /etc/my.cnf

server-id = 1
log-bin = mysql-bin
# 当InnoDB存储引擎需要处理事务,为了尽可能满足持久性和一致性,应该设置如下两项
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1

需要在主节点上设置一个可供从节点连接的账号,并赋予相应的权限:

mysql> GRANT REPLICATION SLAVE ON *.* TO slave@'10.0.63.%' IDENTIFIED BY 'p4ssword';

然后把主库的数据使用 mysqldump 保存到一个文件中:

## -A => --all-databases
## --skip-lock-tables => --opt 是 --add-drop-table --add-locks --create-options 
##                       --disable-keys --extended-insert --lock-tables --quick
##                       --set-charset 选项的组合,默认是生效的,当使用 InnoDB
##                       时,--single-transation 是一个比 --lock-tables 更好的选
##                       项,因此使用 --skip-lock-tables 来禁掉 --lock-tables
## --single-transaction => 通过将导出操作封装在一个事务内来使得导出的数据是一个
##                         一致性快照, 依赖 InnoDB 的 MVCC 机制。
## --flush-logs => 导出之前先刷新服务器日志文件
## --hex-blog => 使用十六进制表示法导出二进制(如:'abc' 导出为 0x616263)
## --master-data=2 => 将 binlog 的坐标作为注释记录到导出文件中,用于后续操作
## 以上参数详见 https://dev.mysql.com/doc/refman/5.6/en/mysqldump.html
[root@master ~]# mysqldump -uroot -p --skip-lock-tables --single-transaction --flush-logs 
--hex-blob --master-data=2 -A  > all-databases.sql

上面的 –single-transaction 和 –master-data=2 选项组合在导出数据前做了如下几件事:

  1. FLUSH TABLES WITH READ LOCK;
  2. SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  3. START TRANSACTION;
  4. SHOW MASTER STATUS;
  5. UNLOCK TABLES;

什么意思呢?就是说这条命令在运行的时候既保证保证了导出的数据是 binary log 坐标(MASTER_LOG_FILE, MASTER_LOG_POS)位置的数据库快照,又不影响后续写命令的执行。

由于上述命令将 binlog 坐标作为注释记录在了 all-databases.sql 文件中,因此可以使用如下命令获取:

[root@master ~]# head all-databases.sql -n80 | grep "MASTER_LOG_POS"
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000002', MASTER_LOG_POS=120;

然后将 all-databases.sql 传输到从节点上(如果数据文件较大,可用 gzip 压缩后再传输)。

需要注意的一点,FLUSH TABLES WITH READ LOCK; 命令在获取全局读锁之前,必须等待所有的查询结束,如果有长时间的查询操作,将会使得该操作的过程非常漫长,并导致整个数据库处于只读状态甚至连读操作都会阻塞(见参考8~13)。因此,mysqldump 操作应该选择在数据库负载最小的时刻进行。

配置从节点

slave /etc/my.cnf

server-id=2
# 在从节点开启 log_bin 和 log-slave-update 可用于配置级联复制架构
log_bin = mysql-bin
log-slave-update = 1
relay-log = mysql-relay-bin
# the server permits no client updates except from users who have the SUPER privilege
read-only = 1

重新启动 Slave Server 并将 all-databases.sql 的语句在 Slave Server 执行。

[root@slave ~]# systemctl restart mysqld.service
[root@slave ~]# mysql -uroot -p < all-databases.sql

执行完上述命令之后,从节点的数据库就跟 binlog 坐标点的数据一模一样了。接下来就是进入从节点 MySQL 的控制台,告诉它应该从主节点的什么位置进行接下来的同步了:

mysql> CHANGE MASTER TO MASTER_HOST='10.0.63.202',MASTER_USER='slave',MASTER_PASSWORD='p4ssword',MASTER_LOG_FILE='mysql-bin.000002',MASTER_LOG_POS=120;
Query OK, 0 rows affected, 2 warnings (0.01 sec)

mysql> START SLAVE;
Query OK, 0 rows affected (0.03 sec)

查看 Slave 状态命令:

mysql> SHOW SLAVE STATUS G

以上为 MySQL 主从复制的过程,其中比较关键的是如何获取 mysqldump 运行时的数据库快照和 binlog 的坐标,即充分理解几个参数的含义。

在写本文之前,笔者对主从复制存有一些疑问,经过两天的调研整理,基本能把之前的疑问做一简单回答,如下:

  1. 是否需要选择需要备份的库?
    对于现在我接触到的应用,基本上是多个微服务各自对应一个数据库(database),但却同时存在于一个 MySQL Server 上。备份的时候使用 –all-databases 选项可将所有数据库(–all-databases 选项不会备份 performance_schema 和 information_schema)导出到文件,并后续同步到 slave 节点。在配置文件中没有配置 binlog-do-db,会将所有数据库的变动写入 binary log,包括创建database的命令。
    如果是一个微服务对应一个 MySQL Server 的场景,既然只有一个库了,备份整个库也无所谓啊:)

  2. 如何处理存储过程、函数及触发器?
    这个问题之前一直困扰着我,通过调研,发现它们存储在 mysql database 中,mysqldump –all-databases 会将 mysql 数据库导出,使得从节点与主节点拥有一样的 mysql 数据库,而任何新创建的存储过程、函数及触发器都会写到 binary log 中,进而同步到从节点的 mysql 数据库。因此,只需要操作主节点的 MySQL Server,而不需对从节点进行任何操作。

  3. 如果需要变动表结构需要如何处理?
    如果没有配置 binlog-do-db,那么任何数据库的改动都会写入 binary log,因此,也不用关心从节点。

  4. 如果新增数据库需要如何处理?
    同问题3。

  5. 如果需要将一主一丛扩展为一主二从应该如何操作?
    选则在主节点负载最小的时刻再进行一次上面的操作即可。

结语

本文仅介绍了一种 MySQL 的主从复制过程,还有很多其它方法(如利用文件系统的snapshot或Percona XtraBackup 工具)可能有更好的性能,在今后的实践中会进行尝试。

另外,在查阅资料的过程中在 MySQL 官网上看到了 InnoDB Cluster 和 MySQL NDB Cluster 相关的内容。前者通过将一组 MySQL Server 配置为一个集群,在默认额单主节点模式下,集群具有一个读写主节点和多个只读副节点,客户端程序通过连接 MySQL Router,Router 会选择一个合适的 Server 来提供服务;后者通过 NDB 存储引擎提供存储能力,SQL 层(mysqld)负责存储层之上的所有事情,如连接管理,query 优化及响应,Cache 管理等等。这些笔者还没有进行深入了解,这里列出作为后续调研的方向。

– 20180119 更新 –

当应用连接到主库进行测试的时候,出现了如下错误:

SQL Error: 1418, SQLState: HY000
This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary 
logging is enabled (you *might* want to use the less safe log_bin_trust_function_creators variable)

原因是因为 SQL 语句中含有存储程序(stored precedures and functions/triggers/events),如:

SELECT distinct a.permission_value FROM auth_permission a INNER JOIN auth_role_permission b 
    ON a.id=b.permission_id INNER JOIN auth_user_role c ON b.role_id=c.role_id 
    WHERE a.deleted=0 and c.user_id=? AND FIND_IN_SET(a.id, getPermissionChildList(?))

如果该语句被路由到 Slave 节点且 getPermissionChildList 含有更改数据的操作,会造成主从库不一致,存在安全隐患,所以 MySQL 默认禁止这种操作。如果明确知道存储程序不会造成主从库不一致,则可以以通过以下两种方式放宽这一限制:

  1. 在 MySQL 控制台执行 SET GLOBAL log_bin_trust_function_creators = 1;
  2. 在配置文件中添加 log_bin_trust_function_creators = 1; 并重新启动

浅谈MySQL用户账号认证方式

为了有效控制数据库用户的访问权限,在MySQL数据库中创建了一个新用户,但使用刚创建的用户和密码却发现连接不了MySQL数据库,通过查看官网手册及《MySQL技术内幕》一书,才逐渐熟悉MySQL的用户账号认证方式,这里做一个简单总结。另外本篇文章基于MySQL5.5版本,如果需要测试文章内容,低于此版本请先升级MySQL数据库https://www.zerostopbits.com/how-to-upgrade-mysql-5-1-to-mysql-5-5-on-centos-6-7/

MySQL数据库中有一个名称为mysql的系统数据库,USE mysql;进入该数据库,SHOW TABLES查看该数据库下的表,可以看到大概有二三十张表,本篇文章仅讨论user表

下面使用CREATE命令创建一个用户账号:

CREATE USER JING IDENTIFIED BY 'TESTPASSWORD';

每新创建的用户账号就会在user表下增加一条记录,使用SELECT命令查看user表可以找到刚刚创建的那个用户,用户名(User)为JING,密码(Password)已被加密:

未分类

使用Xshell连接到CentOS服务器(我的MySQL数据库装在京东云的CentOS服务器上),输入mysql -u JING -p连接MySQL数据库服务器,输入密码后却拒绝访问,返回如下结果:

ERROR 1045 (28000): Access denied for user 'JING'@'localhost' (using password: YES)

这就非常奇怪了,明明使用正确的账号和密码,怎么就连接不了了呢,这种时候如果不了解MySQL的账户认证方式,可能会急得直跺脚吧! ̄へ ̄

下面就来简单介绍一下MySQL的用户账号认证方式:

首先MySQL的用户账号(account)由两部分组成:用户名(User)和主机名(Host)。格式为’User’@’Host’,简单举几个创建用户的例子:

# 用户名为Mary,密码为watermelon,必须从指定的主机地址192.168.128.3连接服务器
CREATE USER 'Mary'@'192.168.128.3' IDENTIFIED BY 'watermelon';
# 用户名为Jack,密码为pineapple,必须从C类子网192.168.128连接服务器
CREATE USER 'Jack'@'192.168.128.%' IDENTIFIED BY 'pineapple';
# 用户名为Jane,密码为pear,必须是test.com域名或其子域名下的主机连接服务器
CREATE USER 'Jane'@'%.test.com' IDENTIFIED BY 'pear';
# 用户名为Michael,密码为banana,只能从本地连接服务器
CREATE USER 'Michael'@'localhost' IDENTIFIED BY 'banana';
# 用户名为Bob@localhost,密码为jujube,可从任意主机连接服务器
CREATE USER 'Bob@localhost' IDENTIFIED BY 'jujube';
# 用户名为Smith,密码为orange,可从任意主机连接服务器
CREATE USER 'Smith'@'%' IDENTIFIED BY 'orange';
# 用户名为JING,密码为TESTPASSWORD,可从任意主机连接服务器
CREATE USER 'JING' IDENTIFIED BY 'TESTPASSWORD';

从这几个例子可以得出下面几条结论:

  • MySQL的账户认证除了需要用户名和密码正确之外,还必须从指定的主机连接服务器才能够成功访问MySQL数据库服务器

  • 主机名可以使用%通配符

  • 主机名可以是域名、IPv4地址、localhost,实际上还可以是IPv6地址、127.0.0.1等(localhost和127.0.0.1还是有一些区别的,不要误以为两者完全等价)

  • 主机名可以省略,如果省略则默认主机名为%,表示任意主机,注意如果省略主机名,则@符号也需要省略,’JING’@”的主机名为空字符串并不是%

  • 用户名和主机名需要用@符号隔开

除此之外,还有如下几点需要了解

  • 用户名和主机名可以使用单引号括起来,也可以省略单引号,但如果包含像%、_等特殊字符,则单引号不可以省略

  • MySQL服务器对用户的连接请求先根据主机地址(Host)匹配,再根据用户名(User)行匹配,最后才会匹配密码(Password)
    重点在于第7点,根据第7点写出下面的SQL语句:

SELECT Host, User, Password FROM user ORDER BY Host DESC, User DESC;

返回结果如下:

未分类

回到刚开始的问题,为什么我使用正确的用户名和密码连接MySQL服务器会被拒绝访问呢?

这是因为我使用CREATE USER JING IDENTIFIED BY ‘TESTPASSWORD’;命令创建的用户JING可以通过任意主机访问MySQL服务器,而我通过Xshell登录京东云主机,然后使用mysql -u JING -p命令连接MySQL服务器,使用的主机地址(Host)为:localhost,用户名(User)为:JING,密码(Password)为:TESTPASSWORD。根据SELECT Host, User, Password FROM user ORDER BY Host DESC, User DESC;返回的结果,MySQL会从上往下逐个去校验匹配,最终匹配第2条记录,即Host为localhost,User为空,密码为空。而一旦成功匹配一条记录,便不会继续向下匹配了。

综合上面的分析,如果想要成功连接MySQL,只要执行mysql -u JING -p,当需要输入密码时,不用输入任何内容直接回车就可以了;如果我是通过我的Windows笔记本想要使用用户JING连接我的京东云MySQL数据库,只要打开cmd执行如下命令然后输入密码(此处需要输入密码是因为MySQL只能匹配到Host为%,User为JING,Password为TESTPASSWORD的那条记录)即可成功连接:

mysql -u JING -h 116.196.114.75 -p

未分类

登录之后我们也可以通过如下命令查看当前是通过哪个账号(account)连接的,从而验证上面的结论:

SELECT CURRENT_USER();

Xshell登录京东云主机返回的结果:

未分类

我的Windows命令行返回的结果:

未分类

再谈MySQL auto_increment空洞问题

在项目中时常会有这种需求,用户通过第三方系统登录时如果尚未注册,则自动给用户注册,注册过的用户自动登录。有时候图省事可能就直接INSERT INTO user ON DUPLICAET KEY UPDATE…一句 SQL 解决了,功能都正常,问题就是如果用户表中有auto_increment字段,则会导致auto_increment字段产生空洞问题,一段时间后会发现用户ID会经常出现不连续的情况,虽然mysql的自增ID可以很大,一般系统是够用的,但是对于强迫症患者这个是无法接受的。我测试的mysql版本为5.5.58,使用的是Innodb引擎,隔离级别为Repeatable Read。

1、场景

当用户从第三方登录时,假定用的是手机号做唯一标识,通常在我们自己的系统中会建一个用户表,如下:

 CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `mobile` varchar(11) DEFAULT NULL,
  `last_login_time` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `mobile` (`mobile`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

当用户从第三方登录时,我们校验通过后,会将手机号插入到user表里注册用户。如果用户已经存在,则更新最后登录时间,为了简便,经常像下面这么做,功能上看起来是没错的,问题就是运行一段时间后会发现user表的id字段居然是不连续的,而且经常两个id之间空洞还很大,比如上一个id是4,下一个变成了21。如下面例子中,再插入一条新记录时,id会变成3,也就是说id=2这个值被浪费了。

mysql> INSERT INTO user(mobile, last_login_time) VALUES('15012345678',
 NOW()) ON DUPLICATE KEY UPDATE last_login_time = NOW();
Query OK, 1 row affected (0.00 sec)

mysql> show create table user;
+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                               |
+----------------------------------------------------------------------+
| user  | CREATE TABLE `user` (
......
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 |

mysql> INSERT INTO user(mobile, last_login_time) VALUES('15012345678', 
NOW()) ON DUPLICATE KEY UPDATE last_login_time = NOW();
Query OK, 2 rows affected (0.00 sec)

mysql> show create table user;
+-------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                               |
+-------+---------------------------------------------------------------------
| user  | CREATE TABLE `user` (
......
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 |

2、分析

在MySQL官方文档已经提到过这个问题了其实,当表t1中列a已经有一个值为1的情况下,通常情况执行下面这两条语句效果是一样的,但是注意了,如果表t1是InnoDB引擎而且有一列为auto_increment的情况下,影响是不一样的,会产生前面提到的auto_increment空洞问题。MyISAM引擎的表不受此影响,不会产生空洞问题。

INSERT INTO t1 (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE c=c+1;

UPDATE t1 SET c=c+1 WHERE a=1;

更加确切的说,产生空洞问题还跟innodb_autoinc_lock_mode这个MySQL配置相关。该配置在MySQL5.1引入,是为了提升auto_increment字段的并发性能引入的,默认值为1。该值可以配置为0(traditional lock mode),1(consecutive lock mode),2(interleaved lock mode),除了0基本不产生空洞外,配置其他值都是可能有auto_increment空洞的,简单总结如下,更详细的可以参考 innodb-auto-increment-handling。

  • 1)如果事务回滚了,则不管是0,1,2都会导致事务中使用过的auto_increment的值浪费。

  • 2)如果设置为0,是traditional lock mode,则任意插入语句都会加 AUTO-INC 锁,基本不会产生空洞,除了1中的rollback情况外。

  • 3)如果设置为1或者2的时候,simple inserts语句(simple inserts指的是那种能够事先确定插入行数的语句,比如INSERT/REPLACE INTO 等插入单行或者多行的语句,语句中不包括嵌套子查询)不会有空洞。但是对于bulk inserts(bulk inserts指的是事先无法确定插入行数的语句,比如INSERT/REPLACE INTO … SELECT FROM…, LOAD DATA等)和mixed-mode inserts(指的是simple inserts类型中有些行指定了auto_increment列的值有些没有指定,比如:INSERT INTO t1 (c1,c2) VALUES (1,’a’), (NULL,’b’), (5,’c’), (NULL,’d’)和INSERT … ON DUPLICATE KEY UPDATE这种语句)会预先分配auto_increment值,导致一些浪费。 特别是设置为2的时候,在执行任意插入语句都不会加 AUTO-INC 锁,从而在语句执行过程中都可能产生空洞。

3、一种错误示范

那为了减少第一节中的auto_increment空洞问题,一种方法就是INSERT前先判断下用户是否存在,不存在才执行插入语句,否则用户每次登录都会导致auto_increment值被浪费。方案如下:

with transaction:
    user = SELECT * FROM user WHERE mobile = '15012345678' FOR UPDATE;
    if not user:
       INSERT INTO user(mobile, last_login_time) VALUES('15012345678', NOW()) 
    UPDATE user SET last_login_time = NOW();

这个代码乍看是没有问题了,mobile是unique key,这样的FOR UPDATE似乎木有问题,这是一个lock read,而且是排他锁,一个session对这条记录加了排他锁,其他session不能对这条记录加锁和修改(不能 LOCK IN SHARE MODE 以及 UPDATE 等,要注意下SELECT FOR UPDATE只在事务中或者autocommit关闭的情况下才会加锁)。但是,这只在记录存在的情况下才是对记录加X锁,没有Gap锁。而如果这个记录不存在,则对第一个不满足条件的记录加Gap锁,保证没有满足条件的记录插入。

如果mobile=15012345678这条记录不存在,并发的多个session都可以进入SELECT … FOR UPDATE,因为都是加的Gap锁(X locks gap before rec),Gap锁之间是兼容的。此时,其中任意一个session再执行 INSERT INTO user(mobile, last_login_time) VALUES(‘15012345678’, NOW())语句会因为加insert intention lock(注:插入意向锁是一种特殊的Gap锁,不是MySQL的表级意向锁IS,IX等)超时而执行失败。其实此时的Gap锁不只是锁住了 15012345678 这条记录,如果表中有其他的记录,会将可能插入 15012345678 的区间都锁住,MySQL加锁详细分析可以见参考资料5。

4、解决方案

为此,如果要优化auto_increment的浪费问题,又要避免上一节提到的加锁超时问题,还是有点事情要做的。可行的几种方法如下:

  • a) 通过GET_LOCK(name, timeout)而不是FOR UPDATE来避免上一节提到的问题。

  • b) 如果对数据没有很强的提交读的需求,可以不加FOR UPDATE查询,如果记录不存在,然后再INSERT IGNORE INTO …。

  • c) 更tricky的做法,percona的这篇文章avoiding-auto-increment-holes-on-innodb-with-insert-ignore描述了一种很独特的方法来避免auto_increment的空洞问题,有兴趣的可以参考。
    MySQL Innodb如果出现了一些加锁问题,可以通过下面这几个命令来辅助分析。

show engine innodb status;
select * from information_schema.innodb_locks;
select * from information_schema.innodb_lock_waits;
select * from information_schema.innodb_trx;

docker构建 mongodb 集群服务

安装

docker run -p 27018:27017 -v /root/docker/mongo/data:/data/db  -d --name=mongo361 mongo --bind_ip_all --auth

进入容器

docker exec -it mongo361 mongo admin

创建 所有数据库角色

db.createUser({ user: 'zan', pwd: 'zan', roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] });

更多角色说明: https://docs.mongodb.com/manual/reference/built-in-roles/#built-in-roles

认证进入 操作状态

db.auth("zan","zan")
use test

test 只读账号

db.createUser({user:"test",pwd:"test",roles:[{role: "read", db: "test"}]})

test1 读写账号

db.createUser({user:"test1",pwd:"test",roles:[{role: "readWrite", db: "test"}]})

使用docker直接运行mongodb,解决Kitematic崩溃问题

在新大学习webapp 使用express mongodb数据库,并且mongodb是在docker容器下的,docker自带的用户界面UI Kitematic经常崩溃,
但是docker却没有问题,我就想能不能在 terminal 使用mongodb ,下面我就简单说下怎么使用docker的

未分类

先看看后面会使用到的几个命令

docker ps
docker images
docker start
docker stop
docker pull
docker run
docker exec
docker   COMMAND --help 最重要的一个

1. 使用terminal

未分类

Windows自带的powershell
打开powershell terminal 使用 docker ps 看下docker容器中都启动了那些服务.

未分类

如果已经运行了 mongodb 可以使用 docker stop SERVER-NAME SERVER-NAME 是使用 docker ps 中查看到的;

2. 下载镜像文件

使用 docker images 查看是否有可用的镜像文件

如果没有可以使用 docker pull mongodb 命令从官网上下载 https://store.docker.com/ 或者直接使用
https://store.docker.com/images/mongo 下载

未分类

然后根据自己现在运行的docker容器类型选择,默认是linux容器,下图是查看方式.点一下就会切换到windows containers.

未分类

3. 在docker容器中安装mongodb

docker run -d --name mongo-test -p 27017:27017 mongo

-d Run container in background and print container ID 老实说我也不太懂
– name 是指给你的mongodb起个名字
-p 这个参数一定要有,不然你本地的webapp连接不到数据库,
它的意思是将虚拟机中的mongodb的端口号(mongodb端口号使用docker ps 查看)映射到你的电脑localhost:27017上面 ,当然你的映射端 口号根据自己需要可以自定义.

4. 进入到 mongdb 创建数据库

使用如下命令

docker exec -it mongo-test mongo 

看下数据库 show dbs;

创建数据库 use myapp;

好了现在可以试试你的webapp是不是能连接上了. localhost:27017

MongoDB 分片集群技术

1.1 MongoDB复制集简介

一组Mongodb复制集,就是一组mongod进程,这些进程维护同一个数据集合。复制集提供了数据冗余和高等级的可靠性,这是生产部署的基础。

1.1.1 复制集的目的

  保证数据在生产部署时的冗余和可靠性,通过在不同的机器上保存副本来保证数据的不会因为单点损坏而丢失。能够随时应对数据丢失、机器损坏带来的风险。

  换一句话来说,还能提高读取能力,用户的读取服务器和写入服务器在不同的地方,而且,由不同的服务器为不同的用户提供服务,提高整个系统的负载。

1.1.2 简单介绍

  一组复制集就是一组mongod实例掌管同一个数据集,实例可以在不同的机器上面。实例中包含一个主导,接受客户端所有的写入操作,其他都是副本实例,从主服务器上获得数据并保持同步。

  主服务器很重要,包含了所有的改变操作(写)的日志。但是副本服务器集群包含有所有的主服务器数据,因此当主服务器挂掉了,就会在副本服务器上重新选取一个成为主服务器。

  每个复制集还有一个仲裁者,仲裁者不存储数据,只是负责通过心跳包来确认集群中集合的数量,并在主服务器选举的时候作为仲裁决定结果。

1.2 复制的基本架构

  基本的架构由3台服务器组成,一个三成员的复制集,由三个有数据,或者两个有数据,一个作为仲裁者。

1.2.1 三个存储数据的复制集

具有三个存储数据的成员的复制集有:

一个主库;

两个从库组成,主库宕机时,这两个从库都可以被选为主库。

未分类

当主库宕机后,两个从库都会进行竞选,其中一个变为主库,当原主库恢复后,作为从库加入当前的复制集群即可。

未分类

1.2.2 当存在arbiter节点

在三个成员的复制集中,有两个正常的主从,及一台arbiter节点:

一个主库

一个从库,可以在选举中成为主库

一个aribiter节点,在选举中,只进行投票,不能成为主库

未分类

说明:

  由于arbiter节点没有复制数据,因此这个架构中仅提供一个完整的数据副本。arbiter节点只需要更少的资源,代价是更有限的冗余和容错。

当主库宕机时,将会选择从库成为主,主库修复后,将其加入到现有的复制集群中即可。

未分类

1.2.3 Primary选举

  复制集通过replSetInitiate命令(或mongo shell的rs.initiate())进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry选举操作,获得『大多数』成员投票支持的节点,会成为Primary,其余节点成为Secondary。

『大多数』的定义

  假设复制集内投票成员(后续介绍)数量为N,则大多数为 N/2 + 1,当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。

未分类

  通常建议将复制集成员数量设置为奇数,从上表可以看出3个节点和4个节点的复制集都只能容忍1个节点失效,从『服务可用性』的角度看,其效果是一样的。(但无疑4个节点能提供更可靠的数据存储)

1.3 复制集中成员说明

1.3.1 所有成员说明

未分类

1.3.2 Priority 0节点

  作为一个辅助可以作为一个备用。在一些复制集中,可能无法在合理的时间内添加新成员的时候。备用成员保持数据的当前最新数据能够替换不可用的成员。

未分类

1.3.3 Hidden 节点(隐藏节点)

  客户端将不会把读请求分发到隐藏节点上,即使我们设定了 复制集读选项 。

  这些隐藏节点将不会收到来自应用程序的请求。我们可以将隐藏节点专用于报表节点或是备份节点。 延时节点也应该是一个隐藏节点。

未分类

1.3.4 Delayed 节点(延时节点)

  延时节点的数据集是延时的,因此它可以帮助我们在人为误操作或是其他意外情况下恢复数据。

  举个例子,当应用升级失败,或是误操作删除了表和数据库时,我们可以通过延时节点进行数据恢复。

未分类

1.4 配置MongoDB复制集

1.4.1 环境说明

系统环境说明:

[root@MongoDB ~]# cat /etc/redhat-release 
CentOS release 6.9 (Final)
[root@MongoDB ~]# uname -r
2.6.32-696.el6.x86_64
[root@MongoDB ~]# /etc/init.d/iptables status
iptables: Firewall is not running.
[root@MongoDB ~]# getenforce 
Disabled
[root@MongoDB ~]# hostname -I
10.0.0.152 172.16.1.152

软件版本说明

本次使用的mongodb版本为:mongodb-linux-x86_64-3.2.8.tgz

1.4.2 前期准备,在root用户下操作

本次复制集复制采用Mongodb多实例进行

所有的操作都基于安装完成的mongodb服务,详情参照:http://www.cnblogs.com/clsn/p/8214194.html#_label3

#创建mongod用户
    useradd -u800 mongod
    echo 123456|passwd --stdin mongod 
# 安装mongodb
    mkdir -p /mongodb/bin
   cd  /mongodb
   wget http://downloads.mongodb.org/linux/mongodb-linux-x86_64-rhel62-3.2.8.tgz
    tar xf  mongodb-linux-x86_64-3.2.8.tgz
    cd mongodb-linux-x86_64-3.2.8/bin/ &&
    cp * /mongodb/bin
    chown -R mongod.mongod /mongodb
# 切换到mongod用户进行后续操作
    su - mongod

1.4.3 创建所需目录

for  i in 28017 28018 28019 28020
    do 
      mkdir -p /mongodb/$i/conf  
      mkdir -p /mongodb/$i/data  
      mkdir -p /mongodb/$i/log
done 

1.4.4 配置多实例环境

编辑第一个实例配置文件

cat >>/mongodb/28017/conf/mongod.conf<<'EOF'
systemLog:
  destination: file
  path: /mongodb/28017/log/mongodb.log
  logAppend: true
storage:
  journal:
    enabled: true
  dbPath: /mongodb/28017/data
  directoryPerDB: true
  #engine: wiredTiger
  wiredTiger:
    engineConfig:
      # cacheSizeGB: 1
      directoryForIndexes: true
    collectionConfig:
      blockCompressor: zlib
    indexConfig:
      prefixCompression: true
processManagement:
  fork: true
net:
  port: 28017
replication:
  oplogSizeMB: 2048
  replSetName: my_repl
EOF

复制配置文件

for i in 28018 28019 28020
  do  
   cp  /mongodb/28017/conf/mongod.conf  /mongodb/$i/conf/
done

修改配置文件

for i in 28018 28019 28020
  do 
    sed  -i  "s#28017#$i#g" /mongodb/$i/conf/mongod.conf
done

启动服务

for i in 28017 28018 28019 28020
  do  
    mongod -f /mongodb/$i/conf/mongod.conf 
done
# 关闭服务的方法

for i in 28017 28018 28019 28020
   do  
     mongod --shutdown  -f /mongodb/$i/conf/mongod.conf 
done

1.4.5 配置复制集

登陆数据库,配置mongodb复制

shell> mongo --port 28017

config = {_id: 'my_repl', members: [
                          {_id: 0, host: '10.0.0.152:28017'},
                          {_id: 1, host: '10.0.0.152:28018'},
                          {_id: 2, host: '10.0.0.152:28019'}]
          }

初始化这个配置

> rs.initiate(config)

到此复制集配置完成

1.4.6 测试主从复制

在主节点插入数据

my_repl:PRIMARY> db.movies.insert([ { "title" : "Jaws", "year" : 1975, "imdb_rating" : 8.1 },
   { "title" : "Batman", "year" : 1989, "imdb_rating" : 7.6 },
  ] );

在主节点查看数据

my_repl:PRIMARY> db.movies.find().pretty()
{
    "_id" : ObjectId("5a4d9ec184b9b2076686b0ac"),
    "title" : "Jaws",
    "year" : 1975,
    "imdb_rating" : 8.1
}
{
    "_id" : ObjectId("5a4d9ec184b9b2076686b0ad"),
    "title" : "Batman",
    "year" : 1989,
    "imdb_rating" : 7.6
}

注:在mongodb复制集当中,默认从库不允许读写。

在从库打开配置(危险)

注意:严禁在从库做任何修改操作

my_repl:SECONDARY> rs.slaveOk()
my_repl:SECONDARY> show tables;
movies
my_repl:SECONDARY> db.movies.find().pretty()
{
    "_id" : ObjectId("5a4d9ec184b9b2076686b0ac"),
    "title" : "Jaws",
    "year" : 1975,
    "imdb_rating" : 8.1
}
{
    "_id" : ObjectId("5a4d9ec184b9b2076686b0ad"),
    "title" : "Batman",
    "year" : 1989,
    "imdb_rating" : 7.6
}

  
在从库查看完成在登陆到主库

1.4.7 复制集管理操作

(1)查看复制集状态:

rs.status();     # 查看整体复制集状态
rs.isMaster();   #  查看当前是否是主节点

(2)添加删除节点

rs.add("ip:port");     #  新增从节点
rs.addArb("ip:port"); #  新增仲裁节点
rs.remove("ip:port"); #  删除一个节点

注:

添加特殊节点时,

1>可以在搭建过程中设置特殊节点

2>可以通过修改配置的方式将普通从节点设置为特殊节点

/*找到需要改为延迟性同步的数组号*/;

(3)配置延时节点(一般延时节点也配置成hidden)

cfg=rs.conf() 
cfg.members[2].priority=0
cfg.members[2].slaveDelay=120
cfg.members[2].hidden=true

注:这里的2是rs.conf()显示的顺序(除主库之外),非ID

重写复制集配置

rs.reconfig(cfg)   

也可将延时节点配置为arbiter节点

cfg.members[2].arbiterOnly=true

配置成功后,通过以下命令查询配置后的属性

rs.conf();

1.4.8 副本集其他操作命令

查看副本集的配置信息

my_repl:PRIMARY> rs.config()

查看副本集各成员的状态

my_repl:PRIMARY> rs.status()

1.4.8.1 副本集角色切换(不要人为随便操作)

rs.stepDown()
rs.freeze(300)  # 锁定从,使其不会转变成主库,freeze()和stepDown单位都是秒。
rs.slaveOk()    # 设置副本节点可读:在副本节点执行

插入数据

> use app
switched to db app
app> db.createCollection('a')
{ "ok" : 0, "errmsg" : "not master", "code" : 10107 }

查看副本节点

> rs.printSlaveReplicationInfo()
source: 192.168.1.22:27017
    syncedTo: Thu May 26 2016 10:28:56 GMT+0800 (CST)
    0 secs (0 hrs) behind the primary

MongoDB分片(Sharding)技术

  分片(sharding)是MongoDB用来将大型集合分割到不同服务器(或者说一个集群)上所采用的方法。尽管分片起源于关系型数据库分区,但MongoDB分片完全又是另一回事。

  和MySQL分区方案相比,MongoDB的最大区别在于它几乎能自动完成所有事情,只要告诉MongoDB要分配数据,它就能自动维护数据在不同服务器之间的均衡。

2.1 MongoDB分片介绍

2.1.1 分片的目的

  高数据量和吞吐量的数据库应用会对单机的性能造成较大压力,大的查询量会将单机的CPU耗尽,大的数据量对单机的存储压力较大,最终会耗尽系统的内存而将压力转移到磁盘IO上。

为了解决这些问题,有两个基本的方法: 垂直扩展和水平扩展。

垂直扩展:增加更多的CPU和存储资源来扩展容量。

水平扩展:将数据集分布在多个服务器上。水平扩展即分片。

2.1.2 分片设计思想

  分片为应对高吞吐量与大数据量提供了方法。使用分片减少了每个分片需要处理的请求数,因此,通过水平扩展,集群可以提高自己的存储容量和吞吐量。举例来说,当插入一条数据时,应用只需要访问存储这条数据的分片.

  使用分片减少了每个分片存储的数据。

  例如,如果数据库1tb的数据集,并有4个分片,然后每个分片可能仅持有256 GB的数据。如果有40个分片,那么每个切分可能只有25GB的数据。

未分类

2.1.3 分片机制提供了如下三种优势

1. 对集群进行抽象,让集群“不可见

  MongoDB自带了一个叫做mongos的专有路由进程。mongos就是掌握统一路口的路由器,其会将客户端发来的请求准确无误的路由到集群中的一个或者一组服务器上,同时会把接收到的响应拼装起来发回到客户端。

2. 保证集群总是可读写

  MongoDB通过多种途径来确保集群的可用性和可靠性。将MongoDB的分片和复制功能结合使用,在确保数据分片到多台服务器的同时,也确保了每分数据都有相应的备份,这样就可以确保有服务器换掉时,其他的从库可以立即接替坏掉的部分继续工作。

3. 使集群易于扩展

  当系统需要更多的空间和资源的时候,MongoDB使我们可以按需方便的扩充系统容量。

2.1.4 分片集群架构

未分类

未分类

分片集群的构造

(1)mongos :数据路由,和客户端打交道的模块。mongos本身没有任何数据,他也不知道该怎么处理这数据,去找config server

(2)config server:所有存、取数据的方式,所有shard节点的信息,分片功能的一些配置信息。可以理解为真实数据的元数据。

(3)shard:真正的数据存储位置,以chunk为单位存数据。

  Mongos本身并不持久化数据,Sharded cluster所有的元数据都会存储到Config Server,而用户的数据会议分散存储到各个shard。Mongos启动后,会从配置服务器加载元数据,开始提供服务,将用户的请求正确路由到对应的碎片。

Mongos的路由功能

当数据写入时,MongoDB Cluster根据分片键设计写入数据。

当外部语句发起数据查询时,MongoDB根据数据分布自动路由至指定节点返回数据。

2.2 集群中数据分布

2.2.1 Chunk是什么

  在一个shard server内部,MongoDB还是会把数据分为chunks,每个chunk代表这个shard server内部一部分数据。chunk的产生,会有以下两个用途:

  Splitting:当一个chunk的大小超过配置中的chunk size时,MongoDB的后台进程会把这个chunk切分成更小的chunk,从而避免chunk过大的情况

  Balancing:在MongoDB中,balancer是一个后台进程,负责chunk的迁移,从而均衡各个shard server的负载,系统初始1个chunk,chunk size默认值64M,生产库上选择适合业务的chunk size是最好的。ongoDB会自动拆分和迁移chunks。

分片集群的数据分布(shard节点)

(1)使用chunk来存储数据

(2)进群搭建完成之后,默认开启一个chunk,大小是64M,

(3)存储需求超过64M,chunk会进行分裂,如果单位时间存储需求很大,设置更大的chunk

(4)chunk会被自动均衡迁移。

2.2.2 chunksize的选择

适合业务的chunksize是最好的。

chunk的分裂和迁移非常消耗IO资源;chunk分裂的时机:在插入和更新,读数据不会分裂。

chunksize的选择:

  小的chunksize:数据均衡是迁移速度快,数据分布更均匀。数据分裂频繁,路由节点消耗更多资源。大的chunksize:数据分裂少。数据块移动集中消耗IO资源。通常100-200M

2.2.3 chunk分裂及迁移

  随着数据的增长,其中的数据大小超过了配置的chunk size,默认是64M,则这个chunk就会分裂成两个。数据的增长会让chunk分裂得越来越多。

未分类

  这时候,各个shard 上的chunk数量就会不平衡。这时候,mongos中的一个组件balancer 就会执行自动平衡。把chunk从chunk数量最多的shard节点挪动到数量最少的节点。

未分类

chunkSize 对分裂及迁移的影响

  MongoDB 默认的 chunkSize 为64MB,如无特殊需求,建议保持默认值;chunkSize 会直接影响到 chunk 分裂、迁移的行为。

  chunkSize 越小,chunk 分裂及迁移越多,数据分布越均衡;反之,chunkSize 越大,chunk 分裂及迁移会更少,但可能导致数据分布不均。

  chunkSize 太小,容易出现 jumbo chunk(即shardKey 的某个取值出现频率很高,这些文档只能放到一个 chunk 里,无法再分裂)而无法迁移;chunkSize 越大,则可能出现 chunk 内文档数太多(chunk 内文档数不能超过 250000 )而无法迁移。

  chunk 自动分裂只会在数据写入时触发,所以如果将 chunkSize 改小,系统需要一定的时间来将 chunk 分裂到指定的大小。

  chunk 只会分裂,不会合并,所以即使将 chunkSize 改大,现有的 chunk 数量不会减少,但 chunk 大小会随着写入不断增长,直到达到目标大小。

2.3 数据区分

2.3.1 分片键shard key

  MongoDB中数据的分片是、以集合为基本单位的,集合中的数据通过片键(Shard key)被分成多部分。其实片键就是在集合中选一个键,用该键的值作为数据拆分的依据。

  所以一个好的片键对分片至关重要。片键必须是一个索引,通过sh.shardCollection加会自动创建索引(前提是此集合不存在的情况下)。一个自增的片键对写入和数据均匀分布就不是很好,因为自增的片键总会在一个分片上写入,后续达到某个阀值可能会写到别的分片。但是按照片键查询会非常高效。

  随机片键对数据的均匀分布效果很好。注意尽量避免在多个分片上进行查询。在所有分片上查询,mongos会对结果进行归并排序。

  对集合进行分片时,你需要选择一个片键,片键是每条记录都必须包含的,且建立了索引的单个字段或复合字段,MongoDB按照片键将数据划分到不同的数据块中,并将数据块均衡地分布到所有分片中。

  为了按照片键划分数据块,MongoDB使用基于范围的分片方式或者 基于哈希的分片方式。

注意:

分片键是不可变。

分片键必须有索引。

分片键大小限制512bytes。

分片键用于路由查询。

MongoDB不接受已进行collection级分片的collection上插入无分片

键的文档(也不支持空值插入)

2.3.2 以范围为基础的分片Sharded Cluster

  Sharded Cluster支持将单个集合的数据分散存储在多shard上,用户可以指定根据集合内文档的某个字段即shard key来进行范围分片(range sharding)。

未分类

  对于基于范围的分片,MongoDB按照片键的范围把数据分成不同部分。

  假设有一个数字的片键:想象一个从负无穷到正无穷的直线,每一个片键的值都在直线上画了一个点。MongoDB把这条直线划分为更短的不重叠的片段,并称之为数据块,每个数据块包含了片键在一定范围内的数据。在使用片键做范围划分的系统中,拥有”相近”片键的文档很可能存储在同一个数据块中,因此也会存储在同一个分片中。

2.3.3 基于哈希的分片

  分片过程中利用哈希索引作为分片的单个键,且哈希分片的片键只能使用一个字段,而基于哈希片键最大的好处就是保证数据在各个节点分布基本均匀。

未分类

  对于基于哈希的分片,MongoDB计算一个字段的哈希值,并用这个哈希值来创建数据块。在使用基于哈希分片的系统中,拥有”相近”片键的文档很可能不会存储在同一个数据块中,因此数据的分离性更好一些。

  Hash分片与范围分片互补,能将文档随机的分散到各个chunk,充分的扩展写能力,弥补了范围分片的不足,但不能高效的服务范围查询,所有的范围查询要分发到后端所有的Shard才能找出满足条件的文档。

2.3.4 分片键选择建议

1、递增的sharding key

数据文件挪动小。(优势)

因为数据文件递增,所以会把insert的写IO永久放在最后一片上,造成最后一片的写热点。同时,随着最后一片的数据量增大,将不断的发生迁移至之前的片上。

2、随机的sharding key

数据分布均匀,insert的写IO均匀分布在多个片上。(优势)

大量的随机IO,磁盘不堪重荷。

3、混合型key

大方向随机递增,小范围随机分布。

为了防止出现大量的chunk均衡迁移,可能造成的IO压力。我们需要设置合理分片使用策略(片键的选择、分片算法(range、hash))

分片注意:

分片键是不可变、分片键必须有索引、分片键大小限制512bytes、分片键用于路由查询。

MongoDB不接受已进行collection级分片的collection上插入无分片键的文档(也不支持空值插入)

2.4 部署分片集群

本集群的部署基于1.1的复制集搭建完成。

2.4.1 环境准备

创建程序所需的目录

for  i in 17 18 19 20 21 22 23 24 25 26 
  do 
  mkdir -p /mongodb/280$i/conf  
  mkdir -p /mongodb/280$i/data  
  mkdir -p /mongodb/280$i/log
done

2.4.2 shard集群配置

编辑shard集群配置文件

cat > /mongodb/28021/conf/mongod.conf <<'EOF'
systemLog:
  destination: file
  path: /mongodb/28021/log/mongodb.log   
  logAppend: true
storage:
  journal:
    enabled: true
  dbPath: /mongodb/28021/data
  directoryPerDB: true
  #engine: wiredTiger
  wiredTiger:
    engineConfig:
      cacheSizeGB: 1
      directoryForIndexes: true
    collectionConfig:
      blockCompressor: zlib
    indexConfig:
      prefixCompression: true
net:
  bindIp: 10.0.0.152
  port: 28021
replication:
  oplogSizeMB: 2048
  replSetName: sh1
sharding:
  clusterRole: shardsvr
processManagement: 
  fork: true
EOF

复制shard集群配置文件

for  i in  22 23 24 25 26  
  do  
   cp  /mongodb/28021/conf/mongod.conf  /mongodb/280$i/conf/
done

修改配置文件端口

for  i in   22 23 24 25 26  
  do 
    sed  -i  "s#28021#280$i#g" /mongodb/280$i/conf/mongod.conf
done

修改配置文件复制集名称(replSetName)

for  i in    24 25 26  
  do 
    sed  -i  "s#sh1#sh2#g" /mongodb/280$i/conf/mongod.conf
done

启动shard集群

for  i in  21 22 23 24 25 26
  do  
    mongod -f /mongodb/280$i/conf/mongod.conf 
done

配置复制集1

mongo --host 10.0.0.152 --port 28021  admin

配置复制集

config = {_id: 'sh1', members: [
                          {_id: 0, host: '10.0.0.152:28021'},
                          {_id: 1, host: '10.0.0.152:28022'},
                          {_id: 2, host: '10.0.0.152:28023',"arbiterOnly":true}]
           }  
 # 初始化配置
rs.initiate(config)  

配置复制集2

mongo --host 10.0.0.152 --port 28024  admin

配置复制集

config = {_id: 'sh2', members: [
                          {_id: 0, host: '10.0.0.152:28024'},
                          {_id: 1, host: '10.0.0.152:28025'},
                          {_id: 2, host: '10.0.0.152:28026',"arbiterOnly":true}]
           }
# 初始化配置
rs.initiate(config)

2.4.3 config集群配置

创建主节点配置文件

cat > /mongodb/28018/conf/mongod.conf <<'EOF'
systemLog:
  destination: file
  path: /mongodb/28018/log/mongodb.conf
  logAppend: true
storage:
  journal:
    enabled: true
  dbPath: /mongodb/28018/data
  directoryPerDB: true
  #engine: wiredTiger
  wiredTiger:
    engineConfig:
      cacheSizeGB: 1
      directoryForIndexes: true
    collectionConfig:
      blockCompressor: zlib
    indexConfig:
      prefixCompression: true
net:
  bindIp: 10.0.0.152
  port: 28018
replication:
  oplogSizeMB: 2048
  replSetName: configReplSet
sharding:
  clusterRole: configsvr
processManagement: 
  fork: true
EOF

将配置文件分发到从节点

for  i in 19 20 
  do  
   cp  /mongodb/28018/conf/mongod.conf  /mongodb/280$i/conf/
done

修改配置文件端口信息

for  i in 19 20  
  do 
    sed  -i  "s#28018#280$i#g" /mongodb/280$i/conf/mongod.conf
done

启动config server集群

for  i in  18 19 20 
  do  
    mongod -f /mongodb/280$i/conf/mongod.conf 
done

配置config server复制集

mongo --host 10.0.0.152 --port 28018  admin

配置复制集信息

config = {_id: 'configReplSet', members: [
                          {_id: 0, host: '10.0.0.152:28018'},
                          {_id: 1, host: '10.0.0.152:28019'},
                          {_id: 2, host: '10.0.0.152:28020'}]
           }
# 初始化配置
rs.initiate(config)    

注:config server 使用复制集不用有arbiter节点。3.4版本以后config必须为复制集

2.4.4 mongos节点配置

修改配置文件

cat > /mongodb/28017/conf/mongos.conf <<'EOF'
systemLog:
  destination: file
  path: /mongodb/28017/log/mongos.log
  logAppend: true
net:
  bindIp: 10.0.0.152
  port: 28017
sharding:
  configDB: configReplSet/10.0.0.152:28108,10.0.0.152:28019,10.0.0.152:28020
processManagement: 
  fork: true
EOF

启动mongos

mongos -f /mongodb/28017/conf/mongos.conf

登陆到mongos

mongo 10.0.0.152:28017/admin

添加分片节点

db.runCommand( { addshard : "sh1/10.0.0.152:28021,10.0.0.152:28022,10.0.0.152:28023",name:"shard1"} )
db.runCommand( { addshard : "sh2/10.0.0.152:28024,10.0.0.152:28025,10.0.0.152:28026",name:"shard2"} )

列出分片

mongos> db.runCommand( { listshards : 1 } )
{
    "shards" : [
        {
            "_id" : "shard2",
            "host" : "sh2/10.0.0.152:28024,10.0.0.152:28025"
        },
        {
            "_id" : "shard1",
            "host" : "sh1/10.0.0.152:28021,10.0.0.152:28022"
        }
    ],
    "ok" : 1
}

整体状态查看

mongos> sh.status();

至此MongoDB的分片集群就搭建完成。

2.4.5 数据库分片配置

激活数据库分片功能

语法:( { enablesharding : "数据库名称" } )

mongos> db.runCommand( { enablesharding : "test" } )

指定分片建对集合分片,范围片键–创建索引

mongos> use test 
mongos> db.vast.ensureIndex( { id: 1 } )
mongos> use admin
mongos> db.runCommand( { shardcollection : "test.vast",key : {id: 1} } )

集合分片验证

mongos> use test
mongos> for(i=0;i<20000;i++){ db.vast1.insert({"id":i,"name":"clsn","age":70,"date":new Date()}); }
mongos> db.vast.stats()

插入数据的条数尽量大些,能够看出更好的效果。

2.5 分片集群的操作

2.5.1 不同分片键的配置

范围片键

admin> sh.shardCollection("数据库名称.集合名称",key : {分片键: 1}  )
或
admin> db.runCommand( { shardcollection : "数据库名称.集合名称",key : {分片键: 1} } )

eg:

admin > sh.shardCollection("test.vast",key : {id: 1}  )
或
admin> db.runCommand( { shardcollection : "test.vast",key : {id: 1} } )

哈希片键

admin > sh.shardCollection( "数据库名.集合名", { 片键: "hashed" } )

创建哈希索引

admin> db.vast.ensureIndex( { a: "hashed" } )
admin > sh.shardCollection( "test.vast", { a: "hashed" } )

2.5.2 分片集群的操作

判断是否Shard集群

admin> db.runCommand({ isdbgrid : 1})

列出所有分片信息

admin> db.runCommand({ listshards : 1})

列出开启分片的数据库

admin> use config
config> db.databases.find( { "partitioned": true } )
config> db.databases.find() //列出所有数据库分片情况

查看分片的片键

config> db.collections.find()
{
    "_id" : "test.vast",
    "lastmodEpoch" : ObjectId("58a599f19c898bbfb818b63c"),
    "lastmod" : ISODate("1970-02-19T17:02:47.296Z"),
    "dropped" : false,
    "key" : {
        "id" : 1
    },
    "unique" : false
}

查看分片的详细信息

admin> db.printShardingStatus()
或
admin> sh.status()

删除分片节点

sh.getBalancerState()
mongos> db.runCommand( { removeShard: "shard2" } )

2.6 balance操作

查看mongo集群是否开启了 balance 状态

mongos> sh.getBalancerState()
true

当然你也可以通过在路由节点mongos上执行sh.status() 查看balance状态。

如果balance开启,查看是否正在有数据的迁移

连接mongo集群的路由节点

mongos> sh.isBalancerRunning()
false

2.6.1 设置balance 窗口

(1)连接mongo集群的路由节点

(2)切换到配置节点

use config

(3)确定balance 开启中

sh.getBalancerState()

如果未开启,执行命令

sh.setBalancerState( true )

(4)修改balance 窗口的时间

db.settings.update(
   { _id: "balancer" },
   { $set: { activeWindow : { start : "<start-time>", stop : "<stop-time>" } } },
   { upsert: true }
)

eg:

db.settings.update({ _id : "balancer" }, { $set : { activeWindow : { start : "00:00", stop : "5:00" } } }, true )

当你设置了activeWindow,就不能用sh.startBalancer() 启动balance

NOTE

The balancer window must be sufficient to complete the migration of all data inserted during the day.

As data insert rates can change based on activity and usage patterns, it is important to ensure that the balancing window you select will be sufficient to support the needs of your deployment.

(5)删除balance 窗口

use config
db.settings.update({ _id : "balancer" }, { $unset : { activeWindow : true } })

2.6.2 关闭balance

默认balance 的运行可以在任何时间,只迁移需要迁移的chunk,如果要关闭balance运行,停止一段时间可以用下列方法:

(1) 连接到路由mongos节点

(2) 停止balance

sh.stopBalancer()

(3) 查看balance状态

sh.getBalancerState()

(4)停止balance 后,没有迁移进程正在迁移,可以执行下列命令

use config
while( sh.isBalancerRunning() ) {
          print("waiting...");
          sleep(1000);
}

2.6.3 重新打开balance

如果你关闭了balance,准备重新打开balance

(1) 连接到路由mongos节点

(2) 打开balance

sh.setBalancerState(true)

如果驱动没有命令 sh.startBalancer(),可以用下列命令

use config
db.settings.update( { _id: "balancer" }, { $set : { stopped: false } } , { upsert: true } )

2.6.4 关于集合的balance

关闭某个集合的balance

sh.disableBalancing("students.grades")

打开某个集合的balance

sh.enableBalancing("students.grades")

确定某个集合的balance是开启或者关闭

db.getSiblingDB("config").collections.findOne({_id : "students.grades"}).noBalance;

2.6.5 问题解决

mongodb在做自动分片平衡的时候,或引起数据库响应的缓慢,可以通过禁用自动平衡以及设置自动平衡进行的时间来解决这一问题。

(1)禁用分片的自动平衡

// connect to mongos
> use config
> db.settings.update( { _id: "balancer" }, { $set : { stopped: true } } , true );

(2)自定义 自动平衡进行的时间段

// connect to mongos
> use config
> db.settings.update({ _id : "balancer" }, { $set : { activeWindow : { start : "21:00", stop : "9:00" } } }, true )

php7.0编译memcached扩展

场景

系统自带的memcache.so扩展只适用于系统自带的php5.3,由于生产环境的php7.0是自己编译的,所以各种扩展也要重新编译生成

php的memcache客户端扩展有两种

1. memcache扩展

列表地址:http://pecl.php.net/package/memcache

源码包包直接下载地址:http://pecl.php.net/get/memcache

这个最新的版本也是2013年的了,下载编译了一下,报了一个 not found php_smart_str_public.h文件的错误,查看了一下 php安装目录下的 include/php/ext/standard 目录,发现这个头文件在php7.0中已经被改名为php_smart_string_public.h。由此可见这个memcache的客户端版本已太旧,不支持php7.0了

2. memcached扩展

列表地址:http://pecl.php.net/package/memcached

源码包直接下载地址:http://pecl.php.net/get/memcached

此外还有一个git维护地址 https://github.com/php-memcached-dev/php-memcached

这个包最新版本是2017年11月份更新的,是支持php7.0的。

下载流程

git clone https://github.com/php-memcached-dev/php-memcached

wget http://pecl.php.net/get/memcached

解压后将源码放入 /root 下或者 /home/用户目录下

编译流程

进入源码目录

#cd ~/php-memcached

调用phpize(根据实际phpize路径)

#/usr/local/php7.0/bin/phpize

configure目录

#./configure --with-php-config=/usr/local/php7.0/bin/php-config --with-zlib-dir

编译 & 安装

#make && make install

tips

因为memcached版本的扩展基于libmemcached,如果服务器上未安装,编译的时候会提示错误

解决方案就是yum安装即可

yum install libmemcached

yum install libmemcached-devel

成功

打开 php安装目录/lib/php/extensions/no-debug-zts-*/即可看到memcached.so扩展

然后再php.ini配置文件加载即可

使用memcached加速typecho

安装libmemcached

yum install cyrus-sasl-devel -y
wget https://launchpad.net/libmemcached/1.0/1.0.18/+download/libmemcached-1.0.18.tar.gz
tar zxf libmemcached-1.0.18.tar.gz
cd libmemcached-1.0.18
./configure --libdir=/usr/lib64
make
make install clean
ldconfig
ll /usr/lib64/libmemcached* -th
-rw-r--r-- 1 root root 212K Jan 14 19:01 /usr/lib64/libmemcachedutil.a
-rw-r--r-- 1 root root 2.9M Jan 14 19:01 /usr/lib64/libmemcached.a
-rwxr-xr-x 1 root root 1.1K Jan 14 19:01 /usr/lib64/libmemcachedutil.la
lrwxrwxrwx 1 root root   25 Jan 14 19:01 /usr/lib64/libmemcachedutil.so -> libmemcachedutil.so.2.0.0
lrwxrwxrwx 1 root root   25 Jan 14 19:01 /usr/lib64/libmemcachedutil.so.2 -> libmemcachedutil.so.2.0.0
-rwxr-xr-x 1 root root 111K Jan 14 19:01 /usr/lib64/libmemcachedutil.so.2.0.0
-rwxr-xr-x 1 root root  974 Jan 14 19:01 /usr/lib64/libmemcached.la
lrwxrwxrwx 1 root root   22 Jan 14 19:01 /usr/lib64/libmemcached.so -> libmemcached.so.11.0.0
lrwxrwxrwx 1 root root   22 Jan 14 19:01 /usr/lib64/libmemcached.so.11 -> libmemcached.so.11.0.0
-rwxr-xr-x 1 root root 1.3M Jan 14 19:01 /usr/lib64/libmemcached.so.11.0.0
lrwxrwxrwx 1 root root   25 Jan 14 18:31 /usr/lib64/libmemcachedutil.so.0 -> libmemcachedutil.so.0.0.0
lrwxrwxrwx 1 root root   21 Jan 14 18:31 /usr/lib64/libmemcached.so.2 -> libmemcached.so.2.0.0
-rwxr-xr-x 1 root root  64K Nov 12  2010 /usr/lib64/libmemcached.so.2.0.0
-rwxr-xr-x 1 root root 6.9K Nov 12  2010 /usr/lib64/libmemcachedutil.so.0.0.0

安装php-memcached

/production/server/php/bin/phpize
./configure --with-php-config=/production/server/php/bin/php-config --enable-memcached --enable-memcached-json
make -j2
make install clean

Installing shared extensions:     /production/server/php/lib/php/extensions/no-debug-non-zts-20131226/
find . -name *.gcno -o -name *.gcda | xargs rm -f
find . -name *.lo -o -name *.o | xargs rm -f
find . -name *.la -o -name *.a | xargs rm -f
find . -name *.so | xargs rm -f
find . -name .libs -a -type d|xargs rm -rf
rm -f libphp.la       modules/* libs/*

打开

vim php/etc/php.ini

# 添加以下配置
[memcached]
extension=memcached.so

sbin/php-fpm -t
[14-Jan-2018 19:13:55] NOTICE: configuration file /production/server/php/etc/php-fpm.conf test is successful
service php-fpm reload
/production/server/php/bin/php -m | grep memcached
memcached

安装配置memcached

yum install memcached -y
diff /etc/sysconfig/memcached /etc/sysconfig/memcached.ori
5c5
< OPTIONS="-l 127.0.0.1"
---
> OPTIONS=""

安装扩展

git clone https://github.com/phpgao/TpCache.git

MariaDB初学者管理命令

早些时候,我们已经学会了在CentOS/RHEL 7上安装MariaDB服务器(见 http://www.linuxidc.com/Linux/2018-01/150296.htm),这个服务器现在是RHEL/CentOS 7的默认数据库。现在我们将讨论一些有用的MariaDB管理命令。 这些是一些非常基本的命令,可以让你开始使用MariaDB,这些命令也可以和MySQL一起使用,因为Mariadb只支持MySQL版本

MariaDB管理命令

检查您的MariaDB安装的版本

要检查数据库安装的当前版本,请在终端中输入以下命令

$ mysql –version

该命令为您提供当前版本的数据库。 另外你也可以运行下面提到的命令来查看详细的版本,

$ mysqladmin –u root –p version

登录到mariadb

要登录到mariadb服务器,请运行

$ mysql –u root –p

然后输入密码登录会话。

显示所有数据库

要显示你的maridb目前所有的数据库,运行

$ show databases;

在你登录到mariadb后。

创建新的数据库

要在mariadb中创建一个新的数据库,运行

$ create database linuxidc;

当登录到mariabdb。要从终端直接创建数据库,请运行

$ mysqladmin -u user -p create linuxidc

这里,linuxidc是新数据库的名称。

删除数据库

要删除数据库,请运行

$ drop database linuxidc;

从mariadb登录会话。或者你也可以使用,

$ mysqladmin –u root –p drop linuxidc

注意:如果在运行mysqladmin命令时出现“访问被拒绝”错误,那可能是因为我们没有赋予root权限。为此,请运行第7点中提到的命令,用root替换用户的名称。

创建新用户

要为数据库创建新用户,请运行

$ CREATE USER ‘linuxidc’@’localhost’ IDENTIFIED BY ‘password’;

允许用户访问数据库

为了向用户提供对单个数据库的访问,请运行

$ GRANT ALL PRIVILEGES ON test.* to ‘linuxidc’@’localhost’;

这将为用户提供完整的数据库命名测试权限。我们也可以授予用户SELECT,INSERT,DELETE权限。

为了提供对所有数据库的访问,用*即

$ GRANT ALL PRIVILEGES ON *.* to ‘linuxidc’@’localhost’;

创建数据库的备份/转储

要创建单个数据库,请从终端窗口运行以下命令,

$ mysqldump –u root –p database_name>db_backup.sql

要在单个命令中创建多个数据库的备份,

$ mysqldump –u root –p – – databases db1 db2 > db12_backup.sql

要在单个命令中创建所有数据库的转储,

$ mysqldump –u root –p – – all-databases >all_dbs.sql

从转储中恢复数据库

要从转储中恢复数据库,请运行

$ mysql –u root –p database_name<db_backup.sql

但是这个命令只有在没有以前的数据库名称相同时才能使用。如果要将数据库数据恢复到任何已经创建的数据库,我们需要使用’mysqlimport’命令,

mysqlimport –u root –p database_name<db_backup.sql

在mariadb中更改用户的密码

这个例子我们要更改“root”的密码,但是您可以使用下面的过程来更改任何用户的密码,

登录到mariadb并选择’mysql’数据库,

$ mysql –u root –p
$ use mysql;

然后运行以下,

$ update user set password=PASSWORD(‘your_new_password_here’) where User=’root’;

接下来,重新加载权限,

$ flush privileges;

然后退出会话。

这是我们的一些有用的MariaDB管理命令的教程。请在下面的评论框中留下您的意见或建议。

macOS中使用brew安装MariaDB

MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品。在存储引擎方面,使用XtraDB来代替MySQL的InnoDB。MariaDB由MySQL的创始人Michael Widenius主导开发,他早前曾以10亿美元的价格,将自己创建的公司MySQL卖给了SUN,此后,随着SUN被甲骨文收购,MySQL的所有权也落入Oracle的手中。MariaDB名称来自Michael Widenius的女儿Maria的名字。

查看MariaDB版本

brew info mariadb  

安装MariaDB

brew install mariadb  

运行数据库安装程序

分别执行下面的命令来实现安装:

unset TMPDIR  
cd /usr/local/opt/mariadb/bin/
mysql_install_db  

运行MariaDB

mysql.server start  

brew services start mariaDB

添加环境变量

vi ~/.bash_profile 

添加

export PATH=$PATH:/usr/local/opt/mariadb/bin:/usr/local/sbin/

运行生效

source ~/.bash_profile

重设root用户的密码、移除匿名用户、移除默认的test数据库等等,具体的执行和设置如下:

mysql_secure_installation

依次

Enter current password for root (enter for none):  
Set root password? [Y/n] Y  
Remove anonymous users? [Y/n] Y  
Disallow root login remotely? [Y/n] n  
Remove test database and access to it? [Y/n] Y  
Reload privilege tables now? [Y/n] Y  

连接MariaDB数据库的命令:

mysql -u root -p  

验证MariaDB版本

MariaDB [(none)]> select @@version;  

MariaDB基础命令

-- 显示数据库列表
show databases;

-- 切换到名为mysql的数据库,显示该库中的数据表
use mysql;  
show tables;

-- 显示数据表table的结构
desc table;

-- 建数据库A与删数据库A
create database `database_A`;  
drop database `database_A`;

-- 建表:
use database_A;  
create table table_A(字段列表);  
drop table table_A;