Redis3 集群数据迁移

Redis3 集群 数据迁移 一、概述 要让集群正常工作至少需要3个主节点,在这里我们要创建6个redis节点,其中三个为主节点,三个为从节点,对应的redis节点的ip和端口对应关系如下 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 二、准备环境 安装ruby 集群环境需要ruby,所以需要安装下 yum install ruby yum install ruby-rdoc ruby setup.rb 安装完毕后启动依然报错 需要安装redis库 gem install redis 问题解决 下载最新redis版本 3.06 并进行构建 创建用户 useradd redis passwd redis 切换用户 su – redis 上传redis包到home目录 tar -zxvf redis-3.0.6.tar.gz mv redis-3.0.6 redis3.0 cd redis3.0 make 本环境为伪集群环境,所以在一台机器,多机情况相同 mkdir /home/redis/ cluster-test cd /home/redis/ cluster-test mkdir 7000 mkdir 7001 mkdir 7002 mkdir 7003 mkdir 7004 mkdir 7005 三、修改配置 ...

Created 6066-08-06 · Updated 9096-10-09 · 5 min · 874 words · -

Redis Replication, sentinel

“Redis Replication, sentinel” Redis 主从 Replication 的配置 beanlam 发布于 2015-04-20 本专栏与Redis相关的文章 Redis Sentinel机制与用法 (一) Redis Sentinel机制与用法 (二) Jedis的JedisSentinelPool源代码分析 Jedis的Sharded源代码分析 Redis 主从 Replication 的配置 详解Redis SORT命令 JedisCommand接口说明 本文参考翻译自《Redis Replication documentation》 概述 Redis的replication机制允许slave从master那里通过网络传输拷贝到完整的数据备份。具有以下特点: 异步复制。从2.8版本开始,slave能不时地从master那里获取到数据。 允许单个master配置多个slave slave允许其它slave连接到自己。一个slave除了可以连接master外,它还可以连接其它的slave。形成一个图状的架构。 master在进行replication时是非阻塞的,这意味着在replication期间,master依然能够处理客户端的请求。 slave在replication期间也是非阻塞的,也可以接受来自客户端的请求,但是它用的是之前的旧数据。可以通过配置来决定slave是否在进行replication时用旧数据响应客户端的请求,如果配置为否,那么slave将会返回一个错误消息给客户端。不过当新的数据接收完全后,必须将新数据与旧数据替换,即删除旧数据,在替换数据的这个时间窗口内,slave将会拒绝客户端的请求和连接。 一般使用replication来可以实现扩展性,例如说,可以将多个slave配置为“只读”,或者是纯粹的数据冗余备份。 能够通过replication来避免master每次持久化时都将整个数据集持久化到硬盘中。只需把master配置为不进行save操作(把配置文件中save相关的配置项注释掉即可),然后连接上一个slave,这个slave则被配置为不时地进行save操作的。不过需要注意的是,在这个用例中,必须确保master不会自动启动。更多详情请继续往下读。 Master持久化功能关闭时Replication的安全性 当有需要使用到replication机制时,一般都会强烈建议把master的持久化开关打开。即使为了避免持久化带来的延迟影响,不把持久化开关打开,那么也应该把master配置为不会自动启动的。 为了更好地理解当一个不进行持久化的master如果允许自动启动所带来的危险性。可以看看下面这种失败情形: 假设我们有一个redis节点A,设置为master,并且关闭持久化功能,另外两个节点B和C是它的slave,并从A复制数据。 如果A节点崩溃了导致所有的数据都丢失了,它会有重启系统来重启进程。但是由于持久化功能被关闭了,所以即使它重启了,它的数据集是空的。 而B和C依然会通过replication机制从A复制数据,所以B和C会从A那里复制到一份空的数据集,并用这份空的数据集将自己本身的非空的数据集替换掉。于是就相当于丢失了所有的数据。 即使使用一些HA工具,比如说sentinel来监控master-slaves集群,也会发生上述的情形,因为master可能崩溃后迅速恢复。速度太快而导致sentinel无法察觉到一个failure的发生。 当数据的安全很重要、持久化开关被关闭并且有replication发生的时候,那么应该禁止实例的自启动。 replication工作原理 如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个SYNC命令给master请求复制数据。 master收到SYNC命令后,会在后台进行数据持久化,持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求缓存在内存中。当持久化进行完毕以后,master会把这份数据集发送给slave,slave会把接收到的数据进行持久化,然后再加载到内存中。然后,master再将之前缓存在内存中的命令发送给slave。 当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行一次持久化,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。 当master和slave断开重连后,一般都会对整份数据进行复制。但从redis2.8版本开始,支持部分复制。 数据部分复制 从2.8版本开始,slave与master能够在网络连接断开重连后只进行部分数据复制。 master会在其内存中创建一个复制流的等待队列,master和它所有的slave都维护了复制的数据下标和master的进程id,因此,当网络连接断开后,slave会请求master继续进行未完成的复制,从所记录的数据下标开始。如果进程id变化了,或者数据下标不可用,那么将会进行一次全部数据的复制。 支持部分数据复制的命令是PSYNC 不需硬盘参与的Replication 一般情况下,一次复制需要将内存的数据写到硬盘中,再将数据从硬盘读进内存,再发送给slave。 对于速度比较慢的硬盘,这个操作会给master带来性能上的损失。Redis2.8版本开始,实验性地加上了无硬盘复制的功能。这个功能能将数据从内存中直接发送到slave,而不用经过硬盘的存储。 不过这个功能目前处于实验阶段,还未正式发布。 相关配置 与replication相关的配置比较简单,只需要把下面一行加到slave的配置文件中: slaveof 192.168.1.1 6379 你只需要把ip地址和端口号改一下。当然,你也可以通过客户端发送SLAVEOF命令给slave。 部分数据复制有一些可调的配置参数,请参考redis.conf文件。 无硬盘复制功能可以通过repl-diskless-sync来配置,另外一个配置项repl-diskless-sync-delay用来配置当收到第一个请求时,等待多个slave一起来请求之间的间隔时间。 只读的slave 从redis2.6版本开始,slave支持只读模式,而且是默认的。可以通过配置项slave-read-only来进行配置,并且支持客户端使用CONFIG SET命令来动态修改配置。 ...

