.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
  • 概述:這個WPF項目通過XAML繪製汽車動態速度表盤,實現了0-300的速度刻度,包括數字、指針,並通過定時器模擬速度變化,展示了動態效果。詳細實現包括界面設計、刻度繪製、指針角度計算等,通過C#代碼與XAML文件結合完成。 新建 WPF 項目: 在 Visual Studio 中創建一個新的 WP ...
  • 概述:在WPF中使用`WpfAnimatedGif`庫展示GIF動畫,首先確保全裝了該庫。通過XAML設置Image控制項,指定GIF路徑,然後在代碼中使用庫提供的方法實現動畫控制。這簡化了在WPF應用中處理GIF圖的過程,提供了方便的介面來管理動畫播放和暫停。 當使用 WpfAnimatedGif  ...
  • 您是否曾經訪問過一個網站,它需要很長時間載入,最終你敲擊 F5 重新載入頁面。 即使用戶刷新了瀏覽器取消了原始請求,而對於伺服器來說,API也不會知道它正在計算的值將在結束時被丟棄,刷新五次,伺服器將觸發 5 個請求。 為瞭解決這個問題,ASP.NET Core 為 Web 伺服器提供了一種機制,就 ...
  • 本章將和大家分享如何通過 Elasticsearch 實現自動補全查詢功能。 一、自動補全-安裝拼音分詞器 1、自動補全需求說明 當用戶在搜索框輸入字元時,我們應該提示出與該字元有關的搜索項,如圖: 2、使用拼音分詞 要實現根據字母做補全,就必須對文檔按照拼音分詞。在 GitHub 上恰好有 Ela ...
  • using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Windows.Forms; namespace OOP { pub ...
  • 概述:以上內容詳細介紹了在C#中如何從另一個線程更新GUI,包括基礎功能和高級功能。對於WinForms,使用`Control.Invoke`;對於WPF,使用`Dispatcher.Invoke`。高級功能使用`SynchronizationContext`實現線程間通信,確保清晰、可讀性高的代碼 ...
  • Nuget包 Microsoft.Extensions.Telemetry.Abstractions 包含的新的日誌記錄source generator,它支持使用[LogProperties]將整個對象作為State與日誌一起記錄。 我將展示一種方法來控制如何使用[LogProperties]對象 ...
  • 支持.Net/.Net Core/.Net Framework,可以部署在Docker, Windows, Linux, Mac。 常見的ORM技術(比如:Entity Framework,Dapper,SqlSugar,NHibernate,等…),它們不是在做Sql語句的程式化變種,就是在做Sq ...
  • 一、引言 在現代應用程式開發中,尤其是在涉及I/O操作(如網路請求、文件讀寫等)時,非同步編程成為了提高性能和用戶體驗的關鍵技術。C#作為.NET框架下的主流開發語言,提供了強大的非同步編程支持,通過async/await關鍵字,可以讓開發者以同步的方式編寫非同步代碼,極大地簡化了非同步編程的複雜性。本文將 ...
  • 一、引言 在.NET開發中,操作Office文檔(特別是Excel和Word)是一項常見的需求。然而,在伺服器端或無Microsoft Office環境的場景下,直接使用Office Interop可能會面臨挑戰。為瞭解決這個問題,開源庫NPOI應運而生,它提供了無需安裝Office即可創建、讀取和 ...