用Redis轻松实现秒杀系统

秒杀系统的架构设计

秒杀系统,是典型的短时大量突发访问类问题。对这类问题,有三种优化性能的思路:
写入内存而不是写入硬盘
异步处理而不是同步处理
分布式处理
用上这三招,不论秒杀时负载多大,都能轻松应对。更好的是,Redis能够满足上述三点。因此,用Redis就能轻松实现秒杀系统。
用我这个方案,无论是电商平台特价秒杀,12306火车票秒杀,都不是事:)

下面介绍一下为什么上述三种性能优化思路能够解决秒杀系统的性能问题:

  • 写入内存而不是写入硬盘
    传统硬盘的读写性能是相当差的。SSD硬盘比传统硬盘快100倍。而内存又比SSD硬盘快10倍以上。因此,写入内存而不是写入硬盘,就能使系统的能力提升上千倍。也就是说,原来你的秒杀系统可能需要1000台服务器支撑,现在1台服务器就可以扛住了。
    你可能会有这样的疑问:写入内存而不是持久化,那么如果此时计算机宕机了,那么写入的数据不就全部丢失了吗?如果你就这么倒霉碰到服务器宕机,那你就没秒到了,有什么大不了?
    最后,后面真正处理秒杀订单时,我们会把信息持久化到硬盘中。因此不会丢失关键数据。
    Redis是一个缓存系统,数据写入内存后就返回给客户端了,能够支持这个特性。
  • 异步处理而不是同步处理
    像秒杀这样短时大并发的系统,在性能负载上有一个明显的波峰和长期的波谷。为了应对相当短时间的大并发而准备大量服务器来应对,在经济上是相当不合算的。
    因此,对付秒杀类需求,就应该化同步为异步。用户请求写入内存后立刻返回。后台启动多个线程从内存池中异步读取数据,进行处理。如用户请求可能是1秒钟内进入的,系统实际处理完成可能花30分钟。那么一台服务器在异步情况下其处理能力大于同步情况下1800多倍!
    异步处理,通常用MQ(消息队列)来实现。Redis可以看作是一个高性能的MQ。因为它的数据读写都发生在内存中。
  • 分布式处理
    好吧。也许你的客户很多,秒杀系统即使用了上面两招,还是捉襟见肘。没关系,我们还有大招:分布式处理。如果一台服务器撑不住秒杀系统,那么就多用几台服务器。10台不行,就上100台。分布式处理,就是把海量用户的请求分散到多个服务器上。一般使用hash实现均匀分布。
    这类系统在大数据云计算时代的今天已经有很多了。无非是用Paxos算法和Hash Ring实现的。
    Redis Cluster正是这样一个分布式的产品。

使用Redis实现描述系统

Redis和Redis Cluster(分布式版本),是一个分布式缓存系统。其支持多种数据结构,也支持MQ。Redis在性能上做了大量优化。因此使用Redis或者Redis Cluster就可以轻松实现一个强大的秒杀系统。
基本上,你用Redis的这些命令就可以了。
RPUSH key value
插入秒杀请求

当插入的秒杀请求数达到上限时,停止所有后续插入。
后台启动多个工作线程,使用
LPOP key
读取秒杀成功者的用户id,进行后续处理。
或者使用LRANGE key start end命令读取秒杀成功者的用户id,进行后续处理。
每完成一条秒杀记录的处理,就执行INCR key_num。一旦所有库存处理完毕,就结束该商品的本次秒杀,关闭工作线程,也不再接收秒杀请求。

要是还撑不住,该怎么办

也许你会说,我们的客户很多。即使部署了Redis Cluster,仍然撑不住。那该怎么办呢?
记得某个伟人曾经说过:办法总比困难多!

下面,我们具体分析下,还有哪些情况会压垮我们架构在Redis(Cluster)上的秒杀系统。

脚本攻击

如现在有很多抢火车票的软件。它们会自动发起http请求。一个客户端一秒会发起很多次请求。如果有很多用户使用了这样的软件,就可能会直接把我们的交换机给压垮了。

这个问题其实属于网络问题的范畴,和我们的秒杀系统不在一个层面上。因此不应该由我们来解决。很多交换机都有防止一个源IP发起过多请求的功能。开源软件也有不少能实现这点。如linux上的TC可以控制。流行的Web服务器Nginx(它也可以看做是一个七层软交换机)也可以通过配置做到这一点。一个IP,一秒钟我就允许你访问我2次,其他软件包直接给你丢了,你还能压垮我吗?

交换机撑不住了

可能你们的客户并发访问量实在太大了,交换机都撑不住了。
这也有办法。我们可以用多个交换机为我们的秒杀系统服务。
原理就是DNS可以对一个域名返回多个IP,并且对不同的源IP,同一个域名返回不同的IP。如网通用户访问,就返回一个网通机房的IP;电信用户访问,就返回一个电信机房的IP。也就是用CDN了!
我们可以部署多台交换机为不同的用户服务。 用户通过这些交换机访问后面数据中心的Redis Cluster进行秒杀作业。

总结

有了Redis Cluster的帮助,做个支持海量用户的秒杀系统其实So Easy!
这里介绍的方案虽然是针对秒杀系统的,但其背后的原理对其他高并发系统一样有效。
最后,我们再重温一下高性能系统的优化原则:
写入内存而不是写入硬盘
异步处理而不是同步处理
分布式处理

选redis还是memcache

memcache和redis是互联网分层架构中,最常用的KV缓存。不少同学在选型的时候会纠结,到底是选择memcache还是redis。

画外音:不鼓励粗暴的实践,例如“memcache提供的功能是redis提供的功能的子集,不用想太多,选redis准没错”。

虽然redis比memcache更晚出来,且功能确实也更丰富,但对于一个技术人,了解“所以然”恐怕比“选择谁”更重要一些

什么时候倾向于选择redis?

业务需求决定技术选型,当业务有这样一些特点的时候,选择redis会更加适合。

复杂数据结构

value是哈希,列表,集合,有序集合这类复杂的数据结构时,会选择redis,因为mc无法满足这些需求。

最典型的场景,用户订单列表,用户消息,帖子评论列表等。

持久化

mc无法满足持久化的需求,只得选择redis。

但是,这里要提醒的是,真的使用对了redis的持久化功能么?

千万不要把redis当作数据库用:

(1)redis的定期快照不能保证数据不丢失

(2)redis的AOF会降低效率,并且不能支持太大的数据量

不要期望redis做固化存储会比mysql做得好,不同的工具做各自擅长的事情,把redis当作数据库用,这样的设计八成是错误的。

缓存场景,开启固化功能,有什么利弊?

如果只是缓存场景,数据存放在数据库,缓存在redis,此时如果开启固化功能:

优点是,redis挂了再重启,内存里能够快速恢复热数据,不会瞬时将压力压到数据库上,没有一个cache预热的过程。

缺点是,在redis挂了的过程中,如果数据库中有数据的修改,可能导致redis重启后,数据库与redis的数据不一致。

因此,只读场景,或者允许一些不一致的业务场景,可以尝试开启redis的固化功能。

天然高可用

redis天然支持集群功能,可以实现主动复制,读写分离。

redis官方也提供了sentinel集群管理工具,能够实现主从服务监控,故障自动转移,这一切,对于客户端都是透明的,无需程序改动,也无需人工介入。

而memcache,要想要实现高可用,需要进行二次开发,例如客户端的双读双写,或者服务端的集群同步。

但是,这里要提醒的是,大部分业务场景,缓存真的需要高可用么?

(1)缓存场景,很多时候,是允许cache miss

(2)缓存挂了,很多时候可以通过DB读取数据

所以,需要认真剖析业务场景,高可用,是否真的是对缓存的主要需求?

画外音:即时通讯业务中,用户的在线状态,就有高可用需求。

存储的内容比较大

memcache的value存储,最大为1M,如果存储的value很大,只能使用redis。

什么时候倾向于memcache?

纯KV,数据量非常大,并发量非常大的业务,使用memcache或许更适合。

这要从mc与redis的底层实现机制差异说起。

内存分配

memcache使用预分配内存池的方式管理内存,能够省去内存分配时间。

redis则是临时申请空间,可能导致碎片。

从这一点上,mc会更快一些。

虚拟内存使用

memcache把所有的数据存储在物理内存里。

redis有自己的VM机制,理论上能够存储比物理内存更多的数据,当数据超量时,会引发swap,把冷数据刷到磁盘上。

从这一点上,数据量大时,mc会更快一些。

网络模型

memcache使用非阻塞IO复用模型,redis也是使用非阻塞IO复用模型。

但由于redis还提供一些非KV存储之外的排序,聚合功能,在执行这些功能时,复杂的CPU计算,会阻塞整个IO调度。

从这一点上,由于redis提供的功能较多,mc会更快一些。

线程模型

memcache使用多线程,主线程监听,worker子线程接受请求,执行读写,这个过程中,可能存在锁冲突。

redis使用单线程,虽无锁冲突,但难以利用多核的特性提升整体吞吐量。

从这一点上,mc会快一些。

最后说两点

代码可读性,代码质量

看过mc和redis的代码,从可读性上说,redis是我见过代码最清爽的软件,甚至没有之一,或许简单是redis设计的初衷,编译redis甚至不需要configure,不需要依赖第三方库,一个make就搞定了。

而memcache,可能是考虑了太多的扩展性,多系统的兼容性,代码不清爽,看起来费劲。

例如网络IO的部分,redis源码1-2个文件就搞定了,mc使用了libevent,一个fd传过来传过去,又pipe又线程传递的,特别容易把人绕晕。

画外音:理论上,mc只支持kv,而redis支持了这么多功能,mc性能应该高非常多非常多,但实际并非如此,真的可能和代码质量有关。

水平扩展的支持

不管是mc和redis,服务端集群没有天然支持水平扩展,需要在客户端进行分片,这其实对调用方并不友好。如果能服务端集群能够支持水平扩展,会更完美一些。

Redis的7个应用场景

一:缓存——热数据

热点数据(经常会被查询,但是不经常被修改或者删除的数据),首选是使用redis缓存,毕竟强大到冒泡的QPS和极强的稳定性不是所有类似工具都有的,而且相比于memcached还提供了丰富的数据类型可以使用,另外,内存中的数据也提供了AOF和RDB等持久化机制可以选择,要冷、热的还是忽冷忽热的都可选。

结合具体应用需要注意一下:很多人用spring的AOP来构建redis缓存的自动生产和清除,过程可能如下:

上面这种操作,如果并发量很小的情况下基本没问题,但是高并发的情况请注意下面场景:

为了update先删掉了redis中的该数据,这时候另一个线程执行查询,发现redis中没有,瞬间执行了查询SQL,并且插入到redis中一条数据,回到刚才那个update语句,这个悲催的线程压根不知道刚才那个该死的select线程犯了一个弥天大错!于是这个redis中的错误数据就永远的存在了下去,直到下一个update或者delete。

二:计数器

诸如统计点击数等应用。由于单线程,可以避免并发问题,保证不会出错,而且100%毫秒级性能!爽。

命令:INCRBY

当然爽完了,别忘记持久化,毕竟是redis只是存了内存!


三:队列


四:位操作(大数据处理)

用于数据量上亿的场景下,例如几亿用户系统的签到,去重登录次数统计,某用户是否在线状态等等。

想想一下腾讯10亿用户,要几个毫秒内查询到某个用户是否在线,你能怎么做?千万别说给每个用户建立一个key,然后挨个记(你可以算一下需要的内存会很恐怖,而且这种类似的需求很多,腾讯光这个得多花多少钱。。)好吧。这里要用到位操作——使用setbit、getbit、bitcount命令。

原理是:

redis内构建一个足够长的数组,每个数组元素只能是0和1两个值,然后这个数组的下标index用来表示我们上面例子里面的用户id(必须是数字哈),那么很显然,这个几亿长的大数组就能通过下标和元素值(0和1)来构建一个记忆系统,上面我说的几个场景也就能够实现。用到的命令是:setbit、getbit、bitcount


五:分布式锁与单线程机制


六:最新列表

例如新闻列表页面最新的新闻列表,如果总数量很大的情况下,尽量不要使用select a from A limit 10这种low货,尝试redis的 LPUSH命令构建List,一个个顺序都塞进去就可以啦。不过万一内存清掉了咋办?也简单,查询不到存储key的话,用mysql查询并且初始化一个List到redis中就好了。


七:排行榜

谁得分高谁排名往上。命令:ZADD(有续集,sorted set)

最近在研究股票,发现量化交易是个非常好的办法,通过臆想出来规律,用程序对历史数据进行验证,来判断这个臆想出来的规律是否有效,这玩意真牛!

redis持久化存储之RDB VS AOF

RDB 优点:

1.RDB是一种表示某个即时点的 Redis 数据的紧凑文件。RDB 文件适合用于备份。例如,你可能想要每小时归档最近 24 小时的 RDB 文件,每天保存近 30 天的 RDB 快照。这允许你很容易的恢复不同版本的数据集以容灾。

2.RDB非常适合于灾难恢复,作为一个紧凑的单一文件,可以被传输到远程的数据中心,或者是 Amazon S3(可能得加密)。

3.RDB最大化了 Redis 的性能,因为 Redis 父进程持久化时唯一需要做的是启动(fork)一个子进程,由子进程完成所有剩余工作。父进程实例不需要执行像磁盘 IO 这样的操作。

4.RDB 在重启恢复大数据集的实例时比AOF要快。

RDB缺点:

1.当你需要在 Redis 停止工作(例如停电)时最小化数据丢失,RDB 可能不太好。你可以配置不同的保存点。然而,你通常每隔 5 分钟或更久创建一个 RDB 快照,所以一旦 Redis 因为任何原因没有正确关闭而停止工作,你就得做好最近几分钟数据丢失的准备了。

2.RDB 需要经常调用 fork()子进程来持久化到磁盘。如果数据集很大的话,fork()比较耗时,结果就是,当数据集非常大并且 CPU 性能不够强大的话,Redis 会停止服务客户端几毫秒甚至一秒。AOF 也需要 fork(),但是你可以调整多久频率重写日志而不会有损(trade-off)持久性(durability)。

AOF优点:

1.使用 AOF Redis 会更具有可持久性(durable):你可以有很多不同的 fsync 策略:没有 fsync,每秒 fsync,每次请求时 fsync。使用默认的每秒 fsync 策略,写性能也仍然很不错(fsync 是由后台线程完成的,主线程继续努力地执行写请求),即便你也就仅仅只损失一秒钟的写数据。

2.AOF日志是一个追加文件,所以不需要定位,在断电时也没有损坏问题。即使由于某种原因文件末尾是一个写到一半的命令(磁盘满或者其他原因),redis-check-aof 工具也可以很轻易的修复。

3.AOF文件里面包含一个接一个的操作,以易于理解和解析的格式存储。你也可以轻易的导出一个 AOF 文件。例如,即使你不小心错误地使用 FLUSHALL 命令清空一切,如果此时并没有执行重写,你仍然可以保存你的数据集,你只要停止服务器,删除最后一条命令,然后重启 Redis 就可以。

AOF缺点:

1.对同样的数据集,AOF 文件通常要大于等价的 RDB 文件。

2.AOF可能比RDB慢,这取决于准确的 fsync 策略。在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)

RDB和AOF如何取舍

通常来说,你应该同时使用这两种持久化方法,以提高数据安全程度。

如果你很关注你的数据,但是仍然可以接受灾难时有几分钟的数据丢失,你可以只单独使用 RDB。

有很多用户单独使用 AOF,但是我们并不鼓励这样,因为时常进行 RDB 快照非常方便于数据库备份,启动速度也较之快,还避免了 AOF 引擎的 bug。

redis持久化存储

redis支持两种方式的持久化,可以单独使用或者结合起来使用
第一种:RDB方式,redis默认的持久化方式,

第二种:AOF方式,需要手动修改配置。下面我们来看一下两种持久化方式以及持久化中所注意的一些问题。

持久化之RDB

rdb方式的持久化是通过快照完成的,当符合一定条件时redis会自动将内存中的所有数据执行快照操作并存储到硬盘上。默认存储在dump.rdb文件中(文件名在配置文件中dbfilename),默认打开可以到启动的目录去查看,注意: dump.rdb是在哪儿启动redis,就会在哪儿生成rdb文件。

  • dump.rdb
    [root@momo1 ~]# cd /usr/local/redis/
    [root@momo1 redis]# ll
    总用量 156
    -rw-rw-r--. 1 root root 34339 12月 18 23:19 00-RELEASENOTES
    -rw-rw-r--. 1 root root 53 12月 18 23:19 BUGS
    -rw-rw-r--. 1 root root 1805 12月 18 23:19 CONTRIBUTING
    -rw-rw-r--. 1 root root 1487 12月 18 23:19 COPYING
    drwxrwxr-x. 6 root root 4096 4月 27 19:40 deps
    -rw-r--r--. 1 root root 36 4月 29 01:20 dump.rdb ##dump.rdb
    -rw-rw-r--. 1 root root 11 12月 18 23:19 INSTALL
    -rw-rw-r--. 1 root root 151 12月 18 23:19 Makefile
    -rw-rw-r--. 1 root root 4223 12月 18 23:19 MANIFESTO
    -rw-rw-r--. 1 root root 5201 12月 18 23:19 README
    -rw-rw-r--. 1 root root 41561 4月 27 19:53 redis.conf
    -rwxrwxr-x. 1 root root 271 12月 18 23:19 runtest
    -rwxrwxr-x. 1 root root 280 12月 18 23:19 runtest-cluster
    -rwxrwxr-x. 1 root root 281 12月 18 23:19 runtest-sentinel
    -rw-rw-r--. 1 root root 7113 4月 29 00:26 sentinel.conf
    drwxrwxr-x. 2 root root 4096 4月 27 19:41 src
    drwxrwxr-x. 10 root root 4096 12月 18 23:19 tests
    drwxrwxr-x. 5 root root 4096 12月 18 23:19 utils
  1. redis进行快照的时机(在配置文件redis.conf中)
    1. save 900 1:表示900秒内至少一个键被更改则进行快照。
    2. save 300 10
    3. save 60 10000
  2. redis实现快照的过程
    1. redis使用fork函数复制一份当前进程的副本(子进程)
    2. 父进程继续接收并处理客户端发来的命令,而子进程开始将内存中的数据写入硬盘中的临时文件
    3. 当子进程写入完所有数据后会用该临时文件替换旧的RDB文件,至此,一次快照操作完成。
  3. 手动执行save或者bgsave命令让redis执行快照
    1. save是由主进程进行快照操作,会阻塞其它请求。
    2. bgsave是由redis执行fork函数复制出一个子进程来进行快照操作。
  4. 文件恢复:redis-check-dump
    [root@iZ94r8hgrjcZ /]# redis-check-dump dump.rdb 
    ==== Processed 5 valid opcodes (in 45 bytes) ===================================
    CRC64 checksum is OK
  • rdb的优缺点
    优点:由于存储的有数据快照文件,恢复数据很方便。
    缺点:会丢失最后一次快照以后更改的所有数据。
  • 注意
  1. redis在进行快照的过程中不会修改RDB文件,只有快照结束后才会将旧的文件替换成新的,也就是说任何时候RDB文件都是完整的
  2. 我们可以通过定时备份RDB文件来实现redis数据库的备份。
  3. RDB文件是经过压缩的二进制文件,占用的空间会小于内存中的数据,更加利于传输。
  • 示例代码:
    127.0.0.1:6379> set a 123
    OK
    127.0.0.1:6379> set b 456
    OK
    127.0.0.1:6379> keys *
    1) "b"
    2) "a"
    127.0.0.1:6379> shutdown
    not connected> exit
    ###注意此时有值,关掉退出然后我们切换一下目录后再启动,就会是一个新的rdb文件,无值
    
    [root@momo1 redis]# cd /home/up/
    [root@momo1 up]# redis-server /etc/redis.conf 
    [root@momo1 up]# redis-cli 
    127.0.0.1:6379> keys *
    (empty list or set)
    127.0.0.1:6379> shutdown
    not connected> exit
    [root@momo1 up]# ll
    总用量 1348
    -rw-r--r--. 1 root root 18 4月 29 04:04 dump.rdb

