Redis總結(jié)(未拆分)

Redis為什么速度快

1疯坤、完全基于內(nèi)存,絕大部分請(qǐng)求是純粹的內(nèi)存操作深浮,非逞沟。快速。數(shù)據(jù)存在內(nèi)存中飞苇,類似于HashMap菌瘫,HashMap的優(yōu)勢(shì)就是查找和操作的時(shí)間復(fù)雜度都是O(1)蜗顽;

2、數(shù)據(jù)結(jié)構(gòu)簡(jiǎn)單雨让,對(duì)數(shù)據(jù)操作也簡(jiǎn)單雇盖,Redis中的數(shù)據(jù)結(jié)構(gòu)是專門(mén)進(jìn)行設(shè)計(jì)的;

3栖忠、采用單線程崔挖,避免了不必要的上下文切換和競(jìng)爭(zhēng)條件,也不存在多進(jìn)程或者多線程導(dǎo)致的切換而消耗 CPU庵寞,不用去考慮各種鎖的問(wèn)題狸相,不存在加鎖釋放鎖操作,沒(méi)有因?yàn)榭赡艹霈F(xiàn)死鎖而導(dǎo)致的性能消耗捐川;

4脓鹃、使用多路I/O復(fù)用模型,非阻塞IO古沥;

5瘸右、使用底層模型不同,它們之間底層實(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)求纯衍;

以上幾點(diǎn)都比較好理解栋齿,下邊我們針對(duì)多路 I/O 復(fù)用模型進(jìn)行簡(jiǎn)單的探討:

(1)多路 I/O 復(fù)用模型

多路I/O復(fù)用模型是利用 select苗胀、poll襟诸、epoll 可以同時(shí)監(jiān)察多個(gè)流的 I/O 事件的能力,在空閑的時(shí)候基协,會(huì)把當(dāng)前線程阻塞掉歌亲,當(dāng)有一個(gè)或多個(gè)流有 I/O 事件時(shí),就從阻塞態(tài)中喚醒澜驮,于是程序就會(huì)輪詢一遍所有的流(epoll 是只輪詢那些真正發(fā)出了事件的流)陷揪,并且只依次順序的處理就緒的流,這種做法就避免了大量的無(wú)用操作杂穷。

這里“多路”指的是多個(gè)網(wǎng)絡(luò)連接悍缠,“復(fù)用”指的是復(fù)用同一個(gè)線程。采用多路 I/O 復(fù)用技術(shù)可以讓單個(gè)線程高效的處理多個(gè)連接請(qǐng)求(盡量減少網(wǎng)絡(luò) IO 的時(shí)間消耗)耐量,且 Redis 在內(nèi)存中操作數(shù)據(jù)的速度非撤沈荆快,也就是說(shuō)內(nèi)存內(nèi)的操作不會(huì)成為影響Redis性能的瓶頸廊蜒,主要由以上幾點(diǎn)造就了 Redis 具有很高的吞吐量趴拧。


==select和epoll溅漾,poll的區(qū)別==


Redis 有哪些數(shù)據(jù)結(jié)構(gòu)?

  • 普通數(shù)據(jù)結(jié)構(gòu):
  1. 字符串 String
  2. 字典Hash
  3. 列表List
  4. 集合Set
  5. 有序集合 SortedSet
  • 中級(jí)數(shù)據(jù)結(jié)構(gòu):
  1. HyperLogLog
  2. Geo
  3. Pub / Sub
  • 高端數(shù)據(jù)結(jié)構(gòu):
  1. BloomFilter
  2. RedisSearch
  3. Redis-ML
  4. JSON

==Redis數(shù)據(jù)結(jié)構(gòu)的應(yīng)用場(chǎng)景==


有序集合底層原理

  • 有序集合對(duì)象的編碼可以是ziplist或者skiplist
  • ziplist編碼的有序集合對(duì)象底層實(shí)現(xiàn)是壓縮列表著榴,每個(gè)有序集合的元素使用兩個(gè)緊挨在一起的壓縮列表節(jié)點(diǎn)來(lái)保存添履,第一個(gè)節(jié)點(diǎn)保存有序集合的元素,第二個(gè)節(jié)點(diǎn)保存元素的分值脑又。壓縮列表的集合元素按照分值從小到大開(kāi)始排序暮胧,分值較小的節(jié)點(diǎn)靠近壓縮列表的表頭方向,分值較大的節(jié)點(diǎn)靠近壓縮列表的表尾方向
  • skiplist編碼的有序集合對(duì)象使用zset作為底層實(shí)現(xiàn)问麸,一個(gè)zset結(jié)構(gòu)同時(shí)包含一個(gè)字典和一個(gè)跳躍表叔壤。zset結(jié)構(gòu)中的zsl跳躍表按照分值從小到大保存了所有集合元素,每個(gè)跳躍表節(jié)點(diǎn)都保存了一個(gè)集合元素口叙,跳躍表節(jié)點(diǎn)的object屬性保存了元素的成員炼绘,score屬性保存了元素的分支,通過(guò)跳躍表妄田,程序可以對(duì)有序集合進(jìn)行范圍型操作俺亮,如ZRANK、ZRANGE等命令疟呐。zset結(jié)構(gòu)中的dict字典為有序集合創(chuàng)建了一個(gè)從成員到分值的映射脚曾,字典的每一個(gè)鍵值對(duì)都保存了一個(gè)集合元素,字典的鍵保存了元素的成員启具,字典的值保存了元素的分值本讥,通過(guò)這個(gè)字典,程序可以以O(shè)(1)復(fù)雜度查找給定成員的分值鲁冯,ZSCORE命令就是根據(jù)這一特性實(shí)現(xiàn)的拷沸。

==跳躍表原理==


為什么有序集合需要同時(shí)使用跳躍表和字典來(lái)實(shí)現(xiàn)呢

理論上來(lái)講,無(wú)論有序集合單獨(dú)使用跳躍表和字典來(lái)實(shí)現(xiàn)有序集合薯演,性能都會(huì)比同時(shí)使用跳躍表和字典有所降低撞芍。例如,如只使用字典來(lái)實(shí)現(xiàn)有序集合跨扮,那么在使用有序集合的ZRANK序无、ZRANGE命令時(shí),程序都需要對(duì)字典保存的所有元素進(jìn)行排序衡创,完成這種排序的復(fù)雜度為O(nlog(n)),以及額外的O(N)的空間(創(chuàng)建數(shù)組保存排序后的元素)帝嗡;如只使用跳躍表實(shí)現(xiàn)有序集合,那么在根據(jù)指定成員查詢分值時(shí)璃氢,復(fù)雜度就會(huì)變成O(log(n))哟玷,而不是字典的O(1)。綜合以上原因拔莱,為了讓有序集合同事?lián)碛凶值浜吞S表的所有特點(diǎn)碗降,Redis選擇同時(shí)使用跳躍表和字典來(lái)實(shí)現(xiàn)有序集合隘竭。


