使用Docker 实现微服务并搭建博客,一文全掌握。

未分类

Docker 是一个容器工具,提供虚拟环境。很多人认为,它改变了我们对软件的认识。

本文,通过搭建一个博客的例子,来介绍如何使用Docker实现微服务。
站在 Docker 的角度,软件就是容器的组合:业务逻辑容器、数据库容器、储存容器、队列容器……Docker 使得软件可以拆分成若干个标准化容器,然后像搭积木一样组合起来。
这正是微服务(microservices)的思想:软件把任务外包出去,让各种外部服务完成这些任务,软件本身只是底层服务的调度中心和组装层。

未分类

微服务很适合用 Docker 容器实现,每个容器承载一个服务。一台计算机同时运行多个容器,从而就能很轻松地模拟出复杂的微服务架构。

未分类

上一篇教程介绍了 Docker 的概念和基本用法,本文接着往下介绍,如何在一台计算机上实现多个服务,让它们互相配合,组合出一个应用程序。

未分类

我选择的示例软件是 WordPress。它是一个常用软件,全世界用户据说超过几千万。同时它又非常简单,只要两个容器就够了(业务容器 + 数据库容器),很适合教学。而且,这种”业务 + 数据库”的容器架构,具有通用性,许多应用程序都可以复用。

为了加深读者理解,本文采用三种方法,演示如何架设 WordPress 网站。

  • 方法 A:自建 WordPress 容器
  • 方法 B:采用官方的 WordPress 容器
  • 方法 C:采用 Docker Compose 工具

一、预备工作:image 仓库的镜像网址

本教程需要从仓库下载 image 文件,但是国内访问 Docker 的官方仓库很慢,还经常断线,所以要把仓库网址改成国内的镜像站。这里推荐使用官方镜像 registry.docker-cn.com 。下面是我的 Debian 系统的默认仓库修改方法,其他系统的修改方法参考官方文档。
打开/etc/default/docker文件(需要sudo权限),在文件的底部加上一行。

DOCKER_OPTS="--registry-mirror=https://registry.docker-cn.com"

然后,重启 Docker 服务。

$ sudo service docker restart

现在就会自动从镜像仓库下载 image 文件了。

二、方法 A:自建 WordPress 容器

前面说过,本文会用三种方法演示 WordPress 的安装。第一种方法就是自建 WordPress 容器。

2.1 官方 的 PHP image

首先,新建一个工作目录,并进入该目录。

$ mkdir docker-demo && cd docker-demo

然后,执行下面的命令。

$ docker container run 
  --rm 
  --name wordpress 
  --volume "$PWD/":/var/www/html 
  php:5.6-apache

上面的命令基于php的 image 文件新建一个容器,并且运行该容器。php的标签是5.6-apache,说明装的是 PHP 5.6,并且自带 Apache 服务器。该命令的三个参数含义如下。

  • –rm:停止运行后,自动删除容器文件。
  • –name wordpress:容器的名字叫做wordpress。
  • –volume “$PWD/”:/var/www/html:将当前目录($PWD)映射到容器的/var/www/html(Apache 对外访问的默认目录)。因此,当前目录的任何修改,都会反映到容器里面,进而被外部访问到。

运行上面的命令以后,如果一切正常,命令行会提示容器对外的 IP 地址,请记下这个地址,我们要用它来访问容器。我分配到的 IP 地址是 172.17.0.2。

打开浏览器,访问 172.17.0.2,你会看到下面的提示。

Forbidden
You don't have permission to access / on this server.

这是因为容器的/var/www/html目录(也就是本机的docker-demo目录)下面什么也没有,无法提供可以访问的内容。

请在本机的docker-demo目录下面,添加一个最简单的 PHP 文件index.php。

<?php 
phpinfo();?>

保存以后,浏览器刷新172.17.0.2,应该就会看到熟悉的phpinfo页面了。

未分类

2.2 拷贝 WordPress 安装包

既然本地的docker-demo目录可以映射到容器里面,那么把 WordPress 安装包拷贝到docker-demo目录下,不就可以通过容器访问到 WordPress 的安装界面了吗?

首先,在docker-demo目录下,执行下面的命令,抓取并解压 WordPress 安装包。

$ wget https://cn.wordpress.org/wordpress-4.9.4-zh_CN.tar.gz
$ tar -xvf wordpress-4.9.4-zh_CN.tar.gz

解压以后,WordPress 的安装文件会在docker-demo/wordpress目录下。

这时浏览器访问http://172.17.0.2/wordpress,就能看到 WordPress 的安装提示了。

未分类

2.3 官方的 MySQL 容器

WordPress 必须有数据库才能安装,所以必须新建 MySQL 容器。

打开一个新的命令行窗口,执行下面的命令。

$ docker container run 
  -d 
  --rm 
  --name wordpressdb 
  --env MYSQL_ROOT_PASSWORD=123456 
  --env MYSQL_DATABASE=wordpress 
  mysql:5.7

上面的命令会基于 MySQL 的 image 文件(5.7版本)新建一个容器。该命令的五个命令行参数的含义如下。

  • -d:容器启动后,在后台运行。
  • –rm:容器终止运行后,自动删除容器文件。
  • –name wordpressdb:容器的名字叫做wordpressdb
  • –env MYSQL_ROOT_PASSWORD=123456:向容器进程传入一个环境变量MYSQL_ROOT_PASSWORD,该变量会被用作 MySQL 的根密码。
  • –env MYSQL_DATABASE=wordpress:向容器进程传入一个环境变量MYSQL_DATABASE,容器里面的 MySQL 会根据该变量创建一个同名数据库(本例是WordPress)。

运行上面的命令以后,正常情况下,命令行会显示一行字符串,这是容器的 ID,表示已经新建成功了。

这时,使用下面的命令查看正在运行的容器,你应该看到wordpress和wordpressdb两个容器正在运行。

$ docker container ls

其中,wordpressdb是后台运行的,前台看不见它的输出,必须使用下面的命令查看。

$ docker container logs wordpressdb

2.4 定制 PHP 容器

现在 WordPress 容器和 MySQL 容器都已经有了。接下来,要把 WordPress 容器连接到 MySQL 容器了。但是,PHP 的官方 image 不带有mysql扩展,必须自己新建 image 文件。

首先,停掉 WordPress 容器。

$ docker container stop wordpress

停掉以后,由于–rm参数的作用,该容器文件会被自动删除。

然后,在docker-demo目录里面,新建一个Dockerfile文件,写入下面的内容。

FROM php:5.6-apache
RUN docker-php-ext-install mysqli
CMD apache2-foreground

上面代码的意思,就是在原来 PHP 的 image 基础上,安装mysqli的扩展。然后,启动 Apache。

基于这个 Dockerfile 文件,新建一个名为phpwithmysql的 image 文件。

$ docker build -t phpwithmysql .

2.5 WordPress 容器连接 MySQL

现在基于 phpwithmysql image,重新新建一个 WordPress 容器。

$ docker container run 
  --rm 
  --name wordpress 
  --volume "$PWD/":/var/www/html 
  --link wordpressdb:mysql 
  phpwithmysql

跟上一次相比,上面的命令多了一个参数–link wordpressdb:mysql,表示 WordPress 容器要连到wordpressdb容器,冒号表示该容器的别名是mysql。

这时还要改一下wordpress目录的权限,让容器可以将配置信息写入这个目录(容器内部写入的/var/www/html目录,会映射到这个目录)。

$ chmod -R 777 wordpress

