我的阿里面經(jīng),和面試官漫談Redis人乓,面試當(dāng)談笑風(fēng)生

今天勤篮,我不自量力的面試了某大廠的java開發(fā)崗位,迎面走來一位風(fēng)塵仆仆的中年男子色罚,手里拿著屏幕還亮著的mac碰缔,他沖著我禮貌的笑了笑,然后說了句“不好意思保屯,讓你久等了”手负,然后示意我坐下涤垫,說:“我們開始吧姑尺。看了你的簡歷蝠猬,覺得你對(duì)redis應(yīng)該掌握的不錯(cuò)切蟋,我們今天就來討論下redis......”。我想:“來就來榆芦,兵來將擋水來土掩”柄粹。

一、Redis是什么

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

:(這不就是總結(jié)下redis的定義和特點(diǎn)嘛)Redis是一種NoSQL(not-only sql匆绣,泛指非關(guān)系型數(shù)據(jù)庫)的數(shù)據(jù)庫驻右。

我頓了一下,接著說:Redis作為一個(gè)內(nèi)存數(shù)據(jù)庫崎淳。

  1. 性能優(yōu)秀堪夭,數(shù)據(jù)在內(nèi)存中,讀寫速度非臣鸢迹快森爽,支持并發(fā)10W QPS;
  2. 單進(jìn)程單線程嚣镜,是線程安全的爬迟,采用IO多路復(fù)用機(jī)制;
  3. 豐富的數(shù)據(jù)類型菊匿,支持字符串(strings)付呕、散列(hashes)、列表(lists)跌捆、集合(sets)徽职、有序集合(sorted sets)等;
  4. 支持?jǐn)?shù)據(jù)持久化疹蛉』罨可以將內(nèi)存中數(shù)據(jù)保存在磁盤中,重啟時(shí)加載;
  5. 主從復(fù)制育韩,哨兵克蚂,高可用;
  6. 可以用作分布式鎖筋讨;
  7. 可以作為消息中間件使用埃叭,支持發(fā)布訂閱

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

面試官:總結(jié)的不錯(cuò)悉罕,看來是早有準(zhǔn)備啊赤屋。剛來聽你提到redis支持五種數(shù)據(jù)類型,那你能簡單說下這五種數(shù)據(jù)類型嗎壁袄?

:當(dāng)然可以类早,但是在說之前,我覺得有必要先來了解下Redis內(nèi)部內(nèi)存管理是如何描述這5種數(shù)據(jù)類型的嗜逻。說著涩僻,我拿著筆給面試官畫了一張圖:

:首先redis內(nèi)部使用一個(gè)redisObject對(duì)象來表示所有的key和value,redisObject最主要的信息如上圖所示:type表示一個(gè)value對(duì)象具體是何種數(shù)據(jù)類型栈顷,encoding是不同數(shù)據(jù)類型在redis內(nèi)部的存儲(chǔ)方式逆日。比如:type=string表示value存儲(chǔ)的是一個(gè)普通字符串,那么encoding可以是raw或者int萄凤。

