1. 程式人生 > 其它 >Go 協程

Go 協程

Redis

NoSQL的四大分類

NoSQL = Not Only SQL,泛指非關係型資料庫。

KV鍵值對

  • 新浪(Redis)
  • 美團(Redis + Tair)
  • 阿里、百度(Redis + memecache)

文件型資料庫

  • MongoDB —— MongoDB是一個基於分散式檔案儲存的資料庫,C++編寫,主要用來處理大量的文件。MongoDB是一個介於關係型資料庫和非關係型資料庫中間的產品。MongoDB是非關係型資料庫中功能最豐富,最像關係型資料庫的一個。
  • ConthDB

列儲存資料庫

  • HBase
  • 分散式檔案系統

圖關係資料庫

  • NEO4J

Redis(Remote Dictionary Server)

簡介

  • Redis(Remote Dictionary Server ),即遠端字典服務,是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。
  • Redis會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並且在此基礎上實現了master-slave(主從)同步。
  • 免費和開源,是當下最熱門的NoSQL技術之一,也被人們稱之為結構化資料庫。

用途

Redis可用作資料庫、快取和訊息中介軟體MQ。具體用途如下:

  • 記憶體儲存並持久化(RDB和AOF)
  • 效率高,可用於快取記憶體
  • 釋出訂閱系統
  • 地圖資訊分析
  • 計時器,計數器(如記錄瀏覽量)

特性

Redis官方推薦在Linux伺服器上搭建。

Redis是單執行緒的,它很快,基於記憶體操作

CPU不是Redis的效能瓶頸,Redis的瓶頸是機器的記憶體和網路頻寬。

  • 多樣的資料型別
  • 持久化
  • 叢集
  • 事務

為什麼Redis採用單執行緒還這麼快?

Redis是將所有的資料全部放到記憶體中的,所以說使用單執行緒去操作效率就是最高的,多執行緒(CPU上下文會切換:耗時的操作),對於記憶體系統來說,如果沒有上下文切換效率就是最高的。多次讀寫都是在一個CPU上的,在記憶體情況下,這個就是最佳的方案。

Redis的安裝

Redis安裝(菜鳥教程)

客戶端連線Redis

redis-cli.exe -h 127.0.0.1 -p 6379

Redis壓力測試

100000個寫入請求,100個併發客戶端

redis-benchmark -h localhost -p 6379 -c 100 -n 100000

Redis基本知識

Redis有16個數據庫,預設使用第0個,可以使用select進行切換。

基本指令

指令 作用
keys * 檢視資料庫所有的key
flushdb 清空當前資料庫
flushall 清空所有資料庫
set [key] [value] 設定鍵值對,重複設定一個鍵會將原來的值給覆蓋掉
get [key] 通過鍵名獲取值
EXISTS [key] 檢查鍵是否存在
move [key] [0-15] 將鍵移動到某個資料庫
expire [key] [time] 將鍵設定為n秒後過期
ttl [key] 檢視鍵剩餘存活時間
type [key] 檢視鍵型別

Redis的五大資料型別

String

ps:value除了字串還可以是數字

使用場景

  • 計數器
  • 統計多單位的數目
  • 物件快取儲存
指令 作用
append [key] [value] 在鍵上追加值,若此鍵不存在,相當於set命令
strlen [key] 獲取鍵的值的長度
incr [key] 鍵的值加1
decr [key] 鍵的值減1
incrby [key] [step] 鍵的值加n
decrby [key] [step] 鍵的值減n
getrange [key] [start] [end] 擷取字串。getrange [key] 0 -1 等同get命令
setrange [key] [location] [value] 從指定位置開始替換
setex [key] [time] [value] 可理解為set和expire連用
setnx [key] [value] 若鍵不存在,建立並設定值(分散式鎖中常用)
mset [key1] [value1] [key2] [value2]...... 批量設定
mget[key1] [key2]...... 批量取值
msetnx [key1] [value1] [key2] [value2]...... 若鍵不存在,建立並設定值(批量),具有原子性(若其中一個設定失敗則整體失敗)
getset [key] [value] 先get,返回get到的值,將value覆蓋原有值

List

特點

  • 值有序
  • 可重
  • 指令首的"l"含義:除了push和pop操作代表頭尾的意思,其他都是list的意思
