漲姿勢瞭解一下Kafka消費位移可好?

来源:https://www.cnblogs.com/liuyanling/archive/2020/06/16/13149400.html
-Advertisement-
Play Games

摘要:Kafka中的位移是個極其重要的概念,因為數據一致性、準確性是一個很重要的語義,我們都不希望消息重覆消費或者丟失。而位移就是控制消費進度的大佬。本文就詳細聊聊kafka消費位移的那些事,包括: 概念剖析 kafka的兩種位移 關於位移(Offset),其實在kafka的世界里有兩種位移: 分區 ...


摘要:Kafka中的位移是個極其重要的概念,因為數據一致性、準確性是一個很重要的語義,我們都不希望消息重覆消費或者丟失。而位移就是控制消費進度的大佬。本文就詳細聊聊kafka消費位移的那些事,包括:

概念剖析

kafka的兩種位移

關於位移(Offset),其實在kafka的世界里有兩種位移:

  • 分區位移:生產者向分區寫入消息,每條消息在分區中的位置信息由一個叫offset的數據來表徵。假設一個生產者向一個空分區寫入了 10 條消息,那麼這 10 條消息的位移依次是 0、1、…、9;

  • 消費位移:消費者需要記錄消費進度,即消費到了哪個分區的哪個位置上,這是消費者位移(Consumer Offset)。

註意,這和上面所說的消息在分區上的位移完全不是一個概念。上面的“位移”表徵的是分區內的消息位置,它是不變的,即一旦消息被成功寫入到一個分區上,它的位移值就是固定的了。而消費者位移則不同,它可能是隨時變化的,畢竟它是消費者消費進度的指示器。

消費位移

消費位移,記錄的是 Consumer 要消費的下一條消息的位移,切記,是下一條消息的位移! 而不是目前最新消費消息的位移

假設一個分區中有 10 條消息,位移分別是 0 到 9。某個 Consumer 應用已消費了 5 條消息,這就說明該 Consumer 消費了位移為 0 到 4 的 5 條消息,此時 Consumer 的位移是 5,指向了下一條消息的位移。

至於為什麼要有消費位移,很好理解,當 Consumer 發生故障重啟之後,就能夠從 Kafka 中讀取之前提交的位移值,然後從相應的位移處繼續消費,從而避免整個消費過程重來一遍。就好像書簽一樣,需要書簽你才可以快速找到你上次讀書的位置。

那麼瞭解了位移是什麼以及它的重要性,我們自然而然會有一個疑問,kafka是怎麼記錄、怎麼保存、怎麼管理位移的呢?

位移的提交

Consumer 需要上報自己的位移數據,這個彙報過程被稱為位移提交。因為 Consumer 能夠同時消費多個分區的數據,所以位移的提交實際上是在分區粒度上進行的,即Consumer 需要為分配給它的每個分區提交各自的位移數據。

鑒於位移提交甚至是位移管理對 Consumer 端的巨大影響,KafkaConsumer API提供了多種提交位移的方法,每一種都有各自的用途,這些都是本文將要談到的方案。

void commitSync(Duration timeout);
void commitSync(Map<TopicPartition, OffsetAndMetadata> offsets);
void commitSync(final Map<TopicPartition, OffsetAndMetadata> offsets, final Duration timeout);
void commitAsync();
void commitAsync(OffsetCommitCallback callback);
void commitAsync(Map<TopicPartition, OffsetAndMetadata> offsets, OffsetCommitCallback callback);

先粗略的總結一下。位移提交分為自動提交和手動提交;而手動提交又分為同步提交和非同步提交。

自動提交

當消費配置enable.auto.commit=true的時候代表自動提交位移。

自動提交位移是發生在什麼時候呢?auto.commit.interval.ms預設值是50000ms。即kafka每隔5s會幫你自動提交一次位移。自動位移提交的動作是在 poll()方法的邏輯里完成的,在每次真正向服務端發起拉取請求之前會檢查是否可以進行位移提交,如果可以,那麼就會提交上一次輪詢的位移。假如消費數據量特別大,可以設置的短一點。

