Redis(Remote Dictionary Server)是一款开源的、基于内存的数据结构存储系统,常用于构建高性能、可扩展的应用程序。
而缓存是 Redis 最常见的应用场景之一。将经常被访问的数据(如数据库查询结果、热门文章内容等)存储在 Redis 中,下次请求时直接从 Redis 中获取,减少对后端数据源(如数据库)的访问压力,提升系统整体性能。例如在新闻资讯类网站,将热门新闻详情缓存到 Redis,大量用户浏览时能快速响应。
但是Redis在Java盛行可不只是做缓存这一种功能的实现,他还有其他的功能也都可以实现,下面我们就来用Redis去完成几个其他的功能实现。
在实现点赞功能之前,我先讲一下实现原理,以便我们可以可以更好的去理解程序
数据存储选择:
post:{post_id}:likes(这里post_id是具体朋友圈动态的编号),而这个键对应的值(value)就是点赞的数量,以字符串形式存储的数字。通过 Redis 的INCR(自增)和DECR(自减)等命令可以方便地对这个存储点赞数的字符串值进行原子操作,实现点赞数的增加或减少。"post:12345:likes" : "0",当有用户点赞时,执行INCR命令后就变为"post:12345:likes" : "1",后续再有点赞操作就持续自增。post:{post_id}:liked_users,将点赞用户的唯一标识(比如用户 ID)添加到这个集合中。利用集合的特性,可以方便地判断某个用户是否已经点赞(通过SISMEMBER命令查看元素是否在集合中),也能方便地统计点赞用户的数量(通过SCARD命令获取集合元素个数)等操作。post_id为 12345 的朋友圈动态进行了点赞,那么在 Redis 中对应的集合可能是"post:12345:liked_users",其集合元素有"user1", "user2"。原子操作特性
INCR、DECR这些命令在执行时不会被其他客户端的命令打断。在高并发的场景下(例如很多用户同时对某条朋友圈动态进行点赞或者取消点赞操作),这种原子性能够保证点赞数的计算准确无误。 INCR命令是原子的,它能确保无论多少个并发请求,都会准确地逐个增加点赞数,不会出现数据不一致的情况。现在pom.xml里面集成redis的客户端jedis
<!-- 添加jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.7.0</version>
</dependency>写一个代码示例:
package com.lcyy;
import redis.clients.jedis.Jedis;
public class WeChatMomentsLikesCounter {
private Jedis jedis;
public WeChatMomentsLikesCounter() {
// 连接Redis服务器,这里假设Redis在本地运行,端口为6379,可根据实际情况修改
this.jedis = new Jedis("localhost", 6379);
}
// 点赞操作
public void likePost(String postId) {
String key = "post:" + postId + ":likes";
jedis.incr(key);
}
// 取消点赞操作
public void unlikePost(String postId) {
String key = "post:" + postId + ":likes";
jedis.decr(key);
}
// 获取点赞数
public long getLikesCount(String postId) {
String key = "post:" + postId + ":likes";
String count = jedis.get(key);
if (count == null) {
return 0;
}
return Long.parseLong(count);
}
// 关闭Redis连接
public void close() {
if (jedis!= null) {
jedis.close();
}
}
public static void main(String[] args) {
WeChatMomentsLikesCounter counter = new WeChatMomentsLikesCounter();
// 模拟点赞操作
counter.likePost("12345");
counter.likePost("12345");
// 获取点赞数并打印
long likesCount = counter.getLikesCount("12345");
System.out.println("点赞数: " + likesCount);
// 模拟取消点赞操作
counter.unlikePost("12345");
// 再次获取点赞数并打印
likesCount = counter.getLikesCount("12345");
System.out.println("点赞数: " + likesCount);
// 关闭Redis连接
counter.close();
}
}在redis的客户端可以看到实现点赞功能

