SpringBoot集成Kafka實戰應用

目錄

使用Kafka-Client實現消息收發

引入依賴

發送端:

消費端:

SpringBoot集成

引入maven依賴

消費端


在上一篇我們深度解析了Kafka的運行操作原理以及集群消息消費機制等,請點擊下方鏈接獲取

Kafka消息隊列深度解析與實戰指南

????????本篇我們將著重實戰

  • 使用Kafka-Client實現消息收發

引入依賴

<dependency><groupId>org.apache.kafka</groupId><artifactId>kafka-clients</artifactId><version>3.0.0</version></dependency>

發送端:

import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.serialization.IntegerSerializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import com.google.gson.Gson;import lombok.extern.slf4j.Slf4j;@Slf4j
@SpringBootTest
class KafkaProducerTests {private final static String TOPIC_NAME = "muse-rp";private final static Integer PARTITION_ONE = 0;private final static Integer PARTITION_TWO = 1;private final static Gson GSON = new Gson();/*** 同步阻塞——消息發送*/@Testvoid testBlockingSendMsg() {/** 初始化生產者屬性信息 */Properties properties = initProducerProp();/** 創建消息發送的客戶端 */Producer<Integer, String> producer = new KafkaProducer<>(properties);Message message;for (int i=0; i< 3; i++) {/** 構造消息 */message = new Message(i, "BLOCKING_MSG_"+i);ProducerRecord<Integer, String> producerRecord;int SEND_MSG_METHOD = 0;switch (SEND_MSG_METHOD) {case 0: /** 【發送方式1】未指定發送的分區 */producerRecord = new ProducerRecord<>(TOPIC_NAME, GSON.toJson(message));break;case 1: /** 【發送方式2】未指定發送的分區,根據第二個參數key來判斷發送到哪個分區*/producerRecord = new ProducerRecord<>(TOPIC_NAME, message.getMegId(), GSON.toJson(message));break;default: /** 【發送方式3】指定發送的分區 */producerRecord = new ProducerRecord(TOPIC_NAME, PARTITION_ONE, message.getMegId(), GSON.toJson(message));}/** 同步阻塞——等待消息發送成功 */try {Future<RecordMetadata> recordMetadataFuture = producer.send(producerRecord);log.info("調用send方法完畢,msg={}", producerRecord.value());RecordMetadata recordMetadata = recordMetadataFuture.get();log.info("[topic]={}, [partition]={}, [offset]={}", recordMetadata.topic(), recordMetadata.partition(),recordMetadata.offset());} catch (Throwable e) {log.error("發送消息異常!", e);}}producer.close(); // close方法會阻塞等待之前所有的發送請求完成后再關閉KafkaProducer}/*** 異步回調——消息發送*/@Testvoid testNoBlockingSendMsg() {/** 初始化生產者屬性信息 */Properties properties = initProducerProp();/** 創建消息發送的客戶端 */Producer<Integer, String> producer = new KafkaProducer<>(properties);CountDownLatch latch = new CountDownLatch(5);Message message;for (int i=0; i< 5; i++) {message = new Message(i, "NO_BLOCKING_MSG_" + i);/** 指定發送的分區 */ProducerRecord<Integer, String> producerRecord = new ProducerRecord(TOPIC_NAME, PARTITION_ONE,message.getMegId(), GSON.toJson(message));/** 異步回調方式發送消息 */producer.send(producerRecord, (metadata, exception) -> {if (exception != null) {log.error("消息發送失敗!", exception);}if (metadata != null) {log.info("[topic]={}, [partition]={}, [offset]={}", metadata.topic(), metadata.partition(),metadata.offset());}latch.countDown();});log.info("調用send方法完畢,msg={}", producerRecord.value());}producer.close();}/*** 初始化生產者屬性*/private Properties initProducerProp() {Properties properties = new Properties();// properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9093,127.0.0.1:9094,127.0.0.1:9095"); // 配置kafka的Broker列表properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");/*** 發出消息持久化機制參數* acks=0:  表示producer不需要等待任何broker確認收到消息的ACK回復,就可以繼續發送下一條消息。性能最高,但是最容易丟失消息* acks=1:  表示至少等待leader已經成功將數據寫入本地log,但是不需要等待所有follower都寫入成功,就可以繼續發送下一條消息。*          這種情況下,如果follower沒有成功備份數據,而此時leader又掛掉,則消息就會丟失。* acks=-1: 表示kafka ISR列表中所有的副本同步數據成功,才返回消息給客戶端,這是最強的數據保證。min.insync.replicas 這個配置是*          用來設置同步副本個數的下限的, 并不是只有 min.insync.replicas 個副本同步成功就返回ack。而是,只要acks=all就意味著*          ISR列表里面的副本必須都要同步成功。*/properties.put(ProducerConfig.ACKS_CONFIG, "1");/*** 發送失敗重試的次數,默認是間隔100ms* 重試能保證消息發送的可靠性,但是也可能造成消息重復發送,所以需要在消費者端做好冪等性處理*/properties.put(ProducerConfig.RETRIES_CONFIG, 3); // 失敗重試3次properties.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 300); // 重試間隔300ms/*** 設置發送消息的本地緩沖區* 如果設置了該緩沖區,消息會先發送到本地緩沖區,可以提高消息發送性能,默認值為32MB*/properties.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 32*1024*1024);/*** 設置批量發送消息的大小* kafka本地線程會從緩沖區去取數據,然后批量發送到Broker,默認值16KB,即:一個批次滿足16KB就會發送出去。*/properties.put(ProducerConfig.BATCH_SIZE_CONFIG, 16*1024);/*** 默認值為0,表示消息必須立即被發送,但這樣會影響性能* 一般設置10ms左右,也就是說這個消息發送完后會進入本地的一個批次中,如果10ms內,這個批次滿足了16KB,那么就會隨著批次一起被發送出去* 如果10ms內,批次沒滿,那么也必須要把消息發送出去,不能讓消息的發送延遲時間太長*/properties.put(ProducerConfig.LINGER_MS_CONFIG, 10);/** 把發送的key和消息value從字符串序列化為字節數組 */properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, IntegerSerializer.class.getName());properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());return properties;}}

消費端:

import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;import org.apache.kafka.clients.consumer.Consumer;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.CooperativeStickyAssignor;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.clients.consumer.RangeAssignor;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.TopicPartition;
import org.apache.kafka.common.serialization.IntegerDeserializer;
import org.apache.kafka.common.serialization.IntegerSerializer;
import org.apache.kafka.common.serialization.StringDeserializer;
import org.assertj.core.util.Lists;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;import com.google.gson.Gson;import lombok.extern.slf4j.Slf4j;@Slf4j
@SpringBootTest
class KafkaConsumerTests {private final static String TOPIC_NAME = "muse-rp";private final static String CONSUMER_GROUP_NAME = "museGroup";private final static Integer PARTITION_ONE = 0;private final static Gson GSON = new Gson();/*** 自動提交offset*/@Testvoid testAutoCommitOffset() throws Throwable {Properties properties = initConsumerProp();/** 是否自動提交offset,默認:true*/properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");/** 自動提交offset的時間間隔 */properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");/** 配置Rebalance策略 */
//        properties.put(ConsumerConfig.PARTITION_ASSIGNMENT_STRATEGY_CONFIG, Arrays
//                .asList(RangeAssignor.class, CooperativeStickyAssignor.class));/** 創建消息發送的客戶端 */Consumer<Integer, String> consumer = new KafkaConsumer<>(properties);int RECV_MSG_METHOD = 0;switch (RECV_MSG_METHOD) {case 0: /** 【接收方式1】未指定接收的分區 */consumer.subscribe(Lists.newArrayList(TOPIC_NAME));break;case 1: /** 【接收方式2】指定分區消費 */consumer.assign(Lists.newArrayList(new TopicPartition(TOPIC_NAME, PARTITION_ONE)));break;case 2: /** 【接收方式3】指定從頭開始消費 */consumer.assign(Lists.newArrayList(new TopicPartition(TOPIC_NAME, PARTITION_ONE)));consumer.seekToBeginning(Lists.newArrayList(new TopicPartition(TOPIC_NAME, PARTITION_ONE)));break;default: /**【接收方式4】指定分區和offset進行消費*/consumer.assign(Lists.newArrayList(new TopicPartition(TOPIC_NAME, PARTITION_ONE)));consumer.seek(new TopicPartition(TOPIC_NAME, PARTITION_ONE), 10);}ConsumerRecords<Integer, String> records;while (true) {/** 長輪詢的方式拉取消息 */records = consumer.poll(Duration.ofMillis(1000));for (ConsumerRecord record : records) {log.info(" [topic]={}, [partition]={}, [offset]={}, [value]={}", record.topic(), record.partition(),record.offset(), record.value());}Thread.sleep(3000);}}/*** 手動提交offset*/@Testvoid testManualCommitOffset() throws Throwable {Properties properties = initConsumerProp();/** 是否自動提交offset,默認:true*/properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");/** 創建消息發送的客戶端 */Consumer<Integer, String> consumer = new KafkaConsumer<>(properties);consumer.subscribe(Lists.newArrayList(TOPIC_NAME));// consumer.assign(Lists.newArrayList(new TopicPartition(TOPIC_NAME, PARTITION_ONE)));ConsumerRecords<Integer, String> records;while (true) {/** 長輪詢的方式拉取消息 */records = consumer.poll(Duration.ofMillis(1000));for (ConsumerRecord record : records) {log.info(" [topic]={}, [partition]={}, [offset]={}, [value]={}", record.topic(), record.partition(),record.offset(), record.value());}boolean isSync = true;if (records.count() > 0) {if (isSync) {/** 【手動同步提交offset】當前線程會阻塞直到offset提交成功;常用同步提交方式 */consumer.commitSync();} else {/** 【手動異步提交offset】當前線程提交offset不會阻塞,可以繼續執行后面的邏輯代碼 */consumer.commitAsync((offsets, exception) -> {log.error("offset={}", GSON.toJson(offsets));if (exception != null) {log.error("提交offset發生異常!", exception);}});}}Thread.sleep(1000);}}/*** 初始化消費者配置*/private Properties initConsumerProp() {Properties properties = new Properties();// 配置kafka的Broker列表// properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9093,127.0.0.1:9094,127.0.0.1:9095");properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "127.0.0.1:9092");/** 配置消費組——museGroup */properties.put(ConsumerConfig.GROUP_ID_CONFIG, CONSUMER_GROUP_NAME);/*** offset的重置策略——例如創建一個新的消費組,offset是不存在的,如何對offset賦值消費* latest(默認):只消費自己啟動之后發送到主題的消息。* earliest:第一次從頭開始消費,以后按照消費offset記錄繼續消費。*/properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");/** Consumer給Broker發送心跳的時間間隔 */properties.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 1000);/** 如果超過10秒沒有接收到消費者的心跳,則會把消費者踢出消費組,然后重新進行rebalance操作,把分區分配給其他消費者 */properties.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 10*1000);/** 一次poll最大拉取消息的條數,可以根據消費速度的快慢來設置 */properties.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);/** 如果兩次poll的時間超出了30秒的時間間隔,kafka會認為整個Consumer的消費能力太弱,會將它踢出消費組。將分區分配給其他消費者 */properties.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 30*1000);/** key和value的反序列化 */properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, IntegerDeserializer.class.getName());properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());return properties;}}
  • SpringBoot集成

引入maven依賴

<dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId></dependency>
import javax.annotation.Resource;import org.apache.kafka.clients.producer.RecordMetadata;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;import com.muse.springbootdemo.entity.Message;import lombok.extern.slf4j.Slf4j;@Slf4j
@Service
public class ProducerService {private final static String TOPIC_NAME = "muse-rp";private final static Integer PARTITION_ONE = 0;private final static Integer PARTITION_TWO = 1;@Resourceprivate KafkaTemplate<String, Message> kafkaTemplate;/*** 同步阻塞——消息發送*/public void blockingSendMsg() throws Throwable {Message message;for (int i=0; i< 5; i++) {message = new Message(String.valueOf(i), "BLOCKING_MSG_SPRINGBOOT_" + i);ListenableFuture<SendResult<String, Message>> future = kafkaTemplate.send(TOPIC_NAME, PARTITION_ONE,"" + message.getMegId(), message);SendResult<String, Message> sendResult = future.get();RecordMetadata recordMetadata = sendResult.getRecordMetadata();log.info("---BLOCKING_MSG_SPRINGBOOT--- [topic]={}, [partition]={}, [offset]={}", recordMetadata.topic(),recordMetadata.partition(), recordMetadata.offset());}}/*** 異步回調——消息發送*/public void noBlockingSendMsg() {Message message;for (int i=0; i< 5; i++) {message = new Message(String.valueOf(i), "NO_BLOCKING_MSG_SPRINGBOOT_" + i);ListenableFuture<SendResult<String, Message>> future = kafkaTemplate.send(TOPIC_NAME, PARTITION_ONE,"" + message.getMegId(), message);future.addCallback(new ListenableFutureCallback<SendResult<String, Message>>() {@Overridepublic void onFailure(Throwable ex) {log.error("消息發送失敗!", ex);}@Overridepublic void onSuccess(SendResult<String, Message> result) {RecordMetadata recordMetadata = result.getRecordMetadata();log.info("---NO_BLOCKING_MSG_SPRINGBOOT---[topic]={}, [partition]={}, [offset]={}",recordMetadata.topic(), recordMetadata.partition(), recordMetadata.offset());}});}}
}

消費端

package com.muse.springbootdemo.service;import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.kafka.annotation.KafkaListener;
import org.springframework.kafka.annotation.PartitionOffset;
import org.springframework.kafka.annotation.TopicPartition;
import org.springframework.stereotype.Service;import com.muse.springbootdemo.entity.Message;import lombok.extern.slf4j.Slf4j;@Slf4j
@Service
public class ConsumerService {private final static String TOPIC_NAME = "muse-rp";private final static String CONSUMER_GROUP_NAME = "museGroup";/*** 消息消費演示*/@KafkaListener(topics = TOPIC_NAME, groupId = CONSUMER_GROUP_NAME)public void listenGroup(ConsumerRecord<String, Message> record) {log.info(" [topic]={}, [partition]={}, [offset]={}, [value]={}", record.topic(), record.partition(),record.offset(), record.value());}}

下一篇將解析關于Kafka應用過程中的常見問題及大廠高頻面試題

