日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

【面朝大厂】万字+图解 Redis,面试不用愁了!

發布時間:2023/12/20 数据库 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【面朝大厂】万字+图解 Redis,面试不用愁了! 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

點擊上方“Java基基”,選擇“設為星標”

做積極的人,而不是積極廢人!

源碼精品專欄

?
  • 原創 | Java 2020?超神之路,很肝~

  • 中文詳細注釋的開源項目

  • RPC 框架 Dubbo 源碼解析

  • 網絡應用框架 Netty 源碼解析

  • 消息中間件 RocketMQ 源碼解析

  • 數據庫中間件 Sharding-JDBC 和 MyCAT 源碼解析

  • 作業調度中間件 Elastic-Job 源碼解析

  • 分布式事務中間件 TCC-Transaction 源碼解析

  • Eureka 和 Hystrix 源碼解析

  • Java 并發源碼

來源:juejin.cn/post/6844904017387077640

  • Redis是什么

  • 五種數據類型

  • Redis緩存

  • 緩存問題

  • Redis為何這么快

  • Redis和Memcached的區別

  • 淘汰策略

  • 持久化

  • 主從復制

  • 哨兵

  • 總結


今天,我不自量力的面試了某大廠的java開發崗位,迎面走來一位風塵仆仆的中年男子,手里拿著屏幕還亮著的mac,他沖著我禮貌的笑了笑,然后說了句“不好意思,讓你久等了”,然后示意我坐下,說:“我們開始吧。看了你的簡歷,覺得你對redis應該掌握的不錯,我們今天就來討論下redis......”。我想:“來就來,兵來將擋水來土掩”。

Redis是什么

  • 面試官:你先來說下redis是什么吧

  • 我:(這不就是總結下redis的定義和特點嘛)Redis是C語言開發的一個開源的(遵從BSD協議)高性能鍵值對(key-value)的內存數據庫,可以用作數據庫、緩存、消息中間件等。它是一種NoSQL(not-only sql,泛指非關系型數據庫)的數據庫。

  • 我頓了一下,接著說:Redis作為一個內存數據庫。

    • 1、性能優秀,數據在內存中,讀寫速度非常快,支持并發10W QPS;

    • 2、單進程單線程,是線程安全的,采用IO多路復用機制;

    • 3、豐富的數據類型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等;4、支持數據持久化。可以將內存中數據保存在磁盤中,重啟時加載;5、主從復制,哨兵,高可用;6、可以用作分布式鎖;7、可以作為消息中間件使用,支持發布訂閱

五種數據類型

  • 面試官:總結的不錯,看來是早有準備啊。剛來聽你提到redis支持五種數據類型,那你能簡單說下這五種數據類型嗎?

  • 我:當然可以,但是在說之前,我覺得有必要先來了解下Redis內部內存管理是如何描述這5種數據類型的。說著,我拿著筆給面試官畫了一張圖:

  • 我:首先redis內部使用一個redisObject對象來表示所有的key和value,redisObject最主要的信息如上圖所示:type表示一個value對象具體是何種數據類型,encoding是不同數據類型在redis內部的存儲方式。比如:type=string表示value存儲的是一個普通字符串,那么encoding可以是raw或者int。

  • 我頓了一下,接著說:下面我簡單說下5種數據類型:

    • 1、string是redis最基本的類型,可以理解成與memcached一模一樣的類型,一個key對應一個value。value不僅是string,也可以是數字。string類型是二進制安全的,意思是redis的string類型可以包含任何數據,比如jpg圖片或者序列化的對象。string類型的值最大能存儲512M。

    • 2、Hash是一個鍵值(key-value)的集合。redis的hash是一個string的key和value的映射表,Hash特別適合存儲對象。常用命令:hget,hset,hgetall等。

    • 3、list列表是簡單的字符串列表,按照插入順序排序。可以添加一個元素到列表的頭部(左邊)或者尾部(右邊) ?常用命令:lpush、rpush、lpop、rpop、lrange(獲取列表片段)等。應用場景:list應用場景非常多,也是Redis最重要的數據結構之一,比如twitter的關注列表,粉絲列表都可以用list結構來實現。數據結構:list就是鏈表,可以用來當消息隊列用。redis提供了List的push和pop操作,還提供了操作某一段的api,可以直接查詢或者刪除某一段的元素。實現方式:redis list的是實現是一個雙向鏈表,既可以支持反向查找和遍歷,更方便操作,不過帶來了額外的內存開銷。

    • 4、set是string類型的無序集合。集合是通過hashtable實現的。set中的元素是沒有順序的,而且是沒有重復的。常用命令:sdd、spop、smembers、sunion等。應用場景:redis set對外提供的功能和list一樣是一個列表,特殊之處在于set是自動去重的,而且set提供了判斷某個成員是否在一個set集合中。

    • 5、zset和set一樣是string類型元素的集合,且不允許重復的元素。常用命令:zadd、zrange、zrem、zcard等。使用場景:sorted set可以通過用戶額外提供一個優先級(score)的參數來為成員排序,并且是插入有序的,即自動排序。當你需要一個有序的并且不重復的集合列表,那么可以選擇sorted set結構。和set相比,sorted set關聯了一個double類型權重的參數score,使得集合中的元素能夠按照score進行有序排列,redis正是通過分數來為集合中的成員進行從小到大的排序。實現方式:Redis sorted set的內部使用HashMap和跳躍表(skipList)來保證數據的存儲和有序,HashMap里放的是成員到score的映射,而跳躍表里存放的是所有的成員,排序依據是HashMap里存的score,使用跳躍表的結構可以獲得比較高的查找效率,并且在實現上比較簡單。

  • 我:我之前總結了一張圖,關于數據類型的應用場景,如果您感興趣,可以去我的掘金看。。