越簡單的東西功能越不足,自動提交位移省事的同時肯定會帶來一些問題。自動提交帶來重覆消費和消息丟失的問題:

  • 重覆消費: 在預設情況下,Consumer 每 5 秒自動提交一次位移。現在,我們假設提交位移之後的 3 秒發生了 Rebalance 操作。在 Rebalance 之後,所有 Consumer 從上一次提交的位移處繼續消費,但該位移已經是 3 秒前的位移數據了,故在 Rebalance 發生前 3 秒消費的所有數據都要重新再消費一次。雖然你能夠通過減少 auto.commit.interval.ms 的值來提高提交頻率,但這麼做只能縮小重覆消費的時間視窗,不可能完全消除它。這是自動提交機制的一個缺陷。

  • 消息丟失: 假設拉取了100條消息,正在處理第50條消息的時候,到達了自動提交視窗期,自動提交線程將拉取到的每個分區的最大消息位移進行提交,如果此時消費服務掛掉,消息並未處理結束,但卻提交了最大位移,下次重啟就從100條那消費,即發生了50-100條的消息丟失。

手動提交

當消費配置enable.auto.commit=false的時候代表手動提交位移。用戶必須在適當的時機(一般是處理完業務邏輯後),手動的調用相關api方法提交位移。比如在下麵的案例中,我需要確認我的業務邏輯返回true之後再手動提交位移

 while (true) {
     try {
         ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMinutes(KafkaConfig.pollTimeoutOfMinutes));
         if (!consumerRecords.isEmpty()) {
             for (ConsumerRecord<String, String> record : consumerRecords) {
                 KafkaMessage kafkaMessage = JSON.parseObject(record.value(), KafkaMessage.class);
                 // 處理業務
                 boolean handleResult = handle(kafkaMessage);
                 if (handleResult) {
                     log.info(" handle success, kafkaMessage={}" ,kafkaMessage);
                 } else {
                     log.info(" handle failed, kafkaMessage={}" ,kafkaMessage);
                 }
             }
             // 手動提交offset
             consumer.commitSync(Duration.ofMinutes(KafkaConfig.pollTimeoutOfMinutes));
        
         } 
     } catch (Exception e) {
         log.info("kafka consume error." ,e);
     }
 }

手動提交明顯能解決消息丟失的問題,因為你是處理完業務邏輯後再提交的,假如此時消費服務掛掉,消息並未處理結束,那麼重啟的時候還會重新消費。

但是對於業務層面的失敗導致消息未消費成功,是無法處理的。因為業務層的邏輯千變萬化、比如格式不正確,你叫Kafka消費端程式怎麼去處理?應該要業務層面自己處理,記錄失敗日誌做好監控等。

但是手動提交不能解決消息重覆的問題,也很好理解,假如消費0-100條消息,50條時掛了,重啟後由於沒有提交這一批消息的offset,是會從0開始重新消費。至於如何避免重覆消費的問題,在這篇文章有說。

手動提交又分為非同步提交和同步提交。

同步提交

上面案例代碼使用的是commitSync() ,顧名思義,是同步提交位移的方法。同步提交位移Consumer 程式會處於阻塞狀態,等待 Broker 返回提交結果。同步模式下提交失敗的時候一直嘗試提交,直到遇到無法重試的情況下才會結束。在任何系統中,因為程式而非資源限制而導致的阻塞都可能是系統的瓶頸,會影響整個應用程式的 TPS。當然,你可以選擇拉長提交間隔,但這樣做的後果是 Consumer 的提交頻率下降,在下次 Consumer 重啟回來後,會有更多的消息被重新消費。因此,為瞭解決這些不足,kafka還提供了非同步提交方法。

非同步提交

非同步提交會立即返回,不會阻塞,因此不會影響 Consumer 應用的 TPS。由於它是非同步的,Kafka 提供了回調函數,供你實現提交之後的邏輯,比如記錄日誌或處理異常等。下麵這段代碼展示了調用 commitAsync() 的方法

 consumer.commitAsync((offsets, exception) -> {
 if (exception != null)
     handleException(exception);
 });

