网易MySQL中间件的负载均衡策略及性能优化

团队介绍
网易乐得DBA组,负责网易乐得电商、网易邮箱、网易技术部数据库日常运维,负责数据库私有云平台的开发和维护,负责数据库及数据库中间件Cetus的开发和测试等等。

一、背景

随着业务的爆发式增长,电商系统中的读写压力越来越高,单节点MySQL实例压力越来越大,单纯升级服务器硬件已经无法满足生产环境的需要。解决读请求压力,需要支持从库扩展;解决写请求压力,对数据分片增加多个节点,降低单节点MySQL实例的压力成了更优的选择。

传统的分片是通过DAO层进行的,但是DAO层对数据分片存在诸多问题。从业务角度看,配置修改需要重启服务,代价巨大;需要对分片结果集进行处理,业务逻辑愈加复杂;功能相对简单。从数据库运维角度看,配置管理的统一化难度较大;DB的升级、迁移等操作复杂。

网易电商同样面临着这些问题,为了彻底解决数据库瓶颈,网易乐得团队在实际生产中研发了自己的中间件Cetus。其具有正统基因,基于官方MySQL-Proxy的版本进行全面修复和再创新,已于不久前开源,在各个产品线上得到广泛应用,性能和稳定性均表现良好。

Cetus兼容MySQL协议,前端应用不用修改即可通过Cetus访问数据库,方便DBA运维同学和开发同学使用,实现了数据库层面的横向扩展。

目前Cetus有读写分离和Sharding两个版本,可通过编译参数选择适合的版本。它支持对用户透明的多项功能,例如分布式事务、连接池、结果集压缩、安全管理、状态监控、Tcp Stream传输等等。

二、负载均衡策略及性能优化

本文所讨论的负载均衡,指的是读流量的负载均衡,即读流量如何分配到后端同一MySQL集群内的各个DB。

Cetus的负载均衡策略,主要分为两部分:

  • 主从库之间读流量的负载策略;
  • 从库之间读流量的负载策略。

具体实现时候,流量的分配单位与Atlas等中间件也略有不同,进行了性能优化。下面章节将依次详细介绍。

1、主从库之间读流量的负载策略

默认情况下,非事务中、未通过注释强制路由主库或未使用锁的读流量会优先路由到从库,各个从库之间负载均衡。只有当从库都不可用时,读流量才会路由到主库。

有些业务场景下,主库可以分担部分读流量,这时就涉及到读流量在主库和从库上配置负载策略了。

Cetus中,可以通过配置参数read-master-percentage来指定默认的读流量路由到主库的百分比,该参数的取值范围是[0, 100]。

该值默认为0,即所有读流量会优先路由从库,所有从库均不可用时,才会路由主库;如果该参数设置为100时,则所有读流量都会路由到主库;如果该值设置为(0, 100)时,则会按照设置的比例进行路由。需要注意的是,该值表示的是主库和所有从库的比例。

2、从库之间的读流量负载策略

路由到从库的流量会在各个从库之间进行负载均衡。目前Cetus各个从库之间的读流量负载策略仅支持轮询(RR)方式。

在流量分配方面,Cetus也进行了优化。一些MySQL数据库中间件(例如Atlas)是基于SQL的维度做负载均衡的,不会考虑SQL是同一个连接还是不同连接发送来的,中间件依次将接收到的SQL按照策略发往后端的数据库。

未分类

在实际使用中发现,长连接的场景下,该策略会造成大量的连接切换,从而导致session级变量的频繁调整,影响SQL执行效率。因此,Cetus对其进行了优化,并非完全按照SQL的维度做负载均衡。

Cetus考虑了同一个连接连续发送SQL请求的情况,不会立即将当前SQL使用完的Cetus与MySQL的连接放回连接池复用,而是持有短暂(256毫秒)时间,以期后续仍有SQL执行,从而避免了session级变量的调整,大大增加了SQL执行的效率。

未分类

长连接场景下,对优化前后的Cetus进行了简单测试。通过测试发现,通过优化后的Cetus针对长连场景下的读流量的吞吐量有了明显提升。下图是在docker环境下的简单测试对比:

未分类

为了防止IO过高,简单改造了sysbench发送的SQL,限制了返回的结果集大小。禁用事务和prepare的情况下,采用100个线程每次测试60s,连续测试5次,结果如下:

未分类

由于本机Docker性能较差,且sysbench模拟测试的语句较为简单,不涉及session变量的切换,因此对比效果不甚明显,本次测试性能仅提升30%左右。长连接业务场景下,性能优化可能会更加明显。

3、读流量的路由策略总结

在存在至少1个可用从库的情况下,影响查询语句的路由策略的因素主要有:

a. 事务中的查询;
b. select…for update 或 select … lock in share mode;
c. Cetus设置参数master-preferred=true所有流量默认全部路由主库;
d. Cetus设置参数read-master-percentage控制主从读流量负载;
e. 使用注释/#mode=READWRITE/或/#mode=READONLY/。

默认情况下,读流量会优先路由到从库,从库之间按照轮询策略在各个从库之间做负载均衡;一旦所有从库均不可用,会路由到主库上。目前Cetus的各个从库暂不支持按照权重做负载。

  • 对于a、b、c点,Cetus会将查询语句直接路由主库;
  • 对于d点,如果设置read-master-percentage=100,所有的查询流量均路由到主库;如果设置read-master-percentage=[0, 100),Cetus会将读流量按照该比例路由到主库和从库(注意,这里的从库指的是全部的从库,即该比例指的是主库和全部从库的比例);
  • 对于e点,如果使用注释/#mode=READWRITE/,读流量会路由到主库;如果使用注释/#mode=READONLY/读流量会路由从库,如果所有从库均不可用时才会路由到主库。

上面的各个因素的优先级,注释的优先级最高,其次是参数master-preferred,最后是参数read-master-percentage。

三、总结

MySQL数据库中间件的主要特性是对客户端发送的SQL进行路由,而其中负载均衡便是路由策略中的重要部分。通过了解Cetus的负载均衡机制,可以在后续维护过程中,更好的对数据库中间件进行调优,更灵活地控制SQL的路由。

Cetus中间件开源地址:https://github.com/Lede-Inc/cetus/blob/master/doc/cetus-quick-try.md

Mysql主从架构-主库宕机如何恢复业务

在我们日常工作场景,首先要做到架构无单点隐患,其次在优化【安全、性能、高可用、高并发等】,Mysql这款关系型数据库稳定、高效,所以使用广泛,如果企业架构是1主多从,那如果Mysql主库宕机,如何解决?

MySQL 主从同步原理图

未分类

一、Mysql主库宕机情况分类:

1)硬件问题,(服务器、ecs、虚拟主机等等)宕机

2)service问题,Mysql宕机,服务异常,端口异常等

二、硬件问题处理思路

硬件问题我们可以查看IDC巡检记录,或通过远程控制卡查看硬件运行状态,根据事实情况就行硬件故障报修进行处理,恢复业务步骤:

1)查看报警信息,确认业务是否收到影响,必要时切从库进行数据交互
2)IDC询问排查
3)确认硬件故障,短时间无法修复开Case处理
4)通知部门领导,处理进度,并实时记录
5)事件处理完成后,拟写故障报告,会议通报。

三、MySQL service问题处理思路

1)首先要做的就是判断是否影响业务,是否需要切库,保证业务运行时首要任务
2)如果此时需要切从库,安装如下步骤进行:

1>   先查看MySQL 从库状态
       show processlistG
