目录
Redis Set
127.0.0.1:6379> help set
SET key value [EX seconds|PX milliseconds|KEEPTTL] [NX|XX]
summary: Set the string value of a key
since: 1.0.0
group: string
Set key
to hold the string value
. If key
already holds a value, it is overwritten, regardless of its type. Any previous time to live associated with the key is discarded on successful SET operation.
Options
The SET command supports a set of options that modify its behavior:
EX
seconds -- Set the specified expire time, in seconds.PX
milliseconds -- Set the specified expire time, in milliseconds.EXAT
timestamp-seconds -- Set the specified Unix time at which the key will expire, in seconds.PXAT
timestamp-milliseconds -- Set the specified Unix time at which the key will expire, in milliseconds.NX
-- Only set the key if it does not already exist.XX
-- Only set the key if it already exist.KEEPTTL
-- Retain the time to live associated with the key.- GET -- Return the old string stored at key, or nil if key did not exist. An error is returned and SET aborted if the value stored at key is not a string.
Note: Since the SET command options can replace SETNX, SETEX, PSETEX, GETSET, it is possible that in future versions of Redis these commands will be deprecated and finally removed.
History
>= 2.6.12
: Added theEX
,PX
,NX
andXX
options.>= 6.0
: Added theKEEPTTL
option.>= 6.2
: Added the GET,EXAT
andPXAT
option.>= 7.0
: Allowed theNX
and GET options to be used together.
redis> SET mykey "Hello"
"OK"
redis> GET mykey
"Hello"
redis> SET anotherkey "will expire in a minute" EX 60
"OK"
Redis HINCRBY
redis> HSET myhash field 5
(integer) 1
redis> HINCRBY myhash field 1
(integer) 6
redis> HINCRBY myhash field -1
(integer) 5
redis> HINCRBY myhash field -10
Redis SetEX
SET mykey value
EXPIRE mykey seconds
Redis Keyspace Notifications
参考:Redis Keyspace Notifications – Redis
想一想一个需求:
- 设置了生存时间的Key,在过期时能不能有所提示?
- 如果能对过期Key有个监听,如何对过期Key进行一个回调处理?
- 如何使用 Redis 来实现定时任务?
#notify-keyspace-events Ex
1466 #
1467 # By default all notifications are disabled because most users don't need
1468 # this feature and the feature has some overhead. Note that if you don't
1469 # specify at least one of K or E, no events will be delivered.
notify-keyspace-events ""
这里需要配置 notify-keyspace-events 的参数为 “Ex”。x 代表了过期事件。notify-keyspace-events "Ex" 保存配置后,重启Redis服务,使配置生效。
在 Redis 的 2.8.0 版本之后,其推出了一个新的特性——键空间消息(Redis Keyspace Notifications),它配合 2.0.0 版本之后的 SUBSCRIBE 就能完成这个定时任务的操作了,不过定时的单位是秒。
(1)Publish / Subscribe
Redis 在 2.0.0 之后推出了 Pub / Sub 的指令,大致就是说一边给 Redis 的特定频道发送消息,另一边从 Redis 的特定频道取值——形成了一个简易的消息队列。
(2)Redis Keyspace Notifications
在 Redis 里面有一些事件,比如键到期、键被删除等。然后我们可以通过配置一些东西来让 Redis 一旦触发这些事件的时候就往特定的 Channel 推一条消息。大致的流程就是我们给 Redis 的某一个 db 设置过期事件,使其键一旦过期就会往特定频道推消息,我在自己的客户端这边就一直消费这个频道就好了。
以后一来一条定时任务,我们就把这个任务状态压缩成一个键,并且过期时间为距这个任务执行的时间差。那么当键一旦到期,就到了任务该执行的时间,Redis 自然会把过期消息推去,我们的客户端就能接收到了。这样一来就起到了定时任务的作用。
redis-cli -h 127.0.01.4 -p 63789
127.0.0.1:63789> psubscribe __keyevent@0__:expired
Reading messages... (press Ctrl-C to quit)
1) "psubscribe"
2) "__keyevent@0__:expired"
3) (integer) 1
Redis分布式锁
Redis的原子性
同一个Redis实例,它只以单个进程运行,并可以确保所有请求都是在同一个序列中执行的,因此可以保证Redis执行的语句是原子性的。 对于使用EVAL,通过LUA运行的多条语句,也可以保证像数据库事务一样具有原子性。
单实例Redis
一个Go的实现:https://github.com/bsm/redis-lock
单实例Redis只需借助SETNX(2.6.12后续版本只需SET key value NX也可以做到)即可:
LOCK(lockKey, ownerID):
SET lockKey ownerID NX PX expirationInMilliseconds
Unlock(lockKey, ownerID):
if GET(lockKey) == ownerID:
DEL(lockKey)
redis分布式锁
为lockKey设置一个独一无二的值ownerID,这样在Unlock时,就不会出现lockKey正好被自动Expire删除后,原拥有者误将别人的锁释放掉的情况。
如果一系列操作需要多个Redis操作,那么应当EVAL将多个操作封装到同一段LUA代码中,否则可能导致多次通讯时差中出现意外。
这种情况仅适用于同一条key存在于同一个Redis实例的情况,例如Redis只有一个,或者不使用Master-Slave的Redis集群,例如无slave的hashring集群(利用类似一致性环形哈希计算key,最终请求落到特定节点上)
如果是使用Master-Slave的Redis集群,同一个key可以存在若干个备份,写入master的数据同步到slave中需要一段时间。考虑以下情形:
- Client A在master上获取了对key的锁: key:A
- master短暂故障(网络故障,重启等),但key:A尚未同步到slave
- Client B向slave请求获得锁 key:B成功
- master恢复运行,现在Client A、B都认为自己获得了锁
Red-lock分布式锁算法
一个Go的实现:https://github.com/go-redsync/redsync
在使用master-slave集群时,上述锁的问题在于同步过程中发生了冲突,因此一种解决方案是同时在多个节点上获取锁,当在多数节点成功时,就意味着其它client必然只能获得少数成功,该算法来自https://redis.io/topics/distlock,根据Redis文档的说法,并未在生产环境全面验证,但理论上是行的通的。算法如下:
假定我们想要锁定的时间为T,
- 记录下当前时间start,以毫秒(ms)为单位
- 借助多线程/协程同时向所有Redis实例请求获得锁 K:V, px=T,设置一个请求的超时时间,例如如果T为10s,那么我们可以设置请求超时为5-50ms,这样就可以避免在一个已经死掉的client上花费过多时间,但该值不应当低于网络通讯时间
- 不论成功与否,所有过程结束之后,计算剩余锁的时间 t=now-start
- 如果t<T,或者成功获得锁的数量不超过集群的半数,则认为失败
- 如果获取锁失败,那么释放掉所有集群上的锁(仅限于K:V一致)。为什么不只释放自己成功获得锁的实例呢?考虑到集群中存在Master-Slave的同步机制,以及我们设置的请求超时,最终存有我们的锁的实例将不限于曾经成功的那些,因此必须对所有实例释放我们的锁。
Redis GeoHash
redis> GEOADD Sicily 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
(integer) 2
redis> GEORADIUS Sicily 15 37 200 km WITHDIST
1) 1) "Palermo"
2) "190.4424"
2) 1) "Catania"
2) "56.4413"
redis> GEORADIUS Sicily 15 37 200 km WITHCOORD
1) 1) "Palermo"
2) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) 1) "15.08726745843887329"
2) "37.50266842333162032"
redis> GEORADIUS Sicily 15 37 200 km WITHDIST WITHCOORD
1) 1) "Palermo"
2) "190.4424"
3) 1) "13.36138933897018433"
2) "38.11555639549629859"
2) 1) "Catania"
2) "56.4413"
3) 1) "15.08726745843887329"
2) "37.50266842333162032"
获取元素的 hash 值
geohash 可以获取元素的经纬度编码字符串,上面已经提到,它是 base32 编码。 你可
以使用这个编码值去 http://geohash.org/${hash}中进行直接定位,它是 geohash 的标准编码
值。
redis> geohash company ireader
1) "wx4g52e1ce0"
redis> geohash company juejin
1) "wx4gd94yjn0"
存储结构
Redis 在存储数据不同数据类型的数据时都有对应的编码方式。 Redis GEO是采用哪种编码方式进行存储的呢?在翻阅Redis GEO API时发现其并没有删除指令,因为其底层是使用 zset 进行实现的。 我们可以使用zrem 进行数据的删除。再尝试用zset的查询指令,查询上文中添加的GEO信息
redis> ZRANGE cars:locations 0 -1 WITHSCORES
1) "1"
2) "4054421060663027"
3) "2"
4) "4054421167795118"
发现车辆编号为1的位置信息为4054421060663027;车辆编号为2的位置信息为4054421167795118。 再回顾一下zset增加成员的指令
ZADD key score member [[score member] [score member] ...]
复制代码
至此可以推断出Redis GEO 添加经、纬度位置信息的指令的过程是
ZADD cars:locations 4054421060663027 1
4054421060663027为对经纬度进行编码后的值。使用4054421060663027作为score 可以快速实现对经纬度的索引。
Redis处理Json
1. redisJson module
redis-server --loadmodule ./target/release/librejson.so
127.0.0.1:6379> JSON.SET amoreinterestingexample . '[ true, { "answer": 42 }, null ]'
OK
127.0.0.1:6379> JSON.GET amoreinterestingexample
"[true,{\"answer\":42},null]"
127.0.0.1:6379> JSON.GET amoreinterestingexample [1].answer
“42”
127.0.0.1:6379> JSON.DEL amoreinterestingexample [-1]
1
127.0.0.1:6379> JSON.GET amoreinterestingexample
"[true,{\"answer\":42}]"
_, err = rejson.JSONSet(conn, "JohnDoeJSON", ".info.Major", "EE", false, false)
if err != nil {
return
}
2. scan struct
// 使用 Do 方法调用命令
_, err = conn.Do("HMSET", Redis.Args{"simple_object"}.AddFlat(simpleObject)...)
if err != nil {
return
}
value, err := Redis.Values(conn.Do("HGETALL", key))
if err != nil {
return
}
object := SimpleStruct{}
err = Redis.ScanStruct(value, &object)
if err != nil {
return
}
利用redis库自带的Args 和 AddFlat对结构体进行转换。然后以hash类型存储。该方式实现简单,但存在最大的问题是不支持复杂结构(如:结构体中内嵌结构体、数组等)。
3.json 序列化存储
Redis Search
https://github.com/RediSearch/RediSearch
RediSearch is a Redis module that provides querying, secondary indexing, and full-text search for Redis. To use RediSearch, you first declare indexes on your Redis data. You can then use the RediSearch query language to query that data.
https://github.com/RediSearch/redisearch-go
ACL
ACL LIST
1) "user default on nopass ~* +@all"
每一行最前面的两个词是”user”和用户名。之后的词是具体ACL定义的规则。 我们下面将会看到怎样使用这些规则,但是现在,可以简单理解为默认(default)用户的配置是开启的(active(on)),不需要密码(require no password(nopass)),可以访问所有键(to access every possible key(~*))和可以执行任何命令(call every possible command(+@all))的。
另外,对于默认(default)用户来说,没有配置密码规则意味着新的客户端连接会自动使用默认(default)进行验证而不用显式的执行AUTH命令。