基本特性

基于内存的,可持久化的,键值对方式的存储,数据保存在内存中,速度快,根据不同场景使用

命令操作

通过标识数字定位,默认开始标识是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):常量 appendsismember
  • O(log(N)):经过迭代处理 zadd
  • O(N):线性 delltrim (在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": "..."}'

参考

Redis官方命令手册

The Little Redis Book