Redis介紹以及使用

 

 

  1. Redis介紹
    1. 什麼是Redis?
  1. Redis是用C語言開發的一個開源的高性能鍵值對(key-value)內存數據庫
  2. 它提供五種數據類型來存儲值:字符串類型、散列類型、列表類型、集合類型、有序集合類型
  3. 它是一種NoSQL數據庫。
    1. 什麼是NoSQL?
  1. NoSQL,即Not-Only SQL(不僅僅是SQL),泛指非關係型的數據庫
  2. 什麼是關係型數據庫?數據結構是一種有行有列的數據庫
  3. NoSQL數據庫是爲了解決高併發、高可用、高可擴展、大數據存儲問題而產生的數據庫解決方案。
  4. NoSQL可以作爲關係型數據庫的良好補充,但是不能替代關係型數據庫
    1. NoSQL數據庫分類
  1. 鍵值(Key-Value)存儲數據庫

相關產品: Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB

典型應用: 內容緩存,主要用於處理大量數據的高訪問負載。

數據模型: 一系列鍵值對

優勢: 快速查詢

劣勢: 存儲的數據缺少結構化

 

  1. 列存儲數據庫

相關產品:Cassandra, HBase, Riak

典型應用:分佈式的文件系統

數據模型:以列簇式存儲,將同一列數據存在一起

優勢:查找速度快,可擴展性強,更容易進行分佈式擴展

劣勢:功能相對侷限

 

  1. 文檔型數據庫

相關產品:CouchDB、MongoDB

典型應用:Web應用(與Key-Value類似,Value是結構化的)

數據模型: 一系列鍵值對

優勢:數據結構要求不嚴格

劣勢:

 

  1. 圖形(Graph)數據庫

相關數據庫:Neo4J、InfoGrid、Infinite Graph

典型應用:社交網絡

數據模型:圖結構

優勢:利用圖結構相關算法。

劣勢:需要對整個圖做計算才能得出結果,不容易做分佈式的集羣方案。

 

    1. Redis歷史發展
  1. 2008,意大利的一家創業公司Merzia推出了一款基於MySQL的網站實時統計系統LLOOGG,然而沒過多久該公司的創始人 Salvatore Sanfilippo便 對MySQL的性能感到失望,於是他決定親自爲LLOOGG量身定做一個數據庫,並於2009年開發完成,這個數據庫就是Redis
  2. 不過Salvatore Sanfilippo並不滿足只將Redis用於LLOOGG這一款產品,而是希望更多的人使用它,於是在同一年Salvatore Sanfilippo將Redis開源發佈
  3. 並開始和Redis的另一名主要的代碼貢獻者Pieter Noordhuis一起繼續着Redis的開發,直到今天
  4. Salvatore Sanfilippo自己也沒有想到,短短的幾年時間,Redis就擁有了龐大的用戶羣體。Hacker News在2012年發佈了一份數據庫的使用情況調查,結果顯示有近12%的公司在使用Redis。國內如新浪微博、街旁網、知乎網,國外如GitHub、Stack Overflow、Flickr等都是Redis的用戶。
  5. VMware公司從2010開始贊助Redis的開發, Salvatore Sanfilippo和Pieter Noordhuis也分別在3月和5月加入VMware,全職開發Redis

 

    1. Redis的應用場景
  1. 內存數據庫(登錄信息、購物車信息、用戶瀏覽記錄等)
  2. 緩存服務器(商品數據、廣告數據等等)。(最多使用
  3. 解決分佈式集羣架構中的session分離問題(session共享)。
  4. 任務隊列。(秒殺、搶購、12306等等)
  5. 支持發佈訂閱的消息模式
  6. 應用排行榜。
  7. 網站訪問統計。
  8. 數據過期處理(可以精確到毫秒)

 

  1. Redis單機版安裝配置
    1. Redis下載

* 官網地址:http://redis.io/

* 中文官網地址:http://www.redis.cn/

* 下載地址:http://download.redis.io/releases/

 

    1. Redis安裝環境

Redis沒有官方的Windows版本,所以建議在Linux系統上安裝運行,本教程使用CentOS6.5(Linux操作系統的一個系列)作爲安裝環境。

    1. Redis安裝
  1. 第一步:在VMware中安裝CentOS(參考Linux教程中的安裝虛擬機)
  2. 第二步:安裝C語言需要的GCC環境

yum install gcc-c++

  1. 第三步:解壓縮Redis源碼壓縮包

tar -zxf redis-3.2.9.tar.gz

  1. 第四步:編譯Redis源碼,進入redis-3.2.9目錄,執行編譯命令

make

  1. 第五步:安裝Redis,需要通過PREFIX指定安裝路徑

make install PREFIX=/usr/local/redis

 

    1. Redis啓動
      1. 前端啓動
  1. 啓動命令:redis-server,直接運行bin/redis-server將以前端模式啓動。

./redis-server

  1. 關閉命令:ctrl+c

 

啓動缺點:客戶端窗口關閉則redis-server程序結束,不推薦使用此方法

啓動圖例:

 

      1. 後端啓動(守護進程啓動)
  1. 第一步:拷貝redis-3.2.9/redis.conf配置文件到Redis安裝目錄的bin目錄

cp redis.conf /usr/local/redis/bin/

 

  1. 第二步:修改redis.conf,將daemonize由no改爲yes

vim redis.conf

修改redis.conf中的61行代碼

bind 127.0.0.1

改爲

bind <redis實例所在機器的真實IP>,比如192.168.10.133

 

  1. 第三步:執行命令

./redis-server redis.conf

 

      1. 後端啓動的關閉方式
  1. 非正常關閉(不推薦使用):

kill 5528

 

  1. 正常關閉:

./redis-cli shutdown

 

    1. 其他命令說明
  1. redis-server :啓動redis服務
  2. redis-cli :進入redis命令客戶端
  3. redis-benchmark: 性能測試的工具
  4. redis-check-aof : aof文件進行檢查的工具
  5. redis-check-dump :  rdb文件進行檢查的工具
  6. redis-sentinel :  啓動哨兵監控服務
  1. Redis客戶端
    1. 自帶命令行客戶端

  1. 命令格式:

./redis-cli -h 127.0.0.1 -p 6379

  1. 修改redis配置文件(解決IP綁定問題

 

# bind 127.0.0.1   綁定的IP才能訪問redis服務器,註釋掉該配置

protected-mode yes  是否開啓保護模式,由yes該爲no

 

  1. 參數說明:

       -h:redis服務器的ip地址

       -p:redis實例的端口號

  1. 默認方式

如果不指定主機和端口也可以

./redis-cli

* 默認主機地址是127.0.0.1

* 默認端口是6379

 

    1. 圖形界面客戶端(瞭解)

前提:需要安裝圖形界面管理器

 

      1. 連接超時解決

遠程連接redis服務,需要關閉或者修改防火牆配置。

 

修改防火牆設置:

  1. 第一步:編輯iptables

vim /etc/sysconfig/iptables

在命令模式下,選定要複製的那一行的末尾,然後點擊鍵盤yyp,就完成複製,然後修改

  1. 第二步:重啓防火牆

service iptables restart

iptables:清除防火牆規則:                                 [確定]

iptables:將鏈設置爲政策 ACCEPT:filter                    [確定]

iptables:正在卸載模塊:                                   [確定]

iptables:應用防火牆規則:                                 [確定]

      1. 多數據庫支持
  1. 默認一共是16個數據庫,每個數據庫之間是相互隔離(但是可以使用flushall一次清空所有的庫)。數據庫的數量是在redis.conf中配置的。

  1. 切換數據庫使用命令:select 數據庫編號(0-15)

例如:select 1

 

    1. 程序客戶端之Java客戶端Jedis
      1. Jedis介紹
  1. Redis不僅使用命令客戶端來操作,而且可以使用程序客戶端操作。
  2. 現在基本上主流的語言都有客戶端支持,比如Java、C、C#、C++、php、Node.js、Go等。
  3. 在官方網站裏列一些Java的客戶端,有Jedis、Redisson、Jredis、JDBC-Redis、等其中官方推薦使用Jedis和Redisson。
  4. 企業中用的最多的就是Jedis,下面我們就重點學習下Jedis。
  5. Jedis同樣也是託管在github上,地址:https://github.com/xetorthio/jedis

 

      1. 添加jar包

<dependency>

                         <groupId>redis.clients</groupId>

                         <artifactId>jedis</artifactId>

                         <version>2.9.0</version>

                 </dependency>

 

      1. 單實例連接

注意事項:需要去設置redis服務器的防火牆策略(臨時關閉、永久關閉、端口暴露)

        @Test

        public void testJedis() {

                 //創建一個Jedis的連接

                 Jedis jedis = new Jedis("127.0.0.1", 6379);

                 //執行redis命令

                 jedis.set("key1", "hello world");

                 //redis中取值

                 String result = jedis.get("key1");

                 //打印結果

                 System.out.println(result);

                 //關閉連接

                 jedis.close();

                

        }

 

      1. 連接池連接

        @Test

        public void testJedisPool() {

                 //創建一連接池對象

                 JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);

                 //從連接池中獲得連接

                 Jedis jedis = jedisPool.getResource();

                 String result = jedis.get("key1") ;

                 System.out.println(result);

                 //關閉連接

                 jedis.close();

                

                 //關閉連接池

                 jedisPool.close();

        }

 

      1. Spring整合JedisPool(自學)

添加spring的jar包

  1. 配置spring配置文件applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>

<beans xmlns="http://www.springframework.org/schema/beans"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"

        xmlns:context="http://www.springframework.org/schema/context"

        xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"

        xsi:schemaLocation="http://www.springframework.org/schema/beans

                 http://www.springframework.org/schema/beans/spring-beans.xsd

                 http://www.springframework.org/schema/mvc

                 http://www.springframework.org/schema/mvc/spring-mvc.xsd

                 http://www.springframework.org/schema/context

                 http://www.springframework.org/schema/context/spring-context.xsd

                 http://www.springframework.org/schema/aop

                 http://www.springframework.org/schema/aop/spring-aop.xsd

                 http://www.springframework.org/schema/tx

                 http://www.springframework.org/schema/tx/spring-tx.xsd ">

 

        <!-- 連接池配置 -->

        <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">

                 <!-- 最大連接數 -->

                 <property name="maxTotal" value="30" />

                 <!-- 最大空閒連接數 -->

                 <property name="maxIdle" value="10" />

                 <!-- 每次釋放連接的最大數目 -->

                 <property name="numTestsPerEvictionRun" value="1024" />

                 <!-- 釋放連接的掃描間隔(毫秒) -->

                 <property name="timeBetweenEvictionRunsMillis" value="30000" />

                 <!-- 連接最小空閒時間 -->

                 <property name="minEvictableIdleTimeMillis" value="1800000" />

                 <!-- 連接空閒多久後釋放, 當空閒時間>該值 且 空閒連接>最大空閒連接數 時直接釋放 -->

                 <property name="softMinEvictableIdleTimeMillis" value="10000" />

                 <!-- 獲取連接時的最大等待毫秒數,小於零:阻塞不確定的時間,默認-1 -->

                 <property name="maxWaitMillis" value="1500" />

                 <!-- 在獲取連接的時候檢查有效性, 默認false -->

                 <property name="testOnBorrow" value="false" />

                 <!-- 在空閒時檢查有效性, 默認false -->

                <property name="testWhileIdle" value="true" />

                 <!-- 連接耗盡時是否阻塞, false報異常,ture阻塞直到超時, 默認true -->

                 <property name="blockWhenExhausted" value="false" />

        </bean>

 

        <!-- redis單機 通過連接池 -->

        <bean id="jedisPool" class="redis.clients.jedis.JedisPool"

                 destroy-method="close">

                 <constructor-arg name="poolConfig" ref="jedisPoolConfig" />

                 <constructor-arg name="host" value="192.168.242.130" />

                 <constructor-arg name="port" value="6379" />

        </bean>

</beans>

 

  1. 測試代碼

@Test

        public void testJedisPool() {

                 JedisPool pool = (JedisPool) applicationContext.getBean("jedisPool");

                 Jedis jedis = null;

                 try {

                         jedis = pool.getResource();

 

                         jedis.set("name", "lisi");

                         String name = jedis.get("name");

                         System.out.println(name);

                 } catch (Exception ex) {

                         ex.printStackTrace();

                 } finally {

                         if (jedis != null) {

                                  // 關閉連接

                                  jedis.close();

                         }

                 }

        }

 

 

  1. Redis數據類型

官方命令大全網址:http://www.redis.cn/commands.html

 

Redis中存儲數據是通過key-value格式存儲數據的,其中value可以定義五種數據類型:

  • String(字符類型)
  • Hash(散列類型)
  • List(列表類型)
  • Set(集合類型)
  • SortedSet(有序集合類型,簡稱zset)

 

注意:在redis中的命令語句中,命令是忽略大小寫的,而key是不忽略大小寫的。

    1. String類型
      1. 命令
        1. 賦值

語法:SET key value

127.0.0.1:6379> set test 123

OK

 

        1. 取值

語法:GET key

127.0.0.1:6379> get test

"123“

        1. 取值並賦值

語法:GETSET key value

127.0.0.1:6379> getset s2 222

"111"

127.0.0.1:6379> get s2

"222"

 

        1. 數值增減

注意實現:

  1. 當value爲整數數據時,才能使用以下命令操作數值的增減。
  2. 數值遞增都是原子操作。

       非原子性操作示例:

       int i = 1;

       i++;

       System.out.println(i)

 

 

  1. 遞增數字

語法:INCR key

127.0.0.1:6379> incr num

(integer) 1

127.0.0.1:6379> incr num

(integer) 2

127.0.0.1:6379> incr num

(integer) 3

 

  1. 增加指定的整數

語法:INCRBY key increment

127.0.0.1:6379> incrby num 2

(integer) 5

127.0.0.1:6379> incrby num 2

(integer) 7

127.0.0.1:6379> incrby num 2

(integer) 9

 

  1. 遞減數值

語法:DECR key

127.0.0.1:6379> decr num

(integer) 9

127.0.0.1:6379> decr num

(integer) 8

 

  1. 減少指定的整數

語法:DECRBY key decrement

127.0.0.1:6379> decr num

(integer) 6

127.0.0.1:6379> decr num

(integer) 5

127.0.0.1:6379> decrby num 3

(integer) 2

127.0.0.1:6379> decrby num 3

(integer) -1

 

        1. 僅當不存在時賦值

使用該命令可以實現分佈式鎖的功能,後續講解!!!

語法:setnx key value

 

redis> EXISTS job                # job 不存在

(integer) 0

redis> SETNX job "programmer"    # job 設置成功

(integer) 1

redis> SETNX job "code-farmer"   # 嘗試覆蓋 job ,失敗

(integer) 0

redis> GET job                   # 沒有被覆蓋

"programmer"

        1. 其它命令
          1. 向尾部追加值

APPEND命令,向鍵值的末尾追加value。如果鍵不存在則將該鍵的值設置爲value,即相當於 SET key value。返回值是追加後字符串的總長度。

 

語法:APPEND key value

127.0.0.1:6379> set str hello

OK

127.0.0.1:6379> append str " world!"

(integer) 12

127.0.0.1:6379> get str

"hello world!"

 

 

          1. 獲取字符串長度

STRLEN命令,返回鍵值的長度,如果鍵不存在則返回0。

 

語法:STRLEN key

127.0.0.1:6379> strlen str

(integer) 0

127.0.0.1:6379> set str hello

OK

127.0.0.1:6379> strlen str

(integer) 5

 

 

          1. 同時設置/獲取多個鍵值

語法:

MSET key value [key value …]

MGET key [key …]

 

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3

OK

127.0.0.1:6379> get k1

"v1"

127.0.0.1:6379> mget k1 k3

1) "v1"