skiplist中的zset為什么不使用紅黑數(shù)而使用跳躍表

簡(jiǎn)單分析一下skiplist的時(shí)間復(fù)雜度和空間復(fù)雜度,以便對(duì)于skiplist的性能有一個(gè)直觀的了解讼渊。

我們先來(lái)計(jì)算一下每個(gè)節(jié)點(diǎn)所包含的平均指針數(shù)目(概率期望)动看。節(jié)點(diǎn)包含的指針數(shù)目,相當(dāng)于這個(gè)算法在空間上的額外開(kāi)銷(overhead)爪幻,可以用來(lái)度量空間復(fù)雜度菱皆。

跳躍表中產(chǎn)生越高的節(jié)點(diǎn)層數(shù),概率越低挨稿。定量的分析如下:

  • 節(jié)點(diǎn)層數(shù)至少為1仇轻。而大于1的節(jié)點(diǎn)層數(shù),滿足一個(gè)概率分布奶甘。
  • 節(jié)點(diǎn)層數(shù)恰好等于1的概率為1-p篷店。
  • 節(jié)點(diǎn)層數(shù)大于等于2的概率為p,而節(jié)點(diǎn)層數(shù)恰好等于2的概率為p(1-p)臭家。
  • 節(jié)點(diǎn)層數(shù)大于等于3的概率為p^2疲陕,
    而節(jié)點(diǎn)層數(shù)恰好等于3的概率為p^2(1-p)。
  • 節(jié)點(diǎn)層數(shù)大于等于4的概率為p^3钉赁,
    而節(jié)點(diǎn)層數(shù)恰好等于4的概率為p^3(1-p)蹄殃。
    因此,一個(gè)節(jié)點(diǎn)的平均層數(shù)(也即包含的平均指針數(shù)目)你踩,計(jì)算如下:
(1-p)+2p(1-p)+3p^2 (1-p)+4p^3 (1-)p+kp^(k-1) (1-p)=(1-)p·1/(1-p)^2=1/(1-p)

上述結(jié)果可以使用錯(cuò)位相減法計(jì)算得到诅岩。

現(xiàn)在很容易計(jì)算出:

  • 當(dāng)p=1/2時(shí),每個(gè)節(jié)點(diǎn)所包含的平均指針數(shù)目為2带膜;
  • 當(dāng)p=1/4時(shí)吩谦,每個(gè)節(jié)點(diǎn)所包含的平均指針數(shù)目為1.33。這也是Redis里的skiplist實(shí)現(xiàn)在空間上的開(kāi)銷钱慢。

==skiplist與平衡樹(shù)逮京、哈希表的比較==

  • skiplist和各種平衡樹(shù)(如AVL卿堂、紅黑樹(shù)等)的元素是有序排列的束莫,而哈希表不是有序的。因此草描,在哈希表上只能做單個(gè)key的查找览绿,不適宜做范圍查找。所謂范圍查找穗慕,指的是查找那些大小在指定的兩個(gè)值之間的所有節(jié)點(diǎn)饿敲。
  • 在做范圍查找的時(shí)候,平衡樹(shù)比skiplist操作要復(fù)雜逛绵。在平衡樹(shù)上怀各,我們找到指定范圍的小值之后倔韭,還需要以中序遍歷的順序繼續(xù)尋找其它不超過(guò)大值的節(jié)點(diǎn)。如果不對(duì)平衡樹(shù)進(jìn)行一定的改造瓢对,這里的中序遍歷并不容易實(shí)現(xiàn)寿酌。而在skiplist上進(jìn)行范圍查找就非常簡(jiǎn)單,只需要在找到小值之后硕蛹,對(duì)第1層鏈表進(jìn)行若干步的遍歷就可以實(shí)現(xiàn)醇疼。
  • 平衡樹(shù)的插入和刪除操作可能引發(fā)子樹(shù)的調(diào)整,邏輯復(fù)雜法焰,而skiplist的插入和刪除只需要修改相鄰節(jié)點(diǎn)的指針秧荆,操作簡(jiǎn)單又快速。
  • 從內(nèi)存占用上來(lái)說(shuō)埃仪,skiplist比平衡樹(shù)更靈活一些乙濒。一般來(lái)說(shuō),平衡樹(shù)每個(gè)節(jié)點(diǎn)包含2個(gè)指針(分別指向左右子樹(shù))卵蛉,而skiplist每個(gè)節(jié)點(diǎn)包含的指針數(shù)目平均為1/(1-p)琉兜,具體取決于參數(shù)p的大小。如果像Redis里的實(shí)現(xiàn)一樣毙玻,取p=1/4豌蟋,那么平均每個(gè)節(jié)點(diǎn)包含1.33個(gè)指針,比平衡樹(shù)更有優(yōu)勢(shì)桑滩。
  • 查找單個(gè)key梧疲,skiplist和平衡樹(shù)的時(shí)間復(fù)雜度都為O(log n),大體相當(dāng)运准;而哈希表在保持較低的哈希值沖突概率的前提下幌氮,查找時(shí)間復(fù)雜度接近O(1),性能更高一些胁澳。所以我們平常使用的各種Map或dictionary結(jié)構(gòu)该互,大都是基于哈希表實(shí)現(xiàn)的。
  • 從算法實(shí)現(xiàn)難度上來(lái)比較韭畸,skiplist比平衡樹(shù)要簡(jiǎn)單得多宇智。

