后端程序员,不管是出去面试,还是当面试官,Redis几乎是100%会问到的技术点;究其原因,主要是因为他实在过于强大、使用率太高了;导致项目中几乎无处不在。
那Redis部分,不出意外,第一个问题就是:你做的项目,用Redis干了些啥?大部分人的回答都会是:缓存;当问到是否还有其他场景中使用时,部分用的少的朋友就会微微摇头;
其实也没错,Redis绝不部分使用场景就是用来做缓存;但是,由于Redis 支持比较丰富的数据结构,因此他能实现的功能并不仅限于缓存,而是可以运用到各种业务场景中,开发出既简洁、又高效的系统;
下面整理了20种 Redis 的妙用场景,每个方案都用一个实际的业务需求并结合数据结构的API来讲解,希望大家能够理解其底层的实现方式,学会举一反三,并运用到项目的方方面面:
1、缓存
本文假定你已经了解过Redis,并知晓Redis最基础的一些使用,如果你对Redis的基础API还不了解,可以先看一下菜鸟教程:https://www.runoob.com/redis,那么缓存部分及基础API的演示,就不过多来讲解了;
但是,基本的数据结构,在这里再列举一下,方便后续方案的理解:
结构类型 | 结构存储的值 | 结构的读写能力 |
---|---|---|
String字符串 | 可以是字符串、整数或浮点数 | 对整个字符串或字符串的一部分进行操作;对整数或浮点数进行自增或自减操作; |
List列表 | 一个链表,链表上的每个节点都包含一个字符串 | 对链表的两端进行push和pop操作,读取单个或多个元素;根据值查找或删除元素; |
Set集合 | 包含字符串的无序集合 | 字符串的集合,包含基础的方法有:看是否存在、添加、获取、删除;还包含计算交集、并集、差集等 |
Hash散列 | 包含键值对的无序散列表 | 包含方法有:添加、获取、删除单个元素 |
Zset有序集合 | 和散列一样,用于存储键值对 | 字符串成员与浮点数分数之间的有序映射;元素的排列顺序由分数的大小决定;包含方法有:添加、获取、删除单个元素以及根据分值范围或成员来获取元素 |
-
依赖以下所有通过SpringBoot测试的用例,都需要引入 Redis 的依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
2、抽奖
曾几何时,抽奖是互联网APP热衷的一种推广、拉新的方式,节假日没有好的策划,那就抽个奖吧!一堆用户参与进来,然后随机抽取几个幸运用户给予实物/虚拟的奖品;此时,开发人员就需要写上一个抽奖的算法,来实现幸运用户的抽取;其实我们完全可以利用Redis的集合(Set),就能轻松实现抽奖的功能;
功能实现需要的API
-
SADD key member1 [member2]:添加一个或者多个参与用户; -
SRANDMEMBER KEY [count]:随机返回一个或者多个用户; -
SPOP key:随机返回一个或者多个用户,并删除返回的用户;
SRANDMEMBER 和 SPOP 主要用于两种不同的抽奖模式,SRANDMEMBER 适用于一个用户可中奖多次的场景(就是中奖之后,不从用户池中移除,继续参与其他奖项的抽取);而 SPOP 就适用于仅能中一次的场景(一旦中奖,就将用户从用户池中移除,后续的抽奖,就不可能再抽到该用户); 通常 SPOP 会用的会比较多。
Redis-cli 操作
127.0.0.1:6379> SADD raffle user1 (integer) 1 127.0.0.1:6379> SADD raffle user2 user3 user4 user5 user6 user7 user8 user9 user10 (integer) 9 127.0.0.1:6379> SRANDMEMBER raffle 2 1) "user5" 2) "user2" 127.0.0.1:6379> SPOP raffle 2 1) "user3" 2) "user4" 127.0.0.1:6379> SPOP raffle 2 1) "user10" 2) "user9"
SpringBoot 实现
import lombok.extern.slf4j.Slf4j; 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.RedisTemplate; import java.util.List; /** * @title: RaffleMain * @projectName ehang-spring-boot * @description: TODO * @date 2022/7/18 15:17 */ @Slf4j @SpringBootTest public class RaffleMain { private final String KEY_RAFFLE_PROFIX = "raffle:"; @Autowired RedisTemplate redisTemplate; @Test void test() { Integer raffleId = 1; join(raffleId, 1000, 1001, 2233, 7890, 44556, 74512); List lucky = lucky(raffleId, 2); log.info("活动:{} 的幸运中奖用户是:{}", raffleId, lucky); } public void join(Integer raffleId, Integer... userIds) { String key = KEY_RAFFLE_PROFIX + raffleId; redisTemplate.opsForSet().add(key, userIds); } public List lucky(Integer raffleId, long num) { String key = KEY_RAFFLE_PROFIX + raffleId; // 随机抽取 抽完之后将用户移除奖池 List list = redisTemplate.opsForSet().pop(key, num); // 随机抽取 抽完之后用户保留在池子里 //List list = redisTemplate.opsForSet().randomMembers(key, num); return list; } }
3、Set实现点赞/收藏功能
有互动属性APP一般都会有点赞/收藏/喜欢等功能,来提升用户之间的互动。
传统的实现:用户点赞之后,在数据库中记录一条数据,同时一般都会在主题库中记录一个点赞/收藏汇总数,来方便显示;
Redis方案:基于Redis的集合(Set),记录每个帖子/文章对应的收藏、点赞的用户数据,同时set还提供了检查集合中是否存在指定用户,用户快速判断用户是否已经点赞过
功能实现需要的API
-
SADD key member1 [member2]:添加一个或者多个成员(点赞) -
SCARD key:获取所有成员的数量(点赞数量) -
SISMEMBER key member:判断成员是否存在(是否点赞) -
SREM key member1 [member2] :移除一个或者多个成员(点赞数量)
Redis-cli API操作
127.0.0.1:6379> sadd like:article:1 user1 (integer) 1 127.0.0.1:6379> sadd like:article:1 user2 (integer) 1 # 获取成员数量(点赞数量) 127.0.0.1:6379> SCARD like:article:1 (integer) 2 # 判断成员是否存在(是否点在) 127.0.0.1:6379> SISMEMBER like:article:1 user1 (integer) 1 127.0.0.1:6379> SISMEMBER like:article:1 user3 (integer) 0 # 移除一个或者多个成员(取消点赞) 127.0.0.1:6379> SREM like:article:1 user1 (integer) 1 127.0.0.1:6379> SCARD like:article:1 (integer) 1
SpringBoot 操作
import lombok.extern.slf4j.Slf4j; 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.RedisTemplate; /** * @author 一行Java * @title: LikeMain * @projectName ehang-spring-boot * @description: TODO * @date 2022/7/18 15:38 */ @Slf4j @SpringBootTest public class LikeMain { private final String KEY_LIKE_ARTICLE_PROFIX = "like:article:"; @Autowired RedisTemplate redisTemplate; @Test void test() { long articleId = 100; Long likeNum = like(articleId, 1001, 1002, 2001, 3005, 4003); unLike(articleId, 2001); likeNum = likeNum(articleId); boolean b2001 = isLike(articleId, 2001); boolean b3005 = isLike(articleId, 3005); log.info("文章:{} 点赞数量:{} 用户2001的点赞状态:{} 用户3005的点赞状态:{}", articleId, likeNum, b2001, b3005); } /** * 点赞 * * @param articleId 文章ID * @return 点赞数量 */ public Long like(Long articleId, Integer... userIds) { String key = KEY_LIKE_ARTICLE_PROFIX + articleId; Long add = redisTemplate.opsForSet().add(key, userIds); return add; } public Long unLike(Long articleId, Integer... userIds) { String key = KEY_LIKE_ARTICLE_PROFIX + articleId; Long remove = redisTemplate.opsForSet().remove(key, userIds); return remove; } public Long likeNum(Long articleId) { String key = KEY_LIKE_ARTICLE_PROFIX + articleId; Long size = redisTemplate.opsForSet().size(key); return size; } public Boolean isLike(Long articleId, Integer userId) { String key = KEY_LIKE_ARTICLE_PROFIX + articleId; return redisTemplate.opsForSet().isMember(key, userId); } }
文章评论