##如果看到两个状态,说明此时的从库和主库是同步的  =====[如果不同步,建议考出binlog进行同步]
#state: waiting for master to send event   I/O线程
#state:has read all relay log;waiting for the slave I/O thread to update it    sql线程

2>   登录从库分别查看:【多个从库 哪个替代主库呢??】
cat /data/3306/data/master.info
cat /data/3307/data/master.info
##看哪个从库的哪个master.info哪个更新,就说明哪个从库一致性更高,所以此时就确定最新的库为主库。
选个pos最大的作为主库
或利用半同步的功能,直接选择做实时同步的这个从库。

3> 确保所有relay log全部更新完毕。
stop slave io_thread;show processlist;    [在每个从库上执行]
##直到看到has read all relay log;表示从库更新都执行完毕

4> 登录从库
mysql -uroot -p您的密码 -S /data/3306/mysql.sock  #sock路径根据自己的进行修改
stop slave;
reset master;
quit;

5> 进入数据库数据目录,删除master.info relay-log.info
cd /data/3306/data/master
rm -f master.info relay-log.info
## 检查授权表,类似read-only参数

6>  3306 提升从库为主库
vim /data/3306/my.cnf
开启:
log-bin =/data/3306/mysql-bin
## //如果存在log-slave-updates  read-only等参数一定要注释掉。
/data/3306/mysql  stop
/data/3306/mysql  start

到此,提升主库完毕

四、所有slave指向新的master

7> 如果主库服务器没down,需要去主库拉取bin-log补全提升主库的从库

8> 其它从库操作 【指向新的master】

已检查(同步user rep均存在)
登录从库
stop slave;
change master to master_host='192.168.1.32'; //如果不同步,就指定位置点
start slave;
show slave statusG;

以上就是关于数据库如何快速回复业务的介绍,如果大家有更好的方法可以告诉我,下面是集中数据库高可用方案:

1. MHA 高可用 http://blog.51cto.com/qiuyt/1930629

2. RDS 高可用+自动容灾 https://www.aliyun.com/product/rds/mysql【借用图】

未分类

3. 快照

方法很多,就看您如何选择。

五、另外关于ECS无法使用 MHA 说法问题解释

1、mha必须是0.56版本才支持GTID

2、阿里云ECS不支持浮动IP  阿里云HaVIP禁用
##要注意:阿里云的上ECS服务器跟我们自己的虚拟机不一样,它不支持浮动IP的

3、阿里云服务器上keepalived只能设置单播

4、keepalived配置文件里加脚本判断mysql是否启动需要首尾写好配置文件

阿里云工单反馈截图

未分类

MySQL一般查询日志或者慢查询日志历史数据的清理

general log&slow query log

对于MySQL的一般查询日志和慢查询日志,开启比较简单,其中公用的一个参数是log_output,log_output控制着慢查询和一般查询日志的输出方向
可以是表(mysql.general_log,mysql.slow_log)或者文件(有参数general_log_file和slow_query_log_file配置决定)
或者同时输出到表和文件(想不明白,什么时候需要同时输出到表和文件)。
但是两者受log_output参数影响,输出的目标总是一致的,也就是要么都写入表,或者要么都写入文件,不会一个输出到表,一个输出到文件。

--slow log 相关参数
select * 
from performance_schema.global_variables 
where variable_name in
('slow_query_log','log_output','slow_query_log_file','long_query_time')

--general log 相关参数
select * 
from performance_schema.global_variables 
where variable_name in
('general_log','log_output','general_log_file')

对于上述两种日志,系统默认不会清理,因此在开启了相关日志之后,需要人为清理。

如何清理历史general log&slow query log

1、当输出目标为表的时候

无法直接删除,如果直接删除的话,会出现“ERROR 1556 (HY000): You can’t use locks with log tables.”的错误提示

未分类

以general log为例,需要先关闭general_log,然后重命名general_log这个表,

未分类

在对重命名之后的表执行删除,最后在重命名回来,最后开启general_log(如果有必要的话)

SET GLOBAL general_log = 'OFF';
RENAME TABLE general_log TO general_log_temp;
DELETE FROM general_log_temp WHERE event_time < DATE(NOW());
RENAME TABLE general_log_temp TO general_log;
SET GLOBAL general_log = 'ON';


--slow log 同理
SET GLOBAL slow_query_log = 'OFF';
RENAME TABLE slow_log TO slow_log_temp;
DELETE FROM slow_log_temp WHERE start_time < DATE(NOW());
RENAME TABLE slow_log_temp TO slow_log;
SET GLOBAL slow_query_log = 'ON';

  如果对重命名之后的表(general_log或者是slow_log)没有再次重命名回来,会发生什么?
  参加如下截图,如果没有找到对应的表(general_log或者是slow_log),在输出目标为表的情况下,会提示无法找到对应的表,将无法开启对应的日志

未分类

2、当输出目标为文件的时候

  当输出目标为文件的时候,在linux下,直接使用rm命名删除即可,如果在开启了一般查询日志或者是慢查询日志,删除对应的日志文件,并不影响数据库的正常使用
  网上有说需要停止MySQL服务然后重命名文件然后在创建新的文件啥的,在Linux下并不是必须的,不知道在windows下是什么情况,没兴趣试。
  当然也不是说就建议始终这种暴力的方式清理日志文件,
  在Linux下,删除了默认的日志文件(或者重命名了原日志文件),要想再次生成日志文件
  1,可以使用mysqladmin flush-logs
  2,是SQL命令flush slow logs;flush general logs;
  3,重启MySQL服务
  均可重新生成对应的日志文件。

未分类

注意:当对应的文件是存在的时候,上述命名执行之后是没有影响的(也不会清理对应的日志文件)



以下偏离主题

当输出目标为表的时候的解析

  不管是general_log或者是slow_log,对应的SQL语句都是二进制格式的,需要使用convert(sql_text using UTF8)做一个转换,才变得具有可读性。

未分类

当输出目标为表的时候对性能的影响

  据个人测试,在请求量不大的数据库上,开启general_log或者是slow_log,对性能影响并没有非常明显。
  理论家们一方面强调说MySQL的处理并发上多强悍,一方面又说开启general_log对性能影响很大,会不会自相矛盾呢?
  关于general_log,在zabbix监控下,测试环境TPS不过百的情况下(每秒写入general log不超过100条数据),开启general_log之后并CPU负载几乎没有变化,CPU高点是在做其他压力测试。
  尤其是slow_log这种写入不是太频繁的日志,直接写入到表中,对性能的影响有限,比后面再去花时间解析文件……  
  当然不排除TPS在上千或者上万甚至更高之后,开启general_log会产生较大的影响,当然没事也不会闲的蛋疼去开general_log。

未分类

mongodb分布式集群搭建手记

摘要: 一、架构简介 目标 单机搭建mongodb分布式集群(副本集 + 分片集群),演示mongodb分布式集群的安装部署、简单操作。 说明 在同一个vm启动由两个分片组成的分布式集群,每个分片都是一个PSS(Primary-Secondary-Secondary)模式的数据副本集; Config副本集采用PSS(Primary-Secondary-Secondary)模式。

一、架构简介

目标

单机搭建mongodb分布式集群(副本集 + 分片集群),演示mongodb分布式集群的安装部署、简单操作。

未分类

说明

在同一个vm启动由两个分片组成的分布式集群,每个分片都是一个PSS(Primary-Secondary-Secondary)模式的数据副本集;
Config副本集采用PSS(Primary-Secondary-Secondary)模式。

二、配置说明

端口通讯

