狂神說redis課程連結
Redis
NoSQL的四大分類
KV鍵值對
- Redis
文檔型資料庫(bson格式,和json一樣)
- MongoDB
- 基于分布式檔案存儲的資料庫,主要用來處理大量的檔案
- 介于關系型資料庫和非關系型資料的中間産品,是非關系型資料庫中功能最豐富,最像關系型資料庫
- ConthDB
列存儲資料庫
- HBASE
- 分布式檔案系統
圖關系資料庫
- 不是存圖形,存放的是關系
- Neo4j
四種資料庫的特點
Redis入門
Redis是什麼?
Redis(Remote Dictionary Server)遠端字典服務
開源的,使用ANSI C語言編寫
持久化政策:RDB、AOF
Redis預設安裝路徑:/usr/local/bin
測試性能
redis-benchmark是一個壓力測試工具
# 簡單測試:100個并發連接配接 100000請求
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
基礎知識
Redis預設有十六個資料庫,預設使用第0個資料庫,可以使用select進行切換
select 3 # 切換第三個資料庫
DBSIZE # 檢視DB大小
Redis是單線程的,Redis是基于記憶體操作,CPU并不是Redis的性能瓶頸,Redis的瓶頸是機器的記憶體和網絡帶寬
Redis為什麼單線程還這麼快
- 誤區1:高性能的伺服器一定是多線程的
- 誤區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…
# 檢視某一個天是否打卡
getbit sign 3
>1
getbit sign 6
>0
# 統計操作,統計一周打卡的天數
bitcount sign
>3
事務
Redis單條指令是保證原子性的,但是事務不保證原子性
Redis事務本質:一組指令的集合,一個事務中的所有指令都會被序列化,在事務執行過程中,會按照順序執行
一次性,順序性,排他性
Redis事務沒有隔離級别的概念
所有的指令在事務中并沒有直接被執行,隻有發起執行指令時才會被執行(Exec)
Redis的事務:
- 開啟事務(multi)
- 指令入隊(…)
- 執行事務(exec)
正常執行事務
放棄事務
編譯性異常(代碼有問題,指令有錯),事務中所有的指令都不會被執行
運作時異常,如果事務中某一條指令存在邏輯問題,那麼執行指令的時,其他指令可以正常執行,錯誤指令抛出異常
監控!Watch(面試)
悲觀鎖:
- 認為什麼時候都會出問題,無論做什麼都會加鎖
樂觀鎖:
- 認為什麼時候都不會出問題,是以不會上鎖
- 更新資料的時候去判斷一下,在此期間是否有人修改過這個資料
- 擷取version,更新的時候比較version
測試多線程修改值,使用watch可以當做Redis的樂觀鎖操作,可以使用unwatch解鎖
事務執行失敗後操作:
- 如果發現事務執行失敗,就先使用unwatch解鎖
- 再使用watch擷取最新的值,再次監視,select version
- 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模式
源碼分析
測試
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、機關
配置檔案機關對大小寫不敏感
2、包含其他配置檔案
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的密碼,預設是沒有密碼
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會單獨建立一個子程序來進行持久化,會先将資料寫入到一個臨時檔案中,待持久化過程都結束了,在用這個臨時檔案替換上次持久化好的檔案,整個過程中,主程序是不進行任何IO操作的,確定了極高的性能。如果需要進行大規模資料的恢複,且對資料恢複的完整性不是非常敏感,RDB方式要比AOF方式更加高效
缺點:最後一次持久化後的資料可能丢失
預設就是RDB,一般情況下不需要修改配置
RDB儲存的檔案是dump.rdb,可以在配置檔案中快照中進行配置
# 配置儲存的檔案名
dbfilename dump.rdb
觸發機制
- save的規則滿足的情況下,會自動觸發rdb規則
- 執行fllushall指令,會觸發rdb規則
- 退出Redis,會産生rdb檔案
備份就自動生成一個dump.rdb
恢複rdb檔案:隻需要将rdb檔案放在Redis的啟動目錄就可以,Redis啟動時會自動檢查dump.rdb,恢複其中的資料
檢視存放位置:
127.0.0.1:6379>config gei dir
dir
/usr/local/bin # 如果這個目錄下存在dump.rdb,啟動時就會自動恢複其中的資料
優點:
- 适合大規模的資料恢複
- 對資料完整性要求不高
缺點:
- 需要一定的時間間隔進行操作,如果Redis意外當機了,這個最後一次修改的資料就會丢失
- fork程序的時候,會占用一定的記憶體空間
AOF(Append Only File)
将所有的指令都記錄下來,恢複的時候就把整個檔案全部執行一遍
以日志的形式來記錄每個寫操作,将Redis執行過的所有指令記錄下來(讀操作不記錄),隻許追加檔案但不可以改寫檔案,Redis啟動之初會讀取該檔案重新建構資料
AOF儲存的是appendonly.aof檔案
開啟後重新開機Redis就可以生效了
如果aof檔案有錯誤,Redis是不能啟動的,我們要使用Redis提供的redis-check-aof工具來修複
# 修複指令
redis-check-aof appendonly.aof
修複之後,重新開機就可以恢複資料
優點:
- 每次修改都同步,檔案完整性強
- 預設開啟每秒同步一次,可能會丢失一秒的資料
- 從不同步效率是最高的
缺點:
- 相對于資料檔案來說,aof遠大于rdb,修複的速度比rdb慢
- aof運作效率比rdb低
Redis釋出訂閱
微網誌、微信關注系統
Redis釋出訂閱(pub/sub)是一種消息通信模式:發送者(pub)發送消息,訂閱者(sub)接收消息
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伺服器都是主節點,且一個主節點可以有多個從節點(或沒有從節點),但一個從節點隻能有一個主節點
主從複制的作用:
- 資料備援:主從複制實作了資料的熱備份,是持久化之外的一種資料備援方式
- 故障恢複:當主節點出現問題時,可以由從節點提供服務,實作快速的故障恢複。實際上是一種服務的備援
- 負載均衡:在主從複制的基礎上,配合讀寫分離,可以由主節點提供寫服務,由從節點提供讀服務(即寫Redis資料時應用連接配接主節點,讀Redis資料時應用連接配接從節點),分擔伺服器負載;尤其是在寫少讀多的場景下,通過多個從節點分擔讀負載,可以大大提高Redis伺服器的并發量
- 高可用(叢集)基石:主從複制是哨兵模式和叢集能夠實施的基礎
環境配置
隻配置從節點,不用配置主節點
# 檢視目前庫的資訊
info replication
複制多個配置檔案,然後修改對應的資訊
- 端口
- pid名字
- log檔案名
- dump.rdb檔案名
從機配置
# 選擇哪台作為主機,認老大
SLAVEOF host port # 在從機上使用這條指令
真實的主從配置應該寫入配置檔案
細節:
- 主機寫,從機隻能讀,主機中的所有資訊和資料都會自動被從機儲存
- 主機斷開,從機依舊連接配接到主機,但是沒有寫操作。如果主機恢複了,從機依舊可以直接擷取到主機寫入的資料
- 如果是使用指令行配置主從,如果從機重新開機了,就變為主機了。如果再次配置為從機,同樣可以從主機中擷取所有值
複制原理:
Slave啟動成功連接配接到master後會發送一個sync同步指令
master接收到指令,啟動背景的存盤程序,同時收集所有接收到的用于修改資料集的指令,在背景程序執行完畢後,master将傳送整個資料檔案到salve,并完成一次完全同步
全量複制:slave服務在接受到資料檔案後,将其存盤并加載到記憶體中
增量複制:master繼續将新的所有收集到的修改指令依次傳給slave,完成同步
隻要是重新連接配接master,一次完全同步(全量複制)将被自動執行,可以在從節點看到全部資料
層層依賴:
這時候也可以完成我們的主從複制
如果主機當機,可以使用
SLAVEOF no one
讓目前伺服器變成主機,其他的節點就可以手動連接配接到最新的主節點。如果原master修複,那就需要重新進行配置
哨兵模式
自動選舉master的模式
概述:當master當機之後,根據投票數自動将slave轉換為master
哨兵模式一種特殊的模式,首先Redis提供了哨兵的指令,哨兵是一個獨立的程序,它會獨立運作,原理是哨兵通過發送指令,等待Redis伺服器響應,進而監控正在運作的多個Redis執行個體
作用:
- 通過發送指令,讓Redis伺服器傳回監控其運作狀态,包括master和slave
- 當哨兵檢測到master當機,會自動将slave切換成master,然後通過釋出訂閱模式通知其他的從伺服器,修改配置檔案,讓它們切換主機
多哨兵模式:一個哨兵程序對Redis進行監控可能會出現問題,可以使用多個哨兵進行監控,各個哨兵之間還會進行監控
假設master當機,哨兵1先檢測到這個結果,系統并不會馬上進行failover過程,僅僅是哨兵1主觀的認為master不可用,這個現象稱為主觀下線,當後面的哨兵也檢測到master不可用,并且數量達到一定值時,那麼哨兵之間就會進行一次投票,投票的結果由任意一個哨兵發起,之後進行failover(故障轉移)操作。切換成功後,就會通過釋出訂閱模式,讓各個哨兵把自己監控的從伺服器實作切換主機,這個過程稱為客觀下線
配置:
- 配置哨兵配置檔案sentinel.conf
# sentinel monitor 被監控的主機名字 host port num
# myredis為主機名字,可以任意。1是投票機制,當1個sentinel認為master失效時,則正式失效
sentinel monitor myredis 127.0.0.1 6379 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記憶體資料庫中沒有,也就是緩存沒有命中,于是向持久層資料庫查詢,發現也沒有,于是本次查詢失敗。
當使用者很多的時候,緩存都沒有命中,于是都去請求了持久層資料庫,這會給持久層資料庫造成很大的壓力,就出現了緩存穿透
解決方案:
1、布隆過濾器
布隆過濾器是一種資料結構,對所有可能查詢的參數以hash的形式存儲,在控制層先進行校驗,不符合則丢棄,進而避免了對底層存儲系統的查詢壓力
2、緩存空對象
當存儲層不命中後,即使傳回的空對象也将其緩存起來,同時會設定一個過期時間,之後再通路這個資料将會從緩存中擷取,保護了後端資料源
這種方法會存在兩個問題:
- 如果空值能夠被緩存起來,這就意味着緩存需要更多的空間存儲更多地鍵
- 即使對空值設定了過期時間,還是會存在緩存層和存儲層的資料會有一段時間的不一緻,這對于需要保持一緻性的業務會有影響
緩存擊穿
指一個key非常熱點,不停得扛着大并發,集中對這一個點進行通路,當這個key失效的瞬間,持續的大并發就會穿透緩存,直接請求資料庫,就像在緩存中鑿開了一個洞
當某個key失效的瞬間,有大量的請求并發通路,這類資料一般是熱點資料,由于緩存過期,會同時通路資料庫來查詢最新的資料,并且回寫緩存,會導緻資料庫瞬間壓力過大
解決方案:
1、設定熱點資料永不過期
從緩存層面來看,沒有設定過期時間,是以不會出現熱點key過期後産生的問題
2、加互斥鎖
使用分布式鎖,保證對于每個key同時隻有一個線程區查詢後端服務,其他線程沒有獲得分布式鎖的權限,隻能等待。這種方式将高并發的壓力轉移到分布式鎖,是以對分布式鎖的考驗很大
緩存雪崩
某一個時間段,緩存集中過期失效。或者Redis當機或斷電斷網
緩存集中過期并不是非常緻命的,比較緻命的緩存雪崩,是緩存伺服器某個節點當機或斷網。因為自然形成的緩存雪崩,一定是在某個時間段集中建立緩存,資料庫也是可以頂住壓力的,無非就是對資料庫産生周期性的壓力而已;而緩存服務節點的當機,對資料庫伺服器造成的壓力是不可預知的,很有可能瞬間就把資料庫壓垮
解決方案:
1、Redis高可用
多增設Redis伺服器,多搭建叢集
2、限流降級
在緩存失效後,通過加鎖或者隊列來控制讀取資料庫寫緩存的線程數量
3、資料預熱
在正式部署之前,先把可能的資料預先通路一遍,這樣部分可能大量通路的資料就會加載到緩存中。在即将發生大并發通路前手動觸發加載緩存不同的key,設定不同的過期時間,讓緩存實效的時間點盡量均勻