Redis(入门)
文章目錄
- 一、 Redis簡介
- 二、 基于Docker安裝Redis單機版
- 三、 Redis常用命令
- 1 Key操作
- 2 字符串值(String)(值的長度不超過512MB)
- 3 哈希表(Hash)
- 4 列表(List)
- 5 集合(Set)
- 6 有序集合(Sorted Set)
- 四、 Redis持久化策略
- 1 RDB
- 2 AOF
- 五、 Redis主從復制
- 六、 哨兵(Sentinel)
- 七、Redis集群(Cluster)
- 八、 Jedis(了解)
- 九、 使用SpringBoot整合SpringDataRedis操作redis
- 十、 高并發下Redis可能存在的問題及解決方案
- 1 緩存擊穿
- 2 緩存雪崩
- 3 緩存穿透(查詢不存在數據)
- 4 邊路緩存
- 5 Redis腦裂
- 6 Redis 緩存淘汰策略/當內存不足時如何回收數據/保證Redis中數據不出現內存溢出情況
一、 Redis簡介
1 NoSQL簡介
目前市場主流數據存儲都是使用關系型數據庫。每次操作關系型數據庫時都是I/O操作,I/O操作是主要影響程序執行性能原因之一,連接數據庫關閉數據庫都是消耗性能的過程。盡量減少對數據庫的操作,能夠明顯的提升程序運行效率。
針對上面的問題,市場上就出現了各種NoSQL(Not Only SQL,不僅僅可以使用關系型數據庫)數據庫,它們的宣傳口號:不是什么樣的場景都必須使用關系型數據庫,一些特定的場景使用NoSQL數據庫更好。
常見NoSQL數據庫:
memcached :鍵值對,內存型數據庫,所有數據都在內存中。
Redis:和Memcached類似,還具備持久化能力。
HBase:以列作為存儲。
MongoDB:以Document做存儲。
2 Redis簡介
Redis是以Key-Value形式進行存儲的NoSQL數據庫。
Redis是使用C語言進行編寫的。
平時操作的數據都在內存中,效率特高,讀的效率110000/s,寫81000/s,所以多把Redis當做緩存工具使用。
Redis以solt(槽)作為數據存儲單元,每個槽中可以存儲N多個鍵值對。Redis中固定具有16384。理論上可以實現一個槽是一個Redis。每個向Redis存儲數據的key都會進行crc16算法得出一個值后對16384取余就是這個key存放的solt位置。
同時通過Redis Sentinel提供高可用,通過Redis Cluster提供自動分區。
二、 基于Docker安裝Redis單機版
1 拉取鏡像
docker pull redis:6.2.12 創建并啟動容器
docker run -d --name redis -p 6379:6379 --restart always redis:6.2.13 客戶端測試
docker exec -it redis bash docker exec -it redis redis-cli 在任意目錄在輸入redis-cli 即可進入redis命令行。三、 Redis常用命令
Redis中數據是key-value形式,key為字符串類型,value可取類型如下:
? String 字符串
? Hash 哈希表
? List 列表
? Set 集合
? Sorted Set 有序集合
Redis命令相關手冊有很多,下面為其中比較好用的兩個
https://www.redis.net.cn/order/
http://doc.redisfans.com/
1 Key操作
1.1 exists
判斷key是否存在。
語法:exists key名稱
返回值:存在返回數字,不存在返回0
1.2 expire
設置key的過期時間,單位秒
語法:expire key 秒數
返回值:成功返回1,失敗返回0
1.3 ttl
查看key的剩余過期時間
語法:ttl key
返回值:返回剩余時間,如果不過期返回-1,如key不存在返回-2
1.4 del
根據key刪除鍵值對。
語法:del key [key…]
返回值:被刪除key的數量
1.5 keys
查看當前redis中的key數據
語法: keys 表達式 如: keys *
返回值:符合表達式的key列表
2 字符串值(String)(值的長度不超過512MB)
應?場景:
驗證碼、計數器、訂單重復提交、?戶登錄信息、商品詳情、分布式鎖
2.1 set(mset批量設置)
設置指定key的值
語法:set key value
返回值:成功OK
2.2 get(mget批量獲取)
獲取指定key的值
語法:get key
返回值:key的值。不存在返回nil
2.3 setnx
當且僅當key不存在時才新增。恒新增,無修改功能。
語法:setnx key value
返回值:不存在時返回1,存在返回0
底層:
setnx具備分布式鎖能力。在編寫代碼時如果調用setnx,時會對代碼進行加鎖。直到刪除該key時會解鎖。
setnx();// 加鎖
// 代碼
del();//解鎖。
如果在并發訪問時第一個線程setnx()時發現沒有指定key會正常向下運行。其他線程在執行setnx()時發現有這個key就會等待,等待第一個線程刪除key時才會繼續向下執行。
常見的鎖
鎖:在Java中可以通過鎖,讓多線程執行時某個代碼塊或方法甚至類是線程安全的。通俗點說:一個線程訪問,別的線程需要等待。
線程鎖:同一個應用。多線程訪問時添加的鎖。synchronized(自動釋放)或Lock(手動釋放)
進程鎖:不同進程(一個進程就是一個應用)需要訪問同一個資源時,可以通過添加進程鎖進行實現。
分布式鎖:在分布式項目中不同項目訪問同一個資源時,可以通過添加分布式鎖保證線程安全。常見的分布式鎖有兩種:Redis的分布式鎖和Zookeeper的分布式鎖(通過調用Zookeeper的API給Zookeeper集群添加一個節點。如果節點能添加繼續向下執行,執行結束刪除該節點。如果其他線程發現該節點已經添加,會阻塞等待該節點刪除才繼續向下執行。)。
2.4 setex
設置key的存活時間,無論是否存在指定key都能新增,如果存在key覆蓋舊值。同時必須指定過期時間。
語法:setex key seconds value
返回值:OK
2.5 incr
對key的值加一,返回新值
語法:incr key
返回值:新值
2.5 incrby
對key的值加increment,返回新值,若key不存在,操作之前key置為0
語法:incr key increment
返回值:新值
2.5 getset
對key設置新值,返回舊值(set返回為ok)
語法:getset key aa
返回值:key的舊值
3 哈希表(Hash)
應用場景:
購物?、?戶個人信息、商品詳情
3.1 hset
給key中field設置值。
語法:hset key field value
返回值:成功1,失敗0
3.2 hget
獲取key中某個field的值
語法:hget key field
返回值:返回field的內容
3.3 hmset
給key中多個filed設置值
語法:hmset key field value field value
返回值:成功OK
3.4 hmget
一次獲取key中多個field的值
語法:hmget key field field
返回值:value列表
3.5 hkeys
獲取key中所有的field的值
語法: hkeys key
返回值: field 列表
3.6 hvals
獲取key中所有value的值
語法:hvals key
返回值:value列表
3.7 hgetall
獲取所有field和value
語法:hgetall key
返回值:field和value交替顯示列表
3.8 hdel
刪除key中任意個field
語法:hdel key field field
返回值:成功刪除field的數量,當刪除key中所有的field,key自動刪除。
4 列表(List)
應?場景:
簡單隊列、最新評論列表、非實時排行榜:定時計算榜單(如手機日銷榜單)
4.1 rpush
向列表末尾中插入一個或多個值
語法;rpush key value value
返回值:列表長度
4.2 lrange
返回列表中指定區間內的值。可以使用-1代表列表末尾
語法:lrange list 0 -1
返回值:查詢到的值
4.3 lpush
將一個或多個值插入到列表前面
語法:lpush key value value
返回值:列表長度
4.4 llen
獲取列表長度
語法:llen key
返回值:列表長度
4.5 lrem
刪除列表中元素。count為正數表示從左往右刪除的數量。負數從右往左刪除的數量。
語法:lrem key count value
返回值:刪除數量。
4.6 lpop rpop
移除并獲取最后一個元素,并返回該元素
語法:lpop key rpop key
返回值:刪除元素。
4.6 brpop
移除并獲取最后一個元素,沒有元素會阻塞列表直到超時或發現有可彈出元素
語法:brpop key timeout
返回值:刪除元素。
5 集合(Set)
應用場景:
去重
社交應用關注、粉絲、共同好友
統計網站的PV、uV、IP
大數據里面的用戶畫像標簽集合
5.1 sadd
向集合中添加內容。不允許重復。
語法:sadd key value value value
返回值:本次命令新增數據個數
5.2 scard
返回集合元素數量
語法:scard key
返回值:集合長度
5.3 smembers
查看集合中元素內容
語法:smembers key
返回值:集合中元素
5.4 srem
刪除集合中的元素
語法: srem key member [member…]
返回值:刪除元素個數
6 有序集合(Sorted Set)
應用場景:
實時排行榜:商品熱銷榜、
體育類應用熱門球隊、積分榜優先級任務、隊列
朋友圈文章點贊-取消,邏輯:用戶只能點贊或取消,統計一篇文章被點贊了多少次,可以直接取里面有多少個成員
數據結構介紹:
使?HashMap+跳表skipList保證數據存儲和有序
跳躍表性能堪?紅?樹,?且實現起來?紅?樹簡單很多
有序集合中每個value都有一個分數(score),根據分數進行排序。
6.1 zadd
向有序集合中添加一個或者多個數據
語法:zadd key score value score value
返回值:新增的元素個數
6.2 zrange
返回區間內容,從小到大,withscores表示帶有分數
語法:zrange key start stop [withscores]
返回值:值列表
6.3 zrem
刪除集合內容
語法: zrem key member [member …]
返回值: 刪除元素個數
6.4 zcard
獲取有序集合成員數
語法: zcard key
返回值: 個數
6.5 zcount
獲取有序集合指定區間內的成員數
語法: zcard key min max
返回值: 個數
6.6 zincrby
給指定元素增加increment分數
語法: zincrby key increment member
返回值: 增量后的分數
6.2 zrevange
返回區間內容,從大到小,withscores表示帶有分數
語法:zrange key start stop [withscores]
返回值:值列表
四、 Redis持久化策略
Redis不僅僅是一個內存型數據庫,還具備持久化能力。
1 RDB
rdb模式是默認模式,可以在指定的時間間隔內生成數據快照(snapshot),默認保存到dump.rdb文件中。當redis重啟后會自動加載dump.rdb文件中內容到內存中。
用戶可以使用SAVE(同步)或BGSAVE(異步)手動保存數據。
可以設置服務器配置的save選項,讓服務器每隔一段時間自動執行一次BGSAVE命令,可以通過save選項設置多個保存條件,但只要其中任意一個條件被滿足,服務器就會執行BGSAVE命令。
例如:
save 900 1
save 300 10
save 60 10000
那么只要滿足以下三個條件中的任意一個,BGSAVE命令就會被執行
服務器在900秒之內,對數據庫進行了至少1次修改
服務器在300秒之內,對數據庫進行了至少10次修改
服務器在60秒之內,對數據庫進行了至少10000次修改
1.1 優點
rdb文件是一個緊湊文件,直接使用rdb文件就可以還原數據。
數據保存會由一個子進程進行保存,不影響父進程。
恢復數據的效率要高于aof
1.2 缺點
每次保存點之間,因redis不可意料的關閉,可能會導致丟失數據。
由于每次保存數據都需要fork()子進程,在數據量比較大時可能會比較耗費性能。
2 AOF
AOF默認是關閉的,需要在配置文件中開啟AOF。Redis支持AOF和RDB同時生效,如果同時存在,AOF優先級高于RDB(Redis重新啟動時會使用AOF進行數據恢復)
監聽執行的命令,如果發現執行了修改數據的操作,同時直接同步到數據庫文件中。
2.1 優點
相對RDB數據更加安全。
2.2 缺點
相同數據集AOF要大于RDB。
相對RDB可能會慢一些。
2.3 開啟辦法
修改redis.conf中。
appendonly yes 開啟aof
appendfilename 設置aof數據文件,名稱隨意。
五、 Redis主從復制
Redis支持集群功能。為了保證單一節點可用性,redis支持主從復制功能。每個節點有N個復制品(replica),其中一個復制品是主(master),另外N-1個復制品是從(Slave),也就是說Redis支持一主多從。
一個主可有多個從,而一個從又可以看成主,它還可以有多個從。
1 主從優點
增加單一節點的健壯性,從而提升整個集群的穩定性。(Redis中當超過1/2節點不可用時,整個集群不可用)
從節點可以對主節點數據備份,提升容災能力。
讀寫分離。在redis主從中,主節點一般用作寫(具備讀的能力),從節點只能讀,利用這個特性實現讀寫分離,寫用主,讀用從。
2 基于Docker一主多從搭建
2.1 拉取redis鏡像
2.2 創建并運行三個Docker容器
先停止單機版Redis。單機版Redis端口6379
三個容器分別占用系統的6479、6480、6481端口
2.3 在從中指定主的ip和端口
設定redis1容器為主機(master)。redis2和redis3容器為從機(slave)
進入redis2容器內部設置主的ip和端口,連接從機,指定主機端口
進入redis2容器內部設置主的ip和端口
# docker exec -it redis3 redis-cli # slaveof 192.168.108.128 6379 # exit2.4 測試主從效果
進入redis1容器內部,新增key-value
分別進入redis2和redis3容器,查看是否有name鍵
# docker exec -it redis1 redis-cli # get name六、 哨兵(Sentinel)
在redis主從默認是只有主具備寫的能力,而從只能讀。如果主宕機,整個節點不具備寫能力。但是如果這是讓一個從變成主,整個節點就可以繼續工作。即使之前的主恢復過來也當做這個節點的從即可。
Redis的哨兵就是幫助監控整個節點的,當節點主宕機等情況下,幫助重新選取主。
Redis中哨兵支持單哨兵和多哨兵。單哨兵是只要這個哨兵發現master宕機了,就直接選取另一個master。而多哨兵是根據我們設定,達到一定數量哨兵認為master宕機后才會進行重新選取主。我們以多哨兵演示。
1 沒有哨兵下主從效果
只要殺掉主,整個節點無法在寫數據,從身份不會變化,主的信息還是以前的信息。
七、Redis集群(Cluster)
1 集群原理
a) 集群搭建完成后由集群節點平分(不能平分時,前幾個節點多一個槽)16384個槽。
b) 客戶端可以訪問集群中任意節點。所以在寫代碼時都是需要把集群中所有節點都配置上。
c) 當向集群中新增或查詢一個鍵值對時,會對Key進行Crc16算法得出一個小于16384值,這個值就是放在哪個槽中,在判斷槽在哪個節點上,然后就操作哪個節點。
集群:集群中所有節點都安裝在不同服務器上。
偽集群:所有節點都安裝在一臺服務器上,通過不同端口號進行區分不同節點。
當集群中超過或等于1/2節點不可用時,整個集群不可用。為了搭建穩定集群,都采用奇數節點。
Redis每個節點都支持一主多從。會有哨兵監控主的狀態。如果出現(配置文件中配置當多少個哨兵認為主失敗時)哨兵發現主不可用時會進行投票,投票選舉一個從當作主,如果后期主恢復了,主當作從加入節點。在搭建redis集群時,內置哨兵策略。
演示時:創建3個節點,每個節點搭建一主一從。一共需要有6個redis。
2 Redis集群安裝步驟
2.1 新建配置模板文件
紅色IP部分需要修改為自己的Linux虛擬機IP
port ${PORT} protected-mode no cluster-enabled yes cluster-config-file nodes.conf cluster-node-timeout 5000 cluster-announce-ip 192.168.137.128 cluster-announce-port ${PORT} cluster-announce-bus-port 1${PORT} appendonly yes2.2 使用Shell腳本創建6個目錄
for port in `seq 7000 7005`; do \mkdir -p ./${port}/conf \&& PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf \&& mkdir -p ./${port}/data; \ done2.3 創建橋連網絡
# docker network create redis-net查看網絡是否創建成功
# docker network ls2.4 創建并啟動6個容器
for port in `seq 7000 7005`; do \docker run -d -ti -p ${port}:${port} -p 1${port}:1${port} \-v /usr/local/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \-v /usr/local/redis-cluster/${port}/data:/data \--restart no --name redis-${port} --net redis-net \--sysctl net.core.somaxconn=1024 redis:5.0.5 redis-server /usr/local/etc/redis/redis.conf; \ done2.5 查看6個容器ip及端口
# docker inspect redis-7000 redis-7001 redis-7002 redis-7003 redis-7004 redis-7005 | grep IPAddress2.6 執行集群腳本
進入6個容器中任意一個。示例中以redis-7000舉例
執行創建腳本命令。 --cluster-relicas 1表示每個主有1個從。
redis-cli --cluster create \ 172.18.0.2:7000 \ 172.18.0.3:7001 \ 172.18.0.4:7002 \ 172.18.0.5:7003 \ 172.18.0.6:7004 \ 172.18.0.7:7005 \ --cluster-replicas 1輸入后給出集群信息,輸入yes后創建集群
2.7 驗證集群
在任意Redis容器內部,進入Redis客戶端工具。
示例中還是以Redis-7000舉例。
八、 Jedis(了解)
Redis給Java語言提供了客戶端API,稱之為Jedis。
Jedis API和Redis 命令幾乎是一樣的。
例如:Redis對String值新增時set命令,Jedis中也是set方法。所以本課程中沒有重點把所有方法進行演示,重要演示Jedis如何使用。
Jedis API特別簡單,基本上都是創建對象調用方法即可。由于Jedis不具備把對象轉換為字符串的能力,所以每次都需要借助Json轉換工具進行轉換,這個功能在Spring Data Redis中已經具備,推薦使用Spring Data Redis。
九、 使用SpringBoot整合SpringDataRedis操作redis
1 Spring Data簡介
Spring Data是Spring公司的頂級項目,里面包含了N多個二級子項目,這些子項目都是相對獨立的項目。每個子項目是對不同API的封裝。
所有Spring Boot整合Spring Data xxxx的啟動器都叫做spring-boot-starter-data-xxxx
Spring Data 好處很方便操作對象類型(基于POJO模型)。
只要是Spring Data 的子項目被Spring Boot整合后都會有一個XXXXTemplate示實例。
把Redis不同值得類型放到一個opsForXXX方法中。
opsForValue : String值(最常用),如果存儲Java對象或Java中集合時就需要使用序列化器,進行序列化成JSON字符串。
opsForList : 列表List
opsForHash: 哈希表Hash
opsForZSet: 有序集合Sorted Set
opsForSet : 集合
2 Spring Data Redis序列化器介紹
經常需要向Redis中保存Java中Object或List等類型,這個時候就需要通過序列化器把Java中對象轉換為字符串進行存儲。
2.1 JdkSerializationRedisSerializer
是RedisTemplate類默認的序列化方式。JdkSerializationRedisSerializer使用JDK自帶的序列化方式。要求被序列化的對象必須實現java.io.Serializable接口,而且存儲的內容為二進制數據,這對開發者是不友好的。會出現雖然不影響使用,但是直接使用Redis客戶端查詢Redis中數據時前面出現亂碼問題。
2.2 OxmSerializer
以字符串格式的xml存儲。解析起來也比較復雜,效率也比較低。已經很少有人在使用該序列化器。
2.3 StringRedisSerializer
只能對String類型序列化操作。
2.4 GenericToStringSerializer
需要調用者給傳遞一個對象到字符串互轉的Converter(轉換器),使用比較麻煩。
2.5 Jackson2JsonRedisSerializer
該序列化器可以將對象自動轉換為Json的形式存儲,效率高且對調用者友好。
優點:
速度快,序列化后的字符串短小精悍,不需要實現Serializable接口。
缺點:
此類的構造函數中有一個類型參數,必須提供要序列化對象的類型信息(.class對象)。如果存儲List等帶有泛型的類型,此序列化器是無法識別泛型的,會直接把泛型固定設置為LinkedHashMap。
例如:存儲時List , 取出時是List
2.6 GenericJackson2JsonRedisSerializer
與Jackson2JsonRedisSerializer功能相似。底層依然使用Jackson工具包。相比Jackson2JsonRedisSerializer多了_class列,列里面存儲類(新增時類型)的全限定路徑,從Redis取出時根據_class類型進行轉換,解決了泛型問題。
該序列化器不需要指定對象類型信息(.class對象)使用Object作為默認類型。目前都使用這個序列化器。
3 代碼步驟
基于單元測試演示
3.1 添加依賴
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.0-M3</version> </parent><dependencies><!-- 為了要在項目中jackson工具包 --><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency> </dependencies>3.2 配置配置文件
spring.redis.host=localhost 默認值
spring.redis.port=6379 端口號默認值
如果連接Redis集群,不需要配置host,配置spring.redis.cluster.nodes,取值為redis集群所在ip:port,ip:port。由于word排版問題nodes后面取值沒有和nodes在一行。
3.3 編寫配置類
@Configuration public class RedisConfig {@Beanpublic RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();redisTemplate.setConnectionFactory(factory);//配置key和value的序列化器。不適用默認的jdk序列化,使用json序列化redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());//如果使用hash數據類型。可以提供格外的序列化器redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());return redisTemplate;} }3.4 編寫代碼
package com.bjsxt;import com.bjsxt.pojo.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.test.context.junit4.SpringRunner;import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit;@SpringBootTest public class TestDataRedis {/*** Spring Data Redis提供的客戶端對象。這個對象* 是spring-boot-starter-data-redis構建的。* 類型是RedisTemplate* 默認創建的客戶端泛型是RedisTemplate<Object, Object>* 代表,當前客戶端對象在訪問Redis的時候,對key的類型約束是Object,對value的類型約束是Object** RedisTemplate中,提供了key和value的序列化器。* 默認提供的序列化器都是JDKSerializer,基于Serializable實現的序列化。* 數據讀寫的時候,RedisTemplate先把參數key和value,用序列化器轉換成字節數組。* 在實現讀寫操作。** RedisTemplate是基于模板設計模式開發的類型。其中也包含很多其他的設計模式,包括不限于:* 工廠方法設計模式,構建器設計模式,模板方法設計模式,裝飾模式等。* RedisTemplate基于工廠方法,為不同的數據類型,準備了不同的訪問客戶端。* 如:字符串操作,ValueOperations,通過redisTemplate.opsForValue()方法獲取。* 如:hash操作,HashOperations,通過redisTemplate.opsForHash()獲取。* RedisTemplate中也有大量的直接訪問Redis服務器的方法,這些方法都是操作key的或者管理服務器的。* 如:刪除鍵值對;查詢有效時間;刪除有效時間;設置有效時間等。*/@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Testpublic void testKeys(){redisTemplate.delete("user1");redisTemplate.expire("users", 1L, TimeUnit.MINUTES);System.out.println(redisTemplate.getExpire("users"));redisTemplate.persist("users");System.out.println(redisTemplate.getExpire("users"));}@Testpublic void testUsers(){List<User> user = new ArrayList<>();for(int i = 0; i < 3; i++){user.add(new User(i, "name"+i, "male"));}redisTemplate.opsForValue().set("users", user);}@Testpublic void testList(){redisTemplate.opsForList().rightPushAll("test-list", "1", "2", "3");System.out.println(redisTemplate.opsForList().range("test-list", 0, -1));}@Testpublic void testHash(){redisTemplate.opsForHash().put("test-hash1", "f1", "v1");System.out.println(redisTemplate.opsForHash().get("test-hash1", "f1"));}@Testpublic void testSetUser(){User user = new User(1, "張三", "男");ValueOperations<String, Object> valueOps = redisTemplate.opsForValue();valueOps.set("user3", user);System.out.println(valueOps.get("user3"));/*redisTemplate.opsForValue().set("user2", user);System.out.println(redisTemplate.opsForValue().get("user2"));*/}// 新增字符串數據@Testpublic void testSet(){redisTemplate.opsForValue().set("data-k2", "data-v2");Object value = redisTemplate.opsForValue().get("data-k2");System.out.println(value);System.out.println(redisTemplate.opsForValue().get("data-k1"));}@Testpublic void testClient(){System.out.println(redisTemplate);} }十、 高并發下Redis可能存在的問題及解決方案
1 緩存擊穿
緩存中沒有但數據庫中有的數據,假如是熱點數據,那key在緩存過期的?刻,同時有?量的請求,這些請求都會擊穿到DB,造成瞬時DB請求量?、壓?增?。和緩存雪崩的區別在于這?針對某?key緩存,后者則是很多key。
解決辦法:
設置熱點數據不過期,定時任務定時更新緩存
設置互斥鎖
1.1 ReentrantLock(重入鎖)
JDK對對于并發訪問處理的內容都放入了java.util.concurrent中
ReentrantLock性能和synchronized沒有區別的,但是API使用起來更加方便。
@SpringBootTest public class MyTest {@Testpublic void test(){new Thread(){@Overridepublic void run() {test2("第一個線程111111");}}.start();new Thread(){@Overridepublic void run() {test2("第二個線程222222");}}.start();try {Thread.sleep(20000);} catch (InterruptedException e) {e.printStackTrace();}}ReentrantLock lock = new ReentrantLock();public void test2(String who){lock.lock();if(lock.isLocked()) {System.out.println("開始執行:" + who);try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("執行完:" + who);lock.unlock();}} }1.2 解決緩存擊穿實例代碼
只有在第一次訪問時和Key過期時才會訪問數據庫。對于性能來說沒有過大影響,因為平時都是直接訪問redis。
private ReentrantLock lock = new ReentrantLock(); @Override public Item selectByid(Integer id) {String key = "item:"+id;if(redisTemplate.hasKey(key)){return (Item) redisTemplate.opsForValue().get(key);}lock.lock();if(lock.isLocked()) {Item item = itemDubboService.selectById(id);// 由于設置了有效時間,就可能出現緩存擊穿問題redisTemplate.opsForValue().set(key, item, 7, TimeUnit.DAYS);lock.unlock();return item;}// 如果加鎖失敗,為了保護數據庫,直接返回nullreturn null; }SpringCache解決?案:
緩存的同步 ,sync 可以指示底層將緩存鎖住,使只有?個線程可以進?計算,?其他線程堵塞,直到返回結果更新到緩存中
@Cacheable(value = {"product"},key ="#root.args[0]", cacheManager ="customCacheManager", sync=true)2 緩存雪崩
緩存雪崩 (多個熱點key都過期)?量的key設置了相同的過期時間,導致在緩存在同?時刻全部失效,造成瞬時DB請求量?、壓?驟增,引起雪崩
預防:
存數據的過期時間設置隨機,防?同?時間?量數據過期現象發?
設置熱點數據永遠不過期,定時任務定時更新
SpringCache解決?案
1 設置差別的過時時間,?如CacheManager配置多個過期時間維度
2 配置?件 time-to-live 配置
3 緩存穿透(查詢不存在數據)
查詢?個不存在的數據,由于緩存是不命中的,并且出于容錯考慮,如發起為id為“-1”不存在的數據如果從存儲層查不到數據則不寫?緩存這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。存在?量查詢不存在的數據,可能DB就掛掉了,這也是?客利?不存在的key頻繁攻擊應?的?種?式。
預防
接?層增加校驗,數據合理性校驗
緩存取不到的數據,在數據庫中也沒有取到,這時也可以將key-value對寫為key-null,設置短點的過期時間,防?同個key被?直攻擊
SpringCache解決?案
空結果也緩存,默認不配置condition或者unless就?
spring:cache:type: redis#過時時間redis:time-to-live: 3600000# 開啟前綴,默以為trueuse-key-prefix: true# 鍵的前綴,默認就是緩存名cacheNameskey-prefix: XD_CACHE# 是否緩存空結果,防?緩存穿透,默以為truecache-null-values: true4 邊路緩存
cache aside pattern 邊路緩存問題。其實是一種指導思想,思想中包含:
5 Redis腦裂
Redis腦裂主要是指因為一些網絡原因導致Redis Master和Redis Slave和Sentinel集群處于不同的網絡分區。Sentinel連接不上Master就會重新選擇Master,此時就會出現兩個不同Master,好像一個大腦分裂成兩個一樣。
Redis集群中不同節點存儲不同的數據,腦裂會導致大量數據丟失。
解決Redis腦裂只需要在Redis配置文件中配置兩個參數
min-slaves-to-write 3 //連接到master的最小slave數量
min-slaves-max-lag 10 //slave連接到master的最大延遲時間
6 Redis 緩存淘汰策略/當內存不足時如何回收數據/保證Redis中數據不出現內存溢出情況
Redis中數據都放入到內存中。如果沒有淘汰策略將會導致內存中數據越來越多,最終導致內存溢出。在Redis5中內置了緩存淘汰策略。在配置文件中有如下配置
# maxmemory-policy noeviction 默認策略noevication # maxmemory <bytes> 緩存最大閾值 # volatile-lru -> 在設置過期key集中選擇使用數最小的。 # allkeys-lru -> 在所有key中選擇使用最小的。 # volatile-lfu -> 在設置過期時間key集中采用lfu算法。 # allkeys-lfu -> 在所有key中采用lfu算法。 # volatile-random -> 在設置過期key集中隨機刪除。 # allkeys-random -> 在所有key中隨機刪除。 # volatile-ttl -> 在設置了過期時間key中刪除最早過期時間的。 # noeviction -> 不刪除key,超過時報錯。6.1 Lru和lfu算法
6.1.1 LRU
LRU (Least recently used) 最近最少使用,如果數據最近被訪問過,那么將來被訪問的幾率也更高。LRU算法實現簡單,運行時性能也良好,被廣泛的使用在緩存/內存淘汰中。
? 新數據插入到鏈表頭部
? 每當緩存命中(即緩存數據被訪問),則將數據移到鏈表頭部
? 當鏈表滿的時候,將鏈表尾部的數據丟棄
6.1.2 LFU
Least Frequently Used(最近最不經常使用)如果一個數據在最近一段時間很少被訪問到,那么可以認為在將來它被訪問的可能性也很小。因此,當空間滿時,最小頻率訪問的數據最先被淘汰。
6.1.3 LRU和LFU的區別
LRU淘汰時淘汰的是鏈表最末尾的數據。而LFU是一段時間內訪問次數最少的。
6.2 何時淘汰數據
在Redis中每次新增數據都會判斷是否超過閾值。如果超過了,就會按照淘汰策略刪除一些key。
6.3 每次刪除多少
淘汰數據量和新增數據量進行判斷。
總結
- 上一篇: 手机为什么不能root
- 下一篇: Redis(案例一:注册登录-图形验证码