当前集群中存在shard、config、mongos共12个进程节点,端口矩阵编排如下:

编号  实例类型
1   mongos
2   mongos
3   mongos
4   config
5   config
6   config
7   shard1
8   shard1
9   shard1
10  shard2
11  shard2
12  shard2

内部鉴权

节点间鉴权采用keyfile方式实现鉴权,mongos与分片之间、副本集节点之间共享同一套keyfile文件。 官方说明

账户设置

管理员账户:admin/Admin@01,具有集群及所有库的管理权限
应用账号:appuser/AppUser@01,具有appdb的owner权限

关于初始化权限

keyfile方式默认会开启鉴权,而针对初始化安装的场景,Mongodb提供了localhost-exception机制,
可以在首次安装时通过本机创建用户、角色,以及副本集初始操作。

三、准备工作

1. 下载安装包

官方地址:https://www.mongodb.com/download-center

wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-rhel70-3.6.3.tgz

2. 部署目录

解压压缩文件,将bin目录拷贝到目标路径/opt/local/mongo-cluster,参考以下命令:

tar -xzvf mongodb-linux-x86_64-rhel70-3.6.3.tgz
mkdir -p  /opt/local/mongo-cluster
cp -r mongodb-linux-x86_64-rhel70-3.6.3/bin  /opt/local/mongo-cluster

3. 创建配置文件

cd /opt/local/mongo-cluster
mkdir conf 

A. mongod 配置文件 mongo_node.conf

mongo_node.conf 作为mongod实例共享的配置文件,内容如下:

storage:
    engine: wiredTiger
    directoryPerDB: true
    journal:
        enabled: true
systemLog:
    destination: file
    logAppend: true
operationProfiling:
  slowOpThresholdMs: 10000
replication:
    oplogSizeMB: 10240
processManagement:
    fork: true
net:
    http:
      enabled: false
security:
    authorization: "enabled"

选项说明可参考这里

B. mongos 配置文件 mongos.conf

systemLog:
    destination: file
    logAppend: true
processManagement:
    fork: true
net:
    http:
      enabled: false

4. 创建keyfile文件

cd /opt/local/mongo-cluster
mkdir keyfile
openssl rand -base64 756 > mongo.key
chmod 400 mongo.key
mv mongo.key keyfile

mongo.key 采用随机算法生成,用作节点内部通讯的密钥文件

5. 创建节点目录

WORK_DIR=/opt/local/mongo-cluster
mkdir -p $WORK_DIR/nodes/config/n1/data
mkdir -p $WORK_DIR/nodes/config/n2/data
mkdir -p $WORK_DIR/nodes/config/n3/data

mkdir -p $WORK_DIR/nodes/shard1/n1/data
mkdir -p $WORK_DIR/nodes/shard1/n2/data
mkdir -p $WORK_DIR/nodes/shard1/n3/data

mkdir -p $WORK_DIR/nodes/shard2/n1/data
mkdir -p $WORK_DIR/nodes/shard2/n2/data
mkdir -p $WORK_DIR/nodes/shard2/n3/data

mkdir -p $WORK_DIR/nodes/mongos/n1
mkdir -p $WORK_DIR/nodes/mongos/n2
mkdir -p $WORK_DIR/nodes/mongos/n3

以config 节点1 为例,nodes/config/n1/data是数据目录,而pid文件、日志文件都存放于n1目录
以mongos 节点1 为例,nodes/mongos/n1 存放了pid文件和日志文件

四、搭建集群

1. Config副本集

按以下脚本启动3个Config实例

WORK_DIR=/opt/local/mongo-cluster
KEYFILE=$WORK_DIR/keyfile/mongo.key
CONFFILE=$WORK_DIR/conf/mongo_node.conf
MONGOD=$WORK_DIR/bin/mongod

$MONGOD --port 26001 --configsvr --replSet configReplSet --keyFile $KEYFILE --dbpath $WORK_DIR/nodes/config/n1/data --pidfilepath $WORK_DIR/nodes/config/n1/db.pid --logpath $WORK_DIR/nodes/config/n1/db.log --config $CONFFILE

$MONGOD --port 26002 --configsvr --replSet configReplSet --keyFile $KEYFILE --dbpath $WORK_DIR/nodes/config/n2/data --pidfilepath $WORK_DIR/nodes/config/n2/db.pid --logpath $WORK_DIR/nodes/config/n2/db.log --config $CONFFILE

$MONGOD --port 26003 --configsvr --replSet configReplSet --keyFile $KEYFILE --dbpath $WORK_DIR/nodes/config/n3/data --pidfilepath $WORK_DIR/nodes/config/n3/db.pid --logpath $WORK_DIR/nodes/config/n3/db.log --config $CONFFILE

待成功启动后,输出日志如下:

about to fork child process, waiting until server is ready for connections.
forked process: 4976
child process started successfully, parent exiting

此时通过ps 命令也可以看到3个启动的进程实例。

连接其中一个Config进程,执行副本集初始化

./bin/mongo --port 26001 --host 127.0.0.1
> MongoDB server version: 3.4.7
> cfg={
    _id:"configReplSet", 
    configsvr: true,
    members:[
        {_id:0, host:'127.0.0.1:26001'},
        {_id:1, host:'127.0.0.1:26002'}, 
        {_id:2, host:'127.0.0.1:26003'}
    ]};
rs.initiate(cfg);

其中configsvr:true指明这是一个用于分片集群的Config副本集。
关于副本集配置可参考这里

2. 创建分片

按以下脚本启动Shard1的3个实例

WORK_DIR=/opt/local/mongo-cluster
KEYFILE=$WORK_DIR/keyfile/mongo.key
CONFFILE=$WORK_DIR/conf/mongo_node.conf
MONGOD=$WORK_DIR/bin/mongod

echo "start shard1 replicaset"

$MONGOD --port 27001 --shardsvr --replSet shard1 --keyFile $KEYFILE --dbpath $WORK_DIR/nodes/shard1/n1/data --pidfilepath $WORK_DIR/nodes/shard1/n1/db.pid --logpath $WORK_DIR/nodes/shard1/n1/db.log --config $CONFFILE
$MONGOD --port 27002 --shardsvr --replSet shard1 --keyFile $KEYFILE --dbpath $WORK_DIR/nodes/shard1/n2/data --pidfilepath $WORK_DIR/nodes/shard1/n2/db.pid --logpath $WORK_DIR/nodes/shard1/n2/db.log --config $CONFFILE
$MONGOD --port 27003 --shardsvr --replSet shard1 --keyFile $KEYFILE --dbpath $WORK_DIR/nodes/shard1/n3/data --pidfilepath $WORK_DIR/nodes/shard1/n3/db.pid --logpath $WORK_DIR/nodes/shard1/n3/db.log --config $CONFFILE

待成功启动后,输出日志如下:

about to fork child process, waiting until server is ready for connections.
forked process: 5976
child process started successfully, parent exiting

此时通过ps 命令也可以看到3个启动的Shard进程实例。

连接其中一个Shard进程,执行副本集初始化

./bin/mongo --port 27001 --host 127.0.0.1
> MongoDB server version: 3.4.7
> cfg={
    _id:"shard1", 
    members:[
        {_id:0, host:'127.0.0.1:27001'},
        {_id:1, host:'127.0.0.1:27002'}, 
        {_id:2, host:'127.0.0.1:27003'}
    ]};
rs.initiate(cfg);

参考以上步骤,启动Shard2的3个实例进程,并初始化副本集。

3. 启动mongos路由

执行以下脚本启动3个mongos进程

