天天看點

【狂神說】REDIS上課筆記Redis

狂神說redis課程連結

Redis

NoSQL的四大分類

KV鍵值對

  • Redis

文檔型資料庫(bson格式,和json一樣)

  • MongoDB
    • 基于分布式檔案存儲的資料庫,主要用來處理大量的檔案
    • 介于關系型資料庫和非關系型資料的中間産品,是非關系型資料庫中功能最豐富,最像關系型資料庫
  • ConthDB

列存儲資料庫

  • HBASE
  • 分布式檔案系統

圖關系資料庫

  • 不是存圖形,存放的是關系
  • Neo4j
四種資料庫的特點
【狂神說】REDIS上課筆記Redis

Redis入門

Redis是什麼?

Redis(Remote Dictionary Server)遠端字典服務

開源的,使用ANSI C語言編寫

持久化政策:RDB、AOF

Redis預設安裝路徑:/usr/local/bin

測試性能

redis-benchmark是一個壓力測試工具

【狂神說】REDIS上課筆記Redis
# 簡單測試:100個并發連接配接 100000請求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
           
【狂神說】REDIS上課筆記Redis

基礎知識

Redis預設有十六個資料庫,預設使用第0個資料庫,可以使用select進行切換

select 3  # 切換第三個資料庫

DBSIZE 	  # 檢視DB大小
           

Redis是單線程的,Redis是基于記憶體操作,CPU并不是Redis的性能瓶頸,Redis的瓶頸是機器的記憶體和網絡帶寬

Redis為什麼單線程還這麼快

  1. 誤區1:高性能的伺服器一定是多線程的
  2. 誤區2:多線程(CPU上下文會切換)一定比單線程效率高

核心:Redis是将所有的資料全部放在記憶體中的,是以說使用單線程區操作效率就是最高的,多線程CPU上下文會切換,這是耗時的操作。對于記憶體系統來說,沒有上下文切換效率就是最高的

基礎操作

# 檢視資料庫所有的key
keys *

# 清除目前資料庫
flushdb

# 清除全部資料庫的内容
FLUSHALL
           

五大資料類型

redis-key

# 判斷對應的Key是否存在
EXISTS keyName

# 從指定的資料庫中移除key
move keyName DBNum

# 為key設定過期時間,機關為秒
EXPIRE keyName seconds
# 檢視目前key剩餘的時間
ttl keyName

# 檢視目前key的類型
type keyName

# 設定值
set keyName value

# 獲得值
get keyName
           

String

# 追加字元串,如果目前key不存在,就相當于set key
APPEND keyName value

# 擷取字元串長度
STRLEN keyName

# 自增1
incr keyName

# 自減1
decr keyName

# 設定步長,指定增量
INCRBY keyName num

# 設定步長,指定減量
DECRBY keyName num

# 擷取指定範圍的字元串[start,end]
GETRANGE keyName start end 
GETRANGE keyName 0 -1   ## 擷取全部的字元串,和get key是一樣的

# 用string1替換指定位置開始的字元串
SETRANGE keyName start string1
key2:12345678
SETRANGE key2 1 xx
key2: 1xx45678

# 設定過期時間
setex keyName seconds value
setex key3 30 "hello" ##設定key3的值為hello,30秒後過期

# 不存在再設定(分布式鎖中常常使用)
setnx key value ##如果key不存在,建立key;如果存在,建立失敗

# 批量設定和擷取
mset key1 value1 key2 value2.......
mget key1 key2 key3

# 批量不存在再設定
msetnx key1 value1 key2 value2......  # 是一個原子性的操作,要麼一起成功,要麼一起失敗

# 設定json對象
set key:num {}

set user:1 {name:zhangsan, age:3} #設定一個user:1對象,值為json格式來儲存對象

這裡的key是一個巧妙的設定,user:{id}:{filed}

#先get然後再set
getset keyName value ##如果值不存在,則傳回null;如果存在,則擷取原來的值,并設定新的值

           

使用場景:

  • 計數器
  • 統計多機關的數量
  • 粉絲數
  • 對象緩存存儲

List

基本的資料類型,清單

# 将一個值或多個值插入到清單的頭部(左)
LPUSH listName value1......

# 将一個值或者多個值插入到清單的尾部(右)
RPUSH listName value1......

# 擷取list中的值
LRANGE listName 0 -1

# 擷取指定區間的list的值[start,end]
LRANGE listName start end

#移除清單的第一個元素
LPOP listName

# 移除清單的最後一個元素
RPOP listName

#通過下标擷取list中的某一個值
LINDEX listName num

# 傳回清單的長度
LLEN listName

# 移除清單中指定個數的值
LREM listName num value

LREM list 1 one ## 移除list清單中的1個one

# 通過下标截取指定的長度,這個list已經被改變了,隻剩下截取的元素[start,end]
LTRIM listName start end

# 移除清單中的最後一個元素,并将它移動到新的清單中
RPOPLPUSH listName newListName

# 将清單中指定下标的值替換為另外一個值。如果清單不存在就會報錯;如果存在,則更新目前下标的值
LSET listName num newValue

# 将某個具體的value插入到清單中的某一個元素的前面或者後面
LINSERT listName before|after value newValue
           

使用場景:

  • 消息排隊
  • 消息隊列(Lpush Rpop)
  • 棧(Lpush Lpop)
小結:
  • List實際上是一個連結清單
  • key不存在,建立新的
  • key存在,新增内容
  • 如果移除了所有值,空連結清單,也代表不存在
  • 在兩邊插入或者改動值,效率最高

set(集合)

set中的值是不能重複,且無序的

# 向集合中添加元素
sadd setName value

# 檢視指定set的所有值
SMEMBERS setName

# 判斷某一個值是不是在set集合中
SISMEMBER setName value

# 擷取集合中的元素個數
scard setName

# 移除中set集合中的指定元素
srem setName value

# 随機抽選一個元素
SRANDMEMBER setName

# 随機抽選出指定個數的元素
SRANDMEMBER setName num

# 随機删除集合中的元素
SPOP setName

# 數字集合類
## 差集
SDIFF set1 set2
## 交集 (可以實作共同好友)
SINTER set1 set2
## 并集
SUNION set1 set2
           

應用場景:

  • 共同關注
  • 共同愛好
  • 二度好友
  • 推薦好友

Hash

Map集合,key-map,值是map集合

# 存值
hset hashName field1 value1
## hset myhash field1 luoqing

# 取值
hget hashName fieldName
## hget myhash field1
## luoqing

# 存入多個key-value
HMSET hashName field1 value1 field2 value2......

# 擷取多個字段值
HMGET hashName field1 field2......

# 擷取全部的資料
HGETALL hashName

# 删除Hash指定的key字段,對應的value值就沒有了
hdel hashName fieldName
## hdel myhash field1 
""

# 擷取指定的hash長度,傳回鍵值對的個數
hlen hashName

# 判斷hash中指定字段是否存在,非0為存在
HEXISTS hashName fieldName

# 隻擷取所有的field
hkeys hashName

# 隻擷取所有的value
hvals hashName

# 指定增量,若num為正則增加;若為負則減少
HINCRBY hashName fieldName num

# 不存在再設定
HSETNX hashName fieldName value ## 如果不存在則設定,如果存在則不能設定
           

應用場景:

  • 使用者資訊等經常變動的資訊
  • hash更适合對象的存儲,string更适合字元串的存儲

Zset(有序集合)

在set的基礎上增加了一個值,set setName score1 value1

# 添加一個值
zadd setName keyName value

# 添加多個值
zadd setName key1 value1 key2 value2

# 檢視全部的元素
ZRANGE setName 0 -1

# 實作排序
## 1. 添加三個使用者:zadd salary 2500 xiaohong; zadd salary 5000 zhangsan; zadd 500 luoqing
## 元素從低到高排列
ZRANGEBYSCORE setName min max

###  顯示全部使用者,從負無窮到正無窮
ZRANGEBYSCORE salary -inf +inf
> luoqing
> xiaohong
> zhangsan
### 顯示全部使用者,并且附帶成績
ZRANGEBYSCORE salary -inf +inf withscore
> luoqing
> 500
> xiaohong
> 2500
> zhangsan
> 5000
### 顯示從最小到2500的元素
ZRANGEBYSCORE salary -ing 2500 [withscore]