但是非同步提交會有一個問題,那就是它沒有重試機制,不過一般情況下,針對偶爾出現的提交失敗,不進行重試不會有太大問題,因為如果提交失敗是因為臨時問題導致的,那麼後續的提交總會有成功的。所以消息也是不會丟失和重覆消費的。
但如果這是發生在關閉消費者或再均衡前的最後一次提交,就要確保能夠提交成功。因此,組合使用commitAsync()commitSync()是最佳的方式。

try {
    while (true) {
        ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMinutes(KafkaConfig.pollTimeoutOfMinutes));
        if (!consumerRecords.isEmpty()) {
             for (ConsumerRecord<String, String> record : consumerRecords) {
                KafkaMessage kafkaMessage = JSON.parseObject(record.value(), KafkaMessage.class);
                boolean handleResult = handle(kafkaMessage);             
             }
             //非同步提交位移               
             consumer.commitAsync((offsets, exception) -> {
             if (exception != null)
                 handleException(exception);
             });
           
        }
    }
} catch (Exception e) {
    System.out.println("kafka consumer error:" + e.toString());
} finally {
    try {
        //最後同步提交位移
        consumer.commitSync();
    } finally {
        consumer.close();
    }
}

讓位移提交更加靈活和可控

如果細心的閱讀了上面所有demo的代碼,那麼你會發現這樣幾個問題:

1、所有的提交,都是提交 poll 方法返回的所有消息的位移,poll 方法一次返回1000 條消息,則一次性地將這 1000 條消息的位移一併提交。可這樣一旦中間出現問題,位移沒有提交,下次會重新消費已經處理成功的數據。所以我想做到細粒度控制,比如每次提交100條,該怎麼辦?

答:可以通過commitSync(Map<TopicPartition, OffsetAndMetadata>)commitAsync(Map<TopicPartition, OffsetAndMetadata>)對位移進行精確控制。

2、poll和commit方法對於普通的開發人員而言是一個黑盒,無法精確地掌控其消費的具體位置。我都不知道這次的提交,是針對哪個partition,提交上去的offset是多少。

答:可以通過record.topic()獲取topic信息, record.partition()獲取分區信息,record.offset() + 1獲取消費位移,記住消費位移是指示下一條消費的位移,所以要加一。

3、我想自己管理offset怎麼辦?一方面更加保險,一方面下次重啟之後可以精準的從資料庫讀取最後的offset就不存在丟失和重覆消費了。
答:可以將消費位移保存在資料庫中。消費端程式使用comsumer.seek方法指定從某個位移開始消費。

綜合以上幾個可優化點,並結合全文,可以給出一個比較完美且完整的demo:聯合非同步提交和同步提交,對處理過程中所有的異常都進行了處理。細粒度的控制了消費位移的提交,並且保守的將消費位移記錄到了資料庫中,重新啟動消費端程式的時候會從資料庫讀取位移。這也是我們消費端程式位移提交的最佳實踐方案。你只要繼承這個抽象類,實現你具體的業務邏輯就可以了。