接着,回到浏览器的http://172.17.0.2/wordpress页面,点击”现在就开始!”按钮,开始安装。

WordPress 提示要输入数据库参数。输入的参数如下。

未分类

  • 数据库名:wordpress
  • 用户名:root
  • 密码:123456
  • 数据库主机:mysql
  • 表前缀:wp_(不变)

点击”下一步”按钮,如果 WordPress 连接数据库成功,就会出现下面的页面,这就表示可以安装了。

未分类

至此,自建 WordPress 容器的演示完毕,可以把正在运行的两个容器关闭了(容器文件会自动删除)。

$ docker container stop wordpress wordpressdb

三、方法 B:Wordpress 官方镜像

上一部分的自建 WordPress 容器,还是挺麻烦的。其实不用这么麻烦,Docker 已经提供了官方 WordPress image,直接用那个就可以了。有了上一部分的基础,下面的操作就很容易理解了。

3.1 基本用法

首先,新建并启动 MySQL 容器。

$ docker container run 
  -d 
  --rm 
  --name wordpressdb 
  --env MYSQL_ROOT_PASSWORD=123456 
  --env MYSQL_DATABASE=wordpress 
  mysql:5.7

然后,基于官方的 WordPress image,新建并启动 WordPress 容器。

$ docker container run 
  -d 
  --rm 
  --name wordpress 
  --env WORDPRESS_DB_PASSWORD=123456 
  --link wordpressdb:mysql 
  wordpress

上面命令中,各个参数的含义前面都解释过了,其中环境变量WORDPRESS_DB_PASSWORD是 MySQL 容器的根密码。

上面命令指定wordpress容器在后台运行,导致前台看不见输出,使用下面的命令查出wordpress容器的 IP 地址。

$ docker container inspect wordpress

上面命令运行以后,会输出很多内容,找到IPAddress字段即可。我的机器返回的 IP 地址是172.17.0.3。

浏览器访问172.17.0.3,就会看到 WordPress 的安装提示。

未分类

3.2 WordPress 容器的定制

到了上一步,官方 WordPress 容器的安装就已经成功了。但是,这种方法有两个很不方便的地方。

  • 每次新建容器,返回的 IP 地址不能保证相同,导致要更换 IP 地址访问 WordPress。
  • WordPress 安装在容器里面,本地无法修改文件。

解决这两个问题很容易,只要新建容器的时候,加两个命令行参数就可以了。

先把刚才启动的 WordPress 容器终止(容器文件会自动删除)。

$ docker container stop wordpress

然后,使用下面的命令新建并启动 WordPress 容器。

$ docker container run 
  -d 
  -p 127.0.0.2:8080:80 
  --rm 
  --name wordpress 
  --env WORDPRESS_DB_PASSWORD=123456 
  --link wordpressdb:mysql 
  --volume "$PWD/wordpress":/var/www/html 
  wordpress

上面的命令跟前面相比,命令行参数只多出了两个。

  • -p 127.0.0.2:8080:80:将容器的 80 端口映射到127.0.0.2的8080端口。
  • –volume “$PWD/wordpress”:/var/www/html:将容器的/var/www/html
    目录映射到当前目录的wordpress子目录。

浏览器访问127.0.0.2:8080:80就能看到 WordPress 的安装提示了。而且,你在wordpress子目录下的每次修改,都会反映到容器里面。

最后,终止这两个容器(容器文件会自动删除)。

$ docker container stop wordpress wordpressdb

四、方法 C:Docker Compose 工具

上面的方法 B 已经挺简单了,但是必须自己分别启动两个容器,启动的时候,还要在命令行提供容器之间的连接信息。因此,Docker 提供了一种更简单的方法,来管理多个容器的联动。

4.1 Docker Compose 简介

未分类

Compose 是 Docker 公司推出的一个工具软件,可以管理多个 Docker 容器组成一个应用。你需要定义一个 YAML 格式的配置文件docker-compose.yml,写好多个容器之间的调用关系。然后,只要一个命令,就能同时启动/关闭这些容器。

$ docker-compose up
$ docker-compose stop

4.2 Docker Compose 的安装

Mac 和 Windows 在安装 docker 的时候,会一起安装 docker compose。Linux 系统下的安装参考官方文档。

安装完成后,运行下面的命令。

$ docker-compose --version

4.3 WordPress 示例

在docker-demo目录下,新建docker-compose.yml文件,写入下面的内容。

mysql:
    image: mysql:5.7
    environment:
     - MYSQL_ROOT_PASSWORD=123456
     - MYSQL_DATABASE=wordpress
web:
    image: wordpress
    links:
     - mysql
    environment:
     - WORDPRESS_DB_PASSWORD=123456
    ports:
     - "127.0.0.3:8080:80"
    working_dir: /var/www/html
    volumes:
     - wordpress:/var/www/html

上面代码中,两个顶层标签表示有两个容器mysql和web。每个容器的具体设置,前面都已经讲解过了,还是挺容易理解的。

启动两个容器。

$ docker-compose up

浏览器访问 http://127.0.0.3:8080,应该就能看到 WordPress 的安装界面。

现在关闭两个容器。

$ docker-compose stop

关闭以后,这两个容器文件还是存在的,写在里面的数据不会丢失。下次启动的时候,还可以复用。下面的命令可以把这两个容器文件删除(容器必须已经停止运行)。

$ docker-compose rm

搭建keepalived+mysql主从复制高可用

准备工作

  • 完成keepalived的安装
  • 完成docker的安装
  • docker镜像里面自行安装iproute2, vim, iputils-ping(可选)等工具,便于测试
apt-get install iproute2
apt-get install vim
apt-get install iputils-ping

主数据库master

1. 使用docker安装mysql

mkdir -p ~/compose/mysql-master
cd ~/compose/mysql-master

cat docker-compose.yml
version: '2'
services:
  mysql-master:
    image: mysql:5
    restart: always
    container_name: mysql-master
    ports:
      - 3306:3306
    volumes:
      - ./conf.d:/etc/mysql/conf.d
      - /data/docker/mysql-master/data:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=123456
networks:
  default:
    external:
      name: service

假如没有把3306端口映射到宿主机,在宿主机上可通过docker-ip:3306来访问。

2. mysql的配置

cat conf.d/lowercase.cnf
[mysqld]
lower_case_table_names = 1
default-time-zone = '+08:00'
character-set-server = utf8
event_scheduler = on
log-bin = mysql-bin
server-id = 1

参数说明:

  • lower_case_table_names 设置不区分大小写
  • default-time-zone 设置时区为东八区
  • character-set-server 修改字符集为utf8
  • log-bin 开启二进制日志
  • server-id 设置server-id

master开启二进制日志后默认记录所有库所有表的操作,可以通过配置来指定只记录指定的数据库甚至指定的表的操作,具体在mysql配置文件的[mysqld]可添加修改如下选项:

# 不同步哪些数据库
binlog-ignore-db = mysql  
binlog-ignore-db = test  
binlog-ignore-db = information_schema  
# 只同步哪些数据库,除此之外,其他不同步
binlog-do-db = mydatabase

3. 启动服务

docker-compose pull && docker-compose up -d

4. 在宿主机连接mysql

说明: 由于我是使用虚拟机安装的字符版Ubuntu系统,所以使用MyCli作为mysql命令行工具来连接mysql。

# 查看master主机的ip地址为192.168.11.188,使用MyCli连接mysql
mycli -h 192.168.11.188 -u root -p 123456