  • ?包括 1)防止消息丟失;2)防止重復消費通過冪等處理;3)順序消費需單分區單消費者;4)消息積壓時提升消費能力;5)延遲隊列通過時間判斷實現;6)高吞吐依靠頁面緩存+順序寫+零拷貝技術等問題 將在12h內更新
  • Kafka應用過程中的高頻問題

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/95192.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/95192.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/95192.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

單元測試總結2

1、重載和重寫的區別01、定義不同&#xff1a;重載是在同一個類中定義多個方法名相同但參數列表不同的方法&#xff1b;重寫是子類對父類中同名同參數列表的方法進行重新實現02、范圍不同&#xff1a;重載發生在同一個類中&#xff0c;重寫發生在子類和父類中03、參數要求不同&…

Wi-Fi技術——MAC特性

有線和無線網絡在數據鏈路層的特性存在差異&#xff0c;具體為&#xff1a; CSMA/CD 用于有線網絡&#xff0c;通過檢測和處理沖突來維持網絡的穩定性。CSMA/CA 用于無線網絡&#xff0c;強調沖突的預防&#xff0c;以應對無線信道共享的挑戰 1 有線網 CSMA/CD 有線網 CSMA/…

OpenHarmony 分布式感知中樞深度拆解:MSDP 框架從 0 到 1 的實戰指南

