从上面提到的方案中可以看出,如果采用该方式,Redis的运行时效率将会是非常高效的,既每当有新的数据修改发生时,仅仅是内存中的缓存数据发生改变,而这样的改变并不会被立即持久化到磁盘上,从而在绝大多数的修改操作中避免了磁盘IO的发生。然而事情往往是存在其两面性的,在该方法中我们确实得到了效率上的提升,但是却失去了数据可靠性。如果在内存快照被持久化到磁盘之前,Redis所在的服务器出现宕机,那么这些未写入到磁盘的已修改数据都将丢失。为了保证数据的高可靠性,Redis还提供了另外一种数据持久化机制--Append模式。如果Redis服务器被配置为该方式,那么每当有数据修改发生时,都会被立即持久化到磁盘。
string一、概述: 字符串类型是Redis中最为基础的数据存储类型,它在Redis中是二进制安全的,这便意味着该类型可以接受任何格式的数据,如JPEG图像数据或Json对象描述信息等。在Redis中字符串类型的Value最多可以容纳的数据长度是512M。 二、相关命令列表:
命令原型 时间复杂度 命令描述 返回值APPEND keyvalue O(1)如果该Key已经存在,APPEND命令将参数Value的数据追加到已存在Value的末尾。如果该Key不存在,APPEND命令将会创建一个新的Key/Value。追加后Value的长度。DECR keyO(1) 将指定Key的Value原子性的递减1。如果该Key不存在,其初始值为0,在decr之后其值为-1。如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。注意:该操作的取值范围是64位有符号整型。递减后的Value值。INCR keyO(1) 将指定Key的Value原子性的递增1。如果该Key不存在,其初始值为0,在incr之后其值为1。如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。注意:该操作的取值范围是64位有符号整型。 递增后的Value值。 DECRBY key decrement O(1) 将指定Key的Value原子性的减少decrement。如果该Key不存在,其初始值为0,在decrby之后其值为-decrement。如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。注意:该操作的取值范围是64位有符号整型。 减少后的Value值。INCRBY key increment O(1) 将指定Key的Value原子性的增加increment。如果该Key不存在,其初始值为0,在incrby之后其值为increment。如果Value的值不能转换为整型值,如Hello,该操作将执行失败并返回相应的错误信息。注意:该操作的取值范围是64位有符号整型。 增加后的Value值。GET key O(1) 获取指定Key的Value。如果与该Key关联的Value不是string类型,Redis将返回错误信息,因为GET命令只能用于获取string Value。 与该Key相关的Value,如果该Key不存在,返回nil。SET key value O(1) 设定该Key持有指定的字符串Value,如果该Key已经存在,则覆盖其原有值。总是返回"OK"。GETSET key valueO(1) 原子性的设置该Key为指定的Value,同时返回该Key的原有值。和GET命令一样,该命令也只能处理string Value,否则Redis将给出相关的错误信息。返回该Key的原有值,如果该Key之前并不存在,则返回nil。STRLEN keyO(1)返回指定Key的字符值长度,如果Value不是string类型,Redis将执行失败并给出相关的错误信息。返回指定Key的Value字符长度,如果该Key不存在,返回0。SETEX key seconds valueO(1)原子性完成两个操作,一是设置该Key的值为指定字符串,同时设置该Key在Redis服务器中的存活时间(秒数)。该命令主要应用于Redis被当做Cache服务器使用时。 SETNX key value O(1) 如果指定的Key不存在,则设定该Key持有指定字符串Value,此时其效果等价于SET命令。相反,如果该Key已经存在,该命令将不做任何操作并返回。1表示设置成功,否则0。SETRANGE key offset value O(1) 替换指定Key的部分字符串值。从offset开始,替换的长度为该命令第三个参数value的字符串长度,其中如果offset的值大于该Key的原有值Value的字符串长度,Redis将会在Value的后面补齐(offset - strlen(value))数量的0x00,之后再追加新值。如果该键不存在,该命令会将其原值的长度假设为0,并在其后添补offset个0x00后再追加新值。鉴于字符串Value的最大长度为512M,因此offset的最大值为536870911。最后需要注意的是,如果该命令在执行时致使指定Key的原有值长度增加,这将会导致Redis重新分配足够的内存以容纳替换后的全部字符串,因此就会带来一定的性能折损。 修改后的字符串Value长度。GETRANGE key start endO(1) 如果截取的字符串长度很短,我们可以该命令的时间复杂度视为O(1),否则就是O(N),这里N表示截取的子字符串长度。该命令在截取子字符串时,将以闭区间的方式同时包含start(0表示第一个字符)和end所在的字符,如果end值超过Value的字符长度,该命令将只是截取从start开始之后所有的字符数据。子字符串 SETBIT key offset value O(1) 设置在指定Offset上BIT的值,该值只能为1或0,在设定后该命令返回该Offset上原有的BIT值。如果指定Key不存在,该命令将创建一个新值,并在指定的Offset上设定参数中的BIT值。如果Offset大于Value的字符长度,Redis将拉长Value值并在指定Offset上设置参数中的BIT值,中间添加的BIT值为0。最后需要说明的是Offset值必须大于0。 在指定Offset上的BIT原有值。GETBIT key offset O(1) 返回在指定Offset上BIT的值,0或1。如果Offset超过string value的长度,该命令将返回0,所以对于空字符串始终返回0。在指定Offset上的BIT值。 MGET key [key ...] O(N) N表示获取Key的数量。返回所有指定Keys的Values,如果其中某个Key不存在,或者其值不为string类型,该Key的Value将返回nil。返回一组指定Keys的Values的列表。MSET key value [key value ...] O(N) N表示指定Key的数量。该命令原子性的完成参数中所有key/value的设置操作,其具体行为可以看成是多次迭代执行SET命令。 该命令不会失败,始终返回OK。 MSETNX key value [key value ...] O(N)N表示指定Key的数量。该命令原子性的完成参数中所有key/value的设置操作,其具体行为可以看成是多次迭代执行SETNX命令。然而这里需要明确说明的是,如果在这一批Keys中有任意一个Key已经存在了,那么该操作将全部回滚,即所有的修改都不会生效。1表示所有Keys都设置成功,0则表示没有任何Key被修改。三、命令示例: 1. SET/GET/APPEND/STRLEN: /> redis-cli #执行Redis客户端工具。 redis 127.0.0.1:6379> exists mykey #判断该键是否存在,存在返回1,否则返回0。 (integer) 0 redis 127.0.0.1:6379> append mykey "hello" #该键并不存在,因此append命令返回当前Value的长度。 (integer) 5 redis 127.0.0.1:6379> append mykey " world" #该键已经存在,因此返回追加后Value的长度。 (integer) 11 redis 127.0.0.1:6379> get mykey #通过get命令获取该键,以判断append的结果。 "hello world" redis 127.0.0.1:6379> set mykey "this is a test" #通过set命令为键设置新值,并覆盖原有值。 OK redis 127.0.0.1:6379> get mykey "this is a test" redis 127.0.0.1:6379> strlen mykey #获取指定Key的字符长度,等效于C库中strlen函数。 (integer) 14 2. INCR/DECR/INCRBY/DECRBY: redis 127.0.0.1:6379> set mykey 20 #设置Key的值为20 OK redis 127.0.0.1:6379> incr mykey #该Key的值递增1 (integer) 21 redis 127.0.0.1:6379> decr mykey #该Key的值递减1 (integer) 20 redis 127.0.0.1:6379> del mykey #删除已有键。 (integer) 1 redis 127.0.0.1:6379> decr mykey #对空值执行递减操作,其原值被设定为0,递减后的值为-1 (integer) -1 redis 127.0.0.1:6379> del mykey (integer) 1 redis 127.0.0.1:6379> incr mykey #对空值执行递增操作,其原值被设定为0,递增后的值为1 (integer) 1 redis 127.0.0.1:6379> set mykey hello #将该键的Value设置为不能转换为整型的普通字符串。 OK redis 127.0.0.1:6379> incr mykey #在该键上再次执行递增操作时,Redis将报告错误信息。 (error) ERR value is not an integer or out of range redis 127.0.0.1:6379> set mykey 10 OK redis 127.0.0.1:6379> decrby mykey 5 (integer) 5 redis 127.0.0.1:6379> incrby mykey 10 (integer) 15 3. GETSET: redis 127.0.0.1:6379> incr mycounter #将计数器的值原子性的递增1 (integer) 1 #在获取计数器原有值的同时,并将其设置为新值,这两个操作原子性的同时完成。 redis 127.0.0.1:6379> getset mycounter 0 "1" redis 127.0.0.1:6379> get mycounter #查看设置后的结果。 "0" 4. SETEX: redis 127.0.0.1:6379> setex mykey 10 "hello" #设置指定Key的过期时间为10秒。 OK #通过ttl命令查看一下指定Key的剩余存活时间(秒数),0表示已经过期,-1表示永不过期。 redis 127.0.0.1:6379> ttl mykey (integer) 4 redis 127.0.0.1:6379> get mykey #在该键的存活期内我们仍然可以获取到它的Value。 "hello" redis 127.0.0.1:6379> ttl mykey #该ttl命令的返回值显示,该Key已经过期。 (integer) 0 redis 127.0.0.1:6379> get mykey #获取已过期的Key将返回nil。 (nil) 5. SETNX: redis 127.0.0.1:6379> del mykey #删除该键,以便于下面的测试验证。 (integer) 1 redis 127.0.0.1:6379> setnx mykey "hello" #该键并不存在,因此该命令执行成功。 (integer) 1 redis 127.0.0.1:6379> setnx mykey "world" #该键已经存在,因此本次设置没有产生任何效果。 (integer) 0 redis 127.0.0.1:6379> get mykey #从结果可以看出,返回的值仍为第一次设置的值。 "hello" 6. SETRANGE/GETRANGE: redis 127.0.0.1:6379> set mykey "hello world" #设定初始值。 OK redis 127.0.0.1:6379> setrange mykey 6 dd #从第六个字节开始替换2个字节(dd只有2个字节) (integer) 11 redis 127.0.0.1:6379> get mykey #查看替换后的值。 "hello ddrld" redis 127.0.0.1:6379> setrange mykey 20 dd #offset已经超过该Key原有值的长度了,该命令将会在末尾补0。 (integer) 22 redis 127.0.0.1:6379> get mykey #查看补0后替换的结果。 "hello ddrld\x00\x00\x00\x00\x00\x00\x00\x00\x00dd" redis 127.0.0.1:6379> del mykey #删除该Key。 (integer) 1 redis 127.0.0.1:6379> setrange mykey 2 dd #替换空值。 (integer) 4 redis 127.0.0.1:6379> get mykey #查看替换空值后的结果。 "\x00\x00dd" redis 127.0.0.1:6379> set mykey "0123456789" #设置新值。 OK redis 127.0.0.1:6379> getrange mykey 1 2 #截取该键的Value,从第一个字节开始,到第二个字节结束。 "12" redis 127.0.0.1:6379> getrange mykey 1 20 #20已经超过Value的总长度,因此将截取第一个字节后面的所有字节。 "123456789" 7. SETBIT/GETBIT: redis 127.0.0.1:6379> del mykey (integer) 1 redis 127.0.0.1:6379> setbit mykey 7 1 #设置从0开始计算的第七位BIT值为1,返回原有BIT值0 (integer) 0 redis 127.0.0.1:6379> get mykey #获取设置的结果,二进制的0000 0001的十六进制值为0x01 "\x01" redis 127.0.0.1:6379> setbit mykey 6 1 #设置从0开始计算的第六位BIT值为1,返回原有BIT值0 (integer) 0 redis 127.0.0.1:6379> get mykey #获取设置的结果,二进制的0000 0011的十六进制值为0x03 "\x03" redis 127.0.0.1:6379> getbit mykey 6 #返回了指定Offset的BIT值。 (integer) 1 redis 127.0.0.1:6379> getbit mykey 10 #Offset已经超出了value的长度,因此返回0。 (integer) 0 8. MSET/MGET/MSETNX: redis 127.0.0.1:6379> mset key1 "hello" key2 "world" #批量设置了key1和key2两个键。 OK redis 127.0.0.1:6379> mget key1 key2 #批量获取了key1和key2两个键的值。 1) "hello" 2) "world" #批量设置了key3和key4两个键,因为之前他们并不存在,所以该命令执行成功并返回1。 redis 127.0.0.1:6379> msetnx key3 "stephen" key4 "liu" (integer) 1 redis 127.0.0.1:6379> mget key3 key4 1) "stephen" 2) "liu" #批量设置了key3和key5两个键,但是key3已经存在,所以该命令执行失败并返回0。 redis 127.0.0.1:6379> msetnx key3 "hello" key5 "world" (integer) 0 #批量获取key3和key5,由于key5没有设置成功,所以返回nil。 redis 127.0.0.1:6379> mget key3 key5 1) "stephen" 2) (nil)
list
一、概述: 在Redis中,List类型是按照插入顺序排序的字符串链表。和数据结构中的普通链表一样,我们可以在其头部(left)和尾部(right)添加新的元素。在插入时,如果该键并不存在,Redis将为该键创建一个新的链表。与此相反,如果链表中所有的元素均被移除,那么该键也将会被从数据库中删除。List中可以包含的最大元素数量是4294967295。 从元素插入和删除的效率视角来看,如果我们是在链表的两头插入或删除元素,这将会是非常高效的操作,即使链表中已经存储了百万条记录,该操作也可以在常量时间内完成。然而需要说明的是,如果元素插入或删除操作是作用于链表中间,那将会是非常低效的。相信对于有良好数据结构基础的开发者而言,这一点并不难理解。 二、相关命令列表:
命令原型 时间复杂度 命令描述 返回值LPUSH key value [value ...] O(1)在指定Key所关联的List Value的头部插入参数中给出的所有Values。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的头部插入。如果该键的Value不是链表类型,该命令将返回相关的错误信息。 插入后链表中元素的数量。LPUSHX key value O(1) 仅有当参数中指定的Key存在时,该命令才会在其所关联的List Value的头部插入参数中给出的Value,否则将不会有任何操作发生。插入后链表中元素的数量。 LRANGE key start stop O(S+N)时间复杂度中的S为start参数表示的偏移量,N表示元素的数量。该命令的参数start和end都是0-based。即0表示链表头部(leftmost)的第一个元素。其中start的值也可以为负值,-1将表示链表中的最后一个元素,即尾部元素,-2表示倒数第二个并以此类推。该命令在获取元素时,start和end位置上的元素也会被取出。如果start的值大于链表中元素的数量,空链表将会被返回。如果end的值大于元素的数量,该命令则获取从start(包括start)开始,链表中剩余的所有元素。返回指定范围内元素的列表。LPOP key O(1) 返回并弹出指定Key关联的链表中的第一个元素,即头部元素,。如果该Key不存,返回nil。链表头部的元素。LLEN keyO(1) 返回指定Key关联的链表中元素的数量,如果该Key不存在,则返回0。如果与该Key关联的Value的类型不是链表,则返回相关的错误信息。链表中元素的数量。LREM key count value O(N) 时间复杂度中N表示链表中元素的数量。在指定Key关联的链表中,删除前count个值等于value的元素。如果count大于0,从头向尾遍历并删除,如果count小于0,则从尾向头遍历并删除。如果count等于0,则删除链表中所有等于value的元素。如果指定的Key不存在,则直接返回0。返回被删除的元素数量。LSET key index value O(N) 时间复杂度中N表示链表中元素的数量。但是设定头部或尾部的元素时,其时间复杂度为O(1)。设定链表中指定位置的值为新值,其中0表示第一个元素,即头部元素,-1表示尾部元素。如果索引值Index超出了链表中元素的数量范围,该命令将返回相关的错误信息。 LINDEX key index O(N) 时间复杂度中N表示在找到该元素时需要遍历的元素数量。对于头部或尾部元素,其时间复杂度为O(1)。该命令将返回链表中指定位置(index)的元素,index是0-based,表示头部元素,如果index为-1,表示尾部元素。如果与该Key关联的不是链表,该命令将返回相关的错误信息。返回请求的元素,如果index超出范围,则返回nil。LTRIM key start stop O(N) N表示被删除的元素数量。该命令将仅保留指定范围内的元素,从而保证链接中的元素数量相对恒定。start和stop参数都是0-based,0表示头部元素。和其他命令一样,start和stop也可以为负值,-1表示尾部元素。如果start大于链表的尾部,或start大于stop,该命令不错报错,而是返回一个空的链表,与此同时该Key也将被删除。如果stop大于元素的数量,则保留从start开始剩余的所有元素。 LINSERT key BEFORE|AFTER pivot value O(N) 时间复杂度中N表示在找到该元素pivot之前需要遍历的元素数量。这样意味着如果pivot位于链表的头部或尾部时,该命令的时间复杂度为O(1)。该命令的功能是在pivot元素的前面或后面插入参数中的元素value。如果Key不存在,该命令将不执行任何操作。如果与Key关联的Value类型不是链表,相关的错误信息将被返回。成功插入后链表中元素的数量,如果没有找到pivot,返回-1,如果key不存在,返回0。RPUSH key value [value ...] O(1) 在指定Key所关联的List Value的尾部插入参数中给出的所有Values。如果该Key不存在,该命令将在插入之前创建一个与该Key关联的空链表,之后再将数据从链表的尾部插入。如果该键的Value不是链表类型,该命令将返回相关的错误信息。 插入后链表中元素的数量。 RPUSHX key value O(1) 仅有当参数中指定的Key存在时,该命令才会在其所关联的List Value的尾部插入参数中给出的Value,否则将不会有任何操作发生。 插入后链表中元素的数量。 RPOP key O(1) 返回并弹出指定Key关联的链表中的最后一个元素,即尾部元素,。如果该Key不存,返回nil。 链表尾部的元素。 RPOPLPUSH source destination O(1) 原子性的从与source键关联的链表尾部弹出一个元素,同时再将弹出的元素插入到与destination键关联的链表的头部。如果source键不存在,该命令将返回nil,同时不再做任何其它的操作了。如果source和destination是同一个键,则相当于原子性的将其关联链表中的尾部元素移到该链表的头部。返回弹出和插入的元素。三、命令示例: 1. LPUSH/LPUSHX/LRANGE: /> redis-cli #在Shell提示符下启动redis客户端工具。 redis 127.0.0.1:6379> del mykey (integer) 1 #mykey键并不存在,该命令会创建该键及与其关联的List,之后在将参数中的values从左到右依次插入。 redis 127.0.0.1:6379> lpush mykey a b c d (integer) 4 #取从位置0开始到位置2结束的3个元素。 redis 127.0.0.1:6379> lrange mykey 0 2 1) "d" 2) "c" 3) "b" #取链表中的全部元素,其中0表示第一个元素,-1表示最后一个元素。 redis 127.0.0.1:6379> lrange mykey 0 -1 1) "d" 2) "c" 3) "b" 4) "a" #mykey2键此时并不存在,因此该命令将不会进行任何操作,其返回值为0。 redis 127.0.0.1:6379> lpushx mykey2 e (integer) 0 #可以看到mykey2没有关联任何List Value。 redis 127.0.0.1:6379> lrange mykey2 0 -1 (empty list or set) #mykey键此时已经存在,所以该命令插入成功,并返回链表中当前元素的数量。 redis 127.0.0.1:6379> lpushx mykey e (integer) 5 #获取该键的List Value的头部元素。 redis 127.0.0.1:6379> lrange mykey 0 0 1) "e" 2. LPOP/LLEN: redis 127.0.0.1:6379> lpush mykey a b c d (integer) 4 redis 127.0.0.1:6379> lpop mykey "d" redis 127.0.0.1:6379> lpop mykey "c" #在执行lpop命令两次后,链表头部的两个元素已经被弹出,此时链表中元素的数量是2 redis 127.0.0.1:6379> llen mykey (integer) 2 3. LREM/LSET/LINDEX/LTRIM: #为后面的示例准备测试数据。 redis 127.0.0.1:6379> lpush mykey a b c d a c (integer) 6 #从头部(left)向尾部(right)变量链表,删除2个值等于a的元素,返回值为实际删除的数量。 redis 127.0.0.1:6379> lrem mykey 2 a (integer) 2 #看出删除后链表中的全部元素。 redis 127.0.0.1:6379> lrange mykey 0 -1 1) "c" 2) "d" 3) "c" 4) "b" #获取索引值为1(头部的第二个元素)的元素值。 redis 127.0.0.1:6379> lindex mykey 1 "d" #将索引值为1(头部的第二个元素)的元素值设置为新值e。 redis 127.0.0.1:6379> lset mykey 1 e OK #查看是否设置成功。 redis 127.0.0.1:6379> lindex mykey 1 "e" #索引值6超过了链表中元素的数量,该命令返回nil。 redis 127.0.0.1:6379> lindex mykey 6 (nil) #设置的索引值6超过了链表中元素的数量,设置失败,该命令返回错误信息。 redis 127.0.0.1:6379> lset mykey 6 hh (error) ERR index out of range #仅保留索引值0到2之间的3个元素,注意第0个和第2个元素均被保留。 redis 127.0.0.1:6379> ltrim mykey 0 2 OK #查看trim后的结果。 redis 127.0.0.1:6379> lrange mykey 0 -1 1) "c" 2) "e" 3) "c" 4. LINSERT: #删除该键便于后面的测试。 redis 127.0.0.1:6379> del mykey (integer) 1 #为后面的示例准备测试数据。 redis 127.0.0.1:6379> lpush mykey a b c d e (integer) 5 #在a的前面插入新元素a1。 redis 127.0.0.1:6379> linsert mykey before a a1 (integer) 6 #查看是否插入成功,从结果看已经插入。注意lindex的index值是0-based。 redis 127.0.0.1:6379> lindex mykey 0 "e" #在e的后面插入新元素e2,从返回结果看已经插入成功。 redis 127.0.0.1:6379> linsert mykey after e e2 (integer) 7 #再次查看是否插入成功。 redis 127.0.0.1:6379> lindex mykey 1 "e2" #在不存在的元素之前或之后插入新元素,该命令操作失败,并返回-1。 redis 127.0.0.1:6379> linsert mykey after k a (integer) -1 #为不存在的Key插入新元素,该命令操作失败,返回0。 redis 127.0.0.1:6379> linsert mykey1 after a a2 (integer) 0 5. RPUSH/RPUSHX/RPOP/RPOPLPUSH: #删除该键,以便于后面的测试。 redis 127.0.0.1:6379> del mykey (integer) 1 #从链表的尾部插入参数中给出的values,插入顺序是从左到右依次插入。 redis 127.0.0.1:6379> rpush mykey a b c d (integer) 4 #通过lrange的可以获悉rpush在插入多值时的插入顺序。 redis 127.0.0.1:6379> lrange mykey 0 -1 1) "a" 2) "b" 3) "c" 4) "d" #该键已经存在并且包含4个元素,rpushx命令将执行成功,并将元素e插入到链表的尾部。 redis 127.0.0.1:6379> rpushx mykey e (integer) 5 #通过lindex命令可以看出之前的rpushx命令确实执行成功,因为索引值为4的元素已经是新元素了。 redis 127.0.0.1:6379> lindex mykey 4 "e" #由于mykey2键并不存在,因此该命令不会插入数据,其返回值为0。 redis 127.0.0.1:6379> rpushx mykey2 e (integer) 0 #在执行rpoplpush命令前,先看一下mykey中链表的元素有哪些,注意他们的位置关系。 redis 127.0.0.1:6379> lrange mykey 0 -1 1) "a" 2) "b" 3) "c" 4) "d" 5) "e" #将mykey的尾部元素e弹出,同时再插入到mykey2的头部(原子性的完成这两步操作)。 redis 127.0.0.1:6379> rpoplpush mykey mykey2 "e" #通过lrange命令查看mykey在弹出尾部元素后的结果。 redis 127.0.0.1:6379> lrange mykey 0 -1 1) "a" 2) "b" 3) "c" 4) "d" #通过lrange命令查看mykey2在插入元素后的结果。 redis 127.0.0.1:6379> lrange mykey2 0 -1 1) "e" #将source和destination设为同一键,将mykey中的尾部元素移到其头部。 redis 127.0.0.1:6379> rpoplpush mykey mykey "d" #查看移动结果。 redis 127.0.0.1:6379> lrange mykey 0 -1 1) "d" 2) "a" 3) "b" 4) "c" 四、链表结构的小技巧: 针对链表结构的Value,Redis在其官方文档中给出了一些实用技巧,如RPOPLPUSH命令,下面给出具体的解释。 Redis链表经常会被用于消息队列的服务,以完成多程序之间的消息交换。假设一个应用程序正在执行LPUSH操作向链表中添加新的元素,我们通常将这样的程序称之为"生产者(Producer)",而另外一个应用程序正在执行RPOP操作从链表中取出元素,我们称这样的程序为"消费者(Consumer)"。如果此时,消费者程序在取出消息元素后立刻崩溃,由于该消息已经被取出且没有被正常处理,那么我们就可以认为该消息已经丢失,由此可能会导致业务数据丢失,或业务状态的不一致等现象的发生。然而通过使用RPOPLPUSH命令,消费者程序在从主消息队列中取出消息之后再将其插入到备份队列中,直到消费者程序完成正常的处理逻辑后再将该消息从备份队列中删除。同时我们还可以提供一个守护进程,当发现备份队列中的消息过期时,可以重新将其再放回到主消息队列中,以便其它的消费者程序继续处理。
hash
一、概述: 我们可以将Redis中的Hashes类型看成具有String Key和String Value的map容器。所以该类型非常适合于存储值对象的信息。如Username、Password和Age等。如果Hash中包含很少的字段,那么该类型的数据也将仅占用很少的磁盘空间。每一个Hash可以存储4294967295个键值对。 二、相关命令列表:
命令原型 时间复杂度 命令描述 返回值HSET key field valueO(1)为指定的Key设定Field/Value对,如果Key不存在,该命令将创建新Key以参数中的Field/Value对,如果参数中的Field在该Key中已经存在,则用新值覆盖其原有值。 1表示新的Field被设置了新值,0表示Field已经存在,用新值覆盖原有值。 HGET key field O(1) 返回指定Key中指定Field的关联值。返回参数中Field的关联值,如果参数中的Key或Field不存,返回nil。HEXISTS key field O(1) 判断指定Key中的指定Field是否存在。1表示存在,0表示参数中的Field或Key不存在。HLEN key O(1)获取该Key所包含的Field的数量。返回Key包含的Field数量,如果Key不存在,返回0。HDEL key field [field ...] O(N)时间复杂度中的N表示参数中待删除的字段数量。从指定Key的Hashes Value中删除参数中指定的多个字段,如果不存在的字段将被忽略。如果Key不存在,则将其视为空Hashes,并返回0.实际删除的Field数量。HSETNX key field valueO(1)只有当参数中的Key或Field不存在的情况下,为指定的Key设定Field/Value对,否则该命令不会进行任何操作。 1表示新的Field被设置了新值,0表示Key或Field已经存在,该命令没有进行任何操作。HINCRBY key field increment O(1)增加指定Key中指定Field关联的Value的值。如果Key或Field不存在,该命令将会创建一个新Key或新Field,并将其关联的Value初始化为0,之后再指定数字增加的操作。该命令支持的数字是64位有符号整型,即increment可以负数。 返回运算后的值。HGETALL keyO(N) 时间复杂度中的N表示Key包含的Field数量。获取该键包含的所有Field/Value。其返回格式为一个Field、一个Value,并以此类推。Field/Value的列表。HKEYS key O(N)时间复杂度中的N表示Key包含的Field数量。返回指定Key的所有Fields名。Field的列表。HVALS key O(N)时间复杂度中的N表示Key包含的Field数量。返回指定Key的所有Values名。 Value的列表。 HMGET key field [field ...] O(N) 时间复杂度中的N表示请求的Field数量。获取和参数中指定Fields关联的一组Values。如果请求的Field不存在,其值返回nil。如果Key不存在,该命令将其视为空Hash,因此返回一组nil。返回和请求Fields关联的一组Values,其返回顺序等同于Fields的请求顺序。HMSET key field value [field value ...]O(N)时间复杂度中的N表示被设置的Field数量。逐对依次设置参数中给出的Field/Value对。如果其中某个Field已经存在,则用新值覆盖原有值。如果Key不存在,则创建新Key,同时设定参数中的Field/Value。三、命令示例: 1. HSET/HGET/HDEL/HEXISTS/HLEN/HSETNX: #在Shell命令行启动Redis客户端程序 /> redis-cli #给键值为myhash的键设置字段为field1,值为stephen。 redis 127.0.0.1:6379> hset myhash field1 "stephen" (integer) 1 #获取键值为myhash,字段为field1的值。 redis 127.0.0.1:6379> hget myhash field1 "stephen" #myhash键中不存在field2字段,因此返回nil。 redis 127.0.0.1:6379> hget myhash field2 (nil) #给myhash关联的Hashes值添加一个新的字段field2,其值为liu。 redis 127.0.0.1:6379> hset myhash field2 "liu" (integer) 1 #获取myhash键的字段数量。 redis 127.0.0.1:6379> hlen myhash (integer) 2 #判断myhash键中是否存在字段名为field1的字段,由于存在,返回值为1。 redis 127.0.0.1:6379> hexists myhash field1 (integer) 1 #删除myhash键中字段名为field1的字段,删除成功返回1。 redis 127.0.0.1:6379> hdel myhash field1 (integer) 1 #再次删除myhash键中字段名为field1的字段,由于上一条命令已经将其删除,因为没有删除,返回0。 redis 127.0.0.1:6379> hdel myhash field1 (integer) 0 #判断myhash键中是否存在field1字段,由于上一条命令已经将其删除,因为返回0。 redis 127.0.0.1:6379> hexists myhash field1 (integer) 0 #通过hsetnx命令给myhash添加新字段field1,其值为stephen,因为该字段已经被删除,所以该命令添加成功并返回1。 redis 127.0.0.1:6379> hsetnx myhash field1 stephen (integer) 1 #由于myhash的field1字段已经通过上一条命令添加成功,因为本条命令不做任何操作后返回0。 redis 127.0.0.1:6379> hsetnx myhash field1 stephen (integer) 0 2. HINCRBY: #删除该键,便于后面示例的测试。 redis 127.0.0.1:6379> del myhash (integer) 1 #准备测试数据,该myhash的field字段设定值1。 redis 127.0.0.1:6379> hset myhash field 5 (integer) 1 #给myhash的field字段的值加1,返回加后的结果。 redis 127.0.0.1:6379> hincrby myhash field 1 (integer) 6 #给myhash的field字段的值加-1,返回加后的结果。 redis 127.0.0.1:6379> hincrby myhash field -1 (integer) 5 #给myhash的field字段的值加-10,返回加后的结果。 redis 127.0.0.1:6379> hincrby myhash field -10 (integer) -5 3. HGETALL/HKEYS/HVALS/HMGET/HMSET: #删除该键,便于后面示例测试。 redis 127.0.0.1:6379> del myhash (integer) 1 #为该键myhash,一次性设置多个字段,分别是field1 = "hello", field2 = "world"。 redis 127.0.0.1:6379> hmset myhash field1 "hello" field2 "world" OK #获取myhash键的多个字段,其中field3并不存在,因为在返回结果中与该字段对应的值为nil。 redis 127.0.0.1:6379> hmget myhash field1 field2 field3 1) "hello" 2) "world" 3) (nil) #返回myhash键的所有字段及其值,从结果中可以看出,他们是逐对列出的。 redis 127.0.0.1:6379> hgetall myhash 1) "field1" 2) "hello" 3) "field2" 4) "world" #仅获取myhash键中所有字段的名字。 redis 127.0.0.1:6379> hkeys myhash 1) "field1" 2) "field2" #仅获取myhash键中所有字段的值。 redis 127.0.0.1:6379> hvals myhash 1) "hello" 2) "world"
set
一、概述: 在Redis中,我们可以将Set类型看作为没有排序的字符集合,和List类型一样,我们也可以在该类型的数据值上执行添加、删除或判断某一元素是否存在等操作。需要说明的是,这些操作的时间复杂度为O(1),即常量时间内完成次操作。Set可包含的最大元素数量是4294967295。 和List类型不同的是,Set集合中不允许出现重复的元素,这一点和C++标准库中的set容器是完全相同的。换句话说,如果多次添加相同元素,Set中将仅保留该元素的一份拷贝。和List类型相比,Set类型在功能上还存在着一个非常重要的特性,即在服务器端完成多个Sets之间的聚合计算操作,如unions、intersections和differences。由于这些操作均在服务端完成,因此效率极高,而且也节省了大量的网络IO开销。 二、相关命令列表:
命令原型 时间复杂度 命令描述 返回值SADD key member [member ...]O(N)时间复杂度中的N表示操作的成员数量。如果在插入的过程用,参数中有的成员在Set中已经存在,该成员将被忽略,而其它成员仍将会被正常插入。如果执行该命令之前,该Key并不存在,该命令将会创建一个新的Set,此后再将参数中的成员陆续插入。如果该Key的Value不是Set类型,该命令将返回相关的错误信息。本次操作实际插入的成员数量。SCARD keyO(1)获取Set中成员的数量。返回Set中成员的数量,如果该Key并不存在,返回0。SISMEMBER key memberO(1)判断参数中指定成员是否已经存在于与Key相关联的Set集合中。1表示已经存在,0表示不存在,或该Key本身并不存在。SMEMBERS keyO(N)时间复杂度中的N表示Set中已经存在的成员数量。获取与该Key关联的Set中所有的成员。返回Set中所有的成员。
SPOP key O(1) 随机的移除并返回Set中的某一成员。 由于Set中元素的布局不受外部控制,因此无法像List那样确定哪个元素位于Set的头部或者尾部。返回移除的成员,如果该Key并不存在,则返回nil。SREM key member [member ...]O(N) 时间复杂度中的N表示被删除的成员数量。从与Key关联的Set中删除参数中指定的成员,不存在的参数成员将被忽略,如果该Key并不存在,将视为空Set处理。从Set中实际移除的成员数量,如果没有则返回0。SRANDMEMBER key O(1) 和SPOP一样,随机的返回Set中的一个成员,不同的是该命令并不会删除返回的成员。返回随机位置的成员,如果Key不存在则返回nil。SMOVE source destination memberO(1) 原子性的将参数中的成员从source键移入到destination键所关联的Set中。因此在某一时刻,该成员或者出现在source中,或者出现在destination中。如果该成员在source中并不存在,该命令将不会再执行任何操作并返回0,否则,该成员将从source移入到destination。如果此时该成员已经在destination中存在,那么该命令仅是将该成员从source中移出。如果和Key关联的Value不是Set,将返回相关的错误信息。1表示正常移动,0表示source中并不包含参数成员。SDIFF key [key ...]O(N) 时间复杂度中的N表示所有Sets中成员的总数量。返回参数中第一个Key所关联的Set和其后所有Keys所关联的Sets中成员的差异。如果Key不存在,则视为空Set。差异结果成员的集合。SDIFFSTORE destination key [key ...] O(N) 该命令和SDIFF命令在功能上完全相同,两者之间唯一的差别是SDIFF返回差异的结果成员,而该命令将差异成员存储在destination关联的Set中。如果destination键已经存在,该操作将覆盖它的成员。返回差异成员的数量。SINTER key [key ...] O(N*M) 时间复杂度中的N表示最小Set中元素的数量,M则表示参数中Sets的数量。该命令将返回参数中所有Keys关联的Sets中成员的交集。因此如果参数中任何一个Key关联的Set为空,或某一Key不存在,那么该命令的结果将为空集。交集结果成员的集合。SINTERSTORE destination key [key ...]O(N*M) 该命令和SINTER命令在功能上完全相同,两者之间唯一的差别是SINTER返回交集的结果成员,而该命令将交集成员存储在destination关联的Set中。如果destination键已经存在,该操作将覆盖它的成员。返回交集成员的数量。 SUNION key [key ...] O(N)时间复杂度中的N表示所有Sets中成员的总数量。该命令将返回参数中所有Keys关联的Sets中成员的并集。并集结果成员的集合。SUNIONSTORE destination key [key ...] O(N) 该命令和SUNION命令在功能上完全相同,两者之间唯一的差别是SUNION返回并集的结果成员,而该命令将并集成员存储在destination关联的Set中。如果destination键已经存在,该操作将覆盖它的成员。 返回并集成员的数量。三、命令示例: 1. SADD/SMEMBERS/SCARD/SISMEMBER: #在Shell命令行下启动Redis的客户端程序。 /> redis-cli #插入测试数据,由于该键myset之前并不存在,因此参数中的三个成员都被正常插入。 redis 127.0.0.1:6379> sadd myset a b c (integer) 3 #由于参数中的a在myset中已经存在,因此本次操作仅仅插入了d和e两个新成员。 redis 127.0.0.1:6379> sadd myset a d e (integer) 2 #判断a是否已经存在,返回值为1表示存在。 redis 127.0.0.1:6379> sismember myset a (integer) 1 #判断f是否已经存在,返回值为0表示不存在。 redis 127.0.0.1:6379> sismember myset f (integer) 0 #通过smembers命令查看插入的结果,从结果可以,输出的顺序和插入顺序无关。 redis 127.0.0.1:6379> smembers myset 1) "c" 2) "d" 3) "a" 4) "b" 5) "e" #获取Set集合中元素的数量。 redis 127.0.0.1:6379> scard myset (integer) 5 2. SPOP/SREM/SRANDMEMBER/SMOVE: #删除该键,便于后面的测试。 redis 127.0.0.1:6379> del myset (integer) 1 #为后面的示例准备测试数据。 redis 127.0.0.1:6379> sadd myset a b c d (integer) 4 #查看Set中成员的位置。 redis 127.0.0.1:6379> smembers myset 1) "c" 2) "d" 3) "a" 4) "b" #从结果可以看出,该命令确实是随机的返回了某一成员。 redis 127.0.0.1:6379> srandmember myset "c" #Set中尾部的成员b被移出并返回,事实上b并不是之前插入的第一个或最后一个成员。 redis 127.0.0.1:6379> spop myset "b" #查看移出后Set的成员信息。 redis 127.0.0.1:6379> smembers myset 1) "c" 2) "d" 3) "a" #从Set中移出a、d和f三个成员,其中f并不存在,因此只有a和d两个成员被移出,返回为2。 redis 127.0.0.1:6379> srem myset a d f (integer) 2 #查看移出后的输出结果。 redis 127.0.0.1:6379> smembers myset 1) "c" #为后面的smove命令准备数据。 redis 127.0.0.1:6379> sadd myset a b (integer) 2 redis 127.0.0.1:6379> sadd myset2 c d (integer) 2 #将a从myset移到myset2,从结果可以看出移动成功。 redis 127.0.0.1:6379> smove myset myset2 a (integer) 1 #再次将a从myset移到myset2,由于此时a已经不是myset的成员了,因此移动失败并返回0。 redis 127.0.0.1:6379> smove myset myset2 a (integer) 0 #分别查看myset和myset2的成员,确认移动是否真的成功。 redis 127.0.0.1:6379> smembers myset 1) "b" redis 127.0.0.1:6379> smembers myset2 1) "c" 2) "d" 3) "a" 3. SDIFF/SDIFFSTORE/SINTER/SINTERSTORE: #为后面的命令准备测试数据。 redis 127.0.0.1:6379> sadd myset a b c d (integer) 4 redis 127.0.0.1:6379> sadd myset2 c (integer) 1 redis 127.0.0.1:6379> sadd myset3 a c e (integer) 3 #myset和myset2相比,a、b和d三个成员是两者之间的差异成员。再用这个结果继续和myset3进行差异比较,b和d是myset3不存在的成员。 redis 127.0.0.1:6379> sdiff myset myset2 myset3 1) "d" 2) "b" #将3个集合的差异成员存在在diffkey关联的Set中,并返回插入的成员数量。 redis 127.0.0.1:6379> sdiffstore diffkey myset myset2 myset3 (integer) 2 #查看一下sdiffstore的操作结果。 redis 127.0.0.1:6379> smembers diffkey 1) "d" 2) "b" #从之前准备的数据就可以看出,这三个Set的成员交集只有c。 redis 127.0.0.1:6379> sinter myset myset2 myset3 1) "c" #将3个集合中的交集成员存储到与interkey关联的Set中,并返回交集成员的数量。 redis 127.0.0.1:6379> sinterstore interkey myset myset2 myset3 (integer) 1 #查看一下sinterstore的操作结果。 redis 127.0.0.1:6379> smembers interkey 1) "c" #获取3个集合中的成员的并集。 redis 127.0.0.1:6379> sunion myset myset2 myset3 1) "b" 2) "c" 3) "d" 4) "e" 5) "a" #将3个集合中成员的并集存储到unionkey关联的set中,并返回并集成员的数量。 redis 127.0.0.1:6379> sunionstore unionkey myset myset2 myset3 (integer) 5 #查看一下suiionstore的操作结果。 redis 127.0.0.1:6379> smembers unionkey 1) "b" 2) "c" 3) "d" 4) "e" 5) "a" 四、应用范围: 1). 可以使用Redis的Set数据类型跟踪一些唯一性数据,比如访问某一博客的唯一IP地址信息。对于此场景,我们仅需在每次访问该博客时将访问者的IP存入Redis中,Set数据类型会自动保证IP地址的唯一性。 2). 充分利用Set类型的服务端聚合操作方便、高效的特性,可以用于维护数据对象之间的关联关系。比如所有购买某一电子设备的客户ID被存储在一个指定的Set中,而购买另外一种电子产品的客户ID被存储在另外一个Set中,如果此时我们想获取有哪些客户同时购买了这两种商品时,Set的intersections命令就可以充分发挥它的方便和效率的优势了。
sorted-set
一、概述: Sorted-Sets和Sets类型极为相似,它们都是字符串的集合,都不允许重复的成员出现在一个Set中。它们之间的主要差别是Sorted-Sets中的每一个成员都会有一个分数(score)与之关联,Redis正是通过分数来为集合中的成员进行从小到大的排序。然而需要额外指出的是,尽管Sorted-Sets中的成员必须是唯一的,但是分数(score)却是可以重复的。 在Sorted-Set中添加、删除或更新一个成员都是非常快速的操作,其时间复杂度为集合中成员数量的对数。由于Sorted-Sets中的成员在集合中的位置是有序的,因此,即便是访问位于集合中部的成员也仍然是非常高效的。事实上,Redis所具有的这一特征在很多其它类型的数据库中是很难实现的,换句话说,在该点上要想达到和Redis同样的高效,在其它数据库中进行建模是非常困难的。 二、相关命令列表:
命令原型 时间复杂度 命令描述 返回值ZADD key score member [score] [member] O(log(N))时间复杂度中的N表示Sorted-Sets中成员的数量。添加参数中指定的所有成员及其分数到指定key的Sorted-Set中,在该命令中我们可以指定多组score/member作为参数。如果在添加时参数中的某一成员已经存在,该命令将更新此成员的分数为新值,同时再将该成员基于新值重新排序。如果键不存在,该命令将为该键创建一个新的Sorted-Sets Value,并将score/member对插入其中。如果该键已经存在,但是与其关联的Value不是Sorted-Sets类型,相关的错误信息将被返回。本次操作实际插入的成员数量。ZCARD key O(1)获取与该Key相关联的Sorted-Sets中包含的成员数量。返回Sorted-Sets中的成员数量,如果该Key不存在,返回0。ZCOUNT key min maxO(log(N)+M) 时间复杂度中的N表示Sorted-Sets中成员的数量,M则表示min和max之间元素的数量。该命令用于获取分数(score)在min和max之间的成员数量。针对min和max参数需要额外说明的是,-inf和+inf分别表示Sorted-Sets中分数的最高值和最低值。缺省情况下,min和max表示的范围是闭区间范围,即min zrank myzset one (integer) 0 #成员four并不存在,因此返回nil。 redis 127.0.0.1:6379> zrank myzset four (nil) #获取myzset键中成员的数量。 redis 127.0.0.1:6379> zcard myzset (integer) 3 #返回与myzset关联的Sorted-Set中,分数满足表达式1 zrem myzset one two (integer) 2 #查看是否删除成功。 redis 127.0.0.1:6379> zcard myzset (integer) 1 #获取成员three的分数。返回值是字符串形式。 redis 127.0.0.1:6379> zscore myzset three "3" #由于成员two已经被删除,所以该命令返回nil。 redis 127.0.0.1:6379> zscore myzset two (nil) #将成员one的分数增加2,并返回该成员更新后的分数。 redis 127.0.0.1:6379> zincrby myzset 2 one "3" #将成员one的分数增加-1,并返回该成员更新后的分数。 redis 127.0.0.1:6379> zincrby myzset -1 one "2" #查看在更新了成员的分数后是否正确。 redis 127.0.0.1:6379> zrange myzset 0 -1 WITHSCORES 1) "one" 2) "2" 3) "two" 4) "2" 5) "three" 6) "3" 2. ZRANGEBYSCORE/ZREMRANGEBYRANK/ZREMRANGEBYSCORE redis 127.0.0.1:6379> del myzset (integer) 1 redis 127.0.0.1:6379> zadd myzset 1 one 2 two 3 three 4 four (integer) 4 #获取分数满足表达式1 zrangebyscore myzset -inf +inf limit 2 3 1) "three" 2) "four" #删除分数满足表达式1 zrange myzset 0 -1 1) "three" 2) "four" #删除位置索引满足表达式0 zcard myzset (integer) 0 3. ZREVRANGE/ZREVRANGEBYSCORE/ZREVRANK: #为后面的示例准备测试数据。 redis 127.0.0.1:6379> del myzset (integer) 0 redis 127.0.0.1:6379> zadd myzset 1 one 2 two 3 three 4 four (integer) 4 #以位置索引从高到低的方式获取并返回此区间内的成员。 redis 127.0.0.1:6379> zrevrange myzset 0 -1 WITHSCORES 1) "four" 2) "4" 3) "three" 4) "3" 5) "two" 6) "2" 7) "one" 8) "1" #由于是从高到低的排序,所以位置等于0的是four,1是three,并以此类推。 redis 127.0.0.1:6379> zrevrange myzset 1 3 1) "three" 2) "two" 3) "one" #由于是从高到低的排序,所以one的位置是3。 redis 127.0.0.1:6379> zrevrank myzset one (integer) 3 #由于是从高到低的排序,所以four的位置是0。 redis 127.0.0.1:6379> zrevrank myzset four (integer) 0 #获取分数满足表达式3 >= score >= 0的成员,并以相反的顺序输出,即从高到底的顺序。 redis 127.0.0.1:6379> zrevrangebyscore myzset 3 0 1) "three" 2) "two" 3) "one" #该命令支持limit选项,其含义等同于zrangebyscore中的该选项,只是在计算位置时按照相反的顺序计算和获取。 redis 127.0.0.1:6379> zrevrangebyscore myzset 4 0 limit 1 2 1) "three" 2) "two" 四、应用范围: 1). 可以用于一个大型在线游戏的积分排行榜。每当玩家的分数发生变化时,可以执行ZADD命令更新玩家的分数,此后再通过ZRANGE命令获取积分TOP TEN的用户信息。当然我们也可以利用ZRANK命令通过username来获取玩家的排行信息。最后我们将组合使用ZRANGE和ZRANK命令快速的获取和某个玩家积分相近的其他用户的信息。 2). Sorted-Sets类型还可用于构建索引数据。 key操作一、概述: 在该系列的前几篇博客中,主要讲述的是与Redis数据类型相关的命令,如String、List、Set、Hashes和Sorted-Set。这些命令都具有一个共同点,即所有的操作都是针对与Key关联的Value的。而该篇博客将主要讲述与Key相关的Redis命令。学习这些命令对于学习Redis是非常重要的基础,也是能够充分挖掘Redis潜力的利器。 在该篇博客中,我们将一如既往的给出所有相关命令的明细列表和典型示例,以便于我们现在的学习和今后的查阅。 二、相关命令列表:
命令原型 时间复杂度 命令描述 返回值KEYS patternO(N)时间复杂度中的N表示数据库中Key的数量。获取所有匹配pattern参数的Keys。需要说明的是,在我们的正常操作中应该尽量避免对该命令的调用,因为对于大型数据库而言,该命令是非常耗时的,对Redis服务器的性能打击也是比较大的。pattern支持glob-style的通配符格式,如*表示任意一个或多个字符,?表示任意字符,[abc]表示方括号中任意一个字母。匹配模式的键列表。DEL key [key ...]O(N)时间复杂度中的N表示删除的Key数量。从数据库删除中参数中指定的keys,如果指定键不存在,则直接忽略。还需要另行指出的是,如果指定的Key关联的数据类型不是String类型,而是List、Set、Hashes和Sorted Set等容器类型,该命令删除每个键的时间复杂度为O(M),其中M表示容器中元素的数量。而对于String类型的Key,其时间复杂度为O(1)。实际被删除的Key数量。EXISTS key O(1)判断指定键是否存在。1表示存在,0表示不存在。MOVE key db O(1)将当前数据库中指定的键Key移动到参数中指定的数据库中。如果该Key在目标数据库中已经存在,或者在当前数据库中并不存在,该命令将不做任何操作并返回0。 移动成功返回1,否则0。RENAME key newkey O(1)为指定指定的键重新命名,如果参数中的两个Keys的命令相同,或者是源Key不存在,该命令都会返回相关的错误信息。如果newKey已经存在,则直接覆盖。 RENAMENX key newkeyO(1)如果新值不存在,则将参数中的原值修改为新值。其它条件和RENAME一致。1表示修改成功,否则0。PERSIST keyO(1)如果Key存在过期时间,该命令会将其过期时间消除,使该Key不再有超时,而是可以持久化存储。1表示Key的过期时间被移出,0表示该Key不存在或没有过期时间。EXPIRE key seconds O(1) 该命令为参数中指定的Key设定超时的秒数,在超过该时间后,Key被自动的删除。如果该Key在超时之前被修改,与该键关联的超时将被移除。 1表示超时被设置,0则表示Key不存在,或不能被设置。EXPIREAT key timestamp O(1) 该命令的逻辑功能和EXPIRE完全相同,唯一的差别是该命令指定的超时时间是绝对时间,而不是相对时间。该时间参数是Unix timestamp格式的,即从1970年1月1日开始所流经的秒数。1表示超时被设置,0则表示Key不存在,或不能被设置。 TTL key O(1)获取该键所剩的超时描述。 返回所剩描述,如果该键不存在或没有超时设置,则返回-1。RANDOMKEYO(1) 从当前打开的数据库中随机的返回一个Key。返回的随机键,如果该数据库是空的则返回nil。TYPE key O(1) 获取与参数中指定键关联值的类型,该命令将以字符串的格式返回。返回的字符串为string、list、set、hash和zset,如果key不存在返回none。SORT key [BY pattern] [LIMIT offset count] [GET pattern [GET pattern ...]] [ASC|DESC] [ALPHA] [STORE destination] O(N+M*log(M)) 这个命令相对来说是比较复杂的,因此我们这里只是给出最基本的用法,有兴趣的网友可以去参考redis的官方文档。返回排序后的原始列表。三、命令示例: 1. KEYS/RENAME/DEL/EXISTS/MOVE/RENAMENX: #在Shell命令行下启动Redis客户端工具。 /> redis-cli #清空当前选择的数据库,以便于对后面示例的理解。 redis 127.0.0.1:6379> flushdb OK #添加String类型的模拟数据。 redis 127.0.0.1:6379> set mykey 2 OK redis 127.0.0.1:6379> set mykey2 "hello" OK #添加Set类型的模拟数据。 redis 127.0.0.1:6379> sadd mysetkey 1 2 3 (integer) 3 #添加Hash类型的模拟数据。 redis 127.0.0.1:6379> hset mmtest username "stephen" (integer) 1 #根据参数中的模式,获取当前数据库中符合该模式的所有key,从输出可以看出,该命令在执行时并不区分与Key关联的Value类型。 redis 127.0.0.1:6379> keys my* 1) "mysetkey" 2) "mykey" 3) "mykey2" #删除了两个Keys。 redis 127.0.0.1:6379> del mykey mykey2 (integer) 2 #查看一下刚刚删除的Key是否还存在,从返回结果看,mykey确实已经删除了。 redis 127.0.0.1:6379> exists mykey (integer) 0 #查看一下没有删除的Key,以和上面的命令结果进行比较。 redis 127.0.0.1:6379> exists mysetkey (integer) 1 #将当前数据库中的mysetkey键移入到ID为1的数据库中,从结果可以看出已经移动成功。 redis 127.0.0.1:6379> move mysetkey 1 (integer) 1 #打开ID为1的数据库。 redis 127.0.0.1:6379> select 1 OK #查看一下刚刚移动过来的Key是否存在,从返回结果看已经存在了。 redis 127.0.0.1:6379[1]> exists mysetkey (integer) 1 #在重新打开ID为0的缺省数据库。 redis 127.0.0.1:6379[1]> select 0 OK #查看一下刚刚移走的Key是否已经不存在,从返回结果看已经移走。 redis 127.0.0.1:6379> exists mysetkey (integer) 0 #准备新的测试数据。 redis 127.0.0.1:6379> set mykey "hello" OK #将mykey改名为mykey1 redis 127.0.0.1:6379> rename mykey mykey1 OK #由于mykey已经被重新命名,再次获取将返回nil。 redis 127.0.0.1:6379> get mykey (nil) #通过新的键名获取。 redis 127.0.0.1:6379> get mykey1 "hello" #由于mykey已经不存在了,所以返回错误信息。 redis 127.0.0.1:6379> rename mykey mykey1 (error) ERR no such key #为renamenx准备测试key redis 127.0.0.1:6379> set oldkey "hello" OK redis 127.0.0.1:6379> set newkey "world" OK #由于newkey已经存在,因此该命令未能成功执行。 redis 127.0.0.1:6379> renamenx oldkey newkey (integer) 0 #查看newkey的值,发现它也没有被renamenx覆盖。 redis 127.0.0.1:6379> get newkey "world" 2. PERSIST/EXPIRE/EXPIREAT/TTL: #为后面的示例准备的测试数据。 redis 127.0.0.1:6379> set mykey "hello" OK #将该键的超时设置为100秒。 redis 127.0.0.1:6379> expire mykey 100 (integer) 1 #通过ttl命令查看一下还剩下多少秒。 redis 127.0.0.1:6379> ttl mykey (integer) 97 #立刻执行persist命令,该存在超时的键变成持久化的键,即将该Key的超时去掉。 redis 127.0.0.1:6379> persist mykey (integer) 1 #ttl的返回值告诉我们,该键已经没有超时了。 redis 127.0.0.1:6379> ttl mykey (integer) -1 #为后面的expire命令准备数据。 redis 127.0.0.1:6379> del mykey (integer) 1 redis 127.0.0.1:6379> set mykey "hello" OK #设置该键的超时被100秒。 redis 127.0.0.1:6379> expire mykey 100 (integer) 1 #用ttl命令看一下当前还剩下多少秒,从结果中可以看出还剩下96秒。 redis 127.0.0.1:6379> ttl mykey (integer) 96 #重新更新该键的超时时间为20秒,从返回值可以看出该命令执行成功。 redis 127.0.0.1:6379> expire mykey 20 (integer) 1 #再用ttl确认一下,从结果中可以看出果然被更新了。 redis 127.0.0.1:6379> ttl mykey (integer) 17 #立刻更新该键的值,以使其超时无效。 redis 127.0.0.1:6379> set mykey "world" OK #从ttl的结果可以看出,在上一条修改该键的命令执行后,该键的超时也无效了。 redis 127.0.0.1:6379> ttl mykey (integer) -1 3. TYPE/RANDOMKEY/SORT: #由于mm键在数据库中不存在,因此该命令返回none。 redis 127.0.0.1:6379> type mm none #mykey的值是字符串类型,因此返回string。 redis 127.0.0.1:6379> type mykey string #准备一个值是set类型的键。 redis 127.0.0.1:6379> sadd mysetkey 1 2 (integer) 2 #mysetkey的键是set,因此返回字符串set。 redis 127.0.0.1:6379> type mysetkey set #返回数据库中的任意键。 redis 127.0.0.1:6379> randomkey "oldkey" #清空当前打开的数据库。 redis 127.0.0.1:6379> flushdb OK #由于没有数据了,因此返回nil。 redis 127.0.0.1:6379> randomkey (nil)
事务一、概述: 和众多其它数据库一样,Redis作为NoSQL数据库也同样提供了事务机制。在Redis中,MULTI/EXEC/DISCARD/WATCH这四个命令是我们实现事务的基石。相信对有关系型数据库开发经验的开发者而言这一概念并不陌生,即便如此,我们还是会简要的列出Redis中事务的实现特征: 1). 在事务中的所有命令都将会被串行化的顺序执行,事务执行期间,Redis不会再为其它客户端的请求提供任何服务,从而保证了事物中的所有命令被原子的执行。 2). 和关系型数据库中的事务相比,在Redis事务中如果有某一条命令执行失败,其后的命令仍然会被继续执行。 3). 我们可以通过MULTI命令开启一个事务,有关系型数据库开发经验的人可以将其理解为"BEGIN TRANSACTION"语句。在该语句之后执行的命令都将被视为事务之内的操作,最后我们可以通过执行EXEC/DISCARD命令来提交/回滚该事务内的所有操作。这两个Redis命令可被视为等同于关系型数据库中的COMMIT/ROLLBACK语句。 4). 在事务开启之前,如果客户端与服务器之间出现通讯故障并导致网络断开,其后所有待执行的语句都将不会被服务器执行。然而如果网络中断事件是发生在客户端执行EXEC命令之后,那么该事务中的所有命令都会被服务器执行。 5). 当使用Append-Only模式时,Redis会通过调用系统函数write将该事务内的所有写操作在本次调用中全部写入磁盘。然而如果在写入的过程中出现系统崩溃,如电源故障导致的宕机,那么此时也许只有部分数据被写入到磁盘,而另外一部分数据却已经丢失。Redis服务器会在重新启动时执行一系列必要的一致性检测,一旦发现类似问题,就会立即退出并给出相应的错误提示。此时,我们就要充分利用Redis工具包中提供的redis-check-aof工具,该工具可以帮助我们定位到数据不一致的错误,并将已经写入的部分数据进行回滚。修复之后我们就可以再次重新启动Redis服务器了。 二、相关命令列表:
命令原型 时间复杂度 命令描述 返回值MULTI 用于标记事务的开始,其后执行的命令都将被存入命令队列,直到执行EXEC时,这些命令才会被原子的执行。始终返回OKEXEC 执行在一个事务内命令队列中的所有命令,同时将当前连接的状态恢复为正常状态,即非事务状态。如果在事务中执行了WATCH命令,那么只有当WATCH所监控的Keys没有被修改的前提下,EXEC命令才能执行事务队列中的所有命令,否则EXEC将放弃当前事务中的所有命令。原子性的返回事务中各条命令的返回结果。如果在事务中使用了WATCH,一旦事务被放弃,EXEC将返回NULL-multi-bulk回复。DISCARD 回滚事务队列中的所有命令,同时再将当前连接的状态恢复为正常状态,即非事务状态。如果WATCH命令被使用,该命令将UNWATCH所有的Keys。始终返回OK。WATCH key [key ...]O(1)在MULTI命令执行之前,可以指定待监控的Keys,然而在执行EXEC之前,如果被监控的Keys发生修改,EXEC将放弃执行该事务队列中的所有命令。始终返回OK。UNWATCHO(1)取消当前事务中指定监控的Keys,如果执行了EXEC或DISCARD命令,则无需再手工执行该命令了,因为在此之后,事务中所有被监控的Keys都将自动取消。始终返回OK。三、命令示例: 1. 事务被正常执行: #在Shell命令行下执行Redis的客户端工具。 /> redis-cli #在当前连接上启动一个新的事务。 redis 127.0.0.1:6379> multi OK #执行事务中的第一条命令,从该命令的返回结果可以看出,该命令并没有立即执行,而是存于事务的命令队列。 redis 127.0.0.1:6379> incr t1 QUEUED #又执行一个新的命令,从结果可以看出,该命令也被存于事务的命令队列。 redis 127.0.0.1:6379> incr t2 QUEUED #执行事务命令队列中的所有命令,从结果可以看出,队列中命令的结果得到返回。 redis 127.0.0.1:6379> exec 1) (integer) 1 2) (integer) 1 2. 事务中存在失败的命令: #开启一个新的事务。 redis 127.0.0.1:6379> multi OK #设置键a的值为string类型的3。 redis 127.0.0.1:6379> set a 3 QUEUED #从键a所关联的值的头部弹出元素,由于该值是字符串类型,而lpop命令仅能用于List类型,因此在执行exec命令时,该命令将会失败。 redis 127.0.0.1:6379> lpop a QUEUED #再次设置键a的值为字符串4。 redis 127.0.0.1:6379> set a 4 QUEUED #获取键a的值,以便确认该值是否被事务中的第二个set命令设置成功。 redis 127.0.0.1:6379> get a QUEUED #从结果中可以看出,事务中的第二条命令lpop执行失败,而其后的set和get命令均执行成功,这一点是Redis的事务与关系型数据库中的事务之间最为重要的差别。 redis 127.0.0.1:6379> exec 1) OK 2) (error) ERR Operation against a key holding the wrong kind of value 3) OK 4) "4" 3. 回滚事务: #为键t2设置一个事务执行前的值。 redis 127.0.0.1:6379> set t2 tt OK #开启一个事务。 redis 127.0.0.1:6379> multi OK #在事务内为该键设置一个新值。 redis 127.0.0.1:6379> set t2 ttnew QUEUED #放弃事务。 redis 127.0.0.1:6379> discard OK #查看键t2的值,从结果中可以看出该键的值仍为事务开始之前的值。 redis 127.0.0.1:6379> get t2 "tt" 四、WATCH命令和基于CAS的乐观锁: 在Redis的事务中,WATCH命令可用于提供CAS(check-and-set)功能。假设我们通过WATCH命令在事务执行之前监控了多个Keys,倘若在WATCH之后有任何Key的值发生了变化,EXEC命令执行的事务都将被放弃,同时返回Null multi-bulk应答以通知调用者事务执行失败。例如,我们再次假设Redis中并未提供incr命令来完成键值的原子性递增,如果要实现该功能,我们只能自行编写相应的代码。其伪码如下: val = GET mykey val = val + 1 SET mykey $val 以上代码只有在单连接的情况下才可以保证执行结果是正确的,因为如果在同一时刻有多个客户端在同时执行该段代码,那么就会出现多线程程序中经常出现的一种错误场景--竞态争用(race condition)。比如,客户端A和B都在同一时刻读取了mykey的原有值,假设该值为10,此后两个客户端又均将该值加一后set回Redis服务器,这样就会导致mykey的结果为11,而不是我们认为的12。为了解决类似的问题,我们需要借助WATCH命令的帮助,见如下代码: WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC 和此前代码不同的是,新代码在获取mykey的值之前先通过WATCH命令监控了该键,此后又将set命令包围在事务中,这样就可以有效的保证每个连接在执行EXEC之前,如果当前连接获取的mykey的值被其它连接的客户端修改,那么当前连接的EXEC命令将执行失败。这样调用者在判断返回值后就可以获悉val是否被重新设置成功。
主从复制服务器管理
一、概述: Redis在设计之初就被定义为长时间不间断运行的服务进程,因此大多数系统配置参数都可以在不重新启动进程的情况下立即生效。即便是将当前的持久化模式从AOF切换到RDB也无需重启。 在Redis中,提供了一组和服务器管理相关的命令,其中就包含和参数设置有关的CONFIG SET/GET command。 二、相关命令列表:
命令原型 时间复杂度 命令描述 返回值CONFIGGET parameter 主要用于读取服务器的运行时参数,但是并不是所有的配置参数都可以通过该命令进行读取。其中该命令的参数接受glob风格的模式匹配规则,因此如果参数中包含模式元字符,那么所有匹配的参数都将以key/value方式被列出。如果参数是*,那么该命令支持的所有参数都将被列出。最后需要指出的是,和redis.conf中不同的是,在命令中不能使用数量缩写格式,如GB、KB等,只能使用表示字节数量的整数值。 CONFIG SET parameter value 该命令用于重新配置Redis服务器的运行时参数,在设置成功之后无需重启便可生效。然而并非所有的参数都可以通过该命令进行动态设置,如果需要获悉该命令支持哪些参数,可以查看CONFIG GET * 命令的执行结果。如果想在一个命令中设置多个同类型参数,如redis.conf配置文件中的save参数:save 900 1/save 300 10。在该命令中我们可以将多个key/value用双引号括起,并用空格符隔开,如:config set save "900 1 300 10"。 OK表示设置成功,否则返回相关的错误信息。CONFIG RESETSTAT O(1)Reset INFO命令给出的统计数字。始终返回OK。DBSIZE 返回当前打开的数据库中Keys的数量。Key的数量。FLUSHALL 清空当前服务器管理的数据库中的所有Keys,不仅限于当前打开的数据库。 FLUSHDB 清空当前数据库中的所有Keys。 INFO 获取和服务器运行状况相关的一些列统计数字。 SAVE 设置RDB持久化模式的保存策略。 SHUTDOWN 停止所有的客户端,同时以阻塞的方式执行内存数据持久化。如果AOF模式被启用,则将缓存中的数据flush到AOF文件。退出服务器。 SLAVEOF host port 该命令用于修改SLAVE服务器的复制设置。如果一个Redis服务器已经处于SLAVE状态,SLAVEOF NO ONE命令将关闭当前服务器的被复制状态,与此同时将该服务器切换到MASTER状态。该命令的参数将指定MASTER服务器的监听IP和端口。还有一种情况是,当前服务器已经是另外一台MASTER的SLAVE了,在执行该命令后,当前服务器将终止和之前MASTER之间的复制关系,而将成为新MASTER的SLAVE,之前MASTER中的数据也将被清空,改为新MASTER中的数据。然而如果在当前SLAVE服务器上执行的是SLAVEOF NO ONE命令,那么该服务器只是中断与当前MASTER的复制关系,并升级为独立的MASTER,其中的数据也不会被清空。 SLOWLOG subcommand [argument] 该命令主要用于读取执行时间较长的命令。其中执行时间的评判标准仅为命令本身的执行时间,并不包括网络交互时间。和该命令相关的配置参数主要有两个,第一个就是执行之间的阈值(以微秒为单位),即执行时间超过该值的命令都会被存入slowlog队列,以供该命令读取。第二个是slowlog队列的长度,如果当前命令在存入之前,该队列中的命令已经等于该参数,在命令进入之前,需要将队列中最老的命令移出队列。这样可以保证该队列所占用的内存总量保持在一个相对恒定的大小。由于slowlog队列不会被持久化到磁盘,因此Redis在收集命令时不会对性能产生很大的影响。通常我们可以将参数"slowlog-log-slower-than"设置为0,以便收集所有命令的执行时间。该命令还包含以下几个子命令: 1). SLOWLOG GET N: 从slowlog队列中读取命令信息,N表示最近N条命令的信息。 2). SLOWLOG LEN:获取slowlog队列的长度。 3). SLOWLOG RESET:清空slowlog中的内容。 最后给出SLOWLOG GET命令返回信息的解释。 redis 127.0.0.1:6379> slowlog get 10 1) 1) (integer) 5 #唯一表示符,在Redis重启之前,该值保证唯一。 2) (integer) 1330369320 #Unix Timestamp格式表示的命令执行时间。 3) (integer) 13 #命令执行所用的微秒数。 4) 1) "slowlog" #以字符串数组的格式输出收集到的命令及其参数。 2) "reset"一、请求应答协议和RTT: Redis是一种典型的基于C/S模型的TCP服务器。在客户端与服务器的通讯过程中,通常都是客户端率先发起请求,服务器在接收到请求后执行相应的任务,最后再将获取的数据或处理结果以应答的方式发送给客户端。在此过程中,客户端都会以阻塞的方式等待服务器返回的结果。见如下命令序列: Client: INCR X Server: 1 Client: INCR X Server: 2 Client: INCR X Server: 3 Client: INCR X Server: 4 在每一对请求与应答的过程中,我们都不得不承受网络传输所带来的额外开销。我们通常将这种开销称为RTT(Round Trip Time)。现在我们假设每一次请求与应答的RTT为250毫秒,而我们的服务器可以在一秒内处理100k的数据,可结果则是我们的服务器每秒至多处理4条请求。要想解决这一性能问题,我们该如何进行优化呢? 二、管线(pipelining): Redis在很早的版本中就已经提供了对命令管线的支持。在给出具体解释之前,我们先将上面的同步应答方式的例子改造为基于命令管线的异步应答方式,这样可以让大家有一个更好的感性认识。 Client: INCR X Client: INCR X Client: INCR X Client: INCR X Server: 1 Server: 2 Server: 3 Server: 4 从以上示例可以看出,客户端在发送命令之后,不用立刻等待来自服务器的应答,而是可以继续发送后面的命令。在命令发送完毕后,再一次性的读取之前所有命令的应答。这样便节省了同步方式中RTT的开销。 最后需要说明的是,如果Redis服务器发现客户端的请求是基于管线的,那么服务器端在接受到请求并处理之后,会将每条命令的应答数据存入队列,之后再发送到客户端。 三、Benchmark: 以下是来自Redis官网的测试用例和测试结果。需要说明的是,该测试是基于loopback(127.0.0.1)的,因此RTT所占用的时间相对较少,如果是基于实际网络接口,那么管线机制所带来的性能提升就更为显著了。
1 require 'rubygems' 2 require 'redis' 3 4 def bench(descr) 5 start = Time.now 6 yield 7 puts "#{descr} #{Time.now-start} seconds" 8 end 9 10 def without_pipelining 11 r = Redis.new 12 10000.times { 13 r.ping 14 } 15 end 16 17 def with_pipelining 18 r = Redis.new 19 r.pipelined { 20 10000.times { 21 r.ping 22 } 23 } 24 end 25 26 bench("without pipelining") { 27 without_pipelining 28 } 29 bench("with pipelining") { 30 with_pipelining 31 } 32 //without pipelining 1.185238 seconds 33 //with pipelining 0.250783 seconds
在之前的博客中已经非常详细的介绍了Redis的各种操作命令、运行机制和服务器初始化参数配置。本篇博客是该系列博客中的最后一篇,在这里将给出基于Redis客户端组件访问并操作Redis服务器的代码示例。然而需要说明的是,由于Redis官方并未提供基于C接口的Windows平台客户端,因此下面的示例仅可运行于Linux/Unix平台。但是对于使用其它编程语言的开发者而言,如C#和Java,Redis则提供了针对这些语言的客户端组件,通过该方式,同样可以达到基于Windows平台与Redis服务器进行各种交互的目的。 该篇博客中使用的客户端来自于Redis官方网站,是Redis推荐的基于C接口的客户端组件,见如下链接: https://github.com/antirez/hiredis 在下面的代码示例中,将给出两种最为常用的Redis命令操作方式,既普通调用方式和基于管线的调用方式。 注:在阅读代码时请留意注释。
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 9 void doTest() 10 { 11 int timeout = 10000; 12 struct timeval tv; 13 tv.tv_sec = timeout / 1000; 14 tv.tv_usec = timeout * 1000; 15 //以带有超时的方式链接Redis服务器,同时获取与Redis连接的上下文对象。 16 //该对象将用于其后所有与Redis操作的函数。 17 redisContext* c = redisConnectWithTimeout("192.168.149.137",6379,tv); 18 if (c->err) { 19 redisFree(c); 20 return; 21 } 22 const char* command1 = "set stest1 value1"; 23 redisReply* r = (redisReply*)redisCommand(c,command1); 24 //需要注意的是,如果返回的对象是NULL,则表示客户端和服务器之间出现严重错误,必须重新链接。 25 //这里只是举例说明,简便起见,后面的命令就不再做这样的判断了。 26 if (NULL == r) { 27 redisFree(c); 28 return; 29 } 30 //不同的Redis命令返回的数据类型不同,在获取之前需要先判断它的实际类型。 31 //至于各种命令的返回值信息,可以参考Redis的官方文档,或者查看该系列博客的前几篇 32 //有关Redis各种数据类型的博客。:) 33 //字符串类型的set命令的返回值的类型是REDIS_REPLY_STATUS,然后只有当返回信息是"OK" 34 //时,才表示该命令执行成功。后面的例子以此类推,就不再过多赘述了。 35 if (!(r->type == REDIS_REPLY_STATUS && strcasecmp(r->str,"OK") == 0)) { 36 printf("Failed to execute command[%s].\n",command1); 37 freeReplyObject(r); 38 redisFree(c); 39 return; 40 } 41 //由于后面重复使用该变量,所以需要提前释放,否则内存泄漏。 42 freeReplyObject(r); 43 printf("Succeed to execute command[%s].\n",command1); 44 45 const char* command2 = "strlen stest1"; 46 r = (redisReply*)redisCommand(c,command2); 47 if (r->type != REDIS_REPLY_INTEGER) { 48 printf("Failed to execute command[%s].\n",command2); 49 freeReplyObject(r); 50 redisFree(c); 51 return; 52 } 53 int length = r->integer; 54 freeReplyObject(r); 55 printf("The length of 'stest1' is %d.\n",length); 56 printf("Succeed to execute command[%s].\n",command2); 57 58 const char* command3 = "get stest1"; 59 r = (redisReply*)redisCommand(c,command3); 60 if (r->type != REDIS_REPLY_STRING) { 61 printf("Failed to execute command[%s].\n",command3); 62 freeReplyObject(r); 63 redisFree(c); 64 return; 65 } 66 printf("The value of 'stest1' is %s.\n",r->str); 67 freeReplyObject(r); 68 printf("Succeed to execute command[%s].\n",command3); 69 70 const char* command4 = "get stest2"; 71 r = (redisReply*)redisCommand(c,command4); 72 //这里需要先说明一下,由于stest2键并不存在,因此Redis会返回空结果,这里只是为了演示。 73 if (r->type != REDIS_REPLY_NIL) { 74 printf("Failed to execute command[%s].\n",command4); 75 freeReplyObject(r); 76 redisFree(c); 77 return; 78 } 79 freeReplyObject(r); 80 printf("Succeed to execute command[%s].\n",command4); 81 82 const char* command5 = "mget stest1 stest2"; 83 r = (redisReply*)redisCommand(c,command5); 84 //不论stest2存在与否,Redis都会给出结果,只是第二个值为nil。 85 //由于有多个值返回,因为返回应答的类型是数组类型。 86 if (r->type != REDIS_REPLY_ARRAY) { 87 printf("Failed to execute command[%s].\n",command5); 88 freeReplyObject(r); 89 redisFree(c); 90 //r->elements表示子元素的数量,不管请求的key是否存在,该值都等于请求是键的数量。 91 assert(2 == r->elements); 92 return; 93 } 94 for (int i = 0; i < r->elements; ++i) { 95 redisReply* childReply = r->element[i]; 96 //之前已经介绍过,get命令返回的数据类型是string。 97 //对于不存在key的返回值,其类型为REDIS_REPLY_NIL。 98 if (childReply->type == REDIS_REPLY_STRING) 99 printf("The value is %s.\n",childReply->str); 100 } 101 //对于每一个子应答,无需使用者单独释放,只需释放最外部的redisReply即可。 102 freeReplyObject(r); 103 printf("Succeed to execute command[%s].\n",command5); 104 105 printf("Begin to test pipeline.\n"); 106 //该命令只是将待发送的命令写入到上下文对象的输出缓冲区中,直到调用后面的 107 //redisGetReply命令才会批量将缓冲区中的命令写出到Redis服务器。这样可以 108 //有效的减少客户端与服务器之间的同步等候时间,以及网络IO引起的延迟。 109 //至于管线的具体性能优势,可以考虑该系列博客中的管线主题。 110 if (REDIS_OK != redisAppendCommand(c,command1) 111 || REDIS_OK != redisAppendCommand(c,command2) 112 || REDIS_OK != redisAppendCommand(c,command3) 113 || REDIS_OK != redisAppendCommand(c,command4) 114 || REDIS_OK != redisAppendCommand(c,command5)) { 115 redisFree(c); 116 return; 117 } 118 119 redisReply* reply = NULL; 120 //对pipeline返回结果的处理方式,和前面代码的处理方式完全一直,这里就不再重复给出了。 121 if (REDIS_OK != redisGetReply(c,(void**)&reply)) { 122 printf("Failed to execute command[%s] with Pipeline.\n",command1); 123 freeReplyObject(reply); 124 redisFree(c); 125 } 126 freeReplyObject(reply); 127 printf("Succeed to execute command[%s] with Pipeline.\n",command1); 128 129 if (REDIS_OK != redisGetReply(c,(void**)&reply)) { 130 printf("Failed to execute command[%s] with Pipeline.\n",command2); 131 freeReplyObject(reply); 132 redisFree(c); 133 } 134 freeReplyObject(reply); 135 printf("Succeed to execute command[%s] with Pipeline.\n",command2); 136 137 if (REDIS_OK != redisGetReply(c,(void**)&reply)) { 138 printf("Failed to execute command[%s] with Pipeline.\n",command3); 139 freeReplyObject(reply); 140 redisFree(c); 141 } 142 freeReplyObject(reply); 143 printf("Succeed to execute command[%s] with Pipeline.\n",command3); 144 145 if (REDIS_OK != redisGetReply(c,(void**)&reply)) { 146 printf("Failed to execute command[%s] with Pipeline.\n",command4); 147 freeReplyObject(reply); 148 redisFree(c); 149 } 150 freeReplyObject(reply); 151 printf("Succeed to execute command[%s] with Pipeline.\n",command4); 152 153 if (REDIS_OK != redisGetReply(c,(void**)&reply)) { 154 printf("Failed to execute command[%s] with Pipeline.\n",command5); 155 freeReplyObject(reply); 156 redisFree(c); 157 } 158 freeReplyObject(reply); 159 printf("Succeed to execute command[%s] with Pipeline.\n",command5); 160 //由于所有通过pipeline提交的命令结果均已为返回,如果此时继续调用redisGetReply, 161 //将会导致该函数阻塞并挂起当前线程,直到有新的通过管线提交的命令结果返回。 162 //最后不要忘记在退出前释放当前连接的上下文对象。 163 redisFree(c); 164 return; 165 } 166 167 int main() 168 { 169 doTest(); 170 return 0; 171 } 172 173 //输出结果如下: 174 //Succeed to execute command[set stest1 value1]. 175 //The length of 'stest1' is 6. 176 //Succeed to execute command[strlen stest1]. 177 //The value of 'stest1' is value1. 178 //Succeed to execute command[get stest1]. 179 //Succeed to execute command[get stest2]. 180 //The value is value1. 181 //Succeed to execute command[mget stest1 stest2]. 182 //Begin to test pipeline. 183 //Succeed to execute command[set stest1 value1] with Pipeline. 184 //Succeed to execute command[strlen stest1] with Pipeline. 185 //Succeed to execute command[get stest1] with Pipeline. 186 //Succeed to execute command[get stest2] with Pipeline. 187 //Succeed to execute command[mget stest1 stest2] with Pipeline.