Redis cluster学习 & Redis常识 & sort操做

Redis中的5种数据类型String、Hash、List、Set、Sorted Set。html

Redis源码总代码一万多行。java

这篇文章有一些Redis “常识” http://www.searchdatabase.com.cn/showcontent_70423.htmnode

key能够是任意类型,最后都存成byte[];做者建议用 : 分隔表名,用.做为单词间的链接。(据我所知,redis只有库没有表)mysql

 

针对KEY的操做:redis

命令 sort(按某个key从小到大排序,desc则是从大到小):算法

参考 http://www.cnblogs.com/linjiqin/archive/2013/06/14/3135921.htmlsql

10.117.146.16:8379> lpush price 30 1.5 10 8
(integer) 4
10.117.146.16:8379> sort price
1) "1.5"
2) "8"
3) "10"
4) "30"
10.117.146.16:8379> sort price desc
1) "30"
2) "10"
3) "8"
4) "1.5"

1.还能够使用alpha修饰符对字符串进行排序数据库

2.使用limit修饰符限制返回结果json

3.使用外部key进行排序安全

4.get有一个额外的参数规则,那就是能够用#获取被排序键的值。

5.经过将一个不存在的键做为参数传给 by 选项, 可让 sort 跳过排序操做,直接返回结果。

6.这种用法在单独使用时,没什么实际用处。不过,经过将这种用法和get选项配合,就能够在不排序的状况下,获取多个外部键,至关于执行一个整合的获取操做(相似于 sql数据库的join关键字)。

保存排序结果

10.117.146.16:8379> sort price store ordered_price
(integer) 4
10.117.146.16:8379> lrange ordered_price 0 -1
1) "1.5"
2) "8"
3) "10"
4) "30"

返回值:
没有使用 store 参数,返回列表形式的排序结果。
使用 store 参数,返回排序结果的元素数量。

其余命令还有:KEYS显示全部的key,支持通配符 "KEYS a*" , "keys a?c",但不建议在生产环境大数据量下使用。

SORT,对集合按数字或字母顺序排序后返回,或者存到另外一个List,还能够关联到外部Key等。由于会耗用CPU,有时会安排到slave上执行。

EXPIRE/EXPREAT/PERSIST/TTL/,关于Key超时的操做,默认以秒为单位,也有p字头的以毫秒为单位的版本。 
其余命令: EXISTS,DEL,RENAME/RENAMENX(仅当new key不存在时),MOVE/MIGRATE(实例内今后db到彼db/今后实例到彼实例),
RANDOMKEY,TYPE/Object(Key的类型/对象编码类型,空置时间),DUMP/RESTORE(value值的持久化)

 

2.2 String   

最普通的key-value,除了支持最基本的get/set, Redis也很喜欢添加一些简便的指令,在服务端作起来是举手之劳,客户端便方便不少。
incr/decr/incrby/incrbyfloat, 若是key还不存在时建立key并设原值为0。

setEx/pSetEx, Set + Expire 的简便写法,p字头以毫秒为单位。

setNx, key不存在时才put进去。

getset, 设置新值,返回旧值。

mget/mset/msetex, 一次get/set多个key。

getbit/setbit/bitop/bitcount bitmap玩法,好比统计今天的访问用户,每一个用户有一个offset,今天进来的话就把那个位为1。 
append/setrange/getrange,只对特定的数据格式好比字段定长的有用,json格式就没用。

 

2.3 Hash

2.4 List   

Redis里能够当双向链表来用,还提供blocking版本的pop函数,能够当Message Queue来用。

不过List并无JMS的ack机制,若是消费者把job给Pop走了又没处理完就死机了怎么办? 
解决方法之一是加多一个sorted set,以分发时间为score,用户把job作完了以后要去消掉它。    除了List标准的双向POP/PUSH外,还支持对队列内容的直接操做,好比LREM/LSET/LINSERT/LINDEX。    另外常常用LTRIM限制List的大小,好比只保留最新的20条消息。LRANGE不一样于POP直接弹走元素,只是返回列表内一段下标的元素。LLEN获取列表的长度。

 


2.5 Set   

Set就是Set,还提供一些交集,并集,差集的集合操做。   

 

