Redis筆記(狂神說)
Redis
單機MySQL的演進
1.單機MYSQL的年代
APP—>DAL—>MySql
2.Memcached(快取) + MySql + 垂直拆分(讀寫分離)
網站百分之八十的情況都在讀,每次都要查詢資料庫的話十分麻煩。所以我們希望減輕資料庫的壓力,可以通過快取來提高效率!
分庫分表 + 水平拆分 + MySQL叢集
NoSql
NpSql
NoSql = Not Only Sql (不僅僅是Sql)
關係型資料庫:表格,行,列
很多使用者的個人資訊、社交網路、地理位置,這些型別的儲存不需要一個固定的格式。
傳統RDBMS和NoSql
傳統的RDBMS - 結構化組織 - SQL - 資料和關係都存在單獨的表中 - 嚴格的一致性
NoSql
、- 不僅僅是資料
- 沒有固定的查詢語言
- 鍵值對儲存 列儲存 文件儲存 圖形資料庫(社交關係)
- 最終一致性
- CAP原理和BASE
- 高可用 高效能 高可擴
NoSql的四大分類
-
KV鍵值對:redis
-
文件型資料庫(bson格式和json一樣):MongoDB
MongoDB:一個基於分散式檔案儲存的資料庫,主要用於處理大量的文件。是一個介於關係型資料庫和非關係型資料中中間的產品。
-
列儲存資料庫:HBase、分散式檔案系統
-
圖關係資料庫:Neo4j,InfoGrid 不是存圖形,而是放關係 如朋友圈社交網路、廣告推薦
Redis
Redis是什麼?
Redis(Remote Dictionary Server),即遠端字典服務。redis會週期性的把更新的資料寫入磁碟或者把修改操作寫入追加的記錄檔案,並實現了master-slave(主從)同步。Redis 是一個開源(BSD許可)的,記憶體中的資料結構儲存系統,它可以用作 資料庫、快取和訊息中介軟體 。 它支援多種型別的資料結構,如 , , , , 與範圍查詢, , 和 索引半徑查詢。 Redis 內建了 ,, , 和不同級別的 , 並通過 和自動 提供高可用性(high availability)。
Redis能幹嘛?
-
內容儲存、持久化,記憶體中的資料是斷電即失,所以持久化很重要(rdb、aof)
-
效率高,可用於快取記憶體
-
釋出訂閱系統(訊息佇列)
-
地圖資訊分析
-
計時器、計數器(瀏覽量)
-
。。。。
特性
- 多樣的資料型別
- 持久化
- 叢集
- 事務
- …
Redis推薦搭建在Linux伺服器上
中文官方網站:http://www.redis.cn/
Redis不區分大小寫命令
基本知識
Redis有16個數據庫 預設使用的是第0個,可以使用select進行切換
基礎語法:
select index --切換資料庫
dbsize --資料庫大小
set key value --儲存鍵值對
get key --獲得key所對應的value
keys * --檢視所有的key
flushdb --清空當前資料庫
flushall --清空所有資料庫
EXISTS key --判斷某個key是否存在 返回1則代表存在
move key --移出某個值
EXPIRE key seconds --設定某個key的過期時間
ttl key --檢視還有多久過期
type key --檢視key的型別
shutdown --停止執行
Redis是單執行緒的
Redis速度很快。Redis基於記憶體操作,CPU不是Redis的效能瓶頸,而是機器的記憶體和網路頻寬。
Redis為什麼單執行緒還這麼快?
1:高效能的伺服器不一定是多執行緒的
2:多執行緒(CPU會上下文切換)的效率不一定比單執行緒高
核心:redis是將所有的資料全部放在記憶體中的,所以使用單執行緒操作效率就是最高的,多執行緒(CPU會上下文切換,耗時的操作),對弈記憶體系統來說,如果沒有上下文切換那麼效率就是最高的。多次讀寫在一個CPU上,這個就是最佳的方案!
五大資料型別
String
適用場景:更適合字串儲存
命令:
APPEND key value --在key對應的值上附加值 若append的值不存在,則新建一個值
STRLEN key --檢視key的長度
incr key --key值加1
decr key --key值減1
INCRBY key count --key值增長count
DECRBY key count --key值減少count
GETRANGE key start end --獲得某個範圍的字串(類似於subString)
GETRANGE key 0 -1 --檢視全部的字串
SETRANGE key offset value --替換指定位置開始的字串
setex key seconds value --設定key的過期時間
setnx --不存在則設定 在分散式鎖中常使用
mset k1 v1 k2 v2 --批量設定值
mget k1 k2 --批量獲取值
msetnx k1 v1 k2 v2 --原子操作 只要有一個存在 就不會建立成功
set user:1{name:zhangsan,age:3} --設定一個user:1物件 值為json字串來儲存一個物件
另一種儲存物件的方式:
mset user:1 name zhangsan user:1:age 2
getset key value --先get 再set
String類似的使用場景:value除了字串,還可以是數字(可以應用於計數器)
List
在redis裡面,我們可以把List設計成棧、佇列、阻塞佇列!
所有的List命令都是以l開頭的
基本命令
LPUSH list value --向list中新增一個值 新增到列表頭部(左)
RPUSH list value --新增到列表尾部
LRANGE list 0 -1 --檢視list中的所有值
LRANGE list start end --檢視指定位置的元素值
LPOP list --移出list最左邊的元素
RPOP list --移出list最右邊的元素
LINDEX list index --獲取指定位置的元素
Llen list --返回l;ist的長度
Lrm list count value --移出count個value元素
Ltrim list start end --擷取操作 只會保留範圍內的資料
rpoplpush list1 list2 --移出列表最後一個元素 並將該元素新增到另一個列表的頭部
lset list index value --指定位置更新值(注意:此位置必須存在值才可以進行設定)
LINSERT list after(before) sourcevalue aimvalue --在原位置後(前)插入資料
小結
- 實際是一個連結串列 (left Node right)
- 如果key不存在,建立新的連結串列。如果key存在,新增內容
- 如果移除了所有值,空連結串列,也代表不存在
- 在兩邊插入或改動值,效率最高。中間元素,效率較低
- 訊息排隊,訊息佇列(Lpush Rpop) 棧(Lpush Lpop)
Set
無序 不重複 集合
所有的命令以s開頭
基本命令
sadd set mermber #set集合中新增一個元素
smembers set #檢視set集合中的所有元素
sismember set member #檢視set集合中是否存在某個元素
scard set #檢視set集合中所有元素的個數
srem set mermer #移除某個元素
srandmember set #隨機抽選出一個元素
srandmember set 2 #隨機抽選出兩個元素
spop set #隨機刪除一個元素
smove source destination member #將某個集合的指定元素移動到另一個集合中
數學集合類
sdiff set1 set2 #差集 找出set1中有而set2中沒有的元素
sinter set1 set2 #交集 共同好友的實現
sunion set1 set2 #並集
Hash
儲存結構:key->Map
適用場景:儲存變更資料,尤其是使用者資訊之類的 更適合物件的儲存
基本命令:
hset hash field value #新增一個entry(field->value)
hget hash field #獲取指定hash的欄位值
hmset hash field1 value1 field2 value2 #批量新增值
hget hash field1 field2 #批量獲取值
hgetall hash #獲取當前hash的所有值
hdel hash field #刪除hash指定的entry
hlen hash #當前hash中entry的個數
hexists hash field #判斷hash中指定欄位是否存在
hkeys hash #獲取hash中所有entry的key
hvals hash #獲取hash中所有entry的value
hincrby hash field 1 #指定欄位值自增1
hdecrby hash field 1 #指定欄位值自減1
hsetnx hash field value #field不存在時建立
hset user:1 name zhangsan #儲存一個物件
Zset
在set的基礎上增加一個score欄位 用於排序
基本命令
zadd set score member #增加一個值
zadd set score1 member1 score2 member2 #增加多個值
zrange set 0 -1 #檢視set中的所有值
ZRANGEBYSCORE set set min max #按照score進行排序 升序
ZRANGEBYSCORE set -inf +inf withscores #按照score進行排序 並帶出score值
ZREVRANGE set 0 -1 #降序排序
ZREM set member #移出某個元素
ZCARD set #集合中的元素個數
zcount set start end #獲取指定區間的元素個數
三種特殊資料型別
geospatial(地理位置)
適用場景:定位、附近的人、打車距離、方圓幾裡的人
GEOADD
當座標位置超出指定範圍時,該命令將會返回一個錯誤。
GEOADD key 緯度 經度 member #新增一個地理空間位置 兩級是無法直接新增的 我們一般會下載城市資料 通過java一次性匯入
GEOPOS
GEOPOS key member1 member2 #獲取指定位置的經度和緯度
GEODIST
返回兩個給定位置之間的距離。
如果兩個位置之間的其中一個不存在, 那麼命令返回空值。
指定單位的引數 unit 必須是以下單位的其中一個:
- m 表示單位為米。
- km 表示單位為千米。
- mi 表示單位為英里。
- ft 表示單位為英尺。
GEODIST key member1 member2 unit #返回兩個位置之間的距離
GEORADIUS
以給定的經緯度為中心,找出某一半徑內的元素(元素必須在集合中)
適用場景:附近的人
GEORIDUS key 經度 緯度 radius unit
GEORADIUSBYMEMBER
找出位於指定位置內的元素,中心點是由給定的位置元素決定
GEORIDUSBYMEMBER key member radius unit
GEOHASH
返回一個或多個位置的GEOHASH表示
該命令將返回11個字元的Geohash字串(將二維的經緯度轉化為一維的字串,如果兩個字串越接近,則距離越近)
GEO底層的實現原理是Zset,我們可以使用Zset命令來操作GEO
hyperloglog(基數統計的演算法)
基數:不重複的元素
優點:佔用記憶體小
0.81%的錯誤率
適用場景:網頁的瀏覽量(一個人瀏覽網頁多次,還是算作一個人)
傳統方式,set來儲存使用者id,然後就可以統計set中的元素數量作為標準判斷
基礎知識
PFadd key element1 element2 element3 #新增元素
PFcount key #統計key的元素
PFmerge destkey sourcekey #合併key
bitmap
操作二進位制位來進行記錄的 ,就只有0和1兩個狀態
適用場景:統計使用者資訊(如活躍、不活躍;365天打卡;兩個狀態的都適用)
基本命令
setbit key offset value #設定值
get key offset #獲取指定位置的值
bit count key #獲取狀態為1的個數
使用bitmap來記錄週一到週日的打卡情況
統計打卡的天數
事務
Redis單條命令是保證原子性的,但是事務不保證原子性!
Redis事務本質:一組命令的集合!一個事務中的所有命令都會被序列化,在事務執行的過程中,會按照順序執行。
特性:一次性、順序性、排他性(事務執行中不允許被打斷)
------佇列 set set set 執行 -----
Redis事務沒有隔離級別的概念
所有的命令在事務中,並沒有直接執行!只有發起執行命令的時候才會執行!Exec
redis的事務:
- 開啟事務(multi)
- 命令入隊
- 執行事務(exec)
執行事務!
放棄事務(discard):事務佇列中命令都不會被執行
編譯型異常:事務中的所有命令都不會執行
執行時異常:事務中的其他命令可以正常執行,錯誤命令丟擲異常
監控 (Watch):可用於做樂觀鎖
悲觀鎖:很悲觀,做什麼都會加鎖(Synchronize關鍵字就是這樣)
樂觀鎖:很樂觀,認為什麼時候都不會出問題,所以不會上鎖。更新的時候去判斷一下,在此期間是否有人操作過這個資料。
watch key #監視一個key 如果資料在此期間沒有發生變動,就會正常執行成功 事務執行之後 監控也會自動取消 但如果事務執行失敗 就需要使用unwatch先解鎖
Jedis
Jedis:Redis官方推薦的java連線開發工具。使用Java來操作Redis中介軟體。
1:匯入依賴
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
2:編碼測試
- 連線資料庫
Jedis jedis = new Jedis("127.0.0.1", 6379);
System.out.println(jedis.ping());//輸出PONG即連線成功
- 操作命令
- 斷開連線
jedis.close();
事務
//開啟事務
Transaction transaction = jedis.multi();
try {
//命令
transaction.set("user1", result);
transaction.set("user2", result);
//執行事務
transaction.exec();
} catch (Exception e) {
//放棄事務
transaction.discard();
e.printStackTrace();
} finally {
//關閉連線
jedis.close();
}
SpringBoot整合Redis
SpringBoot操作資料:spring-data jpa jdbc mongodb redis
SpringData也是和SpringBoot齊名的專案!
說明:在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
port: 6379
- 簡單測試
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//redisTemplate 操作不同的資料型別
//opsForValue 操作字串 類似String
//opsForList 操作List
//opsForSet
//opsForHash
//opsForGeo 操作地理位置
//獲取redis的連線物件
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushDb();
// connection.flushAll();
redisTemplate.opsForValue().set("key", "this is a key");
System.out.println(redisTemplate.opsForValue().get("key"));
}
自定義RedisTemplate
public class RedisConfig {
//編寫我們自己的redisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//為了開發方便 一般使用<String, Object>
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//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.setHashKeySerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
RedisUtils工具類編寫
/**
* Redis工具類,使用之前請確保RedisTemplate成功注入
*
* @author ye17186
* @version 2019/2/22 10:48
*/
public class RedisUtils {
private RedisUtils() {
}
@SuppressWarnings("unchecked")
private static RedisTemplate<String, Object> redisTemplate = SpringUtils
.getBean("redisTemplate", RedisTemplate.class);
/**
* 設定有效時間
*
* @param key Redis鍵
* @param timeout 超時時間
* @return true=設定成功;false=設定失敗
*/
public static boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 設定有效時間
*
* @param key Redis鍵
* @param timeout 超時時間
* @param unit 時間單位
* @return true=設定成功;false=設定失敗
*/
public static boolean expire(final String key, final long timeout, final TimeUnit unit) {
Boolean ret = redisTemplate.expire(key, timeout, unit);
return ret != null && ret;
}
/**
* 刪除單個key
*
* @param key 鍵
* @return true=刪除成功;false=刪除失敗
*/
public static boolean del(final String key) {
Boolean ret = redisTemplate.delete(key);
return ret != null && ret;
}
/**
* 刪除多個key
*
* @param keys 鍵集合
* @return 成功刪除的個數
*/
public static long del(final Collection<String> keys) {
Long ret = redisTemplate.delete(keys);
return ret == null ? 0 : ret;
}
/**
* 存入普通物件
*
* @param key Redis鍵
* @param value 值
*/
public static void set(final String key, final Object value) {
redisTemplate.opsForValue().set(key, value, 1, TimeUnit.MINUTES);
}
// 儲存普通物件操作
/**
* 存入普通物件
*
* @param key 鍵
* @param value 值
* @param timeout 有效期,單位秒
*/
public static void set(final String key, final Object value, final long timeout) {
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
/**
* 獲取普通物件
*
* @param key 鍵
* @return 物件
*/
public static Object get(final String key) {
return redisTemplate.opsForValue().get(key);
}
// 儲存Hash操作
/**
* 往Hash中存入資料
*
* @param key Redis鍵
* @param hKey Hash鍵
* @param value 值
*/
public static void hPut(final String key, final String hKey, final Object value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 往Hash中存入多個數據
*
* @param key Redis鍵
* @param values Hash鍵值對
*/
public static void hPutAll(final String key, final Map<String, Object> values) {
redisTemplate.opsForHash().putAll(key, values);
}
/**
* 獲取Hash中的資料
*
* @param key Redis鍵
* @param hKey Hash鍵
* @return Hash中的物件
*/
public static Object hGet(final String key, final String hKey) {
return redisTemplate.opsForHash().get(key, hKey);
}
/**
* 獲取多個Hash中的資料
*
* @param key Redis鍵
* @param hKeys Hash鍵集合
* @return Hash物件集合
*/
public static List<Object> hMultiGet(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
// 儲存Set相關操作
/**
* 往Set中存入資料
*
* @param key Redis鍵
* @param values 值
* @return 存入的個數
*/
public static long sSet(final String key, final Object... values) {
Long count = redisTemplate.opsForSet().add(key, values);
return count == null ? 0 : count;
}
/**
* 刪除Set中的資料
*
* @param key Redis鍵
* @param values 值
* @return 移除的個數
*/
public static long sDel(final String key, final Object... values) {
Long count = redisTemplate.opsForSet().remove(key, values);
return count == null ? 0 : count;
}
// 儲存List相關操作
/**
* 往List中存入資料
*
* @param key Redis鍵
* @param value 資料
* @return 存入的個數
*/
public static long lPush(final String key, final Object value) {
Long count = redisTemplate.opsForList().rightPush(key, value);
return count == null ? 0 : count;
}
/**
* 往List中存入多個數據
*
* @param key Redis鍵
* @param values 多個數據
* @return 存入的個數
*/
public static long lPushAll(final String key, final Collection<Object> values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}
/**
* 往List中存入多個數據
*
* @param key Redis鍵
* @param values 多個數據
* @return 存入的個數
*/
public static long lPushAll(final String key, final Object... values) {
Long count = redisTemplate.opsForList().rightPushAll(key, values);
return count == null ? 0 : count;
}
/**
* 從List中獲取begin到end之間的元素
*
* @param key Redis鍵
* @param start 開始位置
* @param end 結束位置(start=0,end=-1表示獲取全部元素)
* @return List物件
*/
public static List<Object> lGet(final String key, final int start, final int end) {
return redisTemplate.opsForList().range(key, start, end);
}
Redis配置檔案
redis啟動的時候,就要通過配置檔案來啟動。
單位(Unit)
配置檔案unit單位對大小寫不敏感
包含(INCLUDES)
可以使用多種配置檔案整合起來(好比spring的import)
網路(NETWORK)
bind 127.0.0.1 #繫結的ip
protected-mode yes #保護模式
port 6379 #埠設定
通用(GENERAL)
daemonize yes #以守護程序的方式執行,預設是no,我們需要開啟為yes
piffile /var/run/redis_6379.pid #如果以後臺的方式執行,我們就需要指定一個pid檔案
loglevel notice #日誌級別
logfile "" #日誌的檔案位置名
database 16 #資料庫的數量,預設是16個數據庫
always-show-logo yes #是否總是顯示logo
快照(SNAPSHOT)
持久化,在規定的時間內執行了多少次湊在哦,則會持久化到檔案.rdb .aof
redis是記憶體資料庫,如果沒有持久化,那麼資料斷電即失
save 900 1 #如果900秒內,至少有一個key進行了修改,那麼久進行持久化操作
stop-writes-on-bgsave-error yes #持久化如果出錯,是否繼續工作
rdbcompression yes #是否壓縮rdb檔案,需要消耗一些CPU資源
rdbchecksum yes #儲存rdb檔案時,進行錯誤的檢查校驗
dir ./ #rdb檔案儲存的目錄
賦值(REPLICATION)
主從複製時,再講解
安全(SECURITY)
可以在這裡設定redis的密碼,預設是沒有密碼的
限制(CLIENTS)
maxclients 10000 #設定能連線redis的最大客戶數量
maxmemory <bytes> #redis配置最大的記憶體數量
maxmemory-policy noeviction #記憶體達到上限之後的處理策略
APPEND ONLY模式 aof配置
appendonly no #預設不開啟aof模式,預設開啟rdb方式持久化
appendfilename "appendonly.aof" #持久化的檔名字
#appendfsync always #每次修改都會sync 消耗效能
appendfsync everysec #每秒執行一次 可能會丟失這一秒的資料
#appendfsync no #不執行sync 這個時候作業系統自己同步資料 速度最快
在redis持久化中詳細講解
持久化
redis是記憶體資料庫,如果不將記憶體中的資料庫狀態儲存到磁碟,那麼一旦伺服器程序退出,伺服器中的資料庫狀態也會消失,所以redis提供了持久化的功能
如果只做快取,是不用持久化的
RDB(Redis DataBase)
指定的時間間隔內將記憶體中的資料集快照寫入磁碟,也就是行話Snapshot。它恢復時是將快照檔案直接讀到記憶體中。
Redis會單獨建立一個fork子程序來進行持久化,先將資料寫入一個臨時檔案,持久化結束後,用這個臨時檔案替代上次持久化好的檔案。整個過程中,主程序是不進行任何IO操作的,這就確保了極高的效能。
如果需要進行大規模的資料恢復,且對於資料恢復的完整性不是特別敏感,那麼RDB方式比AOF更加高效能。RDB的缺點就是最後一次持久化的資料可能會丟失。
在主從複製中,rdb就是備用的,放在從機上面
有時候在生產環境 我們會將dump.rdb進行備份
在rdb儲存的檔案時dump.rdb 都是在我們的配置檔案中的快照模組進行配置的
觸發機制
1:save(save 900 1)的規則滿足的情況下
2:執行flushall命令
3:退出redis
會 生成一個dump.rdb檔案
如何恢復rdb檔案
1:只需要將rdb檔案放在我們的redis啟動目錄即可,redis啟動的時候就會自動檢查dump.rdb,並恢復其中的資料
2:檢視rdb存放的位置
優點
- 使用大場景的資料恢復
- 對資料的完整性不高
缺點
1:需要一定的時間間隔進行操作,如果redis意外宕機,那麼最後一次修改資料就沒有了
2:fork程序的時候,會佔用一定的記憶體空間
AOF(Append Only File)
將我們所有的命令都記錄下來,恢復的時候 就把這個檔案全部再執行一遍
以日誌的形式來記錄每個寫操作,將Redis執行過的所有命令記錄下來(讀操作不記錄),只許追加檔案但不可以改寫檔案,redis啟動之初就會讀取該檔案重新構造資料。換言之,redis重啟的話就根據日誌檔案的內容將寫指令從前到後執行有一次完成資料的恢復。
AOF儲存的是appendonly.aof檔案
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-rm72lHaF-1610615935678)(C:\Users\Anita\Pictures\60.png)]
預設是不開啟的 需要手動開啟(appendonly yes),重啟,即可生效
如果aof檔案有錯位的話,這時候redis是啟動不起來的。
redis提供了一個工具 redis-check-aof --fix (使用這個命令即可修復aof檔案)
如果檔案正常,重啟即可恢復
缺點:
- 相對於資料檔案來說,aof遠遠大於rdb,修復的速度也比rdb慢
- aof執行效率也要比rdb慢。所以redis預設配置愛就是rdb持久化
重寫規則說明(瞭解即可)
aof預設就是檔案的無線追加,檔案會越來越大
如果檔案大於64m,就會fork一個新的程序來將我們的檔案進行重寫
Redis釋出訂閱
要求更多的話可以使用訊息佇列(Kafka等MQ)
Redis釋出訂閱(pub/sub)是一種訊息通訊模式:釋出者傳送訊息,訂閱者接收訊息。(如微博、微信、關注系統等)
Redis客戶端可以訂閱任意數量的頻道
訂閱/釋出訊息圖
訊息傳送者、訊息訂閱者、頻道
基礎命令
SUBSCRIBE channel1 channel2 #訂閱頻道
PUBLISH channel message #釋出資訊
UNSUBSCRIBE channel1 channel2 #退訂頻道
PSUBSCRIBE pattern1 pattern2 #訂閱一個或多個符合給定模式的頻道
PUBSUB suncommand #檢視訂閱與釋出系統的狀態
PUNSUBSCRIBE pattern1 pattern2 #退訂所給模式的頻道
這些命令被廣泛用於構建即時通訊應用,比如網路聊天室、實時廣播和實時通訊等
一定要訂閱之後才會接收,退訂之後是無法接收的
原理
Redis是使用C實現的,通過分析Redis原始碼裡面的pubsub.c檔案,即可瞭解釋出和訂閱機制的底層實現。
Redis是通過PUBLISH、SUBSCRIBE、PSUBCRIBE等命令實現釋出和訂閱功能
通過SUBSCRTIBE命令訂閱某個頻道後,redis-server裡維護了一個字典,字典的鍵就是頻道,而字典的值就是一個連結串列,這個連結串列儲存了所有訂閱這個頻道的客戶端。SUBCRIBE命令的關鍵,就是將客戶端新增到給定頻道的訂閱連結串列中。
用過PUBLISH命令向訂閱者釋出訊息,redis-server會使用給定的頻道作為鍵,在它所維護的頻道子弟班中查詢訂閱了這個頻道的所有客戶端連結串列,遍歷這個連結串列,將訊息釋出給所有訂閱者。
使用場景:
- 實時訊息系統
- 實時聊天(頻道當做聊天室,將資訊回顯給所有人即可)
- 訂閱、關注系統
稍微複雜的場景,就會使用訊息中介軟體MQ
Redis主從複製
主從複製,讀寫分離!80%的情況都是在進行讀操作。減緩伺服器的壓力,架構中經常使用,最少都需要一主二從!
概念
主從複製,就是指一臺redis伺服器上的資料複製到其他的redis伺服器上。前者稱為主節點(master/leader),後者稱為從節點(slave/follower)。資料的複製是單向的,只能由主節點到從節點。主節點寫,從節點讀。
預設情況下,每臺redis伺服器都是主節點,且一個主節點可以有多個從節點或沒有從節點,但一個從節點只能有一個主節點。
主從複製的作用:
- 資料冗餘:主從複製實現了資料的熱備份,是持久化之外的一種資料冗餘方式
- 故障恢復:主節點出現問題後,可以由從節點提供服務,實現快速的故障恢復。實際上是一種服務冗餘
- 負載均衡:主從複製中,配合讀寫分離,可以由主節點提供寫服務,從節點提供讀服務,分擔伺服器負載,可以大大提高redis伺服器的併發量
- 高可用(叢集)基石:主從複製還是哨兵和叢集能夠實施的基礎
一般來說,將Redis運用於工程專案,只使用一臺redis是萬萬不能的,原因如下:
- 從結構上,單個Redis伺服器會發生單點故障,並且一臺伺服器需要處理所有的請求負載,壓力較大
- 從容量上,單個redis伺服器容量有限,一般來說,單臺redis的最大使用記憶體不應該超過20G
環境配置
只配置從庫,不配置主庫(因為redis預設就是主庫)
- 配置配置檔案
先拷貝三份
-
修改配置檔案(三個配置檔案都要改(79、80、81)
1:修改埠
2:開啟守護執行緒模式
3:修改pidfile
4:修改logfile
5:修改dbfilename
-
修改完畢之後,啟動三個redis伺服器,可以通過程序資訊進行檢視
-
從機認證主機
SLAVEOF host port #配置從機
真實的主從配置是在配置檔案中配置的,這樣的話才是永久的。我們這裡使用的是命令,是暫時的
細節
-
主機可以寫,從機只能讀。
主機
從機
沒有配置哨兵的話,即使主機宕機,從機所屬的主機依然不會改變。這個時候如果主機恢復了,從機依然可以獲取主機寫入的資訊
如果是使用命令列配置的主從,這個時候如果重啟了,就會變回主機。只要變回從機,就會立馬從主機中獲取值
複製原理
Slave啟動成功連線到master後會傳送一個sync同步命令
Master接收到命令後,啟動後臺的存檔程序,同時手機所有接收到的用於修改資料集命令,後臺程序執行完畢之後,master將傳送整個資料檔案到slave,並完成一次完全同步
全量複製:slave獲取到master的所有資料
增量複製:master新增的修改命令傳給slave
只要是重新連線master,一次完全同步(全量複製)將被自動執行。
哨兵模式
沒有哨兵模式的時候
當主機宕機後,這時候需要從從機中選出一個來當主節點。即使主機回來後,也無法當之前的主節點了。
SLAVEOF no one #從節點變為主節點
哨兵模式(自動選舉老大的模式)
概述:
主從切換技術的方法是:當主伺服器宕機後,需要手動把一臺伺服器切換為主伺服器,這就需要人工干預,費時費力,還會造成一段時間內服務不可用。這不是一種推薦的方式,更多時候,我們優先考慮哨兵模式。Redis從2.8開始正式提供Sentinel(哨兵)架構來解決這個問題。
能夠監控主機是否故障,如果故障了根據投票數自動將從庫轉化為主庫
哨兵模式是一種特殊的模式,首先redis提供了哨兵的命令,哨兵是一個獨立的程序,作為程序,它會獨立執行。原理是:哨兵通過傳送命令,等待redis伺服器響應,從而監控多個redis示例。
(單機哨兵)
一個哨兵進行監控,可能會出現問題,為此,我們可以使用多個哨兵進行監控。各個哨兵之間還會進行監控,這樣就形成了哨兵模式
(多哨兵模式)
假設主伺服器宕機,哨兵1先檢測到這個結果,系統並不會馬上進行failover(重新選舉),僅僅是哨兵1主觀認為主伺服器不可用,稱為主觀下線。當後面的哨兵也檢測到主伺服器不可用,並且打到一定數量是,那麼哨兵就會進行一次投票,投票的結果由一個哨兵發起,進行failover【故障轉移】操作。切換成功後,通過釋出訂閱模式,讓各個哨兵把自己監控的從伺服器切換成主伺服器,這個過程稱為客觀下線
開啟哨兵模式
-
配置sentinel.conf檔案
-
建立一個哨兵配置檔案(sentinel.conf)
-
配置檔案
sentinel monitor 被監控的主機名稱 host port 1 #1代表主機掛了,要進行重新選舉
-
-
啟動哨兵
redis-sentinel kconfig/sentinel.conf #啟動哨兵模式
主機宕機後,會重新選擇主機。這時候,即使主機回來,也只能當新主機的從機
優缺點
優點:
- 主從可以切換、故障可以轉移,可用性高
- 哨兵模式是主從模式的升級,手動到自動,更加健壯
缺點:
-
redis不好線上擴容,叢集數量一旦打到上限,擴容就十分麻煩
-
實現哨兵模式的配置其實很麻煩,裡面有很多選擇
哨兵模式的全部配置(百度檢視)
Redis快取穿透和雪崩
快取穿透(查不到導致的)
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-FsrFttit-1610615935740)(C:\Users\Anita\Pictures\81.png)]
概念
使用者查詢一個數據,發現redis資料庫沒有,也就是快取沒有命中,於是向持久層資料庫查詢,發現也沒有,於是此次查詢失敗。當用戶很多的時候,快取都沒有命中,於是都去請求資料庫,這會給持久層資料庫造成很大壓力,也就相當於出現了快取穿透。
解決方案
布隆過濾器
布隆過濾器是一種資料結構,對所有可能查詢的引數以hash形式儲存,在控制層先校驗,不符合則丟棄,從而避免了對底層儲存系統的查詢壓力。
快取空物件
當儲存層不命中後,即便返回的空物件也將其快取起來,同時設定一個過期時間,之後再訪問這個資料將會從快取中獲取,保護了後端資料來源
這種方法會存在兩個問題:
- 儲存空值會消耗大量的空間
- 即使對控制設定了過期時間,還是會存在快取層和儲存層的資料會有一段時間的視窗不一致,這對需要儲存一直性的業務會有影響
快取擊穿(量太大、快取過期導致的)
概念
快取擊穿是值一個key非常熱點(如微博熱搜),在不停的扛著大併發,大併發集中對這一個點進行訪問,當這個key在失效的瞬間,持續的大併發就穿透快取,直接請求資料庫。這就像在一個品章上鑿開了一個洞。
解決方案
設定熱點資料永不過期
從快取層面來說,沒有設定過期時間,就不會出現熱點key過期後產生的問題
加互斥鎖
分散式鎖:使用分散式鎖,保證每個key同時只有一個執行緒去查詢後端服務(後端資料庫),其他key沒有獲得分散式鎖的許可權,因此只需等待即可。這種方式將高併發的壓力轉移到了分散式鎖,因此對分散式鎖的考驗很大
快取雪崩
概念
在某個時間段,快取集中過期失效(redis宕機也會導致)。
其實快取集中過期不是非常致命,而是快取伺服器某個節點宕機或者斷網才是致命的,很有可能瞬間就把資料庫壓垮
解決方案
redis高可用
多設幾臺redis,即使一臺掛掉之後其他的也可以繼續工作。其實就是搭建叢集(異地多活)
限流降級
快取失效後,通過加鎖或者佇列來控制讀資料庫寫快取的執行緒數量。比如某個key只允許一個執行緒查詢資料和寫快取,其他執行緒等待。
資料預熱
正式部署前,將可能的資料預先訪問一遍,這樣部分可能大量訪問的資料就會加在到快取中。在即將發生大併發前,手動觸發載入快取不同的key,設定不同的過期時間,讓快取失效的時間儘量均勻。