string类型
类型介绍
在Redis中的所有的key都是string类型,而value的类型有多种。
Redis中的字符串是直接按照二进制的方式进行存储的,也就是不会做任何的编码转换,存的是什么,取出来的就是什么。这样一般来说,Redis遇见乱码的可能性会变小。因为这里会出现乱码就只跟存入端和读取端有关,而像mysql这样自己会做编码转换的,那么出现的乱码的原因还可能跟mysql自身的编码转换有关。
另外因为Redis存储字符串是按二进制存储的,所以不仅仅只可以存文本数据,
甚至还可以存图片,音频。
set/get
再看一下set的语法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
对于后面的选项语法格式:
[expiration EX seconds|PX milliseconds] 也就是在设置key的时候,同时设置超时时间,EX就是以秒级单位的,PX就是毫秒。
[NX|XX] NX:表示 如果key存在,那么就不设置 ,返回nil;如果不存在,那么才设置。
XX:表示 key存在,才设置(相当于更新key);如果key不存在,那么就不设置。
这样的额外选项可以减少网络轮次,在客户端-服务器这样的程序下还是很有用的。 并且对于一条命令来说,它的执行效果是原子的,而分开的话就不好说了。
用命令
flushall
可以进行删库操作,将所有的数据全部删除。这个操作非常危险,几乎不会用到。
mset/mget
mset:⼀次性设置多个 key 的值。
MSET key value [key value ...]
mget:⼀次性获取多个 key 的值。如果对应的 key 不存在或者对应的数据类型不是 string,返回 nil。
MGET key [key ...]
这里的时间复杂度官方给的是:O(N),不过这里的N跟我们之前说的一样,指的是key的数目。
这两个命令同样是为了减少请求的网络轮次的。
setnx/setex/psetex
这三个命令其实就是对set的一些常见用法进行了缩写。
incr/incrby/decr/decrby/incrbyfloat
incr只能针对一个整数使用,返回这个数加一后的值。相当于 ++i,其他的命令同理。
incrby可以针对value进行 + n的操作。并且这里的n可以是负数,如果是负数就相当于减法操作。
另外在Redis中的int是64位的。
为什么Redis明明可以使用一套命令就可以实现加减法,为什么要设计两套呢?这一点跟刚刚的setnx那些命令是一样的,是为了让Redis的使用更加符合人的直觉,从而降低用户的使用门槛。
另外,如果我们 incr/incrby的key不存在时,它会帮我们插入一个value为0的值,然后进行对应的加减操作。
剩下三个命令的使用原理跟incr和incrby差不多。这些命令的时间复杂度都是O(1)。
另外这个 incrbyfloat 中的float其实是按double的标准来的。
append
APPEND KEY VALUE
注意:append这里的返回值 的单位是字节。
如果我们想让Redis客户端能够自动的把二进制数据尝试翻译,那么在启动客户端的时候要加上一个 --raw的选项
可以看到,当我们启动时没有带 --raw时,我们查出来的汉字是十六进制显示的,其中每个十六进制前的 \x 是转义字符,表示这是一个十六进制。
getrange
GETRANGE key start end
在C++和Java中,谈到一个区间大多都是左闭右开的 [x,y) 。而Redis这里都是闭区间 [x,y]
注意:当我们存的值是汉字时,因为一个汉字占3个字节,所以使用这个切分的时候还需要考虑到这一点,不然切出来的可能是乱码
setrange
覆盖字符串的⼀部分,从指定的偏移开始。 如果从指定偏移位置覆盖的字符串超出了原本能覆盖的大小,那么多余的字符串会以追加的形式覆盖。
SETRANGE key offset value
注意:当value是一个中文字符串的时候,进行setrange是有可能出问题的。
另外,setrange对于不存在的key也是可以操作的, 不过会把 offset(偏移位置)之前的内容填充成:0x00。
strlen
获取 key 对应的 string 的⻓度。当 key 存放的类似不是 string 时,报错。
STRLEN key
string编码方式
不过不建议记住这样的数字,记住数字是没有意义的。
根据业务场景的不同,最佳的长度不一定是39字节,此时就需要自行修改这个数字的大小
关于修改:
另外,我们发现Redis存储整形会用int类型(8字节大小),但是存储小数其实是用embstr的,也就是说存储小数本质还是当作字符串来存储的。
这样的差别其实是很大的,因为对于整形 进行算术运算的时候是比较方便的,但是如果是字符串存储,那么进行算术运算的时候,要先将字符串转化为小数,然后再进行算术运算,最后还要再转回字符串,开销就大多了。
string的应用场景
作为缓存
作为缓存的思路:应用服务器访问数据时,先查询Redis,如果Redis上有这个数据,那么就直接返回给应用服务器,如果没有,那么就去MySQL中去查找,在MySQL中找到后再返回给应用服务器,同时会把这个数据写入到Redis中,这个写入到Redis中的值,作为key往往被设置了过期时间
Redis这样的缓存经常存储热点数据,也就是高频被访问的数据。这样能够加速查询和缓解MySQL服务器的压力。
伪代码模拟业务数据访问过程:
1.根据用户uid获取用户信息:
UserInfo getUserInfo(long uid) {
...
}
2.⾸先从 Redis 获取⽤⼾信息,我们假设⽤⼾信息保存在 "user:info:<uid>" 对应的键中:
// 根据 uid 得到 Redis 的键
String key = "user:info:" + uid;
// 尝试从 Redis 中获取对应的值
String value = Redis 执⾏命令:get key;
// 如果缓存命中(hit)
if (value != null) {
// 假设我们的⽤⼾信息按照 JSON 格式存储
UserInfo userInfo = JSON 反序列化(value);
return userInfo;
}
3.
// 如果缓存未命中(miss)
if (value == null) {
// 从数据库中,根据 uid 获取⽤⼾信息
UserInfo userInfo = MySQL 执⾏ SQL:select * from user_info where uid =
<uid>
// 如果表中没有 uid 对应的⽤⼾信息
if (userInfo == null) {
响应 404
return null;
}
// 将⽤⼾信息序列化成 JSON 格式
String value = JSON 序列化(userInfo);
// 写⼊缓存,为了防⽌数据腐烂(rot),设置过期时间为 1 ⼩时(3600 秒)
Redis 执⾏命令:set key value ex 3600
// 返回⽤⼾信息
return userInfo;
}
注意:
使⽤,可以省略业务名 "vs:"。如果键名过程,则可以使⽤团队内部都认同的缩写替代,例如
计数功能
Redis将播放量同步到其他数据源的方式是异步的。
不过Redis用作计数功能是挺不错的,但是让Redis统计数据就不行了,比如让Redis统计播放量前百的视频就很麻烦,如果让MySQL来的话一行sql就搞定了 (排序加limit 100)
共享会话
手机验证码
String 发送验证码(phoneNumber) {
key = "shortMsg:limit:" + phoneNumber;
// 设置过期时间为 1 分钟(60 秒)
// 使⽤ NX,只在不存在 key 时才能设置成功
bool r = Redis 执⾏命令:set key 1 ex 60 nx
if (r == false) {
// 说明之前设置过该⼿机的验证码了
long c = Redis 执⾏命令:incr key
if (c > 5) {
// 说明超过了⼀分钟 5 次的限制了
// 限制发送
return null;
}
}
// 说明要么之前没有设置过⼿机的验证码;要么次数没有超过 5 次
String validationCode = ⽣成随机的 6 位数的验证码();
validationKey = "validation:" + phoneNumber;
// 验证码 5 分钟(300 秒)内有效
Redis 执⾏命令:set validationKey validationCode ex 300;
// 返回验证码,随后通过⼿机短信发送给⽤⼾
return validationCode ;
}
// 验证⽤⼾输⼊的验证码是否正确
bool 验证验证码(phoneNumber, validationCode) {
validationKey = "validation:" + phoneNumber;
String value = Redis 执⾏命令:get validationKey;
if (value == null) {
// 说明没有这个⼿机的验证码记录,验证失败
return false;
}
if (value == validationCode) {
return true;
} else {
return false;
}
}
hash类型
类型介绍
基础命令 1:hset/hget/hexists/hdel
hset
设置 hash 中指定的字段(field)的值(value)。
HSET key field value [field value ...]
hget
获取 hash 中指定字段的值。
HGET key field
hexists
判断 hash 中是否有指定的字段。
HEXISTS key field
hdel
删除 hash 中指定的字段。
HDEL key field [field ...]
如果要删除所有字段,那么就是
del key
基础命令2:hkeys / hvals / hgetall
hkeys
获取 hash 中的所有字段。
HKEYS key
HVALS key
hgetall
获取 hash 中的所有字段以及对应的值。
HGETALL key
可以看到,查询出来的结果 是一对 field和value 的形式规律显示的。
上述的操作都是风险比较大的,数据量多大时就可能会导致Redis阻塞。
基础命令3:hmget / hlen / hsetnx / hincrby
hmget
⼀次获取 hash 中多个字段的值
HMGET key field [field ...]
对于hmget命令,同样也还有一个hmset命令,可以同时设置多个 filed value,但是没必要,因为hset本身就已经有这个功能了。
hlen
HLEN key
hsetnx
在字段不存在的情况下,设置 hash 中的字段和值。
HSETNX key field value
hincrby
将 hash 中字段对应的数值添加指定的值。
HINCRBY key field increment
hincrbyfloat
HINCRBYFLOAT key field increment
使用示例:
hash的编码方式
压缩的本质就是对数据进行重新编码,不同的数据有不同的特点,结合这些特点,进行精妙的设计,重新编码之后,就可以缩小体积。
示例:
哈希类型的应用场景
作为缓存
之前说过,string类型也可以作为缓存,哈希类型也可以作为缓存。
当存储结构化的数据时,使用hash作为缓存就更合适一些。
比如:
如果用string来存储这样的具有表结构的数据的话,那么就得把数据转化为json的格式。
但是如果我们只想要操作某一对filed value,那么就需要将整个key (json)读取出来,然后解析成对象,然后对其进行修改,修改完之后,又需要将字符串重新转为json,再写回去。
但是如果此时是用hash存储的数据,那么就可以很方便的取出和修改数据了。
总结:使用hash的方式确实可以高效的对filed进行读写,但是hash也付出了更大的空间上的代价
其实还有一种缓存方式,那就是用原生字符串类型。每一个属性用一个键(key)
但是这种方式是不推荐的,因为它把同一个数据的各个属性给分散开了,也就是低内聚。这里不要把内聚和耦合混淆了。
一般写代码追求的是:高内聚,低耦合。
还有一个问题就是:
缓存方式对比
string类型缓存
序列化字符串类型,例如JSON格式
哈希类型缓存
原生字符串类型
使⽤字符串类型,每个属性⼀个键。