WORK_DIR=/opt/local/mongo-cluster
KEYFILE=$WORK_DIR/keyfile/mongo.key
CONFFILE=$WORK_DIR/conf/mongos.conf
MONGOS=$WORK_DIR/bin/mongos

echo "start mongos instances"
$MONGOS --port=25001 --configdb configReplSet/127.0.0.1:26001,127.0.0.1:26002,127.0.0.1:26003 --keyFile $KEYFILE --pidfilepath $WORK_DIR/nodes/mongos/n1/db.pid --logpath $WORK_DIR/nodes/mongos/n1/db.log --config $CONFFILE
$MONGOS --port 25002 --configdb configReplSet/127.0.0.1:26001,127.0.0.1:26002,127.0.0.1:26003 --keyFile $KEYFILE --pidfilepath $WORK_DIR/nodes/mongos/n2/db.pid --logpath $WORK_DIR/nodes/mongos/n2/db.log --config $CONFFILE
$MONGOS --port 25003 --configdb configReplSet/127.0.0.1:26001,127.0.0.1:26002,127.0.0.1:26003 --keyFile $KEYFILE --pidfilepath $WORK_DIR/nodes/mongos/n3/db.pid --logpath $WORK_DIR/nodes/mongos/n3/db.log --config $CONFFILE

待成功启动后,通过ps命令看到mongos进程:

dbuser      7903    1  0 17:49 ?        00:00:00 /opt/local/mongo-cluster/bin/mongos --port=25001 --configdb configReplSet/127.0.0.1:26001,127.0.0.1:26002,127.0.0.1:26003 --keyFile /opt/local/mongo-cluster/keyfile/mongo.key --pidfilepath /opt/local/mongo-cluster/nodes/mongos/n1/db.pid --logpath /opt/local/mongo-cluster/nodes/mongos/n1/db.log --config /opt/local/mongo-cluster/conf/mongos.conf
dbuser      7928    1  0 17:49 ?        00:00:00 /opt/local/mongo-cluster/bin/mongos --port 25002 --configdb configReplSet/127.0.0.1:26001,127.0.0.1:26002,127.0.0.1:26003 --keyFile /opt/local/mongo-cluster/keyfile/mongo.key --pidfilepath /opt/local/mongo-cluster/nodes/mongos/n2/db.pid --logpath /opt/local/mongo-cluster/nodes/mongos/n2/db.log --config /opt/local/mongo-cluster/conf/mongos.conf
dbuser      7954    1  0 17:49 ?        00:00:00 /opt/local/mongo-cluster/bin/mongos --port 25003 --configdb configReplSet/127.0.0.1:26001,127.0.0.1:26002,127.0.0.1:26003 --keyFile /opt/local/mongo-cluster/keyfile/mongo.key --pidfilepath /opt/local/mongo-cluster/nodes/mongos/n3/db.pid --logpath /opt/local/mongo-cluster/nodes/mongos/n3/db.log --config /opt/local/mongo-cluster/conf/mongos.conf

接入其中一个mongos实例,执行添加分片操作:

./bin/mongo --port 25001 --host 127.0.0.1
mongos> MongoDB server version: 3.4.7
mongos> sh.addShard("shard1/127.0.0.1:27001")
{ "shardAdded" : "shard1", "ok" : 1 }
mongos> sh.addShard("shard2/127.0.0.1:27004")
{ "shardAdded" : "shard2", "ok" : 1 }

至此,分布式集群架构启动完毕,但进一步操作需要先添加用户。

4. 初始化用户

接入其中一个mongos实例,添加管理员用户

use admin
db.createUser({
    user:'admin',pwd:'Admin@01',
    roles:[
        {role:'clusterAdmin',db:'admin'},
        {role:'userAdminAnyDatabase',db:'admin'},
        {role:'dbAdminAnyDatabase',db:'admin'},
        {role:'readWriteAnyDatabase',db:'admin'}
]})

当前admin用户具有集群管理权限、所有数据库的操作权限。
需要注意的是,在第一次创建用户之后,localexception不再有效,接下来的所有操作要求先通过鉴权。

use admin
db.auth('admin','Admin@01')

检查集群状态

mongos> sh.status()
--- Sharding Status --- 
  sharding version: {
    "_id" : 1,
    "minCompatibleVersion" : 5,
    "currentVersion" : 6,
    "clusterId" : ObjectId("5aa39c3e915210dc501a1dc8")
}
  shards:
    {  "_id" : "shard1",  "host" : "shard1/127.0.0.1:27001,127.0.0.1:27002,127.0.0.1:27003",  "state" : 1 }
    {  "_id" : "shard2",  "host" : "shard2/127.0.0.1:27004,127.0.0.1:27005,127.0.0.1:27006",  "state" : 1 }
  active mongoses:
    "3.4.7" : 3
autosplit:
    Currently enabled: yes

集群用户

分片集群中的访问都会通过mongos入口,而鉴权数据是存储在config副本集中的,即config实例中system.users数据库存储了集群用户及角色权限配置。mongos与shard实例则通过内部鉴权(keyfile机制)完成,因此shard实例上可以通过添加本地用户以方便操作管理。在一个副本集上,只需要在Primary节点上添加用户及权限,相关数据会自动同步到Secondary节点。
关于集群鉴权https://docs.mongodb.com/manual/core/security-users/?spm=a2c4e.11153940.blogcont617224.15.77552d7eSApnpI#sharded-cluster-users
在本案例中,我们为两个分片副本集都添加了本地admin用户。

通过mongostat工具可以显示集群所有角色:

          host insert query update delete getmore command dirty used flushes mapped vsize  res faults qrw arw net_in net_out conn    set repl                time
127.0.0.1:27001    *0    *0    *0    *0      0    6|0  0.1% 0.1%      0        1.49G 44.0M    n/a 0|0 0|0  429b  56.1k  25 shard1  PRI Mar 10 19:05:13.928
127.0.0.1:27002    *0    *0    *0    *0      0    7|0  0.1% 0.1%      0        1.43G 43.0M    n/a 0|0 0|0  605b  55.9k  15 shard1  SEC Mar 10 19:05:13.942
127.0.0.1:27003    *0    *0    *0    *0      0    7|0  0.1% 0.1%      0        1.43G 43.0M    n/a 0|0 0|0  605b  55.9k  15 shard1  SEC Mar 10 19:05:13.946
127.0.0.1:27004    *0    *0    *0    *0      0    6|0  0.1% 0.1%      0        1.48G 43.0M    n/a 0|0 0|0  546b  55.8k  18 shard2  PRI Mar 10 19:05:13.939
127.0.0.1:27005    *0    *0    *0    *0      0    6|0  0.1% 0.1%      0        1.43G 42.0M    n/a 0|0 0|0  540b  54.9k  15 shard2  SEC Mar 10 19:05:13.944
127.0.0.1:27006    *0    *0    *0    *0      0    6|0  0.1% 0.1%      0        1.46G 44.0M    n/a 0|0 0|0  540b  54.9k  17 shard2  SEC Mar 10 19:05:13.936

五、数据操作

在案例中,创建appuser用户、为数据库实例appdb启动分片。

use appdb
db.createUser({user:'appuser',pwd:'AppUser@01',roles:[{role:'dbOwner',db:'appdb'}]})
sh.enableSharding("appdb")

创建集合book,为其执行分片初始化。

use appdb
db.createCollection("book")
db.device.ensureIndex({createTime:1})
sh.shardCollection("appdb.book", {bookId:"hashed"}, false, { numInitialChunks: 4} )