數據類型應用場景總結

類型簡介特性場景
string(字符串)二進制安全可以包含任何數據,比如jpg圖片或者序列化對象---
Hash(字典)鍵值對集合,即編程語言中的map類型適合存儲對象,并且可以像數據庫中的update一個屬性一樣只修改某一項屬性值存儲、讀取、修改用戶屬性
List(列表)鏈表(雙向鏈表)增刪快,提供了操作某一元素的api最新消息排行;消息隊列
set(集合)hash表實現,元素不重復添加、刪除、查找的復雜度都是O(1),提供了求交集、并集、差集的操作共同好友;利用唯一性,統計訪問網站的所有Ip
sorted set(有序集合)將set中的元素增加一個權重參數score,元素按score有序排列數據插入集合時,已經進行了天然排序排行榜;帶權重的消息隊列

  • 面試官:想不到你平時也下了不少工夫,那redis緩存你一定用過的吧

  • 我:用過的。。

  • 面試官:那你跟我說下你是怎么用的?

  • 我是結合spring boot使用的。一般有兩種方式,一種是直接通過RedisTemplate來使用,另一種是使用spring cache集成Redis(也就是注解的方式)。具體的代碼我就不說了,在我的掘金中有一個demo(見下)。

Redis緩存

  • 直接通過RedisTemplate來使用

  • 使用spring cache集成Redis pom.xml中加入以下依賴:

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency> </dependencies>
  • spring-boot-starter-data-redis:在spring boot 2.x以后底層不再使用Jedis,而是換成了Lettuce。

  • commons-pool2:用作redis連接池,如不引入啟動會報錯

  • spring-session-data-redis:spring session引入,用作共享session。配置文件application.yml的配置:

server:port:?8082servlet:session:timeout:?30ms spring:cache:type:?redisredis:host:?127.0.0.1port:?6379password:#?redis默認情況下有16個分片,這里配置具體使用的分片,默認為0database:?0lettuce:pool:#?連接池最大連接數(使用負數表示沒有限制),默認8max-active:?100

創建實體類User.java

public?class?User?implements?Serializable{private?static?final?long?serialVersionUID?=?662692455422902539L;private?Integer?id;private?String?name;private?Integer?age;public?User()?{}public?User(Integer?id,?String?name,?Integer?age)?{this.id?=?id;this.name?=?name;this.age?=?age;}public?Integer?getId()?{return?id;}public?void?setId(Integer?id)?{this.id?=?id;}public?String?getName()?{return?name;}public?void?setName(String?name)?{this.name?=?name;}public?Integer?getAge()?{return?age;}public?void?setAge(Integer?age)?{this.age?=?age;}@Overridepublic?String?toString()?{return?"User{"?+"id="?+?id?+",?name='"?+?name?+?'\''?+",?age="?+?age?+'}';} }

RedisTemplate的使用方式

默認情況下的模板只能支持RedisTemplate<String, String>,也就是只能存入字符串,所以自定義模板很有必要。添加配置類RedisCacheConfig.java