# 给root用户分配远程访问权限:
grant all on *.* to root@'%' identified by "123456";
flush privileges; 

# 查看master状态
mysql [email protected]:(none)> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000003 | 586      |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set
Time: 0.004s

需要记录主数据库的二进制文件名(mysql-bin.000003)和位置586。

从数据库slave

1. docker-compose.yml

只需要从主数据库的配置名称由mysql-master改为mysql-slave即可。

2. mysql配置

cat conf.d/lowercase.cnf
[mysqld]
lower_case_table_names = 1
default-time-zone = '+08:00'
character-set-server = utf8
event_scheduler = on
server-id= 2

需添加server-id并且与主数据库中不一致

3. 启动服务

docker-compose pull && docker-compose up -d

4. 在宿主机连接mysql

# 查看slave主机的ip地址为192.168.11.186,使用MyCli连接mysql
mycli -h 192.168.11.186 -u root -p 123456
# 给root用户分配远程访问权限,略

# 执行同步SQL语句,参照MASTER配置:
CHANGE MASTER TO MASTER_HOST='192.168.11.188',MASTER_USER='root',MASTER_PASSWORD='123456',MASTER_LOG_FILE='mysql-bin.000003',MASTER_LOG_POS=586;

# 启动slave同步进程
start slave;

# 查看slave状态:
show slave statusG;

其中下面两项为YES则表示成功:
Slave_IO_Running: Yes
Slave_SQL_Running: Yes

测试主从复制

在MASTER中新建数据库和表,发现数据在SLAVE中已经实时同步过来

Keepalived监控mysql服务

1. master主机上的配置

cat /etc/keepalived/keepalived.conf

vrrp_script chk_mysql_port {     #检测mysql服务是否在运行。有很多方式,比如进程,用脚本检测等等
    script "/opt/chk_mysql.sh"   #这里通过脚本监测
    interval 2                   #脚本执行间隔,每2s检测一次
    weight -5                    #脚本结果导致的优先级变更,检测失败(脚本返回非0)则优先级 -5
    fall 2                    #检测连续2次失败才算确定是真失败。会用weight减少优先级(1-255之间)
    rise 1                    #检测1次成功就算成功。但不修改优先级
}
vrrp_instance VI_1 {
    state MASTER
    interface ens33 #指定虚拟ip的网卡接口,不一定是eth0根据ifconfig确定
    virtual_router_id 51 #路由器标识,MASTER和BACKUP必须是一致的
    priority 100 #定义优先级,数字越大,优先级越高,在同一个vrrp_instance下,MASTER的优先级必须大于BACKUP的优先级。这样MASTER故障恢复后,就可以将VIP资源再次抢回来
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    virtual_ipaddress {
        192.168.11.25
    }
    track_script {
       chk_mysql_port
    }
}

需要配置的地方有:script,state,interface,virtual_router_id,priority,virtual_ipaddress等

2. slave主机上的配置

cat /etc/keepalived/keepalived.conf

vrrp_script chk_mysql_port {     
    script "/opt/chk_mysql.sh"
    interval 2
    weight -5
    fall 2
    rise 1
}
vrrp_instance VI_1 {
    state BACKUP
    interface ens33
    virtual_router_id 51
    priority 99
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 123456
    }
    virtual_ipaddress {
        192.168.11.25
    }
    track_script {
       chk_mysql_port
    }
}

只需要设置state为BACKUP, priority比MASTER低即可。

3. 监测监本的配置

cat /opt/chk_mysql.sh

#!/bin/bash
counter=$(netstat -na|grep "LISTEN"|grep "3306"|wc -l)
if [ "${counter}" -eq 0 ]
then
    /etc/init.d/keepalived stop
else
   echo "running..." >> /opt/keepalived-running-info.log
   sleep 5000
fi

Keepalived监测Mysql测试

先要保证两台服务器的mysql服务正常启动哦~

1. 启动Keepalived

# 在master和slave上执行
sudo /etc/init.d/keepalived start

# 查看脚本是否正常执行
tail -f /opt/keepalived-running-info.log

# 查看master的ip,发现虚拟ip绑定成功
ip addr

2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:64:35:17 brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.188/24 brd 192.168.11.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 192.168.11.25/32 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe64:3517/64 scope link
       valid_lft forever preferred_lft forever

2. 高可用测试

在任意一台主机执行以下命令测试:

mycli -h 192.168.11.25 -u root -p 123456 #ok
mycli -h 192.168.11.188 -u root -p 123456 #ok
mycli -h 192.168.11.186 -u root -p 123456 #ok

再次查看master主机的ip,发现虚拟ip不见了:

ip addr

2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:64:35:17 brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.188/24 brd 192.168.11.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fe64:3517/64 scope link
       valid_lft forever preferred_lft forever

此时查看slave主机的ip定, 发现ip漂移情况,虚拟ip自动绑定到到了slave主机上:

ip addr

2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 00:0c:29:f0:00:ad brd ff:ff:ff:ff:ff:ff
    inet 192.168.11.186/24 brd 192.168.11.255 scope global ens33
       valid_lft forever preferred_lft forever
    inet 192.168.11.25/32 scope global ens33
       valid_lft forever preferred_lft forever
    inet6 fe80::20c:29ff:fef0:ad/64 scope link
       valid_lft forever preferred_lft forever

此时查看slave主机情况,Slave_IO_Running变成了Connecting:

mycli -h 192.168.11.186 -u root -p 123456
show slave statusG;

Slave_IO_Running | Connecting
Slave_SQL_Running | Yes

继续测试

现在把master重新启动

mycli -h 192.168.11.188 -u root -p 123456 #ok
show master status;
+------------------+----------+--------------+-------------------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB              | Executed_Gtid_Set |
+------------------+----------+--------------+-------------------------------+-------------------+
| mysql-bin.000009 | 154      |              | mysql,test,information_schema |                   |
+------------------+----------+--------------+-------------------------------+-------------------+
1 row in set
Time: 0.004s

会发现信息发生了改变,再次查看slave的状态恢复正常:

Slave_IO_Running | Yes
Slave_SQL_Running | Yes

继续查看master主机发现未绑定vip,vip依然存在于slave所属主机上面。

那么现在把slave停掉试试看:

docker stop mysql-slave

mycli -h 192.168.11.186 -u root -p 123456 #error
mycli -h 192.168.11.25 -u root -p 123456 # error
mycli -h 192.168.11.188 -u root -p 123456 # ok

出现只有master主机的mysql服务能访问的情况,是因为上面测试把两个mysql服务停止,脚本监测不到3306端口执行了/etc/init.d/keepalived stop,所以需要重新启动keepalived :

# master主机
sudo /etc/init.d/keepalived start

# slave主机
docker start mysql-slave
sudo /etc/init.d/keepalived start

这个时候发现vip又重新绑定到master上面,OK,一切正常。

总结

写的可能有些啰嗦,但是每一步的操作和测试又是必要的,只为记录自己的一次学习心得。

如何连接远程mysql数据库

前言

最近买了个滴滴云,在上面装了wordpress,想看到数据库的内容,虽然可以直接用命令行的方式来看,但是实在是不友好,因此想用小海豚在本地连接远程数据库,实现增删改查。

赋予数据库账号远程连接的权限

首先用xshell登陆到vps,进入到数据库命令行:

mysql -u root -p

接着输入密码

执行:

grant all  on 数据库名.* to 用户名@'%' identified by 用户密码;

%代表的是全部IP都可访问。

扩展:

grant用法
    grant 权限 on 数据库.* to 用户名@'登录主机' identified by '密码'
