【初識Redis】

1. 前言 

 1.1 Reis是什麼

 Redis 是 C 語言開發的一個開源的(聽從 BSD 協議)高性能鍵值對(key-value)的   NoSQL的內存數據庫,能夠用做緩存、消息中間件等;具備如下特色:node

 1. 性能優秀,數據在內存中,讀寫速度很是快,支持併發 10W QPS;redis

 2. 單進程單線程,是線程安全的,採用 IO 多路複用機制;算法

 3. 豐富的數據類型,支持字符串(strings)、散列(hashes)、列表(lists)、集合(sets)、有序集合(sorted sets)等;spring

 4. 支持數據持久化。能夠將內存中數據保存在磁盤中,重啓時加載;數據庫

 5. 主從複製,哨兵,高可用;json

能夠用做分佈式鎖。能夠做爲消息中間件使用,支持發佈訂閱;緩存

 1.2 Redis支持的功能

 1. 緩存: 提高數據的訪問性能安全

 2. 可用做低配版的消息中間件,支持發佈訂閱springboot

 3. 可用作分佈式鎖服務器

 4. 能夠實現session共享

 1.3 與memoryCache對比

對比內容

Redis

memoryCache
存儲方式  Redis 支持持久化 Memcache不支持持久化,會把數據所有存在內存,很難解決緩存雪崩的問題
數據類型  Redis 支持五種數據類型 Memcache 對數據類型的支持簡單,只支持簡單的 key-value
底層模型

Redis 直接本身構建了 VM 機制

由於通常的系統調用系統函數的話

會浪費必定的時間去移動和請求

調用系統
Value大小 Redis 能夠達到 1GB Memcache 只有 1MB

2. Redis內存管理

2.1 redisObject

Redis對象的類型、內部編碼、內存回收、共享對象等功能,都須要redisObject支持

這樣設計的好處:能夠針對不一樣場景,對5種經常使用的數據類型設置多種不一樣的數據結構實現,從而優化對象在不一樣場景下的使用效率;

type: 表明一個 value 對象具體是何種數據類型。

encoding: 不一樣數據類型在 redis 內部的存儲方式。

  好比:type=string 表明 value 存儲的是一個普通字符串,那麼對應的 encoding 能夠是 raw 或者是 int。

  若是是 int 則表明實際 redis 內部是按數值型類存儲和表示這個字符串的。

  固然前提是這個字符串自己能夠用數值表示,好比:"123" "456"這樣的字符串。

vm字段: 只有打開了 Redis 的虛擬內存功能,此字段纔會真正的分配內存,該功能默認是關閉狀態的。

Redis 使用 redisObject 來表示全部的 key/value 數據是比較浪費內存的

固然這些內存管理成本的付出主要也是爲了給 Redis 不一樣數據類型提供一個統一的管理接口,實際做者也提供了多種方法幫助咱們儘可能節省內存使用。

2.2 基礎命令

  服務端啓動:redis-server redis.config

  客戶端啓動: redis-cli -p [服務器端口] -h [服務端ip]

1. 查看全部的key:keys *

2. 設置過時時間:set username nick EX 10  // ttl username 查看key剩餘時間

     

3. 選擇庫:select 1 // redis有16個庫0~15,能夠選擇不一樣的庫存儲數據,redis集羣默認用0號庫

4. 判斷key是否存在:exits username  

5. 刪除key:del username

6. 查看key類型: type username

7. 數據持久化: save //保存在dump.rdb文件

8. 清空redis全部數據:flushall //flushdb爲清空分庫的數據,不刪除dump.rdb

9. 單節點批量操做:mset/mget  //mset username nick mset address dg

10. 冷點數據設置超時清除:expire username 100 // expire爲秒維度,pexpire爲毫秒維度 

11. 追加數據:若是 key 已經存在而且是一個字符串,append  key value

2.3 五種數據類型

  2.3.1 String

String類型的數據結構,是key-value形式,value爲字符串/json串

1. 增(原生redis命令):set user1  {name:nick, age:15, gender:male}

2. 查(原生redis命令):get user1

3. 改(原生redis命令):set user1 {name:nick, age:15, gender:male}

