標(biāo)簽:redis
腳本編程, WordPress技巧修復(fù)anylink增加Redis緩存后存在的bug
先森最近更新了一篇文章,發(fā)布后先森自己沒(méi)有去看,今天去看了一眼,結(jié)果突然發(fā)現(xiàn)這篇文章中所有的外鏈沒(méi)有自動(dòng)轉(zhuǎn)成內(nèi)鏈。以前優(yōu)化SEO的時(shí)候,看到的優(yōu)化建議基本都有不要直接跳轉(zhuǎn)到外鏈,會(huì)導(dǎo)致權(quán)重降低,所以先森一直都有用anylink這個(gè)插件來(lái)實(shí)現(xiàn)這個(gè)功能。對(duì)于anylink這個(gè)插件,先森的使用體驗(yàn)還是非常好的,之前一共還發(fā)過(guò)三篇博文:WordPress為anylink插件外鏈跳轉(zhuǎn)添加漂亮的跳轉(zhuǎn)頁(yè)面WordPress:WPJAM BASIC插件與anylink沖突WordPress優(yōu)化:為anylink插件增加緩存既然發(fā)現(xiàn)了問(wèn)題,那么就得解決問(wèn)題,正好是清明節(jié)放假期間,托疫情的福先森哪里也不好去,所以來(lái)會(huì)會(huì)這個(gè)bug。排查過(guò)程首先,出現(xiàn)沒(méi)有轉(zhuǎn)內(nèi)鏈的文章只有先森最新發(fā)布的那一篇,之前的文章都是正常的。1、懷疑緩存可能是CDN有緩存,先森將本機(jī)hosts改成源站,直接訪問(wèn)源站,測(cè)試依舊正常,pass。除了CDN,先森WordPress還有緩存,先森用的是插件wp-super-cache進(jìn)行緩存。先森清理了插件緩存后,問(wèn)題依舊;然后想是不是插件有問(wèn)題,直接將wp-super-cache插件停用了,問(wèn)題依舊。2、懷疑anylink插件排除了緩存的問(wèn)題,先森又想是不是anylink插件自己出了什么問(wèn)題,先森將anylink插件停用后再啟用,發(fā)現(xiàn)問(wèn)題依舊,插件運(yùn)行問(wèn)題pass。然后先森考慮是不是插件在本篇文章的執(zhí)行有問(wèn)題,開(kāi)debug看一看。先森在WordPress的wp-config.php中打開(kāi)了dubug:define( 'WP_DEBUG', true );define( 'SAVEQUERIES', true );先森開(kāi)debug主要是想看本篇文章中的SQL查詢,所以在后臺(tái)開(kāi)啟了Debug Queries插件,結(jié)果開(kāi)啟后并沒(méi)有看到anylink的查詢,然后先森想到了為anylink添加redis緩存這篇文章中,已經(jīng)將相關(guān)的SQL查詢優(yōu)化了,這樣debug確實(shí)已經(jīng)看不到去MySQL的查詢了。此時(shí)先森已經(jīng)開(kāi)始懷疑是不是當(dāng)時(shí)優(yōu)化有什么bug了,但還是需要先確認(rèn)一下是不是MySQL、Redis數(shù)據(jù)庫(kù)有問(wèn)題。3、懷疑數(shù)據(jù)庫(kù)有問(wèn)題先森的MySQL是云數(shù)據(jù)庫(kù),Redis是部署在源站本機(jī)的。MySQL運(yùn)行肯定沒(méi)有問(wèn)題, 不然就不只是外鏈有問(wèn)題了;Redis檢查后發(fā)現(xiàn)也是正常運(yùn)行的。先森再去看anylink的MySQL表,發(fā)現(xiàn)wp_al_urls表里是有出問(wèn)題這篇文章的外鏈轉(zhuǎn)內(nèi)鏈對(duì)應(yīng)表的,這就奇怪了,到這里MySQL的問(wèn)題排除了,先森就懷疑是自己之前的優(yōu)化存在問(wèn)題了。4、懷疑優(yōu)化存在BUG先森本來(lái)想直接在redis里查有問(wèn)題文章的緩存,但是redis的key太多了,先森已經(jīng)忘了key是什么規(guī)則了,所以就先去研究了一下anylink的代碼,先搞清楚之前先森的優(yōu)化。首先確認(rèn)到,先森優(yōu)化的是插件的classes/al_filter.php文件,優(yōu)化了getAllLnks()和get_slug_by_url()這兩個(gè)類中的函數(shù),增加了先查redis緩存,沒(méi)有再查數(shù)據(jù)庫(kù)并將結(jié)果存到redis的代碼。修改的部分這兩個(gè)函數(shù)先森研究了一下,get_slug_by_url()這個(gè)函數(shù)是用在把留言者的鏈接轉(zhuǎn)換成內(nèi)鏈的,所以出現(xiàn)問(wèn)題的不可能是這個(gè)函數(shù),那只能是getAllLnks()這個(gè)函數(shù)了。getAllLnks()這個(gè)函數(shù)保存緩存的key是‘getAllLnks:’開(kāi)頭的,后面跟的是文章的id,先森就去redis查相關(guān)key。應(yīng)該是redis的key不支持冒號(hào),所以實(shí)際存儲(chǔ)的key是‘getAllLnks-’開(kāi)頭的。先森發(fā)現(xiàn)有問(wèn)題的文章ID是1968,所以它的key是‘getAllLnks-1968’,先森先查的是正常文章的key,查出來(lái)是一大堆的內(nèi)容,而查到1968就發(fā)現(xiàn)明顯有問(wèn)題:getAllLnks有問(wèn)題這個(gè)key的內(nèi)容簡(jiǎn)直就沒(méi)有內(nèi)容,按邏輯來(lái)想起碼有兩個(gè)內(nèi)容:外鏈地址、內(nèi)鏈地址,將這個(gè)key刪除,然后重新訪問(wèn)有問(wèn)題的文章,發(fā)現(xiàn)外鏈就已經(jīng)自動(dòng)轉(zhuǎn)為內(nèi)鏈了,此時(shí)再來(lái)查redis的key查詢結(jié)果,正常多了:getAllLnks正常結(jié)果至此,問(wèn)題已經(jīng)臨時(shí)得到了解決,但是原因還需要分析一下。出現(xiàn)bug的原因一開(kāi)始,先森以為這個(gè)問(wèn)題原因是anylink在管理員登錄時(shí)不會(huì)處理外鏈,先森預(yù)覽文章被緩存了,但是找了一圈anylink的代碼,沒(méi)有找到判斷管理員是否登錄的代碼,所以應(yīng)該不是這個(gè)原因。好好想了一下,發(fā)現(xiàn)bug的原因應(yīng)該是這個(gè):當(dāng)文章還在編輯的過(guò)程中,預(yù)覽文章,由于此時(shí)文章還沒(méi)有外鏈,所以數(shù)據(jù)庫(kù)查詢結(jié)果為空,但是這個(gè)空結(jié)果被先森的代碼保存到Redis中了,由于Redis的緩存沒(méi)有設(shè)置過(guò)期或到期時(shí)間未到,先森也沒(méi)有設(shè)置redis緩存更新,導(dǎo)致后續(xù)anylink插件來(lái)查詢這篇文章的外鏈時(shí)一直是空的。解決方案問(wèn)題原因找到了,就好解決了,先森想到了兩個(gè)解決方案:方法1:管理員登錄時(shí)anylink不處理鏈接方法2:文章發(fā)布、變更時(shí)更新anylink的緩存第一種方法,管理員登錄時(shí),尤其是正在編輯文章時(shí),預(yù)覽文章不會(huì)造成錯(cuò)誤的key被redis緩存。但此方法還是有bug,管理員未來(lái)更新文章時(shí)新增了其他外鏈,此時(shí)由于Redis已經(jīng)緩存了這篇文章的外鏈查詢情況,如果改緩存未過(guò)期,新增的外鏈在文章中會(huì)出現(xiàn)未被轉(zhuǎn)換的情況。且這種方法容易造成管理員視角和訪客視角不同,先森用的其它插件有類似設(shè)定,實(shí)際使用中會(huì)讓人比較頭疼,所以先森不太希望使用這種方式進(jìn)行修復(fù)。相較而言,第二種方法就要合理的多,只要文章有變動(dòng),那就把a(bǔ)nylink該文章的redis緩存刪了,有人訪問(wèn)該文章時(shí)再緩存。實(shí)現(xiàn)也比較簡(jiǎn)單,在自己的主體function.php最后加一個(gè)publish_post鉤子,實(shí)現(xiàn)這個(gè)需求:/** * 更新或發(fā)布文章清理Redis緩存 * 增加時(shí)間:2022-04-04 12:43:00 * By:http://www.cnidcc.cn/ */function Clean_Redis($post_ID){ wp_cache_delete( 'getAllLnks:'.$post_ID );}add_action('publish_post', 'Clean_Redis', 0);來(lái)測(cè)試一下,在管理后臺(tái)隨便更新了一篇已發(fā)布的文章,發(fā)布前查詢?cè)撐恼碌膔edis緩存,刷新后再去查詢,該文章的key已經(jīng)查不到數(shù)據(jù)了。測(cè)試hook刪除redis緩存2020年埋下的bug終于在2022年被修復(fù)了,還好期間先森新發(fā)布的文章只有2021兩篇(有點(diǎn)慚愧),且不涉及外鏈,所以影響不大。按理說(shuō)也會(huì)影響到舊文章的更新,但是先森最近幾年對(duì)博客這邊的關(guān)注實(shí)在太少,影響著實(shí)不大。無(wú)論怎樣,寫(xiě)代碼還是的考慮周到一點(diǎn),還好先森不是程序員,只是一枚小運(yùn)維。
WordPress技巧WordPress優(yōu)化:為anylink插件增加緩存
先森最近在梳理網(wǎng)站的代碼,想辦法為網(wǎng)站加速,主要從代碼、軟件、網(wǎng)絡(luò)層面進(jìn)行優(yōu)化,這一切都是從網(wǎng)站切換到HTTPS開(kāi)始的。先森已經(jīng)連續(xù)觀察了多日的CDN了,目前也就到了查缺補(bǔ)漏的階段了。先森還將整個(gè)網(wǎng)站目前備份到了另一臺(tái)服務(wù)器,將網(wǎng)站在本地解析到這臺(tái)服務(wù)器上,開(kāi)始了對(duì)代碼的檢查。開(kāi)啟了debug,把大的一些問(wèn)題都處理了,然后也把主題的代碼理了一遍,并且網(wǎng)站也加上了Redis緩存。但是先森發(fā)現(xiàn),即使加上了Redis,有時(shí)候網(wǎng)頁(yè)打開(kāi)生成時(shí)間還是得一秒多,先森就很納悶,一直想搞明白到底是什么情況。排查工具很早以前,先森就在主題的footer.php末尾,添加了下面的代碼,以便于登錄之后可以看到當(dāng)前網(wǎng)頁(yè)的查詢次數(shù),生成時(shí)間:<?php if (is_user_logged_in()){ echo "<pre>".get_num_queries().'次查詢,用時(shí)'; timer_stop(3); echo '秒</pre>';?>這個(gè)代碼網(wǎng)絡(luò)上到處都是,相信很多人都添加的有。先森網(wǎng)站加Redis之前,網(wǎng)頁(yè)的查詢次數(shù)都是150+次,生成時(shí)間2-3秒,甚至更多。用上之后減少到50+次,但是有時(shí)還是會(huì)需要1秒多,讓先森百思不得其解。然后先森把主題代碼該優(yōu)化的都優(yōu)化后,查詢次數(shù)30+次,生成時(shí)間降到1秒左右,但是先森還是不太滿足,所以想看看到底是執(zhí)行了哪些查詢,然后就在網(wǎng)上找到了這段代碼,和上面的有些類似。首先需要先在WordPress的根目錄配置文件wp-config.php中添加保存查詢的代碼:define('SAVEQUERIES', true);然后也是在footer.php的網(wǎng)頁(yè)最后部分添加打印代碼:<?phpif (current_user_can('administrator')){ global $wpdb; echo "<pre>"; print_r($wpdb->queries); echo "</pre>";}?>但是先森添加后看了一下,差點(diǎn)當(dāng)場(chǎng)去世,這樣打印出來(lái)的是一個(gè)很大的多維數(shù)組,看的人眼花繚亂,重點(diǎn)是太長(zhǎng)了還顯示不全。先森將打印復(fù)制出來(lái),拿到NotePad++里面打開(kāi),依舊顯得很亂。查詢的打印不過(guò)大概看了一下,大數(shù)組的每一個(gè)鍵值表示一個(gè)查詢,然后一個(gè)查詢數(shù)組了,第一個(gè)值是執(zhí)行的SQL,第二個(gè)值是使用的時(shí)間,第三個(gè)值是調(diào)用的代碼位置。其實(shí)可以用循環(huán)做一個(gè)網(wǎng)格,讓前端顯示看著方便一點(diǎn),但是先森很懶,網(wǎng)上看了一下,有插件可以做到相關(guān)功能,且不用修改wp-config.php,即Debug Queries,所以先森就懶得自己寫(xiě)了,直接裝了一個(gè)來(lái)進(jìn)行排查。需要注意的是,Debug Queries很久沒(méi)有更新了,安裝可能會(huì)報(bào)錯(cuò),不過(guò)還是可以正常使用的。其實(shí)Debug Queries介紹頁(yè)也推薦使用Debug Objects插件,但是先森試了一下Debug Queries可以用,也就懶得再試另一個(gè)插件了。排查問(wèn)題工具準(zhǔn)備好了,先森就來(lái)好好排查到底是哪里查詢比較慢了。插件裝好了,再去看打印出來(lái)的查詢信息,就比較清晰了。先森對(duì)比了一下,大部分的查詢都是0.00x秒的,就是幾毫秒的,但是只要涉及到wp_al_urls的查詢,就會(huì)是即時(shí)甚至上百毫秒。wp_al_urls的查詢先森看了一下,這個(gè)表是插件anylink的,這個(gè)插件主要是將網(wǎng)站上的外鏈全都轉(zhuǎn)化成內(nèi)鏈,點(diǎn)擊后可以跳轉(zhuǎn)到外鏈。對(duì)于anylink,先森這里也發(fā)過(guò)兩篇相關(guān)的文章:WordPress為anylink插件外鏈跳轉(zhuǎn)添加漂亮的跳轉(zhuǎn)頁(yè)面WordPress:WPJAM BASIC插件與anylink沖突這個(gè)插件先森也是從建站伊始就在用了,是一個(gè)很好用的插件,但沒(méi)有想到這個(gè)插件會(huì)出現(xiàn)慢查詢。通過(guò)上面的截圖可以看到,對(duì)wp_al_urls的查詢條件是網(wǎng)站鏈接,看了一下數(shù)據(jù)庫(kù),這個(gè)SQL是為了去拿到內(nèi)鏈的slug記錄:anylink獲取slug這里去查的,實(shí)際上是網(wǎng)頁(yè)正文里的外鏈、各位評(píng)論大佬的網(wǎng)址對(duì)應(yīng)的內(nèi)鏈地址。先森看了一下這個(gè)表,沒(méi)想到竟然有10M的大小,接近10萬(wàn)條數(shù)據(jù),而且這里查詢的是al_origURL字段,先森看了一下,這個(gè)字段是沒(méi)有索引的。解決問(wèn)題對(duì)于MySQL的查詢,先森能想到的優(yōu)化方法就是加索引,所以先森直接就操作加索引,但是報(bào)錯(cuò)了:給al_origURL字段加索引報(bào)錯(cuò)看報(bào)錯(cuò)是跟字段格式有關(guān)的,看了一下這個(gè)字段的類型是mediumtext的,這個(gè)字段是存URL的,有些URL非常的長(zhǎng),如果該varchar的話,可能會(huì)出問(wèn)題,varchar最長(zhǎng)255個(gè)字符。網(wǎng)上找了一陣子解決方案,都是說(shuō)text相關(guān)的類型無(wú)法加索引。先森本來(lái)就對(duì)數(shù)據(jù)庫(kù)索引什么的不太了解,所以只能放棄這條路。先森還能想到的辦法,就是看下這個(gè)查詢的代碼,想辦法把結(jié)果存到Redis上緩存起來(lái)。至于怎么找到實(shí)際執(zhí)行的代碼,先森看了一下,直接找調(diào)用的最后一段就可以了。找到慢查詢的調(diào)用代碼可以看到,兩個(gè)SQL實(shí)際上是一樣的,查的是同個(gè)網(wǎng)址,結(jié)果執(zhí)行的時(shí)間竟然都比較長(zhǎng),所以確實(shí)得把結(jié)果緩存起來(lái)。為了優(yōu)化代碼,先森把整個(gè)網(wǎng)站都作為了一個(gè)PhpStorm里的一個(gè)項(xiàng)目,不得不說(shuō)一個(gè)好的IDE工具寫(xiě)起代碼來(lái)是真的舒服。直接全局搜索,尋找get_slug_by_url這個(gè)函數(shù),順利找到了代碼所在。搜索get_slug_by_url函數(shù)這里有兩個(gè)結(jié)果,第一個(gè)是原本的函數(shù),已經(jīng)被先森注釋起來(lái)了,第二個(gè)是先森改了之后的。可以看到這個(gè)函數(shù)就是調(diào)用$wpdb來(lái)執(zhí)行SQL語(yǔ)句,將得到的結(jié)果再返回一下。函數(shù)比較簡(jiǎn)單,也很利于先森修改。然后先森又找了一下WordPress如何添加緩存,結(jié)果找到一下,發(fā)現(xiàn)非常簡(jiǎn)單。WordPress操作緩存WordPress 為我們提供了使用對(duì)象緩存的函數(shù),方便我們使用對(duì)象緩存。wp_cache_add() :添加數(shù)據(jù)到緩存中,如果數(shù)據(jù)已存在,返回 flasewp_cache_set() :添加數(shù)據(jù)到緩存中,如果數(shù)據(jù)已存在,會(huì)覆蓋數(shù)據(jù)wp_cache_get() :獲取緩存中的數(shù)據(jù),如果數(shù)據(jù)不存在,返回 falsewp_cache_delete() : 從緩存中刪除數(shù)據(jù)wp_cache_replace() :替換緩存中的數(shù)據(jù),類似 wp_cache_set,但是如果數(shù)據(jù)不存在,不自動(dòng)添加wp_cache_flush():清除所有緩存如果沒(méi)有裝redis緩存插件,上面的這些函數(shù)是在./wp-includes/cache.php里。如果裝了Redis Object Cache等插件,就會(huì)自動(dòng)增加一個(gè)./wp-content/object-cache.php文件,這些函數(shù)也會(huì)存在于這個(gè)文件中,用于存入緩存。WordPress 對(duì)象緩存使用使用示例$result = wp_cache_get( 'my_result' );if ( false === $result ) { $result = $wpdb->get_results( $query ); wp_cache_set( 'my_result', $result );}對(duì)anylink的get_slug_by_url函數(shù)改造有了上面這個(gè)案例,先森為anylink的函數(shù)增加緩存就很方便了。示例很簡(jiǎn)單,get_slug_by_url函數(shù)本身也簡(jiǎn)單,所以改造后如下:public function get_slug_by_url( $url ) { $arr_slug = wp_cache_get( $url ); if ( false === $arr_slug ) { global $wpdb; $arr_slug = array(); $arr_slug = $wpdb->get_row($wpdb->prepare( "SELECT * FROM " . ANYLNK_DBTB . " WHERE al_origURL = %s", $url ), ARRAY_A); wp_cache_set( $url, $arr_slug ); } return $arr_slug;}因?yàn)槭纠秃瘮?shù)太契合了,所以這個(gè)函數(shù)幾乎就和示例結(jié)構(gòu)一樣。首先去Redis獲取緩存數(shù)據(jù),獲取不到就去MySQL查詢,查到后再存到Redis。2022年4月4日更新:此代碼存在億點(diǎn)點(diǎn)bug,修復(fù)參考此文章:修復(fù)anylink增加Redis緩存后存在的bug檢查效果代碼修改后,同步到測(cè)試服務(wù)器,訪問(wèn)了兩次之前訪問(wèn)的頁(yè)面,查詢次數(shù)和用時(shí)都降下來(lái)了。然后看到查詢次數(shù)還是有33次,其中有部分是查詢wp_al_urls_index這個(gè)表的,雖然速度不慢,但是次數(shù)比較多,先森重復(fù)上面的方法也修改了一下相關(guān)函數(shù),最終效果如下圖:最終效果查看Redis的keys上面改造的代碼中,是拿url地址去做的key名稱,那么先森也來(lái)看下緩存數(shù)據(jù)在Redis里的情況:redis的緩存上面改造的代碼比較簡(jiǎn)陋,直接拿的URL做的key名稱,如果URL比較短還好,如果長(zhǎng)的話就可能出現(xiàn)問(wèn)題,key名稱其實(shí)可以做一下長(zhǎng)度限制。提示:Redis最好不要使用默認(rèn)端口6379,除非安全做的非常好。使用Redis時(shí)注意以下幾點(diǎn):1、一定要配置強(qiáng)密碼;2、安全組、防火墻一定要最小范圍放通Redis端口,即針對(duì)指定IP放通訪問(wèn);3、盡量不要使用默認(rèn)端口。因?yàn)镽edis而導(dǎo)致服務(wù)器中木馬病毒的保障,先森這邊經(jīng)常遇到。總結(jié)先森以前以為給WordPress配上Redis很麻煩,實(shí)際使用發(fā)現(xiàn)真香,建議有能力的朋友都上一下,畢竟生命不止,折騰不息。本文最主要的還是記錄一下排查網(wǎng)頁(yè)查詢慢的過(guò)程和解決方法,希望能夠給其他朋友提供思路。

川公網(wǎng)安備 51011202000104號(hào)