2.6 Sorted Set   

有序集,元素放入集合的时候要同时提供该元素的分数。
ZRANGE/ZREVRANGE 按排名的上下限返回元素,正数与倒数。

ZRANGEBYSCORE/ZREVRANGEBYSCORE 按分数的上下限返回元素,正数与倒数。

ZREMRANGEBYRANK/ZREMRANGEBYSCORE 按排名/按分数删除元素。

ZCOUNT 统计分数上下限之间的元素个数。

ZRANK/ZREVRANK 显示某个元素的正倒序的排名。

ZSCORE/ZINCRBY 显示元素的分数/增长元素的分数。

ZADD/ZREM/ZCARD/ZINTERSTORE/ZUNIONSTORE 集合操做与SET相同,少了个差集的操做。

 

2.7 事务   

用Multi/Exec/Discard实现, 隔离级别是这边事务一天不提交,那边另外一个事务仍是看到旧的值。

还有个Watch指令,起到CAS的效果,若是事务提交时,Key的值已被别的事务改变,事务会被打断。


2.8 Lua Script   

Redis2.6内置的Lua Script支持,能够在Redis的Server端一次过运行大量逻辑。
整个Script默认是在一个事务里的。 Script里涉及的全部Key尽可能用变量,从外面传入,使Redis一开始就知道你要改变哪些key。

EVAL每次传输一整段Script比较费带宽,能够先用SCRIPT LOAD载入script,返回哈希值。而后用EVALHASH执行。

内置的LUA库里还很贴心的带了CJSON,能够处理JSON字符串。

 


3. 性能

速度太快了,用光了带宽也测不出极限。

若是是本地socket直连,incr能够去到很吓人的几十万TPS。

普通的get/set操做,通过了LAN,延时也只有1毫秒左右,能够放心使用,不用像调用REST接口和访问数据库那样,多一次外部访问都心痛。

自带的redis-benchmark默认只是基于一个很小的数据集进行测试,
但可调整命令行参数如 redis-benchmark -r 10000000 -n 10000000 -d 128 -t SET,GET 就能够默认开50条线程,
SET 6M条左右(random)key是21字节长,value是128字节长的数据进去, 再GET出来。 若是要一次发送多条指令,PipeLining模式能让性能更快,由于它在设计上正视了网络往返的时间。 更快的是Lua Script模式,还能够包含逻辑,直接在服务端又get又set的 (见2.
8 Lua Script)。 单线程单CPU架构,但做者认为CPU不是瓶颈,内存与网络带宽才是。

 

发现执行缓慢的命令,可配置执行超过多少时间的指令算是缓慢指令(默认10毫秒,不含IO时间),能够用slowlog get 指令查看(默认只保留最后的128条)。单线程的模型下,某个请求占掉10毫秒是件大事情。


4. 容量   

最大内存: 必定要设置最大内存,不然物理内存用爆了就会大量使用Swap,写RDB文件时的速度慢得你想死。

多留一倍内存是最安全的。重写AOF文件和RDB文件的进程(即便不作持久化,复制到Slave的时候也要写RDB)会fork出一条新进程来,
采用了操做系统的Copy
-On-Write策略(若是父进程的内存没被修改,子进程与父进程共享Page。若是父进程的Page被修改, 会复制一份改动前的内容给新进程), 留意Console打出来的报告,如"RDB: 1215 MB of memory used by copy-on-write"。在系统极度繁忙时,
若是父进程的全部Page在子进程写RDB过程当中都被修改过了,就须要两倍内存。 按照Redis启动时的提醒,设置 vm.overcommit_memory
= 1 ,使得fork()一条10G的进程时,由于COW策略而不必定须要有10G的free memory. 当最大内存到达时,按照配置的Policy进行处理, 默认policy为volatile-lru, 对设置了expire time的key进行LRU清除(不是按实际expire time)。
若是沒有数据设置了expire time或者policy为noeviction,则直接报错,但此时系统仍支持get之类的读操做。
另外还有几种policy,好比volatile-ttl按最接近expire time的,allkeys-lru对全部key都作LRU。 原来2.0版的VM(将Value放到磁盘,Key仍然放在内存),2.4版后又不支持了。

 