继续往device集合写入1000W条记录,观察chunks的分布情况

use appdb
var cnt = 0;
for(var i=0; i<1000; i++){
    var dl = [];
    for(var j=0; j<100; j++){
        dl.push({
                "bookId" : "BBK-" + i + "-" + j,
                "type" : "Revision",
                "version" : "IricSoneVB0001",
                "title" : "Jackson's Life",
                "subCount" : 10,
                "location" : "China CN Shenzhen Futian District",
                "author" : {
                      "name" : 50,
                      "email" : "[email protected]",
                      "gender" : "female"
                },
                "createTime" : new Date()
            });
      }
      cnt += dl.length;
      db.book.insertMany(dl);
      print("insert ", cnt);
}

执行db.book.getShardDistribution(),输出如下:

Shard shard1 at shard1/127.0.0.1:27001,127.0.0.1:27002,127.0.0.1:27003
data : 13.41MiB docs : 49905 chunks : 2
estimated data per chunk : 6.7MiB
estimated docs per chunk : 24952

Shard shard2 at shard2/127.0.0.1:27004,127.0.0.1:27005,127.0.0.1:27006
data : 13.46MiB docs : 50095 chunks : 2
estimated data per chunk : 6.73MiB
estimated docs per chunk : 25047

Totals
data : 26.87MiB docs : 100000 chunks : 4
Shard shard1 contains 49.9% data, 49.9% docs in cluster, avg obj size on shard : 281B
Shard shard2 contains 50.09% data, 50.09% docs in cluster, avg obj size on shard : 281B

六、总结

Mongodb集群架构由Mongos、Config副本集和多个分片组成;
安装过程中先初始化Config副本集、分片副本集,最后通过Mongos添加分片
Config副本集存储了集群访问的用户及角色权限,为了方便管理,可以给分片副本集添加本地用户
Mongodb提供了LocalException机制,首次安装数据库时可以在本机直接添加用户

MongoDB 4.0 事务实现解析

基于飞天分布式系统和高性能存储,提供三节点副本集的高可用架构,容灾切换,故障迁移完全透明化。并提供专业的数据库在线扩容、备份回滚、性能优化等解决方案。

上个月底 MongoDB Wolrd 宣布发布 MongoDB 4.0, 支持复制集多文档事务,阿里云数据库团队 研发工程师第一时间对事务功能的时间进行了源码分析,解析事务实现机制。

MongoDB 4.0 引入的事务功能,支持多文档ACID特性,例如使用 mongo shell 进行事务操作

> s = db.getMongo().startSession()
session { "id" : UUID("3bf55e90-5e88-44aa-a59e-a30f777f1d89") }
> s.startTransaction()
> db.coll01.insert({x: 1, y: 1})
WriteResult({ "nInserted" : 1 })
> db.coll02.insert({x: 1, y: 1})
WriteResult({ "nInserted" : 1 })
> s.commitTransaction()  (或者 s.abortTransaction()回滚事务)

支持 MongoDB 4.0 的其他语言 Driver 也封装了事务相关接口,用户需要创建一个 Session,然后在 Session 上开启事务,提交事务。例如

python 版本

with client.start_session() as s:
    s.start_transaction()
    collection_one.insert_one(doc_one, session=s)
    collection_two.insert_one(doc_two, session=s)
    s.commit_transaction()

java 版本

try (ClientSession clientSession = client.startSession()) {
   clientSession.startTransaction();
   collection.insertOne(clientSession, docOne);
   collection.insertOne(clientSession, docTwo);
   clientSession.commitTransaction();
}

Session

Session 是 MongoDB 3.6 版本引入的概念,引入这个特性主要就是为实现多文档事务做准备。Session 本质上就是一个「上下文」。

在以前的版本,MongoDB 只管理单个操作的上下文,mongod 服务进程接收到一个请求,为该请求创建一个上下文 (源码里对应 OperationContext),然后在服务整个请求的过程中一直使用这个上下文,内容包括,请求耗时统计、请求占用的锁资源、请求使用的存储快照等信息。有了 Session 之后,就可以让多个请求共享一个上下文,让多个请求产生关联,从而有能力支持多文档事务。

每个 Session 包含一个唯一的标识 lsid,在 4.0 版本里,用户的每个请求可以指定额外的扩展字段,主要包括:

  • lsid: 请求所在 Session 的 ID, 也称 logic session id
  • txnNmuber: 请求对应的事务号,事务号在一个 Session 内必须单调递增
  • stmtIds: 对应请求里每个操作(以insert为例,一个insert命令可以插入多个文档)操作ID

实际上,用户在使用事务时,是不需要理解这些细节,MongoDB Driver 会自动处理,Driver 在创建 Session 时分配 lsid,接下来这个 Session 里的所以操作,Driver 会自动为这些操作加上 lsid,如果是事务操作,会自动带上 txnNumber。

值得一提的是,Session lsid 可以通过调用 startSession 命令让 server 端分配,也可以客户端自己分配,这样可以节省一次网络开销;而事务的标识,MongoDB 并没有提供一个单独的 startTransaction的命令,txnNumber 都是直接由 Driver 来分配的,Driver 只需保证一个 Session 内,txnNumber 是递增的,server 端收到新的事务请求时,会主动的开始一个新事务。

MongoDB 在 startSession 时,可以指定一系列的选项,用于控制 Session 的访问行为,主要包括:

  • causalConsistency: 是否提供 causal consistency 的语义,如果设置为true,不论从哪个节点读取,MongoDB 会保证 “read your own write” 的语义。参考 causal consistency
  • readConcern:参考 MongoDB readConcern 原理解析
  • writeConcern:参考 MongoDB writeConcern 原理解析
  • readPreference: 设置读取时选取节点的规则,参考 read preference
  • retryWrites:如果设置为true,在复制集场景下,MongoDB 会自动重试发生重新选举的场景; 参考retryable write

ACID

Atomic

针对多文档的事务操作,MongoDB 提供 “All or nothing” 的原子语义保证。

Consistency

太难解释了,还有抛弃 Consistency 特性的数据库?

Isolation

MongoDB 提供 snapshot 隔离级别,在事务开始创建一个 WiredTiger snapshot,然后在整个事务过程中使用这个快照提供事务读。

Durability

事务使用 WriteConcern {j: ture} 时,MongoDB 一定会保证事务日志提交才返回,即使发生 crash,MongoDB 也能根据事务日志来恢复;而如果没有指定 {j: true} 级别,即使事务提交成功了,在 crash recovery 之后,事务的也可能被回滚掉。

事务与复制

复制集配置下,MongoDB 整个事务在提交时,会记录一条 oplog(oplog 是一个普通的文档,所以目前版本里事务的修改加起来不能超过文档大小 16MB的限制),包含事务里所有的操作,备节点拉取oplog,并在本地重放事务操作。

事务 oplog 示例,包含事务操作的 lsid,txnNumber,以及事务内所有的操作日志(applyOps字段)

"ts" : Timestamp(1530696933, 1), "t" : NumberLong(1), "h" : NumberLong("4217817601701821530"), "v" : 2, "op" : "c", "ns" : "admin.$cmd", "wall" : ISODate("2018-07-04T09:35:33.549Z"), "lsid" : { "id" : UUID("e675c046-d70b-44c2-ad8d-3f34f2019a7e"), "uid" : BinData(0,"47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=") }, "txnNumber" : NumberLong(0), "stmtId" : 0, "prevOpTime" : { "ts" : Timestamp(0, 0), "t" : NumberLong(-1) }, "o" : { "applyOps" : [ { "op" : "i", "ns" : "test.coll2", "ui" : UUID("a49ccd80-6cfc-4896-9740-c5bff41e7cce"), "o" : { "_id" : ObjectId("5b3c94d4624d615ede6097ae"), "x" : 20000 } }, { "op" : "i", "ns" : "test.coll3", "ui" : UUID("31d7ae62-fe78-44f5-ba06-595ae3b871fc"), "o" : { "_id" : ObjectId("5b3c94d9624d615ede6097af"), "x" : 20000 } } ] } }

