搞懂這些Redis知識(shí)點(diǎn),吊打面試官!

來(lái)自:掘金佩抹,作者:堅(jiān)持就是勝利
鏈接:https://juejin.im/post/5dccf260f265da0bf66b626d

今天,我不自量力的面試了某大廠的 Java 開(kāi)發(fā)崗位朋譬,迎面走來(lái)一位風(fēng)塵仆仆的中年男子,手里拿著屏幕還亮著的 Mac兴垦。

他沖著我禮貌的笑了笑徙赢,然后說(shuō)了句“不好意思,讓你久等了”探越,然后示意我坐下狡赐,說(shuō):“我們開(kāi)始吧,看了你的簡(jiǎn)歷钦幔,覺(jué)得你對(duì) Redis 應(yīng)該掌握的不錯(cuò)枕屉,我們今天就來(lái)討論下 Redis……”。我想:“來(lái)就來(lái)鲤氢,兵來(lái)將擋水來(lái)土掩”搀擂。

Redis 是什么

面試官:你先來(lái)說(shuō)下 Redis 是什么吧!

我:(這不就是總結(jié)下 Redis 的定義和特點(diǎn)嘛)Redis 是 C 語(yǔ)言開(kāi)發(fā)的一個(gè)開(kāi)源的(遵從 BSD 協(xié)議)高性能鍵值對(duì)(key-value)的內(nèi)存數(shù)據(jù)庫(kù)铜异,可以用作數(shù)據(jù)庫(kù)哥倔、緩存、消息中間件等揍庄。

它是一種 NoSQL(not-only sql,泛指非關(guān)系型數(shù)據(jù)庫(kù))的數(shù)據(jù)庫(kù)东抹。

我頓了一下蚂子,接著說(shuō),Redis 作為一個(gè)內(nèi)存數(shù)據(jù)庫(kù):

  • 性能優(yōu)秀缭黔,數(shù)據(jù)在內(nèi)存中食茎,讀寫速度非常快馏谨,支持并發(fā) 10W QPS别渔。

  • 單進(jìn)程單線程,是線程安全的,采用 IO 多路復(fù)用機(jī)制哎媚。

  • 豐富的數(shù)據(jù)類型喇伯,支持字符串(strings)、散列(hashes)拨与、列表(lists)稻据、集合(sets)、有序集合(sorted sets)等买喧。

  • 支持?jǐn)?shù)據(jù)持久化捻悯。

    可以將內(nèi)存中數(shù)據(jù)保存在磁盤中,重啟時(shí)加載淤毛。

  • 主從復(fù)制今缚,哨兵,高可用低淡。

  • 可以用作分布式鎖姓言。

  • 可以作為消息中間件使用,支持發(fā)布訂閱查牌。

五種數(shù)據(jù)類型

面試官:總結(jié)的不錯(cuò)事期,看來(lái)是早有準(zhǔn)備啊。剛來(lái)聽(tīng)你提到 Redis 支持五種數(shù)據(jù)類型纸颜,那你能簡(jiǎn)單說(shuō)下這五種數(shù)據(jù)類型嗎兽泣?我:當(dāng)然可以,但是在說(shuō)之前胁孙,我覺(jué)得有必要先來(lái)了解下 Redis 內(nèi)部?jī)?nèi)存管理是如何描述這 5 種數(shù)據(jù)類型的唠倦。

說(shuō)著,我拿著筆給面試官畫了一張圖:

image

我:首先 Redis 內(nèi)部使用一個(gè) redisObject 對(duì)象來(lái)表示所有的 key 和 value涮较。

redisObject 最主要的信息如上圖所示:type 表示一個(gè) value 對(duì)象具體是何種數(shù)據(jù)類型稠鼻,encoding 是不同數(shù)據(jù)類型在 Redis 內(nèi)部的存儲(chǔ)方式。

比如:type=string 表示 value 存儲(chǔ)的是一個(gè)普通字符串狂票,那么 encoding 可以是 raw 或者 int候齿。

我頓了一下,接著說(shuō)闺属,下面我簡(jiǎn)單說(shuō)下 5 種數(shù)據(jù)類型:

①String 是 Redis 最基本的類型慌盯,可以理解成與 Memcached一模一樣的類型,一個(gè) Key 對(duì)應(yīng)一個(gè) Value掂器。Value 不僅是 String亚皂,也可以是數(shù)字。

String 類型是二進(jìn)制安全的国瓮,意思是 Redis 的 String 類型可以包含任何數(shù)據(jù)灭必,比如 jpg 圖片或者序列化的對(duì)象狞谱。String 類型的值最大能存儲(chǔ) 512M。

②Hash是一個(gè)鍵值(key-value)的集合禁漓。Redis 的 Hash 是一個(gè) String 的 Key 和 Value 的映射表跟衅,Hash 特別適合存儲(chǔ)對(duì)象。常用命令:hget璃饱,hset与斤,hgetall 等。

③List 列表是簡(jiǎn)單的字符串列表荚恶,按照插入順序排序撩穿。可以添加一個(gè)元素到列表的頭部(左邊)或者尾部(右邊) 常用命令:lpush谒撼、rpush食寡、lpop、rpop廓潜、lrange(獲取列表片段)等抵皱。

應(yīng)用場(chǎng)景:List 應(yīng)用場(chǎng)景非常多,也是 Redis 最重要的數(shù)據(jù)結(jié)構(gòu)之一辩蛋,比如 Twitter 的關(guān)注列表呻畸,粉絲列表都可以用 List 結(jié)構(gòu)來(lái)實(shí)現(xiàn)。

數(shù)據(jù)結(jié)構(gòu):List 就是鏈表悼院,可以用來(lái)當(dāng)消息隊(duì)列用伤为。Redis 提供了 List 的 Push 和 Pop 操作,還提供了操作某一段的 API据途,可以直接查詢或者刪除某一段的元素绞愚。

實(shí)現(xiàn)方式:Redis List 的是實(shí)現(xiàn)是一個(gè)雙向鏈表,既可以支持反向查找和遍歷颖医,更方便操作位衩,不過(guò)帶來(lái)了額外的內(nèi)存開(kāi)銷。

④Set 是 String 類型的無(wú)序集合熔萧。集合是通過(guò) hashtable 實(shí)現(xiàn)的糖驴。Set 中的元素是沒(méi)有順序的,而且是沒(méi)有重復(fù)的佛致。常用命令:sdd遂赠、spop、smembers晌杰、sunion 等。