Redis中的skiplist和經(jīng)典有何不同

  • 分?jǐn)?shù)(score)允許重復(fù),即skiplist的key允許重復(fù)胰丁。這在最開(kāi)始介紹的經(jīng)典skiplist中是不允許的随橘。
  • 在比較時(shí),不僅比較分?jǐn)?shù)(相當(dāng)于skiplist的key)锦庸,還比較數(shù)據(jù)本身机蔗。在Redis的skiplist實(shí)現(xiàn)中,數(shù)據(jù)本身的內(nèi)容唯一標(biāo)識(shí)這份數(shù)據(jù),而不是由key來(lái)唯一標(biāo)識(shí)萝嘁。另外梆掸,當(dāng)多個(gè)元素分?jǐn)?shù)相同的時(shí)候,還需要根據(jù)數(shù)據(jù)內(nèi)容來(lái)進(jìn)字典排序牙言。
  • 第1層鏈表不是一個(gè)單向鏈表沥潭,而是一個(gè)雙向鏈表。這是為了方便以倒序方式獲取一個(gè)范圍內(nèi)的元素嬉挡。
  • 在skiplist中可以很方便地計(jì)算出每個(gè)元素的排名(rank)钝鸽。

如何使用Redis進(jìn)行限流

1、SortedSet
利用zset數(shù)據(jù)結(jié)構(gòu)的score值來(lái)作為時(shí)間窗口庞钢,value保證唯一性即可,比如UUID或者雪花算法snowflake拔恰。

用一個(gè)zset結(jié)構(gòu)記錄用戶的歷史行為,每一個(gè)行為都會(huì)作為zset中的一個(gè)key保存下來(lái)基括,同一個(gè)用戶的同一種行為用一個(gè)zset記錄颜懊。

為節(jié)省內(nèi)存,只保留時(shí)間窗口內(nèi)的行為記錄风皿,如果用戶是冷用戶河爹,窗口內(nèi)的行為是空記錄,則這個(gè)zset可以從內(nèi)存中移除桐款。

通過(guò)統(tǒng)計(jì)窗口內(nèi)的行為數(shù)量與閾值進(jìn)行比較就可以得出當(dāng)前行為是否允許咸这。

代碼

public class SimpleRateLimiter {
    private Jedis jedis;

    public SimpleRateLimiter(Jedis jedis) {
        this.jedis = jedis;
    }

    // 指定用戶 user_id 的某個(gè)行為 action_key 在特定的時(shí)間內(nèi) period 只允許發(fā)生一定的次數(shù) max_co
    public boolean isActionAllowed(String userId, String actionKey, int period, int maxCount) {
        String key = String.format("hist:%s:%s", userId, actionKey);
        long nowTs = System.currentTimeMillis();
        Pipeline pipe = jedis.pipelined();
        pipe.multi();
        // value 沒(méi)有實(shí)際的意義,保證唯一就可以
        pipe.zadd(key, nowTs, "" + nowTs);
        pipe.zremrangeByScore(key, 0, nowTs - period * 1000);
        Response<Long> count = pipe.zcard(key);
        pipe.expire(key, period + 1);
        pipe.exec();
        pipe.close();
        return count.get() <= maxCount;
    }

    public static void main(String[] args) {
        Jedis jedis = new Jedis();
        SimpleRateLimiter limiter = new SimpleRateLimiter(jedis);
        for (int i = 0; i < 20; i++) {
           // 調(diào)用這個(gè)接口 , 一分鐘內(nèi)只允許最多回復(fù) 5 個(gè)帖子
           System.out.println(limiter.isActionAllowed("laoqian", "reply", 60, 5));
        }
    }
}

缺點(diǎn):因?yàn)樗涗洉r(shí)間窗口內(nèi)所有的行為記錄魔眨, 如果這個(gè)量很大媳维,比如“限定 60s 內(nèi)操作不得超過(guò) 100 萬(wàn)次”之類,它是不適合做這樣的限流的,因?yàn)闀?huì)消耗大量的存儲(chǔ)空間。

Redission版本

public static void main(String[] args) {
        RedissonClient redisson = Redisson.create();

        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");

        String id = "123";
        RBucket<Boolean> bucket = redisson.getBucket("blackId:" + id);
        // 是否是黑名單得
        if(bucket.get() == true){
            // 或者返回最近的一個(gè)請(qǐng)求的緩存
            return;
        }

        long nanoTime = System.nanoTime();
        RScoredSortedSet<Object> zset = redisson.getScoredSortedSet(id);
        zset.expire(10, TimeUnit.SECONDS);
        zset.add(nanoTime, nanoTime);
        zset.removeRangeByScore(0, true,
                nanoTime - 10 * 1000 * 1000 * 1000, true);
        int size = zset.size();
        // 超過(guò)了10次武鲁,則進(jìn)入黑名單
        if(size > 10){
            // 加入黑名單,30秒之后不能再訪問(wèn)
            bucket.set(true, 30, TimeUnit.SECONDS);
            // 或者返回最近的一個(gè)請(qǐng)求的緩存
            return;
        }
        // 放行
    }

不使用Redis的單機(jī)Java版本

public class TestLimitFlow {

   private Lock lock = new ReentrantLock();

   private Map<String, Entity> map = new ConcurrentHashMap<>();

