Redis基础操作
第一章 Redis快速入门
Redis 是一个 Key-Value 存储系统。和 Memcached 类似,它支持存储的 value 类型相对更多, 包括 string(字符串)、list(链表)、set(集合)和 zset(有序集合)。这些数据类型都支持 push/pop、add/remove 及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,Redis 支持各种不同方式的排序。与 memcached 一样,为了保证效率,数据都是缓存在内存中。区别的是 Redis 会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了 master-slave(主从)同步。
1.1 Key-Value Store 存储系统的特点:
1、Key-value store:一个 key-value 数据存储系统,只支持一些基本操作,如:SET(key, value) 和 GET(key) 等;
2、分布式:多台机器(nodes)同时存储数据和状态,彼此交换消息来保持数据一致,可 视为一个完整的存储系统;
3、数据一致:所有机器上的数据都是同步更新的、不用担心得到不一致的结果;
4、冗余:所有机器(nodes)保存相同的数据,整个系统的存储能力取决于单台机器(node) 的能力;
5、容错:如果有少数 nodes 出错,比如重启、当机、断网、网络丢包等各种 fault/fail 都 不影响整个系统的运行;
6、高可靠性:容错、冗余等保证了数据库系统的可靠性。
1.2 Redis 的部署场景很多,大概分为如下的 2 种:
第一种是应用程序直接访问 Redis 数据库
第二种是应用程序直接访问 Redis,只有当 Redis 访问失败时才访问 MySQL
1.3 初识 Redis
Redis 是一个开源的使用 ANSI C 语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value 数据库,并提供多种语言的 API。从 2010 年 3 月 15 日起,Redis 的开发工作由 VMware主持。
1.3.1 数据类型
作为 Key-value 型数据库,Redis 也提供了键(Key)和键值(Value)的映射关系。但是,除了常规的数值或字符串,Redis 的键值还可以是以下形式之一:
1、Lists (列表)
2、Sets (集合)
3、Sorted sets (有序集合)
4、Hashes (哈希表)
键值的数据类型决定了该键值支持的操作。Redis 支持诸如列表、集合或有序集合的交集、并集、查集等高级原子操作;同时,如果键值的类型是普通数字,Redis 则提供自增等原子操作。
1.3.2 持久化
通常,Redis 将数据存储于内存中,或被配置为使用虚拟内存。通过两种方式可以实现数据 持久化:使用截图的方式,将内存中的数据不断写入磁盘;或使用类似 MySQL 的日志方式, 记录每次更新的日志。前者性能较高,但是可能会引起一定程度的数据丢失;后者相反。
1.3.3主从同步
Redis 支持将数据同步到多台从库上,这种特性对提高读取性能非常有益。
1.3.4 性能
相比需要依赖磁盘记录每个更新的数据库,基于内存的特性无疑给 Redis 带来了非常优秀的性能。读写操作之间有显著的性能差异
1.3.5 适用场合
毫无疑问,Redis 开创了一种新的数据存储思路,使用 Redis,我们不用在面对功能单调的数 据库时,把精力放在如何把大象放进冰箱这样的问题上,而是利用 Redis 灵活多变的数据结 构和数据操作,为不同的大象构建不同的冰箱。希望你喜欢这个比喻
下面是 Redis 适用的一些场景:
1、取最新 N 个数据的操作
比如典型的取你网站的最新文章,通过下面方式,我们可以将最新的 5000 条评论的 ID 放在Redis 的 List 集合中,并将超出集合部分从数据库获取。使用 LPUSH latest.comments<ID>命令,向 list 集合中插入数据插入完成后再用 LTRIM latest.comments 0 5000 命令使其永远只保存最近 5000 个 ID后我们在客户端获取某一页评论时可以用下面的逻辑 FUNCTION get_latest_comments(start,num_items):
id_list = redis.lrange("latest.comments",start,start+num_items-1) IF id_list.length < num_items
id_list = SQL_DB("SELECT ... ORDER BY time LIMIT ...")
END
RETURN id_list
END
如果你还有不同的筛选维度,比如某个分类的最新 N 条,那么你可以再建一个按此分类的List,只存 ID 的话,Redis 是非常高效的。
2、排行榜应用,取 TOP N 操作
这个需求与上面需求的不同之处在于,前面操作以时间为权重,这个是以某个条件为权重,比如按顶的次数排序,这时候就需要我们的 sorted set 出马了,将你要排序的值设置成 sortedset 的 score,将具体的数据设置成相应的 value,每次只需要执行一条 ZADD 命令即可。
3、需要精准设定过期时间的应用
比如你可以把上面说到的 sorted set 的 score 值设置成过期时间的时间戳,那么就可以简单 地通过过期时间排序,定时清除过期数据了,不仅是清除 Redis 中的过期数据,你完全可以 把 Redis 里这个过期时间当成是对数据库中数据的索引,用 Redis 来找出哪些数据需要过期删除,然后再精准地从数据库中删除相应的记录。
4、计数器应用
Redis 的命令都是原子性的,你可以轻松地利用 INCR,DECR 命令来构建计数器系统。
5、Uniq 操作,获取某段时间所有数据排重值
这个使用 Redis 的 set 数据结构最合适了,只需要不断地将数据往 set 中扔就行了,set 意为集合,所以会自动排重。
6、实时系统,反垃圾系统
通过上面说到的 set 功能,你可以知道一个终端用户是否进行了某个操作,可以找到其操作的集合并进行分析统计对比等。没有做不到,只有想不到。
7、Pub/Sub 构建实时消息系统
Redis 的 Pub/Sub 系统可以构建实时的消息系统,比如很多用 Pub/Sub 构建的实时聊天系统的例子。
8、构建队列系统
使用 list 可以构建队列系统,使用 sorted set 甚至可以构建有优先级的队列系统。
9、缓存
这个不必说了,性能优于 Memcached,数据结构更多样化。
1.4快速入门
1.4.1 安装 Redis
Redis 的官方下载站是 http://redis.io/download 可以去上面下载最新的安装程序下来
1、下载 Redis 并安装
wget http://download.redis.io/releases/redis-3.0.1.tar.gz 下载 tar -xzvf redis-3.0.1.tar.gz 解压 cd redis-3.0.1 进入解压目录 make 编译 安装 make install
2、在bin下可执行的程序
redis-server:Redis服务器 redis-cli:命令行客户端 redis-benchmark:Redis的性能测试工具 redis-check-aof:AOF文件修复工具 redis-check-dump:RDB文件检测工具
3、启动和停止Redis
直接启动 redis-server redis-server /ect/redis.conf 停止Redis shutdown 结束Redis的进程也可以
4、将 Redis 作为 Linux 服务随机启动
vi /etc/rc.local, 使用 vi 编辑器打开随机启动配置文件,并在其中加入下面一行代码
/root/4setup/redis-2.2.12/src/redis-server
4、客户端连接验证
新打开一个 Session 输入:src/redis-cli,如果出现下面提示,那么您就可以开始 Redis 之旅了
[root@localhost redis-2.2.12]# src/redis-cli redis 127.0.0.1:6379> 以上的几个步骤就 OK 了!!这样一个简单的 Redis 数据库就可以畅通无阻地运行起来了
1.4.2 配置 Redis
如果是一个专业的 DBA,那么实例启动时会加很多的参数以便使系统运行的非常稳定,这样就可能会在启动时在 Redis 后面加一个参数,以指定配置文件的路径,就象 mysql 一样的读取启动配置文件的方式来启动数据库。源码编译完成后,在 redis-2.2.12 目录下有一个redis.conf 文件,这个文件即是 Redis 的配置文件,用配置文件来启动 Redis 的方法如下:
Redis 支持很多的参数,但都有默认值
daemonize: 默认情况下,redis 不是在后台运行的,如果需要在后台运行,把该项的值更改为 yes pidfile : 当 Redis 在后台运行的时候,Redis 默认会把 pid 文件放在/var/run/redis.pid,你可以配 置到其他地址。当运行多个 redis 服务时,需要指定不同的 pid 文件和端口 bind : 指定 Redis 只接收来自于该 IP 地址的请求,如果不进行设置,那么将处理所有请求,在 生产环境中最好设置该项 port 监听端口,默认为 6379 timeout 设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令, 那么关闭该连接 loglevel log 等级分为 4 级,debug, verbose, notice, 和 warning。生产环境下一般开启 notice # debug:很详细的信息,适合开发和测试 # verbose :包含很多不太有用的信息 # notice :比较适合生产环境 # warning :警告信息 logfile 配置 log 文件地址,默认使用标准输出,即打印在命令行终端的窗口上 databases 设置数据库的个数,可以使用 SELECT <dbid>命令来切换数据库。默认使用的数据库是 0 save 设置 Redis 进行数据库镜像的频率。 if(在 60 秒之内有 10000 个 keys 发生变化时){ 进行镜像备份 }else if(在 300 秒之内有 10 个 keys 发生了变化){ 进行镜像备份 }else if(在 900 秒之内有 1 个 keys 发生了变化){ 进行镜像备份 }
rdbcompression 在进行镜像备份时,是否进行压缩
dbfilename 镜像备份文件的文件名
dir 数据库镜像备份的文件放置的路径。这里的路径跟文件名要分开配置是因为 Redis 在进行备份时,先会将当前数据库的状态写入到一个临时文件中,等备份完成时,再把该该临时文件替换为上面所指定的文件,而这里的临时文件和上面所配置的备份文件都会放在这个指定的路径当中
slaveof 设置该数据库为其他数据库的从数据库
masterauth 当主数据库连接需要密码验证时,在这里指定
requirepass 设置客户端连接后进行任何其他指定前需要使用的密码。警告:因为 redis 速度相当快, 所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进行 150K 次的密码尝试, 这意味着你需要指定非常非常强大的密码来防止暴力破解。
maxclients 限制同时连接的客户数量。当连接数超过这个值时,redis 将不再接收其他连接请求, 客户端尝试连接时将收到 error 信息。
maxmemory 设置 redis 能够使用的最大内存。当内存满了的时候,如果还接收到 set 命令,redis 将 先尝试剔除设置过 expire 信息的 key,而不管该 key 的过期时间还没有到达。在删除时, 将按照过期时间进行删除,最早将要被过期的 key 将最先被删除。如果带有 expire 信息 的 key 都删光了,那么将返回错误。这样,redis 将不再接收写请求,只接收 get 请求。 maxmemory 的设置比较适合于把 redis 当作于类似 memcached 的缓存来使用。
appendonly 默认情况下,redis 会在后台异步的把数据库镜像备份到磁盘,但是该备份是非常耗时 的,而且备份也不能很频繁,如果发生诸如拉闸限电、拔插头等状况,那么将造成比较 大范围的数据丢失。所以 redis 提供了另外一种更加高效的数据库备份及灾难恢复方式。 开 启 append only 模式之后 ,redis 会把所 接收到的 每一次写操作 请求都追 加到 appendonly.aof 文件中,当 redis 重新启动时,会从该文件恢复出之前的状态。但是这样 会造成 appendonly.aof 文件过大,所以 redis 还支持了 BGREWRITEAOF 指令,对 appendonly.aof 进行重新整理。所以我认为推荐生产环境下的做法为关闭镜像,开启 appendonly.aof,同时可以选择在访问较少的时间每天对 appendonly.aof 进行重写一次。
appendfsync 设置对 appendonly.aof 文件进行同步的频率。always 表示每次有写操作都进行同步, everysec 表示对写操作进行累积,每秒同步一次。这个需要根据实际业务场景进行配置
vm-enabled 是否开启虚拟内存支持。因为 redis 是一个内存数据库,而且当内存满的时候,无法接 收新的写请求,所以在 redis 2.0 中,提供了虚拟内存的支持。但是需要注意的是,redis 中,所有的 key 都会放在内存中,在内存不够时,只会把 value 值放入交换区。这样保 证 了 虽 然 使 用 虚 拟 内 存, 但 性 能 基 本 不 受 影 响, 同 时 , 你 需 要 注 意 的是 你 要 把 vm-max-memory 设置到足够来放下你的所有的 key
vm-swap-file 设置虚拟内存的交换文件路径
vm-max-memory 这里设置开启虚拟内存之后,redis 将使用的最大物理内存的大小。默认为 0,redis 将 把他所有的能放到交换文件的都放到交换文件中,以尽量少的使用物理内存。在生产环 境下,需要根据实际情况设置该值,最好不要使用默认的 0
vm-page-size 设置虚拟内存的页大小,如果你的 value 值比较大,比如说你要在 value 中放置博客、 新闻之类的所有文章内容,就设大一点,如果要放置的都是很小的内容,那就设小一点。
m-pages 设置交换文件的总的 page 数量,需要注意的是,page table 信息会放在物理内存中,每 8 个 page 就会占据 RAM 中的 1 个 byte。总的虚拟内存大小 = vm-page-size * vm-pages
vm-max-threads 设置 VM IO 同时使用的线程数量。因为在进行内存交换时,对数据有编码和解码的过 程,所以尽管 IO 设备在硬件上本上不能支持很多的并发读写,但是还是如果你所保存 的 vlaue 值比较大,将该值设大一些,还是能够提升性能的
glueoutputbuf 把小的输出缓存放在一起,以便能够在一个 TCP packet 中为客户端发送多个响应,具体 原理和真实效果我不是很清楚。所以根据注释,你不是很确定的时候就设置成 yes
hash-max-zipmap-entries 在 redis 2.0 中引入了 hash 数据结构。当 hash 中包含超过指定元素个数并且最大的元素 没有超过临界时,hash 将以一种特殊的编码方式(大大减少内存使用)来存储,这里 可以设置这两个临界值
activerehashing 开启之后,redis 将在每 100 毫秒时使用 1 毫秒的 CPU 时间来对 redis 的 hash 表进行重 新 hash,可以降低内存的使用。当你的使用场景中,有非常严格的实时性需要,不能 够接受 Redis 时不时的对请求有 2 毫秒的延迟的话,把这项配置为 no。如果没有这么严 格的实时性要求,可以设置为 yes,以便能够尽可能快的释放内存
1.4.3 操作数据库
连接操作相关的命令 •默认直接连接 远程连接-h 192.168.1.20 -p 6379 •ping:测试连接是否存活如果正常会返回pong •echo:打印 •select:切换到指定的数据库,数据库索引号 index 用数字值指定,以 0 作为起始索引值 •quit:关闭连接(connection) •auth:简单密码认证
服务端相关命令
time:返回当前服务器时间 client list: 返回所有连接到服务器的客户端信息和统计数据 参见http://redisdoc.com/server/client_list.html client kill ip:port:关闭地址为 ip:port 的客户端 save:将数据同步保存到磁盘 bgsave:将数据异步保存到磁盘 lastsave:返回上次成功将数据保存到磁盘的Unix时戳 shundown:将数据同步保存到磁盘,然后关闭服务 info:提供服务器的信息和统计 config resetstat:重置info命令中的某些统计数据 config get:获取配置文件信息 config set:动态地调整 Redis 服务器的配置(configuration)而无须重启,可以修改的配置参数可以使用命令 CONFIG GET * 来列出 config rewrite:Redis 服务器时所指定的 redis.conf 文件进行改写 monitor:实时转储收到的请求 slaveof:改变复制策略设置
对KEY操作的命令
•exists(key):确认一个key是否存在
•del(key):删除一个key
•type(key):返回值的类型
•keys(pattern):返回满足给定pattern的所有key
•randomkey:随机返回key空间的一个
•keyrename(oldname, newname):重命名key
•dbsize:返回当前数据库中key的数目
•expire:设定一个key的活动时间(s)
•ttl:获得一个key的活动时间
•move(key, dbindex):移动当前数据库中的key到dbindex数据库
•flushdb:删除当前选择数据库中的所有key
•flushall:删除所有数据库中的所有key
对String操作的命令
•set(key, value):给数据库中名称为key的string赋予值value
•get(key):返回数据库中名称为key的string的value
•getset(key, value):给名称为key的string赋予上一次的value
•mget(key1, key2,…, key N):返回库中多个string的value
•setnx(key, value):添加string,名称为key,值为value
•setex(key, time, value):向库中添加string,设定过期时间time
•mset(key N, value N):批量设置多个string的值
•msetnx(key N, value N):如果所有名称为key i的string都不存在
•incr(key):名称为key的string增1操作
•incrby(key, integer):名称为key的string增加integer
•decr(key):名称为key的string减1操作
•decrby(key, integer):名称为key的string减少integer
•append(key, value):名称为key的string的值附加value
•substr(key, start, end):返回名称为key的string的value的子串
对List操作的命令
rpush(key, value):在名称为key的list尾添加一个值为value的元素
lpush(key, value):在名称为key的list头添加一个值为value的 元素
llen(key):返回名称为key的list的长度
lrange(key, start, end):返回名称为key的list中start至end之间的元素
ltrim(key, start, end):截取名称为key的list
lindex(key, index):返回名称为key的list中index位置的元素
lset(key, index, value):给名称为key的list中index位置的元素赋值
lrem(key, count, value):删除count个key的list中值为value的元素
lpop(key):返回并删除名称为key的list中的首元素
rpop(key):返回并删除名称为key的list中的尾元素
blpop(key1, key2,… key N, timeout):lpop命令的block版本。
brpop(key1, key2,… key N, timeout):rpop的block版本。
rpoplpush(srckey, dstkey):返回并删除名称为srckey的list的尾元素,并将该元素添加到名称为dstkey的list的头部
对Set操作的命令
sadd(key, member):向名称为key的set中添加元素member
srem(key, member) :删除名称为key的set中的元素member
spop(key) :随机返回并删除名称为key的set中一个元素
smove(srckey, dstkey, member) :移到集合元素
scard(key) :返回名称为key的set的基数
sismember(key, member) :member是否是名称为key的set的元素
sinter(key1, key2,…key N) :求交集
sinterstore(dstkey, (keys)) :求交集并将交集保存到dstkey的集合
sunion(key1, (keys)) :求并集
sunionstore(dstkey, (keys)) :求并集并将并集保存到dstkey的集合
sdiff(key1, (keys)) :求差集
sdiffstore(dstkey, (keys)) :求差集并将差集保存到dstkey的集合
smembers(key) :返回名称为key的set的所有元素
srandmember(key) :随机返回名称为key的set的一个元素
对Hash操作的命令
hset(key, field, value):向名称为key的hash中添加元素field
hget(key, field):返回名称为key的hash中field对应的value
hmget(key, (fields)):返回名称为key的hash中field i对应的value
hmset(key, (fields)):向名称为key的hash中添加元素field
hincrby(key, field, integer):将名称为key的hash中field的value增加integer
hexists(key, field):名称为key的hash中是否存在键为field的域
hdel(key, field):删除名称为key的hash中键为field的域
hlen(key):返回名称为key的hash中元素个数
hkeys(key):返回名称为key的hash中所有键
hvals(key):返回名称为key的hash中所有键对应的value
hgetall(key):返回名称为key的hash中所有的键(field)及其对应的value
第二章、Redis 高级实用特性
2.1 安全性
设置客户端连接后进行任何其他指定前需要使用的密码。
警告:因为 redis 速度相当快,所以在一台比较好的服务器下,一个外部的用户可以在一秒钟进行 150K 次的密码尝试,这意味着你需要指定非常非常强大的密码来防止暴力破解。
# requirepass foobared
requirepass beijing
我们设置了连接的口令是 beijing那么们启动一个客户端试一下:
[root@localhost redis-2.2.12]# 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-2.2.12]# src/redis-cli -a beijing redis 127.0.0.1:6379> keys * 1) "name" redis 127.0.0.1:6379>
2.2 主从复制
redis 主从复制配置和使用都非常简单。通过主从复制可以允许多个 slave server 拥有和master server 相同的数据库副本。
2.2.1 redis 主从复制特点:
(1)、master 可以拥有多个 slave
(2)、多个 slave 可以连接同一个 master 外,还可以连接到其他 slave
(3)、主从复制不会阻塞 master,在同步数据时,master 可以继续处理 client 请求
(4)、提高系统的伸缩性
2.2.2 redis 主从复制过程:
当配置好 slave 后,slave 与 master 建立连接,然后发送 sync 命令。无论是第一次连接还是重新连接,master 都会启动一个后台进程,将数据库快照保存到文件中,同时 master 主进程会开始收集新的写命令并缓存。后台进程完成写文件后,master 就发送文件给 slave,slave
将文件保存到硬盘上,再加载到内存中,接着 master 就会把缓存的命令转发给 slave,后master 将收到的写命令发送给 slave。如果 master 同时收到多个 slave 发来的同步连接命令,master 只会启动一个进程来写数据库镜像,然后发送给所有的 slave。
2.2.3 如何配置
配置 slave 服务器很简单,只需要在 slave 的配置文件中加入如下配置
# slaveof <masterip> <masterport>
slaveof localhost 6379
我们在一台机器上启动主库(端口 6379),从库(端口 6378)
启动后主库控制台日志如下: [root@localhost redis-2.2.12]# src/redis-server redis.conf [7064] 09 Aug 20:13:12 * Server started, Redis version 2.2.12 [7064] 09 Aug 20:13:12 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. [7064] 09 Aug 20:13:12 * The server is now ready to accept connections on port 6379 [7064] 09 Aug 20:13:13 - 0 clients connected (0 slaves), 539512 bytes in use [7064] 09 Aug 20:13:18 - 0 clients connected (0 slaves), 539512 bytes in use [7064] 09 Aug 20:13:20 - Accepted 127.0.0.1:37789 [7064] 09 Aug 20:13:20 * Slave ask for synchronization [7064] 09 Aug 20:13:20 * Starting BGSAVE for SYNC [7064] 09 Aug 20:13:20 * Background saving started by pid 7067 [7067] 09 Aug 20:13:20 * DB saved on disk [7064] 09 Aug 20:13:20 * Background saving terminated with success [7064] 09 Aug 20:13:20 * Synchronization with slave succeeded [7064] 09 Aug 20:13:23 - 0 clients connected (1 slaves), 547380 bytes in use
启动后从库控制台日志如下:
[root@localhost redis-2.2.12]# src/redis-server redis.slave [7066] 09 Aug 20:13:20 * Server started, Redis version 2.2.12 [7066] 09 Aug 20:13:20 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect. [7066] 09 Aug 20:13:20 * The server is now ready to accept connections on port 6378 [7066] 09 Aug 20:13:20 - 0 clients connected (0 slaves), 539548 bytes in use [7066] 09 Aug 20:13:20 * Connecting to MASTER... [7066] 09 Aug 20:13:20 * MASTER <-> SLAVE sync started: SYNC sent [7066] 09 Aug 20:13:20 * MASTER <-> SLAVE sync: receiving 10 bytes from master [7066] 09 Aug 20:13:20 * MASTER <-> SLAVE sync: Loading DB in memory [7066] 09 Aug 20:13:20 * MASTER <-> SLAVE sync: Finished with success [7068] 09 Aug 20:13:20 * SYNC append only file rewrite performed [7066] 09 Aug 20:13:20 * Background append only file rewriting started by pid 7068 [7066] 09 Aug 20:13:21 * Background append only file rewriting terminated with success [7066] 09 Aug 20:13:21 * Parent diff flushed into the new append log file with success (0 bytes) [7066] 09 Aug 20:13:21 * Append only file successfully rewritten. [7066] 09 Aug 20:13:21 * The new append only file was selected for future appends. [7066] 09 Aug 20:13:25 - 1 clients connected (0 slaves), 547396 bytes in use
我们在主库上设置一对键值对
redis 127.0.0.1:6379> set name HongWan
OK
redis 127.0.0.1:6379>
在从库上取一下这个键
redis 127.0.0.1:6378> get name "HongWan" redis 127.0.0.1:6378>
说明主从是同步正常的.
那么我们如何判断哪个是主哪个是从呢?我们只需调用 info 这个命令就可以得到主从的信息了,我们在从库上执行 info 命令
redis 127.0.0.1:6378> info . . . role:slave master_host:localhost master_port:6379 master_link_status:up master_last_io_seconds_ago:10 master_sync_in_progress:0 db0:keys=1,expires=0 redis 127.0.0.1:6378>
里 面 有 一 个 角 色 标 识 , 来 判 断 是 主 库 还 是 从 库 , 对 于 本 例 是 一 个 从 库 , 同 时 还 有 一 个 master_link_status 用于标明主从是否异步,如果此值=up,说明同步正常;如果此值=down,说明同步异步;db0:keys=1,expires=0, 用于说明数据库有几个 key,以及过期 key 的数量。
2.3 事务控制
redis 对事务的支持目前还比较简单。redis 只能保证一个 client 发起的事务中的命令可以连续的执行,而中间不会插入其他 client 的命令。 由于 redis 是单线程来处理所有 client 的请求的所以做到这点是很容易的。一般情况下 redis 在接受到一个 client 发来的命令后会立即处理并 返回处理结果,但是当一个 client 在一个连接中发出 multi 命令有,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一个队列中。当从此连接受到 exec 命令后,redis 会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给 client.然后此连接就 结束事务上下文。
2.3.1 简单事务控制
下面可以看一个例子
redis 127.0.0.1:6379> get age "33" redis 127.0.0.1:6379> multi OK redis 127.0.0.1:6379> set age 10 QUEUED redis 127.0.0.1:6379> set age 20 QUEUED redis 127.0.0.1:6379> exec 1)OK 2)OK redis 127.0.0.1:6379> get age "20" redis 127.0.0.1:6379>
从这个例子我们可以看到 2 个 set age 命令发出后并没执行而是被放到了队列中。调用 exec后 2 个命令才被连续的执行,最后返回的是两条命令执行后的结果。
2.3.2 如何取消一个事务
我们可以调用 discard 命令来取消一个事务,让事务回滚。接着上面例子 redis 127.0.0.1:6379> get age "20" redis 127.0.0.1:6379> multi OK redis 127.0.0.1:6379> set age 30 QUEUED redis 127.0.0.1:6379> set age 40 QUEUED redis 127.0.0.1:6379> discard OK redis 127.0.0.1:6379> get age "20" redis 127.0.0.1:6379>
可以发现这次 2 个 set age 命令都没被执行。discard 命令其实就是清空事务的命令队列并退出事务上下文,也就是我们常说的事务回滚。
2.3.3 乐观锁复杂事务控制
乐观锁:大多数是基于数据版本(version)的记录机制实现的。何谓数据版本?即为数据增 加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表添加一个 “version”字段来实现读取出数据时,将此版本号一同读出,之后更新时,对此版本号加 1、此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版 本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
乐观锁实例:假设数据库中帐户信息表中有一个 version 字段,当前值为 1;而当前帐户余额字段(balance)为$100。下面我们将用时序表的方式来为大家演示乐观锁的实现原理:
这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果来覆盖操作员 A 的操作结果的可能。
即然乐观锁比悲观锁要好很多,redis 是否也支持呢?答案是支持, redis 从 2.1.0 开始就支持
乐观锁了,可以显式的使用 watch 对某个 key 进行加锁,避免悲观锁带来的一系列问题。
Redis 乐观锁实例:假设有一个 age 的 key,我们开 2 个 session 来对 age 进行赋值操作,我们来看一下结果如何。
第一步,Session 1 还没有来得及对 age 的值进行修改
第二步,Session 2 已经将 age 的值设为 30
第三步,Session 1 希望将 age 的值设为 20,但结果一执行返回是 nil,说明执行失败,之后
我们再取一下 age 的值是 30,这是由于 Session 1 中对 age 加了乐观锁导致的。watch 命令会监视给定的 key,当 exec 时候如果监视的 key 从调用 watch 后发生过变化,则整个事务会失败。也可以调用 watch 多次监视多个 key.这 样就可以对指定的 key 加乐观锁了。注意 watch 的 key 是对整个连接有效的,事务也一样。如果连接断开,监视和事务都会被自动清除。当然了 exec,discard,unwatch 命令都会清除连接中的所有监视。
redis 的事务实现是如此简单,当然会存在一些问题。第一个问题是 redis 只能保证事务的每个命令连续执行,但是如果事务中的一个命令失败了,并不回滚其他命令,比如使用的命令类型不匹配。下面将以一个实例的例子来说明这个问题:
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>
从这个例子中可以看到,age 由于是个数字,那么它可以有自增运算,但是 name 是个字符 串,无法对其进行自增运算,所以会报错,如果按传统关系型数据库的思路来讲,整个事务 都会回滚,但是我们看到 redis 却是将可以执行的命令提交了,所以这个现象对于习惯于关 系型数据库操作的朋友来说是很别扭的,这一点也是 redis 今天需要改进的地方。
2.4 持久化机制
redis 是一个支持持久化的内存数据库,也就是说 redis 需要经常将内存中的数据同步到磁盘来保证持久化。redis 支持两种持久化方式,一种是 Snapshotting(快照)也是默认方式,另一种是 Append-only file(缩写 aof)的方式。下面分别介绍:
2.4.1 snapshotting 方式
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为 dump.rdb。可以通过配置设置自动做快照持久化的方式。我们可以配置 redis在 n 秒内如果超过 m 个 key 被修改就自动做快照,下面是默认的快照保存配置
save 900 1 #900 秒内如果超过 1 个 key 被修改,则发起快照保存
save 300 10 #300 秒内容如超过 10 个 key 被修改,则发起快照保存 save 60 10000
下面介绍详细的快照保存过程:
1.redis 调用 fork,现在有了子进程和父进程。
2. 父进程继续处理 client 请求,子进程负责将内存内容写入到临时文件。由于 os 的实时复制机制(copy on write)父子进程会共享相同的物理页面,当父进程处理写请求时 os 会为父进程要修改的页面创建副本,而不是写共享的页面。所以子进程地址空间内的数据是 fork
时刻整个数据库的一个快照。
3.当子进程将快照写入临时文件完毕后,用临时文件替换原来的快照文件,然后子进程退出。client 也可以使用 save 或者 bgsave 命令通知 redis 做一次快照持久化。save 操作是在主线程中保存快照的,由于 redis 是用一个主线程来处理所有 client 的请求,这种方式会阻塞所有client 请求。所以不推荐使用。另一点需要注意的是,每次快照持久化都是将内存数据完整写入到磁盘一次,并不是增量的只同步变更数据。如果数据量大的话,而且写操作比较多,必然会引起大量的磁盘 io 操作,可能会严重影响性能。
2.4.2 aof 方式
另外由于快照方式是在一定间隔时间做一次的,所以如果 redis 意外 down 掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用 aof 持久化方式。下面介绍 Append-only file:
aof 比快照方式有更好的持久化性,是由于在使用 aof 持久化方式时,redis 会将每一个收到的写命令都通过 write 函数追加到文件中(默认是 appendonly.aof)。当 redis 重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。当然由于 os 会在内核中缓存 write 做的修改,所以可能不是立即写到磁盘上。这样 aof 方式的持久化也还是有可能会丢失部分修改。不过我们可以通过配置文件告诉 redis 我们想要通过 fsync 函数强制 os 写入 到磁盘的时机。有三种方式如下(默认是:每秒 fsync 一次)
appendonly yes //启用 aof 持久化方式 # appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化 appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中 # appendfsync no //完全依赖 os,性能最好,持久化没保证
aof 的方式也同时带来了另一个问题。持久化文件会变的越来越大。例如我们调用 incr test命令 100 次,文件中必须保存全部的 100 条命令,其实有 99 条都是多余的。因为要恢复数据库的状态其实文件中保存一条 set test 100 就够了。为了压缩 aof 的持久化文件。redis 提供了 bgrewriteaof 命令。收到此命令 redis 将使用与快照类似的方式将内存中的数据以命令 的方式保存到临时文件中,最后替换原来的文件。具体过程如下
1、redis 调用 fork ,现在有父子两个进程
2、子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令
3、父进程继续处理 client 请求,除了把写命令写入到原来的 aof 文件中。同时把收到的写命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
4、当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
5、现在父进程可以使用临时文件替换老的 aof 文件,并重命名,后面收到的写命令也开始往新的 aof 文件中追加。
需要注意到是重写 aof 文件的操作,并没有读取旧的 aof 文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的 aof 文件,这点和快照有点类似。接来我们看一下实际的例子:
我们先调用 5 次 incr age 命令:
redis 127.0.0.1:6379> incr age (integer) 21 redis 127.0.0.1:6379> incr age (integer) 22 redis 127.0.0.1:6379> incr age (integer) 23 redis 127.0.0.1:6379> incr age (integer) 24 redis 127.0.0.1:6379> incr age (integer) 25 redis 127.0.0.1:6379>
接下来我们看一下日志文件的大小
[root@localhost redis-2.2.12]# ll 总计 188 -rw-rw-r-- 1 root root 9602 2011-07-22 00-RELEASENOTES -rw-r--r-- 1 root root 259 08-09 19:43 appendonly.aof -rw-rw-r-- 1 root root 55 2011-07-22 BUGS -rw-rw-r-- 1 root root 84050 2011-07-22 Changelog
大小为 259 个字节,接下来我们调用一下 bgrewriteaof 命令将内存中的数据重新刷到磁盘的日志文件中
redis 127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started
redis 127.0.0.1:6379>
再看一下磁盘上的日志文件大小
[root@localhost redis-2.2.12]# ll 总计 188 -rw-rw-r-- 1 root root 9602 2011-07-22 00-RELEASENOTES -rw-r--r-- 1 root root 127 08-09 19:45 appendonly.aof -rw-rw-r-- 1 root root 55 2011-07-22 BUGS rw-rw-r-- 1 root root 84050 2011-07-22 Changelog
日志文件大小变为 127 个字节了,说明原来日志中的重复记录已被刷新掉了。
2.5 虚拟内存的使用
首先说明下 redis 的虚拟内存与操作系统的虚拟内存不是一码事,但是思路和目的都是相同的。就是暂时把不经常访问的数据从内存交换到磁盘中,从而腾出宝贵的内存空间用于其他需要访问的数据。尤其是对于 redis 这样的内存数据库,内存总是不够用的。除了可以将数据分割到多个 redis server 外。另外的能够提高数据库容量的办法就是使用虚拟内存把那些不经常访问的数据交换的磁盘上。如果我们的存储的数据总是有少部分数据被经常访问,大部分数据很少被访问,对于网站来说确实总是只有少量用户经常活跃。当少量数据被经常访问时,使用虚拟内存不但能提高单台 redis server 数据库的容量,而且也不会对性能造成太多影响。
redis 没有使用操作系统提供的虚拟内存机制而是自己在实现了自己的虚拟内存机制,主要的理由有两点:
1、操作系统的虚拟内存是已 4k 页面为最小单位进行交换的。而 redis 的大多数对象都远小于 4k,所以一个操作系统页面上可能有多个 redis 对象。另外 redis 的集合对象类型如 list,set可能存在与多个操作系统页面上。最终可能造成只有 10%key 被经常访问,但是所有操作系统页面都会被操作系统认为是活跃的,这样只有内存真正耗尽时操作系统才会交换页面。
2、相比于操作系统的交换方式,redis 可以将被交换到磁盘的对象进行压缩,保存到磁盘的对象可以去除指针和对象元数据信息,一般压缩后的对象会比内存中的对象小 10 倍,这样 redis的虚拟内存会比操作系统虚拟内存能少做很多 io 操作。
下面是 vm 相关配置
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 对象换入换出的工作线程数量
edis 的虚拟内存在设计上为了保证 key 的查找速度,只会将 value 交换到 swap 文件中。所以如果是内存问题是由于太多 value 很小的 key 造成的,那么虚拟内存并不能解决,和操作系统一样 redis 也是按页面来交换对象的。redis 规定同一个页面只能保存一个对象。但是一个对象可以保存在多个页面中。在 redis 使用的内存没超过 vm-max-memory 之前是不会交换任何 value 的。当超过最大内存限制后,redis 会选择较过期的对象。如果两个对象一样过期会优先交换比较大的对象,精确的公式 swappability = age*log(size_in_memory)。对于vm-page-size 的设置应该根据自己的应用将页面的大小设置为可以容纳大多数对象的大小,太大了会浪费磁盘空间,太小了会造成交换文件出现碎片。对于交换文件中的每个页面,redis会在内存中对应一个 1bit 值来记录页面的空闲状态。所以像上面配置中页面数量(vm-pages134217728 )会占用 16M 内存用来记录页面空闲状态。vm-max-threads 表示用做交换任务的线程数量。如果大于 0 推荐设为服务器的 cpu 内核的数量,如果是 0 则交换过程在主线程进
行。