2) "v3"

 

 

      1. 應用場景之自增主鍵
  1. 需求:商品編號、訂單號採用INCR命令生成。
  2. 設計:key命名要有一定的設計
  3. 實現:定義商品編號key:items:id

192.168.101.3:7003> INCR items:id

(integer) 2

192.168.101.3:7003> INCR items:id

(integer) 3

    1. Hash類型
      1. hash介紹

hash叫散列類型,它提供了字段和字段值的映射。字段值只能是字符串類型,不支持散列類型、集合類型等其它類型。如下:

 

 

 

      1. 命令
        1. 賦值

HSET命令不區分插入和更新操作,當執行插入操作時HSET命令返回1,當執行更新操作時返回0。

 

  • 一次只能設置一個字段值

語法:HSET key field value 

127.0.0.1:6379> hset user username zhangsan

(integer) 1

 

  • 一次可以設置多個字段值

語法:HMSET key field value [field value ...]       

127.0.0.1:6379> hmset user age 20 username lisi

OK

 

  • 當字段不存在時賦值,類似HSET,區別在於如果字段存在,該命令不執行任何操作

語法:HSETNX key field value

127.0.0.1:6379> hsetnx user age 30     如果user中沒有age字段則設置age值爲30,否則不做任何操作

(integer) 0

 

        1. 取值
  • 一次只能獲取一個字段值

語法:HGET key field               

127.0.0.1:6379> hget user username

"zhangsan“

 

  • 一次可以獲取多個字段值

語法:HMGET key field [field ...]                       

127.0.0.1:6379> hmget user age username

1) "20"

2) "lisi"

 

  • 獲取所有字段值

語法:HGETALL key

127.0.0.1:6379> hgetall user

1) "age"

2) "20"

3) "username"

4) "lisi"

 

 

 

 

        1. 刪除字段

可以刪除一個或多個字段,返回值是被刪除的字段個數

 

語法:HDEL key field [field ...]

127.0.0.1:6379> hdel user age

(integer) 1

127.0.0.1:6379> hdel user age name

(integer) 0

127.0.0.1:6379> hdel user age username

(integer) 1

 

 

        1. 增加數字

語法:HINCRBY key field increment

127.0.0.1:6379> hincrby user age 2      將用戶的年齡加2

(integer) 22

127.0.0.1:6379> hget user age              獲取用戶的年齡

"22“

 

 

        1. 其它命令(自學)
          1. 判斷字段是否存在

語法:HEXISTS key field

127.0.0.1:6379> hexists user age          查看user中是否有age字段

(integer) 1

127.0.0.1:6379> hexists user name       查看user中是否有name字段

(integer) 0

 

 

 

          1. 只獲取字段名或字段值

語法:

HKEYS key

HVALS key