指令 作用
lpush [listname] [value] 往列表頭部插入資料
lrange [listname] [range(0 1)] 獲取列表中某個範圍的值(獲取列表中第0和第1個值)
rpush [listname] [value] 往列表尾部插入資料
lpop [listname] 移除列表頭元素
rpop [listname] 移除列表尾元素
lindex [listname] [index] 通過下標獲取值
llen [listname] 獲取列表長度
lrem [listname] [count] [value] 移除n個重複的指定值
ltrim [listname] [range(1 2)] 擷取範圍內值(擷取列表中第1和第2個值)(注:原list將被改變,未被擷取到的資料將被刪除)
rpoplpush [source] [destination] 移除列表的最後一個元素並將其插入另一個列表的頭部
lset [listname] [index] [value] 修改下標值
exist [listname] 判斷某列表是否存在
linsert [listname] [before|after] [value] [value] 在某個值的前面/後面插入值

底層實現:快速連結串列

使用場景:訊息佇列、棧

Set

特點

  • 值無序
  • 不可重複
  • 指令首"s"的含義:代表set
指令 作用
sadd [setname] [value] 往集合中新增值
smembers [setname] 檢視某集合
sismember [setname] [value] 判斷集合中是否存在該值(返回0表示不存在,返回1表示存在)
scard [setname] 獲取集合元素個數
srem [setname] [value] 移除集合中的指定元素
srandmember [setname] 隨機取出一個元素
srandmember [setname] [count] 隨機取出若干個元
spop [setname] 移除一個元素
smove [source] [destination] [value] 移除列表的一個指定元素並將其新增到另一個集合
diff [set1] [set2] 找出屬於set1但不屬於set2的元素(求差集
sinter [set1] [set2] 求交集
sunion [set1] [set2] 求並集

使用場景

  • 集合操作(交、並、差)

Hash

使用場景

  • 物件儲存
  • 經常變更的K-V資料,例如使用者資訊
指令 作用
hset [hashname] [key] [value] 存鍵值對
hget [hashname] [key] 由鍵取值
hmset [hashname] [key1] [value1] [key2][value2]...... 批量存鍵值對
hmget [hashname] [key1] [key2]...... 根據鍵批量取值
hgetall [hashname] 獲取所有鍵值對
hdel [hashname] [key] 刪除指定鍵值對
hlen [hashname] 獲取雜湊表長度(即有幾個鍵值對)
hexist [hashname] [key] 判斷表中鍵值對是否存在(返回0表示不存在,返回1表示存在)
hkeys [hashname] 獲取表中所有的key
hvals [hashname] 獲取表中所有的value
hincrby [hashname] [key] [increase] 將key對應的value增加若干大小(可通過負數實現減少)
hsetnx [hashname] [key] [value] 若鍵不存在,建立並設定值

Zset

特點

  • 在set基礎上增加排序權重
  • 有序集合
指令 作用
zadd [setname] [score] [value] 往集合中新增值
zrange [setname] [range(0 1)] 獲取集合中某個範圍的值(獲取集合中第0和第1個值)
zrangebyscore [setname] -inf +inf 按分數從小到大獲取集合中的值
zrevrangebyscore [setname] +inf -inf 按分數從大到小獲取集合中的值
zrangebyscore [setname] -inf +inf withscores 按分數從小到大獲取集合中的值並顯示分數
zrem [setname] [value] 移除集合中的指定元素
zcard [setname] 獲取集合元素個數
zcount [setname] [scorerange(1 3)] 某分數範圍內的值的個數(分數在[1,3]範圍內的值的個數)

Redis三種特殊資料型別

geospatial

底層基於Zset

指令 作用
geoadd [key] [longitude] [latitude] [cityname] 新增地理位置(經緯度)
geopos [key] [cityname] 獲取指定城市的地理位置
geodist [key] [cityname1] [cityname2] [unit] 查詢兩地的直線距離(預設單位為米)
georadius [key] [longitude] [latitude] [radius] [unit] [count] 以某處為中心查詢其方圓若干(單位)的城市(可限制顯示數量)
georadius [key] [longitude] [latitude] [radius] [unit] withdist [count] 以某處為中心查詢其方圓若干(單位)的城市(帶直線距離)
georadius [key] [longitude] [latitude] [radius] [unit] withcoord [count] 以某處為中心查詢其方圓若干(單位)的城市(帶經緯度)
georadiusbymember [key] [cityname] [radius] [unit] 以某城市為中心查詢其方圓若干(單位)的城市
geohash [key] [cityname] 返回城市的雜湊值(二維的經緯度轉換成一維的字串,兩個字串顯示越靠近說明兩地距離越接近)

Hyperloglog

概述

它是用作基數統計的演算法。基數計算(cardinality counting)指的是統計一批資料中的不重複元素的個數。

應用場景

  • 網頁的UV(使用者訪問量),同一個人訪問網站多次,視為一次訪問

優點

佔用的記憶體是固定的,264個不同元素的資料,只需要廢12KB記憶體。所以如果要從記憶體角度來比較的話,Hyperloglog是首選。

缺點

具有0.81%的錯誤率

指令 作用
pfadd [key] [value1] [value2]...... 存放資料
pfcount [key] 統計元素數量
pfmerge [newkey] [key1] [key2]...... 合併若干key到一個新的key

Bitmap

概述

點陣圖,即位儲存,操作二進位制位來進行記錄,只有0和1兩個狀態。

應用場景

使用者狀態統計。使用者活躍度,登入狀態,打卡記錄。擁有兩個狀態的使用者資訊的儲存都可以用Bitmap。

指令 作用
setbit [key] [offset] [value] 存值
getbit [key] [offset] 取值
bitcount [key] 統計1的個數

Redis基本事務操作

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

Redis事務本質

一組命令的集合!一個事務中的所有命令都會被序列化,在事務執行過程中,會按照一次性、順序性、排他性等原則執行一系列的命令!

Redis事務沒有隔離級別的概念!

所有的命令在事務中,並沒有直接被執行!只有發起執行命令的時候才會執行!Exec

事務

  • 開啟事務 —— multi
  • 命令入隊 —— 一系列命令
  • 執行事務 —— exec
  • 放棄事務 —— discard

異常

  • 編譯型異常 —— 命令使用錯誤,事務中其他命令都不會被執行
  • 執行時異常 —— 命令沒有語法錯誤,但是執行時產生錯誤,除了報錯的命令外,事務中其他命令依然會被執行

Redis監控——悲觀鎖和樂觀鎖

悲觀鎖 —— 認為什麼時候都可能出現問題,因此無論做什麼都加鎖

樂觀鎖(常用) —— 認為什麼時候不會出現問題,不會對任何操作上鎖。只會更新資料的時候去判斷一下,在此期間是否有人修改過這個資料,步驟如下:

  • 獲取version (watch data)
  • 更新時比對version,若version發生變動,則告訴事務,事務執行失敗,執行失敗後先unwatch data,再watch data更新version

Jedis —— Java連線開發工具

連線

1. 匯入包

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.5.1</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.73</version>
</dependency>

2.連線Redis

package com.youzikeji.redis;

import redis.clients.jedis.Jedis;

public class TestJedis {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        System.out.println(jedis.ping());
    }
}

