面試官發(fā)問:什么是高并發(fā)下的請(qǐng)求合并?

從一道面試題說起
前段時(shí)間一個(gè)在深圳的,兩年經(jīng)驗(yàn)的小伙伴出去面試了一圈,收割了幾個(gè)大廠 offer 的同時(shí),還總結(jié)了一下面試的過程中遇到的面試題,面試題有很多,文末的時(shí)候我會(huì)分享給大家。
這次的文章主要分享他面試過程中遇到的一個(gè)場景題:

他說對(duì)于這個(gè)場景題,面試的時(shí)候沒有什么思路。
說真的,請(qǐng)求合并我知道,高并發(fā)無非就是快速的請(qǐng)求合并。
但是在我有限的認(rèn)知里面,如果類似于秒殺的高并發(fā)扣庫存這個(gè)場景,用請(qǐng)求合并的方式來做,我個(gè)人感覺是有點(diǎn)怪怪的不夠傳統(tǒng)。
在傳統(tǒng)的,或者說是業(yè)界常用的秒殺解決方案中,從前端到后臺(tái),你也找不到請(qǐng)求合并的字樣。
我理解請(qǐng)求合并更加適用的場景是查詢類的,或者說是數(shù)值增加類的需求,對(duì)于庫存扣減這種,你稍不留神,就會(huì)出現(xiàn)超賣的情況。
當(dāng)然也有可能是我理解錯(cuò)題意了,看到高并發(fā)扣庫存就想到秒殺場景了。
但是不重要,我們也不能直接和面試官硬剛。