我頓了一下室抽,接著說:下面我簡單說下5種數(shù)據(jù)類型:

  1. 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已日。

  2. Hash是一個(gè)鍵值(key-value)的集合。redis的hash是一個(gè)string的key和value的映射表栅屏,Hash特別適合存儲(chǔ)對(duì)象飘千。常用命令:hget,hset,hgetall等。

  3. list列表是簡單的字符串列表栈雳,按照插入順序排序护奈。可以添加一個(gè)元素到列表的頭部(左邊)或者尾部(右邊) 常用命令:lpush哥纫、rpush霉旗、lpop、rpop、lrange(獲取列表片段)等厌秒。

  • 應(yīng)用場景:list應(yīng)用場景非常多读拆,也是Redis最重要的數(shù)據(jù)結(jié)構(gòu)之一,比如twitter的關(guān)注列表鸵闪,粉絲列表都可以用list結(jié)構(gòu)來實(shí)現(xiàn)檐晕。
  • 數(shù)據(jù)結(jié)構(gòu):list就是鏈表,可以用來當(dāng)消息隊(duì)列用蚌讼。redis提供了List的push和pop操作辟灰,還提供了操作某一段的api,可以直接查詢或者刪除某一段的元素篡石。
  • 實(shí)現(xiàn)方式:redis list的是實(shí)現(xiàn)是一個(gè)雙向鏈表芥喇,既可以支持反向查找和遍歷,更方便操作夏志,不過帶來了額外的內(nèi)存開銷乃坤。
  1. set是string類型的無序集合苛让。集合是通過hashtable實(shí)現(xiàn)的沟蔑。set中的元素是沒有順序的,而且是沒有重復(fù)的狱杰。
  • 常用命令:sdd瘦材、spop、smembers仿畸、sunion等食棕。
  • 應(yīng)用場景:redis set對(duì)外提供的功能和list一樣是一個(gè)列表,特殊之處在于set是自動(dòng)去重的错沽,而且set提供了判斷某個(gè)成員是否在一個(gè)set集合中簿晓。
  1. zset和set一樣是string類型元素的集合,且不允許重復(fù)的元素千埃。常用命令:zadd憔儿、zrange、zrem放可、zcard等谒臼。
  • 使用場景:sorted set可以通過用戶額外提供一個(gè)優(yōu)先級(jí)(score)的參數(shù)來為成員排序,并且是插入有序的耀里,即自動(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正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序。
  • 實(shí)現(xiàn)方式:Redis sorted set的內(nèi)部使用HashMap和跳躍表(skipList)來保證數(shù)據(jù)的存儲(chǔ)和有序,HashMap里放的是成員到score的映射皱蹦,而跳躍表里存放的是所有的成員,排序依據(jù)是HashMap里存的score刃泡,使用跳躍表的結(jié)構(gòu)可以獲得比較高的查找效率锨络,并且在實(shí)現(xiàn)上比較簡單。

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

面試官:想不到你平時(shí)也下了不少工夫拒课,那redis緩存你一定用過的吧

:用過的徐勃。。

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

我是結(jié)合spring boot使用的僻肖。一般有兩種方式,一種是直接通過RedisTemplate來使用卢鹦,另一種是使用spring cache集成Redis(也就是注解的方式)

三臀脏、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連接池揉稚,如不引入啟動(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ù)表示沒有限制),默認(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 +
                '}';
    }
}

3.1 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;
    }
}

測試類

@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());
    }

然后在瀏覽器訪問含末,觀察后臺(tái)日志http://localhost:8082/user/test

3.2 使用spring cache集成redis

spring cache具備很好的靈活性猜拾,不僅能夠使用SPEL(spring expression language)來定義緩存的key和各種condition,還提供了開箱即用的緩存臨時(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ù)庫的操作磨总,這里直接定義了一個(gè)Map<Integer,User> userMap,這里的核心是三個(gè)注解@Cachable笼沥、@CachePut和@CacheEvict蚪燕。

@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è)注解開啟緩存

@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ù)了鲁驶,因?yàn)樯弦徊絘dd方法已經(jīng)把id=4的用戶數(shù)據(jù)放入了redis緩存

3鉴裹、調(diào)用刪除方法,刪除id=4的用戶信息钥弯,同時(shí)清除緩存

4径荔、再次調(diào)用查詢接口,查詢id=4的用戶信息:

沒有了緩存脆霎,所以進(jìn)入了get方法总处,從userMap中獲取。

3.3 緩存注解

1睛蛛、@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)行緩存键袱。

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

3澜汤、@CacheEvict
根據(jù)條件對(duì)緩存進(jìn)行清空

  • key:同上
  • value:同上
  • condition:同上
  • allEntries:是否清空所有緩存內(nèi)容蚜迅,缺省為false,如果指定為true俊抵,則方法調(diào)用后將立即清空所有緩存
  • beforeInvocation:是否在方法執(zhí)行前就清空谁不,缺省為false,如果指定為true徽诲,則在方法還沒有執(zhí)行的時(shí)候就清空緩存刹帕。缺省情況下吵血,如果方法執(zhí)行拋出異常,則不會(huì)清空緩存偷溺。

四蹋辅、緩存問題

面試官:看了一下你的demo,簡單易懂挫掏。那你在實(shí)際項(xiàng)目中使用緩存有遇到什么問題或者會(huì)遇到什么問題你知道嗎侦另?