127.0.0.1:6379> hmset user age 20 name lisi

OK

127.0.0.1:6379> hkeys user

1) "age"

2) "name"

127.0.0.1:6379> hvals user

1) "20"

2) "lisi"

 

 

          1. 獲取字段數量

語法:HLEN key

127.0.0.1:6379> hlen user

(integer) 2

 

 

          1. 獲取所有字段

作用:獲得hash的所有信息,包括key和value

語法:hgetall key

      1. 應用之存儲商品信息

注意事項:存儲那些對象數據,特別是對象屬性經常發生增刪改操作的數據。

  • 商品信息字段

【商品id、商品名稱、商品描述、商品庫存、商品好評】

 

  • 定義商品信息的key

商品ID爲1001的信息在 Redis中的key爲:[items:1001]

 

  • 存儲商品信息

192.168.101.3:7003> HMSET items:1001 id 3 name apple price 999.9

OK

 

  • 獲取商品信息

192.168.101.3:7003> HGET items:1001 id

"3"

192.168.101.3:7003> HGETALL items:1001

1) "id"

2) "3"

3) "name"

4) "apple"

5) "price"

6) "999.9"

 

    1. List類型
      1. ArrayList與LinkedList的區別

       ArrayList使用數組方式存儲數據,所以根據索引查詢數據速度快,而新增或者刪除元素時需要設計到位移操作,所以比較慢。

       LinkedList使用雙向鏈表方式存儲數據,每個元素都記錄前後元素的指針,所以插入、刪除數據時只是更改前後元素的指針指向即可,速度非常快。然後通過下標查詢元素時需要從頭開始索引,所以比較慢,但是如果查詢前幾個元素或後幾個元素速度比較快。

 

 

      1. list介紹

Redis的列表類型(list)可以存儲一個有序的字符串列表,常用的操作是向列表兩端添加元素,或者獲得列表的某一個片段

       列表類型內部是使用雙向鏈表(double linked list)實現的,所以向列表兩端添加元素的時間複雜度爲0(1),獲取越接近兩端的元素速度就越快。這意味着即使是一個有幾千萬個元素的列表,獲取頭部或尾部的10條記錄也是極快的。

 

      1. 命令
        1. 向列表兩端增加元素
  • 向列表左邊增加元素

語法:LPUSH key value [value ...]

127.0.0.1:6379> lpush list:1 1 2 3

(integer) 3

 

  • 向列表右邊增加元素

語法:RPUSH key value [value ...]

127.0.0.1:6379> rpush list:1 4 5 6

(integer) 3

 

        1. 查看列表

語法:LRANGE key start stop

LRANGE命令是列表類型最常用的命令之一,獲取列表中的某一片段,將返回start、stop之間的所有元素(包含兩端的元素),索引從0開始。索引可以是負數,如:“-1”代表最後邊的一個元素。

 

127.0.0.1:6379> lrange list:1 0 2

1) "2"

2) "1"

3) "4"

 

        1. 從列表兩端彈出元素

LPOP命令從列表左邊彈出一個元素,會分兩步完成:

  1. 第一步是將列表左邊的元素從列表中移除
  2. 第二步是返回被移除的元素值。

 

語法:

LPOP key

RPOP key

127.0.0.1:6379> lpop list:1

"3“

127.0.0.1:6379> rpop list:1

"6“

 

        1. 獲取列表中元素的個數

語法:LLEN key

127.0.0.1:6379> llen list:1

(integer) 2

 

        1. 其它命令(自學)
          1. 刪除列表中指定個數的值

LREM命令會刪除列表中前count個值爲value的元素,返回實際刪除的元素個數。根據count值的不同,該命令的執行方式會有所不同:

  1. 當count>0時, LREM會從列表左邊開始刪除。
  2. 當count<0時, LREM會從列表後邊開始刪除。
  3. 當count=0時, LREM刪除所有值爲value的元素。

 

語法:LREM key count value

 

          1. 獲得/設置指定索引的元素值
  • 獲得指定索引的元素值

語法:LINDEX key index

127.0.0.1:6379> lindex l:list 2

"1"

 

  • 設置指定索引的元素值

語法:LSET key index value

127.0.0.1:6379> lset l:list 2 2

OK

127.0.0.1:6379> lrange l:list 0 -1

1) "6"

2) "5"

3) "2"

4) "2"

 

          1. 只保留列表指定片段

指定範圍和LRANGE一致

 

語法:LTRIM key start stop

127.0.0.1:6379> lrange l:list 0 -1

1) "6"

2) "5"

3) "0"

4) "2"

127.0.0.1:6379> ltrim l:list 0 2

OK

127.0.0.1:6379> lrange l:list 0 -1

1) "6"

2) "5"

3) "0"

 

 

          1. 向列表中插入元素

該命令首先會在列表中從左到右查找值爲pivot的元素,然後根據第二個參數是BEFORE還是AFTER來決定將value插入到該元素的前面還是後面。

 

 

語法:LINSERT key BEFORE|AFTER pivot value

127.0.0.1:6379> lrange list 0 -1

1) "3"

2) "2"

3) "1"

127.0.0.1:6379> linsert list after 3 4

(integer) 4

127.0.0.1:6379> lrange list 0 -1

1) "3"

2) "4"

3) "2"

4) "1"

 

          1. 將元素從一個列表轉移到另一個列表中

語法:RPOPLPUSH source destination

127.0.0.1:6379> rpoplpush list newlist

"1"

127.0.0.1:6379> lrange newlist 0 -1

1) "1"

127.0.0.1:6379> lrange list 0 -1

1) "3"

2) "4"

3) "2"

 

      1. 應用之商品評論列表
  1. 需求1:用戶針對某一商品發佈評論,一個商品會被不同的用戶進行評論,存儲商品評論時,要按時間順序排序。
  2. 需求2:用戶在前端頁面查詢該商品的評論,需要安裝時間順序降序排序。

 

思路:

       使用list存儲商品評論信息,KEY是該商品的ID,VALUE是商品評論信息列表

商品編號爲1001的商品評論key【items: comment:1001】

192.168.101.3:7001> LPUSH items:comment:1001 '{"id":1,"name":"商品不錯,很好!!","date":1430295077289}'

 

    1. Set類型
      1. set介紹

set類型即集合類型,其中的數據是不重複且沒有順序

集合類型和列表類型的對比:

       集合類型的常用操作是向集合中加入或刪除元素、判斷某個元素是否存在等,由於集合類型的Redis內部是使用值爲空的散列表實現,所有這些操作的時間複雜度都爲0(1)。

Redis還提供了多個集合之間的交集、並集、差集的運算。

      1. 命令
        1. 增加/刪除元素

語法:SADD key member [member ...]

127.0.0.1:6379> sadd set a b c

(integer) 3

127.0.0.1:6379> sadd set a

(integer) 0

 

語法:SREM key member [member ...]

127.0.0.1:6379> srem set c d

(integer) 1

 

 

        1. 獲得集合中的所有元素

語法:SMEMBERS key

127.0.0.1:6379> smembers set

1) "b"

2) "a”

 

        1. 判斷元素是否在集合中

語法:SISMEMBER key member

127.0.0.1:6379> sismember set a

(integer) 1

127.0.0.1:6379> sismember set h

(integer) 0

 

      1. 集合運算命令
        1. 集合的差集運算 A-B

屬於A並且不屬於B的元素構成的集合。

語法:SDIFF key [key ...]

127.0.0.1:6379> sadd setA 1 2 3

(integer) 3

127.0.0.1:6379> sadd setB 2 3 4

(integer) 3

127.0.0.1:6379> sdiff setA setB

1) "1"

127.0.0.1:6379> sdiff setB setA

1) "4"

 

        1. 集合的交集運算 A ∩ B

屬於A且屬於B的元素構成的集合。

語法:SINTER key [key ...]

127.0.0.1:6379> sinter setA setB

1) "2"

2) "3"

 

        1. 集合的並集運算 A ∪ B

屬於A或者屬於B的元素構成的集合

 

語法:SUNION key [key ...]

127.0.0.1:6379> sunion setA setB

1) "1"

2) "2"

3) "3"

4) "4"

 

      1. 其它命令(自學)
        1. 獲得集合中元素的個數

語法:SCARD key

127.0.0.1:6379> smembers setA

1) "1"

2) "2"

3) "3"

127.0.0.1:6379> scard setA

(integer) 3

 

        1. 從集合中彈出一個元素

注意:由於集合是無序的,所有SPOP命令會從集合中隨機選擇一個元素彈出

 

語法:SPOP key

127.0.0.1:6379> spop setA

"1“

 

    1. Zset類型 (sortedset)
      1. Zset介紹

       在集合類型的基礎上,有序集合類型爲集合中的每個元素都關聯一個分數,這使得我們不僅可以完成插入、刪除和判斷元素是否存在在集合中,還能夠獲得分數最高或最低的前N個元素、獲取指定分數範圍內的元素等與分數有關的操作。

 

在某些方面有序集合和列表類型有些相似。

1、二者都是有序的。

2、二者都可以獲得某一範圍的元素。

但是,二者有着很大區別:

1、列表類型是通過鏈表實現的,獲取靠近兩端的數據速度極快,而當元素增多後,訪問中間數據的速度會變慢。

2、有序集合類型使用散列表實現,所有即使讀取位於中間部分的數據也很快。