應(yīng)用場(chǎng)景:Redis Set 對(duì)外提供的功能和 List 一樣是一個(gè)列表筷弦,特殊之處在于 Set 是自動(dòng)去重的肋演,而且 Set 提供了判斷某個(gè)成員是否在一個(gè) Set 集合中抑诸。

⑤Zset 和 Set 一樣是 String 類型元素的集合,且不允許重復(fù)的元素爹殊。常用命令:zadd蜕乡、zrange、zrem梗夸、zcard 等层玲。

使用場(chǎng)景:Sorted Set 可以通過(guò)用戶額外提供一個(gè)優(yōu)先級(jí)(score)的參數(shù)來(lái)為成員排序,并且是插入有序的反症,即自動(dòng)排序辛块。

當(dāng)你需要一個(gè)有序的并且不重復(fù)的集合列表,那么可以選擇 Sorted Set 結(jié)構(gòu)铅碍。

和 Set 相比润绵,Sorted Set關(guān)聯(lián)了一個(gè) Double 類型權(quán)重的參數(shù) Score,使得集合中的元素能夠按照 Score 進(jìn)行有序排列胞谈,Redis 正是通過(guò)分?jǐn)?shù)來(lái)為集合中的成員進(jìn)行從小到大的排序尘盼。

實(shí)現(xiàn)方式:Redis Sorted Set 的內(nèi)部使用 HashMap 和跳躍表(skipList)來(lái)保證數(shù)據(jù)的存儲(chǔ)和有序,HashMap 里放的是成員到 Score 的映射烦绳。

而跳躍表里存放的是所有的成員卿捎,排序依據(jù)是 HashMap 里存的 Score,使用跳躍表的結(jié)構(gòu)可以獲得比較高的查找效率径密,并且在實(shí)現(xiàn)上比較簡(jiǎn)單午阵。

數(shù)據(jù)類型應(yīng)用場(chǎng)景總結(jié):

image

面試官:想不到你平時(shí)也下了不少工夫,那 Redis 緩存你一定用過(guò)的吧睹晒?
我:用過(guò)的趟庄。
面試官:那你跟我說(shuō)下你是怎么用的?
我是結(jié)合 Spring Boot 使用的伪很。一般有兩種方式戚啥,一種是直接通過(guò) RedisTemplate 來(lái)使用,另一種是使用 Spring Cache 集成 Redis(也就是注解的方式)锉试。

Redis 緩存

直接通過(guò) RedisTemplate 來(lái)使用猫十,使用 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 連接池拖云,如不引入啟動(dòng)會(huì)報(bào)錯(cuò)。
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個(gè)分片,這里配置具體使用的分片株扛,默認(rèn)為0
    database: 0
    lettuce:
      pool:
        # 連接池最大連接數(shù)(使用負(fù)數(shù)表示沒(méi)有限制),默認(rèn)8
        max-active: 100

創(chuàng)建實(shí)體類 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<String, String>尤筐,也就是只能存入字符串汇荐,所以自定義模板很有必要。

添加配置類 RedisCacheConfig.java:

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class RedisCacheConfig {

    @Bean
    public 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;
    }
}

測(cè)試類:

@RestController
@RequestMapping("/user")
public class UserController {

    public static Logger logger = LogManager.getLogger(UserController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private 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("當(dāng)前獲取對(duì)象:{}", user.toString());
    }

然后在瀏覽器訪問(wèn)盆繁,觀察后臺(tái)日志 http://localhost:8082/user/test

image

使用 Spring Cache 集成 Redis

Spring Cache 具備很好的靈活性掀淘,不僅能夠使用 SPEL(spring expression language)來(lái)定義緩存的 Key 和各種 Condition,還提供了開(kāi)箱即用的緩存臨時(shí)存儲(chǔ)方案油昂,也支持和主流的專業(yè)緩存如 EhCache革娄、Redis、Guava 的集成冕碟。

定義接口 UserService.java:

public interface UserService {

    User save(User user);

    void delete(int id);

    User get(Integer id);
}

接口實(shí)現(xiàn)類 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, "肖戰(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("進(jìn)入save方法拦惋,當(dāng)前存儲(chǔ)對(duì)象:{}", user.toString());
        return user;
    }

    @CacheEvict(value="user", key = "#id")
    @Override
    public void delete(int id) {
        userMap.remove(id);
        logger.info("進(jìn)入delete方法,刪除成功");
    }

    @Cacheable(value = "user", key = "#id")
    @Override
    public User get(Integer id) {
        logger.info("進(jìn)入get方法鸣哀,當(dāng)前獲取對(duì)象:{}", userMap.get(id)==null?null:userMap.get(id).toString());
        return userMap.get(id);
    }
}

為了方便演示數(shù)據(jù)庫(kù)的操作架忌,這里直接定義了一個(gè) Map<Integer,User> userMap。這里的核心是三個(gè)注解:

  • @Cachable

  • @CachePut

  • @CacheEvict

測(cè)試類:UserController

@RestController
@RequestMapping("/user")
public class UserController {

    public static Logger logger = LogManager.getLogger(UserController.class);

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate<String, Serializable> 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)前獲取對(duì)象:{}", 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());
    }
}

用緩存要注意我衬,啟動(dòng)類要加上一個(gè)注解開(kāi)啟緩存:

@SpringBootApplication(exclude=DataSourceAutoConfiguration.class)
@EnableCaching
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

①先調(diào)用添加接口:http://localhost:8082/user/add

image

②再調(diào)用查詢接口叹放,查詢 id=4 的用戶信息:

image

可以看出,這里已經(jīng)從緩存中獲取數(shù)據(jù)了挠羔,因?yàn)樯弦徊?add 方法已經(jīng)把 id=4 的用戶數(shù)據(jù)放入了 Redis 緩存 3井仰、調(diào)用刪除方法,刪除 id=4 的用戶信息破加,同時(shí)清除緩存:

image

④再次調(diào)用查詢接口俱恶,查詢 id=4 的用戶信息:

image

沒(méi)有了緩存,所以進(jìn)入了 get 方法范舀,從 userMap 中獲取合是。

緩存注解

①@Cacheable

