bboyjing's blog

Redis学习笔记二【Redis数据结构实践】

上一章对Redis的5种数据结构有了基本的了解之后,下面来学习下怎样使用这些结构来解决实际问题。下面用Redis构建一个简单的文章投票网站的后端来演示,代码示例依然使用Springboot,给出GitHub地址

Redis配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# REDIS (RedisProperties)
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=0
@Configuration
public class RedisConfig {
@Bean
public JedisConnectionFactory jedisConnectionFactory() {
return new JedisConnectionFactory();
}
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//初始化序列化框架(Jackson2JsonRedisSerializer)
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer =
new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//初始化序列化框架(StringRedisSerializer)
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//设置key的序列化方式
template.setKeySerializer(stringRedisSerializer);
//设置value的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
//设置hash key的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//设置hash value的序列化方式
template.setHashValueSerializer(stringRedisSerializer);
return template;
}
}

数据格式存储

采用散列的数据格式来存储文章,格式如下:

  • key
    • article:××××××
  • hash value
    • subKey : title | subValue : ××××××
    • subKey : link | subValue : ××××××
    • subKey : poster | subValue : ××××××
    • subKey : time | subValue : ××××××
    • subKey : votes | subValue : ××××××

采用两个有序的集合来有序地存储文章:第一个有序集合的成员为文章ID,分值为文章的发布时间;第二个有序集合的成员同样为文章ID,而分值则为文章的评分。数据格式如下:

  • key
    • time:
  • zset value
    • member : article:×××××× | score : ××××××
  • key
    • score:
  • zset value
    • member : article:×××××× | score : ××××××

采用集合来为每篇文章记存储一个已投票的用户名单,数据格式如下:

  • key
    • vote:××××××
  • set value
    • ××××××

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public String postArticle( String poster, String title, String link){
//简单地获取文章自增ID
Long incrementId = redisTemplate.opsForValue().increment("article:", 1);
Long now = System.currentTimeMillis() / 1000;
String articleId = "article:" + incrementId;
Map<String, Object> map = new HashMap<>();
map.put("title", title);
map.put("link", link);
map.put("poster", poster);
map.put("time", String.valueOf(now));
map.put("votes", "1");
redisTemplate.opsForHash().putAll(articleId, map);
//新增文章投票记录,自动清除一周以前的投票记录
String voted = "voted:" + incrementId;
redisTemplate.opsForSet().add(voted, poster);
redisTemplate.expire(voted, ONE_WEEK_IN_SECONDS, TimeUnit.SECONDS);
//新增文章分数、发布时间排行榜
redisTemplate.opsForZSet().add("score:", articleId, now + VOTE_SCORE);
redisTemplate.opsForZSet().add("time:", articleId, now);
return articleId;
}
### 投票功能
为已经存在的文章投票
```java
public void voteArticle( String user, String articleId){
long cutoff = (System.currentTimeMillis() / 1000) - ONE_WEEK_IN_SECONDS;
//若文章超过一周则不再投票
if(redisTemplate.opsForZSet().score("time:", articleId) < cutoff){
return;
}
String id = articleId.substring(articleId.indexOf(':') + 1);
if(redisTemplate.opsForSet().add("voted:" + id, user) == 1){
/**
* sadd返回1,表示投票成功
* 分数+VOTE_SCORE,投票数+1
*/
redisTemplate.opsForZSet().incrementScore("score:",articleId,VOTE_SCORE);
redisTemplate.opsForHash().increment(articleId, "votes", 1);
}
}

注:上述opsForSet().add、opsForZSet().incrementScore、opsForHash().increment应该在同一个事物中,后面再讲Redis的事物,暂时先就这样

取出评分最高以及最新发布的文章

1
2
3
4
5
6
7
public List<Map<String,String>> getArticles(int page, String key) {
int start = (page - 1) * ARTICLES_PER_PAGE;
int end = start + ARTICLES_PER_PAGE - 1;
Set<Object> ids = redisTemplate.opsForZSet().reverseRange(key, start, end);
return getArticlesByIds(ids);
}

对文章进行分组

采用一个集合来存储同一组的文章

1
2
3
4
public void addGroups(String groupId, String articleId) {
String article = "article:" + articleId;
redisTemplate.opsForSet().add("group:" + groupId, articleId);
}

按组查询文章

1
2
3
4
public List<Map<String,String>> getGroupArticles(String groupId) {
Set<Object> ids = redisTemplate.opsForSet().members(groupId.toString());
return getArticlesByIds(ids);
}

本章实践了下几种数据结构的用法,对redisTemplate还不太熟悉,有待进一步研究。