Redis 101

2017-11-10  本文已影响0人  xiaofudeng

参考

常用命令

set (不是指集合)

value属性是字符串.

set users:leto '{"name": leto, "planet": dune, "likes": ["spice"]}'

strlen

STRLEN users:leto
(integer) 50

getrange key start end # 两边都是闭区间[start, end]

GETRANGE users:leto 0 1
"{\""

append

APPEND users:leto " Over 9000"
(integer) 60

incr, incrby, decr, decrby

> INCR stats:page:about
(integer) 1
> INCR stats:page:about
(integer) 2
> INCRBY stats:page:about 5
(integer) 7

> INCRBY ratings:video:12333 5
(integer) 5

mset, mget

127.0.0.1:6379> MSET users:count 10 users:male 4 users:female 6
OK
127.0.0.1:6379> MGET users:count users:male users:female
1) "10"
2) "4"
3) "6"

setbit 和 getbit的扩展阅读

fast-easy-realtime-metrics-using-redis-bitmaps/

how Spool uses these two commands to efficiently answer the question “how many unique visitors did we have today”. For 128 million users
a laptop generates the answer in less than 50ms and takes only 16MB of memory.

Hashes

设置/读取单个字段

127.0.0.1:6379> HSET users:xiaofu name "xiaofudeng"
(integer) 1
127.0.0.1:6379> HGET users:xiaofu name
"xiaofudeng"

一次性设置/读取多个字段
hmset hmget

127.0.0.1:6379> HMSET users:xiaofu gender "male" education "bachelor"
OK
127.0.0.1:6379> HMGET users:xiaofu name gender education
1) "xiaofudeng"
2) "male"
3) "bachelor"

hgetall 读取所有key和value

127.0.0.1:6379> HGETALL users:xiaofu
1) "name"
2) "xiaofudeng"
3) "gender"
4) "male"
5) "education"
6) "bachelor"

hkeys 读取所有keys

127.0.0.1:6379> HKEYS users:xiaofu
1) "name"
2) "gender"
3) "education"

hdel 删除某个key

127.0.0.1:6379> HDEL users:xiaofu gender
(integer) 1
127.0.0.1:6379> HKEYS users:xiaofu
1) "name"
2) "education"

Lists

lpush 从列表开头插入数据

127.0.0.1:6379> lpush newusers xiaofu
(integer) 1
127.0.0.1:6379> LPUSH newusers chunk
(integer) 2
127.0.0.1:6379> LPUSH newusers userA
(integer) 3

127.0.0.1:6379> LRANGE newusers 0 -1
1) "userA"
2) "chunk"
3) "xiaofu"

# 保持记录最新的两个用户
127.0.0.1:6379> LTRIM newusers 0 1
OK
127.0.0.1:6379> LRANGE newusers 0 -1
1) "userA"
2) "chunk"

Sets

元素必须是独特的, 集合是无序的, 但是提供了一些基于值的高效操作.

# 朋友列表
127.0.0.1:6379> sadd friends:xiaofu huangjing chunk zhiwei
(integer) 3
# 验证是否是其朋友, O(1)操作
127.0.0.1:6379> SISMEMBER friends:xiaofu huangjing
(integer) 1
127.0.0.1:6379> SISMEMBER friends:xiaofu non
(integer) 0
# 查看相同的朋友列表
127.0.0.1:6379> SADD friends:chunk xiaofu zhiwei jiayi
(integer) 3
# sinter https://redis.io/commands/sinter, 可以传递多个集合, 求出全部集合的交集.
127.0.0.1:6379> SINTER friends:chunk friends:xiaofu
1) "zhiwei"

## 还可以方便的将结果记录下来
127.0.0.1:6379> SINTERSTORE friends:chunk_xiaofu friends:chunk friends:xiaofu
(integer) 1
127.0.0.1:6379> SMEMBERS friends:chunk_xiaofu
1) "zhiwei"

sorted sets