   public TestLimitFlow() {
       new Thread(() -> {
           while (true) {
               System.out.println(map.size());
               map.forEach((key, value) -> {
                   long now = System.nanoTime();
                   if (value.expireTime < now) {
                       map.remove(key);
                   }
               });
               try {
                   TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }

       }).start();
   }


   class Entity<T> {
       public long expireTime;
       T o;
   }

   public void interceptor(String ip) throws InterruptedException {
       lock.lock();
       Entity e = map.get(ip);
       long nanoTime = System.nanoTime();
       if (e == null) {
           TreeSet<Long> treeSet = new TreeSet<>();
           e = new Entity<TreeSet<Long>>();
           e.expireTime = nanoTime + 10L * 1000 * 1000 * 1000;
           e.o = treeSet;
           map.put(ip, e);
       }
       lock.unlock();
       synchronized (e) {
           e.expireTime = nanoTime + 10L * 1000 * 1000 * 1000;
           TreeSet<Long> zset = (TreeSet<Long>) e.o;
           zset.add(nanoTime);
           List<Long> deleteKey = Lists.newArrayList();
           for(Long item : zset){
               if(item < nanoTime - 10L * 1000 * 1000 * 1000){
                   System.out.println("true");
//                    zset.remove(item);
                   deleteKey.add(item);
               }else {
                   break;
               }
           }
           deleteKey.forEach(item -> {
               zset.remove(item);
           });
           System.out.println(zset.size());
           if (zset.size() > 10) {
               // 加入黑名單
               System.out.println("黑名單:" + ip + ":" + zset.size());
           }
       }

   }

   public static void main(String[] args) throws InterruptedException {
       TestLimitFlow testXL = new TestLimitFlow();
       for(int j = 0; j < 10;j++){
           int finalJ = j;
           new Thread(){
               @Override
               public void run() {
                   for (int i = 0; i < 12; i++) {
//            TimeUnit.SECONDS.sleep(1);
                       try {
                           testXL.interceptor("localhost" + finalJ);
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
               }
           }.start();
       }


   }
}

2欢搜、令牌桶算法


如何使用 Redis 實(shí)現(xiàn)分布式鎖?

分布式鎖要解決的問(wèn)題

  • 互斥性
  • 安全性
  • 死鎖
  • 容錯(cuò)

版本1

使用 SEATNX key value:如果key不存在,則創(chuàng)建并賦值

@Service
public class RedisSeckillServiceImpl {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    public String deductStock() {
        String lockKey = "lambo";
        try {
            String value = "value";
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value);
            if (!result) {
                return "error_code";
            }
            // 此處機(jī)器宕機(jī),將發(fā)生死鎖
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣減成功墓毒,剩余庫(kù)存:" + realStock);
            } else {
                System.out.println("扣減失敗,庫(kù)存不足");
            }
        } catch (Exception e) {

        } finally {
            stringRedisTemplate.delete(lockKey);
        }
        return "end";
    }

}

分析

在setnx后如果機(jī)器宕機(jī)盖灸,鎖得不到釋放蚁鳖,就會(huì)產(chǎn)生死鎖

版本2

給key設(shè)置過(guò)期時(shí)間

EXPIRE key seconds

@Service
public class RedisSeckillServiceImpl {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 設(shè)置過(guò)期時(shí)間,防止宕機(jī)帶來(lái)的死鎖
     *
     * @return
     */
    public String deductStock2() {
        String lockKey = "lambo";
        try {
            String value = "value";
            Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, value);
            // 設(shè)置過(guò)期時(shí)間赁炎,但是set和expire不是原子性操作,還沒(méi)有設(shè)置過(guò)期時(shí)間,機(jī)器宕機(jī)了
            stringRedisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
            if (!result) {
                return "error_code";
            }
        
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣減成功徙垫,剩余庫(kù)存:" + realStock);
            } else {
                System.out.println("扣減失敗讥裤,庫(kù)存不足");
            }
        } catch (Exception e) {

        } finally {
            stringRedisTemplate.delete(lockKey);
        }
        return "end";
    }
   
}

缺點(diǎn)

原子性等不到滿足,死鎖問(wèn)題依舊會(huì)出現(xiàn)

版本3

使用原子性操作
SETNX key value [EX seconds] [PX milliseconds] [NX|XX]
NX:只在鍵不存在時(shí)姻报,才對(duì)鍵進(jìn)行設(shè)置
XX:只在鍵存在時(shí)己英,才對(duì)鍵進(jìn)行設(shè)置

@Service
public class RedisSeckillServiceImpl {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
  
    /**
     * set+expire變更成原子操作
     *
     * @return
     */
    public String deductStock3() {
        String lockKey = "lambo";

        try {
            String value = "value";
            // 原子操作
            Boolean result = stringRedisTemplate.opsForValue().setIfPresent(lockKey, value, 10, TimeUnit.SECONDS);
            if (!result) {
                return "error_code";
            }
            
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣減成功,剩余庫(kù)存:" + realStock);
            } else {
                System.out.println("扣減失敗吴旋,庫(kù)存不足");
            }
        } catch (Exception e) {

        } finally {
            
            // 線程可能會(huì)釋放其他線程的鎖损肛,無(wú)法保證安全性
            stringRedisTemplate.delete(lockKey);
        }
        return "end";
    }
}

分析

比如線程1設(shè)置key10秒過(guò)期,執(zhí)行業(yè)務(wù)需要15s荣瑟,10秒后線程2進(jìn)入治拿,由于key過(guò)期自動(dòng)刪除后,線程2拿到鎖笆焰,執(zhí)行業(yè)務(wù)需要8秒劫谅,在第15秒的時(shí)候線程一將鎖釋放了,但是此時(shí)線程也還沒(méi)操作完嚷掠,這樣其他線程又可以搶到鎖了

版本4

給線程設(shè)置唯一值捏检,釋放鎖的時(shí)候只有加鎖的線程才可以釋放鎖

@Service
public class RedisSeckillServiceImpl {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    /**
     * 線程只能釋放自己加的鎖,不能釋放別的線程加的鎖
     *
     * @return
     */
    public String deductStock4() {
        String lockKey = "lambo";
        String clientId = UUID.randomUUID().toString();
        try {
            // 原子操作
            Boolean result =
            // 設(shè)置唯一值
            stringRedisTemplate.opsForValue().setIfPresent(lockKey, clientId, 10, TimeUnit.SECONDS);
            if (!result) {
                return "error_code";
            }
          
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣減成功不皆,剩余庫(kù)存:" + realStock);
            } else {
                System.out.println("扣減失敗贯城,庫(kù)存不足");
            }
        } catch (Exception e) {

        } finally {
            if (clientId.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                stringRedisTemplate.delete(lockKey);
            }
        }
        return "end";
    }

}

分析

此時(shí)線程是只能釋放自己加的鎖了,無(wú)法釋放其他線程的鎖霹娄,但是有可能業(yè)務(wù)本身執(zhí)行所需要的時(shí)間就是超過(guò)過(guò)期時(shí)間冤狡,而且這個(gè)時(shí)間是無(wú)法預(yù)估的,此時(shí)我們一種機(jī)制项棠,每隔一段時(shí)間去判斷線程是否還持有鎖悲雳,如果持有鎖則延長(zhǎng)鎖的過(guò)期時(shí)間。

版本5

使用redisson的看門(mén)狗機(jī)制

@Service
public class RedisSeckillServiceImpl {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 基于redisson設(shè)置分布式鎖
     *
     * @return
     */
    public String deductStock5() {
        String lockKey = "lambo";
        RLock redissonLock = redissonClient.getLock(lockKey);
        try {
            redissonLock.lock();
            int stock = Integer.parseInt(stringRedisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                stringRedisTemplate.opsForValue().set("stock", realStock + "");
                System.out.println("扣減成功香追,剩余庫(kù)存:" + realStock);
            } else {
                System.out.println("扣減失敗合瓢,庫(kù)存不足");
            }
        } catch (Exception e) {

        } finally {
            redissonLock.unlock();
        }
        return "end";

    }
}