根據(jù)方法的請(qǐng)求參數(shù)對(duì)其結(jié)果進(jìn)行緩存:

  • Key:緩存的 Key,可以為空锭环,如果指定要按照 SPEL 表達(dá)式編寫聪全,如果不指定,則按照方法的所有參數(shù)進(jìn)行組合辅辩。

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

  • Condition:緩存的條件,可以為空玫锋,使用 SPEL 編寫蛾茉,返回 true 或者 false,只有為 true 才進(jìn)行緩存撩鹿。

②@CachePut

根據(jù)方法的請(qǐng)求參數(shù)對(duì)其結(jié)果進(jìn)行緩存谦炬,和 @Cacheable 不同的是,它每次都會(huì)觸發(fā)真實(shí)方法的調(diào)用节沦。參數(shù)描述見(jiàn)上吧寺。

③@CacheEvict

根據(jù)條件對(duì)緩存進(jìn)行清空:

  • Key:同上窜管。

  • Value:同上。

  • Condition:同上稚机。

  • allEntries:是否清空所有緩存內(nèi)容,缺省為 false获搏,如果指定為 true赖条,則方法調(diào)用后將立即清空所有緩存。

  • beforeInvocation:是否在方法執(zhí)行前就清空常熙,缺省為 false纬乍,如果指定為 true,則在方法還沒(méi)有執(zhí)行的時(shí)候就清空緩存裸卫。缺省情況下仿贬,如果方法執(zhí)行拋出異常,則不會(huì)清空緩存墓贿。

緩存問(wèn)題

面試官:看了一下你的 Demo茧泪,簡(jiǎn)單易懂。那你在實(shí)際項(xiàng)目中使用緩存有遇到什么問(wèn)題或者會(huì)遇到什么問(wèn)題你知道嗎聋袋?

我:緩存和數(shù)據(jù)庫(kù)數(shù)據(jù)一致性問(wèn)題:分布式環(huán)境下非常容易出現(xiàn)緩存和數(shù)據(jù)庫(kù)間數(shù)據(jù)一致性問(wèn)題队伟,針對(duì)這一點(diǎn),如果項(xiàng)目對(duì)緩存的要求是強(qiáng)一致性的幽勒,那么就不要使用緩存嗜侮。

我們只能采取合適的策略來(lái)降低緩存和數(shù)據(jù)庫(kù)間數(shù)據(jù)不一致的概率,而無(wú)法保證兩者間的強(qiáng)一致性啥容。

合適的策略包括合適的緩存更新策略锈颗,更新數(shù)據(jù)庫(kù)后及時(shí)更新緩存、緩存失敗時(shí)增加重試機(jī)制咪惠。

面試官:Redis 雪崩了解嗎击吱?

我:我了解的,目前電商首頁(yè)以及熱點(diǎn)數(shù)據(jù)都會(huì)去做緩存硝逢,一般緩存都是定時(shí)任務(wù)去刷新姨拥,或者查不到之后去更新緩存的,定時(shí)任務(wù)刷新就有一個(gè)問(wèn)題渠鸽。

舉個(gè)栗子:如果首頁(yè)所有 Key 的失效時(shí)間都是 12 小時(shí)叫乌,中午 12 點(diǎn)刷新的,我零點(diǎn)有個(gè)大促活動(dòng)大量用戶涌入徽缚,假設(shè)每秒 6000 個(gè)請(qǐng)求憨奸,本來(lái)緩存可以抗住每秒 5000 個(gè)請(qǐng)求,但是緩存中所有 Key 都失效了凿试。

此時(shí) 6000 個(gè)/秒的請(qǐng)求全部落在了數(shù)據(jù)庫(kù)上排宰,數(shù)據(jù)庫(kù)必然扛不住似芝,真實(shí)情況可能 DBA 都沒(méi)反應(yīng)過(guò)來(lái)直接掛了。

此時(shí)板甘,如果沒(méi)什么特別的方案來(lái)處理党瓮,DBA 很著急,重啟數(shù)據(jù)庫(kù)盐类,但是數(shù)據(jù)庫(kù)立馬又被新流量給打死了寞奸。這就是我理解的緩存雪崩。

我心想:同一時(shí)間大面積失效在跳,瞬間 Redis 跟沒(méi)有一樣枪萄,那這個(gè)數(shù)量級(jí)別的請(qǐng)求直接打到數(shù)據(jù)庫(kù)幾乎是災(zāi)難性的。

你想想如果掛的是一個(gè)用戶服務(wù)的庫(kù)猫妙,那其他依賴他的庫(kù)所有接口幾乎都會(huì)報(bào)錯(cuò)瓷翻。

如果沒(méi)做熔斷等策略基本上就是瞬間掛一片的節(jié)奏,你怎么重啟用戶都會(huì)把你打掛割坠,等你重啟好的時(shí)候齐帚,用戶早睡覺(jué)去了,臨睡之前韭脊,罵罵咧咧“什么垃圾產(chǎn)品”童谒。

面試官摸摸了自己的頭發(fā):嗯,還不錯(cuò)沪羔,那這種情況你都是怎么應(yīng)對(duì)的饥伊?

我:處理緩存雪崩簡(jiǎn)單,在批量往 Redis 存數(shù)據(jù)的時(shí)候蔫饰,把每個(gè) Key 的失效時(shí)間都加個(gè)隨機(jī)值就好了琅豆,這樣可以保證數(shù)據(jù)不會(huì)再同一時(shí)間大面積失效。

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

如果 Redis 是集群部署篓吁,將熱點(diǎn)數(shù)據(jù)均勻分布在不同的 Redis 庫(kù)中也能避免全部失效茫因。

或者設(shè)置熱點(diǎn)數(shù)據(jù)永不過(guò)期,有更新操作就更新緩存就好了(比如運(yùn)維更新了首頁(yè)商品杖剪,那你刷下緩存就好了冻押,不要設(shè)置過(guò)期時(shí)間),電商首頁(yè)的數(shù)據(jù)也可以用這個(gè)操作盛嘿,保險(xiǎn)洛巢。

面試官:那你了解緩存穿透和擊穿么,可以說(shuō)說(shuō)他們跟雪崩的區(qū)別嗎次兆?

我:嗯稿茉,了解,先說(shuō)下緩存穿透吧,緩存穿透是指緩存和數(shù)據(jù)庫(kù)中都沒(méi)有的數(shù)據(jù)漓库,而用戶(黑客)不斷發(fā)起請(qǐng)求恃慧。