持久化之AOF

aof方式的持久化是通过日志文件的方式。默认情况下redis没有开启aof,可以通过参数appendonly参数开启。appendonly yes
aof文件的保存位置和rdb文件的位置相同,都是dir参数设置的,默认的文件名是appendonly.aof,可以通过appendfilename参数修改
appendfilename appendonly.aof

appendonly.aofappendonly.aof
aof同步的时机aof同步的时机
  1. redis写命令同步的时机
    1. appendfsync always 每次都会执行
    2. appendfsync everysec 默认 每秒执行一次同步操作(推荐,默认)
    3. appendfsync no不主动进行同步,由操作系统来做,30秒一次
  2. aof日志文件重写
    1. auto-aof-rewrite-percentage 100(当目前aof文件大小超过上一次重写时的aof文件大小的百分之多少时会再次进行重写,如果之前没有重写,则以启动时的aof文件大小为依据)
    2. auto-aof-rewrite-min-size 64mb
  3. 手动执行bgrewriteaof进行重写

    重写的过程只和内存中的数据有关,和之前的aof文件无关。即针对数据库中当前数据进行重新整理成redis语句

  4. 文件恢复:redis-check-aof
  • 示例代码:
    ## 在rdb是持久化下游三个keys
    [root@momo1 redis]# redis-cli
    127.0.0.1:6379> keys *
    1) "d"
    2) "a"
    3) "b"
    
    ## 
    [root@momo1 redis]# vim redis.conf #修改appendonly yes
    [root@momo1 redis]# redis-server ./redis.conf 
    [root@momo1 redis]# redis-cli
    127.0.0.1:6379> keys *
    (empty list or set)

动态切换redis持久方式

RDB 切换到 AOF支持Redis 2.2及以上

  • 问题: 在很多情况下,默认使用了rdb持久化方式,如果我们再开启aof持久化方式,就会发现一个问题 : 原先的redis数据丢失了!
  • 主要原因: redis的aof持久化方式是最安全的,如果开启aof之后,redis会优先选择aof的方式,而appendonly.aof文件此时还是空文件于是之前的数据就丢失了。
  • 解决办法: 使用config命令,首先动态修改配置,然后再修改配置文件!
    1. cinfig set appendonly yes
    2. config set save “”(可选,如果开启了aof可以选择关闭rdb)
  • 总结:
  1. 当redis启动时,如果rdb持久化和aof持久化都打开了,那么程序会优先使用aof方式来恢复数据集,因为aof方式所保存的数据通常是最完整的。如果aof文件丢失了,则启动之后数据库内容为空。
  2. 如果想把正在运行的redis数据库,从RDB切换到AOF,先使用动态切换方式,再修改配置文件,重启数据库。(不能自己修改配置文件,重启数据库,否则数据库中数据就为空了。)
  3. 如果我们失误操作,没有动态切换数据为空了,我们应该尽快手动kill掉redis进程,然后删掉aof备份文件改掉配置后重新动态切换。
  • 示例代码:动态切换
    127.0.0.1:6379> config set appendonly yes
    OK
    127.0.0.1:6379> mget a b d
    1) "123"
    2) "123"
    3) "999"
    
    ####然后我们来看一下appendonly.aof文件内容
    [root@momo1 redis]# vim appendonly.aof 
    *2
    $6
    SELECT
    $1
    0
    *3
    $3
    SET
    $1
    d ##d的值
    $3 ##长度为3
    999 ##值为999
    *3
    $3
    SET
    $1
    b
    $3
    123
    *3
    $3
    SET
    $1
    a
    $3
    123
  • 示例代码:如果我们失误操作,没有动态切换数据为空了
    [root@momo1 redis]# vim redis.conf #修改appendonly yes此时没有动态切换
    [root@momo1 redis]# redis-server ./redis.conf ##启动
    [root@momo1 redis]# redis-cli ##此时有数据
    127.0.0.1:6379> keys *
    (empty list or set)
    
    [root@momo1 redis]# ps -ef|grep redis ##重开一个端口kill进程
    root 6399 1 0 04:20 ? 00:00:00 redis-server *:6379 
    root 6402 5794 0 04:20 pts/2 00:00:00 redis-cli
    root 6413 6113 0 04:21 pts/3 00:00:00 grep redis
    [root@momo1 redis]# kill -9 6399 
    [root@momo1 redis]# rm -rf appendonly.aof ##删掉appendonly.aof
    [root@momo1 redis]# vim redis.conf ##再次改回no
    [root@momo1 redis]# redis-server ./redis.conf ##启动
    [root@momo1 redis]# redis-cli
    127.0.0.1:6379> keys * ##数据还在,万幸
    1) "d"
    2) "b"
    3) "a"

config命令

  • 1、使用config set可以动态设置参数信息,服务器重启之后就失效了。
    config set appendonly yes
    config set save "90 1 30 10 60 100"
  • 2、使用config get可以查看所有可以使用config set命令设置的参数
    127.0.0.1:6379> config get * ##这里我省略了很多
     1) "dbfilename" ##rdb存储文件
     2) "dump.rdb"
     9) "logfile" ##日志文件
     10) "" ##可重新配置
     11) "pidfile" ##pid文件
     12) "/var/run/redis.pid"
     13) "maxmemory"
     14) "0"
     15) "maxmemory-samples"
     16) "5"
     17) "timeout"
     18) "0"
     40) "3000"
     41) "lua-time-limit"
     42) "5000"
     43) "slowlog-log-slower-than" 
     44) "10000"
     45) "latency-monitor-threshold"
     46) "0"
     47) "slowlog-max-len"
     48) "128"
     49) "port" ##端口
     50) "6379"
     51) "tcp-backlog"
     52) "511"
     53) "databases" ##支持数据库
     54) "16"
     75) "cluster-node-timeout" ##集群的一些信息
     76) "15000"
     77) "cluster-migration-barrier"
     78) "1"
     79) "cluster-slave-validity-factor"
     80) "10"
     81) "repl-diskless-sync-delay"
     82) "5"
     83) "cluster-require-full-coverage"
     84) "yes"
     85) "no-appendfsync-on-rewrite"
     86) "no"
     87) "slave-serve-stale-data"
     88) "yes"
     89) "slave-read-only"
     90) "yes"
     91) "stop-writes-on-bgsave-error"
     92) "yes"
     93) "daemonize" ##是否开启daemonize
     94) "yes"
     95) "rdbcompression"
     96) "yes"
     97) "rdbchecksum"
     98) "yes"
     99) "activerehashing"
    100) "yes"
    101) "repl-disable-tcp-nodelay"
    102) "no"
    103) "repl-diskless-sync"
    104) "no"
    105) "aof-rewrite-incremental-fsync"
    106) "yes"
    107) "aof-load-truncated"
    108) "yes"
    109) "appendonly" ##
    110) "no"
    111) "dir" ##dir 目录,可修改
    112) "/root"
    117) "save" ##rdb持久化点
    118) "900 1 300 10 60 10000"
    119) "loglevel" ##日志级别
    120) "notice"
    124) "0"
    125) "slaveof"  ##主从
    129) "bind"  ##bind ip
    130) ""
  • 3、 使用config rewrite命令对启动 Redis 服务器时所指定的 redis.conf 文件进行改写(Redis 2.8 及以上版本才可以使用),主要是把使用
    config set动态指定的命令保存到配置文件中。
    config rewrite
  • 注意:

config rewrite命令对 redis.conf 文件的重写是原子性的, 并且是一致的:如果重写出错或重写期间服务器崩溃, 那么重写失败, 原有 redis.conf 文件不会被修改。 如果重写成功, 那么 redis.conf 文件为重写后的新文件

修改持久化、日志路径:

###########1、创建目录存储redis,博主在/data/创建redis目录来存放数据###
mkdir /data/redis 
kill -9 redis进程号或shutdown redis#关掉redis进程

###########2、修改日志文件、dump文件路径###########
logfile "/data/redis/redis.log"
dir /data/redis/

###########3、移动之前dump文件到新目录##########
mv /dump.rdb /data/redis/ #我这里之前dump文件在根目录下

###########4、启动redis、查看数据正常##########
[root@iZ94r8hgrjcZ usr]# service redis start ##启动redis
Starting Redis server...
[root@iZ94r8hgrjcZ redis]# pwd  
/data/redis
[root@iZ94r8hgrjcZ redis]# ll         ##以生成日志文件
total 8
-rw-r--r-- 1 root root   62 Apr 30 11:51 dump.rdb
-rw-r--r-- 1 root root 2206 Apr 30 13:04 redis.log
[root@iZ94r8hgrjcZ redis]# redis-cli  ##数据正常
127.0.0.1:6379> keys *
//......

////////##########如果还想测试aof#############
127.0.0.1:6379> config set appendonly yes ##1、动态修改持久化
OK

[root@iZ94r8hgrjcZ redis]# vim /etc/redis/6379.conf ##2、修改配置appendonly yes
[root@iZ94r8hgrjcZ redis]# ll   ##3、查看数据正常
total 12
-rw-r--r-- 1 root root  132 Apr 30 13:20 appendonly.aof
-rw-r--r-- 1 root root   62 Apr 30 11:51 dump.rdb
-rw-r--r-- 1 root root 2937 Apr 30 13:20 redis.log
参考:
http://redisbook.readthedocs.io/en/latest/internal/aof.html

https://lanjingling.github.io/2015/11/16/redis-chijiuhua/

MongoDB分片实验

下面来实战一下如何搭建高可用的mongodb集群:

首先确定各个组件的数量,mongos 3个, config server 3个,数据分3片 shard server 3个,每个shard 有一个副本一个仲裁也就是 3 * 2 = 6 个,总共需要部署15个实例。这些实例可以部署在独立机器也可以部署在一台机器,我们这里测试资源有限,只准备了 3台机器,在同一台机器只要端口不同就可以,看一下物理部署图:

5

实验环境:

Vmware 10 + 32位CentOS_6.5 + mongodb-3.2(32位)
3台虚拟机:192.168.192.132、192.168.192.133、192.168.192.134

1.创建如下目录:

/data/mongodbtest/mongos/log #mongos日志目录
/data/mongodbtest/config/data #config server数据目录
/data/mongodbtest/config/log #config server日志目录
/data/mongodbtest/shard1|2|3/data #分片1|2|3数据目录
/data/mongodbtest/shard1|2|3/log #分片1|2|3日志目录

2.启动配置服务器:

mongod --configsvr --dbpath /data/mongodbtest/config/data --port 21000 --logpath /data/mongodbtest/config/log/config.log --storageEngine=mmapv1 --fork

说明:测试机上加上这个参数【因为32位系统不支持默认的引擎】:--storageEngine=mmapv1

3. 启动mongos服务器:

mongos --configdb 192.168.192.132:21000,192.168.192.133:21000,192.168.192.134:21000 --port 20000 --logpath  /data/mongodbtest/mongos/log/mongos.log  --fork

4. 配置各分片的副本集:

mongod --shardsvr --replSet shard1 --port 22001 --dbpath /data/mongodbtest/shard1/data --storageEngine=mmapv1 --logpath /data/mongodbtest/shard1/log/shard1.log --fork --nojournal  --oplogSize 10
mongod --shardsvr --replSet shard2 --port 22002 --dbpath /data/mongodbtest/shard2/data --storageEngine=mmapv1 --logpath /data/mongodbtest/shard2/log/shard2.log --fork --nojournal  --oplogSize 10
mongod --shardsvr --replSet shard3 --port 22003 --dbpath /data/mongodbtest/shard3/data --storageEngine=mmapv1 --logpath /data/mongodbtest/shard3/log/shard3.log --fork --nojournal  --oplogSize 10
说明:测试机上加上这个参数【因为32位系统不支持默认的引擎】:--storageEngine=mmapv1

5. 登陆任意一台分片机配置副本集:[如:登陆192.168.192.132]

> mongo 127.0.0.1:22001

> use admin

> config = { _id:"shard1", members:[
{_id:0,host:"192.168.192.132:22001"},
{_id:1,host:"192.168.192.133:22001"},                      {_id:2,host:"192.168.192.134:22001",arbiterOnly:true}
]}

> rs.initiate(config);

> exit;

> mongo 127.0.0.1:22002

> use admin

> config = { _id:"shard2", members:[
{_id:0,host:"192.168.192.132:22002"},
{_id:1,host:"192.168.192.133:22002"},
{_id:2,host:"192.168.192.134:22002",arbiterOnly:true}
]}

> rs.initiate(config);

> exit;

> mongo 127.0.0.1:22003

> use admin

> config = { _id:"shard3", members:[
{_id:0,host:"192.168.192.132:22003"},{_id:1,host:"192.168.192.133:22003"},
{_id:2,host:"192.168.192.134:22003",arbiterOnly:true}
]}

> rs.initiate(config);

> exit;

6. 建立mongos与分片之间的联系:

mongo  127.0.0.1:20000

mongos > use admin

mongos> db.runCommand( { addshard : "shard1/192.168.192.132:22001,192.168.192.133:22001,192.168.192.134:22001"});

mongos> db.runCommand( { addshard : "shard2/192.168.192.132:22002,192.168.192.133:22002,192.168.192.134:22002"}); 

mongos > db.runCommand( { addshard : "shard3/192.168.192.132:22003,192.168.192.133:22003,192.168.192.134:22003"});   

mongos > db.runCommand( { listshards : 1 } );//查看分片列表

mongos > db.runCommand({enablesharding:"testdb"});//测试,设置要分片的库testdb