如果Redis有1億個(gè)key,使用keys命令是否會(huì)影響線上服務(wù)

首先keys pattern命令可以從redis中查找所有符合給定模式pattern的key
如果Redis有1億個(gè)key透典,使用keys命令是會(huì)影響線上服務(wù)的晴楔,因?yàn)镽edis是單線程模型,當(dāng)然可以部署多個(gè)節(jié)點(diǎn)峭咒。
但是redis提供從海量數(shù)據(jù)里查詢某一固定前綴的key的命令:
SCAN cursor [MATCH pattern] [COUNT count]
1税弃、基于游標(biāo)的迭代器。需要基于上一次的游標(biāo)延續(xù)之前的迭代過(guò)程
2凑队、以0作為游標(biāo)開(kāi)始一次新的迭代则果,直到命令返回游標(biāo)0完成一次遍歷
3、不保證每次執(zhí)行都返回某個(gè)給定數(shù)量的元素,支持模糊查詢
4西壮、一次返回的數(shù)量不可控遗增,只能是大概率符合count數(shù)
例如 scan 0 match k* count 1;


如何使用 Redis 實(shí)現(xiàn)消息隊(duì)列?

一般使用 list 結(jié)構(gòu)作為隊(duì)列款青,rpush 生產(chǎn)消息做修,lpop 消費(fèi)消息。當(dāng) lpop 沒(méi)有消息的時(shí)候抡草,要適當(dāng) sleep 一會(huì)再重試饰及。

可不可以不用 sleep 呢?

list 還有個(gè)指令叫 blpop 康震,在沒(méi)有消息的時(shí)候燎含,它會(huì)阻塞住直到消息到來(lái)。

能不能生產(chǎn)一次消費(fèi)多次呢签杈?

使用 pub / sub 主題訂閱者模式瘫镇,發(fā)送者(pub)發(fā)送消息,訂閱者(sub)接收消息答姥∠吵可以實(shí)現(xiàn) 1:N 的消息隊(duì)列。

  • 訂閱命令
    subscribe channelName
  • 發(fā)布命令
    publish channelName value

pub / sub 有什么缺點(diǎn)鹦付?

在消費(fèi)者下線的情況下尚粘,生產(chǎn)的消息會(huì)丟失,得使用專業(yè)的消息隊(duì)列如 rabbitmq 等敲长。


如何實(shí)現(xiàn)延時(shí)隊(duì)列郎嫁?

使用 sortedset ,拿時(shí)間戳作為 score 祈噪,消息內(nèi)容作為 key 調(diào)用 zadd 來(lái)生產(chǎn)消息泽铛,消費(fèi)者用 zrangebyscore 指令獲取 N 秒之前的數(shù)據(jù)輪詢進(jìn)行處理。


談?wù)劸彺鎿舸┘穑彺嫜┍揽唬彺娲┩?/p>

緩存擊穿(單個(gè)key失效同時(shí)有大量請(qǐng)求)

定義

緩存中的key一般有設(shè)置過(guò)期時(shí)間,如果某個(gè)key過(guò)期了月褥,在這個(gè)時(shí)候弛随,有大量的并發(fā)請(qǐng)求訪問(wèn)這個(gè)key,則這寫(xiě)請(qǐng)求都會(huì)進(jìn)入到DB中宁赤,導(dǎo)致DB瞬間壓力過(guò)大舀透,壓垮DB

解決方案

1、設(shè)置互斥鎖决左,mutex愕够。當(dāng)緩存失效的時(shí)候不是立即去訪問(wèn)數(shù)據(jù)庫(kù)走贪,而是使用分布式鎖,比如redis链烈,redission,zookeeper,

缺點(diǎn)

可能造成死鎖厉斟,或者線程池阻塞


緩存穿透(key本身不存在于數(shù)據(jù)庫(kù))

定義

數(shù)據(jù)庫(kù)中不存在的key挚躯,自然而然緩存中而不存在强衡,如果有大量地訪問(wèn)不存在的key的請(qǐng)求,請(qǐng)求會(huì)直接到數(shù)據(jù)庫(kù)码荔,壓垮數(shù)據(jù)庫(kù)

解決方案

1漩勤、布隆過(guò)濾器
布隆過(guò)濾器是一個(gè)非常神奇的數(shù)據(jù)結(jié)構(gòu),通過(guò)它我們可以非常方便地判斷一個(gè)給定數(shù)據(jù)是否存在與海量數(shù)據(jù)中缩搅。我們需要的就是判斷 key 是否合法越败,有沒(méi)有感覺(jué)布隆過(guò)濾器就是我們想要找的那個(gè)“人”。具體是這樣做的:把所有可能存在的請(qǐng)求的值都存放在布隆過(guò)濾器中硼瓣,當(dāng)用戶請(qǐng)求過(guò)來(lái)究飞,我會(huì)先判斷用戶發(fā)來(lái)的請(qǐng)求的值是否存在于布隆過(guò)濾器中。不存在的話堂鲤,直接返回請(qǐng)求參數(shù)錯(cuò)誤信息給客戶端亿傅,存在的話才會(huì)走下面的流程。
具體可看這篇文章
https://github.com/Snailclimb/JavaGuide/blob/master/docs/dataStructures-algorithms/data-structure/bloom-filter.md

2瘟栖、不存在的key葵擎,在緩存中的value值設(shè)置為null,并且設(shè)置過(guò)期時(shí)間半哟,

java偽代碼如下

