Redis是一個基於內存的Key-Value非關系型數據庫,由C語言進行編寫。
Redis速度快的原因:基於內存、單線程、支持多路復用。
2.Redis的數據類型
Redis中提供了七種數據類型,分別是String、Hash、List、Set、ZSet、HyperLogLog、BitMap。
2.1 String
Key對應的Value是一個字符串類型。
#設置字符串類型的Key
set key value
#僅當Key不存在時設置字符串類型的Key
setnx key value
#設置字符串類型的Key並添加過期時間
setex key second value
#獲取Key對應的Value
get key
#讓Key對應的Value值遞增1
incr key
#讓Key對應的Value值遞減1
decr key
#讓Key對應的Value遞增指定的數值
incrby key num
#讓Key對應的Value遞減指定的數值
decrby key num
#往Key對應的Value中追加字符串
append key str
*在使用set、append命令時,如果Key不存在則創建,否則替換Key對應的Value值。
*在使用incr、incrby、decr、decrby命令時,如果Key不存在則初始化為0后再進行操作,如果Key對應的Value不是數值類型字符串,那么將會報錯。
2.2 Hash
Key對應的Value是一個哈希類型,每個哈希類型中都包含若干個鍵值對(屬性名與屬性值)
#往Hash中添加一個屬性
hset key field value
#僅當Key不存在時往Hash中添加一個屬性
hsetnx key field value
#往Hash中添加多個屬性
hmset key field1 value1 field2 value2
#獲取Hash中指定的一個屬性
hget key field
#獲取Hash中指定的多個屬性
hmget key field1 field2
#獲取Hash中所有的屬性
hgetall key
#讓Hash中指定的屬性遞增指定的數值(屬性值必須是數值類型)
hincrby key field num
#判斷Hash中指定的屬性是否存在
hexists key field
#獲取Hash中屬性的個數
hlen key
#獲取Hash中所有的屬性名
hkeys key
#獲取Hash中所有的屬性值
hvals key
#刪除Hash中指定的多個屬性
hdel key field1 field2
*在使用hset、hmset命令時,如果Key不存在則創建,否則往Key對應的Hash中添加屬性,如果屬性名相同則替換屬性值。
*Hash中只有hincryby命令,沒有類似String的incr、decr、decrby命令。
*當使用hdel命令刪除了Hash中的所有屬性,那么此時Key也會被刪除。
2.3 List
Key對應的Value是一個列表類型(有序可重復)
#從鏈表的左側添加元素 lpush key value #從鏈表的右側添加元素 rpush key value #僅當Key存在時從鏈表的左側添加元素 lpushx key value #僅當Key存在時從鏈表的右側添加元素 rpushx key value #獲取鏈表中指定索引范圍的元素(從鏈表的左側開始遍歷,包括begin和end的位置,如果end為-1表示倒數第一個元素) lrange key begin end #從鏈表的左側彈出一個元素 lpop key #從鏈表的右側彈出一個元素 rpop key #獲取鏈表中元素的個數 llen key #刪除鏈表中指定個數個Value(若count為正數,則從鏈表的左側開始刪除指定個數個Value,若count為負數,則從鏈表的右側開始刪除指定個數個Value,若count為0,則刪除鏈表中所有指定的Value) lrem key count value #設置鏈表中指定索引的值 lset key index value #從鏈表的右側彈出元素並將其放入到其他鏈表的左側(一般用在消息隊列的備份) rpoplpush key otherKey
*當使用lpush、rpush命令時,如果Key不存在則創建,否則往Key對應的鏈表中追加元素。
*通過Redis的List可以實現隊列和棧結構,當遵循lpush、rpop時,此時為隊列結構,當遵循lpush、lpop時,此時為棧結構。
2.4 Set
Key對應的Value是一個集合類型(無序不可重復)
#往Set中添加元素
sadd key value
#刪除Set中指定的元素
srem key value
#查看Set中的元素
smembers key
#判斷Set中是否包含某個元素
sismemeber key value
#返回Set中元素的個數
scard set
#返回兩個Set的交集
sinter set1 set2
#返回兩個Set的並集
sunion set1 set2
#返回Set1中Set2沒有的元素(補集)
sdiff set1 set2
#將Set1和Set2的交集放入到新的Set中
sinterstore destSet set1 set2
#將Set1和Set2的並集放入到新的Set中
sunionstore destSet set1 set2
#將Set1中Set2沒有的元素放入到新的Set中
sdiffstore destSet set1 set2
*在使用sadd命令時,如果Key不存在則創建,否則往Key對應的Set中追加元素。
2.5 ZSet
Key對應的Value是一個有序集合(分數可以相同但Value不能相同)
#往ZSet中添加元素 zadd key score value #獲取ZSet中指定Value的分數 zscore key value #返回ZSet中元素的個數 zcard key #獲取ZSet中指定索引范圍的元素(包括begin和end的位置,end為-1時表示倒數第一個元素) zrange key begin end #獲取ZSet中指定索引范圍的元素以及分數,返回的元素按照分數從小到大排序 zrange key begin end withscores #獲取ZSet中指定索引范圍的元素以及分數,返回的元素按照分數從大到小進行排序 zrevrange key begin end withscores #獲取ZSet中指定分數范圍的元素(包括begin和end的位置) zrangebyscore key begin end #獲取ZSet中指定分數范圍的元素並限制返回的個數 zrangebyscore key begin end limit num #返回ZSet中指定分數范圍元素的個數 zcount key begin end #刪除ZSet中指定的元素 zrem key value #刪除ZSet中指定分數范圍的元素(包括begin和end的位置) zremrangebyscore key begin end #讓ZSet中指定元素的分數遞增指定的值 zincrby key score value
*當使用zadd命令時,如果Key不存在則創建,否則往Key對應的ZSet中添加元素,如果Value相同則更新Score。
*Set、ZSet中沒有類似String、Hash、List的,當Key不存在或存在時才進行操作的命令。
2.6 HyperLogLog
Key對應的Value是一個HyperLogLog,用於做基數統計(支持在集群中使用)
#往HyperLogLog中添加元素
pfadd key value [value]
#統計HyperLogLog中的基數
pfcount key
#將多個HyperLogLog合並成一個
pfmerge destkey originKey [originKey...]
HyperLogLog是用於做基數統計的算法,優點是在輸入元素的數量或者體積非常大時,計算基數所需要的空間是固定的(12KB)
HyperLogLog只會根據輸入元素來計算基數,並不會存儲元素本身。
2.7 BitMap

