介绍
字符串类型是Redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数据。可以用其存储用户的邮箱、JSON化的对象甚至是一张图片。一个字符串类型键允许存储的数据的最大容量是512MB。
字符串类型是其他4种数据类型的基础,其他数据类型和字符串类型的差别从某种角度来说只是组织字符串的形式不同。例如,列表类型是以列表的形式组织字符串,而集合类型是以集合的形式组织字符串。
命令
赋值与取值
SET key value
GET key
递增数字
INCR key
字符串类型可以存储任何形式的字符串,当存储的字符串是整数形式时,redis提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值:
当要操作的键不存在时会默认键值为0,所以第一次递增后的结果是1。当键值不是整数时redis会提示错误:
可以借助GET和SET两个命令自己实现incr函数,伪代码如下:
def incr($key)
$value = GET $key
if not $value
$value = 0
$value = $value + 1
SET $key,$value
return $value
当同一个时间有多个客户端连接到redis时则有可能出现竞态条件(race condition)。包括INCR在内的所有redis命令都是原子操作,无论多少客户端同时连接,都不会出现不同客户端读取到相同的值,递增后相同的情况。
应用
文章访问量统计
博客的一个常见功能是统计文章的访问量,我们可以为每篇文章使用一个名为post:文章ID:page.view的键来记录文章的访问量,每次访问文章的时候使用INCR命令使相应的键值递增。
redis对于键的命名并没有强制的要求,但比较好的实践是用“对象类型:对象ID:对象属性”来命名一个键,如使用键user:1:friends来存储ID为1的用户的好友列表。对于多个单词则推荐使用“.”分隔,一方面是沿用以前的习惯(reids以前版本的键名不能包含空格等特殊字符),另一方面是在redis-cli中容易输入,无须使用双引号包裹。
生成自增ID
如何为每篇文章生成一个唯一ID呢?在关系数据库中我们通过设置字段属性为AUTO_INCREMENT来实现每增加一条记录自动为其生成一个唯一的递增ID的目的,而在redis中可以通过另一种模式来实现:对于每一类对象使用名为对象类型(复数形式):count的键(如users:count)来存储当前类型对象的数量,每增加一个新对象都使用INCR命令递增该键的值。由于使用INCR命令建立的初始键值是1,所以可以很容易得知,INCR命令的返回值既是加入该对象后的当前类型的对象总数,又是该新增对象的ID。
存储文章数据
由于每个字符串类型键只能存储一个字符串,而一篇博客文章是由标题、正文、作者与发布时间等多个元素构成的。为了存储这些元素,我们需要使用序列化函数(java中的JSON.toJsonString())将它们转换成一个字符串。也可以使用MessagePack序列化为二进制数据,速度更快,占用空间也更小。
发布新文章时与Redis操作相关的伪代码如下:
//首先获得新文章的id
$postId = INCR post:count
//将博客文章的诸多元素序列化成字符串
$serializedPost = serialize($title,$content,$author,$time)
//把序列化后的字符串存入一个字符串类型的键中
SET post:$postId:data,$serializedPost
获取文章数据的伪代码:
//从redis中读取文章数据
$serializedPost = GET post:42:data
//将文章数据反序列化文章的各个元素
$title,$content,$author,$time=unserialize($serializedPost)
//获取并递增文章的访问数量
$count = INCR post:42:page.view
除了使用序列化函数将文章的多个元素存入一个字符串类型键中外,还可以对每个元素使用一个字符串类型键来存储。
补充
增加指定的整数
INCRBY key increment
INCRBY命令与INCR命令基本一样,只不过前者可以通过increment参数指定一次增加的数值,如:
减少指定的整数
DECR key
DECR key increment
增加指定浮点数
INCRBYFLOAT key increment
INCRBYFLOAT命令类似INCRBY命令,差别是前者可以递增一个双精度浮点数,如:
向尾部追加值
APPEND key value
APPEND作用是向键值的末尾追加value。如果键不存在则将该键的值设置为value,即相当于set key value。返回值是追加后字符串的总长度。如:
APPEND命令添加了双引号,原因是该参数包含空格,在redis-cli中输入需要双引号以示区分。
获取字符串长度
STRLEN key
STRLEN命令返回键值的长度,如果键不存在则返回0。例如:
同时获得/设置多个键值
MGET key [key…]
MSET key value [key value]
位操作
GETBIT key offset
SETBIT key offset value
BITCOUNT key [start] [end]
BITOP operation destkey key [key…]
一个字节由8个二进制位组成,redis提供了4个命令可以直接对二进制位进行操作。为了演示我们首先将foo键赋值为bar:
bar的3个字母转化成二进制分别为1100010、1100001和1110010,结构如下图所示:
b | a | r |
---|---|---|
01100010 | 01100001 | 01110010 |
GETBIT命令可以获得一个字符串类型键指定位置的二进制位的值(0或1),索引从0开始:
如果超过最大索引,默认值为0
SETBIT命令可以设置字符串类型键指定位置的二进制位的值,返回值是该位置的旧值。如我们要将foo键值设置为aar,可以通过将foo键的二进制位的索引第6位设为0,第7位设为1
如果要设置的位置超过了键值的二进制位的长度,SETBIT命令会自动将中间的二进制位设置为0,同理设置一个不存在的键的指定二进制的值会自动将其前面的位赋值为0:
BITCOUNT命令可以获得字符串类型键中值是1的二进制位个数,例如:
可以通过参数来限制统计的字节范围,如我们只希望统计前两个字节(即“ba”)
bitcount foo 0 1
(integer) 6
BITOP命令可以对对各字符串类型键进行位运算,并将结果存储在destkey参数指定的键中。BITOP命令支持的运算操作有AND、OR、XOR和NOT。如我们可以对bar和aar进行OR运算:
SET foo1 bar
OK
SET foo2 aar
OK
BITOP OR res foo1 foo2
(integer)3
GET res
“car”
BITOPS可以获得指定键的第一个位值是0或者1的位置。BITOPS命令的第二个和第三个参数分别可以用来指定要查询的起始字节(同样从0开始算起)和结束字节(注意这里的单位不再是二进制位)。
利用位操作命令可以非常紧凑的存储布尔值。比如如果网站的每个用户都有一个递增的整数ID,如果使用一个字符串类型键配合位操作来记录每个用户的性别(用户ID作为索引,二进制位值1和0表示男性和女性),那么记录100万个用户的性别只需占用100KB多的空间,而且由于GETBIT和SETBIT的时间复杂度都是O(1),所以读取二进制位值性能很高。
注意:使用SETBIT命令时,如果当前键的键值长度小于要设置的二进制位的偏移量时,redis会自动分配内存并将键值的当前长度到指定的偏移量之间的二进制位都设置为0.如果要分配的内存过大,则很可能会造成服务器的在那时阻塞而无法接收同一时间的其他请求。举例而言,在一台2014年的MacBook Pro笔记本上,设置偏移量2^32-1的值(即分配500MB的内存)需要耗费将近1s的时间。分配过大的偏移量除了会造成服务器阻塞,还会造成空间浪费。还是举刚才存储网站用户性别的例子,如果这个网站的用户ID是从100000001开始的,那么会造成10多MB的浪费,正确的做法是给每个用户的ID减去100000000再进行存储。