3、列表中不能簡單的調整某個元素的位置,但是有序集合可以(通過更改分數實現)

4、有序集合要比列表類型更耗內存。

 

      1. 命令
        1. 增加元素

       向有序集合中加入一個元素和該元素的分數,如果該元素已經存在則會用新的分數替換原有的分數。返回值是新加入到集合中的元素個數,不包含之前已經存在的元素。

 

語法:ZADD key score member [score member ...]

127.0.0.1:6379> zadd scoreboard 80 zhangsan 89 lisi 94 wangwu

(integer) 3

127.0.0.1:6379> zadd scoreboard 97 lisi

(integer) 0

 

        1. 獲得排名在某個範圍的元素列表

獲得排名在某個範圍的元素列表

  • 按照元素分數從小到大的順序返回索引從start到stop之間的所有元素(包含兩端的元素)

 

語法:ZRANGE key start stop [WITHSCORES]              

127.0.0.1:6379> zrange scoreboard 0 2

1) "zhangsan"

2) "wangwu"

3) "lisi“

 

  • 按照元素分數從大到小的順序返回索引從start到stop之間的所有元素(包含兩端的元素)

 

語法:ZREVRANGE key start stop [WITHSCORES]        

127.0.0.1:6379> zrevrange scoreboard 0 2

1) " lisi "

2) "wangwu"

3) " zhangsan “

 

如果需要獲得元素的分數的可以在命令尾部加上WITHSCORES參數

127.0.0.1:6379> zrange scoreboard 0 1 WITHSCORES

1) "zhangsan"

2) "80"

3) "wangwu"

4) "94"

 

        1. 獲取元素的分數

語法:ZSCORE key member

127.0.0.1:6379> zscore scoreboard lisi

"97"

 

        1. 刪除元素

移除有序集key中的一個或多個成員,不存在的成員將被忽略。

當key存在但不是有序集類型時,返回一個錯誤。

 

語法:ZREM key member [member ...]

127.0.0.1:6379> zrem scoreboard lisi

(integer) 1

 

 

        1. 其它命令(自學)
          1. 獲得指定分數範圍的元素

語法:ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]

127.0.0.1:6379> ZRANGEBYSCORE scoreboard 90 97 WITHSCORES

1) "wangwu"

2) "94"

3) "lisi"

4) "97"

127.0.0.1:6379> ZRANGEBYSCORE scoreboard 70 100 limit 1 2

1) "wangwu"

2) "lisi"

 

          1. 增加某個元素的分數

返回值是更改後的分數

 

語法:ZINCRBY  key increment member

127.0.0.1:6379> ZINCRBY scoreboard 4 lisi

"101“

 

 

          1. 獲得集合中元素的數量

語法:ZCARD key

127.0.0.1:6379> ZCARD scoreboard

(integer) 3

 

          1. 獲得指定分數範圍內的元素個數

語法:ZCOUNT key min max

127.0.0.1:6379> ZCOUNT scoreboard 80 90

(integer) 1

 

          1. 按照排名範圍刪除元素

語法:ZREMRANGEBYRANK key start stop

127.0.0.1:6379> ZREMRANGEBYRANK scoreboard 0 1

(integer) 2

127.0.0.1:6379> ZRANGE scoreboard 0 -1

1) "lisi"

          1. 按照分數範圍刪除元素

語法:ZREMRANGEBYSCORE key min max

127.0.0.1:6379> zadd scoreboard 84 zhangsan 

(integer) 1

127.0.0.1:6379> ZREMRANGEBYSCORE scoreboard 80 100

(integer) 1

 

          1. 獲取元素的排名
  • 從小到大

語法:ZRANK key member

127.0.0.1:6379> ZRANK scoreboard lisi

(integer) 0

 

  • 從大到小

語法:ZREVRANK key member

127.0.0.1:6379> ZREVRANK scoreboard zhangsan

(integer) 1

 

      1. 應用之商品銷售排行榜
  1. 需求:根據商品銷售量對商品進行排行顯示
  2. 思路:定義商品銷售排行榜(sorted set集合),Key爲items:sellsort,分數爲商品銷售量。

 

寫入商品銷售量:

  • 商品編號1001的銷量是9,商品編號1002的銷量是10

192.168.101.3:7007> ZADD items:sellsort 9 1001 10 1002

 

  • 商品編號1001的銷量加1

192.168.101.3:7001> ZINCRBY items:sellsort 1 1001

 

  • 商品銷量前10名:

192.168.101.3:7001> ZRANGE items:sellsort 0 9 withscores

 

    1. 通用命令
      1. keys

返回滿足給定pattern 的所有key

語法:keys pattern

redis 127.0.0.1:6379> keys mylist*

1) "mylist"

2) "mylist5"

3) "mylist6"

4) "mylist7"

5) "mylist8"

 

 

      1. del

語法:DEL key

127.0.0.1:6379> del test

(integer) 1

 

      1. exists

作用:確認一個key 是否存在

語法:exists key

示例:從結果來看,數據庫中不存在HongWan 這個key,但是age 這個key 是存在的

redis 127.0.0.1:6379> exists HongWan

(integer) 0

redis 127.0.0.1:6379> exists age

(integer) 1

redis 127.0.0.1:6379>

 

      1. expire

Redis在實際使用過程中更多的用作緩存,然而緩存的數據一般都是需要設置生存時間的,即:到期後數據銷燬。

 

EXPIRE key seconds                  設置key的生存時間(單位:秒)key在多少秒後會自動刪除

TTL key                                  查看key生於的生存時間

PERSIST key                          清除生存時間

PEXPIRE key milliseconds      生存時間設置單位爲:毫秒

 

 

例子:

192.168.101.3:7002> set test 1             設置test的值爲1

OK

192.168.101.3:7002> get test                獲取test的值

"1"

192.168.101.3:7002> EXPIRE test 5     設置test的生存時間爲5秒

(integer) 1

192.168.101.3:7002> TTL test               查看test的生於生成時間還有1秒刪除

(integer) 1

192.168.101.3:7002> TTL test

(integer) -2

192.168.101.3:7002> get test                獲取test的值,已經刪除

(nil)

 

 

      1. rename

作用:重命名key

語法:rename  oldkey  newkey

示例:age 成功的被我們改名爲age_new 了

redis 127.0.0.1:6379[1]> keys *

1) "age"

redis 127.0.0.1:6379[1]> rename age age_new

OK

redis 127.0.0.1:6379[1]> keys *

1) "age_new"

redis 127.0.0.1:6379[1]>

 

      1. type

作用:顯示指定key的數據類型

語法:type key

示例:這個方法可以非常簡單的判斷出值的類型

redis 127.0.0.1:6379> type addr

string

redis 127.0.0.1:6379> type myzset2

zset

redis 127.0.0.1:6379> type mylist

list