## 元素從大到小進行排序
ZREVRANGE setName 0 -1

# 移除有序集合中的指定元素
ZREM setName value

# 擷取有序集合中的個數
ZCARD setName

# 擷取指定區間的成員數量
ZCOUNT setName start end
> zadd myset 1 hello 2 world 3 luoqing
> zcount myset 1 3
3

           

應用場景:

  • 存儲班級成績表
  • 普通消息,重要消息。帶權重進行判斷
  • 排行榜應用實作,取Top N測試

三種特殊資料類型

geospatial(地理位置)

朋友的定位,附近的人(獲得所有附近的人的位址,定位,通過半徑來查詢),打車距離計算

這個功能可以推算地理位置的資訊,兩地之間的距離

# 添加地理位置
GEOADD key longitude latitude member [longitude latitude member ...]
> geoadd china:city 116 39 beijing 114 22 shengzhen
(Integer)2

# 擷取目前定位:一定是一個坐标值
GEOPOS key member [member2 ...]

# 傳回兩個給定位置的距離
# 指定機關的參數 unit 必須是以下機關的其中一個:
# m 表示機關為米。
# km 表示機關為千米。
# mi 表示機關為英裡。
# ft 表示機關為英尺。
GEODIST key member1 member2 [unit]
> GEODIST china:city beijing shanghai km
1080

           
GEORADIUS以給定的經緯度為中心,找出某一半徑内的元素

附近的人(獲得所有附近的人的位址,定位)通過半徑來查詢

獲得指定數量的人,200

所有資料都應該錄入資料庫中,才讓結果更加準确

# 以110 30 這個經緯度為中心,尋找方圓1000KM内的城市
GEORADIUS china:city 110 30 1000 km

# 顯示到中心位置的距離
GEORADIUS china:city 110 30 500 km withdist
>chongqing
>341
>chengdu
>483

# 顯示他人的定位資訊
GEORADIUS china:city 110 30 500 km withcoord
>chongqing
	>106
	>29
	
# 篩選出指定的結果
GEORADIUS china:city 110 30 500 km withdist withcoord count 1 # 隻顯示一個結果

# 找出位于指定元素周圍的内容
GEORADIUSBYMEMBER china:city beijing 1000 km # 找出位于北京1000KM内的城市

# 傳回一個或多個位置元素的Geohash表示
GEOHASH china:city beijing chongqing	# 該指令将傳回11個字元串的Geohash字元串,将二維的經緯度轉換為一維的字元串,如果兩個字元串越接近,表示距離越近

           

GEO底層的實作原理其實就是ZSet,可以使用ZSet指令來操作GEO

hyperloglog

基數統計的算法

優點:占用的記憶體是固定的,264不同的元素的基數,隻需要12KB的記憶體

0.81%錯誤率,如果不允許容錯,就是用set或者自己的資料類型即可

例:統計網頁的UV(一個人通路一個網站多次,但是還是算作一個人)

傳統的方法:set儲存使用者的ID,就可以統計set中的元素數量作為标準判斷

# 添加
PFADD keyName member1 [member2....]

# 統計基數數量
PFCOUNT keyName

# 合并兩組元素
PFMERGE newKeyName keyName1 keyName2	# keyName1 keyName2 --> newKeyName
           

bitmap

位存儲

統計使用者資訊,活躍,不活躍,登入,未登入,打卡,365天打卡,兩個狀态的都可以使用bitmap

bitmap位圖,都是操作二進制位來進行記錄的,就隻有0和1兩個狀态

使用bitmap來記錄sign的一周打卡,0-6表示周一到周日,0未打卡,1打卡

周一:1 周二:0…

【狂神說】REDIS上課筆記Redis
# 檢視某一個天是否打卡
getbit sign 3
>1
getbit sign 6
>0

# 統計操作,統計一周打卡的天數
bitcount sign
>3
           

事務

Redis單條指令是保證原子性的,但是事務不保證原子性

Redis事務本質:一組指令的集合,一個事務中的所有指令都會被序列化,在事務執行過程中,會按照順序執行