MSDP設備狀態感知框架技術開發文檔 1. 系統概述 1.1 框架定位 MSDP (Multi-Sensor Data Processing) 設備狀態感知框架是OpenHarmony系統中負責設備狀態識別和分發的核心服務,基于多傳感器融合技術,為系統應用提供設備狀態感知能力。 1.2 核心功能 靜止狀態識別:基于加速…

圖像 OSD層數據 顯示--OSD LOGO單色黑色顯示,按區域大小申請MMZ內存的優缺點分析

在監控攝像機、嵌入式顯示設備等場景中,OSD(On-Screen Display,屏幕顯示)LOGO 常需單色黑色顯示,且按區域大小申請 MMZ(Multi-Media Zone,多媒體專用內存)內存,該方案的優缺點需結合硬件資源、顯示效率、功能適配性等維度綜合分析,具體如下: 一、核心優勢:針對性優…

徐真妍最新雜志封面大片曝光,探索鏡頭下的多面魅力

近日&#xff0c;青年演員徐真妍拍攝的一組大片正式曝光。這組以 “森林系” 為主題的大片&#xff0c;登上時尚雜志《慵懶LAZY DAYS》8-9月刊封面。融合了優雅與現代先鋒感&#xff0c;展現了徐真妍甜美溫婉的表現力。鏡頭前的她&#xff0c;在多種風格間自如切換&#xff0c;…