redis 127.0.0.1:6379>

 

  1. Redis事務
    1. Redis事務介紹
  1. Redis的事務是通過MULTI,EXEC,DISCARD和WATCH這四個命令來完成的。
  2. Redis的單個命令都是原子性的,所以這裏確保事務性的對象是命令集合
  3. Redis將命令集合序列化並確保處於同一事務的命令集合連續且不被打斷的執行
  4. Redis不支持回滾操作
    1. 相關命令
  1. MULTI

       用於標記事務塊的開始

       Redis會將後續的命令逐個放入隊列中,然後使用EXEC命令原子化地執行這個命令序列。

       語法:multi

  1. EXEC

       在一個事務中執行所有先前放入隊列的命令,然後恢復正常的連接狀態

       語法:exec

  1. DISCARD

       清除所有先前在一個事務中放入隊列的命令,然後恢復正常的連接狀態。

       語法:discard

  1. WATCH

       當某個事務需要按條件執行時,就要使用這個命令將給定的鍵設置爲受監控的狀態。

       語法:watch key [key…]

      注意事項:使用該命令可以實現redis的樂觀鎖

  1. UNWATCH

       清除所有先前爲一個事務監控的鍵。

       語法:unwatch

    1. 事務失敗處理
  1. Redis語法錯誤(可以理解爲編譯期錯誤

  1. Redis類型錯誤(可以理解爲運行期錯誤

  1. Redis不支持事務回滾

      爲什麼redis不支持事務回滾?

  1. 大多數事務失敗是因爲語法錯誤或者類型錯誤,這兩種錯誤,在開發階段都是可以預見的
  2. redis爲了性能方面就忽略了事務回滾
  1. Redis實現分佈式鎖
    1. 鎖的處理
  1. 單應用中使用鎖:單進程多線程

       synchronize、Lock

  1. 分佈式應用中使用鎖:多進程
    1. 分佈式鎖的實現方式
  1. 基於數據庫的樂觀鎖實現分佈式鎖
  2. 基於zookeeper臨時節點的分佈式鎖
  3. 基於redis的分佈式鎖
    1. 分佈式鎖的注意事項
  1. 互斥性在任意時刻,只有一個客戶端能持有鎖
  2. 同一性:加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。
  3. 可重入性:即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證後續其他客戶端能加鎖。
    1. 實現分佈式鎖
      1. 獲取鎖

  1. 方式1(使用set命令實現) --推薦:

/**

         * 使用redis的set命令實現獲取分佈式鎖

         * @param lockKey       可以就是鎖

         * @param requestId               請求ID,保證同一性

         * @param expireTime    過期時間,避免死鎖

         * @return

         */

        public static boolean getLock(String lockKey,String requestId,int expireTime) {

                 //NX:保證互斥性

                 String result = jedis.set(lockKey, requestId, "NX", "EX", expireTime);

                 if("OK".equals(result)) {

                         return true;

                 }

                

                 return false;

        }

 

  1. 方式2(使用setnx命令實現):

public static boolean getLock(String lockKey,String requestId,int expireTime) {

                     Long result = jedis.setnx(lockKey, requestId);

                     if(result == 1) {

                                jedis.expire(lockKey, expireTime);

                                return true;

                     }

                    

                     return false;

           }

 

      1. 釋放鎖
  1. 方式1(del命令實現):

        /**

         * 釋放分佈式鎖

         * @param lockKey

         * @param requestId

         */

        public static void releaseLock(String lockKey,String requestId) {

            if (requestId.equals(jedis.get(lockKey))) {

                jedis.del(lockKey);

            }

        }

 

  1. 方式2(redis+lua腳本實現)--推薦:

 

           public static boolean releaseLock(String lockKey, String requestId) {

                     String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

                     Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

 

                     if (result.equals(1L)) {

                                return true;

                     }

                     return false;

           }

 

  1. Redis持久化方案

Redis是一個內存數據庫,爲了保證數據的持久性,它提供了兩種持久化方案:

  1. RDB方式(默認)
  2. AOF方式
    1. RDB方式
      1. 介紹
  1. RDB是Redis默認採用的持久化方式。
  2. RDB方式是通過快照(snapshotting)完成的,當符合一定條件時Redis會自動將內存中的數據進行快照並持久化到硬盤。
  3. Redis會在指定的情況下觸發快照
  1. 符合自定義配置的快照規則
  2. 執行save或者bgsave命令
  3. 執行flushall命令
  4. 執行主從複製操作
  1. redis.conf中設置自定義快照規則
  1. RDB持久化條件

       格式:save <seconds> <changes>

      

       示例:

       save 900 1  : 表示15分鐘(900秒鐘)內至少1個鍵被更改則進行快照。

       save 300 10 : 表示5分鐘(300秒)內至少10個鍵被更改則進行快照。

    save 60 10000 :表示1分鐘內至少10000個鍵被更改則進行快照。

      

       可以配置多個條件(每行配置一個條件),每個條件之間是“或”的關係。

 

save 900 1

save 300 10

save 60 10000

 

  1. 配置dir指定rdb快照文件的位置

# Note that you must specify a directory here, not a file name.

dir ./

 

 

  1. 配置dbfilename指定rdb快照文件的名稱

# The filename where to dump the DB

dbfilename dump.rdb

 

  1. 特別說明:

       * Redis啓動後會讀取RDB快照文件,將數據從硬盤載入到內存。

       * 根據數據量大小與結構和服務器性能不同,這個時間也不同。通常將記錄一千萬個字符串類型鍵、大小爲1GB的快照文件載入到內存中需要花費20~30秒鐘。

 

 

      1. 快照的實現原理
  1. 快照過程
  1. redis使用fork函數複製一份當前進程的副本(子進程)
  2. 父進程繼續接收並處理客戶端發來的命令,而子進程開始將內存中的數據寫入硬盤中的臨時文件。
  3. 當子進程寫入完所有數據後會用該臨時文件替換舊的RDB文件,至此,一次快照操作完成。  

 

  1. 注意事項
  1. redis在進行快照的過程中不會修改RDB文件,只有快照結束後纔會將舊的文件替換成新的,也就是說任何時候RDB文件都是完整的。 
  2. 這就使得我們可以通過定時備份RDB文件來實現redis數據庫的備份, RDB文件是經過壓縮的二進制文件,佔用的空間會小於內存中的數據,更加利於傳輸。
      1. RDB優缺點
  1. 缺點:使用RDB方式實現持久化,一旦Redis異常退出,就會丟失最後一次快照以後更改的所有數據。這個時候我們就需要根據具體的應用場景,通過組合設置自動快照條件的方式來將可能發生的數據損失控制在能夠接受範圍。如果數據相對來說比較重要,希望將損失降到最小,則可以使用AOF方式進行持久化
  2. 優點: RDB可以最大化Redis的性能:父進程在保存RDB文件時唯一要做的就是fork出一個子進程,然後這個子進程就會處理接下來的所有保存工作,父進程無序執行任何磁盤I/O操作。同時這個也是一個缺點,如果數據集比較大的時候,fork可以能比較耗時,造成服務器在一段時間內停止處理客戶端的請求;
    1. AOF方式
      1. 介紹
  1. 默認情況下Redis沒有開啓AOF(append only file)方式的持久化
  2. 開啓AOF持久化後每執行一條會更改Redis中的數據的命令,Redis就會將該命令寫入硬盤中的AOF文件,這一過程顯然會降低Redis的性能,但大部分情況下這個影響是能夠接受的,另外使用較快的硬盤可以提高AOF的性能
  3. 可以通過修改redis.conf配置文件中的appendonly參數開啓

appendonly yes

  1. AOF文件的保存位置和RDB文件的位置相同,都是通過dir參數設置的。

dir ./

  1. 默認的文件名是appendonly.aof,可以通過appendfilename參數修改:

appendfilename appendonly.aof

 

      1. AOF重寫原理(優化AOF文件)
  1. Redis 可以在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫
  2. 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合
  3. 整個重寫操作是絕對安全的,因爲 Redis 在創建新 AOF 文件的過程中,會繼續將命令追加到現有的 AOF 文件裏面,即使重寫過程中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件創建完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操作。
  4. AOF 文件有序地保存了對數據庫執行的所有寫入操作, 這些寫入操作以 Redis 協議的格式保存, 因此 AOF 文件的內容非常容易被人讀懂, 對文件進行分析(parse)也很輕鬆

 

  1. 參數說明

# auto-aof-rewrite-percentage 100  表示當前aof文件大小超過上一次aof文件大小的百分之多少的時候會進行重寫。如果之前沒有重寫過,以啓動時aof文件大小爲準

# auto-aof-rewrite-min-size 64mb   限制允許重寫最小aof文件大小,也就是文件大小小於64mb的時候,不需要進行優化

 

      1. 同步磁盤數據

Redis每次更改數據的時候, aof機制都會將命令記錄到aof文件,但是實際上由於操作系統的緩存機制數據並沒有實時寫入到硬盤,而是進入硬盤緩存再通過硬盤緩存機制去刷新到保存到文件

 

  1. 參數說明:

* appendfsync always  每次執行寫入都會進行同步  , 這個是最安全但是是效率比較低的方式

* appendfsync everysec   每一秒執行

* appendfsync no  不主動進行同步操作,由操作系統去執行,這個是最快但是最不安全的方式

      1. AOF文件損壞以後如何修復

服務器可能在程序正在對 AOF 文件進行寫入時停機, 如果停機造成了 AOF 文件出錯(corrupt), 那麼 Redis 在重啓時會拒絕載入這個 AOF 文件, 從而確保數據的一致性不會被破壞。

當發生這種情況時, 可以用以下方法來修復出錯的 AOF 文件:

  1. 爲現有的 AOF 文件創建一個備份
  2. 使用 Redis 附帶的 redis-check-aof 程序,對原來的 AOF 文件進行修復。

redis-check-aof --fix readonly.aof

  1. 重啓 Redis 服務器,等待服務器載入修復後的 AOF 文件,並進行數據恢復。
    1. 如何選擇RDB和AOF
  1. 一般來說,如果對數據的安全性要求非常高的話,應該同時使用兩種持久化功能。
  2. 如果可以承受數分鐘以內的數據丟失,那麼可以只使用 RDB 持久化。
  3. 有很多用戶都只使用 AOF 持久化, 但並不推薦這種方式: 因爲定時生成 RDB 快照(snapshot)非常便於進行數據庫備份, 並且 RDB 恢復數據集的速度也要比 AOF 恢復的速度要快
  4. 兩種持久化策略可以同時使用,也可以使用其中一種。如果同時使用的話, 那麼Redis重啓時,會優先使用AOF文件來還原數據
  1. Redis的主從複製
    1. 什麼是主從複製

                                          

       持久化保證了即使redis服務重啓也不會丟失數據,因爲redis服務重啓後會將硬盤上持久化的數據恢復到內存中,但是當redis服務器的硬盤損壞了可能會導致數據丟失,不過通過redis的主從複製機制就可以避免這種單點故障,如下圖:

說明:

  1. 主redis中的數據有兩個副本(replication)即從redis1和從redis2,即使一臺redis服務器宕機其它兩臺redis服務也可以繼續提供服務。
  2. 主redis中的數據和從redis上的數據保持實時同步,當主redis寫入數據時通過主從複製機制會複製到兩個從redis服務上。
  3. 只有一個主redis,可以有多個從redis。
  4. 主從複製不會阻塞master,在同步數據時,master 可以繼續處理client 請求
  5. 一個redis可以即是主又是從,如下圖:

 

    1. 主從配置
      1. 主redis配置

無需特殊配置。

      1. 從redis配置
  • 修改從服務器上的redis.conf文件

# slaveof <masterip> <masterport>

slaveof 192.168.101.3 6379

 

上邊的配置說明當前【從服務器】對應的【主服務器】的IP是192.168.101.3,端口是6379。

 

    1. 實現原理

* Redis的主從同步,分爲全量同步和增量同步。

* 只有從機第一次連接上主機是全量同步

* 斷線重連有可能觸發全量同步也有可能是增量同步(master判斷runid是否一致

* 除此之外的情況都是增量同步

 

      1. 全量同步

Redis的全量同步過程主要分三個階段:

  1. 同步快照階段:Master創建併發送快照給Slave,Slave載入並解析快照。Master同時將此階段所產生的新的寫命令存儲到緩衝區。
  2. 同步寫緩衝階段:Master向Slave同步存儲在緩衝區的寫操作命令。
  3. 同步增量階段:Master向Slave同步寫操作命令。

      1. 增量同步

* Redis增量同步主要指Slave完成初始化後開始正常工作時,Master發生的寫操作同步到Slave的過程

* 通常情況下,Master每執行一個寫命令就會向Slave發送相同的寫命令,然後Slave接收並執行。

 

  1. Redis Sentinel哨兵機制

Redis主從複製的缺點:沒有辦法對master進行動態選舉,需要使用Sentinel機制完成動態選舉

    1. 簡介

* Sentinel(哨兵)進程是用於監控redis集羣中Master主服務器工作的狀態

* 在Master主服務器發生故障的時候,可以實現Master和Slave服務器的切換,保證系統的高可用(HA)

* 其已經被集成在redis2.6+的版本中,Redis的哨兵模式到了2.8版本之後就穩定了下來。

    1. 哨兵進程的作用
  1. 監控(Monitoring): 哨兵(sentinel) 會不斷地檢查你的Master和Slave是否運作正常。
  2. 提醒(Notification)當被監控的某個Redis節點出現問題時, 哨兵(sentinel) 可以通過 API 向管理員或者其他應用程序發送通知。
  3. 自動故障遷移(Automatic failover)一個Master不能正常工作時,哨兵(sentinel) 會開始一次自動故障遷移操作

* 它會將失效Master的其中一個Slave升級爲新的Master, 並讓失效Master的其他Slave改爲複製新的Master;

* 當客戶端試圖連接失效的Master時,集羣也會向客戶端返回新Master的地址,使得集羣可以使用現在的Master替換失效Master。

* Master和Slave服務器切換後,Master的redis.conf、Slave的redis.conf和sentinel.conf的配置文件的內容都會發生相應的改變,即,Master主服務器的redis.conf配置文件中會多一行slaveof的配置,sentinel.conf的監控目標會隨之調換。

    1. 哨兵進程的工作方式
  1. 每個Sentinel(哨兵)進程以每秒鐘一次的頻率向整個集羣中的Master主服務器,Slave從服務器以及其他Sentinel(哨兵)進程發送一個 PING 命令
  2. 如果一個實例(instance)距離最後一次有效回覆 PING 命令的時間超過 down-after-milliseconds 選項所指定的值, 則這個實例會被 Sentinel(哨兵)進程標記爲主觀下線SDOWN)。
  3. 如果一個Master主服務器被標記爲主觀下線(SDOWN),則正在監視這個Master主服務器的所有 Sentinel(哨兵)進程要以每秒一次的頻率確認Master主服務器的確進入了主觀下線狀態
  4. 有足夠數量的 Sentinel(哨兵)進程(大於等於配置文件指定的值)在指定的時間範圍內確認Master主服務器進入了主觀下線狀態(SDOWN), 則Master主服務器會被標記爲客觀下線(ODOWN)
  5. 在一般情況下, 每個 Sentinel(哨兵)進程會以每 10 秒一次的頻率向集羣中的所有Master主服務器、Slave從服務器發送 INFO 命令。
  6. 當Master主服務器被 Sentinel(哨兵)進程標記爲客觀下線(ODOWN)時,Sentinel(哨兵)進程向下線的 Master主服務器的所有 Slave從服務器發送 INFO 命令的頻率會從 10 秒一次改爲每秒一次。
  7. 若沒有足夠數量的 Sentinel(哨兵)進程同意 Master主服務器下線, Master主服務器的客觀下線狀態就會被移除。若 Master主服務器重新向 Sentinel(哨兵)進程發送 PING 命令返回有效回覆,Master主服務器的主觀下線狀態就會被移除。

    1. 案例演示
  1. 修改從機的sentinel.conf

#sentinel monitor <master-name> <master ip> <master port> <quorum>

sentinel monitor mymaster 192.168.10.133 6379 1

其他配置項說明

 

# Example sentinel.conf

 

# 哨兵sentinel實例運行的端口 默認26379

port 26379

 

# 哨兵sentinel的工作目錄

dir /tmp

 

# 哨兵sentinel監控的redis主節點的 ip port

# master-name  可以自己命名的主節點名字 只能由字母A-z、數字0-9 、這三個字符".-_"組成。

# quorum 當這些quorum個數sentinel哨兵認爲master主節點失聯 那麼這時 客觀上認爲主節點失聯了

# sentinel monitor <master-name> <ip> <redis-port> <quorum>

  sentinel monitor mymaster 127.0.0.1 6379 2

 

# 當在Redis實例中開啓了requirepass foobared 授權密碼 這樣所有連接Redis實例的客戶端都要提供密碼

# 設置哨兵sentinel 連接主從的密碼 注意必須爲主從設置一樣的驗證密碼

# sentinel auth-pass <master-name> <password>

sentinel auth-pass mymaster MySUPER--secret-0123passw0rd

 

 

# 指定多少毫秒之後 主節點沒有應答哨兵sentinel 此時 哨兵主觀上認爲主節點下線 默認30秒

# sentinel down-after-milliseconds <master-name> <milliseconds>

sentinel down-after-milliseconds mymaster 30000

 

# 這個配置項指定了在發生failover主備切換時最多可以有多少個slave同時對新的master進行 同步,

這個數字越小,完成failover所需的時間就越長,

但是如果這個數字越大,就意味着越 多的slave因爲replication而不可用。

可以通過將這個值設爲 1 來保證每次只有一個slave 處於不能處理命令請求的狀態。

# sentinel parallel-syncs <master-name> <numslaves>

sentinel parallel-syncs mymaster 1

 

 

 

# 故障轉移的超時時間 failover-timeout 可以用在以下這些方面:

#1. 同一個sentinel對同一個master兩次failover之間的間隔時間。

#2. 當一個slave從一個錯誤的master那裏同步數據開始計算時間。直到slave被糾正爲向正確的master那裏同步數據時。

#3.當想要取消一個正在進行的failover所需要的時間。 

#4.當進行failover時,配置所有slaves指向新的master所需的最大時間。不過,即使過了這個超時,slaves依然會被正確配置爲指向master,但是就不按parallel-syncs所配置的規則來了

# 默認三分鐘

# sentinel failover-timeout <master-name> <milliseconds>

sentinel failover-timeout mymaster 180000

 

# SCRIPTS EXECUTION

 

#配置當某一事件發生時所需要執行的腳本,可以通過腳本來通知管理員,例如當系統運行不正常時發郵件通知相關人員。

#對於腳本的運行結果有以下規則:

#若腳本執行後返回1,那麼該腳本稍後將會被再次執行,重複次數目前默認爲10

#若腳本執行後返回2,或者比2更高的一個返回值,腳本將不會重複執行。

#如果腳本在執行過程中由於收到系統中斷信號被終止了,則同返回值爲1時的行爲相同。

#一個腳本的最大執行時間爲60s,如果超過這個時間,腳本將會被一個SIGKILL信號終止,之後重新執行。

 

#通知型腳本:當sentinel有任何警告級別的事件發生時(比如說redis實例的主觀失效和客觀失效等等),將會去調用這個腳本,

這時這個腳本應該通過郵件,SMS等方式去通知系統管理員關於系統不正常運行的信息。調用該腳本時,將傳給腳本兩個參數,

一個是事件的類型,

一個是事件的描述。

如果sentinel.conf配置文件中配置了這個腳本路徑,那麼必須保證這個腳本存在於這個路徑,並且是可執行的,否則sentinel無法正常啓動成功。

#通知腳本

# sentinel notification-script <master-name> <script-path>

  sentinel notification-script mymaster /var/redis/notify.sh

 

# 客戶端重新配置主節點參數腳本

# 當一個master由於failover而發生改變時,這個腳本將會被調用,通知相關的客戶端關於master地址已經發生改變的信息。

# 以下參數將會在調用腳本時傳給腳本:

# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>

# 目前<state>總是“failover”,

# <role>是“leader”或者“observer”中的一個。

# 參數 from-ip, from-port, to-ip, to-port是用來和舊的master和新的master(即舊的slave)通信的

# 這個腳本應該是通用的,能被多次調用,不是針對性的。

# sentinel client-reconfig-script <master-name> <script-path>

 sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

 

 

 

  1. 通過redis-sentinel啓動哨兵服務

./redis-sentinel sentinel.conf

 

  1. Redis Cluster集羣

redis3.0以後推出的redis cluster 集羣方案,redis cluster集羣保證了高可用、高性能、高可擴展性。

    1. redis-cluster架構圖

 

 

架構細節:

(1)所有的redis節點彼此互聯(PING-PONG機制),內部使用二進制協議優化傳輸速度和帶寬.

(2)節點的fail是通過集羣中超過半數的節點檢測失效時才生效.

(3)客戶端與redis節點直連,不需要中間proxy層.客戶端不需要連接集羣所有節點,連接集羣中任何一個可用節點即可

(4)redis-cluster把所有的物理節點映射到[0-16383]slot上,cluster 負責維護node<->slot<->value

Redis 集羣中內置了 16384 個哈希槽,當需要在 Redis 集羣中放置一個 key-value 時,redis 先對 key 使用 crc16 算法算出一個結果,然後把結果對 16384 求餘數,這樣每個 key 都會對應一個編號在 0-16383 之間的哈希槽,redis 會根據節點數量大致均等的將哈希槽映射到不同的節點

示例如下:

    1. redis-cluster投票:容錯

 

最小節點數:3

(1)節點失效判斷:集羣中所有master參與投票,如果半數以上master節點與其中一個master節點通信超過(cluster-node-timeout),認爲該master節點掛掉.

(2)集羣失效判斷:什麼時候整個集羣不可用(cluster_state:fail)? 

  • 如果集羣任意master掛掉,且當前master沒有slave,則集羣進入fail狀態。也可以理解成集羣的[0-16383]slot映射不完全時進入fail狀態。
  • 如果集羣超過半數以上master掛掉,無論是否有slave,集羣進入fail狀態。

 

    1. 安裝Ruby環境

redis集羣需要使用集羣管理腳本redis-trib.rb,它的執行相應依賴ruby環境。

 

  1. 第一步:安裝ruby

yum install ruby

yum install rubygems

  1. 第三步:安裝ruby和redis的接口程序redis-3.2.2.gem

gem install redis -V 3.2.2

  1. 第四步:複製redis-3.2.9/src/redis-trib.rb文件到/usr/local/redis目錄

cp redis-3.2.9/src/redis-trib.rb /usr/local/redis-cluster/ -r

 

    1. 安裝Redis集羣(RedisCluster)

Redis集羣最少需要三臺主服務器,三臺從服務器

端口號分別爲:7001~7006

 

  1. 第一步:創建7001實例,並編輯redis.conf文件,修改port爲7001。

注意:創建實例,即拷貝單機版安裝時,生成的bin目錄,爲7001目錄。

 

  1. 查看集羣結點發現7007已添加到集羣中

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

 

10.7.2hash槽重新分配(數據遷移)

添加完主節點需要對主節點進行hash槽分配,這樣該主節纔可以存儲數據

 

  1. 查看集羣中槽佔用情況

 

redis集羣有16384個槽,集羣中的每個結點分配自已槽,通過查看集羣結點可以看到槽佔用情況。

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

  1. 給剛添加的7007結點分配槽

* 第一步:連接上集羣(連接集羣中任意一個可用結點都行)

./redis-trib.rb reshard 192.168.10.133:7001

 

* 第二步:輸入要分配的槽數量

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

輸入:3000,表示要給目標節點分配3000個槽

 

* 第三步:輸入接收槽的結點id

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

輸入:15b809eadae88955e36bcdbb8144f61bbbaf38fb

PS:這裏準備給7007分配槽,通過cluster nodes查看7007結點id爲:

15b809eadae88955e36bcdbb8144f61bbbaf38fb

 

* 第四步:輸入源結點id

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

輸入:all

 

* 第五步:輸入yes開始移動槽到目標結點id

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

輸入:yes

 

      1. 添加從節點

 

  1. 添加7008從結點,將7008作爲7007的從結點

 

命令:./redis-trib.rb add-node --slave --master-id  主節點id   新節點的ip和端口   舊節點ip和端口(集羣中任一節點都可以)

 

執行如下命令:

./redis-trib.rb add-node --slave --master-id  35da64607a02c9159334a19164e68dd95a3b943c 192.168.10.103:7008 192.168.10.103:7001

 

35da64607a02c9159334a19164e68dd95a3b943c是7007結點的id,可通過cluster nodes查看。

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

 

注意:如果原來該結點在集羣中的配置信息已經生成到cluster-config-file指定的配置文件中(如果cluster-config-file沒有指定則默認爲nodes.conf),這時可能會報錯:

[ERR] Node XXXXXX is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0

解決方法是刪除生成的配置文件nodes.conf,刪除後再執行./redis-trib.rb add-node指令

 

  1. 查看集羣中的結點,剛添加的7008爲7007的從節點:

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

 

      1. 刪除結點

命令:./redis-trib.rb del-node 127.0.0.1:7005 4b45eb75c8b428fbd77ab979b85080146a9bc017

 

 

刪除已經佔有hash槽的結點會失敗,報錯如下:

[ERR] Node 127.0.0.1:7005 is not empty! Reshard data away and try again.

 

需要將該結點佔用的hash槽分配出去(參考hash槽重新分配章節)。

    1. Jedis連接集羣

需要開啓防火牆,或者直接關閉防火牆。

service iptables stop

 

      1. 代碼實現

創建JedisCluster類連接redis集羣。

 

@Test

public void testJedisCluster() throws Exception {

        //創建一連接,JedisCluster對象,在系統中是單例存在

        Set<HostAndPort> nodes = new HashSet<>();

        nodes.add(new HostAndPort("192.168.10.133", 7001));

        nodes.add(new HostAndPort("192.168.10.133", 7002));

        nodes.add(new HostAndPort("192.168.10.133", 7003));

        nodes.add(new HostAndPort("192.168.10.133", 7004));

        nodes.add(new HostAndPort("192.168.10.133", 7005));

        nodes.add(new HostAndPort("192.168.10.133", 7006));

        JedisCluster cluster = new JedisCluster(nodes);

        //執行JedisCluster對象中的方法,方法和redis一一對應。

        cluster.set("cluster-test", "my jedis cluster test");

        String result = cluster.get("cluster-test");

        System.out.println(result);

        //程序結束時需要關閉JedisCluster對象

        cluster.close();

}

      1. 使用spring
  • 配置applicationContext.xml

<!-- 連接池配置 -->

<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">

        <!-- 最大連接數 -->

        <property name="maxTotal" value="30" />

        <!-- 最大空閒連接數 -->

        <property name="maxIdle" value="10" />

        <!-- 每次釋放連接的最大數目 -->

        <property name="numTestsPerEvictionRun" value="1024" />

        <!-- 釋放連接的掃描間隔(毫秒) -->

        <property name="timeBetweenEvictionRunsMillis" value="30000" />

        <!-- 連接最小空閒時間 -->

        <property name="minEvictableIdleTimeMillis" value="1800000" />

        <!-- 連接空閒多久後釋放, 當空閒時間>該值 且 空閒連接>最大空閒連接數 時直接釋放 -->

        <property name="softMinEvictableIdleTimeMillis" value="10000" />

        <!-- 獲取連接時的最大等待毫秒數,小於零:阻塞不確定的時間,默認-1 -->

        <property name="maxWaitMillis" value="1500" />

        <!-- 在獲取連接的時候檢查有效性, 默認false -->

        <property name="testOnBorrow" value="true" />

        <!-- 在空閒時檢查有效性, 默認false -->

        <property name="testWhileIdle" value="true" />

        <!-- 連接耗盡時是否阻塞, false報異常,ture阻塞直到超時, 默認true -->

        <property name="blockWhenExhausted" value="false" />

</bean>

<!-- redis集羣 -->

<bean id="jedisCluster" class="redis.clients.jedis.JedisCluster">

        <constructor-arg index="0">

                 <set>

                         <bean class="redis.clients.jedis.HostAndPort">

                                  <constructor-arg index="0" value="192.168.101.3"></constructor-arg>

                                  <constructor-arg index="1" value="7001"></constructor-arg>

                         </bean>

                         <bean class="redis.clients.jedis.HostAndPort">

                                  <constructor-arg index="0" value="192.168.101.3"></constructor-arg>

                                  <constructor-arg index="1" value="7002"></constructor-arg>

                         </bean>

                         <bean class="redis.clients.jedis.HostAndPort">

                                  <constructor-arg index="0" value="192.168.101.3"></constructor-arg>

                                  <constructor-arg index="1" value="7003"></constructor-arg>

                         </bean>

                         <bean class="redis.clients.jedis.HostAndPort">

                                  <constructor-arg index="0" value="192.168.101.3"></constructor-arg>

                                  <constructor-arg index="1" value="7004"></constructor-arg>

                         </bean>

                         <bean class="redis.clients.jedis.HostAndPort">

                                 <constructor-arg index="0" value="192.168.101.3"></constructor-arg>

                                  <constructor-arg index="1" value="7005"></constructor-arg>

                         </bean>

                         <bean class="redis.clients.jedis.HostAndPort">

                                  <constructor-arg index="0" value="192.168.101.3"></constructor-arg>

                                  <constructor-arg index="1" value="7006"></constructor-arg>

                         </bean>

                 </set>

        </constructor-arg>

        <constructor-arg index="1" ref="jedisPoolConfig"></constructor-arg>

</bean>

 

 

  • 測試代碼

 

private ApplicationContext applicationContext;

        @Before

        public void init() {

                 applicationContext = new ClassPathXmlApplicationContext(

                                  "classpath:applicationContext.xml");

        }

 

        // redis集羣

        @Test

        public void testJedisCluster() {

                 JedisCluster jedisCluster = (JedisCluster) applicationContext

                                  .getBean("jedisCluster");

 

                 jedisCluster.set("name", "zhangsan");

                 String value = jedisCluster.get("name");

                 System.out.println(value);

        }

 

  1. Redis+LUA整合使用
    1. 什麼是LUA?
  1. Lua 是一種輕量小巧的腳本語言,用標準C語言編寫並以源代碼形式開放, 其設計目的是爲了嵌入應用程序中,從而爲應用程序提供靈活的擴展和定製功能。
    1. Redis中使用LUA的好處

1.    減少網絡開銷,在Lua腳本中可以把多個命令放在同一個腳本中運行

2.    原子操作,redis會將整個腳本作爲一個整體執行,中間不會被其他命令插入。換句話說,編寫腳本的過程中無需擔心會出現競態條件

3.    複用性,客戶端發送的腳本會永遠存儲在redis中,這意味着其他客戶端可以複用這一腳本來完成同樣的邏輯

    1. LUA的安裝
  1. 下載

地址:http://www.lua.org/download.html

可以本地下載上傳到linux,也可以使用curl命令在linux系統中進行在線下載

curl -R -O http://www.lua.org/ftp/lua-5.3.5.tar.gz

  1. 安裝
  1. yum -y install readline-devel ncurses-devel
  2. tar -zxvf lua-5.3.5.tar.gz
  3. make linux
  4. make install

 

如果報錯,說找不到readline/readline.h, 可以通過yum命令安裝

yum -y install readline-devel ncurses-devel

安裝完以後再make linux  / make install

最後,直接輸入 lua命令即可進入lua的控制檯

    1. LUA常見語法

詳見http://www.runoob.com/lua/lua-tutorial.html

 

    1. Redis + LUA整合使用

從Redis2.6.0版本開始,通過內置的Lua解釋器,可以使用EVAL命令對Lua腳本進行求值。

      1. EVAL命令
  1. 命令的格式如下:

EVAL script numkeys key [key ...] arg [arg ...]  

  1. 命令說明:

* script參數:是一段Lua腳本程序,它會被運行在Redis服務器上下文中,這段腳本不必(也不應該)定義爲一個Lua函數。

* numkeys參數:用於指定鍵名參數的個數。

* key [key ...]參數: 從EVAL的第三個參數開始算起,使用了numkeys個鍵(key),表示在腳本中所用到的那些Redis鍵(key),這些鍵名參數可以在Lua中通過全局變量KEYS數組,用1爲基址的形式訪問( KEYS[1] , KEYS[2] ,以此類推)。

* arg [arg ...]參數:,可以在Lua中通過全局變量ARGV數組訪問,訪問的形式和KEYS變量類似( ARGV[1] 、 ARGV[2] ,諸如此類)。

* 例如:

> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second

1) "key1"

2) "key2"