If hashes are like strings but with fields, then sorted sets are like sets but with a score. The score provides sorting and ranking capabilities.

# zadd https://redis.io/commands/zadd
# zadd key socre1 value1 socre2 value2
127.0.0.1:6379> zadd friendWithRank:xiaofu 100 huangjing 95 chunk 95 zhiwei 95 daijie 
(integer) 4
# 统计分数区间
127.0.0.1:6379> ZCOUNT friendWithRank:xiaofu 99 100
(integer) 1
# zrange
127.0.0.1:6379> ZRANGE friendWithRank:xiaofu 0 -1
1) "chunk"
2) "daijie"
3) "zhiwei"
4) "huangjing"
# zrange withsocres
127.0.0.1:6379> ZRANGE friendWithRank:xiaofu 0 -1 WITHSCORES
1) "chunk"
2) "95"
3) "daijie"
4) "95"
5) "zhiwei"
6) "95"
7) "huangjing"
8) "100"
# 找到排名
# 按照分数升序
127.0.0.1:6379> ZRANK friendWithRank:xiaofu huangjing
(integer) 3
# 降序
127.0.0.1:6379> ZREVRANK friendWithRank:xiaofu huangjing
(integer) 0

模拟Query

以下通常是用其他编程语言的Redis Driver来实现.

127.0.0.1:6379> set users:1001 '{"id": 1001, "email": "example@email.com"}'
OK
127.0.0.1:6379> hset users:lookup:email example@email.com 1001
(integer) 1
127.0.0.1:6379> HGET users:lookup:email example@email.com
"1001"
127.0.0.1:6379> GET users:1001
"{\"id\": 1001, \"email\": \"example@email.com\"}"

Transactions

Documentation

Redis is actually single-threaded, which is how every command is guaranteed to be atomic.

run multiple commands as an atomic group

A Redis transaction is entered using the MULTI command. The command always replies with OK. At this point the user can issue multiple commands. Instead of executing these commands, Redis will queue them. All the commands are executed once EXEC is called.
Calling DISCARD instead will flush the transaction queue and will exit the transaction.

multi
commands...
exec (或者discard)

What guarantee does Redis make about transactions?

example:

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR counter2
QUEUED
127.0.0.1:6379> INCR counter2
QUEUED
127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 1

# discard
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DEL counter1 counter2
QUEUED
127.0.0.1:6379> DISCARD
OK

127.0.0.1:6379> MGET counter1 counter2
1) "1"
2) "1"

出错的情况

if the command replies with QUEUED it was queued correctly, otherwise Redis returns an error. If there is an error while queueing a command, most clients will abort the transaction discarding it.

Redis2.5.6版起, 如果在EXEC之前发生了错误(比如说语法错误), 那么在调用EXEC之后, Server会废除这个Transaction, 并且返回错误.

However starting with Redis 2.6.5, the server will remember that there was an error during the accumulation of commands, and will refuse to execute the transaction returning also an error during EXEC, and discarding the transaction automatically.
例子:

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET online:count 1
QUEUED
127.0.0.1:6379> LPOP deliberately causing syntax error
(error) ERR wrong number of arguments for 'lpop' command
127.0.0.1:6379> EXEC
(error) EXECABORT Transaction discarded because of previous errors.

# 查看online:count
127.0.0.1:6379> GET online:count
(nil)
# 可以看到online:count并没有被设定值

而Redis 2.6.5之前的版本的行为是执行那些成功QUEUED的命令, 而不管发生了错误的命令.

Before Redis 2.6.5 the behavior was to execute the transaction with just the subset of commands queued successfully in case the client called EXEC regardless of previous errors.

Errors happening after EXEC instead are not handled in a special way: all the other commands will be executed even if some command fails during the transaction.

例子:
可见SET a 3还是被执行了, 并没有回滚.

127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET a 3
QUEUED
127.0.0.1:6379> LPOP a
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> GET a
"3"