舉個(gè)栗子:我們數(shù)據(jù)庫(kù)的 id 都是從 1 自增的,如果發(fā)起 id=-1 的數(shù)據(jù)或者 id 特別大不存在的數(shù)據(jù)渺蒿,這樣的不斷攻擊導(dǎo)致數(shù)據(jù)庫(kù)壓力很大痢士,嚴(yán)重會(huì)擊垮數(shù)據(jù)庫(kù)。

我又接著說(shuō):至于緩存擊穿嘛蘸嘶,這個(gè)跟緩存雪崩有點(diǎn)像良瞧,但是又有一點(diǎn)不一樣,緩存雪崩是因?yàn)榇竺娣e的緩存失效训唱,打崩了 DB。

而緩存擊穿不同的是緩存擊穿是指一個(gè) Key 非常熱點(diǎn)挚冤,在不停地扛著大量的請(qǐng)求况增,大并發(fā)集中對(duì)這一個(gè)點(diǎn)進(jìn)行訪問(wèn),當(dāng)這個(gè) Key 在失效的瞬間训挡,持續(xù)的大并發(fā)直接落到了數(shù)據(jù)庫(kù)上澳骤,就在這個(gè) Key 的點(diǎn)上擊穿了緩存。

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

我:緩存穿透我會(huì)在接口層增加校驗(yàn)为肮,比如用戶鑒權(quán),參數(shù)做校驗(yàn)肤京,不合法的校驗(yàn)直接 return颊艳,比如 id 做基礎(chǔ)校驗(yàn),id<=0 直接攔截忘分。

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

我:我記得 Redis 里還有一個(gè)高級(jí)用法布隆過(guò)濾器(Bloom Filter)這個(gè)也能很好的預(yù)防緩存穿透的發(fā)生。

它的原理也很簡(jiǎn)單妒峦,就是利用高效的數(shù)據(jù)結(jié)構(gòu)和算法快速判斷出你這個(gè) Key 是否在數(shù)據(jù)庫(kù)中存在重斑,不存在你 return 就好了,存在你就去查 DB 刷新 KV 再 return肯骇。

緩存擊穿的話窥浪,設(shè)置熱點(diǎn)數(shù)據(jù)永不過(guò)期,或者加上互斥鎖就搞定了笛丙。作為暖男漾脂,代碼給你準(zhǔn)備好了,拿走不謝若债。

public static String getData(String key) throws InterruptedException {
        //從Redis查詢數(shù)據(jù)
        String result = getDataByKV(key);
        //參數(shù)校驗(yàn)
        if (StringUtils.isBlank(result)) {
            try {
                //獲得鎖
                if (reenLock.tryLock()) {
                    //去數(shù)據(jù)庫(kù)查詢
                    result = getDataByDB(key);
                    //校驗(yàn)
                    if (StringUtils.isNotBlank(result)) {
                        //插進(jìn)緩存
                        setDataToKV(key, result);
                    }
                } else {
                    //睡一會(huì)再拿
                    Thread.sleep(100L);
                    result = getData(key);
                }
            } finally {
                //釋放鎖
                reenLock.unlock();
            }
        }
        r

面試官:嗯嗯符相,還不錯(cuò)。

Redis 為何這么快

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

我:當(dāng)然了镜豹,官方提供的數(shù)據(jù)可以達(dá)到 100000+ 的 QPS(每秒內(nèi)的查詢次數(shù)),這個(gè)數(shù)據(jù)不比 Memcached 差蓝牲!

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

我:您是想問(wèn) Redis 這么快例衍,為什么還是單線程的吧昔期。Redis 確實(shí)是單進(jìn)程單線程的模型,因?yàn)?Redis 完全是基于內(nèi)存的操作佛玄,CPU 不是 Redis 的瓶頸硼一,Redis 的瓶頸最有可能是機(jī)器內(nèi)存的大小或者網(wǎng)絡(luò)帶寬。

既然單線程容易實(shí)現(xiàn)梦抢,而且 CPU 不會(huì)成為瓶頸般贼,那就順理成章的采用單線程的方案了(畢竟采用多線程會(huì)有很多麻煩)。

面試官:嗯奥吩,是的哼蛆。那你能說(shuō)說(shuō) Redis 是單線程的,為什么還能這么快嗎?

我:可以這么說(shuō)吧,總結(jié)一下有如下四點(diǎn):

  • Redis 完全基于內(nèi)存续搀,絕大部分請(qǐng)求是純粹的內(nèi)存操作,非常迅速叠洗,數(shù)據(jù)存在內(nèi)存中,類似于 HashMap靴迫,HashMap 的優(yōu)勢(shì)就是查找和操作的時(shí)間復(fù)雜度是 O(1)惕味。

  • 數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單,對(duì)數(shù)據(jù)操作也簡(jiǎn)單玉锌。

  • 采用單線程名挥,避免了不必要的上下文切換和競(jìng)爭(zhēng)條件,不存在多線程導(dǎo)致的 CPU 切換主守,不用去考慮各種鎖的問(wèn)題禀倔,不存在加鎖釋放鎖操作,沒(méi)有死鎖問(wèn)題導(dǎo)致的性能消耗参淫。

  • 使用多路復(fù)用 IO 模型救湖,非阻塞 IO。

Redis 和 Memcached 的區(qū)別

面試官:嗯嗯涎才,說(shuō)的很詳細(xì)鞋既。那你為什么選擇 Redis 的緩存方案而不用 Memcached 呢力九?

我:原因有如下四點(diǎn):

  • 存儲(chǔ)方式上:Memcache 會(huì)把數(shù)據(jù)全部存在內(nèi)存之中,斷電后會(huì)掛掉邑闺,數(shù)據(jù)不能超過(guò)內(nèi)存大小跌前。Redis 有部分?jǐn)?shù)據(jù)存在硬盤上,這樣能保證數(shù)據(jù)的持久性陡舅。

  • 數(shù)據(jù)支持類型上:Memcache 對(duì)數(shù)據(jù)類型的支持簡(jiǎn)單抵乓,只支持簡(jiǎn)單的 key-value,靶衍,而 Redis 支持五種數(shù)據(jù)類型灾炭。

  • 使用底層模型不同:它們之間底層實(shí)現(xiàn)方式以及與客戶端之間通信的應(yīng)用協(xié)議不一樣。Redis 直接自己構(gòu)建了 VM 機(jī)制颅眶,因?yàn)橐话愕南到y(tǒng)調(diào)用系統(tǒng)函數(shù)的話蜈出,會(huì)浪費(fèi)一定的時(shí)間去移動(dòng)和請(qǐng)求。

  • Value 的大刑涡铩:Redis 可以達(dá)到 1GB掏缎,而 Memcache 只有 1MB。

