文章目錄
- 一、分布式消息系統(Kafka相關問題5-10)
- 5. Kafka如何保證消息不丟失?
- 6. 項目中Kafka具體怎么使用的?
- 7. 消息異常未發送成功怎么解決?
- 8. 重試具體怎么做的,循環嗎?
- 9. 重試多次失敗怎么辦,拋出異常嗎?
- 10. 消息一直沒發出去是什么原因?
- 二、數據庫設計與優化(問題11-18)
- 11. SQL怎么優化?
- 12. 怎么判斷是慢查詢?
- 13. 怎么設計一個好的數據庫?
- 14. 項目中數據庫表怎么設計的?
- 15. 主鍵怎么設計?普通遞增 vs 分布式雪花算法
- 16. 除了雪花算法和UUID,還有什么主鍵不重復方案?
- 17. 具體字段類型設計:用戶名用什么類型?
- 18. 什么時候用到JOIN?
- 三、加密算法(問題19-20)
- 19. 左連接、右連接、外連接的區別?
- 20. 加密算法有哪些,什么區別?
以下是針對您提出的20個問題的詳細解答,分為分布式消息系統(Kafka)、數據庫設計與優化、加密算法三個模塊:
一、分布式消息系統(Kafka相關問題5-10)
5. Kafka如何保證消息不丟失?
Kafka通過生產者、Broker、消費者三端機制確保消息不丟失:
- 生產者端:
- 設置
acks=all
(或acks=-1
):等待所有ISR副本確認接收消息。 - 啟用
retries
重試機制(默認10次),處理網絡抖動等臨時錯誤。 - 記錄
producer.send()
返回的RecordMetadata
,用于故障后重試(需避免冪等性問題,可通過enable.idempotence=true
開啟冪等生產者)。
- 設置
- Broker端:
- 副本機制:消息寫入分區的Leader副本后,需等待ISR(In-Sync Replicas)中的Follower副本同步完成才標記為“已提交”。
- 持久化:消息寫入磁盤時使用
fsync
強制刷盤(可通過log.flush.interval.messages
等參數控制)。
- 消費者端:
- 手動提交offset:關閉自動提交(
auto.commit.offset=false
),確保消費成功后再提交。 - 重復消費處理:通過業務層去重(如利用消息唯一ID+Redis存儲已消費記錄)。
- 手動提交offset:關閉自動提交(
6. 項目中Kafka具體怎么使用的?
典型應用場景:
- 異步解耦:訂單系統下單后,通過Kafka通知庫存、物流、支付等系統,避免同步調用超時。
- 流量削峰:秒殺活動中,將用戶請求寫入Kafka,消費者(如Java服務)按限流速度處理,防止數據庫被瞬時流量沖垮。
- 日志采集:將應用日志發送到Kafka,供ELK(Elasticsearch+Logstash+Kibana)或Flink等系統進行實時分析。
代碼示例(Java生產者):
Properties props = new Properties();
props.put("bootstrap.servers", "kafka:9092");
props.put("acks", "all");
props.put("retries", 3);
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");KafkaProducer<String, String> producer = new KafkaProducer<>(props);
ProducerRecord<String, String> record = new ProducerRecord<>("order-topic", "123", "{\"orderId\":\"1001\",\"amount\":1000}");try {producer.send(record, (metadata, exception) -> {if (exception != null) {log.error("消息發送失敗:{}", exception.getMessage());} else {log.info("消息已發送至分區{},偏移量{}", metadata.partition(), metadata.offset());}});
} finally {producer.close();
}
7. 消息異常未發送成功怎么解決?
排查步驟:
- 生產者日志:查看是否有網絡異常(如
org.apache.kafka.common.errors.NetworkException
)、分區不存在等錯誤。 - Broker狀態:檢查Kafka集群是否正常(節點存活、分區Leader是否存在),使用
kafka-topics.sh --describe
查看分區ISR狀態。 - 重試機制:若為臨時性錯誤(如分區負載過高),通過
retries
自動重試;若為永久性錯誤(如消息格式錯誤),需捕獲異常并記錄到死信隊列(Dead Letter Queue,DLQ)。 - 監控告警:通過Prometheus+Grafana監控
producer_request_error_rate
等指標,及時發現發送失敗趨勢。
8. 重試具體怎么做的,循環嗎?
- Kafka內置重試:
生產者通過retries
參數設置重試次數(默認10次),每次重試間隔由retry.backoff.ms
控制(默認100ms)。重試邏輯為指數退避(如第1次重試間隔100ms,第2次200ms,避免頻繁重試加劇網絡負載)。 - 業務層重試:
若內置重試耗盡仍失敗,可將消息存入數據庫(如MySQL)或Redis,通過定時任務(如Spring Task)或分布式調度框架(如Elastic-Job)進行循環重試,直至成功或標記為“需要人工處理”。
9. 重試多次失敗怎么辦,拋出異常嗎?
- 有限重試后終止:
設定最大重試次數(如5次),超過后不再自動重試,而是:- 發送告警(郵件/短信通知運維人員)。
- 將消息寫入死信隊列(如Kafka的
dead-letter-topic
),供人工排查(如消息格式錯誤、下游系統故障)。
- 異常處理:
在生產者的回調函數中捕獲最終異常,記錄詳細日志(如消息內容、失敗原因),但不建議直接拋出異常中斷業務流程,應保證主流程繼續運行,僅對失敗消息做異步處理。
10. 消息一直沒發出去是什么原因?
常見原因分析:
- 網絡問題:
- 生產者與Broker之間網絡斷開(如防火墻攔截、VPN中斷)。
- Broker節點負載過高,無法處理新請求(可通過
kafka-consumer-groups.sh
查看分區Lag)。
- 元數據問題:
- 生產者未正確獲取分區元數據(如首次連接時未等待
metadata.max.age.ms
刷新)。 - 主題被刪除或分區數變更,導致生產者緩存的元數據失效。
- 生產者未正確獲取分區元數據(如首次連接時未等待
- 配置錯誤:
acks
設置為0
(不等待Broker確認),但網絡故障導致消息丟失。- 消息大小超過Broker限制(
message.max.bytes
默認1MB)。
- 權限問題:
- 生產者未被授予主題的寫入權限(如使用ACL認證時未正確配置)。
- 下游系統故障:
- 消費者組掛掉或消費速度過慢,導致分區積壓,Broker拒絕新消息寫入(需調整分區數或消費者并行度)。
二、數據庫設計與優化(問題11-18)
11. SQL怎么優化?
優化方向:
- 索引優化:
- 為高頻查詢字段添加索引(如
WHERE
、JOIN
、ORDER BY
字段),避免全表掃描。 - 避免過度索引(索引會增加寫入成本),使用
EXPLAIN
分析執行計劃,查看type
是否為range
/ref
(優于ALL
)。
- 為高頻查詢字段添加索引(如
- 分頁優化:
- 大偏移量分頁(如
LIMIT 100000, 10
)性能差,可通過子查詢或WHERE id > last_id
優化。
- 大偏移量分頁(如
- 查詢語句優化:
- 避免在索引字段上使用函數(如
SELECT * FROM users WHERE YEAR(create_time) = 2023
)。 - 用
EXISTS
替代IN
查詢子集(如SELECT * FROM A WHERE EXISTS (SELECT 1 FROM B WHERE B.a_id = A.id)
)。
- 避免在索引字段上使用函數(如
- 分庫分表:
- 單表數據量超過500萬行時,可按業務維度(如用戶ID取模)拆分到多個庫/表。
12. 怎么判斷是慢查詢?
方法:
- 開啟慢查詢日志:
MySQL中通過SET GLOBAL slow_query_log = ON;
開啟,設置long_query_time
閾值(默認10秒,可調整為0.5秒)。 - 監控工具:
- 使用
pt-query-digest
分析慢查詢日志,統計執行頻率、平均耗時、鎖等待等。 - 數據庫監控平臺(如Prometheus+MySQL Exporter)實時監控
slow_queries
指標。
- 使用
- 執行計劃分析:
對疑似慢查詢執行EXPLAIN
,查看:type
:ALL
表示全表掃描,需優化索引。rows
:掃描行數是否過大(理論上應小于總數據量的5%)。Extra
:是否包含Using filesort
(文件排序)或Using temporary
(臨時表),這兩者通常性能較差。
13. 怎么設計一個好的數據庫?
設計原則:
- 需求分析:
- 明確業務場景(如電商訂單、社交動態),識別核心實體(用戶、訂單、商品)及關系(一對一、一對多)。
- 范式設計:
- 遵循3范式(如消除重復數據、確保依賴關系正確),但可適當反范式(如冗余字段)提升查詢性能。
- 字段設計:
- 選擇合適的數據類型(如
INT
存儲用戶ID,VARCHAR(255)
存儲用戶名),避免TEXT
等大字段濫用。 - 允許
NULL
的字段需添加默認值(如create_time DEFAULT CURRENT_TIMESTAMP
)。
- 選擇合適的數據類型(如
- 性能考量:
- 主鍵設計:使用自增ID或雪花算法(分布式場景),確保主鍵查詢高效。
- 分區分表:按時間(如按年/月分表)或業務維度拆分,降低單表數據量。
- 擴展性:
- 預留字段(如
extend_info JSON
)應對未來需求變化,避免頻繁修改表結構。
- 預留字段(如
14. 項目中數據庫表怎么設計的?
示例:電商訂單系統核心表
-- 用戶表
CREATE TABLE user (user_id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 自增主鍵username VARCHAR(50) NOT NULL UNIQUE, -- 用戶名(唯一索引)password VARCHAR(64) NOT NULL, -- 密碼(加密存儲)mobile CHAR(11) UNIQUE, -- 手機號(唯一索引)create_time DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB CHARSET=utf8mb4;-- 商品表
CREATE TABLE product (product_id BIGINT PRIMARY KEY AUTO_INCREMENT,product_name VARCHAR(100) NOT NULL,category_id INT NOT NULL, -- 分類ID(外鍵關聯category表)price DECIMAL(10, 2) NOT NULL, -- 價格(精確到分)stock INT NOT NULL DEFAULT 0
) ENGINE=InnoDB;-- 訂單表
CREATE TABLE order (order_id BIGINT PRIMARY KEY, -- 雪花算法生成user_id BIGINT NOT NULL, -- 買家ID(外鍵)total_amount DECIMAL(10, 2) NOT NULL,status TINYINT NOT NULL DEFAULT 0, -- 訂單狀態(0待支付,1已支付,2已發貨)create_time DATETIME DEFAULT CURRENT_TIMESTAMP,FOREIGN KEY (user_id) REFERENCES user(user_id)
) ENGINE=InnoDB;-- 訂單詳情表
CREATE TABLE order_detail (order_id BIGINT NOT NULL, -- 訂單ID(聯合主鍵)product_id BIGINT NOT NULL, -- 商品ID(聯合主鍵)quantity INT NOT NULL,price DECIMAL(10, 2) NOT NULL,PRIMARY KEY (order_id, product_id),FOREIGN KEY (order_id) REFERENCES order(order_id),FOREIGN KEY (product_id) REFERENCES product(product_id)
) ENGINE=InnoDB;
15. 主鍵怎么設計?普通遞增 vs 分布式雪花算法
- 單庫場景:
使用AUTO_INCREMENT
自增主鍵,簡單高效,適合單節點數據庫。 - 分布式場景:
- 雪花算法(Snowflake):生成64位唯一ID(時間戳+工作節點+序列號),支持高并發,如Java的
Twitter雪花算法實現
。 - UUID:生成36位字符串(如
550e8400-e29b-41d4-a716-446655440000
),雖全球唯一但占用空間大,不適合作為主鍵(可作為業務唯一標識)。
- 雪花算法(Snowflake):生成64位唯一ID(時間戳+工作節點+序列號),支持高并發,如Java的
16. 除了雪花算法和UUID,還有什么主鍵不重復方案?
- 數據庫自增+分庫鍵:
分庫場景下,為每個庫設置不同的自增起始值和步長(如庫1起始1,步長2;庫2起始2,步長2),確保跨庫唯一。 - Redis生成ID:
使用INCR
命令原子性生成遞增ID,適合分布式系統,如:import redis r = redis.Redis(host='localhost', port=6379) order_id = r.incr('order_id_counter')
- UUID變種:
- UUID without hyphens:去掉連字符(如
550e8400e29b41d4a716446655440000
),節省空間。 - ULID(Universally Unique Lexicographical Identifier):比UUID更短(26字符),按字典序排列,適合索引。
- UUID without hyphens:去掉連字符(如
17. 具體字段類型設計:用戶名用什么類型?
- 推薦類型:
- VARCHAR(n):
n
根據業務需求設定(如VARCHAR(50)
),存儲用戶名文本(支持中文、字母、數字混合)。 - CHAR(n):固定長度(如
CHAR(20)
),適合長度一致的場景(如學號),但浪費空間,不推薦用戶名。
- VARCHAR(n):
- 注意事項:
- 字符集使用
utf8mb4
(支持Emoji和生僻字):CREATE TABLE ... CHARSET=utf8mb4;
- 唯一性約束:
UNIQUE KEY(username)
,避免重復注冊。 - 密碼字段:絕不存儲明文,使用
SHA-256
+鹽值加密(如password VARCHAR(64) NOT NULL
存儲哈希值)。
- 字符集使用
18. 什么時候用到JOIN?
適用場景:
- 關聯多張表查詢數據(如查詢訂單對應的用戶姓名和商品信息)。
- 替代子查詢,提升性能(如
SELECT u.name, o.total_amount FROM user u JOIN order o ON u.user_id = o.user_id;
)。
JOIN類型選擇:
- INNER JOIN:僅返回兩張表中匹配的行(如用戶存在且訂單存在)。
- LEFT JOIN:返回左表所有行,右表匹配不到的行用
NULL
填充(如查詢所有用戶及其訂單,包括未下單用戶)。 - RIGHT JOIN:與LEFT JOIN相反,返回右表所有行,實際中較少使用,可用LEFT JOIN+表交換替代。
- FULL OUTER JOIN(外連接):返回左右表所有行,匹配不到的用
NULL
填充(MySQL不支持,需用LEFT JOIN UNION RIGHT JOIN
模擬)。
三、加密算法(問題19-20)
19. 左連接、右連接、外連接的區別?
類型 | 定義 | 示例(表A: 用戶,表B: 訂單) |
---|---|---|
INNER JOIN | 僅返回A和B中ON 條件匹配的行 | 只返回有訂單的用戶 |
LEFT JOIN | 返回A的所有行,B中無匹配的行用NULL 填充 | 返回所有用戶,無訂單的用戶訂單字段為NULL |
RIGHT JOIN | 返回B的所有行,A中無匹配的行用NULL 填充 | 返回所有訂單,無用戶的訂單(異常數據)字段為NULL |
FULL OUTER JOIN | 返回A和B的所有行,無匹配的行用NULL 填充(MySQL不支持) | 需用A LEFT JOIN B UNION A RIGHT JOIN B 模擬 |
20. 加密算法有哪些,什么區別?
常見加密算法分類及對比:
類別 | 算法名稱 | 密鑰類型 | 用途 | 特點 |
---|---|---|---|---|
對稱加密 | AES(AES-128/256) | 單密鑰 | 數據加密(如敏感字段存儲、通信加密) | 速度快,密鑰需安全傳輸(常用作HTTPS底層加密) |
DES/3DES | 單密鑰 | 歷史系統兼容(已逐漸被AES取代) | 密鑰長度短(56位),安全性低 | |
Blowfish | 單密鑰 | 快速加密(如VPN) | 可變密鑰長度(32-448位),適合資源受限環境 | |
非對稱加密 | RSA(2048/4096位) | 公鑰+私鑰 | 數字簽名、密鑰交換(如HTTPS證書) | 安全性高,但速度慢,適合少量數據加密 |
ECC(橢圓曲線) | 公鑰+ |