新聞中心
- Redis是什么
- 五種數(shù)據(jù)類型
- Redis緩存
- 緩存問題
- Redis為何這么快
- Redis和Memcached的區(qū)別
- 淘汰策略
- 持久化
- 主從復(fù)制
- 哨兵
- 總結(jié)
今天,我不自量力的面試了某大廠的java開發(fā)崗位,迎面走來一位風(fēng)塵仆仆的中年男子,手里拿著屏幕還亮著的mac,他沖著我禮貌的笑了笑,然后說了句“不好意思,讓你久等了”,然后示意我坐下,說:“我們開始吧??戳四愕暮啔v,覺得你對redis應(yīng)該掌握的不錯,我們今天就來討論下redis......”。我想:“來就來,兵來將擋水來土掩”。

10年積累的網(wǎng)站設(shè)計、成都網(wǎng)站設(shè)計經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認(rèn)識你,你也不認(rèn)識我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有平橋免費網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。
Redis是什么
- 面試官:你先來說下redis是什么吧
- 我:(這不就是總結(jié)下redis的定義和特點嘛)Redis是C語言開發(fā)的一個開源的(遵從BSD協(xié)議)高性能鍵值對(key-value)的內(nèi)存數(shù)據(jù)庫,可以用作數(shù)據(jù)庫、緩存、消息中間件等。它是一種NoSQL(not-only sql,泛指非關(guān)系型數(shù)據(jù)庫)的數(shù)據(jù)庫。
- 我頓了一下,接著說:Redis作為一個內(nèi)存數(shù)據(jù)庫。1、性能優(yōu)秀,數(shù)據(jù)在內(nèi)存中,讀寫速度非??欤С植l(fā)10W QPS;2、單進程單線程,是線程安全的,采用IO多路復(fù)用機制;3、豐富的數(shù)據(jù)類型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等;4、支持?jǐn)?shù)據(jù)持久化??梢詫?nèi)存中數(shù)據(jù)保存在磁盤中,重啟時加載;5、主從復(fù)制,哨兵,高可用;6、可以用作分布式鎖;7、可以作為消息中間件使用,支持發(fā)布訂閱
五種數(shù)據(jù)類型
- 面試官:總結(jié)的不錯,看來是早有準(zhǔn)備啊。剛來聽你提到redis支持五種數(shù)據(jù)類型,那你能簡單說下這五種數(shù)據(jù)類型嗎?
- 我:當(dāng)然可以,但是在說之前,我覺得有必要先來了解下Redis內(nèi)部內(nèi)存管理是如何描述這5種數(shù)據(jù)類型的。說著,我拿著筆給面試官畫了一張圖:
- 我:首先redis內(nèi)部使用一個redisObject對象來表示所有的key和value,redisObject最主要的信息如上圖所示:type表示一個value對象具體是何種數(shù)據(jù)類型,encoding是不同數(shù)據(jù)類型在redis內(nèi)部的存儲方式。比如:type=string表示value存儲的是一個普通字符串,那么encoding可以是raw或者int。
- 我頓了一下,接著說:下面我簡單說下5種數(shù)據(jù)類型:
- 1、string是redis最基本的類型,可以理解成與memcached一模一樣的類型,一個key對應(yīng)一個value。value不僅是string,也可以是數(shù)字。string類型是二進制安全的,意思是redis的string類型可以包含任何數(shù)據(jù),比如jpg圖片或者序列化的對象。string類型的值最大能存儲512M。
- 2、Hash是一個鍵值(key-value)的集合。redis的hash是一個string的key和value的映射表,Hash特別適合存儲對象。常用命令:hget,hset,hgetall等。
- 3、list列表是簡單的字符串列表,按照插入順序排序 。可以添加一個元素到列表的頭部(左邊)或者尾部(右邊) 常用命令:lpush、rpush、lpop、rpop、lrange(獲取列表片段)等。應(yīng)用場景:list應(yīng)用場景非常多,也是Redis最重要的數(shù)據(jù)結(jié)構(gòu)之一,比如twitter的關(guān)注列表,粉絲列表都可以用list結(jié)構(gòu)來實現(xiàn)。數(shù)據(jù)結(jié)構(gòu):list就是鏈表,可以用來當(dāng)消息隊列用。redis提供了List的push和pop操作,還提供了操作某一段的api,可以直接查詢或者刪除某一段的元素。實現(xiàn)方式:redis list的是實現(xiàn)是一個雙向鏈表,既可以支持反向查找和遍歷,更方便操作,不過帶來了額外的內(nèi)存開銷。
- 4、set是string類型的無序集合。集合是通過hashtable實現(xiàn)的。set中的元素是沒有順序的,而且是沒有重復(fù)的。常用命令:sdd、spop、smembers、sunion等。應(yīng)用場景:redis set對外提供的功能和list一樣是一個列表,特殊之處在于set是自動去重的,而且set提供了判斷某個成員是否在一個set集合中。
- 5、zset和set一樣是string類型元素的集合,且不允許重復(fù)的元素。常用命令:zadd、zrange、zrem、zcard等。使用場景:sorted set可以通過用戶額外提供一個優(yōu)先級(score)的參數(shù)來為成員排序,并且是插入有序的,即自動排序。當(dāng)你需要一個有序的并且不重復(fù)的集合列表,那么可以選擇sorted set結(jié)構(gòu)。和set相比,sorted set關(guān)聯(lián)了一個double類型權(quán)重的參數(shù)score,使得集合中的元素能夠按照score進行有序排列,redis正是通過分?jǐn)?shù)來為集合中的成員進行從小到大的排序。實現(xiàn)方式:Redis sorted set的內(nèi)部使用HashMap和跳躍表(skipList)來保證數(shù)據(jù)的存儲和有序,HashMap里放的是成員到score的映射,而跳躍表里存放的是所有的成員,排序依據(jù)是HashMap里存的score,使用跳躍表的結(jié)構(gòu)可以獲得比較高的查找效率,并且在實現(xiàn)上比較簡單。
- 我:我之前總結(jié)了一張圖,關(guān)于數(shù)據(jù)類型的應(yīng)用場景,如果您感興趣,可以去我的掘金看。。
數(shù)據(jù)類型應(yīng)用場景總結(jié)
| 類型 | 簡介 | 特性 | 場景 |
|---|---|---|---|
| string(字符串) | 二進制安全 | 可以包含任何數(shù)據(jù),比如jpg圖片或者序列化對象 | --- |
| Hash(字典) | 鍵值對集合,即編程語言中的map類型 | 適合存儲對象,并且可以像數(shù)據(jù)庫中的update一個屬性一樣只修改某一項屬性值 | 存儲、讀取、修改用戶屬性 |
| List(列表) | 鏈表(雙向鏈表) | 增刪快,提供了操作某一元素的api | 最新消息排行;消息隊列 |
| set(集合) | hash表實現(xiàn),元素不重復(fù) | 添加、刪除、查找的復(fù)雜度都是O(1),提供了求交集、并集、差集的操作 | 共同好友;利用唯一性,統(tǒng)計訪問網(wǎng)站的所有Ip |
| sorted set(有序集合) | 將set中的元素增加一個權(quán)重參數(shù)score,元素按score有序排列 | 數(shù)據(jù)插入集合時,已經(jīng)進行了天然排序 | 排行榜;帶權(quán)重的消息隊列 |
- 面試官:想不到你平時也下了不少工夫,那redis緩存你一定用過的吧
- 我:用過的。。
- 面試官:那你跟我說下你是怎么用的?
- 我是結(jié)合spring boot使用的。一般有兩種方式,一種是直接通過RedisTemplate來使用,另一種是使用spring cache集成Redis(也就是注解的方式)。具體的代碼我就不說了,在我的掘金中有一個demo(見下)。
Redis緩存
- 直接通過RedisTemplate來使用
- 使用spring cache集成Redis pom.xml中加入以下依賴:
org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 org.springframework.boot spring-boot-starter-web org.springframework.session spring-session-data-redis org.projectlombok lombok true org.springframework.boot spring-boot-starter-test test
- 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: 8082
- servlet:
- session:
- timeout: 30ms
- spring:
- cache:
- type: redis
- redis:
- host: 127.0.0.1
- port: 6379
- password:
- # redis默認(rèn)情況下有16個分片,這里配置具體使用的分片,默認(rèn)為0
- database: 0
- lettuce:
- pool:
- # 連接池最大連接數(shù)(使用負(fù)數(shù)表示沒有限制),默認(rèn)8
- max-active: 100
創(chuàng)建實體類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;
- }
- @Override
- public String toString() {
- return "User{" +
- "id=" + id +
- ", name='" + name + '\'' +
- ", age=" + age +
- '}';
- }
- }
RedisTemplate的使用方式
默認(rèn)情況下的模板只能支持RedisTemplate
- @Configuration
- @AutoConfigureAfter(RedisAutoConfiguration.class)
- public class RedisCacheConfig {
- @Bean
- public RedisTemplate
redisCacheTemplate(LettuceConnectionFactory connectionFactory) { - RedisTemplate
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);
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- @Autowired
- private RedisTemplate
redisCacheTemplate; - @RequestMapping("/test")
- public void test() {
- redisCacheTemplate.opsForValue().set("userkey", new User(1, "張三", 25));
- User user = (User) redisCacheTemplate.opsForValue().get("userkey");
- logger.info("當(dāng)前獲取對象:{}", user.toString());
- }
然后在瀏覽器訪問,觀察后臺日志 http://localhost:8082/user/test
使用spring cache集成redis
spring cache具備很好的靈活性,不僅能夠使用SPEL(spring expression language)來定義緩存的key和各種condition,還提供了開箱即用的緩存臨時存儲方案,也支持和主流的專業(yè)緩存如EhCache、Redis、Guava的集成。定義接口UserService.java
- public interface UserService {
- User save(User user);
- void delete(int id);
- User get(Integer id);
- }
接口實現(xiàn)類UserServiceImpl.java
- @Service
- public class UserServiceImpl implements UserService{
- public static Logger logger = LogManager.getLogger(UserServiceImpl.class);
- private static Map
userMap = new HashMap<>(); - static {
- userMap.put(1, new User(1, "肖戰(zhàn)", 25));
- userMap.put(2, new User(2, "王一博", 26));
- userMap.put(3, new User(3, "楊紫", 24));
- }
- @CachePut(value ="user", key = "#user.id")
- @Override
- public User save(User user) {
- userMap.put(user.getId(), user);
- logger.info("進入save方法,當(dāng)前存儲對象:{}", user.toString());
- return user;
- }
- @CacheEvict(value="user", key = "#id")
- @Override
- public void delete(int id) {
- userMap.remove(id);
- logger.info("進入delete方法,刪除成功");
- }
- @Cacheable(value = "user", key = "#id")
- @Override
- public User get(Integer id) {
- logger.info("進入get方法,當(dāng)前獲取對象:{}", userMap.get(id)==null?null:userMap.get(id).toString());
- return userMap.get(id);
- }
- }
為了方便演示數(shù)據(jù)庫的操作,這里直接定義了一個Map
- @RestController
- @RequestMapping("/user")
- public class UserController {
- public static Logger logger = LogManager.getLogger(UserController.class);
- @Autowired
- private StringRedisTemplate stringRedisTemplate;
- @Autowired
- private RedisTemplate
redisCacheTemplate; - @Autowired
- private UserService userService;
- @RequestMapping("/test")
- public void test() {
- redisCacheTemplate.opsForValue().set("userkey", new User(1, "張三", 25));
- User user = (User) redisCacheTemplate.opsForValue().get("userkey");
- logger.info("當(dāng)前獲取對象:{}", user.toString());
- }
- @RequestMapping("/add")
- public void add() {
- User user = userService.save(new User(4, "李現(xiàn)", 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)
- @EnableCaching
- public class Application {
- public static void main(String[] args) {
- SpringApplication.run(Application.class, args);
- }
- }
1、先調(diào)用添加接口:http://localhost:8082/user/add
2、再調(diào)用查詢接口,查詢id=4的用戶信息:
可以看出,這里已經(jīng)從緩存中獲取數(shù)據(jù)了,因為上一步add方法已經(jīng)把id=4的用戶數(shù)據(jù)放入了redis緩存 3、調(diào)用刪除方法,刪除id=4的用戶信息,同時清除緩存
3、調(diào)用刪除方法,刪除id=4的用戶信息,同時清除緩存
4、再次調(diào)用查詢接口,查詢id=4的用戶信息:
沒有了緩存,所以進入了get方法,從userMap中獲取。
緩存注解
1、@Cacheable 根據(jù)方法的請求參數(shù)對其結(jié)果進行緩存
- key:緩存的key,可以為空,如果指定要按照SPEL表達式編寫,如果不指定,則按照方法的所有參數(shù)進行組合。
- value:緩存的名稱,必須指定至少一個(如 @Cacheable (value='user')或者@Cacheable(value={'user1','user2'}))
- condition:緩存的條件,可以為空,使用SPEL編寫,返回true或者false,只有為true才進行緩存。
2、@CachePut 根據(jù)方法的請求參數(shù)對其結(jié)果進行緩存,和@Cacheable不同的是,它每次都會觸發(fā)真實方法的調(diào)用。參數(shù)描述見上。
3、@CacheEvict 根據(jù)條件對緩存進行清空
- key:同上
- value:同上
- condition:同上
- allEntries:是否清空所有緩存內(nèi)容,缺省為false,如果指定為true,則方法調(diào)用后將立即清空所有緩存
- beforeInvocation:是否在方法執(zhí)行前就清空,缺省為false,如果指定為true,則在方法還沒有執(zhí)行的時候就清空緩存。缺省情況下,如果方法執(zhí)行拋出異常,則不會清空緩存。
緩存問題
- 面試官:看了一下你的demo,簡單易懂。那你在實際項目中使用緩存有遇到什么問題或者會遇到什么問題你知道嗎?
- 我:緩存和數(shù)據(jù)庫數(shù)據(jù)一致性問題:分布式環(huán)境下非常容易出現(xiàn)緩存和數(shù)據(jù)庫間數(shù)據(jù)一致性問題,針對這一點,如果項目對緩存的要求是強一致性的,那么就不要使用緩存。我們只能采取合適的策略來降低緩存和數(shù)據(jù)庫間數(shù)據(jù)不一致的概率,而無法保證兩者間的強一致性。合適的策略包括合適的緩存更新策略,更新數(shù)據(jù)庫后及時更新緩存、緩存失敗時增加重試機制。
- 面試官:Redis雪崩了解嗎?
- 我:我了解的,目前電商首頁以及熱點數(shù)據(jù)都會去做緩存,一般緩存都是定時任務(wù)去刷新,或者查不到之后去更新緩存的,定時任務(wù)刷新就有一個問題。舉個栗子:如果首頁所有Key的失效時間都是12小時,中午12點刷新的,我零點有個大促活動大量用戶涌入,假設(shè)每秒6000個請求,本來緩存可以抗住每秒5000個請求,但是緩存中所有Key都失效了。此時6000個/秒的請求全部落在了數(shù)據(jù)庫上,數(shù)據(jù)庫必然扛不住,真實情況可能DBA都沒反應(yīng)過來直接掛了,此時,如果沒什么特別的方案來處理,DBA很著急,重啟數(shù)據(jù)庫,但是數(shù)據(jù)庫立馬又被新流量給打死了。這就是我理解的緩存雪崩。
- 我心想:同一時間大面積失效,瞬間Redis跟沒有一樣,那這個數(shù)量級別的請求直接打到數(shù)據(jù)庫幾乎是災(zāi)難性的,你想想如果掛的是一個用戶服務(wù)的庫,那其他依賴他的庫所有接口幾乎都會報錯,如果沒做熔斷等策略基本上就是瞬間掛一片的節(jié)奏,你怎么重啟用戶都會把你打掛,等你重啟好的時候,用戶早睡覺去了,臨睡之前,罵罵咧咧“什么垃圾產(chǎn)品”。
- 面試官摸摸了自己的頭發(fā):嗯,還不錯,那這種情況你都是怎么應(yīng)對的?
- 我:處理緩存雪崩簡單,在批量往Redis存數(shù)據(jù)的時候,把每個Key的失效時間都加個隨機值就好了,這樣可以保證數(shù)據(jù)不會再同一時間大面積失效。
- setRedis(key, value, time+Math.random()*10000);
如果Redis是集群部署,將熱點數(shù)據(jù)均勻分布在不同的Redis庫中也能避免全部失效。或者設(shè)置熱點數(shù)據(jù)永不過期,有更新操作就更新緩存就好了(比如運維更新了首頁商品,那你刷下緩存就好了,不要設(shè)置過期時間),電商首頁的數(shù)據(jù)也可以用這個操作,保險。
- 面試官:那你了解緩存穿透和擊穿么,可以說說他們跟雪崩的區(qū)別嗎?
- 我:嗯,了解,先說下緩存穿透吧,緩存穿透是指緩存和數(shù)據(jù)庫中都沒有的數(shù)據(jù),而用戶(黑客)不斷發(fā)起請求,舉個栗子:我們數(shù)據(jù)庫的id都是從1自增的,如果發(fā)起id=-1的數(shù)據(jù)或者id特別大不存在的數(shù)據(jù),這樣的不斷攻擊導(dǎo)致數(shù)據(jù)庫壓力很大,嚴(yán)重會擊垮數(shù)據(jù)庫。
- 我又接著說:至于緩存擊穿嘛,這個跟緩存雪崩有點像,但是又有一點不一樣,緩存雪崩是因為大面積的緩存失效,打崩了DB,而緩存擊穿不同的是緩存擊穿是指一個Key非常熱點,在不停地扛著大量的請求,大并發(fā)集中對這一個點進行訪問,當(dāng)這個Key在失效的瞬間,持續(xù)的大并發(fā)直接落到了數(shù)據(jù)庫上,就在這個Key的點上擊穿了緩存。
- 面試官露出欣慰的眼光:那他們分別怎么解決?
- 我:緩存穿透我會在接口層增加校驗,比如用戶鑒權(quán),參數(shù)做校驗,不合法的校驗直接return,比如id做基礎(chǔ)校驗,id<=0直接攔截。
- 面試官:那你還有別的方法嗎?
- 我:我記得Redis里還有一個高級用法**布隆過濾器(Bloom Filter)**這個也能很好的預(yù)防緩存穿透的發(fā)生,他的原理也很簡單,就是利用高效的數(shù)據(jù)結(jié)構(gòu)和算法快速判斷出你這個Key是否在數(shù)據(jù)庫中存在,不存在你return就好了,存在你就去查DB刷新KV再return。緩存擊穿的話,設(shè)置熱點數(shù)據(jù)永不過期,或者加上互斥鎖就搞定了。作為暖男,代碼給你準(zhǔn)備好了,拿走不謝。
- public static String getData(String key) throws InterruptedException {
- //從Redis查詢數(shù)據(jù)
- String result = getDataByKV(key);
- //參數(shù)校驗
- if (StringUtils.isBlank(result)) {
- try {
- //獲得鎖
- if (reenLock.tryLock()) {
- //去數(shù)據(jù)庫查詢
- 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一定很快咯?
- 我:當(dāng)然了,官方提供的數(shù)據(jù)可以達到100000+的QPS(每秒內(nèi)的查詢次數(shù)),這個數(shù)據(jù)不比Memcached差!
- 面試官:redis這么快,它的“多線程模型”你了解嗎?(露出邪魅一笑)
- 我:您是想問Redis這么快,為什么還是單線程的吧。Redis確實是單進程單線程的模型,因為Redis完全是基于內(nèi)存的操作,CPU不是Redis的瓶頸,Redis的瓶頸最有可能是機器內(nèi)存的大小或者網(wǎng)絡(luò)帶寬。既然單線程容易實現(xiàn),而且CPU不會成為瓶頸,那就順理成章的采用單線程的方案了(畢竟采用多線程會有很多麻煩)。
- 面試官:嗯,是的。那你能說說Redis是單線程的,為什么還能這么快嗎?
- 我:可以這么說吧。第一:Redis完全基于內(nèi)存,絕大部分請求是純粹的內(nèi)存操作,非常迅速,數(shù)據(jù)存在內(nèi)存中,類似于HashMap,HashMap的優(yōu)勢就是查找和操作的時間復(fù)雜度是O(1)。第二:數(shù)據(jù)結(jié)構(gòu)簡單,對數(shù)據(jù)操作也簡單。第三:采用單線程,避免了不必要的上下文切換和競爭條件,不存在多線程導(dǎo)致的CPU切換,不用去考慮各種鎖的問題,不存在加鎖釋放鎖操作,沒有死鎖問題導(dǎo)致的性能消耗。第四:使用多路復(fù)用IO模型,非阻塞IO。
Redis和Memcached的區(qū)別
- 面試官:嗯嗯,說的很詳細(xì)。那你為什么選擇Redis的緩存方案而不用memcached呢
- 我:
- 1、存儲方式上:memcache會把數(shù)據(jù)全部存在內(nèi)存之中,斷電后會掛掉,數(shù)據(jù)不能超過內(nèi)存大小。redis有部分?jǐn)?shù)據(jù)存在硬盤上,這樣能保證數(shù)據(jù)的持久性。
- 2、數(shù)據(jù)支持類型上:memcache對數(shù)據(jù)類型的支持簡單,只支持簡單的key-value,,而redis支持五種數(shù)據(jù)類型。
- 3、使用底層模型不同:它們之間底層實現(xiàn)方式以及與客戶端之間通信的應(yīng)用協(xié)議不一樣。redis直接自己構(gòu)建了VM機制,因為一般的系統(tǒng)調(diào)用系統(tǒng)函數(shù)的話,會浪費一定的時間去移動和請求。
- 4、value的大小:redis可以達到1GB,而memcache只有1MB。
淘汰策略
- 面試官:那你說說你知道的redis的淘汰策略有哪些?
- 我:Redis有六種淘汰策略
| 策略 | 描述 |
|---|---|
| volatile-lru | 從已設(shè)置過期時間的KV集中優(yōu)先對最近最少使用(less recently used)的數(shù)據(jù)淘汰 |
| volitile-ttl | 從已設(shè)置過期時間的KV集中優(yōu)先對剩余時間短(time to live)的數(shù)據(jù)淘汰 |
| volitile-random | 從已設(shè)置過期時間的KV集中隨機選擇數(shù)據(jù)淘汰 |
| allkeys-lru | 從所有KV集中優(yōu)先對最近最少使用(less recently used)的數(shù)據(jù)淘汰 |
| allKeys-random | 從所有KV集中隨機選擇數(shù)據(jù)淘汰 |
| noeviction | 不淘汰策略,若超過最大內(nèi)存,返回錯誤信息 |
補充一下:Redis4.0加入了LFU(least frequency use)淘汰策略,包括volatile-lfu和allkeys-lfu,通過統(tǒng)計訪問頻率,將訪問頻率最少,即最不經(jīng)常使用的KV淘汰。
持久化
- 面試官:你對redis的持久化機制了解嗎?能講一下嗎?
- 我:redis為了保證效率,數(shù)據(jù)緩存在了內(nèi)存中,但是會周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件中,以保證數(shù)據(jù)的持久化。Redis的持久化策略有兩種:1、RDB:快照形式是直接把內(nèi)存中的數(shù)據(jù)保存到一個dump的文件中,定時保存,保存策略。2、AOF:把所有的對Redis的服務(wù)器進行修改的命令都存到一個文件里,命令的集合。Redis默認(rèn)是快照RDB的持久化方式。當(dāng)Redis重啟的時候,它會優(yōu)先使用AOF文件來還原數(shù)據(jù)集,因為AOF文件保存的數(shù)據(jù)集通常比RDB文件所保存的數(shù)據(jù)集更完整。你甚至可以關(guān)閉持久化功能,讓數(shù)據(jù)只在服務(wù)器運行時存。
- 面試官:那你再說下RDB是怎么工作的?
- 我:默認(rèn)Redis是會以快照"RDB"的形式將數(shù)據(jù)持久化到磁盤的一個二進制文件dump.rdb。工作原理簡單說一下:當(dāng)Redis需要做持久化時,Redis會fork一個子進程,子進程將數(shù)據(jù)寫到磁盤上一個臨時RDB文件中。當(dāng)子進程完成寫臨時文件后,將原來的RDB替換掉,這樣的好處是可以copy-on-write。
- 我:RDB的優(yōu)點是:這種文件非常適合用于備份:比如,你可以在最近的24小時內(nèi),每小時備份一次,并且在每個月的每一天也備份一個RDB文件。這樣的話,即使遇上問題,也可以隨時將數(shù)據(jù)集還原到不同的版本。RDB非常適合災(zāi)難恢復(fù)。RDB的缺點是:如果你需要盡量避免在服務(wù)器故障時丟失數(shù)據(jù),那么RDB不合適你。
- 面試官:那你要不再說下AOF??
- 我:(說就一起說下吧)使用AOF做持久化,每一個寫命令都通過write函數(shù)追加到appendonly.aof中,配置方式如下:
- appendfsync yes
- appendfsync always #每次有數(shù)據(jù)修改發(fā)生時都會寫入AOF文件。
- appendfsync everysec #每秒鐘同步一次,該策略為AOF的缺省策略。
AOF可以做到全程持久化,只需要在配置中開啟 appendonly yes。這樣redis每執(zhí)行一個修改數(shù)據(jù)的命令,都會把它添加到AOF文件中,當(dāng)redis重啟時,將會讀取AOF文件進行重放,恢復(fù)到redis關(guān)閉前的最后時刻。
- 我頓了一下,繼續(xù)說:使用AOF的優(yōu)點是會讓redis變得非常耐久??梢栽O(shè)置不同的fsync策略,aof的默認(rèn)策略是每秒鐘fsync一次,在這種配置下,就算發(fā)生故障停機,也最多丟失一秒鐘的數(shù)據(jù)。缺點是對于相同的數(shù)據(jù)集來說,AOF的文件體積通常要大于RDB文件的體積。根據(jù)所使用的fsync策略,AOF的速度可能會慢于RDB。
- 面試官又問:你說了這么多,那我該用哪一個呢?
- 我:如果你非常關(guān)心你的數(shù)據(jù),但仍然可以承受數(shù)分鐘內(nèi)的數(shù)據(jù)丟失,那么可以額只使用RDB持久。AOF將Redis執(zhí)行的每一條命令追加到磁盤中,處理巨大的寫入會降低Redis的性能,不知道你是否可以接受。數(shù)據(jù)庫備份和災(zāi)難恢復(fù):定時生成RDB快照非常便于進行數(shù)據(jù)庫備份,并且RDB恢復(fù)數(shù)據(jù)集的速度也要比AOF恢復(fù)的速度快。當(dāng)然了,redis支持同時開啟RDB和AOF,系統(tǒng)重啟后,redis會優(yōu)先使用AOF來恢復(fù)數(shù)據(jù),這樣丟失的數(shù)據(jù)會最少。
主從復(fù)制
- 面試官:redis單節(jié)點存在單點故障問題,為了解決單點問題,一般都需要對redis配置從節(jié)點,然后使用哨兵來監(jiān)聽主節(jié)點的存活狀態(tài),如果主節(jié)點掛掉,從節(jié)點能繼續(xù)提供緩存功能,你能說說redis主從復(fù)制的過程和原理嗎?
- 我有點懵,這個說來就話長了。但幸好提前準(zhǔn)備了:主從配置結(jié)合哨兵模式能解決單點故障問題,提高redis可用性。從節(jié)點僅提供讀操作,主節(jié)點提供寫操作。對于讀多寫少的狀況,可給主節(jié)點配置多個從節(jié)點,從而提高響應(yīng)效率。
- 我頓了一下,接著說:關(guān)于復(fù)制過程,是這樣的:1、從節(jié)點執(zhí)行slaveof[masterIP][masterPort],保存主節(jié)點信息 2、從節(jié)點中的定時任務(wù)發(fā)現(xiàn)主節(jié)點信息,建立和主節(jié)點的socket連接 3、從節(jié)點發(fā)送Ping信號,主節(jié)點返回Pong,兩邊能互相通信 4、連接建立后,主節(jié)點將所有數(shù)據(jù)發(fā)送給從節(jié)點(數(shù)據(jù)同步) 5、主節(jié)點把當(dāng)前的數(shù)據(jù)同步給從節(jié)點后,便完成了復(fù)制的建立過程。接下來,主節(jié)點就會持續(xù)的把寫命令發(fā)送給從節(jié)點,保證主從數(shù)據(jù)一致性。
- 面試官:那你能詳細(xì)說下數(shù)據(jù)同步的過程嗎?
- (我心想:這也問的太細(xì)了吧)我:可以。redis2.8之前使用sync[runId][offset]同步命令,redis2.8之后使用psync[runId][offset]命令。兩者不同在于,sync命令僅支持全量復(fù)制過程,psync支持全量和部分復(fù)制。介紹同步之前,先介紹幾個概念:runId:每個redis節(jié)點啟動
本文題目:大廠面試!我和面試官之間關(guān)于Redis的一場對弈!
標(biāo)題路徑:http://m.fisionsoft.com.cn/article/dhghppe.html


咨詢
建站咨詢
