Redis 实战
第一章 初识 Redis
1.1 Redis 简介
- Redis 是内存数据库,但可以通过 RDB 和 AOF 两种方式来支持数据持久化。
- Redis 通过主从复制特性来扩展读性能(主服务器写,从服务器读)
- Redis 通过客户端分片来扩展写性能
1.1.1 持久化
Redis 拥有两种不同形式的持久化方法,这两种方式都可以用小而紧凑的格式将内存中的数据写入到磁盘中:
方式一:时间点转储(point-in-time dump)
转储操作既可以在 ”指定时间段内有指定数量的写操作执行“ 时触发,也可以通过调用两条特定的转储命令来触发
方式二:AOF(append-only)文件
可以根据数据的重要程度,对AOF文件进行设置,从不重要到重要依次为:不同步、一秒同步一次、一次命令同步一次
1.1.2 Redis 的主从复制特性
- 执行复制的从服务器会连接上主服务器,接收主服务器发送的整个数据库的初始副本(copy)
- 主服务器执行的写命令,都会被发送给从服务器,从而更新从服务器中的数据集
- 因为从服务器中的数据不断被更新,因此客户端可以从任意一台从服务器中读取数据,来避免对主服务器进行集中式的访问,减轻主服务器的压力
1.2 Redis 数据结构简介
结构类型 | 结构存储的值 | 结构的读写能力 |
---|---|---|
String | 字符串、整数、浮点数 | 字符串操作; 整数和浮点数执行自增、自减操作 |
List | 双向链表结构,链表的每个节点都包含一个String | |
Set | 无序集合 | 集合的增删查; 随机获取一个集合中的元素; 计算集合与集合之间的交集、差集、并集 |
Hash | ||
ZSet | value值限制为score的一个Hash结构, 自动根据value值进行排序(升序) |
获取[stop, end]之间的元素; 获取value在给定范围内的元素 |
1.3 Redis 应用场景
最近越来越多的网站提供对网页链接、文章或问题进行投票的功能,以 StackOverflow 为例,网站会根据文章的发布时间和获得的投票数来计算一个分数,默认按照该分数来进行文章的排序。
1.3.1 对文章进行投票
为了产生一个能够随着时间流逝而不断减少的评分,程序需要根据文章的发布时间和当前时间来计算文章的评分,具体方法为:文章评分 = 文章得到的票数 $\times$ 某个常数 + 文章的发布时间的时间戳(秒数)。其中,常数表示的含义为一票能够等价对应的秒数。
根据评分进行排序只是展示的一种方式,还可以根据发布时间进行排序,因此需要提供多个 ZSet 集合,还是以 StackOverflow 为例
为防止一个用户对同一篇文章进行多次投票,因此需要为每一篇文章记录一个已投票的用户名单。因此,每篇文章都会创建一个集合。为了节省内存,可以规定在一段时间后禁止投票,将文章的评分几乎确定下来,此时也可以自动删除这个已投票的用户名单集合(设置该集合的过期时间)。
1.3.2 发布并获取文章
1.3.3 对文章进行分组
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ZSetOperations;
@SpringBootTest
public class RedisInActionMainTest {
// 一周对应的毫秒数
private static final long ONE_WEEK_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
// 一张投票对应的毫秒数, 计算得出
private static final int VOTE_SCORE = 432 * 1000;
// 记录文章是否过期
private static final String TIMEOUT_PREFIX = "timeout:";
// 记录文章详细信息
private static final String POST_PREFIX = "post:";
// 记录文章的投票人
private static final String VOTED_PREFIX = "voted:";
@Autowired
private RedisTemplate redisTemplate;
/**
* 发布文章
*/
@Test
public void publishPostTest() {
}
/**
* 对文章进行投票
*/
@Test
public void votePostTest() {
ZSetOperations zSetOperations = redisTemplate.opsForZSet();
SetOperations setOperations = redisTemplate.opsForSet();
HashOperations hashOperations = redisTemplate.opsForHash();
String userId = "root";
String postId = "123456";
// 检查是否已经过了能够投票的时间
if (zSetOperations.score(TIMEOUT_PREFIX, postId) + ONE_WEEK_IN_MILLISECONDS > System.currentTimeMillis()) {
System.out.println("This Post isn't voted!");
return;
}
// TODO: 使用Lua脚本
// 为该文章进行投票
// 1. 记录投票人, 如果不是重复投票的话
if (setOperations.add(VOTED_PREFIX + postId, userId) > 0) {
// 2. 更新文章的得分
hashOperations.increment(POST_PREFIX + postId, "score", VOTE_SCORE);
// 3. 更新文章的得票数
hashOperations.increment(POST_PREFIX + postId, "votes", 1);
}
}
}