public abstract class PrefectCosumer {
    private Map<TopicPartition, OffsetAndMetadata> offsets = new HashMap<>();
    int count = 0;
    public final void consume() {
        Properties properties = PropertiesConfig.getConsumerProperties();
        properties.put("group.id", getGroupId());
        Consumer<String, String> consumer = new KafkaConsumer<>(properties);
        consumer.subscribe(getTopics());
        consumer.poll(0);
        // 把offset記錄到資料庫中 從指定的offset處消費 
        consumer.partitionsFor(getTopics()).stream().map(info ->
        new TopicPartition(getTopics(), info.partition()))
        .forEach(tp -> {
               consumer.seek(tp, JdbcUtils.queryOffset().get(tp.partition()));   
         });
        try {
            while (true) {
                ConsumerRecords<String, String> consumerRecords = consumer.poll(Duration.ofMinutes(KafkaConfig.pollTimeoutOfMinutes));
                if (!consumerRecords.isEmpty()) {
                    for (ConsumerRecord<String, String> record : consumerRecords) {

                        KafkaMessage kafkaMessage = JSON.parseObject(record.value(), KafkaMessage.class);
                        boolean handleResult = handle(kafkaMessage);
                        if (handleResult) {
                            //註意:提交的是下一條消息的位移。所以OffsetAndMetadata 對象時,必須使用當前消息位移加 1。
                            offsets.put(new TopicPartition(record.topic(), record.partition()),
                                    new OffsetAndMetadata(record.offset() + 1));

                            // 細粒度控制提交 每10條提交一次offset
                            if (count % 10 == 0) {
                                // 非同步提交offset
                                consumer.commitAsync(offsets, (offsets, exception) -> {
                                    if (exception != null) {
                                        handleException(exception);
                                    }
                                    // 將消費位移再記錄一份到資料庫中
                                    offsets.forEach((k, v) -> {
                                        String s = "insert into kafka_offset(`topic`,`group_id`,`partition_id`,`offset`) values" +
                                                " ('" + k.topic() + "','" + getGroupId() + "'," + k.partition() + "," + v.offset() + ")" +
                                                " on duplicate key update offset=values(offset);";
                                        JdbcUtils.insertTable(s);
                                    });


                                });
                            }
                            count++;
                        } else {         
                            System.out.println("消費消息失敗 kafkaMessage={}" + getTopics() + getGroupId() + kafkaMessage.toString());                         
                        }
                    }


                }
            }
        } catch (Exception e) {
            System.out.println("kafka consumer error:" + e.toString());
        } finally {
            try {
                // 最後一次提交 使用同步提交offset
                consumer.commitSync();
            } finally {
                consumer.close();
            }


        }
    }


    /**
     * 具體的業務邏輯
     *
     * @param kafkaMessage
     * @return
     */
    public abstract boolean handle(KafkaMessage kafkaMessage);

    public abstract List<String> getTopics();

    public abstract String getGroupId();

    void handleException(Exception e) {
        //異常處理
    }
}

控制位移提交的N種方式

剛剛我們說自己控制位移,使用seek方法可以指定offset消費。那到底怎麼控制位移?怎麼重設消費組位移?seek是什麼?現在就來仔細說說。

並不是所有的消息隊列都可以重設消費者組位移達到重新消費的目的。比如傳統的RabbitMq,它們處理消息是一次性的,即一旦消息被成功消費,就會被刪除。而Kafka消費消息是可以重演的,因為它是基於日誌結構(log-based)的消息引擎,消費者在消費消息時,僅僅是從磁碟文件上讀取數據而已,所以消費者不會刪除消息數據。同時,由於位移數據是由消費者控制的,因此它能夠很容易地修改位移的值,實現重覆消費歷史數據的功能。

瞭解如何重設位移是很重要的。假設這麼一個場景,我已經消費了1000條消息後,我發現處理邏輯錯了,所以我需要重新消費一下,可是位移已經提交了,我到底該怎麼重新消費這1000條呢??假設我想從某個時間點開始消費,我又該如何處理呢?

首先說個誤區:auto.offset.reset=earliest/latest這個參數大家都很熟悉,但是初學者很容易誤會它。大部分朋友都覺得在任何情況下把這兩個值設置為earliest或者latest ,消費者就可以從最早或者最新的offset開始消費,但實際上並不是那麼回事,他們生效都有一個前提條件,那就是對於同一個groupid的消費者,如果這個topic某個分區有已經提交的offset,那麼無論是把auto.offset.reset=earliest還是latest,都將失效,消費者會從已經提交的offset開始消費。因此這個參數並不能解決用戶想重設消費位移的需求。

kafka有七種控制消費組消費offset的策略,主要分為位移維度和時間維度,包括:

  • 位移維度。這是指根據位移值來重設。也就是說,直接把消費者的位移值重設成我們給定的位移值。包括Earliest/Latest/Current/Specified-Offset/Shift-By-N策略

  • 時間維度。我們可以給定一個時間,讓消費者把位移調整成大於該時間的最小位移;也可以給出一段時間間隔,比如 30 分鐘前,然後讓消費者直接將位移調回 30 分鐘之前的位移值。包括DateTime和Duration策略

