一、概述
字符串类型是Redis最基础的数据结构,Redis中的键都是字符串类型,其他几种数据结构都是在字符串基础之上构建的;
字符串类型的值实际可以是字符串(如简单的字符串、JSON、XML),数字(整型、浮点数),也可以是二进制(图片、音频、视频),但是值的最大不能超过512MB。
二、常用命令
- 设置值:
set key value [ex seconds] [px milliseconds] [nx | xx]
set命令选项说明:
- ex seconds:为键设置秒级过期时间
- px milliseconds:为键设置毫秒级过期时间
- nx:键必须不存在时,才可以设置成功,用于添加
- xx:与xx相反,键必须存在,才可以设置成功,用于更新
同时,Redis也提供了 setex 和 setnx两个命令,其作用与ex,nx选项一样,但在实际应用中需要注意,当同时用到ex和nx机制时,要尽可能地使用set ex nx的组合命令,而不能拆分成两条setex 和setnx,根据Redis性能测试报告:how fast is redis,Redis执行一条指令的并发量约为7w,当同时使用setnx和setex而不使用set ex nx组合命令时,Redis的并发量相当于减少了一半,这点是应用当中的细节部分,需要铭记。
当然,setmx和setex也有其优点和对应的应用场景,以setnx为例,由于Redis的单线程命令处理机制,如果多个客户端同时执行setnx key value,根据setnx的特性,只有一个客户端能设置成功,setnx可以作为分布式锁的一种实现方案,Redis官方废除了使用setnx实现分布式锁的方法:
- 获取值:
- 获取单个值:get key,如果获取的键不存在,则返回nil(空)
- 批量获取值:mget key [key]...,批量操作命令可以提高开发效率,学会合理使用批量操作,有助于提高业务处理效率,但是并不是说批量操作是无节制的,如果key数量过多,很可能造成Redis阻塞或者网络拥塞。
- 计数:
incr key:用于对值做自增操作,返回结果分为3种情况:
- 值不是整数,返回错误;
- 值是整数,返回自增后的结果;
- 键不存在,按照值为0自增,返回结果为1;
例如,对一个不存在的键执行Incr操作,返回结果为1;
除了自增命令外,Redis还提供了decr(自减)、incrby(自增指定数字)、decrby(自减指定数字)、incrbyfloat(自增浮点数):
decr key
incrby key increment
decrby key decrement
incrbyfloat key increment
在Java里面可以使用CAS(compare and swap)机制来实现计数功能,但会有一定的CPU开销(线程安全,通过硬件上的阻塞来实现软件上的非阻塞,再次基础上可以拓展出AQS机制),在Redis中不存在CPU开销问题,因为Redis是单线程架构,任何命令都在Redis服务端上顺序执行。
- 其他命令:
del key:删除key;
mset key value [key value ...]:批量设置键值;
append key value:向字符串尾部追加值;
strlen key:字符串长度;
getset key value:设置并返回原值;
setrange key offeset value:设置指定位置的字符;
getrange ket start end:获取部分字符串
三、内部编码
字符串类型的内部编码有3种:
- int:8个字节的长整型;
- embstr:小于等于39个字节的字符串;
- raw:大于39个字节的字符串;
Redis会根据当前值的类型和长度决定使用哪种内部编码实现。
四、应用场景
Redis字符串应用场景非常广泛,这里列举几个比较常见的应用场景:
- 缓存功能:
使用Redis作为缓存在业界应用非常地广泛,常常与memcache做比较,关于其选型,可参考该文章:技术选型:Redis还是memcache
Redis作为缓存设计的架构图大致如下,Redis作为缓存层,MySQL最为存储层,绝大部分请求的数据都是从Redis中获取,用于Redis具有支撑高并发的特性,所以缓存通常能加速读写和降低后端压力。
实现思路:首先定义一个方法用于获取用户的基础信息,当请求到来时,优先从Redis中获取用户信息,如果命中缓存则直接返回结果,如果不命中缓存,则从MySQL中获取数据,同时将结果写入Redis缓存中,并添加过期时间,一种可能的实现(伪代码)如下:
public UserInfo getUserInfo(long id){
userRediskey = "user:info:" +id;
value = redis.get(userRediskey);
UserInfo userInfo;
if(value != null){
userInfo = deserialize(value);
}else{
userInfo = mysql.get(id);
if(userInfo != null){
redis.setnx(userRediskey,3600,serialize(userInfo));
}
}
return userInfo;
}
- 分布式session共享:
在分布式应用中,一个亟需解决的问题便是session的一致性问题,分布式web服务将用户的session信息(如:登录信息)保存在各自服务器上,这样会造成一个问题,出于负载均衡的考虑,分布式服务会将用户的访问均衡到不同的服务器上,用户刷新一次访问可能会发现需要重新登录,这个问题对于用户来说是不可容忍的。
为解决这个问题,可以使用Redis将用户的session进行集中管理,如下图所示,在这种模式下,只要保证Redis是高可用和扩展性的,每次用户更新或者查询登录信息都直接从Redis中集中获取。
- 计数器:
Redis还可以作为计数的基础工具,实现快速计数、查询缓存的功能,同时数据可以异步落地到其他数据源,如视频播放量、文章阅读量的记录,一种可能的简单实现如下:
public long incrVideoCounter(long id){
key = "video:playCount:" + id;
return redis.incr(key);
}
实际上,一个真实的计数系统还要考虑:防作弊、不同维度计数、数据持久化等问题。
- 限速:
出于安全考虑(防脚本),很多应用会在每次进行登录时,会让用户输入手机验证码,从而确定是否是用户本人,短信接口会限制用户每分钟获取验证码的频率,如一分钟内不超过5次,一种可能的实现如下:
iphoneNum = "138xxxxx";
key = "shortMsg:limit:" +phoneNum;
//set key value EX 60 NX
isExists = redis.set(key,1,"EX 60","NX");
if(isExists != null || redis.incr(key)<=5){
//pass,通过
}else{
//wait,限速
}
- 总结:Redis的应用场景非常广,远不止以上列出的几种,更多的功能设计仍需要结合业务去挖掘。
感谢阅读~~
参考资料:
《Redis开发与运维》付磊、张益军[著]