.NET與大數據

来源:https://www.cnblogs.com/s0611163/archive/2023/02/03/17088410.html
-Advertisement-
Play Games

前言 當別人做大數據用Java、Python的時候,我使用.NET做大數據、數據挖掘,這確實是值得一說的事。 寫的並不全面,但都是實際工作中的內容。 .NET在大數據項目中,可以做什麼? 寫腳本(使用控制台程式+頂級語句) 寫工具(使用Winform) 寫介面、寫服務 使用C#寫代碼的優點是什麼? ...


前言

當別人做大數據用Java、Python的時候,我使用.NET做大數據、數據挖掘,這確實是值得一說的事。
寫的並不全面,但都是實際工作中的內容。

.NET在大數據項目中,可以做什麼?

  1. 寫腳本(使用控制台程式+頂級語句)
  2. 寫工具(使用Winform)
  3. 寫介面、寫服務

使用C#寫代碼的優點是什麼?

  1. 靜態類型+匿名類型,一次性使用的實體類就用匿名類型,多次或多個地方使用的實體類就用靜態類型,靜態類型優於Python,匿名類型優於Java。你是不是想說Python也有靜態類型?你倒是寫啊?!
  2. 代碼的可維護性好,這是相對於Python說的,不一定是語言的鍋,還有固有的代碼組織習慣,靜態類型本身就是很好的註釋
  3. 性能好,非同步併發的代碼易編寫。
    想起來一個事,就是前同事用Python2做數據挖掘,先用的es,性能差,改用的clickhouse,我就納悶,es性能差?現在我想我明白了,我看了其中一個挖掘演算法,它需要在雙層迴圈遍歷中去請求es進行查詢,它沒有使用非同步,也沒有使用多線程,那不就是一個線程在查詢嗎?我們現網es伺服器配置這麼強這麼多,它居然用一個線程去同步請求,能快才怪!實際上一個請求耗時極短,因為es有各種緩存,而查詢條件精確可以命中緩存,所以可以併發請求多個es節點。
    那前同事為什麼沒有使用非同步併發或多線程呢?Python2不支持嗎?或者Python2支持,但寫起來不方便?或者前同事不會寫?(原因:寫起來不方便,C#一樣也不太方便,而且會使整個程式的併發請求量變得難以控制,可以針對某個介面單獨優化,但所有介面都這樣寫,也挺麻煩的)

使用.NET開發的優點是什麼?

其中一個優點是應用程式類型豐富,目前我用到的應用程式類型有:

  1. 控制台
  2. Winform
  3. Web API
  4. Blazor
    你是不是想說Java和Python也可以寫控制台、窗體程式、Web API?一個熟悉Ptyhon的程式員,可不一定會寫窗體程式,需要一點時間學習,一個做了幾年.NET的程式員天然會寫Winform,就是拖控制項啊。當然,也可能他們不用Windows。
    每一種應用程式類型,都意味著學習成本,而這些我已經會了,時間就省下了(Blazor一開始不會,學習花了一兩天)。

.NET與ClickHouse

我寫了一個大雜燴腳本項目,裡面有很多工程是查詢ClickHouse統計分析,代碼流程就是讀取Excel數據作為查詢輸入條件,查詢ClickHouse統計分析,統計結果導出到Excel。一個統計分析工作任務小半天就完成了。
用的ORM是我自己寫的Dapper.LiteSql。沒什麼人用,可能是功能不強吧。不過很適合我自己的需求,我自己經常用。
比如:

int count = session.CreateSql<XXX>(@"
    select count(distinct t.xxx, t.xxx, t.xxx) as cnt
    from xxx t
")
.Where(t => t.PassTime >= startTime && t.PassTime <= endTime)
.Where("t.Name in (" + kkNames + ")")
.QuerySingle<int>();

再比如:

var query = session.CreateSql<XXX>(@"
        select t.xxx, t.xxx, t.xxx
        from xxx t
    ")
    .Where(t => t.PassTime >= firstTime && t.PassTime <= firstTime.AddDays(7).AddSeconds(-1));
query.Where(t => plateList.Skip((page - 1) * pageSize).Take(pageSize).ToList().Contains(t.PlateNo));
var temp = query.ToList();

對於統計查詢,我經常SQL和Lambda表達式混寫,感覺這樣非常靈活。
某些情況下,混寫比純Lambda寫法,是要清晰的:

List<XXX> list = session.CreateSql<XXX>(@"
    select xxx, xxx as xxx, max(xxx) as xxx
    from (
    select xxx, toDate(xxx) as xxx, xxx, count(*) as xxx
    from (
    select distinct t.xxx, t.xxx, t.xxx
    from xxx t
").Where(t => t.Xxx != "xxx")
.Where(t => t.XxxTime >= startTime && t.XxxTime <= endTime)
.Where(t => xxxList.Contains(t.Xxx))
.Where(@"(
    (formatDateTime(t.xxx_time ,'%H:%M:%S') >= '07:00:00' and formatDateTime(t.xxx_time ,'%H:%M:%S') <= '08:59:59') or
    (formatDateTime(t.xxx_time ,'%H:%M:%S') >= '14:00:00' and formatDateTime(t.xxx_time ,'%H:%M:%S') <= '20:59:59')
)")
.Append(@")")
.GroupBy("xxx, xxx, xxx")
.Append(@") 
    group by xxx, xxx
")
.QueryList<XXX>();

上述代碼說明:

  1. group by寫了兩種寫法比較隨意
  2. 三層select嵌套,當然主流ORM都能實現,但不一定易編寫、易閱讀
  3. 我不用針對ClickHouse去實現formatDateTime,也不用實現toDate、max、distinct、count,也不用糾結是count(*)還是count(1),只要實現的功能足夠少,BUG就少。

.NET與ElasticSearch

本打算使用Elasticsearch.Net,為什麼沒有使用?

  1. 學習成本,項目中沒有學習時間,雖然造測試數據是本職工作,但寫小工具不是本職工作不能耽誤太多時間,所以沒有學習時間
  2. 我使用HttpClient查詢es,這種查詢es的方式和kibana中寫的查詢語句、以及前同事留下的創建索引的文檔、模板最接近,方便抄現成的。下麵是一個完整的查詢es方法:
public async Task<TicketAgg> QueryAgg(string strStartTime, string strEndTime, string idCard)
{
    Stopwatch sw = Stopwatch.StartNew();

    string esUrl = $"http://{esIPs[_rnd.Next(0, esIPs.Length)]}:24100/out_xxx/_search";

    var esQueryBody = new
    {
        size = 0,
        query = new
        {
            @bool = new
            {
                must = new dynamic[]
                {
                    new
                    {
                        range = new
                        {
                            travel_time = new
                            {
                                gte = strStartTime,
                                lte = strEndTime,
                                format = "yyyyMMddHHmmss"
                            }
                        }
                    },
                    new
                    {
                        match_phrase = new
                        {
                            zjhm = idCard
                        }
                    }
                }
            }
        },
        aggs = new
        {
            countByZjhm = new
            {
                terms = new
                {
                    field = "zjhm",
                    size = 10000
                }
            }
        }
    };

    string esPostData = JsonConvert.SerializeObject(esQueryBody);
    Console.WriteLine($"ES請求URL:{esUrl}");
    Console.WriteLine($"ES請求參數:{esPostData}");
    HttpClient httpClient = HttpClientFactory.GetClient();
    HttpContent content = new StringContent(esPostData);
    content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
    string strEsResult = await (await httpClient.PostAsync(esUrl, content)).Content.ReadAsStringAsync();
    var resultObj = new
    {
        took = 0,
        aggregations = new
        {
            countByZjhm = new
            {
                buckets = new[]
                {
                    new
                    {
                        key = "",
                        doc_count = 0
                    }
                }
            }
        }
    };
    var esResult = JsonConvert.DeserializeAnonymousType(strEsResult, resultObj);

    TicketAgg agg = new TicketAgg();
    agg.IdCard = idCard;
    agg.Count = esResult.aggregations.countByZjhm.buckets[0].doc_count;

    sw.Stop();
    Console.WriteLine($"統計數據,耗時:{sw.Elapsed.TotalSeconds.ToString("0.000")} 秒");

    return agg;
}

代碼中esQueryBody和resultObj都是一次性使用的,直接用匿名動態類型,而TicketAgg是需要實例化作為返回值給其它方法使用的,所以定義成靜態類型。
評論區有人問可選條件怎麼寫,代碼如下:

string strStartTime = DateTime.Now.AddDays(-7).ToString("yyyyMMddHHmmss");
string strEndTime = DateTime.Now.ToString("yyyyMMddHHmmss");
string idCard = "33";

var esQueryBody = new
{
    size = 10000,
    query = new
    {
        @bool = new
        {
            must = new List<dynamic>
            {
                new
                {
                    range = new
                    {
                        travel_time = new
                        {
                            gte = strStartTime,
                            lte = strEndTime,
                            format="yyyyMMddHHmmss"
                        }
                    }
                }
            }
        }
    }
};

if (idCard != null)
{
    [email protected](new
    {
        match_phrase = new
        {
            zjhm = idCard
        }
    });
}

string esPostData = JsonConvert.SerializeObject(esQueryBody);

上述代碼說明:

  1. must原來是dynamic[],它的長度是不可變的,不方便追加,所以修改成List,就可以動態追加了。
  2. 寫這段代碼,我沒有百度,沒有找文檔,花了幾分鐘試出來的。優秀的語法可以讓使用者舉一反三。

下麵一段代碼,生產測試數據用的:

public async Task MockXxxData(string indexName, int count, DateTime startDate, DateTime endDate, string[] departures, string[] destinations, dynamic peoples)
{
    int days = (int)endDate.Subtract(startDate).TotalDays;

    List<Task> taskList = new List<Task>();
    for (int i = 0; i < count; i++)
    {
        DateTime date = startDate.AddDays(_rnd.Next(0, days + 1));
        long time = (long)(_rnd.NextDouble() * 3600 * 24);
        var people = peoples[_rnd.Next(0, peoples.Length)];

        var esRequestBody = new
        {
            xxx_type = _rnd.Next(1, 4).ToString(),
            zjlx = "xxx",
            zjhm = people.zjhm,
            xm = people.xm,
            departure = departures[_rnd.Next(0, departures.Length)],
            destination = destinations[_rnd.Next(0, destinations.Length)],
            xxx_date = date.ToString("yyyyMMdd"),
            xxx_time = date.AddSeconds(time).ToString("yyyyMMddHHmmss"),
            xxx_time = date.AddSeconds(time).AddHours(0.5 + _rnd.NextDouble()).ToString("yyyyMMddHHmmss"),
            xxx_time = date.AddSeconds(time).AddDays(-2 + _rnd.NextDouble()).ToString("yyyyMMddHHmmss"),
            xxx = "",
            xxx = ""
        };

        var task = ServiceFactory.Get<EsWriteService>().Write(indexName, esRequestBody);
        taskList.Add(task);
    }
    await Task.WhenAll(taskList);
}

上述代碼說明:

  1. 程式跑起來生產數據,一般會有幾十個線程,也就是請求es的併發量是幾十
  2. 如果你覺得幾十的併發量,還是有點高,可以在調用的Write非同步方法中使用Semaphore類限制一下併發量,代碼如下:
private Semaphore _sem = new Semaphore(20, 20); //限制非同步請求的併發數量

public async Task<bool> Write(string indexName, dynamic esRequestBody)
{
    _sem.WaitOne();
    try
    {
        Stopwatch sw = new Stopwatch();
        sw.Start();

        indexName = $"{indexName}-{DateTime.Now.Year}-{DateTime.Now.Month:00}";
        string esUrl = $"http://{esIPs[_rnd.Next(0, esIPs.Length)]}:24100/{indexName}/doc";

        string esRequestData = JsonConvert.SerializeObject(esRequestBody);
        HttpContent content = new StringContent(esRequestData);
        content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
        HttpClient httpClient = HttpClientFactory.GetClient();
        string strEsResult = await (await httpClient.PostAsync(esUrl, content)).Content.ReadAsStringAsync();
        var resultObj = new
        {
            status = 0
        };
        var esResult = JsonConvert.DeserializeAnonymousType(strEsResult, resultObj);

        sw.Stop();
        _log?.Info($"【寫入ES索引】【{(esResult.status == 0 ? "成功" : "失敗")}】耗時:{sw.Elapsed.TotalSeconds:0.000} 秒,索引名稱:{indexName},請求URL:{esUrl},請求參數:{esRequestData}");
        return esResult.status == 0;
    }
    catch
    {
        throw;
    }
    finally
    {
        _sem.Release();
    }
}

用到的庫

評論區有人問技術棧,這裡列一下主要的庫:

  1. Microsoft.Extensions.DependencyInjection 和 Autofac (依賴註入)
  2. AutoMapper (實體類映射)
  3. Microsoft.Extensions.Http (HttpClient,用於操作ElasticSearch、網路請求)
  4. Quartz (定時任務)
  5. Dapper、Dapper.LiteSql (ORM)
  6. Newtonsoft.Json (Json序列化)
  7. ClickHouse.Client (操作ClickHouse)
  8. Oracle.ManagedDataAccess.Core (操作Oracle)
  9. MySqlConnector (操作MySQL)

我最近寫了哪些工程

  1. 大雜燴腳本工程,包括查詢clickhouse統計分析輸出Excel、查詢MySQL和Oracle、各種小腳本工具
  2. Blazor工程,做了一套簡單的增刪改查,精力有限,自己測試用,不用手動改資料庫了
  3. 數據挖掘服務,主要是Web API和定時任務
  4. Winform工具,用於測試時創建ES索引、生產模擬數據。為什麼寫這個?因為做數據挖掘,不給數據,只能自己造了。

為什麼從這篇博客看起來這個項目只有我一個人在做?沒團隊?

還有項目經理、產品經理、前端等一共幾個人,項目資金投入少,所以不可能有很多人的。

為什麼沒有使用Python?

我一開始是想使用Python的,但就我用.NET寫的這些東西,如果改用Python,沒個2、3年經驗,寫不順暢。

我用.NET做一個項目,Swagger有了,創建工程時自帶的,當然Python的Swagger也是有的,你可以百度"python 從註釋自動生成 swagger",之前看到過一個不錯的,沒保存,一時半會就找不到了。
用Blazor做了簡單的配置頁面,測試時不用去手動修改資料庫了
寫了一個Mock工程,生產模擬測試數據,寫入速度可以達到6000條/秒(一條數據請求一次,不是批量寫入),界面如下:

最後

寫此博客是為了給.NET正名,在大數據項目中,.NET大有可為。
我寫代碼沒有用到什麼特別的技術,看起來很簡單,但也不是隨便學學就能寫,沒個3、5年經驗,很難寫的這麼快。
我寫代碼也沒有什麼條條框框,可能不規範,但很靈活。
例如,winform程式註入日誌工具類怎麼寫?來不急百度了,就這麼寫吧,一樣每秒6000條的狂寫日誌,還不卡界面:

public partial class Form1 : Form, ILog
{
    ...省略

    public Form1()
    {
        InitializeComponent();

        ...省略

        //註入日誌工具類
        ServiceFactory.Get<IndexCreationService>().InjectLog(this);
        ServiceFactory.Get<EsWriteService>().InjectLog(this);
        ServiceFactory.Get<MockDataService>().InjectLog(this);
    }
}

internal class EsWriteService : ServiceBase
{
    ...省略
    private ILog? _log;
    public void InjectLog(ILog log) => _log = log;

    public async Task<bool> Write(string indexName, dynamic esRequestBody)
    {
        ...省略
        _log?.Info("xxx");
        ...省略
    }
}

就目前這些項目、腳本、工具而言,感覺這就是我寫的最佳實踐。不知道最佳實踐,代碼也能寫,容易寫成屎山,要麼寫的服務三天兩頭崩。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 基於php+webuploader的大文件分片上傳,帶進度條,支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況)。文件上傳前先檢測該文件是否已上傳,如果已上傳提示“文件已存在”,如果未上傳則直接上傳。視頻上傳時會根據設定的參數(分片大小、分片數量)進行上傳,上傳過程中會在目標文件夾中生成一個臨 ...
  • 背景 我們的業務共使用11台(阿裡雲)伺服器,使用SpringcloudAlibaba構建微服務集群,共計60個微服務,全部註冊在同一個Nacos集群 流量轉發路徑: nginx->spring-gateway->業務微服務 使用的版本如下: spring-boot.version:2.2.5.RE ...
  • 摘要:在jvm中有很多的參數可以進行設置,這樣可以讓jvm在各種環境中都能夠高效的運行。絕大部分的參數保持預設即可。 本文分享自華為雲社區《為什麼需要對jvm進行優化,jvm運行參數之標準參數》,作者:共飲一杯無。 我們為什麼要對jvm做優化? 在本地開發環境中我們很少會遇到需要對jvm進行優化的需 ...
  • 1 簡介 我們之前使用了dapr的本地托管模式,但在生產中我們一般使用Kubernetes托管,本文介紹如何在GKE(GCP Kubernetes)安裝dapr。 相關文章: dapr本地托管的服務調用體驗與Java SDK的Spring Boot整合 dapr入門與本地托管模式嘗試 2 安裝GKE ...
  • 前段時間因業務需要完成了一個工作流組件的編碼工作。藉著這個機會跟大家分享一下整個創作過程,希望大家喜歡,組件暫且命名為"easyFlowable"。 接下來的文章我將從什麼是工作流、為什麼要自研這個工作流組件、架構設計三個維度跟大家來做個整體介紹。 ...
  • 本文介紹基於Python語言中TensorFlow的Keras介面,實現深度神經網路回歸的方法。 1 寫在前面 前期一篇文章Python TensorFlow深度學習回歸代碼:DNNRegressor詳細介紹了基於TensorFlow tf.estimator介面的深度學習網路;而在TensorFl ...
  • 某一日晚上上線,測試同學在回歸項目黃金流程時,有一個工單項目介面報JSF序列化錯誤,馬上升級對應的client包版本,編譯部署後錯誤消失。 線上問題是解決了,但是作為程式員要瞭解問題發生的原因和本質。但這都是為什麼呢? ...
  • 前言 本文寫給想學C#的朋友,目的是以儘快的速度入門 C#好學嗎? 對於這個問題,我以前的回答是:好學!但仔細想想,不是這麼回事,對於新手來說,C#沒有那麼好學。 反而學Java還要容易一些,學Java Web就行了,就是SpringBoot那一套。 但是C#方向比較多,你是學控制台程式、WebAP ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...