整个重放过程如下:

  1. 获取当前 Batch (后台不断拉取 oplog 放入 Batch)
  2. 设置 OplogTruncateAfterPoint 时间戳为 Batch里第一条 oplog 时间戳 (存储在 local.replset.oplogTruncateAfterPoint 集合)
  3. 写入 Batch 里所有的 oplog 到 local.oplog.rs 集合,根据 oplog 条数,如果数量较多,会并发写入加速
  4. 清理 OplogTruncateAfterPoint, 标识 oplog 完全成功写入;如果在本步骤完成前 crash,重启恢复时,发现 oplogTruncateAfterPoint 被设置,会将 oplog 截短到该时间戳,以恢复到一致的状态点。
  5. 将 oplog 划分到到多个线程并发重放,为了提升并发效率,事务产生的 oplog 包含的所有修改操作,跟一条普通单条操作的 oplog 一样,会据文档ID划分到多个线程。
  6. 更新 ApplyThrough 时间戳为 Batch 里最后一条 oplog 时间戳,标识下一次重启后,从该位置重新同步,如果本步骤之前失败,重启恢复时,会从 ApplyThrough 上一次的值(上一个 Batch 最后一条 oplog)拉取 oplog。
  7. 更新 oplog 可见时间戳,如果有其他节点从该备节点同步,此时就能读到这部分新写入的 oplog
  8. 更新本地 Snapshot(时间戳),新的写入将对用户可见。

事务与存储引擎

事务时序统一

WiredTiger 很早就支持事务,在 3.x 版本里,MongoDB 就通过 WiredTiger 事务,来保证一条修改操作,对数据、索引、oplog 三者修改的原子性。但实际上 MongoDB 经过多个版本的迭代,才提供了事务接口,核心难点就是时序问题。

MongoDB 通过 oplog 时间戳来标识全局顺序,而 WiredTiger 通过内部的事务ID来标识全局顺序,在实现上,2者没有任何关联。这就导致在并发情况下, MongoDB 看到的事务提交顺序与 WiredTiger 看到的事务提交顺序不一致。

为解决这个问题,WiredTier 3.0 引入事务时间戳(transaction timestamp)机制,应用程序可以通过 WT_SESSION::timestamp_transaction 接口显式的给 WiredTiger 事务分配 commit timestmap,然后就可以实现指定时间戳读(read “as of” a timestamp)。有了 read “as of” a timestamp 特性后,在重放 oplog 时,备节点上的读就不会再跟重放 oplog 有冲突了,不会因重放 oplog 而阻塞读请求,这是4.0版本一个巨大的提升。

/*
 * __wt_txn_visible --
 *  Can the current transaction see the given ID / timestamp?
 */
static inline bool
__wt_txn_visible(
    WT_SESSION_IMPL *session, uint64_t id, const wt_timestamp_t *timestamp)
{
    if (!__txn_visible_id(session, id))
        return (false);

    /* Transactions read their writes, regardless of timestamps. */
    if (F_ISSET(&session->txn, WT_TXN_HAS_ID) && id == session->txn.id)
        return (true);

#ifdef HAVE_TIMESTAMPS
    {
    WT_TXN *txn = &session->txn;

    /* Timestamp check. */
    if (!F_ISSET(txn, WT_TXN_HAS_TS_READ) || timestamp == NULL)
        return (true);

    return (__wt_timestamp_cmp(timestamp, &txn->read_timestamp) <= 0);
    }
#else
    WT_UNUSED(timestamp);
    return (true);
#endif
}

从上面的代码可以看到,再引入事务时间戳之后,在可见性判断时,还会额外检查时间戳,上层读取时指定了时间戳读,则只能看到该时间戳以前的数据。而 MongoDB 在提交事务时,会将 oplog 时间戳跟事务关联,从而达到 MongoDB Server 层时序与 WiredTiger 层时序一致的目的。

事务对 cache 的影响

WiredTiger(WT) 事务会打开一个快照,而快照的存在的 WiredTiger cache evict 是有影响的。一个 WT page 上,有N个版本的修改,如果这些修改没有全局可见(参考 __wt_txn_visible_all),这个 page 是不能 evict 的(参考 __wt_page_can_evict)。

在 3.x 版本里,一个写请求对数据、索引、oplog的修改会放到一个 WT 事务里,事务的提交由 MongoDB 自己控制,MongoDB 会尽可能快的提交事务,完成写清求;但 4.0 引入事务之后,事务的提交由应用程序控制,可能出现一个事务修改很多,并且很长时间不提交,这会给 WT cache evict 造成很大的影响,如果大量内存无法 evict,最终就会进入 cache stuck 状态。

为了尽量减小 WT cache 压力,MongoDB 4.0 事务功能有一些限制,但事务资源占用超过一定阈值时,会自动 abort 来释放资源。规则包括

  1. 事务的生命周期不能超过 transactionLifetimeLimitSeconds (默认60s),该配置可在线修改
  2. 事务修改的文档数不能超过 1000 ,不可修改
  3. 事务修改产生的 oplog 不能超过 16mb,这个主要是 MongoDB 文档大小的限制, oplog 也是一个普通的文档,也必须遵守这个约束。

Read as of a timestamp 与 oldest timestamp

Read as of a timestamp 依赖 WiredTiger 在内存里维护多版本,每个版本跟一个时间戳关联,只要 MongoDB 层可能需要读的版本,引擎层就必须维护这个版本的资源,如果保留的版本太多,也会对 WT cache 产生很大的压力。

WiredTiger 提供设置 oldest timestamp 的功能,允许由 MongoDB 来设置该时间戳,含义是Read as of a timestamp 不会提供更小的时间戳来进行一致性读,也就是说,WiredTiger 无需维护 oldest timestamp 之前的所有历史版本。MongoDB 层需要频繁(及时)更新 oldest timestamp,避免让 WT cache 压力太大。

引擎层 Rollback 与 stable timestamp

在 3.x 版本里,MongoDB 复制集的回滚动作是在 Server 层面完成,但节点需要回滚时,会根据要回滚的 oplog 不断应用相反的操作,或从回滚源上读取最新的版本,整个回滚操作效率很低。

4.0 版本实现了存储引擎层的回滚机制,当复制集节点需要回滚时,直接调用 WiredTiger 接口,将数据回滚到某个稳定版本(实际上就是一个 Checkpoint),这个稳定版本则依赖于 stable timestamp。WiredTiger 会确保 stable timestamp 之后的数据不会写到 Checkpoint里,MongoDB 根据复制集的同步状态,当数据已经同步到大多数节点时(Majority commited),会更新 stable timestamp,因为这些数据已经提交到大多数节点了,一定不会发生 ROLLBACK,这个时间戳之前的数据就都可以写到 Checkpoint 里了。

MongoDB 需要确保频繁(及时)的更新 stable timestamp,否则影响 WT Checkpoint 行为,导致很多内存无法释放。

分布式事务