一次性,順序性,排他性

Redis事務沒有隔離級别的概念

所有的指令在事務中并沒有直接被執行,隻有發起執行指令時才會被執行(Exec)

Redis的事務:

  1. 開啟事務(multi)
  2. 指令入隊(…)
  3. 執行事務(exec)

正常執行事務

【狂神說】REDIS上課筆記Redis

放棄事務

【狂神說】REDIS上課筆記Redis

編譯性異常(代碼有問題,指令有錯),事務中所有的指令都不會被執行

【狂神說】REDIS上課筆記Redis

運作時異常,如果事務中某一條指令存在邏輯問題,那麼執行指令的時,其他指令可以正常執行,錯誤指令抛出異常

【狂神說】REDIS上課筆記Redis

監控!Watch(面試)

悲觀鎖:

  • 認為什麼時候都會出問題,無論做什麼都會加鎖

樂觀鎖:

  • 認為什麼時候都不會出問題,是以不會上鎖
  • 更新資料的時候去判斷一下,在此期間是否有人修改過這個資料
  • 擷取version,更新的時候比較version

測試多線程修改值,使用watch可以當做Redis的樂觀鎖操作,可以使用unwatch解鎖

【狂神說】REDIS上課筆記Redis

事務執行失敗後操作:

  1. 如果發現事務執行失敗,就先使用unwatch解鎖
  2. 再使用watch擷取最新的值,再次監視,select version
  3. exec,比對監事的值是否發生了變化,如果沒有變化,那麼可以執行成功,如果變化則執行失敗

Jedis

Redis官方推薦的Java連接配接開發工具,使用Java操作Redis中間件,如果你要使用Java操作Redis,那麼一定要對Jedis十分的熟悉

1、導入依賴

<!--    導入Jedis的包    -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>
<!--   fastjosn     -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>
           

2、編碼測試

  • 連接配接資料庫
  • 操作指令
  • 斷開連接配接
package com.kuangshen;


import redis.clients.jedis.Jedis;

/**
 * Created by 羅星
 * 2020/10/20 15:07
 */
public class TestPing {
    public static void main(String[] args) {
//        1.建立Jedis對象
        Jedis jedis = new Jedis("127.0.0.1", 6379);
//        jedis所有的指令就是之前學習過的所有指令
        System.out.println(jedis.ping()); // 輸出PONG
        //關閉連接配接
        jedis.close();
    }
}
           

事務

package com.kuangshen;


import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

/**
 * Created by 羅星
 * 2020/10/20 15:07
 */
public class TestPing {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello", "world");
        jsonObject.put("name", "kuangshen");
        //開啟事務
        Transaction multi = jedis.multi();
        String result = jsonObject.toJSONString();
        try {
            //将指令添加到隊列
            multi.set("user1", result);
            multi.set("user2", result);
            //執行事務
            multi.exec();
        } catch (Exception e) {
            multi.discard();    //異常則放棄事務
            e.printStackTrace();
        } finally {
            jedis.close();      //關閉連接配接
        }
    }
}
           

SpringBoot整合

springboot2之後,原來使用的Jedis被替換為了lettuce

Jedis:采用的直鍊模式,多個線程操作的話是不安全的,如果想要避免不安全,使用Jedis pool連接配接池,BIO模式

lettuce:采用netty,執行個體可以在多個線程中進行共享,不存線上程不安全的情況。NIO模式

源碼分析

【狂神說】REDIS上課筆記Redis

測試

1、導入依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
           

2、配置連接配接

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
           

3、測試

package com.luoqing.kuangshenredisspringboot;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;

@SpringBootTest
class KuangshenRedisSpringbootApplicationTests {

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    void contextLoads() {
        //redisTamplate 操作不同的資料類型,ops和所學指令是一樣的
        //opsForValue   操作字元串   類似String
        //opsForList    操作List  類似List
        //opsForSet
        //opsForHash
        //opsForGeo
        //opsForZSet
        //opsForHyperLogLog

        //除了基本的操作,常用的方法都可以直接通過redisTemplate操作,比如事務和基本的CRUD

        //擷取Redis的連接配接對象
//        RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//        connection.flushDb();
//        connection.flushAll();

        redisTemplate.opsForValue().set("user1", "luoqing");
        redisTemplate.opsForValue().get("user1");
    }

}
           