内存占用:

测试代表,stirng类型须要90字节的额外代价,就是说key1个字节,value一个字节时,仍是须要占用92字节的长度,
而上述的benchmark的记录就占用了239个字节。 用make 32bit能够编译出32位的版本,每一个指针占用的内存更小,但只支持最大4GB内存。

 


Sharding:

Jedis支持在客户端作分区,局限是不能动态re-sharding, 有分区的master倒了,必须用slave顶上。要增长分区的话,呃.....

Redis-Cluster是今年工做重点,支持automatic re-sharding, 采用和Hazelcast相似的算法,总共有N个分区,每台Server负责若干个分区。
客户端先hash出key 属于哪一个分区,而后发给负责这个分区的Server。Re-sharding时,会将某些分区的数据移到新的Server上,
而后各Server周知分区<->Server映射的变化,由于分区数量有限,因此通信量不大。 在迁移过程当中,原server对于已经迁移走的数据的get请求,
会回一个临时转向的应答。

 

5. 高可用性   

5.1 持久化

RDB文件: 整个内存的压缩过的Snapshot,RDB的数据结构, 能够配置写Snapshot的复合触发条件,默认是60秒内改了1万次或300秒内改了10次或900秒内改了1次。

RDB在写入过程当中,会连内存一块儿Fork出一个新进程,遍历新进程内存中的数据写RDB。 先写到临时文件再Rename,这样外部程序对RDB文件的备份和传输过程是安全的。并且即便写新快照的过程当中Server被强制关掉了,旧的RDB文件还在。

可配置是否进行压缩,方法是是字符串的LZF算法 和String形式的数字变回int形式存储。

 

 

AOF文件:

append only的操做日志,等于mysql的binlog,记录全部有效的写操做,格式是明文的Redis协议的纯文本文件。

通常配置成每秒调用一次fsync将数据写到磁盘,坏处是操做系统非正常关机时,可能会丢1秒的数据。 若是设为fsync always,性能只剩几百TPS,不用考虑。 若是使用了AOF,重启时只会从AOF文件载入数据,不会管RDB文件。

AOF文件过大时,会fork出一条新进程来将文件重写(也是先写临时文件再rename), 遍历新进程的内存中数据,每条记录有一条的Set语句。默认配置是当AOF文件大小是上次rewrite后的大小的一倍时触发

Redis协议的内容,如set mykey hello, 将持久化成*3 $3 set $5 mykey $5 hello, 第一个数字表明这条语句有多少元,其余的数字表明后面字符串的长度。这样的设计,使得即便在写文件过程当中忽然关机致使文件不完整,也能自我修复,执行redis-check-aof便可。    

RDB不会实时写入数据,并且若是同时使用二者,但服务器重启只会找AOF文件。那要不要只使用AOF呢?做者建议不要,由于RDB更适合用于备份数据库,快速重启,并且不会有AOF可能潜在的bug,留着做为一个万一的手段。


读写性能:

AOF重写和RDB写入都是在fork出进程后,遍历新进程内存顺序写的,既不影响主进程,顺序写的速度也比随机写快,在普通PC服务器上把刚才的1.5G数据写成一个200M的RDB文件大约8秒, 启动时载入一个1.4G的AOF文件大约13秒。

2.4版之后,lpush能够一次push多个值了,使得AOF重写时能够将旧版本中的多个lpush指令合成一个。 有人建议设置no-appendfsync-on-rewrite 为 yes,aof rewrite时就不执行fsync了,先都存在内存里,减小IO资源争用。 固然这样会丢数据。 Fork一个使用了大量内存的进程也要时间,大约10ms per GB的样子,各类系统的对比。

 

其余:

正确关闭服务器:redis-cli shutdown 或者 kill,都会graceful shutdown,保证写RDB文件以及将AOF文件fsync到磁盘,不会丢失数据。

若是是Ctrl+C,或者kill -9 就会丢失数据。

执行指令bgsave 可触发rdb存盘,bgrewriteaof可触发aof重写。

 


5.2 Master-Slave复制