3.關閉連線

jedis.close();

事務

package com.youzikeji.redis;

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

public class TestTransaction {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("127.0.0.1", 6379);
        jedis.flushDB();
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("name", "caoyusang");
        jsonObject.put("age", 23);
        String result = jsonObject.toString();
        System.out.println(result);
        // 開啟事務
        Transaction multi = jedis.multi();
        // jedis.watch(result); // 開啟監控——樂觀鎖
        try {
            multi.set("user1", result);
            multi.lpush("list",result);
//            int i= 1 / 0;  // 程式碼出現異常,執行失敗!
            multi.exec();   // 執行事務
        } catch (Exception e) {
            multi.discard();    // 放棄事務
        } finally {
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.lpop("list"));
            jedis.close();  // 關閉連線
        }
    }
}

SpringBoot整合Redis

簡介

SpringBoot操作資料:spring-data (jpa jdbc mongodb redis elasticsearch)!

SpringBoot2.x之後,原來使用的Jedis被替換為了lettuce。

Jedis:採用的直連,多個執行緒操作的話,是不安全的,如果想要避免不安全的,使用jedis pool 連線池! 更像 BIO 模式

lettuce: 採用netty,例項可以再多個執行緒中進行共享,不存線上程不安全的情況!可以減少執行緒資料了,更像NIO模式