在開發中,所有的pojo都會序列化

在企業開中,我們一般不會直接使用原生的API。自己編寫工具類封裝原生API

自定義RedisTemplate

package com.luoqing.kuangshenredisspringboot.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import org.springframework.beans.PropertyAccessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Created by 羅星
 * 2020/10/20 19:15
 * 我們為了自己開發友善,一般直接使用<Stirng, Object>
 */
@Configuration
public class RedisConfig{
//    自定義RedisTemplate
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        //Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //String序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key采用String序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value采用Jackson的序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的value采用Jackson的序列化方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;

    }
}

           

Redis.conf詳解

1、機關

【狂神說】REDIS上課筆記Redis

配置檔案機關對大小寫不敏感

2、包含其他配置檔案

【狂神說】REDIS上課筆記Redis

3、網絡配置

bind 127.0.0.1	# 允許通路的IP

protectd-mode yes	# 保護模式

port 6379	# 端口設定
           

4、通用配置

daemonize yes	# 是否以守護程序的方式運作,預設NO

pidfile /var/run/redis_6379.pid	# 如果以背景的方式運作,就需要指定一個pid檔案

loglevel notice	# 适用于生産環境

logfile “”	# 日志檔案位置,如果為空則為标準輸出

databases 16	#資料庫數量,預設16個資料庫

always-show-logo yes	# 是否顯示logo
           

5、快照(持久化,在規定的時間内,執行了多少次操作則會持久化到檔案.rdb中)

save 900 1	# 900秒内如果至少有1個key進行了修改,就進行持久化操作
save 300 10	# 300秒内如果至少有10個key進行了修改,就進行持久化操作
save 60 10000	# 60秒内如果至少有10000個key進行了修改,就進行持久化操作

stop-writes-on-bgsave-error yes	# 如果持久化出錯,是否還需要繼續工作

rdbcompression yes	# 是否壓縮rdb檔案,需要消耗一些CPU資源

rdbchecksum yes	# 儲存rdb檔案時進行錯誤的檢查和校驗

dir ./	#rdb檔案儲存的目錄

           

6、REPLICATION(主從複制)

# 先将#删除
replicaof <masterip> <masterport>	# 分别寫入主機的IP和端口号

# 寫入主機的密碼
masterauth <master-password>
           

7、SECURITY(安全)

配置Redis的密碼,預設是沒有密碼

【狂神說】REDIS上課筆記Redis

8、限制CLIENTS

maxclients 10000	# 設定能連接配接上Redis的最大用戶端的數量

maxmemory <bytes>	# 設定最大的記憶體容量

maxmemory-policy noeviction	# 記憶體到達上限之後的處理政策
# 六種政策
1、volatile-lru:隻對設定了過期時間的key進行LRU(預設值) 
2、allkeys-lru : 删除lru算法的key   
3、volatile-random:随機删除即将過期key   
4、allkeys-random:随機删除   
5、volatile-ttl : 删除即将過期的   
6、noeviction : 永不過期,傳回錯誤
           

9、APPEND ONLY模式,aof配置

appendonly no	# 預設是不開啟aof模式,預設是使用rdb方式持久化的,在大部分的情況下,rdb完全夠用

appendfilename "appendonly.aof"	# 持久化的檔案名

#appendfsync always		# 每次修改都會sync(同步),消耗性能
appendfsync everysec	# 每秒執行一次sync(同步),可能會丢失這一秒的數
#appendfsync no			# 不執行同步,這個時候作業系統自己同步資料,速度最快
           

Redis持久化(重點)

RDB(Redis DataBase)

在指定的時間間隔内将記憶體中的資料集快照寫入磁盤,Snapshot快照,回複時是将快照檔案直接讀到記憶體中

在主從複制中,rdb是用來備用的,放在從機上

【狂神說】REDIS上課筆記Redis

