刚才解决了吾爱破解论坛查询陡增导致性能下降问题,我来分享一下解决过程和解决原理
在分享原理之前,我首先讲解一下锁是什么:
在并发执行程序的时候,多个线程可能会同时访问一段共享数据,若没有保护,可能导致数据不同步,甚至错误!
所以,为了解决这种需求,锁被发明:
在访问共享数据前,首先去申请锁(或者说叫临界区访问权限,共享数据访问权限),若检测到已被占用,则等待或放弃操作,若获取锁成功,则进行共享数据的访问操作,然后释放锁
这样完美的解决了上述问题。
然后我们讲解具体问题:
大家首先看下面一段代码(这段代码用于周期性的更新缓存,当用户请求内容的时候,判断是否到缓存更新时间,若不到,则从缓存返回数据,否则从数据库中读取并更新缓存后返回缓存数据):
[PHP] 纯文本查看 复制代码
$readlock1 = 'readlock1';
//中间代码略去
//读取数据库的所有贴子并筛选出某些列,按照TID排序降序排列并取出前面的若干项,此处代码省略
if(discuz_process::islocked($readlock1, 600))
{
writetocache($readlock1, $cacheArray);//写缓存
discuz_process::unlock($readlock1);
}
$readlock2 = 'readlock2';
//中间代码略去
//读取数据库的所有贴子并筛选出某些列,按照TID排序降序排列并取出前面的若干项,此处代码省略
if(discuz_process::islocked($readlock2, 600))
{
writetocache($readlock2, $cacheArray);//写缓存
discuz_process::unlock($readlock2);
}
$readlock3 = 'readlock3';
//中间代码略去
//读取数据库的所有贴子并筛选出某些列,按照TID排序降序排列并取出前面的若干项,此处代码省略
if(discuz_process::islocked($readlock3, 600))
{
writetocache($readlock3, $cacheArray);//写缓存
discuz_process::unlock($readlock3);
}
//这里更新最后缓存时间
以上代码大家有没有注意到有什么问题?
我们来看一下discuz_process::islocked与discuz_process::unlock函数的说明(这两个是discuz论坛提供的插件开发函数,用于锁的管理):
discuz_process:islocked函数用于获取锁,如果锁已被占用则返回true,否则获取锁并返回false
discuz_process::unlock函数用于释放锁
上述函数的第一个参数是一个tag,或者说是锁的标识符,用于唯一的标识一个锁
我们看上面代码关于锁的判断,按照函数的说明,这个if块内的代码只有当锁已被占用后才会执行!正确的逻辑应该是:锁未被占用才获取
这将会导致下面这个问题:
第一个用户访问论坛,申请到了锁,但是进不了if块无法更新缓存
第二个用户访问论坛,因为锁已被第一个用户申请,所以成功进入了if块更新缓存,并释放了锁
第三个用户访问论坛,申请到了锁,但是进不了if块无法更新缓存
第四个用户访问论坛,因为锁已被第三个用户申请,所以成功进入了if块更新缓存,并释放了锁
....(省略若干)
当并发用户数多的时候,这个问题尤其明显:直接导致缓存更新代码被执行若干次,增大论坛服务器的负担,但这还不是主要问题!
我们注意到上面的代码进行了多次数据库读取操作并进行了多次加锁解锁操作,但是数据库读取操作并未加锁!!这直接导致数据库并发访问会大量产生!
但是,即使数据库访问也加锁,还存在这样一个问题:
第一个用户访问论坛,遇到readlock1锁,申请成功并更新缓存
此时,第二个用户访问论坛,遇到readlock1锁发现该锁已被占用,然后跳过第一个if块判断readlock2锁是否被占用,若此时第一个用户的readlock1锁还未释放,则会去执行第二个if块的内容
然后,第三个用户访问论坛,假设还没有一个用户的程序执行到最后的“更新最后缓存访问时间”,那么依旧认为缓存未被更新,执行程序,若此时用户一刚刚释放readlock1锁,那么用户三此时可以得到readlock1锁,并对缓存再一次更新!
这种情况会造成论坛严重卡顿,那么解决方案是什么呢?
很简单,不再使用独立锁,而是使用全局锁,示例代码如下:
[PHP] 纯文本查看 复制代码
//中间代码略去
$readlock = 'readlock';
//上锁
if(!discuz_process::islocked($readlock, 600))
{
//中间代码略去
//读取数据库的所有贴子并筛选出某些列,按照TID排序降序排列并取出前面的若干项,此处代码省略
writetocache($readlock1, $cacheArray);//写缓存
//中间代码略去
//读取数据库的所有贴子并筛选出某些列,按照TID排序降序排列并取出前面的若干项,此处代码省略
writetocache($readlock2, $cacheArray);//写缓存
//中间代码略去
//读取数据库的所有贴子并筛选出某些列,按照TID排序降序排列并取出前面的若干项,此处代码省略
writetocache($readlock3, $cacheArray);//写缓存
discuz_process::unlock($readlock);//解锁
}
这样带来的显著效果就是:
原来每次刷新缓存会导致论坛卡顿十几秒到几十秒,现在完全感觉不出来 |