能够在配置文件、命令行参数、以及执行SLAVEOF指令的来设置。 当前版本,一旦执行SlaveOF, slave会清掉本身的全部数据,执行一次全同步:Master要bgsave出本身的一个RDB文件,发给Slave。而后再增量同步: Master做为一个普通的client连入slave,将全部写操做转发给slave,没有特殊的同步协议。

做者在2.8版本中将支持PSYNC部分同步 测试代表同步延时很是小。

有建议只在Slave上写RDB和AOF文件,但这样master启动时就须要从slave copy文件,fail-over脚本也更复杂。只要有足够内存,master平时IO也不高的话,仍是简化架构更好。

 

5.3 Fail-Over   

5.3.1 概述   

Redis-sentinel是2.6版开始加入的另外一组独立运行的节点, 提供自动Fail Over的支持。


每秒钟对全部master,slave和其余sentinel执行ping,redis-server节点要应答+PONG或-LOADING或-MASTERDOWN.

若是某一台Sentinel没有在30秒内(可配置得短一些哦)收到上述正确应答,它就会认为master处于sdown状态(主观Down) 
它向其余sentinel询问是否也认为master倒了(SENTINEL
is-master-down-by-addr ), 若是quonum台(默认是2)sentinels在5秒钟内都这样认为,
就会认为master真是odown了(客观Down)。 此时会选出一台sentinel做为Leader执行fail
-over, Leader会从slave中选出一个提高为master(执行slaveof none),这台slave必须状态正常,
并且INFO显示它与master的复制链接并无断开过久。而后让其余slave指向它(执行slaveof new master)。

 


5.3.2 master/slave 及 其余sentinels的发现   

master地址在sentinel的配置文件里, sentinel会每10秒一次向master发送INFO,知道master的slave有哪些。

若是master已经变为slave,sentiel会分析INFO的应答指向新的master。

 

 

因此当sentiel重启时,它的配置文件里master的地址并没变,那它仍然会去找old master,而后被redirect到新的master。

但若是old master还没起来,或者old master没把本身变成slave,悲剧就可能发生。   另外,sentinel会在master上建一个pub/sub channel,通告各类信息,好比+sdown/-sdown, 并且sentinel们也是经过接收pub/sub channel上的+sentinel的信息发现彼此,由于每台sentinel每5秒会发送一次__sentinel__:hello,宣告本身的存在。

自定义脚本和Client

5.3.3 自定义脚本 
sentinel在failover时还会执行配置文件里指定的用户reconfig脚本,让master变为slave并指向新的master。
脚本在以下时机被调用:
1. slave开始提高成master,
2.全部slave都已指向新master,
3.各类缘由提高被终止。 脚本的将会在命令行按顺序传入以下参数: 脚本返回0是正常,若是返回1会被从新执行,若是返回2或以上不会。
若是超过60秒没返回会被强制终止。   

另外一种notify脚本在收到任何pub/sub信息时都会调用,让你去通知O&M系统。   

5.3.4 Client集成   
client中执行语句SENTINEL get-master-addr-by-name mymaster 可得到当前master的地址。
可是Jedis还没集成sentinel,只有一个热心网友提交了pull request   

淘宝的Tedis driver,使用了彻底不一样的思路,不基于Sentinel,而是多写随机读,学术名词是ReadOne-WriteAll-tx(see NoSQL数据库的分布式算法),
一开始就同步写入到全部节点,读的话随便读一个还活着的节点就好了。(但节点死掉从新起来后怎么从新同步?何时能够从新做为一个可选的master?)  
Redis做者也在博客里抱怨怎么没有人作Dynamo-style 的client。

监控技巧见: http://blog.nosqlfan.com/html/4166.html

SlowLog 检查慢操做(见2.性能)

配置sentinel的notify.sh脚本对全部事件告警或本身用PING/INFO监控节点状态(见5.3.3)

MONITOR能够显示Server收到的全部指令,能够用于debug。 Redis live, 基于Python的DashBoard,使用INFO和MONITOR得到系统状况和指令统计分析。 
Instagram的Redis Faina,基于Python,使用MONITOR对指令进行统计分析. Redis
-rdb-tools 基于Python,能够分析RDB文件,
好比每条Key对应value所占的大小,还能够将RDB dump成文本文件而后进行比较,还能够将RDB输出成JSON格式。
Redis做者本身写的Redis Sampler,基于Ruby,统计数据分布状况。

 