:緩存和數(shù)據(jù)庫數(shù)據(jù)一致性問題:分布式環(huán)境下非常容易出現(xiàn)緩存和數(shù)據(jù)庫間數(shù)據(jù)一致性問題,針對(duì)這一點(diǎn)尉共,如果項(xiàng)目對(duì)緩存的要求是強(qiáng)一致性的淋肾,那么就不要使用緩存。我們只能采取合適的策略來降低緩存和數(shù)據(jù)庫間數(shù)據(jù)不一致的概率爸邢,而無法保證兩者間的強(qiáng)一致性樊卓。合適的策略包括合適的緩存更新策略,更新數(shù)據(jù)庫后及時(shí)更新緩存杠河、緩存失敗時(shí)增加重試機(jī)制碌尔。

面試官:Redis雪崩了解嗎?

:我了解的券敌,目前電商首頁以及熱點(diǎn)數(shù)據(jù)都會(huì)去做緩存唾戚,一般緩存都是定時(shí)任務(wù)去刷新,或者查不到之后去更新緩存的待诅,定時(shí)任務(wù)刷新就有一個(gè)問題叹坦。舉個(gè)栗子:如果首頁所有Key的失效時(shí)間都是12小時(shí),中午12點(diǎn)刷新的卑雁,我零點(diǎn)有個(gè)大促活動(dòng)大量用戶涌入募书,假設(shè)每秒6000個(gè)請(qǐng)求,本來緩存可以抗住每秒5000個(gè)請(qǐng)求测蹲,但是緩存中所有Key都失效了莹捡。此時(shí)6000個(gè)/秒的請(qǐng)求全部落在了數(shù)據(jù)庫上,數(shù)據(jù)庫必然扛不住扣甲,真實(shí)情況可能DBA都沒反應(yīng)過來直接掛了篮赢,此時(shí),如果沒什么特別的方案來處理琉挖,DBA很著急启泣,重啟數(shù)據(jù)庫,但是數(shù)據(jù)庫立馬又被新流量給打死了示辈。這就是我理解的緩存雪崩寥茫。

我心想:同一時(shí)間大面積失效,瞬間Redis跟沒有一樣顽耳,那這個(gè)數(shù)量級(jí)別的請(qǐng)求直接打到數(shù)據(jù)庫幾乎是災(zāi)難性的坠敷,你想想如果掛的是一個(gè)用戶服務(wù)的庫妙同,那其他依賴他的庫所有接口幾乎都會(huì)報(bào)錯(cuò),如果沒做熔斷等策略基本上就是瞬間掛一片的節(jié)奏膝迎,你怎么重啟用戶都會(huì)把你打掛粥帚,等你重啟好的時(shí)候,用戶早睡覺去了限次,臨睡之前芒涡,罵罵咧咧“什么垃圾產(chǎn)品”。

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

:處理緩存雪崩簡單羊始,在批量往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庫中也能避免全部失效≡扔停或者設(shè)置熱點(diǎn)數(shù)據(jù)永不過期缘缚,有更新操作就更新緩存就好了(比如運(yùn)維更新了首頁商品,那你刷下緩存就好了敌蚜,不要設(shè)置過期時(shí)間)桥滨,電商首頁的數(shù)據(jù)也可以用這個(gè)操作,保險(xiǎn)弛车。

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

:嗯帅韧,了解里初,先說下緩存穿透吧,緩存穿透是指緩存和數(shù)據(jù)庫中都沒有的數(shù)據(jù)忽舟,而用戶(黑客)不斷發(fā)起請(qǐng)求,舉個(gè)栗子:我們數(shù)據(jù)庫的id都是從1自增的淮阐,如果發(fā)起id=-1的數(shù)據(jù)或者id特別大不存在的數(shù)據(jù)叮阅,這樣的不斷攻擊導(dǎo)致數(shù)據(jù)庫壓力很大,嚴(yán)重會(huì)擊垮數(shù)據(jù)庫泣特。

我又接著說:至于緩存擊穿嘛浩姥,這個(gè)跟緩存雪崩有點(diǎn)像,但是又有一點(diǎn)不一樣状您,緩存雪崩是因?yàn)榇竺娣e的緩存失效勒叠,打崩了DB兜挨,而緩存擊穿不同的是緩存擊穿是指一個(gè)Key非常熱點(diǎn),在不停地扛著大量的請(qǐng)求眯分,大并發(fā)集中對(duì)這一個(gè)點(diǎn)進(jìn)行訪問拌汇,當(dāng)這個(gè)Key在失效的瞬間,持續(xù)的大并發(fā)直接落到了數(shù)據(jù)庫上弊决,就在這個(gè)Key的點(diǎn)上擊穿了緩存噪舀。

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