mongos > db.runCommand({shardcollection:"testdb.table1",key:{id:1}});//设置要分片的集合testdb.table1

7. 实践中遇到的问题:

mongos启动失败:

(1)报错:

Error initializing sharding system: ConfigServersInconsistent: hash from 192.168.192.132:21000: { chunks: "d41d8cd98f00b204e9800998ecf8427e", shards: "d41d8cd98f00b204e9800998ecf8427e", version: "34eb51f63387b033ac38ec27ab15aba4" } vs hash from 192.168.192.133:21000: {}

原因:

各配置服初始配置不一致,需要删除初始化配置文件重新启动,以保持多个配置服的一致性。

对策:

删除所有配置服务器data中的config.*,然后kill -9掉配置服启动进程重新启动,此时查看mongos.log发现报错消失,出现新的报错(2)

(2)报错:

Error initializing sharding system: DistributedClockSkewed: clock skew of the cluster 192.168.192.132:21000,192.168.192.133:21000,192.168.192.134:21000 is too far out of bounds to allow distributed locking.

原因:各配置服的linux时间不一致。

对策:使用ntpdate同步时间,使各配置服的linux时间保持一致:ntpdate  202.108.6.95

8.参考:

http://www.lanceyan.com/tech/arch/mongodb_shard1.html

MongoDB分片原理

在系统早期,数据量还小的时候不会引起太大的问题,但是随着数据量持续增多,后续迟早会出现一台机器硬件瓶颈问题的。而mongodb主打的就是海量数据架构,他不能解决海量数据怎么行!不行!“分片”就用这个来解决这个问题。

传统数据库怎么做海量数据读写?其实一句话概括:分而治之。上图看看就清楚了,如下 taobao岳旭强在infoq中提到的 架构图:

1

上图中有个TDDL,是taobao的一个数据访问层组件,他主要的作用是SQL解析、路由处理。根据应用的请求的功能解析当前访问的sql判断是在哪个业务数据库、哪个表访问查询并返回数据结果。具体如图:

2

说了这么多传统数据库的架构,那Nosql怎么去做到了这些呢?mysql要做到自动扩展需要加一个数据访问层用程序去扩展,数据库的增加、删除、备份还需要程序去控制。一但数据库的节点一多,要维护起来也是非常头疼的。不过mongodb所有的这一切通过他自己的内部机制就可以搞定!顿时石化了,这么牛X!还是上图看看mongodb通过哪些机制实现路由、分片:

3

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

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

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

shard,这就是传说中的分片了。上面提到一个机器就算能力再大也有天花板,就像军队打仗一样,一个人再厉害喝血瓶也拼不过对方的一个师。俗话说三个臭皮匠顶个诸葛亮,这个时候团队的力量就凸显出来了。在互联网也是这样,一台普通的机器做不了的多台机器来做,如下图:

4

一台机器的一个数据表 Collection1 存储了 1T 数据,压力太大了!在分给4个机器后,每个机器都是256G,则分摊了集中在一台机器的压力。也许有人问一台机器硬盘加大一点不就可以了,为什么要分给四台机器呢?不要光想到存储空间,实际运行的数据库还有硬盘的读写、网络的IO、CPU和内存的瓶颈。在mongodb集群只要设置好了分片规则,通过mongos操作数据库就能自动把对应的数据操作请求转发到对应的分片机器上。在生产环境中分片的片键可要好好设置,这个影响到了怎么把数据均匀分到多个分片机器上,不要出现其中一台机器分了1T,其他机器没有分到的情况,这样还不如不分片!

replica set,上两节已经详细讲过了这个东东,怎么这里又来凑热闹!其实上图4个分片如果没有 replica set 是个不完整架构,假设其中的一个分片挂掉那四分之一的数据就丢失了,所以在高可用性的分片架构还需要对于每一个分片构建 replica set 副本集保证分片的可靠性。生产环境通常是 2个副本 + 1个仲裁。

 

MongoDB副本集心跳和同步机制

MongoDB建议副本集成员为奇数。最多12个副本节点最多7个节点参与选举。限制副本节点的数量,主要是因为一个集群中过多的副本节点,增加了复制的成本,反而拖累了集群的整体性能。太多的副本节点参与选举,也会增加选举的时间。MongoDB官方推荐节点数量为奇数。主要在于副本集常常为分布式,可能位于不同的IDC。如果为偶数,可能出现每个IDC的节点数一样,从而如果网络故障,那么每个IDC里的节点都无法选出主节点,导致全部不可用的情况。比如,节点数为4,分处于2个IDC,现在IDC之间的网络出现故障,每个IDC里的节点都没有大于2,所以副本集没有主节点,变成只读。

MongoDB节点会向副本集中的其他节点每两秒就会发送一次pings包,如果其他节点在10秒钟之内没有返回就标示为不能访问。每个节点内部都会维护一个状态映射表,表明当前每个节点是什么角色、日志时间戳等关键信息。如果是主节点,除了维护映射表外还需要检查自己能否和集群中内大部分节点通讯,如果不能则把自己降级为secondary只读节点。

副本集同步分为初始化同步和keep复制。初始化同步指全量从主节点同步数据,如果主节点数据量比较大同步时间会比较长。而keep复制指初始化同步过后,节点之间的实时同步一般是增量同步。初始化同步不只是在第一次才会被触发,有以下两种情况会触发:

secondary第一次加入,这个是肯定的。

secondary落后的数据量超过了oplog的大小,这样也会被全量复制。

那什么是oplog的大小?前面说过oplog保存了数据的操作记录,secondary复制oplog并把里面的操作在secondary执行一遍。但是oplog也是mongodb的一个集合,保存在local.oplog.rs里,但是这个oplog是一个capped collection也就是固定大小的集合,新数据加入超过集合的大小会覆盖。所以这里需要注意,跨IDC的复制要设置合适的oplogSize,避免在生产环境经常产生全量复制。oplogSize 可以通过–oplogSize设置大小,对于linux 和windows 64位,oplog size默认为剩余磁盘空间的5%。

同步也并非只能从主节点同步,假设集群中3个节点,节点1是主节点在IDC1,节点2、节点3在IDC2,初始化节点2、节点3会从节点1同步数据。后面节点2、节点3会使用就近原则从当前IDC的副本集中进行复制,只要有一个节点从IDC1的节点1复制数据。

设置同步还要注意以下几点:

secondary不会从delayed和hidden成员上复制数据。

只要是需要同步,两个成员的buildindexes必须要相同无论是否是true和false。buildindexes主要用来设置是否这个节点的数据用于查询,默认为true。

如果同步操作30秒都没有反应,则会重新选择一个节点进行同步。

附:副本集不可用的应对之道:

当节点发生故障,或者因为网络原因失联,造成余下的节点小于等于副本集总节点数的一半,那么整个副本集中都不再存在主节点,原来的主节点会降级成为从节点!此时整个副本集呈现只读状态,也即不再可用。
当出现这种瘫痪情况,目前我还没找到将某个从节点切换为主节点的方法,也许本来就不存在这种方法。因为这和MongoDB的Primary选举策略有关:如果情况不是Secondary宕机,而是网络断开,那么可能出现位于不同IDC的节点各自选取自己为Primary。这样在网络恢复后就需要处理复杂的一致性问题。而且断开的时间越长,时间越复杂。所以MongoDB选择的策略是如果集群中存活节点数量不够,则不选取Primary。
所以
1) 一方面要极力避免出现这种存活节点不够半数的情况,在规划副本集的时候就注意:
设置仲裁节点。
节点总数为奇数,且主节点所在的IDC,拥有超过半数的节点数量
2) 注意对节点的备份,必要时可以对节点进行恢复
也可以按照相同配置建立一个全新的从节点,恢复副本集后,系统会自动同步数据。但猜测数据量比较大的情况下,耗时会比较长,所以平时对从节点进行备份,还是有必要。

MongoDB副本集

MongoDB的副本集不同于以往的主从模式。
在集群Master故障的时候,副本集可以自动投票,选举出新的Master,并引导其余的Slave服务器连接新的Master,
而这个过程对于应用是透明的。可以说MongoDB的副本集是自带故障转移功能的主从复制。

1.相对于传统主从模式的优势


传统的主从模式,需要手工指定集群中的Master。
如果Master发生故障,一般都是人工介入,指定新的Master。
这个过程对于应用一般不是透明的,往往伴随着应用重新修改配置文件,重启应用服务器等。
11
而MongoDB副本集,集群中的任何节点都可能成为Master节点。
一旦Master节点故障,则会在其余节点中选举出一个新的Master节点。
并引导剩余节点连接到新的Master节点。这个过程对于应用是透明的。
12

2. Bully选举算法


Bully算法是一种协调者(主节点)竞选算法,主要思想是集群的每个成员都可以声明它是主节点并通知其他节点。
别的节点可以选择接受这个声称或是拒绝并进入主节点竞争。被其他所有节点接受的节点才能成为主节点。
节点按照一些属性来判断谁应该胜出。这个属性可以是一个静态ID,也可以是更新的度量像最近一次事务ID(最新的节点会胜出)


他的选举过程大致如下:

1.得到每个服务器节点的最后操作时间戳。每个mongodb都有oplog机制会记录本机的操作,方便和主服务器进行对比数据是否同步还可以用于错误恢复。

2.如果集群中大部分服务器down机了,保留活着的节点都为 secondary状态并停止,不选举了。

3.如果集群中选举出来的主节点或者所有从节点最后一次同步时间看起来很旧了,停止选举等待人来操作。

4.如果上面都没有问题就选择最后操作时间戳最新(保证数据是最新的)的服务器节点作为主节点。
选举的触发条件
1.初始化一个副本集时。
2.副本集和主节点断开连接,可能是网络问题。
3.主节点挂掉。
4.人为介入,比如修改节点优先级等
5.选举还有个前提条件,参与选举的节点数量必须大于副本集总节点数量的一半,如果已经小于一半了所有节点保持只读状态。


3 .搭建副本集集群


每个虚拟机都使用如下的配置文件启动实例:
mongod --dbpath=/data/db --logpath=/var/log/mongodb.log --port 27017 --replSet mvbox --maxConns=2000 --logappend --fork

然后在任意一台虚拟机登陆mongo,输入如下设置
config = { _id:"mvbox", members:[
{_id:0,host:"192.168.1.1:27017"},
{_id:1,host:"192.168.1.2:27017"},
{_id:2,host:"192.168.1.3:27017"}]
}
rs.initiate(config);
可以看到副本集已经生效


2 可以使用rs.status()查看集群状态,或者rs.isMaster() 3

4. 更改节点优先级

修改节点的优先级可以触发重新选举,这样可以人工指定主节点。
使用如下命令,在主节点登录,将192.168.1.3提升为Master。
rs.conf();
cfg=rs.conf();
cfg.members[0].priority=1
cfg.members[1].priority=1
cfg.members[2].priority=10
rs.reconfig(cfg);

需要注意的是,修改节点优先级需要登录Master节点运行。否则报错。
4

再次查看集群状态,可以看到192.168.1.3已经作为Master运行
5

5 .节点类型


MongoDB的节点类型有主节点(Master),副本节点(Slave或者称为Secondary),仲裁节点,Secondary-Only节点,Hidden节点,Delayed节点和Non-Voting节点。

仲裁节点不存储数据,只是负责故障转移的群体投票,这样就少了数据复制的压力。

Secondary-Only:不能成为primary节点,只能作为secondary副本节点,防止一些性能不高的节点成为主节点。

Hidden:这类节点是不能够被客户端制定IP引用,也不能被设置为主节点,但是可以投票,一般用于备份数据。

Delayed:可以指定一个时间延迟从primary节点同步数据。主要用于备份数据,如果实时同步,误删除数据马上同步到从节点。所以延迟复制主要用于避免用户错误。

Non-Voting:没有选举权的secondary节点,纯粹的备份数据节点。

6 .设置隐藏节点(Hidden)


隐藏节点可以在选举中投票,但是不能被客户端引用,也不能成为主节点。也就是说这个节点不能用于读写分离的场景。
将192.168.1.3设置为隐藏节点。
注意,只有优先级为0的成员才能设置为隐藏节点。
如果设置优先级不为0的节点为隐藏节点,则报错如下
13

使用如下命令设置隐藏节点
cfg=rs.conf();
cfg.members[0].priority=10
cfg.members[1].priority=1
cfg.members[2].priority=0
cfg.members[2].hidden=1
rs.reconfig(cfg);
设置完成之后,使用rs.status()查看该节点还是SECONDARY状态。
但是通过rs.isMaster()和rs.conf()可以看到这个节点的变化。
rs.isMaster()的hosts中192.168.1.3节点已经不可见
14
并且rs.conf()显示该节点状态为hidden

15

7 .设置仲裁节点


仲裁节点不存储数据,只是用于投票。所以仲裁节点对于服务器负载很低。
节点一旦以仲裁者的身份加入集群,他就只能是仲裁者,无法将仲裁者配置为非仲裁者,反之也是一样。
另外一个集群最多只能使用一个仲裁者,额外的仲裁者拖累选举新Master节点的速度,同时也不能提供更好的数据安全性。
初始化集群时,设置仲裁者的配置如下
config = { _id:"mvbox", members:[
{_id:0,host:"192.168.1.1:27017"},
{_id:1,host:"192.168.1.2:27017",arbiterOnly:true},
{_id:2,host:"192.168.1.3:27017"}]
}
使用仲裁者主要是因为MongoDB副本集需要奇数成员,而又没有足够服务器的情况。在服务器充足的情况下,不应该使用仲裁者节点。