3) "first"

4) "second"

 

      1. lua腳本中調用redis命令

redis.call()

redis.pcall()

這兩個函數的唯一區別在於它們使用不同的方式處理執行命令所產生的錯誤

 

示例:

> eval "return redis.call('set',KEYS[1],'bar')" 1 foo

OK

      1. EVALSHA

* EVAL 命令要求你在每次執行腳本的時候都發送一次腳本主體(script body)。

* Redis 有一個內部的緩存機制,因此它不會每次都重新編譯腳本,不過在很多場合,付出無謂的帶寬來傳送腳本主體並不是最佳選擇。

* 爲了減少帶寬的消耗, Redis 實現了 EVALSHA 命令,它的作用和 EVAL 一樣,都用於對腳本求值,但它接受的第一個參數不是腳本,而是腳本的 SHA1 校驗和(sum)

 

EVALSHA 命令的表現如下:

 

* 如果服務器還記得給定的 SHA1 校驗和所指定的腳本,那麼執行這個腳本

* 如果服務器不記得給定的 SHA1 校驗和所指定的腳本,那麼它返回一個特殊的錯誤,提醒用戶使用 EVAL 代替 EVALSHA

 

示例

> evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0

"bar"

      1. SCRIPT命令
  1. SCRIPT FLUSH 清除所有腳本緩存
  2. SCRIPT EXISTS 根據給定的腳本校驗和,檢查指定的腳本是否存在於腳本緩存
  3. SCRIPT LOAD 將一個腳本裝入腳本緩存,返回SHA1摘要,但並不立即運行它
  4. SCRIPT KILL 殺死當前正在運行的腳本
      1. redis-cli --eval