廣度優先搜索(BFS, Breadth-First Search)

好的&#xff0c;我給你講 廣度優先搜索&#xff08;BFS, Breadth-First Search&#xff09;&#xff0c;并配一個直觀例子。1?? 什么是廣度優先廣度優先搜索的特點&#xff1a;按層訪問&#xff1a;先訪問根節點&#xff0c;然后訪問它的直接子節點&#xff0c;再訪問子節點…

GD32入門到實戰22--紅外NEC通信協議

ir_drv.c紅外傳輸協議地位在前&#xff0c;所以我們可以這樣保存數據到數組假使接收到1就>>1再|0x80&#xff0c;如果接收到0就>>1新建紅外驅動層代碼ir_drv.c#include <stdio.h> #include "gd32f30x.h" #include <stdbool.h> static voi…

zkML-JOLT——更快的ZK隱私機器學習:Sumcheck +Lookup

1. 引言 ICME團隊開源的zkML項目&#xff1a; https://github.com/ICME-Lab/jolt-atlas&#xff08;Rust&#xff09; zkML-JOLT&#xff08;JOLT ‘Atlas’&#xff09;構建在a16z Crypto團隊的JOLT研究和實現基礎上&#xff0c;其性能比其他zkML項目快了3到7倍。 a16z Cr…

