python字符串连接示例

python中有很多字符串连接方式

  • # 最原始的字符串连接方式:str1 + str2
  • # python 新字符串连接语法:str1, str2
  • # 奇怪的字符串方式:str1 str2
  • # % 连接字符串:‘name:%s; sex: ‘ % (‘tom’, ‘male’)
  • # 字符串列表连接:str.join(some_list)

1、第一种,想必只要是有编程经验的人,估计都知道,直接用 “+” 来连接两个字符串:

'Jim' + 'Green' = 'JimGreen'

2、第二种比较特殊,如果两个字符串用“逗号”隔开,那么这两个字符串将被连接,但是,字符串之间会多出一个空格:

‘Jim’, ‘Green’ = ‘Jim Green’

3、第三种也是 python 独有的,只要把两个字符串放在一起,中间有空白或者没有空白:两个字符串自动连接为一个字符串:

'Jim''Green' = 'JimGreen'
'Jim' 'Green' = 'JimGreen'

4、第四种功能比较强大,借鉴了C语言中 printf 函数的功能,如果你有C语言基础,看下文档就知道了。这种方式用符号“%”连接一个字符串和一组变量,字符串中的特殊标记会被自动用右边变量组中的变量替换:

'%s, %s' % ('Jim', 'Green') = 'Jim, Green'

5、第五种就属于技巧了,利用字符串的函数 join 。这个函数接受一个列表,然后用字符串依次连接列表中每一个元素:

var_list = ['tom', 'david', 'john']
a = '###'
a.join(var_list) = 'tom###david###john'

6、其实,python 中还有一种字符串连接方式,不过用的不多,就是字符串乘法,如:

a = 'abc'
a * 3 = 'abcabcabc'

解决nginx 502 php-cgi.sock failed (13: Permission denied)问题

报错信息是

connect() to unix:/tmp/php-cgi.sock failed (13: Permission denied) while connecting to upstream

此时需要检查一下 /tmp/php-cgi.sock 的运行权限

如果php的运行权限是www,而php-cgi.sock的权限是root,则会出错

此时需要修改 php-fpm.conf (如果没有,增加此配置即可)

listen.owner = www
listen.group = www

意思是和php的运行权限保持一致,如果你的php运行权限不是www,则此处对应修改即可。

如何隐藏Nginx版本号和Server响应头

隐藏 Nginx 版本号

其实隐藏 Nginx 版本号无非就是为了防止“漏洞”被人利用而已,这点 Nginx 其实很早就考虑到了,在 Nginx 的配置文件里只要加上server_tokens off就可以在网页head里隐藏掉 Nginx 的版本号了。

具体操作如下:

第一步:

vim /usr/local/nginx/conf/nginx.conf

在http{}中加入

server_tokens off;

第二歩:

vi /usr/local/nginx/conf/fastcgi_params

将里面的

fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;

修改为:

fastcgi_param SERVER_SOFTWARE nginx;

隐藏 Web 服务器名称

关于如何隐藏Web 服务器名称,目前看除了在编译 Web 服务器名称的时候进行伪装以外没有很好的办法了。下面以 Nginx 为例:

伪装Nginx的具体办法

vi /src/core/nginx.h

修改其中:

#define NGINX_VERSION “1.0″

#define NGINX_VER “GWS/” NGINX_VERSION

重新编译nginx即可。

注:程序重新编译完后,要reload不会生效,需要用kill命令杀死原来的进程,再重新启动 Nginx 。

其实还有一个最简单的隐藏 Web 服务器名称的方法,那就启用 CDN 服务,当客户端访问的是 CDN 节点的时候,看到的当然也就是 CDN 节点的 Web 服务器名称了,至于自己网站的 Web 服务器名称只要没有“真实IP”几乎是不可能获得了,国内很多的免费 CDN 都可以的,这里明月就自己的经验推荐百度云加速、360网站卫士这两个免费的 CDN 。

Nginx ngx_http_limit_conn ngx_http_limit_conn模块(请求限制和连接数限制)使用指南

限制单IP地址请求频率:
http://nginx.org/en/docs/http/ngx_http_limit_req_module.html

限制每个IP地址发起的请求数:
http://nginx.org/en/docs/http/ngx_http_limit_conn_module.html

http 字段加入以下内容:

# traffic control
# The module is used to limit the request processing rate per a defined key, in particular,
# the processing rate of requests coming from a single IP address.
# The limitation is done using the “leaky bucket” method.
limit_req_zone $binary_remote_addr zone=perip:10m rate=60r/s;

# The module is used to limit the number of connections per the defined key;
limit_conn_zone $binary_remote_addr zone=perconn:10m;

