深入浅出Redis(一)简介

来源:互联网 发布:淘宝网舞蹈服装上衣 编辑:程序博客网 时间:2024/06/05 15:11

Redis是什么

官方描述Redis是一个开源的,支持网络、基于内存亦可持久化的日志型键-值仓储。个人认为这样描述过于简单,不足以体现出Redis的强大。Redis比起传统的键-值仓储要强大许多,事实上Redis支持五种数据结构,而传统的键-值存储只是其中之一。要真正认识Redis的强大所在,就必须理解它所支持的五种数据结构,如何操作它们以及它们能够解决哪些类型的问题。

灵活的数据结构只是Redis强大的一方面,另一方面则它的高性能,实在是太快了,有测评说比Memcached还快。Redis的出现,很大程度弥补了memcached这类键-值存储的不足,在部分场合可以对关系数据库起到很好的补充作用。另外,Redis支持大量的客户端API,从java、C到Ruby等,可以从官网http://redis.io/clients查询是否有你需要的API。


Redis基础

既然Redis是一个键-值仓储,我们先来说说键、值。
键(key)是一个标识符,就像程序语里中我们定义一个变量的名字一样,比如a:b:c这个键,其中冒号':'没有特殊含义,跟其它字符一样,但是在实际中可以用做分隔符来更好地维护系统的所有键。值(value)是我们真在关心的内容,它与键(key)绑定在一起,你可以存放一个字符串,一个数字,或者以json或xml格式序列化一个对象,因为Redis并不关心你存放什么东西,它只是以字节数组的形式来保存它们。如:
set people:id001 '{"id":"id001","name":"ZhangSan","age":"21","sex":"male"}'

这里使用set命令把一个值(对象以json格式序列化)存放到键people:zhangsan中去。之后,可以使用get命令查询对应的值:
get people:id001

能不能像关系数据库或文档型数据库那样,基于对象的年龄或性别来查询呢?对不起,不可以,Redis不支持基于对象的属性来查询,因为Redis只是简单的以字节数据的形式来存储,并不关心存放的内容。这既是Redis的限制,同时也成就了它最大的优点:快。

虽然Redis本身不支持基于对象的属性来查询,我们还是可以自己来实现,比如要基于name来查询,我们可以这么做:
hset people:lookup:name ZhangSan id001

把name与id的对应关系保存起来,之后便可以通过name查询到id,再根据id查询对象了。这里我们提出了实现的方案,但在实际中是否值得这么做却需要更多的考虑,毕竟我们还有关系数据库,而且人为的维护这样的依赖关系可不是件容易的事,想想如果删除了一个对象,我们还得更新people:lookup:name这个键。


Redis的五种数据结构

到目前为止,我们已经看到了三个命令,set、get和hset,Redis是如何知道该使用哪一种数据结构的呢?事实上,每种数据结构都单独提供了若干命令,当使用某个具体的命令时,就决定了具体的数据结构了。比如使用set或get命令时,操作的是String类型的值;而使用hset命令时,操作的是Hash类型的值。

Strings

Strings是Redis中最基本的数据类型,但是别被这个名字迷惑了,因为Redis并不关心值是什么,所以你可以存放字符串、数字、十进制串等任何东西,甚至是对象的序列化。就像之前我们使用set命令把值存入键中,再通过get命令取出键对应的值一样,Redis就是这么简单。当然,与Strings相关的命令还有很多,可以到http://redis.io/commands#string查看全部的命令,这里我们就不一一介绍了。

值得强调的是Strings类型还支持位操作,如:
127.0.0.1:6379> setbit mykey 1 1(integer) 0127.0.0.1:6379> setbit mykey 2 1(integer) 0127.0.0.1:6379> setbit mykey 3 1(integer) 0127.0.0.1:6379> setbit mykey 10 1(integer) 0127.0.0.1:6379> getbit mykey 3(integer) 1127.0.0.1:6379> bitcount mykey(integer) 4

setbit命令可以设置某个key指定位置为1或0,getbit可以查询key指定位置的值(0或1),bitcount命令可以查询某个key一共有多少为1的十进制位。这里有一篇文章提到有人使用Redis位操作来统计“一天中有多少活跃用户访问”,对于1.3亿的访问量在一台macbook上只用了50ms和16MB的内存就计算出来了,是不是太神奇了。

还有一点,虽然Redis对存放的值并不关心,但有些命令却依赖于值的类型。比如incr、decr等命令,当键的值不是数值的时候就会报错:
127.0.0.1:6379> set mykey 1OK127.0.0.1:6379> incr mykey(integer) 2127.0.0.1:6379> incr mykey(integer) 3127.0.0.1:6379> get mykey"3"127.0.0.1:6379> set akey abcOK127.0.0.1:6379> incr akey(error) ERR value is not an integer or out of range

