一份热乎乎的字节面试真题
前言
有位伙伴面試了字節(四年半工作經驗),分享下面試真題,大家一起加油哈。
1.說說Redis為什么快
1.1 基于內存存儲實現
內存讀寫是比在磁盤快很多的,Redis基于內存存儲實現的數據庫,相對于數據存在磁盤的MySQL數據庫,省去磁盤I/O的消耗。
1.2 高效的數據結構
Mysql索引為了提高效率,選擇了B+樹的數據結構。其實合理的數據結構,就是可以讓你的應用/程序更快。先看下Redis的數據結構&內部編碼圖:
1.2.1 SDS簡單動態字符串
- 字符串長度處理:Redis獲取字符串長度,時間復雜度為O(1),而C語言中,需要從頭開始遍歷,復雜度為O(n);
- 空間預分配:字符串修改越頻繁的話,內存分配越頻繁,就會消耗性能,而SDS修改和空間擴充,會額外分配未使用的空間,減少性能損耗。
- 惰性空間釋放:SDS 縮短時,不是回收多余的內存空間,而是free記錄下多余的空間,后續有變更,直接使用free中記錄的空間,減少分配。
1.2.2 字典
Redis 作為 K-V 型內存數據庫,所有的鍵值就是用字典來存儲。字典就是哈希表,比如HashMap,通過key就可以直接獲取到對應的value。而哈希表的特性,在O(1)時間復雜度就可以獲得對應的值。
1.2.3 跳躍表
- 跳躍表是Redis特有的數據結構,就是在鏈表的基礎上,增加多級索引提升查找效率。
- 跳躍表支持平均 O(logN),最壞 O(N)復雜度的節點查找,還可以通過順序性操作批量處理節點。
1.3 合理的數據編碼
Redis 支持多種數據類型,每種基本類型,可能對多種數據結構。什么時候,使用什么樣數據結構,使用什么樣編碼,是redis設計者總結優化的結果。
- String:如果存儲數字的話,是用int類型的編碼;如果存儲非數字,小于等于39字節的字符串,是embstr;大于39個字節,則是raw編碼。
- List:如果列表的元素個數小于512個,列表每個元素的值都小于64字節(默認),使用ziplist編碼,否則使用linkedlist編碼
- Hash:哈希類型元素個數小于512個,所有值小于64字節的話,使用ziplist編碼,否則使用hashtable編碼。
- Set:如果集合中的元素都是整數且元素個數小于512個,使用intset編碼,否則使用hashtable編碼。
- Zset:當有序集合的元素個數小于128個,每個元素的值小于64字節時,使用ziplist編碼,否則使用skiplist(跳躍表)編碼
1.4 合理的線程模型
I/O 多路復用
多路I/O復用技術可以讓單個線程高效的處理多個連接請求,而Redis使用用epoll作為I/O多路復用技術的實現。并且,Redis自身的事件處理模型將epoll中的連接、讀寫、關閉都轉換為事件,不在網絡I/O上浪費過多的時間。
2. Redis有幾種數據結構,底層分別是怎么存儲的
常用的,Redis有以下這五種基本類型:
-
String(字符串)
-
Hash(哈希)
-
List(列表)
-
Set(集合)
-
zset(有序集合) 它還有三種特殊的數據結構類型
-
Geospatial
-
Hyperloglog
-
Bitmap
2.1 Redis 的五種基本數據類型
String(字符串)
- 簡介:String是Redis最基礎的數據結構類型,它是二進制安全的,可以存儲圖片或者序列化的對象,值最大存儲為512M
- 簡單使用舉例: set key value、get key等
- 應用場景:共享session、分布式鎖,計數器、限流。
- 內部編碼有3種,int(8字節長整型)/embstr(小于等于39字節字符串)/raw(大于39個字節字符串)
Hash(哈希)
- 簡介:在Redis中,哈希類型是指v(值)本身又是一個鍵值對(k-v)結構
- 簡單使用舉例:hset key field value 、hget key field
- 內部編碼:ziplist(壓縮列表) 、hashtable(哈希表)
- 應用場景:緩存用戶信息等。
List(列表)
- 簡介:列表(list)類型是用來存儲多個有序的字符串,一個列表最多可以存儲2^32-1個元素。
- 簡單實用舉例:lpush key value [value ...] 、lrange key start end
- 內部編碼:ziplist(壓縮列表)、linkedlist(鏈表)
- 應用場景:消息隊列,文章列表
Set(集合)
- 簡介:集合(set)類型也是用來保存多個的字符串元素,但是不允許重復元素
- 簡單使用舉例:sadd key element [element ...]、smembers key
- 內部編碼:intset(整數集合)、hashtable(哈希表)
- 應用場景:用戶標簽,生成隨機數抽獎、社交需求。
有序集合(zset)
- 簡介:已排序的字符串集合,同時元素不能重復
- 簡單格式舉例:zadd key score member [score member ...],zrank key member
- 底層內部編碼:ziplist(壓縮列表)、skiplist(跳躍表)
- 應用場景:排行榜,社交需求(如用戶點贊)。
2.2 Redis 的三種特殊數據類型
- Geo:Redis3.2推出的,地理位置定位,用于存儲地理位置信息,并對存儲的信息進行操作。
- HyperLogLog:用來做基數統計算法的數據結構,如統計網站的UV。
- Bitmaps :用一個比特位來映射某個元素的狀態,在Redis中,它的底層是基于字符串類型實現的,可以把bitmaps成作一個以比特位為單位的數組
3. Redis有幾種持久化方式
Redis是基于內存的非關系型K-V數據庫,既然它是基于內存的,如果Redis服務器掛了,數據就會丟失。為了避免數據丟失了,Redis提供了持久化,即把數據保存到磁盤。
Redis提供了RDB和AOF兩種持久化機制,它持久化文件加載流程如下:
3.1 RDB
RDB,就是把內存數據以快照的形式保存到磁盤上。
什么是快照?可以這樣理解,給當前時刻的數據,拍一張照片,然后保存下來。
RDB持久化,是指在指定的時間間隔內,執行指定次數的寫操作,將內存中的數據集快照寫入磁盤中,它是Redis默認的持久化方式。執行完操作后,在指定目錄下會生成一個dump.rdb文件,Redis 重啟的時候,通過加載dump.rdb文件來恢復數據。RDB觸發機制主要有以下幾種:
RDB 的優點
適合大規模的數據恢復場景,如備份,全量復制等
RDB缺點
- 沒辦法做到實時持久化/秒級持久化。
- 新老版本存在RDB格式兼容問題
3.2 AOF
AOF(append only file) 持久化,采用日志的形式來記錄每個寫操作,追加到文件中,重啟時再重新執行AOF文件中的命令來恢復數據。它主要解決數據持久化的實時性問題。默認是不開啟的。
AOF的工作流程如下:
AOF的優點
數據的一致性和完整性更高
AOF的缺點
AOF記錄的內容越多,文件越大,數據恢復變慢。
4. 多線程情況下,如何保證線程安全?
加鎖,比如悲觀鎖select for update,sychronized等,如,樂觀鎖,樂觀鎖如CAS等,還有redis分布式鎖等等。
5. 用過volatile嗎?它是如何保證可見性的,原理是什么
volatile關鍵字是Java虛擬機提供的的最輕量級的同步機制,它作為一個修飾符, 用來修飾變量。它保證變量對所有線程可見性,禁止指令重排,但是不保證原子性。
我們先來看下java內存模型(jmm):
- Java虛擬機規范試圖定義一種Java內存模型,來屏蔽掉各種硬件和操作系統的內存訪問差異,以實現讓Java程序在各種平臺上都能達到一致的內存訪問效果。
- Java內存模型規定所有的變量都是存在主內存當中,每個線程都有自己的工作內存。這里的變量包括實例變量和靜態變量,但是不包括局部變量,因為局部變量是線程私有的。
- 線程的工作內存保存了被該線程使用的變量的主內存副本,線程對變量的所有操作都必須在工作內存中進行,而不能直接操作操作主內存。并且每個線程不能訪問其他線程的工作內存。
volatile變量,保證新值能立即同步回主內存,以及每次使用前立即從主內存刷新,所以我們說volatile保證了多線程操作變量的可見性。
volatile保證可見性跟內存屏障有關。我們來看一段volatile使用的demo代碼:
public class Singleton { private volatile static Singleton instance; private Singleton (){} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; } } 復制代碼編譯后,對比有volatile關鍵字和沒有volatile關鍵字時所生成的匯編代碼,發現有volatile關鍵字修飾時,會多出一個lock addl $0x0,(%esp),即多出一個lock前綴指令,lock指令相當于一個內存屏障
lock指令相當于一個內存屏障,它保證以下這幾點:
第2點和第3點就是保證volatile保證可見性的體現嘛
6. MySQL的索引結構,聚簇索引和非聚簇索引的區別
- 一個表中只能擁有一個聚集索引,而非聚集索引一個表可以存在多個。
- 索引是通過二叉樹的數據結構來描述的,我們可以這么理解聚簇索引:索引的葉節點就是數據節點。而非聚簇索引的葉節點仍然是索引節點,只不過有一個指針指向對應的數據塊。
- 聚集索引:物理存儲按照索引排序;非聚集索引:物理存儲不按照索引排序
7. MySQL有幾種高可用方案,你們用的是哪一種
- 主從或主主半同步復制
- 半同步復制優化
- 高可用架構優化
- 共享存儲
- 分布式協議
7.1 主從或主主半同步復制
用雙節點數據庫,搭建單向或者雙向的半同步復制。架構如下:
通常會和proxy、keepalived等第三方軟件同時使用,即可以用來監控數據庫的健康,又可以執行一系列管理命令。如果主庫發生故障,切換到備庫后仍然可以繼續使用數據庫。
這種方案優點是架構、部署比較簡單,主機宕機直接切換即可。缺點是完全依賴于半同步復制,半同步復制退化為異步復制,無法保證數據一致性;另外,還需要額外考慮haproxy、keepalived的高可用機制。
7.2 半同步復制優化
半同步復制機制是可靠的,可以保證數據一致性的。但是如果網絡發生波動,半同步復制發生超時會切換為異步復制,異復制是無法保證數據的一致性的。因此,可以在半同復制的基礎上優化一下,盡可能保證半同復制。如雙通道復制方案
- 優點:這種方案架構、部署也比較簡單,主機宕機也是直接切換即可。比方案1的半同步復制,更能保證數據的一致性。
- 缺點:需要修改內核源碼或者使用mysql通信協議,沒有從根本上解決數據一致性問題。
7.3 高可用架構優化
保證高可用,可以把主從雙節點數據庫擴展為數據庫集群。Zookeeper可以作為集群管理,它使用分布式算法保證集群數據的一致性,可以較好的避免網絡分區現象的產生。
- 優點:保證了整個系統的高可用性,擴展性也較好,可以擴展為大規模集群。
- 缺點:數據一致性仍然依賴于原生的mysql半同步復制;引入Zookeeper使系統邏輯更復雜。
7.4 共享存儲
共享存儲實現了數據庫服務器和存儲設備的解耦,不同數據庫之間的數據同步不再依賴于MySQL的原生復制功能,而是通過磁盤數據同步的手段,來保證數據的一致性。
DRBD磁盤復制
DRBD是一個用軟件實現的、無共享的、服務器之間鏡像塊設備內容的存儲復制解決方案。主要用于對服務器之間的磁盤、分區、邏輯卷等進行數據鏡像,當用戶將數據寫入本地磁盤時,還會將數據發送到網絡中另一臺主機的磁盤上,這樣的本地主機(主節點)與遠程主機(備節點)的數據就可以保證實時同步。常用架構如下:
當本地主機出現問題,遠程主機上還保留著一份相同的數據,即可以繼續使用,保證了數據的安全。
- 優點:部署簡單,價格合適,保證數據的強一致性
- 缺點:對IO性能影響較大,從庫不提供讀操作
7.5 分布式協議
分布式協議可以很好解決數據一致性問題。常見的部署方案就是MySQL cluster,它是官方集群的部署方案,通過使用NDB存儲引擎實時備份冗余數據,實現數據庫的高可用性和數據一致性。如下:
- 優點:不依賴于第三方軟件,可以實現數據的強一致性;
- 缺點:配置較復雜;需要使用NDB儲存引擎;至少三節點;
8. 說說你做過最有挑戰性的項目, 你負責那個模塊,哪些最有挑戰性,說說你做了哪些優化
項目這塊的話,大家可以結合自己實際做的項目說哈。也可以加我微信,跟我一起交流哈,加油加油。
9.秒殺采用什么方案。
設計一個秒殺系統,需要考慮這些問題:
如何解決這些問題呢?
- 頁面靜態化
- 按鈕至灰控制
- 服務單一職責
- 秒殺鏈接加鹽
- 限流
- 分布式鎖
- MQ異步處理
- 限流&降級&熔斷
9.1 頁面靜態化
秒殺活動的頁面,大多數內容都是固定不變的,如商品名稱,商品圖片等等,可以對活動頁面做靜態化處理,減少訪問服務端的請求。秒殺用戶會分布在全國各地,有的在上海,有的在深圳,地域相差很遠,網速也各不相同。為了讓用戶最快訪問到活動頁面,可以使用CDN(Content Delivery Network,內容分發網絡)。CDN可以讓用戶就近獲取所需內容。
9.2 按鈕至灰控制
秒殺活動開始前,按鈕一般需要置灰的。只有時間到了,才能變得可以點擊。這是防止,秒殺用戶在時間快到的前幾秒,瘋狂請求服務器,然后秒殺時間點還沒到,服務器就自己掛了。
9.3 服務單一職責
我們都知道微服務設計思想,也就是把各個功能模塊拆分,功能那個類似的放一起,再用分布式的部署方式。
如用戶登錄相關的,就設計個用戶服務,訂單相關的就搞個訂單服務,再到禮物相關的就搞個禮物服務等等。那么,秒殺相關的業務邏輯也可以放到一起,搞個秒殺服務,單獨給它搞個秒殺數據庫?!?服務單一職責有個好處:如果秒殺沒抗住高并發的壓力,秒殺庫崩了,服務掛了,也不會影響到系統的其他服務。
9.4 秒殺鏈接加鹽
鏈接如果明文暴露的話,會有人獲取到請求Url,提前秒殺了。因此,需要給秒殺鏈接加鹽??梢园裊RL動態化,如通過MD5加密算法加密隨機的字符串去做url。
9.5 限流
一般有兩種方式限流:nginx限流和redis限流。
- 為了防止某個用戶請求過于頻繁,我們可以對同一用戶限流;
- 為了防止黃牛模擬幾個用戶請求,我們可以對某個IP進行限流;
- 為了防止有人使用代理,每次請求都更換IP請求,我們可以對接口進行限流。
- 為了防止瞬時過大的流量壓垮系統,還可以使用阿里的Sentinel、Hystrix組件進行限流。
9.6 分布式鎖
可以使用redis分布式鎖解決超賣問題。
使用Redis的SET EX PX NX + 校驗唯一隨機值,再刪除釋放鎖。
if(jedis.set(key_resource_id, uni_request_id, "NX", "EX", 100s) == 1){ //加鎖try {do something //業務處理}catch(){}finally {//判斷是不是當前線程加的鎖,是才釋放if (uni_request_id.equals(jedis.get(key_resource_id))) {jedis.del(lockKey); //釋放鎖}} } 復制代碼在這里,判斷是不是當前線程加的鎖和釋放鎖不是一個原子操作。如果調用jedis.del()釋放鎖的時候,可能這把鎖已經不屬于當前客戶端,會解除他人加的鎖。
為了更嚴謹,一般也是用lua腳本代替。lua腳本如下:
if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) elsereturn 0 end; 復制代碼9.7 MQ異步處理
如果瞬間流量特別大,可以使用消息隊列削峰,異步處理。用戶請求過來的時候,先放到消息隊列,再拿出來消費。
9.8 限流&降級&熔斷
- 限流,就是限制請求,防止過大的請求壓垮服務器;
- 降級,就是秒殺服務有問題了,就降級處理,不要影響別的服務;
- 熔斷,服務有問題就熔斷,一般熔斷降級是一起出現。
10. 聊聊分庫分表,分表為什么要停服這種操作,如果不停服可以怎么做
10.1 分庫分表方案
- 水平分庫:以字段為依據,按照一定策略(hash、range等),將一個庫中的數據拆分到多個庫中。
- 水平分表:以字段為依據,按照一定策略(hash、range等),將一個表中的數據拆分到多個表中。
- 垂直分庫:以表為依據,按照業務歸屬不同,將不同的表拆分到不同的庫中。
- 垂直分表:以字段為依據,按照字段的活躍性,將表中字段拆到不同的表(主表和擴展表)中。
10.2 常用的分庫分表中間件:
- sharding-jdbc(當當)
- Mycat
- TDDL(淘寶)
- Oceanus(58同城數據庫中間件)
- vitess(谷歌開發的數據庫中間件)
- Atlas(Qihoo 360)
10.3 分庫分表可能遇到的問題
- 事務問題:需要用分布式事務啦
- 跨節點Join的問題:解決這一問題可以分兩次查詢實現
- 跨節點的count,order by,group by以及聚合函數問題:分別在各個節點上得到結果后在應用程序端進行合并。
- 數據遷移,容量規劃,擴容等問題
- ID問題:數據庫被切分后,不能再依賴數據庫自身的主鍵生成機制啦,最簡單可以考慮UUID
- 跨分片的排序分頁問題(后臺加大pagesize處理?)
10.4 分表要停服嘛?不停服怎么做?
不用。不停服的時候,應該怎么做呢,分五個步驟:
11. redis掛了怎么辦?
Redis是基于內存的非關系型K-V數據庫,既然它是基于內存的,如果Redis服務器掛了,數據就會丟失。為了避免數據丟失了,Redis提供了持久化,即把數據保存到磁盤。
Redis提供了RDB和AOF兩種持久化機制,它持久化文件加載流程如下:
11.1 RDB
RDB,就是把內存數據以快照的形式保存到磁盤上。
什么是快照? 可以這樣理解,給當前時刻的數據,拍一張照片,然后保存下來。
RDB持久化,是指在指定的時間間隔內,執行指定次數的寫操作,將內存中的數據集快照寫入磁盤中,它是Redis默認的持久化方式。執行完操作后,在指定目錄下會生成一個dump.rdb文件,Redis 重啟的時候,通過加載dump.rdb文件來恢復數據。RDB觸發機制主要有以下幾種:
RDB 的優點
- 適合大規模的數據恢復場景,如備份,全量復制等
RDB缺點
- 沒辦法做到實時持久化/秒級持久化。
- 新老版本存在RDB格式兼容問題
11.2 AOF
AOF(append only file) 持久化,采用日志的形式來記錄每個寫操作,追加到文件中,重啟時再重新執行AOF文件中的命令來恢復數據。它主要解決數據持久化的實時性問題。默認是不開啟的。
AOF的工作流程如下:
AOF的優點
- 數據的一致性和完整性更高
AOF的缺點
- AOF記錄的內容越多,文件越大,數據恢復變慢。
12.你怎么防止優惠券有人重復刷?
對于重復請求,要考慮接口冪等和接口防重。
防刷的話,可以限流以及加入黑名單處理。
- 為了防止某個用戶請求優惠券過于頻繁,我們可以對同一用戶限流。
- 為了防止黃牛等模擬幾個用戶請求,我們可以對某個IP進行限流。
- 為了防止有人使用代理,每次請求都更換IP請求,我們可以對接口進行限流。
13. 抖音評論系統怎么設計,如果加入好友關系呢?
需要考慮性能,以及可擴展性。大家平時有沒有做過評論、好友關注等項目需求呀,發揮你聰明的小腦袋瓜,怎么去回答好這道題吧。
14. 怎么設計一個短鏈地址,要考慮跨機房部署問題
14.1 為什么需要短連接?
為什么需要短連接呢?長鏈接不香嗎?因為有些平臺有長度限制,并且鏈接太長容易被識別為超鏈接等等。
14.2 短鏈接的原理
其實就是一個302重定向而已。
302狀態碼表示臨時重定向。
14.3 短鏈接生成的方法
可以用哈希算法生成短鏈,但是會存在哈希沖突。怎么解決呢?可以用布隆過濾器。
有沒有別的方案?自增序列算法,每次收到一個長鏈時,就分配一個ID,并轉成62進制拼接到短域后面。因為高并發下,ID 自增生成器可能成為瓶頸。
一般有四種分布式ID生成方法:
15.有一個整型數組,數組元素不重復,數組元素先升序后降序,找出最大值。
例如:1,3,5,7,9,8,6,4,2,請寫一個函數找出數組最大的元素
這道題大家都會覺得很簡單,因為用快速排序排一下,時間復雜是O(nlgn),面試官可能不是很滿意。其實可以用二分查找法,只要找到升序最后一個元素即可。
我們以1,6,5,4,2為例子,用二分法圖解一下哈:
如何用二分法縮小空間呢?只要比較中間元素與其下一個元素大小即可
因為nums[mid]=5>nums[mid+1]=4,因此右指針左移,right=mid-1=2-1=1
mid = left+ (right-left)/2=left=0,因為nums[mid]=1<nums[mid+1]=6,所以左指針右移,left = mid+1=1;
最后得出最大值是nums[left] =nums[1]=6
實現代碼如下:
class Solution {public static void main(String[] args) {int[] nums = {1,3,5,7,9,8,6,4,2};System.out.println(getLargestNumInArray(nums));}private static int getLargestNumInArray(int[] nums) {if (nums == null || nums.length == 0) {return -1;}int left = 0, right = nums.length - 1;while (left <= right) {int mid = left + ((right - left) / 2);if (nums[mid] < nums[mid + 1]) {left = mid + 1;} else {right = mid - 1;}}return nums[left];} }總結
以上是生活随笔為你收集整理的一份热乎乎的字节面试真题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大话Chrome浏览器原理
- 下一篇: ButterKnife被弃用,ViewB