在互联网的迅速发展,网民的群体越来越庞大,互联网公司的需要各种横向扩展,分层架构等等技术去满足用户快速访问的体验,那么就不得不说器缓存,缓存有许多种方案在整体的应用架构中部署的位置,应用场景都不同,例如页面缓存,nginx缓存等等,那么我们今天学学Redis数据库前端缓存(更准确说Redis不只是一个缓存的作用,就是一个数据库,读完此文会深有感触)。
以前在没深入学习redis的时候认为redis 就是一个纯粹的数据库前端的key-value 缓存中间件,可以代替memcached的缓存中间件,但是深入学习redis后我的认识发生了改变,它不止一个缓存中间件,它是一个数据库。
2008年,意大利的一家创业公司Merzia.① 推出了一款基于MySQL的网站实时统计系统
LLOOGG② ,然而没过多久该公司的创始人Salvatore Sanfilippo便开始对MySQL的性能感到失
望,于是他决定亲自为LLOOGG量身定做一个数据库,并于2009年开发完成,这个数据库就是
Redis。不过Salvatore Sanfilippo并不满足只将Redis用于LLOOGG这一款产品,而是希望让更多
的人使用它,于是在同一年Salvatore Sanfilippo将Redis开源发布,并开始和Redis的另一名主要
的代码贡献者Pieter Noordhuis一起继续着Redis的开发,直到今天。
------------------------------------------------------------------------------------------
Redis存储结构
到目前为止Redis支持的键值数据类型如
下:
●字符串类型
●散列类型
●列表类型
●集合类型
●有序集合类型
字符串类型
介绍
字符串类型是Redis中最基本的数据类型,它能存储任何形式的字符串,包括二进制数
据。你可以用其存储用户的邮箱、JSON化的对象甚至是一张图片。一个字符串类型键允许存
储的数据的最大容量是512MB① 。
注释:①在Redis 3.0版本中可能会放宽这一限制,但无论如何,考虑到Redis的数据是使用
内存存储的,512MB的限制已经非常宽松了。
字符串类型是其他4种数据类型的基础,其他数据类型和字符串类型的差别从某种角度来
说只是组织字符串的形式不同。例如,列表类型是以列表的形式组织字符串,而集合类型是以
集合的形式组织字符串。学习过本章后面几节后相信读者对此会有更深的理解。
命令
1.赋值与取值
SET key value
GET key
SET和GET是Redis中最简单的两个命令,它们实现的功能和编程语言中的读写变量相
似,如key="hello"在Redis中是这样表示的:
redis>SET key hello
OK
想要读取键值则更简单:
redis>GET key
"hello"
当键不存在时会返回空结果。
2.递增数字
INCR key
前面说过字符串类型可以存储任何形式的字符串,当存储的字符串是整数形式时,Redis
提供了一个实用的命令INCR,其作用是让当前键值递增,并返回递增后的值,用法为:
redis>INCR num
(integer) 1
redis>INCR num
(integer) 2
当要操作的键不存在时会默认键值为0,所以第一次递增后的结果是1。当键值不是整数
时Redis会提示错误:
redis>SET foo lorem
OK
redis>INCR foo
(error) ERR value is not an integer or out of range
有些读者会想到可以借助GET和SET两个命令自己实现incr函数,伪代码如下:
def incr( key)
value=GET key
if not value
value=0
value= value+1
SET key, value
return value
如果Redis同时只连接了一个客户端,那么上面的代码没有任何问题(其实还没有加入错
误处理,不过这并不是此处讨论的重点)。可当同一时间有多个客户端连接到Redis时则有可能
出现竞态条件(race condition)① 。例如,有两个客户端A和B都要执行我们自己实现的incr函数
并准备将同一个键的键值递增,当它们恰好同时执行到代码第二行时二者读取到的键值是一
样的,如“5”,而后它们各自将该值递增到“6”并使用SET命令将其赋给原键,结果虽然对键执
行了两次递增操作,最终的键值却是“6”而不是预想中的“7”。包括INCR在内的所有Redis命令
都是原子操作(atomic operation)② ,无论多少个客户端同时连接,都不会出现上述情况。之后
我们还会介绍利用事务(4.1 节)和脚本(第6章)实现自定义的原子操作的方法。
注释:①竞态条件是指一个系统或者进程的输出,依赖于不受控制的事件的出现顺序或者
出现时机。
注释:②原子操作取“原子”的“不可拆分”的意思,原子操作是最小的执行单位,不会在执
行的过程中被其他命令插入打断。
3.存储文章数据
由于每个字符串类型键只能存储一个字符串,而一篇博客文章是由标题、正文、作者与发
布时间等多个元素构成的。为了存储这些元素,我们需要使用序列化函数(如PHP中的serialize
和JavaScript中的JSON.stringify)将它们转换成一个字符串。除此之外因为字符串类型键可以
存储二进制数据,所以也可以使用MessagePack② 进行序列化,速度更快,占用空间也更小。
注释:②MessagePack和JSON一样可以将对象序列化成字符串,但其性能更高,序列化后
的结果占用空间更小,序列化后的结果是二进制格式。MessagePack的项目地址是
http://msgpack.org。
至此我们已经可以写出发布新文章时与Redis操作相关的伪代码了:
#首先获得新文章的ID
postID=INCR posts:count
#将博客文章的诸多元素序列化成字符串
serializedPost=serialize( title, content, author, time)
#把序列化后的字符串存一个入字符串类型的键中
SET post: postID:data, serializedPost
获取文章数据的伪代码如下(以访问ID 为42的文章为例):
#从Redis 中读取文章数据
serializedPost=GET post:42:data
#将文章数据反序列化成文章的各个元素
title, content, author, time=unserialize( serializedPost)
#获取并递增文章的访问数量
count=INCR post:42:page.view