维护

用redis-check-aof/redis-check-dump 修复烂掉的文件。

在系统闲时调用 bgrewriteaof 重写AOF文件。

其余   

过时数据清除 ,过时数据的清除历来不容易,Redis使用了一种相对务实的作法 当client主动get的时候会先进行判断。

若是clien永远都再也不get那条key呢? 它会在后台,每秒10次的执行以下操做: 随机选取100个key校验是否过时,若是有25个以上的key过时了,
马上随机选取下100个key,可见若是数据不被主动get,它何时最终被清理掉是未知的。 上述主动清理只会在master进行,slave们会收到从master发过来的DEL指令,master的AOF文件也会插入一条DEL。

 

Java Driver   

各个Driver好像只有Jedis比较活跃。   

不须要Spring Data Redis的封装,由于Jedis已足够简单,因此它没有像对MongoDB java driver的封装那样能简化代码,
所谓屏蔽各类底层driver的差别并不太吸引人,由于我就没打算选其余几种driver。    Jedis基于Apache Commons Pool作的链接池,默认最大链接数只有8,通常须要从新设置。

 

 

下面这一篇讲Redis Cluster的原理还比较清晰 http://www.cnblogs.com/foxmailed/p/3630875.html

  Redis Cluster 是Redis的集群实现,内置数据自动分片机制,集群内部将全部的key映射到16384个Slot中,
集群中的每一个Redis Instance负责其中的一部分的Slot的读写。
集群客户端链接集群中任一Redis Instance便可发送命令,当Redis Instance收到本身不负责的Slot的请求时,
会将负责请求Key所在Slot的Redis Instance地址返回给客户端,客户端收到后自动将原请求从新发往这个地址,对外部透明。
一个Key到底属于哪一个Slot由crc16(key) % 16384 决定。 关于负载均衡,集群的Redis Instance之间能够迁移数据,以Slot为单位,但不是自动的,须要外部命令触发。 关于集群成员管理,集群的节点(Redis Instance)和节点之间两两按期交换集群内节点信息而且更新,从发送节点的角度看,这些信息包括:
集群内有哪些节点,IP和PORT是什么,节点名字是什么,节点的状态(好比OK,PFAIL,FAIL,后面详述)是什么,包括节点角色(master 或者 slave)等。 关于可用性,集群由N组主从Redis Instance组成。主能够没有从,可是没有从
意味着主宕机后主负责的Slot读写服务不可用。一个主能够有多个从,主宕机时,某个从会被提高为主,具体哪一个从被提高为主,协议相似于Raft,参见这里
如何检测主宕机?Redis Cluster采用quorum
+心跳的机制。从节点的角度看,节点会按期给其余全部的节点发送Ping,
cluster-node-timeout(可配置,秒级)时间内没有收到对方的回复,则单方面认为对端节点宕机,将该节点标为PFAIL状态。
经过节点之间交换信息收集到quorum个节点都认为这个节点为PFAIL,则将该节点标记为FAIL,
而且将其发送给其余全部节点,其余全部节点收到后当即认为该节点宕机。
从这里能够看出,主宕机后,至少cluster-node-timeout时间内该主所负责的Slot的读写服务不可用。

 

这里有一些使用Redis遇到的坑和经验 http://www.360doc.com/content/16/0425/23/16915_553797555.shtml

疑似 Cluster 脑裂?(这个名称好可怕。。)
脑裂在所谓的分布式系统中很常见,你们也不陌生,作为DBA最怕的就是Mysql keepalived 脑裂,形成主库双写。难道 Redis Cluster中也会有脑裂么?
凌晨5点接到电话,发现应用看到数据不一致,偶尔是无数据,偶尔有数据,很像读到了脏数据。
登上 Redis, Cluster Nodes, Cluster Config,确实发现不一样 Redis 实例配置了不一样的Cluster Nodes。
想起了昨天有对该集群迁移,下掉了几个实例,可是在 PHP 配置端没有推送配置,致使 PHP 可能读到了旧实例数据,立刻从新推送一遍配置,问题解决。

相关文章
相关标签/搜索