C# Redis緩存架構(gòu)設(shè)計(jì)一

一些項(xiàng)目整理出的項(xiàng)目中引入緩存的架構(gòu)設(shè)計(jì)方案磺陡,希望能幫助你更好地管理項(xiàng)目緩存减途,作者水平有限捎废,如有不足還望指點(diǎn)。

一藐鹤、基礎(chǔ)結(jié)構(gòu)介紹

image

項(xiàng)目中對(duì)外提供方法的是CacheProvider和MQProvider兩個(gè)類瓤檐,一切緩存或隊(duì)列應(yīng)用都從這里做入口,后期更換緩存或隊(duì)列只需要更改后面的提供者即可

主要結(jié)構(gòu)設(shè)計(jì)分為三部分:

1娱节、Key管理(用于管理緩存Key挠蛉、過期時(shí)間、是否啟用肄满、調(diào)用識(shí)別Key等)

Configs -> Cache -> KeyConfigList.xml(配置Key的具體信息)

Cache -> Key -> KeyEntity.cs(XML的序列化對(duì)象)

Cache -> Key -> KeyManager.cs(讀取XML并監(jiān)聽XML文件的變更碌秸,如果變更重新讀壬芤啤)

Cache -> Key -> KeyNames.cs(Key名稱的枚舉悄窃,控制Key從這里集中管理讥电,不會(huì)到處都是)

2、內(nèi)部操作(對(duì)接的多個(gè)緩存實(shí)際提供技術(shù)比如Redis轧抗、Memcached恩敌、LocalCache等)

Cache -> Redis -> RedisManager.cs(Redis的連接對(duì)象及基本配置)

3、對(duì)外提供(對(duì)項(xiàng)目中應(yīng)用緩存提供支持函數(shù)横媚,如更改緩存提供技術(shù)只需從這里調(diào)整代碼纠炮,不影響項(xiàng)目主體代碼)

Cache -> CacheProvider.cs(項(xiàng)目中的緩存操作提供函數(shù)類)

MQ -> MQProvider.cs(項(xiàng)目中的隊(duì)列操作提供函數(shù)類)

二、代碼詳細(xì)介紹

1灯蝴、KeyConfigList.xml

用于存儲(chǔ)緩存中數(shù)據(jù)的Key恢口、有效時(shí)間、是否啟用此緩存等配置信息

name:用來尋找此條Key信息的標(biāo)識(shí)

key:緩存中存的Key

validTime:便于計(jì)算此緩存的有效時(shí)間穷躁,比如只緩存5分鐘

enabled:是否啟用此緩存耕肩,不啟用則每次都讀庫(kù)

{0}、{1}问潭、{2}:緩存Key的占位符用于區(qū)分某個(gè)類型的緩存其中的一個(gè)猿诸,比如商品緩存格式為Goods:{0},可能實(shí)際存儲(chǔ)Key是Goods:1狡忙、Goods:2梳虽、Goods:3,這個(gè)1灾茁、2窜觉、3是商品Id來區(qū)分具體某個(gè),如果量大禁用時(shí)會(huì)導(dǎo)致緩存雪崩北专,可以考慮再根據(jù)類型或其他來細(xì)分

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"><?xml version="1.0" encoding="utf-8" ?>
<configuration>

<list>

<item name="Admin_User_Session" key="Admin:User:Session:{0}" validTime="60" enabled="true"></item>

<item name="Admin_User_List" key="Admin:User:List" validTime="30" enabled="true"></item>

<item name="Admin_User_Search" key="Admin:User:Search:{0}:{1}:{2}" validTime="5" enabled="true"></item>
</list>
</configuration></pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

2禀挫、KeyEntity.cs

這個(gè)比較簡(jiǎn)單,就是把xml的內(nèi)容讀取出來序列化為對(duì)象逗余,只是為了便于檢索特咆,name和key都小寫化了

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> /// <summary>
/// Key配置對(duì)象(公開) /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary>
public sealed class KeyEntity
{ private string name; /// <summary>
/// Cache Name(Use for search cache key) /// </summary>
public string Name
{ get { return name; } set { name = value.Trim().ToLower(); }
} private string key; /// <summary>
/// Cache Key /// </summary>
public string Key
{ get { return key; } set { key = value.Trim().ToLower(); }
} /// <summary>
/// Valid Time (Unit:minute) /// </summary>
public int ValidTime { get; set; } /// <summary>
/// Enaled /// </summary>
public bool Enabled { get; set; }
}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

3、 KeyManager.cs

負(fù)責(zé)訪問Key配置的XML文件录粱,并將其緩存到靜態(tài)Hashtable中腻格,使用時(shí)直接從中檢索到要用的信息,設(shè)置監(jiān)聽程序FileSystemWatcher如果文件發(fā)生變動(dòng)則重置Hashtable使其重新讀取啥繁,配置文件及名稱可以自行變更或配置