:緩存穿透我會(huì)在接口層增加校驗(yàn)飘诗,比如用戶鑒權(quán)与倡,參數(shù)做校驗(yàn),不合法的校驗(yàn)直接return昆稿,比如id做基礎(chǔ)校驗(yàn)纺座,id<=0直接攔截。

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

:我記得Redis里還有一個(gè)高級(jí)用法布隆過濾器(Bloom Filter)這個(gè)也能很好的預(yù)防緩存穿透的發(fā)生净响,他的原理也很簡單,就是利用高效的數(shù)據(jù)結(jié)構(gòu)和算法快速判斷出你這個(gè)Key是否在數(shù)據(jù)庫中存在岛抄,不存在你return就好了别惦,存在你就去查DB刷新KV再return。緩存擊穿的話夫椭,設(shè)置熱點(diǎn)數(shù)據(jù)永不過期掸掸,或者加上互斥鎖就搞定了。作為暖男蹭秋,代碼給你準(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ù)庫查詢
                    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();
            }
        }
        return result;
    }

面試官:嗯嗯仁讨,還不錯(cuò)羽莺。

五、Redis為何這么快

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

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

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

:您是想問Redis這么快蛔趴,為什么還是單線程的吧。Redis確實(shí)是單進(jìn)程單線程的模型例朱,因?yàn)镽edis完全是基于內(nèi)存的操作孝情,CPU不是Redis的瓶頸鱼蝉,Redis的瓶頸最有可能是機(jī)器內(nèi)存的大小或者網(wǎng)絡(luò)帶寬。既然單線程容易實(shí)現(xiàn)箫荡,而且CPU不會(huì)成為瓶頸魁亦,那就順理成章的采用單線程的方案了(畢竟采用多線程會(huì)有很多麻煩)。

面試官:嗯菲茬,是的吉挣。那你能說說Redis是單線程的,為什么還能這么快嗎婉弹?

:可以這么說吧睬魂。

  • 第一:Redis完全基于內(nèi)存,絕大部分請(qǐng)求是純粹的內(nèi)存操作镀赌,非常迅速氯哮,數(shù)據(jù)存在內(nèi)存中,類似于HashMap商佛,HashMap的優(yōu)勢就是查找和操作的時(shí)間復(fù)雜度是O(1)喉钢。
  • 第二:數(shù)據(jù)結(jié)構(gòu)簡單,對(duì)數(shù)據(jù)操作也簡單良姆。
  • 第三:采用單線程肠虽,避免了不必要的上下文切換和競爭條件,不存在多線程導(dǎo)致的CPU切換玛追,不用去考慮各種鎖的問題税课,不存在加鎖釋放鎖操作,沒有死鎖問題導(dǎo)致的性能消耗痊剖。
  • 第四:使用多路復(fù)用IO模型韩玩,非阻塞IO。

六陆馁、Redis和Memcached的區(qū)別

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

  1. 存儲(chǔ)方式上:memcache會(huì)把數(shù)據(jù)全部存在內(nèi)存之中叮贩,斷電后會(huì)掛掉击狮,數(shù)據(jù)不能超過內(nèi)存大小。redis有部分?jǐn)?shù)據(jù)存在硬盤上益老,這樣能保證數(shù)據(jù)的持久性帘不。
  2. 數(shù)據(jù)支持類型上:memcache對(duì)數(shù)據(jù)類型的支持簡單,只支持簡單的key-value杨箭,,而redis支持五種數(shù)據(jù)類型储狭。
  3. 使用底層模型不同:它們之間底層實(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)求慈参。
  4. value的大星荷:redis可以達(dá)到1GB,而memcache只有1MB驮配。

七娘扩、淘汰策略

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

:Redis有六種淘汰策略

補(bǔ)充一下:Redis4.0加入了LFU(least frequency use)淘汰策略壮锻,包括volatile-lfu和allkeys-lfu琐旁,通過統(tǒng)計(jì)訪問頻率,將訪問頻率最少猜绣,即最不經(jīng)常使用的KV淘汰灰殴。

八、持久化

面試官:你對(duì)redis的持久化機(jī)制了解嗎掰邢?能講一下嗎牺陶?