【大模型記憶-Mem0詳解-2】系統架構

概述 Mem0 實現了雙架構系統&#xff0c;通過兩種主要部署模型為 AI 應用提供智能內存能力&#xff1a; 托管平臺 &#xff1a;通過 MemoryClient 和 AsyncMemoryClient 類訪問的托管服務開源 &#xff1a;以 Memory 類為中心的自托管組件&#xff0c;具有可插拔提供程序 此架構…

[Java]PTA:jmu-Java-01入門-取數字浮點數

本題目要求讀入若干以回車結束的字符串表示的整數或者浮點數&#xff0c;然后將每個數中的所有數字全部加總求和。輸入格式:每行一個整數或者浮點數。保證在浮點數范圍內。輸出格式:整數或者浮點數中的數字之和。題目保證和在整型范圍內。輸入樣例:-123.01 234輸出樣例:7 9代碼…

FFmpeg音視頻處理解決方案

核心組件&#xff1a; ffmpeg&#xff1a;主要的命令行工具&#xff0c;用于轉碼、轉換格式等 ffprobe&#xff1a;用于分析多媒體文件信息的工具 ffplay&#xff1a;簡單的媒體播放器 主要功能&#xff1a; ? 格式轉換&#xff08;轉碼&#xff09; ? 視頻裁剪、合并 ? 調整…