MongoDB 4.0 支持副本集多文档事务,并计划在 4.2 版本支持分片集群事务功能。下图是从 MongoDB 3.0 引入 WiredTiger 到 4.0 支持多文档事务的功能迭代图,可以发现一盘大棋即将上线,敬请期待。

未分类

基于飞天分布式系统和高性能存储,提供三节点副本集的高可用架构,容灾切换,故障迁移完全透明化。并提供专业的数据库在线扩容、备份回滚、性能优化等解决方案。
了解更多

Mongodb和mysql的区别

1. Mongodb简介及优缺点分析

Mongodb是非关系型数据库(nosql ),属于文档型数据库。文档是mongoDB中数据的基本单元,类似关系数据库的行,多个键值对有序地放置在一起便是文档,语法有点类似javascript面向对象的查询语言,它是一个面向集合的,模式自由的文档型数据库。

存储方式:虚拟内存+持久化。
查询语句:是独特的Mongodb的查询方式。
适合场景:事件的记录,内容管理或者博客平台等等。
架构特点:可以通过副本集,以及分片来实现高可用。
数据处理:数据是存储在硬盘上的,只不过需要经常读取的数据会被加载到内存中,将数据存储在物理内存中,从而达到高速读写。
成熟度与广泛度:新兴数据库,成熟度较低,Nosql数据库中最为接近关系型数据库,比较完善的DB之一,适用人群不断在增长。

优点:
快速!在适量级的内存的Mongodb的性能是非常迅速的,它将热数据存储在物理内存中,使得热数据的读写变得十分快。高扩展性,存储的数据格式是json格式!

未分类

缺点:
① mongodb不支持事务操作。
② mongodb占用空间过大。
③ 开发文档不是很完全,完善。

未分类

2. MySQL优缺点分析

优点:
在不同的引擎上有不同 的存储方式。
查询语句是使用传统的sql语句,拥有较为成熟的体系,成熟度很高。
开源数据库的份额在不断增加,mysql的份额页在持续增长。

缺点:
在海量数据处理的时候效率会显著变慢。

3. Mongodb和MySQL数据库的对比

  • 传统的关系数据库一般由数据库(database)、表(table)、记录(record)三个层次概念组成,MongoDB是由数据库(database)、集合(collection)、文档对象(document)三个层次组成。
  • MongoDB对于关系型数据库里的表,但是集合中没有列、行和关系概念,这体现了模式自由的特点。

未分类

未分类

4. MongoDB常用语句

# 连接Mongo数据库,并设置数据存储地址
mongod.exe --dbpath "d:softwareMongoDBServer3.0data"

#-----------------------#1# 数据库
# 查看所有的数据库
show dbs
# 删除当前使用的数据库
db.dropDatabase()
# 使用这个数据库(只有插入数据后完成创建数据库)
use dbt
# 查看当前使用的数据库
db
db.getName()
# 查看当前数据库状态
db.stats()
# 修复当前数据库
db.repairDatabase()
# 从一个数据库复制到另一个数据库
db.copyDatabase("mydb", "temp", "127.0.0.1");

#-----------------------#2# 集合
# 查看当前数据库下所有的集合
show collections
show tables
# 创建名称为coll集合
db.createCollection('coll')
db.createCollection("coll2", {capped:true, autoIndexId:true, size:6142800, max:10000})      # 可选参数
# 查看当前集合状态
db.coll.stats()
# 删除名称为coll集合
db.coll.drop()


#-----------------------#3# 集合数据
# 插入空数据并且直接创建名称为coll集合
db.coll.insert({})
# 插入一个或多个数据
db.coll.insert({name:'tom', age:22})
db.coll.insert([{name:'adam', age:10},{name:'john', age:23}])
# 添加数据(save方法可以修改相同id的数据)
db.coll.save({name:'allen'})
# 删除一个或所有的数据
db.coll.remove({name:'tom'})
db.coll.remove({})
# 删除符合条件的数据中的第一条
db.coll.remove({name:'tom'}, 1)
# 更改数据
db.coll.update({name:'tom', age:22}, {$set:{name:'tom', age:222}})
# 查看数据
db.coll.find()
# 查看一条数据
db.coll.findOne()
db.coll.find({}, {name:1, '_id':0})     # 1表示显示,0表示不显示(find默认显示_id)
# 格式化显示数据,使数据更加清晰明了
db.coll.find().pretty()
# 使用and,or查看数据
db.coll.find({name:'tom', age:22})      # 等同and使用
db.coll.find({$or:[{name:'tom'}, {age:21}]})        # or使用



# 操作符大于,小于,等于,不等于,大于不等于,小于不等于
db.coll.find({age: {$gt: 22}})      # 大于
db.coll.find({age: {$lt: 22}})      # 大于
db.coll.find({age: 22})      # 等于
db.coll.find({age: {$ne: 22}})      # 不等于
db.coll.find({age: {$gte: 22}})      # 大于等于
db.coll.find({age: {$lte: 22}})      # 小于等于

# 显示从skip之后limit个
db.coll.find().limit(2).skip(1)

#-----------------------# # 用户
# 3.x之后版本添加用户
use admin
db.createUser({user:'nu', pwd:'nu', roles:[{role:'readWrite',db:'admin'}]})
# 用户认证
db.auth("nu", "nu");
# 显示当前所有用户
show users;
db.system.users.find()
3.x版本删除用户
db.removeUser('nu')     # 不推荐使用,已经废弃
db.dropUser("nu");

# 当前db版本
db.version();

# 当前db的链接机器地址和端口
db.getMongo();

# 备份到备份目录
mongodump

# 从备份目录恢复备份语句。
mongorestore

MongoDb 判断字段长度的方法

查询某字段长度超过一定长度时的方法, MongoDB中可能不好处理,一般这样:

 db.test.find({
    $where:"this.F_DAQDATA.legnth>600"
});

但用$where查询时性能可能不太好,在网上搜索之后,发现使用正则可能会更好,同时判断字段是否存在:

 db.test.find({
    F_DAQDATA: {
        $type:2,        // 字段类型为2,表示有此字段,或者用: $exists: true
        $regex: /^.{600,}$/       // 长度大于600
    }     
});

Memcached的MemCachedClient设置过期时限

之前做的项目用到了Memcached,用来存储图片验证码.不过需要设置过期时限.代码参照下面.

testMemcache.java