Redis會單獨建立一個子程序來進行持久化,會先将資料寫入到一個臨時檔案中,待持久化過程都結束了,在用這個臨時檔案替換上次持久化好的檔案,整個過程中,主程序是不進行任何IO操作的,確定了極高的性能。如果需要進行大規模資料的恢複,且對資料恢複的完整性不是非常敏感,RDB方式要比AOF方式更加高效

缺點:最後一次持久化後的資料可能丢失

預設就是RDB,一般情況下不需要修改配置

RDB儲存的檔案是dump.rdb,可以在配置檔案中快照中進行配置

# 配置儲存的檔案名
dbfilename dump.rdb
           

觸發機制

  1. save的規則滿足的情況下,會自動觸發rdb規則
  2. 執行fllushall指令,會觸發rdb規則
  3. 退出Redis,會産生rdb檔案

備份就自動生成一個dump.rdb

恢複rdb檔案:隻需要将rdb檔案放在Redis的啟動目錄就可以,Redis啟動時會自動檢查dump.rdb,恢複其中的資料

檢視存放位置:

127.0.0.1:6379>config gei dir
dir
/usr/local/bin	# 如果這個目錄下存在dump.rdb,啟動時就會自動恢複其中的資料
           

優點:

  1. 适合大規模的資料恢複
  2. 對資料完整性要求不高

缺點:

  1. 需要一定的時間間隔進行操作,如果Redis意外當機了,這個最後一次修改的資料就會丢失
  2. fork程序的時候,會占用一定的記憶體空間

AOF(Append Only File)

将所有的指令都記錄下來,恢複的時候就把整個檔案全部執行一遍
【狂神說】REDIS上課筆記Redis

以日志的形式來記錄每個寫操作,将Redis執行過的所有指令記錄下來(讀操作不記錄),隻許追加檔案但不可以改寫檔案,Redis啟動之初會讀取該檔案重新建構資料

AOF儲存的是appendonly.aof檔案

開啟後重新開機Redis就可以生效了

如果aof檔案有錯誤,Redis是不能啟動的,我們要使用Redis提供的redis-check-aof工具來修複

# 修複指令
redis-check-aof appendonly.aof
           

修複之後,重新開機就可以恢複資料

優點:

  1. 每次修改都同步,檔案完整性強
  2. 預設開啟每秒同步一次,可能會丢失一秒的資料
  3. 從不同步效率是最高的

缺點:

  1. 相對于資料檔案來說,aof遠大于rdb,修複的速度比rdb慢
  2. aof運作效率比rdb低

Redis釋出訂閱

微網誌、微信關注系統

Redis釋出訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息

Redis用戶端可以訂閱任意數量的頻道

訂閱/釋出消息:

第一個:消息發送者 第二個:頻道 第三個:消息訂閱者

【狂神說】REDIS上課筆記Redis

指令:

這些指令被廣泛用于建構即時通信應用,比如網絡聊天室和實時廣播,實時提醒等
# 訂閱一個或多個符合給定模式的頻道
PSUBSCRIBE pattern [pattern...]

# 檢視訂閱與釋出系統狀态
PUBSUB subcommand [argument [argument...]]

# 将資訊發送到指定的頻道
PUBLISH channet message

# 退訂所有給定模式的頻道
PUNSUBSCRIBE [pattern [pattern...]]

# 訂閱給定的一個或多個頻道的資訊
SUBSCRIBE channet [channet...]

# 退訂給定的頻道
UNSUBSCRIBE [channet [channet...]]
           

使用場景:

  • 實時消息系統
  • 實時聊天(頻道當做聊天室,将資訊回顯給所有人即可)
  • 訂閱,關注系統

稍微複雜的場景就會使用消息中間件MQ

Redis主從複制

概念:

​ 指将一台Redis伺服器的資料複制到其他的Redis伺服器,前者稱為主節點(master/leader),後者稱為從節點(slave/follower);資料複制是單向的,隻能從主節點到從節點。master以寫為主,slave以讀為主

預設情況下,每台Redis伺服器都是主節點,且一個主節點可以有多個從節點(或沒有從節點),但一個從節點隻能有一個主節點

