1.概述
Redis共有五种基本数据类型:String(字符串)、List(列表)、Hash(散列)、Set(集合)、ZSet(有序集合)。这些基础数据结构支持丰富的原子操作,底层通过内存压缩算法(如ziplist、intset)实现空间与速度的平衡。本文将详细介绍五种基础数据结构,帮助大家更好地理解Redis基础数据的使用及原理。
2.基本数据类型详解
2.1 String
String类型是Redis中使用最多的类型,key是唯一标识,value代表对应key存储的值,value可以是字符串、数字(整型或浮点数),value最多可以容纳的数据长度是512M。
2.1.1 String类型常用指令
| 命令 | 说明 |
|---|---|
| SET key value | 设置指定 key 的值 |
| GET key | 获取指定 key 的值 |
| MSET key1 value1 key2 value2 ...... | 批量设置key、value |
| MGET key1 key2 ... | 获取一个或多个指定 key 的值 |
| STRLEN key | 返回key所存储的字符串长度 |
| SETNX key value | key 不存在时设置 key 的值 |
| SETEX key seconds value | 设置key、value和过期时间 |
| INCR key | 将 key 中储存的数字值增一 |
| DECR key | 将 key 中储存的数字值减一 |
| APPEND key value | 向当前key的字符串后面追加字符串,key不存在相当于set key value |
| GETRANGE key start end | 截取key以start为起点,end为终点的字符串 |
| KEYS * | 获取当前数据库所有的key(通用指令) |
| EXISTS key | 判断指定 key 是否存在(通用指令) |
| DEL key | 删除key(通用指令) |
| GETSET key value | 先get后set |
2.1.2 指令实测
bash
> set books java
OK
> get books
java
> MSET name zhangsan age 18
OK
> mget name age
zhangsan
18
> STRLEN name
8
> SETNX name zhangsan
0
> SETEX address 30 beijing
OK
> TTL address
18
> INCR age
19
> INCR age
20
> GET age
20
> DECR age
19
> APPEND name ',hello world'
20
> GET name
zhangsan,hello world
> GETRANGE name 0 4
zhang
> GETRANGE name 0 3
zhan
> keys *
name
key1
count
books
key2
age
> EXISTS count
1
> del count
1
> GETSET db redis
null
> GET db
redis
2.1.3 应用场景
1.计数器
例如,可以用来记录网站的访问次数、用户登录次数等。
使用场景:使用 INCR 和 DECR 指令对计数器进行递增或递减操作,实现记录网站的访问次数、用户登录次数等。
2.缓存功能例如,存储用户信息、配置信息等。
使用场景:使用 SET 和 GET 指令实现简单的键值对缓存。
3.分布式锁例如:在分布式系统中确保某个操作在同一时间内只能由一个实例执行。
使用场景:使用 SET 指令的 NX(仅当键不存在时设置)和 EX(设置过期时间)选项实现分布式锁。
2.2 List
2.2.1 List类型常用指令
| 命令 | 说明 |
|---|---|
| LPUSH key value1 value2 ... | 在指定列表的头部(左边)添加一个或多个元素 |
| RPUSH key value1 value2 ... | 在指定列表的头部(右边)添加一个或多个元素 |
| LSET key index value | 将指定列表索引 index 位置的值设置为 value |
| LPOP key | 移除并获取指定列表的第一个元素(最左边) |
| RPOP key | 移除并获取指定列表的最后一个元素(最右边) |
| LLEN key | 获取列表元素数量 |
| LRANGE key start end | 获取列表 start 和 end 之间 的元素 |
2.2.2 List指令实测
bash
> LPUSH books java python go
3
> LLEN books
3
> LRANGE books 0 -1
go
python
java
> LPUSH books javascript
4
> LRANGE books 0 -1
javascript
go
python
java
> RPUSH books c
5
> LRANGE books 0 -1
javascript
go
python
java
c
> RPOP books
c
> LRANGE books 0 -1
javascript
go
python
java
> LPOP books
javascript
> LRANGE books 0 -1
go
python
java
> LINDEX books 0
go
> LINDEX books 2
java
> LSET books 2 javascript
OK
> LRANGE books 0 -1
go
python
javascript
2.2.3 使用场景
1.简单消息队列
例如,通过订阅同一个list的key实现消息队列等。
使用场景:通过LPUSH/RPOP或者RPUSH/LPOP可以实现简易消息队列,实现数据先进先出。
2.模拟栈实现例如,通过模拟栈先进后出。
使用场景:通过LPUSH/LPOP或者RPUSH/RPOP可以实现栈,实现数据后进先出,实现特点场景下的规则解析。
2.3 Hash
2.3.1 Hash类型常用指令
| 命令 | 说明 |
|---|---|
| HSET key field value | 设置指定哈希表中指定字段的值 |
| HGET key field | 获取指定哈希表中指定字段的值 |
| HMSET key field1 value1 field2 value2 ... | 同时设置一个或多个 field-value 到指定哈希表中 |
| HMGET key field1 field2 ... | 获取指定哈希表中一个或者多个指定字段的值 |
| HGETALL key | 获取指定哈希表中所有的键值对 |
| HEXISTS key field | 查看指定哈希表中指定的字段是否存在 |
| HDEL key field1 field2 ... | 删除一个或多个哈希表字段 |
| HLEN key | 获取指定哈希表中字段的数量 |
| HSETNX key field value | 当指定哈希表中的字段不存在时,才添加值 |
| HINCRBY key field increment | 对指定哈希中的指定字段做运算操作(正数为加,负数为减) |
2.3.2 Hash指令实测
bash
> HSET userInfo-1 name zhangsan
1
> HSET userInfo-1 age 18
1
> HSET userInfo-1 sex male
1
> HGET userInfo-1 name
zhangsan
> HMSET userInfo-2 name lisi age 20 sex female
OK
> HMGET userInfo-1 name age sex
zhangsan
18
male
> HGETALL userInfo-1
name
zhangsan
age
18
sex
male
> HEXISTS userInfo-1 name
1
> HDEL userInfo-1 sex
1
> HGETALL userInfo-1
name
zhangsan
age
18
> HLEN userInfo-1
2
> HSETNX userInfo-1 name zhangsan
0
> HINCRBY userInfo-1 age 5
23
> HGETALL userInfo-1
name
zhangsan
age
23
2.3.2 Hash使用场景
1.对象信息存储
例如,存储用户信息等。
使用场景:以信息标签+用户唯一id作为key,属性分别是作为field,这样相对于String存储的优势是提升了效率(string类型需要进行数据转换后才能获取到值)。
2.购物车功能例如,实现购物车功能
使用场景:将用户id作为key,商品id作为field,field的值为商品数量。
2.4 Set
2.4.1 Set类型常用指令
| 命令 | 说明 |
|---|---|
| SADD key member1 member2 ... | 向指定集合添加一个或多个元素 |
| SMEMBERS key | 获取指定集合中的所有元素 |
| SCARD key | 获取指定集合的元素数量 |
| SREM key memeber | 移除集合中的指定元素 |
| SISMEMBER key member | 判断指定元素是否在指定集合中 |
| SINTER key1 key2 ... | 获取给定所有集合的交集 |
| SINTERSTORE destination key1 key2 ... | 将给定所有集合的交集存储在 destination 中 |
| SUNION key1 key2 ... | 获取给定所有集合的并集 |
| SUNIONSTORE destination key1 key2 ... | 将给定所有集合的并集存储在 destination 中 |
| SDIFF key1 key2 ... | 获取给定所有集合的差集 |
| SDIFFSTORE destination key1 key2 ... | 将给定所有集合的差集存储在 destination 中 |
| SPOP key count | 随机移除并获取指定集合中一个或多个元素 |
| SRANDMEMBER key count | 随机获取指定集合中指定数量的元素 |
2.4.2 Set指令实测
bash
> sadd db oracle mysql sqlite
3
> SADD db mysql
0
> SMEMBERS db
oracle
mysql
sqlite
> SCARD db
3
> SISMEMBER db oracle
1
> SREM db mysql
1
> SMEMBERS db
oracle
sqlite
> SADD db1 mysql oracle hbase
3
> SINTERSTORE db2 db db1
1
> SMEMBERS db2
oracle
> SUNION db db1
oracle
sqlite
mysql
hbase
> SDIFF db db1
sqlite
> SRANDMEMBER db1 2
oracle
hbase
> SPOP db1 2
oracle
hbase
> SMEMBERS db1
mysql
2.4.3 Set使用场景
1.共同关注好友
例如,微博、B站等共同关注博主等。
使用场景:可以将A用户的关注博主、B用户的关注博主分别做一个Set集合,通过取并集获取共同关注对象。
2.获取随机值例如,实现某些具体场景随机值获取
使用场景:将用户id作为一个Set集合,通过SPOP指令随机获取用户id,实现抽奖等场景功能。
3.快速去重在某些应用中,可以使用有序集合来管理定时任务,其中任务的执行时间作为分数存储。
使用场景:获取网站UV数据,将网站域名作为key,用户唯一标识作为集合值,实现快速去重,获取当日、周该网站UV数据。
2.5 ZSet
2.5.1 ZSet类型常用指令
有序集合相对于Set增加了一个权重参数score,集合中的元素能够按照score进行有序排列,也可以按照score的范围来获取集合中的元素。
| 命令 | 说明 |
|---|---|
| ZADD key score1 member1 score2 member2 ... | 向指定有序集合中添加一个或多个元素 |
| ZSCORE key member | 获取有序集合中指定元素的score值 |
| ZCARD KEY | 获取指定有序集合的元素数量 |
| ZRANGEBYSCORE key min max [WITHSCORES] | 根据分数获取有序集合中元素 |
| ZREVRANK key member | 返回有序集中成员的排名,排名以0为底,分数值最大的成员排名为0 |
| ZLEXCOUNT key min max | 对于一个所有成员的分值都相同的有序集合键 key 来说, 这个命令会返回该集合中, 成员介于 min 和 max 范围内的元素数量。 |
| ZINTERSTORE destination numkeys key1 key2 ... | 将给定所有有序集合的交集存储在destination中,对相同元素对应的score值进行sum聚合操作,numkeys 为集合数量 |
| ZUNIONSTORE destination numkeys key1 key2 ... | 求并集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination |
| ZDIFFSTORE destination numkeys key1 key2 ... | 求差集,其中给定 key 的数量必须以 numkeys 参数指定,并将该并集(结果集)储存到 destination |
| ZRANGE key start end | 获取指定有序集合 start 和 end 之间的元素,score由低到高排序 |
| ZREVRANGE key start end | 获取指定有序集合 start 和 end 之间的元素,score由低到高排序 |
| ZREM key member | 移除有序集合中指定元素 |
2.5.3 ZSet指令实测
bash
> zadd salary 3000 zhangsan 4000 lisi 8000 wangwu
3
> ZSCORE salary zhangsan
3000
> ZADD salary 12000 zhaoliu
1
> ZCARD salary
4
> ZRANGEBYSCORE salary 3000 5000
zhangsan
lisi
> ZRANGEBYSCORE salary 3000 5000 WITHSCORES
zhangsan
3000
lisi
4000
> ZREVRANK salary zhaoliu
0
> ZREVRANK salary wangwu
1
> ZINTERSTORE salary2 2 salary salary1
1
> ZRANGE salary 0 -1 withscores
zhangsan
3000
lisi
4000
wangwu
8000
> ZRANGE salary2 0 -1 withscores
zhangsan
6000
> ZUNIONSTORE salary3 2 salary salary1
5
> ZRANGE salary3 0 -1 withscores
lisi
4000
tony
5000
tom
6000
zhangsan
6000
wangwu
8000
> ZDIFFSTORE salary4 2 salary salary1
2
> ZRANGE salary4 0 -1 withscores
lisi
4000
wangwu
8000
> ZREVRANGE salary 0 -1
wangwu
lisi
zhangsan
> ZREM salary zhangsan
1
2.5.3 ZSet使用场景
1.排行榜系统
例如,游戏中的玩家分数排行榜、视频网站上的视频点赞数排行榜等。
使用场景:可以实时更新分数,并利用 ZADD 命令添加或更新元素及其分数,使用 ZREVRANGE 或 ZREVRANGEBYSCORE 命令获取排名靠前的元素。
2.延迟消息队列使用有序集合存储消息及其延迟时间(以时间戳或相对延迟时间表示),然后通过 ZRANGEBYSCORE 命令获取当前时间之前的所有消息进行处理。
使用场景:可以确保消息按照预定的延迟时间被处理,非常适合需要延迟处理的场景。
3.定时任务调度在某些应用中,可以使用有序集合来管理定时任务,其中任务的执行时间作为分数存储。
使用场景:通过定期查询当前时间之前的任务并执行它们,可以实现一个简单的任务调度器。
3.代码实现
3.1 pom文件引入
java
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
3.2 RedisUtil工具类实现
java
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.data.redis.core.types.RedisClientInfo;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
private static final Logger LOG = LoggerFactory.getLogger(RedisUtil.class);
/**
* 默认过期时间,单位:秒,即,24个小时 后 过期
*/
public static final long DEFAULT_EXPIRE = 60 * 60 * 24;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource(name = "strRedisTemplate")
private RedisTemplate<String, String> stringRedisTemplate;
/**
* set 方法
*
* @param key key
* @param value value
*/
public void set(String key, Object value) {
set(key, value, null);
}
public void setWithDefaultExpire(String key, Object value) {
set(key, value, DEFAULT_EXPIRE);
}
/**
* @param key redis key
* @param value redis值
* @param expire 过期时间,秒
*/
public void set(String key, Object value, Long expire) {
if (expire != null && expire.longValue() != 0L) {
redisTemplate.boundValueOps(key).set(value, expire, TimeUnit.SECONDS);
} else {
redisTemplate.boundValueOps(key).set(value);
}
}
/**
* @param key redis key
* @param value redis值
* @param expire 过期时间,秒
*/
public void setString(String key, String value, Long expire) {
if (expire != null && expire.longValue() != 0L) {
stringRedisTemplate.boundValueOps(key).set(value, expire, TimeUnit.SECONDS);
} else {
stringRedisTemplate.boundValueOps(key).set(value);
}
}
public Double increment(String key, double score) {
return redisTemplate.opsForValue().increment(key, score);
}
/**
* 获取redis value
*
* @param key
* @return Object 对象
*/
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
/**
* 判断key是否存在
*
* @param key
* @return Set 集合
*/
public Set<String> keys(String key) {
return redisTemplate.keys(key);
}
public void deleteKeys(Set<String> keys) {
redisTemplate.delete(keys);
}
public String getString(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
public void delete(String key) {
redisTemplate.delete(key);
}
public void expire(String key, Long expire) {
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
}
public Boolean hsetAbsent(String key, String hkey, Object value) {
return redisTemplate.opsForHash().putIfAbsent(key, hkey, value);
}
public void hset(String key, String hkey, Object value) {
redisTemplate.opsForHash().put(key, hkey, value);
}
public void hmset(String key, Map<?, ?> hashMap) {
redisTemplate.opsForHash().putAll(key, hashMap);
}
public Long lpushString(String key, String val) {
return stringRedisTemplate.boundListOps(key).leftPush(val);
}
public String rpopString(String key) {
return stringRedisTemplate.boundListOps(key).rightPop();
}
public Long llen(String key) {
return stringRedisTemplate.boundListOps(key).size();
}
public Properties info() {
RedisConnection connection = null;
Properties p = null;
try {
connection = redisTemplate.getConnectionFactory().getConnection();
p = connection.info("memory");
} catch (Exception e) {
LOG.error("redis获取连接失败", e);
} finally {
if (connection != null) {
connection.close();
}
}
return p;
}
public String clients() {
List<RedisClientInfo> clientList = redisTemplate.getClientList();
return JSON.toJSONString(clientList);
}
/**
* 左边入队
*/
public Long lpush(String key, Object val) {
return redisTemplate.boundListOps(key).leftPush(val);
}
/**
* 右边出队
*/
public Object rpop(String key) {
return redisTemplate.boundListOps(key).rightPop();
}
/**
* 右边出队
*/
public String rpop(String key, Integer timeout) {
return stringRedisTemplate.opsForList().rightPop(key, timeout, TimeUnit.SECONDS);
}
public Object hget(String key, String hkey) {
return redisTemplate.opsForHash().get(key, hkey);
}
public String hgetStr(String key, String hkey) {
return (String) stringRedisTemplate.opsForHash().get(key, hkey);
}
public Long hdel(String key, String hkey) {
return redisTemplate.opsForHash().delete(key, hkey);
}
public void hincrement(String key, String hkey) {
redisTemplate.opsForHash().increment(key, hkey, 1);
}
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
public Double zscore(String key, Object val) {
return stringRedisTemplate.opsForZSet().score(key, val);
}
public boolean zadd(String key, String value, double score) {
return stringRedisTemplate.opsForZSet().add(key, value, score);
}
public Long zadd(String key, Set<ZSetOperations.TypedTuple<String>> tuples) {
return stringRedisTemplate.opsForZSet().add(key, tuples);
}
public void zaddObj(String key, Object value, double score) {
redisTemplate.opsForZSet().add(key, value, score);
}
public Long removeRangeByScoreObj(String key, double minScore, double maxScore) {
return redisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
}
public Long removeRangeByObj(String key, Object value) {
return redisTemplate.opsForZSet().remove(key, value);
}
public Long removeRangeByScoreStr(String key, double minScore, double maxScore) {
return stringRedisTemplate.opsForZSet().removeRangeByScore(key, minScore, maxScore);
}
public Double zStringScore(String key, Object value) {
return stringRedisTemplate.opsForZSet().score(key, value);
}
public Set<String> zrangeByScore(String key, double minScore, double maxScore) {
return stringRedisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
}
public Set<String> zreverseRangeByScore(String key, double minScore, double maxScore) {
return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore);
}
public Set<String> zrangeByScore(String key, double minScore, double maxScore, long offset, long count) {
return stringRedisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore, offset, count);
}
public Set<String> zreverseRangeByScore(String key, double minScore, double maxScore, long offset, long count) {
return stringRedisTemplate.opsForZSet().reverseRangeByScore(key, minScore, maxScore, offset, count);
}
public Set zrangeByScoreObj(String key, double minScore, double maxScore) {
return redisTemplate.opsForZSet().rangeByScore(key, minScore, maxScore);
}
public Long zrank(String key, String value) {
return stringRedisTemplate.opsForZSet().rank(key, value);
}
public void zremObj(String key, String member) {
redisTemplate.opsForZSet().remove(key, member);
}
public Long zremStr(String key, String member) {
return stringRedisTemplate.opsForZSet().remove(key, member);
}
public Map<Object, Object> hGetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
public Set<Object> hkeys(String key){
return redisTemplate.opsForHash().keys(key);
}
public List<Object> hValues(String key) {
return redisTemplate.opsForHash().values(key);
}
public Map<String, String> hGetAllConvertString(String key) {
Map<Object, Object> tmp = redisTemplate.opsForHash().entries(key);
return tmp != null ? convertToString(tmp) : null;
}
public Set<ZSetOperations.TypedTuple<Object>> rangeByScoreWithScores(String name, double min, double max, long offset, long count) {
return redisTemplate.opsForZSet().rangeByScoreWithScores(name, min, max, offset, count);
}
public Set<ZSetOperations.TypedTuple<String>> strRangeByScoreWithScores(String name, double min, double max, long offset, long count) {
return stringRedisTemplate.opsForZSet().rangeByScoreWithScores(name, min, max, offset, count);
}
public Set<String> strZRange(String key, int start, int end) {
return stringRedisTemplate.opsForZSet().range(key, start, end);
}
public Set<ZSetOperations.TypedTuple<String>> strZRangeWithScores(String key, int start, int end) {
return stringRedisTemplate.opsForZSet().rangeWithScores(key, start, end);
}
public Double strIncrementScore(String key, String value, double delta) {
return stringRedisTemplate.opsForZSet().incrementScore(key, value, delta);
}
public Double incrementScore(String key, String value, double score) {
return redisTemplate.opsForZSet().incrementScore(key, value, score);
}
public Set<String> members(String key) {
return stringRedisTemplate.opsForSet().members(key);
}
public boolean isMembers(String key, String value) {
return stringRedisTemplate.opsForSet().isMember(key, value);
}
public Set<String> sMembersStr(String key) {
return stringRedisTemplate.opsForSet().members(key);
}
public void sAddStr(String key, String value) {
stringRedisTemplate.opsForSet().add(key, value);
}
public void sAdd(String key, Object val) {
redisTemplate.opsForSet().add(key, val);
}
public String sPop(String key) {
return stringRedisTemplate.opsForSet().pop(key);
}
public void leftPush(String key, String value) {
stringRedisTemplate.boundListOps(key).leftPush(value);
}
public void sRemoveStr(String key, String value) {
stringRedisTemplate.opsForSet().remove(key, value);
}
public void publish(String channel, String value) {
stringRedisTemplate.convertAndSend(channel, value);
}
public static Map<String, String> convertToString(Map<Object, Object> map) {
Objects.requireNonNull(map);
Map<String, String> result = new ConcurrentHashMap<>(map.size());
map.forEach((key, value) -> result.put(key.toString(), value.toString()));
return result;
}
}
4.小结
1.本文主要讲解redis的基础数据类型和使用方式,同时实操指令,说明其使用场景;
2.本文利用JAVA语言实现了个Redis工具类,对RedisTemplate做了二次封装,供大家参考;
3.关于Redis的底层数据结构,数据存储原理,本文没有详细叙述,可参考文献部分,写的都十分详细。