淘汰策略

面試官:那你說(shuō)說(shuō)你知道的 Redis 的淘汰策略有哪些煤杀?

我:Redis 有六種淘汰策略,如下圖:

image

補(bǔ)充一下:Redis 4.0 加入了 LFU(least frequency use)淘汰策略沪哺,包括 volatile-lfu 和 allkeys-lfu沈自,通過(guò)統(tǒng)計(jì)訪問(wèn)頻率,將訪問(wèn)頻率最少辜妓,即最不經(jīng)常使用的 KV 淘汰枯途。

持久化

面試官:你對(duì) Redis 的持久化機(jī)制了解嗎?能講一下嗎籍滴?我:Redis 為了保證效率酪夷,數(shù)據(jù)緩存在了內(nèi)存中,但是會(huì)周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件中孽惰,以保證數(shù)據(jù)的持久化晚岭。

Redis 的持久化策略有兩種:

  • RDB:快照形式是直接把內(nèi)存中的數(shù)據(jù)保存到一個(gè) dump 的文件中,定時(shí)保存勋功,保存策略坦报。

  • AOF:把所有的對(duì) Redis 的服務(wù)器進(jìn)行修改的命令都存到一個(gè)文件里,命令的集合狂鞋。Redis 默認(rèn)是快照 RDB 的持久化方式片择。

當(dāng) Redis 重啟的時(shí)候,它會(huì)優(yōu)先使用 AOF 文件來(lái)還原數(shù)據(jù)集骚揍,因?yàn)?AOF 文件保存的數(shù)據(jù)集通常比 RDB 文件所保存的數(shù)據(jù)集更完整字管。你甚至可以關(guān)閉持久化功能,讓數(shù)據(jù)只在服務(wù)器運(yùn)行時(shí)存。

面試官:那你再說(shuō)下 RDB 是怎么工作的嘲叔?

我:默認(rèn) Redis 是會(huì)以快照"RDB"的形式將數(shù)據(jù)持久化到磁盤的一個(gè)二進(jìn)制文件 dump.rdb亡呵。

工作原理簡(jiǎn)單說(shuō)一下:當(dāng) Redis 需要做持久化時(shí),Redis 會(huì) fork 一個(gè)子進(jìn)程借跪,子進(jìn)程將數(shù)據(jù)寫到磁盤上一個(gè)臨時(shí) RDB 文件中政己。

當(dāng)子進(jìn)程完成寫臨時(shí)文件后,將原來(lái)的 RDB 替換掉掏愁,這樣的好處是可以 copy-on-write歇由。

我:RDB 的優(yōu)點(diǎn)是:這種文件非常適合用于備份:比如,你可以在最近的 24 小時(shí)內(nèi)果港,每小時(shí)備份一次沦泌,并且在每個(gè)月的每一天也備份一個(gè) RDB 文件。

這樣的話辛掠,即使遇上問(wèn)題谢谦,也可以隨時(shí)將數(shù)據(jù)集還原到不同的版本。RDB 非常適合災(zāi)難恢復(fù)萝衩。

RDB 的缺點(diǎn)是:如果你需要盡量避免在服務(wù)器故障時(shí)丟失數(shù)據(jù)回挽,那么RDB不合適你。

面試官:那你要不再說(shuō)下 AOF猩谊?

我:(說(shuō)就一起說(shuō)下吧)使用 AOF 做持久化千劈,每一個(gè)寫命令都通過(guò) write 函數(shù)追加到 appendonly.aof 中,配置方式如下:

appendfsync yes   
appendfsync always     #每次有數(shù)據(jù)修改發(fā)生時(shí)都會(huì)寫入AOF文件牌捷。
appendfsync everysec   #每秒鐘同步一次墙牌,該策略為AOF的缺省策略。

AOF 可以做到全程持久化暗甥,只需要在配置中開(kāi)啟 appendonly yes喜滨。這樣 Redis 每執(zhí)行一個(gè)修改數(shù)據(jù)的命令,都會(huì)把它添加到 AOF 文件中撤防,當(dāng) Redis 重啟時(shí)虽风,將會(huì)讀取 AOF 文件進(jìn)行重放,恢復(fù)到 Redis 關(guān)閉前的最后時(shí)刻即碗。

我頓了一下焰情,繼續(xù)說(shuō):使用 AOF 的優(yōu)點(diǎn)是會(huì)讓 Redis 變得非常耐久“粒可以設(shè)置不同的 Fsync 策略内舟,AOF的默認(rèn)策略是每秒鐘 Fsync 一次,在這種配置下初橘,就算發(fā)生故障停機(jī)验游,也最多丟失一秒鐘的數(shù)據(jù)充岛。

缺點(diǎn)是對(duì)于相同的數(shù)據(jù)集來(lái)說(shuō),AOF 的文件體積通常要大于 RDB 文件的體積耕蝉。根據(jù)所使用的 Fsync 策略崔梗,AOF 的速度可能會(huì)慢于 RDB。

面試官又問(wèn):你說(shuō)了這么多垒在,那我該用哪一個(gè)呢蒜魄?

我:如果你非常關(guān)心你的數(shù)據(jù),但仍然可以承受數(shù)分鐘內(nèi)的數(shù)據(jù)丟失场躯,那么可以額只使用 RDB 持久谈为。

AOF 將 Redis 執(zhí)行的每一條命令追加到磁盤中,處理巨大的寫入會(huì)降低Redis的性能踢关,不知道你是否可以接受伞鲫。

數(shù)據(jù)庫(kù)備份和災(zāi)難恢復(fù):定時(shí)生成 RDB 快照非常便于進(jìn)行數(shù)據(jù)庫(kù)備份,并且 RDB 恢復(fù)數(shù)據(jù)集的速度也要比 AOF 恢復(fù)的速度快签舞。

當(dāng)然了秕脓,Redis 支持同時(shí)開(kāi)啟 RDB 和 AOF,系統(tǒng)重啟后儒搭,Redis 會(huì)優(yōu)先使用 AOF 來(lái)恢復(fù)數(shù)據(jù)吠架,這樣丟失的數(shù)據(jù)會(huì)最少。

主從復(fù)制

