为什么要构建锁呢?因为构建合适的锁可以在高并发下能够保持数据的一致性,即客户端在执行连贯的命令时上锁的数据不会被别的客户端的更改而发生错误。同时还能够保证命令执行的成功率。
看到这里你不禁要问Redis中不是有事务操作么?事务操作不能够实现上面的功能么?
的确,redis中的事务可以watch可以监控数据,从而能够保证连贯执行的时数据的一致性,但是我们必须清楚的认识到,在多个客户端同时处理相同的数据的时候,很容易导致事务的执行失败,甚至会导致数据的出错。
在关系型数据库中,用户首先向数据库服务器发送BEGIN,然后执行各个相互一致的写操作和读操作,最后用户可以选择发送COMMIT来确认之前的修改,或者发送ROLLBACK进行回滚。
在redis中,通过特殊的命令MULTI为开始,之后用户传入一连贯的命令,最后EXEC为结束(在这一过程中可以使用watch进行监控一些key)。进一步分析,redis事务中的命令会先推入队列,等到EXEC命令出现的时候才会将一条条命令执行。假若watch监控的key发生改变,这个事务将会失败。这也就说明Redis事务中不存在锁,其他客户端可以修改正在执行事务中的有关数据,这也就为什么在多个客户端同时处理相同的数据时事务往往会发生错误。
2、简单理解redis的单线程IO多路复用
Redis采用单线程IO多路复用模型来实现高内存数据服务。何为单线程IO多路复用呢?从字面的意思可以知道redis采用的是单线程、使用的是多个IO。整个过程简单的来讲就是,哪个命令的数据流先到达就先执行。
请看下面的形象理解图:图中是一座窄桥,只能允许一辆车通过,左边是车辆进入的通道,哪一辆车先到达就先进入。即哪个IO流先到达就先处理哪个。
Linux下网络IO使用socket套接字来通讯,普通IO模型只能监听一个socket,而IO多路复用可同时监控多个socket。IO多路复用避免阻塞在IO上,单线程保存多个socket的状态后轮循处理。
3、并发测试
我们就模拟一个简单典型的并发测试,然后从这个测试中得出问题,再进一步研究。
并发测试思路:
1、在redis中设置一个字符串count,运用程序将其取出来加+1,再存储回去,一直循环十万次
2、在两个浏览器上同时执行这个代码
3、将count取出来,查看结果
测试步骤:
1、建立test.php文件
1 <?php 2 $redis=new Redis(); 3 $redis->connect('192.168.95.11','6379'); 4 for ($i=0; $i < 100000; $i++) 5 { 6 $count=$redis->get('count'); 7 $count=$count+1; 8 $redis->set('count',$count); 9 } 10 echo "this OK"; 11 ?>
2、分别在两个浏览器中访问test.php文件
结果由上图可知,总共执行两次,count原本应该是二十万才对的,但实际上count等于十三万多,远远小于二十万,这是为什么呢?
由前面的内容可知,redis是采用单线程IO多路复用模型的。因此我们使用两个浏览器即为两个会话(A、B),取出、加1、存入这三个命令并不是原子操作,并且在执行取出、存入这两个redis命令时是哪个客户端先到就先执行。
例如:1、此时count=120
2、A取出count=120,紧接着B的取出命令流到了,也将count=120取出
3、A取出后立即加1,并将count=121存回去
4、此时B也紧跟着,也将count=121存进去了
注意:
1、设置循环次数尽量大一点,太小的话,当在第一个浏览器执行完毕,第二个浏览器还没开始进行呢
2、必须要两个浏览器同时执行。假若在一个浏览器中同时执行两次test.php文件,不管是否同时执行,最终结果就是count=200000。因为在同一个浏览器中执行,都是属于同一个会话(所有命令都在同一个通道通过),所以redis会让先执行的十万次执行完,再接着执行其他的十万次。
4、事务解决与原子性操作解决 4.1、事务解决更改后的test.php文件