使用緩存防擊穿,解決微信”被動(dòng)回復(fù)用戶消息”重試回復(fù)問題
本文轉(zhuǎn)載自微信公眾號(hào)「UP技術(shù)控」,作者conan5566。轉(zhuǎn)載本文請(qǐng)聯(lián)系UP技術(shù)控公眾號(hào)。
背景
做微信公眾號(hào)開發(fā)的時(shí)候,其中有個(gè)接收普通消息、接收事件推送 API。
有這么條規(guī)則, ”微信服務(wù)器在五秒內(nèi)收不到響應(yīng)會(huì)斷掉連接,并且重新發(fā)起請(qǐng)求,總共重試三次。假如服務(wù)器無法保證在五秒內(nèi)處理并回復(fù),可以直接回復(fù)空串,微信服務(wù)器不會(huì)對(duì)此作任何處理,并且不會(huì)發(fā)起重試。詳情請(qǐng)見“發(fā)送消息-被動(dòng)回復(fù)消息””。
概括起來就2點(diǎn)
1、就是說5s沒響應(yīng),這個(gè)請(qǐng)求就會(huì)被放棄;
2、會(huì)重新發(fā)起請(qǐng)求,具有冪等性;
問題
這樣就會(huì)產(chǎn)生2個(gè)問題。
1、假設(shè)我的方法就正好需要6s,那么即使返回結(jié)果也是沒用的,因?yàn)檎?qǐng)求被放棄了。
2、我需要返回給用戶正確的回信,假設(shè)第一次超時(shí)沒法及時(shí)回信,比如綁定操作,第一次沒回信,第二次再來總不能回復(fù)綁定過了,這樣顯然不合理。
或者直接回復(fù) success ,這樣顯然沒法正常的進(jìn)行消息提醒。
那么怎么做到既執(zhí)行了操作(第一次超時(shí)了),(第二次微信重試)又及時(shí)回復(fù)正確的回信呢 。
代碼實(shí)現(xiàn)
1、定義緩存的key,就是消息MsgId。
- string cacheKey = model.MsgId.ToString();
2、使用緩存機(jī)制,把結(jié)果緩存起來,下次進(jìn)來,直接回復(fù)上次執(zhí)行的結(jié)果。
- TimeSpan expired = new TimeSpan(0, 0, 20);
- string cacheKey = model.MsgId.ToString();
- return _cacheLayer.Get(cacheKey, () =>
- {
- MsgReply param = new MsgReply() { ToUserName = model.FromUserName, FromUserName = model.ToUserName };
- string Jsonstr = WeiXinHelper.ReadAccess(HttpRuntime.AppDomainAppPath.ToString() + "/App_Data/WeChat/KeyWordReplay.json");
- var r = JsonConvert.DeserializeObject<AutoReplay>(Jsonstr);
- param.Content = r.content;
- if (String.Equals(model.MsgType, "text", StringComparison.CurrentCultureIgnoreCase))
- {
- var item = r.keywordcontent.FirstOrDefault(o => o.keyword.Contains(model.Content));
- if (item != null)
- {
- param.Content = item.content;
- }
- }
- string response = _weChatAlertsService.SubscribeReply(param);
- AddReceiveLog(model, xml, response);
- return response;
- }, expired);
3、這樣既解決冪等問題,也返回了正確的結(jié)果。
4、這里需要注意,緩存取得每個(gè) Key專有的 lock object;若同時(shí)有多個(gè) thread要求相同資料,只會(huì)(到數(shù)據(jù)庫)查第一次,剩下的從 cache讀取。
- public T Get<T>(string key, Func<T> getDataWork, TimeSpan absoluteExpireTime, bool forceRefresh = false, bool returnCopy = true) where T : class
- {
- try
- {
- lock (GetMemoryCacheLockObject(key))
- {
- private static object GetMemoryCacheLockObject(string key)
- {
- string cacheLockKey = string.Format(MemoryCacheLockObjectFormat, key);
- lock (CacheObject)
- {
- var lockObject = CacheObject[cacheLockKey];
- if (lockObject == null)
- {
- // 取得每個(gè) Key專屬的 lock object;若同時(shí)有多個(gè) thread要求相同資料,只會(huì)(到資料庫)查第一次,剩下的從 cache讀取
- lockObject = new object();
- CacheObject.Set(
- cacheLockKey,
- lockObject,
- new System.Runtime.Caching.CacheItemPolicy()
- {
- AbsoluteExpiration = DateTimeOffset.UtcNow.AddMinutes(10)
- }
- );
- }
- return lockObject;
- }
- }
總結(jié)
1、使用緩存機(jī)制,把第一次的結(jié)果保存下來,對(duì)方重試的時(shí)候,直接返回上次的結(jié)果。
2、使用lock ,保證并發(fā)的時(shí)候,若同時(shí)有多個(gè) thread要求相同資料,只會(huì)(到數(shù)據(jù)庫)查第一次,剩下的從 cache讀取。