面試官:Redis 單節(jié)點(diǎn)存在單點(diǎn)故障問(wèn)題搂鲫,為了解決單點(diǎn)問(wèn)題诵肛,一般都需要對(duì) Redis 配置從節(jié)點(diǎn),然后使用哨兵來(lái)監(jiān)聽(tīng)主節(jié)點(diǎn)的存活狀態(tài)默穴,如果主節(jié)點(diǎn)掛掉,從節(jié)點(diǎn)能繼續(xù)提供緩存功能褪秀,你能說(shuō)說(shuō) Redis 主從復(fù)制的過(guò)程和原理嗎蓄诽?

我有點(diǎn)懵,這個(gè)說(shuō)來(lái)就話長(zhǎng)了媒吗。但幸好提前準(zhǔn)備了:主從配置結(jié)合哨兵模式能解決單點(diǎn)故障問(wèn)題仑氛,提高 Redis 可用性。

從節(jié)點(diǎn)僅提供讀操作闸英,主節(jié)點(diǎn)提供寫操作锯岖。對(duì)于讀多寫少的狀況,可給主節(jié)點(diǎn)配置多個(gè)從節(jié)點(diǎn)甫何,從而提高響應(yīng)效率出吹。

我頓了一下,接著說(shuō):關(guān)于復(fù)制過(guò)程辙喂,是這樣的:

  • 從節(jié)點(diǎn)執(zhí)行 slaveof[masterIP][masterPort]捶牢,保存主節(jié)點(diǎn)信息鸠珠。

  • 從節(jié)點(diǎn)中的定時(shí)任務(wù)發(fā)現(xiàn)主節(jié)點(diǎn)信息,建立和主節(jié)點(diǎn)的 Socket 連接秋麸。

  • 從節(jié)點(diǎn)發(fā)送 Ping 信號(hào)渐排,主節(jié)點(diǎn)返回 Pong,兩邊能互相通信灸蟆。

  • 連接建立后驯耻,主節(jié)點(diǎn)將所有數(shù)據(jù)發(fā)送給從節(jié)點(diǎn)(數(shù)據(jù)同步)。

  • 主節(jié)點(diǎn)把當(dāng)前的數(shù)據(jù)同步給從節(jié)點(diǎn)后炒考,便完成了復(fù)制的建立過(guò)程可缚。接下來(lái),主節(jié)點(diǎn)就會(huì)持續(xù)的把寫命令發(fā)送給從節(jié)點(diǎn)票腰,保證主從數(shù)據(jù)一致性城看。

面試官:那你能詳細(xì)說(shuō)下數(shù)據(jù)同步的過(guò)程嗎?

(我心想:這也問(wèn)的太細(xì)了吧)我:可以杏慰。Redis 2.8 之前使用 sync[runId][offset] 同步命令测柠,Redis 2.8 之后使用 psync[runId][offset] 命令。

兩者不同在于缘滥,Sync 命令僅支持全量復(fù)制過(guò)程轰胁,Psync 支持全量和部分復(fù)制。

介紹同步之前朝扼,先介紹幾個(gè)概念:

  • runId:每個(gè) Redis 節(jié)點(diǎn)啟動(dòng)都會(huì)生成唯一的 uuid赃阀,每次 Redis 重啟后,runId 都會(huì)發(fā)生變化擎颖。

  • offset:主節(jié)點(diǎn)和從節(jié)點(diǎn)都各自維護(hù)自己的主從復(fù)制偏移量 offset榛斯,當(dāng)主節(jié)點(diǎn)有寫入命令時(shí),offset=offset+命令的字節(jié)長(zhǎng)度搂捧。

    從節(jié)點(diǎn)在收到主節(jié)點(diǎn)發(fā)送的命令后驮俗,也會(huì)增加自己的 offset,并把自己的 offset 發(fā)送給主節(jié)點(diǎn)允跑。

    這樣王凑,主節(jié)點(diǎn)同時(shí)保存自己的 offset 和從節(jié)點(diǎn)的 offset,通過(guò)對(duì)比 offset 來(lái)判斷主從節(jié)點(diǎn)數(shù)據(jù)是否一致聋丝。

  • repl_backlog_size:保存在主節(jié)點(diǎn)上的一個(gè)固定長(zhǎng)度的先進(jìn)先出隊(duì)列索烹,默認(rèn)大小是 1MB。

主節(jié)點(diǎn)發(fā)送數(shù)據(jù)給從節(jié)點(diǎn)過(guò)程中弱睦,主節(jié)點(diǎn)還會(huì)進(jìn)行一些寫操作百姓,這時(shí)候的數(shù)據(jù)存儲(chǔ)在復(fù)制緩沖區(qū)中。

從節(jié)點(diǎn)同步主節(jié)點(diǎn)數(shù)據(jù)完成后况木,主節(jié)點(diǎn)將緩沖區(qū)的數(shù)據(jù)繼續(xù)發(fā)送給從節(jié)點(diǎn)瓣戚,用于部分復(fù)制端圈。

主節(jié)點(diǎn)響應(yīng)寫命令時(shí),不但會(huì)把命名發(fā)送給從節(jié)點(diǎn)子库,還會(huì)寫入復(fù)制積壓緩沖區(qū)舱权,用于復(fù)制命令丟失的數(shù)據(jù)補(bǔ)救。

image

上面是 Psync 的執(zhí)行流程仑嗅,從節(jié)點(diǎn)發(fā)送 psync[runId][offset] 命令宴倍,主節(jié)點(diǎn)有三種響應(yīng):

  • FULLRESYNC:第一次連接,進(jìn)行全量復(fù)制

  • CONTINUE:進(jìn)行部分復(fù)制

  • ERR:不支持 psync 命令仓技,進(jìn)行全量復(fù)制

面試官:很好鸵贬,那你能具體說(shuō)下全量復(fù)制和部分復(fù)制的過(guò)程嗎?

我:可以脖捻!

image