說完了重設策略,我們就來看一下具體應該如何實現,可以從兩個角度,API方式和命令行方式。

重設位移的方法之API方式

API方式只要記住用seek方法就可以了,包括seek,seekToBeginning 和 seekToEnd。

void seek(TopicPartition partition, long offset);    
void seek(TopicPartition partition, OffsetAndMetadata offsetAndMetadata);    
void seekToBeginning(Collection<TopicPartition> partitions);    
void seekToEnd(Collection<TopicPartition> partitions);    

從方法簽名我們可以看出seekToBeginningseekToEnd是可以一次性重設n個分區的位移,而seek 只允許重設指定分區的位移,即為每個分區都單獨設置位移,因為不難得出,如果要自定義每個分區的位移值則用seek,如果希望kafka幫你批量重設所有分區位移,比如從最新數據消費或者從最早數據消費,那麼用seekToEnd和seekToBeginning。

Earliest 策略:從最早的數據開始消費

從主題當前最早位移處開始消費,這個最早位移不一定就是 0 ,因為很久遠的消息會被 Kafka 自動刪除,主要取決於你的刪除配置。

代碼如下:

Properties properties = PropertiesConfig.getConsumerProperties();
properties.put("group.id", getGroupId());
Consumer<String, String> consumer = new KafkaConsumer<>(properties);
consumer.subscribe(getTopics());
consumer.poll(0);
consumer.seekToBeginning(
consumer.partitionsFor(getTopics()).stream().map(partitionInfo ->
   new TopicPartition(getTopics(), partitionInfo.partition()))
   .collect(Collectors.toList()));

首先是構造consumer對象,這樣我們可以通過partitionsFor獲取到分區的信息,然後我們就可以構造出TopicPartition集合,傳給seekToBegining方法。需要註意的一個地方是:需要用consumer.poll(0),而不能用consumer.poll(Duration.ofMillis(0))

在poll(0)中consumer會一直阻塞直到它成功獲取了所需的元數據信息,之後它才會發起fetch請求去獲取數據。而poll(Duration)會把元數據獲取也計入整個超時時間。由於本例中使用的是0,即瞬時超時,因此consumer根本無法在這麼短的時間內連接上coordinator,所以只能趕在超時前返回一個空集合。

Latest策略:從最新的數據開始消費

    consumer.seekToEnd(
        consumer.partitionsFor(getTopics().get(0)).stream().map(partitionInfo ->
            new TopicPartition(getTopics().get(0), partitionInfo.partition()))
              .collect(Collectors.toList()));

Current策略:從當前已經提交的offset處消費

consumer.partitionsFor(getTopics().get(0)).stream().map(info ->
        new TopicPartition(getTopics().get(0), info.partition()))
        .forEach(tp -> {
            long committedOffset = consumer.committed(tp).offset();
            consumer.seek(tp, committedOffset);
        });

**Special-offset策略:從指定的offset處消費 **