ZADD命令。这个命令会将指定的成员(玩家 ID)及其对应的分数插入到有序集合中,同时按照分数对整个集合进行重新排序(如果有需要),以确保集合始终保持有序状态。ZADD game_ranking 120 player4,就是将玩家 4(ID 为 “player4”)及其得分 120 添加到 “game_ranking” 这个有序集合中,集合会自动根据新插入的元素调整排序顺序。ZRANGE game_ranking 0 2会返回 “game_ranking” 有序集合中排名前 3(索引 0 到 2)的玩家 ID,结合后续使用ZSCORE命令获取对应分数,就能完整呈现排行榜前几名的情况。ZINCRBY命令。这个命令会按照指定的增量去改变指定成员(玩家 ID)的分数,然后同样会根据更新后的分数对有序集合进行重新排序,确保排行榜始终反映最新的得分情况。ZINCRBY game_ranking 20 player1后,玩家 1 的得分变为 120,并且 “game_ranking” 有序集合会自动调整排序,将玩家 1 放在新的合适位置上。package com.lcyy;
import redis.clients.jedis.Jedis;
import java.util.Set;
import java.util.TreeMap;
public class LeaderboardExample {
private Jedis jedis;
public LeaderboardExample() {
// 连接Redis服务器,这里假设Redis在本地运行,端口为6379,可根据实际情况修改
this.jedis = new Jedis("localhost", 6379);
}
// 添加玩家得分到排行榜
public void addPlayerScore(String playerId, double score) {
String key = "game_ranking";
jedis.zadd(key, score, playerId);
}
// 获取排行榜前N名玩家信息
public TreeMap<Double, String> getRanking(int topN) {
String key = "game_ranking";
Set<String> playerIds = jedis.zrange(key, 0, topN - 1);
TreeMap<Double, String> ranking = new TreeMap<>();
for (String playerId : playerIds) {
double score = jedis.zscore(key, playerId);
ranking.put(score, playerId);
}
return ranking;
}
// 更新玩家得分
public void updatePlayerScore(String playerId, double newScore) {
String key = "game_ranking";
jedis.zincrby(key, newScore, playerId);
}
// 关闭Redis连接
public void close() {
if (jedis!= null) {
jedis.close();
}
}
public static void main(String[] args) {
LeaderboardExample leaderboard = new LeaderboardExample();
// 添加玩家得分
leaderboard.addPlayerScore("player1", 100.0);
leaderboard.addPlayerScore("player2", 150.0);
leaderboard.addPlayerScore("player3", 80.0);
// 获取排行榜前3名玩家信息
TreeMap<Double, String> ranking = leaderboard.getRanking(3);
System.out.println("排行榜前3名:");
for (Double score : ranking.keySet()) {
System.out.println("玩家ID: " + ranking.get(score) + ", 得分: " + score);
}
// 更新玩家得分
leaderboard.updatePlayerScore("player1", 120.0);
// 再次获取排行榜前3名玩家信息
ranking = leaderboard.getRanking(3);
System.out.println("更新后排行榜前3名:");
for (Double score : ranking.keySet()) {
System.out.println("玩家ID: " + ranking.get(score) + ", 得分: " + score);
}
// 关闭Redis连接
leaderboard.close();
}
}
在客户端工具也可以看到

Redis 在布隆过滤器中的角色
SETBIT和GETBIT等命令来操作这些比特位。例如,把一个元素经过哈希函数计算后对应到 Redis 字符串表示的位数组中的第 10 位,若要添加该元素到布隆过滤器,就可以使用SETBIT命令将这个第 10 位的比特位设置为 1;当要判断元素是否存在时,通过GETBIT命令获取对应比特位的值进行判断。bloom_filter的字符串用来存储布隆过滤器的位数组,初始时所有比特位都为 0,元素"apple"经过两个哈希函数计算后对应到第 5 位和第 12 位,那么添加"apple"时,就会分别执行SETBIT bloom_filter 5 1和SETBIT bloom_filter 12 1这两个命令,将相应比特位设置为 1。在代码实现之前首先需要加入一个Guaua框架
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.2-jre</version>
</dependency>运行代码
import com.google.common.hash.BloomFilter;
import com.google.common.hash.Funnels;
import com.google.common.hash.HashFunction;
import com.google.common.hash.Hashing;
import redis.clients.jedis.Jedis;
import java.nio.charset.StandardCharsets;
public class RedisBloomFilter {
private static final String BLOOM_FILTER_KEY = "bloom_filter";
private Jedis jedis;
private BloomFilter<String> bloomFilter;
private HashFunction hashFunction;
// 初始化布隆过滤器,设置预计元素数量和误判率
public RedisBloomFilter(int expectedInsertions, double falsePositiveRate) {
this.jedis = new Jedis("localhost", 6379);
this.bloomFilter = BloomFilter.create(Funnels.stringFunnel(StandardCharsets.UTF_8),
expectedInsertions, falsePositive率);
this.hashFunction = Hashing.murmur3_128(); // 这里选择了murmur3_128哈希函数,可根据需要更换
}
// 计算元素的哈希值(用于在Redis中定位比特位)
private long hash(String element) {
return hashFunction.hashBytes(element.getBytes(StandardCharsets.UTF_8)).asLong();
}
// 将元素添加到布隆过滤器(同时添加到Redis和本地布隆过滤器实例)
public void add(String element) {
if (!contains(element)) {
bloomFilter.put(element);
jedis.setbit(BLOOM_FILTER_KEY, hash(element), true);
}
}
// 检查元素是否可能存在于布隆过滤器中(先在本地布隆过滤器实例检查,再在Redis中检查)
public boolean contains(String element) {
if (!bloomFilter.mightContain(element)) {
return false;
}
return jedis.getbit(BLOOM_FILTER_KEY, hash(element)) == true;
}
// 关闭Redis连接
public void close() {
if (jedis!= null) {
jedis.close();
}
}
public static void main(String[] args) {
// 初始化布隆过滤器,预计插入1000个元素,误判率为0.01
RedisBloomFilter bloomFilter = new RedisBloomFilter(1000, 0.01);
// 添加元素到布隆过滤器
bloomFilter.add("element1");
bloomFilter.add("element2");
// 检查元素是否存在
System.out.println("element1是否存在: " + bloomFilter.contains("element1"));
System.out.println("element2是否存在: " + bloomFilter.contains("element2"));
System.out.println("element3是否存在: " + bloomFilter.contains("element3"));
// 关闭Redis连接
bloomFilter.close();
}
}