上面是全量復(fù)制的流程阔逼。主要有以下幾步:

  • 從節(jié)點(diǎn)發(fā)送 psync ? -1 命令(因?yàn)榈谝淮伟l(fā)送,不知道主節(jié)點(diǎn)的 runId地沮,所以為?嗜浮,因?yàn)槭堑谝淮螐?fù)制,所以 offset=-1)摩疑。

  • 主節(jié)點(diǎn)發(fā)現(xiàn)從節(jié)點(diǎn)是第一次復(fù)制危融,返回 FULLRESYNC {runId} {offset},runId 是主節(jié)點(diǎn)的 runId雷袋,offset 是主節(jié)點(diǎn)目前的 offset吉殃。

  • 從節(jié)點(diǎn)接收主節(jié)點(diǎn)信息后,保存到 info 中楷怒。

  • 主節(jié)點(diǎn)在發(fā)送 FULLRESYNC 后蛋勺,啟動(dòng) bgsave 命令,生成 RDB 文件(數(shù)據(jù)持久化)鸠删。

  • 主節(jié)點(diǎn)發(fā)送 RDB 文件給從節(jié)點(diǎn)迫卢。到從節(jié)點(diǎn)加載數(shù)據(jù)完成這段期間主節(jié)點(diǎn)的寫命令放入緩沖區(qū)。

  • 從節(jié)點(diǎn)清理自己的數(shù)據(jù)庫(kù)數(shù)據(jù)冶共。

  • 從節(jié)點(diǎn)加載 RDB 文件,將數(shù)據(jù)保存到自己的數(shù)據(jù)庫(kù)中每界。如果從節(jié)點(diǎn)開(kāi)啟了 AOF捅僵,從節(jié)點(diǎn)會(huì)異步重寫 AOF 文件。

關(guān)于部分復(fù)制有以下幾點(diǎn)說(shuō)明:

①部分復(fù)制主要是 Redis 針對(duì)全量復(fù)制的過(guò)高開(kāi)銷做出的一種優(yōu)化措施眨层,使用 psync[runId][offset] 命令實(shí)現(xiàn)庙楚。

當(dāng)從節(jié)點(diǎn)正在復(fù)制主節(jié)點(diǎn)時(shí),如果出現(xiàn)網(wǎng)絡(luò)閃斷或者命令丟失等異常情況時(shí)趴樱,從節(jié)點(diǎn)會(huì)向主節(jié)點(diǎn)要求補(bǔ)發(fā)丟失的命令數(shù)據(jù)馒闷,主節(jié)點(diǎn)的復(fù)制積壓緩沖區(qū)將這部分?jǐn)?shù)據(jù)直接發(fā)送給從節(jié)點(diǎn)酪捡。

這樣就可以保持主從節(jié)點(diǎn)復(fù)制的一致性。補(bǔ)發(fā)的這部分?jǐn)?shù)據(jù)一般遠(yuǎn)遠(yuǎn)小于全量數(shù)據(jù)纳账。

②主從連接中斷期間主節(jié)點(diǎn)依然響應(yīng)命令逛薇,但因復(fù)制連接中斷命令無(wú)法發(fā)送給從節(jié)點(diǎn),不過(guò)主節(jié)點(diǎn)內(nèi)的復(fù)制積壓緩沖區(qū)依然可以保存最近一段時(shí)間的寫命令數(shù)據(jù)疏虫。

③當(dāng)主從連接恢復(fù)后永罚,由于從節(jié)點(diǎn)之前保存了自身已復(fù)制的偏移量和主節(jié)點(diǎn)的運(yùn)行 ID。因此會(huì)把它們當(dāng)做 psync 參數(shù)發(fā)送給主節(jié)點(diǎn)卧秘,要求進(jìn)行部分復(fù)制呢袱。

④主節(jié)點(diǎn)接收到 psync 命令后首先核對(duì)參數(shù) runId 是否與自身一致,如果一致翅敌,說(shuō)明之前復(fù)制的是當(dāng)前主節(jié)點(diǎn)羞福。

之后根據(jù)參數(shù) offset 在復(fù)制積壓緩沖區(qū)中查找,如果 offset 之后的數(shù)據(jù)存在蚯涮,則對(duì)從節(jié)點(diǎn)發(fā)送+COUTINUE 命令治专,表示可以進(jìn)行部分復(fù)制。因?yàn)榫彌_區(qū)大小固定恋昼,若發(fā)生緩沖溢出看靠,則進(jìn)行全量復(fù)制。

⑤主節(jié)點(diǎn)根據(jù)偏移量把復(fù)制積壓緩沖區(qū)里的數(shù)據(jù)發(fā)送給從節(jié)點(diǎn)液肌,保證主從復(fù)制進(jìn)入正常狀態(tài)挟炬。

哨兵

面試官:那主從復(fù)制會(huì)存在哪些問(wèn)題呢?

我:主從復(fù)制會(huì)存在以下問(wèn)題:

  • 一旦主節(jié)點(diǎn)宕機(jī)嗦哆,從節(jié)點(diǎn)晉升為主節(jié)點(diǎn)谤祖,同時(shí)需要修改應(yīng)用方的主節(jié)點(diǎn)地址,還需要命令所有從節(jié)點(diǎn)去復(fù)制新的主節(jié)點(diǎn)老速,整個(gè)過(guò)程需要人工干預(yù)粥喜。

  • 主節(jié)點(diǎn)的寫能力受到單機(jī)的限制。

  • 主節(jié)點(diǎn)的存儲(chǔ)能力受到單機(jī)的限制橘券。

  • 原生復(fù)制的弊端在早期的版本中也會(huì)比較突出额湘,比如:Redis 復(fù)制中斷后,從節(jié)點(diǎn)會(huì)發(fā)起 psync旁舰。

    此時(shí)如果同步不成功锋华,則會(huì)進(jìn)行全量同步,主庫(kù)執(zhí)行全量備份的同時(shí)箭窜,可能會(huì)造成毫秒或秒級(jí)的卡頓毯焕。

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

我:當(dāng)然是哨兵啊磺樱。

面試官:那么問(wèn)題又來(lái)了纳猫。那你說(shuō)下哨兵有哪些功能婆咸?

image

我:如圖,是 Redis Sentinel(哨兵)的架構(gòu)圖芜辕。Redis Sentinel(哨兵)主要功能包括主節(jié)點(diǎn)存活檢測(cè)尚骄、主從運(yùn)行情況檢測(cè)、自動(dòng)故障轉(zhuǎn)移物遇、主從切換乖仇。

Redis Sentinel 最小配置是一主一從。Redis 的 Sentinel 系統(tǒng)可以用來(lái)管理多個(gè) Redis 服務(wù)器询兴。