還要提供根據(jù)KeyName獲取Key配置對(duì)象的方法菜职,這樣就可以使用Key存到實(shí)際的緩存中,如果Key需要進(jìn)行構(gòu)造還可以傳送Key的標(biāo)識(shí)數(shù)組旗闽,從此方法中自動(dòng)整合返回

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> /// <summary>
/// 緩存Key管理 /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary>
public static class KeyManager
{ //KeyName集合
private static Hashtable keyNameList; //鎖對(duì)象
private static object objLock = new object(); //監(jiān)控文件對(duì)象
private static FileSystemWatcher watcher; //緩存Key配置文件路徑
private static readonly string configFilePath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "Configs\Cache\"; //緩存Key配置文件名
private static readonly string configFileName = "KeyConfigList.xml"; /// <summary>
/// 靜態(tài)構(gòu)造只執(zhí)行一次 /// </summary>
static KeyManager()
{ //創(chuàng)建對(duì)配置文件夾的監(jiān)聽酬核,如果遇到文件更改則清空KeyNameList蜜另,重新讀取
watcher = new FileSystemWatcher();
watcher.Path = configFilePath;//監(jiān)聽路徑
watcher.Filter = configFileName;//監(jiān)聽文件名
watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite | NotifyFilters.Size;//僅監(jiān)聽文件創(chuàng)建時(shí)間、文件變更時(shí)間嫡意、文件大小
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;//最后開啟監(jiān)聽
} /// <summary>
/// 讀取KeyName文件 /// </summary>
private static void ReaderKeyFile()
{ if (keyNameList == null || keyNameList.Count == 0)
{ //鎖定讀取xml操作
lock (objLock)
{ //獲取配置文件
string configFile = String.Concat(configFilePath, configFileName); //檢查文件
if (!File.Exists(configFile))
{ throw new FileNotFoundException(String.Concat("file not exists:", configFile));
} //讀取xml文件
XmlReaderSettings xmlSetting = new XmlReaderSettings();
xmlSetting.IgnoreComments = true;//忽略注釋
XmlReader xmlReader = XmlReader.Create(configFile, xmlSetting); //一次讀完整個(gè)文檔
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlReader);
xmlReader.Close();//關(guān)閉讀取對(duì)象 //獲取指定節(jié)點(diǎn)下的所有子節(jié)點(diǎn)
XmlNodeList nodeList = xmlDoc.SelectSingleNode("http://configuration//list").ChildNodes; //獲得一個(gè)線程安全的Hashtable對(duì)象
keyNameList = Hashtable.Synchronized(new Hashtable()); //將xml中的屬性賦值給Hashtable
foreach (XmlNode node in nodeList)
{
XmlElement element = (XmlElement)node;//轉(zhuǎn)為元素獲取屬性
KeyEntity entity = new KeyEntity();
entity.Name = element.GetAttribute("name");
entity.Key = element.GetAttribute("key");
entity.ValidTime = Convert.ToInt32(element.GetAttribute("validTime"));
entity.Enabled = Convert.ToBoolean(element.GetAttribute("enabled"));

                    keyNameList.Add(entity.Name, entity);
                }
            }
        }
    } /// <summary>
    /// 變更事件會(huì)觸發(fā)兩次是正常情況举瑰,是系統(tǒng)保存文件機(jī)制導(dǎo)致 /// </summary>
    /// <param name="source"></param>
    /// <param name="e"></param>
    private static void OnChanged(object source, FileSystemEventArgs e)
    { if (e.ChangeType == WatcherChangeTypes.Changed)
        { if (e.Name.ToLower() == configFileName.ToLower())
            {
                keyNameList = null; //因?yàn)榇耸录?huì)被調(diào)用兩次,所以里面的代碼要有幕等性蔬螟,如果無法實(shí)現(xiàn)幕等性此迅, //則應(yīng)該在Init()中綁定事件 //watcher.Changed += new FileSystemEventHandler(OnChanged); //在OnChanged()事件中解綁事件 //watcher.Changed -= new FileSystemEventHandler(OnChanged);

}
}
} /// <summary>
/// 根據(jù)KeyName獲取Key配置對(duì)象 /// </summary>
/// <param name="name">Key名稱</param>
/// <returns></returns>
public static KeyEntity Get(KeyNames name)
{ return Get(name, null);
} /// <summary>
/// 根據(jù)KeyName獲取Key配置對(duì)象 /// </summary>
/// <param name="name">Key名稱</param>
/// <param name="identities">Key標(biāo)識(shí)(用于替換Key中的{0}占位符)</param>
/// <returns></returns>
public static KeyEntity Get(KeyNames name, params string[] identities)
{ //檢查Hash中是否有值
if (keyNameList == null || keyNameList.Count == 0)
KeyManager.ReaderKeyFile(); //檢查Hash中是否有此Key
string tmpName = name.ToString().ToLower(); if (!keyNameList.ContainsKey(tmpName)) throw new ArgumentException("keyNameList中不存在此KeyName", "name"); var entity = keyNameList[tmpName] as KeyEntity; //檢查Key是否需要含有占位符
if (entity.Key.IndexOf('{') > 0)
{ //檢查參數(shù)數(shù)組是否有值
if (identities != null && identities.Length > 0)
entity.Key = String.Format(entity.Key, identities); else
throw new ArgumentException("需要此參數(shù)identities標(biāo)識(shí)字段,但并未傳遞", "identities");
} return entity;
}

}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