:redis為了保證效率,數(shù)據(jù)緩存在了內(nèi)存中辣之,但是會(huì)周期性的把更新的數(shù)據(jù)寫入磁盤或者把修改操作寫入追加的記錄文件中掰伸,以保證數(shù)據(jù)的持久化。Redis的持久化策略有兩種:

  1. RDB:快照形式是直接把內(nèi)存中的數(shù)據(jù)保存到一個(gè)dump的文件中怀估,定時(shí)保存狮鸭,保存策略。
  2. AOF:把所有的對(duì)Redis的服務(wù)器進(jìn)行修改的命令都存到一個(gè)文件里奏夫,命令的集合怕篷。Redis默認(rèn)是快照RDB的持久化方式。

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

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

:默認(rèn)Redis是會(huì)以快照"RDB"的形式將數(shù)據(jù)持久化到磁盤的一個(gè)二進(jìn)制文件dump.rdb呛哟。工作原理簡單說一下:當(dāng)Redis需要做持久化時(shí)叠荠,Redis會(huì)fork一個(gè)子進(jìn)程,子進(jìn)程將數(shù)據(jù)寫到磁盤上一個(gè)臨時(shí)RDB文件中扫责。當(dāng)子進(jìn)程完成寫臨時(shí)文件后榛鼎,將原來的RDB替換掉,這樣的好處是可以copy-on-write。

:RDB的優(yōu)點(diǎn)是:這種文件非常適合用于備份:比如者娱,你可以在最近的24小時(shí)內(nèi)抡笼,每小時(shí)備份一次,并且在每個(gè)月的每一天也備份一個(gè)RDB文件黄鳍。這樣的話推姻,即使遇上問題膳汪,也可以隨時(shí)將數(shù)據(jù)集還原到不同的版本醉旦。RDB非常適合災(zāi)難恢復(fù)。RDB的缺點(diǎn)是:如果你需要盡量避免在服務(wù)器故障時(shí)丟失數(shù)據(jù)烟阐,那么RDB不合適你忍燥。

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

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

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

AOF可以做到全程持久化,只需要在配置中開啟 appendonly yes炭玫。這樣redis每執(zhí)行一個(gè)修改數(shù)據(jù)的命令奈嘿,都會(huì)把它添加到AOF文件中,當(dāng)redis重啟時(shí)吞加,將會(huì)讀取AOF文件進(jìn)行重放裙犹,恢復(fù)到redis關(guān)閉前的最后時(shí)刻。

我頓了一下衔憨,繼續(xù)說:使用AOF的優(yōu)點(diǎn)是會(huì)讓redis變得非常耐久叶圃。可以設(shè)置不同的fsync策略践图,aof的默認(rèn)策略是每秒鐘fsync一次掺冠,在這種配置下,就算發(fā)生故障停機(jī)码党,也最多丟失一秒鐘的數(shù)據(jù)德崭。缺點(diǎn)是對(duì)于相同的數(shù)據(jù)集來說,AOF的文件體積通常要大于RDB文件的體積揖盘。根據(jù)所使用的fsync策略眉厨,AOF的速度可能會(huì)慢于RDB。

面試官又問:你說了這么多兽狭,那我該用哪一個(gè)呢憾股?

:如果你非常關(guān)心你的數(shù)據(jù)鹿蜀,但仍然可以承受數(shù)分鐘內(nèi)的數(shù)據(jù)丟失,那么可以額只使用RDB持久荔燎。AOF將Redis執(zhí)行的每一條命令追加到磁盤中耻姥,處理巨大的寫入會(huì)降低Redis的性能,不知道你是否可以接受有咨。數(shù)據(jù)庫備份和災(zāi)難恢復(fù):定時(shí)生成RDB快照非常便于進(jìn)行數(shù)據(jù)庫備份,并且RDB恢復(fù)數(shù)據(jù)集的速度也要比AOF恢復(fù)的速度快蒸健。當(dāng)然了座享,redis支持同時(shí)開啟RDB和AOF,系統(tǒng)重啟后似忧,redis會(huì)優(yōu)先使用AOF來恢復(fù)數(shù)據(jù)渣叛,這樣丟失的數(shù)據(jù)會(huì)最少。

九盯捌、主從復(fù)制

面試官:redis單節(jié)點(diǎn)存在單點(diǎn)故障問題淳衙,為了解決單點(diǎn)問題,一般都需要對(duì)redis配置從節(jié)點(diǎn)饺著,然后使用哨兵來監(jiān)聽主節(jié)點(diǎn)的存活狀態(tài)箫攀,如果主節(jié)點(diǎn)掛掉,從節(jié)點(diǎn)能繼續(xù)提供緩存功能幼衰,你能說說redis主從復(fù)制的過程和原理嗎靴跛?