* 可以使用redis-cli 命令直接執行腳本

* 格式如下:

$ redis-cli --eval script KEYS[1] KEYS[2] , ARGV[1] ARGV[2] ...

 

  1. Redis消息模式
    1. 隊列模式

使用list類型的lpush和rpop實現消息隊列

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

 

注意事項:

* 消息接收方如果不知道隊列中是否有消息,會一直髮送rpop命令,如果這樣的話,會每一次都建立一次連接,這樣顯然不好。

* 可以使用brpop命令,它如果從隊列中取不出來數據,會一直阻塞,在一定範圍內沒有取出則返回null、

    1. 發佈訂閱模式
  1. 訂閱消息(subscribe

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

示例:subscribe kkb-channel

  1. 發佈消息(publish

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

publish kkb-channel “我是滅霸詹

  1. Redis發佈訂閱命令

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

  1. 緩存穿透、緩存擊穿、緩存失效
    1. 緩存數據的步驟
  1. 查詢緩存,如果沒有數據,則查詢數據庫
  2. 查詢數據庫,如果數據不爲空,將結果寫入緩存
    1. 緩存穿透
  1. 什麼叫緩存穿透?

一般的緩存系統,都是按照key去緩存查詢,如果不存在對應的value,就應該去後端系統查找(比如DB)。如果key對應的value是一定不存在的,並且對該key併發請求量很大,就會對後端系統造成很大的壓力。這就叫做緩存穿透。

  1. 如何解決?

1:對查詢結果爲空的情況也進行緩存,緩存時間設置短一點,或者該key對應的數據insert了之後清理緩存。

2:對一定不存在的key進行過濾。可以把所有的可能存在的key放到一個大的Bitmap中,查詢時通過該bitmap過濾。(布隆表達式

    1. 緩存雪崩
  1. 什麼叫緩存雪崩?

當緩存服務器重啓或者大量緩存集中在某一個時間段失效,這樣在失效的時候,也會給後端系統(比如DB)帶來很大壓力。

  1. 如何解決?

1:在緩存失效後,通過加鎖或者隊列來控制讀數據庫寫緩存的線程數量。比如對某個key只允許一個線程查詢數據和寫緩存,其他線程等待。

2:不同的key,設置不同的過期時間,讓緩存失效的時間點儘量均勻。

3:做二級緩存,A1爲原始緩存,A2爲拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置爲短期,A2設置爲長期(此點爲補充)

    1. 緩存擊穿
  1. 什麼叫緩存擊穿?

* 對於一些設置了過期時間的key,如果這些key可能會在某些時間點被超高併發地訪問,是一種非常“熱點”的數據。這個時候,需要考慮一個問題:緩存被“擊穿”的問題,這個和緩存雪崩的區別在於這裏針對某一key緩存,前者則是很多key。

 

* 緩存在某個時間點過期的時候,恰好在這個時間點對這個Key有大量的併發請求過來,這些請求發現緩存過期一般都會從後端DB加載數據並回設到緩存,這個時候大併發的請求可能會瞬間把後端DB壓垮。

  1. 如何解決?

使用redis的setnx互斥鎖先進行判斷,這樣其他線程就處於等待狀態,保證不會有大併發操作去操作數據庫。

 

if(redis.sexnx()==1){

      //查詢數據庫

      //加入線程

}

  1. 緩存淘汰策略之LRU
    1. Redis內置緩存淘汰策略
  1. 最大緩存

* 在 redis 中,允許用戶設置最大使用內存大小maxmemory,默認爲0,沒有指定最大緩存,如果有新的數據添加,超過最大內存,則會使redis崩潰,所以一定要設置。

* redis 內存數據集大小上升到一定大小的時候,就會實行數據淘汰策略

  1. 淘汰策略

redis淘汰策略配置:maxmemory-policy voltile-lru,支持熱配置

  1. redis 提供 6種數據淘汰策略:
  1. voltile-lru:從已設置過期時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰
  2. volatile-ttl:從已設置過期時間的數據集(server.db[i].expires)中挑選將要過期的數據淘汰
  3. volatile-random:從已設置過期時間的數據集(server.db[i].expires)中任意選擇數據淘汰
  4. allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰
  5. allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
  6. no-enviction(驅逐):禁止驅逐數據
    1. LRU原理

LRULeast recently used,最近最少使用)算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是“如果數據最近被訪問過,那麼將來被訪問的機率也更高”。

    1. LRU實現

最常見的實現是使用一個鏈表保存緩存數據,詳細算法實現如下:

轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消轉存失敗重新上傳取消

1. 新數據插入到鏈表頭部;

2. 每當緩存命中(即緩存數據被訪問),則將數據移到鏈表頭部;

3. 當鏈表滿的時候,將鏈表尾部的數據丟棄。

 

在Java中可以使用LinkHashMap去實現LRU。

 

    1. 分析

【命中率】

當存在熱點數據時,LRU的效率很好,但偶發性的、週期性的批量操作會導致LRU命中率急劇下降,緩存污染情況比較嚴重。

【複雜度】

實現簡單。

【代價】

命中時需要遍歷鏈表,找到命中的數據塊索引,然後需要將數據移到頭部

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章