4旧巾、KeyNames.cs

用枚舉類型是為了控制傳遞的KeyName能夠被限制耸序,不會(huì)隨便傳個(gè)string過來導(dǎo)致出錯(cuò),實(shí)際還是使用了KeyNames.Admin_User_Session.ToString()來識(shí)別的鲁猩,此處是根據(jù)枚舉名查找KeyConfigList.xml中的name屬性

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> /// <summary>
/// KeyName枚舉(公開) /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary>
public enum KeyNames
{ /// <summary>
/// 后臺(tái)用戶會(huì)話key /// </summary>
Admin_User_Session,

    Admin_User_List,

    Admin_User_Search

}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

5坎怪、RedisManager.cs

這里可以是Redis也可以是Memcached主要就是提供緩存技術(shù)的管理,熱門的dll有ServiceStack.Redis和StackExchange.Redis廓握,可前者已經(jīng)收費(fèi)(免費(fèi)使用有使用限額)搅窿,無限額免費(fèi)只能用4.0之前的版本,所以采用了后者

IConnectionMultiplexer是核心對(duì)象疾棵,此處使用單例模式創(chuàng)建連接對(duì)象戈钢,因?yàn)閯?chuàng)建連接的資源消耗較高,后面有測(cè)試結(jié)果可以證明

在靜態(tài)構(gòu)造中綁定了幾個(gè)異常事件是尔,如果發(fā)生了錯(cuò)誤可以寫日志便于我們調(diào)試使用殉了,GetDatabase()方法很輕量可以放心直接調(diào)用,配置文件可以采用其他方式

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> /// <summary>
/// Redis緩存管理類 /// Author:taiyonghai /// CreateTime:2017-08-28 /// </summary>
public static class RedisManager
{ //Redis連接對(duì)象
private static IConnectionMultiplexer redisMultiplexer; //程序鎖
private static object objLock = new object(); //Redis連接串(多個(gè)服務(wù)器用逗號(hào)隔開)"10.11.12.237:6379, password='',keepalive=300,connecttimeout=5000,synctimeout=1000"
private static readonly string connectStr = "10.11.12.237:6379"; /// <summary>
/// 靜態(tài)構(gòu)造用于注冊(cè)監(jiān)聽事件 /// </summary>
static RedisManager()
{ //注冊(cè)事件
GetMultiplexer().ConnectionFailed += ConnectionFailed;
GetMultiplexer().InternalError += InternalError;
GetMultiplexer().ErrorMessage += ErrorMessage;
} /// <summary>
/// 連接失敗 /// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void ConnectionFailed(object sender, ConnectionFailedEventArgs e)
{ //e.Exception
} /// <summary>
/// 內(nèi)部錯(cuò)誤 /// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void InternalError(object sender, InternalErrorEventArgs e)
{ //e.Exception
} /// <summary>
/// 發(fā)生錯(cuò)誤 /// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void ErrorMessage(object sender, RedisErrorEventArgs e)
{ //e.Message
} /// <summary>
/// 獲得連接對(duì)象 /// </summary>
/// <returns></returns>
private static IConnectionMultiplexer GetMultiplexer()
{ if (redisMultiplexer == null || !redisMultiplexer.IsConnected)
{ lock (objLock)
{ //創(chuàng)建Redis連接對(duì)象
redisMultiplexer = ConnectionMultiplexer.Connect(connectStr);
}
} return redisMultiplexer;
} /// <summary>
/// 獲得客戶端對(duì)象 /// </summary>
/// <param name="db">選填指明使用那個(gè)數(shù)據(jù)庫(kù)0-16</param>
/// <returns></returns>
public static IDatabase GetClient(int db = -1)
{ return GetMultiplexer().GetDatabase(db);
}

}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

如果每次都ConnectionMultiplexer.Connect()一個(gè)連接對(duì)象的測(cè)試結(jié)果如下:

image

采用單例模式處理連接對(duì)象的測(cè)試結(jié)果如下:

image

6拟枚、CacheProvider.cs

對(duì)項(xiàng)目中提供的緩存操作類薪铜,提供多個(gè)方法,我只提供了String類型和Hash類型恩溅,Set集合類型我用不到就沒有提供隔箍,需要的朋友可以自己添加

image

View Code

7、MQProvider.cs

對(duì)項(xiàng)目中提供的消息隊(duì)列操作類脚乡,我偷懶應(yīng)用了Redis的List類型來提供消息隊(duì)列的操作蜒滩,少數(shù)據(jù)量的情況下比如msg在10k以下性能很好,大數(shù)據(jù)量時(shí)性能下降嚴(yán)重奶稠,有興趣可以百度一下看看測(cè)試俯艰,但他沒有事務(wù)級(jí)的能力所以小規(guī)模使用可以,需求高還是需要更專業(yè)的隊(duì)列比如RabbitMQ等

image

View Code

三锌订、項(xiàng)目調(diào)用代碼

Redis如果遇到同樣Key且同類型(String竹握、Hash、List)時(shí)是直接覆蓋值辆飘,如果不同類型的話就會(huì)報(bào)錯(cuò)了啦辐,我偷懶使用了同一個(gè)KeyNames就使用加前綴的方式來區(qū)分同類型不重復(fù)

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">CacheProvider cache = new CacheProvider();
MQProvider mq = new MQProvider(); //基礎(chǔ)類型
cache.SetString(KeyNames.Cache_Admin_User_Session, "taiyonghai", "100"); var str = cache.GetString(KeyNames.Cache_Admin_User_Session); //Hash類型
var dict = new Dictionary<string, string>();
dict.Add("1", "待處理");
dict.Add("2", "處理中");
dict.Add("3", "處理完成");
cache.SetHash(KeyNames.Cache_Hash_Admin_User_List, dict); var tmpDict = cache.GetHash(KeyNames.Cache_Hash_Admin_User_List); //List隊(duì)列
mq.SetMsg(KeyNames.Msg_Admin_User_Search, "Hello");
mq.GetMsg(KeyNames.Msg_Admin_User_Search);</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谓传,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子芹关,更是在濱河造成了極大的恐慌续挟,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件充边,死亡現(xiàn)場(chǎng)離奇詭異庸推,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)浇冰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聋亡,“玉大人肘习,你說我怎么就攤上這事∑戮螅” “怎么了漂佩?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)罪塔。 經(jīng)常有香客問我投蝉,道長(zhǎng),這世上最難降的妖魔是什么征堪? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任瘩缆,我火速辦了婚禮,結(jié)果婚禮上佃蚜,老公的妹妹穿的比我還像新娘庸娱。我一直安慰自己,他們只是感情好谐算,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布熟尉。 她就那樣靜靜地躺著,像睡著了一般洲脂。 火紅的嫁衣襯著肌膚如雪斤儿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天恐锦,我揣著相機(jī)與錄音往果,去河邊找鬼。 笑死踩蔚,一個(gè)胖子當(dāng)著我的面吹牛棚放,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播馅闽,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼飘蚯,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼馍迄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起局骤,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤攀圈,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后峦甩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赘来,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年凯傲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了犬辰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡冰单,死狀恐怖幌缝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情诫欠,我是刑警寧澤涵卵,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站荒叼,受9級(jí)特大地震影響轿偎,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜被廓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一坏晦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伊者,春花似錦英遭、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至法精,卻和暖如春多律,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搂蜓。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工狼荞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帮碰。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓相味,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親殉挽。 傳聞我的和親對(duì)象是個(gè)殘疾皇子丰涉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理拓巧,服務(wù)發(fā)現(xiàn),斷路器一死,智...
    卡卡羅2017閱讀 134,633評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法肛度,類相關(guān)的語法,內(nèi)部類的語法投慈,繼承相關(guān)的語法承耿,異常的語法,線程的語...
    子非魚_t_閱讀 31,598評(píng)論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,805評(píng)論 0 11
  • 人類是生來就具有損毀欲的生物伪煤。 幼兒會(huì)涂抹白墻加袋,踩踏草坪,拿著折來的樹枝到處揮舞带族。 越是無瑕潔凈的東西就越是不能放...
    洌靈閱讀 225評(píng)論 0 2
  • 有點(diǎn)財(cái)產(chǎn)就要臉了锁荔,有些話張不開口有些事兒下不了手,最后終于進(jìn)化為好面子的偽中產(chǎn)階級(jí)蝙砌,畢竟不好意思自己跟自己承認(rèn),錢...
    茶小狗子閱讀 122評(píng)論 0 0