Redis 是一个高性能的键值存储系统,支持多种数据结构。每种数据结构都有其独特的特点和适用场景。
1、String(字符串)
(1)、特点
- 最简单的数据类型:字符串是最基本的数据类型,可以存储字符串、整数或浮点数。最大长度为 512 MB。
- 支持原子操作:Redis 提供了多种原子操作,如递增、递减、追加字符串等。
- 持久化支持:字符串可以持久化到磁盘,确保数据不会因服务器重启而丢失。
(2)、适用场景
- 缓存:用于缓存网页内容、API 响应等。
- 计数器:用于实现计数器功能,如点赞数、访问量等。
- 会话管理:用于存储用户会话信息,如登录状态、购物车等。
(3)、redis-cli示例
1、设置键 “name” 的值为 “Alice”
SET name “Alice”
2、获取键 “name” 的值
GET name
3、递增键 “counter” 的值
INCR counter
4、递减键 “counter” 的值
DECR counter
5、追加字符串到键 “message”
APPEND message " Hello, World!"
(4)、Java示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class StringService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public void setString(String key, String value) {
redisTemplate.opsForValue().set(key, value); // 设置key
}
public String getString(String key) {
return redisTemplate.opsForValue().get(key); // 获取key的值
}
public Long incrementCounter(String key) {
return redisTemplate.opsForValue().increment(key); // 自增某key的值
}
public Long decrementCounter(String key) {
return redisTemplate.opsForValue().decrement(key); // 自减某key的值
}
public void appendString(String key, String value) {
redisTemplate.opsForValue().append(key, value); // 追加某key的值
}
}
2、List(列表)
双端链表,可以从两端插入和删除元素。适用于队列和栈的实现。
(1)、特点
- 双向链表:列表是一个双向链表,支持从两端插入和删除元素。可以在列表的头部(左端)或尾部(右端)进行操作。
- 先进先出(FIFO)和后进先出(LIFO):可以通过不同的命令实现队列(FIFO)或栈(LIFO)的行为。
- 阻塞操作:Redis 提供了阻塞式命令,如 BLPOP 和 BRPOP,可以在没有元素时阻塞等待,适用于消息队列场景。
解释一下如BLPOP的阻塞行为:
1、会阻塞其他客户端的请求吗?
答案:不会,其他客户端可以继续正常访问 Redis。
简单说:redis虽然只有一个线程处理命令,但它内部不是使用悲观锁处理数据安全问题,而是采取乐观锁的思路去解决的。对于暂时阻塞的请求可以迅速得到阻塞结果,将阻塞请求挂起,继续执行其他客户端的请求。所以可以同时处理多个客户端的请求。
具体解释:
(1)、Redis 使用单线程来处理所有命令的执行。这意味着在同一时间点上,Redis 只能处理一个命令。然而,Redis 的设计非常高效,能够快速处理每个命令,即使是阻塞的命令也会迅速快得到阻塞的结果,因此在大多数情况下,用户感觉不到延迟。
(2)、Redis是使用多路复用(multiplexing)技术来管理多个客户端连接。每个客户端的请求都被放入事件队列中,Redis 会依次处理这些请求。
(3)、Redis处理事件队列中请求的逻辑是使用了非阻塞 I/O 和事件循环(epoll、kqueue 等),这使得它可以在等待当前任务 I/O 操作完成时,同时继续处理其他其他客户端的请求。
(4)、因此,即使一个客户端被 BLPOP 阻塞,Redis 仍然可以处理来自其他客户端的请求。被 BLPOP 阻塞的请求会被暂时挂起,直到获取到元素或超时。
2、会阻塞当前客户端之后的请求吗?
答案:会,该客户端之后的请求将无法在同一连接上继续执行。即一个客户端的一个连接是一个通道,如果通道内阻塞则会阻塞。但是同一个客户端可以和redis建立多个连接。
解释:
因为在 Redis 的单线程模型中,每个客户端连接是串行处理的,即在一个连接上,Redis 会等待当前命令(如 BLPOP)完成或超时后,才会处理该连接上的下一个命令。
但如果是同一个客户端的其他连接则不会被阻塞。
3、如果阻塞的连接断开了,阻塞的请求会怎样?
答案:请求会立即取消,即使是阻塞的请求。
解释:
当 Redis 客户端与 Redis 服务器之间的连接断开时,所有正在该连接上执行的命令(包括阻塞命令如 BLPOP、BRPOP、BZPOPMIN 等)都会被中断。
即:Redis 服务器会立即取消该连接上的所有未完成的命令,包括正在阻塞的命令(如 BLPOP
)。这意味着 Redis 不会继续等待这些命令的结果,而是会立即释放与该连接相关的所有资源,包括内存中的临时数据结构(如阻塞队列中的等待状态)。因此,一旦连接断开,Redis 不会保留任何与该连接相关的信息。
(2)、适用场景
- 消息队列:用于实现任务队列、消息队列等。
- 历史记录:用于存储用户的操作历史、聊天记录等。
- 最近使用列表:用于实现 LRU(Least Recently Used)缓存机制。
(3)、redis-cli示例
向列表 “queue1” 的尾部添加元素
RPUSH queue1 task1
向列表 “queue1” 的头部添加元素
LPUSH queue1 task0
从列表 “queue1” 的头部取出元素
LPOP queue1
从列表 “queue1” 的尾部取出元素
RPOP queue1
获取列表 “queue1” 的所有元素 // redis的列表,都是0作为第一个元素的索引
LRANGE queue1 0 -1
阻塞式从列表 “queue” 的头部取出元素(超时 5 秒)
BLPOP queue1 5
(4)、Java示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class ListService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final ListOperations<String, Object> listOps;
public ListService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.listOps = redisTemplate.opsForList(); // 指定list命令
}
public void pushToListRight(String key, Object value) {
listOps.rightPush(key, value); // 队尾添加value
}
public void pushToListLeft(String key, Object value) {
listOps.leftPush(key, value); // 队首添加value
}
public Object popFromListLeft(String key) {
return listOps.leftPop(key); // 队首取出一个元素
}
public Object popFromListRight(String key) {
return listOps.rightPop(key); // 队尾取出一个元素
}
public List<Object> rangeFromList(String key, long start, long end) {
return listOps.range(key, start, end); // 查询队列(0,-1)为查询全部
}
public Object blockPopFromListLeft(String key, long timeout, TimeUnit unit) {
return listOps.leftPop(key, timeout, unit); // 阻塞获取元素,设置超时时间
}
}
3、Set(集合)
(1)、特点
- 无序集合:集合是一个无序的集合,不允许重复元素。每个元素是唯一的。
- 高效去重:集合非常适合用于去重操作,如获取多个集合的交集、并集、差集等。
- 成员检查:可以快速检查某个元素是否存在于集合中。
(2)、适用场景
- 去重:用于去除重复数据,如用户好友列表、标签系统等。
- 唯一性验证:用于确保某个元素在集合中只出现一次,如黑名单、白名单等。
- 集合运算:用于执行集合的交集、并集、差集等操作,如推荐系统中的共同爱好的好友等。
(3)、redis-cli示例
向集合 “users” 添加元素 “Alice”
SADD users Alice
检查元素 “Alice” 是否存在于集合 “users”
SISMEMBER users Alice
获取集合 “users” 中的所有元素
SMEMBERS users
从集合 “users” 中随机移除一个元素
SPOP users
获取两个集合 “users” 和 “admins” 的交集
SINTER users admins
获取两个集合 “users” 和 “admins” 的并集
SUNION users admins
获取两个集合 “users” 和 “admins” 的差集 // 即前者独有的元素有哪些
SDIFF users admins
(4)、Java示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class SetService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final SetOperations<String, Object> setOps;
public SetService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.setOps = redisTemplate.opsForSet(); // 指定set操作
}
public void addElementToSet(String key, Object... values) {
setOps.add(key, values); // 添加元素
}
public boolean isMemberOfSet(String key, Object value) {
return setOps.isMember(key, value); // 是否包含value元素
}
public Set<Object> getMembersOfSet(String key) {
return setOps.members(key); // 获取set集合
}
public Object popRandomElementFromSet(String key) {
return setOps.pop(key); // 随机取set中的一个元素
}
public Set<Object> intersectSets(String... keys) {
return setOps.intersect(keys); // 交集
}
public Set<Object> unionSets(String... keys) {
return setOps.union(keys); // 并集
}
public Set<Object> differenceSets(String... keys) {
return setOps.difference(keys); // 差集
}
}
4、Sorted Set(有序集合)
(1)、特点
- 带分数的集合:有序集合是一个带分数的集合,每个元素都有一个关联的分数(score),根据分数进行排序。
- 范围查询:可以基于分数或排名进行范围查询,如获取前 N 个最高分的用户。
- 高效插入和删除:有序集合支持高效的插入、删除和查找操作,适用于需要排序的场景。
(2)、适用场景
- 排行榜:用于实现排行榜功能,如游戏得分榜、热门文章排行等。
- 时间序列数据:用于存储带有时间戳的数据,如日志、事件流等。
- 优先级队列:用于实现优先级队列,如任务调度系统。
(3)、redis-cli示例
向有序集合 “leaderboard” 添加元素 “Alice”,分数为 100
ZADD leaderboard 100 Alice
获取有序集合 “leaderboard” 中排名前 3 的元素
ZRANGE leaderboard 0 2 WITHSCORES
获取有序集合 “leaderboard” 中分数在 50 到 150 之间的元素
ZRANGEBYSCORE leaderboard 50 150 WITHSCORES
获取元素 “Alice” 在有序集合 “leaderboard” 中的排名
ZRANK leaderboard Alice
递增元素 “Alice” 的分数
ZINCRBY leaderboard 10 Alice
(4)、Java示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class SortedSetService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final ZSetOperations<String, Object> zSetOps;
public SortedSetService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.zSetOps = redisTemplate.opsForZSet(); // 指定zset操作
}
public void addElementToSortedSet(String key, Object value, double score) {
zSetOps.add(key, value, score); // 添加元素和排名
}
public Set<ZSetOperations.TypedTuple<Object>> rangeByScore(String key, double min, double max) {
return zSetOps.rangeByScoreWithScores(key, min, max); // 获取指定区间的排名
}
public Set<Object> rangeByRank(String key, long start, long end) {
return zSetOps.range(key, start, end);
}
public Long getRankInSortedSet(String key, Object value) {
return zSetOps.rank(key, value);
}
public Double incrementScore(String key, Object value, double increment) {
return zSetOps.incrementScore(key, value, increment);
}
}
5、Hash(哈希表)
(1)、特点
- 键值对集合:哈希是一个键值对的集合,非常适用于存储对象。每个哈希字段(field)对应一个值(value),并且可以独立操作。
- 高效存储:哈希适合存储复杂的对象结构,且占用较少的内存空间。
- 原子操作:可以对哈希中的字段进行原子操作,如增加、删除、获取单个字段或多字段。
(2)、适用场景
- 对象存储:用于存储对象的属性,如用户信息、商品详情等。
- 配置管理:用于存储应用程序的配置项。
- 会话管理:用于存储用户的会话信息,如登录状态、权限等。
(3)、redis-cli示例
1、设置哈希 “user:1001” 的字段 “name” 为 “Alice”
HSET user:1001 name “Alice”
2、获取哈希 “user:1001” 的字段 “name”
HGET user:1001 name
3、获取哈希 “user:1001” 的所有字段和值
HGETALL user:1001
4、删除哈希 “user:1001” 的字段 “age”
HDEL user:1001 age
5、检查哈希 “user:1001” 是否存在字段 “name”
HEXISTS user:1001 name
(4)、Java示例
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@Service
public class HashService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private final HashOperations<String, String, Object> hashOps;
public HashService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.hashOps = redisTemplate.opsForHash(); // 指定hash命令操作
}
public void setHashField(String key, String field, Object value) {
hashOps.put(key, field, value); // 设置key对象的field属性为value值
}
public Object getHashField(String key, String field) {
return hashOps.get(key, field); // 获取key对象的field属性值
}
public Map<String, Object> getAllHashFields(String key) {
return hashOps.entries(key); // 获取key对象的全部属性值
}
public void deleteHashField(String key, String field) {
hashOps.delete(key, field); // 删除key对象的field属性
}
public boolean existsHashField(String key, String field) {
return hashOps.hasKey(key, field); // 校验key对象是否存在field属性
}
}
学海无涯苦作舟!!!