Key對應的Value是一個BitMap(字節數組),數組的大小被限制在512M之內,因此最大的長度為512 * 1024 * 1024個字節。
#設置bitMap,並指定offset,value取值為0或1
setbit key offset value
#獲取bitMap中指定offset的value
getbit key offset
#返回bitMap中value為1的個數
bitcount key
#進行bitMap之間的與、或、非、異或運算,並將運算后的結果放入目標bitmap
bitop and/or/not/xor destKey sourceKey [sourceKey]
BitMap是一個字節數組,存在offset,value只能是0或者1,可以使用BitMap來做日活統計,每天對應一個BitMap,如果用戶登錄了,則使用userId來作為offset,並且對應的value設置為1,最終使用bitcount命令統計bitmap中value為1的個數,也就是當天的日活,當有多天的bitmap后,可以使用bitop命令進行一些與或運算,得到不同維度的統計。
2.8 通用命令
#查看Redis中的Key(支持通配符,*代表任意個字符,?代表任意一個字符) keys pattern #刪除Key del key #判斷Key是否存在 exists key #對Key進行重命名 rename oldKey newKey #對Key設置過期時間 expire key seconds #查看Key的有效時間(若Key沒有設置過期時間則返回-1) ttl key #查看Key的類型 type key
3.Redis事務
Redis中提供了事務的功能,Redis會將事務中的所有命令當作一個原子執行,即一個事務在執行時不會被其他命令所干擾。
#開啟事務
multi
#執行事務
exec
#放棄事務
discard
4.Redis的持久化
由於Redis是基於內存的Key-Value非關系型數據庫,因此當Redis服務掛掉后由於內存被釋放會導致數據丟失,此時可以使用Redis的持久化功能。
4.1 RDB持久化方式
RDB持久化方式即Redis每隔一定時間,就會將內存中的所有Key-Value寫入到磁盤文件中(全量寫入),當Redis服務重啟時,讀取RDB文件自動進行數據的恢復。
RDB持久化方式是默認開啟的,可以通過redis.conf配置文件中修改相關配置。
#在900秒內如果至少有1個Key發生變化,那么執行一次寫入操作 save 900 1 #在300秒內如果至少有10個Key發生改變,那么執行一次寫入操作 save 300 10 #在60秒內如果至少有10000個Key發生改變,那么執行一次寫入操作 save 60 10000 #rdb文件的保存路徑(相對於redis.conf文件) dir ./ #rdb文件的名稱 dbfilename dump.rdb
*使用RDB持久化方式有很大可能會發生Key的修改未來得及寫入到磁盤中服務器就宕機了(可以調整默認的同步策略)
*RDB持久化方式由Redis進程執行fork操作創建子進程來完成,因此阻塞只會發生在fork階段,客戶端可以通過save、bgsave命令手動觸發RDB操作,其中save命令會阻塞redis進程直到RDB持久化完成,而bgsave命令由redis進程fork子進程進行完成。
4.2 AOF持久化方式
AOF持久化方式即Redis將所有的Key-Value操作都寫入到日志文件中(追加,增量寫入),當Redis服務重啟時,讀取AOF文件自動進行數據的恢復。
AOF持久化方式提供了三種同步策略:每修改同步、每秒同步、不同步。
#開啟AOF方式 appendonly yes #AOF文件名 appendfilename "appendonly.aof" #AOF同步策略 #每修改同步 appendsync always #每秒同步 appendsync everysec #不同步 appendsync no
*AOF持久化方式也是由Redis進程執行fork操作創建子進程來完成,並且當日志文件達到一定大小時Redis會對其壓縮(重寫)
*當同時使用RDB和AOF持久化方式時,數據的恢復將固定使用AOF的。
RDB持久化方式與AOF持久化方式的區別
1.文件大小:AOF持久化方式所產生的日志文件要比RDB持久化方式所產生的rdb文件要大。
2.安全性:AOF持久化方式數據丟失的可能性要比RDB持久化方式低。
3.效率:AOF持久化方式在進行數據恢復時的效率要比RDB持久化方式的低。
5.Redis的過期清除策略
Redis中可以對Key設置過期時間,當Key已經到達過期時間時,並不會立即被刪除,而是通過Redis的過期清除策略進行處理。
Redis中使用惰性刪除和主動刪除兩種過期策略
惰性刪除:當訪問一個已經過期的Key時,將該Key刪除。
主動刪除:Redis定時刪除緩存中部分過期的Key,通過抽樣的方式保證Redis中過期的Key在低於25%以下。
*Redis定時刪除緩存中部分過期的Key是通過Redis常規任務處理的,常規任務還包含其他一些任務處理,可以通過修改redis.conf配置文件中的hz參數來調整Redis常規任務的執行頻率,默認值是10,表示每秒執行10次,其取值為1~500,但Redis不建議該值超過100,否則會影響其他的業務請求,造成延時。
6.Redis的內存淘汰機制
當Redis中有效數據使用的內存大小達到預配置的maxmemory時,將會觸發Redis的內存淘汰機制,按預先設置的淘汰策略主動刪除緩存中部分的Key。
Redis中提供了六種淘汰策略:
1.allkeys-lru(推薦):刪除最近最少使用的Key。
2.volatile-lru:在設置了過期時間的Key中,刪除最近最少使用的Key。
3.allkeys-random:隨機刪除Key。
4.volatile-random:在設置了過期時間的Key中隨機刪除Key。
5.volatile-ttl:刪除即將過期的Key。
6.noeviction:不清除,對於寫請求直接返回錯誤。
*可以通過redis.conf配置文件中的maxmemory-policy參數設置Redis的淘汰策略。
7.Redis的應用場景
7.1 使用Redis作為分布式緩存
簡單的把對象放入Redis,需要避免Redis中的數據與數據庫的不一致。
7.2 使用Redis作為分布式鎖(保持獨占)
通過Redis中的一個Key來進行控制,當Key已存在,則表示鎖已經被其它線程鎖持有,當Key不存在時,設置Key,表示獲取鎖資源。
1.如何保證當系統宕機時鎖也能夠被釋放?
通過對Key設置過期時間,當Key已經過期並再次訪問時,通過惰性刪除過期策略刪除該Key。
2.如何保證獲取鎖時的同步性?
通過使用Redis提供的set(String key, String value, String nxxx, String expx, long time)命令,將判斷Key是否存在以及設置Key作為一個原子操作。
3.如何保證釋放的鎖是當前線程的?
給Key設置一個代表當前線程的Value,當刪除該Key時判斷Value是否屬於當前線程的。
/** * 獲取鎖 */ publicbooleangetLock(Stringkey, Stringvalue, intsecond) { return"OK".equalsIgnoreCase(jedis.set(key, value, "nx", "ex", second)); } /** * 釋放鎖 */ publicbooleanunLock(Stringkey, Stringvalue) { if(value.equals(jedis.get(key))) { jedis.del(key); returntrue; } returnfalse; }
7.3 使用Redis進行數量控制(秒殺)以及頻次控制(錯誤次數)
數量控制(秒殺、搶購)
1.系統在搶購前初始化該Key對應的Value。
2.在進行搶購時直接使用decr命令,並根據返回值判斷是否搶購成功,當返回值大於等於0,表示搶購成功,否則搶購失敗。
*依賴incr、incrby、decr、decrby命令的返回值,可以避免並發導致的同步問題(不需要獲取值再判斷是否大於0,因為不是原子操作,會有同步問題)
頻次控制(登錄錯誤次數)
1.用戶輸入用戶名以及密碼,首先判斷代表用戶的Key是否存在,僅當Key不存在或Key的值小於所允許的最大登錄錯誤次數時,執行登錄操作,否則不允許登錄。
2.執行登錄操作,如果登錄成功則繼續處理,否則使用incr命令+1(Key不存在時初始化為0再操作),並根據命令返回值判斷當前的錯誤次數是否已經達到了所允許的最大值,若達到了則設置Key的過期時間。
7.4 使用Redis進行限流
限流的算法一般有計數器、漏桶、令牌桶。
1.如果接口的QPS為100,那么先執行incr命令,如果incr命令的返回值為1,表示Redis中之前不存在這個key,那么設置過期時間為1s,然后繼續處理。
2.如果incr的返回值不為1,同時小於等於100,則繼續處理,否則將被限流。
7.5 使用Redis作為注冊中心
分布式服務框架的注冊中心。
8.使用Redis面臨的問題
8.1 緩存雪崩
緩存雪崩即Redis中有大量的Key集中時間失效,那么此時所有的客戶端請求都會去到數據庫中,從而增加了數據庫的壓力。
*解決緩存雪崩的方法是對於被頻繁訪問的Key不設置過期時間,對於不頻繁訪問的Key分散設置過期時間。
8.2 緩存穿透
緩存穿透即不斷查詢Redis中不存在Key,從而占用Redis以及數據庫的連接,容易被惡意占用資源。
*解決緩存穿透的方法是當數據庫查詢不到記錄時,也將該Key放入到緩存中,其Value是一個空字符串。
8.3 緩存擊穿
緩存擊穿即Redis中一個被高並發訪問的Key突然過期,那么此時將會有大量的請求直接去到數據庫,從而增加了數據庫的壓力。
*解決緩存擊穿的方法是對於被頻繁訪問的Key不設置過期時間。
8.4 關於Redis中的數據與數據庫的不一致
當使用Redis作為分布式緩存時如何避免Redis中的數據與數據庫的不一致
1.當查詢記錄時,先從緩存中進行查詢,如果緩存中已存在則直接返回,否則從數據庫進行查詢,然后將記錄放入到緩存並且設置過期時間。
2.當新增記錄時,先執行SQL,如果有必要的話則放入緩存(因為並不一定所有新增的記錄在它的過期時間內就能夠被訪問,因此為了節省空間,可以在第一次查詢時才放入緩存(需要注意緩存擊穿問題)
3.當更新記錄時,先執行SQL,再刪除緩存,如果刪除失敗則重試。
4.當刪除記錄時,先執行SQL,再刪除緩存,如果刪除失敗則重試。
為什么當更新記錄時,要先執行SQL再刪除緩存?
因為如果是先刪除緩存再執行SQL,那么有可能在刪除緩存以及執行SQL這個過程,如果有其他線程查詢這條記錄,那么將會把數據庫的舊值放入到緩存,最終導致緩存中的數據是新的,數據庫中的數據是舊的。
為什么當更新記錄時,不能夠直接更新緩存?
如果存在線程A和線程B同時修改同一條記錄,如果SQL的執行順序是先執行線程A再執行線程B,那么數據庫將會是線程B更新的值,如果緩存的更新順序是先執行線程B再執行線程A,那么緩存中將會是線程A的值,最終導致緩存中的數據與數據庫的不一致,因為應用程序與數據庫以及應用程序與Redis都是通過網絡進行通訊的,竟然是通過網絡進行通訊,那么就不能夠保證請求的順序性(除非加上分布式鎖)
由於一些特殊情況,還是有可能導致Redis中的數據與數據庫的不一致(比如在執行更新SQL后,應用或者Redis服務宕機)
1.由於緩存項有設置過期時間,當緩存項到達過期時間后自動被清除。
2.定期對緩存進行全量更新(把所有緩存項刪除或者重新初始化緩存)
*導致緩存中的數據與數據庫中的不一致主要是在更新的過程。
9.Redis的安裝
1.下載Redis源碼並進行解壓

2.由於Redis是使用C語言編寫的,因此需要安裝gcc編譯器,並使用make命令進行編譯
make
3.進行redis的安裝
make PREFIX=/usr/redis install
*安裝完后生成bin目錄,里面包含一些Redis的可執行命令。
4.將redis源碼目錄下的redis.conf配置文件復制到安裝目錄中,並將配置文件中的daemonize改為yes,通過后台的方式啟動Redis

5.啟動Redis Server

*Redis Server是一個進程,包含了若干個線程,但只有一個線程用於處理客戶端的請求(Redis單線程處理任務,不會出現共享變量不一致的問題)
6.啟動Redis Client

*Redis Server中包含16個數據庫,編號從0~15,每個數據庫之間的數據相互獨立,客戶端默認連接的是第一個數據庫,可以通過select num命令修改連接的數據庫。
10.Java中使用Redis
Jedis是Redis官方推薦的Redis Java客戶端類庫。
1.導入依賴
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.0.1</version> </dependency>
2.獲取Redis連接
//1.直接創建Jedis實例 Jedisjedis=newJedis(Stringhost, intport); //2.通過Jedis連接池來管理連接 JedisPoolConfigpoolConfig=newJedisPoolConfig(); //最大連接數 poolConfig.setMaxTotal(intnum); //最大空閑連接數 poolConfig.setMaxIdle(intnum); //創建連接池對象 JedisPooljedisPool=newJedisPool(poolConfig, Stringhost, intport); //獲取Redis連接 Jedisjedis=jedisPool.getResource();
*當Redis連接使用完畢后需要手動關閉。
/** * @Auther: ZHUANGHAOTANG * @Date: 2019/4/2 17:11 * @Description: */ publicclassRedisUtils{ privatestaticfinalStringhost="192.168.1.80"; privatestaticfinalintport=6379; privatestaticJedisPooljedisPool=null; static{ JedisPoolConfigpoolConfig=newJedisPoolConfig(); //最大連接數 poolConfig.setMaxTotal(10); //最大空閑連接數 poolConfig.setMaxIdle(5); //創建連接池對象 jedisPool=newJedisPool(poolConfig, host, port); } publicstaticJedisgetConnection() { returnjedisPool.getResource(); } }