該策略使用的方法和current策略一樣,區別在於,current策略是直接從kafka元信息中讀取中已經提交的offset值,而special策略需要用戶自己為每一個分區指定offset值,我們一般是把offset記錄到資料庫中然後可以從資料庫去讀取這個值

    consumer.partitionsFor(getTopics().get(0)).stream().map(info ->
                new TopicPartition(getTopics().get(0), info.partition()))
                .forEach(tp -> {
                    try {
                        consumer.seek(tp, JdbcUtils.queryOffset().get(tp.partition()));
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                });

以上演示了用API方式重設位移,演示了四種常見策略的代碼,另外三種沒有演示,一方面是大同小異,另一方面在實際生產中,用API的方式不太可能去做時間維度的重設,而基本都是用命令行方式。

重設位移的方法之命令行方式

命令行方式重設位移是通過 kafka-consumer-groups 腳本。比起 API 的方式,用命令行重設位移要簡單得多。

Earliest 策略指定–to-earliest。

bin/kafka-consumer-groups.sh --bootstrap-server kafka-host:port --group test-group --reset-offsets --all-topics --to-earliest –execute

Latest 策略指定–to-latest。

bin/kafka-consumer-groups.sh --bootstrap-server kafka-host:port --group test-group --reset-offsets --all-topics --to-latest --execute

Current 策略指定–to-current。

bin/kafka-consumer-groups.sh --bootstrap-server kafka-host:port --group test-group --reset-offsets --all-topics --to-current --execute

Specified-Offset 策略指定–to-offset。

bin/kafka-consumer-groups.sh --bootstrap-server kafka-host:port --group test-group --reset-offsets --all-topics --to-offset <offset> --execute

Shift-By-N 策略指定–shift-by N。

bin/kafka-consumer-groups.sh --bootstrap-server kafka-host:port --group test-group --reset-offsets --shift-by <offset_N> --execute

DateTime 策略指定–to-datetime。

DateTime 允許你指定一個時間,然後將位移重置到該時間之後的最早位移處。常見的使用場景是,你想重新消費昨天的數據,那麼你可以使用該策略重設位移到昨天 0 點。

bin/kafka-consumer-groups.sh --bootstrap-server kafka-host:port --group test-group --reset-offsets --to-datetime 2019-06-20T20:00:00.000 --execute

Duration 策略指定–by-duration。
Duration 策略則是指給定相對的時間間隔,然後將位移調整到距離當前給定時間間隔的位移處,具體格式是 PnDTnHnMnS。如果你熟悉 Java 8 引入的 Duration 類的話,你應該不會對這個格式感到陌生。它就是一個符合 ISO-8601 規範的 Duration 格式,以字母 P 開頭,後面由 4 部分組成,即 D、H、M 和 S,分別表示天、小時、分鐘和秒。舉個例子,如果你想將位移調回到 15 分鐘前,那麼你就可以指定 PT0H15M0S

bin/kafka-consumer-groups.sh --bootstrap-server kafka-host:port --group test-group --reset-offsets --by-duration PT0H30M0S --execute

提交的位移都去哪了?

通過上面那幾部分的內容,我們已經搞懂了位移提交的方方面面,那麼提交的位移它保存在哪裡呢?這就要去位移主題的的世界里一探究竟了。kafka把位移保存在一個叫做__consumer_offsets的內部主題中,叫做位移主題。

註意:老版本的kafka其實是把位移保存在zookeeper中的,但是zookeeper並不適合這種高頻寫的場景。所以新版本已經是改進了這個方案,直接保存到kafka。畢竟kafka本身就適合高頻寫的場景,並且kafka也可以保證高可用性和高持久性。

既然它也是主題,那麼離不開分區和副本這兩個機制。我們並沒有手動創建這個主題並且指定,所以是kafka自動創建的, 分區的數量取決於Broker 端參數 offsets.topic.num.partitions,預設是50個分區,而副本參數取決於offsets.topic.replication.factor,預設是3。

既然也是主題,肯定會有消息,那麼消息格式是什麼呢?參考前面我們手動設計將位移寫入資料庫的方案,我們保存了topic,group_id,partition,offset四個欄位。topic,group_id,partition無疑是數據表中的聯合主鍵,而offset是不斷更新的。無疑kafka的位移主題消息也是類似這種設計。key也是那三個欄位,而消息體其實很複雜,你可以先簡單理解為就是offset。

既然也是主題,肯定也會有刪除策略,否則消息會無限膨脹。但是位移主題的刪除策略和其他主題刪除策略又不太一樣。我們知道普通主題的刪除是可以通過配置刪除時間或者大小的。而位移主題的刪除,叫做 Compaction。Kafka 使用Compact 策略來刪除位移主題中的過期消息,對於同一個 Key 的兩條消息 M1 和 M2,如果 M1 的發送時間早於 M2,那麼 M1 就是過期消息。Compact 的過程就是掃描日誌的所有消息,剔除那些過期的消息,然後把剩下的消息整理在一起。

Kafka 提供了專門的後臺線程定期地巡檢待 Compact 的主題,看看是否存在滿足條件的可刪除數據。這個後臺線程叫 Log Cleaner。很多實際生產環境中都出現過位移主題無限膨脹占用過多磁碟空間的問題,如果你的環境中也有這個問題,我建議你去檢查一下 Log Cleaner 線程的狀態,通常都是這個線程掛掉了導致的。

總結

kafka的位移是個極其重要的概念,控制著消費進度,也即控制著消費的準確性,完整性,為了保證消息不重覆和不丟失。我們最好做到以下幾點:

  • 手動提交位移。

  • 手動提交有非同步提交和同步提交兩種方式,既然兩者有利也有弊,那麼我們可以結合起來使用。

  • 細粒度的控制消費位移的提交,這樣可以避免重覆消費的問題。

  • 保守的將消費位移再記錄到了資料庫中,重新啟動消費端程式的時候從資料庫讀取位移。

獲取Kafka全套原創學習資料及思維導圖,關註【胖滾豬學編程】公眾號,回覆"kafka"。

本文來源於公眾號:【胖滾豬學編程】。一枚集顏值與才華於一身,不算聰明卻足夠努力的女程式媛。用漫畫形式讓編程so easy and interesting!求關註!


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

-Advertisement-
Play Games
更多相關文章
  • 對於在不同 Unix 系統之間移植程式,最重要的事情就是確定新系統的一些編譯時、運行時固定或不固定的限制值了。例如文件路徑最大長度 PATH_MAX、進程最大可打開文件句柄數 OPEN_MAX、用戶可加入的附加用戶組最大數量 NGROUPS_MAX、進程命令行參數最大位元組數 ARG_MAX、記憶體頁大 ...
  • 一、下載CentOS7的鏡像文件ISO CentOS官方鏡像站點列表地址: https://www.centos.org/download/mirrors/ 中國境內CentOS鏡像下載地址: 阿裡: http://mirrors.aliyun.com/centos/ 清華: https://mir ...
  • 由於開發工作的需要把數據從Hive導入到Elasticsearch,最開始使用了java寫了個程式跑數據,80W的數據跑了2個小時左右,想想1000W數據那得要一天的時間,這效率到一次痛苦一次,就放棄了最初的想法,找到了用了hive的的方法,直接把數據導入到elasticsearch,以下是創建過程 ...
  • Kubernetes全系列總結如下,後期不定期更新。歡迎基於學習、交流目的的轉載和分享,禁止任何商業盜用,同時希望能帶上原文出處,尊重ITer的成果,也是尊重知識。若發現任何錯誤或紕漏,留言反饋或右側添加本人反饋。 正篇 001.Kubernetes簡介 002.Kubernetes簡單入門實例 0 ...
  • 更多精彩文章請關註公眾號『大海的BLOG』 上一篇文章給大家講述了重裝系統後的驅動問題,那麼接下來的內容是給大家推薦一些增快辦公效率的軟體。 在配置好顯卡驅動後,可以說一個嶄新的生產力就躍然眼前了,那麼接下來我們需要做什麼呢? 更新系統 Tips:剛展開完畢的系統還在後臺下載更新微軟自己的軟體,所以 ...
  • SRAM是控制器電路中重要的元器件,控制器硬體出廠時,要對所有元器件進行檢測。對SRAM某個地址讀寫,可以判斷SRAM晶元是否損壞,以及數據線是否虛焊。 為解決現有技術中不能對SRAM晶元地址引腳線短路故障進行快速精確檢測定位的問題;提供一種SRAM晶元地址引腳線短路檢測方法,包括以下步驟: 一、根 ...
  • kubeadm介紹 kubeadm概述 參考《附003.Kubeadm部署Kubernetes》。 kubeadm功能 參考《附003.Kubeadm部署Kubernetes》。 本方案描述 本方案採用kubeadm部署Kubernetes 1.18.3版本; etcd採用混部方式; KeepAli ...
  • 環境:Ubuntu 16.04 研究原因:https://www.cnblogs.com/sjsea/p/13126996.html 1.SCP # 上傳 scp /home/sea/Desktop/test.sh [email protected]:/home/sea/Desktop/test.sh ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...