Redis设计与实现(四)、功能的实现

| 分类 redis  | 标签 redis 

##前言

这一章主要讲解redis内部的一些功能,主要分为以下4个:

  • 事务
  • 订阅与发布
  • Lua脚本
  • 慢查询日志

那么,我们就来逐个击破!

##1. 事务

事务对于刚接触计算机的人来说可能会比较抽象。因为事务是对计算机某些操作的称谓。通俗来说,事务就是一个命令、一组命令执行的最小单元。事务一般具有ACID属性(redis只支持两种,下文详细说明):

  • 原子性(atomicity):一个事务是一个不可分割的最小工作单位,事务中包括的诸操作要么都做,要么都不做。
  • 一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
  • 隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(durability):持续性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

那么,redis是通过MULTI/DISCARD/EXEC/WATCH这4个命令来实现事务功能。对事务,我们必须知道事务安全性是一个非常重要的

事务提供了一种“将多个命令打包,然后一次性、按顺序执行”的机制,并且在事务执行期间不会中断——意思就是在事务完成之前,客户端的其他命令都是阻塞状态。

以下是一个事务的例子,它先以 MULTI 开始一个事务,然后将多个命令入队到事务中,最后 由EXEC 命令触发事务,一并执行事务中的所有命令:

redis> MULTI
OK
redis> SET book-name "Mastering C++ in 21 days"
81
QUEUED
redis> GET book-name
QUEUED
redis> SADD tag "C++" "Programming" "Mastering Series"
QUEUED
redis> SMEMBERS tag
QUEUED
redis> EXEC
1) OK
2) "Mastering C++ in 21 days"
3) (integer) 3
4) 1) "Mastering Series"
   2) "C++"
   3) "Programming"

一个事务主要经历3个阶段:

  1. 开始事务
  2. 命令入队(看,上面都有QUEUED这个返回值)
  3. 执行事务

这几个过程都比较简单,开始事务就是切换到事务模式;命令入队就是把事务中的每条命令记录下来,包括是第几条命令,命令参数什么的(当然,事务中是不能再嵌套事务的,所以再有事务关键字(MULTI/DISCARD/WATCH)会立即执行的);执行事务就是一下子把刚才那个事务的命令执行完。

  • DISCARD: 取消一个事务,它会清空客户端的整个事务队列,然后将客户端从事务状态调整回非事务状态,最终返回字符串OK给客户端,说明事务已经取消
  • MULTI:因为redis不允许事务嵌套,所以,当在事务中输入MULTI时,redis服务器会简单返回一个错误,然后继续等待该事务的其他操作,就好像没有输入过MULTI一样
  • WATCH:WATCH用于在事务开始之前监视任意数量的键,当调用EXEC执行事务时,如果任意一个监视的键被修改了,那么整个事务就不再执行,直接返回失败。【事务安全性检查】

对于上面的WATCH来说,我们可以看成一个锁。这个锁在执行期间是不可以修改(类比为打开锁)的,这样才能保证这次事务是隔离的,安全的。那么,WATCH是如何触发的呢?

在任何对数据库键空间进行修改的命令执行成功后,multi.c/touchWatchKey函数都会被调用——它会检查数据库的watch_keys字典,看是否有客户端在监视被修改的键,如果有的话,就把这个监视的是客户端的REDIS_DIRTY_CAS打开。之后,执行EXEC前,会对这个事务的客户端检查是否REDIS_DIRTY_CAS被打开,打开的话就说明事务的安全性被破坏,直接返回失败;反之则正常进行事务操作。

###事务的ACID性质

前面说到,事务一般具有ACID属性,但是redis只保证两种机制:一致性和隔离性。对于原子性和持久性并没有支持,下面说明redis为什么这样做。

  1. 原子性:redis的单条命令是原子性的,但是redis没有对事务进行原子性保护。如果一个事务没有执行成功,是不会进行重试或者回滚的。
  2. 一致性【redis保证】:这个要分三个层次:
    1. 入队错误:如果执行一个错误的命令(比如命令参数不对:set key),那么会被标记为REDIS_DIRTY_EXEC,执行会直接返回错误
    2. 执行错误:对某个类型key执行其他类型的操作,不会影响结果,所以不会影响事务的一致性。事务会继续进行
    3. redis进程被冻结:简单来说,redis有持久化功能。但是这个持久化是建立在执行成功的基础上,如果不成功是不会进行持久化的。所以,出问题时都会保证要么事务没有执行;要么事务执行成功。所以保证了数据的一致性。
  3. 隔离性【redis保证】:因为redis是单进程程序,并在执行事务时不会中断,一直执行到事务对列为空,所以隔离性是可以保证的。
  4. 持久性:不管是单纯的内存模式,还是开启了持久化文件的功能,事务的每条命令执行过程中都会有时间间隙,如果这时候出现问题,持久化还是无法保证。所以,redis使用的是事务没执行或者事务执行完成才会进行持久化工作(AOF模式除外,虽然现在还没有看到- -)

##2. 订阅与发布

这个东西没有仔细看,但是大概知道是啥功能的。我想了一下,可以使用这个功能来完成跨平台之间消息的推送。比如我开发了一个app,分别有web版本、ios版本、Android版本、Symbian版本。那么,我可以结合模式+频道,将消息推送到所有安装此应用的平台上。

##3. Lua脚本

这是redis2.6版本最大的亮点。但是我们好像木有用过- -所以,以后有需求的时候再好好研究一下吧。

##4. 慢查询日志

慢查询日志是redis系统提供的一个查看系统性能的功能。它的每一条记录的是一条命令的执行时间。所以,你可以在redis.conf中设置当超过slowlog_log_slower_than的时候,将这个命令记录下来;因为慢查询日志是一个FIFO队列(用链表实现的),所以还有一个slowlog_max_than来限制队列长度,如果溢出,就从队头删除最旧的,将最新的添加到队尾。


上一篇     下一篇