該系統(tǒng)可以執(zhí)行以下四個(gè)任務(wù):

  • 監(jiān)控:不斷檢查主服務(wù)器和從服務(wù)器是否正常運(yùn)行乃沙。

  • 通知:當(dāng)被監(jiān)控的某個(gè) Redis 服務(wù)器出現(xiàn)問(wèn)題,Sentinel 通過(guò) API 腳本向管理員或者其他應(yīng)用程序發(fā)出通知诗舰。

  • 自動(dòng)故障轉(zhuǎn)移:當(dāng)主節(jié)點(diǎn)不能正常工作時(shí)警儒,Sentinel 會(huì)開(kāi)始一次自動(dòng)的故障轉(zhuǎn)移操作,它會(huì)將與失效主節(jié)點(diǎn)是主從關(guān)系的其中一個(gè)從節(jié)點(diǎn)升級(jí)為新的主節(jié)點(diǎn)眶根,并且將其他的從節(jié)點(diǎn)指向新的主節(jié)點(diǎn)蜀铲,這樣人工干預(yù)就可以免了。

  • 配置提供者:在 Redis Sentinel 模式下属百,客戶端應(yīng)用在初始化時(shí)連接的是 Sentinel 節(jié)點(diǎn)集合记劝,從中獲取主節(jié)點(diǎn)的信息。

面試官:那你能說(shuō)下哨兵的工作原理嗎族扰?

我:話不多說(shuō)厌丑,直接上圖:

image

①每個(gè) Sentinel 節(jié)點(diǎn)都需要定期執(zhí)行以下任務(wù):每個(gè) Sentinel 以每秒一次的頻率,向它所知的主服務(wù)器渔呵、從服務(wù)器以及其他的 Sentinel 實(shí)例發(fā)送一個(gè) PING 命令怒竿。(如上圖)

image

②如果一個(gè)實(shí)例距離最后一次有效回復(fù) PING 命令的時(shí)間超過(guò) down-after-milliseconds 所指定的值,那么這個(gè)實(shí)例會(huì)被 Sentinel 標(biāo)記為主觀下線扩氢。(如上圖)

image

③如果一個(gè)主服務(wù)器被標(biāo)記為主觀下線耕驰,那么正在監(jiān)視這個(gè)服務(wù)器的所有 Sentinel 節(jié)點(diǎn),要以每秒一次的頻率確認(rèn)主服務(wù)器的確進(jìn)入了主觀下線狀態(tài)录豺。

image

④如果一個(gè)主服務(wù)器被標(biāo)記為主觀下線朦肘,并且有足夠數(shù)量的 Sentinel(至少要達(dá)到配置文件指定的數(shù)量)在指定的時(shí)間范圍內(nèi)同意這一判斷,那么這個(gè)主服務(wù)器被標(biāo)記為客觀下線双饥。

image

⑤一般情況下媒抠,每個(gè) Sentinel 會(huì)以每 10 秒一次的頻率向它已知的所有主服務(wù)器和從服務(wù)器發(fā)送 INFO 命令。

當(dāng)一個(gè)主服務(wù)器被標(biāo)記為客觀下線時(shí)兢哭,Sentinel 向下線主服務(wù)器的所有從服務(wù)器發(fā)送 INFO 命令的頻率,會(huì)從 10 秒一次改為每秒一次夫嗓。

image

⑥Sentinel 和其他 Sentinel 協(xié)商客觀下線的主節(jié)點(diǎn)的狀態(tài)迟螺,如果處于 SDOWN 狀態(tài)冲秽,則投票自動(dòng)選出新的主節(jié)點(diǎn),將剩余從節(jié)點(diǎn)指向新的主節(jié)點(diǎn)進(jìn)行數(shù)據(jù)復(fù)制矩父。

image

⑦當(dāng)沒(méi)有足夠數(shù)量的 Sentinel 同意主服務(wù)器下線時(shí)锉桑,主服務(wù)器的客觀下線狀態(tài)就會(huì)被移除。

當(dāng)主服務(wù)器重新向 Sentinel 的 PING 命令返回有效回復(fù)時(shí)窍株,主服務(wù)器的主觀下線狀態(tài)就會(huì)被移除民轴。

面試官:不錯(cuò),面試前沒(méi)少下工夫啊球订,今天 Redis 這關(guān)你過(guò)了后裸,明天找個(gè)時(shí)間我們?cè)倭牧钠渌摹#冻鲂牢康奈⑿Γ?/p>

我:沒(méi)問(wèn)題冒滩。

總結(jié)

本文在一次面試的過(guò)程中講述了 Redis 是什么微驶,Redis 的特點(diǎn)和功能,Redis 緩存的使用开睡,Redis 為什么能這么快因苹,Redis 緩存的淘汰策略,持久化的兩種方式篇恒,Redis 高可用部分的主從復(fù)制和哨兵的基本原理扶檐。

只要功夫深,鐵杵磨成針胁艰,平時(shí)準(zhǔn)備好款筑,面試不用慌。雖然面試不一定是這樣問(wèn)的蝗茁,但萬(wàn)變不離其“宗”醋虏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市哮翘,隨后出現(xiàn)的幾起案子颈嚼,更是在濱河造成了極大的恐慌,老刑警劉巖饭寺,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阻课,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡艰匙,警方通過(guò)查閱死者的電腦和手機(jī)限煞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)员凝,“玉大人署驻,你說(shuō)我怎么就攤上這事。” “怎么了旺上?”我有些...
    開(kāi)封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵瓶蚂,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我宣吱,道長(zhǎng)窃这,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任征候,我火速辦了婚禮杭攻,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疤坝。我一直安慰自己兆解,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布卒煞。 她就那樣靜靜地躺著痪宰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪畔裕。 梳的紋絲不亂的頭發(fā)上衣撬,一...
    開(kāi)封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音扮饶,去河邊找鬼具练。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甜无,可吹牛的內(nèi)容都是我干的扛点。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼岂丘,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼陵究!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起奥帘,我...
    開(kāi)封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤铜邮,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后寨蹋,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體松蒜,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年已旧,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了秸苗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡运褪,死狀恐怖惊楼,靈堂內(nèi)的尸體忽然破棺而出玖瘸,到底是詐尸還是另有隱情,我是刑警寧澤檀咙,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布店读,位于F島的核電站,受9級(jí)特大地震影響攀芯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜文虏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一侣诺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧氧秘,春花似錦年鸳、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至灭忠,卻和暖如春膳算,著一層夾襖步出監(jiān)牢的瞬間道偷,已是汗流浹背屿愚。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工屁擅, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留洽故,地道東北人剃执。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓牙咏,卻偏偏與公主長(zhǎng)得像智玻,于是被迫代替她去往敵國(guó)和親证膨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萨西,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361