- [redis](#redis) - [Using Command](#using-command) - [Keys and values](#keys-and-values) - [Content of Keys](#content-of-keys) - [Hashtags](#hashtags) - [altering and querying the key space](#altering-and-querying-the-key-space) - [key expiration](#key-expiration) - [Navigating the keyspace](#navigating-the-keyspace) - [Scan](#scan) - [keys](#keys) - [pipelining](#pipelining) - [Request/Response protocols and round-trip time(RTT)](#requestresponse-protocols-and-round-trip-timertt) - [redis pipelining](#redis-pipelining) - [Performance Imporvement](#performance-imporvement) - [pipelining vs scripting](#pipelining-vs-scripting) - [Transactions](#transactions) - [Usage](#usage) - [Errors inside transaction](#errors-inside-transaction) - [rollback for redis Transaction](#rollback-for-redis-transaction) - [Discard the command queue](#discard-the-command-queue) - [Optimistic locking using check-and-set](#optimistic-locking-using-check-and-set) - [WATCH](#watch) - [UNWATCH](#unwatch) - [Using WATCH to implement ZPOP](#using-watch-to-implement-zpop) - [Data Types](#data-types) - [Redis Strings](#redis-strings) - [SET / GET](#set--get) - [set with additional arguments](#set-with-additional-arguments) - [GETSET](#getset) - [MSET / MGET](#mset--mget) - [strings as counters](#strings-as-counters) - [Limits](#limits) - [JSON](#json) - [`JSON.SET`](#jsonset) - [json数值操作](#json数值操作) - [json数组操作](#json数组操作) - [`JSON.DEL`](#jsondel) - [`JSON.ARRAPPEND`](#jsonarrappend) - [`JSON.ARRTRIM`](#jsonarrtrim) - [json object操作](#json-object操作) - [format output](#format-output) - [Limitation](#limitation) - [Redis lists](#redis-lists) - [Blocking commands](#blocking-commands) - [Queue(first in, first out)](#queuefirst-in-first-out) - [Stack(first in, last out)](#stackfirst-in-last-out) - [check length of list](#check-length-of-list) - [Atomically pop one element from one list and push to another](#atomically-pop-one-element-from-one-list-and-push-to-another) - [trim the list](#trim-the-list) - [Redis List Impl](#redis-list-impl) - [`LPUSH, RPUSH`](#lpush-rpush) - [`LRANGE`](#lrange) # redis ## Using Command ### Keys and values #### Content of Keys key为data model中`拥有含义的文本名称`。Redis key在命名格式方面几乎没有限制,故而key name中可以包含空格或标点符号。`redis key并不支持namespace或categories`,故而在命名时应当避免命名冲突。 通常,会使用`:`符号来将redis的命名分割为多个sections,例如`office:London`,可以使用该命名规范来实现类似`categories`的效果。 尽管keys通常是文本的,redis中也实现了`binary-safe` key。可以使用任何`byte sequence`来作为有效的key,并且,在redis中`empty string`也可以作为有效的key。 redis key在命名时存在如下规范: - 不推荐使用长度很长的key,会在存储和key-comparisions方面带来负面影响 - 不推荐使用长度非常短的key,其会减少可读性,通常`user:1000:followers`的可读性相较`u1000flw`来说可读性要更好,并且前者带来的额外存储开销也较小 - 应在命名时使用统一的命名模式,例如`object-type:id`,在section中包含多个单词时,可以使用`.`或`-`符号来进行分隔,例如`comment:4321:reply.to`或`comment:4321:reply-to` - key size的最大允许大小为512MB #### Hashtags redis通过hashing来获取`key`关联的value。 通常,整个key都会被用作hash index的计算,但是,在部分场景下,开发者可能只希望使用key中的一部分来计算hash index。此时,可以通过`{}`包围key中`想要计算hash index的部分`,该部分被称为hash-tag`。 例如,`person:1`和`person:2`这两个key会计算出不同的hash index;但是`{persion}:1`和`{person}:2`这两个key计算出的hash index却是相同的,因为只有`person`会被用于计算hash index。 通常,hashtag的应用场景是`在集群场景下进行multi-key operations`。在集群场景下,除非所有key计算出的hash index相同,否则集群并不允许执行multi-key操作。 例如,`SINTER`命令用于查询两个不同`set values`的交集,可以接收多个key。在集群场景下: ```redis SINTER group:1 group:2 ``` 上述命名并无法成功执行,因为`group:1`和`group:2`两个key的hash index不同。 但是,如下命令在集群环境下则是可以正常执行: ```redis SINTER {group}:1 {group}:2 ``` hashtag让两个key产生相同的hash值。 虽然hashtag在上述场景下有效,但是,不应该过度的使用hashtag。因为hashtag相同的key其hash index都相同,故而会被散列到同一个slot中,当同一slot中元素过多时,会导致redis的性能下降。 #### altering and querying the key space 存在部分命令,其并不针对特定的类型,而是用于和key space进行交互,其可以被用于所有类型的key。 例如,`EXISTS`命令会返回0和1,代表给定key在redis中是否存在;而`DEL`命令则是用于删除key和key关联的value,无论value是什么类型。 示例如下: ```bash > set mykey hello OK > exists mykey (integer) 1 > del mykey (integer) 1 > exists mykey (integer) 0 ``` 在上述示例中,`DEL`命令返回的值为1或0,代表要被删除的值在redis中是否存在。 `TYPE`命令则是可以返回`key所关联value的类型`: ```bash > set mykey x OK > type mykey string > del mykey (integer) 1 > type mykey none ``` #### key expiration 在redis中,不管key对应的value为何种类型,都支持`key expiration`特性。`key exipiration`支持为key设置超时,`key expiration`也被称为`time to live`/`TTL`,当`ttl`指定的时间过去后,key将会被自动移除。 对于key expiration: - 在对key设置key expiration时,可以按照秒或毫秒的精度进行设置 - 但是,`expire time`在解析时单位永远为毫秒 - expire相关的信息会被`replicated`并存储在磁盘中,即使在redis宕机时,`time virtually passes`(即redis key其expire若为1天,宕机4小时后恢复,其expire会变为8小时,宕机并不会导致key expire停止计算) 可以通过`EXPIRE`命令来设置key expiration: ```bash > set key some-value OK > expire key 5 (integer) 1 > get key (immediately) "some-value" > get key (after some time) (nil) ``` 在第二次调用时,delay超过5s,key已经不存在。 上述示例中,`expire key 5`将key的超时时间设置为了5s,`EXPIRE`用于为key指定不同的超时时间。 类似的,可以通过`PERSIST`命令来取消key的超时设置,让key永久被保留。 除了使用`expire`来设置超时外,在创建时也能会key指定expiration: ```bash > set key 100 ex 10 OK > ttl key (integer) 9 ``` 上述示例中,使用`ttl`命令来检查key的超时时间。 如果想要按照毫秒来设置超时,可以使用`PEXPIRE`和`PTTL`命令。 #### Navigating the keyspace ##### Scan `SCAN`命令支持对redis中key的增量迭代,在每次调用时只会返回一小部分数据。该命令可以在生产中使用,并不会像`keys`或`smembers`等命令一样,在处理大量elements或keys时可能产生长时间的阻塞。 scan使用实例如下: ```bash > scan 0 1) "17" 2) 1) "key:12" 2) "key:8" 3) "key:4" 4) "key:14" 5) "key:16" 6) "key:17" 7) "key:15" 8) "key:10" 9) "key:3" 10) "key:7" 11) "key:1" > scan 17 1) "0" 2) 1) "key:5" 2) "key:18" 3) "key:0" 4) "key:2" 5) "key:19" 6) "key:13" 7) "key:6" 8) "key:9" 9) "key:11" ``` scan是一个cursor based iterator,每次在调用scan命令时,都会返回一个`update cursor`,并且在下次调用scan时需要使用上次返回的cursor。 当cursor被设置为0时,iteration将会开始,并且当server返回的cursor为0时,iteration结束。 ##### keys 除了scan外,还可以通过keys命令来迭代redis中所有的key。但是,和`scan`的增量迭代不同的是,keys会一次性返回所有的key,在返回前会阻塞redis-server。 keys命令支持glob-style pattern: - `h?llo`:`?`用于匹配单个字符 - `h*llo`: `*`用于匹配除`/`外的任何内容 - `h[ae]llo`: 匹配`hallo`和`hello` - `h[^e]llo`: [^e]匹配除`e`外的任何字符 - `h[a-b]llo`: 匹配`hallo`和`hbllo` global-style pattern中转义符为`\` ### pipelining redis pipelining支持一次发送多条命令,而非`逐条发送命令,并且发送后一条命令之前必须要等待前一条请求执行完成`。pipelining被绝大多数redis client支持,能够提高性能。 #### Request/Response protocols and round-trip time(RTT) redis是一个使用`client-server model`的tcp server,在请求完成前,会经历如下步骤: - client向server发送query,并且阻塞的从socket中读取server的响应 - server接收到client的请求后,处理命令,并且将处理结果返回给client 例如,包含4条命令的命令序列如下: 1. client: incr x 2. server: 1 3. client: incr x 4. server: 2 5. client: incr x 6. server: 3 7. client: incr x 8. server: 4 client和server之间通过网络进行连接,每次client和server的请求/响应,都需要经历`client发送请求,server发送响应`的过程,该过程会经过网络来传输,带来传输延迟。 该延迟被称为`RTT`(round trip time), 如果在一次请求中能够发送`n`条命令,那么将能够节省`n-1`次网络传输的往返时间。例如,RTT如果为`250ms`,即使server能够以`100K/s`的速度处理请求,对于同一client,其每秒也只能处理4条命令。 #### redis pipelining 在redis server处理命令时,其处理新请求前并不要求client接收到旧请求,并且client在发送多条命令后,会一次性统一读取执行结果。 该技术被称为`Pipelining`,在其他协议中也有广泛使用,例如`POP3`。 pipelining在redis的所有版本中都被支持,示例如下: ```bash $ (printf "PING\r\nPING\r\nPING\r\n"; sleep 1) | nc localhost 6379 +PONG +PONG +PONG ``` 通过pipelining,并不需要对每个命令都花费RTT用于网络传输,而是在一次网络传输时就包含3条命令。 > 当client使用pipelining发送commands时,server会在内存中对replies进行排队。故而,在client需要使用pipeline向server发送大量的请求时,其需要分批发送,每批中包含合适数量的命令。 > pipeline会积累多条命令并一次性发送给server。 #### Performance Imporvement pipeline不仅能够减少RTT的次数,其也能够增加redis server在每秒的执行操作数。 在redis server处理command时,实际的处理逻辑开销很小,但是和socket io的交互开销却很大。在进行socket io时,会进行`write`和`read`的系统调用,其涉及到用户态和内核态的切换,这将带来巨大的开销。 如果使用pipeline,多条指令只需要调用一次`read`系统调用,并且多条执行的执行结果只需要通过一次`write`系统调用即能够执行。通过使用pipeline,能够有效降低redis server的系统调用次数,这将减少socket io带来的开销,故而redis server能够在一秒内执行更多的commands。 #### pipelining vs scripting 相比于pipelining,scripting可以在`read, compute, write`的场景下带来更高的性能。pipelining并无法处理`read, compute, write`的场景,因为在执行write command之前,需要先获取read command的执行结果,故而无法将read和write命令通过pipeline同时发送给server。 ### Transactions redis transaction支持`execute a group of commands in a single step`,其涉及到`multi, exec, discard, watch`命令。 - 在redis事务中,所有命令都会被串行、按顺序执行,在redis transaction执行时,其他client发送的请求永远不会插入到redis transaction中间。在redis transaction中,所有命令都会`executed as a single siolated operation`,事务执行的过程中不会被其他命令打断 - `EXEC`命令会触发事务中所有命令的执行,故而,当client在`事务上下文中exec命令调用之前`,失去了与server的连接,事务中的命令都不会被执行。只有当exec被调用后,事务中的命令才会实际开始执行 #### Usage 可以通过`multi`命令来进入redis事务,该命令总会返回`ok`。在进入事务后,可以向事务中添加多条命令,这些命令并不会被立马执行,而是被排队。只有当发送`EXEC`命令后,之前排队的命令才会被实际执行。 `DISCARD`命令可以清空被排队的命令,并且退出事务的上下文。 如下示例展示了如何通过事务原子的执行一系列命令: ```bash > MULTI OK > INCR foo QUEUED > INCR bar QUEUED > EXEC 1) (integer) 1 2) (integer) 1 ``` `EXEC`命令会返回一个数组,数组中元素为之前QUEUED COMMANDS的返回结果,顺序和命令被添加到队列中的顺序相同。 在事务上下文中,所有命令(`EXEC`除外)都会返回`QUEUED`。 #### Errors inside transaction 在使用事务时,可能遇到如下两种errors: - 将命令添加到queue中时可能发生失败,该时机在`EXEC`被执行之前。例如,command可能存在语法错误,或是存在内存错误等,都可能导致命令添加到queue失败 - 调用`EXEC`对入队的命令实际执行时,可能发生异常,例如在实际执行command时,对string类型的value执行了list操作 对于`EXEC`时产生的错误,并没有特殊处理:`即使事务中部分命令实际执行失败,其他的命令也都会被执行`。 示例如下所示: ```redis Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. MULTI +OK SET a abc +QUEUED LPOP a +QUEUED EXEC *2 +OK -WRONGTYPE Operation against a key holding the wrong kind of value ``` 上述示例中执行了两个命令,其中命令1执行成功而命令2执行失败。 需要注意的是,`事务中即使某个命令执行失败,queue中的其他命令仍然会被执行`,redis在执行某条命令失败时,并不会对别的命令执行造成影响。 #### rollback for redis Transaction 对于redis transaction,其并不对`rollback`做支持,rollback会对redis的性能造成巨大影响,也会影响redis的易用性。 #### Discard the command queue 如果想要对事务进行abort,可以调用`DISCARD`命令,在该场景下,并不会有命令被实际执行,并且连接状态也会恢复为正常: ```redis > SET foo 1 OK > MULTI OK > INCR foo QUEUED > DISCARD OK > GET foo "1" ``` #### Optimistic locking using check-and-set 在redis transaction中,`WATCH`命令用于提供`CAS`行为。`watched keys`将会被监控,并探知其是否发生变化。 在执行`EXEC`命令前,如果存在任一key发生过修改,那么整个事务都会发生`abort`,并且会返回`NULL`,用以提示事务失败。 如下是一个`read, compute, write`的示例: ``` val = GET mykey val = val + 1 SET mykey val ``` 上述逻辑在`只存在一个客户端`的场景下工作正常,但是当存在多个客户端时,将会发生竞争。由于上述逻辑并不是原子的,故而可能出现如下场景: 1. client A read old value 2. client B read old value 3. client A compute `old value + 1` 4. client B compute `old value + 1` 5. client A set new value with `old value + 1` 6. client B set new value with `old value + 1` 故而,在多client场景下,假设old value为10,即使client A和client B都对value进行了incr,最后new value的值仍有可能为11而不是12 通过WATCH机制能够解决该问题 ``` WATCH mykey val = GET mykey val = val + 1 MULTI SET mykey $val EXEC ``` 在上述示例中,在进入事务上下文前,client对mykey进行了watch并完成新值的计算,之后,进入事务上下文后,用new value设置mykey,并调用`EXEC`命令。 如果WATCH和EXEC之间,存在其他client修改了mykey的值,那么当前事务将会失败。 只需要在发生竞争时重新执行上述流程,那么其即是乐观锁。 #### WATCH `WATCH`命令会让`EXEC`是条件的: - 只有当所有watched keys都未修改的前提下,才会让redis实际执行transaction `watched keys`可能发生如下的修改: - watched keys可能被其他client修改 - watched keys可能被redis本身修改,redis本身的修改包含如下场景 - expiration - eviction 如果在`对keys进行watch`和实际调用`exec`之间,keys发生的变化,整个transaction都会被abort。 > 在redis 6.0.9之前,expired keys并不会造成redis transaction被abort > 在本事务内的命令并不会造成WATCH condition被触发,因为WATCH机制的时间范围为keys watched的时间点到exec调用前的时间点,而queued commands在调用exec后才会实际执行 `watch`命令可以被多次调用,所有的watch命令都会生效,并且在watch被调用后就开始监控key的变化,监控一直到`EXEC`被调用后才结束。 对于`WATCH`命令,可以传递任意数量的参数。 在`EXEC`命令被调用后,所有的watched keys都会被`unwatched`,不管事务是否被aborted。并且,当client连接关闭后,所有keys都会被unwatched。 对于`discard`命令,其在调用后所有watched keys也会自动被`unwatched`。 #### UNWATCH 可以通过`UNWATCH`命令(无参数)来清空所有的watched keys。 通常,在调用`MULTI`进入事务前,会执行如下操作: - `WATCH mykey` - `GET mykey` 如果`在GET mykey`后,`调用MULTI`之前,如果在读取mykey的值后不再想执行后续事务了,那么可以直接调用`UNWATCH`,对先前监视的所有key取消监视。 #### Using WATCH to implement ZPOP 如下是一个使用WATCH的示例 ```redis WATCH zset element = ZRANGE zset 0 0 MULTI ZREM zset element EXEC ``` > 如果`EXEC失败,那么其将返回Null`,所以仅需对之前操作进行重试即可 ## Data Types ### Redis Strings Redis strings存储字节序列,包含文本、serialized objects、binary array。strings通常被用于缓存,但是支持额外的功能,例如counters和位操作。 redis keys也为strings。 string data type可用于诸多用例,例如对html fragement/html page的缓存。 #### SET / GET 通过set和get命令,可以对string value进行设置和获取。 ```redis-cli > SET bike:1 Deimos OK > GET bike:1 "Deimos" ``` 在使用`SET`命令时,如果key已存在对应的值,那么set指定的value将会对已经存在的值进行替换。`即使key对应的旧值并不是strings类型,set也会对其进行替换`。 values可以是`strings of every kind`(包含binary data),故而支持在value中存储jpeg image。value的值不能超过512MB. #### set with additional arguments 在使用`set`命令时,可以为其提供额外的参数,例如`NX | XX`. - `NX`: 仅当redis不存在对应的key时才进行设置,否则失败(返回nil) - `XX`: 仅当redis存在对应的key时的才进行设置,否则失败(返回nil) #### GETSET GETSET命令将会将key设置为指定的new value,并且返回oldvalue的值。 ```redis-cli 127.0.0.1:6379> get bike:1 (nil) 127.0.0.1:6379> GETSET bike:1 3 (nil) 127.0.0.1:6379> GET bike:1 "3" ``` #### MSET / MGET strings类型支持通过`mset, mget`命令来一次性获取和设置多个keys,这将能降低RTT带来的延迟。 ```redis-cli > mset bike:1 "Deimos" bike:2 "Ares" bike:3 "Vanth" OK > mget bike:1 bike:2 bike:3 1) "Deimos" 2) "Ares" 3) "Vanth" ``` #### strings as counters strings类型支持atomic increment: ```redis-cli > set total_crashes 0 OK > incr total_crashes (integer) 1 > incrby total_crashes 10 (integer) 11 ``` `incr`命令会将string value转化为integer,并且对其进行加一操作。类似命令还有`incrby, decr, drcrby`。 #### Limits 默认情况下,单个redis string的最大限制为`512MB`。 ### JSON redis支持对json值的存储、更新和获取。redis json可以和redis query engine进行协作,从而允许`index and query json documents`。 > 在redis 8中内置支持了RedisJSON,否则需要手动安装RedisJSON module。 #### `JSON.SET` `JSON.SET`命令支持将redis的key设置为JSON value,示例如下: ```redis-cli 127.0.0.1:6379> JSON.SET bike $ '"Hyperion"' OK 127.0.0.1:6379> JSON.GET bike $ "[\"Hyperion\"]" 127.0.0.1:6379> type bike ReJSON-RL 127.0.0.1:6379> JSON.TYPE bike $ 1) "string" ``` 在上述示例中,`$`代表的是指向json document中value的`path`: - 在上述示例中,`$`代表root 除此之外,JSON还支持其他string operation。`JSON.STRLNE`支持获取string长度,并且可以通过`JSON.STRAPPEND`来在当前字符串后追加其他字符串: ```redis-cli > JSON.STRLEN bike $ 1) (integer) 8 > JSON.STRAPPEND bike $ '" (Enduro bikes)"' 1) (integer) 23 > JSON.GET bike $ "[\"Hyperion (Enduro bikes)\"]" ``` #### json数值操作 RedisJSON支持`increment`和`multiply`操作: ```redis-cli > JSON.SET crashes $ 0 OK > JSON.NUMINCRBY crashes $ 1 "[1]" > JSON.NUMINCRBY crashes $ 1.5 "[2.5]" > JSON.NUMINCRBY crashes $ -0.75 "[1.75]" > JSON.NUMMULTBY crashes $ 24 "[42]" ``` #### json数组操作 RedisJSON支持通过`JSON.SET`将值赋值为数组,`path expression`支持数组操作 ```redis-cli > JSON.SET newbike $ '["Deimos", {"crashes": 0}, null]' OK > JSON.GET newbike $ "[[\"Deimos\",{\"crashes\":0},null]]" > JSON.GET newbike $[1].crashes "[0]" > JSON.DEL newbike $[-1] (integer) 1 > JSON.GET newbike $ "[[\"Deimos\",{\"crashes\":0}]]" ``` ##### `JSON.DEL` `JSON.DEL`支持通过`path`对json值进行删除。 ##### `JSON.ARRAPPEND` 支持向json array中追加值。 ##### `JSON.ARRTRIM` 支持对json array进行裁剪。 ```redis-cli > JSON.SET riders $ [] OK > JSON.ARRAPPEND riders $ '"Norem"' 1) (integer) 1 > JSON.GET riders $ "[[\"Norem\"]]" > JSON.ARRINSERT riders $ 1 '"Prickett"' '"Royce"' '"Castilla"' 1) (integer) 4 > JSON.GET riders $ "[[\"Norem\",\"Prickett\",\"Royce\",\"Castilla\"]]" > JSON.ARRTRIM riders $ 1 1 1) (integer) 1 > JSON.GET riders $ "[[\"Prickett\"]]" > JSON.ARRPOP riders $ 1) "\"Prickett\"" > JSON.ARRPOP riders $ 1) (nil) ``` #### json object操作 json oject操作同样有其自己的命令,示例如下: ```redis-cli > JSON.SET bike:1 $ '{"model": "Deimos", "brand": "Ergonom", "price": 4972}' OK > JSON.OBJLEN bike:1 $ 1) (integer) 3 > JSON.OBJKEYS bike:1 $ 1) 1) "model" 2) "brand" 3) "price"> JSON.SET bike:1 $ '{"model": "Deimos", "brand": "Ergonom", "price": 4972}' ``` #### format output redis-cli支持对json内容的输出进行格式化,步骤如下: - 在执行`redis-cli`时指定`--raw`选项 - 通过formatting keywords来进行格式化 - `INDENT` - `NEWLINE` - `SPACE` ```bash $ redis-cli --raw > JSON.GET obj INDENT "\t" NEWLINE "\n" SPACE " " $ [ { "name": "Leonard Cohen", "lastSeen": 1478476800, "loggedOut": true } ] ``` #### Limitation 传递给command的json值最大深度只能为128,如果嵌套深度大于128,那么command将返回错误。 ### Redis lists Redis lists为string values的链表,redis list通常用于如下场景: - 实现stack和queue - 用于backgroup worker system的队列管理 #### Blocking commands redis lists中支持阻塞命令 - `BLPOP`: 从`head of a list`移除并且返回一个element,如果list为空,该command会阻塞,直至list被填充元素或发生超时 - `BLMOVE`: 从source list中pop一个element,并且将其push到target list中。如果source list为空,那么command将会被阻塞,直到source list中出现new element > 在上述文档描述中,`阻塞`实际指的是针对客户端的阻塞。该`Blocking Command`命令的调用会实际的阻塞客户端,直到redis server返回结果。 > > 但是,`server并不会被blocking command所阻塞`。redis server为单线程模型,当用户发送blocking command给server,并且该command无法立即被执行而导致客户端阻塞时,`server会挂起该客户端的连接`,并且转而处理其他请求,直至该客户端的阻塞命令满足执行条件时,才会将挂起的连接唤醒重新执行。 > > 故而,`Blocking Command`并不会对server造成阻塞,而是会阻塞客户端的调用。 #### Queue(first in, first out) 依次调用`LPUSH`后再依次调用`RPOP`,可模拟队列行为,元素的弹出顺序和元素的添加顺序相同: ```redis-cli > LPUSH bikes:repairs bike:1 (integer) 1 > LPUSH bikes:repairs bike:2 (integer) 2 > RPOP bikes:repairs "bike:1" > RPOP bikes:repairs "bike:2" ``` #### Stack(first in, last out) 依次调用`LPUSH`后再依次调用`LPOP`,可模拟栈的行为,元素的移除顺序和添加顺序相反: ```redis-cli > LPUSH bikes:repairs bike:1 (integer) 1 > LPUSH bikes:repairs bike:2 (integer) 2 > LPOP bikes:repairs "bike:2" > LPOP bikes:repairs "bike:1" ``` #### check length of list 可通过`LLEN`命令来检查list的长度 ```redis-cli > LLEN bikes:repairs (integer) 0 ``` #### Atomically pop one element from one list and push to another 通过`lmove`命令能够实现原子的`从srclist移除并添加到dstlist`的操作 ```redis-cli > LPUSH bikes:repairs bike:1 (integer) 1 > LPUSH bikes:repairs bike:2 (integer) 2 > LMOVE bikes:repairs bikes:finished LEFT LEFT "bike:2" > LRANGE bikes:repairs 0 -1 1) "bike:1" > LRANGE bikes:finished 0 -1 1) "bike:2" ``` #### trim the list 可以通过`LTRIM`命令来完成对list的裁剪操作: ```redis-cli > LPUSH bikes:repairs bike:1 (integer) 1 > LPUSH bikes:repairs bike:2 (integer) 2 > LMOVE bikes:repairs bikes:finished LEFT LEFT "bike:2" > LRANGE bikes:repairs 0 -1 1) "bike:1" > LRANGE bikes:finished 0 -1 1) "bike:2" ``` #### Redis List Impl redis lists是通过Linked List来实现的,其元素的添加操作并开销永远是常量的,并不会和Array一样因扩容而可能导致内存的复制。 #### `LPUSH, RPUSH` `LPUSH`会将元素添加到list的最左端(头部),而`RPUSH`则会将新元素添加奥list的最右端(尾部)。 LPUSH和RPUSH接收的参数都是可变的,在单次调用中可以向list中添加多个元素。 例如,向空list中调用`lpush 1 2 3`时,其等价于`lpush 1; lpush 2; lpush3`,即调用后list中元素为`3,2,1` #### `LRANGE` `LRANGE`命令能够从list中解析范围内的数据,其接收两个indexes,为range中开始和结束元素的位置。index可以是负的,负数代表从尾端开始计数: - `-1`代表最后的元素 - `-2`代表倒数第二个元素 ```redis-cli > RPUSH bikes:repairs bike:1 (integer) 1 > RPUSH bikes:repairs bike:2 (integer) 2 > LPUSH bikes:repairs bike:important_bike (integer) 3 > LRANGE bikes:repairs 0 -1 1) "bike:important_bike" 2) "bike:1" 3) "bike:2" ```