8 .设置延迟复制节点


MongoDB官方没有增量备份方案,只有一个导出的工具mongodump。
他不能像数据库一样,通过binlog或者归档日志将数据推到事故发生的前一刻。
假设每天凌晨2点使用mongodump备份,而下午5点发生事故,数据库损毁,则凌晨2点到下午5点的数据全部都会丢失。
虽然副本集可以一定程度避免这个问题,但是默认情况下不能避免人为的失误。
比如没有指定筛选条件删除了全部的数据。副本节点会应用这个命令,删除所有副本节点的数据。
在这个场景下,可以使用延迟节点,它会延迟应用复制。
如果主节点发生了人为的失误,而这个操作因为延迟的原因,还没有应用在延迟节点。
这个时候,修改延迟节点的优先级为最高级,使他成为新的Master服务器。

延迟节点的优先级必须为0.这个和hidden节点是一样的。
设置192.168.1.2为延迟节点
cfg=rs.conf();
cfg.members[1].priority=0
cfg.members[1].slaveDelay=3600
rs.reconfig(cfg);
slaveDelay的单位是秒
在192.168.1.1主节点删除一个集合所有数据,模拟人为失误。
16

在192.168.1.3查看,发现数据已经全部丢失。
17
而在192.168.1.2延迟节点,可以看到因为延迟复制的缘故,数据还在。
18
这个时候千万不要提升延迟节点的优先级。因为这样他会立即应用原主节点的所有操作,并成为新的主节点。这样误操作就同步到了延迟节点。
首先,关闭副本集中其他的成员,除了延迟节点。
删除其他成员数据目录中的所有数据。确保每个其他成员的数据目录都是空的(除了延迟节点)
重启其他成员,他们会自动从延迟节点中恢复数据。
9 .设置Secondary-Only节点

Priority为0的节点永远不能成为主节点,所以设置Secondary-only节点只需要将其priority设置为0.

10 .设置Non-Voting节点


假设设置192.168.1.1不能投票,则使用如下命令
cfg=rs.conf();
cfg.members[0].votes=0;
rs.reconfig(cfg);
11 .副本集成员状态

副本集成员状态指的是rs.status()的stateStr字段
19
STARTUP:刚加入到复制集中,配置还未加载

STARTUP2:配置已加载完,初始化状态

RECOVERING:正在恢复,不适用读

ARBITER: 仲裁者

DOWN:节点不可到达

UNKNOWN:未获取其他节点状态而不知是什么状态,一般发生在只有两个成员的架构,脑裂

REMOVED:移除复制集

ROLLBACK:数据回滚,在回滚结束时,转移到RECOVERING或SECONDARY状态

FATAL:出错。查看日志grep “replSet FATAL”找出错原因,重新做同步

PRIMARY:主节点

SECONDARY:备份节点

12. 读写分离

如果Master节点读写压力过大,可以考虑读写分离的方案。
20

不过需要考虑一种场景,就是主服务器的写入压力非常的大,所以副本节点复制的写入压力同样很大。
这时副本节点如果读取压力也很大的话,根据MongoDB库级别读写锁的机制,
很可能复制写入程序拿不到写锁,从而导致副本节点与主节点有较大延迟。

如果进行读写分离,首先需要在副本节点声明其为slave,

21
其中的ReadRreference有几种设置,

primary:默认参数,只从主节点上进行读取操作;

primaryPreferred:大部分从主节点上读取数据,只有主节点不可用时从secondary节点读取数据。

secondary:只从secondary节点上进行读取操作,存在的问题是secondary节点的数据会比primary节点数据“旧”。

secondaryPreferred:优先从secondary节点进行读取操作,secondary节点不可用时从主节点读取数据;

nearest:不管是主节点、secondary节点,从网络延迟最低的节点上读取数据。
参考:http://www.lanceyan.com/tech/mongodb/mongodb_repset1.html

MongoDB副本集实验

一、实验环境:

Vmware 10 + 32位CentOS_6.5 + mongodb-3.2(32位)
1

二、下载并安装mongodb:

1.下载:
mongodb-3.2:百度云盘下载

也可以根据自己的环境,到官网上下载

2.安装:
分别创建:/usr/local/mongodb /data/db目录及文件/var/log/mongodb.log
解压mongodb-linux-i686-3.2.11.tgz,将其文件拷贝到/usr/local/mongodb/

3.加入环境变量:
vi /etc/profile
export PATH=/usr/local/mongodb/bin:$PATH
执行:source /etc/profile使其立即生效

三、搭建副本集:

介绍一下mongod启动涉及到的参数

--dbpath           数据文件路径

--logpath           日志文件路径

--port        端口号,默认是27017.我这里使用的也是这个端口号.

--replSet          复制集的名字,一个replica sets中的每个节点的这个参                       数都要用一个复制集名字,这里是mvbox.

--maxConns        最大连接数

--fork       后台运行

--logappend       日志文件循环使用,如果日志文件已满,那么新日志覆盖最久日志。
1.分别在1,2,3机器上启动mongod:

 mongod --dbpath=/data/db --logpath=/var/log/mongodb.log --port 27017 --replSet mvbox --maxConns=2000 --logappend --fork
说明:启动前如果已有mongod.lock先删除:rm -rf /data/db/mongod.lock

2.在1,2,3任意节点上登陆mongo:
> mongo
> config = { _id:"mvbox", members:[
{_id:0,host:"192.168.1.1:27017"},
{_id:1,host:"192.168.1.2:27017"},
{_id:2,host:"192.168.1.3:27017"}]
}
rs.initiate(config);

2


可以使用rs.status()查看集群状态,或者rs.isMaster()
3

四、同步验证:

1.在主节点的机器上,插入一条数据:

> use test;
> db.my.insert({'name':zrp,'age':28});

2.在副本集的节点上,先开启同步:

> rs.slaveOk(true);

3.查看副本集节点是否已同步数据:

> use test;
> db.my.find();

ok,数据同步成功,实验宣告成功!

五、变更节点:

1.变更192.168.1.3为仲裁节点: 

(1)在主节点上执行:
PRIMARY> rs.remove("192.168.1.3:27017");//先删除其副节点的角色
PRIMARY> rs.addArb("192.168.1.3:27017");//再追加其仲裁节点的角色
PRIMARY> rs.status();//查看状态
(2)登陆192.168.1.3的mongo查看其角色是否变化

2.更改节点优先级:

修改节点的优先级可以触发重新选举,这样可以人工指定主节点。
使用如下命令,在主节点登录,将192.168.1.3提升为Master。
rs.conf();
cfg=rs.conf();
cfg.members[0].priority=1
cfg.members[1].priority=1
cfg.members[2].priority=10
rs.reconfig(cfg);
需要注意的是,修改节点优先级需要登录Master节点运行。否则报错。
4

再次查看集群状态,可以看到192.168.1.3已经作为Master运行
5