权限:
    常用总结, ALL/ALTER/CREATE/DROP/SELECT/UPDATE/DELETE
数据库:
     *.*                    表示所有库的所有表
     test.*                表示test库的所有表
     test.test_table  表示test库的test_table表     
用户名:
     mysql账户名
登陆主机:
     允许登陆mysql server的客户端ip
     '%'表示所有ip
     'localhost' 表示本机
     '192.168.10.2' 特定IP
密码:
      账户对应的登陆密码

开放VPS的安全组策略

这步很重要,前面我一直连接不上就是因为这个原因,每个VPS厂商都有自己的安全组策略,什么是安全组策略呢,就是开放允许访问的端口,端口不通的话,流量是没办法进出的,那么也就没办法访问了。

那么我们去具体的VPS厂商的网站去更改安全组,开放数据库的端口即可,如果你没特殊设置的话,一般是3306。

mysql数据库导入导出

mysql数据库导入导出整理。

1. 数据库导入

mysql -uroot -p123456 --default-character-set=utf8 liuyj_ctisd < liuyj_ctisd.sql

2. 数据库导出

导出整个数据库

mysqldump -uroot -p123456 liuyj_ctisd > liuyj_ctisd.sql

导出数据库单表

mysqldump -uroot -p123456 liuyj_ctisd log> liuyj_ctisd_log.sql

导出数据库表结构

mysqldump -uroot -p123456 -d --add-drop-table liuyj_ctisd> liuyj_ctisd.sql

-d  导出空表
--add-drop-table 每个数据表创建之前添加drop数据表语句

3. 问题记录

mysqldump在命令行导出时,如果输入密码,提示如下警告信息(已执行成功,只是会提示警告信息)

Warning: Using a password on the command line interface can be insecure.

3.1.修复方法

  • 在命令行导出时,手动输入密码
  • 在/etc/mysql/my.cnf文件最后追加
[mysqldump]
user=user_name
password=password

MySQL运维系列 之 如何快速定位IO瓶颈

摘要: MySQL的瓶颈,一般分为IO密集型和CPU密集型 CPU出问题的情况比较少,最近就遇到过一次比较大的故障,这个话题后面会有一篇专题介绍 今天主要聊聊IO密集型的应用中,我们应该如何快速定位到是谁占用了IO资源比较多 背景 环境1.

MySQL的瓶颈,一般分为IO密集型和CPU密集型

CPU出问题的情况比较少,最近就遇到过一次比较大的故障,这个话题后面会有一篇专题介绍

今天主要聊聊IO密集型的应用中,我们应该如何快速定位到是谁占用了IO资源比较多

背景

  • 环境
1. MySQL 5.7 +
    低版本MySQL这边不再考虑,就像还有使用SAS盘的公司一样,费时费力,MySQL5.7+ 标配
2. InnoDB 存储引擎
3. Centos 6

实战

关于IO的问题,大家能想到的监控工具有哪些

  • iostat
  • dstat
  • iotop

没错,以上都是神器,可以直接用iotop找到占用资源最多的进程

先上一张图

未分类

是的,根据这张图,你能发现的就是MySQL的某个io线程占用了比较多的disk资源,然后呢?

然后,就是去MySQL里面去找,有经验的DBA会去看slow log,或者processlist中去查找相关的sql语句

通常情况下,DBA只会一脸茫然的看到一堆MySQL的query语句,一堆slow log里面去分析,有如大海捞针,定位问题繁琐而低效

如果,你使用的是MySQL5.7+ 版本,那么你就会拥有一件神器(说了好多遍了),可以快速而精准的定位问题

  • 如何快速定位到IO瓶颈消耗在哪里

iotop + threads

dba:lc> select * from performance_schema.threads where thread_os_id=37012G
*************************** 1. row ***************************
          THREAD_ID: 96
               NAME: thread/sql/one_connection
               TYPE: FOREGROUND
     PROCESSLIST_ID: 15
   PROCESSLIST_USER: dba
   PROCESSLIST_HOST: NULL
     PROCESSLIST_DB: sbtest