4. 刪(原生redis命令):del user1

5. 記步器:incrby/decrby key //對value進行自增/自減,例如統計在線人數; 

  2.3.2 Hash

Hash類型的數據結構是key-value形式,每一條value有是key-value形式

1. 增(原生redis命令):

  hset user1 name nick age 10 gender male //name age gender單條添加

  hmset user1 name nick age 10 gender male   //3個屬性批量添加

  hincrby user1 age //age自增

2. 查(原生redis命令):

  hget user1 name //獲取key爲user1指向的name這個key指向的數據

  hmget user1 name age gender   //批量獲取數據

     hgetall user1 // 獲取key爲user1指向的全部數據

  hkeys user1 // 獲取key爲user1指向的全部的hkey字段

  hlen user1 //獲取key爲user1指向的數據的個數

3.  改(原生redis命令):hset user1 name nick1

4. 刪(原生redis命令):hdel user1 name

  2.3.3 List

List類型字符串列表,按照插入順序排序。能夠添加一個元素到列表的頭部(左邊)或者尾部(右邊)

 

1. 增(原生redis命令):

  LPUSH key value [value ...]  // lpush nameList jack // 從左側push

  LINSERT key BEFORE|AFTER pivot value   // linsert nameList before jerry tom//從左邊,在jerty前面插入tom

  LTRIM key begin end // 保留鏈表範圍內的數據元素

  rpoplpush key1 key2 // 原子級操做,兩個鏈表數據交互,從key1刪除,key2備份

2. 查(原生redis命令):

  LPOP key // lpop key  從鏈表中獲取數據

  LRANGE key start stop // lrange nameList 0 -1 查看nameList中全部元素的個數

3. 改(原生redis命令):LSET key index value // lset nameList 1 nick2,修改nick->nick2

4. 刪(原生redis命令):LREM key count value // lrem key count value 刪除count個 數據爲value的元素 count取0時,所有刪除value

  2.3.4 Set

Set集合用來保存多個字符串元素,和list不用的是不容許有重複元素,而且無序

 

應用場景:用戶的興趣、愛好、關注人列表、粉絲列表等

1. 增(原生redis命令):

  SADD key member [member ...] // sadd nameList jack nick tom jerry

2. 查(原生redis命令):

  SRANDMEMBER key // srandmember nameList 隨機獲取一條數據,

  SMEMBERS key  //  smembers  nameList 查看nameList key的全部數據

3. 刪(原生redis命令):

  SREM key member // srem nameList nick

4. 判斷元素是否在集合中:

  SISMEMBER key member // sismember nameList jack

5. 不一樣集合間的交、並、差集

  SINTER key [key ...] // sinter nameList nameList2 

  SUINON key [key ...] // sinter nameList nameList2 

  SDIFF key [key ...] // sinter nameList nameList2  以第一個key爲主

  2.3.5 ZSet

ZSet在Set集合的基礎之上綁定了一個score做爲排序的依據

1. 增(原生redis命令):

  ZADD key [NX|XX] [CH] [INCR] score member [score member ...]

  zadd result 100 jack  //100爲score

  ZINCRBY key increment member // zincrby result 50 jack 增長jack成員的分數

2. 查(原生redis命令):

  ZCARD key // 查看key裏面數據的個數

  ZSCORE key member // zscore result jack 查當作員的score

  ZRANGE key start stop [WITHSCORES]  // zrange result 0 -1 查看排名

  ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] //根據score篩選

  ZRANK key member //計算成員的score排名

 3. 刪(原生redis命令):

  ZREM key member [member ...] // zrem result jack 刪除某個成員

  ZREMRANGEBYRANK key start stop // 刪除指定排名內的升序元素

  ZREMRANGEBYSCORE key min max // 刪除score在[min, max]範圍的元素

  4. 不一樣集合間的交、並、差集

  ZINTERSTORE destination numkeys key [key ...]  //  zinterstore out 2 result result1,destination表示輸出的ZSet  交集的score相加

3. Jedis客戶端

3.1 redis配置文件

 1. bind 127.0.0.1 ###指定redis只接收來自該IP地址的請求,若是不進行設置,將處理全部請求

 2. protected-mode yes ###是否開啓保護模式,默認開啓;若是關閉,外部網絡能夠直接訪問;若是開啓,則須要bind ip或者設置訪問密碼(安全考慮)

 3. port 6379  ###redis監聽的端口

 4. tcp-backlog 511 ###默認511,此參數標識肯定了TCP鏈接中已完成隊列(完成三次握手以後)的長度,能夠理解爲進程尚未accept的TCP鏈接的隊列,tcp-backlog必須小於等於Linux系統定義的/proc/sys/net/somaxconn值

     Linux的somaxconn默認爲128,當系統併發量大,而且客戶端速度緩慢的時候,能夠將這兩個參數一塊兒參考設置,建議修改成2048,在/etc/sysctl.conf中添加 net.core.somaxconn=2048,而後終端執行sysctl -p命令生效

 5. timeout 0 ###設置客戶端空閒超過timeout時,redis服務端會斷開鏈接,爲0時表示服務端不會主動斷開鏈接,此值不能小於0

 6. tcp-keepalive 300 ###redis3.2之後默認爲300秒,配置上這個參數以後,對於一些沒有正常關閉的客戶端,也能夠及時關閉,鏈接超時受tcp-keepalive和另外三個參數影響:

     tcp_keepalive_time default 7200 seconds

     tcp_keepalive_probes default 9

     tcp_keepalive_intvl default 75 seconds

     鏈接超時公式爲: tcp_keepalive_time+tcp_keepalive_intvl*tcp_keepalive_probes=7895s=131.58min  

 7. daemonize yes 

 8. pidfile /var/run/redis/redis.pid ####redis的進程文件

 9. loglevel notice ###服務端日誌級別,包括debug(不少信息,方便開發、測試),verbose(許多有用的信息,可是沒有debug級別信息多),notice(適當的日誌級別,適合生產環境),warn(只有很是重要的信息)

3.2. redis客戶端、服務端啓動

 基於上述配置文件的修改項:bind、port 

 1. 啓動redis服務端

     redis-server redis.config

 2. 啓動客戶端

     redis-cli redis-cli -h [host_ip]  -p  [port]

3. 中止redis服務端

    登陸客戶端執行:redis-cli -h [host_ip] -p [port] shutdown

3.3 Jedis代碼實踐

  3.3.1 Jedis鏈接池鏈接

1. 添加maven依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-redis</artifactId>
        <version>1.4.7.RELEASE</version>
    </dependency>
</dependencies>

2. application.properties配置

spring.redis.sharedpool.nodes=192.168.75.*:6739,192.168.75.*:6780,192.168.75.*:6381
spring.redis.sharedpool.maxTotal=200
spring.redis.sharedpool.maxIdle=10
spring.redis.sharedpool.minIdle=2

3. springboot管理redis鏈接池

@Configuration
@ConfigurationProperties("spring.redis.sharedpool")
@Setter
@Getter
public class SharedJedisConfigPool {

    private List<String> nodes; //[192.168.75.132:6739,192.168.75.132:6780,192.168.75.132:6381]
    private Integer maxTotal;
    private Integer maxIdle;
    private Integer minIdle;

    @Bean
    public ShardedJedisPool sharedPoolInit() {
        List<JedisShardInfo> infoList = new ArrayList<>();
        for (String node : nodes) {
            String ip = node.split(":")[0];
            int port = Integer.parseInt(node.split(":")[1]);
            infoList.add(new JedisShardInfo(ip, port));
        }
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        return new ShardedJedisPool(config, infoList);
    }
}

4. 鏈接池測試

class DemoApplicationTests {
    @Autowired
    private ShardedJedisPool pool;
    @Test
    public void func4() {
        ShardedJedis jedis = pool.getResource();
        jedis.set("user", "nick");
    }
}

  3.3.2 redis緩存邏輯

class DemoApplicationTests {
    @Autowired
    private ShardedJedisPool pool;
    @Test
    public void func1() {
        String key = "04f2c34c";
        ShardedJedis jedis = pool.getResource();
        if (jedis.exists(key)) { //redis緩存有數據
            String value = jedis.get(key);
        } else {
            String key2 = "04f2c34c";//查詢數據庫
            jedis.set(key2, "nick");
        }
    }
}

  3.3.3 SharedJedis分片對象