server {
listen 80;
server_name www.zhangluya.com;
charset utf-8;

location / {

# 队列模式 burst=1500 相当于超过限制速率的IP地址将会进入队列状态,如果api_ip zone处理完毕
# 将会放行队列中的请求,如果队列中的请求=1500满了,请求直接退回 客户端会收到一个服务器繁忙的请求;
# 排队不会一直继续 nginx的超时设置会限制排队在一定时间内的请求 直接退回 返回服务器繁忙请求.
limit_req zone=perip burst=1500;

# 非队列模式
# limit_req zone=one burst=8 nodelay; #不用队列burst=0

# allow only sixty connection per an IP address at a time
limit_conn perconn 100;

proxy_pass http://127.0.0.1:81;
proxy_redirect off;
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

}

在单台机器上搭建Mysql的多个实例

随着互联网技术的发展,数据量越来越庞大,我们急需一个大的存储和大的分析系统。虽然有nosql数据库、hadoop文件存储等数据存储方式能够解决该问题,但是,关系型数据库依然有它的优势所在,尤其是对结构化数据的处理,性能仍然很棒。或者,从公司的项目开发成本讲,关系型数据库的使用比nosql数据库使用更加简易,更加便于维护。

因此,本文介绍一下Mycat使用的第一步(当然,这一步不是必须的),学会如何搭建Mysql单机多实例,从而应对大数据量查询慢的问题。

1、启动项

vim /etc/apparmor.d/usr.sbin.mysqld/etc/init.d/apparmor reload

AppArmor(Application Armor)是Linux内核的一个安全模块,AppArmor允许系统管理员将每个程序与一个安全配置文件关联,从而限制程序的功能。简单的说,AppArmor是与SELinux类似的一个访问控制系统,通过它你可以指定程序可以读、写或运行哪些文件,是否可以打开网络端口等。作为对传统Unix的自主访问控制模块的补充,AppArmor提供了强制访问控制机制,它已经被整合到2.6版本的Linux内核中。

详细资料查看:
Apparmor——Linux内核中的强制访问控制系统
http://www.cnblogs.com/-Lei/archive/2013/02/24/2923947.html

2、创建新实例的数据目录

mkdir /var/lib/mysql2   创建目录chown mysql /var/lib/mysql2  给mysql用户权限

3、创建数据库,初始化数据库

mysql 5.7以下 
mysql_install_db –user=mysql –datadir=/var/lib/mysql2
mysql 5.7以上 
mysqld –user=mysql –datadir=/var/lib/mysql2

4、配置多实例配置文件

[mysqld_multi]
mysqld     = /install/mysql/bin/mysqld_safe  
mysqladmin = /install/mysql/bin/mysqladmin  
user       = root  
# The MySQL server  
[mysqld1]  
port            = 3306  
socket          = /tmp/mysql.sock  
datadir         =/var/lib/mysql  
pid-file        =/var/lib/mysql/mysql.pid  
user            =mysql  
log-bin         =master-bin  
log-bin-index           =master-bin.index  
...
[mysqld2]  
port            = 3307  
socket          =/tmp/mysql2.sock  
datadir         =/var/lib/mysql2  
pid-file        =/var/lib/mysql2/mysql.pid  
user            =mysql  
...

5、启动实例

mysqld_multi   --defaults-file=/etc/mysql/my_multi.cnf start 1
mysqld_multi   --defaults-file=/etc/mysql/my_multi.cnf start 2

6、登陆Mysql

# 登陆Mysql服务器,执行mysql命令进入mysql控制台
mysql -uroot -P3307 -p -S/tmp/mysql2.sock
# 不用输入密码,直接回车
# 查看当前用户
select User from mysql.user;
# 创建Mysql用户test,并赋权限
CREATE USER 'test'@'%' IDENTIFIED BY '123456';
GRANT GRANT OPTION ON *.* TO 'test'@'%';
GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, RELOAD, SHUTDOWN, PROCESS, FILE, REFERENCES, INDEX, ALTER, SHOW DATABASES, SUPER, CREATE TEMPORARY TABLES, LOCK TABLES, EXECUTE, REPLICATION SLAVE, REPLICATION CLIENT, CREATE VIEW, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, CREATE USER, EVENT, TRIGGER ON *.* TO 'test'@'%';

无备份情况下MySQL innodb表被意外删除的恢复

这里我们首先来测试innodb_file_per_table为off的情况,即表结构和数据存在同一个文件中。这里我分别测试了表存在主键和不存在主键的情况,供参考。

innodb_file_per_table参数为off(有主键的情况)

1、创建测试表

mysql> use recover;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> create table test_drop0801(id int);
Query OK, 0 rows affected (0.02 sec)

mysql> insert into test_drop0801 values(100);
Query OK, 1 row affected (0.01 sec)

mysql> insert into test_drop0801 values(101);
Query OK, 1 row affected (0.00 sec)

mysql> insert into test_drop0801 values(102);
Query OK, 1 row affected (0.00 sec)

mysql> alter table test_drop0801 add primary key(id);
Query OK, 3 rows affected (0.02 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> explain select * from test_drop0801 where id=102;
+----+-------------+---------------+-------+---------------+---------+---------+-------+------+-------------+
| id | select_type | table        | type  | possible_keys | key    | key_len | ref  | rows | Extra      |
+----+-------------+---------------+-------+---------------+---------+---------+-------+------+-------------+
|  1 | SIMPLE      | test_drop0801 | const | PRIMARY      | PRIMARY | 4      | const |    1 | Using index |
+----+-------------+---------------+-------+---------------+---------+---------+-------+------+-------------+
1 row in set (0.00 sec)


mysql> show global variables like '%file_per%';
+-----------------------+-------+
| Variable_name        | Value |
+-----------------------+-------+
| innodb_file_per_table | OFF  |
+-----------------------+-------+
1 row in set (0.00 sec)

mysql> show create table test_drop0801 G;
*************************** 1. row ***************************
      Table: test_drop0801
Create Table: CREATE TABLE `test_drop0801` (
  `id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

ERROR:
No query specified

2、备份表结构

[root@killdb ~]# mysqldump --opt -d -uroot -proger recover test_drop0801 > /tmp/innodb_recovery/recover/test_drop0801.sql
[root@killdb ~]#
1
2

[root@killdb ~]# mysqldump --opt -d -uroot -proger recover test_drop0801 > /tmp/innodb_recovery/recover/test_drop0801.sql
[root@killdb ~]#

3、删除表

mysql> drop table test_drop0801;
Query OK, 0 rows affected (0.00 sec)

4、扫描数据文件

[root@killdb innodb_recovery]# ./stream_parser -f /var/lib/mysql/ibdata1
Opening file: /var/lib/mysql/ibdata1
File information:

ID of device containing file:        64768
inode number:                      924765
protection:                        100660 (regular file)
number of hard links:                    1
user ID of owner:                      496
group ID of owner:                    491
device ID (if special file):            0
blocksize for filesystem I/O:        4096
number of blocks allocated:          69632
time of last access:            1496441095 Sat Jun  3 06:04:55 2017
time of last modification:      1496464241 Sat Jun  3 12:30:41 2017
time of last status change:    1496464241 Sat Jun  3 12:30:41 2017
total size, in bytes:            35651584 (34.000 MiB)

Size to process:                  35651584 (34.000 MiB)
All workers finished in 1 sec

5、创建用于恢复的数据字典

[root@killdb innodb_recovery]# ./recover_dictionary.sh
Generating dictionary tables dumps... OK
Creating test database ... OK
Creating dictionary tables in database test:
SYS_TABLES ... OK
SYS_COLUMNS ... OK
SYS_INDEXES ... OK
SYS_FIELDS ... OK
All OK
Loading dictionary tables data:
SYS_TABLES ... 234 recs OK
SYS_COLUMNS ... 324 recs OK
SYS_INDEXES ... 123 recs OK
SYS_FIELDS ... 248 recs OK
All OK

6、查询需要恢复表的信息

mysql> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql>
mysql> select * from SYS_TABLES where name like 'recover/test_drop0801%';
+-----------------------+-----+--------+------+--------+---------+--------------+-------+
| NAME                  | ID  | N_COLS | TYPE | MIX_ID | MIX_LEN | CLUSTER_NAME | SPACE |
+-----------------------+-----+--------+------+--------+---------+--------------+-------+
| recover/test_drop0801 | 187 |      1 |    1 |      0 |      0 |              |    0 |
+-----------------------+-----+--------+------+--------+---------+--------------+-------+
1 row in set (0.00 sec)

mysql> select * from SYS_INDEXES where table_id=187;
+----------+-----+---------+----------+------+-------+------------+
| TABLE_ID | ID  | NAME    | N_FIELDS | TYPE | SPACE | PAGE_NO    |
+----------+-----+---------+----------+------+-------+------------+
|      187 | 184 | PRIMARY |        1 |    3 |    0 | 4294967295 |
+----------+-----+---------+----------+------+-------+------------+
1 row in set (0.00 sec)

7、确认数据page中数据是否存在

[root@killdb innodb_recovery]#  ./c_parser -6f pages-ibdata1/FIL_PAGE_INDEX/0000000000000184.page -t recover/test_drop0801.sql |head -5
Line 22: syntax error at 'DROP'
21:
22: DROP TABLE IF EXISTS `test_drop0801`;
Failed to parse table structure
[root@killdb innodb_recovery]#

这里的报错是因为脚本的问题,需要修改备份脚本(mysqldump产生的).

[root@killdb innodb_recovery]#  ./c_parser -6f pages-ibdata1/FIL_PAGE_INDEX/0000000000000184.page -t recover/test_drop0801.sql |head -5
-- Page id: 562, Format: COMPACT, Records list: Valid, Expected records: (3 3)
000000001517    94000001800110test_drop0801    100
000000001517    9400000180011Dtest_drop0801    101
000000001517    9400000180012Atest_drop0801    102
SET FOREIGN_KEY_CHECKS=0;
LOAD DATA LOCAL INFILE '/tmp/innodb_recovery/dumps/default/test_drop0801' REPLACE INTO TABLE `test_drop0801` FIELDS TERMINATED BY 't' OPTIONALLY ENCLOSED BY '"' LINES STARTING BY 'test_drop0801t' (`id`);
-- Page id: 562, Found records: 3, Lost records: NO, Leaf page: YES
[root@killdb innodb_recovery]#

8、抽取page中的数据

[root@killdb innodb_recovery]#  ./c_parser -6f pages-ibdata1/FIL_PAGE_INDEX/0000000000000184.page -t recover/test_drop0801.sql > dumps/default/test_drop0801 2> dumps/default/test_drop0801_load.sql          
[root@killdb innodb_recovery]#
[root@killdb innodb_recovery]# ls -ltr dumps/default/test_drop0801*
-rw-r--r--. 1 root root 232 Jun  3 12:34 dumps/default/test_drop0801_load.sql
-rw-r--r--. 1 root root 285 Jun  3 12:34 dumps/default/test_drop0801

9、加载数据到数据库

mysql> use recover
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> source recover/test_drop0801.sql
Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.02 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

Query OK, 0 rows affected (0.00 sec)


mysql> source dumps/default/test_drop0801_load.sql
Query OK, 0 rows affected (0.00 sec)

Query OK, 3 rows affected (0.00 sec)
Records: 3  Deleted: 0  Skipped: 0  Warnings: 0

mysql> select * from test_drop0801;
+-----+
| id  |
+-----+
| 100 |
| 101 |
| 102 |
+-----+
3 rows in set (0.00 sec)

mysql> 

我们可以看到,顺利完成了drop table的恢复,而且数据完好无损。实际上我这里还同时测试了无主键的情况,经过测试都类似,可以进行完美的恢复。这里不再累述。

使用Prometheus监控MySQL状态

Prometheus官方提供了mysqld_exporter,我们直接使用即可。

在每个要监控的MySQL中创建监控用户并授予权限。

CREATE USER 'exporter'@'127.0.0.1' IDENTIFIED BY 'XXXXXXXX' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'127.0.0.1';
flush privileges;

mysqld_exporter也是用Go语言写的,安装十分简单,我们的环境MySQL有3个节点,使用ansible部署mysqld_exporter。 这里贴一下其中一个MySQL节点上生成的systemd的单元文件:

[Unit]
Description=mysqld_exporter
After=network.target
[Service]
Type=simple
User=prometheus
Environment=DATA_SOURCE_NAME=exporter:exporterpass@tcp(127.0.0.1:3306)/?loc=Local
ExecStart=/home/prometheus/mysqld_exporter/mysqld_exporter 
 -web.listen-address=:9104
Restart=on-failure
[Install]
WantedBy=multi-user.target
  • mysqld_exporter从环境变量DATA_SOURCE_NAME获取连接MySQL的dns信息,注意以前面我们创建的单独的监控用户
  • -web.listen-address设置mysqld_exporter的监听端口,默认为9104

接下来在Prometheus的配置文件中配置收集MySQL信息的Job和Instance,这里还是贴一下我们的配置文件片段,实际上这个片段也是有ansible编排生成的:

scrape_configs:
  - job_name: 'mysql'
        static_configs:
         - targets:
            - 192.168.1.11:9104
           labels:
             instance: db1
         - targets:
            - 192.168.1.12:9104
           labels:
             instance: db2
         - targets:
            - 192.168.1.13:9104
           labels:
             instance: db3

重启Prometheus之后,Prometheus就可以从mysqld_exporter中收集数据了。

在监控图表上我们使用的Grafana,因此可以直接使用percona grafana-dashboards提供的图表。

使用Docker打造MySQL私有云

前言

  • 前几月经常看到有 MySQL 到底能不能放到 Docker 里跑的各种讨论。这样做是错的!这样做是对的!说错的理由也说了一大堆,说对的思想也很明确。大家都有道理。但是我本人觉得这样的讨论落地意义不大。因为对与错还是要实践来得出的。

  • 所以同程旅游也很早开始了 MySQL 的 Docker 化实践,到目前已经有超一千多个 MySQL 实例在 Docker 平台安全稳定地跑着,DB 运维能力发生了质的提高(DBA 再也不用担心删库跑路了)。

  • 当然这样是不是可以证明之前的讨论结论——是对的。我想也不一定,因为我们还只是一只在学飞行的小鸟,还要更多的学习,所以我们特将我们在 MySQL 的 Docker 化上的实践分享给大家。

背景介绍

  • 同程旅游早期的数据库都以 MSSQL 为主,这个产品有个特点就是 UI 操作很棒。但是批量和自动化管理很难做,人力的工作很多。后来逐渐替换为 MySQL 后也是按照传统的运维方式管理。导致大部分的工作需要人肉运维。

  • 当然像我们早期使用过的 MSSQL 也是有优点的:就是单机性能比较好,在当年那个资源不够的年代里我们常可以在高可用的实例上运行多个库。这种情况下物理机数量与实例数量还是比较可控的,相对数量比较少,人肉运维完全可以应对。

  • 但是 MSSQL 的缺陷也很多,比如做水平拆分比较困难,导致数据库成为系统中最大的一个瓶颈。但在我们使用 MySQL+ 中间件(我们做这个中间件也是下了不少心思的,以后可以分享一下)做水平拆分后就开始解决了这个瓶颈。

  • 水平拆分的引入也带来了一个小缺点,就是会造成数据库实例数量大幅上升。举个例子我们做 1024 分片的话一般是做 32 个 node,一主一从是必须的(大部分情况是一主两从),那么至少 64 个实例,再加上应急扩展和备份用的节点那就更多了(中间件的开发者更希望是 1024 片就是 1024 个实例)。

  • 一次上线做一个 32node 分片扩展从库,两个 DBA 足足花了 4 个小时。另外,如果做单机单实例那肯定更不行了,别的不说,成本也会是个大问题,且物理机的资源也未能最大化利用。况且因为 MySQL 单体的性能没优势所以分片居多所以大部分情况下并不是每个库都能跑满整个物理机的。即使有部分能跑满整机资源的库,它的多节点备份,环境一致性和运维动作统一等问题也会让 DBA 一头糟,忙碌又容易出错的工作其实是无意义的。

  • 有了单机多实例运行 MySQL 实例的需求。单机多实例要思考的主要问题就是如果进行资源隔离和限制,实现方案有很多,怎么选?KVM,Docker,Cgroups 是目前的可以实现隔离主流方案。

  • KVM 对一个 DB 的隔离来说太重了,性能影响太大,在生产环境用不合适。这是因为 MySQL 运行的就是个进程而且对 IO 要求比较高,所以 KVM 不满足要求 (虽然优化以后 IO 能有点提升)。

  • cgroups 比较轻,虽然隔离性不是很高,但对于我们的 MySQL 多实例隔离来说是完全够用了(Docker 的资源限制用的就是 cgroups)。但是我们还想针对每个 MySQL 实例运行额外的管理进程 (比如监控等等)。用 cgroups 实现起来会比较复杂,并且我们还想让实例管理和物理机区分开,那 cgroups 也放弃。

  • 至于 Docker,那就很不错了,那些裸用 cgroups 的麻烦它都给搞定了。并且有 API 可以提供支持,开发成本低。而且我们可以基于 Docker 镜像来做部署自动化,那么环境的一致性也可轻松解决。所以最终我们选择了 Docker 作为云平台的资源隔离方案 (当然过程中也做了很多性能、稳定性等的适配工作,这里就不赘述了)。

下面两个图可以形象展示这款产品带来的革命性意义:

未分类

当然要能称之为云,那么平台最基本的要求就是具备资源计算、资源调度功能,且资源分配无需人工参与。对用户来讲,拿到的应该是直接可用的资源,并且天生自带高可用、自动备份、监控告警、慢日志分析等功能,无需用户关心资源背后的事情。其次才是各种日常的 DBA 运维操作需求服务化输出。下面我们就来讲讲我们这个平台是如何一步步实现的。

平台实现过程

站在巨人的肩膀上

我一直认为评价一款数据库的优劣,不能只评价数据库本身。我们要综合它的周边生态是否健全,比如:高可用方案、备份方案、日常维护难度、人才储备等等。当然对于一个云平台也一样,所以我们进行了短平快的试错工作,将平台分为多期版本开发。第一个版本的开发周期比较短,主要用来试验,所以我们要尽可能运用已有的开源产品来实现我们的需求,或者对已有开源产品进行二次开发以后实现定制化的需求。以下是我们当时用到的部分开源产品和技术。

未分类

下面选几个产品简单说一下我们通过它实现什么功能:

  • Percona:我们的备份、慢日志分析、过载保护等功能都是基于 pt-tools 工具包来实现的。

  • Prometheus:性能优越且功能强大的 TSDB,用于实现整个平台实例的监控告警。缺点是没有集群功能,单机性能是个瓶颈 (虽然单机的处理能力已经很强了),所以我们在业务层面进行了 DB 拆分,实现了分布式存储及扩展。

  • Consul:分布式的服务发现和配置共享软件,配合 prometheus 实现监控节点注册。

  • Python:管理 Docker 容器中 MySQL 实例的 agent 以及部分操作脚本。

  • Docker:承载 MySQL 实例并实现资源隔离和资源限制。

总体架构

未分类

容器调度系统如何选择

容器调度的开源产品主要有 Kubernetes 和 mesos,但是我们并没有选用这两个。主要原因是我们内部已经开发了一套基于 Docker 的资源管理、调度的系统,至今稳定运行 2 年多了。这套架构稍作修改是符合需求的。

另外第三方的资源调度系统兼容我们目前的高可用架构,其他自动化管理有些难度,同时资源分配策略也需要定制化。所以最终还是选择采用了自研的资源调度管理。适合自己现状的需求才是最好的。当然后面有机会做到计算调度和存储调度分离的情况下我们可能会转向 Kubernetes 的方案。

工作原理

我们就拿创建集群来举例吧。当平台发起一个创建集群的任务后,首先会根据集群规模 (一主一从还是一主多从,或者是分片集群) 确定要创建的实例数量,然后根据这个需求按照我们的资源筛选规则 (比如主从不能在同一台机器、内存配置不允许超卖等等),从现有的资源池中匹配出可用资源,然后依次创建主从关系、创建高可用管理、检查集群复制状态、推送集群信息到中间件 (选用了中间件的情况下) 控制中心、最后将以上相关信息都同步到 CMDB。

以上的每一个工作都是通过服务端发送消息到 agent,然后由 agent 执行对应的脚本,脚本会返回指定格式的执行结果,这些脚本是由 DBA 开发的。这种方式的优势在于,DBA 比任何人都了解数据库,所以通过这种方式可以有效提升项目开发效率,也能让 DBA 参与到项目当中去。开发只需要写前台逻辑,DBA 负责后端具体执行的指令。如果未来功能有变更或迭代的话,只需要迭代脚本即可,维护量极小。

资源的调度分配原则

经过对同程多年的 DB 运维数据分析得到如下经验:

  • CPU 最大超卖 3 倍,内存不超卖;

  • 同一机房优先选择资源最空闲的机器;

  • 主从角色不允许在同一台机器上;

  • 若有 VIP 需求的主从端口需要一致,无 VIP 需求直接对接中间件的无端口一致的限制;

  • 分片的集群将节点分布在多台物理机上;

产品分类

未分类

核心功能

未分类

以上是已经上线的部分核心功能,还有很多功能就不再一一展示。

备份恢复系统

未分类

备份工具我们是用 percona-xtrabackup。通过流备份的方式将数据备份到远端的备份服务器。备份服务器有多台,分别按照所属机房划分。

我们提供了手工备份和定时备份来满足不同场景的需求。多实例备份一定要关注磁盘 IO 和网络,所以我们的备份策略会限制单个物理机上并行备份的数量,另外单个机房备份任务队列的并行度也有控制,确保并行备份任务始终保持到我们指定的数量。

假如整个机房并行的是 50 个任务,那么这 50 个当中如果有 5 个提前备份完成,那么会新加入 5 个等待备份的任务进入这个备份队列。我们后来改造了备份的存储方式,直接将备份流入分式存储。

监控告警系统

未分类

在上线这套云平台前,我们还是用传统的 zabbix 来实现监控告警的。zabbix 的功能的确非常强大,但是后端的数据库是个瓶颈,当然可以通过数据库拆分的方式解决。

数据库要监控的指标比较多,如果采集的项目比较多,zabbix 就需要加 proxy,架构越来越复杂,再加上和我们平台对接的成本比较高,对一些复杂的统计类查询 (95 值、预测值等) 性能比较差。

所以我们选了一款 TSDB——prometheus,这是一款性能极强、极其适合监控系统使用的时序性数据库。prometheus 优点就是单机性能超强。但凡事又有两面性,它的缺点就是不支持集群架构 (不过我们解决了扩展的问题,下面会讲到)。

prometheus 的使用应该是从一年前就开始的,那时候我们只是把它作为辅助的监控系统来使用的,随着逐渐熟悉,越来越觉得这个是容器监控的绝佳解决方案。所以在上云平台的时候就选择了它作为整个平台的监控系统。

监控数据采集

prometheus 是支持 pushgateway 和 pull 的方式。我们选用了 pull 的方式。因为结构简单,开发成本低的同时还能和我们的系统完美对接。consul 集群负责注册实例信息和服务信息,比如 MySQL 实例主从对应的服务、Linux 主从对应的服务、容器注册对应的服务。然后 prometheus 通过 consul 上注册的信息来获取监控目标,然后去 pull 监控数据。监控客户端是以 agent 的形式存在,prometheus 通过 HTTP 协议获取 agent 端采集到的数据。

监控指标画图

不得不说 grafana 是监控画图界的扛把子,功能齐全的度量仪表盘和图形编辑器,经过简单配置就能完成各种监控图形的展示。然后我们打通了云平台和 grafana 的关联,用户在云平台需要查看实例或集群信息,只要点击按钮即可。

未分类

告警管理

告警管理分为:告警发送、告警接收人管理、告警静默等功能。prometheus 有一个告警发送模块 alertmanager,我们通过 webhook 的方式让 alertmanager 把告警信息发送到云平台的告警 API,然后在云平台来根据后面的逻辑进行告警内容发送。

alertmanager 推过来的只是实例纬度的告警,所以我们结合告警平台的实例相关信息,会拼出一个多维信息的告警内容。让 DBA 一看就知道是谁的哪个集群在什么时间触发了什么等级的什么告警。告警恢复后也会再发一次恢复的通知。

未分类

alertmanager 也是功能强大的工具,支持告警抑制、告警路由策略、发送周期、静默告警等等。有需要可以自行配置。但是这种和平台分离的管理方式不是我们想要的,所以就想把 alertmanager 对告警信息处理的这部分功能集成到云平台内。

但是官方文档并没有提及到 alertmanager 的 API,通过对源码的分析,我们找到了告警管理相关的 API。然后 alertmanager 的原生 UI 上操作的功能完美移植到了我们的云平台,同时新增了实例相关集群名称、负责人等更多纬度的信息。

下面是一些操作样例:

当前告警:

未分类

添加告警静默:

未分类

已创建的静默规则:

未分类

慢日志分析系统

未分类

慢日志的收集是通过 pt-query-digest 每小时进行本地分析,分析完成以后将结果写入慢日志存储的数据库来完成的。当然如果用户需要立刻查看当前慢日志的情况,也可以在界面点击慢日志分析。分析完成后可以在 UI 界面点击慢日志查看,就能看到该实例的慢日志分析结果。它同时集成了 explain、查看 table status 等功能。

集群管理

集群管理作为该平台的核心功能之一,占据了整个平台 70% 的工作。这些功能就是 DBA 运维中经常需要用到的。我们的设计思路是以集群为单位,所以同时只能操作一个集群上的实例。这样就不会在一个页面上显示过多无用的信息,看着乱还有可能导致误操作。看了下图中的这些功能就能更明白为什么要这么设计了。

未分类

图中只是一部分,还有部分未展示出的功能 (集成中间件、Dashboard、黑屏诊断窗口等),在后版中功能更多。

高可用

高可用方案我们使用了目前最流行的 MySQL 高可用方案 MHA。MHA 的优缺点就不在这里讲了,有 DBA 同学的应该都已经很熟悉了。这里我说一下我们基于同程业务做的调整。

GTID

因为我们主要使用的 MariaDB,但是 MHA 最新版本也是不能支持 MariaDB 的 GTID 切换。所以我们在原有的基础上做了改进,支持了 MariaDB 的 GTID。使用 GTID 以后灵活切换是一个方面,另外一个方面是 sync_master_info 和 sync_relay_log_info 就不需要设置成 1 了 (MariaDB 不支持写 table,只能写 file),极大减少了从库复制带来的 IOPS。

切换时调整相关参数

我们在切换时调整 sync_binlog 和 innodb_flush_log_at_trx_commit 参数,这两个参数是决定数据落盘方式的,默认大家都是设置双 1。这样相对数据最安全,但是 IO 也最高。

云服务的多实例部署会导致一台物理机上既有 master 又有 slave。我们肯定不希望 slave 产生太高的 IO 影响到同机器的其他 slave(虽然可以 IO 隔离,但是优先降低不必要 IO 才靠谱)。所以理论上来说 Master 上面设置双 1,slave 则可以不这样设置。但是切换后原来的 salve 可能会变成了 master。所以我们默认 slave 非双 1,在 MHA 切换的时候会自动将新 master 的这两个参数设置为 1。

哨兵

我们在多个点部署了哨兵服务。这个哨兵是一个简单的 API 服务,带上响应的参数可以请求到指定的实例。当 MHA manager 检测到有 Master 无法连接时,会触发 secondary check 机制,带着 master 相关信息请求哨兵节点的 API,根据哨兵节点返回情况,若超过半数无法连接则切换。否则放弃切换。

高可用切换对接 DB 中间件

未分类

DB 中间件和 DB 通过物理 IP 连接,当发生高可用切换时将最新的 Master IP、Master port 信息推送到 DB 中间件控制中心,DB 中间件拿到配置后立刻下发并生效。

实例、库迁移

未分类

迁移功能初衷是为了将平台外的实例或者库迁移到平台里面来,后来随着逐渐使用发现这个功能可挖掘的空间很大,比如可以做平台内库表拆分等需求。实现原理也很简单,用 mydumper 将指定数据备份下来以后,再用 myloader 恢复到指定数据库。

这是一个全量的过程,增量复制用的是用我们自己开发的一个支持并行复制的工具,这个工具还支持等幂处理,使用更灵活。没有用原生复制的原因是,假如要将源实例多个库中的一个库迁移到目标实例,那么原生复制就需要对 binlog 做复制过滤,这里面涉及到配置修改,实例重启,所以果断不考虑。

实现过程并没有高大上,但是完全满足需求。当然 mydumper 和 myloader 也有一些问题,我们也做了小改动以后才实现的。后面我们计划用流的方式去做数据导出导入 (类似于阿里开源的 datax)。

迁移完成,增量无延迟的情况下,大家会关心迁移前后数据一致性的问题,我们提供了自研的数据校验工具。实测 300G 的数据校验时间约为 2 至 3 分钟,快慢取决于开多少线程。

屏蔽底层物理资源

对用户来讲,平台提供的是一个或一组数据库服务,不需要关系后端的实例是在哪台机器上。资源计算和调度全部由系统的算法进行管理。

提升资源利用率 (CPU、内存)

通过单机多实例,CPU 资源可超卖,有效提高 CPU 资源的利用。内存资源未超卖,但是可以控制到每个实例的内存使用,确保每个实例都能有足够的内存。若有剩余内存,则继续分配容器即可,不 OOM 的情况下压榨内存资源。

提升运维效率

效率的提升得益于标准化以后带来的自动化。批量运维的成本很低。以前部署一套分片集群需要花费将近 6 个小时 (不包含对接中间件的 1 到 2 个小时),而现在只需要 5 分钟即可部署完成。并且部署完成以后会将提供一套中间件 +DB 分片集群的服务。

精细化管理

平台上线后有效提高了资源利用率,同时我们按照 1 库 1 实例的方式,可以有效避免不同库的压力不均导致相互影响的问题。并且性能监控也能精准到库级别。

使用Nodejs对Mongodb简单的增删改查

首先电脑上要装有Node、Mongodb

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*创建数据库连接*/
var db = mongoose.createConnection('localhost','mytest');

/*创建Schema*/
var mySchema = new Schema({
    name : String,
    age : Number
});

/*创建Model*/
var dbModel = db.model('test1',mySchema);

/*需要插入的数据*/
var lisiData = {
    name : '李四',
    age : 28
};

/*首先实例化一个对象*/
var person = new dbModel(lisiData);

/*调用对象的save方法进行保存 方法接收一个回调函数
*回调函数第一个参数为错误信息,如果没有错误为空,第二个是成功返回的信息
*/
person.save(function(err,_d){
    console.log(_d);
})

控制台打印内容:

未分类

接下来我们可以看到数据库多了一条数据:

未分类

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*创建数据库连接*/
var db = mongoose.createConnection('localhost','mytest');

/*创建Schema*/
var mySchema = new Schema({
    name : String,
    age : Number
});

/*创建Model*/
var dbModel = db.model('test1',mySchema);

/*查询name为李四的数据*/
dbModel.find({name:'李四'},function(err,_d){
    if(err){
        console.log(err);
    }else{
        console.log(_d);
    }
})

如果查询成功 会返回一个数组 如下图所示:

未分类

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*创建数据库连接*/
var db = mongoose.createConnection('localhost','mytest');

/*创建Schema*/
var mySchema = new Schema({
    name : String,
    age : Number
});

/*创建Model*/
var dbModel = db.model('test1',mySchema);

/*查询name为李四的数据 并将其name字段值更新为王五*/
dbModel.update({name:'李四'},{name:'王五'},function(err,_d){
    if(err){
        console.log(err);
    }else{
        console.log(_d);
    }
})

如果更新成功 控制台显示如下图所示:

未分类

咱们再看一下数据库是不是确实更新了:

未分类

var mongoose = require('mongoose');
var Schema = mongoose.Schema;

/*创建数据库连接*/
var db = mongoose.createConnection('localhost','mytest');

/*创建Schema*/
var mySchema = new Schema({
    name : String,
    age : Number
});

/*创建Model*/
var dbModel = db.model('test1',mySchema);

/*删除name为王五的数据*/
dbModel.remove({name:'王五'},function(err,_d){
    if(err){
        console.log(err);
    }else{
        console.log(_d);
    }
})

搭建mongodb 3.4分片副本集集群

mongodb是最常用的nodql数据库,在数据库排名中已经上升到了前六。这篇文章介绍如何搭建高可用的mongodb(分片+副本)集群。

在搭建集群之前,需要首先了解几个概念:路由,分片、副本集、配置服务器等。

相关概念

先来看一张图:

未分类

从图中可以看到有四个组件:mongos、config server、shard、replica set。

  • mongos,数据库集群请求的入口,所有的请求都通过mongos进行协调,不需要在应用程序添加一个路由选择器,mongos自己就是一个请求分发中心,它负责把对应的数据请求请求转发到对应的shard服务器上。在生产环境通常有多mongos作为请求的入口,防止其中一个挂掉所有的mongodb请求都没有办法操作。

  • config server,顾名思义为配置服务器,存储所有数据库元信息(路由、分片)的配置。mongos本身没有物理存储分片服务器和数据路由信息,只是缓存在内存里,配置服务器则实际存储这些数据。mongos第一次启动或者关掉重启就会从 config server 加载配置信息,以后如果配置服务器信息变化会通知到所有的 mongos 更新自己的状态,这样 mongos 就能继续准确路由。在生产环境通常有多个 config server 配置服务器,因为它存储了分片路由的元数据,防止数据丢失!

  • shard,分片(sharding)是指将数据库拆分,将其分散在不同的机器上的过程。将数据分散到不同的机器上,不需要功能强大的服务器就可以存储更多的数据和处理更大的负载。基本思想就是将集合切成小块,这些块分散到若干片里,每个片只负责总数据的一部分,最后通过一个均衡器来对各个分片进行均衡(数据迁移)。

  • replica set,中文翻译副本集,其实就是shard的备份,防止shard挂掉之后数据丢失。复制提供了数据的冗余备份,并在多个服务器上存储数据副本,提高了数据的可用性, 并可以保证数据的安全性。

仲裁者(Arbiter),是复制集中的一个MongoDB实例,它并不保存数据。仲裁节点使用最小的资源并且不要求硬件设备,不能将Arbiter部署在同一个数据集节点中,可以部署在其他应用服务器或者监视服务器中,也可部署在单独的虚拟机中。为了确保复制集中有奇数的投票成员(包括primary),需要添加仲裁节点做为投票,否则primary不能运行时不会自动切换primary。

简单了解之后,我们可以这样总结一下,应用请求mongos来操作mongodb的增删改查,配置服务器存储数据库元信息,并且和mongos做同步,数据最终存入在shard(分片)上,为了防止数据丢失同步在副本集中存储了一份,仲裁在数据存储到分片的时候决定存储到哪个节点。

环境准备

  • 系统系统 centos6.5
  • 三台服务器:192.168.0.75/84/86
  • 安装包: mongodb-linux-x86_64-3.4.6.tgz

服务器规划

未分类

端口分配:

mongos:20000
config:21000
shard1:27001
shard2:27002
shard3:27003

集群搭建

1、安装mongodb

#解压
tar -xzvf mongodb-linux-x86_64-3.4.6.tgz -C /usr/local/
#改名
mv mongodb-linux-x86_64-3.4.6 mongodb

分别在每台机器建立conf、mongos、config、shard1、shard2、shard3六个目录,因为mongos不存储数据,只需要建立日志文件目录即可。

mkdir -p /usr/local/mongodb/conf
mkdir -p /usr/local/mongodb/mongos/log
mkdir -p /usr/local/mongodb/config/data
mkdir -p /usr/local/mongodb/config/log
mkdir -p /usr/local/mongodb/shard1/data
mkdir -p /usr/local/mongodb/shard1/log
mkdir -p /usr/local/mongodb/shard2/data
mkdir -p /usr/local/mongodb/shard2/log
mkdir -p /usr/local/mongodb/shard3/data
mkdir -p /usr/local/mongodb/shard3/log

配置环境变量

vim /etc/profile
# 内容
export MONGODB_HOME=/usr/local/mongodb
export PATH=$MONGODB_HOME/bin:$PATH
# 使立即生效
source /etc/profile

2、config server配置服务器

mongodb3.4以后要求配置服务器也创建副本集,不然集群搭建不成功。

添加配置文件

vi /usr/local/mongodb/conf/config.conf

## 配置文件内容
pidfilepath = /usr/local/mongodb/config/log/configsrv.pid
dbpath = /usr/local/mongodb/config/data
logpath = /usr/local/mongodb/config/log/congigsrv.log
logappend = true

bind_ip = 0.0.0.0
port = 21000
fork = true

#declare this is a config db of a cluster;
configsvr = true

#副本集名称
replSet=configs

#设置最大连接数
maxConns=20000

启动三台服务器的config server

mongod -f /usr/local/mongodb/conf/config.conf

登录任意一台配置服务器,初始化配置副本集

#连接
mongo --port 21000
#config变量
config = {
...    _id : "configs",
...     members : [
...         {_id : 0, host : "192.168.0.75:21000" },
...         {_id : 1, host : "192.168.0.84:21000" },
...         {_id : 2, host : "192.168.0.86:21000" }
...     ]
... }

#初始化副本集
rs.initiate(config)

其中,”_id” : “configs”应与配置文件中配置的 replicaction.replSetName 一致,”members” 中的 “host” 为三个节点的 ip 和 port

3、配置分片副本集(三台机器)

设置第一个分片副本集

配置文件

vi /usr/local/mongodb/conf/shard1.conf

#配置文件内容
#——————————————–
pidfilepath = /usr/local/mongodb/shard1/log/shard1.pid
dbpath = /usr/local/mongodb/shard1/data
logpath = /usr/local/mongodb/shard1/log/shard1.log
logappend = true

bind_ip = 0.0.0.0
port = 27001
fork = true

#打开web监控
httpinterface=true
rest=true

#副本集名称
replSet=shard1

#declare this is a shard db of a cluster;
shardsvr = true

#设置最大连接数
maxConns=20000

启动三台服务器的shard1 server

mongod -f /usr/local/mongodb/conf/shard1.conf

登陆任意一台服务器,初始化副本集

mongo --port 27001
#使用admin数据库
use admin
#定义副本集配置,第三个节点的 "arbiterOnly":true 代表其为仲裁节点。
config = {
...    _id : "shard1",
...     members : [
...         {_id : 0, host : "192.168.0.75:27001" },
...         {_id : 1, host : "192.168.0.84:27001" },
...         {_id : 2, host : "192.168.0.86:27001” , arbiterOnly: true }
...     ]
... }
#初始化副本集配置
rs.initiate(config);

设置第二个分片副本集

配置文件

vi /usr/local/mongodb/conf/shard2.conf

#配置文件内容
#——————————————–
pidfilepath = /usr/local/mongodb/shard2/log/shard2.pid
dbpath = /usr/local/mongodb/shard2/data
logpath = /usr/local/mongodb/shard2/log/shard2.log
logappend = true

bind_ip = 0.0.0.0
port = 27002
fork = true

#打开web监控
httpinterface=true
rest=true

#副本集名称
replSet=shard2

#declare this is a shard db of a cluster;
shardsvr = true

#设置最大连接数
maxConns=20000

启动三台服务器的shard2 server

mongod -f /usr/local/mongodb/conf/shard2.conf

登陆任意一台服务器,初始化副本集

mongo --port 27002
#使用admin数据库
use admin
#定义副本集配置
config = {
...    _id : "shard2",
...     members : [
...         {_id : 0, host : "192.168.0.75:27002"  , arbiterOnly: true },
...         {_id : 1, host : "192.168.0.84:27002" },
...         {_id : 2, host : "192.168.0.86:27002" }
...     ]
... }

#初始化副本集配置
rs.initiate(config);

设置第三个分片副本集

配置文件

vi /usr/local/mongodb/conf/shard3.conf

#配置文件内容
#——————————————–
pidfilepath = /usr/local/mongodb/shard3/log/shard3.pid
dbpath = /usr/local/mongodb/shard3/data
logpath = /usr/local/mongodb/shard3/log/shard3.log
logappend = true

bind_ip = 0.0.0.0
port = 27003
fork = true

#打开web监控
httpinterface=true
rest=true

#副本集名称
replSet=shard3

#declare this is a shard db of a cluster;
shardsvr = true

#设置最大连接数
maxConns=20000

启动三台服务器的shard3 server

mongod -f /usr/local/mongodb/conf/shard3.conf

登陆任意一台服务器,初始化副本集

mongo --port 27003
#使用admin数据库
use admin
#定义副本集配置
config = {
...    _id : "shard3",
...     members : [
...         {_id : 0, host : "192.168.0.75:27003" },
...         {_id : 1, host : "192.168.0.84:27003" , arbiterOnly: true},
...         {_id : 2, host : "192.168.0.86:27003" }
...     ]
... }

#初始化副本集配置
rs.initiate(config);

4、配置路由服务器 mongos

先启动配置服务器和分片服务器,后启动路由实例启动路由实例:(三台机器)

vi /usr/local/mongodb/conf/mongos.conf

#内容
pidfilepath = /usr/local/mongodb/mongos/log/mongos.pid
logpath = /usr/local/mongodb/mongos/log/mongos.log
logappend = true

bind_ip = 0.0.0.0
port = 20000
fork = true

#监听的配置服务器,只能有1个或者3个 configs为配置服务器的副本集名字
configdb = configs/192.168.0.75:21000,192.168.0.84:21000,192.168.0.86:21000

#设置最大连接数
maxConns=20000

启动三台服务器的mongos server

mongod -f /usr/local/mongodb/conf/mongos.conf

5、启用分片

目前搭建了mongodb配置服务器、路由服务器,各个分片服务器,不过应用程序连接到mongos路由服务器并不能使用分片机制,还需要在程序里设置分片配置,让分片生效。

登陆任意一台mongos

mongo --port 20000
#使用admin数据库
user  admin
#串联路由服务器与分配副本集
sh.addShard("shard1/192.168.0.75:27001,192.168.0.84:27001,192.168.0.86:27001")
sh.addShard("shard2/192.168.0.75:27002,192.168.0.84:27002,192.168.0.86:27002")
sh.addShard("shard3/192.168.0.75:27003,192.168.0.84:27003,192.168.0.86:27003")
#查看集群状态
sh.status()

6、测试

目前配置服务、路由服务、分片服务、副本集服务都已经串联起来了,但我们的目的是希望插入数据,数据能够自动分片。连接在mongos上,准备让指定的数据库、指定的集合分片生效。

#指定testdb分片生效
db.runCommand( { enablesharding :"testdb"});
#指定数据库里需要分片的集合和片键
db.runCommand( { shardcollection : "testdb.table1",key : {id: 1} } )

我们设置testdb的 table1 表需要分片,根据 id 自动分片到 shard1 ,shard2,shard3 上面去。要这样设置是因为不是所有mongodb 的数据库和表 都需要分片!

测试分片配置结果

mongo  127.0.0.1:20000
#使用testdb
use  testdb;
#插入测试数据
for (var i = 1; i <= 100000; i++)
db.table1.save({id:i,"test1":"testval1"});
#查看分片情况如下,部分无关信息省掉了
db.table1.stats();

{
        "sharded" : true,
        "ns" : "testdb.table1",
        "count" : 100000,
        "numExtents" : 13,
        "size" : 5600000,
        "storageSize" : 22372352,
        "totalIndexSize" : 6213760,
        "indexSizes" : {
                "_id_" : 3335808,
                "id_1" : 2877952
        },
        "avgObjSize" : 56,
        "nindexes" : 2,
        "nchunks" : 3,
        "shards" : {
                "shard1" : {
                        "ns" : "testdb.table1",
                        "count" : 42183,
                        "size" : 0,
                        ...
                        "ok" : 1
                },
                "shard2" : {
                        "ns" : "testdb.table1",
                        "count" : 38937,
                        "size" : 2180472,
                        ...
                        "ok" : 1
                },
                "shard3" : {
                        "ns" : "testdb.table1",
                        "count" :18880,
                        "size" : 3419528,
                        ...
                        "ok" : 1
                }
        },
        "ok" : 1
}

可以看到数据分到3个分片,各自分片数量为: shard1 “count” : 42183,shard2 “count” : 38937,shard3 “count” : 18880。已经成功了!

后期运维

启动关闭

mongodb的启动顺序是,先启动配置服务器,在启动分片,最后启动mongos.

mongod -f /usr/local/mongodb/conf/config.conf
mongod -f /usr/local/mongodb/conf/shard1.conf
mongod -f /usr/local/mongodb/conf/shard2.conf
mongod -f /usr/local/mongodb/conf/shard3.conf
mongod -f /usr/local/mongodb/conf/mongos.conf

关闭时,直接killall杀掉所有进程

killall mongod
killall mongos