为何Redis不支持roll back

  • Redis commands can fail only if called with a wrong syntax (and the problem is not detectable during the command queueing), or against keys holding the wrong data type: this means that in practical terms a failing command is the result of a programming errors, and a kind of error that is very likely to be detected during development, and not in production.
  • Redis is internally simplified and faster because it does not need the ability to roll back.

watch

Optimistic locking using check-and-set

WATCH is used to provide a check-and-set (CAS) behavior to Redis transactions.

WATCHed keys are monitored in order to detect changes against them. If at least one watched key is modified before the EXEC command, the whole transaction aborts, and EXEC returns a Null reply to notify that the transaction failed.

例子:

For example, imagine we have the need to atomically increment the value of a key by 1 (let's suppose Redis doesn't have INCR).

可能的操作是
pesudo代码:

val = GET key
val = val + 1
SET key $val

如果在一段时间内仅有一个用户想要执行这个操作, 那么是没问题的. 如果不同的用户想用进行该操作, 那么就会出现race condition. 比如说A和B都读到的是老值(即读了对方修改之前的值), 假设为10. 那么两个人执行+1操作后, 写回去的值都是11, 但是理论上来说这个值应该是12才对.

使用WATCH来解决这个问题:
pseudo:

WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC

如果在调用WATCHEXEC之间, 有人修改了mykey的值, 这一事务就会被废除. 所以通常WATCH是和循环一起使用, 所以我们只需要再重复执行一次上面的操作, 期待这次能成功. 这种形式的锁也叫乐观锁(optimistic locking).

watch解析

WATCH使得EXEC变得条件化, 即只有当被WATCHED的keys没有被修改时, 事务才会进行. 否则事务直接就被忽略了.

有以下几个情况需要考虑:

Watched keys might be changed by the same client inside the transaction without aborting it

来源: https://github.com/antirez/redis-doc/issues/734

例子:

127.0.0.1:6379> WATCH o
OK
127.0.0.1:6379> set o 1
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> GET o
QUEUED
127.0.0.1:6379> EXEC
(nil)
# 最后EXEC返回nil, 表明事务被忽略了, 因为watch条件不满足

所以被WATCH的keys只能在调用该WATCH本身的客户端的与该WATCH对应的MULTI事务中修改, 否则都会将该WATCH后续事务忽略.
成功例子:

127.0.0.1:6379> WATCH o
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> SET o 1
QUEUED
127.0.0.1:6379> EXEC
1) OK
127.0.0.1:6379> GET o
"1"

WATCH可以被调用多次, 也可以在它后面一次性添加多个keys. WATCH有效期直到EXEC(或者DISCARD)被调用.

EXEC(或者DISCARD)被调用后, 所有的watch都将被unwatched. 也可以用UNWATCH命令失效所有的WATCHED keys. 另外, 如果客户端断开连接, 那么所有的keys也会被unwatch.


实例: 使用WATCH实现ZPOP

A good example to illustrate how WATCH can be used to create new atomic operations otherwise not supported by Redis is to implement ZPOP, that is a command that pops the element with the lower score from a sorted set in an atomic way. This is the simplest implementation:

WATCH zset
element = ZRANGE zset 0 0
MULTI
ZREM zset element
EXEC

其他命令

Expiration

这是对于key的操作, 使得key过期, 有两种形式: 相对时间, 具体时间戳.

可以通过ttl命令得知一个key还有多少存活时间, 也可以通过persist来取消过期操作.

127.0.0.1:6379> SET topScore 100
OK
127.0.0.1:6379> EXPIRE topScore 100
(integer) 1
127.0.0.1:6379> ttl topScore
(integer) 96
127.0.0.1:6379> persist topScore
(integer) 1
127.0.0.1:6379> ttl topScore
(integer) -1
SET mykey value
EXPIRE mykey seconds

Publication and Subscriptions

打开两个Redis终端:

  1. 执行subscribe key命令
127.0.0.1:6379> SUBSCRIBE message
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "message"
3) (integer) 1
  1. 另一个终端, 执行 publish
