閱讀目錄:
介紹
之前有童鞋問到關於首次為空的問題,這里簡單補充下:
- 一般來說並發量小、緩存數據量小的網站讓用戶自行觸發頁面讓其緩存即可。
- 大點網站都會多台部署,用負載均衡路由。常見的策略是在每台機器發布應用時,節點從負載均衡節點集合中移除,發布結束后,首次訪問通過人工或自動請求下頁面讓其緩存,也包括預編譯。
- 還有一種是並發量大,緩存數據量大的情況,這也是本文的主題,下面會詳細介紹。
如果緩存數據量大的情況下,預熱就麻煩些。比如LZ公司單在內存中的緩存大小都過G,每次預熱都需要數分鍾,假設放在應用進程內,對運維工作非常不方便的。如果有意外導致進程池回收,對用戶來說就是災難性的。所以需要把應用進程的數據緩存給單獨抽離出來存放,與應用解耦。
關於高並發的解決方案包括緩存更新策略可參見前幾篇博客的介紹。
進程緩存
在網站架構演化中,這個階段就需要引入分布式緩存了,比如memcached、redis。好處就不多講了,壞處就是速度慢。這里速度慢是與本機內存緩存相比,跨機器通信跟直接讀內存差的不是一數量級,對於並發量高、操作頻繁的數據就不適用了。
所以把應用進程緩存的數據抽離出來,放在單獨進程中,給應用提供一層緩存。緩存的業務邏輯、並發處理在獨立進程中做,使用進程通信進行交互。這樣不但解決了數據量大預熱的麻煩,還能解耦部分應用的業務。
另外單獨的進程也可以供外部使用,比如以WCF服務的方式提供給其他子系統使用。
缺點是跨進程讀取的速度比進程內讀取要稍慢。
通信方式
獨立進程與應用進程的幾種常見通信方式:
Namedpipe
Namedpipe一種相對高效的進程通信方式,支持局域網內通信。
Service端:

var txt = File.ReadAllText("b.txt"); ServerPipeConnection PipeConnection = new ServerPipeConnection("MyPipe", 512, 512, 5000, false); Console.WriteLine("listening.."); while (true) { try { PipeConnection.Disconnect(); PipeConnection.Connect(); string request = PipeConnection.Read(); if (!string.IsNullOrEmpty(request)) { PipeConnection.Write(txt); if (request.ToLower() == "break") break; } } catch (Exception ex) { Console.WriteLine(ex.Message); break; } } PipeConnection.Dispose(); Console.Write("press any key to exit.."); Console.Read();
client端:
IInterProcessConnection clientConnection = new ClientPipeConnection("MyPipe", ".");
clientConnection.Connect();
var val = clientConnection.Read();
clientConnection.Close();
Wcf Namedpipe
Wcf在原生namedpipe包裝了一下,使用起來更為簡單方便。
Service端:
ServiceHost host = new ServiceHost( typeof (CacheService));
var NamePipe = new NetNamedPipeBinding();
host.AddServiceEndpoint(typeof(ICacheService), NamePipe, "net.pipe://localhost/CacheService");
host.Open();
Console.WriteLine("服務可用");
Console.ReadLine();
host.Close();
Client端:
ChannelFactory pipeFactory = new ChannelFactory(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/CacheService")); ICacheService pipeProxy = pipeFactory.CreateChannel(); var obj=pipeProxy.GetVal();
SharedMemory
共享內存是進程間通信最快的一種方式,數據無需在進程間復制傳輸,直接開辟一塊公共內存,供其他進程進行讀寫。
service端:
var mmf = MemoryMappedFile.CreateFromFile(@"a.txt", FileMode.Open, "cachea"); Console.ReadLine(); mmf.Dispose();
Client端:
var mmf = MemoryMappedFile.OpenExisting("cachea"); var accessor = mmf.CreateViewAccessor(0, 2000000);
accessor.ReadChar(1000); accessor.Dispose(); mmf.Dispose();
WCF TCP方式
使用WcfTcp的方式,可以供外部網絡使用。
Service端:
var ServiceHost host = new ServiceHost( typeof (CacheService)); host.AddServiceEndpoint(typeof (ICacheService), new NetTcpBinding(), "net.tcp://192.168.0.115:8057/CacheService/"); host.Open(); Console.WriteLine("服務可用"); Console.ReadLine(); host.Close();
Client端:
ChannelFactory NetcpFactory = new ChannelFactory(netTcpBindingBinding,new EndpointAddress("net.tcp://192.168.0.115:8057/CacheService/")); ICacheService tcpProxy= NetcpFactory.CreateChannel(); var obj=tcpProxy.GetVal();
速度對比
上圖是在windows7 i5-3230CPU上跑的,13M和1M文本數據各100次傳輸測試的均值。
其中原生namedpipe相較已經非常快了,在可以接受的范圍,共享內存的方式速度會更快些。
測試結果表明Wcf的namedpipe要慢於wcf-tcp的方式,這個讓人有些意外。
WcfTcp綁的是保留地址:
new ChannelFactory(netTcpBindingBinding, new EndpointAddress("net.tcp://192.168.0.115:8057/CacheService/"));
WcfTcp localhost綁的是127.0.0.1:
new ChannelFactory(netTcpBindingBinding, new EndpointAddress("net.tcp://localhost:8057/CacheService/"));
總結
在大型網站開發中,緩存是個永遠避免不了的話題,也不存在一種方案能解決所有的問題。
而緩存開發過程經常碰到的問題:過期策略(惰性)、緩存更新(獨立)、多級緩存、分布 式緩存(分片)、高可用(單點)、高並發(雪崩)、命中率(穿透)、緩存淘汰(LRU)等。
其多級緩存方案的層級關系大都是由瀏覽器->cdn->反向代理緩存->線程級->內存級->進程級->文件(靜態資源)->分布式(redis)->Db結果。
多數內容在LZ前面博文中有過介紹,有興趣的童鞋可以看看。
參考資源
[1] http://www.codeproject.com/Articles/7176/Inter-Process-Communication-in-NET-Using-Named-Pip