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 指令实测
> 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指令实测
> 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指令实测
> 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指令实测
> 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指令实测
> 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文件引入
<dependency>
<groupid>org.springframework.boot</groupid>
<artifactid>spring-boot-starter-data-redis</artifactid>
</dependency>
3.2 redisutil工具类实现
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的底层数据结构,数据存储原理,本文没有详细叙述,可参考文献部分,写的都十分详细。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论