Created 18186-06-18 · Updated 9096-10-09 · 1 min · 133 words · -

redis ziplist

“redis list” linkedlist 每个node包含了三个部分,指向前一个节点和后一个节点的指针,以及一个数据值。而一个list包含了指向首尾的指针、整个list的长度,以及三个函数指针,用来复制节点的值、释放节点的值,以及比较节点内容。 ziplist 压缩列表 ziplist是一种特殊编码的节省内存空间的双链表,能以O(1)的时间复杂度在两端push和pop数据 ziplist是redis中实现的一个地址连续的链表。 每次操作都需要重新分配内存 先看下官方对ziplist的整体描述 /* The ziplist is a specially encoded dually linked list that is designed to be very memory efficient. It stores both strings and integer values, where integers are encoded as actual integers instead of a series of characters. It allows push and pop operations on either side of the list * in O(1) time. However, because every operation requires a reallocation of the memory used by the ziplist, the actual complexity is related to the ...

Created 4046-07-04 · Updated 9096-10-09 · 3 min · 428 words · -

redis scan

“redis scan” redis用scan代替keys 众所周知,当redis中key数量越大,keys 命令执行越慢,而且最重要的会阻塞服务器,对单线程的redis来说,简直是灾难,且在生产环境,keys命令一般是被禁止的。scan可用来替换keys请求。 所以说官方的建议是: 生产环境屏蔽掉 keys 命令。 在对键进行增量式迭代的过程中, 键可能会被修改, 所以增量式迭代命令只能对被返回的元素提供有限的保证 。 scan用法 SCAN cursor [MATCH pattern] [COUNT count] scan 0 match *news* count 3 scan是一个增量迭代式的命令,这意味着每次调用这个命令都会返回一个游标cursor,该游标用于下次查询。查询开始时,cursor值为0;当查询结束时,cursor的值也回归到0。 举个例子: 开始查询,scan cursor为0,返回的cursor为17 redis 127.0.0.1:6379> scan 0 “17” “key:12” “key:8” “key:4” “key:14” “key:16” “key:17” “key:15” “key:10” “key:3” “key:7” “key:1” 下一次查询,以上一次查询返回的cursor为起始位置 redis 127.0.0.1:6379> scan 17 查询返回cursor为0,标志查询结束 “0” “key:5” “key:18” “key:0” “key:2” “key:19” “key:13” “key:6” “key:9” “key:11” count count可理解为迭代过程中的步长,指每次调用scan时应执行的工作量,该值默认为10。每次调用count的值可以随意指定,只要下一次传递cursor是上一次调用返回的cursor就行。 match 需要注意的是,match操作时在元素被检出后执行的。假设redis中只有少量元素符合pattern条件,那么很可能在多次调用中scan返回的数据为空,例如: 查找key中包含11的键,因为这里没有指定count,所以默认为10 redis 127.0.0.1:6379> scan 0 MATCH 11 ...

Created 21216-07-21 · Updated 9096-10-09 · 2 min · 333 words · -

Redis 主从模式 VS 哨兵 sentinel 模式 VS Redis cluster 模式

“主从模式 VS 哨兵sentinel模式 VS Redis cluster模式” 主从模式 (redis2.8版本之前的模式) 、 哨兵 sentinel模式 (redis2.8及之后的模式) 、 redis cluster模式 (redis3.0版本之后) 主从模式原理 同MySQL主从复制的原因一样,Redis虽然读取写入的速度都特别快,但是也会产生读压力特别大的情况。为了分担读压力,Redis支持主从复制,Redis的主从结构可以采用一主多从或者级联结构,Redis主从复制可以根据是否是全量分为全量同步和增量同步。下图为级联结构。 优点: 解决数据备份问题 做到读写分离,提高服务器性能 缺点: 每个客户端连接redis实例的时候都是指定了ip和端口号的,如果所连接的redis实例因为故障下线了,而主从模式也没有提供一定的手段通知客户端另外可连接的客户端地址,因而需要手动更改客户端配置重新连接 主从模式下,如果主节点由于故障下线了,那么从节点因为没有主节点而同步中断,因而需要人工进行故障转移工作 无法实现动态扩容 sentinel 哨兵模式 Sentinel (哨兵) 是 Redis 的高可用性解决方案: 由一个或多个 Sentinel实例组成的Sentinel系统可以监视任意多个主服务器, 以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。 优点 Master 状态监测 如果 Master 异常,则会进行 Master-slave 转换,将其中一个 Slave 作为 Master,将之前的 Master 作为 Slave Master-Slave 切换后,master_redis.conf、slave_redis.conf 和 sentinel.conf 的内容都会发生改变,即 master_redis.conf 中会多一行 slaveof 的配置,sentinel.conf 的监控目标会随之调换 缺点: 如果是从节点下线了,sentinel 是不会对其进行故障转移的,连接从节点的客户端也无法获取到新地可用从节点 无法实现动态扩容 cluster 模式 在这个图中,每一个蓝色的圈都代表着一个redis的服务器节点。它们任何两个节点之间都是相互连通的。客户端可以与任何一个节点相连接,然后就可以访问集群中的任何一个节点。对其进行存取和其他操作。 一般集群建议搭建三主三从架构,三主提供服务,三从提供备份功能。每一个节点都存有这个集群所有主节点以及从节点的信息。 它们之间通过互相的ping-pong判断是否节点可以连接上。如果有一半以上的节点去ping一个节点的时候没有回应,集群就认为这个节点宕机了,然后去连接它的备用节点。如果某个节点和所有从节点全部挂掉,我们集群就进入faill状态。还有就是如果有一半以上的主节点宕机,那么我们集群同样进入发力了状态。这就是我们的redis的投票机制,具体原理如下图所示: (1)投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超时(cluster-node-timeout),认为当前master节点挂掉. (2):什么时候整个集群不可用(cluster_state:fail)? ...

Created 9096-10-09 · Updated 9096-10-09 · 4 min · 693 words · -

缓存一致性问题

概述 应用读多写少时,常在 PostgreSQL 与 Redis 之间加一层缓存:热数据放 Redis,持久化仍由 PostgreSQL 负责。难点不在「能不能缓存」,而在写路径与读路径如何协调——数据库已更新,缓存仍是旧值;或缓存已失效,并发读把脏数据又写回缓存。 下文以 Redis 作缓存、PostgreSQL 作权威数据源,梳理几种主流缓存模式、各自的一致性特征与适用场景。示例以伪代码和常见实践为主,便于对照落地。 模式总览 模式 谁加载缓存 谁写缓存 一致性 复杂度 典型场景 Cache-Aside 应用 应用 最终一致,需自行处理竞态 低 通用业务,最常用 Read-Through 缓存层 缓存层 同 Cache-Aside 中 统一缓存 SDK Write-Through 应用 缓存层同步写 DB 较强一致 中 读多、可接受写延迟 Write-Back 应用 缓存层异步刷盘 弱一致,有丢数据风险 高 写密集、可容忍短暂不一致 Binlog 同步 中间件 CDC 消费 binlog 更新缓存 最终一致,解耦业务 高 多服务共享缓存、不想侵入业务 分布式事务 应用 + 协调者 2PC / TCC / Saga 强一致或业务级一致 很高 资金、库存等强一致域 graph LR App[应用] Redis[(Redis)] PG[(PostgreSQL)] App -->|Cache-Aside / Read-Through / Write-Through| Redis App -->|直接写或经缓存层| PG Redis -->|miss 时回源| PG PG -->|Binlog / WAL| CDC[CDC 消费者] CDC -->|更新/失效| Redis Cache-Aside(旁路缓存) 也叫 Lazy Loading。应用自己管缓存:读先查 Redis,miss 再查 PostgreSQL 并回填;写先更新 PostgreSQL,再删除(推荐)或更新 Redis。 ...

Created 19196-06-19 · Updated 19196-06-19 · 6 min · 1080 words · -

Conflict Check(2024)

此文用于记录在 AI 时代到来之前我手搓的最后一个复杂模块. 这个 conflict check 模块的重写是在 2024 年,历时约 3 个月,当时我还没有开始大规模使用 AI 辅助编程。重写的时候注意力都在功能需求上,并没有想「要用哪种设计模式」,只是在纠结怎么把问题拆清楚、用面向对象的方式把责任分开、把可能变化的部分封装起来。Redis 里的缓存数据是嵌套的树形结构,解析时写了几个递归调用——当时认为是最复杂的几个函数。 这个文档写于此后。某次和一个朋友聊天,对方问起对设计模式有没有了解,当时长期没有专门研究过,没说出个所以然。后来那个问题偶尔还会浮上来,然后有一天和 conflict check 关联到了起来, 然后用 Copilot + Claude Sonnet 4.6 分析了一下 — 有些设计模式接近标准实现,有的因业务结构或 Python 语言特性而有所偏差,还有两个(Builder 和 State)目前只是符合模式意图、需要重构才能达到标准结构。整理的过程中还发现了有性能优化空间的地方。 背景 防火墙策略在应用到设备之前,需要先在缓存中进行冲突检测。“网络编排器” 将每台设备的数据缓存在 Redis 中,每份 profile 包含三类数据:地址对象和分组、服务对象和分组,以及策略列表。 每条策略的关键字段: src:源地址数组,每个元素是一个 address object name 或 address group name dst:目标地址数组,结构与 src 相同 service:服务数组,每个元素是一个 service object name 或 service group name 地址对象和服务对象共用同一种数据结构,各自包含两种角色: object:基础对象,value 是实际 IP 地址/CIDR 网段(address)或协议/端口(service)的数组 group_object:分组对象,value 是 object name 或另一个 group_object name 的数组 group 和 object 形成嵌套结构:一个 group 的 value 列表里,既可以直接引用 object,也可以引用其他 group,形成递归树。地址对象和服务对象结构相同,因此 PolicyObject 类可以统一表达这两种类型,同时处理 src、dst、service 的解析。 ...

Created 14146-05-14 · Updated 5056-06-05 · 6 min · 1181 words · w1100n

redis pipeline

“redis pipeline” why pipeline ? Redis 客户端与 server 的请求/响应模型 前面的文章 Redis 底层协议RESP详解 ,介绍到 redis 客户端与 redis-server 交互通信,采用的 TCP 请求/响应模型; 我们通过 Redis 客户端执行命令,如set key value,客户端遵循RESP协议,将命令的协议串发送给redis-server执行,redis-server执行完成后再同步返回结果。 手写Redis客户端-实现自己的Jedis 对这一过程进行了重点分析,并遵循RESP实现了自己简易版的Redis客户端。 Redis客户端与server通信,使用的是客户端-服务器 (CS) 模式;每次交互,都是完整的请求/响应模式。 这意味着通常情况下一个请求会遵循以下步骤: 客户端连接服务端,基于特定的端口,发送一个命令,并监听Socket返回,通常是以阻塞模式,等待服务端响应。 服务端处理命令,并将结果返回给客户端。 很显然,我们使用jedis或lettuce执行Redis命令,每次都是建立socket连接,并等待返回。 每个命令底层建立TCP连接的时间是省不掉的,即使我们都是在内网使用Redis,内网快但请求/响应的往返时间是不会减少的。 当需要对一组kv进行批量操作时,这组命令的耗时=sum(N*(建立连接时间+发送命令、返回结果的往返时间RTT)),随批量操作的key越多,时间累加呈线性增长。 顺理成章的,就出现了像数据库连接池等池化思想的衍生,redis连接也进行“池化”,如JedisPool。 JedisPool就足够了? 池化connection后,每次执行命令都从池子里“借”,用完之后再将connection“还”到池子。只是节省了创建TCP连接的时间; 当需要对一组kv进行批量操作时,JedisPool池子里的connection连接、极端情况都被用完了,怎么办? ——需要等待JedisPool池里有可复用的connection才能继续执行; redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool … Caused by: java.util.NoSuchElementException: Timeout waiting for idle object at org.apache.commons.pool2.impl.GenericObjectPool.borrowObject(GenericObjectPool.java:449) 如果在指定的等待时间内没有等到idle空闲连接,就报异常了。 尽管使用了池化、将connection进行复用,但不可避免的带来其他问题: https://jjlu521016.github.io/2018/12/09/JedisPool常见问题.html 除了池化的connection会被瞬间用完,Redis官网还给出了另外一个性能损耗的原因: It’s not just a matter of RTT https://redis.io/topics/pipelining 虽然池化的connection,节省了建立连接的时间,但多条命令(发送命令到sever、server返回结果)分别执行多次socket网络IO,涉及到read()和write() syscall系统调用,这意味着从用户态到内核态。上下文切换是巨大的速度损失。 ...

Created 24246-07-24 · Updated 24246-07-24 · 1 min · 184 words · -

redis list

redis list 在 Redis 中, List 类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是4294967295。 从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。 Redis 的列表经常被用作队列(queue),用于在不同程序之间有序地交换消息(message)。 一个程序(称之为生产者,producer)通过LPUSH命令将消息放入队列中,而另一个程序(称之为消费者,consumer)通过RPOP命令取出队列中等待时间最长的消息。 不幸的是,在这个过程中,一个消费者可能在获得一个消息之后崩溃,而未执行完成的消息也因此丢失。 使用RPOPLPUSH命令可以解决这个问题,因为它在返回一个消息之余,还将该消息添加到另一个列表当中,另外的这个列表可以用作消息的备份表: 假如一切正常,当消费者完成该消息的处理之后,可以用LREM命令将该消息从备份表删除。 另一方面,助手(helper)程序可以通过监视备份表,将超过一定处理时限的消息重新放入队列中去(负责处理该消息的消费者可能已经崩溃),这样就不会丢失任何消息了。 头元素和尾元素 头元素指的是列表左端/前端第一个元素,尾元素指的是列表右端/后端第一个元素。 举个例子,列表list包含三个元素: x, y, z,其中x是头元素,而z则是尾元素。 空列表 指不包含任何元素的列表,Redis将不存在的key也视为空列表。 一个列表最多可以包含 232 – 1 个元素 (4294967295, 每个列表超过40亿个元素)。 相关命令 LPUSH LPUSH key value1 [value2] 将一个或多个值插入到列表头部 RPUSH RPUSH key value1 [value2] 将一个或多个值value插入到列表key的表尾。 LPOP key 移出并获取列表的第一个元素 RPOP key RPOP key 移除列表的最后一个元素,返回值为移除的元素。 LLEN key 获取列表长度 LRANGE key start stop LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。 BLPOP BLPOP key1 [key2 ] timeout 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 BRPOP key1 [key2 ] timeout 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。 ...

Created 1016-12-01 · Updated 1016-12-01 · 4 min · 771 words · -

redis hash

redis hash Redis hash 是一个 string 类型的 field和 value的映射表.一个 key可对应多个 field, 一个 field对应一个 value。将一个对象存储为 hash类型,较于每个字段都存储成string类型更能节省内存。新建一个 hash对象时开始是用 zipmap(又称为small hash)来存储的。这个 zipmap其实并不是 hash table,但是zipmap 相比正常的 hash实现可以节省不少 hash本身需要的一些元数据存储开销。尽管 zipmap的添加, 删除,查找都是O(n),但是由于一般对象的field数量都不太多。所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者value的大小超出一定限制后,Redis会在内部自动将zipmap替换成正常的hash实现。 hash操作命令如下: 删除 key del key hset 向名称为 key 的 hash 中添加元素 hset key field value hget hget(key, field) 返回名称为key的hash中field对应的value hsetnx HSETNX key field value 将哈希表key中的域field的值设置为value,当且仅当域field不存在。若域field已经存在,该操作无效。 如果key不存在,一个新哈希表被创建并执行h#setnx命令。 hmget hmget(key, field1, …,field N) 返回名称为key的hash中field i对应的value hmset hmset(key, field1, value1,…,field N, value N) 向名称为key的hash中添加元素field i<—>value i ...

Created 28286-07-28 · Updated 28286-07-28 · 1 min · 177 words · -

Redis

Redis REmote DIctionary Server(Redis) 是一个由SalvatoreSanfilippo写的key-value存储系统。 Redis是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash (哈希类型) 。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave (主从)同步。 Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。 Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。 redis的官网地址,非常好记,是redis.io。 (特意查了一下,域名后缀io属于国家域名,是british Indian Ocean territory,即英属印度洋领地) 目前,Vmware在资助着redis项目的开发和维护。redis[2] 的作者,叫Salvatore Sanfilippo,来自意大利的西西里岛,现在居住在卡塔尼亚。目前供职于Pivotal公司。他使用的网名是antirez。 Redis 4.0 之后的版本,情况就有了一些变动,新版的 Redis 服务在执行一些命令时就会使用『主处理线程』之外的其他线程,例如 UNLINK、FLUSHALL ASYNC, FLUSHDB ASYNC 等非阻塞的删除操作。 Redis 在较新的版本中引入了多线程,不过是在部分命令上引入的,其中包括非阻塞的删除操作,在整体的架构设计上,主处理程序还是单线程模型的 线程模型 使用单线程模型能带来更好的可维护性,方便开发和调试; 使用单线程模型也能并发的处理客户端的请求; Redis 服务中运行的绝大多数操作的性能瓶颈都不是 CPU 6.0 Redis 的多线程部分只是用来处理网络数据的读写和协议解析,执行命令仍然是单线程。之所以这么设计是不想因为多线程而变得复杂 I/O 多路复用的主要作用是让我们可以使用一个线程来监控多个连接是否可读或者可写,但是从网络另一头发的数据包需要先解序列化成 Redis 内部其他模块可以理解的命令,这个过程就是 Redis 6.0 引入多线程来并发处理的。 I/O 多路复用模块收到数据包之后将其丢给后面多个 I/O 线程进行解析,I/O 线程处理结束后,主线程会负责串行的执行这些命令,由于向客户端发回数据包的过程也是比较耗时的,所以执行之后的结果也会交给多个 I/O 线程发送回客户端。 AOF AOF 是 Redis 的一种持久化机制,它会在每次收到来自客户端的写请求时,将其记录到日志中,每次 Redis 服务器启动时都会重放 AOF 日志构建原始的数据集,保证数据的持久性。 Keyspace Notifications, 键空间通知 在Redis2.8.0版本的时候,推出 Keyspace Notifications future。 Keyspace Notifications 此特性允许客户端可以以 订阅/发布 (Sub/Pub) 模式,接收那些对数据库中的键和值有影响的操作事件。这些操作事件具体来说,就是 hash , del, expire , set , lpop 等。 那么你可能会问,redis keyspace 到底有啥用处? 简单说,对于我个人主要关注keyspace几个扩展场景: ...

Created 13136-07-13 · Updated 13136-07-13 · 2 min · 357 words · -

Redis, Memcache, Guava, Ehcache 中的算法

Redis, Memcache, Guava, Ehcache 中的算法 缓存那些事,一是内存爆了要用LRU(最近最少使用, Least Recently Used)、LFU(最少访问次数, Least Frequently Used)、FIFO的算法清理一些;二是设置了超时时间的键过期便要删除,用主动或惰性的方法。 在看所有的细节之前,可以看一篇相当专业的《缓存算法》,世界真宽阔,算法真奇妙。 LRU 简单粗暴的Redis 今天看Redis3.0的发行通告里说,LRU算法大幅提升了,就翻开源码来八卦一下,结果哭笑不得,这旧版的"近似LRU"算法,实在太简单,太偷懒,太Redis了。 在Github的Redis项目里搜索lru,找到代码在redis.c的freeMemoryIfNeeded()函数里。 先看2.6版的代码: 竟然就是随机找三条记录出来,比较哪条空闲时间最长就删哪条,然后再随机三条出来,一直删到内存足够放下新记录为止…….可怜我看配置文档后的想象,一直以为它会帮我在整个Redis里找空闲时间最长的,哪想到我有一百万条记录的时候,它随便找三条就开始删了。 好,收拾心情再看3.0版的改进: 现在每次随机五条记录出来,插入到一个长度为十六的按空闲时间排序的队列里,然后把排头的那条删掉,然后再找五条出来,继续尝试插入队列………嗯,好了一点点吧,起码每次随机多了两条,起码不只在一次随机的五条里面找最久那条,会连同之前的一起做比较…… 中规中矩的Memcached 相比之下,Memcached实现的是再标准不过的LRU算法,专门使用了一个教科书式的双向链表来存储slab内的LRU关系,代码在item.c里,详见memcached源码分析–LRU队列与item结构体,元素插入时把自己放到列头,删除时把自己的前后两个元素对接起来,更新时先做删除再做插入。 分配内存超限时,很自然就会从LRU的队尾开始清理。 同样中规中矩的Guava Cache Guava Cache同样做了一个双向的Queue,见LocalCache中的AccessQueue类,也会在超限时从Queue的队尾清理,见evictEntries()函数。 和Redis旧版一样的Ehcache/Hazelcast 看文档,居然和Redis2.6一样,直接随机8条记录,找出最旧那条,刷到磁盘里,再看代码,Eviction类 和 OnHeapStore的evict()函数。 再看Hazelcast,几乎一样,随机取25条。 这种算法,切换到LFU也非常简单。 小结 不过后来再想想,也许Redis本来就不是主打做Cache的,这种内存爆了需要通过LRU删掉一些元素不是它的主要功能,默认设置都是noeviction——内存不够直接报错的,所以就懒得建个双向链表,而且每次访问时都要更新它了,看Google Group里长长的讨论,新版算法也是社区智慧的结晶。何况,Ehcache和Hazelcast也是和它的旧版一样的算法,Redis的新版还比这两者强了。 后来,根据@刘少壮同学的提示,JBoss的InfiniSpan里还实现了比LRU更高级的LIRS算法,可以避免一些冷数据因为某个原因被大量访问后,把热数据挤占掉。 过期键删除 如果能为每一个设置了过期的元素启动一个Timer,一到时间就触发把它删掉,那无疑是能最快删除过期键最省空间的,在Java里用一条DeplayQueue存着,开条线程不断的读取就能做到。但因为该线程消耗CPU较多,在内存不紧张时有点浪费,似乎大家都不用这个方法。 所以有了惰性检查,就是每次元素被访问时,才去检查它是否已经超时了,这个各家都一样。但如果那个元素后来都没再被访问呢,会永远占着位子吗?所以各家都再提供了一个定期主动删除的方式。 Redis 代码在redis.c的activeExpireCycle()里,看过文档的人都知道,它会在主线程里,每100毫秒执行一次,每次随机抽20条Key检查,如果有1/4的键过期了,证明此时过期的键可能比较多,就不等100毫秒,立刻开始下一轮的检查。不过为免把CPU时间都占了,又限定每轮的总执行时间不超过1毫秒。 Memcached Memcached里有个文不对题的LRU爬虫线程,利用了之前那条LRU的队列,可以设置多久跑一次(默认也是100毫秒),沿着列尾一直检查过去,每次检查LRU队列中的N条数据。虽然每条Key设置的过期时间可能不一样,但怎么说命中率也比Redis的随机选择N条数据好一点,但它没有Redis那种过期的多了立马展开下一轮检查的功能,所以每秒最多只能检查10N条数据,需要自己自己权衡N的设置。 Guava Cache 在Guava Cache里,同一个Cache里所有元素的过期时间是一样的,所以它比Memached更方便,顺着之前那条LRU的Queue检查超时,不限定个数,直到不超时为止。而且它这个检查的调用时机并不是100毫秒什么的,而是每次各种写入数据时的preWriteCleanup()方法中都会调用。 吐槽一句,Guava的Localcache类里面已经4872行了,一点都不轻量了。 Ehcache Ehcache更乱,首先它的内存存储中只有惰性检查,没有主动检查过期的,只会在内存超限时不断用近似LRU算法(见上)把内存中的元素刷到磁盘中,在文件存储中才有超时检查的线程,FAQ里专门解释了原因。 然后磁盘存储那有一条8小时左右跑一次的线程,每次遍历所有元素…..见DiskStorageFactory里的DiskExpiryTask。 一圈看下来,Ehcache的实现最弱。 文章持续修改,转载时请保留原链接: http://calvin1978.blogcn.com/articles/lru.html

Created 28286-06-28 · Updated 28286-06-28 · 1 min · 56 words · -