原始碼分析

@Bean
@ConditionalOnMissingBean(
    name = {"redisTemplate"}
)    // 我們可以自己定義一個redisTemplate來替換這個預設的!
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    // 預設的RedisTemplate 沒有過多的設定,redis物件都是需要序列化的!
    // 兩個泛型都是object,object的型別,我們後續使用需要強制轉換成<String,object>
    RedisTemplate<Object, Object> template = new RedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}
@Bean
@ConditionalOnMissingBean    // 由於String 是redis中最常使用的型別,所以說單獨提出來了一個bean!
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
    StringRedisTemplate template = new StringRedisTemplate();
    template.setConnectionFactory(redisConnectionFactory);
    return template;
}

依賴匯入

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

配置連線

spring.redis.host=127.0.0.1
spring.redis.port=6379

自定義RedisTemplate

package com.youzikeji.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
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;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
        // 為了自己開發方便,一般直接使用<String,Object>
        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);
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        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;
    }
}

測試

package com.youzikeji;

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

@SpringBootTest
class SpringbootApplicationTests {

    @Autowired
    @Qualifier("redisTemplate")
    private RedisTemplate redisTemplate;
    // redisTemplate 操作不同的資料型別,api和我們的命令一樣
    // opsForValue 操作字串 類似 String
    // opsForList 操作list 類似list
    // 。。。依此類推
    // 除了基本的操作,我們常用的方法都可以直接通過redisTemplate操作,比如事務和基本的crud
    // 獲取redis 的連線物件
    // RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
    // connection.flushAll();
    // connection.flushDb();
    @Test
    void contextLoads() {
        redisTemplate.opsForValue().set("name", "caoyusang");
        System.out.println(redisTemplate.opsForValue().get("name"));
    }

}

Redis配置檔案詳解

unit

記憶體大小

include

引入其他配置檔案的內容

網路

bind 127.0.0.1    # 繫結的ip
protected-mode yes     # 保護模式
port 6379    # 埠設定

通用

daemonize yes    # 以守護程序的方式執行,預設是no,我們需要自己開啟為yes!
pidfile /var/run/redis_6379.pid    # 如果以後臺的方式執行,我們就需要指定一個pid檔案!
# 日誌
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)    生產環境
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" # 日誌檔案位置
databases 16    # 預設資料庫個數 16個
always-show-logo yes    # 是否總是顯示logo

快照

redis是記憶體資料庫,如果沒有持久化,那麼資料斷電及失!

快照是持久化的一種方式,若在規定的時間內,redis-server執行了多少次操作,則會持久化到檔案.rdb.aof

# 如果900s內,如果至少有1個key進行了修改,我們就進行持久化操作
save 900 1
# 如果300s內,如果至少有10個key進行了修改,我們就進行持久化操作
save 300 10
# 如果60s內,如果至少有10000個key進行了修改,我們就進行持久化操作
save 60 10000
stop-writes-on-bgsave-error yes    # 持久化如果出錯,是否還要繼續工作!
rdbcompression yes    # 是否壓縮 rdb 檔案,需要消耗一些cpu資源!
rdbchecksum yes    # 儲存rdb檔案的時候,進行錯誤的檢查校驗!
dir ./    # rdb 檔案儲存的目錄!

安全

127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456"
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

使用者連線限制

# maxclients 10000    設定能連線上redis的最大客戶端的數量
# maxmemory <bytes>    redis 配置最大的記憶體容量
# 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 : 永不過期,返回錯誤

AOF配置

appendonly no    # 預設是不開啟aof模式的,預設是使用rdb方式持久化的,在大部分所有的情況下,rdb完全夠用!
appendfilename "appendonly.aof"    # 持久化的檔案的名字
# appendfsync always    # 每次修改都會 sync,消耗效能
appendfsync everysec    # 每秒執行一次 sync,可能會丟失這1s的資料
# appendfsync no    # 不執行 sync ,這個時候作業系統自己同步資料,速度最快!

Redis持久化

RDB(Redis Database)

概述