主從複制的作用:

  1. 資料備援:主從複制實作了資料的熱備份,是持久化之外的一種資料備援方式
  2. 故障恢複:當主節點出現問題時,可以由從節點提供服務,實作快速的故障恢複。實際上是一種服務的備援
  3. 負載均衡:在主從複制的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis資料時應用連接配接主節點,讀Redis資料時應用連接配接從節點),分擔伺服器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis伺服器的并發量
  4. 高可用(叢集)基石:主從複制是哨兵模式和叢集能夠實施的基礎

環境配置

隻配置從節點,不用配置主節點

# 檢視目前庫的資訊
info replication
           
【狂神說】REDIS上課筆記Redis

複制多個配置檔案,然後修改對應的資訊

  • 端口
  • pid名字
  • log檔案名
  • dump.rdb檔案名

從機配置

# 選擇哪台作為主機,認老大
SLAVEOF host port	# 在從機上使用這條指令
           
【狂神說】REDIS上課筆記Redis
【狂神說】REDIS上課筆記Redis

真實的主從配置應該寫入配置檔案

細節:

  1. 主機寫,從機隻能讀,主機中的所有資訊和資料都會自動被從機儲存
  2. 主機斷開,從機依舊連接配接到主機,但是沒有寫操作。如果主機恢複了,從機依舊可以直接擷取到主機寫入的資料
  3. 如果是使用指令行配置主從,如果從機重新開機了,就變為主機了。如果再次配置為從機,同樣可以從主機中擷取所有值

複制原理:

Slave啟動成功連接配接到master後會發送一個sync同步指令

master接收到指令,啟動背景的存盤程序,同時收集所有接收到的用于修改資料集的指令,在背景程序執行完畢後,master将傳送整個資料檔案到salve,并完成一次完全同步

全量複制:slave服務在接受到資料檔案後,将其存盤并加載到記憶體中

增量複制:master繼續将新的所有收集到的修改指令依次傳給slave,完成同步

隻要是重新連接配接master,一次完全同步(全量複制)将被自動執行,可以在從節點看到全部資料

層層依賴:

【狂神說】REDIS上課筆記Redis

這時候也可以完成我們的主從複制

如果主機當機,可以使用

SLAVEOF no one

讓目前伺服器變成主機,其他的節點就可以手動連接配接到最新的主節點。如果原master修複,那就需要重新進行配置

哨兵模式

自動選舉master的模式

概述:當master當機之後,根據投票數自動将slave轉換為master

哨兵模式一種特殊的模式,首先Redis提供了哨兵的指令,哨兵是一個獨立的程序,它會獨立運作,原理是哨兵通過發送指令,等待Redis伺服器響應,進而監控正在運作的多個Redis執行個體

【狂神說】REDIS上課筆記Redis

作用:

  1. 通過發送指令,讓Redis伺服器傳回監控其運作狀态,包括master和slave
  2. 當哨兵檢測到master當機,會自動将slave切換成master,然後通過釋出訂閱模式通知其他的從伺服器,修改配置檔案,讓它們切換主機

多哨兵模式:一個哨兵程序對Redis進行監控可能會出現問題,可以使用多個哨兵進行監控,各個哨兵之間還會進行監控

【狂神說】REDIS上課筆記Redis

假設master當機,哨兵1先檢測到這個結果,系統并不會馬上進行failover過程,僅僅是哨兵1主觀的認為master不可用,這個現象稱為主觀下線,當後面的哨兵也檢測到master不可用,并且數量達到一定值時,那麼哨兵之間就會進行一次投票,投票的結果由任意一個哨兵發起,之後進行failover(故障轉移)操作。切換成功後,就會通過釋出訂閱模式,讓各個哨兵把自己監控的從伺服器實作切換主機,這個過程稱為客觀下線

配置:

  1. 配置哨兵配置檔案sentinel.conf
# sentinel monitor 被監控的主機名字 host port num
# myredis為主機名字,可以任意。1是投票機制,當1個sentinel認為master失效時,則正式失效
sentinel monitor myredis 127.0.0.1 6379 1
           
  1. 啟動哨兵
# redis-sentinel 配置檔案位置
redis-sentinel sentinel.conf
           

