Redis(CS-Notes) 青旅半醒 2021-12-05 14:41 136阅读 0赞 转载自[https://github.com/CyC2018/CS-Notes/blob/master/notes/Redis.md\#%E4%B8%80%E6%A6%82%E8%BF%B0][https_github.com_CyC2018_CS-Notes_blob_master_notes_Redis.md_E4_B8_80_E6_A6_82_E8_BF_B0] -------------------- **目录** 1 概述 2 数据类型 2.1 STRING 2.2 LIST 2.3 SET 2.4 HASH 2.5 ZSET 3 数据结构 3.1 字典 3.2 跳跃表 4 使用场景 4.1 计数器 4.2 缓存 4.3 查找表 4.4 消息队列 4.5 会话缓存 4.6 分布式锁实现 4.7 其它 5 Redis 与 Memcached的对比 5.1 数据类型 5.2 数据持久化 5.3 分布式 5.4 内存管理机制 6 键的过期时间 7 数据淘汰策略 缓存命中率的介绍 8 持久化 8.1 RDB(Redis DataBase) 持久化 8.2 AOF 持久化 9 事务 10 事件 10.1 文件事件 10.2 时间事件 10.3 事件的调度与执行 11 复制 连接过程 主从链 12 Sentinel 13 分片 14 一个简单的论坛系统分析 文章信息 点赞功能 对文章进行排序 参考资料 微信公众号 -------------------- # 1 概述 # Redis 是速度非常快的**非关系型**(NoSQL)**内存 键值**数据库,可以存储 键 和五种不同类型的值 之间的映射。 **键的类型 只能为字符串**,**值支持五种数据类型**:字符串、列表、集合、散列表、有序集合。 Redis 支持很多特性,例如 **将内存中的数据 持久化到硬盘中**,使用**复制**来扩展读性能,使用**分片**来扩展写性能。 /* 在cmd里面如何打开Redis */ redis-server.exe //开启服务器 redis-cli.exe -h 127.0.0.1 -p 6379 //开启客户端 127.0.0.1:6379> keys * 1) "redisdemo" 127.0.0.1:6379> get redisdemo "12345" # 2 数据类型 # <table> <thead> <tr> <th>数据类型</th> <th>可以存储的值</th> <th>操作</th> </tr> </thead> <tbody> <tr> <td>STRING</td> <td>字符串、整数或者浮点数</td> <td>对整个字符串或者字符串的其中一部分执行操作<br> 对整数和浮点数执行自增或者自减操作</td> </tr> <tr> <td>LIST</td> <td>列表</td> <td>从两端压入或者弹出元素 <br> 对单个或者多个元素进行修剪,<br> 只保留一个范围内的元素</td> </tr> <tr> <td>SET</td> <td>无序集合</td> <td>添加、获取、移除单个元素<br> 检查一个元素是否存在于集合中<br> 计算交集、并集、差集<br> 从集合里面随机获取元素</td> </tr> <tr> <td>HASH</td> <td>包含键值对的无序散列表</td> <td>添加、获取、移除单个键值对<br> 获取所有键值对<br> 检查<u>某个键是否存在</u></td> </tr> <tr> <td>ZSET</td> <td><span style="color:#f33b45;">有序集合</span></td> <td>添加、获取、删除元素<br> 根据分值范围或者成员来获取元素<br> 计算<u>一个键的排名</u></td> </tr> </tbody> </table> > [What Redis data structures look like][] ## 2.1 STRING ## ![format_png][] > set hello world //键是hello,值是world OK > get hello //取出键hello的值 "world" > del hello //删除hello这个键,对应的键值对 (integer) 1 > get hello //再次尝试取出键hello的值,发现值为空 (nil) ## 2.2 LIST ## ![format_png 1][] > rpush list-key item (integer) 1 > rpush list-key item2 (integer) 2 > rpush list-key item (integer) 3 > lrange list-key 0 -1 1) "item" 2) "item2" 3) "item" > lindex list-key 1 "item2" > lpop list-key "item" > lrange list-key 0 -1 1) "item2" 2) "item" ## 2.3 SET ## ![format_png 2][] > sadd set-key item (integer) 1 > sadd set-key item2 (integer) 1 > sadd set-key item3 (integer) 1 > sadd set-key item (integer) 0 > smembers set-key 1) "item" 2) "item2" 3) "item3" > sismember set-key item4 (integer) 0 > sismember set-key item (integer) 1 > srem set-key item2 (integer) 1 > srem set-key item2 (integer) 0 > smembers set-key 1) "item" 2) "item3" ## 2.4 HASH ## ![format_png 3][] > hset hash-key sub-key1 value1 (integer) 1 > hset hash-key sub-key2 value2 (integer) 1 > hset hash-key sub-key1 value1 (integer) 0 > hgetall hash-key 1) "sub-key1" 2) "value1" 3) "sub-key2" 4) "value2" > hdel hash-key sub-key2 (integer) 1 > hdel hash-key sub-key2 (integer) 0 > hget hash-key sub-key1 "value1" > hgetall hash-key 1) "sub-key1" 2) "value1" ## 2.5 ZSET ## ![format_png 4][] > zadd zset-key 728 member1 (integer) 1 > zadd zset-key 982 member0 (integer) 1 > zadd zset-key 982 member0 (integer) 0 > zrange zset-key 0 -1 withscores 1) "member1" 2) "728" 3) "member0" 4) "982" > zrangebyscore zset-key 0 800 withscores 1) "member1" 2) "728" > zrem zset-key member1 (integer) 1 > zrem zset-key member1 (integer) 0 > zrange zset-key 0 -1 withscores 1) "member0" 2) "982" -------------------- # 3 数据结构 # ## 3.1 字典 ## dictht 是一个散列表结构,使用**拉链法**解决哈希冲突。 /* This is our hash table structure. Every dictionary has two of this as we * implement incremental rehashing, for the old to the new table. */ typedef struct dictht { dictEntry **table; unsigned long size; unsigned long sizemask; unsigned long used; } dictht; typedef struct dictEntry { void *key; union { void *val; uint64_t u64; int64_t s64; double d; } v; struct dictEntry *next; // dictEntry 是一个链表 } dictEntry; **Redis 的字典 dict 中,包含两个哈希表 dictht**,这是为了方便进行 **rehash 操作**。在扩容时,将其中一个 dictht 上的键值对 rehash 到另一个 dictht 上面,完成之后释放空间 并交换两个 dictht 的角色。 typedef struct dict { dictType *type; void *privdata; dictht ht[2]; long rehashidx; /* rehashing not in progress if rehashidx == -1 */ unsigned long iterators; /* number of iterators currently running */ } dict; rehash 操作不是一次性完 rehash 操作不是一次性完成,而是采用渐进方式,这是为了避免一次性执行过多的 rehash 操作给服务器带来过大的负担。 **渐进式 rehash** 通过记录 dict 的 rehashidx 完成,它从 0 开始,然后每执行一次 rehash 都会递增。例如在一次 rehash 中,要把 dict\[0\] rehash 到 dict\[1\],这一次会把 dict\[0\] 上 table\[rehashidx\] 的键值对 rehash 到 dict\[1\] 上,dict\[0\] 的 table\[rehashidx\] 指向 null,并令 rehashidx++。 在 **rehash 期间**,**每次**对字典执行添加、删除、查找或者更新操作时,**都会执行一次**渐进式 rehash。 采用渐进式 rehash 会导致字典中的数据分散在两个 dictht 上,因此对字典的查找操作也需要到对应的 dictht 去执行。 /* Performs N steps of incremental rehashing. Returns 1 if there are still * keys to move from the old to the new hash table, otherwise 0 is returned. * * Note that a rehashing step consists in moving a bucket (that may have more * than one key as we use chaining) from the old to the new hash table, however * since part of the hash table may be composed of empty spaces, it is not * guaranteed that this function will rehash even a single bucket, since it * will visit at max N*10 empty buckets in total, otherwise the amount of * work it does would be unbound and the function may block for a long time. */ int dictRehash(dict *d, int n) { int empty_visits = n * 10; /* Max number of empty buckets to visit. */ if (!dictIsRehashing(d)) return 0; while (n-- && d->ht[0].used != 0) { dictEntry *de, *nextde; /* Note that rehashidx can't overflow as we are sure there are more * elements because ht[0].used != 0 */ assert(d->ht[0].size > (unsigned long) d->rehashidx); while (d->ht[0].table[d->rehashidx] == NULL) { d->rehashidx++; if (--empty_visits == 0) return 1; } de = d->ht[0].table[d->rehashidx]; /* Move all the keys in this bucket from the old to the new hash HT */ while (de) { uint64_t h; nextde = de->next; /* Get the index in the new hash table */ h = dictHashKey(d, de->key) & d->ht[1].sizemask; de->next = d->ht[1].table[h]; d->ht[1].table[h] = de; d->ht[0].used--; d->ht[1].used++; de = nextde; } d->ht[0].table[d->rehashidx] = NULL; d->rehashidx++; } /* Check if we already rehashed the whole table... */ if (d->ht[0].used == 0) { zfree(d->ht[0].table); d->ht[0] = d->ht[1]; _dictReset(&d->ht[1]); d->rehashidx = -1; return 0; } /* More to rehash... */ return 1; } ## 3.2 跳跃表 ## 是有序集合的底层实现之一。 跳跃表是基于 多指针有序链表 实现的,可以看成多个有序链表。 ![format_png 5][] > 在查找时,从**上层指针**开始查找。找到对应的区间之后,再到**下一层**去查找。 > > (PS:上层的区间范围大。每往下一层,区间范围都会细化一点。当我们要查找某个数值时。我们从上层往下层,逐层缩小查找范围,直到找到我们想要的值) 下图演示了查找 22 的过程: ![format_png 6][] 与红黑树等平衡树相比,**跳跃表具有以下优点**: * 插入速度非常快速,因为不需要进行旋转等操作来维护平衡性; * 更容易实现; * **支持无锁操作**。 -------------------- # 4 使用场景 # ## 4.1 计数器 ## 可以对 String 进行自增自减运算,从而实现计数器功能。 **Redis 这种内存型数据库的读写性能非常高,很适合存储频繁读写的计数量。(例如微博的点赞点踩)** ## 4.2 缓存 ## 将热点数据放到内存中,设置内存的最大使用量以及淘汰策略 来保证缓存的命中率。 ## 4.3 查找表 ## [https://baike.baidu.com/item/%E6%9F%A5%E6%89%BE%E8%A1%A8/2968924?fr=aladdin][https_baike.baidu.com_item_E6_9F_A5_E6_89_BE_E8_A1_A8_2968924_fr_aladdin] > 查找表是用简单的查询操作替换运行时计算的数组。 > > (PS:由于从内存中提取数值经常要比复杂的计算速度快很多,所以这样得到的速度提升是很显著的。直接把常用到的信息存到查找表里,免得自己重复的去计算出那些信息。) 例如 DNS 记录就很适合使用 Redis 进行存储。 查找表和缓存类似,也是利用了 **Redis 快速的查找特性**。但是**查找表的内容不能失效**,而**缓存的内容可以失效**,因为缓存不作为可靠的数据来源。 ## 4.4 消息队列 ## **List 是一个双向链表**,可以通过 **lpush** 和 **rpop** 写入和读取消息 不过最好使用 Kafka、RabbitMQ 等消息中间件。 ## 4.5 会话缓存 ## 可以**使用 Redis 来统一存储** 多台应用服务器的会话信息。 当应用服务器不再存储用户的会话信息,也就不再具有状态,一个用户可以请求任意一个应用服务器,从而更容易实现高可用性以及可伸缩性。 ## 4.6 分布式锁实现 ## 在分布式场景下,无法使用单机环境下的锁来对多个节点上的进程进行同步。 可以使用 **Redis 自带的 SETNX 命令**实现分布式锁。除此之外,还可以使用官方提供的 **RedLock 分布式锁实现**。 ## 4.7 其它 ## Set 可以实现交集、并集等操作,从而实现共同好友等功能。 ZSet 可以实现有序性操作,从而实现排行榜等功能。 -------------------- # 5 Redis 与 Memcached的对比 # 两者都是**非关系型 内存 键值数据库**,主要有以下不同: ## 5.1 数据类型 ## Memcached 仅支持字符串类型,而 Redis 支持五种不同的数据类型,可以更灵活地解决问题。 ## 5.2 数据持久化 ## Redis 支持两种持久化策略:**RDB 快照**和 **AOF 日志**,而 Memcached 不支持持久化。 ## 5.3 分布式 ## Memcached 不支持分布式,只能通过**在客户端使用一致性哈希**来实现分布式存储。这种方式在存储和查询时,都需要先在客户端计算一次数据所在的节点。 Redis Cluster 实现了分布式的支持。 ## 5.4 内存管理机制 ## * 在 Redis 中,并不是所有数据都一直存储在内存中,可以将一些很久没用的 value 交换到磁盘,而 Memcached 的数据则会一直在内存中。 * Memcached 将内存分割成特定长度的块来存储数据,以完全解决内存碎片的问题。但是这种方式会使得内存的利用率不高,例如块的大小为 128 bytes,只存储 100 bytes 的数据,那么剩下的 28 bytes 就浪费掉了。 -------------------- # 6 键的过期时间 # Redis 可以为**每个键** 设置过期时间。当键过期时,会**自动删除该键**。 对于散列表这种容器,只能为**整个键**设置过期时间(整个散列表),而不能为键里面的单个元素设置过期时间。 # 7 数据淘汰策略 # 可以设置内存最大使用量,当内存使用量超出时,会施行数据淘汰策略。 Redis 具体有 **6 种淘汰策略**: <table> <thead> <tr> <th style="width:196px;">策略</th> <th style="width:653px;">描述</th> </tr> </thead> <tbody> <tr> <td style="width:196px;"><strong>volatile-lru</strong></td> <td style="width:653px;">从<u>已设置过期时间的数据集</u>中,挑选<u>最近最少使用的数据</u>淘汰</td> </tr> <tr> <td style="width:196px;"><strong>volatile-ttl</strong></td> <td style="width:653px;">从<u>已设置过期时间的数据集</u>中,挑选<u>将要过期的数据</u>淘汰</td> </tr> <tr> <td style="width:196px;"><strong>volatile-random</strong></td> <td style="width:653px;">从<u>已设置过期时间的数据集</u>中,<u>任意选择</u>数据淘汰</td> </tr> <tr> <td style="width:196px;"><strong>allkeys-lru</strong></td> <td style="width:653px;">从<u>所有数据集</u>中,挑选<u>最近最少使用</u>的数据淘汰</td> </tr> <tr> <td style="width:196px;"><strong>allkeys-random</strong></td> <td style="width:653px;">从<u>所有数据集</u>中,<u>任意选择数据</u>进行淘汰</td> </tr> <tr> <td style="width:196px;"><strong>noeviction</strong></td> <td style="width:653px;">禁止驱逐数据</td> </tr> </tbody> </table> 作为内存数据库,出于对性能和内存消耗的考虑,Redis 的淘汰算法 实际实现上**并非针对所有 key**,而是**抽样一小部分并且从中选出**被淘汰的 key。 使用 Redis 缓存数据时,为了提高缓存命中率,需要保证缓存数据都是热点数据。可以将内存最大使用量设置为热点数据占用的内存量,然后启用 allkeys-lru 淘汰策略,将最近最少使用的数据淘汰。 Redis 4.0 引入了 volatile-lfu 和 allkeys-lfu 淘汰策略,LFU 策略通过统计访问频率,将访问频率最少的键值对淘汰。 > [https://www.cnblogs.com/shamo89/p/8383915.html][https_www.cnblogs.com_shamo89_p_8383915.html] > > ## 缓存命中率的介绍 ## > > 命中:可以直接通过缓存获取到需要的数据。 > > 不命中:无法直接通过缓存获取到想要的数据,需要再次查询数据库或者执行其它的操作。原因可能是由于缓存中根本不存在,或者缓存已经过期。 > > 通常来讲,缓存的命中率越高 则表示使用缓存的收益越高,应用的性能越好(响应时间越短、吞吐量越高),抗并发的能力越强。 > > 由此可见,在高并发的互联网系统中,缓存的命中率是至关重要的指标。 -------------------- # 8 持久化 # Redis 是内存型数据库,为了保证数据**在断电后不会丢失**,需要将内存中的数据持久化到硬盘上。 > [https://www.cnblogs.com/itdragon/p/7906481.html][https_www.cnblogs.com_itdragon_p_7906481.html] > > [https://blog.csdn.net/a1007720052/article/details/79126253][https_blog.csdn.net_a1007720052_article_details_79126253] > * **RDB 持久化**可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot)。 > * AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集。 AOF 文件中的命令全部以 Redis 协议的格式来保存,新命令会被追加到文件的末尾。 > * Redis 还可以在后台对 AOF 文件进行重写(rewrite),使得 AOF 文件的体积不会超出保存数据集状态所需的实际大小。Redis 还可以同时使用 AOF 持久化和 RDB 持久化。 在这种情况下, 当 Redis 重启时, 它会优先使用 AOF 文件来还原数据集, 因为 AOF 文件保存的数据集通常比 RDB 文件所保存的数据集更完整。 > * 你甚至可以关闭持久化功能,让数据只在服务器运行时存在。 ## 8.1 RDB(Redis DataBase) 持久化 ## 将某个时间点的所有数据都存放到硬盘上。 可以将快照复制到其它服务器,从而创建具有相同数据的服务器副本。 如果系统发生故障,将会丢失最后一次创建快照之后的数据。 如果数据量很大,保存快照的时间会很长。 ## 8.2 AOF 持久化 ## 将写命令添加到 **AOF 文件(Append Only File)**的末尾。 使用 AOF 持久化需要设置同步选项,从而确保写命令同步到磁盘文件上的时机。这是因为对文件进行写入并不会马上将内容同步到磁盘上,而是先存储到缓冲区,然后由操作系统决定什么时候同步到磁盘。有以下**同步选项**: <table> <thead> <tr> <th>选项</th> <th>同步频率</th> </tr> </thead> <tbody> <tr> <td>always</td> <td><strong>每个写命令</strong>都同步</td> </tr> <tr> <td>everysec</td> <td><strong>每秒</strong>同步一次</td> </tr> <tr> <td>no</td> <td><strong>让操作系统来决定</strong>何时同步</td> </tr> </tbody> </table> * **always** 选项会严重降低服务器的性能; * **everysec** 选项比较合适,可以保证系统崩溃时只会丢失一秒左右的数据,并且 Redis 每秒执行一次同步对服务器性能几乎没有任何影响; * **no** 选项并不能给服务器性能带来多大的提升,而且也会增加系统崩溃时数据丢失的数量。 随着服务器写请求的增多,AOF 文件会越来越大。Redis 提供了一种将 AOF 重写的特性,能够去除 AOF 文件中的冗余写命令。 -------------------- # 9 事务 # 一个事务包含了多个命令,服务器在执行事务期间,不会改去执行其它客户端的命令请求。 事务中的多个命令被一次性发送给服务器,而不是一条一条发送,这种方式被称为流水线,它可以减少客户端与服务器之间的网络通信次数从而提升性能。 **Redis 最简单的事务实现方式**是使用 **MULTI** 和 **EXEC** 命令将事务操作包围起来。 -------------------- # 10 事件 # Redis 服务器是一个事件驱动程序。 ## 10.1 文件事件 ## 服务器 通过**套接字** 与客户端或者其它服务器 进行通信。**文件事件**就是对套接字操作的抽象。 Redis 基于 Reactor 模式开发了自己的网络事件处理器,**使用 I/O 多路复用程序**来**同时监听 多个套接字**,并将到达的事件传送给文件事件分派器。分派器会根据套接字产生的事件类型 调用 相应的事件处理器。 ![format_png 7][] ## 10.2 时间事件 ## 服务器有一些操作需要在给定的时间点执行,**时间事件**是对这类定时操作的抽象。 时间事件又分为: * 定时事件:是让一段程序在指定的时间之内执行一次; * 周期性事件:是让一段程序每隔指定时间就执行一次。 Redis 将所有时间事件 都放在一个无序链表中,通过遍历整个链表 查找出已到达的时间事件,并调用相应的事件处理器。 ## 10.3 事件的调度与执行 ## 服务器需要**不断监听 文件事件的套接字**,才能得到待处理的文件事件;但是**不能一直监听**,否则时间事件 无法在规定的时间内执行,因此监听时间 应该根据 距离现在最近的时间事件 来决定。 事件调度与执行 由 **aeProcessEvents 函数**负责,伪代码如下: def aeProcessEvents(): # 获取 到达时间离当前时间最接近的 时间事件 time_event = aeSearchNearestTimer() #1.这个事件可能已经到了,可能还没到。但是这个事件是离当前时间最近的 # 计算 最接近的时间事件 距离到达 还有多少毫秒 remaind_ms = time_event.when - unix_ts_now() #2.计算 事件到达的时间 减去 当前时间 的值。若差值小于0,说明事件已经到达了。 # 如果事件已到达,那么 remaind_ms 的值可能为负数,将它设为 0 if remaind_ms < 0: remaind_ms = 0 # 根据 remaind_ms 的值,创建 timeval timeval = create_timeval_with_ms(remaind_ms) #3.根据事件还有多久到达,生成监听时间 # 阻塞并等待 文件事件产生,最大阻塞时间 由传入的 timeval 决定 aeApiPoll(timeval) #4.监听timeval时间,得到待处理的事件 # 处理 所有已产生的文件事件 procesFileEvents() # 处理 所有已到达的时间事件 processTimeEvents() 将 aeProcessEvents 函数 置于一个循环里面,加上初始化和清理函数,就构成**了 Redis 服务器的主函数**,伪代码如下: def main(): # 初始化服务器 init_server() # 一直处理事件,直到服务器关闭为止 while server_is_not_shutdown(): aeProcessEvents() # 服务器关闭,执行清理操作 clean_server() 从事件处理的角度来看,服务器运行流程如下: ![format_png 8][] # 11 复制 # 通过使用 **slaveof host port 命令,**来让一个服务器成为另一个服务器的**从服务器**。 一个**从服务器**只能有一个主服务器,并且**不支持主主复制**。 > * **主主复制其实的意思就是节点直接互为主从复制。** > * **主主复制即在两台MySQL主机内都可以变更数据,而且另外一台主机也会做出相应的变更** > * **主从复制:主库可写(会写入从库)可读,从库只能读** > * **主主复制:两个库可写可读,一个库修改会写入另一个库** ## 连接过程 ## 1. **主服务器**创建快照文件,发送给从服务器,并在快照文件发送期间使用缓冲区记录执行的写命令。快照文件发送完毕之后,开始向**从服务器**发送存储在缓冲区中的写命令; 2. **从服务器**丢弃所有旧数据,载入主服务器发来的快照文件;之后**从服务器**开始接受主服务器发来的写命令; 3. **主服务器**每执行一次写命令,就向**从服务器**发送 相同的写命令。 ## 主从链 ## 随着负载不断上升,主服务器可能无法很快地更新所有从服务器,或者重新连接和重新同步从服务器将导致系统超载。为了解决这个问题,可以创建一个中间层来分担主服务器的复制工作。中间层的服务器是最上层服务器的从服务器,又是最下层服务器的主服务器。 > **中间层的服务器**是最上层服务器的**从服务器**,又是最下层服务器的**主服务器**。 ![format_png 9][] -------------------- # 12 Sentinel # Sentinel(哨兵)可以**监听**集群中的服务器,并在**主服务器**进入**下线状态**时,自动从**从服务器**中选举出新的主服务器。 # 13 分片 # 分片是将数据划分为多个部分的方法,可以将数据存储到多台机器里面,这种方法在解决某些问题时可以获得线性级别的性能提升。 假设有 4 个 Redis 实例 R0,R1,R2,R3,还有很多表示用户的键 user:1,user:2,... ,有不同的方式来选择一个指定的键存储在哪个实例中。 * 最简单的方式是**范围分片**,例如用户 id 从 0~1000 的存储到实例 R0 中,用户 id 从 1001~2000 的存储到实例 R1 中,等等。但是这样需要维护一张**映射范围表**,维护操作代价很高。 * 还有一种方式是**哈希分片**,使用 CRC32 哈希函数**将键转换为一个数字,再对实例数量求模**就能知道应该存储的实例。 根据**执行分片的位置**,可以分为**三种分片方式**: * **客户端分片**:客户端使用一致性哈希等算法决定键应当分布到哪个节点。 * **代理分片**:将客户端请求 发送到代理上,由代理转发请求到正确的节点上。 * **服务器分片**:Redis Cluster。 -------------------- # 14 一个简单的论坛系统分析 # 该论坛系统功能如下: * 可以发布文章; * 可以对文章进行点赞; * 在首页可以按文章的发布时间或者文章的点赞数进行排序显示。 ## 文章信息 ## 文章包括标题、作者、赞数等信息,在关系型数据库中很容易构建一张表来存储这些信息,在 Redis 中可以使用 **HASH** 来存储每种信息以及其对应的值的**映射**。 Redis 没有关系型数据库中的**表**这一概念 来将同种类型的数据存放在一起,而是使用**命名空间**的方式来实现这一功能。**键名的前面部分**存储命名空间,**后面部分的内容**存储 ID,通常使用 " : "来进行分隔。例如下面的 HASH 的键名为 article:92617,其中 **article 为命名空间,ID 为 92617**。 ![format_png 10][] ## 点赞功能 ## 当有用户为一篇文章点赞时,除了要对该文章的 votes 字段进行加 1 操作,还**必须记录**该用户已经对该文章进行了点赞,防止用户点赞次数超过 1。可以建立**文章的已投票用户集合**来进行记录。 为了节约内存,规定一篇文章发布满一周之后,就不能再对它进行投票,而文章的已投票集合也会被删除,可以为文章的已投票集合设置一个**一周的过期时间**就能实现这个规定。 ![format_png 11][] ## 对文章进行排序 ## 为了按发布时间和点赞数进行排序,可以建立一个文章发布时间的有序集合和一个文章点赞数的有序集合。(下图中的 **score** 就是这里所说的**点赞数**;下面所示的有序集合分值并不直接是时间和点赞数,而是根据时间和点赞数**间接计算出来的**) ![format_png 12][] -------------------- # 参考资料 # * Carlson J L. Redis in Action\[J\]. Media.johnwiley.com.au, 2013. * [黄健宏. Redis 设计与实现 \[M\]. 机械工业出版社, 2014.][. Redis _ _M_. _ 2014.] * [REDIS IN ACTION][] * [Skip Lists: Done Right][Skip Lists_ Done Right] * [论述 Redis 和 Memcached 的差异][Redis _ Memcached] * [Redis 3.0 中文版- 分片][Redis 3.0 _-] * [Redis 应用场景][Redis] * [Using Redis as an LRU cache][] # 微信公众号 # 更多精彩内容将发布在微信公众号 CyC2018 上,你也可以在公众号后台和我交流学习和求职相关的问题。另外,公众号提供了该项目的 PDF 等离线阅读版本,后台回复 "下载" 即可领取。公众号也提供了一份技术面试复习大纲,不仅系统整理了面试知识点,而且标注了各个知识点的重要程度,从而帮你理清多而杂的面试知识点,后台回复 "大纲" 即可领取。我基本是按照这个大纲来进行复习的,对我拿到了 BAT 头条等 Offer 起到很大的帮助。你们完全可以和我一样根据大纲上列的知识点来进行复习,就不用看很多不重要的内容,也可以知道哪些内容很重要从而多安排一些复习时间。 [https_github.com_CyC2018_CS-Notes_blob_master_notes_Redis.md_E4_B8_80_E6_A6_82_E8_BF_B0]: https://github.com/CyC2018/CS-Notes/blob/master/notes/Redis.md#%E4%B8%80%E6%A6%82%E8%BF%B0 [What Redis data structures look like]: https://redislabs.com/ebook/part-1-getting-started/chapter-1-getting-to-know-redis/1-2-what-redis-data-structures-look-like/ [format_png]: /images/20211205/3735c23c65224523ad34d76108a3f48d.png [format_png 1]: /images/20211205/29a010ec526d4cfcba9345c886e8a571.png [format_png 2]: /images/20211205/c10324e67e714463a9c51214c5da53f2.png [format_png 3]: /images/20211205/ace3a38c8af9499db93967892382c627.png [format_png 4]: /images/20211205/e34975eb722d4ac8b7fd48c79140eb7d.png [format_png 5]: /images/20211205/1352efddbbe64a7ab1ed4d12d24bfc9f.png [format_png 6]: /images/20211205/bd7c083a82904381b8e4308cc035598c.png [https_baike.baidu.com_item_E6_9F_A5_E6_89_BE_E8_A1_A8_2968924_fr_aladdin]: https://baike.baidu.com/item/%E6%9F%A5%E6%89%BE%E8%A1%A8/2968924?fr=aladdin [https_www.cnblogs.com_shamo89_p_8383915.html]: https://www.cnblogs.com/shamo89/p/8383915.html [https_www.cnblogs.com_itdragon_p_7906481.html]: https://www.cnblogs.com/itdragon/p/7906481.html [https_blog.csdn.net_a1007720052_article_details_79126253]: https://blog.csdn.net/a1007720052/article/details/79126253 [format_png 7]: /images/20211205/ae1d1099cef44793b4958c768a75e797.png [format_png 8]: /images/20211205/24fc4cc366f54716aa3257ca9623db3c.png [format_png 9]: /images/20211205/60cd0690759641daa9fa1de00fe170d7.png [format_png 10]: /images/20211205/1aca77dacf5a4e13a82e0071f3e59491.png [format_png 11]: /images/20211205/092c7371b3ee4791aaa28f6deb5eb219.png [format_png 12]: /images/20211205/d86b33ab00e546fb85d12baaac8d3bda.png [. Redis _ _M_. _ 2014.]: http://redisbook.com/index.html [REDIS IN ACTION]: https://redislabs.com/ebook/foreword/ [Skip Lists_ Done Right]: http://ticki.github.io/blog/skip-lists-done-right/ [Redis _ Memcached]: http://www.cnblogs.com/loveincode/p/7411911.html [Redis 3.0 _-]: http://wiki.jikexueyuan.com/project/redis-guide [Redis]: http://www.scienjus.com/redis-use-case/ [Using Redis as an LRU cache]: https://redis.io/topics/lru-cache
还没有评论,来说两句吧...