@Configuration @AutoConfigureAfter(RedisAutoConfiguration.class) public?class?RedisCacheConfig?{@Beanpublic?RedisTemplate<String,?Serializable>?redisCacheTemplate(LettuceConnectionFactory?connectionFactory)?{RedisTemplate<String,?Serializable>?template?=?new?RedisTemplate<>();template.setKeySerializer(new?StringRedisSerializer());template.setValueSerializer(new?GenericJackson2JsonRedisSerializer());template.setConnectionFactory(connectionFactory);return?template;} }

測試類

@RestController @RequestMapping("/user") public?class?UserController?{public?static?Logger?logger?=?LogManager.getLogger(UserController.class);@Autowiredprivate?StringRedisTemplate?stringRedisTemplate;@Autowiredprivate?RedisTemplate<String,?Serializable>?redisCacheTemplate;@RequestMapping("/test")public?void?test()?{redisCacheTemplate.opsForValue().set("userkey",?new?User(1,?"張三",?25));User?user?=?(User)?redisCacheTemplate.opsForValue().get("userkey");logger.info("當前獲取對象:{}",?user.toString());}

然后在瀏覽器訪問,觀察后臺日志 http://localhost:8082/user/test

使用spring cache集成redis

spring cache具備很好的靈活性,不僅能夠使用SPEL(spring expression language)來定義緩存的key和各種condition,還提供了開箱即用的緩存臨時存儲方案,也支持和主流的專業緩存如EhCache、Redis、Guava的集成。定義接口UserService.java

public?interface?UserService?{User?save(User?user);void?delete(int?id);User?get(Integer?id); }

接口實現類UserServiceImpl.java

@Service public?class?UserServiceImpl?implements?UserService{public?static?Logger?logger?=?LogManager.getLogger(UserServiceImpl.class);private?static?Map<Integer,?User>?userMap?=?new?HashMap<>();static?{userMap.put(1,?new?User(1,?"肖戰",?25));userMap.put(2,?new?User(2,?"王一博",?26));userMap.put(3,?new?User(3,?"楊紫",?24));}@CachePut(value?="user",?key?=?"#user.id")@Overridepublic?User?save(User?user)?{userMap.put(user.getId(),?user);logger.info("進入save方法,當前存儲對象:{}",?user.toString());return?user;}@CacheEvict(value="user",?key?=?"#id")@Overridepublic?void?delete(int?id)?{userMap.remove(id);logger.info("進入delete方法,刪除成功");}@Cacheable(value?=?"user",?key?=?"#id")@Overridepublic?User?get(Integer?id)?{logger.info("進入get方法,當前獲取對象:{}",?userMap.get(id)==null?null:userMap.get(id).toString());return?userMap.get(id);} }

為了方便演示數據庫的操作,這里直接定義了一個Map<Integer,User> userMap,這里的核心是三個注解@Cachable、@CachePut和@CacheEvict。測試類:UserController

@RestController @RequestMapping("/user") public?class?UserController?{public?static?Logger?logger?=?LogManager.getLogger(UserController.class);@Autowiredprivate?StringRedisTemplate?stringRedisTemplate;@Autowiredprivate?RedisTemplate<String,?Serializable>?redisCacheTemplate;@Autowiredprivate?UserService?userService;@RequestMapping("/test")public?void?test()?{redisCacheTemplate.opsForValue().set("userkey",?new?User(1,?"張三",?25));User?user?=?(User)?redisCacheTemplate.opsForValue().get("userkey");logger.info("當前獲取對象:{}",?user.toString());}@RequestMapping("/add")public?void?add()?{User?user?=?userService.save(new?User(4,?"李現",?30));logger.info("添加的用戶信息:{}",user.toString());}@RequestMapping("/delete")public?void?delete()?{userService.delete(4);}@RequestMapping("/get/{id}")public?void?get(@PathVariable("id")?String?idStr)?throws?Exception{if?(StringUtils.isBlank(idStr))?{throw?new?Exception("id為空");}Integer?id?=?Integer.parseInt(idStr);User?user?=?userService.get(id);logger.info("獲取的用戶信息:{}",user.toString());} }

用緩存要注意,啟動類要加上一個注解開啟緩存

@SpringBootApplication(exclude=DataSourceAutoConfiguration.class)@EnableCachingpublic?class?Application?{?????public?static?void?main(String[]?args)?{??????????SpringApplication.run(Application.class,?args);?????}}

1、先調用添加接口:http://localhost:8082/user/add

2、再調用查詢接口,查詢id=4的用戶信息:

可以看出,這里已經從緩存中獲取數據了,因為上一步add方法已經把id=4的用戶數據放入了redis緩存 3、調用刪除方法,刪除id=4的用戶信息,同時清除緩存

4、再次調用查詢接口,查詢id=4的用戶信息:

沒有了緩存,所以進入了get方法,從userMap中獲取。


緩存注解

1、@Cacheable 根據方法的請求參數對其結果進行緩存

  • key:緩存的key,可以為空,如果指定要按照SPEL表達式編寫,如果不指定,則按照方法的所有參數進行組合。

  • value:緩存的名稱,必須指定至少一個(如 @Cacheable (value='user')或者@Cacheable(value={'user1','user2'}))

  • condition:緩存的條件,可以為空,使用SPEL編寫,返回true或者false,只有為true才進行緩存。

2、@CachePut 根據方法的請求參數對其結果進行緩存,和@Cacheable不同的是,它每次都會觸發真實方法的調用。參數描述見上。

3、@CacheEvict 根據條件對緩存進行清空

  • key:同上

  • value:同上

  • condition:同上

  • allEntries:是否清空所有緩存內容,缺省為false,如果指定為true,則方法調用后將立即清空所有緩存

  • beforeInvocation:是否在方法執行前就清空,缺省為false,如果指定為true,則在方法還沒有執行的時候就清空緩存。缺省情況下,如果方法執行拋出異常,則不會清空緩存。

緩存問題

  • 面試官:看了一下你的demo,簡單易懂。那你在實際項目中使用緩存有遇到什么問題或者會遇到什么問題你知道嗎?

  • 我:緩存和數據庫數據一致性問題:分布式環境下非常容易出現緩存和數據庫間數據一致性問題,針對這一點,如果項目對緩存的要求是強一致性的,那么就不要使用緩存。我們只能采取合適的策略來降低緩存和數據庫間數據不一致的概率,而無法保證兩者間的強一致性。合適的策略包括合適的緩存更新策略,更新數據庫后及時更新緩存、緩存失敗時增加重試機制。

  • 面試官:Redis雪崩了解嗎?

  • 我:我了解的,目前電商首頁以及熱點數據都會去做緩存,一般緩存都是定時任務去刷新,或者查不到之后去更新緩存的,定時任務刷新就有一個問題。舉個栗子:如果首頁所有Key的失效時間都是12小時,中午12點刷新的,我零點有個大促活動大量用戶涌入,假設每秒6000個請求,本來緩存可以抗住每秒5000個請求,但是緩存中所有Key都失效了。此時6000個/秒的請求全部落在了數據庫上,數據庫必然扛不住,真實情況可能DBA都沒反應過來直接掛了,此時,如果沒什么特別的方案來處理,DBA很著急,重啟數據庫,但是數據庫立馬又被新流量給打死了。這就是我理解的緩存雪崩。

  • 我心想:同一時間大面積失效,瞬間Redis跟沒有一樣,那這個數量級別的請求直接打到數據庫幾乎是災難性的,你想想如果掛的是一個用戶服務的庫,那其他依賴他的庫所有接口幾乎都會報錯,如果沒做熔斷等策略基本上就是瞬間掛一片的節奏,你怎么重啟用戶都會把你打掛,等你重啟好的時候,用戶早睡覺去了,臨睡之前,罵罵咧咧“什么垃圾產品”。

  • 面試官摸摸了自己的頭發:嗯,還不錯,那這種情況你都是怎么應對的?

  • 我:處理緩存雪崩簡單,在批量往Redis存數據的時候,把每個Key的失效時間都加個隨機值就好了,這樣可以保證數據不會再同一時間大面積失效。

setRedis(key,?value,?time+Math.random()*10000);

如果Redis是集群部署,將熱點數據均勻分布在不同的Redis庫中也能避免全部失效。或者設置熱點數據永不過期,有更新操作就更新緩存就好了(比如運維更新了首頁商品,那你刷下緩存就好了,不要設置過期時間),電商首頁的數據也可以用這個操作,保險。

  • 面試官:那你了解緩存穿透和擊穿么,可以說說他們跟雪崩的區別嗎?

  • 我:嗯,了解,先說下緩存穿透吧,緩存穿透是指緩存和數據庫中都沒有的數據,而用戶(黑客)不斷發起請求,舉個栗子:我們數據庫的id都是從1自增的,如果發起id=-1的數據或者id特別大不存在的數據,這樣的不斷攻擊導致數據庫壓力很大,嚴重會擊垮數據庫。

  • 我又接著說:至于緩存擊穿嘛,這個跟緩存雪崩有點像,但是又有一點不一樣,緩存雪崩是因為大面積的緩存失效,打崩了DB,而緩存擊穿不同的是緩存擊穿是指一個Key非常熱點,在不停地扛著大量的請求,大并發集中對這一個點進行訪問,當這個Key在失效的瞬間,持續的大并發直接落到了數據庫上,就在這個Key的點上擊穿了緩存。

  • 面試官露出欣慰的眼光:那他們分別怎么解決?

  • 我:緩存穿透我會在接口層增加校驗,比如用戶鑒權,參數做校驗,不合法的校驗直接return,比如id做基礎校驗,id<=0直接攔截。

  • 面試官:那你還有別的方法嗎?

  • 我:我記得Redis里還有一個高級用法布隆過濾器(Bloom Filter) 這個也能很好的預防緩存穿透的發生,他的原理也很簡單,就是利用高效的數據結構和算法快速判斷出你這個Key是否在數據庫中存在,不存在你return就好了,存在你就去查DB刷新KV再return。緩存擊穿的話,設置熱點數據永不過期,或者加上互斥鎖就搞定了。作為暖男,代碼給你準備好了,拿走不謝。

public?static?String?getData(String?key)?throws?InterruptedException?{//從Redis查詢數據String?result?=?getDataByKV(key);//參數校驗if?(StringUtils.isBlank(result))?{try?{//獲得鎖if?(reenLock.tryLock())?{//去數據庫查詢result?=?getDataByDB(key);//校驗if?(StringUtils.isNotBlank(result))?{//插進緩存setDataToKV(key,?result);}}?else?{//睡一會再拿Thread.sleep(100L);result?=?getData(key);}}?finally?{//釋放鎖reenLock.unlock();}}return?result;}
  • 面試官:嗯嗯,還不錯。

Redis為何這么快

  • 面試官:redis作為緩存大家都在用,那redis一定很快咯?

  • 我:當然了,官方提供的數據可以達到100000+的QPS(每秒內的查詢次數),這個數據不比Memcached差!

  • 面試官:redis這么快,它的“多線程模型”你了解嗎?(露出邪魅一笑)

  • 我:您是想問Redis這么快,為什么還是單線程的吧。Redis確實是單進程單線程的模型,因為Redis完全是基于內存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內存的大小或者網絡帶寬。既然單線程容易實現,而且CPU不會成為瓶頸,那就順理成章的采用單線程的方案了(畢竟采用多線程會有很多麻煩)。

  • 面試官:嗯,是的。那你能說說Redis是單線程的,為什么還能這么快嗎?

  • 我:可以這么說吧。第一:Redis完全基于內存,絕大部分請求是純粹的內存操作,非常迅速,數據存在內存中,類似于HashMap,HashMap的優勢就是查找和操作的時間復雜度是O(1)。第二:數據結構簡單,對數據操作也簡單。第三:采用單線程,避免了不必要的上下文切換和競爭條件,不存在多線程導致的CPU切換,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有死鎖問題導致的性能消耗。第四:使用多路復用IO模型,非阻塞IO。

Redis和Memcached的區別

  • 面試官:嗯嗯,說的很詳細。那你為什么選擇Redis的緩存方案而不用memcached呢

  • 我:1、存儲方式上:memcache會把數據全部存在內存之中,斷電后會掛掉,數據不能超過內存大小。redis有部分數據存在硬盤上,這樣能保證數據的持久性。2、數據支持類型上:memcache對數據類型的支持簡單,只支持簡單的key-value,,而redis支持五種數據類型。3、使用底層模型不同:它們之間底層實現方式以及與客戶端之間通信的應用協議不一樣。redis直接自己構建了VM機制,因為一般的系統調用系統函數的話,會浪費一定的時間去移動和請求。4、value的大小:redis可以達到1GB,而memcache只有1MB。

淘汰策略

  • 面試官:那你說說你知道的redis的淘汰策略有哪些?

  • 我:Redis有六種淘汰策略

策略描述
volatile-lru從已設置過期時間的KV集中優先對最近最少使用(less recently used)的數據淘汰
volitile-ttl從已設置過期時間的KV集中優先對剩余時間短(time to live)的數據淘汰
volitile-random從已設置過期時間的KV集中隨機選擇數據淘汰
allkeys-lru從所有KV集中優先對最近最少使用(less recently used)的數據淘汰
allKeys-random從所有KV集中隨機選擇數據淘汰
noeviction不淘汰策略,若超過最大內存,返回錯誤信息

補充一下:Redis4.0加入了LFU(least frequency use)淘汰策略,包括volatile-lfu和allkeys-lfu,通過統計訪問頻率,將訪問頻率最少,即最不經常使用的KV淘汰。

持久化

  • 面試官:你對redis的持久化機制了解嗎?能講一下嗎?

  • 我:redis為了保證效率,數據緩存在了內存中,但是會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件中,以保證數據的持久化。Redis的持久化策略有兩種:1、RDB:快照形式是直接把內存中的數據保存到一個dump的文件中,定時保存,保存策略。2、AOF:把所有的對Redis的服務器進行修改的命令都存到一個文件里,命令的集合。Redis默認是快照RDB的持久化方式。當Redis重啟的時候,它會優先使用AOF文件來還原數據集,因為AOF文件保存的數據集通常比RDB文件所保存的數據集更完整。你甚至可以關閉持久化功能,讓數據只在服務器運行時存。

  • 面試官:那你再說下RDB是怎么工作的?

  • 我:默認Redis是會以快照"RDB"的形式將數據持久化到磁盤的一個二進制文件dump.rdb。工作原理簡單說一下:當Redis需要做持久化時,Redis會fork一個子進程,子進程將數據寫到磁盤上一個臨時RDB文件中。當子進程完成寫臨時文件后,將原來的RDB替換掉,這樣的好處是可以copy-on-write。

  • 我:RDB的優點是:這種文件非常適合用于備份:比如,你可以在最近的24小時內,每小時備份一次,并且在每個月的每一天也備份一個RDB文件。這樣的話,即使遇上問題,也可以隨時將數據集還原到不同的版本。RDB非常適合災難恢復。RDB的缺點是:如果你需要盡量避免在服務器故障時丟失數據,那么RDB不合適你。

  • 面試官:那你要不再說下AOF??

  • 我:(說就一起說下吧)使用AOF做持久化,每一個寫命令都通過write函數追加到appendonly.aof中,配置方式如下:

appendfsync yes ? appendfsync always ????#每次有數據修改發生時都會寫入AOF文件。appendfsync everysec ??#每秒鐘同步一次,該策略為AOF的缺省策略。復制代碼

AOF可以做到全程持久化,只需要在配置中開啟 appendonly yes。這樣redis每執行一個修改數據的命令,都會把它添加到AOF文件中,當redis重啟時,將會讀取AOF文件進行重放,恢復到redis關閉前的最后時刻。

  • 我頓了一下,繼續說:使用AOF的優點是會讓redis變得非常耐久。可以設置不同的fsync策略,aof的默認策略是每秒鐘fsync一次,在這種配置下,就算發生故障停機,也最多丟失一秒鐘的數據。缺點是對于相同的數據集來說,AOF的文件體積通常要大于RDB文件的體積。根據所使用的fsync策略,AOF的速度可能會慢于RDB。

  • 面試官又問:你說了這么多,那我該用哪一個呢?

  • 我:如果你非常關心你的數據,但仍然可以承受數分鐘內的數據丟失,那么可以額只使用RDB持久。AOF將Redis執行的每一條命令追加到磁盤中,處理巨大的寫入會降低Redis的性能,不知道你是否可以接受。數據庫備份和災難恢復:定時生成RDB快照非常便于進行數據庫備份,并且RDB恢復數據集的速度也要比AOF恢復的速度快。當然了,redis支持同時開啟RDB和AOF,系統重啟后,redis會優先使用AOF來恢復數據,這樣丟失的數據會最少。

主從復制

  • 面試官:redis單節點存在單點故障問題,為了解決單點問題,一般都需要對redis配置從節點,然后使用哨兵來監聽主節點的存活狀態,如果主節點掛掉,從節點能繼續提供緩存功能,你能說說redis主從復制的過程和原理嗎?

  • 我有點懵,這個說來就話長了。但幸好提前準備了:主從配置結合哨兵模式能解決單點故障問題,提高redis可用性。從節點僅提供讀操作,主節點提供寫操作。對于讀多寫少的狀況,可給主節點配置多個從節點,從而提高響應效率。

  • 我頓了一下,接著說:關于復制過程,是這樣的:

    • 1、從節點執行slaveof[masterIP][masterPort],保存主節點信息

    • 2、從節點中的定時任務發現主節點信息,建立和主節點的socket連接

    • 3、從節點發送Ping信號,主節點返回Pong,兩邊能互相通信

    • 4、連接建立后,主節點將所有數據發送給從節點(數據同步)

    • 5、主節點把當前的數據同步給從節點后,便完成了復制的建立過程。接下來,主節點就會持續的把寫命令發送給從節點,保證主從數據一致性。

  • 面試官:那你能詳細說下數據同步的過程嗎?

  • (我心想:這也問的太細了吧)我:可以。redis2.8之前使用sync[runId][offset]同步命令,redis2.8之后使用psync[runId][offset]命令。兩者不同在于,sync命令僅支持全量復制過程,psync支持全量和部分復制。介紹同步之前,先介紹幾個概念:runId:每個redis節點啟動都會生成唯一的uuid,每次redis重啟后,runId都會發生變化。offset:主節點和從節點都各自維護自己的主從復制偏移量offset,當主節點有寫入命令時,offset=offset+命令的字節長度。從節點在收到主節點發送的命令后,也會增加自己的offset,并把自己的offset發送給主節點。這樣,主節點同時保存自己的offset和從節點的offset,通過對比offset來判斷主從節點數據是否一致。repl_backlog_size:保存在主節點上的一個固定長度的先進先出隊列,默認大小是1MB。

    (1)主節點發送數據給從節點過程中,主節點還會進行一些寫操作,這時候的數據存儲在復制緩沖區中。從節點同步主節點數據完成后,主節點將緩沖區的數據繼續發送給從節點,用于部分復制。

    (2)主節點響應寫命令時,不但會把命名發送給從節點,還會寫入復制積壓緩沖區,用于復制命令丟失的數據補救。

上面是psync的執行流程:從節點發送psync[runId][offset]命令,主節點有三種響應:(1)FULLRESYNC:第一次連接,進行全量復制 (2)CONTINUE:進行部分復制 (3)ERR:不支持psync命令,進行全量復制

  • 面試官:很好,那你能具體說下全量復制和部分復制的過程嗎?

  • 我:可以

    上面是全量復制的流程。主要有以下幾步:

    1、從節點發送psync ? -1命令(因為第一次發送,不知道主節點的runId,所以為?,因為是第一次復制,所以offset=-1)。

    2、主節點發現從節點是第一次復制,返回FULLRESYNC {runId} {offset},runId是主節點的runId,offset是主節點目前的offset。

    3、從節點接收主節點信息后,保存到info中。

    4、主節點在發送FULLRESYNC后,啟動bgsave命令,生成RDB文件(數據持久化)。

    5、主節點發送RDB文件給從節點。到從節點加載數據完成這段期間主節點的寫命令放入緩沖區。

    6、從節點清理自己的數據庫數據。

    7、從節點加載RDB文件,將數據保存到自己的數據庫中。

    8、如果從節點開啟了AOF,從節點會異步重寫AOF文件。

關于部分復制有以下幾點說明:1、部分復制主要是Redis針對全量復制的過高開銷做出的一種優化措施,使用psync[runId][offset]命令實現。當從節點正在復制主節點時,如果出現網絡閃斷或者命令丟失等異常情況時,從節點會向主節點要求補發丟失的命令數據,主節點的復制積壓緩沖區將這部分數據直接發送給從節點,這樣就可以保持主從節點復制的一致性。補發的這部分數據一般遠遠小于全量數據。2、主從連接中斷期間主節點依然響應命令,但因復制連接中斷命令無法發送給從節點,不過主節點內的復制積壓緩沖區依然可以保存最近一段時間的寫命令數據。3、當主從連接恢復后,由于從節點之前保存了自身已復制的偏移量和主節點的運行ID。因此會把它們當做psync參數發送給主節點,要求進行部分復制。4、主節點接收到psync命令后首先核對參數runId是否與自身一致,如果一致,說明之前復制的是當前主節點;之后根據參數offset在復制積壓緩沖區中查找,如果offset之后的數據存在,則對從節點發送+COUTINUE命令,表示可以進行部分復制。因為緩沖區大小固定,若發生緩沖溢出,則進行全量復制。5、主節點根據偏移量把復制積壓緩沖區里的數據發送給從節點,保證主從復制進入正常狀態。

哨兵

  • 面試官:那主從復制會存在哪些問題呢?

  • 我:主從復制會存在以下問題:1、一旦主節點宕機,從節點晉升為主節點,同時需要修改應用方的主節點地址,還需要命令所有從節點去復制新的主節點,整個過程需要人工干預。2、主節點的寫能力受到單機的限制。3、主節點的存儲能力受到單機的限制。4、原生復制的弊端在早期的版本中也會比較突出,比如:redis復制中斷后,從節點會發起psync。此時如果同步不成功,則會進行全量同步,主庫執行全量備份的同時,可能會造成毫秒或秒級的卡頓。

  • 面試官:那比較主流的解決方案是什么呢?

  • 我:當然是哨兵啊。

  • 面試官:那么問題又來了。那你說下哨兵有哪些功能?

  • 我:如圖,是Redis Sentinel(哨兵)的架構圖。Redis Sentinel(哨兵)主要功能包括主節點存活檢測、主從運行情況檢測、自動故障轉移、主從切換。Redis Sentinel最小配置是一主一從。Redis的Sentinel系統可以用來管理多個Redis服務器,該系統可以執行以下四個任務:1、監控:不斷檢查主服務器和從服務器是否正常運行。2、通知:當被監控的某個redis服務器出現問題,Sentinel通過API腳本向管理員或者其他應用程序發出通知。3、自動故障轉移:當主節點不能正常工作時,Sentinel會開始一次自動的故障轉移操作,它會將與失效主節點是主從關系的其中一個從節點升級為新的主節點,并且將其他的從節點指向新的主節點,這樣人工干預就可以免了。4、配置提供者:在Redis Sentinel模式下,客戶端應用在初始化時連接的是Sentinel節點集合,從中獲取主節點的信息。

  • 面試官:那你能說下哨兵的工作原理嗎?

  • 我:話不多說,直接上圖:

1、每個Sentinel節點都需要定期執行以下任務:每個Sentinel以每秒一次的頻率,向它所知的主服務器、從服務器以及其他的Sentinel實例發送一個PING命令。(如上圖)

2、如果一個實例距離最后一次有效回復PING命令的時間超過down-after-milliseconds所指定的值,那么這個實例會被Sentinel標記為主觀下線。(如上圖)

3、如果一個主服務器被標記為主觀下線,那么正在監視這個服務器的所有Sentinel節點,要以每秒一次的頻率確認主服務器的確進入了主觀下線狀態。

4、如果一個主服務器被標記為主觀下線,并且有足夠數量的Sentinel(至少要達到配置文件指定的數量)在指定的時間范圍內同意這一判斷,那么這個主服務器被標記為客觀下線。

5、一般情況下,每個Sentinel會以每10秒一次的頻率向它已知的所有主服務器和從服務器發送INFO命令,當一個主服務器被標記為客觀下線時,Sentinel向下線主服務器的所有從服務器發送INFO命令的頻率,會從10秒一次改為每秒一次。

6、Sentinel和其他Sentinel協商客觀下線的主節點的狀態,如果處于SDOWN狀態,則投票自動選出新的主節點,將剩余從節點指向新的主節點進行數據復制。

7、當沒有足夠數量的Sentinel同意主服務器下線時,主服務器的客觀下線狀態就會被移除。當主服務器重新向Sentinel的PING命令返回有效回復時,主服務器的主觀下線狀態就會被移除。

  • 面試官:不錯,面試前沒少下工夫啊,今天Redis這關你過了,明天找個時間我們再聊聊其他的。(露出欣慰的微笑)

  • 我:沒問題。

總結

本文在一次面試的過程中講述了Redis是什么,Redis的特點和功能,Redis緩存的使用,Redis為什么能這么快,Redis緩存的淘汰策略,持久化的兩種方式,Redis高可用部分的主從復制和哨兵的基本原理。只要功夫深,鐵杵磨成針,平時準備好,面試不用慌。雖然面試不一定是這樣問的,但萬變不離其“宗”。(筆者覺得這種問答形式的博客很不錯,可讀性強而且讀后記的比較深刻)

- END -

歡迎加入我的知識星球,一起探討架構,交流源碼。加入方式,長按下方二維碼噢

已在知識星球更新源碼解析如下:

最近更新《芋道 SpringBoot 2.X 入門》系列,已經 20 余篇,覆蓋了?MyBatis、Redis、MongoDB、ES、分庫分表、讀寫分離、SpringMVC、Webflux、權限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能測試等等內容。

提供近 3W 行代碼的 SpringBoot 示例,以及超 4W 行代碼的電商微服務項目。

獲取方式:點“在看”,關注公眾號并回復?666?領取,更多內容陸續奉上。

文章有幫助的話,在看,轉發吧。 謝謝支持喲 (*^__^*)

總結

以上是生活随笔為你收集整理的【面朝大厂】万字+图解 Redis,面试不用愁了!的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。