目录
一、单线程的Redis为什么那么快?
二、Redis的持久化?
三、Redis的主从复制?
四、Redis集群?
五、Redis的Value有哪几种类型?
六、Redis有哪些应用场景?
七、内存爆了咋办?
Maxmemory配置指令
回收策略
回收进程如何工作
近似LRU算法
Redis【Remote Dictionary Service】,K-V数据库,属于NoSQL中的一员。推荐Redis中文官网。
全部数据操作都是在内存中完成的,这是其中一个原因。
另一个原因是,操作系统提供了epoll(早期是select/poll)函数形成多路复用的API,可以将接受连接、读、写的文件描述符绑定到selector上基于事件通知机制。Redis的工作线程进行轮询监视,一旦有任何事件到来就可以进行相关操作。
6.x版本增加了多线程io threads,但是工作线程worker还是只有一个。
两种机制,全量备份的快照(RDB)和连续增量备份的AOF日志。
RDB:内存数据的二进制序列化形式,存储紧凑。fork子进程copy on write(COW)。
AOF:内存数据修改的指令记录文本(先缓存后执行,内核fsync函数将其刷到磁盘)。重启时加载重放,需要定期重写(首先遍历内存转换成一系列指令然后序列化到新的AOF日志文件,接着追加期间发生的增量AOF日志,最后替换旧的AOF日志文件)。
持久化操作主要在从节点进行,没有压力。
Redis 4.0之后增加混合持久化选项——将rdb文件的内容和增量的AOF日志文件(持久化过程发生的,不是全量的)存在一起。在重启时,首先加载rdb的内容,然后再重放增量AOF日志,提高效率(之前采用AOF日志重放太慢了)。
异步复制,不满足强一致性,因为是否成功不影响主节点对外提供写服务,所以保证可用性。
从节点会使用各种策略全力追赶主节点,所以最终一致。
Redis同步支持主从同步和从从同步(后续版本增加,以减轻主节点的同步负担)。
主节点会把写指令记录在本地的内存buffer中,然后异步复制到从节点,从节点一边重放一边向主节点反馈自己同步的偏移量。buffer有限且是定长的环形数组,如果内容满了会被从头覆盖,这时候需要执行快照同步。
主节点快照同步时把当前内存的全部数据快照到磁盘文件然后把它们传送到从节点,从节点接收完毕首先清空当前缓存执行全量加载然后继续增量同步。快照同步过程中如果主节点又发生覆盖buffer的情况,只能再次进行快照同步了。避免这种死循环可以配置合适的buffer大小参数。
新节点加入时,先快照同步,完成后增量同步。
Redis 2.8.18版本 之后支持快照同步的无盘复制,主节点遍历内存直接将序列化的内容发送到从节点。
Redis 3.0版本 之后使用wait指令可以让异步复制变身同步复制,这样就保证了强一致性。
哨兵模式Sentinel可以监控主节点状态实现自动主从切换,它的min-slaves-to-write和min-slaves-max-lag两个选项可以限制主从延迟过大,表示在指定时间内(第二个参数,秒)至少有指定数量(第一个参数)的从节点完成了正常复制,否则就对外停止写服务,丧失可用性。
高可用方案:Redis Sentinel(哨兵模式),基于Zookeeper实现的Master-Slave架构。
集群方案一:Codis,转发代理中间件。首先将请求的key哈希,然后这个值对槽位数量(1024)取模得到对应key的槽位,最后根据Redis实例和槽位的映射关系将请求转发到对应的Redis实例。
集群方案二:RedisCluster,去中心化。将所有数据划分为16384的slots,每个节点负责一部分槽位。槽位的信息存储于每个节点。客户端连接时会得到槽位配置信息(需要缓存),可以直接定位(哈希取模)某个具体的key所在的节点。节点发现客户端发出指令的key不归自己管理时,它会发送携带目标地址的跳转指令让客户端去对应的节点操作。Redis迁移的单位是槽,迁移时处于中间过渡状态(原节点migrating,新节点importing),过程同步,从源节点获取内容->存到目标节点->从源节点删除内容。每个主节点可以设置若干个从节点保证HA(Height Availability)。
1)string(字符串):动态字符串可修改。小于1M时加倍扩容,超过1M每次多扩1M。最大长度512M。
字符串操作、数值计算(限流、秒杀)、二进制bitmap(指定范围统计-统计用户任意时间窗口内登录天数、位与、位或)
2)list(列表):双向链表。
模拟栈结构、模拟队列结构、模拟数组结构
3)hash(哈希,字典):无序。底层是数组+链表二维结构。
聚合对象
4)set(集合):无序、唯一。
去重、随机数获取-抽奖、交集-共同好友、并集-朋友圈、差集-可能认识的人
5)zset(有序列表):有序集合,跳跃列表实现(随机翻硬币)。
动态排名、前几名
附上Jedis客户端的方法列表
String set(String key, String value)
String set(String key, String value, String nxxx, String expx, long time)
String get(String key)
Long exists(String... keys)
Boolean exists(String key)
Long del(String... keys)
Long del(String key)
String type(String key)
Set keys(String pattern)
String randomKey()
String rename(String oldkey, String newkey)
Long renamenx(String oldkey, String newkey)
Long expire(String key, int seconds)
Long expireAt(String key, long unixTime)
Long ttl(String key)
Long move(String key, int dbIndex)
String getSet(String key, String value)
List mget(String... keys)
Long setnx(String key, String value)
String setex(String key, int seconds, String value)
String mset(String... keysvalues)
Long msetnx(String... keysvalues)
Long decrBy(String key, long integer)
Long decr(String key)
Long incrBy(String key, long integer)
Double incrByFloat(String key, double value)
Long incr(String key)
Long append(String key, String value)
String substr(String key, int start, int end)
Long hset(String key, String field, String value)
String hget(String key, String field)
Long hsetnx(String key, String field, String value)
String hmset(String key, Map hash)
List hmget(String key, String... fields)
Long hincrBy(String key, String field, long value)
Double hincrByFloat(String key, String field, double value)
Boolean hexists(String key, String field)
Long hdel(String key, String... fields)
Long hlen(String key)
Set hkeys(String key)
List hvals(String key)
Map hgetAll(String key)
Long rpush(String key, String... strings)
Long lpush(String key, String... strings)
Long llen(String key)
List lrange(String key, long start, long end)
String ltrim(String key, long start, long end)
String lindex(String key, long index)
String lset(String key, long index, String value)
Long lrem(String key, long count, String value)
String lpop(String key)
String rpop(String key)
String rpoplpush(String srckey, String dstkey)
Long sadd(String key, String... members)
Set smembers(String key)
Long srem(String key, String... members)
String spop(String key)
Set spop(String key, long count)
Long smove(String srckey, String dstkey, String member)
Long scard(String key)
Boolean sismember(String key, String member)
Set sinter(String... keys)
Long sinterstore(String dstkey, String... keys)
Set sunion(String... keys)
Long sunionstore(String dstkey, String... keys)
Set sdiff(String... keys)
Long sdiffstore(String dstkey, String... keys)
String srandmember(String key)
List srandmember(String key, int count)
Long zadd(String key, double score, String member)
Long zadd(String key, double score, String member, ZAddParams params)
Long zadd(String key, Map scoreMembers)
Long zadd(String key, Map scoreMembers, ZAddParams params)
Set zrange(String key, long start, long end)
Long zrem(String key, String... members)
Double zincrby(String key, double score, String member)
Double zincrby(String key, double score, String member, ZIncrByParams params)
Long zrank(String key, String member)
Long zrevrank(String key, String member)
Set zrevrange(String key, long start, long end)
Set zrangeWithScores(String key, long start, long end)
Set zrevrangeWithScores(String key, long start, long end)
Long zcard(String key)
Double zscore(String key, String member)
String watch(String... keys)
List sort(String key)
List sort(String key, SortingParams sortingParameters)
List blpop(int timeout, String... keys)
List blpop(String... args)
List brpop(String... args)
List blpop(String arg)
List brpop(String arg)
Long sort(String key, SortingParams sortingParameters, String dstkey)
Long sort(String key, String dstkey)
List brpop(int timeout, String... keys)
Long zcount(String key, double min, double max)
Long zcount(String key, String min, String max)
Set zrangeByScore(String key, double min, double max)
Set zrangeByScore(String key, String min, String max)
Set zrangeByScore(String key, double min, double max, int offset, int count)
Set zrangeByScore(String key, String min, String max, int offset, int count)
Set zrangeByScoreWithScores(String key, double min, double max)
Set zrangeByScoreWithScores(String key, String min, String max)
Set zrangeByScoreWithScores(String key, double min, double max, int offset, int count)
Set zrangeByScoreWithScores(String key, String min, String max, int offset, int count)
Set zrevrangeByScore(String key, double max, double min)
Set zrevrangeByScore(String key, String max, String min)
Set zrevrangeByScore(String key, double max, double min, int offset, int count)
Set zrevrangeByScoreWithScores(String key, double max, double min)
Set zrevrangeByScoreWithScores(String key, double max, double min, int offset, int count)
Set zrevrangeByScoreWithScores(String key, String max, String min, int offset, int count)
Set zrevrangeByScore(String key, String max, String min, int offset, int count)
Set zrevrangeByScoreWithScores(String key, String max, String min)
Long zremrangeByRank(String key, long start, long end)
Long zremrangeByScore(String key, double start, double end)
Long zremrangeByScore(String key, String start, String end)
Long zunionstore(String dstkey, String... sets)
Long zunionstore(String dstkey, ZParams params, String... sets)
Long zinterstore(String dstkey, String... sets)
Long zinterstore(String dstkey, ZParams params, String... sets)
Long zlexcount(String key, String min, String max)
Set zrangeByLex(String key, String min, String max)
Set zrangeByLex(String key, String min, String max, int offset, int count)
Set zrevrangeByLex(String key, String max, String min)
Set zrevrangeByLex(String key, String max, String min, int offset, int count)
Long zremrangeByLex(String key, String min, String max)
Long strlen(String key)
Long lpushx(String key, String... string)
Long persist(String key)
Long rpushx(String key, String... string)
String echo(String string)
Long linsert(String key, LIST_POSITION where, String pivot, String value)
String brpoplpush(String source, String destination, int timeout)
Boolean setbit(String key, long offset, boolean value)
Boolean setbit(String key, long offset, String value)
Boolean getbit(String key, long offset)
Long setrange(String key, long offset, String value)
String getrange(String key, long startOffset, long endOffset)
Long bitpos(String key, boolean value)
Long bitpos(String key, boolean value, BitPosParams params)
List configGet(String pattern)
String configSet(String parameter, String value)
Object eval(String script, int keyCount, String... params)
void subscribe(JedisPubSub jedisPubSub, String... channels)
Long publish(String channel, String message)
void psubscribe(JedisPubSub jedisPubSub, String... patterns)
Object eval(String script, List keys, List args)
Object eval(String script)
Object evalsha(String script)
Object evalsha(String sha1, List keys, List args)
Object evalsha(String sha1, int keyCount, String... params)
Boolean scriptExists(String sha1)
List scriptExists(String... sha1)
String scriptLoad(String script)
List slowlogGet()
List slowlogGet(long entries)
Long objectRefcount(String string)
String objectEncoding(String string)
Long objectIdletime(String string)
Long bitcount(String key)
Long bitcount(String key, long start, long end)
Long bitop(BitOP op, String destKey, String... srcKeys)
List
上面Value类型部分也有涉及。
Redis 的业务应用范围非常广泛,让我们以掘金技术社区(juejin.im)的帖子模块为实 例,梳理一下,Redis 可以用在哪些地方?
1、记录帖子的点赞数、评论数和点击数 (hash)。
2、记录用户的帖子 ID 列表 (排序),便于快速显示用户的帖子列表 (zset)。
3、记录帖子的标题、摘要、作者和封面信息,用于列表页展示 (hash)。
4、记录帖子的点赞用户 ID 列表,评论 ID 列表,用于显示和去重计数 (zset)。
5、缓存近期热帖内容 (帖子内容空间占用比较大),减少数据库压力 (hash)。
6、记录帖子的相关文章 ID,根据内容推荐相关帖子 (list)。
7、如果帖子 ID 是整数自增的,可以使用 Redis 来分配帖子 ID(计数器)。
8、收藏集和帖子之间的关系 (zset)。
9、记录热榜帖子 ID 列表,总热榜和分类热榜 (zset)。
10、缓存用户行为历史,进行恶意行为过滤 (zset,hash)。
maxmemory
配置指令用于配置Redis存储数据时指定限制的内存大小。通过redis.conf可以设置该指令,或者之后使用CONFIG SET命令来进行运行时配置。
例如为了配置内存限制为100mb,以下的指令可以放在redis.conf
文件中。
maxmemory 100mb
设置maxmemory
为0代表没有内存限制。对于64位的系统这是个默认值,对于32位的系统默认内存限制为3GB。
当指定的内存限制大小达到时,需要选择不同的行为,也就是策略。 Redis可以仅仅对命令返回错误,这将使得内存被使用得更多,或者回收一些旧的数据来使得添加数据时可以避免内存限制。
当maxmemory限制达到的时候Redis会使用的行为由 Redis的maxmemory-policy配置指令来进行配置。
以下的策略是可用的:
如果没有键满足回收的前提条件的话,策略volatile-lru, volatile-random以及volatile-ttl就和noeviction 差不多了。
选择正确的回收策略是非常重要的,这取决于你的应用的访问模式,不过你可以在运行时进行相关的策略调整,并且监控缓存命中率和没命中的次数,通过RedisINFO命令输出以便调优。
一般的经验规则:
allkeys-lru 和 volatile-random策略对于当你想要单一的实例实现缓存及持久化一些键时很有用。不过一般运行两个实例是解决这个问题的更好方法。
为了键设置过期时间也是需要消耗内存的,所以使用allkeys-lru这种策略更加高效,因为没有必要为键取设置过期时间当内存有压力时。
理解回收进程如何工作是非常重要的:
如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
Redis的LRU算法并非完整的实现。这意味着Redis并没办法选择最佳候选来进行回收,也就是最久未被访问的键。相反它会尝试运行一个近似LRU的算法,通过对少量keys进行取样,然后回收其中一个最好的key(被访问时间较早的)。
不过从Redis 3.0算法已经改进为回收键的候选池子。这改善了算法的性能,使得更加近似真是的LRU算法的行为。
Redis LRU有个很重要的点,你通过调整每次回收时检查的采样数量,以实现调整算法的精度。这个参数可以通过以下的配置指令调整:
maxmemory-samples 5
Redis为什么不使用真实的LRU实现是因为这需要太多的内存。
※文章内容部分摘自《Redis深度历险:核心原理和应用实践》和Redis中文官网※