Hash

Hash的存在使的Redis变得更加强大,功能上它类似于Java语言中的Map。对于Hash来说,一个值可以有若干个字段(field),或者叫属性,比起Strings类型使用序列化的方式来存储对象来说,使用Hash来存储对话更加合理:
127.0.0.1:6379> hset people:id001 id id001(integer) 1127.0.0.1:6379> hset people:id001 name ZhangSan(integer) 1127.0.0.1:6379> hset people:id001 age 21(integer) 1127.0.0.1:6379> hset people:id001 sex male(integer) 1127.0.0.1:6379> hget people:id001 name"ZhangSan"

比起Strings,使用Hash来存放对象的一个好处是需要更新某个属性时,不用更新整个对象的序列化字串。

List

List相当于编程语言中的数组,但比数组的操作要灵活很多。List类型在Redis中是有序的,使用lpush和rpush命令可以把元素分别加到数据的左边和右边,lindex命令可以获取指定位置的元素,lrange命令可以获得指定位置区间的多个元素,lpop(rpop)命令删除并返回最左边(最右边)的元素等:
127.0.0.1:6379> lpush mylist b(integer) 1127.0.0.1:6379> lpush mylist a(integer) 2127.0.0.1:6379> rpush mylist c(integer) 3127.0.0.1:6379> rpush mylist d(integer) 4127.0.0.1:6379> lindex mylist 1"b"127.0.0.1:6379> lrange mylist 2 31) "c"2) "d"127.0.0.1:6379> lpop mylist"a"127.0.0.1:6379> rpop mylist"d"127.0.0.1:6379> lrange mylist 0 -11) "b"2) "c"

通过List,我们可以非常容易的实现各个链表、栈等数据结构,得益于Redis的优点,使用这些实现在性能上都非常的快。

Sets

Sets是一个集合,比起List,它不允许重复的值,而且也不是有序的,所以不能通过下标索引来获取元素。可以使用sadd命令向集合中加入新的元素,smember命令返回集合中的所有元素,sismember命令判断一个元素是否属于某个集合(值得一提的是sismember命令的复杂度是O(1),不管集合中有多少个元素,它总是花费固定的时间完成执行)。
127.0.0.1:6379> sadd myset java(integer) 1127.0.0.1:6379> sadd myset c++(integer) 1127.0.0.1:6379> sadd myset objective-c(integer) 1127.0.0.1:6379> sadd myset java(integer) 0127.0.0.1:6379> smembers myset1) "objective-c"2) "c++"3) "java"127.0.0.1:6379> sismember myset java(integer) 1127.0.0.1:6379> sismember myset c(integer) 0

对于具体超大数据量的系统来说,使用Sets做来唯一性判断未尝不是一个好的方案。

Sorted Sets

Sorted Sets是一个非常强大的数据结构,它在Sets的基础上,为集合中每个元素绑定了一个数值,这样一来就可以对集合做一些排序相关的操作了。zadd命令向集合中新增一个元素,同时指定该元素对应的数值;zank返回元素在集合中根据数值排序后的下标索引,zrevrank与zank类似,只是排序方式由大到小;zrange与zrevrange命令可以根据下标获取排序后的元素,非常有用的命令。
比如我们使用Sorted Sets来存放一个成绩表,可以非常容易处理诸如:多少人在90分以上,多少人不及格,80分以上的人是哪些,排名第一的是谁,多少分等等查询:
127.0.0.1:6379> zadd math:score 58 person1 63 person2 78 person3 85 person4 90 person5 100 person6(integer) 6127.0.0.1:6379> zrevrangebyscore math:score 100 60 //及格的人是哪些1) "person6"2) "person5"3) "person4"4) "person3"5) "person2"127.0.0.1:6379> zrevrangebyscore math:score 100 90 //90分以上(包括90)的人是哪些1) "person6"2) "person5"127.0.0.1:6379> zrevrangebyscore math:score 100 (90 //90分以上(不包括90)的人是哪些1) "person6"127.0.0.1:6379> zcount math:score 90 +inf //90分以上有多少人(integer) 2127.0.0.1:6379> zcount math:score -inf 59 //多少人不及格(integer) 1127.0.0.1:6379> zrevrange math:score 0 0 withscores //排名第一的是谁,多少分1) "person6"2) "100"

现实生活中有很多类似的场景、业务都可以使用Sorted Sets来解决。电商系统可以使用Sorted Sets来维护每个用户的月消费额度,对用户分级并进行不同的促销措施;门户网站可以记录用户每周或每月的登录次数,并对活跃用户加以积分奖励等手段;博额系统可以对每篇博客的阅读量、好评率进行统计,进而评选精品文章等等,应用的场景实在是太多了,只有你想不到,没有做不到,而且别忘了,Redis很快。