public Object getObjectInclNullById(Integer id) {
    // 從緩存中獲取數(shù)據(jù)
    Object cacheValue = cache.get(id);
    // 緩存為空
    if (cacheValue != null) {
        // 從數(shù)據(jù)庫(kù)中獲取
        Object storageValue = storage.get(key);
        // 緩存空對(duì)象
        cache.set(key, storageValue);
        // 如果存儲(chǔ)數(shù)據(jù)為空酬滤,需要設(shè)置一個(gè)過(guò)期時(shí)間(300秒)
        if (storageValue == null) {
            // 必須設(shè)置過(guò)期時(shí)間,否則有被攻擊的風(fēng)險(xiǎn)
            cache.expire(key, 60 * 5);
        }
        return storageValue;
    }
    return cacheValue;

緩存雪崩(大量key同時(shí)失效)

定義

緩存中大量的key在同一時(shí)間失效寓涨,這時(shí)候大量的請(qǐng)求就會(huì)直接到數(shù)據(jù)庫(kù)盯串,使數(shù)據(jù)庫(kù)的壓力過(guò)大

解決方案

1、在過(guò)期時(shí)間上添加隨機(jī)值戒良,把緩存失效的時(shí)間錯(cuò)開(kāi)体捏,這樣的話失效的時(shí)間的重復(fù)率就降低了,降低了集體失效的概率


Redis 有幾種數(shù)據(jù)“過(guò)期”策略蔬墩?
Redis key過(guò)期的方式有三種:

首先刪除策略有三種

定時(shí)刪除

在設(shè)置鍵的過(guò)期時(shí)間的同時(shí)译打,創(chuàng)建一個(gè)定時(shí)器(timer),讓定時(shí)器在鍵的過(guò)期時(shí)間來(lái)臨拇颅,立即執(zhí)行對(duì)鍵的刪除操作

優(yōu)點(diǎn):對(duì)內(nèi)存友好

缺點(diǎn):對(duì)CPU不友好

惰性刪除

放任鍵過(guò)期時(shí)間不管奏司,但是每次從鍵空間中獲取鍵時(shí),都檢查取得的鍵是否過(guò)期樟插,如果過(guò)期的話就刪除該鍵韵洋;如果沒(méi)有過(guò)期就返回該鍵

優(yōu)點(diǎn):對(duì)CPU友好

缺點(diǎn):對(duì)內(nèi)存不友好竿刁,造成內(nèi)存泄漏

定期刪除

每隔一段時(shí)間,程序就對(duì)數(shù)據(jù)庫(kù)進(jìn)行一次檢查沒(méi)刪除里面的過(guò)期鍵搪缨。至于要?jiǎng)h除多少過(guò)期鍵食拜,以及檢查多少個(gè)數(shù)據(jù)庫(kù),則由算法決定

優(yōu)點(diǎn):是對(duì)定時(shí)刪除和惰性刪除的一種整合和折中

缺點(diǎn):難以缺點(diǎn)刪除操作執(zhí)行的時(shí)長(zhǎng)和頻率副编。執(zhí)行太頻繁會(huì)退化成定時(shí)策略负甸,執(zhí)行太少又會(huì)退化成惰性刪除策略

定時(shí)刪除和定期刪除為主動(dòng)刪除策略,惰性刪除為被動(dòng)刪除策略

Redis刪除策略

被動(dòng)刪除:當(dāng)讀/寫(xiě)一個(gè)已經(jīng)過(guò)期的key時(shí)痹届,會(huì)觸發(fā)惰性刪除策略呻待,直接刪除掉這個(gè)過(guò)期key

主動(dòng)刪除(定期刪除策略):由于惰性刪除策略無(wú)法保證冷數(shù)據(jù)被及時(shí)刪掉,所以Redis會(huì)定期主動(dòng)淘汰一批已過(guò)期的key

當(dāng)前已用內(nèi)存超過(guò)maxmemory限定時(shí)队腐,觸發(fā)主動(dòng)清理策略(即下面的淘汰策略)


Redis 有哪幾種數(shù)據(jù)“淘汰”策略蚕捉?

==Redis 提供了 6 種數(shù)據(jù)淘汰策略:==

  • volatile-lru 只對(duì)設(shè)置了過(guò)期時(shí)間的key進(jìn)行LRU
  • volatile-ttl 刪除即將過(guò)期了的key
  • volatile-random
    只對(duì)設(shè)置了過(guò)期時(shí)間的key進(jìn)行隨機(jī)刪除
  • allkeys-lru
    對(duì)所有key進(jìn)行LRU算法刪除
  • allkeys-random
    對(duì)所有key進(jìn)行隨機(jī)刪除
  • no-enviction
    永不過(guò)期,直接拋錯(cuò)

Redis持久化機(jī)制有哪些柴淘?

Redis有兩種持久化機(jī)制迫淹,AOF和RDB。

  • AOF为严,記錄每次寫(xiě)請(qǐng)求的命令敛熬,以追加的方式在文件尾部追加,直接在尾部追加梗脾,效率比較高荸型。
    對(duì)于操作系統(tǒng)來(lái)說(shuō),不是每次寫(xiě)都直接寫(xiě)到磁盤(pán)炸茧,操作系統(tǒng)自己會(huì)有一層cache瑞妇,redis寫(xiě)磁盤(pán)的數(shù)據(jù)會(huì)先緩存在os cache里,redis每隔1秒調(diào)用一次操作系統(tǒng)的fsync操作梭冠,強(qiáng)制將os cache中的數(shù)據(jù)刷入AOF文件中辕狰。

當(dāng)redis重啟的時(shí)候,就把AOF中記錄的命令重新執(zhí)行一遍就可以了控漠,但是如果文件很大的話蔓倍,執(zhí)行會(huì)耗費(fèi)較多的時(shí)間,對(duì)于數(shù)據(jù)恢復(fù)來(lái)說(shuō)耗時(shí)會(huì)多一點(diǎn)盐捷。

  • RDB偶翅,是快照文件,每隔一定時(shí)間將redis內(nèi)存中的數(shù)據(jù)生成一份完整的RDB快照文件碉渡,當(dāng)redis重啟的時(shí)候直接加載數(shù)據(jù)即可聚谁,同樣的數(shù)據(jù)比AOF恢復(fù)的要快。

說(shuō)說(shuō)這兩種持久化機(jī)制各自的特點(diǎn)滞诺、優(yōu)缺點(diǎn)吧

==RDB優(yōu)點(diǎn)==

第一點(diǎn)就是他會(huì)生成多個(gè)數(shù)據(jù)文件形导,每個(gè)數(shù)據(jù)文件都代表了某一時(shí)刻redis中的全量數(shù)據(jù)环疼,非常適合做冷備。
第二點(diǎn)朵耕,RDB持久化機(jī)制對(duì)redis對(duì)外提供的讀寫(xiě)服務(wù)影響非常小炫隶,可以讓redis保持高性能,因?yàn)閞edis主進(jìn)程只需要fork一個(gè)子進(jìn)程阎曹,讓子進(jìn)程執(zhí)行磁盤(pán)IO操作來(lái)進(jìn)行RDB持久化即可伪阶。
第三點(diǎn),相對(duì)于AOF持久化機(jī)制來(lái)說(shuō)芬膝,直接基于RDB數(shù)據(jù)文件來(lái)重啟和恢復(fù)redis進(jìn)程望门,更加快速形娇。

