redis(REmote DIctionary Server)是一个由Salvatore Sanfilippo写key-value存储系统,它由C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value类型的数据库,并提供多种语言的API。和Memcached类似,它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、zset(sorted set --有序集合)和hash(哈希类型)。这些数据类型都支持push/pop、add/remove及取交集并集和差集及更丰富的操作,而且这些操作都是原子性的。在此基础上,redis支持各种不同方式的排序。与memcached一样,为了保证效率,数据都是缓存在内存中。区别的是redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步,redis在3.0版本推出集群模式。
[huangwei@localhost ~]$ wget http://download.redis.io/releases/redis-6.0.5.tar.gz
[root@localhost huangwei]# tar xzf redis-6.0.5.tar.gz -C /usr/local/
[root@localhost huangwei]# cd /usr/local/redis-6.0.5/
[root@localhost redis-6.0.5]# make
[root@localhost redis-6.0.5]# gcc –v # 查看gcc版本
[root@localhost redis-6.0.5]# yum -y install centos-release-scl # 升级gcc
[root@localhost redis-6.0.5]# yum -y install devtoolset-9-gcc devtoolset-9-gcc-c++ devtoolset-9-binutils
[root@localhost redis-6.0.5]# scl enable devtoolset-9 bash
# 设置永久升级
[root@localhost redis-6.0.5]# echo "source /opt/rh/devtoolset-9/enable" >>/etc/profile
redis所有的配置参数都可以通过客户端通过“CONFIG GET 参数名” 获取,参数名支持通配符,如*代表所有。所得结果并按照顺序分组,第一个返回结果是参数名,第二个结果是参数对应的值。
logfile
#日志文件位置及文件名称
bind 0.0.0.0
#监听地址,可以有多个 如bind 0.0.0.0 127.0.0.1
daemonize yes
#yes启动守护进程运行,即后台运行,no表示不启用
pidfile /var/run/redis.pid
# 当redis在后台运行的时候,Redis默认会把pid文件在在/var/run/redis.pid,也可以配置到其他地方。
# 当运行多个redis服务时,需要指定不同的pid文件和端口
port 6379
# 指定redis运行的端口,默认是6379
unixsocket
#sock文件位置
unixsocketperm
#sock文件权限
timeout 0
# 设置客户端连接时的超时时间,单位为秒。当客户端在这段时间内没有发出任何指令,那么关闭该连接, 0是关闭此设置
loglevel debug
# 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
logfile ""
# 日志文件配置,默认值为stdout,标准输出,若后台模式会输出到/dev/null
syslog-enabled
# 是否以syslog方式记录日志,yes开启no禁用,与该配置相关配置syslog-ident 和syslog-facility local0 分别是指明syslog的ident和facility
databases 16
#配置可用的数据库个数,默认值为16,默认数据库为0,数据库范围在0-(database-1)之间
always-show-logo yes #4.0以后新增配置
#是否配置日志显示redis徽标,yes显示no不显示
################################ 快照相关配置 #################################
save 900 1
save 300 10
save 60 10000
#配置快照(rdb)促发规则,格式:save
#save 900 1 900秒内至少有1个key被改变则做一次快照
#save 300 10 300秒内至少有300个key被改变则做一次快照
#save 60 10000 60秒内至少有10000个key被改变则做一次快照
dbfilename dump.rdb
#rdb持久化存储数据库文件名,默认为dump.rdb
stop-write-on-bgsave-error yes
#yes代表当使用bgsave命令持久化出错时候停止写RDB快照文件,no则代表继续写
rdbchecksum yes
#开启rdb文件校验
dir "/etc"
#数据文件存放目录,rdb快照文件和aof文件都会存放至该目录
################################# 复制相关配置参数 #################################
slaveof
#设置该数据库为其他数据库的从数据库,设置当本机为slave服务时,设置master服务的IP地址及端口,在Redis启动时,它会自动从master进行数据同步
masterauth
#主从复制中,设置连接master服务器的密码(前提master启用了认证)
slave-serve-stale-data yes
# 当从库同主机失去连接或者复制正在进行,从机库有两种运行方式:
# 1) 如果slave-serve-stale-data设置为yes(默认设置),从库会继续相应客户端的请求
# 2) 如果slave-serve-stale-data是指为no,除了INFO和SLAVOF命令之外的任何请求都会返回一个错误"SYNC with master in progress"
repl-ping-slave-period 10
#从库会按照一个时间间隔向主库发送PING命令来判断主服务器是否在线,默认是10秒
repl-timeout 60
#设置主库批量数据传输时间或者ping回复时间间隔超时时间,默认值是60秒
# 一定要确保repl-timeout大于repl-ping-slave-period
repl-backlog-size 1mb
#设置复制积压大小,只有当至少有一个从库连入才会释放。
slave-priority 100
#当主库发生宕机时候,哨兵会选择优先级最高的一个称为主库,从库优先级配置默认100,数值越小优先级越高
min-slaves-to-write 3
min-slaves-max-lag 10
#设置某个时间断内,如果从库数量小于该某个值则不允许主机进行写操作,以上参数表示10秒内如果主库的从节点小于3个,则主库不接受写请求,min-slaves-to-write 0代表关闭此功能。
################################## 安全相关配置 ###################################
requirepass
#客户端连接认证的密码,默认为空,即不需要密码,若配置则命令行使用AUTH进行认证
maxclients 10000
# 设置同一时间最大客户端连接数,4.0默认10000,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置 maxclients 0,表示不作限制。
# 当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max number of clients reached错误信息
maxmemory 4gb
#设置最大使用的内存大小
maxmemory-policy noeviction
#设置达到最大内存采取的策略:
# volatile-lru -> 利用LRU算法移除设置过过期时间的key (LRU:最近使用 Least Recently Used )
# allkeys-lru -> 利用LRU算法移除任何key
# volatile-random -> 移除设置过过期时间的随机key
# allkeys->random -> remove a random key, any key
# volatile-ttl -> 移除即将过期的key(minor TTL)
# 4.0默认noeviction代表不删除任何key,只在写操作时候返回错误。
maxmemory-samples 5
#LRU,LFU等算法样本设置,默认5个key
############################## AOF相关配置###############################
appendonly no
# 设置AOF持久化,yes开启,no禁用,开启后redis会把所接收到的每一次写操作请求都追加到appendonly.aof文件中,当redis重新启动时,会从该文件恢复出之前的状态。
# 但是这样会造成appendonly.aof文件过大,所以redis还支持了BGREWRITEAOF指令,对appendonly.aof 进行重写。
appendfilename "appendonly.aof"
#设置AOF文件名
appendfsync everysec
# AOF文件写策略,Redis支持三种同步AOF文件的策略:
# no: 不进行同步,交给操作系统去执行 ,速度较快
# always: always表示每次有写操作都调用fsync方法强制内核将该写操作写入到文件,速度会慢, 但是安全,因为每次写操作都在AOF文件中.
# everysec: 表示对写操作进行累积,每秒同步一次,折中方案.
# 默认是"everysec",按照速度和安全折中这是最好的。
no-appendfsync-on-rewrite no
# AOF策略设置为always或者everysec时,后台处理进程(后台保存或者AOF日志重写)会执行大量的I/O操作
# 在某些Linux配置中会阻止过长的fsync()请求。注意现在没有任何修复,即使fsync在另外一个线程进行处理,为了减缓这个问题,可以设置下面这个参数no-appendfsync-on-rewrite
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
#当AOF文件增长到一定大小的时候Redis能够调用BGREWRITEAOF对日志文件进行重写,它是这样工作的:Redis会记住上次进行些日志后文件的大小(如果从开机以来还没进行过重写,那日子大小在开机的时候确定)。
#基础大小会同现在的大小进行比较。如果现在的大小比基础大小大制定的百分比,重写功能将启动
# 同时需要指定一个最小大小用于AOF重写,这个用于阻止即使文件很小但是增长幅度很大也去重写AOF文件的情况
# 设置 percentage 为0就关闭这个特性
#auto-aof-rewrite-percentage 代表AOF文件每次重写文件大小(以百分数代表),100表示百分之百,即当文件增加了1倍(100%),则开始重写AOF文件
#auto-aof-rewrite-min-size 设置最小重写文件大小,避免文件小而执行太多次的重写
aof-load-truncated yes
#当redis突然运行崩溃时,会出现aof文件被截断的情况,Redis可以在发生这种情况时退出并加载错误,以下选项控制此行为。
#如果aof-load-truncated设置为yes,则加载截断的AOF文件,Redis服务器启动发出日志以通知用户该事件。
#否则,如果该选项设置为no,则服务器将中止并显示错误并停止启动。当该选项设置为no时,用户需要在重启之前使用“redis-check-aof”实用程序修复AOF文件在进行重启
################################## 慢查询配置 ###################################
slowlog-log-slower-than 10000
#Redis Slow Log 记录超过特定执行时间的命令。执行时间不包括I/O计算比如连接客户端,返回结果等,只是命令执行时间,可以通过两个参数设置slow log:一个是告诉Redis执行超过多少时间被记录的参数slowlog-log-slower-than(微秒,因此1000000代表一分钟
#另一个是slow log 的长度。当一个新命令被记录的时候最早的命令将被从队列中移除
slowlog-max-len 128
#慢查询命令记录队列长度设置,该队列占用内存,可以使用SLOWLOG RESET清空队列
############################### 高级配置 ###############################
hash-max-zipmap-entries 512
hash-max-zipmap-value 64
# 当hash中包含超过指定元素个数并且最大的元素没有超过临界时,hash将以一种特殊的编码方式(大大减少内存使用)来存储,这里可以设置这两个临界值
# Redis Hash对应Value内部实际就是一个HashMap,实际这里会有2种不同实现,
# 这个Hash的成员比较少时Redis为了节省内存会采用类似一维数组的方式来紧凑存储,而不会采用真正的HashMap结构,对应的value redisObject的encoding为zipmap,当成员数量增大时会自动转成真正的HashMap,此时encoding为ht。
list-max-ziplist-size -2
#Lists也以特殊方式编码,以节省大量空间。
#可以指定每个内部列表节点允许的条目数
#作为固定的最大大小或最大元素数。
#对于固定的最大大小,使用-5到-1表示:
#-5:最大大小:64 Kb < - 不建议用于正常工作负载
#-4:最大尺寸:32 Kb < - 不推荐
#-3:最大尺寸:16 Kb < - 可能不推荐
#-2:最大尺寸:8 Kb < - 好
#-1:最大尺寸:4 Kb < - 良好
#正数意味着存储_exactly_元素数量
#每个列表节点。
#性能最高的选项通常为-2(8 Kb大小)或-1(4 Kb大小)
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
# list数据类型多少节点以下会采用去指针的紧凑存储格式。
# list数据类型节点值大小小于多少字节会采用紧凑存储格式。
activerehashing yes
# Redis将在每100毫秒时使用1毫秒的CPU时间来对redis的hash表进行重新hash,可以降低内存的使用
# 当你的使用场景中,有非常严格的实时性需要,不能够接受Redis时不时的对请求有2毫秒的延迟的话,把这项配置为no。
# 如果没有这么严格的实时性要求,可以设置为yes,以便能够尽可能快的释放内存
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
#客户端输出缓冲区限制可用于强制断开客户端,由于某种原因,没有足够快地从服务器读取数据,常见的原因是Pub / Sub客户端不能像很快的消费一条消息,可以为三种不同类型的客户端设置不同的限制:
#normal - >普通客户端,包括MONITOR客户端
#subve - >从服务器客户端
#pubsub - >客户端订阅了至少一个pubsub通道或模式
#设置方法:client-output-buffer-limit 软限制大小 硬限制大小 秒数
#当客户端达到硬限制大小则立即断开连接,当客户端达到软限制时候并且在设置的秒数缓冲大小任然超了,则在设置的秒数后断开连接
在Redis安装目录src目录下有redis服务程序redis-server,还有用于客户端测试程序redis-cli。
使用默认配置启动redis
$ cd src
$ ./redis-server
通过指定配置文件启动redis
$ ./redis-server ../redis.conf
$ cd /usr/local/redis-6.0.5/src
$ ./redis-cli
关闭命令
$ ./redis-cli shutdown
Redis没有实现访问控制的功能,但是它提供了一个轻量级的认证方式,可以编辑redis.conf配置来启用认证
在配置文件中有个参数:requirepass 这个就是配置redis访问密码的参数
Redis支持5中数据类型:String(字符串),Hash(哈希),List(列表),set(集合)及zset(sorted set:有序集合)
String是Redis最基本的数据类型,是二进制安全的,,意思是可以包含任何数据,比如jpg图片或者系列化对象,最大能存储512M
127.0.0.1:6379> set name huangwei
OK
127.0.0.1:6379> get name
Redis hash是一个键值对集合,是一个String类型的field和value的映射表,hash特别适合存储对象。
127.0.0.1:6379> hmset student field1 "Hello" field2 "World"
OK
127.0.0.1:6379> hget student field1
"Hello"
127.0.0.1:6379> hget student field2
"World"
Redis List列表是简单的字符串列表,按照插入顺序排序。可以添加一个元素到列表的头部或者尾部
127.0.0.1:6379> LPUSH fruits apple
(integer) 1
127.0.0.1:6379> LPUSH fruits mongo
(integer) 2
127.0.0.1:6379> LPUSH fruits banana
(integer) 3
127.0.0.1:6379> LRANGE fruits 0 10
1) "banana"
2) "mongo"
3) "apple"
Redis的Set是String类型的无序集合
集合是通过哈希表实现的,所有添加,删除,查找的复杂度抖索O(1)
127.0.0.1:6379> sadd color red
(integer) 1
127.0.0.1:6379> sadd color yellow
(integer) 1
127.0.0.1:6379> sadd color blank
(integer) 1
127.0.0.1:6379> sadd color green
(integer) 1
127.0.0.1:6379> sadd color red
(integer) 0
127.0.0.1:6379> smembers color
1) "blank"
2) "red"
3) "green"
4) "yellow"
Redis zset和set一样也是String类型元素的集合,不允许有重复元素
不同的是每个元素都会关联一个double类型的分数,redis正是通过分数来为集合中的成员进行从小到大的排序
Zset的元素是唯一的,但分数可以重复
127.0.0.1:6379> del color
(integer) 1
127.0.0.1:6379> zadd color 0 red
(integer) 1
127.0.0.1:6379> zadd color 0 yellow
(integer) 1
127.0.0.1:6379> zadd color 1 blank
(integer) 1
127.0.0.1:6379> zadd color 2 green
(integer) 1
127.0.0.1:6379> ZRANGEBYSCORE color 0 10
1) "red"
2) "yellow"
3) "blank"
4) "green"
更多的Redis请参照Redis命令中心
命令 | 描述 |
---|---|
DEL key [key …] | 删除指定的一批keys,如果删除中的某些key不存在,则直接忽略。 |
DUMP key | 导出key的值 |
EXISTS key [key …] | 查询一个key是否存在 (integer) 1 —存在 (integer) 0 —不存在 |
EXPIRE key seconds | 设置一个key的过期的秒数 |
EXPIREAT key timestamp | 设置一个UNIX时间戳的过期时间 |
KEYS pattern | 查找所有符合给定模式pattern(正则表达式)的 key |
MIGRATE host port key destination-db timeout [COPY] [REPLACE] | 原子性的将key从redis的一个实例转移到另一个实例 |
MOVE key db | 将当前数据库的 key 移动到给定的数据库 db 当中 |
OBJECT subcommand [arguments [arguments …]] | 查找内部的再分配对象 |
PERSIST key | 移除key的过期时间 |
PEXPIRE key milliseconds | 设置key的有效时间以毫秒为单位 |
PEXPIREAT key milliseconds-timestamp | 设置key的到期UNIX时间戳以毫秒为单位 |
PTTL key | 获取key的有效毫秒数 |
RANDOMKEY | 返回一个随机的key |
RENAME key newkey | 将key重命名为newkey |
RENAMENX key newkey | 当且仅当 newkey 不存在时,将 key 改名为 newkey |
RESTORE key ttl serialized-value [REPLACE] | 反序列化给定的序列化值,并将它和给定的 key 关联 |
SORT key [BY pattern] [LIMIT offset count] [GET pattern] [ASC/DESC] [ALPHA] destination | 对队列、集合、有序集合排序 |
TTL key | 返回key的有效时间(单位:秒) |
TYPE key | 获取key的存储类型 |
SCAN cursor [MATCH pattern] [COUNT count] | 增量迭代的key |
命令 | 描述 |
---|---|
APPEND key value | 追加一个值到key上 |
BITCOUNT key [start end] | 统计字符串指定起始位置的字节数 |
DECR key | 整数原子减1 |
DECRBY key decrement | 原子减指定的整数 |
GET key | 返回key的value |
GETBIT key offset | 返回key对应的string在offset处的bit值 |
GETRANGE key start end | 获取存储在key上的值的一个子字符串 |
GETSET key value | 设置一个key的value,并获取设置前的值 |
INCR key | 执行原子加1操作 |
INCRBY key increment | 执行原子增加一个整数 |
INCRBYFLOAT key increment | 执行原子增加一个浮点数 |
MGET key [key …] | 获取所有指定的key的值 |
MSET key value [key value …] | 设置多个key value |
MSETNX key value [key value …] | 对应给定的keys到他们相应的values上。只要有一个key已经存在,MSETNX一个操作都不会执行 |
SET key value [EX seconds] [PX milliseconds] [NX/XX] } | 设置key的value值 |
SETEX key seconds value | 设置key-value并设置过期时间(单位:秒) |
SETNX key value | 将key设置值为value,仅当key不存在 |
STRLEN key | 获取指定key值的长度 |
命令 | 描述 |
---|---|
BLPOP key [key …] timeout | 删除,并获得该列表的第一个元素,或阻塞,直到有一个可用 |
BRPOP key [key …] timeout | 删除,并获得该列表的最后一个元素,或阻塞,只有有一个可用 |
BRPOPLPUSH source destination timeout | 弹出一个列表的值,将它推到另一个列表,并返回它或阻塞,直到有一个可用 |
LINDEX key index | 获取一个元素,通过其索引列表 |
LINSERT key BEFORE/AFTER pivot value | 在列表的另一个元素之前或之后插入一个元素 |
LLEN key | 获取列表的长度 |
LPOP key | 从列表的左边出一个元素 |
LPUSH key value [value …] | 从列表的左边入队一个或多个元素 |
LPUSHX key value | 当队列存在时,从对列的左边入队一个元素 |
LRANGE key start stop | 从列表中获取指定返回的元素 |
LREM key count value | 从存于 key 的列表里移除前 count 次出现的值为 value 的元素 |
LSET key index value | 设置 index 位置的list元素的值为 value |
LTRIM key start stop | 修剪(trim)一个已存在的 list,这样 list 就会只包含指定范围的指定元素 |
RPOP key | 移除并返回存于 key 的 list 的最后一个元素 |
RPOPLPUSH source destination | 删除列表中的最后一个元素,将其追加到另一个元素 |
RPUSH key value [value …] | 从队列的右边入队一个元素 |
RPUSHX key value | 从队列的右边入队一个元素,仅队列存在时有效 |
命令 | 描述 |
---|---|
HDEL key field [field …] | 删除一个或多个hash的field |
HEXISTS key field | 判断field是否存在hash中 |
HGET key field | 获取hash中的field值 |
HGETALL key | 从hash中读取全部的域和值 |
HLEN key | 获取hash中字段的数量 |
HMGET key field [field …] | 获取hash里面指定的字段的值 |
HINCRBY key field increment | 将hash中指定域的值添加给定的数 |
HINCRBYFLOAT key field increment | 将hash中指定的域的值添加给定的浮点数 |
HKEYS key | 获取hash的所有字段 |
HMSET key field value [field value …] | 设置hash的字段值 |
HSET key field value | 设置hash中的一个字段值 |
HSETNX key field value | 设置hash中的一个字段值,只有这个字段不存在时 |
HSTRLEN key field | 获取hash里面指定字段的长度 |
HVALS key | 获取hash的所有值 |
HSCAN key cursor [MATCH pattern] [COUNT count] | 迭代hash里面的元素 |
命令 | 描述 |
---|---|
ZADD key [NX/XX] [CH] [INCR] score member [score member …] | 添加到有序set一个或多个元素,或更新分数 |
ZCARD key | 返回key的有序集元素个数 |
ZCOUNT key min max | 返回分数范围内的成员数量 |
ZINCRBY key increment member | 为有序集key的成员member的score值加上增量increment |
ZINTERSTORE destination numkeys key [key …] [WEIGHTS weight] [SUM/MIN/MAX] | 计算给定的numkeys个有序集合的交集,并且把结果放到destination中 |
ZLEXCOUNT key min max | 计算有序集合中指定成员之间的成员数量 |
ZRANGE key start stop [WITHSCORES] | 返回存储在有序集合key中的指定范围的元素。 返回的元素可以认为是按得分从最低到最高排列。 如果得分相同,将按字典排序 |
ZRANGEBYLEX key min max [LIMIT offset count] | 返回指定成员区间内的成员,按成员字典正序排序, 分数必须相同 |
ZREVRANGEBYLEX key max min [LIMIT offset count] | 返回指定成员区间内的成员,按成员字典倒序排序, 分数必须相同。 |
ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] | 返回指定成员区间内的成员,按照分数由低到高排序 |
ZRANK key member | 返回有序集key中成员member的排名 |
ZREM key member [member …] | 从有序结合中删除一个或多个成员 |
ZREMRANGEBYLEX key min max | 删除名称按字段由低到高排序成员之间的所有成员 |
ZREMRANGEBYRANK key start stop | 移除有序集key中,指定排名(rank)区间内的所有成员 |
ZREMRANGEBYSCORE key min max | 移除有序集key中,所有score值介于min和max之间(包括等于min或max)的成员 |
ZREVRANGE key start stop [WITHSCORES] | 返回有序集key中,指定区间内的成员。其中成员的位置按score值递减(从大到小)来排列 |
ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] | 返回有序集合中指定分数区间内的成员,分数由高到低排序 |
ZREVRANK key member | 返回有序集key中成员member的排名,其中有序集成员按score值从大到小排列 |
ZSCORE key member | 返回有序集key中,成员member的score值 |
ZUNIONSTORE destination numkeys key [key …] [WEIGHTS weight] [SUM/MIN/MAX] | 计算给定的numkeys个有序集合的并集,并且把结果放到destination中 |
ZSCAN key cursor [MATCH pattern] [COUNT count] | 迭代sorted sets中的元素 |
命令 | 描述 |
---|---|
AUTH password | 验证服务器命令 |
PING | PING 服务器 |
QUIT | 关闭连接,退出 |
SELECT index | 选择数据库 |
SWAPDB index index | 交换同一Redis服务器上的两个DATABASE |
订阅,取消订阅和发布实现了发布/订阅消息范式(引自wikipedia),发送者(发布者)不是计划发送消息给特定的接收者(订阅者)。而是发布的消息分到不同的频道,不需要知道什么样的订阅者订阅。订阅者对一个或多个频道感兴趣,只需接收感兴趣的消息,不需要知道什么样的发布者发布的。这种发布者和订阅者的解耦合可以带来更大的扩展性和更加动态的网络拓扑。
命令 | 描述 |
---|---|
PSUBSCRIBE pattern [pattern…] | 订阅一个或多个符合符合给定模式的频道 |
PUBSUB subcommand [argument…] | 查看订阅发布系统状态 |
PUBLISH channel message | 将信息发送到指定的频道 |
PUNSUBSCRIBE [pattern…] | 退订所有给定模式的频道 |
SUBSCRIBE channel [channel…] | 订阅给定的一个或多个频道的信息 |
UNSUBSCRIBE [channel…] | 指退订给定的频道 |
创建一个名为:RedisChat的频道
重启个redis客户端,然后在同一个频道redisChat发布消息,订阅者就能接受到消息
MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:
EXEC 命令负责触发并执行事务中的所有命令:
MULTI 命令用于开启一个事务,它总是返回 OK 。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。
EXEC 命令的回复是一个数组, 数组中的每个元素都是执行事务中的命令所产生的回复。 其中, 回复元素的先后顺序和命令发送的先后顺序一致。
当客户端处于事务状态时, 所有传入的命令都会返回一个内容为 QUEUED 的状态回复(status reply), 这些被入队的命令将在 EXEC 命令被调用时执行。
使用事务时可能会遇上以下两种错误:
不过,从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。
在 Redis 2.6.5 以前, Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。
至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。**
如果你有使用关系式数据库的经验, 那么 “Redis 在事务失败时不进行回滚,而是继续执行余下的命令”这种做法可能会让你觉得有点奇怪。
以下是这种做法的优点:
Redis 命令只会因为错误的语法而失败(并且这些问题不能在入队时发现),或是命令用在了错误类型的键上面:这也就是说,从实用性的角度来说,失败的命令是由编程错误造成的,而这些错误应该在开发的过程中被发现,而不应该出现在生产环境中。
因为不需要对回滚进行支持,所以 Redis 的内部可以保持简单且快速。
有种观点认为 Redis 处理事务的做法会产生 bug , 然而需要注意的是, 在通常情况下, 回滚并不能解决编程错误带来的问题。 举个例子, 如果你本来想通过 INCR 命令将键的值加上 1 , 却不小心加上了 2 , 又或者对错误类型的键执行了 INCR , 回滚是没有办法处理这些情况的。
当执行 DISCARD 命令时, 事务会被放弃, 事务队列会被清空, 并且客户端会从事务状态中退出
WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。
被 WATCH 的键会被监视,并会发觉这些键是否被改动过了。 如果有至少一个被监视的键在 EXEC 执行之前被修改了, 那么整个事务都会被取消, EXEC 返回nil-reply来表示事务已经失败。
举个例子, 假设我们需要原子性地为某个值进行增 1 操作(假设 INCR 不存在)。
val = GET mykey
val = val + 1
SET mykey $val
上面的这个实现在只有一个客户端的时候可以执行得很好。 但是, 当多个客户端同时对同一个键进行这样的操作时, 就会产生竞争条件。举个例子, 如果客户端 A 和 B 都读取了键原来的值, 比如 10 , 那么两个客户端都会将键的值设为 11 , 但正确的结果应该是 12 才对。
有了 WATCH , 我们就可以轻松地解决这类问题了:
WATCH mykey
val = GET mykey
val = val + 1
MULTI
SET mykey $val
EXEC
使用上面的代码, 如果在 WATCH 执行之后, EXEC 执行之前, 有其他客户端修改了 mykey 的值, 那么当前客户端的事务就会失败。 程序需要做的, 就是不断重试这个操作, 直到没有发生碰撞为止。
这种形式的锁被称作乐观锁, 它是一种非常强大的锁机制。 并且因为大多数情况下, 不同的客户端会访问不同的键, 碰撞的情况一般都很少, 所以通常并不需要进行重试。
WATCH 使得 EXEC 命令需要有条件地执行: 事务只能在所有被监视键都没有被修改的前提下执行, 如果这个前提不能满足的话,事务就不会被执行
WATCH 命令可以被调用多次。 对键的监视从 WATCH 执行之后开始生效, 直到调用 EXEC 为止。
用户还可以在单个 WATCH 命令中监视任意多个键
WATCH key1 key2 key3
OK
当 EXEC 被调用时, 不管事务是否成功执行, 对所有键的监视都会被取消。
另外, 当客户端断开连接时, 该客户端对键的监视也会被取消。
使用无参数的 UNWATCH 命令可以手动取消对所有键的监视。 对于一些需要改动多个键的事务, 有时候程序需要同时对多个键进行加锁, 然后检查这些键的当前值是否符合程序的要求。 当值达不到要求时, 就可以使用 UNWATCH 命令来取消目前对键的监视, 中途放弃这个事务, 并等待事务的下次尝试。
从定义上来说, Redis 中的脚本本身就是一种事务, 所以任何在事务里可以完成的事, 在脚本里面也能完成。 并且一般来说, 使用脚本要来得更简单,并且速度更快。
因为脚本功能是 Redis 2.6 才引入的, 而事务功能则更早之前就存在了, 所以 Redis 才会同时存在两种处理事务的方法。
不过我们并不打算在短时间内就移除事务功能, 因为事务提供了一种即使不使用脚本, 也可以避免竞争条件的方法, 而且事务本身的实现并不复杂。
不过在不远的将来, 可能所有用户都会只使用脚本来实现事务也说不定。 如果真的发生这种情况的话, 那么我们将废弃并最终移除事务功能。