NoSQL数据库Redis(REmote DIctionary Server)使用介绍和特性
下面先对Redis做比较系统的介绍,对Redis的特性及各种数据类型及操作进行了介绍。
1.介绍Redis 是一个由Salvatore Sanfilippo写的免费开源的key-value存储系统,通常被认为是一个数据结构服务器。 优点如下: 性能高 – Redis能支持超过 100K+ 每秒的读写频率。丰富的数据类型 – Redis支持二进制案例的 Strings,Lists,Hashes,Sets 及 Ordered Sets 数据类型操作。 原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。 丰富的特性 – Redis还支持 publish/subscribe,通知,key 过期等等特性。 2.数据类型有着丰富的数据结构 strings、hashes、 list、sets、 sorted sets (1) String类型Redis能存储二进制安全的字符串,最大长度为1GBredis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> GET name "John Doe" String类型还支持批量的读写操作 redis 127.0.0.1:6379> MSET age 30 sex "male" OK redis 127.0.0.1:6379> MGET age sex 1) "30" 2) "male" String类型其实也可以用来存储数字,并支持对数字的加减操作。 redis 127.0.0.1:6379> INCR age (integer) 31 redis 127.0.0.1:6379> INCRBY age 4 (integer) 35 redis 127.0.0.1:6379> GET age "35" redis 127.0.0.1:6379> DECR age (integer) 34 redis 127.0.0.1:6379> DECRBY age 4 (integer) 30 redis 127.0.0.1:6379> GET age "30" String类型还支持对其部分的修改和获取操作 redis 127.0.0.1:6379> APPEND name " Mr." (integer) 12 redis 127.0.0.1:6379> GET name "John Doe Mr." redis 127.0.0.1:6379> STRLEN name (integer) 12 redis 127.0.0.1:6379> SUBSTR name 0 3 "John" (2) List类型Redis能够将数据存储成一个链表,并能对这个链表进行丰富的操作redis 127.0.0.1:6379> LPUSH students "John Doe" (integer) 1 redis 127.0.0.1:6379> LPUSH students "Captain Kirk" (integer) 2 redis 127.0.0.1:6379> LPUSH students "Sheldon Cooper" (integer) 3 redis 127.0.0.1:6379> LLEN students (integer) 3 redis 127.0.0.1:6379> LRANGE students 0 2 1) "Sheldon Cooper" 2) "Captain Kirk" 3) "John Doe" redis 127.0.0.1:6379> LPOP students "Sheldon Cooper" redis 127.0.0.1:6379> LLEN students (integer) 2 redis 127.0.0.1:6379> LRANGE students 0 1 1) "Captain Kirk" 2) "John Doe" redis 127.0.0.1:6379> LREM students 1 "John Doe" (integer) 1 redis 127.0.0.1:6379> LLEN students (integer) 1 redis 127.0.0.1:6379> LRANGE students 0 0 1) "Captain Kirk" Redis也支持很多修改操作 redis 127.0.0.1:6379> LINSERT students BEFORE "Captain Kirk" "Dexter Morgan" (integer) 3 redis 127.0.0.1:6379> LRANGE students 0 2 1) "Dexter Morgan" 2) "Captain Kirk" 3) "John Doe" redis 127.0.0.1:6379> LPUSH students "Peter Parker" (integer) 4 redis 127.0.0.1:6379> LRANGE students 0 3 1) "Peter Parker" 2) "Dexter Morgan" 3) "Captain Kirk" 4) "John Doe" redis 127.0.0.1:6379> LTRIM students 1 3 OK redis 127.0.0.1:6379> LLEN students (integer) 3 redis 127.0.0.1:6379> LRANGE students 0 2 1) "Dexter Morgan" 2) "Captain Kirk" 3) "John Doe" redis 127.0.0.1:6379> LREM students 1 "John Doe" (integer) 1 redis 127.0.0.1:6379> LLEN students (integer) 1 redis 127.0.0.1:6379> LRANGE students 0 1 1) "Captain Kirk" (3) 集合(Sets)类型Redis能够将一系列不重复的值存储成一个集合redis 127.0.0.1:6379> SADD birds crow (integer) 1 redis 127.0.0.1:6379> SADD birds pigeon (integer) 1 redis 127.0.0.1:6379> SADD birds bat (integer) 1 redis 127.0.0.1:6379> SADD mammals dog (integer) 1 redis 127.0.0.1:6379> SADD mammals cat (integer) 1 redis 127.0.0.1:6379> SADD mammals bat (integer) 1 redis 127.0.0.1:6379> SMEMBERS birds 1) "bat" 2) "crow" 3) "pigeon" redis 127.0.0.1:6379> SMEMBERS mammals 1) "bat" 2) "cat" 3) "dog" Sets结构也支持相应的修改操作 redis 127.0.0.1:6379> SREM mammals cat (integer) 1 redis 127.0.0.1:6379> SMEMBERS mammals 1) "bat" 2) "dog" redis 127.0.0.1:6379> SADD mammals human (integer) 1 redis 127.0.0.1:6379> SMEMBERS mammals 1) "bat" 2) "human" 3) "dog" Redis还支持对集合的子交并补等操作 redis 127.0.0.1:6379> SINTER birds mammals 1) "bat" redis 127.0.0.1:6379> SUNION birds mammals 1) "crow" 2) "bat" 3) "human" 4) "pigeon" 5) "dog" redis 127.0.0.1:6379> SDIFF birds mammals 1) "crow" 2) "pigeon" (4) 有序集合(Sorted Sets)类型Sorted Sets和Sets结构相似,不同的是存在Sorted Sets中的数据会有一个score属性,并会在写入时就按这个score排好序。redis 127.0.0.1:6379> ZADD days 0 mon (integer) 1 redis 127.0.0.1:6379> ZADD days 1 tue (integer) 1 redis 127.0.0.1:6379> ZADD days 2 wed (integer) 1 redis 127.0.0.1:6379> ZADD days 3 thu (integer) 1 redis 127.0.0.1:6379> ZADD days 4 fri (integer) 1 redis 127.0.0.1:6379> ZADD days 5 sat (integer) 1 redis 127.0.0.1:6379> ZADD days 6 sun (integer) 1 redis 127.0.0.1:6379> ZCARD days (integer) 7 redis 127.0.0.1:6379> ZRANGE days 0 6 1) "mon" 2) "tue" 3) "wed" 4) "thu" 5) "fri" 6) "sat" 7) "sun" redis 127.0.0.1:6379> ZSCORE days sat "5" redis 127.0.0.1:6379> ZCOUNT days 3 6 (integer) 4 redis 127.0.0.1:6379> ZRANGEBYSCORE days 3 6 1) "thu" 2) "fri" 3) "sat" 4) "sun" (5) Hash类型Redis能够存储key对多个属性的数据(比如user1.uname user1.passwd)redis 127.0.0.1:6379> HKEYS student 1) "name" 2) "age" 3) "sex" redis 127.0.0.1:6379> HVALS student 1) "Ganesh" 2) "30" 3) "Male" redis 127.0.0.1:6379> HGETALL student 1) "name" 2) "Ganesh" 3) "age" 4) "30" 5) "sex" 6) "Male" redis 127.0.0.1:6379> HDEL student sex (integer) 1 redis 127.0.0.1:6379> HGETALL student 1) "name" 2) "Ganesh" 3) "age" 4) "30" Hash数据结构能够批量修改和获取 redis 127.0.0.1:6379> HMSET kid name Akshi age 2 sex Female OK redis 127.0.0.1:6379> HMGET kid name age sex 1) "Akshi" 2) "2" 3) "Female" 3.Publish/Subscribe
3、订阅发布Redis支持这样一种特性,你可以将数据推到某个信息管道中,然后其它人可以通过订阅这些管道来获取推送过来的信息。3.1 订阅信息管道 用一个客户端订阅管道 redis 127.0.0.1:6379> SUBSCRIBE channelone Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channelone" 3) (integer) 1 另一个客户端往这个管道推送信息 redis 127.0.0.1:6379> PUBLISH channelone hello (integer) 1 redis 127.0.0.1:6379> PUBLISH channelone world (integer) 1 然后第一个客户端就能获取到推送的信息 redis 127.0.0.1:6379> SUBSCRIBE channelone Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "channelone" 3) (integer) 1 1) "message" 2) "channelone" 3) "hello" 1) "message" 2) "channelone" 3) "world" 3.2 按一定模式批量订阅 用下面的命令订阅所有channel开头的信息通道 redis 127.0.0.1:6379> PSUBSCRIBE channel* Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "channel*" 3) (integer) 1 在另一个客户端对两个推送信息 redis 127.0.0.1:6379> PUBLISH channelone hello (integer) 1 redis 127.0.0.1:6379> PUBLISH channeltwo world (integer) 1 然后在第一个客户端就能收到推送的信息 redis 127.0.0.1:6379> PSUBSCRIBE channel* Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "channel*" 3) (integer) 1 1) "pmessage" 2) "channel*" 3) "channelone" 4) "hello" 1) "pmessage" 2) "channel*" 3) "channeltwo" 4) "world" 4、数据过期设置Redis支持按key设置过期时间,过期后值将被删除(在客户端看来是补删除了的)用TTL命令可以获取某个key值的过期时间(-1表示永不过期) redis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> TTL name (integer) -1 下面命令先用EXISTS命令查看key值是否存在,然后设置了5秒的过期时间 redis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> EXISTS name (integer) 1 redis 127.0.0.1:6379> EXPIRE name 5 (integer) 1 5秒后再查看 redis 127.0.0.1:6379> EXISTS name (integer) 0 redis 127.0.0.1:6379> GET name (nil) 这个值已经没有了。 上在是直接设置多少秒后过期,你也可以设置在某个时间点过期,下面例子是设置2011-09-24 00:40:00过期。 redis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> EXPIREAT name 1316805000 (integer) 1 redis 127.0.0.1:6379> EXISTS name (integer) 0 5、事务性Redis本身支持一些简单的组合型的命令,比如以NX结尾命令都是判断在这个值没有时才进行某个命令。redis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> SETNX name "Dexter Morgan" (integer) 0 redis 127.0.0.1:6379> GET name "John Doe" redis 127.0.0.1:6379> GETSET name "Dexter Morgan" "John Doe" redis 127.0.0.1:6379> GET name "Dexter Morgan" 当然,Redis还支持自定义的命令组合,通过MULTI和EXEC,将几个命令组合起来执行 redis 127.0.0.1:6379> SET counter 0 OK redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> INCR counter QUEUED redis 127.0.0.1:6379> INCR counter QUEUED redis 127.0.0.1:6379> INCR counter QUEUED redis 127.0.0.1:6379> EXEC 1) (integer) 1 2) (integer) 2 3) (integer) 3 redis 127.0.0.1:6379> GET counter "3" 你还可以用DICARD命令来中断执行中的命令序列 redis 127.0.0.1:6379> SET newcounter 0 OK redis 127.0.0.1:6379> MULTI OK redis 127.0.0.1:6379> INCR newcounter QUEUED redis 127.0.0.1:6379> INCR newcounter QUEUED redis 127.0.0.1:6379> INCR newcounter QUEUED redis 127.0.0.1:6379> DISCARD OK redis 127.0.0.1:6379> GET newcounter "0" 6、持久化Redis的所有数据都存储在内存中,但是他也提供对这些数据的持久化。(1) 数据快照 数据快照的原理是将整个Redis中存的所有数据遍历一遍存到一个扩展名为rdb的数据文件中。通过SAVE命令可以调用这个过程。 redis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> SAVE OK redis 127.0.0.1:6379> SET name "Sheldon Cooper" OK redis 127.0.0.1:6379> BGSAVE Background saving started 如果你是使用的brew在Mac OSX上安全的Redis,那么rdb文件会存在如下路径 /usr/local/var/db/redis/dump.rdb (2) Append-Only File(追加式的操作日志记录) Redis还支持一种追加式的操作日志记录,叫append only file,其日志文件以aof结局,一般称为aof文件。要开启aof日志的记录,你需要在配置文件中进行如下设置: appendonly yes 这时候你所有的操作都会记录在aof日志文件中 redis 127.0.0.1:6379> GET name (nil) redis 127.0.0.1:6379> SET name "Ganesh Gunasegaran" OK redis 127.0.0.1:6379> EXIT → cat /usr/local/var/db/redis/appendonly.aof *2 $6 SELECT $1 0 *3 $3 SET $4 name $18 Ganesh Gunasegaran
7、管理命令Redis支持多个DB,默认是16个,你可以设置将数据存在哪一个DB中,不同DB间的数据具有隔离性。也可以在多个DB间移动数据。redis 127.0.0.1:6379> SELECT 0 OK redis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> SELECT 1 OK redis 127.0.0.1:6379[1]> GET name (nil) redis 127.0.0.1:6379[1]> SELECT 0 OK redis 127.0.0.1:6379> MOVE name 1 (integer) 1 redis 127.0.0.1:6379> SELECT 1 OK redis 127.0.0.1:6379[1]> GET name "John Doe" Redis还能进行一些如下操作,获取一些运行信息 redis 127.0.0.1:6379[1]> DBSIZE (integer) 1 redis 127.0.0.1:6379[1]> INFO redis_version:2.2.13 redis_git_sha1:00000000 redis_git_dirty:0 arch_bits:64 multiplexing_api:kqueue ...... Redis还支持对某个DB数据进行清除(当然清空所有数据的操作也是支持的) redis 127.0.0.1:6379> SET name "John Doe" OK redis 127.0.0.1:6379> DBSIZE (integer) 1 redis 127.0.0.1:6379> SELECT 1 OK redis 127.0.0.1:6379[1]> SET name "Sheldon Cooper" OK redis 127.0.0.1:6379[1]> DBSIZE (integer) 1 redis 127.0.0.1:6379[1]> SELECT 0 OK redis 127.0.0.1:6379> FLUSHDB OK redis 127.0.0.1:6379> DBSIZE (integer) 0 redis 127.0.0.1:6379> SELECT 1 OK redis 127.0.0.1:6379[1]> DBSIZE (integer) 1 redis 127.0.0.1:6379[1]> FLUSHALL OK redis 127.0.0.1:6379[1]> DBSIZE (integer) 0 8、客户端Redis的客户端很丰富,几乎所有流行的语言都有其客户端,可以上Redis的Clients页面去查找。 hiredis是redis的c客户端,使用时需要注意其操作的同步特性: (1)block方式: 每个命令都是单独发向Redis的,且也会等待每个的结果返回。即,通过redisCommand调用,即完成了向Redis发送命令,也等待Redis返回结果。换言之,是一个阻塞的过程。如果在是并发量比较高的情况下,此种方式,会使得客户端效率较差。即便是将Redis部署在本地,通过local进行访问,也会有0.02毫秒级别的等待过程。如果客户端有很多次的访问操作,每次都会阻塞一小段时间,会使得客户端本身的处理速度降下来。为了降低这种开销,可以采用pipeline的方式。也即一次向Redis发送多个命令。将这多个命令发送完毕后,再进行统一处理。当然,这样的代价是客户端的发送缓冲区会变大,接收缓冲区也会变大。但处理的吞吐量会增加。 (2)non-block方式: 默认的调用redisCommand就会立即返回,相当于block模式下显式的调用AppendCommand。在non-block方式下,要显式的调用获取回应的replay,并将其释放掉,以免造成内存泄漏。 9、Redis数据恢复(1)数据快照数据快照的原理是将整个Redis内存中存的所有数据遍历一遍存到一个扩展名为rdb的数据文件中.通过SAVE命令可以调用这个过程.数据快照配置 save 900 1 save 300 10 save 60 10000 以上在redis.conf中的配置指出在多长时间内,有多少次更新操作,就将数据同步到数据文件,这个可以多个条件配合.上面的含义是900秒后有一个 key发生改变就执行save,300秒后有10个key发生改变执行save,60秒有10000个key发生改变执行save 数据快照的缺点 是持久化之后如果出现crash则会丢失一段数据,因此作者增加了另外一种追加式的操作日志记录,叫append only file,其日志文件以aof结尾,我们一般称为aof文件. (2)aof日志appendonly配置如果不开启,可能会在断电时导致一段时间内的数据丢失. 因为redis本身同步数据文件是按save条件来同步的,所以有的数据会在一段时间内只存在于内存中. (3)恢复数据当Redis服务器挂掉时,重启时将按以下优先级恢复数据到内存中:1. 如果只配置了AOF,重启时加载AOF文件恢复数据. 2.如果同时配置了RBD和AOF,启动时只加载AOF文件恢复数据. 3.如果只配置了RDB,启动时将加载dump文件恢复数据. 10、Redis主从复制(1)Master/Slave配置Master IP:175.41.209.118Master Redis Server Port:6379 Slave配置很简单,只需要在slave服务器的redis.conf加入: slaveof 175.41.209.118 6379 启动master和slave,然后写入数据到master,读取slave,可以看到数据被复制到slave了. 用途:读写分离,数据备份,灾难恢复等 (2)Redis主从复制过程配置好slave后,slave与master建立连接,然后发送sync命令. 无论是第一次连接还是重新连接,master都会启动一个后台进程,将数据库快照保存到文件中,同时master主进程会开始收集新的写命令并缓存. 后台进程完成写文件后,master就发送文件给slave,slave将文件保存到硬盘上,再加载到内存中,接着master就会把缓存的命令转发给slave,后续master将收到的写命令发送给slave. 如果master同时收到多个slave发来的同步连接命令,master只会启动一个进程来写数据库镜像,然后发送给所有的slave.(3)Redis主从复制特点1. master可以拥有多个slave.2. 多个slave可以连接同一个master外,还可以连接到其他slave. 3. 主从复制不会阻塞master,在同步数据时,master可以继续处理client请求. 4. 可以在master禁用数据持久化,注释掉master配置文件中的所有save配置,只需在slave上配置数据持久化. 5. 提高系统的伸缩性. (4)Redis主从复制速度官方提供了一个数据,Slave在21秒即完成了对Amazon网站 10G key set的复制.
11、 跟传统存储差异(1)跟sql server、mySQL等关系型数据库差异 Memcached是多线程,分为监听线程、worker线程,引入锁,带来了性能损耗。Redis使用单线程的IO复用模型,将速度优势发挥到最大,也提供了较简单的计算功能 Memcached使用预分配的内存池的方式,带来一定程度的空间浪费 并且在内存仍然有很大空间时,新的数据也可能会被剔除,而Redis使用现场申请内存的方式来存储数据,不会剔除任何非临时数据 Redis更适合作为存储而不是cache Memcached提供了cas命令来保证.而Redis提供了事务的功能,可以保证一串 命令的原子性,中间不会被任何操作打断 Memcached只支持简单的key-value存储,不支持枚举,不支持持久化和复制等功能。redis很大程度弥补了memcached这类key/value存储的不足,在部分场合可以对关系数据库起到很好的补充作用。 12、 内存优化
通过我们上面的一些实现上的分析可以看出redis实际上的内存管理成本非常高,即占用了过多的内存,作者对这点也非常清楚,所以提供了一系列的参数和手段来控制和节省内存。
(1)虚拟内存功能首先最重要的一点是不要开启Redis的VM选项,即虚拟内存功能,这个本来是作为Redis存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,但是其内存管理成本也非常的高,并且此种持久化策略并不成熟,所以要关闭VM功能,请检查你的redis.conf文件中 vm-enabled 为 no。(2)最大使用内存其次最好设置下redis.conf中的maxmemory选项,该选项是告诉Redis当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的Redis不会因为使用了过多的物理内存而导致swap,最终严重影响性能甚至崩溃。(3)数据类型内存参数Redis为不同数据类型分别提供了一组参数来控制内存使用,我们在前面提到过Redis Hash的value内部是一个HashMap,如果该Map的成员数比较少,则会采用一维数组的方式来紧凑存储该Map,即省去了大量指针的内存开销,这个参数在redis.conf配置文件中下面2项:
含义是当value这个Map内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即value内部有64个以下的成员就是使用线性紧凑存储,超过该值自动转成真正的HashMap. 含义是当value这个Map内部的每个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间. 那么这个值是不是设置的越大越好呢,答案当然是否定的,HashMap的优势就是查找和操作的时间复杂度都是O(1)的,而放弃Hash采用一维存储则是O(n)的时间复杂度,如果成员数量很少,则影响不大,否则会严重影响性能,所以要权衡好这个值的设置,总体上还是时间成本和空间成本上的权衡. 同样类似的参数 13、Redis 集群当前稳定版本的redis(2.4.5)只支持简单的master-slave replication: 一个master写,多个slave读.只能通过客户端一致性哈希自己做sharding. (1)Redis使用在旅馆的例子1)一主一从Master提供读服务,Slave提供写服务 使用亚马逊AWS虚拟机,机器配置如下: 每天约更新30万左右的数据,现在库里有400W条纪录(400W个Key,使用的是Hash结构存储),每条数据都设有过期时间,占用了2.5G的内存. Master提供读服务,Slave提供持久性备份服务 数据结构上:旅店的价格存储使用Redis的Hash 结构也非常适合,HotelCode+Date最为key,这样 性能上:从目前使用的情况来看,总体效果还是比较理想的。目前每天从GTA到Choice大约500w个请求,整个过程80%的请求在500毫秒内返回,基本无超时现象发生. 14、其他特性(1)性能特性 1. 速度快:使用标准C写,所有数据都在内存中完成,读写速度分别达到10万/20万 2. 持久化:对数据的更新采用Copy-on-write技术,可以异步地保存到磁盘上,主要有两种策略,一是根据时间,更新次数的快照(save 300 10 )二是基于语句追加方式(Append-only file,aof) 自动操作:对不同数据类型的操作都是自动的,很安全
(3)拓展特性 1. Sharding技术: 很容易将数据分布到多个Redis实例中,数据库的扩展是个永恒的话题,在关系型数据库中,主要是以添加硬件、以分区为主要技术形式的纵向扩展解决了很多的应用场景,但随着web2.0、移动互联网、云计算等应用的兴起,这种扩展模式已经不太适合了,所以近年来,像采用主从配置、数据库复制形式的,Sharding这种技术把负载分布到多个特理节点上去的横向扩展方式用处越来越多。 15、redis使用注意1. 要进行Master-slave配置,出现服务故障时可以支持切换。 2. 在master侧禁用数据持久化,只需在slave上配置数据持久化。 3. 物理内存+虚拟内存不足,这个时候dump一直死着,时间久了机器挂掉。这个情况就是灾难! 4. 当Redis物理内存使用超过内存总容量的3/5时就会开始比较危险了,就开始做swap,内存碎片大 5. 当达到最大内存时,会清空带有过期时间的key,即使key未到过期时间. 6. redis与DB同步写的问题,先写DB,后写redis,因为写内存基本上没有问题(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |