前言
博客一直用着Purge Varnish Cache这个插件进行Varnish的缓存管理,插件通过Varnish提供的Admin面板进行管理,其功能也相对强大,可以选择在收到新的(修改)评论、发布(编辑)文章、添加(修改)菜单时对指定页面(首页/文章/整个Varnish缓存)进行更新。但实际上上述特性中的第一点并不能实现。
Bug重现及排查
在退出登录后(VCL中设置当登录时Varnish缓存机制不启用),对任意一篇文章进行评论,重新刷新文章页面,发现Response Header中X-Cache的状态依然是Hit
同时评论列表中也没有刚刚发表的评论,后台中该评论显示已经通过,以登录状态重新刷新页面,评论能够正常显示,那么想必是Varnish的缓存没有更新了。
先从Varnish查起,Shell中用varnishstat监视Varnish缓存状态,同时重复上述步骤,发现MAIN.bans并没有CHANGE,也就是说现在可以确定是Purge Varnish Cache插件的问题了。
巧在这个插件提供了debug模式,在wp-config.php中添加define(‘WP_VARNISH_PURGE_DEBUG’, true);即可开启。
继续重复上述的评论步骤,在wp-content\uploads中打开log文件如下
1 2 3 4 | ban req.http.host == "edlinus.cn" && req.url ~ "^/$" {"code":"200","msg":"\n"} ban req.http.host == "" && req.url ~ "" {"code":"104","msg":"Wrong number or parameter"} |
首页的确是更新了,但是第二条语句里的req.http.host和req.url都是空字符,这里本应该是edlinus.cn和评论文章的链接,故猜测插件在获取文章链接时可能出了些问题,遂在插件代码中搜索生成ban命令的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | /** * Build command to purge the cache. */ function purge_varnish_get_command($url, $flag = NULL) { $parse_url = $this->purge_varnish_parse_url($url); $host = $parse_url['host']; $path = $parse_url['path']; $command = "ban req.http.host == \"$host\" && req.url ~ \"^$path$\""; if ($flag == 'front') { $command = "ban req.http.host == \"$host\" && req.url ~ \"^$path/$\""; } elseif ($flag == 'purgeall') { $command = "ban req.http.host == \"$host\""; } return $command; } |
参数只有一个url,看来还得找上一层的函数,经过一番查找之后找到了万恶之源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | /* * Callback to purge various type of varnish objects on comment edited. */ function purge_varnish_comment_post_trigger($ID) { if (!is_object($comment)) { return; } $post_id = $comment->comment_post_ID; $post = get_post($ID); if (is_object($comment) && $comment->comment_approved <> 1 && $post->post_status <> 'publish') { return; } // Call to purge $this->purge_varnish_trigger_comment_expire($post); } ... ... // Trigger post action to purge varnish objects. $purge_varnish_action = get_option('purge_varnish_action', ''); if (!empty($purge_varnish_action)) { $actions = unserialize($purge_varnish_action); if (is_array($actions)) { foreach ($actions as $action) { switch ($action) { case 'post_updated': add_action($action, array($purge_varnish, 'purge_varnish_post_updated_trigger')); break; case 'transition_post_status': add_action($action, array($purge_varnish, 'purge_varnish_post_status_trigger'), 10, 3); break; case 'wp_trash_post': add_action($action, array($purge_varnish, 'purge_varnish_wp_trash_post_trigger')); break; case 'edit_attachment': add_action($action, array($purge_varnish, 'purge_varnish_attachment_update_trigger')); break; case 'comment_post': add_action($action, array($purge_varnish, 'purge_varnish_comment_post_trigger')); break; case 'trash_comment': add_action($action, array($purge_varnish, 'purge_varnish_comment_trash_trigger')); break; case 'wp_update_nav_menu': add_action($action, array($purge_varnish, 'purge_varnish_update_nav_menu_trigger')); break; case 'after_switch_theme': add_action($action, array($purge_varnish, 'purge_varnish_switch_theme_trigger')); break; } } } } |
可以看到,插件通过add_action把purge_varnish_comment_post_trigger动作添加到comment_post这个事件上,然而通过Wordpress Codex可以查到:https://codex.wordpress.org/Plugin_API/Action_Reference/comment_post
comment_post这个事件的属性有两个
$comment_ID
The comment that is created.
$comment_approved
1 (true) if the comment is approved, 0 (false) if not
也就是说实质上purge_varnish_comment_post_trigger接受到的参数$ID是$comment_ID也就是评论ID,可以看到插件通过$post = get_post($ID);获得post的object,然而用评论的ID肯定是没法通过get_post直接获得post的object的,那么接下来要解决这个BUG就十分容易了。
解决方案
实际上解决起来非常简单,在传入的$comment对象中,有comment_post_ID这一项字段,通过这个字段就可以通过get_post获得post的object了。
将
1 | $post = get_post($ID); |
修改为
1 | $post = get_post($comment->comment_post_ID); |
即可解决问题。
修改后重新评论文章,发现Response Header中X-Cache状态变为MISS,表明Varnish已经成功清空了这个页面的缓存,BUG Fixed!
Bonjour!