PROCESSLIST_COMMAND: Query
   PROCESSLIST_TIME: 0
  PROCESSLIST_STATE: query end
   PROCESSLIST_INFO: INSERT INTO sbtest1(k, c, pad) VALUES(25079106, '33858784348-81663287461-16031064329-06006952037-79426243027-69964324491-90950423034-40185804987-62166137368-06259615216', '47186118229-42754
696460-81034599900-41836403072-66805611739'),(24907169, '77074724245-16833049423-38868029911-54850236074-63700733526-39699866447-52646750572-85552352492-59476301007-32196580154', '79013412600-99031855741-696987
96712-65630963686-19653514942'),(24896311, '28403978193-66350947863-03931166713-97714847962-65299790981-39948912629-14070597101-63277652140-34421148430-61801121402', '05239379274-22840441238-37771744512-9234774
1972-52847679847'),(18489383, '89292717216-01584483614-67433536730-45584233994-29817613740-77179131661-10692787267-83942773303-14971155500-36206705010', '55201342831-85536327239-84383935287-06948377235-96437333
726'),(24790463, '99362943588-41160434740-62783664419-16002619743-04761662097-94273988379-52564232648-19738707042-79143532768-89687113917', '09717575620-89781830996-88443720661-19001024583-14971953687'),(2
   PARENT_THREAD_ID: NULL
               ROLE: NULL
       INSTRUMENTED: YES
            HISTORY: YES
    CONNECTION_TYPE: Socket
       THREAD_OS_ID: 37012
1 row in set (0.00 sec)

你看,消耗资源的SQL语句立刻就呈现在你眼前,就是如此高效

好了,以上列出的,还只是全部功能的冰山一角,更多的玩法等待你去解锁。

以上定位的问题也比较的简单,还有一些复杂的IO问题,比如:binlog写入过大、binlog扫描过多、同步线程阻塞、临时表造成的IO过大,等等问题,都可以用此神器一窥究竟

总结

  1. MySQL5.7 默默的提供了非常多的实用工具和新特性,需要DBA们去挖掘和探索。将看似平淡无奇的特性挖掘成黑武器,你才能成为那闪着光芒的Top5 MySQLer

  2. 工欲善其事必先利其器

MySQL root密码忘记,原来还有更优雅的解法!

一直以来,对于MySQL root密码的忘记,以为只有一种解法-skip-grant-tables。

问了下群里的大咖,第一反应也是skip-grant-tables。通过搜索引擎简单搜索了下,无论是百度,抑或Google,只要是用中文搜索,首页都是这种解法。可见这种解法在某种程度上已经占据了使用者的心智。下面具体来看看。

skip-grant-tables的解法

首先,关闭实例

这里,只能通过kill mysqld进程的方式。

注意:不是mysqld_safe进程,也切忌使用kill -9。

# ps -ef |grep mysqld
root      6220  6171  0 08:14 pts/0    00:00:00 /bin/sh bin/mysqld_safe --defaults-file=my.cnf
mysql      6347  6220  0 08:14 pts/0    00:00:01 /usr/local/mysql57/bin/mysqld --defaults-file=my.cnf --basedir=/usr/local/mysql57 --datadir=/usr/local/mysql57/data --plugin-dir=/usr/local/mysql57/lib/plugin --user=mysql --log-error=slowtech.err --pid-file=slowtech.pid --socket=/usr/local/mysql57/data/mysql.sock --port=3307
root      6418  6171  0 08:17 pts/0    00:00:00 grep --color=auto mysqld

# kill 6347

使用–skip-grant-tables参数,重启实例

# bin/mysqld_safe --defaults-file=my.cnf --skip-grant-tables  --skip-networking &

设置了该参数,则实例在启动过程中会跳过权限表的加载,这就意味着任何用户都能登录进来,并进行任何操作,相当不安全。

建议同时添加–skip-networking参数。其会让实例关闭监听端口,自然也就无法建立TCP连接,而只能通过本地socket进行连接。

MySQL8.0就是这么做的,在设置了–skip-grant-tables参数的同时会自动开启–skip-networking。

修改密码

# mysql -S /usr/local/mysql57/data/mysql.sock

mysql> update mysql.user set authentication_string=password('123456') where host='localhost' and user='root';
Query OK, 0 rows affected, 1 warning (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 1

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

注意:
这里的update语句针对的是MySQL 5.7的操作,如果是在5.6版本,修改的应该是password字段,而不是authentication_string。

update mysql.user set password=password('123456') where host='localhost' and user='root';

而在MySQL 8.0.11版本中,这种方式基本不可行,因为其已移除了PASSWORD()函数及不再支持SET PASSWORD … = PASSWORD (‘auth_string’)语法。

不难发现,这种方式的可移植性实在太差,三个不同的版本,就先后经历了列名的改变,及命令的不可用。

下面,介绍另外一种更通用的做法,还是在skip-grant-tables的基础上。

与上面不同的是,其会先通过flush privileges操作触发权限表的加载,再使用alter user语句修改root用户的密码,如:

# bin/mysql -S /usr/local/mysql57/data/mysql.sock

mysql> alter user 'root'@'localhost' identified by '123';
ERROR 1290 (HY000): The MySQL server is running with the --skip-grant-tables option so it cannot execute this statement

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

mysql> alter user 'root'@'localhost' identified by '123';
Query OK, 0 rows affected (0.00 sec)

免密码登录进来后,直接执行alter user操作是不行的,因为此时的权限表还没加载。可先通过flush privileges操作触发权限表的加载,再执行alter user操作。
需要注意的是,通过alter user修改密码只适用于MySQL5.7和8.0,如果是MySQL 5.6,此处可写成

update mysql.user set password=password('123456') where host='localhost' and user='root';

最后重启实例

mysql> shutdown;

# bin/mysqld_safe --defaults-file=my.cnf &

需要注意的是,如果在启动的过程中没有指定–skip-networking参数,无需重启实例。但在网上看到的绝大多数方案,都是没有指定该参数,但重启了实例,实在没有必要。

下面对这个方案做个总结:

  1. 如果只添加了–skip-grant-tables,修改完密码后,其实无需重启,执行flush privileges即可。

  2. 从安全角度出发,建议加上–skip-networking。但因其是静态参数,将其剔除掉需要重启实例。

  3. 加上–skip-networking,虽然可以屏蔽掉TCP连接,但对于本地其它用户,只要有socket文件的可读权限,都能无密码登录。还是存在安全隐患。

  4. 不建议通过update的方式修改密码,更通用的其实是alter user。

更优雅的解法

相对于skip-grant-tables方案,我们来看看另外一种更优雅的解法,其只会重启一次,且基本上不存在安全隐患。

首先,依旧是关闭实例

其次,创建一个sql文件

写上密码修改语句

# vim init.sql 
alter user 'root'@'localhost' identified by '123456';

最后,使用–init-file参数,启动实例

# bin/mysqld_safe --defaults-file=my.cnf --init-file=/usr/local/mysql57/init.sql &

实例启动成功后,密码即修改完毕~

如果mysql实例是通过服务脚本来管理的,除了创建sql文件,整个操作可简化为一步。

# service mysqld restart --init-file=/usr/local/mysql57/init.sql 

注意:该操作只适用于/etc/init.d/mysqld这种服务管理方式,不适用于RHEL 7新推出的systemd。

MySQL 内核深度优化

MYSQL数据库适用场景广泛,相较于Oracle、DB2性价比更高,Web网站、日志系统、数据仓库等场景都有MYSQL用武之地,但是也存在对于事务性支持不太好(MySQL 5.5版本开始默认引擎才是InnoDB事务型)、存在多个分支、读写效率瓶颈等问题。

所以如何用好MYSQL变得至关重要,一方面需要通过MYSQL优化找出系统读写瓶颈,提高数据库性能;另一方面需要合理涉及数据结构、调整参数,以提高用户操作响应;同时还有尽可能节省系统资源,以便系统可以提供更大负荷的服务。本文将为大家介绍腾讯云团队是如何对Mysql进行内核级优化的思路和经验。

早期的CDB主要基于开源的Oracle MySQL分支,侧重于优化运维和运营的OSS系统。在腾讯云,因为用户数的不断增加,对CDB for MySQL提出越来越高的要求,腾讯云CDB团队针对用户的需求和业界发展的技术趋势,对CDB for MySQL分支进行深度的定制优化。优化重点围绕内核性能、内核功能和外围OSS系统三个维度展开,具体的做法如下:

一.内核性能的优化

由于腾讯云上的DB基本都需要跨园区灾备的特性,因此CDB for MySQL的优化主要针对主从DB部署在跨园区网络拓扑的前提下,重点去解决真实部署环境下的性能难题。经过分析和调研,我们将优化的思路归纳为:“消除冗余I/O、缩短I/O路径和避免大锁竞争”。以下是内核性能的部分案例:

1.主备DB间的复制优化

未分类

问题分析

如上图所示,在原生MySQL的复制架构中,Master侧通过Dump线程不断发送Binlog事件给Slave的I/O线程,Slave的I/O线程在接受到Binlog事件后,有两个主要的动作:

  • 写入到Relay Log中,这个过程会和Slave SQL线程争抢保护Relay Log的锁。
  • 更新复制元数据(包含Master的位置等信息)。

优化方法

经过分析,我们的优化策略是:

  • Slave I/O线程和Slave SQL线程是典型的单写单读生产者-消费者模型,是可以做到无锁设计的;因此实现思路就是SlaveI/O线程在每次写完数据后,原子更新Relay Log的长度信息,Slave SQL线程读取RelayLog的时以长度信息为边界。这样就将原本竞争激烈的Relay Log锁化解为无锁;
  • 由于Binlog事件中的GTID(Global Transaction Identifier)和DB事务是一一对应的关系,所以RelayLog中的数据本身已经包含了所需要的复制元数据,所以我们可以不写Master info文件,消除了冗余的文件I/O;
  • 于DB都是以事务为更新粒度的,因为在RelayLog文件I/O上,我们通过合并离散小I/O为事务粒度的大I/O等手段,使磁盘I/O得以大幅提升。

优化效果

未分类

如上图所示,经过优化:左图35.79%的锁竞争(futex)已经被完全消除;同压测压力下,56.15%的文件I/O开销被优化到19.16%,Slave I/O线程被优化为预期的I/O密集型线程。

2.主库事务线程和Dump线程间的优化

未分类

问题分析

如上图所示,在原生MySQL中多个事务提交线程TrxN和多个Dump线程之间会同时竞争Binlog文件资源的保护锁,多个事务提交线程对Binlog执行写入,多个Dump线程从Binlog文件读取数据并发送给Slave。所有的线程之间是串行执行的!

优化方法

经过分析,我们的优化策略是:

  • 将读写分离开来,多个写入的线程还是在锁保护下串行执行,每一个写入线程写入完成后更新当前Binlog的长度信息,多个Dump线程以Binlog文件的长度信息为读取边界,多个Dump线程之间并行执行。以这种方式来让复制拓扑中的Dump线程发送得更快!

效果

未分类

经过测试,优化后的内核,不仅提升了事务提交线程的性能,在Dump线程较多的情况下,对主从复制性能有较大提升。

二.主备库交互流程优化

未分类

问题分析

如上图所示,在原生MySQL中主备库之间的数据发送和ACK回应是简单的串行执行,在上一个事件ACK回应到达之前,不允许继续发送下一个事件;这个行为在跨园区(RTT 2-3ms)的情况性能非常差,而且也不能很好地利用带宽优势。

优化方法

经过分析,我们的优化策略是:

  • 将发送和ACK回应的接收独立到不同的线程中,由于发送和接收都是基于TCP流的传输,所以时序性是有保障的;这样发送线程可以在未收ACK之前继续发送,接受线程收到ACK后唤醒等待的线程执行相应的任务。

效果

根据实际用例测试,优化后的TPS提升为15%左右。

三.内核功能的优化

1. 预留运维帐号连接数配额

在腾讯云上,不时遇到用户APP异常或者BUG从而占满DB的最大连接限制,这是CDB OSS帐号无法登录以进行紧急的运维操作。针对这个现状,我们在MySQL内核单独开辟了一个可配置的连接数配额,即便在上述场景下,运维帐号仍然可以连接到DB进行紧急的运维操作。极大地降低了异常情况下DB无政府状态的风险。该帐号仅有数据库运维管理权限,无法获取用户数据,也保证了用户数据的安全性。

2. 主备强同步

针对一些应用对数据的一致性要求非常高,CDB在MySQL原生半同步的基础上进行了深度优化,确保一个事务在主库上提交之前一定已经复制到至少一个备库上。确保主库宕机时数据的一致性。

四.外围系统的优化

除了以上提到的MySQL内核侧的部分优化,我们也在外围OSS平台进行了多处优化。例如使用异步MySQL ping协议实现大量实例的监控、通过分布式技术来加固原有系统的HA/服务发现和自动扩容等功能、在数据安全/故障切换和快速恢复方面也进行了多处优化。

在此我向大家推荐一个架构学习交流群。交流学习群号:478030634 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

mysql的UNIX_TIMESTAMP用法

mysql的UNIX_TIMESTAMP用法

一、UNIX_TIMESTAMP 一般是用于unix的时间戳。

例子一、日期转化为时间戳

SELECT UNIX_TIMESTAMP("2016-07-11")

结果:
— 1468166400

例子二、日期转化为时间戳

SELECT UNIX_TIMESTAMP("2016-07-17 23:59:59")

结果:

— 1468771199

二、FROM_UNIXTIME:表示把UNIX_TIMESTAMP还原成标准的时间格式

SELECT FROM_UNIXTIME(1468166400),FROM_UNIXTIME(1468771199)

结果:

2016-07-11 00:00:00 2016-07-17 23:59:59

记住,永远不要在MySQL中使用“utf8”

最近我遇到了一个bug,我试着通过Rails在以“utf8”编码的MariaDB中保存一个UTF-8字符串,然后出现了一个离奇的错误:

Incorrect string value: ‘xF0x9Fx98x83 <…’ for column ‘summary’ at row 1

我用的是UTF-8编码的客户端,服务器也是UTF-8编码的,数据库也是,就连要保存的这个字符串“ <…”也是合法的UTF-8。

问题的症结在于,MySQL的“utf8”实际上不是真正的UTF-8。

“utf8”只支持每个字符最多三个字节,而真正的UTF-8是每个字符最多四个字节。

MySQL一直没有修复这个bug,他们在2010年发布了一个叫作“utf8mb4”的字符集,绕过了这个问题。

当然,他们并没有对新的字符集广而告之(可能是因为这个bug让他们觉得很尴尬),以致于现在网络上仍然在建议开发者使用“utf8”,但这些建议都是错误的。

简单概括如下:

  • MySQL的“utf8mb4”是真正的“UTF-8”。
  • MySQL的“utf8”是一种“专属的编码”,它能够编码的Unicode字符并不多。

我要在这里澄清一下:所有在使用“utf8”的MySQL和MariaDB用户都应该改用“utf8mb4”,永远都不要再使用“utf8”。

那么什么是编码?什么是UTF-8?

我们都知道,计算机使用0和1来存储文本。比如字符“C”被存成“01000011”,那么计算机在显示这个字符时需要经过两个步骤:

  • 计算机读取“01000011”,得到数字67,因为67被编码成“01000011”。
  • 计算机在Unicode字符集中查找67,找到了“C”。

同样的:

  • 我的电脑将“C”映射成Unicode字符集中的67。
  • 我的电脑将67编码成“01000011”,并发送给Web服务器。

几乎所有的网络应用都使用了Unicode字符集,因为没有理由使用其他字符集。

Unicode字符集包含了上百万个字符。最简单的编码是UTF-32,每个字符使用32位。这样做最简单,因为一直以来,计算机将32位视为数字,而计算机最在行的就是处理数字。但问题是,这样太浪费空间了。

UTF-8可以节省空间,在UTF-8中,字符“C”只需要8位,一些不常用的字符,比如“”需要32位。其他的字符可能使用16位或24位。一篇类似本文这样的文章,如果使用UTF-8编码,占用的空间只有UTF-32的四分之一左右。

MySQL的“utf8”字符集与其他程序不兼容,它所谓的“”,可能真的是一坨……

MySQL简史

为什么MySQL开发者会让“utf8”失效?我们或许可以从提交日志中寻找答案。

MySQL从4.1版本开始支持UTF-8,也就是2003年,而今天使用的UTF-8标准(RFC 3629)是随后才出现的。

旧版的UTF-8标准(RFC 2279)最多支持每个字符6个字节。2002年3月28日,MySQL开发者在第一个MySQL 4.1预览版中使用了RFC 2279。

同年9月,他们对MySQL源代码进行了一次调整:“UTF8现在最多只支持3个字节的序列”。

是谁提交了这些代码?他为什么要这样做?这个问题不得而知。在迁移到Git后(MySQL最开始使用的是BitKeeper),MySQL代码库中的很多提交者的名字都丢失了。2003年9月的邮件列表中也找不到可以解释这一变更的线索。

不过我可以试着猜测一下。

2002年,MySQL做出了一个决定:如果用户可以保证数据表的每一行都使用相同的字节数,那么MySQL就可以在性能方面来一个大提升。为此,用户需要将文本列定义为“CHAR”,每个“CHAR”列总是拥有相同数量的字符。如果插入的字符少于定义的数量,MySQL就会在后面填充空格,如果插入的字符超过了定义的数量,后面超出部分会被截断。

MySQL开发者在最开始尝试UTF-8时使用了每个字符6个字节,CHAR(1)使用6个字节,CHAR(2)使用12个字节,并以此类推。

应该说,他们最初的行为才是正确的,可惜这一版本一直没有发布。但是文档上却这么写了,而且广为流传,所有了解UTF-8的人都认同文档里写的东西。

不过很显然,MySQL开发者或厂商担心会有用户做这两件事:

  • 使用CHAR定义列(在现在看来,CHAR已经是老古董了,但在那时,在MySQL中使用CHAR会更快,不过从2005年以后就不是这样子了)。
  • 将CHAR列的编码设置为“utf8”。

我的猜测是MySQL开发者本来想帮助那些希望在空间和速度上双赢的用户,但他们搞砸了“utf8”编码。

所以结果就是没有赢家。那些希望在空间和速度上双赢的用户,当他们在使用“utf8”的CHAR列时,实际上使用的空间比预期的更大,速度也比预期的慢。而想要正确性的用户,当他们使用“utf8”编码时,却无法保存像“”这样的字符。

在这个不合法的字符集发布了之后,MySQL就无法修复它,因为这样需要要求所有用户重新构建他们的数据库。最终,MySQL在2010年重新发布了“utf8mb4”来支持真正的UTF-8。

为什么这件事情会让人如此抓狂

因为这个问题,我整整抓狂了一个礼拜。我被“utf8”愚弄了,花了很多时间才找到这个bug。但我一定不是唯一的一个,网络上几乎所有的文章都把“utf8”当成是真正的UTF-8。

“utf8”只能算是个专有的字符集,它给我们带来了新问题,却一直没有得到解决。

总结

如果你在使用MySQL或MariaDB,不要用“utf8”编码,改用“utf8mb4”。这里(https://mathiasbynens.be/notes/mysql-utf8mb4#utf8-to-utf8mb4)提供了一个指南用于将现有数据库的字符编码从“utf8”转成“utf8mb4”。

mongodb集群故障转移实践

简介

NOSQL有这些优势:

  1. 大数据量,可以通过廉价服务器存储大量的数据,轻松摆脱传统mysql单表存储量级限制。
  2. 高扩展性,Nosql去掉了关系数据库的关系型特性,很容易横向扩展,摆脱了以往老是纵向扩展的诟病。
  3. 高性能,Nosql通过简单的key-value方式获取数据,非常快速。还有NoSQL的Cache是记录级的,是一种细粒度的Cache,所以NoSQL在这个层面上来说就要性能高很多。
  4. 灵活的数据模型,NoSQL无需事先为要存储的数据建立字段,随时可以存储自定义的数据格式。而在关系数据库里,增删字段是一件非常麻烦的事情。如果是非常大数据量的表,增加字段简直就是一个噩梦。
  5. 高可用,NoSQL在不太影响性能的情况,就可以方便的实现高可用的架构。比如mongodb通过mongos、mongo分片就可以快速配置出高可用配置。
  6. 支持查询、聚合、完全索引,包含内部对象
  7. 支持复制和故障转移、自动恢复
  8. 易扩展

在nosql数据库里,大部分的查询都是键值对(key、value)的方式。MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中最像关系数据库的。支持类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。所以这个非常方便,我们可以用sql操作MongoDB,从关系型数据库迁移过来,开发人员学习成本会大大减少。如果再对底层的sql API做一层封装,开发基本可以感觉不到mongodb和关系型数据库的区别。

  MongoDB是一个基于分布式文件存储的数据库。由C++语言编写;旨在为WEB应用提供可扩展的高性能数据存储解决方案。

安装mongodb

安装环境

  • 操作系统:Centos7.2

  • mongodb版本: v3.6.1

下载安装

wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-amazon-3.6.1.tgz
tar zxvf mongodb-linux-x86_64-amazon-3.6.1.tgz 
mv  /root/mongodb-linux-x86_64-amazon-3.6.1 /usr/local/mongodb/

创建数据/日志目录

mkdir -p /data/mongodb/{data, logs}

mkdir /data/mongodb/data/mongod
touch /data/mongodb/logs/mongo.logs

创建配置文件

mkdir /usr/local/mongodb/config
cd  /usr/local/mongodb/config && touch mongo.conf

配置文件

1.普通配置文件示例

dbpath=/data/mongodb/data/mongod
logpath=/data/mongodb/logs/mongo.log
logappend=true
replSet=mongo-rs
bind_ip=0.0.0.0
port=27017
fork=true
journal=true

mongodb3.x版本后就是要yaml语法格式的配置文件,下面是yaml配置文件格式如下:
官方yaml配置文件选项参考:https://docs.mongodb.org/manual/reference/configuration-options/#configuration-file
注意:只能使用空格,不支持tab键

2.yaml格式配置文件示例

storage:
    dbPath: /data/mongodb/data/mongod
    journal:
        enabled: true
systemLog:
    destination: file
    path: /data/mongodb/logs/mongo.log
    logAppend: true
    logRotate: rename
net:
    bindIp: 0.0.0.0
    port: 27017
processManagement:
    pidFilePath: /var/run/pid/mongodb.pid
    fork: true
replication:
    oplogSizeMB: 20480
    replSetName: mongo-rs

配置文件参数说明

1.基本参数

--quiet     # 安静输出
--port arg  # 指定服务端口号,默认端口27017
--bind_ip arg   # 绑定服务IP,若绑定127.0.0.1,则只能本机访问,不指定默认本地所有IP
--logpath arg   # 指定MongoDB日志文件,注意是指定文件不是目录
--logappend     # 使用追加的方式写日志
--pidfilepath arg   # PID File 的完整路径,如果没有设置,则没有PID文件
--keyFile arg   # 集群的私钥的完整路径,只对于Replica Set 架构有效
--unixSocketPrefix arg  # UNIX域套接字替代目录,(默认为 /tmp)
--fork  # 以守护进程的方式运行MongoDB,创建服务器进程
--auth  # 启用验证
--cpu   # 定期显示CPU的CPU利用率和iowait
--dbpath arg    # 指定数据库路径
--diaglog arg   # diaglog选项 0=off 1=W 2=R 3=both 7=W+some reads
--directoryperdb    # 设置每个数据库将被保存在一个单独的目录
--journal   # 启用日志选项,MongoDB的数据操作将会写入到journal文件夹的文件里
--journalOptions arg    # 启用日志诊断选项
--ipv6  # 启用IPv6选项
--jsonp     # 允许JSONP形式通过HTTP访问(有安全影响)
--maxConns arg  # 最大同时连接数 默认2000
--noauth    # 不启用验证
--nohttpinterface   # 关闭http接口,默认关闭27018端口访问
--noprealloc    # 禁用数据文件预分配(往往影响性能)
--noscripting   # 禁用脚本引擎
--notablescan   # 不允许表扫描
--nounixsocket  # 禁用Unix套接字监听
--nssize arg (=16)  # 设置信数据库.ns文件大小(MB)
--objcheck  # 在收到客户数据,检查的有效性,
--profile arg   # 档案参数 0=off 1=slow, 2=all
--quota     # 限制每个数据库的文件数,设置默认为8
--quotaFiles arg    # number of files allower per db, requires --quota
--rest  # 开启简单的rest API
--repair    # 修复所有数据库run repair on all dbs
--repairpath arg    # 修复库生成的文件的目录,默认为目录名称dbpath
--slowms arg (=100)     # value of slow for profile and console log
--smallfiles    # 使用较小的默认文件
--syncdelay arg (=60)   # 数据写入磁盘的时间秒数(0=never,不推荐)
--sysinfo   # 打印一些诊断系统信息
--upgrade   # 如果需要升级数据库

2.Replicaton 参数

--fastsync  # 从一个dbpath里启用从库复制服务,该dbpath的数据库是主库的快照,可用于快速启用同步
--autoresync    # 如果从库与主库同步数据差得多,自动重新同步,
--oplogSize arg     # 设置oplog的大小(MB)

3.主/从参数

--master    # 主库模式
--slave     # 从库模式
--source arg    # 从库 端口号
--only arg  # 指定单一的数据库复制
--slavedelay arg    # 设置从库同步主库的延迟时间

4.Replica set(副本集)选项

--replSet arg   # 设置副本集名称 

Sharding(分片)选项
--configsvr     # 声明这是一个集群的config服务,默认端口27019,默认目录/data/configdb
--shardsvr  # 声明这是一个集群的分片,默认端口27018
--noMoveParanoia    # 关闭偏执为moveChunk数据保存

启动

mongod –quiet -f /usr/local/mongodb/config/mongo.conf
配置文件里设置里fork:true,所以会在后台启动,值得注意的是,用到了”–fork”参数就必须启用”–logpath”参数,如不指定配置文件启动,如下:

mongod --dbpath=/data/mongodb/data/mongod --fork --logpath=/data/mongodb/logs/mongo.logs

集群搭建

官方不建议再使用主从集群模式,推荐的集群方式是Replica Set(副本集),主从模式其实就是一个单副本的应用,没有很好的扩展性和容错性。而副本集具有多个副本保证了容错性,就算一个副本挂掉了还有很多副本存在,并且解决了上面第一个问题“主节点挂掉了,整个集群内会自动切换”。

副本集的设计结构

未分类

由图可以看到客户端连接到整个副本集,不关心具体哪一台机器是否挂掉。主服务器负责整个副本集的读写,副本集定期同步数据备份,一但主节点挂掉,副本节点就会选举一个新的主服务器,这一切对于应用服务器不需要关心。

未分类

配置步骤

准备三台机器

172.29.142.17  主
172.29.142.18  备
172.28.226.199 仲裁

按照第二步安装依次在三台机器上安装并启动

/usr/local/mongodb/bin/mongod --quiet -f /usr/local/mongodb/config/mongo.conf

初始化集群配置

三台服务启动并不能表示他们在一个集群,因此需要将集群初始化。连接任意一个节点(不要是仲裁点),执行如下:

rs.initiate({
 _id:"mongo-rs", #集群名称 
 members:[ {_id:0,host:'172.29.142.18:27017',priority:2}, #主
 {_id:1,host:'172.29.142.17:27017',priority:1}, #备
 {_id:2,host:'172.28.226.199:27017',arbiterOnly:true}]  #仲裁节点
}) 

成功上面会返回OK,然后查看集群状态,下面是在备节点上执行的

rs.status()

返回集群的名称和members信息,如:

{
    "set" : "mongo-rs",
    "date" : ISODate("2018-06-26T14:56:08.032Z"),
    "myState" : 2,
    "term" : NumberLong(2),
    "syncingTo" : "172.29.142.18:27017",
    "heartbeatIntervalMillis" : NumberLong(2000),
    "optimes" : {
        "lastCommittedOpTime" : {
            "ts" : Timestamp(1530024958, 1),
            "t" : NumberLong(2)
        },
        "appliedOpTime" : {
            "ts" : Timestamp(1530024958, 1),
            "t" : NumberLong(2)
        },
        "durableOpTime" : {
            "ts" : Timestamp(1530024958, 1),
            "t" : NumberLong(2)
        }
    },
    "members" : [ 
        {
            "_id" : 0,
            "name" : "172.29.142.18:27017",
            "health" : 1.0,
            "state" : 1,
            "stateStr" : "PRIMARY",
            "uptime" : 382251,
            "optime" : {
                "ts" : Timestamp(1530024958, 1),
                "t" : NumberLong(2)
            },
            "optimeDurable" : {
                "ts" : Timestamp(1530024958, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2018-06-26T14:55:58.000Z"),
            "optimeDurableDate" : ISODate("2018-06-26T14:55:58.000Z"),
            "lastHeartbeat" : ISODate("2018-06-26T14:56:07.329Z"),
            "lastHeartbeatRecv" : ISODate("2018-06-26T14:56:06.453Z"),
            "pingMs" : NumberLong(0),
            "electionTime" : Timestamp(1529642739, 1),
            "electionDate" : ISODate("2018-06-22T04:45:39.000Z"),
            "configVersion" : 1
        }, 
        {
            "_id" : 1,
            "name" : "172.29.142.17:27017",
            "health" : 1.0,
            "state" : 2,
            "stateStr" : "SECONDARY",
            "uptime" : 382552,
            "optime" : {
                "ts" : Timestamp(1530024958, 1),
                "t" : NumberLong(2)
            },
            "optimeDate" : ISODate("2018-06-26T14:55:58.000Z"),
            "syncingTo" : "172.29.142.18:27017",
            "configVersion" : 1,
            "self" : true
        }, 
        {
            "_id" : 2,
            "name" : "172.28.226.199:27017",
            "health" : 1.0,
            "state" : 7,
            "stateStr" : "ARBITER",
            "uptime" : 168617,
            "lastHeartbeat" : ISODate("2018-06-26T14:56:06.895Z"),
            "lastHeartbeatRecv" : ISODate("2018-06-26T14:56:04.092Z"),
            "pingMs" : NumberLong(35),
            "configVersion" : 1
        }
    ],
    "ok" : 1.0
}

View Code

返回参数说明

“health” : 1, #代表机器正常
“stteStr” : “PRIMARY”, #代表是主节点,可读写,其中有以下几下状态:

STARTUP:刚加入到复制集中,配置还未加载
STARTUP2:配置已加载完,初始化状态
RECOVERING:正在恢复,不适用读
ARBITER: 仲裁者
DOWN:节点不可到达
UNKNOWN:未获取其他节点状态而不知是什么状态,一般发生在只有两个成员的架构,脑裂
REMOVED:移除复制集
ROLLBACK:数据回滚,在回滚结束时,转移到RECOVERING或SECONDARY状态
FATAL:出错。查看日志grep “replSet FATAL”找出错原因,重新做同步
PRIMARY:主节点
SECONDARY:备份节点

测试副本集数据复制

注意:mongodb默认是从主节点读写数据的,副本节点上不允许读,需要设置副本节点可以读:

repset:SECONDARY> db.getMongo().setSlaveOk();

这个很好测试,直接在主节点插入一条数据,在备节点查询即可

或者可以使用客户端以集群模式连接mongo集群:

未分类

点Test 测试连接:

未分类

三个节点的数据是同步的。

测试副本集故障转移功能

1.查看集群当前状态,如上返回

当前172.29.142.18是Primary, 172.29.142.17是Secondary

未分类

2.停掉主节点172.29.142.18,查看另两台的选票结果

未分类

此时17变成了主节点,原先的仲裁节点不变,重新启动第一次的Primary,则主节点又发生变化,不再截图,整个过程业务是不中断的。只要有一台可用即可。

Nodejs连接mongo集群示例

这里强烈不推荐连接单台mongo服务,因为如果一个mongo节点挂掉,业务就挂了,连接集群的话有一台可用就行。

下面举了个nodejs连接mongo集群的示例:

const mongoose = require('mongoose');
let url = "mongodb://172.29.142.17:27017/testdb,mongodb://172.29.142.18:27017/testdb,mongodb://172.28.226.199:27017/testdb";
let options = {
  "replset": {
    "ha": true,
    "haInterval": 1000,
    "replicaSet": "mongo-rs",
    "connectWithNoPrimary": true,
    "auto_reconnect": true,
    "socketOptions": {
      "keepAlive": 120,
      connectTimeoutMS: 30000
    }
  }
}


mongoose.connect(url, options).connection
  .on('error', function (error) {
    console.log('mongo 连接错误', error)
  }).on('disconnected', mongoConnect).once('open', function () {
    console.log('mongo 连接成功');
  })