關于.net環境下跨進程、高頻率讀寫數據的問題
一、需求背景
1、最近項目要求高頻次地讀寫數據,數據量也不是很大,多表總共加起來在百萬條上下。
單表最大的也在25萬左右,歷史數據表因為不涉及所以不用考慮,
難點在于這個規模的熱點數據,變化非常頻繁。
數據來源于一些檢測設備的采集數據,一些大表,有可能在極短時間內(如幾秒鐘)可能大部分都會變化,
而且主程序也有一些后臺服務需要不斷輪詢、讀寫某種類型的設備,所以要求信息交互時間盡可能短。
2、之前的解決方案是把所有熱點數據,統一加載到共享內存里邊,到也能夠支撐的住(毫秒級的),但是由于系統架構升級,之前的程序(20年前的)不能兼容。
只能重新寫一個,最先想到的是用redis,當時把所有api重寫完成后,測試發現效率不行,是的,你沒有看錯,redis也是有使用范圍的。
3、redis讀寫非常快,但是對于大批量讀寫操作我覺得支持不夠,雖然redis支持批量讀寫,但是效率還是不夠快,
對于字符串(string)類型的批量讀寫,我測試過;效率比較好的在每批次200 至 250條之間,處理20萬條數據耗時5秒左右, (pc機,8g,4核)
而對于有序集合(sorted set)類型,批量寫的操作用起來非常別扭,而且沒有修改api(如有其他方式請指教),我測試過,效率沒string類型那么高
其他類型不適合我的業務場景,就沒考慮使用了
4、所以項目組最后決定還是用回共享內存,先決定在.net環境下使用c#的共享內存,這個功能可能使用的人不多,其實在.net4.0版本就已經集成進來了
在system.io.memorymappedfile命名空間下。這個類庫讓人很無語,因為里邊能用的只有write、read這2種方法,而且只是針對字節的操作,
需要非常多的類型轉換,非常麻煩!想想,只能以字節為單位去構建一個需要存放百萬級數據的內存數據庫,得多麻煩?
需要手動搞定索引功能,因為要支持各種查詢,最后花了一天的時間寫完demo,最后測試后發現效率并沒有很大提高,因為當時加了互斥量測試,
但是離毫秒級差得遠。這個技術點有興趣的可以了解下,園子里有,如:https://www.cnblogs.com/zeroone/archive/2012/04/18/2454776.html
二、沒錯,第一節寫的太多了
1、最后分析,這應該是c#語言的瓶頸,c#對于這種騷操作是不那么成熟的。
2、最后瞄來瞄去,決定使用vc開發一個dll,在里邊封裝對內存數據的讀寫功能,然后c#調用
3、本人的c、c++不那么熟、參考了一些實例,比如園子里的:http://www.cnblogs.com/cwbcwb505/archive/2008/12/08/1350505.html
4、是的,你沒有看錯,2008年的,我還看到一篇更早的,看來底層開發c、c++那么經久不衰不是沒有道理的,很多技術現在都在用
5、看看什么是共享內存
三、開始寫代碼了
1、首先建2個控制臺項目,支持mfc,
2、先這樣:一個負責創建共享內存,初始化數據
3、再這樣:一個讀寫數據測試,最后修改
4、最后修改下圖片細節,測試一下,看看效果
5、完成了,see, 是不是很簡單呀?都會了嗎?
四、真的要貼代碼了
1、先定義個枚舉返回狀態
typedef enum { success = 0, alreadyexists = 1, error = 2, oversize = 3 }enummemory;
2、再定義個結構體用來測試
typedef struct { int tagid; char tagname[32]; int area; double engval; double updatetime; double rawmax; double rawmin; double rawval; char name[50]; char al; double astime; char maskstate; double amtime; char cf; char tdf; char alarmcode[32]; }teng;
3、開始創建共享內存
int create(uint size) { // data handle filemap = createfilemapping(invalid_handle_value, null, page_readwrite, 0, size, “name”); if (filemap == null || filemap == invalid_handle_value) return error; if (getlasterror() == error_already_exists) return alreadyexists; // init void *mapview = mapviewoffile(filemap, file_map_write, 0, 0, size); if (mapview == null) return error; else memset(mapview, 0, size); return success; }
4、再開始寫數據
int write(void *pdate, uint nsize, uint offset) { // open handle filemap = openfilemapping(file_map_write, false, “name”); if (filemap == null) return error; // hander void *mapview = mapviewoffile(filemap, file_map_write, 0, 0, nsize); if (mapview == null) return error; else writedataptr = mapview; // write memcpy(mapview, pdate, nsize); unmapviewoffile(pmapview); return success; }
5、開始讀數據
int read(void *pdata, uint nsize, uint offset) { // open handle filemap = openfilemapping(file_map_read, false, gettablename()); if (filemap == null) return error; // hander void *pmapview = mapviewoffile(filemap, file_map_read, 0, 0, nsize); if (pmapview == null) return error; else readdataptr = pmapview; memcpy(pdata, (pmapview, nsize); unmapviewoffile(pmapview); return success; }
6、ok了,不復雜,網上都有這些資料,最后我們貼上測試程序
int _tmain(int argc, tchar* argv[], tchar* envp[]) { int length = 100000; ceng * ceng = new ceng(); dword dwstart = gettickcount(); for (int i = 0; i < length; i++) { teng eng; ceng->read(&eng, ceng->size, ceng->size * i); eng.engval = i; ceng->write(&eng, ceng->size, (i*ceng->size)); if (i % 10000 == 0 || i == length - 1) printf("正在讀寫的eng.tagname:%s \n", eng.tagname); } printf("總條數%d,耗時:%d 毫秒 \n", length, gettickcount() - dwstart); // 驗證數據 teng eng5000; ceng->read(&eng5000, ceng->size, ceng->size * 5000); printf("\n驗證數據 \n"); printf("第5000個eng的tagid:%d, engval:%lf \n", eng5000.tagid, eng5000.engval); scanf_s("按任意鍵結束"); return 0; }
7、還有寫測試程序
int _tmain(int argc, tchar* argv[], tchar* envp[]) { int length = 100000; ceng * ceng = new ceng(); ceng->create(ceng->size * length); dword dwstart = gettickcount(); for (int i = 0; i < length; i++) { teng eng; memset(&eng, 0, ceng->size); eng.tagid = i; sprintf_s(eng.alarmcode, "alarmcode.%d", i); sprintf_s(eng.tagname, "tagname.%d", i); if (i % 10000 == 0 || i == length - 1) printf("正在寫入的eng.tagname:%s \n", eng.tagname); ceng->write(&eng, ceng->size, (i*ceng->size)); } // print time printf("寫入數據完畢,總條數:%d\n", length); printf("初始化值共享內存區耗時:%d 毫秒 \n", gettickcount() - dwstart); scanf_s("按任意鍵結束"); return 0; }
8、當然得再貼一遍啦
五、差點忘記做成dll了
1、定義外部函數
extern "c" __declspec(dllexport) int readfromsharedmemory(teng *pdata, int nsize, int offset) { return ceng->read(pdata, nsize, offset); } extern "c" __declspec(dllexport) int writetosharedmemory(void *pdata, int nsize, int offset) { return ceng->write(pdata, nsize, offset); }
2、好了,vc到此為止,可以去領盒飯了,c#進場
public class lib { [dllimport("consoleapplication4.dll", callingconvention = callingconvention.cdecl)] public static extern int readfromsharedmemory(intptr pdata, int nsize, int offset); [dllimport("consoleapplication4.dll", callingconvention = callingconvention.cdecl)] public static extern int writetosharedmemory(intptr pdata, int nsize, int offset); }
3、c#測試一下
static void main(string[] args) { var length = 100000; var starttime = datetime.now; var size = marshal.sizeof(typeof(teng)); var intptrout = marshal.allochglobal(size); var intptrin = marshal.allochglobal(size); for (var i = 0; i < length; i++) { lib.readfromsharedmemory(intptrout, size, size * i); var eng = marshal.ptrtostructure<teng>(intptrout); eng.engval = i; marshal.structuretoptr(eng, intptrin, true); lib.writetosharedmemory(intptrin, size, size * i); if (i % 10000 == 0) console.writeline("eng.tagid:{0}", eng.tagid); } console.writeline("總條數{0},耗時:{1} 毫秒", length.tostring(), (datetime.now - starttime).totalmilliseconds.tostring()); // 驗證數據 var intptr100 = marshal.allochglobal(size); lib.readfromsharedmemory(intptr100, size, size * 100); var eng100 = marshal.ptrtostructure<teng>(intptr100); console.writeline(); console.writeline("驗證數據"); console.writeline("第100個eng的tagid:{0},engval:{1}", eng100.tagid, eng100.engval); console.readkey(); }
4、165毫秒,相比在vc下運行,差了一個數量級,但是,也不錯了;
因為c#環境下需要不斷的marshal.ptrtostructure、marshal.structuretoptr,頻繁地把數據在托管內存俞共享內存之間搬運
是需要耗費時間的,這點有更好處理方式的請指教,
六、因為跨線程、進程,所以要考慮加入互斥量哦
1、很簡單,mfc下有現成的類cmutex,加在write里邊在看看效率
互斥量是需要耗費資源的,多了將進100毫秒
2、讀寫都加上互斥量試試看
又多了80多毫秒,
魚與熊掌不可兼得啊。要根據實際運用場景覺得是否加上互斥量
好了,人家51去游玩、我卻宅家里碼程序,可見我的趣味還是挺高的,洗澡、洗衣服、然后去吃飯、一天沒進食了,
以上就是.net環境下跨進程、高頻率讀寫數據的詳細內容,更多關于.net跨進程高頻率讀寫數據的資料請關注碩編程其它相關文章!