我有點(diǎn)懵,這個(gè)說來就話長了渡嚣。但幸好提前準(zhǔn)備了:主從配置結(jié)合哨兵模式能解決單點(diǎn)故障問題梢睛,提高redis可用性。從節(jié)點(diǎn)僅提供讀操作识椰,主節(jié)點(diǎn)提供寫操作绝葡。對(duì)于讀多寫少的狀況,可給主節(jié)點(diǎn)配置多個(gè)從節(jié)點(diǎn)腹鹉,從而提高響應(yīng)效率藏畅。

我頓了一下,接著說:關(guān)于復(fù)制過程种蘸,是這樣的:

  1. 從節(jié)點(diǎn)執(zhí)行slaveof[masterIP][masterPort]墓赴,保存主節(jié)點(diǎn)信息
  2. 從節(jié)點(diǎn)中的定時(shí)任務(wù)發(fā)現(xiàn)主節(jié)點(diǎn)信息,建立和主節(jié)點(diǎn)的socket連接
  3. 從節(jié)點(diǎn)發(fā)送Ping信號(hào)航瞭,主節(jié)點(diǎn)返回Pong诫硕,兩邊能互相通信
  4. 連接建立后,主節(jié)點(diǎn)將所有數(shù)據(jù)發(fā)送給從節(jié)點(diǎn)(數(shù)據(jù)同步)
  5. 主節(jié)點(diǎn)把當(dāng)前的數(shù)據(jù)同步給從節(jié)點(diǎn)后刊侯,便完成了復(fù)制的建立過程章办。接下來,主節(jié)點(diǎn)就會(huì)持續(xù)的把寫命令發(fā)送給從節(jié)點(diǎn),保證主從數(shù)據(jù)一致性藕届。

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

(我心想:這也問的太細(xì)了吧)我:可以。redis2.8之前使用sync[runId][offset]同步命令休偶,redis2.8之后使用psync[runId][offset]命令梁厉。兩者不同在于,sync命令僅支持全量復(fù)制過程踏兜,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é)長度。從節(jié)點(diǎn)在收到主節(jié)點(diǎn)發(fā)送的命令后纳本,也會(huì)增加自己的offset窍蓝,并把自己的offset發(fā)送給主節(jié)點(diǎn)。這樣饮醇,主節(jié)點(diǎn)同時(shí)保存自己的offset和從節(jié)點(diǎn)的offset它抱,通過對(duì)比offset來判斷主從節(jié)點(diǎn)數(shù)據(jù)是否一致。
  • repl_backlog_size:保存在主節(jié)點(diǎn)上的一個(gè)固定長度的先進(jìn)先出隊(duì)列朴艰,默認(rèn)大小是1MB观蓄。
  1. 主節(jié)點(diǎn)發(fā)送數(shù)據(jù)給從節(jié)點(diǎn)過程中,主節(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ù)制亲茅。
  2. 主節(jié)點(diǎn)響應(yīng)寫命令時(shí),不但會(huì)把命名發(fā)送給從節(jié)點(diǎn)狗准,還會(huì)寫入復(fù)制積壓緩沖區(qū)克锣,用于復(fù)制命令丟失的數(shù)據(jù)補(bǔ)救。

上面是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ù)制

面試官:很好捞附,那你能具體說下全量復(fù)制和部分復(fù)制的過程嗎巾乳?

