Redis API

Redis 是一个使用 C 语言开发的开源的高性能的 Key-Value 数据库。常用于数据缓存和高访问负载,Redis 通过提供多种 Value 数据类型来适应不同场景下的存储需求,目前 Redis 支持的 Value 数据类型有 string、hash、list、set、zset 类型。据 Redis 官方测试,有 50 个并发程序来执行 10 万次请求,Redis 读的速度达到了 11 万次/秒,写的速度达到了 8.1 万次/秒。

Redis 的八大特性:

  1. 速度快(官方给的数字是单机 10W OPS,速度快的原因:数据存在内存中;Redis 是 C 语言实现的);
  2. 支持持久化(断电不丢数据);
  3. 多种数据结构(Strings/Blobs/Bitmaps、Hash Tables(objects!)、Linked Lists、Sets、Sorted Sets,除了这五种,在 Redis 迭代中,还提供了一些其他的数据结构,例如 HyperLogLog(超小内存唯一值计数,12k,缺点是计数不太准确)、GEO(地理信息定位);
  4. 支持多种编程语言(Java、php、Python、Ruby、Lua、Node.js);
  5. 功能丰富(Redis 除了提供五种数据结构以外,还提供了发布订阅、Lua 脚本、事务、pipeline);
  6. 简单(核心代码只有2W多行,不依赖外部库,单线程模型);
  7. 主从复制;
  8. 高可用分布式支持(V2.8 版本开始提供了 Redis Sentinel 支持高可用,V3.0 版本开始提供了 Redis Cluster 支持分布式)。

计算机的存储介质从快到慢(从小到大,从昂贵到廉价)依次包括:

Register,
寄存器,最快
L1 Cache,
一级缓存
L2 Cache,
二级缓存
Main Memory,
内存
Local Disk,
本地硬盘
Remote Disk,
远程硬盘,最慢

Redis 是一款基于内存的数据库,那么对于内存的使用是至关重要的,我们在使用一种数据结构的时候,以空间换取时间的话,可以使用一些压缩的结构,比如 hash 里面会有 ziplist。

实际上对于 Redis 源码内部有一个 redisObject 这样的对象,它会有很多属性,其中比较重要的属性有两个,第一个就是数据类型(type),第二个就是编码方式(encoding),除了这两种还有数据指针(ptr)、虚拟内存(vm)和其他信息。

1.Redis API

Redis 的操作都是原子性的。

Redis 基本类型包括 string(字符串)、hash(哈希)、list(列表)、set(集合)、zset(有序集合)五种,除了这五种,还有一些更高级的 Redis 类型,比如用来计数的 HyperLogLog,用于支持存储地理位置信息的 Geo(地理信息定位)。

1.string

value数据结构
value内部编码
key
string
raw
int
embstr

字符串键值结构:key-value。本质上 value 都是二进制的,字符串类型的 value 有一个限制是不能大于 512 MB。

API:

API说明时间复杂度
get key获取 key 对应的 valueO(1)
set key value不管 key 是否存在,都设置 key-valueO(1)
setnx key value必须 key 不存在,才设置 key-valueO(1)
setxx key value必须 key 存在,才设置 key-valueO(1)
del key删除 key-valueO(1)
mget key1 key2 key3批量获取 key,原子操作,1 次 mget 时间 = 1 次网络时间 + n 次命令时间O(n)
mset key1 value1 key2 value2 key3 value3批量设置 key-value,原子操作O(n)
getset key newvalueset key newvalue 并返回旧的 valueO(1)
append key value将 value 追加到旧的 valueO(1)
strlen key返回字符串的长度(UTF-8 中每个英文占 1 个字节,每个中文占 2 个字节)O(1)
getrange key start end获取字符串指定下标所有的值O(1)
setrange key index value设置指定下标所有对应的值O(1)

整型操作增减的 API:

API说明时间复杂度
incr keykey 自增 1,如果 key 不存在,自增后 get key = 1O(1)
decr keykey 自减 1,如果 key 不存在,自减后 get key = -1O(1)
incrby key kkey 自增 k,如果 key 不存在,自增后 get key = kO(1)
decrby key kkey 自减 k,如果 key 不存在,自减后 get key = -kO(1)

浮点型操作的 API:

API说明时间复杂度
incrbyfloat key value增加 key 对应的值 valueO(1)

使用场景:缓存、计数器、分布式锁、分布式 id 生成器等。

2.hash

value数据结构
value内部编码
key
hash
hashtable,哈希表
ziplist,压缩列表

哈希键值结构:key-field-value,值是分为两部分的,属性项 field 和属性值 value,同一个 key 中,field 不能相同,value 可以相同。对比字符串类型,哈希类型的优点是更为直观、节省空间、可以部分更新,缺点是编程稍微复杂、TTL 不好控制(只能对 key 设置过期时间,无法对 field 设置过期时间)。

API(哈希类型的 API 都是以 h 开头的):

API说明时间复杂度
hget key field获取 hash key 对应的 field 的 valueO(1)
hset key field value设置 hash key 对应的 field 的 valueO(1)
hsetnx key field value设置 hash key 对应的 field 的 value,如果 field 已经存在,则失败O(1)
hdel key field删除 hash key 对应的 field 的 valueO(1)
hexists key field判断 hash key 是否有 fieldO(1)
hlen key获取 hash key field 的数量O(1)
hmget key field1 field2 … fieldN批量获取 hash key 的一批 field 对应的值O(n)
hmset key field1 value1 field2 value2 … fieldN valueN批量设置 hash key 的一批 field valueO(n)
hgetall key返回 hash key 对应所有的 field 和 value,小心使用O(n)
hvals key返回 hash key 对应所有 field 的 valueO(n)
hkeys key返回 hash key 对应所有的 fieldO(n)
hincrby key field intCounterhash key 对应的 field 的 value 自增 intCounterO(1)
hincrbyfloat key field intCounterhincrby 浮点数版本O(1)

使用场景:记录网站每个用户个人主页的访问量。

4.list

value数据结构
value内部编码
key
list
linkedlist
ziplist,压缩列表

列表键值结构:key-elements(elements 由 element 组成,例如 a - b - c - d - e - f,可以看出 llen = 6),列表值是有序的且可以重复的,从左右两边插入、弹出。

API(列表类型的 API 都是以 l 开头的):

API说明时间复杂度
rpush key value1 value2 …valueN从 key 对应的列表右端插入值(1~N个)O(1~N)
lpush key value1 value2 …valueN从 key 对应的列表左端插入值(1~N个)O(1~N)
linsert key before/after value newValue在 list 指定的值前/后插入 newValueO(1)
rpop key删除操作,从 key 对应的列表右侧弹出一个 itemO(1)
brpop keyrpop 阻塞版本,timeout 是阻塞超时时间,timeout=0 为永远不阻塞O(1)
lpop key删除操作,从 key 对应的列表左侧弹出一个 itemO(1)
blpop keylpop 阻塞版本,timeout 是阻塞超时时间,timeout=0 为永远不阻塞O(1)
lrem key count value根据 count 值,从列表中删除所有 value 相等的项;
(1)count >0,从左到右,删除最多 count 个 value 相等的项;
(2)count<0,从右到左,删除最多 Math.abs(count) 个 value 相等的项;
(3)count=0,删除所有value相等的项
O(n)
ltrim key start end按照索引范围修剪列表O(n)
lrange key start end获取列表指定索引范围所有 item,包含 start 和 endO(n)
lindex key index获取列表指定索引的itemO(n)
llen key获取列表长度O(1)
lset key index newValue设置列表指定索引值为 newValueO(n)

使用场景:比如微博,将关注的用户的最新微博按照从新到旧的顺序排列,还有分页(lrange 命令)等。如果想要实现一个栈的功能,可以使用 lpush + lpop 命令;如果想要实现一个队列,可以使用 lpush + rpop 命令;如果要想控制一个有固定数量的列表,可以使用 lpush + ltrim;如果想要实现一个消息队列,可以使用 lpush + brpop 命令。

5.set

value数据结构
value内部编码
key
set
hashtable
intset

集合键值结构:key-values(values 由 element 组成,例如 b、a、c),集合中的元素是无序的且不可重复的,同时集合支持集合间操作。

集合内 API:

API说明时间复杂度
sadd key element向集合 key 添加 element(如果 element 已经存在,则添加失败)O(1)
srem key element将集合 key 中的 element 移除掉O(1)
scard key获取集合 key 元素个数O(1)
sismember key element判断 element 是否在集合 key 中
srandmember key count从集合中随机取出 count 个元素
spop key从集合中随机弹出(删除)一个元素
smembers key获取集合中的所有元素,返回结果是无序的,小心使用O(n)

使用场景:抽奖系统、标签(sadd 命令)、随机数场景(spop/srandmember)。

集合间 API:

API说明时间复杂度
sdiff key1 key2差集
sinter key1 key2交集
sunion key1 key2并集
sdiff/sinter/sunion + store destkey …将差集/交集/并集结果保存在 destkey 中

使用场景:例如微博中共同关注的好友。

6.zset

通过分数来为集合中的成员进行从小到大的排序,实际开发中用的比较少。数据结构:???

value数据结构
value内部编码
key
set
ziplist,压缩列表
skiplist,跳跃表

集合键值结构:key-values(values 是由 score 和 element 组成,score 是可以重复的,element 不可重复,例如 (1, a) - (2, b) - (3, c)),集合中的元素是有序的且不可重复的,同时集合支持集合间操作。

有序集合类型相对于集合类型,普遍 API 时间复杂度要更高。

重要 API:

API说明时间复杂度
zadd key score element(可以是多对)添加 score 和 elementO(logN)
zrem key element(可以是多个)删除元素O(1)
zscore key element获取元素的分数O(1)
zincrby key increScore element增加或减少元素的分数O(1)
zcard key返回元素的总个数O(1)
zrange key start end [WITHSCORES]返回指定索引范围内的升序元素[分值]O(log(n)+m),n 指有序集合中元素的个数,m指要获取指定索引范围内元素的个数
zrangebyscore key minScore maxScore [WITHSCORES]返回指定分数范围内的升序元素[分值]O(log(n)+m)
zcount key minScore maxScore返回有序集合内在指定分数范围内的个数O(log(n)+m)
zremrangebyrank key start end删除指定排名内的升序元素O(log(n)+m)
zremrangebyscore key start end删除指定分数内的升序元素O(log(n)+m)
zrevrank
zrevrange
zrevrangebyscore
zinterstore
zunionstore

使用场景:排行榜。

7.HyperLogLog

更加极端的一种节省内存的数据结构。

1、新的数据结构?

基于 HyperLogLog 算法,使用极小空间完成独立数量统计。不是新的数据结构,本质上还是字符串。

2、三个命令

API说明时间复杂度
pfadd key element [element…]向 HyperLogLog 添加元素
pfcount key [key…]计算 HyperLogLog 的独立总数
pfmerge destkey sourcekey [sourcekey…]合并多个 HyperLogLog,并存储到到 destkey

3、内存消耗

百万独立用户:1 天 15KB,1 个月 450 KB。相对于 set 和 Bitmap,消耗内存更小。

4、使用建议

是否能容忍错误?HyperLogLog 是存在错误率的,官方给出 HyperLogLog 错误率在 0.81%;
是否需要单条数据?HyperLogLog 无法取出单条数据。

8.GEO

Redis 3.2 添加的一个特性,用于存储经纬度,计算两地距离,范围计算等。应用场景:周围的酒店等功能。GEO 是使用 zset 实现的(type geoKey = zset)。

1、2 个城市经纬度

城市经度纬度简称
北京116.2839.55beijing
天津117.1239.08tianjin

2、相关命令

API说明时间复杂度
geo key longitude latitude member [longitude latitude member…]增加地理位置信息,member 指标识
zrem key member删除 member 位置信息
geopos key member [member…]获取地理位置信息
geodist key member1 member2 [unit]获取两个地理位置的距离,unit:m(米)、km(千米)、mi(英里)、ft(尺)
georadius key longitude latitude radiusm/km/ft/mi [withcoord] [withdist] [withhash] [COUNT count] [asc/desc] [store key] [storedist key]获取指定经纬度范围内的地理位置信息集合
georadiusbymember key member radiusm/km/ft/mi [withcoord] [withdist] [withhash] [COUNT count] [asc/desc] [store key] [storedist key]获取指定 member 位置范围内的地理位置信息集合

withcoord:返回结果中包含经纬度。
withdist:返回结果中包含距离中心节点位置。
withhash:返回结果中包含 geohash。
COUNT count:指定返回结果的数量。
asc/desc:返回结果按照距离中心节点的距离做升序或者降序。
store key:将返回结果的地理位置信息保存到指定键。
storedist key:将返回结果距离中心节点的距离保存到指定键。

2.Redis 其他功能

Redis 除了提供五种数据结构之外,还提供了很多其他的功能,像慢查询、pipeline(流水线)、发布订阅、Bitmap、HyperLogLog、GEO 的功能。

1.慢查询

帮助找到系统中瓶颈的命令。

1、客户端请求的生命周期:

  1. 客户端发送命令到 Redis;
  2. Redis 中命令排队;
  3. Redis 执行命令;
  4. Redis 返回结果到客户端。

慢查询发生在第 3 阶段,客户端超时不一定慢查询,但慢查询是客户端超时的一个可能因素。

2、两个配置:

slowlog-max-len,默认值 128

首先慢查询是一个先进先出的固定长度的保存在内存中的队列(list 实现的),不会自己持久化。

slowlog-log-slower-than,默认值 10000

慢查询阈值(单位:微秒),slowlog-log-slower-than=0 表示记录所有命令。

动态配置(设置是 config set,获取是 config get):

config set slowlog-max-len 1000
config set slowlog-log-slower-than 1000

3、三个命令:

API说明时间复杂度
slowlog get [n]获取慢查询队列
slowlog len获取慢查询队列长度
slowlog reset清空慢查询队列

4、运维经验:

slowlog-max-len 不要设置过大,默认 10 ms,通常设置 1 ms;slowlog-log-slower-than 不要设置过小,通常设置 1000 左右;定期持久化慢查询。

2.pipeline(流水线)

1 次时间 = 1 次网络时间 + 1 次命令时间,n 次时间 = n 次网络时间 + n 次命令时间。pipeline 将一批命令进行打包,然后在服务端进行批量计算,按顺序将结果返回给我们,所以 1 次 pipeline(n 条命令)时间 = 1 次网络时间 + n 次命令时间,可以大大减少网络时间的开销,提高客户端的效率。这就是 pipeline。

两点需要注意下:Redis 的命令时间是微秒级别的;pipeline 每次条数要控制(网络成为了瓶颈,光速=30000公里/秒,光纤传输速度约等于光速的2/3)。

1、客户端实现:

pipeline-jedis 实现:

Jedis jedis = new Jedis("127.0.0.1", 6379);
Pipeline pipeline = jedis.pipelined();
for (int i = 0; i < 10000; i++) {
	pipeline.hset("hashkey:" + i, "field" + i, "value" + i);
}
pipeline.syncAndReturnAll();

2、与原生操作对比

原生操作是一个原子操作,即在 Redis 服务端是有这样原生的命令的,pipeline 到 Redis 会进行命令拆分,不是一个原子命令,但是返回结果是顺序的。

3、使用建议

注意每次 pipeline 携带数据量;
pipeline 每次只能作用在一个 Redis 节点上,pipeline 是不允许使用在多个 Redis 节点上的;

3.发布订阅

实现发布订阅的功能。

1、角色

分为发布者(publisher)、订阅者(subscriber)、频道(channel)。

2、发布订阅通信模型

发布消息
订阅
发布者,redis-cli
Redis server
vrs频道
pgc频道
订阅者,redis-cli
订阅者,redis-cli
订阅者,redis-cli

需要注意的是,如果发布者已经发布一条消息到一个频道了,新的订阅者订阅频道是收不到之前的消息的,无法做消息的堆积,因为 Redis 不是做一个真正的消息队列或者发布订阅专用的工具,它只是提供了这样一个功能。

3、API

API说明时间复杂度
publish channel message发布者发布 message 到 channel,会返回订阅者个数
subscribe [channel…]订阅者订阅一个或多个频道
unsubscribe订阅者取消订阅一个或多个频道
pubsub channels列出至少有一个订阅者的频道
pubsub numsub [channel…]列出给定频道的订阅者数量

4、发布订阅与消息队列对比

消息队列通信模型

发布消息
消息发布者,redis-cli
Redis server
队列
消息订阅者,redis-cli
消息订阅者,redis-cli
消息订阅者,redis-cli

对比发布订阅,消息队列是一个抢的功能,只有一个消息订阅者可以收到消息。需要知道的是,Redis 并没有原生提供消息队列的功能,需要自己使用 list 类型的 lpush + rpop 命令实现。

pub/sub的缺点: 消息的发布是无状态的,无法保证可达。

4.Bitmap(位图)

提供了一种节省内存的方案。

1、API

API说明时间复杂度
setbit key offset value给位图指定索引设置值
getbit key offset获取位图指定索引的值
bitcount key [start end]获取位图指定范围(start 到 end,单位为字节,如果不指定就是获取全部)位值为 1 的个数
bitop op destkey key [key…]做多个 Bitmap 的 and(交集)、or(并集)、not(非)、xor(异或)操作并将结果保存在 destkey 中
bitpos key targetBit [start] [end]计算位图指定范围(start 到 end,单位为字节,如果不指定就是获取全部)第一个偏移量对应的值等于 targetBit 的位置

2、独立用户统计

以独立用户统计这个功能为例,使用 set 和 Bitmap,1 亿用户,5 千万独立用户,进行对比:

数据类型每个userid占用空间需要存储的用户量全部内存量
set32位(假设userid用的是整型,实际很多网站用的是长整型)50,000,00032位 * 50,000,000 = 200MB
Bitmap1位100,000,0001位 * 100,000,000 = 12.5MB

如果只有 10 万独立用户呢?

数据类型每个userid占用空间需要存储的用户量全部内存量
set32位(假设userid用的是整型,实际很多网站用的是长整型)1,000,00032位 * 1,000,000 = 4MB
Bitmap1位100,000,0001位 * 100,000,000 = 12.5MB

3、使用建议

type = string,最大 512 MB;
注意 setbit 时的偏移量,可能有较大耗时;
位图不是绝对好。

3.常见问题

1、Redis 为什么速度能这么快?

  • 完全基于内存,执行效率高;
  • 采用了单线程架构,单线程避免了多线程环境下的锁开销;
  • 使用了多路 IO 复用模型,非阻塞 IO。

Redis 多路 IO 复用模型:

  • Redis 采用的多路 IO 复用函数:epoll/ kqueue/ evport/ select
  • 优先选择时间复杂度为 O(1) 的多路 IO 复用函数作为底层实现,以时间复杂度为 O(n) 的 select 作为保底;
  • 基于 react 设计模式监听 I/O 事件 。

2、大量的 key 同时过期,清除大量的 key 很耗时,会出现短暂的卡顿现象,怎么解决?

在设置 key 的过期时间的时候,给每个 key 加上随机数。

3、如何从海量数据里查询某一固定前缀的 key?

4、如何使用 Redis 做异步队列?

使用 List 作为队列,rpush 生成消息,lpop 消费消息。缺点是 lpop 不会去等待队列里有值才去消费的,此时可以通过在应用层引入 sleep 机制去调用 lpop 重试。

如果不通过 sleep 机制怎么等待?

还可以通过 blpop key [key …] timeout 命令阻塞直到队列有消息或超时。blpop 的缺点就是只提供一个消费者消费。

如果是一对多,可以看上面说的 发布订阅。

5、Redis 如何做持久化?

Redis 做持久化一般有三种方式:

  • RDB 持久化,即快照方式,保存某个时间点的全量数据快照,默认开启;
  • AOF 持久化,保存写状态,即记录下除了查询以外的所有变更数据库状态的指令,以 append 的形式追加保存到 AOF 文件中,默认关闭;
  • 混合模式,RDB 做全量备份,AOF 做增量备份。

手动触发 RDB 持久化:

API说明时间复杂度
save阻塞 Redis 的服务器进程,知道 RDB 文件被创建完毕,不推荐使用。
bgsavefork 出一个子进程来创建 RDB 文件,不阻塞服务器进程。

自动化触发 RDB 持久化:

  • 根据 redis.conf 配置里的 save m n 定时触发(用的是 bgsave);
  • 主从复制时,主节点自动触发;
  • 执行 debug reload 的时候触发;
  • 执行 shutdown 且没有开启 AOF 持久化的时候触发。

RDB 持久化的缺点:

  • 内存数据的全量同步,数据量大会由于 I/O 而严重影响性能;
  • 可能会因为 Redis 挂掉而丢失从当前至最近一次快照期间的数据。

AOF 持久化:

随着写操作的不断增加,AOF 文件越来越大,日志重写解决了 AOF 文件大小不断增大的问题,原理:

  • 调用系统 fork,创建一个子进程;
  • 子进程把新的 AOF 写到一个临时文件里,不依赖原来的 AOF 文件;
  • 主进程持续将新的变动同时写到内存和原来的 AOF 里;
  • 主进程获取子进程重写 AOF 的完成信号,往新 AOF 同步增量变动;
  • 使用新的 AOF 文件替换掉旧的 AOF 文件。

RDB 和 AOF 的优缺点:

RDB 优点:全量数据快照,文件小,恢复快;
RDB 缺点:无法保存最近一次快照之后的数据;
AOF 优点:可读性高,适合保存增量数据,数据不易丢失;
AOF 缺点:文件体积大,恢复时间长;

RDB-AOF 混合持久化方式,默认的持久化方式。

  • bgsave 做镜像全量持久化,AOF 做增量持久化。
  • 14
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Redis API文档。Redis(全称:Remote Dictionary Server 远程字典服务)是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志、Key-Value数据库,并提供多种语言的API。从2010年3月15日起,Redis的开发工作由VMware主持。从2013年5月开始,Redis的开发由Pivotal赞助。redis是一个key-value存储系统。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。Redis 是一个高性能的key-value数据库。 redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部 分场合可以对关系数据库起到很好的补充作用。它提供了Java,C/C++,C#,PHP,JavaScript,Perl,Object-C,Python,Ruby,Erlang等客户端,使用很方便。Redis支持主从同步。数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。这使得Redis可执行单层树复制。存盘可以有意无意的对数据进行写操作。由于完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。同步对读取操作的可扩展性和数据冗余很有帮助。redis的官网地址,非常好记,是redis.io。(域名后缀io属于国家域名,是british Indian Ocean territory,即英属印度洋领地)目前,Vmware在资助着redis项目的开发和维护。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值