關(guān)于.net環(huán)境下跨進程、高頻率讀寫數(shù)據(jù)的問題

關(guān)于.net環(huán)境下跨進程、高頻率讀寫數(shù)據(jù)的問題

 

一、需求背景

1、最近項目要求高頻次地讀寫數(shù)據(jù),數(shù)據(jù)量也不是很大,多表總共加起來在百萬條上下。

單表最大的也在25萬左右,歷史數(shù)據(jù)表因為不涉及所以不用考慮,

難點在于這個規(guī)模的熱點數(shù)據(jù),變化非常頻繁。

數(shù)據(jù)來源于一些檢測設(shè)備的采集數(shù)據(jù),一些大表,有可能在極短時間內(nèi)(如幾秒鐘)可能大部分都會變化,

而且主程序也有一些后臺服務(wù)需要不斷輪詢、讀寫某種類型的設(shè)備,所以要求信息交互時間盡可能短。

2、之前的解決方案是把所有熱點數(shù)據(jù),統(tǒng)一加載到共享內(nèi)存里邊,到也能夠支撐的?。ê撩爰壍模?,但是由于系統(tǒng)架構(gòu)升級,之前的程序(20年前的)不能兼容。

只能重新寫一個,最先想到的是用redis,當時把所有api重寫完成后,測試發(fā)現(xiàn)效率不行,是的,你沒有看錯,redis也是有使用范圍的。

3、redis讀寫非常快,但是對于大批量讀寫操作我覺得支持不夠,雖然redis支持批量讀寫,但是效率還是不夠快,

對于字符串(string)類型的批量讀寫,我測試過;效率比較好的在每批次200 至 250條之間,處理20萬條數(shù)據(jù)耗時5秒左右, (pc機,8g,4核)

而對于有序集合(sorted set)類型,批量寫的操作用起來非常別扭,而且沒有修改api(如有其他方式請指教),我測試過,效率沒string類型那么高

其他類型不適合我的業(yè)務(wù)場景,就沒考慮使用了

4、所以項目組最后決定還是用回共享內(nèi)存,先決定在.net環(huán)境下使用c#的共享內(nèi)存,這個功能可能使用的人不多,其實在.net4.0版本就已經(jīng)集成進來了

在system.io.memorymappedfile命名空間下。這個類庫讓人很無語,因為里邊能用的只有write、read這2種方法,而且只是針對字節(jié)的操作,

需要非常多的類型轉(zhuǎn)換,非常麻煩!想想,只能以字節(jié)為單位去構(gòu)建一個需要存放百萬級數(shù)據(jù)的內(nèi)存數(shù)據(jù)庫,得多麻煩?

需要手動搞定索引功能,因為要支持各種查詢,最后花了一天的時間寫完demo,最后測試后發(fā)現(xiàn)效率并沒有很大提高,因為當時加了互斥量測試,

但是離毫秒級差得遠。這個技術(shù)點有興趣的可以了解下,園子里有,如:https://www.cnblogs.com/zeroone/archive/2012/04/18/2454776.html

 

二、沒錯,第一節(jié)寫的太多了

1、最后分析,這應(yīng)該是c#語言的瓶頸,c#對于這種騷操作是不那么成熟的。

2、最后瞄來瞄去,決定使用vc開發(fā)一個dll,在里邊封裝對內(nèi)存數(shù)據(jù)的讀寫功能,然后c#調(diào)用

3、本人的c、c++不那么熟、參考了一些實例,比如園子里的:http://www.cnblogs.com/cwbcwb505/archive/2008/12/08/1350505.html

4、是的,你沒有看錯,2008年的,我還看到一篇更早的,看來底層開發(fā)c、c++那么經(jīng)久不衰不是沒有道理的,很多技術(shù)現(xiàn)在都在用

5、看看什么是共享內(nèi)存

 

三、開始寫代碼了

1、首先建2個控制臺項目,支持mfc,

2、先這樣:一個負責(zé)創(chuàng)建共享內(nèi)存,初始化數(shù)據(jù)

3、再這樣:一個讀寫數(shù)據(jù)測試,最后修改

4、最后修改下圖片細節(jié),測試一下,看看效果

5、完成了,see, 是不是很簡單呀?都會了嗎?

 

四、真的要貼代碼了

1、先定義個枚舉返回狀態(tài)

typedef enum
{
  success = 0,
  alreadyexists = 1,
  error = 2,
  oversize = 3
}enummemory;

2、再定義個結(jié)構(gòu)體用來測試

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、開始創(chuàng)建共享內(nèi)存

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、再開始寫數(shù)據(jù)

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、開始讀數(shù)據(jù)

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了,不復(fù)雜,網(wǎng)上都有這些資料,最后我們貼上測試程序

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("總條數(shù)%d,耗時:%d 毫秒 \n", length, gettickcount() - dwstart);

  // 驗證數(shù)據(jù)
  teng eng5000;
  ceng->read(&eng5000, ceng->size, ceng->size * 5000);
  printf("\n驗證數(shù)據(jù) \n");
  printf("第5000個eng的tagid:%d, engval:%lf \n", eng5000.tagid, eng5000.engval);


  scanf_s("按任意鍵結(jié)束");
  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("寫入數(shù)據(jù)完畢,總條數(shù):%d\n", length);
  printf("初始化值共享內(nèi)存區(qū)耗時:%d 毫秒 \n", gettickcount() - dwstart);


  scanf_s("按任意鍵結(jié)束");
  return 0;
}

8、當然得再貼一遍啦

 

五、差點忘記做成dll了

1、定義外部函數(shù)

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到此為止,可以去領(lǐng)盒飯了,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("總條數(shù){0},耗時:{1} 毫秒", length.tostring(),
              (datetime.now - starttime).totalmilliseconds.tostring());

          // 驗證數(shù)據(jù)
          var intptr100 = marshal.allochglobal(size);
          lib.readfromsharedmemory(intptr100, size, size * 100);

          var eng100 = marshal.ptrtostructure<teng>(intptr100);

          console.writeline();
          console.writeline("驗證數(shù)據(jù)");
          console.writeline("第100個eng的tagid:{0},engval:{1}", eng100.tagid, eng100.engval);

          console.readkey();
      }

4、165毫秒,相比在vc下運行,差了一個數(shù)量級,但是,也不錯了;

因為c#環(huán)境下需要不斷的marshal.ptrtostructure、marshal.structuretoptr,頻繁地把數(shù)據(jù)在托管內(nèi)存俞共享內(nèi)存之間搬運

是需要耗費時間的,這點有更好處理方式的請指教,

 

六、因為跨線程、進程,所以要考慮加入互斥量哦

1、很簡單,mfc下有現(xiàn)成的類cmutex,加在write里邊在看看效率

互斥量是需要耗費資源的,多了將進100毫秒

2、讀寫都加上互斥量試試看

又多了80多毫秒,

魚與熊掌不可兼得啊。要根據(jù)實際運用場景覺得是否加上互斥量

好了,人家51去游玩、我卻宅家里碼程序,可見我的趣味還是挺高的,洗澡、洗衣服、然后去吃飯、一天沒進食了,

以上就是.net環(huán)境下跨進程、高頻率讀寫數(shù)據(jù)的詳細內(nèi)容,更多關(guān)于.net跨進程高頻率讀寫數(shù)據(jù)的資料請關(guān)注碩編程其它相關(guān)文章!

下一節(jié):asp.net core 文件響應(yīng)壓縮的常見使用誤區(qū)

asp.net編程技術(shù)

相關(guān)文章
亚洲国产精品第一区二区,久久免费视频77,99V久久综合狠狠综合久久,国产免费久久九九免费视频