1. 舊版redis中一般使用客戶端分片來解決水平擴容問題,即啓動多個redis服務端,客戶端決定key交由哪一個節點存儲,下次客戶端直接到該節點讀取key

2. 能夠實現將整個數據分佈存儲在N個數據庫節點中,每一個節點只存放總數據量的1/N。

3. 對於須要擴容的場景來講,在客戶端分片後,若是想增長節點,須要對數據進行手工遷移,在遷移時爲了保證數據一致性,須要將集羣暫時下線,相對比較複雜

自定義簡單版分片規則以下: 

n個redis服務端幾乎均勻存儲,hash取餘,散列,只要有散列就會有數據傾斜

 key.hashCode()結果可正可負

 key.hashCode()&Integer.MAX_VALUE結果必定爲正數,key和結果惟一對應

 key.hashCode()&Integer.MAX_VALUE%n

4. 分片計算

4.1 自定義分片

 如3.3.3小節討論,在使用redis集羣時,能夠經過自定義的簡單版的hash散列方式分片,可是有如下兩個缺點,所以引出hash一致性算法

 1. 數據傾斜不可避免,只要有hash散列,就必定會有數據傾斜,不可能徹底均勻;

 2. 擴容、縮容時,數據遷移量巨大★

 

4.2 hash一致性

SharedJedis實現了分片底層的算法——hash一致性(解決了取餘算法再擴縮容時,數據遷移量過大的問題);爲何?redis的hash邏輯爲:將全部節點信息hash散列到0~2^32-1區間

4.2 hash一致性問題

 引入問題:

 1. 數據傾斜:

     若是節點的數量不多,而hash環空間很大( 0 ~ 2^32),直接進行一致性hash會致使節點在環上的位置會很不均勻,擠在某個很小的區域。最終對分佈式集羣的每一個實例上儲存的緩存數據量不一致,會發生嚴重的數據傾斜;

 2. 緩存雪崩

     若是每一個節點在環上只有一個節點,那麼能夠想象,當某一集羣從環中消失時,它本來所負責的任務將所有交由順時針方向的下一個集羣處理。

     例如當6379退出時,它本來所負責的緩存將所有交給6380處理。這就意味着6380的訪問壓力會瞬間增大。若是6380由於壓力過大而崩潰,那麼更大的壓力又會向6381壓過去,最終服務壓力就像滾雪球同樣越滾越大,最終致使雪崩

 引入虛擬節點

    解決上述兩個問題最好的辦法就是擴展整個環上的節點數量,所以咱們引入了虛擬節點的概念。一個實際節點將會映射多個虛擬節點,屢次hash,這樣Hash環上的空間分割就會變得均勻。

 

5. 高可用

5.1 主從複製

在同一臺虛擬機上構建1主3從的主從模式,一個主節點通常最多6個從節點;

0. 主節點redis配置文件bind要綁定0.0.0.0表示全部鏈接均可以訪問,沒有綁定的話,不能夠訪問主;

1. 建立從節點redis啓動配置文件,redis02.config、redis03.config,並修改端口爲6380、6381

2. 啓動3個redis-server實例 redis-server redis.config、redis-server redis02.config、redis-server redis03.config

3. 主從關係建立,規定6379節點master、6380節點做爲slave 、6381節點做爲slave;

4. 登陸查看節點狀態的命令,以客戶端登陸6379節點爲例:6379> info replication

 4. 經過配置文件定義主從關係

 5. 經過命令掛在主節點,命令以下:從節點> slaveof masterip masterport

 6. 本處經過命令方式指定主從關係,客戶端登陸6080、6081服務端,執行第五步命令

  7. 查看637九、638一、6382節點的replicatinon信息,

  5.1.1 主從關係測試

測試過程:

主節點6379寫數據,從節點6380讀數據

1. 登陸主節點客戶端 set name nick

2. 登陸從節點客戶端 get name

3. 登陸從節點 flushal   執行失敗

4. 主節點shutdown

測試結果:

從節點配置默認爲只讀模式read-only,主節點宕機,從節點不會自動變爲主節點,從節點info replication狀態變爲以下:

master_link_status:down
master_last_io_seconds_ago:-1

5.2 哨兵模式

  5.2.1 什麼是哨兵

 redis-cluster(redis 3.0)出現以前,redis的使用幾乎都是圍繞哨兵模式展開,哨兵過程以下:

 1. 起單獨線程(特殊的redis-cluster)開啓對主從結構的監聽

 2. 監聽主,從主節點調用命令info replication,獲取主從結構的全部信息,保存在內存,每1秒鐘發起一次心跳檢測(RPC遠程通訊協議),一旦主節點宕機,哨兵集羣發起投票選舉,過半票數肯定結果,增長可信;

 3. 哨兵容忍度:容許宕機的個數是哨兵的容忍度,爲了保障多票選取,5個哨兵集羣節點的容忍度爲2,6個哨兵集羣的容忍度爲2;2n-1個集羣和2n個集羣的容忍度是同樣的,所以哨兵集羣都是奇數個(少用一個資源)

  5.2.2 搭建哨兵集羣

模式:3個哨兵節點,管理6379,6380,6382主從結構,過程以下:

 1. 修改哨兵的模板配置文件(sentinel.conf),配置3個哨兵端口:26379,26380,26381 

 2. 釋放保護模式註釋,修改成no

 3. 修改監聽主節點的配置核心內容:sentine monitor mymaster [master_ip] [master_port]  [num] 

       3..1 mymaster是自定義名稱 標識當前哨兵監聽的主從結構的代號,多個哨兵監聽同一個主從結構的話,此處要保持一致

  3.2 num表示爲主觀下限票數,當哨兵集羣不斷宕機時,最少要剩下的節點數量,和宕機容忍度有關

 4. 拷貝三份,分別作如上修改

 5. 啓動哨兵(啓動以前肯定主從集羣正常):redis-sentinel sentine.conf

 6. 哨兵沒有restart等操做

 7. kill掉主節點,查看哨兵日誌

 8. 哨兵發現主節點宕機,開始投票,查看日誌num,肯定宕機,投票選取新的節點做爲主節點,主節點一旦恢復,則會也從節點角色加入集羣,繼續提供服務

 9. 重啓哨兵日誌時,刪除尾部日誌,避免影響本次啓動

  5.2.2 Jedis鏈接哨兵集羣

 1. application.properties配置

spring.redis.sentinel=192.168.75.132:26379,192.168.75.132:26380,192.168.75.132:26381
spring.redis.sharedpool.maxTotal=200
spring.redis.sharedpool.maxIdle=10
spring.redis.sharedpool.minIdle=2

2. springboot管理sentinel鏈接池

@Configuration
@ConfigurationProperties("spring.redis.sentinel")
@Getter
@Setter
public class SentinelConfig {

    private List<String> nodes;
    private Integer maxTotal;
    private Integer maxIdle;
    private Integer minIdle;

    @Bean
    public JedisSentinelPool sentinelPoolInit() {
        //蒐集哨兵集羣信息
        Set<String> sentinelSet = new HashSet<>();
        for (String node : nodes) {
            String ip = node.split(":")[0];
            int port = Integer.parseInt(node.split(":")[1]);
            sentinelSet.add(new HostAndPort(ip, port).toString());
        }
        //鏈接池配置對象
        GenericObjectPoolConfig config = new GenericObjectPoolConfig();
        config.setMaxTotal(maxTotal);
        config.setMaxIdle(maxIdle);
        config.setMinIdle(minIdle);
        //構建哨兵管理對象
        return new JedisSentinelPool("mymaster", sentinelSet, config);
    }
}

3. 鏈接池測試

@SpringBootTest
class DemoApplicationTests {
@Autowired
private JedisSentinelPool sentinelPool; @Test public void func5() { HostAndPort masterIP = sentinelPool.getCurrentHostMaster(); Jedis jedis = sentinelPool.getResource(); //哨兵模式只能操做主節點 String name = jedis.get("name"); } }

5.3. redis-cluster★

  5.3.1 什麼是redis集羣模式

 5.3.2 構建redis集羣

相關文章
相關標籤/搜索