Redis
如何開啟暂衡、關閉惩歉、連接
linux常規(guī)命令開關
yum安裝gcc-c++環(huán)境
yum install gcc-c++
自動配置文件
make
確認是否全部安裝激率,沒安裝的再次安裝
make install
啟動
cd src
./redis-server
可根據(jù)配置文件啟動
/www/server/redis/redis.conf
寶塔Linux面板命令開關
redis安裝目錄
/www/server/redis
啟動
/etc/init.d/redis start
停止
/etc/init.d/redis stop
redis配置文件目錄
/www/server/redis/redis.conf
連接
/www/server/redis/src/redis-cli
redis-benchmark 壓力測試
cd src
./redis-benchmark [options]
基礎知識
redis一共有16個數(shù)據(jù)庫娄帖,默認使用第1個
select [index] #切換數(shù)據(jù)庫
flushdb #清空當前數(shù)據(jù)庫
flushall #清空所有數(shù)據(jù)庫
keys * #查看當前數(shù)據(jù)庫的所有KV
為什么redis是單線程還快
Redis是基于內(nèi)存的操作,CPU不是Redis的瓶頸插龄,Redis的瓶頸最有可能是機器內(nèi)存的大小或者網(wǎng)絡帶寬愿棋。既然單線程容易實現(xiàn),而且CPU不會成為瓶頸均牢,那就順理成章地采用單線程的方案了糠雨。
1.redis是基于內(nèi)存的,內(nèi)存的讀寫速度非撑枪颍快
2.redis是單線程的甘邀,省去了很多上下文切換線程的時間
3.redis使用多路復用技術琅攘,可以處理并發(fā)的連接。非阻塞IO 內(nèi)部實現(xiàn)采用epoll松邪,采用了epoll+自己實現(xiàn)的簡單的事件框架坞琴。epoll中的讀、寫测摔、關閉置济、連接都轉(zhuǎn)化成了事件解恰,然后利用epoll的多路復用特性锋八,絕不在io上浪費一點時間。
https://zhuanlan.zhihu.com/p/58038188
五大數(shù)據(jù)類型
Redis-key
127.0.0.1:6379> flushall #清除所有數(shù)據(jù)庫的緩存
OK
127.0.0.1:6379> flushdb #清除當前數(shù)據(jù)庫的緩存
OK
127.0.0.1:6379> set name pang #設置key-value為name-pang
OK
127.0.0.1:6379> keys * #查詢所以的key
1) "name"
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> get name #獲取key為name的value
"pang"
127.0.0.1:6379> expire name 10 #設置name的過期時間為10s
(integer) 1
127.0.0.1:6379> ttl name #查看剩余的有效時間
(integer) 3
127.0.0.1:6379> ttl name #過期的話剩余時間為-2
(integer) -2
127.0.0.1:6379> exists name age #查看name和age是否存在
(integer) 1 #存在數(shù)量為1
127.0.0.1:6379> exists name #查看name是否存在
(integer) 0 #不存在
127.0.0.1:6379> move age 1 #將age這個數(shù)據(jù)移動到1號數(shù)據(jù)庫里
(integer) 1
127.0.0.1:6379> select 1 #選擇數(shù)據(jù)庫1
OK
127.0.0.1:6379[1]> keys * #查看所以key
1) "age"
String類型
127.0.0.1:6379> set name pang #設置key-value為name-pang
OK
127.0.0.1:6379> type name #查看類型
string
127.0.0.1:6379> append key1 +hello #拼接字符串
(integer) 12 #長度為12
127.0.0.1:6379> get key1
"value1+hello"
127.0.0.1:6379> strlen key1 #查看字符串長度
(integer) 12
127.0.0.1:6379> set views 0 #設置訪問量
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views #自增
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views #自減
(integer) 1
127.0.0.1:6379> incrby views 10 #自增步長為10
(integer) 11
127.0.0.1:6379> decrby views 5 #自減步長為5
(integer) 6
127.0.0.1:6379> set name1 "hello,pang"
OK
127.0.0.1:6379> getrange name1 0 5 #查看下標0到5的值
"hello,"
127.0.0.1:6379> getrange name1 0 -1 #查看全部
"hello,pang"
127.0.0.1:6379> setrange name1 5 haha #將name1的下標5之后的值覆蓋為haha [5,~
(integer) 10
127.0.0.1:6379> get name1
"hellohahag"
127.0.0.1:6379> setex name1 10 zheng #name1存在設置失效時間為10s并且值為zheng
OK
127.0.0.1:6379> ttl name1
(integer) 5
127.0.0.1:6379> ttl name1
(integer) 3
127.0.0.1:6379> ttl name1
(integer) -2
127.0.0.1:6379> setnx name1 pang #如果不存在則設置name1的值為pang
(integer) 1
127.0.0.1:6379> get name1
"pang"
127.0.0.1:6379> mset name2 pang age2 18 #設置多值
OK
127.0.0.1:6379> mget name2 age2 #獲取多值
1) "pang"
2) "18"
127.0.0.1:6379> msetnx name3 hahah3 age3 didi3 #不存在設置多值
(integer) 1
127.0.0.1:6379> keys *
1) "name3"
2) "key1"
3) "age3"
4) "name1"
5) "views"
6) "age2"
7) "name2"
8) "name"
127.0.0.1:6379> set user:1 {name:pang,age:18} #存儲對象
OK
127.0.0.1:6379> get user:1
"{name:pang,age:18}"
127.0.0.1:6379> getset name1 yang #先取舊值再設置新值
"pang"
使用場景
- 計數(shù)器 incrby
- 統(tǒng)計多單位的數(shù)量 uid:43241:follow 0 有人關注就inrc 取關就decr
- 粉絲數(shù)
- 對象緩存
List(列表)
127.0.0.1:6379> lpush mylist 1 2 3 4 #lpush:往左邊插入
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
127.0.0.1:6379> lrange mylist 0 2
1) "4"
2) "3"
3) "2"
127.0.0.1:6379> rpush mylist 5 #rpush:往右邊插入
(integer) 5
127.0.0.1:6379> rpush mylist 6
(integer) 6
127.0.0.1:6379> lrange mylist 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "5"
6) "6"
127.0.0.1:6379> lpop mylist 1 #lpop:刪除left前1個數(shù)據(jù)
1) "4"
127.0.0.1:6379> rpop mylist 2 #rpop:刪除right前2個數(shù)據(jù)
1) "6"
2) "5"
127.0.0.1:6379> lrange mylist 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> lindex mylist 0 #lindex:查看左邊下標為0的數(shù)據(jù)
"3"
127.0.0.1:6379> lindex mylist 1
"2"
127.0.0.1:6379> llen mylist #llen:查看長度
(integer) 3
127.0.0.1:6379> lpush mylist 1 1 1 1 1
(integer) 8
127.0.0.1:6379> lrange mylist 0 -1
1) "1"
2) "1"
3) "1"
4) "1"
5) "1"
6) "3"
7) "2"
8) "1"
127.0.0.1:6379> lrem mylist 3 1 #lrem:刪除3個1
(integer) 3
127.0.0.1:6379> rpush list2 one two three four five six
(integer) 6
127.0.0.1:6379> lrange list2 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
5) "five"
6) "six"
127.0.0.1:6379> ltrim list2 1 2 #ltrim:截取成下標1和小標2的數(shù)據(jù)
OK
127.0.0.1:6379> lrange list2 0 -1
1) "two"
2) "three"
127.0.0.1:6379> linsert list2 after "three" "four" #linsert:在three的after添加four
(integer) 3
127.0.0.1:6379> lrange list2 0 -1
1) "two"
2) "three"
3) "four"
127.0.0.1:6379> linsert list2 before "two" "one" #在two的before添加one
(integer) 4
小結(jié):
- 實際上是一個鏈表护盈,before after 挟纱,left right 都可以插入
- 如果key不存在創(chuàng)建新鏈表
- 如果key存在則新增內(nèi)容
- 在兩邊插入或改動效率最高!
可用于消息隊列(lpush rpop)腐宋,棧(lpush lpop)
Set(集合)
無序不重復
127.0.0.1:6379> sadd myset "i" "love" "yang" #sadd:添加元素
(integer) 3
127.0.0.1:6379> sismember myset love #sismember:查看是否是集合內(nèi)元素
(integer) 1
127.0.0.1:6379> sismember myset pang
(integer) 0
127.0.0.1:6379> smembers myset #smembers:查看集合所有元素
1) "love"
2) "i"
3) "yang"
127.0.0.1:6379> scard myset #scard:查看集合元素個數(shù)
(integer) 3
127.0.0.1:6379> srem myset i #srem:刪除集合元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 2
127.0.0.1:6379> sadd myset pang wang li zhang
(integer) 4
127.0.0.1:6379> smembers myset
1) "love"
2) "li"
3) "pang"
4) "zhang"
5) "wang"
6) "yang"
127.0.0.1:6379> srandmember myset 1 #srandmember:隨機取1個值
1) "zhang"
127.0.0.1:6379> srandmember myset 1
1) "pang"
127.0.0.1:6379> srandmember myset 1
1) "li"
127.0.0.1:6379> spop myset 1 #spop:隨機彈出1個值
1) "love"
127.0.0.1:6379> smembers myset
1) "li"
2) "pang"
3) "zhang"
4) "wang"
5) "yang"
127.0.0.1:6379> smove myset myset2 li #smove:移動元素去其他集合
(integer) 1
127.0.0.1:6379> smembers myset2
1) "li"
127.0.0.1:6379> sadd myset2 pang tao lv wang
(integer) 4
127.0.0.1:6379> sdiff myset myset2 #sdiff:差集
1) "zhang"
2) "yang"
127.0.0.1:6379> sinter myset myset2 #sinter:交集
1) "pang"
2) "wang"
127.0.0.1:6379> sunion myset myset2 #sunion:并集
1) "lv"
2) "li"
3) "tao"
4) "pang"
5) "wang"
6) "zhang"
7) "yang"
用法:
- 共同關注紊服,共同好友,六度分割理論
Hash (哈希)
127.0.0.1:6379> hset myhash field1 value1 field2 value2 #hset:設值
(integer) 2
127.0.0.1:6379> hget myhash field1 #hget:取值
"value1"
127.0.0.1:6379> hget myhash field2
"value2"
127.0.0.1:6379> hgetall myhash #hgetall:取所有的值
1) "field1"
2) "value1"
3) "field2"
4) "value2"
127.0.0.1:6379> hdel myhash field1 #hdel:刪除值
(integer) 1
127.0.0.1:6379> hlen myhash #hlen:查看個數(shù)
(integer) 1
127.0.0.1:6379> hexists myhash field1 #hexists:是否存在
(integer) 0
127.0.0.1:6379> hexists myhash field2
(integer) 1
127.0.0.1:6379> hkeys myhash #hkeys:查看所以keys
1) "field2"
2) "field3"
3) "field4"
127.0.0.1:6379> hsetnx myhash field0 10 #hsetex胸竞,hsetnx:存在或不存在則設值
(integer) 1
127.0.0.1:6379> hincrby myhash field0 10 #hincrby:自增
(integer) 20
#########
127.0.0.1:6379> hset user:1 name zhangsan age 18 #存入對象user:1
(integer) 2
127.0.0.1:6379> hgetall user:1
1) "name"
2) "zhangsan"
3) "age"
4) "18"
hash更適合儲存對象
Zset(有序集合)
類似于set只是在set的key-value之間加了一個數(shù)量 key-score-value
127.0.0.1:6379> zadd salary 2500 zhangsan #zadd:添加值
(integer) 1
127.0.0.1:6379> zadd salary 1000 lisi
(integer) 1
127.0.0.1:6379> zadd salary 2000 wangwu
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf #zrangebyscore:升序排序
1) "lisi"
2) "wangwu"
3) "zhangsan"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #帶上score [withscores]
1) "lisi"
2) "1000"
3) "wangwu"
4) "2000"
5) "zhangsan"
6) "2500"
127.0.0.1:6379> zcard salary #zcard:查看個數(shù)
(integer) 3
127.0.0.1:6379> zrevrange salary 0 -1 withscores #zrevrange:降序
1) "zhangsan"
2) "2500"
3) "wangwu"
4) "2000"
5) "lisi"
6) "1000"
127.0.0.1:6379> zcount salary 1000 2500 #在score在[1000,2500]區(qū)間的數(shù)據(jù)個數(shù)
(integer) 3
127.0.0.1:6379> zcount salary 1001 2499
(integer) 1
使用:
- 儲存成績表啊欺嗤,工資表啊
- 排行榜
三大特殊數(shù)據(jù)類型
geospatial地理位置
geoadd
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
geopos
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
geodist 兩地距離
127.0.0.1:6379> geodist china:city beijing shanghai km
"1067.3788"
georadius 以經(jīng)緯度為中心搜索范圍內(nèi)的位置
127.0.0.1:6379> georadius china:city 115 39 1000 km #經(jīng)緯115 39為中心1000km內(nèi)的位置
1) "beijing"
127.0.0.1:6379> georadius china:city 115 39 1000 km withdist #顯示到中心點區(qū)域和距離
1) 1) "beijing"
2) "156.4529"
127.0.0.1:6379> georadius china:city 115 39 1000 km withcoord #顯示范圍內(nèi)的城市信息
1) 1) "beijing"
2) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> georadius china:city 115 39 1000 km withcoord count 1 #顯示范圍內(nèi)的城市信息限定數(shù)量1個
1) 1) "beijing"
2) 1) "116.39999896287918091"
2) "39.90000009167092543"
georadiusbymember 顯示以北京為中心10000 km內(nèi)的城市名字
127.0.0.1:6379> georadiusbymember china:city beijing 10000 km
1) "hangzhou"
2) "shanghai"
3) "beijing"
底層基于zset
127.0.0.1:6379> zrange china:city 0 -1
1) "hangzhou"
2) "shanghai"
3) "beijing"
127.0.0.1:6379> zrem china:city beijing #移除元素
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "hangzhou"
2) "shanghai"
Hyperloglog
簡介
Redis2.8.9更新的Hyperloglog數(shù)據(jù)結(jié)構
技術統(tǒng)計算法
優(yōu)點:占用內(nèi)存是固定的,2^64不同的元素卫枝,只需占12kb煎饼。比起用set存儲,大大節(jié)省內(nèi)存校赤。
不過有0.81% 的錯誤率
使用
127.0.0.1:6379> pfadd k1 9 8 4 9 7 3 3 5 4 #添加k1
(integer) 1
127.0.0.1:6379> pfadd k2 1 5 1 5 0 6 7 1 4 6 6 #添加k2
(integer) 1
127.0.0.1:6379> pfcount k1 #查詢k1吆玖,注意不可重復
(integer) 6
127.0.0.1:6379> pfcount k2 #查詢k2,注意不可重復
(integer) 6
127.0.0.1:6379> pfmerge k3 k1 k2 #k1和k2的并集马篮,存于k3
OK
127.0.0.1:6379> pfcount k3
(integer) 9
bitmaps
優(yōu)點:在儲存方面使用0或者1二進制位沾乘,可用于上班打卡這類只存在兩種結(jié)果的事情上
127.0.0.1:6379> setbit daka 0 1 #設置打卡1代表打卡0代表沒打卡
(integer) 0
127.0.0.1:6379> setbit daka 1 0 #bit位1的位置為0
(integer) 1
127.0.0.1:6379> setbit daka 2 0 #bit位2的位置為0
(integer) 0
127.0.0.1:6379> setbit daka 3 0 #...
(integer) 0
127.0.0.1:6379> setbit daka 4 1
(integer) 0
127.0.0.1:6379> setbit daka 5 1
(integer) 0
127.0.0.1:6379> setbit daka 6 1
(integer) 0
127.0.0.1:6379> bitcount daka [start end]#統(tǒng)計打卡天數(shù),可加其實和結(jié)束
(integer) 4
redis的setbit設置或清除的是bit位置,而bitcount計算的是byte位置
start 和 end 參數(shù)的設置浑测,都可以使用負數(shù)值:比如 -1 表示最后一個位翅阵,而 -2 表示倒數(shù)第二個位。
https://www.cnblogs.com/song27/p/12329554.html
事務
redis的事務不保證原子性迁央,所謂原子性就是要成功都成功掷匠,一個失敗全部失敗。redis的事務類似于隊列漱贱,先進的先出槐雾,后進的后出!
redis事務流程
- 開啟事務 multi
- 命令入隊 .....
- 執(zhí)行事務 exec
執(zhí)行事務
127.0.0.1:6379> multi #開啟事務
OK
#命令入隊
127.0.0.1:6379(TX)> set key1 value1
QUEUED
127.0.0.1:6379(TX)> set key2 value2
QUEUED
127.0.0.1:6379(TX)> get key1
QUEUED
127.0.0.1:6379(TX)> get key2
QUEUED
127.0.0.1:6379(TX)> exec #執(zhí)行事務
1) OK #set key1 value1
2) OK #set key2 value2
3) "value1" #get key1
4) "value2" #get key2
放棄事務
127.0.0.1:6379> multi #開啟事務
OK
127.0.0.1:6379(TX)> set key1 value1#設值
QUEUED
127.0.0.1:6379(TX)> discard #放棄事務
OK
127.0.0.1:6379> get key1 #無法查到值
(nil)
編譯時異常(錯誤命令)
127.0.0.1:6379> multi #開啟事務
OK
127.0.0.1:6379(TX)> set key1 value1 #設值
QUEUED
127.0.0.1:6379(TX)> getxxx hahaha #輸入錯誤命令幅狮,觸發(fā)編譯時異常
(error) ERR unknown command `getxxx`, with args beginning with: `hahaha`,
127.0.0.1:6379(TX)> exec #執(zhí)行事務
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get key1 #發(fā)現(xiàn)無法取到key1的值募强,說明事務內(nèi)發(fā)生編譯時異常株灸,則全部命令都不會執(zhí)行
(nil)
運行時異常
127.0.0.1:6379> set name "pxm" #設值
OK
127.0.0.1:6379> multi #開啟事務
OK
127.0.0.1:6379(TX)> set key1 value1 #設值key1
QUEUED
127.0.0.1:6379(TX)> incr name #制造運行時異常,因為字符串非數(shù)字不能自增
QUEUED
127.0.0.1:6379(TX)> set key2 value2 #設值key2
QUEUED
127.0.0.1:6379(TX)> exec #執(zhí)行事務
1) OK #set key1 value1執(zhí)行成功
2) (error) ERR value is not an integer or out of range #incr name執(zhí)行失敗
3) OK #set key2 value2執(zhí)行成功
127.0.0.1:6379> mget key1 key2 #獲取key1 key2成功擎值,事務內(nèi)說明運行時異常發(fā)生只會影響此異常命令
1) "value1"
2) "value2"
監(jiān)控
樂觀鎖:什么時候出現(xiàn)問題什么時候加鎖 攜帶版本號進行比較
悲觀鎖:無論什么時候都會加鎖慌烧,例如java里的sychoinized
使用watch進行監(jiān)控:
正常情況:
127.0.0.1:6379> set inmoney 1000 #設置進賬1000
OK
127.0.0.1:6379> set outmoney 1000 #設置出賬1000
OK
127.0.0.1:6379> watch inmoney #監(jiān)控入賬
OK
127.0.0.1:6379> multi #開啟事務
OK
127.0.0.1:6379(TX)> incrby inmoney 200 #進賬增200
QUEUED
127.0.0.1:6379(TX)> decrby outmoney 200#出賬減200
QUEUED
127.0.0.1:6379(TX)> exec #執(zhí)行事務(成功)
1) (integer) 1200
2) (integer) 800
監(jiān)控中被他線程修改,使用watch當做樂觀鎖:
127.0.0.1:6379> mget inmoney outmoney
1) "1200"
2) "800"
127.0.0.1:6379> watch inmoney #使用watch監(jiān)控鸠儿,當做樂觀鎖
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby inmoney 200
QUEUED
127.0.0.1:6379(TX)> decrby outmoney 200
QUEUED
127.0.0.1:6379(TX)> exec #由于監(jiān)控期間他線程修改了inmoney的值屹蚊,導致觸發(fā)樂觀鎖,事務失敗
(nil)
解決方法:
127.0.0.1:6379> unwatch #使用unwatch 解綁所有的監(jiān)控
OK
127.0.0.1:6379> watch inmoney #再次使用watch綁定进每,確保監(jiān)控的為最新的值
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> incrby inmoney 200
QUEUED
127.0.0.1:6379(TX)> decrby outmoney 200
QUEUED
127.0.0.1:6379(TX)> exec #執(zhí)行事務時汹粤,先對比監(jiān)控的值是否發(fā)生改變,如果沒有則執(zhí)行成功田晚,改變了則失敗嘱兼,則再解綁重綁
1) (integer) 2600
2) (integer) 400
jedis
使用java操作redis的中間件
-
導入依賴
<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.51</version> </dependency>
-
使用
Jedis jedis = new Jedis("47.99.171.23", 6379);//連接遠程需要打開安全組防火墻和修改配置文件 jedis.flushDB(); ....
遠程redis連接:https://blog.csdn.net/weixin_44502804/article/details/90044682
Spring-boot整合
springboot操作數(shù)據(jù):spring-data jpa jdbc redis!
springdata也是和springboot其名的項目
為啥springboot2.x后使用lettuce而舍棄jedis
jedis:采用直連技術贤徒,多線程操作是不安全的芹壕,需要使用jedis pool褂删。更像BIO模式
lettuce:采用netty铺敌,實例可以多個線程中共享,不存在線程安全問題攘残,更像NIO模式
redis自動配置類源碼分析:
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")//如果沒有redisTemplate的類才執(zhí)行序宦,說明我們可以自定義
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();// 泛型為object使用的時候需要轉(zhuǎn)向
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
整合測試
1 導入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2 配置連接測試
配置redis
@SpringBootTest
class SpringbootRedisApplicationTests {
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Test
void contextLoads() {
redisTemplate.opsForValue().set("myKey","myValue");
System.out.println(redisTemplate.opsForValue().get("myKey"));
}
}
重寫自動配置類
由于redis的自動配置類太low了睁壁,另外在傳json數(shù)據(jù)時需要序列化,所以我們自定義自動配置類
@Configuration
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
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);
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-study」
Redis.conf配置文件
INCLUDES 包含
# include /path/to/local.conf
# include /path/to/other.conf
可以導入其他配置文件
NETWORK 網(wǎng)絡
bind 127.0.0.1
protected-mode no
port 6379
GENERAL
daemonize yes #是否以守護進程的方式運行挨厚,默認no堡僻,我們要開啟yes,即后臺運行
pidfile /www/server/redis/redis.pid
#如果指定了pid文件疫剃,Redis會在啟動時指定的地方寫入pid文件
#并在退出時刪除它钉疫。
#當服務器運行非守護時,如果沒有pid文件巢价,則不創(chuàng)建pid文件
#在配置中指定牲阁。當服務器被守護時,pid文件
#被使用壤躲,即使沒有指定城菊,默認為"/var/run/redis.pid"。
#創(chuàng)建一個pid文件是最好的努力:如果Redis不能創(chuàng)建它
#沒有壞的事情發(fā)生碉克,服務器將正常啟動和運行凌唬。
logfile "/www/server/redis/redis.log"
databases 16
always-show-logo yes #就是啟動時像漢堡一樣的東西哈哈
SNAPSHOTTING 快照
持久化,在規(guī)定的時間內(nèi)漏麦,執(zhí)行多少次操作的話客税,則會持久化到文件
沒有持久化的話况褪,數(shù)據(jù)斷點即失
save 900 1 #900s 至少一個key修改了則持久化操作
save 300 10 #...
save 60 10000 #...
stop-writes-on-bgsave-error yes #持久化出錯是否繼續(xù)工作
rdbcompression yes#是否壓縮rdb
rdbchecksum yes #是否檢查rdb
dbfilename dump.rdb #rdb文件名
dir /www/server/redis/ #儲存位置
REPLICATION 復制
replicaof <masterip> <masterport> #配置主機的地址和端口
masterauth <master-password> #主機若有密碼配置密碼
SECURITY 安全
設置密碼可以在配置文件里設置,也可以直接命令行設置
config get requirepass #查看密碼
config set requirepass#設置密碼
auth xxxxx #輸入密碼
CLIENTS 客戶端
# maxclients 10000 #默認最大連接1w臺
MEMORY MANAGEMENT 內(nèi)存管理
# MAXMEMORY POLICY 內(nèi)存達到上限的解決策略
noeviction #不刪除策略, 達到最大內(nèi)存限制時, 如果需要更多內(nèi)存, 直接返回錯誤信息更耻。(默認值)
allkeys-lru #所有key通用; 優(yōu)先刪除最近最少使用(less recently used ,LRU) 的 key测垛。
volatile-lru #只限于設置了 expire 的部分; 優(yōu)先刪除最近最少使用(less recently used ,LRU) 的 key。
allkeys-random #所有key通用; 隨機刪除一部分 key秧均。
volatile-random #只限于設置了 expire 的部分; 隨機刪除一部分 key食侮。
volatile-ttl #只限于設置了 expire 的部分; 優(yōu)先刪除剩余時間(time to live,TTL) 短的key。
APPEND ONLY MODE aof配置
appendonly no #默認不開啟aof模式目胡,默認使用rdb方式持久化
appendfilename "appendonly.aof" #aof文件名
# appendfsync always #每次修改都會sync
appendfsync everysec #每秒都sync锯七,可能會丟失1s的數(shù)據(jù)
# appendfsync no #不執(zhí)行sync,這個時候操作系統(tǒng)自己同步讶隐,速度最快
redis持久化
RDB(Redis DataBase)
RDB其實就是把數(shù)據(jù)以快照的形式保存在磁盤上起胰。什么是快照呢久又,你可以理解成把當前時刻的數(shù)據(jù)拍成一張照片保存下來巫延。
RDB持久化是指在指定的時間間隔內(nèi)將內(nèi)存中的數(shù)據(jù)集快照寫入磁盤。也是默認的持久化方式地消,這種方式是就是將內(nèi)存中數(shù)據(jù)以快照的方式寫入到二進制文件中,默認的文件名為dump.rdb炉峰。
既然RDB機制是通過把某個時刻的所有數(shù)據(jù)生成一個快照來保存,那么就應該有一種觸發(fā)機制脉执,是實現(xiàn)這個過程疼阔。對于RDB來說,提供了2種機制:save半夷、bgsave(默認)我們分別來看一下
- save
該命令會阻塞當前Redis服務器婆廊,執(zhí)行save命令期間,Redis不能處理其他命令巫橄,直到RDB過程完成為止淘邻,執(zhí)行完成時候如果存在老的RDB文件,就把新的替代掉舊的湘换。我們的客戶端可能都是幾萬或者是幾十萬宾舅,這種方式顯然不可取。
- bgsave(默認)
執(zhí)行該命令時彩倚,Redis會在后臺異步進行快照操作筹我,快照同時還可以響應客戶端請求。具體流程如下帆离。具體操作是Redis進程執(zhí)行fork操作創(chuàng)建子進程蔬蕊,RDB持久化過程由子進程負責,完成后自動結(jié)束哥谷。阻塞只發(fā)生在fork階段岸夯,一般時間很短概而。基本上 Redis 內(nèi)部所有的RDB操作都是采用 bgsave 命令
在配置文件可以修改save參數(shù)囱修,save 900 1 (900s 至少一個key修改了則持久化操作)赎瑰,則會生成dump.rdb文件用于持久化。
127.0.0.1:6379> config get dir #rdb文件放在這就可以直接讀取恢復破镰,進行持久化
1) "dir"
2) "/www/server/redis"
rdb的優(yōu)缺點
- 優(yōu)點
- 生成RDB文件的時候餐曼,redis主進程會fork()一個子進程來處理所有保存工作,主進程不需要進行任何磁盤IO操作
- RDB 在恢復大數(shù)據(jù)集時的速度比 AOF 的恢復速度要快
- 缺點
- RDB快照是一次全量備份鲜漩,存儲的是內(nèi)存數(shù)據(jù)的二進制序列化形式源譬,存儲上非常緊湊。當進行快照持久化時孕似,會開啟一個子進程專門負責快照持久化踩娘,子進程會擁有父進程的內(nèi)存數(shù)據(jù),如果redis宕機了喉祭,最后一次的修改數(shù)據(jù)就沒了养渴。
- fork會消耗額外內(nèi)存
AOF(Append Only File)
全量備份總是耗時的,有時候我們提供一種更加高效的方式AOF泛烙,工作機制很簡單理卑,redis會將每一個收到的寫命令都通過write函數(shù)追加到文件中。通俗的理解就是日志記錄蔽氨。
appendonly no #默認不開啟aof模式藐唠,默認使用rdb方式持久化
appendfilename "appendonly.aof" #aof文件名
# appendfsync always #每次修改都會sync
appendfsync everysec #每秒都sync,可能會丟失1s的數(shù)據(jù)
# appendfsync no #不執(zhí)行sync鹉究,這個時候操作系統(tǒng)自己同步宇立,速度最快
通俗的說,就是每寫一條set命令自赔,就會追加到appendonly.aof文件中妈嘹,下次啟動時再次執(zhí)行只寫命令從而做到持久化操作。
數(shù)據(jù)修復
redis-check-aof --fix appendonly.aof
可以用于不小心輸入了flushall刪掉了所有緩存文件匿级,然后打開appendonly.aof刪掉保存的flushall命令后執(zhí)行恢復即可蟋滴。
AOP重寫
aof持久化策略是將Redis執(zhí)行的命令追加到一個文件之中,當Redis重啟時會加載這個文件依次重新執(zhí)行命令痘绎,以達到數(shù)據(jù)恢復的目的津函。但是隨著Redis的運行,aof文件之中會存在很多冗余的命令孤页,以及無效的命令(例如尔苦,先SET一個鍵,在將這個鍵刪除),這樣會導致aof文件過于龐大允坚,還原數(shù)據(jù)所需要的時間也會相應的增大魂那,因此在滿足一定條件的情況下,需要重寫aof文件稠项,將冗余的命令進行合并涯雅,同時將無效的命令進行刪除。
AOF的方式也同時帶來了另一個問題展运。持久化文件會變的越來越大活逆。為了壓縮aof的持久化文件。redis提供了bgrewriteaof命令拗胜。將內(nèi)存中的數(shù)據(jù)以命令的方式保存到臨時文件中蔗候,同時會fork出一條新進程來將文件重寫
重寫aof文件的操作,并沒有讀取舊的aof文件埂软,而是將整個內(nèi)存中的數(shù)據(jù)庫內(nèi)容用命令的方式(因為可能會有很多無效命令例如锈遥,先SET一個鍵,在將這個鍵刪除)重寫了一個新的aof文件勘畔,這點和快照有點類似所灸。
重寫配置:
no-appendfsync-on-rewrite no #默認關閉
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb #舊文件達到64mb時重寫
AOF優(yōu)缺點
優(yōu)點
- AOF可以更好的保護數(shù)據(jù)不丟失,一般AOF會每隔1秒咖杂,最多丟失1秒鐘的數(shù)據(jù)庆寺。
- AOF日志文件即使過大的時候,出現(xiàn)后臺重寫操作诉字,也不會影響客戶端的讀寫
缺點:
- 對于同一份數(shù)據(jù)來說,AOF日志文件通常比RDB數(shù)據(jù)快照文件更大
- 進行數(shù)據(jù)恢復的時候知纷,沒有恢復一模一樣的數(shù)據(jù)出來壤圃。
AOF和RDB比較
[圖片上傳失敗...(image-4c3d8d-1630630196405)]
redis訂閱與發(fā)布
redis發(fā)布訂閱(pub/sub)是一種消息通信模式。
測試
訂閱端:
127.0.0.1:6379> SUBSCRIBE channel1 #訂閱channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "hello world"
1) "message"
2) "channel1"
3) "hello redis"
^C
127.0.0.1:6379> UNSUBSCRIBE channel1 #退訂channel1
1) "unsubscribe"
2) "channel1"
3) (integer) 0
發(fā)送端:
127.0.0.1:6379> PUBLISH channel1 "hello world" #在channel1發(fā)送消息
(integer) 1
127.0.0.1:6379> PUBLISH channel1 "hello redis"
(integer) 1
redis主從復制
未修改配置文件或者命令的機子會自動默認為master琅轧,修改文件的方法redis配置中已經(jīng)闡述伍绳,命令修改配置如下
注意:一臺linux上模擬1主2從復制時候需要修改配置文件的一下參數(shù):
- port xxxx
- pidfile /www/server/redis/redisxx.pid
- logfile "/www/server/redis/redisxx.log"
- dbfilename dumpxx.rdb
Slave0:
[root@pang ~]# /www/server/redis/src/redis-server /root/myconfig/redis80.conf #指定配置文件啟動服務
[root@pang ~]# /www/server/redis/src/redis-cli -p 6380
127.0.0.1:6380> SLAVEOF 127.0.0.1 6379 #修改本機127.0.0.1的端口6379為次slave的master
OK
127.0.0.1:6380> info replication #查看復制的配置
# Replication
role:slave #此機為slave
master_host:127.0.0.1 #master主機為本機
master_port:6379 #主機端口為6379
...
Slave1
[root@pang ~]# /www/server/redis/src/redis-server /root/myconfig/redis81.conf
[root@pang ~]# /www/server/redis/src/redis-cli -p 6381
127.0.0.1:6381> slaveof 127.0.0.1 6379
OK
127.0.0.1:6381> info replication #查看復制的配置
# Replication
role:slave #此機為slave
master_host:127.0.0.1 #master主機為本機
master_port:6379 #主機端口為6379
...
master:
[root@pang src]# /www/server/redis/src/redis-server /root/myconfig/redis79.conf
[root@pang src]# /www/server/redis/src/redis-cli -p 6379
127.0.0.1:6379> info replication #查看復制配置
# Replication
role:master #角色為master
connected_slaves:2 #賬下的叢集數(shù)2
slave0:ip=127.0.0.1,port=6381,state=wait_bgsave,offset=0,lag=0 #slave0信息
slave1:ip=127.0.0.1,port=6380,state=wait_bgsave,offset=0,lag=0 #slave1信息
master_failover_state:no-failover
...
細節(jié)
master可以寫,slave只能讀乍桂。
主機斷開連接冲杀,從機依舊在綁定著主機,主機重新連接再寫入操作睹酌,從機依舊可以獲取主機上的信息
命令綁定主機master是一次性的权谁,關閉連接了就復原了,如果每次開啟服務都綁定master則需要在配置文件里修改replication下的參數(shù)憋沿。
復制原理
slave啟動連接到master會發(fā)送一個sync的同步命令
master接到命令后旺芽,啟動后臺存盤進程,同時收集所有修改數(shù)據(jù)的命令,在后臺進程執(zhí)行之后采章,master將傳送整個數(shù)據(jù)文件到slave运嗜,并完成一次同步。
全量復制:slave服務器在接收到數(shù)據(jù)庫文件數(shù)據(jù)之后悯舟,將其存盤并加載到內(nèi)存中担租。
增量復制:master繼續(xù)將新的所有的收集到的修改數(shù)據(jù)命令依次傳給slave,完成同步抵怎。
但是 只要重新連接master翩活,一次完全同步(全量復制)將別自動執(zhí)行,數(shù)據(jù)肯定可以在slave中看到便贵。
毛毛蟲式主從復制
如果master宕機菠镇,下面的slave遍可以謀朝篡位:
12.0.0.1:6381> slaveof no one #此時6381端口的slave謀朝篡位編程master
即使79端口的master恢復連接也無法再次成為master
當然真正開發(fā)當中不使用普通的主從復制模式,而是使用以下的哨兵模式承璃。
redis哨兵模式(自動選舉master)
概述
當我們主服務器宕機后利耍,需要手動把一臺從服務器切換成中服務器,需要人工干預盔粹,費事費力隘梨,還會造成一段時間內(nèi)服務器不可用。所以說會優(yōu)先使用哨兵模式舷嗡,通過sentinel(哨兵)來解決問題轴猎。
謀朝篡位的自動版本,能夠根據(jù)后臺監(jiān)控主機故障进萄,如果主機故障了捻脖,會根據(jù)投票數(shù)自動將從機轉(zhuǎn)換為主機。
哨兵模式是一種特殊的模式中鼠,首先redis提供了哨兵的命令可婶,哨兵為一個獨立的進程,獨立運行援雇。原理是哨兵通過發(fā)生命令等待redis服務器響應矛渴,從而監(jiān)控運行的多個redis實力。
以防哨兵死亡惫搏,通常使用多個哨兵進行監(jiān)控具温。
假設主服務器宕機,哨兵1會檢測這個結(jié)果筐赔,系統(tǒng)不會馬上進行failover(故障轉(zhuǎn)移)铣猩,僅僅是哨兵1主觀的認為服務器不可以,此現(xiàn)象稱為主觀下線川陆,當后面的服務器也檢測到主服務器不可用時剂习,并且數(shù)量達到一定值時蛮位,那么哨兵之間會進行一次投票,投票的結(jié)果由一個哨兵發(fā)起鳞绕,進行failover操作失仁。之后通過發(fā)布訂閱模式,讓各個哨兵切換監(jiān)控新的主機们何,稱為客觀下線萄焦。
測試
#配置端口 默認為26379
port 26379
#以守護進程模式啟動
daemonize yes
#日志文件名
logfile "sentinel_26379.log"
#存放備份文件以及日志等文件的目錄
dir "/opt/redis/data"
#監(jiān)控的IP 端口號 名稱 sentinel通過投票后認為mater宕機的數(shù)量,此處為至少2個,此配置最重要冤竹,其他可以沒有這個必須有
#mymaster為<master-name>隨便取
sentinel monitor mymaster 127.0.0.1 6379 2
#當在Redis實例中開啟了requirepass foobared授權密碼拂封,這樣所有連接Redis實例的客戶端都要提供密碼
#設置哨兵sentinel連接主從的密碼,注意必須為主從設置一樣的驗證密碼
# sentinel auth-pass <master-name> <password>
#30秒ping不通主節(jié)點的信息鹦蠕,主觀認為master宕機
sentinel down-after-milliseconds mymaster 30000
#故障轉(zhuǎn)移后重新主從復制冒签,1表示串行,>1并行
sentinel parallel-syncs mymaster 1
#故障轉(zhuǎn)移開始钟病,三分鐘內(nèi)沒有完成萧恕,則認為轉(zhuǎn)移失敗
sentinel failover-timeout mymaster 180000
[root@pang ~]# redis-sentinel myconfig/sentinel.conf # 指定配置文件開啟哨兵
當監(jiān)視的master節(jié)點斷開,這時候會投票選舉一個slave成為新的master肠阱。當原先的master重新連接時票唆,會自動把它分配到新的master下當做slave。
哨兵模式的優(yōu)缺點
優(yōu)點:
1屹徘、哨兵集群走趋,基于主從復制模式,所有的主從配置優(yōu)點噪伊,它都有
2簿煌、主從可以切換,故障可以轉(zhuǎn)移酥宴,高可用性的系統(tǒng)
3啦吧、哨兵模式就是主從模式的升級,手動到自動拙寡,更加健壯
缺點:
1、Redis不好在線擴容的琳水,集群容量一旦到達上限肆糕,在線擴容就十分麻煩
2、哨兵模式的配置繁瑣
redis緩存穿透在孝、擊穿與雪崩
正常的緩存流程
穿透
高并發(fā)訪問緩存和數(shù)據(jù)庫都沒有的數(shù)據(jù)诚啃,導致宕機。
解決方法:
- 布隆過濾器私沮。攔截不符合要求的請求
- 緩存空對象始赎,把空的對象寫進redis,避免直接訪問數(shù)據(jù)庫。
擊穿(高并發(fā)訪問單個key)
單個熱點key瞬間失效造垛,大量請求直接訪問數(shù)據(jù)庫魔招,雖數(shù)數(shù)據(jù)庫將此key寫回緩存時間很短,但時間再短五辽,還是有成千上百萬請求在這幾毫秒間直接訪問數(shù)據(jù)庫办斑,從而導致宕機。
解決方法:
-
使用分布式鎖杆逗,在請求數(shù)據(jù)庫這一步上鎖乡翅,就只有一個線程可以搶到鎖訪問數(shù)據(jù)庫,同時讓其他線程睡幾毫秒罪郊,
待數(shù)據(jù)庫寫回數(shù)據(jù)到redis蠕蚜,其他線程就可以直接從緩存里拿數(shù)據(jù)了。
雪崩
大量的redis緩存在同一時間全部失效(或redis宕機)悔橄,直接訪問數(shù)據(jù)庫導致宕機
例如雙11為了防止過多請求直接打到數(shù)據(jù)庫上靶累,提前將訪問量大的數(shù)據(jù)寫到redis緩存中,并設置3小時的失效時間橄维,當3小時過后的一瞬間尺铣,大量的數(shù)據(jù)(key)訪問數(shù)據(jù)庫,導致數(shù)據(jù)庫響應不及時宕機争舞。
解決方法:
- 隨機設置緩存失效時間凛忿,不讓他們同一時間失效
- redis集群部署
- 用不讓key失效,或者通過定時任務在key失效前再次加載key進redis緩存
拓展
布隆過濾器
布隆過濾器會把一個數(shù)據(jù)通過hash算法竞川,把它所對應的bit數(shù)組下標存入1店溢。比如你好算出三個hash分別是357,則下標357則存入1委乌。查詢時通過下標357就可能知道此數(shù)據(jù)為你好床牧,為什么用可能,因為不同的數(shù)據(jù)完全可以擁有相同的hash值遭贸。所以布隆過濾器不便于刪除操作戈咳,因為可能會誤刪相同hash的其他數(shù)據(jù)。
這里為什么是通過3個hash不是5個不是10個以下慢慢做出解釋壕吹。
首先布隆過濾器存在一個很大的缺點就是會誤判著蛙!誤判的原因很簡單,因為不同的數(shù)據(jù)會算出不同的hash耳贬,對應下標也相同踏堡。比如你好和hello算出的hash都是2,在查詢hello是否存在的時候需查詢下標2咒劲,發(fā)現(xiàn)下標2有數(shù)據(jù)(因為為1不為0)顷蟆,則判斷hello是存在的诫隅。其實下標2存入的是你好不是hello,這時就發(fā)生了誤判帐偎。
源碼解析:
public void test2() {
int size = 100000;//預計存入10w個數(shù)據(jù)
double fpp = 0.03;//誤判率為3%
int count = 0;
BloomFilter<Integer> filter = BloomFilter.create(Funnels.integerFunnel(), size, fpp);
for (int i = 0; i < size; i++) {
filter.put(i);
}
for (int i = 0; i < size + 10000; i++) {
if (!filter.test(i)) {
System.out.println(i + "==》不在filter里");
count++;
}
}
System.out.println("誤判個數(shù)=》" + (10000 - count));//誤判個數(shù)=》286
}
那是不是誤判率越低越好呢逐纬?
誤判率0.01和0.03對比發(fā)現(xiàn):
誤判率越低,所需要的hash函數(shù)就越多肮街,帶來的性能消耗也會越大风题,所以并非誤判率越低越好。
redis如何使用布隆過濾器防止穿透:
Config config = new Config();//配置文件
config.useSingleServer().setAddress("127.0.0.1");//設置服務器地址
config.useSingleServer().setPassword("123455");//密碼
RedissonClient redissonClient = Redisson.create(config);//創(chuàng)建redission
//通過redission創(chuàng)建布隆過濾器嫉父,起名為myFilter
RBloomFilter<Object> myFilter = redissonClient.getBloomFilter("myFilter");
// 設置過濾樣本數(shù)量沛硅,以及誤判率
myFilter.tryInit(1000000L, 0.03);
//添加數(shù)據(jù)進過濾器
myFilter.add("10086");
//返回為true
System.out.println(myFilter.contains(10086));