在指定的時間間隔內將記憶體中的資料集快照寫入磁碟,恢復資料時將快照檔案直接讀到記憶體裡面。在建立快照之後,使用者可以備份該快照,可以將快照複製到其他伺服器以建立相同資料的伺服器副本,或者在重啟伺服器後恢復資料。RDB是Redis預設的持久化方式。RDB檔案預設為當前工作目錄下的dump.rdb,可以根據配置檔案中的dbfilenamedir設定RDB的檔名和檔案位置。

save和bgsave

執行savebgsave命令,可以手動觸發快照,生成RDB檔案。

save bgsave
同步操作,會阻塞伺服器程序,伺服器程序在RDB檔案建立完成之前是不能處理任何的命令請求 非同步操作,bgsave命令會fork一個子程序,然後該子程序會負責建立RDB檔案,而伺服器程序會繼續處理命令請求

fork(): 由作業系統提供的函式,用於建立當前程序的一個副本作為子程序。

bgsave命令fork的子程序會把資料集先寫入臨時檔案,寫入成功之後,再替換之前的RDB檔案,用二進位制壓縮儲存,這樣可以保證RDB檔案始終儲存的是完整的持久化內容。

快照觸發規則

  • 手動執行save和bgsave命令
  • 配置檔案中設定save <seconds> <changes>規則,可以自動間隔性執行bgsave命令
  • 執行flushall命令清空伺服器資料
  • 執行shutdown命令關閉Redis,會自動執行save命令
  • 主從複製時,從庫全量複製主庫資料時,主庫會執行bgsave命令

AOF(Append Only File)

概述

AOF持久化會把被執行的寫命令寫到AOF檔案的末尾,記錄資料的變化。預設情況下,Redis是沒有開啟AOF持久化的,開啟後,每執行一條更改Redis資料的命令,都會把該命令追加到AOF檔案中。

AOF實現

命令追加

伺服器每執行一個寫命令,都會把該命令以協議格式先追加到aof_buf快取區的末尾,而不是直接寫入檔案,避免每次有命令都直接寫入硬碟,減少硬碟IO次數

檔案寫入與同步

aof_buf緩衝區的所有內容寫入並同步到AOF檔案,Redis支援多種寫入同步策略:

appendfsync always appendfsync everysec appendfsync
同步 即時同步 每秒同步一次 作業系統自己選擇何時同步
缺點 消耗效能 至多會丟失一秒的資料 系統崩潰時,可能會丟失不定量的資料
優點 安全性最高 兼顧了資料安全和寫入效能 不會影響Redis的效能
AOF重寫

隨著時間的推移,Redis執行的寫命令會越來越多,AOF檔案也會越來越大,過大的AOF檔案可能會對Redis伺服器造成影響,如果使用AOF檔案來進行資料還原所需時間也會越長。

時間長了,AOF檔案中通常會有一些冗餘命令,比如:過期資料的命令、無效的命令(重複設定、刪除)、多個命令可合併為一個命令(批處理命令)。所以AOF檔案是有精簡壓縮的空間的。

AOF重寫的目的就是減小AOF檔案的體積,檔案重寫可分為手動觸發和自動觸發,手動觸發執行bgrewriteaof命令,該命令的執行跟 bgsave觸發快照時類似的,都是先fork一個子程序做具體的工作;而自動觸發會根據auto-aof-rewrite-percentageauto-aof-rewrite-min-size 64mb配置來自動執行bgrewriteaof命令。

# 表示當AOF檔案的體積大於64MB,且AOF檔案的體積比上一次重寫後的體積大了一倍(100%)時,會執行bgrewriteaof命令
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

bgrewriteaof的重寫流程

  • 重寫會有大量的寫入操作,所以伺服器程序會fork一個子程序來建立一個新的AOF檔案。
  • 在重寫期間,伺服器程序繼續處理命令請求,如果有寫入的命令,追加到aof_buf的同時,還會追加到aof_rewrite_bufAOF重寫緩衝區。
  • 當子程序完成重寫之後,會給父程序一個訊號,然後父程序會把AOF重寫緩衝區的內容寫進新的AOF臨時檔案中,再對新的AOF檔案改名完成替換,這樣可以保證新的AOF檔案與當前資料庫資料的一致性。

RDB vs AOF