我會(huì)重新給個(gè)我覺得合理的場景,告訴大家我理解的請(qǐng)求合并和高并發(fā)下的請(qǐng)求合并是什么玩意。
請(qǐng)求合并
現(xiàn)在我們拋開秒殺這個(gè)場景。
換一個(gè)更加合適,大家可能更容易理解的場景來聊聊什么是請(qǐng)求合并。
就是熱點(diǎn)賬戶。
什么是熱點(diǎn)賬戶呢?
在第三方支付系統(tǒng)或者銀行這類交易機(jī)構(gòu)中,每產(chǎn)生一筆轉(zhuǎn)入或者轉(zhuǎn)出的交易,就需要對(duì)交易涉及的賬戶進(jìn)行記賬操作。
記賬一般來說涉及到兩個(gè)部分。
交易系統(tǒng)記錄這一筆交易的信息。 賬戶系統(tǒng)需要增加或減少對(duì)應(yīng)的賬戶余額。
如果對(duì)于某個(gè)賬戶操作非常的頻繁,那么當(dāng)我們對(duì)賬戶余額進(jìn)行操作的時(shí)候,就會(huì)涉及到并發(fā)處理的問題。
并發(fā)了怎么辦?
是的,我們可以對(duì)賬戶進(jìn)行加鎖處理。這樣一來,這個(gè)賬戶就涉及到頻繁的加鎖解鎖操作。
這樣我們可以保證數(shù)據(jù)不出問題,但是隨之帶來的問題是隨著并發(fā)的提高,賬戶系統(tǒng)性能下降。
這個(gè)賬戶,就是熱點(diǎn)賬戶,就是性能瓶頸點(diǎn)。
熱點(diǎn)賬戶是業(yè)界的一個(gè)非常常見的問題。
我所了解到的常規(guī)解決方案大概可以分為三種:
異步緩沖記賬。 設(shè)立影子賬戶。 多筆合一記賬。
本小節(jié)主要是介紹“多筆合一記賬”解決方案,從而引出請(qǐng)求合并的概率。
對(duì)于另外兩個(gè)解決方案,就先簡單的說一下。
首先異步緩沖記賬。
我先不解釋,你就看著這個(gè)名字,想著這個(gè)場景,你覺得你會(huì)想到什么?
異步,是不是想到了 MQ?
那么請(qǐng)問你系統(tǒng)里面為什么要引入 MQ 呢?
來,面試八股文背起來:異步處理、系統(tǒng)解耦、削峰填谷。
你說我們當(dāng)前的這個(gè)場景下屬于哪一種情況?
肯定是為了做削峰填谷呀。
假設(shè)賬務(wù)系統(tǒng)的 TPS 是 200 筆每秒,當(dāng)請(qǐng)求低于 200 筆每秒的時(shí)候,賬務(wù)服務(wù)基本上能夠及時(shí)處理馬上返回。
從用戶的角度來說就是:啪的一下,很快啊。我就收到了記賬成功的通知了,也看到賬戶余額發(fā)生了變化。
但是在業(yè)務(wù)高峰期的時(shí)候,流量直接翻倍,每秒過來了 400 筆請(qǐng)求,這個(gè)時(shí)候?qū)τ谫~務(wù)系統(tǒng)來說就是流量洪峰,需要進(jìn)行削峰了,隊(duì)列里面開始堆積著請(qǐng)求,開始排隊(duì)處理了。
在流量低谷的時(shí)候,就可以把這部分?jǐn)?shù)據(jù)消費(fèi)完成。
相當(dāng)于數(shù)據(jù)扔到隊(duì)列里面之后,就可以告訴用戶記賬成功了,錢馬上就到。
但是這個(gè)方案帶來的問題也是很明顯的,如果流量真的爆了,一天都沒有谷讓你填,隊(duì)列里面堆積著大量的請(qǐng)求還沒來得及處理,你怎么辦?
這對(duì)于用戶而言就是:你明明告訴我記賬成功了,為什么我的賬戶余額遲遲沒有變化呢?是不是想陰我錢,我反手就是一波投訴。
另外一個(gè)風(fēng)險(xiǎn)點(diǎn)就是對(duì)于支出類的請(qǐng)求,如果被削峰,很明顯,我們提前就告訴了用戶操作成功,但是真正動(dòng)賬戶余額的時(shí)候已經(jīng)延遲了,所以可能會(huì)出現(xiàn)賬戶透支的情況。
另外一個(gè)設(shè)立影子賬戶的方案,其實(shí)和我們本次的請(qǐng)求合并的主題是另外一個(gè)不同的方向。
它的思想是拆分。
熱點(diǎn)賬戶說到底還是一個(gè)單點(diǎn)問題,那么對(duì)于單點(diǎn)問題,我們用微服務(wù)的思想去解決的話是什么方案?
就是拆分。
假設(shè)這個(gè)熱點(diǎn)賬戶上有 100w,我設(shè)立 10 個(gè)影子賬戶,每個(gè)賬戶 10w ,那么是不是我們的流量就分散了?從一個(gè)賬戶變成了 10 個(gè)賬戶。
壓力也就進(jìn)行了分?jǐn)偂?/p>
這個(gè)方案就有點(diǎn)類似于秒殺場景中的庫存了,庫存我們也可以拆多份。
但是帶來的問題也很明顯。
一是獲取賬戶余額的時(shí)候需要進(jìn)行匯總操作。
二是假設(shè)用戶要扣 11w 呢?我們總余額是夠的,但是每個(gè)影子賬戶上的錢是不夠的。
三是你的影子賬戶選擇的算法是很重要的,是用隨機(jī)?輪訓(xùn)?加權(quán)?這些對(duì)于賬務(wù)成功率都是有比較大的影響的。
另外這個(gè)思想,我在之前的文章中也提到過,有興趣的可以看看其在 JDK 源碼中的應(yīng)用:我從LongAdder中窺探到了高并發(fā)的秘籍,上面只寫了兩個(gè)字...
好了,回到本次的主題:多筆合一筆記賬。
有個(gè)網(wǎng)紅店,生意非常的好,每天很多人在店里面消費(fèi)。
當(dāng)用戶掃碼支付后,請(qǐng)求會(huì)發(fā)送到這個(gè)店對(duì)接的第三方支付公司。
當(dāng)支付公司收到請(qǐng)求,并完成記賬操作后才會(huì)告知商戶用戶支付成功??梢越o用戶商品了。

隨著店里生意越來越好,帶來的問題是第三方支付公司的系統(tǒng)壓力增加,扛不住這么大的并發(fā)了。導(dǎo)致用戶支付成功率的下降或者用戶支付成功后很長時(shí)間才通知到商戶。
那么針對(duì)這個(gè)商戶的賬戶,我們就可以做多筆合一筆處理。
當(dāng)記錄進(jìn)入緩沖流水記錄表之后,我們就可以通知商戶用戶支付成功了,至于錢,你放心,我有定時(shí)任務(wù),一會(huì)就到賬:

所以當(dāng)用戶下單之后,我們只是先記錄數(shù)據(jù),并不去實(shí)際動(dòng)賬戶。等著定時(shí)任務(wù)去觸發(fā)記賬,進(jìn)行多筆合并一筆的操作。
比如下面的這個(gè)示意圖:

商戶實(shí)際有 5 個(gè)用戶支付記錄,但是這 5 筆記錄對(duì)應(yīng)著一條賬戶流水。我們拿著賬戶流水,也是可以追溯到這 5 筆交易記錄的。
這樣的好處是吞吐量上來了,通知及時(shí),用戶體驗(yàn)也好了。但是帶來的弊端是余額并不是一個(gè)準(zhǔn)確的值。
假設(shè)我們的定時(shí)任務(wù)是一小時(shí)匯總一次,那么商戶在后端看到的交易金額可能是一小時(shí)之前的數(shù)據(jù)。
而且這種方案對(duì)于賬戶收錢的場景非常的適合,但是減錢的場景,也是有可能會(huì)出現(xiàn)金額為負(fù)的情況。
不知道你有沒有看出多筆合一筆處理方案的秘密。
如果我們把緩沖流水記錄表看作是一個(gè)隊(duì)列。那么這個(gè)方案抽象出來就是隊(duì)列加上定時(shí)任務(wù)。
所以,請(qǐng)求合并的關(guān)鍵點(diǎn)也是隊(duì)列加上定時(shí)任務(wù)。
文章看到現(xiàn)在,請(qǐng)求合并我們應(yīng)該是大概的了解到了,也確實(shí)是有真實(shí)的應(yīng)用場景。
除了我上面的例子外,比如還有 redis里面的 mget,數(shù)據(jù)庫里面的批量插入,這玩意不就是一個(gè)請(qǐng)求合并的真實(shí)場景嗎?
比如 redis 把多個(gè) get 合并起來,然后調(diào)用 mget。多次請(qǐng)求合并成一次請(qǐng)求,節(jié)約的是網(wǎng)絡(luò)傳輸時(shí)間。
還有真實(shí)的案例是轉(zhuǎn)賬的場景,有的轉(zhuǎn)賬渠道是按次收費(fèi)的,那么作為第三方公司,我們就可以把用戶的請(qǐng)求先放到表里記錄著,等一小時(shí)之后,一起匯總發(fā)起,假設(shè)這一小時(shí)內(nèi)發(fā)生了 10 次轉(zhuǎn)賬,那么 10 次收費(fèi)就變成了 1 次收費(fèi),雖然讓客戶等的稍微久了點(diǎn),但還是在可以接受的范圍內(nèi),這操作節(jié)約的就是真金白銀了。
高并發(fā)的請(qǐng)求合并
理解了請(qǐng)求合并,那我們?cè)賮碚f說當(dāng)他前面加上高并發(fā)這三個(gè)字之后,會(huì)發(fā)生什么變化。
首先不論是在請(qǐng)求合并的前面加上多么狂拽炫酷吊炸天的形容詞,說的多么的天花亂墜,它也還是一個(gè)請(qǐng)求合并。
那么隊(duì)列和定時(shí)任務(wù)的這個(gè)基礎(chǔ)結(jié)構(gòu)肯定是不會(huì)變的。
高并發(fā)的情況下,就是請(qǐng)求量非常的大嘛,那我們把定時(shí)任務(wù)的頻率調(diào)高一點(diǎn)不就行了?
以前 100ms 內(nèi)就會(huì)過來 50 筆請(qǐng)求,我每收到一筆就是立即處理了。
現(xiàn)在我們把請(qǐng)求先放到隊(duì)列里面緩存著,然后每 100ms 就執(zhí)行一次定時(shí)任務(wù)。
100ms 到了之后,就會(huì)有定時(shí)任務(wù)把這 100ms 內(nèi)的所有請(qǐng)求取走,統(tǒng)一處理。
同時(shí),我們還可以控制隊(duì)列的長度,比如只要 50ms 隊(duì)列的長度就達(dá)到了 50,這個(gè)時(shí)候我也進(jìn)行合并處理。不需要等待到 100ms 之后。
其實(shí)寫到這里,高并發(fā)的請(qǐng)求合并的答案已經(jīng)出來了。關(guān)鍵點(diǎn)就三個(gè):
一是需要借助隊(duì)列加定時(shí)任務(wù)實(shí)現(xiàn)。
二是控制定時(shí)任務(wù)的執(zhí)行時(shí)間.
三是控制緩沖隊(duì)列的任務(wù)長度。
方案都想到了,把代碼寫出來豈不是很容易的事情。而且對(duì)于這種面試的場景圖,一般都是討論技術(shù)方案,而不太會(huì)去討論具體的代碼。
當(dāng)討論到具體的代碼的時(shí)候,要么是對(duì)你的方案存疑,想具體的探討一下落地的可行性。要么就是你答對(duì)了,他要準(zhǔn)備從代碼的交易開始衍生另外的面試題了。
總之,大部分情況下,不會(huì)在你給了一個(gè)面試官覺得錯(cuò)誤的方案之后,他還和你討論代碼細(xì)節(jié)。你們都不在一個(gè)頻道了,趕緊換題吧,還聊啥啊。
實(shí)在要往代碼實(shí)現(xiàn)上聊,那么大概率他是在等著你說出一個(gè)框架:Hystrix。
Hystrix框架
其實(shí)這題,你要是知道 Hystrix,很容易就能給出一個(gè)比較完美的回答。
因?yàn)?Hystrix 就有請(qǐng)求合并的功能。給大家演示一下。
假設(shè)我們有一個(gè)學(xué)生信息查詢接口,調(diào)用頻率非常的高。對(duì)于這個(gè)接口我們需要做請(qǐng)求合并處理。
做請(qǐng)求合并,我們至少對(duì)應(yīng)著兩個(gè)接口,一個(gè)是接收單個(gè)請(qǐng)求的接口,一個(gè)處理把單個(gè)請(qǐng)求匯總之后的請(qǐng)求接口。
所以我們需要先提供兩個(gè) service:

其中根據(jù)指定 id 查詢的接口,對(duì)應(yīng)的 Controller 是這樣的:

服務(wù)啟動(dòng)起來后,我們用線程池結(jié)合 CountDownLatch 模擬 20 個(gè)并發(fā)請(qǐng)求:

從控制臺(tái)可以看到,瞬間接受到了 20 個(gè)請(qǐng)求,執(zhí)行了 20 次查詢 sql:

很明顯,這個(gè)時(shí)候我們就可以做請(qǐng)求合并。每收到 10 次請(qǐng)求,合并為一次處理,結(jié)合 Hystrix 代碼就是這樣的,為了代碼的簡潔性,我采用的是注解方式:

在上面的圖片中,有兩個(gè)方法,一個(gè)是 getUserId,直接返回的是null,因?yàn)檫@個(gè)方法體不重要,根本就不會(huì)執(zhí)行。
在 @HystrixCollapser 里面可以看到有一個(gè) batchMethod 的屬性,其值是 getUserBatchById。
也就是說這個(gè)方法對(duì)應(yīng)的批量處理方法就是 getUserBatchById。當(dāng)我們請(qǐng)求 getUserById 方法的時(shí)候,Hystrix 會(huì)通過一定的邏輯,幫我們轉(zhuǎn)發(fā)到 getUserBatchById 上。
所以我們調(diào)用的還是 getUserById 方法:

同樣,我們用線程池結(jié)合 CountDownLatch 模擬 20 個(gè)并發(fā)請(qǐng)求,只是變換了請(qǐng)求地址:

調(diào)用之后,神奇的事情就出現(xiàn)了,我們看看日志:

同樣是接受到了 20 個(gè)請(qǐng)求,但是每 10 個(gè)一批,只執(zhí)行了兩個(gè)sql語句。
從 20 個(gè) sql 到 2 個(gè) sql,這就是請(qǐng)求合并的威力。請(qǐng)求合并的處理速度甚至比單個(gè)處理還快,這也是性能的提升。
那假設(shè)我們只有 5 個(gè)請(qǐng)求過來,不滿足 10 個(gè)這個(gè)條件呢?
別忘了,我們還有定時(shí)任務(wù)呢。
在 Hystrix 中,定時(shí)任務(wù)默認(rèn)是每 10ms 執(zhí)行一次:

同時(shí)我們可以看到,如果不設(shè)置 maxRequestsInBatch,那么默認(rèn)是 Integer.MAX_VALUE。
也就是說,在 Hystrix 中做請(qǐng)求合并,它更加側(cè)重的是時(shí)間方面。
功能演示,其實(shí)就這么簡單,代碼量也不多,有興趣的朋友可以直接搭個(gè) Demo 跑跑看??纯?Hystrix 的源碼。
我這里只是給大家指幾個(gè)關(guān)鍵點(diǎn)吧。
第一個(gè)肯定是我們需要找到方法入口。
你想,我們的 getUserById 方法的方法體里面直接是 return null,也就是說這個(gè)方法體是什么根本就不重要,因?yàn)椴粫?huì)去執(zhí)行方法體中的代碼。它只需要攔截到方法入?yún)ⅲ⒕彺嫫饋?,然后轉(zhuǎn)發(fā)到批量方法中去即可。
然后方法體上面有一個(gè) @HystrixCollapser 注解。
那么其對(duì)應(yīng)的實(shí)現(xiàn)方式你能想到什么?
肯定是 AOP 了嘛。
所以,我們拿著這個(gè)注解的全路徑,進(jìn)行搜索,啪的一下,很快啊,就能找到方法的入口:
com.netflix.hystrix.contrib.javanica.aop.aspectj.HystrixCommandAspect#methodsAnnotatedWithHystrixCommand

在入口處打上斷點(diǎn),就可以開始調(diào)試了:

第二個(gè)我們看看定時(shí)任務(wù)是在哪兒進(jìn)行注冊(cè)的。
這個(gè)就很好找了。我們已經(jīng)知道默認(rèn)參數(shù)是 10ms 了,只需要順著鏈路看一下,哪里的代碼調(diào)用了其對(duì)應(yīng)的 get 方法即可:

同時(shí),我們可以看到,其定時(shí)功能是基于java.util.concurrent.ScheduledThreadPoolExecutor#scheduleAtFixedRate實(shí)現(xiàn)的。
第三個(gè)我們看看是怎么控制超過指定數(shù)量后,就不等待定時(shí)任務(wù)執(zhí)行,而是直接發(fā)起匯總操作的:

可以看到,在com.netflix.hystrix.collapser.RequestBatch#offer方法中,當(dāng) argumentMap 的 size 大于我們指定的 maxBatchSize 的時(shí)候返回了 null。
如果,返回為 null ,那么說明已經(jīng)不能接受請(qǐng)求了,需要立即處理,代碼里面的注釋也說的很清楚了:

以上就是三個(gè)關(guān)鍵的地方,Hystrix 的源碼讀起來,需要下點(diǎn)功夫,大家自己研究的時(shí)候需要做好心理準(zhǔn)備。
最后再貼一個(gè)官方的請(qǐng)求合并工作流程圖:

打完收工。
面試題分享
前面說的深圳的,兩年經(jīng)驗(yàn)的小伙伴把面試題匯總了一份給我,我也分享給大家吧。
Java基礎(chǔ)
volatile關(guān)鍵字底層原理 線程池各個(gè)參數(shù)含義 lock、Synn區(qū)別 ReentrantLock鎖公平與非公平實(shí)現(xiàn)、重入原理 HashMap擴(kuò)容時(shí)機(jī)(容量初始化為1000和10000是否觸發(fā)擴(kuò)容)、機(jī)制、1.7與1.8的差異 ConcurrentHashMap1.7、1.8的優(yōu)化與差異,size方法實(shí)現(xiàn)差異 ThreadLocal原理與風(fēng)險(xiǎn)、為什么會(huì)內(nèi)存泄露 阻塞隊(duì)列的用途、區(qū)別 LinkedBlockingQueue對(duì)列的add、put區(qū)別,實(shí)際過程中如何使用 悲觀鎖、樂觀鎖、自旋鎖的使用場景、實(shí)現(xiàn)方式、優(yōu)缺點(diǎn) Class.forName、loanClass區(qū)別; 線程生命周期、死鎖條件與死鎖避免、狀態(tài)轉(zhuǎn)換關(guān)系(源碼級(jí)別); String intern方法; cas的優(yōu)缺點(diǎn)與解決方案、ABA問題;
JVM相關(guān)
CMS垃圾回收的碎片解決方式 常用的垃圾回收器 JVM垃圾回收器CMS的優(yōu)缺點(diǎn)、與G1的區(qū)別、進(jìn)入老年代的時(shí)機(jī) JVM內(nèi)存模型 JVM調(diào)優(yōu)思路 GC Root、ModUnionTable 偏向鎖、輕量級(jí)鎖、重量級(jí)鎖底層原理、升級(jí)過程 jmap、jstat、top、MAT CMS與G1對(duì)別 GC Root、ModUnionTable;
Redis相關(guān)
Redis高性能原因 Redis的部署模式 RedisCluster底層原理 Redis持久化機(jī)制 緩存淘汰機(jī)制 緩存穿透、緩存雪崩、緩存擊穿發(fā)生場景與解決方案
SQL相關(guān)
MyBatis攔截器的用途 MyBatis動(dòng)態(tài)SQL原理 分庫分表方案設(shè)計(jì) MySQL怎么解決幻讀、原理(源碼級(jí)別) Gap鎖的作用域原理 RR、RC區(qū)別 MySQL默認(rèn)的事務(wù)隔離級(jí)別、Oracle默認(rèn)的事務(wù)隔離級(jí)別 MySQL為啥使用B+樹索引 redo log、binlog、undo log寫入順序、分別保證了ACID的什么特性 數(shù)據(jù)庫樂觀鎖 MySQL優(yōu)化 MySQL底層原理
Spring相關(guān)
@Bean注解、@Component注解區(qū)別 Spring Aop原理 @Aspect和普通AOP區(qū)別 自定義攔截器和Aop那個(gè)先執(zhí)行 web 攔截器 DispatchServlet原理
Dubbo相關(guān)
Dubbo負(fù)載均衡、集群容錯(cuò) Dubbo SPI機(jī)制、Route重寫使用場景 Dubbo RPC底層原理 全鏈路監(jiān)控實(shí)現(xiàn)原理
分布式相關(guān)
分布式鎖的實(shí)現(xiàn)方式 漏斗算法、令牌桶算法 事務(wù)最終一致性解決方案 SLA 分布式事務(wù)實(shí)現(xiàn)方式與區(qū)別 Tcc Confirm失敗怎么辦? 分布式鎖的各種實(shí)現(xiàn)方式、對(duì)比 分布式ID的各種實(shí)現(xiàn)方式、對(duì)比 雪花算法時(shí)鐘回?fù)軉栴}與應(yīng)對(duì)方案 紅鎖算法
設(shè)計(jì)模式
常用的設(shè)計(jì)模式 狀態(tài)模式 責(zé)任鏈模式解決了什么問題 餓漢式、懶漢式優(yōu)缺點(diǎn)、使用場景 模板方法模式、策略模式、單例模式、責(zé)任鏈模式
Zookeeper
Zookeeper底層架構(gòu)設(shè)計(jì) zk一致性
MQ
Kafka順序消息 MQ消息冪等 Kafka高性能秘訣 Kafka高吞吐原理 Rocket事務(wù)消息、延時(shí)隊(duì)列
計(jì)算機(jī)網(wǎng)絡(luò)
瀏覽器輸入一個(gè)url發(fā)生了什么 Http 1.0、1.1、2.0差異 IO多路復(fù)用 TCP四次揮手過程、狀態(tài)切換 XSS、CRSF攻擊與預(yù)防 301、302區(qū)別
Tomcat
Tomcat大概原理
代碼
手寫發(fā)布訂閱模式 大數(shù)(兩個(gè)String)相加
場景問題
打賞排行榜實(shí)現(xiàn) 高并發(fā)下的請(qǐng)求合并 CPU 100%處理經(jīng)驗(yàn) 短鏈系統(tǒng)設(shè)計(jì) 附近的人項(xiàng)目實(shí)現(xiàn) 10w個(gè)紅包秒級(jí)發(fā)送方案 延時(shí)任務(wù)的實(shí)現(xiàn)方案與優(yōu)缺點(diǎn)對(duì)比
說來慚愧,有些題我也答不上來,所以和大家一起查漏補(bǔ)缺吧。
哦,對(duì)了,那個(gè)小伙子最終收割了好幾個(gè)大廠 offer,跑來問我哪個(gè) offer 好。
你說這問題對(duì)我來說那不是超綱了嗎?我也沒在大廠體驗(yàn)過啊。所以我懷疑他不講武德,來騙,來偷襲我這個(gè)老實(shí)巴交的小號(hào)主,我希望他能耗子尾汁,在鵝廠好好發(fā)展:

荒腔走板

周六早上起來,看到新聞?wù)f北京下雪了。成都最近也降溫了。上面的圖片還是我在北京的時(shí)候拍的。
剛剛懷念完北京個(gè)性鮮明的秋天,又迎來了我最喜歡的冬天。
我懷念北方的冬天,那種一進(jìn)屋,眼鏡上蒙起一層薄薄的霧,然后里面就被暖氣包裹起來的感覺。
在北京,冬天進(jìn)屋是先脫下厚厚的棉衣,而在成都,冬天進(jìn)屋是先下意識(shí)的裹緊了身上的衣服。
當(dāng)然,我作為一個(gè)南方人,最喜歡的還是下雪的時(shí)候。在成都的市區(qū)里面,是極少極少能遇到下雪天的,偶爾碰見飄著幾粒雪花,落在地上也決然是不會(huì)有積雪形成的。
而在北京的時(shí)候,看天氣預(yù)報(bào)說是晚上會(huì)下雪,那第二天早上起來都是滿懷期待的拉開窗簾,急迫的想看看白雪覆蓋的北京。
我喜歡那種穿著大頭靴子,踩在積雪上軟軟的,發(fā)出格嘰格嘰的聲音,那是一種屬于北方的聲音。
說來也是可惜,每次北京下雪的時(shí)候,都不合時(shí)宜,導(dǎo)致我都沒有時(shí)間能去故宮。下雪的時(shí)候去故宮,可能也是無數(shù)人可遇而不可求的事情吧。
寫到這里的時(shí)候我本來想多描述一下我多懷念北京的雪景的,但是給家里安裝投影幕布的師傅打電話過來說他已經(jīng)在等電梯了。
那我必須的快速的收尾了。
這個(gè)周末一直在忙著裝家具,弄軟裝方面的事情,有點(diǎn)疲倦。又想著今年馬上就要結(jié)束了。我計(jì)劃的每年都要寫的《我這一年》還沒開始動(dòng)筆,一想著堅(jiān)持寫了 7 年,不會(huì)在今年真的給斷了吧?
一股焦慮就隨之而來。那能怎么辦呢?要么休息休息,要么繼續(xù)肝唄。沒所謂的,焦慮大多是因?yàn)榭吹奶h(yuǎn)了。
沒關(guān)系,那就先走好腳下的路吧。
哦,對(duì)了,新家還沒安裝網(wǎng)絡(luò),我周末在那邊待了兩個(gè)下午,所以這篇文章是我開手機(jī)熱點(diǎn),用流量寫的。不斷更,是我最后的倔強(qiáng)。
好了,師傅在敲門了。
就這樣吧。
最后說一句(求關(guān)注)
好了,看到了這里安排個(gè)“一鍵三連”(轉(zhuǎn)發(fā)、在看、點(diǎn)贊)吧,周更很累的,不要白嫖我,需要一點(diǎn)正反饋。

才疏學(xué)淺,難免會(huì)有紕漏,如果你發(fā)現(xiàn)了錯(cuò)誤的地方,可以在后臺(tái)提出來,我對(duì)其加以修改。
感謝您的閱讀,十分歡迎并感謝您的關(guān)注。