Redis命令

前面我们已经看到,Redis的每种数据类型都提供了一组相关的命令,掌握并灵活地使用这些命令是用好Redis最基本的要求,官方文档里面有每个命令的详细说明,这里我们就不再重重了。值得注意的是每个命令的时间复杂度,一般有O(1)、O(logN)和O(N)等等几种,在生产环境一定要检查每个命令的复杂度,如果重杂复是类似O(logN)这种与数据量相关的命令,在数据量巨大的时候就可能成为性能瓶颈,这里需要查阅文档看看其它命令或者若干其它命令的组合是否可以完成相同的功能,以此来提升性能。

Redis所有命令都是原子性的,这是因为Redis内部是使用单线程实现的,就算不同的客户端同一时刻发送多条命令到Redis Server,这些命令也是串行执行的,因为在Redis Server中只有一个线程依次地处理它们。Redis能否像数据库事务一样,支持多条命令的原子性呢?答案是肯定的,Redis支持事务,相关的内容我们后续再介绍。

除了与数据类型相关的命令,还有一些一类型无关的命令,这里我们简单介绍一些。

Redis中也有数据库的概念,就像在数据库系统中我们可以建立多个数据库一样。在Redis中数据库是通过一个数字来标识的,默认情况下是连接到0这个库,可以使用select选择当前的数据库。
127.0.0.1:6379> select 1 //改变当前库为1OK127.0.0.1:6379[1]> select 0 //改变当前库为0,默认库OK127.0.0.1:6379> 

exists命令可以检查某个key是否存在;del命令删除key;keys命令可以根据正则查询有哪些key,但这个命令最好不要在生产环境使用;rename可以修改key的名字。
127.0.0.1:6379> set key1 1OK127.0.0.1:6379> set key2 2OK127.0.0.1:6379> exists key1(integer) 1127.0.0.1:6379> exists key0(integer) 0127.0.0.1:6379> del key1(integer) 1127.0.0.1:6379> keys *1) "key2"127.0.0.1:6379> rename key2 key2-newOK127.0.0.1:6379> keys *1) "key2-new"127.0.0.1:6379> 

Redis还支持为key指定一个过期时间,当某个key达到过期时间后,Redis将会删除这个key。这个特性使得Redis非常适合用来做缓存。expire命令可以设置某个key多长时间后过期(单位:秒),ttl命令可以查询key多久后过期,persist命令删除key的过期时间(永不过期)。
127.0.0.1:6379> set key1 1OK127.0.0.1:6379> set key2 2OK127.0.0.1:6379> expire key1 30(integer) 1127.0.0.1:6379> ttl key1 //key1 26秒后过期(integer) 26127.0.0.1:6379> ttl key2 //key2 永不过期(integer) -1127.0.0.1:6379> keys *1) "key1"2) "key2"127.0.0.1:6379> keys * //key1过期后被删除1) "key2"127.0.0.1:6379> expire key2 10(integer) 1127.0.0.1:6379> ttl key2(integer) 5127.0.0.1:6379> persist key2 //使key2永不过期(integer) 1127.0.0.1:6379> ttl key2(integer) -1127.0.0.1:6379> keys *1) "key2"127.0.0.1:6379>


配置Redis

从Redis官网下载下来的是一个.tar.gz文件,解压进入目录执行make命令编译后,就可以直接使用了。
$ cd redis-2.8.19$ make<pre name="code" class="plain">$ ./src/redis-server
编译后在src目录下会生成一些可执行文件,执行redis-server命令就可以启动Redis Server了,之后可以使用redis-cli(客户端连接工具)连接。
$ ./src/redis-cli 127.0.0.1:6379> 

上面我们启动Redis Server时,没有指定任何的参数,Redis会以默认参数来配置。Redis是通过一个配置文件来配置其行为的,可以从解压目录中看到一个redis.conf文件,里面对每个配置都有详细的说明,在启动redis时指定配置文件,可以在redis-server命令后面指定配置文件的路径。
$ ./src/redis-server ./redis.conf

默认情况下,所有的客户端都可以连接并操作Redis Server,可以在redis.conf文件中设置requirepass设置密码。
Redis不支持对权限的管理,客户端一旦验证通过,就可以执行任何命令,这点是非常不安全的,尤其是在生产环境。可以redis.conf文件中通过rename-command重命名一些安全敏感的命令。

关于Redis的其它高级功能与相关配置,尽请期待后续章节...

0 0
原创粉丝点击