持久化方式 RDB AOF
啟動優先順序
體積
恢復速度
資料安全性 會丟資料 由選擇的aof檔案寫入同步策略決定
輕重

Redis釋出訂閱

Redis釋出訂閱-菜鳥教程

Redis快取穿透和雪崩

快取穿透

指查詢一個快取和資料庫中都沒有的資料,由於大部分快取策略是被動載入的,並且出於容錯考慮,如果從儲存層查不到資料則不寫入快取,這將導致這個不存在的資料每次請求都要到儲存層去查詢,失去了快取的意義。使用者不斷髮起請求,在流量大時,就可能對資料庫形成巨大的壓力,利用不存在的key頻繁攻擊應用也是很大的問題。

例如,使用者查詢一個 id = -1 的商品資訊,一般資料庫 id 值都是從 1 開始自增,很明顯這條資訊是不在資料庫中,此時若使用者不斷髮起請求的話,在流量大時,會給當前資料庫的造成很大的併發訪問壓力。

快取空物件

第一次請求快取和資料庫中都不存在 的資訊,則從資料庫中返回一個空物件,並將這個空物件和請求關聯起來存到快取中,當下次還是這個請求過來的時候,這時快取就會命中,就直接從快取中返回這個空物件,這樣可以減少訪問資料庫的壓力,提高當前資料庫的訪問效能。

該方法存在的問題

當有大量不存在的請求時,快取中就會有許多空物件,佔用許多記憶體空間,浪費資源

當然,可以給這些空物件設定過期時間,讓其在一段時間後得到自動清理。

介面層校驗

介面層增加校驗,比如使用者鑑權校驗,id根據資料場景做基礎校驗,id<=0的直接攔截。

布隆過濾器

布隆過濾器是一個非常神奇的資料結構,通過它我們可以非常方便地判斷一個給定資料是否存在於海量資料中

所有可能存在的請求值都存放在布隆過濾器中,當用戶請求過來,先判斷使用者發來的請求值是否存在於布隆過濾器中。不存在的話,直接返回請求引數錯誤資訊給客戶端,存在則判斷快取中是否存在對應的資料,存在則直接返回,不存在就再從資料庫中找,資料庫中找到對應的資料,則更新快取,返回對應資料,如果資料庫中也不存在對應資料,則返回空資料,加入布隆過濾器之後的快取處理流程圖如下:

該方法存在的問題 —— 誤判

布隆過濾器可能會存在誤判的情況:

  • 布隆過濾器說某個元素存在,小概率會誤判

  • 布隆過濾器說某個元素不在,那麼這個元素一定不在

JavaGuide/bloom-filter.md

快取擊穿

造成快取擊穿的原因有兩個:

  • 不經常被訪問的“冷門”key,突然被大量使用者請求訪問
  • 經常被訪問的“熱門”key,在其恰好過期時,有大量使用者訪問

常用解決方案 —— 加鎖

對於key過期的時候,當key要查詢資料庫的時候加上一把鎖,這時只能讓第一個請求進行查詢資料庫,然後把從資料庫中查詢到的值儲存到快取中,對於剩下的相同的key,可以直接從快取中獲取即可。

快取雪崩

描述的是這樣一個場景:

快取在同一時間大面積的失效,後面的請求都直接落到了資料庫上,造成資料庫短時間內承受大量請求

舉個例子 :秒殺開始 12 個小時之前,我們統一存放了一批商品到 Redis 中,設定的快取過期時間也是 12 個小時,那麼秒殺開始的時候,這些秒殺的商品的訪問直接就失效了。導致的情況就是,相應的請求直接就落到了資料庫上,就像雪崩一樣可怕。

解決方案

  • Redis高可用 —— Redis有可能掛掉,多增加幾臺redis例項,(一主多從或者多主多從),這樣一臺掛掉之後其他的還可以繼續工作,其實就是搭建叢集。
  • 限流降級 —— 在快取失效後,通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量,對某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。
  • 資料預熱 —— 在正式部署之前,我先把可能的資料先預先訪問一遍,這樣部分可能大量訪問的資料就會載入到快取中。在即將發生大併發訪問前手動觸發載入快取不同的key。
  • 設定不同的過期時間 —— 讓快取失效的時間點儘量均勻。