點(diǎn))
華為公司招聘避坑指南:面試必問的3個硬核技術(shù)點(diǎn)
看了一堆教程還是不會寫項目?這是很多準(zhǔn)備沖擊大廠校招或社招的同學(xué)最大的痛點(diǎn)。尤其是面對華為公司招聘這種高門檻、高標(biāo)準(zhǔn)的選拔流程,光背八股文根本不夠用。HR和技術(shù)面試官最看重的是你能不能把知識落地成可運(yùn)行的代碼。
面試必問的環(huán)節(jié)里,基礎(chǔ)不扎實(shí)會被秒拒,但基礎(chǔ)扎實(shí)卻不會工程化,同樣會掛。今天我們就以華為公司招聘中常見的后端開發(fā)崗位為例,從零搭建一個高并發(fā)場景下的“短鏈接生成服務(wù)”。這個項目不大,但涵蓋了緩存、數(shù)據(jù)庫索引、異步處理等核心考點(diǎn),足以幫你打通“從教程到項目”的任督二脈。
項目目標(biāo)
我們要做的不是一個簡單的CRUD練習(xí),而是一個能抗住一定并發(fā)壓力的短鏈接服務(wù)。
為什么選這個?因?yàn)樵谌A為公司招聘的歷年面試真題庫中,系統(tǒng)設(shè)計題占比很高,而短鏈接是經(jīng)典的入門級高并發(fā)案例。它能考察你對Redis緩存策略、MySQL自增ID與雪花算法的權(quán)衡、以及后端異步IO的理解。
核心目標(biāo)拆解:輸入:長URL。
處理:生成一個6位以內(nèi)的短碼,存入Redis和MySQL。
輸出:返回短鏈接 http://s.example.com/{code}。
查詢:訪問短鏈接時,先查Redis,未命中再查MySQL并回填緩存。
性能指標(biāo):單機(jī)QPS至少達(dá)到5000,平均響應(yīng)時間10ms。這個目標(biāo)設(shè)定很務(wù)實(shí)。很多新手喜歡搞分布式微服務(wù),但如果你連單機(jī)高并發(fā)都沒吃透,面試官根本不會給你機(jī)會展示架構(gòu)能力。華為公司招聘的技術(shù)面往往從最基礎(chǔ)的原理開始深挖,直到你答不上來為止。
目錄結(jié)構(gòu)
工程化是區(qū)分“學(xué)生代碼”和“生產(chǎn)代碼”的分水嶺。在面試必問的工程實(shí)踐環(huán)節(jié),目錄混亂會被直接扣分。我們采用標(biāo)準(zhǔn)的Spring Boot分層架構(gòu),同時引入Maven管理依賴。
short-link-service/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── shortlink/
│ │ │ ├── ShortLinkApplication.java # 啟動類
│ │ │ ├── config/
│ │ │ │ ├── RedisConfig.java # Redis配置
│ │ │ │ └── WebConfig.java # Web配置
│ │ │ ├── controller/
│ │ │ │ └── ShortLinkController.java
│ │ │ ├── service/
│ │ │ │ ├── ShortLinkService.java # 接口
│ │ │ │ └── impl/
│ │ │ │ └── ShortLinkServiceImpl.java
│ │ │ ├── mapper/
│ │ │ │ └── ShortLinkMapper.java # MyBatis Plus
│ │ │ ├── entity/
│ │ │ │ └── ShortLink.java
│ │ │ └── util/
│ │ │ └── SnowflakeIdGenerator.java
│ │ └── resources/
│ │ ├── application.yml
│ │ ├── mapper/
│ │ │ └── ShortLinkMapper.xml
│ │ └── sql/
│ │ └── init.sql
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── shortlink/
│ └── ShortLinkServiceTest.java注意util包下的SnowflakeIdGenerator。為什么不用數(shù)據(jù)庫自增ID?因?yàn)樵诟卟l(fā)下,自增ID會產(chǎn)生熱點(diǎn)行競爭,導(dǎo)致數(shù)據(jù)庫性能急劇下降。而雪花算法生成的ID是全局唯一的,且趨勢遞增,非常適合做短碼的基數(shù)。這一點(diǎn)在華為公司招聘的數(shù)據(jù)庫優(yōu)化面試中,幾乎必考。
核心代碼實(shí)現(xiàn)
代碼是項目的靈魂。我們不看花哨的框架封裝,直接看核心邏輯。
1. 實(shí)體類與數(shù)據(jù)映射
@Data
@TableName(t_short_link)
public class ShortLink {@TableId(type = IdType.INPUT) // 使用自定義ID,非自增private Long id;private String url; // 原始長鏈接private String shortCode; // 短碼private Integer hitCount; // 點(diǎn)擊次數(shù)private LocalDateTime createTime;
}這里用了MyBatis Plus,IdType.INPUT表示ID由代碼生成,而非數(shù)據(jù)庫生成。
2. 雪花算法生成ID
public class SnowflakeIdGenerator {private final long twepoch = 1288834974657L;private final long workerIdBits = 5L;private final long datacenterIdBits = 5L;private final long maxWorkerId = -1L ^ (-1L workerIdBits);private final long maxDatacenterId = -1L ^ (-1L datacenterIdBits);private final long sequenceBits = 12L;private long workerId;private long datacenterId;private long sequence = 0L;private long lastTimestamp = -1L;public SnowflakeIdGenerator(long workerId, long datacenterId) {if (workerId maxWorkerId || workerId 0) {throw new IllegalArgumentException(String.format(worker Id can't be greater than %d or less than 0, maxWorkerId));}if (datacenterId maxDatacenterId || datacenterId 0) {throw new IllegalArgumentException(String.format(datacenter Id can't be greater than %d or less than 0, maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}public synchronized long nextId() {long timestamp = currentTimeMillis();if (timestamp lastTimestamp) {throw new RuntimeException(String.format(Clock moved backwards. Refusing to generate id for %s milliseconds, lastTimestamp - timestamp));}if (lastTimestamp == timestamp) {sequence = (sequence + 1) sequenceMask;if (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;return ((timestamp - twepoch) timestampLeftShift)| (datacenterId datacenterIdShift)| (workerId workerIdShift)| sequence;}// ... 輔助方法省略
}這段代碼來自Twitter的開源實(shí)現(xiàn),是行業(yè)事實(shí)標(biāo)準(zhǔn)。在官方文檔如Spring Boot或Netty的并發(fā)處理章節(jié)中,都推薦類似的時間戳+機(jī)器ID+序列號的組合方式。面試時,如果面試官問“如果時鐘回?fù)茉趺崔k”,你要能答出“等待時鐘追上”或“使用備用機(jī)器ID”等策略,而不是只會背代碼。
3. 核心服務(wù)邏輯
@Service
public class ShortLinkServiceImpl implements ShortLinkService {@Autowiredprivate StringRedisTemplate redisTemplate;@Autowiredprivate ShortLinkMapper shortLinkMapper;@Autowiredprivate SnowflakeIdGenerator idGenerator;private static final String CACHE_KEY_PREFIX = shortlink:;private static final int CACHE_EXPIRE_SECONDS = 3600;@Overridepublic String generateShortLink(String longUrl) {// 1. 校驗(yàn)URL合法性if (!isValidUrl(longUrl)) {throw new IllegalArgumentException(Invalid URL format);}// 2. 檢查緩存是否已存在該長鏈接String cacheKey = CACHE_KEY_PREFIX + longUrl;String existingCode = redisTemplate.opsForValue().get(cacheKey);if (existingCode != null) {return existingCode;}// 3. 生成短碼String shortCode = generateShortCode();// 4. 保存至數(shù)據(jù)庫ShortLink entity = new ShortLink();entity.setId(idGenerator.nextId());entity.setUrl(longUrl);entity.setShortCode(shortCode);entity.setHitCount(0);entity.setCreateTime(LocalDateTime.now());shortLinkMapper.insert(entity);// 5. 寫入緩存redisTemplate.opsForValue().set(cacheKey, shortCode, CACHE_EXPIRE_SECONDS, TimeUnit.SECONDS);return shortCode;}private String generateShortCode() {long id = idGenerator.nextId();// 將ID轉(zhuǎn)換為36進(jìn)制字符串,縮短長度return Long.toString(id, 36);}private boolean isValidUrl(String url) {// 簡單的正則校驗(yàn),實(shí)際生產(chǎn)環(huán)境建議使用更嚴(yán)格的URI解析return url != null url.matches(https?://.*);}
}逐行解析關(guān)鍵點(diǎn):緩存穿透防護(hù):這里沒有做布隆過濾器,因?yàn)槎替溄邮恰跋炔楹髮憽?,如果URL不存在,我們直接生成新的。但在真實(shí)場景中,如果擔(dān)心惡意查詢不存在的URL,需要加布隆過濾器。
36進(jìn)制轉(zhuǎn)換:Long.toString(id, 36) 是生成短碼的關(guān)鍵。二進(jìn)制轉(zhuǎn)十進(jìn)制太長,轉(zhuǎn)36進(jìn)制(0-9, a-z)可以顯著縮短字符串長度。
原子性:注意,insert和set之間不是原子的。如果DB插入成功但Redis寫入失敗,下次請求會再次生成新短碼,導(dǎo)致同一個長URL對應(yīng)多個短碼。這在業(yè)務(wù)上通常可接受,但如果要求嚴(yán)格唯一,需要使用Redis的SETNX或數(shù)據(jù)庫唯一索引兜底。運(yùn)行與測試
代碼寫完,必須跑通。在華為公司招聘的現(xiàn)場筆試或編程題中,代碼能跑通、輸出正確,是及格線。
1. 數(shù)據(jù)庫初始化
CREATE TABLE `t_short_link` (`id` bigint(20) NOT NULL COMMENT '主鍵ID',`url` varchar(255) NOT NULL COMMENT '原始鏈接',`short_code` varchar(10) NOT NULL COMMENT '短碼',`hit_count` int(11) DEFAULT '0' COMMENT '點(diǎn)擊次數(shù)',`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',PRIMARY KEY (`id`),UNIQUE KEY `uk_short_code` (`short_code`),KEY `idx_url` (`url`(191)) -- 前綴索引,因?yàn)閡rl可能很長
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;避坑提示:url字段如果直接加唯一索引,會占用大量空間且效率低。使用前綴索引idx_url(191)可以解決長字符串索引問題。這也是MySQL優(yōu)化的經(jīng)典考點(diǎn)。
2. JUnit單元測試
@SpringBootTest
public class ShortLinkServiceTest {@Autowiredprivate ShortLinkService shortLinkService;@Testpublic void testGenerateShortLink() {String longUrl = https://www.example.com/very/long/path?id=123name=test;String shortCode = shortLinkService.generateShortLink(longUrl);// 斷言1:短碼不為空assertNotNull(shortCode);// 斷言2:短碼長度在合理范圍內(nèi)assertTrue(shortCode.length() = 10);// 斷言3:再次生成,應(yīng)返回相同的短碼(緩存生效)String shortCode2 = shortLinkService.generateShortLink(longUrl);assertEquals(shortCode, shortCode2);}
}測試時,務(wù)必使用@SpringBootTest加載完整上下文,包括Redis連接。如果本地沒裝Redis,可以用Docker快速啟動:
docker run -d -p 6379:6379 redis:alpine在面試必問的測試環(huán)節(jié),面試官會問:“如果Redis掛了,你的服務(wù)會怎樣?”
答案應(yīng)該是:服務(wù)降級。捕獲Redis異常,直接查MySQL。雖然性能下降,但保證可用性。這叫“熔斷”思想的初級應(yīng)用。
優(yōu)化擴(kuò)展
項目能跑,不代表項目好。在華為公司招聘的終面或架構(gòu)面中,優(yōu)化能力決定了你能拿多高的Offer。
1. 異步落庫優(yōu)化
上面的代碼是同步寫DB和Redis。在高并發(fā)下,DB寫入會成為瓶頸。
優(yōu)化方案:引入消息隊列(如Kafka或RabbitMQ)。生成短碼后,只寫Redis。
發(fā)送消息到MQ。
消費(fèi)者異步消費(fèi)消息,寫入MySQL。這樣,主鏈路(用戶請求)的耗時從“Redis+MySQL”變成了“Redis”,延遲降低50%以上。
2. 緩存雪崩防護(hù)
所有短鏈接的過期時間如果設(shè)置成一樣的,一旦大量key同時過期,流量會瞬間打到DB。
優(yōu)化方案:在CACHE_EXPIRE_SECONDS基礎(chǔ)上,增加一個隨機(jī)數(shù)(例如0-300秒)。
int randomExpire = ThreadLocalRandom.current().nextInt(300);
redisTemplate.opsForValue().set(cacheKey, shortCode, CACHE_EXPIRE_SECONDS + randomExpire, TimeUnit.SECONDS);3. 點(diǎn)擊計數(shù)異步化
hitCount字段如果在每次訪問時都更新DB,性能極差。
優(yōu)化方案:使用Redis的INCR命令原子遞增計數(shù),定時任務(wù)(如每5分鐘)將Redis中的計數(shù)同步回MySQL。
這些優(yōu)化點(diǎn),每一個都足以作為面試必問的深入話題。不要怕答不出來,要敢于展示你的思考過程。面試官喜歡的是“我知道哪里慢,我嘗試過這樣優(yōu)化,雖然沒上線但我知道原理”的候選人。
小結(jié)
這個項目雖然簡單,但覆蓋了華為公司招聘后端崗的三大核心:并發(fā)控制(雪花算法、同步鎖)、緩存策略(穿透、雪崩、一致性)、數(shù)據(jù)庫優(yōu)化(索引、異步)。
記住,教程是死的,項目是活的。你不需要寫出百萬行代碼,但你需要把一個百行代碼的項目,講出深度,講出坑,講出優(yōu)化思路。
你在項目里踩過這個坑嗎?比如雪花算法時鐘回?fù)軐?dǎo)致ID重復(fù),或者Redis與MySQL數(shù)據(jù)不一致?評論區(qū)聊聊,看看有多少人中招。