public void testMemcache(){
MemcacheUtil.set("abc","abc", new Date(1*10*1000));
MemcacheUtil.set("bbb","bbb", new Date(System.currentTimeMillis()+8000));</code>

//大于2000为null
try {
Thread.sleep(1*8*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

System.out.println(MemcacheUtil.get("abc"));
System.out.println(MemcacheUtil.get("bbb"));
try {
Thread.sleep(1*1*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(MemcacheUtil.get("abc"));
System.out.println(MemcacheUtil.get("bbb"));
try {
Thread.sleep(1*1*1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(MemcacheUtil.get("abc"));
System.out.println(MemcacheUtil.get("bbb"));
}

MemcacheUtil.java

import java.util.Date;</code>

import com.danga.MemCached.MemCachedClient;

public class MemcacheUtil {
public static MemCachedClient getMemCachedClient() {
return SpringContextUtils.getBean("memcachedClient", MemCachedClient.class);
}

public static boolean set(String key, Object value) {
String newKey = ResourceUtil.getPropertyValue("jdbc.username") + key;
return getMemCachedClient().set(newKey, value);
}

public static boolean set(String key, Object value,Date date) {
String newKey = ResourceUtil.getPropertyValue("jdbc.username") + key;
return getMemCachedClient().set(newKey, value, date);
}

public static Object get(String key) {
String newKey = ResourceUtil.getPropertyValue("jdbc.username") + key;
return getMemCachedClient().get(newKey);
}

public static boolean keyExists(String key) {
String newKey = ResourceUtil.getPropertyValue("jdbc.username") + key;
return getMemCachedClient().keyExists(newKey);
}

public static void clearCache(String...keys) {
for (String key : keys) {
String newKey = ResourceUtil.getPropertyValue("jdbc.username") + key;
getMemCachedClient().delete(newKey);
}
}

public static boolean clearCacheAll() {
return getMemCachedClient().flushAll();
}

/**
* 删除缓存中的数据
* @param key
*/
public static boolean deleteCache(String key){
String newKey = ResourceUtil.getPropertyValue("jdbc.username") + key;
return getMemCachedClient().delete(newKey);
}
}

输出结果:

abc
null
abc
null
null
null

如何调试Systemctl,以memcached为例。

背景:有时候我们使用systemctl命令,如简单点的systemctl start memcached来讲,出现错时,会有一些提示,但是提示的内容是变量,其并没有将参数给编译后的值放入,于是出现如下所示,但是要失败了怎么排查这些参数呢?此文就讲这个问题,如果不是为了linux开机启动提速而并行启动外,这个systemctl其本质是想接管很多东西,但也带来了很多麻烦,难怪linus对此有一定的意见,系统要保持简单,好用。像开机慢可以少开机或不关机嘛,学学人家苹果升级在半夜,你慢就慢,谁管你,从策略上就规避了,把技术搞复杂还是简单是一门哲学,而创始人的价值就在于坚守设计艺术,而不光是技术。

● memcached.service - Memcached
   Loaded: loaded (/usr/lib/systemd/system/memcached.service; enabled; vendor preset: disabled)
   Active: inactive (dead) since Fri 2018-07-20 18:48:50 CST; 19h ago
  Process: 15266 ExecStart=/usr/local/memcached/bin/memcached -u $USER -p $PORT -m $CACHESIZE -c $MAXCONN $OPTIONS (code=exited, status=0/SUCCESS)
Main PID: 15266 (code=exited, status=0/SUCCESS)

失败:

systemctl start memcached
Job for memcached.service failed because the control process exited with error code. See "systemctl status memcached.service" and "journalctl -xe" for details.
[Unit]
Description=Memcached
Before=httpd.service
After=network.target

[Service]
Type=simple
ExecStartPre=/bin/bash -l -c 'echo "/usr/local/memcached/bin/memcached" -u $USER -p $PORT -m $CACHESIZE -c $MAXC
ONN $OPTIONS > /tmp/systemctl.debug'
EnvironmentFile=-/etc/sysconfig/memcached
ExecStart=/usr/local/memcached/bin/memcached -u $USER -p $PORT -m $CACHESIZE -c $MAXCONN $OPTIONS

[Install]
WantedBy=multi-user.target

/etc/sysconfig/memcached

PORT="11211"
USER="memcached"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS=" -vv >> /data/logs/memcached/11211/memcached.log 2>&1"
systemctl daemon-reload
systemctl start memcached
systemctl status memcached.service
  Active: failed (Result: exit-code) since Sat 2018-07-21 14:35:22 CST; 5s ago
  Process: 11417 ExecStart=/usr/local/memcached/bin/memcached -u $USER -p $PORT -m $CACHESIZE -c $MAXCONN $OPTIONS (code=exited, status=71)
  Process: 11399 ExecStartPre=/bin/bash -l -c echo "/usr/local/memcached/bin/memcached" -u $USER -p $PORT -m $CACHESIZE -c $MAXCONN $OPTIONS > /tmp/systemctl.debug (code=exited, status=0/SUCCESS)
Main PID: 11417 (code=exited, status=71)

如果有错,需要挑食的参数查看 /tmp/systemctl.debug:

cat /tmp/systemctl.debug
/usr/local/memcached/bin/memcached -u root -p 11211 -m 64 -c 1024 -vv >> /data/logs/memcached/11211/memcached.log 2>&1

Memcached群集

实验环境

未分类

实验过程

1、配置memcached主缓存节点和从缓存节点

两台缓存节点配置相同;

yum install gcc gcc-c++ make #安装环境包
tar xf memcached-1.5.6.tar.gz -C /opt/
tar xf libevent-2.1.8-stable.tar.gz -C /opt/
cd /opt/libevent-2.1.8-stable #安装lib插件
./configure --prefix=/usr/
make && make install #编译安装
cd ../memcached-1.5.6 #安装memcached
./configure --with-libevent=/usr
make && make install #编译安装
mkdir /opt/magent
tar xf magent-0.5.tar.gz -C /opt/magent
cd /opt/magent/
vi ketama.h
#ifndef SSIZE_MAX               #在内容开头添加
#define SSIZE_MAX 32767
#endif
vi Makefile
LIBS = -levent -lm
make #编译
cp magent /usr/bin/ #把生成的mgent程序让系统识别
scp magent [email protected]:/usr/bin/ #把产生的magent命令执行文件直接复制到从服务器,从服务器无需再次进行配置了。

2、配置keepalived

两台服务器都需配置,配置不同之处将会标出;

yum install keepalived -y
vi /etc/keepalived/keepalived.conf
router_id test01                #两台服务器不同,从为test02

vrrp_script magent {            
        script "/opt/shell/magent.sh"       #脚本目录,需要创建
        interval 2              #检测脚本时间间隔
}

vrrp_instance VI_1 {
    state MASTER                #从服务器为BACKUP
    interface ens33
    virtual_router_id 51        #两台服务器不同,从服务器为52
    priority 100                #从服务器小,从服务器为90
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }

    track_script {      #调用脚本
        magent
    }
    virtual_ipaddress {     #虚拟IP
        192.168.27.111
    }
}

3、设置magent管理脚本

两台服务器的magent管理脚本不同

1)、主服务器上设置magent管理脚本;

cd /opt
cd /opt
mkdir shell
cd shell
vi magent.sh
#!/bin/bash
K=`ps -ef | grep keepalived | grep -v grep | wc -l`
if [ $K -gt 0 ]; then
        magent -u root -n 51200 -l 192.168.27.111 -p 12000 -s 192.168.27.160:11211 -b 192.168.27.163:11211
else
pkill -9 magent
fi

#-n 51200 //定义用户最大连接数
#-l 192.168.175.188 //指定虚拟IP
#-p 12000  //指定端口号
#-s //指定主缓存服务器
#-b //指定从缓存服务器
chmod +x magent.sh #赋予执行权限
systemctl start keepalived.service #开启服务

2)、从服务器上设置magent管理脚本;

cd /opt
mkdir shell
cd shell
vi magent.sh
#!/bin/bash
K=`ip addr | grep 192.168.27.111 | grep -v grep | wc -l`
if [ $K -gt 0 ]; then
        magent -u root -n 51200 -l 192.168.27.11 -p 12000 -s 192.168.27.160:11211 -b 192.168.27.163:11211
else
pkill -9 magent
fi  
chmod +x magent.sh #赋予执行权限
systemctl start keepalived.service #开启服务

4、启动memcached服务

memcached -m 512k -u root -d -l 192.168.27.160 -p 11211 #启动主
memcached -m 512k -u root -d -l 192.168.27.163 -p 11211 #启动从

5、验证

1)、在客户机上登录并插入内容,在两台主从服务器上都可查看到内容;

未分类

未分类

未分类

2)、将主服务器关掉,在客服机上插入新的内容,从服务器上仍能查看到内容;

未分类

未分类