Redis数据结构及操作 string
set/setnx
get
incr/incrby
decr/decrbys
setex: set value with expire time
setrange: 设置指定 key 的 value 值的子字符串。
getrange: 获取指定 key 的 value 值的子字符串。
mset/msetnx: 一次设置多个key的值
mget
getset:设置key的值,并返回key的旧值
append: 给指定 key 的字符串值追加 value,返回新字符串值的长度。
strlen: 取指定 key 的 value 值的长度。
hash
hset/hsetnx
hmset: 同时设置 hash 的多个 field
hget/hmget
hincrby
hexists: 测试指定 field 是否存在
hlen: 返回指定 hash 的 field 数量
hdel: 返回指定 hash 的 field 数量
hkeys: 返回 hash 的所有 field
hvals: 返回 hash 的所有 value
hgetall: 获取某个 hash 中全部的 filed 及 value
list list支持头部和尾部插入元素,也支持头尾删除元素,可以用作栈和队列
lpush: left push, 在 key 对应 list 的头部添加字符串元素
lpop: lpop
lrang: 取某个范围的值
rpush: right push, 在 key 对应 list 的尾部添加字符串元素
linset: 在 key 对应 list 的特定位置之前或之后添加字符串元素
lset: 设置 list 中指定下标的元素值(下标从 0 开始)
lrem: 从 key 对应 list 中删除 count 个和 value 相同的元素。 count>0 时,按从头到尾的顺序删除; count<0 时,按从尾到头的顺序删除; count=0 时,删除全部
lpop: 从 list 的头部删除元素,并返回删除元素
rpop: 从 list 的尾部删除元素,并返回删除元素
rpoplpush: 从第一个 list 的尾部移除元素并添加到第二个 list 的头部,最后返回被移除的元素值,整个操 作是原子的.如果第一个 list 是空或者不存在返回 nil
lindex: 返回名称为 key 的 list 中 index 位置的元素
llen: 返回 key 对应 list 的长度
set Redis 的 set 是 string 类型的无序集合。set 元素最大可以包 (2 的 32 次方)个元素。set中的元素时唯一的
sadd: 向名称为 key 的 set 中添加元素
smembers: 查看set中的元素
srem: 删除名称为 key 的 set 中的元素 member
spop: 随机返回并删除名称为 key 的 set 中一个元素
sdiff: 返回所有给定 key 与第一个 key 的差集
sdiffstore: 返回所有给定 key 与第一个 key 的差集,并将结果存为另一个 key
sinter: 返回所有给定 key 的交集
sinterstore: 返回所有给定 key 的交集,并将结果存为另一个 key
sunion: 返回所有给定 key 的并集
sunionstore: 返回所有给定 key 的并集,并将结果存为另一个 key
smove: 从第一个 key 对应的 set 中移除 member 并添加到第二个对应 set 中
scard: 返回名称为 key 的 set 的元素个数
sismember: 测试 member 是否是名称为 key 的 set 的元素
srandmember: 随机返回名称为 key 的 set 的一个元素,但是不删除元素
sorted sets sorted sets即有序的set,类似stl中的set概念。
zadd: 向名称为 key 的 zset 中添加元素 member,score 用于排序。如果该元素已经存在,则根据 score 更新该元素的顺序
zrange: 用于查看元素
zrem: 删除名称为 key 的 zset 中的元素 member
zincrby: 如果在名称为 key 的 zset 中已经存在元素 member,则该元素的 score 增加 increment;否则 向集合中添加该元素,其 score 的值为 increment
zrank: 返回名称为 key 的 zset 中 member 元素的排名(按 score 从小到大排序)即下标
zrevrank: 返回名称为 key 的 zset 中 member 元素的排名(按 score 从大到小排序)即下标
zrevrange: 返回名称为 key 的 zset(按 score 从大到小排序)中的 index 从 start 到 end 的所有元素
zrangebyscore: 返回集合中 score 在给定区间的元素
zcount: 返回集合中 score 在给定区间的数量
zcard: 返回集合中元素个数
zscore: 返回给定元素对应的 score
zremrangebyrank: 删除集合中排名在给定区间的元素
zremrangebyscore: 删除集合中 score 在给定区间的元素
这些操作,只要你键入操作名后redis都会提示怎么用。如下图:
Redis常用命令 键值相关命令
keys: 返回满足给定 pattern 的所有 key (使用”keys *”可以查看redis中的所有key)
exists: 返回满足给定 pattern 的所有 key
del: 删除一个 key
expire: 设置一个 key 的过期时间(单位:秒)
select: 选择数据库
move: 将当前数据库中的 key 转移到其它数据库中
persist: 移除给定 key 的过期时间(将key设置为过期)
randomekey: 随机返回 key 空间的一个 key
rename: 重命名 key
type: 返回值的类型服务器相关命令
ping: 测试连接是否存活
echo: 在命令行打印一些内容
select: 选择数据库。Redis 数据库编号从 0~15,我们可以选择任意一个数据库来进行数据的存取。
quit: 退出连接
dbsize: 返回当前数据库中 key 的数目
info: 获取服务器的信息和统计
monitor: 实时转储收到的请求
config get: 获取服务器配置信息
flushdb: 删除当前选择数据库中的所有 key
flushall: 删除所有数据库中的所有 key
Redis高级实用命令 安全性 在redis 配置文件中开启配置: masterauth <master-password>
主从复制 主从复制特点
master 可以拥有多个 slave
多个 slave 可以连接同一个 master 外,还可以连接到其他 slave
主从复制不会阻塞 master,在同步数据时,master 可以继续处理 client 请求
提高系统的伸缩性主从复制过程 当配置好 slave 后,slave 与 master 建立连接,然后发送 sync 命令。无论是第一次连接还是重新连接,master 都会启动一个后台进程,将数据库快照保存到文件中,同时 master 主进 程会开始收集新的写命令并缓存。后台进程完成写文件后,master 就发送文件给 slave,slave 将文件保存到硬盘上,再加载到内存中,接着 master 就会把缓存的命令转发给 slave,后续 master 将收到的写命令发送给 slave。如果 master 同时收到多个 slave 发来的同步连接命令, master 只会启动一个进程来写数据库镜像,然后发送给所有的 slave。主从复制配置
只需在slave的配置文件中添加# slaveof <masterip> <masterport>
如,slaveof 192.168.1.1 6379
修改主从redis server的启动端口为不同的值,分别启动master和slave server。
分别连接主从 redis server进行操作,可见数据是同步的 tips:
client端使用info命令,查看其输出的role值可知连接的是master还是slave。如下图: 同时还有一个 master_link_status 用于标明主从是否异步,如果此值=up,说明同步正常;如果此值=down, 说明同步异步; db0:keys=1,expires=0,avg_ttl=0, 用于说明数据库有几个 key,以及过期 key 的数量。
事务控制 redis 对事务的支持目前还比较简单。redis 只能保证一个 client 发起的事务中的命令可以连 续的执行,而中间不会插入其他 client 的命令。 由于 redis 是单线程来处理所有 client 的请 求的所以做到这点是很容易的。一般情况下 redis 在接受到一个 client 发来的命令后会立即 处理并 返回处理结果,但是当一个 client 在一个连接中发出 multi 命令有,这个连接会进入 一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接 受到 exec 命令后,redis 会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到 一起返回给 client.然后此连接就 结束事务上下文。
简单的事务控制 使用multi + command1 + … + commandN + exec, 可以将多个命令一起发送到redis server,然后一起返回结果。这记为一个事务
取消事务 通过在执行exec前调用discard可以取消事务
通过watch(乐观锁)控制事务 在多个session同时操作数据库中的数据时,可以用watch对操作字段加锁来实现事务同步。其操作原理如下
session 1
session 2
redis 127.0.0.1:6379> get age
“10”
redis 127.0.0.1:6379> watch age
OK
redis 127.0.0.1:6379> multi
OK
redis 127.0.0.1:6379>
redis 127.0.0.1:6379> set age 30
OK
redis 127.0.0.1:6379> get age
“30”
redis 127.0.0.1:6379>
redis 127.0.0.1:6379> set age 20
QUEUED
redis 127.0.0.1:6379> exec
(nil)
redis 127.0.0.1:6379> get age
“30”
redis 127.0.0.1:6379>
从以上实例可以看到在 第一步,Session 1 还没有来得及对 age 的值进行修改 第二步,Session 2 已经将 age 的值设为 30 第三步,Session 1 希望将 age 的值设为 20,但结果一执行返回是 nil,说明执行失败,之后 我们再取一下 age 的值是 30,这是由于 Session 1 中对 age 加了乐观锁导致的。
redis 的事务实现是如此简单,当然会存在一些问题 第一个问题是 redis 只能保证事务的每 个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配。 如:
1
2
3
redis 127.0 .0 .1 :6379 > get age
"30"
redis 127.0 .0 .1 :6379 > get name
"HongWan "
redis 127.0 .0 .1 :6379 > multi
OK
redis 127.0 .0 .1 :6379 > incr age
QUEUED
redis 127.0 .0 .1 :6379 > incr name
QUEUED
redis 127.0 .0 .1 :6379 > exec
1) (integer ) 31
2) (error ) ERR value is not an integer or out of range
redis 127.0 .0 .1 :6379 > get age
"31"
redis 127.0 .0 .1 :6379 > get name "HongWan "
redis 127.0 .0 .1 :6379 >
从以上操作我们可以看出由于name是非数字,不支持incr操作,导致事务失败了,但age却成功加1。 不过我现在用的redis 3.2.7版本已经没有这个问题。
持久化机制 redis 是一个支持持久化的内存数据库,也就是说 redis 需要经常将内存中的数据同步到磁盘 来保证持久化。redis 支持两种持久化方式,一种是 Snapshotting(快照)也是默认方式,另 一种是 Append-only file(缩写 aof)的方式。下面分别介绍:
snapshotting方式 这是默认的持久化方式。这种方式就是将内存中数据以snapshot方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置自动做快照持久化的方式。redis可以配置在n秒内如果超过m个key被修改就自动做snapshot。其默认的配置如下:
1
2
3
4
################################ SNAPSHOTTING ################################
save 900 1 #900 秒内如果超过 1 个 key 被修改,则发起快照保存
save 300 10 #300 秒内容如超过 10 个 key 被修改,则发起快照保存
save 60 10000
snapshot过程如下:
redis调用fork,创建子进程
父进程继续处理client请求,子进程负责将内存内容写入到临时文件。由写时拷贝原理,父子进程共享相同的物理页面。当父进程需要写操作时会重新创建副本,故子进程地址空间内的数据是fork时刻整个数据库的一个快照。
当子进程将快照写入临时文件完毕后,用临时文件替换之前的快照文件,然后子进程退出。 p.s: redis也支持使用save或bgsave命令通知redis做一次快照操作。不过save操作是在主进程中保存快照的,这是会阻塞所有的client请求,不推荐使用。 redis的快照文件为dump.rdb
aof方式 由于快照是在一定间隔时间做一次的,因此如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果要求应用不能丢失任何修改的话,可以采用aof持久化方式。 aof(append-only file)比snapshot具有更好的持久化性,当使用aof方式时,redis会将收到的命令通过write函数追加到文件中(默认是appendonly.aof)。当redis重启时会通过重新执行文件中保存的写命令来重建整个数据库的内容。 由于os会在内核中缓存write做的修改,所以aof文件可能不是立即写到磁盘上,也存在丢失部分修改的情况。不过我们可以通过配置文件告诉redis通过fsync函数强制写入磁盘的时机。aof方式可以通过配置appendonly yes
启用,其有进行sync的时间有如下三种方式:
appendfsync always //收到命令立即写入磁盘,最慢,但是持久性最好
appendfsync everysec //默认方式,每秒钟写入磁盘一次,是持久性和性能的折中
appendfsync no //由操作系统选择,性能最优,但持久性没保证
aof方式的一个问题是持久化文件会越变越大(因为其为保存了每一条命令,如执行incr var
100次,则要保存100条,但其实恢复状态只需set var var 100即可[假设var初值为0])。为了压缩aof的持久化文件。redis提 供了 bgrewriteaof 命令。收到此命令 redis 将使用与快照类似的方式将内存中的数据以命令 的方式保存到临时文件中,最后替换原来的文件。具体过程如下:
redis 调用 fork ,现在有父子两个进程
子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
父进程继续处理 client 请求,除了把写命令写入到原来的 aof 文件中。同时把收到的写命 令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然 后父进程把缓存的写命令也写入到临时文件。
现在父进程可以使用临时文件替换老的 aof 文件,并重命名,后面收到的写命令也开始 往新的 aof 文件中追加。
发布及订阅消息 redis 作为一个 pub/sub 的 server,在订阅者 和发布者之间起到了消息路由的功能。订阅者可以通过 subscribe 和 psubscribe 命令向 redis server 订阅自己感兴趣的消息类型,redis 将消息类型称为通道(channel)。当发布者通过 publish命令向redis server发送特定类型的消息时, 订阅该消息类型的全部client都会收到此消息。这里消息的传递是多对多的。一个 client 可以订阅多个 channel,也可以向多个 channel 发送消息。redis提供相关的命令如下:
SUBSCRIBE channel [channel …]: 用于订阅某个频道或者多个频道
PUBLISH channel message: 用于发布频道信息
PSUBSCRIBE pattern [pattern …]: 批量订阅满足某一pattern的频道,如tv*,表示以tv开头的频道
Pipeline 批量发送请求 由于redis server处理命令的速度很快,如果客户端命令多的话,可以一起打包发送给server,从而减少大部分的网络延迟,达到提高性能的目的。我们可以利用 pipeline 的方式 从 client 打包多条命令一起发出,不需要等待单条命令的响应返回,而 redis 服务端会处理 完多条命令后会将多条命令的处理结果打包到一起返回给客户端。同时,打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令 越多越好。具体多少合适需要根据具体情况测试。
java代码:
1
2
private static void usePipeline () {
try {
ConnectionSpec spec = DefaultConnectionSpec.newSpec("192.168.115.170" , 6379 , 0 , null );
JRedis jredis = new JRedisPipelineService(spec);
for (int i = 0 ; i < 100000 ; i++) { jredis.incr("test2" );}
jredis.quit();
} catch (Exception e) {
}
}
1
2
private static void withoutPipeline () {
try {
JRedis jredis = new JRedisClient("192.168.115.170" , 6379 );
for (int i = 0 ; i < 100000 ; i++) { jredis.incr("test2" );}
jredis.quit();
} catch (Exception e) {
}
}
虚拟内存的使用 redis实现了自己的虚拟内存管理。redis 没有使用操作系统提供的虚拟内存机制而是自己在实现了自己的虚拟内存机制,主要的理由有两点:
操作系统的虚拟内存是已 4k 页面为最小单位进行交换的。而 redis 的大多数对象都远小 于 4k,所以一个操作系统页面上可能有多个 redis 对象。另外 redis 的集合对象类型如 list,set 可能存在与多个操作系统页面上。最终可能造成只有 10%key 被经常访问,但是所有操作系 统页面都会被操作系统认为是活跃的,这样只有内存真正耗尽时操作系统才会交换页面。
相比于操作系统的交换方式,redis 可以将被交换到磁盘的对象进行压缩,保存到磁盘的对 象可以去除指针和对象元数据信息,一般压缩后的对象会比内存中的对象小 10 倍,这样 redis 的虚拟内存会比操作系统虚拟内存能少做很多 io 操作
参考资料 :《redis实战》