基本特性
基于内存的,可持久化的,键值对方式的存储,数据保存在内存中,速度快,根据不同场景使用
命令操作
通过标识数字定位,默认开始标识是0
,命令结束后会响应OK
,并修改提示符后缀
选择数据库:select [标识数字]
key 定义如何标识数据块,key 名一般用符号分割;value 表示 key 的实际数据,当作字节数组看待
添加数据:set <key> <value>
获取数据:get <key>
Redis不能根据值进行查询
持久化
Redis 基于一定量 key 的变更,来触发对数据库进行的快照;默认情况下,Redis 会在每 60 秒,如果有 1000 及以上个 key 发生改变,将对数据快照保存;或每15分钟,即使少于9个 key 发生改变,也会把数据快照保存
还支持增量模式,一旦 key 发生变化,会产生增量包更新到硬盘上,通过更新延迟换取性能上的提升
Redis有多快? 可通过自带性能程序测试:redis-benchmark
数据结构
有五种数据结构,使用命令时会对应到具体的数据结构上,通过 type <key>
来获得类型
比如set
存字符串,hset
存的是哈希,lpush
存列表,sadd
存集合,zadd
存有序集合
字符串 Strings
常用场景:排序、计数、缓存数据等
- 获取value的长度
strlen <key>
- 返回value指定范围的值
getrange <key> <start> <stop>
- 追加值到当前value上
apppend <key> <value>
- 递增/递减
incr/decr <key>
- 列出所有key
keys *
(会对所有key进行线性扫描,很慢)
哈希 Hashes
和字符串最大不同在于,有另外一层中间层:字段,可以拉取/更新/删除部分数据片段
- 添加
hset <key> <field> <value>
- 获取
hget <key> <field>
- 添加多个字段
hmset <key> <field value> [field value...]
- 获取多个字段
hmget <key> <field> [field...]
- 获取所有字段和值
hgetall <key>
- 获取所有字段名
hkeys <key>
- 删除指定字段
hdel <key>
列表 Lists
常用场景:存储日志、追踪访问路径足迹、记录动作队列等
为指定的 key 保存数组格式的 value,以数组形式进行操作;常把查询结果作为 key,进行多次查询操作
- 创建
lpush <key> <value> [value...]
- 修剪
ltrim <key> <start> <stop>
(只保留最新的n个元素) - 获取
lrange <key> <start> <stop>
- 首/尾弹出
lpop/rpop <key>
- 阻塞式首尾弹出:
blpop/brpop <key> <seconds>
集合 Sets
常用场景:需要标记或者跟踪有重复属性的值
无序,常用于存储唯一值,进行集合运算
- 添加
sadd <key> <member> [member...]
- 获取
smembers <key>
- 判断是否在集合中
sismember <key> <member>
时间复杂度为O(1) - 求交集
sinter <key1> <key2...>
- 把交集存到新key中
sinterstore <new-key> <key1> <key2...>
有序集合 Sorted Sets
常用场景:以整数作为权重排序的时候,如排行榜
和集合最大不同在于,有权重(score) ,它提供了排序和排名功能
- 添加
zadd <key> <score> <value> [score value...]
- 找出某值的权重排名(索引)
zrevrank <key> <value>
(zrevrank
降序,zrank
默认升序) - 找出指定权重区间内成员
zrangebyscore <key> <min> <max>
- 找出指定权重区间内成员数量
zcount <key> <min> <max>
实际用例
大O表示法是描述一个算法在最坏情况下运行中耗时多少,Redis根据处理的数据的数量,用它来表示命令执行的速度,官方手册中列出了每个命令的时间复杂度,以几个为例,性能排序由快到慢:
O(1)
:常量append
,sismember
O(log(N))
:经过迭代处理zadd
O(N)
:线性del
,ltrim
(在ltrim
中N是要移除元素的个数)O(log(N)+M)
:zremrangebyscore
O(N+M*log(M))
:sort
用哈希结构优化查询
场景:用不同关键字查相同的值
通过哈希结构去掉冗余内容,但需要去维护value之间的索引和引用
bad,存了两份相同的用户对象数据:
set users:leto@dune.gov '{"id": 9001, "email": "leto@dune.gov", ...}'
set users:9001 '{"id": 9001, "email": "leto@dune.gov", ...}'
good,正常存一份,再用哈希存email的字段(值为id):
set users:9001 '{"id": 9001, "email": "leto@dune.gov", ...}'
hset users:lookup:email leto@dune.gov 9001
查询时用字段作为索引,通过id直接获取用户、以及通过email获取id后再获取用户:
get user:9001
// ruby下
id = redis.hget('users:lookup:email', 'leto@dune.gov')
user = redis.get("users:#{id}")
管道
在 Redis 中频繁访问服务器端是很常见的模式,通常一个客户端向 Redis 发送一个请求,然后在下次请求之前会一直等待返回,而用管道可以发送一堆请求却不用等待它们的响应,这会降低网络开销,显著提高性能
Redis 会用内存给命令排队,这时可以给它们做批处理,但需要根据使用的命令来决定批处理应该有多大(通过参数设置),比如约50个字符长度的 key,规模可以放宽到几千或上万
Ruby下的pipelined
方法:
redis.pipelined do
9001.times do
redis.incr('powerlevel')
end
end
事务
Redis是单线程的,所有的命令是原子性的,使用多命令时支持事务
事务可以确保:
- 命令将被顺序执行
- 命令组将以单原子模式执行
- 事务中的命令,要么全部执行成功,要么全部执行失败
以multi
开头,中间放命令组,exec
结尾,discard
放弃所有命令:
multi
set tran1 1
set tran2 2
exec
Redis可以通过watch
指定监视一个key(s),根据key的改变选择执行事务
watch <key> <key...>
:监视一个key(s) ,如果在事务执行之前这个 key(s) 被其他命令所改动,那么事务将被打断
使用场景:当在同一个事务需要取值,并基于取得结果执行操作的情况
在Ruby下,通过watch
进行监视powerlevel
的例子:
redis.watch('powerlevel')
current = redis.get('powerlevel')
redis.multi()
redis.set('powerlevel', current + 1)
redis.exec()
连接池
通常在操作redis时,会创建一个连接并基于这个连接进行redis操作,操作完成后释放连接,一般情况没什么问题,但在并发量比较高的情况, 这样频繁的连接释放对性能会有影响,这时候可以使用连接池来提升性能,它的原理是通过预先创建多个连接,当进行redis操作时,直接用已经创建的连接进行操作,操作完成后不进行释放,用于后续的其他redis操作 在Python下使用连接池:
rpool = redis.ConnectionPool(host='127.0.0.1')
conn = redis.StrictRedis(connection_pool=rpool)
conn.get('xxx')
conn.set('xxx')
这样就能避免频繁进行redis连接创建与释放, 从而提高性能
慎用 keys 命令
keys
通过指定模式(pattern)返回所有匹配的 key,它不应该用在产品代码中,因为它为了查找匹配的 key 会对所有的 key 做一个线性扫描,很慢
使用场景:开发调试时,比如bug追踪服务
每个账户有自己的id
,把每个 bug 存到一个字符串值里面去,对应的 key 看起来像bug:account_id:bug_id
,获取账号1233
下所有的 bug 信息可使用keys bug:1233:*
使用哈希结构优化,以 bug的id
作为字段,获账号1233
的bug信息时使用hkeys bugs:1233
:
hset bugs:1233 1 '{"id":1, "account": 1233, "subject": "..."}'
hset bugs:1233 2 '{"id":2, "account": 1233, "subject": "..."}'
参考