Redis 介绍
Redis简介
Redis 是一个开源的,先进的 Key-Value 存储。它通常被成为数据结构服务器,因为( Key )键可以包含字符串,哈希,链表,集合和有序集合。
Redis 支持存储的Value类型很多,包括string(字符串)、list(链表)、set(集合)、zset(有序集合)。这些数据类型都支持push/pop、add/remove以及交集和并集更丰富的操作,Redis还支持各种不同方式的排序。(为了保证效率,数据都是缓存在内存中,它也可以周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件。
Redis适用场合
- 目前全球最大的Redis用户是新浪微博
- 取最新 N 个数据的操作
- 排行榜应用,取 TOP N 操作
- 需要精确设定过期时间的应用
- 计数器应用
- Uniq操作,获取某段时间所有数据排重值
- 实时系统,反垃圾系统
- Pub/Sub构建实时消息系统
- 构建队列系统
- 缓存
Redis 安装
wget http://download.redis.io/releases/redis-3.2.9.tar.gz
tar xzf redis-3.2.9.tar.gz
cd redis-3.2.9
make
通常会把redis-server和redis-cli 这两个可执行文件拷贝到 /usr/bin/ 目录下(这里假设 /usr/bin 目录在环境变量中),这样无需进入redis-server和redis-cli 所在的目录打开终端执行redis-server和redis-cli 。
sudo cp src/redis-server /usr/bin/
sudo cp src/redis-cli /usr/bin/
Redis 的数据类型
String类型及操作
String是最简单的类型,一个Key对应一个Value。
String类型是二进制安全的。其可以包含任何数据,比如jpg图片或者序列化的对象。
命令 | 描述 |
---|---|
set key value | 设置指定 key 的值 |
setnx key value | 只有在指定的 key 不存在时,为 key 设置指定的值,否则设置无效 |
setex key time value | 为指定的 key 设置值及其过期时间。如果 key 已经存在, SETEX 命令将会替换旧的值。 |
setrange key offset value | 用指定的字符串覆盖给定 key 所储存的字符串值,覆盖的位置从偏移量 offset 开始 |
mset key1 value1 key2 value2 .. keyN valueN | 同时设置一个或多个 key-value 对 |
msetnx key1 value1 key2 value2 .. keyN valueN | 只有当所有给定 key 都不存在时,同时设置一个或多个 key-value 对 |
get key | 获取指定 key 的值 |
getset key value | 将给定 key 的值设为 value ,并返回 key 的旧值(old value) |
getrange key start end | 用于获取存储在指定 key 中字符串的子字符串。字符串的截取范围由 start 和 end 两个偏移量决定(包括 start 和 end 在内) |
mget key1 key2 ..keyn | 返回所有(一个或多个)给定 key 的值。如果给定的 key 里面,有某个 key 不存在,那么这个 key 返回特殊值 nil 。 |
incr key | 将 key 的值增加 1 |
incrby key value | 将 key 的值增加 value |
decr key | 将 key 的值减少 1 |
decrby key value | 将 key 的值减少 value |
append key value | 如果 key 已经存在并且是一个字符串, APPEND 命令将 value 追加到 key 原来的值的末尾 |
substr key start end | 返回名称为key的value的子串 |
strlen key | 返回 key 所储存的字符串值的长度 |
hashes类型及操作
Redis hash 是一个 string 类型的 field 和 value 的映射表。它的添加、删除操作都是O(1)(平均)
hash 特别适合用于存储对象。相对于将对象的每个字段存成单个 string 类型。将一个对象存储在 hash 类型中会占用更少的内存,并且可以更方便地存取整个对象。
命令 | 描述 |
---|---|
hset key field value | 如果字段是哈希表中的一个新建字段,并且值设置成功,返回 1 。 如果哈希表中域字段已经存在且旧值已被新值覆盖,返回 0 。 |
hsetnx key field value | 用于为哈希表中不存在的的字段赋值。若哈希表不存在,也成功执行该命令。 |
hmset key filed1 value1 filed2 value2 . . filedN valueN | 若key不存在,会创建一个空哈希表,然后添加多个 field-value 若key存在,则覆盖key中多个字段filed对应的值 |
hget key field | 获得键名key的字段filed的值 |
hget key filed1 filed2 .. filedN | 获得键名key的一个或多个给定filed字段的值 |
hgetall key | 获取 key 中所有的字段和值 |
hincrby key filed value | key中字段filed值增加value |
hexists key filed | 查看key中的字段filed是否存在 |
hdel key filed1 filed2 .. filedN | 删除 key 中的一个或多个指定字段,不存在的字段将被忽略 |
hlen key | 获取 key 中的字段 filed 的数量 |
hkeys key | 获取 key 中的所有字段名 |
hvals key | 获取 key 中的所有字段filed对应的值 |
lists类型及操作
list 是一个链表结构,主要功能是 push 、pop、获取一个范围的所有值等等,操作中 key 理解为链表的名字。
list 类型其实就是一个每个子元素都是 string 类型的双向链表。通过 push、pop操作从链表的头部或者尾部添加删除元素,这样 list 既可以作为栈,又可以作为队列。
命令 | 描述 |
---|---|
lpush key value1 value2 . . valueN | 将一个或多个值插入到列表的首部(最左边) |
rpush key value1 value2 . . valueN | 将一个或多个值插入到列表的尾部(最右边) |
linsert key before或after existing_value new_value | 用于在列表的元素前或者后插入元素。当指定元素(existing_value)不存在于列表中时,不执行任何操作。当列表不存在时,被视为空列表,不执行任何操作。如果 key 不是列表类型,返回一个错误。 |
lindex key index | 通过索引获取列表中的元素 |
lrange key start end | 获取列表指定范围内的元素 |
blpop key1 key2 key3 . . keyN time | 移出并获取列表的第一个元素, 如果列表没有元素会阻塞列表直到等待时间(time)超时后返回 nil 或发现可弹出元素为止 |
brpop key1 key2 key3 . . keyN time | 移出并获取列表的最后一个元素, 如果列表没有元素会阻塞列表直到等待时间(time)超时后返回 nil 或发现可弹出元素为止 |
rpoplpush srckey dstkey | 移除列表的最后一个元素,并将该元素添加到另一个列表并返回 |
lpop key | 移出并获取列表的第一个元素 |
rpop key | 移出并获取列表的最后一个元素 |
ltrim key start end | 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除 |
lrem key count value | count > 0 : 从表头开始向表尾搜索,移除与 VALUE 相等的元素,数量为 COUNT count < 0 : 从表尾开始向表头搜索,移除与 VALUE 相等的元素,数量为 COUNT 的绝对值 count = 0 : 移除表中所有与 VALUE 相等的值 |
llen key | 获取列表长度 |
lset key index value | 通过索引设置列表元素的值 |
sets类型及操作
set 是集合,它是 string 类型的无序集合。 set 是通过 hash table 实现的,添加、删除和查找的复杂度都是 O(1)。对集合我们可以取并集、交集、差集。通过这些操作我们可以实现 sns 中的好友推荐和 blog 的 tag 功能。
命令 | 描述 |
---|---|
sadd key value1 value2 . . valueN | 向集合添加一个或多个成员 |
smembers key | 获得集合中的所有成员 |
srandmember key [count] | 获得集合中的一个随机元素。 若 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。 若 count 大于等于集合基数,那么返回整个集合。 若 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值。 |
sinter key key1 key2 . . keyN | 获得所有给定集合的交集 |
sunion key key1 key2 . . keyN | 获得所有给定集合的并集 |
sdiff key key1 key2 key3 . . keyN | 获得给定所有集合的差集 |
sinterstore dstkey key key1 key2 . . keyN | 获得所有给定集合的交集并存储在 dstkey 集合中 |
sunionstore dstkey key key1 key2 . . keyN | 获得所有给定集合的并集并存储在 dstkey 集合中 |
sdiffstore dstkey key key1 key2 . . keyN | 获得所有给定集合的差集并存储在 dstkey 集合中 |
spop key | 移除并返回集合中的一个随机元素 |
srem key member1 member2 . . memberN | 移除集合中的一个或多个成员元素,不存在的成员元素会被忽略 |
smove srckey dstkey member | 将指定成员 member 元素从 source 集合移动到 dstkey 集合 |
scard key | 获取集合的成员数 |
sismember key member | 判断 member 元素是否是集合 key 的成员 |
sets类型及操作
sorted set 是 set 的一个升级版本,它在 set 的基础上增加了一个顺序属性,这一属性在添加修改元素的时候可以指定,每次指定后,zset 会自动重新按新的值调整顺序。可以理解为有两列的 mysql 表,一列存 Value,一列存顺序。操作中 Key 理解为 zset 的名字。
命令 | 描述 |
---|---|
zadd | |
zrem | |
zincrby | |
zrank | |
zrevrank | |
zrevrange | |
zrangebyscore | |
zcount | |
zcard | |
zremrangebyrank | |
zremrangebyscore |
常用的操作命令
命令 | 描述 |
---|---|
ping | 测试连接是否正常 |
echo | 用于打印给定的字符串 |
select index | 切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值。默认使用 0 号数据库 |
quit | 请求服务器关闭与当前客户端的连接 |
dbsize | 返回当前数据库中key的数目 |
info | 获取服务器的信息和统计 |
config get | 用于取得运行中的 Redis 服务器的配置参数 |
exists key | 确认一个key键是否存在 |
del key | 删除一个key键 |
type key | 返回值的类型 |
keys pattern | 查找所有符合给定模式( pattern)的 key |
randomkey | 从当前数据库中随机返回一个 key |
keyrename oldkey newkey | 修改 key 的名称 |
rename key newkey | 当 key 和 newkey 相同,或者 key 不存在时,返回一个错误。 当 newkey 已经存在时,rename 命令将覆盖旧值。 |
expire key value | 为给定 key 设置过期时间 |
persist key | 移除给定 key 的生存时间,将这个 key 从(带生存时间 key )转换成(一个不带生存时间、永不过期的 key ) |
ttl key | 以秒为单位,返回给定 key 的剩余生存时间 |
select(index) | 按索引查询 |
move key db | 将当前数据库的 key 移动到给定的数据库 db 当中 |
flushdb | 删除当前选择数据库中的所有key |
flushall | 删除所有数据库中的所有key |
Redis 高级应用
安全性
设置客户端连接后,进行任何其他指定前需要使用的密码。
在 redis.conf 配置文件中,找到 #requirepass foobared 这一行
#requirepass foobared
requirepass beijing #设置连接的口令是 beijing
[root@localhost redis-3.2.9]#src/redis-cli
redis 127.0.0.1:6379>keys *
(error)ERR operation not permitted
redis 127.0.0.1:6379>
这里显示权限被禁止,我们来设置一下权限
redis 127.0.0.1:6379>auth beijing
OK
redis 127.0.0.1:6379>keys *
1)"name"
redis 127.0.0.1:6379>
我们还可以在连接到服务器期间就指定一个口令
[root@localhost redis-3.2.9]#src/redis-cli -a beijing
redis 127.0.0.1:6379>keys *
1)"name"
redis 127.0.0.1:6379>
注意:因为 redis 速度相当快,所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进行150k 次的密码尝试,这意味着你需要指定非常非常大的密码来防止暴力破解。
主从复制
Redis 主从复制配置和使用都非常简单。通过主从复制可以允许多个 slave server 拥有和 master server 相同的数据库副本。
主从复制的特点
- Master 可以拥有多个 slave
- 多个 slave 可以连接同一个 master 外,还可以连接到其它 slave
- 主从复制不会阻塞 master,在同步数据时,master 可以继续处理 client 请求
- 提高系统的伸缩性
主从复制过程
1、slave 与 master 建立连接,发送 sync 同步命令
2、Master 会启动一个后台进程,将数据库快照保存到文件中,同时 master 主进程会开始收集新的写命令并缓存。
3、后台完成保存后,就将此文件发送给 slave
4、slave 将此文件保存到硬盘上
主从服务器的配置
配置 slave 服务器很简单,只需要在 slave 的配置文件中加入一下配置
slaveof 192.168.1.1 6379 #指定 master 的 ip 和端口
masterauth lamp #这是主机的密码
设置成功后,我们来做一个实验
我们在主数据库上设置一对键值对
redis 127.0.0.1:6379>set name master
OK
redis 127.0.0.1:6379>
在从数据库上取这个键
redis 127.0.0.1:6378>get name
"master"
redis 127.0.0.1:6378>
主从服务器的判断
只需调用 info 就可以得到主从的信息,例如
redis 127.0.0.1:6378>info
role:slave #从此处可以判断出是该服务器是从服务器
master_host:localhost
master_port:6379
master_last_io_seconds_age:10
master_sync_in_progress:0
db0:keys=1,expires=0
redis 127.0.0.1:6378>
事务处理
事务处理
Redis 对事务的支持目前还比较简单。Redis 只能保证一个 client 发起的事务中的命令可以连续的执行,而中间不会插入其它 client 命令。
当一个 client 在一个连接中发出 multi 命令时,这个连接会进入一个事物上下文,该连接后续后续的命令不会立即执行,而是先放到一个队列中,当执行 exec 命令时, redis 会顺序的执行队列中的所有命令。
乐观锁复杂事物控制
乐观锁:大多数是基于数据版本( version )记录机制实现的。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个” version “字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加1。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库当前版本号,则予以更新,否则认为是过期数据。
Redis 乐观锁实例
假设有一个 age 的 key,我们开2个 redis-cli 终端来对 age 进行赋值操作,看一下结果如何。
(1)第一步 redis-cli 终端1
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>
(2)第二步 redis-cli 终端2
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>
(3)第三步:redis-cli 终端1
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>
综上,watch 命令会监视给定的 key,当 exec 时候如果监视的 key 从调用 watch 后发生过变化,则整个事物会失败。也可以调用 watch 多次监视多个 key 是对整个连接有效的,事物也一样。如果连接断开,监视和事物都会被自动清除。当然了 exec,discard,unwatch命令都会清除连接中的所有监视。
事物回滚
redis 127.0.0.1:6379>get age
"30"
redis 127.0.0.1:6379>get name
"lijie"
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
"lijie"
redis 127.0.0.1:6379>
从这个例子中可以看到,age 由于是个数字,那么它可以有自增运算,但是 name 是个字符串,无法对其进行自增运算,所以会报错。如果按传统关系型数据库的思路来讲,整个事物都会回滚,但是我们看到 redis 却是将可以执行的命令提交了,所以这个现象对于习惯于关系型数据库操作的朋友来说是很别扭的,这一点也是 redis 今天需要改进地方。
持久化机制
Redis 是一个支持持久化的内存数据库,也就是说 redis 需要经常将内存中的数据同步到硬盘来保证持久化。
Redis 支持两种持久化方式:
- snapshotting(快照)也是默认方式
- Append-only file(缩写aof)的方式
Snapshotting 方式
快照是默认的持久化方式。这种方式是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为 dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置 redis 在 n 秒内,如果超过 m 个 key,修改就自动做快照。
save 900 1 #900秒内,如果超过1个 key 被修改,则发起快照保存
save 300 10 #300秒内,若果超过10个 key 被修改,则发起快照保存
aof 方式
由于快照方式是在一定间隔时间做一次的,所以如果 redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。
aof 方式比快照方式有更好的持久化性,是由于在使用 aof 时,redis 会将每一个收到的写命令都通过 write 函数追加到文件中,当 redis 重启时,会通过重新执行文件中保存的写命令在内存中重建整个数据库的内容。
当然由于操作系统会在内核中缓存 write 做的修改,所以可能不是立即写到磁盘上。这样 aof 方式的持久化也还是有可能会丢失部分修改。可以通过配置文件告诉 redis 我们想要通过 fsync 函数强制操作系统写入到磁盘的时机。
appendonly yes //启用 aof 持久化方式
appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
appendfsync no //完全依赖操作系统,性能最好,持久化没保证
发布及订阅消息
发布订阅( pub/sub )是一种消息通信模式,主要的目的是解除消息发布者和消息订阅者之间的耦合,Redis 作为一个 pub/sub 的 server,在订阅者和发布者之间起到了消息路由的功能。订阅者可以通过 subscribe 和 psubscribe 命令向 redis server 订阅自己感兴趣的消息类型,redis 将消息类型称为通道( channel )。当发布者通过 publish 命令向 redis server 发送特定类型的信息时,订阅该信息类型的全部 client 都会收到此消息。
虚拟内存的使用
Redis 的虚拟内存与操作系统的虚拟内存不是一回事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据。尤其是对于 redis 这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个 redis server 外。另外能够提高数据库容量的办法就是使用虚拟内存把那些不经常访问的数据交换到磁盘上。
下面是虚拟内存相关配置
vm-enabled yes #开启 vm 功能
vm-swap-file /tmp/redis.swap #交换出来的 value 保存的文件路径
vm-max-memory 1000000 #redis 使用的最大内存上限
vm-page-size 32 #每个页面的大小32字节
vm-pages 134217728 #最多使用多少页面
vm-max-threads 4 #用于执行 value 对象换入换出的工作线程数量