1. 程式人生 > >100臺裝置採集資料,並寫入資料庫

100臺裝置採集資料,並寫入資料庫

需求見: https://bbs.csdn.net/topics/392471595

有 100 臺左右的裝置, 每秒採集一條資料,再向 SQL Server 2008 資料庫寫入資料。

一天的資料量: 100*3600*24=8640000

難點:

  1. 併發量大,資料庫、硬碟壓力大;
  2. 每天的資料量大,必須要分歷史表,做好歸檔

第 2 點屬於資料庫的操作,可以用 SQL Server 的代理作業來完成。

主要是第 1 點。

如果每臺裝置都單獨寫入資料庫,也是可以,但資料庫的效率就比較低了。

我的思路是將這一百臺裝置的資料全部採集,在記憶體中快取,然後用一個專門的執行緒定時寫入到資料庫。

這樣這個表上沒有任何的併發插入,資料庫的壓力將大大降低。


資料庫表的建立指令碼:

--個人測試採用臨時庫 tempdb。 
--正式環境千萬不能用 tempdb,資料在重啟後會消失!
USE tempdb
GO
IF OBJECT_ID('dbo.device_log_data') IS NOT NULL 
	DROP TABLE dbo.device_log_data
GO
CREATE TABLE dbo.device_log_data(
	[logId] INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
	[deviceId] INT NOT NULL,
	[value] INT NOT NULL,
	[deviceTime] DATETIME NOT NULL, 
	[insertTime] DATETIME NOT NULL DEFAULT(GETDATE())
)
GO

程式程式碼:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Timers;

namespace ConsoleApp3
{
    class Program
    {
        static readonly string CONN_STRING = @"Data Source=.\sqlserver2014;Initial Catalog=tempdb;Integrated Security=True";
        static readonly string TABLE_NAME = "device_log_data";
        static List<MyData> myList = new List<MyData>();

        static void Main(string[] args)
        {
            for (int i = 1; i <= 100; i++)
            {
                MyTimer timer = new MyTimer(1000);
                timer.DeiviceId = i;
                timer.Elapsed += Timer_Elapsed;
            }
            MyTimer timerInsert = new MyTimer(10000);
            timerInsert.Elapsed += TimerInsert_Elapsed;

            Console.Read();
        }

        private static void Timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            MyTimer myTimer = (MyTimer)sender;
            lock (myList)
            {
                myList.Add(new MyData() {
                    DeviceId = myTimer.DeiviceId,
                    Value = new Random().Next(1, 1000),
                    DeviceTime = DateTime.Now
                });
            }
        }

        private static void TimerInsert_Elapsed(object sender, ElapsedEventArgs e)
        {
            List<MyData> list = new List<MyData>();
            lock (myList)
            {
                list.AddRange(myList);
                myList.Clear();
            }
            Insert(list);
        }

        private static void Insert(List<MyData> list)
        {
            DataTable dt = new DataTable();
            dt.Columns.Add(new DataColumn("deviceId",typeof(Int32)));
            dt.Columns.Add(new DataColumn("value", typeof(Int32)));
            dt.Columns.Add(new DataColumn("deviceTime", typeof(string)));

            foreach(var item in list)
            {
                DataRow dr = dt.NewRow();
                dr["deviceId"] = item.DeviceId;
                dr["value"] = item.Value;
                dr["deviceTime"] = item.DeviceTime;
                dt.Rows.Add(dr);
            }

            try
            {
                using (SqlBulkCopy bulkcopy = new SqlBulkCopy(CONN_STRING))
                {
                    bulkcopy.DestinationTableName = TABLE_NAME;
                    bulkcopy.ColumnMappings.Add("deviceId", "deviceId");
                    bulkcopy.ColumnMappings.Add("value", "value");
                    bulkcopy.ColumnMappings.Add("deviceTime", "deviceTime");
                    bulkcopy.WriteToServer(dt);
                }
                Console.WriteLine("inserted {0} rows", list.Count);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }
    }
    class MyTimer: Timer
    {
        public MyTimer(double interval)
        {
            this.Interval = interval;
            this.AutoReset = true;
            this.Enabled = true;
        }
        public int DeiviceId { get; set; }
    }
    class MyData
    {
        public int DeviceId { get; set; }
        public int Value { get; set; }
        public DateTime DeviceTime { get; set; }
    }
}

以 Release 來編譯,再執行 .exe 檔案, 檢視系統資源佔用情況:

可見, 效果還是非常不錯的。

需要注意的是,  查詢資料還是應該用 WITH(NOLOCK):

SELECT TOP 10 * FROM dbo.device_log_data WITH(NOLOCK)

有空再寫一個執行緒池的程式碼。