127.0.0.1:6379> PUBLISH message "Hi, the message's coming"
(integer) 1
# 返回值1表示有1个客户收到了这条消息.

此时subscribe那个终端如下:

127.0.0.1:6379> SUBSCRIBE message
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "message"
3) (integer) 1
1) "message"
2) "message"
3) "Hi, the message's coming"

monitor

打开两个Redis命令行终端, 一个输入MONITOR, 然后在另一个终端键入任意命令.
输入任意命令的终端:

127.0.0.1:6379> SET online:count 1234
OK
127.0.0.1:6379> INCRBY online:count 1234
(integer) 2468

监视的终端:

127.0.0.1:6379> MONITOR
OK
1510301294.648671 [0 127.0.0.1:58900] "SET" "online:count" "1234"
1510301304.767538 [0 127.0.0.1:58900] "INCRBY" "online:count" "1234"

sort

It lets you sort the values within a list, set or sorted set (sorted sets
are ordered by score, not the members within the set).

简单例子:

127.0.0.1:6379> RPUSH nums 5 9 8 4 -1 2 5
(integer) 7
127.0.0.1:6379> SORT nums
1) "-1"
2) "2"
3) "4"
4) "5"
5) "5"
6) "8"
7) "9"
127.0.0.1:6379> LRANGE nums 0 -1
1) "5"
2) "9"
3) "8"
4) "4"
5) "-1"
6) "2"
7) "5"

带有limit, desc, alpha参数的例子:

127.0.0.1:6379> SADD likes:xiaofu Python Java Shell C++ Music English
(integer) 3
127.0.0.1:6379> SORT likes:xiaofu limit 0 3 desc alpha
1) "Shell"
2) "Python"
3) "Music"

使用引用值排序的例子:

# 这里的数字表明leto同学要观察的bug的id
sadd watch:leto 12339 1382 338 9338

设置bug的严重程度

set severity:12339 3
set severity:1382 2
set severity:338 5
set severity:9338 4

根据bug严重程度排序:

127.0.0.1:6379> sort watch:leto by severity:* desc
1) "338"
2) "9338"
3) "12339"
4) "1382"

Redis will substitute the * in our pattern (identified via by ) with the values in our list/set/sorted set. This will create the key name that Redis will query for the actual values to sort by.

也可以通过hash来排序.
redis-cli交互模式只支持一行行输入命令, 所以建议通过文章开头参考部分的批量导入那里来进行批量数据导入.

hset bug:12339 severity 3
hset bug:12339 priority 1
hset bug:12339 details '{"id": 12339, ....}'

hset bug:1382 severity 2
hset bug:1382 priority 2
hset bug:1382 details '{"id": 1382, ....}'

hset bug:338 severity 5
hset bug:338 priority 3
hset bug:338 details '{"id": 338, ....}'

hset bug:9338 severity 4
hset bug:9338 priority 2
hset bug:9338 details '{"id": 9338, ....}'

sort命令:

127.0.0.1:6379> sort watch:leto by bug:*->priority get bug:*->details desc
1) "{\"id\": 338, ....}"
2) "{\"id\": 9338, ....}"
3) "{\"id\": 1382, ....}"
4) "{\"id\": 12339, ....}"

*其实是对集合里面的值的引用.get表明输出的字段.
如果没有指明get, 得到的实际上就是id:

127.0.0.1:6379> sort watch:leto by bug:*->priority  desc
1) "338"
2) "9338"
3) "1382"
4) "12339"

保存sort的结果:

sort watch:leto by bug:*->priority get bug:*->details desc store watch_by_priority:leto

结果

127.0.0.1:6379> LRANGE watch_by_priority:leto 0 -1
1) "{\"id\": 338, ....}"
2) "{\"id\": 9338, ....}"
3) "{\"id\": 1382, ....}"
4) "{\"id\": 12339, ....}"

Combining the store capabilities of sort with the expiration commands we’ve already seen makes for a nice combo.

上一篇 下一篇

猜你喜欢

热点阅读