Redis cluster学习 & Redis常识 & sort操作
Redis中的5种数据类型String、Hash、List、Set、Sorted Set。
Redis源码总代码一万多行。
这篇文章有一些Redis “常识” http://www.searchdatabase.com.cn/showcontent_70423.htm
key可以是任意类型,最后都存成byte[];作者建议用 : 分隔表名,用.作为单词间的连接。(据我所知,redis只有库没有表)
针对KEY的操作:
命令 sort(按某个key从小到大排序,desc则是从大到小):
参考 http://www.cnblogs.com/linjiqin/archive/2013/06/14/3135921.html
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修饰符限制返回结果
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 可能读到了旧实例数据,马上重新推送一遍配置,问题解决。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!