:可以

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

  1. 從節(jié)點(diǎn)發(fā)送psync ? -1命令(因?yàn)榈谝淮伟l(fā)送,不知道主節(jié)點(diǎn)的runId胆绊,所以為?氨鹏,因?yàn)槭堑谝淮螐?fù)制,所以offset=-1)压状。
  2. 主節(jié)點(diǎn)發(fā)現(xiàn)從節(jié)點(diǎn)是第一次復(fù)制仆抵,返回FULLRESYNC {runId} {offset},runId是主節(jié)點(diǎn)的runId何缓,offset是主節(jié)點(diǎn)目前的offset肢础。
  3. 從節(jié)點(diǎn)接收主節(jié)點(diǎn)信息后,保存到info中碌廓。
  4. 主節(jié)點(diǎn)在發(fā)送FULLRESYNC后,啟動(dòng)bgsave命令剩盒,生成RDB文件(數(shù)據(jù)持久化)谷婆。
  5. 主節(jié)點(diǎn)發(fā)送RDB文件給從節(jié)點(diǎn)。到從節(jié)點(diǎn)加載數(shù)據(jù)完成這段期間主節(jié)點(diǎn)的寫命令放入緩沖區(qū)辽聊。
  6. 從節(jié)點(diǎn)清理自己的數(shù)據(jù)庫數(shù)據(jù)纪挎。
  7. 從節(jié)點(diǎn)加載RDB文件,將數(shù)據(jù)保存到自己的數(shù)據(jù)庫中跟匆。
  8. 如果從節(jié)點(diǎn)開啟了AOF异袄,從節(jié)點(diǎn)會(huì)異步重寫AOF文件。

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

  1. 部分復(fù)制主要是Redis針對(duì)全量復(fù)制的過高開銷做出的一種優(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ù)橱鹏。
  2. 主從連接中斷期間主節(jié)點(diǎn)依然響應(yīng)命令,但因復(fù)制連接中斷命令無法發(fā)送給從節(jié)點(diǎn)堪藐,不過主節(jié)點(diǎn)內(nèi)的復(fù)制積壓緩沖區(qū)依然可以保存最近一段時(shí)間的寫命令數(shù)據(jù)莉兰。
  3. 當(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ù)制。
  4. 主節(jié)點(diǎn)接收到psync命令后首先核對(duì)參數(shù)runId是否與自身一致苏章,如果一致寂嘉,說明之前復(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ù)制。
  5. 主節(jié)點(diǎn)根據(jù)偏移量把復(fù)制積壓緩沖區(qū)里的數(shù)據(jù)發(fā)送給從節(jié)點(diǎn)句喷,保證主從復(fù)制進(jìn)入正常狀態(tài)镣典。

十、哨兵

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

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

  1. 一旦主節(jié)點(diǎn)宕機(jī)兄春,從節(jié)點(diǎn)晉升為主節(jié)點(diǎn),同時(shí)需要修改應(yīng)用方的主節(jié)點(diǎn)地址锡溯,還需要命令所有從節(jié)點(diǎn)去復(fù)制新的主節(jié)點(diǎn)赶舆,整個(gè)過程需要人工干預(yù)。
  2. 主節(jié)點(diǎn)的寫能力受到單機(jī)的限制祭饭。
  3. 主節(jié)點(diǎn)的存儲(chǔ)能力受到單機(jī)的限制芜茵。
  4. 原生復(fù)制的弊端在早期的版本中也會(huì)比較突出,比如:redis復(fù)制中斷后倡蝙,從節(jié)點(diǎn)會(huì)發(fā)起psync九串。此時(shí)如果同步不成功,則會(huì)進(jìn)行全量同步寺鸥,主庫執(zhí)行全量備份的同時(shí)猪钮,可能會(huì)造成毫秒或秒級(jí)的卡頓。

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

:當(dāng)然是哨兵啊躬贡。

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

:如圖拂玻,是Redis Sentinel(哨兵)的架構(gòu)圖。Redis Sentinel(哨兵)主要功能包括主節(jié)點(diǎn)存活檢測宰译、主從運(yùn)行情況檢測檐蚜、自動(dòng)故障轉(zhuǎn)移、主從切換沿侈。Redis Sentinel最小配置是一主一從闯第。Redis的Sentinel系統(tǒng)可以用來管理多個(gè)Redis服務(wù)器,該系統(tǒng)可以執(zhí)行以下四個(gè)任務(wù):

  1. 監(jiān)控:不斷檢查主服務(wù)器和從服務(wù)器是否正常運(yùn)行缀拭。
  2. 通知:當(dāng)被監(jiān)控的某個(gè)redis服務(wù)器出現(xiàn)問題咳短,Sentinel通過API腳本向管理員或者其他應(yīng)用程序發(fā)出通知填帽。
  3. 自動(dòng)故障轉(zhuǎn)移:當(dāng)主節(jié)點(diǎn)不能正常工作時(shí),Sentinel會(huì)開始一次自動(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ù)就可以免了勾效。
  4. 配置提供者:在Redis Sentinel模式下嘹悼,客戶端應(yīng)用在初始化時(shí)連接的是Sentinel節(jié)點(diǎn)集合,從中獲取主節(jié)點(diǎn)的信息层宫。

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

:話不多說,直接上圖:

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

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

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

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