如果master斷開,就會從從機中随機選擇一個伺服器(有一個投票算法)。如果主機修複了,隻能歸并到新的master下,作為新的master的從機

優點:

  • 哨兵叢集基于主從複制模式,所有的主從複制優點它全有
  • 主從可以切換,故障可以轉移,系統的可用性更好
  • 哨兵模式就會主從模式的更新,手動到自動,更加健壯

缺點:

  • Redis不好線上擴容,叢集容量一旦到達上限,擴容十分麻煩
  • 實作哨兵模式的配置是很麻煩的
哨兵的全部配置
# 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
           

Redis緩存穿透和雪崩(面試高頻,工作常用)

Redis緩存的使用,極大地提升了應用程式和性能和效率,特别是資料查詢方面。但同時也帶來一些問題,其中最重要的就是資料一緻性問題,從嚴格意義上講,這個問題無解。如果對資料的一緻性要求很高,就不能使用緩存
【狂神說】REDIS上課筆記Redis

緩存穿透

概念:

使用者想要查詢一個資料,發現Redis記憶體資料庫中沒有,也就是緩存沒有命中,于是向持久層資料庫查詢,發現也沒有,于是本次查詢失敗。

當使用者很多的時候,緩存都沒有命中,于是都去請求了持久層資料庫,這會給持久層資料庫造成很大的壓力,就出現了緩存穿透

解決方案:

1、布隆過濾器

布隆過濾器是一種資料結構,對所有可能查詢的參數以hash的形式存儲,在控制層先進行校驗,不符合則丢棄,進而避免了對底層存儲系統的查詢壓力

【狂神說】REDIS上課筆記Redis

2、緩存空對象

當存儲層不命中後,即使傳回的空對象也将其緩存起來,同時會設定一個過期時間,之後再通路這個資料将會從緩存中擷取,保護了後端資料源

【狂神說】REDIS上課筆記Redis

這種方法會存在兩個問題:

  • 如果空值能夠被緩存起來,這就意味着緩存需要更多的空間存儲更多地鍵
  • 即使對空值設定了過期時間,還是會存在緩存層和存儲層的資料會有一段時間的不一緻,這對于需要保持一緻性的業務會有影響

緩存擊穿

指一個key非常熱點,不停得扛着大并發,集中對這一個點進行通路,當這個key失效的瞬間,持續的大并發就會穿透緩存,直接請求資料庫,就像在緩存中鑿開了一個洞

當某個key失效的瞬間,有大量的請求并發通路,這類資料一般是熱點資料,由于緩存過期,會同時通路資料庫來查詢最新的資料,并且回寫緩存,會導緻資料庫瞬間壓力過大

解決方案:

1、設定熱點資料永不過期

從緩存層面來看,沒有設定過期時間,是以不會出現熱點key過期後産生的問題

2、加互斥鎖

使用分布式鎖,保證對于每個key同時隻有一個線程區查詢後端服務,其他線程沒有獲得分布式鎖的權限,隻能等待。這種方式将高并發的壓力轉移到分布式鎖,是以對分布式鎖的考驗很大

緩存雪崩

某一個時間段,緩存集中過期失效。或者Redis當機或斷電斷網
【狂神說】REDIS上課筆記Redis

緩存集中過期并不是非常緻命的,比較緻命的緩存雪崩,是緩存伺服器某個節點當機或斷網。因為自然形成的緩存雪崩,一定是在某個時間段集中建立緩存,資料庫也是可以頂住壓力的,無非就是對資料庫産生周期性的壓力而已;而緩存服務節點的當機,對資料庫伺服器造成的壓力是不可預知的,很有可能瞬間就把資料庫壓垮

解決方案:

1、Redis高可用

多增設Redis伺服器,多搭建叢集

2、限流降級

在緩存失效後,通過加鎖或者隊列來控制讀取資料庫寫緩存的線程數量

3、資料預熱

在正式部署之前,先把可能的資料預先通路一遍,這樣部分可能大量通路的資料就會加載到緩存中。在即将發生大并發通路前手動觸發加載緩存不同的key,設定不同的過期時間,讓緩存實效的時間點盡量均勻