機器學習回顧——決策樹詳解

決策樹基礎概念與應用詳解1. 決策樹基礎概念1.1 什么是決策樹決策樹是一種樹形結構的預測模型&#xff0c;其核心思想是通過一系列規則對數據進行遞歸劃分。它模擬人類決策過程&#xff0c;廣泛應用于分類和回歸任務。具體結構包括&#xff1a;內部節點&#xff1a;表示對某個特…

Linux開發必備:yum/vim/gcc/make全攻略

目錄 1.學習yum、apt?具&#xff0c;進?軟件安裝 1-1 什么是軟件包 1-2 yum/apt具體操作 2. 編輯器Vim 2-1 Linux編輯器-vim的引入 2-2 vim的基本概念 2-3 vim的基本操作 2-4 vim正常模式命令集 2-5 vim末?模式命令集 3. 編譯器gcc/g 3-1 背景知識 3-2 gcc編譯選…

【Linux系統】萬字解析,進程間的信號

前言&#xff1a; 上文我們講到了&#xff0c;進程間通信的命名管道與共享內存&#xff1a;【Linux系統】命名管道與共享內存-CSDN博客?????? 本文我們來講一講&#xff0c;進程的信號問題 點個關注&#xff01; 信號概念 信號是OS發送給進程的異步機制&#xff01;所謂異…

AI時代SEO關鍵詞實戰解析

內容概要 隨著人工智能技術深度融入搜索引擎的運行機制&#xff0c;傳統的SEO關鍵詞研究方法正經歷著根本性的變革。本文聚焦于AI時代背景下&#xff0c;如何利用智能化的策略精準定位目標用戶&#xff0c;實現搜索可見度的實質性躍升。我們將深入探討AI技術如何革新關鍵詞研究…

Spring Boot + Spring MVC 項目結構

下面一個既能返回 JSP 頁面&#xff0c;又能提供 JSON API 的 Spring Boot Spring MVC 項目結構&#xff0c;這樣你就能同時用到 Controller 和 RestController 的優勢。 &#x1f3d7; 項目結構 springboot-mvc-mixed/ ├── src/main/java/com/example/demo/ │ ├── …

通俗易懂的講解下Ceph的存儲原理

Ceph存儲原理解析 要理解 Ceph 的存儲原理&#xff0c;我們可以用一個 “分布式倉庫” 的比喻來拆解 —— 把 Ceph 想象成一個由多個 “倉庫管理員”&#xff08;硬件節點&#xff09;共同打理的大型倉庫&#xff0c;能高效存儲、管理海量貨物&#xff08;數據&#xff09;&…

軟件測試小結(1)

一、什么是測試&#xff1f;1.1 生活中常見的測試例如去商場買衣服&#xff1a;①、選擇一件符合審美的衣服 -> 外觀測試&#xff1b;②、穿上身上試試是否合身 -> 試穿測試&#xff1b;③、 看看衣服的材料是否純棉 -> 材料測試&#xff1b;④、 詢問衣服的價格 ->…

Python未來3-5年技術發展趨勢分析:從AI到Web的全方位演進

Python作為全球最流行的編程語言之一&#xff0c;在開發者社區中占據核心地位。其簡潔語法、豐富庫生態和跨領域適用性&#xff0c;使其在AI、Web開發、數據科學等領域持續領先。本文基于當前技術演進趨勢&#xff08;如2023-2024年的開源項目、社區討論和行業報告&#xff09;…

【ComfyUI】SDXL Turbo一步完成高速高效的圖像生成

今天演示的案例是一個基于 ComfyUI 與 Stable Diffusion XL Turbo 的圖生圖工作流。整體流程通過加載輕量化的 Turbo 版本模型&#xff0c;在文本編碼與調度器的配合下&#xff0c;以極快的推理速度完成從提示詞到高質量圖像的生成。 配合演示圖可以直觀感受到&#xff0c;簡潔…