5伏伐、一般情況下,每個(gè)Sentinel會(huì)以每10秒一次的頻率向它已知的所有主服務(wù)器和從服務(wù)器發(fā)送INFO命令晕拆,當(dāng)一個(gè)主服務(wù)器被標(biāo)記為客觀下線時(shí)藐翎,Sentinel向下線主服務(wù)器的所有從服務(wù)器發(fā)送INFO命令的頻率,會(huì)從10秒一次改為每秒一次实幕。

6吝镣、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ù)制。

7整吆、當(dāng)沒有足夠數(shù)量的Sentinel同意主服務(wù)器下線時(shí)拱撵,主服務(wù)器的客觀下線狀態(tài)就會(huì)被移除辉川。當(dāng)主服務(wù)器重新向Sentinel的PING命令返回有效回復(fù)時(shí),主服務(wù)器的主觀下線狀態(tài)就會(huì)被移除拴测。

面試官:不錯(cuò)乓旗,面試前沒少下工夫啊,今天Redis這關(guān)你過了昼扛,明天找個(gè)時(shí)間我們?cè)倭牧钠渌拇缙搿#冻鲂牢康奈⑿Γ?/p>

:沒問題。

總結(jié)

本文在一次面試的過程中講述了Redis是什么抄谐,Redis的特點(diǎn)和功能渺鹦,Redis緩存的使用,Redis為什么能這么快蛹含,Redis緩存的淘汰策略毅厚,持久化的兩種方式,Redis高可用部分的主從復(fù)制和哨兵的基本原理浦箱。只要功夫深吸耿,鐵杵磨成針,平時(shí)準(zhǔn)備好酷窥,面試不用慌咽安。雖然面試不一定是這樣問的,但萬變不離其“宗”蓬推。(筆者覺得這種問答形式的博客很不錯(cuò)妆棒,可讀性強(qiáng)而且讀后記的比較深刻)

推薦閱讀

Java架構(gòu)師進(jì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)場離奇詭異,居然都是意外死亡姆另,警方通過查閱死者的電腦和手機(jī)喇肋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜕青,“玉大人苟蹈,你說我怎么就攤上這事∮液耍” “怎么了慧脱?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長贺喝。 經(jīng)常有香客問我菱鸥,道長宗兼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任氮采,我火速辦了婚禮殷绍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘鹊漠。我一直安慰自己主到,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布躯概。 她就那樣靜靜地躺著登钥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪娶靡。 梳的紋絲不亂的頭發(fā)上牧牢,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音姿锭,去河邊找鬼塔鳍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛呻此,可吹牛的內(nèi)容都是我干的轮纫。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼焚鲜,長吁一口氣:“原來是場噩夢啊……” “哼蜡感!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起恃泪,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎犀斋,沒想到半個(gè)月后贝乎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡叽粹,尸身上長有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
  • 文/蒙蒙 一袭艺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧叨粘,春花似錦猾编、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至冻晤,卻和暖如春苇羡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鼻弧。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工绞铃, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人存璃。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓咐刨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親度帮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子歼捏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 1.1 資料 ,最好的入門小冊(cè)子笨篷,可以先于一切文檔之前看瞳秽,免費(fèi)。 作者Antirez的博客率翅,Antirez維護(hù)的R...
    JefferyLcm閱讀 17,071評(píng)論 1 51
  • 一练俐、Redis高可用概述 在介紹Redis高可用之前,先說明一下在Redis的語境中高可用的含義冕臭。 我們知道腺晾,在w...
    空語閱讀 1,598評(píng)論 0 2
  • 企業(yè)級(jí)redis集群架構(gòu)的特點(diǎn) 海量數(shù)據(jù) 高并發(fā) 高可用 要達(dá)到高可用,持久化是不可減少的辜贵,持久化主要是做災(zāi)難恢復(fù)...
    lucode閱讀 2,208評(píng)論 0 7
  • NOSQL類型簡介鍵值對(duì):會(huì)使用到一個(gè)哈希表悯蝉,表中有一個(gè)特定的鍵和一個(gè)指針指向特定的數(shù)據(jù),如redis托慨,volde...
    MicoCube閱讀 4,000評(píng)論 2 27
  • Redis雜談 Redis是近年來發(fā)展迅速的內(nèi)存數(shù)據(jù)庫鼻由,網(wǎng)上也已經(jīng)有多Redis的文章。但不管是英文還是中文,多數(shù)...
    迷失于重逢閱讀 1,566評(píng)論 0 14