RDB锰霜,就是一份數(shù)據(jù)文件,恢復(fù)的時(shí)候桐早,直接加載到內(nèi)存中即可癣缅。

==RBD的缺點(diǎn)==

1)故障時(shí)可能數(shù)據(jù)丟失的比AOF要多。
可能會(huì)因?yàn)镽edis掛起而且是從當(dāng)前值最近一次快照期間的數(shù)據(jù)

這個(gè)問(wèn)題哄酝,也是rdb最大的缺點(diǎn)友存,就是不適合做第一優(yōu)先的恢復(fù)方案,如果你依賴RDB做第一優(yōu)先恢復(fù)方案陶衅,會(huì)導(dǎo)致數(shù)據(jù)丟失的比較多

2)RDB每次在fork子進(jìn)程來(lái)執(zhí)行RDB快照數(shù)據(jù)文件生成的時(shí)候屡立,如果數(shù)據(jù)文件特別大,可能會(huì)導(dǎo)致對(duì)客戶端提供的服務(wù)暫停數(shù)毫秒搀军,或者甚至數(shù)秒膨俐。RDB是對(duì)內(nèi)存數(shù)據(jù)的全量同步,數(shù)據(jù)量大會(huì)由于I/O而影響性能

所以一般不要讓RDB的間隔太長(zhǎng)罩句,否則每次生成的RDB文件太大了焚刺,對(duì)redis本身的性能可能會(huì)有影響的。

==AOF的優(yōu)點(diǎn)==

AOF持久化门烂,記錄下除了查詢以外的所有變更數(shù)據(jù)量的指令乳愉,以append的形式追加保存到AOF文件中

1)AOF可以更好的保護(hù)數(shù)據(jù)不丟失
一般AOF會(huì)每隔1秒,通過(guò)一個(gè)后臺(tái)線程執(zhí)行一次fsync操作屯远,最多丟失1秒鐘的數(shù)據(jù)蔓姚。

每隔1秒,就執(zhí)行一次fsync操作慨丐,保證os cache中的數(shù)據(jù)寫(xiě)入磁盤(pán)中坡脐。
redis進(jìn)程掛了,最多丟掉1秒鐘的數(shù)據(jù).

2)AOF持久化性能高
AOF日志文件以append-only模式寫(xiě)入咖气,所以沒(méi)有任何磁盤(pán)尋址的開(kāi)銷挨措,寫(xiě)入性能非常高挖滤,而且文件不容易破損,即使文件尾部破損浅役,也很容易修復(fù)斩松。

3)AOF日志文件即使過(guò)大的時(shí)候,出現(xiàn)后臺(tái)重寫(xiě)操作觉既,也不會(huì)影響客戶端的讀寫(xiě)惧盹。
因?yàn)樵趓ewrite log的時(shí)候,會(huì)對(duì)其中的指令進(jìn)行壓縮瞪讼,創(chuàng)建出一份需要恢復(fù)數(shù)據(jù)的最小日志出來(lái)钧椰。再創(chuàng)建新日志文件的時(shí)候,老的日志文件還是照常寫(xiě)入符欠。當(dāng)新的merge后的日志文件ready的時(shí)候嫡霞,再交換新老日志文件即可。

4)AOF日志文件的命令通過(guò)非诚J粒可讀的方式進(jìn)行記錄诊沪,這個(gè)特性非常適合做災(zāi)難性的誤刪除的緊急恢復(fù)。

比如某人不小心用flushall命令清空了所有數(shù)據(jù)曾撤,只要這個(gè)時(shí)候后臺(tái)rewrite還沒(méi)有發(fā)生端姚,那么就可以立即拷貝AOF文件,將最后一條flushall命令給刪了挤悉,然后再將該AOF文件放回去渐裸,就可以通過(guò)恢復(fù)機(jī)制,自動(dòng)恢復(fù)所有數(shù)據(jù)装悲。

==AOF的缺點(diǎn)==

(1)對(duì)于同一份數(shù)據(jù)來(lái)說(shuō)昏鹃,AOF日志文件通常比RDB數(shù)據(jù)快照文件更大

(2)AOF開(kāi)啟后,支持的寫(xiě)QPS會(huì)比RDB支持的寫(xiě)QPS低衅斩,因?yàn)锳OF一般會(huì)配置成每秒fsync一次日志文件盆顾,當(dāng)然,每秒一次fsync畏梆,性能也還是很高的您宪。

如果你要保證一條數(shù)據(jù)都不丟,也是可以的奠涌,AOF的fsync設(shè)置成沒(méi)寫(xiě)入一條數(shù)據(jù)宪巨,fsync一次,但是那樣導(dǎo)致redis的QPS大幅度下降溜畅。

(3)以前AOF發(fā)生過(guò)bug捏卓,就是通過(guò)AOF記錄的日志,進(jìn)行數(shù)據(jù)恢復(fù)的時(shí)候慈格,沒(méi)有恢復(fù)一模一樣的數(shù)據(jù)出來(lái)怠晴。

所以說(shuō)遥金,類似AOF這種較為復(fù)雜的基于命令日志/merge/回放的方式,比基于RDB每次持久化一份完整的數(shù)據(jù)快照文件的方式蒜田,更加脆弱一些稿械,容易有bug。

不過(guò)AOF就是為了避免rewrite過(guò)程導(dǎo)致的bug冲粤,因此每次rewrite并不是基于舊的指令日志進(jìn)行merge的美莫,而是基于當(dāng)時(shí)內(nèi)存中的數(shù)據(jù)進(jìn)行指令的重新構(gòu)建,這樣健壯性會(huì)好很多梯捕。

(4)唯一的比較大的缺點(diǎn)厢呵,其實(shí)就是做數(shù)據(jù)恢復(fù)的時(shí)候,會(huì)比較慢傀顾,做冷備不太合適襟铭。


AOF持久化過(guò)程

  • 調(diào)用fork()創(chuàng)建一個(gè)子進(jìn)程

  • 子進(jìn)程將新的AOF寫(xiě)到一個(gè)臨時(shí)文件里,不依賴原來(lái)的AOF文件

  • 主進(jìn)程持續(xù)將新的變動(dòng)寫(xiě)到內(nèi)存和原來(lái)的AOF文件里

  • 主進(jìn)程獲取子進(jìn)程重新AOF的完成信息锣笨,往新AOF增量變動(dòng)

  • 使用新的AOF文件替換掉舊的AOF文件


==主從復(fù)制==


首先說(shuō)一下Redis Sentinel是怎么工作的蝌矛?重點(diǎn)描述一下故障轉(zhuǎn)移的過(guò)程

  • Sentinel會(huì)以每秒一次的頻率向所有與它創(chuàng)建了命令連接的實(shí)例(包括主服務(wù)器,從服務(wù)器错英,其他Sentinel在內(nèi))發(fā)送PING命令

  • 如果一個(gè)實(shí)例在down-after-milliseconds毫秒內(nèi)沒(méi)有向Sentinel返回有效回復(fù),則該Sentinel任務(wù)該實(shí)例處于下線狀態(tài)隆豹,稱為主觀下線

  • 當(dāng)Sentinel從其他Sentinel那里收到足夠數(shù)量的已下線的判斷后椭岩,Sentinel就會(huì)將從服務(wù)器從主觀下線判定為客觀下線,并對(duì)主服務(wù)器進(jìn)行故障轉(zhuǎn)移操作

  • 當(dāng)一個(gè)主服務(wù)器被判斷為客觀下線后璃赡,監(jiān)視這個(gè)下線主服務(wù)器的各個(gè)Sentinel會(huì)進(jìn)行協(xié)商判哥,選舉出一個(gè)領(lǐng)頭的Sentinel,并由它對(duì)下線主服務(wù)器進(jìn)行故障轉(zhuǎn)移操作碉考。


故障轉(zhuǎn)移操作主要步驟:

  • 在已下線主服務(wù)器下的所有從服務(wù)器器里塌计,挑選出一個(gè)從服務(wù)器,并將其轉(zhuǎn)換成新的主服務(wù)器

  • 讓已下線主服務(wù)器下的其他從服務(wù)器改為復(fù)制新的主服務(wù)器

  • 將已下線主服務(wù)器設(shè)置為新的主服務(wù)器的從服務(wù)器侯谁,當(dāng)這個(gè)舊的主服務(wù)器重新上線時(shí)锌仅,它就會(huì)成為新的主服務(wù)器的從服務(wù)器


故障轉(zhuǎn)移時(shí)會(huì)從剩下的slave選舉一個(gè)新的master,被選舉為master的標(biāo)準(zhǔn)是什么墙贱?

  • Sentinel會(huì)將主服務(wù)器和從服務(wù)器的信息保存在一個(gè)列表中

  • 主服務(wù)器下線時(shí)热芹,會(huì)刪除列表中所有處于下線或者斷線狀態(tài)的從服務(wù)器

  • 刪除列表中所有最近5秒內(nèi)沒(méi)有回復(fù)過(guò)領(lǐng)頭Sentinel的INFO命令的從服務(wù)器,

  • 刪除所有與已下線的主服務(wù)器連接斷開(kāi)炒貨down-after-milliseconds毫秒的從服務(wù)器惨撇,

  • 然后根據(jù)優(yōu)先級(jí)(從高到低)伊脓,復(fù)制偏移量(從高到低),運(yùn)行id(從小到大)進(jìn)行排序選出領(lǐng)頭的Sentinel


執(zhí)行切換的那個(gè)哨兵在完成故障轉(zhuǎn)移后會(huì)做什么魁衙?

會(huì)進(jìn)行configuraiton配置信息傳播报腔。

哨兵完成切換之后株搔,會(huì)在自己本地更新生成最新的master配置,然后通過(guò)pub/sub消息機(jī)制同步給其他的哨兵纯蛾。


==Redis集群==

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末邪狞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子茅撞,更是在濱河造成了極大的恐慌帆卓,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件米丘,死亡現(xiàn)場(chǎng)離奇詭異剑令,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)拄查,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén)吁津,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人堕扶,你說(shuō)我怎么就攤上這事碍脏。” “怎么了稍算?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵典尾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我糊探,道長(zhǎng)钾埂,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任科平,我火速辦了婚禮褥紫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瞪慧。我一直安慰自己髓考,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布弃酌。 她就那樣靜靜地躺著氨菇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪矢腻。 梳的紋絲不亂的頭發(fā)上门驾,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音多柑,去河邊找鬼奶是。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的聂沙。 我是一名探鬼主播秆麸,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼及汉!你這毒婦竟也來(lái)了沮趣?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坷随,失蹤者是張志新(化名)和其女友劉穎房铭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體温眉,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缸匪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了类溢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凌蔬。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闯冷,靈堂內(nèi)的尸體忽然破棺而出砂心,到底是詐尸還是另有隱情,我是刑警寧澤蛇耀,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布辩诞,位于F島的核電站,受9級(jí)特大地震影響蒂窒,放射性物質(zhì)發(fā)生泄漏躁倒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一洒琢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧褐桌,春花似錦衰抑、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至啦撮,卻和暖如春谭网,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赃春。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工愉择, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓锥涕,卻偏偏與公主長(zhǎng)得像衷戈,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子层坠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

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

  • NOSQL類型簡(jiǎn)介鍵值對(duì):會(huì)使用到一個(gè)哈希表殖妇,表中有一個(gè)特定的鍵和一個(gè)指針指向特定的數(shù)據(jù),如redis破花,volde...
    MicoCube閱讀 4,001評(píng)論 2 27
  • Redis是啥 Redis是一個(gè)開(kāi)源的key-value存儲(chǔ)系統(tǒng)谦趣,由于擁有豐富的數(shù)據(jù)結(jié)構(gòu),又被其作者戲稱為數(shù)據(jù)結(jié)構(gòu)...
    一凡呀閱讀 1,176評(píng)論 0 5
  • 一座每、Redis高可用概述 在介紹Redis高可用之前前鹅,先說(shuō)明一下在Redis的語(yǔ)境中高可用的含義。 我們知道尺栖,在w...
    空語(yǔ)閱讀 1,598評(píng)論 0 2
  • redis簡(jiǎn)介 redis是典型的NOSQL數(shù)據(jù)庫(kù)嫡纠,也是一個(gè)高性能的key-value型數(shù)據(jù)庫(kù)。通過(guò)源碼對(duì)redi...
    江北曉白閱讀 488評(píng)論 0 2
  • 一. 數(shù)據(jù)結(jié)構(gòu) 我們知道redis有5種基本類型:string延赌、list除盏、hash、set挫以、zset者蠕,我們來(lái)看一下...
    漂泊的胡蘿卜閱讀 634評(píng)論 1 0