Redis安裝與入門級教學
Redis
Nosql概述
1、什么Nosql
Nosql
Nosql=Not Only SQL(不僅僅是SQL)
關系型數據庫:表格,行,列
很多的數據類型用戶的個人信息,社交網絡,地理位置。這些數據類型的存儲不需要一個固定的格式!不需要多余的操作就可以橫向擴展!Map<String,Object>
Nosql的特點
解耦!
1、方便擴展(數據之間沒關系,很好擴展!)
2、大數據量高性能(Redis一秒寫8萬次,讀取11萬,Nosql的緩存記錄級,是一種細粒度的緩存,性能會比較高!)
3、數據類型是多樣型的!(不需要事先設計數據庫!隨取隨用!如果是數據量十分大的表,很多人數無法設計了!)
4、傳統RDBMS(關系型數據庫管理系統)和Nosql
傳統的RDBMS
- 結構化組織
- SQL
- 數據和關系都存在單獨的表中 row col
- 操作操作,數據定義語言
- 嚴格的一致性
- 基礎的事務
- ...
Nosql
- 不僅僅是數據
- 沒有固定的查詢語言
- 鍵值對存儲,列存儲,文檔存儲,圖形數據庫(社交關系)
- 最終一致性
- CAP定理和BASE (異地多活)
- 高性能,高可用,高可擴
- ...
了解:3V+3高
大數據時代的3V: 主要是描述問題的
- 海量Volume
- 多樣Variety
- 實時Velocity
大數據時代的3高:主要是對程序的要求
-
高并發
-
高可拓
-
高性能
Redis入門
概述
Redis是什么
Redis(Remote Dictionary Server ),即遠程字典服務。
是一個開源的使用ANSI C語言編寫、支持網絡、可基于內存亦可持久化的日志型、Key-Value數據庫,并提供多種語言的API。也被稱之為結構化數據庫
redis會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,并且在此基礎上實現了master-slave(主從)同步。
Redis能干嘛
1、內存存儲、持久化,內存中是斷電即失、所以說持久化很重要(RDB、AOF)
2、效率高,可以用于高速緩存
3、發布訂閱系統
4、地圖信息分析
5、計時器、計數器(瀏覽量)
…
特性
1、多樣的數據類型
2、持久化
3、集群
4、事務
…
Linux安裝
1、下載安裝包
2、解壓Redis的安裝包(程序一般放在/opt目錄下)
3、進入解壓后的文件,可以看到我們redis的配置文件
4、基本的環境安裝
1、yum install gcc-c++
2、make
3、make install(用于確認,非不要操作)
5、redis的默認安裝路徑==/usr/local/bin==
6、將redis配置文件,復制到我們當前目錄下
7、redis默認不是后臺啟動的,我們修改配置文件變成后臺啟動
8、啟動redis
9、使用redis-cli進行測試
10、查看redis的進程是否開啟
11、如何關閉Redis服務
12、再次查看進程是否存在
性能測試
redis-benchmark是一個壓力測試工具
官方自帶的性能測試工具
redis-benchmark命令參數
圖片來自菜鳥教程
redis 性能測試的基本命令如下:
注意:該命令是在 redis 的目錄下執行的,而不是 redis 客戶端的內部指令。
redis-benchmark [option] [option value]
#以上實例中主機為 127.0.0.1,端口號為 6379,執行的命令為 set,lpush,請求數為 10000,通過 -q 參數讓結果只顯示每秒執行的請求數。
redis-benchmark -h 127.0.0.1 -p 6379 -t set,lpush -n 10000 -q
基礎知識
redis默認有16個數據庫
默認使用的是第0個
可以使用select進行切換
keys * #查看數據庫所有的key
清除當前數據庫flushdb
清除全部數據庫flushall
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty list or set)
Redis是單線程的
Redis是很快的,官方表示,Redis是基于內存操作,CPU不是Redis性能瓶頸,Redis的瓶頸是根據機器的內存與網絡帶寬,既然可以使用單線程,就使用單線程。所以就使用單線程
Redis為什么單線程還這么快
1、誤區1:高性能的服務器一定是多線程的?
2、誤區2:多線程(CPU上下文會切換)一定比單線程效率高?
速度:CPU>內存>硬盤
核心:redis是將所有的數據全部放在內存中,所以說使用單線程去操作效率是最高的,多線程(CPU上下文切換:耗時的操作),對于內存系統來說,如果沒有上下文切換效率就是最高的,多次讀寫都是在一個CPU上的,在內存情況下,這個就是最佳方案
五大數據類型
Redis是一個開源(BSD許可),內存存儲的數據結構服務器,可用作數據庫,高速緩存和消息隊列代理。它支持字符串、哈希表、列表、集合、有序集合,位圖,hyperloglogs等數據類型。內置復制、Lua腳本、LRU收回、事務以及不同級別磁盤持久化功能,同時通過Redis Sentinel提供高可用,通過Redis Cluster提供自動分區。
Redis-key
127.0.0.1:6379> flushall #清除所有數據庫的key
OK
127.0.0.1:6379> keys * #查看所有數據庫的key
(empty list or set)
127.0.0.1:6379> set name wh #添加一個key為name,value為wh的數據
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 1
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> exists name #檢測key是否存在
(integer) 1
127.0.0.1:6379> exists name1
(integer) 0
127.0.0.1:6379> move name 1 #移除相應的key
(integer) 1
127.0.0.1:6379> expire name 10 #設置過期時間
(integer) 1
127.0.0.1:6379> ttl name #查看當前key的剩余時間
(integer) 7
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) 1
127.0.0.1:6379> ttl name #key已過期就無法讀取了
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type name #查看當前key的類型
string
后面遇到不會的命令,可以查看官網的幫助文檔
String(字符串)
127.0.0.1:6379> set key1 v1 #設置值
OK
127.0.0.1:6379> get key1 #獲取值
"v1"
127.0.0.1:6379> keys * #獲得所有key
1) "key1"
127.0.0.1:6379> exists key1 #判斷一個key是否存在
(integer) 1
127.0.0.1:6379> append key1 "hello" #追加字符串,如果key不存在,就是相當于set key
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 #獲取字符串長度
(integer) 7
127.0.0.1:6379> append key1 ",wh"
(integer) 10
127.0.0.1:6379> strlen key1
(integer) 10
127.0.0.1:6379> get key1
"v1hello,wh"
###########################################################################
# i++
# 步長 i+=
127.0.0.1:6379> set views 0 # 初始瀏覽量為0
OK
127.0.0.1:6379> incr views # 自增1 瀏覽量變為1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> decr views
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views # 自減1 瀏覽量變為-1
(integer) -1
127.0.0.1:6379> get views
"-1"
127.0.0.1:6379> incrby views 10 #可以設置步長,指定增量
(integer) 9
127.0.0.1:6379> incrby views 10
(integer) 19
127.0.0.1:6379> decrby views 5
(integer) 14
##########################################################################
#字符串范圍 range
127.0.0.1:6379> set key1 "hello,wh" #設置key1的值
OK
127.0.0.1:6379> get key1
"hello,wh"
127.0.0.1:6379> getrange key1 0 3 #截取字符串[0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1 #獲取全部的字符串和get key一樣
"hello,wh"
#替換
127.0.0.1:6379> set key2 abcdefg
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> setrange key2 1 xx #替換指定位置開始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
##########################################################################
# setex (set with expire) #設置過期時間
# setnx (set if not exist) #不存在再設置(在分布式鎖中會常常使用)
127.0.0.1:6379> setex key3 30 "hello" #設置key3的值為hello,30秒后過期
OK
127.0.0.1:6379> ttl key3
(integer) 25
127.0.0.1:6379> keys *
1) "key2"
2) "key3"
3) "key1"
127.0.0.1:6379> setnx myket "redis" #如果myket不存在,創建myket
(integer) 1
127.0.0.1:6379> keys *
1) "key2"
2) "myket"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx myket "mogodb" #如果myket存在,創建失敗
(integer) 0
127.0.0.1:6379> get myket
"redis"
##########################################################################
mset
mget
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3 # 同時設置多個值
OK
127.0.0.1:6379> keys *
1) "k2"
2) "k1"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3 #同時獲取多個值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v1 k4 v4 #msetnx是一個原子性的操作,要么一起成功,要么一起失敗
(integer) 0
127.0.0.1:6379> get k4
(nil)
#對象
set user:1 {name:zhangsan,age:2} #設置一個user:1 對象 值為json字符來保存一個對象
#這里的key是一個巧妙的設計:user:{id}:{filed},如此設計在Redis中完成是OK的
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "2"
##########################################################################
getset #先get然后再set
127.0.0.1:6379> getset db redis #如果不存在,就返回nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb #如果存在值,就獲取原來的值,并設置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"
String類似的使用場景:value除了是我們的字符還可以是我們的數字
-
計數器
-
統計多單位的數量
-
粉絲數
-
對象緩存存儲
List(列表)
所有的list命令都是有l開頭的
1、將一個值或多個值放入列表
127.0.0.1:6379> lpush list one #將一個值或多個值放入列表的頭部(左)
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> rpush list right #將一個值或多個值,插入到列表尾部(右)
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
2、獲取list中的值
#獲取list中的值
lrange
127.0.0.1:6379> lrange list 0 -1 #獲取list中的值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1 #通過區間獲取具體的值
1) "three"
2) "two"
3、移除
#移除
lpop
rpop
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> lpop list # 移除list的第一個元素
"three"
127.0.0.1:6379> rpop list # 移除list的最后一個元素
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
4、通過下標獲取值
#通過下標獲取值
lindex
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 1 #通過下標獲得list中的某一個值
"one"
127.0.0.1:6379> lindex list 0
"two"
5、獲取列表長度
#獲取列表長度
llen
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list two
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> llen list #返回列表長度
(integer) 3
6、移除指定的值
#移除指定的值
lrem
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> lrem list 1 one #移除list集合中指定個數的value,精確匹配
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 1 three
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrem list 2 three
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
7、截取
#截取(list列表會被截斷)
ltrim
127.0.0.1:6379> lpush mylist "hello"
(integer) 1
127.0.0.1:6379> lpush mylist "hello1"
(integer) 2
127.0.0.1:6379> lpush mylist "hello12"
(integer) 3
127.0.0.1:6379> lpush mylist "hello13"
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "hello13"
2) "hello12"
3) "hello1"
4) "hello"
127.0.0.1:6379> ltrim mylist 1 2 #通過下標截取指定的長度,這list已經被改變了,截斷了只剩下截取的元素
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "hello12"
2) "hello1"
8、移除列表的最后一個元素將其移動到新的列表中
#移除列表的最后一個元素,將其移動到新的列表中
rpoplpush
127.0.0.1:6379> rpoplpush mylist myotherlist 移除列表的最后一個元素,將其移動到新的列表中
"hello1"
127.0.0.1:6379> lrange mylist 0 -1 #查看原來列表
1) "hello12"
127.0.0.1:6379> lrange myotherlist 0 -1 #查看目標列表,確實存在改值
1) "hello1"
9、將列表中指定下標的值替換稱為另外一個值,更新操作
#將列表中指定下標的值替換稱為另外一個值,更新操作
lset
127.0.0.1:6379> exists list #判斷這個列表是否存在
(integer) 0
127.0.0.1:6379> lset list 0 item #如果不存在列表,我們去更新就會報錯
(error) ERR no such key
127.0.0.1:6379> lpush list value1
(integer) 1
127.0.0.1:6379> lrange list 0 0
1) "value1"
127.0.0.1:6379> lset lset 0 item #如果存在,更新當前下標的值
(error) ERR no such key
127.0.0.1:6379> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 other #如果不存在,就會報錯
(error) ERR index out of range
10、將某個具體的value插入到列表中某個元素的前后
#將某個具體的value插入到列表中某個元素的前后
linsert
127.0.0.1:6379> lpush mylist hello
(integer) 1
127.0.0.1:6379> lpush mylist world
(integer) 2
127.0.0.1:6379> linsert mylist before world other
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "other"
2) "world"
3) "hello"
127.0.0.1:6379> linsert mylist after world new
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "other"
2) "world"
3) "new"
4) "hello"
11、小結
-
它實際上是一個鏈表,before Node after, left,right都可以插入值
-
如果key不存在,創建新的鏈表
-
如果key存在,新增內容
-
如果移除所有值,空鏈表也代表不存在
-
在兩邊插入或者改動值,效率最高,中間元素,相對來說效率會低一點
使用場景
消息排隊,消息隊列
Set(集合)
set的值是不能重復的
1、添加元素
#添加元素
sadd
127.0.0.1:6379> sadd myset hello
(integer) 1
127.0.0.1:6379> sadd myset wh
(integer) 1
127.0.0.1:6379> sadd myset lovewh
(integer) 1
2、查看指定的set值
#查看指定的set值
smembers
127.0.0.1:6379> smembers myset
1) "hello"
2) "lovewh"
3) "wh"
3、判讀某一個值是否在set集合中
#判讀某一個值是否在set集合中
sismember
127.0.0.1:6379> sismember myset hello
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
4、獲取set集合中的元素個數
#獲取set集合中的元素個數
scard
127.0.0.1:6379> scard myset
(integer) 3
5、移除set集合中指定的元素
#移除set集合中的元素
srem
127.0.0.1:6379> srem myset hello
(integer) 1
127.0.0.1:6379> smembers myset
1) "lovewh"
2) "wh"
6、隨機抽取元素
#隨機抽取元素 (set 無序不重復集合,抽隨機)
srandmember
127.0.0.1:6379> srandmember myset
"likelcy"
127.0.0.1:6379> srandmember myset
"likelcy"
127.0.0.1:6379> srandmember myset
"lovelcy"
127.0.0.1:6379> srandmember myset 2 #隨機抽取指定個數元素
1) "lovewh"
2) "likelcy"
7、隨機刪除元素
#隨機刪除元素
spop
127.0.0.1:6379> smembers myset
1) "lovelcy"
2) "likelcy"
3) "lovewh"
4) "wh"
127.0.0.1:6379> spop myset
"lovelcy"
127.0.0.1:6379> spop myset
"likelcy"
127.0.0.1:6379> smembers myset
1) "lovewh"
2) "wh"
8、移動指定的元素到另一個set集合
#移動指定的元素到另一個set集合
smove
127.0.0.1:6379> smove myset myset2 wh
(integer) 1
127.0.0.1:6379> smembers myset2
1) "wh"
127.0.0.1:6379> smembers myset
1) "lovewh"
9、差集
#差集
sdiff
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sdiff key1 key2
1) "a"
2) "b"
10、交集
#交集
sinter
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sinter key1 key2
1) "c"
11、并集
#并集
sunion
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> sunion key1 key2
1) "d"
2) "c"
3) "b"
4) "a"
5) "e"
12、應用場景
微博的共同好友,共同愛好,推薦好友(將A用戶的所有關注的人放在一個set集合中)
Hash(哈希)
Map集合,key-map。本質和String類型沒有太大區別,還是一個簡單的key-vlaue
1、添加元素
#添加單個元素
hset
127.0.0.1:6379> hset myhash field1 wh
(integer) 1
#添加多個元素
hmset
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
2、查找元素
#查找指定單個key的value
hget
127.0.0.1:6379> hget myhash field1
"wh"
#查找指定多個key的value
hmget
127.0.0.1:6379> hmget myhash field1 field2
1) "hello"
2) "world"
#查找所有元素
hgetall
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
3、刪除元素
#刪除元素(刪除對應的key,對應的value也就消失了)
hdel
127.0.0.1:6379> hdel myhash field1
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
4、獲取字段個數
#獲取hash字段個數
hlen
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
127.0.0.1:6379> hlen myhash
(integer) 1
5、判斷指定的字段是否存在
#判斷指定的字段是否存在
hexists
127.0.0.1:6379> hexists myhash field2
(integer) 1
127.0.0.1:6379> hexists myhash field1
(integer) 0
6、獲取所有key
#獲取所有key
hkeys
127.0.0.1:6379> hkeys myhash
1) "field2"
7、獲取所有value
#獲取所有value
hvalue
127.0.0.1:6379> hvals myhash
1) "world"
8、自增、自減
#自增、自減(只能通過加負數)
127.0.0.1:6379> hset myhash field1 5
(integer) 1
127.0.0.1:6379> hincrby myhash field1 1
(integer) 6
127.0.0.1:6379> hincrby myhash field1 -1
(integer) 5
9、字段存在則不可設置
#字段存在則不可設置,不存在則可設置
hsetnx
127.0.0.1:6379> hsetnx myhash field3 hello
(integer) 1
127.0.0.1:6379> hsetnx myhash field3 world
(integer) 0
10、小結
hash變更的數據user name age,尤其是用戶信息之類的,經常變動的信息!hash更適合于對象的存儲,String更加適合字符串存儲!
Zset(有序集合)
在set的基礎上,增加了一個值,set k1 score1 v1
1、添加元素
#添加元素
zadd
127.0.0.1:6379> zadd myset 1 one 2 two 3 three
(integer) 3
2、查看元素
#查看元素
zrange
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
3、排序
#排序
zrangebyscore (-inf 無窮小 +inf無窮大)
127.0.0.1:6379> zadd salary 1000 xiaoli #添加三個用戶
(integer) 1
127.0.0.1:6379> zadd salary 2000 xiaowu
(integer) 1
127.0.0.1:6379> zadd salary 3000 xiaoliu
(integer) 1
#zrangebyscore key min max
127.0.0.1:6379> zrangebyscore salary -inf +inf #顯示全部用戶從小到大
1) "xiaoli"
2) "xiaowu"
3) "xiaoliu"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #顯示全部用戶從小到大并顯示排序依據
1) "xiaoli"
2) "1000"
3) "xiaowu"
4) "2000"
5) "xiaoliu"
6) "3000"
127.0.0.1:6379> zrevrange salary 0 -1 withscores #從大到小排序并顯示排序依據
1) "xiaoliu"
2) "3000"
3) "xiaowu"
4) "2000"
5) "xiaoli"
6) "1000"
127.0.0.1:6379> zrangebyscore salary -inf 2000 withscores #選擇范圍排序
1) "xiaoli"
2) "1000"
3) "xiaowu"
4) "2000"
4、移除元素
#移除元素
hrem
127.0.0.1:6379> zrange salary 0 -1
1) "xiaoli"
2) "xiaowu"
3) "xiaoliu"
127.0.0.1:6379> zerm salary xiaoli
(error) ERR unknown command `zerm`, with args beginning with: `salary`, `xiaoli`,
127.0.0.1:6379> zrem salary xiaoli
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiaowu"
2) "xiaoliu"
5、獲取有序集合中的個數
#獲取有序集合中的個數
zcard
127.0.0.1:6379> zcard salary
(integer) 2
6、獲取指定區間的成員數量
#獲取指定區間的成員數量
zcount
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> zcount myset 1 3
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
127.0.0.1:6379>
7、應用場景
存儲班級成績表、工資表排序、排行榜應用實現
三大特殊類型
Geospatial 地理位置
getaddd
getpos
geodist
georadius
geohash
geo底層原理實現
Hyperloglog 基數統計
什么是基數
簡介
測試使用
Bitmap 位圖場景詳解
位存儲
測試
事務
Redis事務本質:一組命令的集合!一個事務中的所有命令都會被序列化,在事務執行過程中,會按照順序執行!
一次性、順序性、排他性
------隊列 set set set 執行 ------
Redis事務沒有隔離級別的命令
所有的命令在事務中,并沒有直接被執行,只有發起執行命令的時候才會被執行
Redis單條命令是保證原子性的,當時Redis事務不保證原子性
redis的事務:
- 開啟事務(multi)
- 命令入列(…)
- 執行事務(exec)
1、正常執行事務
#正常執行事務
127.0.0.1:6379> multi #開啟事務
OK
#命令入列
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k1
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec #執行事務
1) OK
2) OK
3) "v1"
4) OK
2、放棄事務
#放棄事務
discard
127.0.0.1:6379> multi #開啟事務
OK
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard #取消事務
OK
127.0.0.1:6379> get k4 #事務隊列中的命令都不會被執行
(nil)
3、編譯型異常
編譯型異常(代碼有問題!命令有錯!),事務中所有的命令都不會執行
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3 v3
QUEUED
127.0.0.1:6379> getset k4 #錯誤命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> exec #執行事務報錯
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1 #所有的命令都不會被執行
(nil)
4、運行時異常
運行時異常(I/O),如果事務隊列中存在語法性,那么執行事務命令的時候,其他命令是可以正常執行的,錯誤命令會拋出異常
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> incr k1 #會執行的時候失敗
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> get k3
QUEUED
127.0.0.1:6379> exec
1) (error) ERR value is not an integer or out of range #雖然第一條命令報錯了,當時依然執行成功
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
5、監控(watch)
悲觀鎖:
- 很悲觀,認為什么時候都會出現問題,無論做什么都會加鎖
樂觀鎖:
-
很樂觀,認為什么時候都不會出問題,所以不會上鎖!更新數據的時候去判斷一下,在此期間是否有人修改過這個數據
-
獲取version
-
更新的時候比較version
Redis測監視測試
1、正常執行成功
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money #監視money對象
OK
127.0.0.1:6379> multi #事務正常結束,數據期間沒有發生變動,這個時候就正常執行成功
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec
1) (integer) 80
2) (integer) 20
2、測試多線程修改值,使用watch可以當做redis的樂觀鎖操作
127.0.0.1:6379> watch money #監視money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec #執行之前,另外一個線程,修改了我們的值,這個時候,就會導致事務執行失敗
(nil)
如果修改失敗,獲取最新的值就好
127.0.0.1:6379> unwatch #1、如果發現事務執行失敗,就先解鎖
OK
127.0.0.1:6379> watch money #2、獲取最新的值,在此監視,select version
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec #3、比對監視的值是否發生了變化,如果沒有變化,那么可以執行成功,如果變化就執行失敗
1) (integer) 90
2) (integer) 40
SpringBoot整合Redis
SpringBoot操作數據:Spring-data jpa jdbc ,mongodb,redis
說明:在SpringBoot2.x之后,原來使用的jedis被替換為了lettuce
jedis:采用的直連,多個線程操作的話,是不安全的,如果想要避免不安全的,使用jedis pool連接池!BIO模式
lettuce: 采用netty,實例可以再多個線程中進行共享,不存在線程不安全的情況!可以減少線程數據里!NIO模式
源碼
來源
分析
#SpringBoot所有的配置類都有一個自動配置類 RedisAutoConfiguration
#自動配置類都會綁定一個properties文件 RedisProperties
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")//我們可以自己定義一個redisTemplate來替換默認的
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
//默認的RedisTemplate沒有過多的設置,redis對象都需要序列化
//兩個泛型都是Object,Object的類型,我們之后使用都需要強制裝換<String,Object>
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean //由于String是redis中最常使用的類型,所以單獨提出來一個bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
整合測試
1、創建項目
2、配置連接
#SpringBoot所有的配置類都有一個自動配置類 RedisAutoConfiguration
#自動配置類都會綁定一個properties文件 RedisProperties
spring.redis.host=127.0.0.1
spring.redis.port=6379
3、測試
package wh.redis;
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 WhTestRedisApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
void contextLoads() {
//redisTemplate 操作不同的數據類型,api和我們的指令是一樣的
//opsForValue() 操作字符串 類似String
//opsForList() 操作List 類似list
//opsForSet()
//opsForHash()
//opsForZSet()
//opsForGeo()
//opsForHyperLogLog()
//除了基本的操作,我們常用的方法都可以直接通過redisTemplate操作,比如事務,基本的CRUD
//獲取redis的連接對象
//RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
//connection.flushDb();
//connection.flushAll();
redisTemplate.opsForValue().set("myset","wh");
System.out.println(redisTemplate.opsForValue().get("myset"));
}
}
測試結果
Redis默認序列化探究
自定義RedisTemplate
成功測試過程
未序列化的User對象
package wh.redis.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
/**
* @program: test-redis
* @description:
* @author: Santa
* @create: 2020-08-29 11:52
**/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Component
public class User {
private String name;
private int num;
}
正常測試(需要將對象序列化)
package wh.redis;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.core.RedisTemplate;
import wh.redis.entity.User;
@SpringBootTest
class WhTestRedisApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
public void test() throws JsonProcessingException {
//真實的開發一般使用json來傳遞對象
User user = new User("wuhao", 3);
//對user對象進行序列化
String josnUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",josnUser);
System.out.println(redisTemplate.opsForValue().get("user"));
}
}
成功結果:
出現異常測試過程
異常測試(對象未序列化)
package wh.redis;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
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.core.RedisTemplate;
import wh.redis.entity.User;
@SpringBootTest
class WhTestRedisApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Test
public void test() throws JsonProcessingException {
//真實的開發一般使用json來傳遞對象
User user = new User("wuhao", 3);
//對user對象進行序列化
//String josnUser = new ObjectMapper().writeValueAsString(user);
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
}
報錯:
自定義的RedisTemplate
package wh.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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;
/**
* @program: test-redis
* @description:
* @author: Santa
* @create: 2020-08-29 11:54
**/
@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);
//序列化配置
//利用json解析任意對象,Json的序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//利用 ObjectMapper對序列化進行轉義
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;
}
}
使用自定義redisTemplate后
整合Redis的工具類
package wh.redis.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* @program: test-redis
* @description:
* @author: Santa
* @create: 2020-08-29 11:54
**/
@Component
public class RedisUtil {
@Autowired
@Qualifier("redisTemplate")
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定緩存失效時間
*
* @param key 鍵
* @param time 時間(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據key 獲取過期時間
*
* @param key 鍵 不能為null
* @return 時間(秒) 返回0代表為永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判斷key是否存在
*
* @param key 鍵
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除緩存
*
* @param key 可以傳一個值 或多個
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通緩存獲取
*
* @param key 鍵
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通緩存放入
*
* @param key 鍵
* @param value 值
* @return true成功 false失敗
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通緩存放入并設置時間
*
* @param key 鍵
* @param value 值
* @param time 時間(秒) time要大于0 如果time小于等于0 將設置無限期
* @return true成功 false 失敗
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 遞增
*
* @param key 鍵
* @param delta 要增加幾(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞增因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 遞減
*
* @param key 鍵
* @param delta 要減少幾(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("遞減因子必須大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
*
* @param key 鍵 不能為null
* @param item 項 不能為null
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 獲取hashKey對應的所有鍵值
*
* @param key 鍵
* @return 對應的多個鍵值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
*
* @param key 鍵
* @param map 對應多個鍵值
* @return true 成功 false 失敗
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并設置時間
*
* @param key 鍵
* @param map 對應多個鍵值
* @param time 時間(秒)
* @return true成功 false失敗
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數據,如果不存在將創建
*
* @param key 鍵
* @param item 項
* @param value 值
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一張hash表中放入數據,如果不存在將創建
*
* @param key 鍵
* @param item 項
* @param value 值
* @param time 時間(秒) 注意:如果已存在的hash表有時間,這里將會替換原有的時間
* @return true 成功 false失敗
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 刪除hash表中的值
*
* @param key 鍵 不能為null
* @param item 項 可以使多個 不能為null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判斷hash表中是否有該項的值
*
* @param key 鍵 不能為null
* @param item 項 不能為null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash遞增 如果不存在,就會創建一個 并把新增后的值返回
*
* @param key 鍵
* @param item 項
* @param by 要增加幾(大于0)
* @return
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash遞減
*
* @param key 鍵
* @param item 項
* @param by 要減少記(小于0)
* @return
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根據key獲取Set中的所有值
*
* @param key 鍵
* @return
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根據value從一個set中查詢,是否存在
*
* @param key 鍵
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將數據放入set緩存
*
* @param key 鍵
* @param values 值 可以是多個
* @return 成功個數
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 將set數據放入緩存
*
* @param key 鍵
* @param time 時間(秒)
* @param values 值 可以是多個
* @return 成功個數
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 獲取set緩存的長度
*
* @param key 鍵
* @return
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值為value的
*
* @param key 鍵
* @param values 值 可以是多個
* @return 移除的個數
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 獲取list緩存的內容
*
* @param key 鍵
* @param start 開始
* @param end 結束 0 到 -1代表所有值
* @return
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 獲取list緩存的長度
*
* @param key 鍵
* @return
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通過索引 獲取list中的值
*
* @param key 鍵
* @param index 索引 index>=0時, 0 表頭,1 第二個元素,依次類推;index<0時,-1,表尾,-2倒數第二個元素,依次類推
* @return
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時間(秒)
* @return
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 將list放入緩存
*
* @param key 鍵
* @param value 值
* @param time 時間(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根據索引修改list中的某條數據
*
* @param key 鍵
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N個值為value
*
* @param key 鍵
* @param count 移除多少個
* @param value 值
* @return 移除的個數
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
Redis.conf詳解
單位
配置文件 unit單位對大小寫不敏感
包含
可以導入多個配置文件
網絡
bind 127.0.0.1 #綁定的ip
protected-mode yes #保護模式
port 6379 #端口設置
通用( GENERAL)
daemonize yes #以守護進程的的方式運行,默認是no,我們學院自己開啟為yes(開始后redis在后臺運行)
pidfile /var/run/redis_6379.pid #如果以后臺的方式運行,我們就需要指定一個pid文件
#日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生產環境
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #日志的文件位置名
databases 16 #數據庫的數量,默認是16個數據庫
always-show-logo yes #是否總是顯示logo
快照( SNAPSHOTTING)
持久化,在規定時間內,執行了多少次操作,這會持久化到文件 .rdb .aof
redis 是內存數據庫,如果沒有持久化,那么數據斷電即失
#在900s內,如果至少有一個key進行了修改,我們就進行持久化操作
save 900 1
#在300s內,如果至少有10個key進行了修改,我們就進行持久化操作
save 300 10
#在60s內,如果至少有10000個key進行了修改,我們就進行持久化操作
save 60 10000
stop-writes-on-bgsave-error yes #持久化如果出錯,是否還需要繼續工作
rdbcompression yes #是否壓縮rdb文件,需要消耗一些cpu資源
rdbchecksum yes #保存rdb文件的時候,進行錯誤的檢測校驗
dbfilename dump.rdb #保存默認的rdb文件名
dir ./ #rdb文件保存的目錄
復制( REPLICATION)
用于主從復制時
安全(SECURITY)
可以在這里設置redis的密碼,默認是沒有密碼的
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass
1) "requirepass" #獲取redis密碼
2) ""
127.0.0.1:6379> config set requirepass "123456" #設置redis的密碼
OK
127.0.0.1:6379> ping #發現所有的命令都沒有權限了
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 #使用密碼登錄
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
限制
( CLIENTS、 MEMORY MANAGEMENT、)
maxclients 10000 #設置能連接上redis的最大客戶端的數量
maxmemory <bytes> #redis配置最大的內存容量
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 : 永不過期,返回錯誤
APPEND ONLY MODE模式(aof配置)
appendonly no #默認是不開啟aof模式的,默認是使用rdb方式持久化的,在大部分所有的情況下,rdb完全夠用
appendfilename "appendonly.aof" #持久化的文件的名字
sync:同步命令
# appendfsync always #每次修改都會sync。消耗性能
appendfsync everysec #每秒執行一次sync,可能會丟失這1s的數據
# appendfsync no #不執行sync 這個時候操作系統自己同步數據,速度最快
Redis持久化
Redis是內存數據庫,如果不將內存中的數據庫狀態保存到磁盤,那么一旦服務器進程退出,服務器中的數據庫狀態也會消失。所以Redis提供了持久化功能
RDB(Redis DataBase)
什么是RDB
RDB其實就是把數據以快照的形式保存在磁盤上。什么是快照呢,你可以理解成把當前時刻的數據拍成一張照片保存下來。
==RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤,也就是 Snapshot 快照(數據庫中所有鍵值對數據)。恢復時是將快照文件直接讀到內存里。==也是默認的持久化方式
,這種方式是就是將內存中數據以快照的方式寫入到二進制文件中,默認的文件名為dump.rdb。
RDB持久化過程
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-rvWevQlB-1603332859295)(https://i.loli.net/2020/10/22/utFAfeJCq3yo5r4.png)]
Redis會單獨創建(fork)一個子進程來進行持久化,會先將數據寫入到一個臨時文件中,待持久化過程都結束了,再用這個臨時文件替換上次持久化好的文件。整個過程中,主進程是不進行任何IO操作的。這就確保了極高的性能。如果需要進行大規模數據的恢復,且對于數據恢復的完整性不是非常敏感,那RDB方式要比AOF方式更加的搞笑。RDB的缺點是最后一次持久化后的數據可能丟失
觸發機制
1、save的規則滿足的情況下,會自動觸發rdb規則
2、執行flushall命令,也會觸發我們的rdb規則
3、退出redis,也會產生rdb文件
備份就自動生成一個dump.rdb
如何恢復rdb文件
1、只需要將rdb文件放在我們redis啟動目錄就可以,redis啟動的時候會自動檢測dump.rdb恢復其中的數據
2、查看需要存放的位置
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" #如果在這個目錄下存在dump.rdb文件,啟動就會自動恢復其中的數據
優缺點
優點:
1、適合大規模數據的恢復
2、對數據的完整性要求不高
缺點:
1、需要一定的時間間隔進程操作,如果redis意外宕機了,這個最后一次修改的數據就沒有了
2、fork進程的時候,會占用一定的內存空間
AOF(Append Only File)
將我們的所有命令都記錄下來,相當于history,恢復的時候就把這個文件全部再執行一遍
什么是AOF
以日志的形式來記錄每個寫操作,將Redis執行過的所有指令記錄下來(讀操作不記錄),只許追加文件打不可以改寫文件,redis啟動之初會讀取該文件重新構建數據,換言之,redis重啟的化就根據日志文件的內容將寫指令從前到后執行一次以完成數據的 恢復工作
aof保存的是appendoly.aof文件
默認是不開啟的,我們需要手動進行配置。只需要將appendonly改為yes就開啟了aof!
重啟,redis就可以生效了!
如果這個aof文件有錯誤,這時候redis是啟動不起來的,我們需要修復這個aof文件
redis給我們提供了一個工具redis-check-aof --fix
如果文件正常,重啟就可以直接恢復了!
重寫機制
aof默認的就是文件的無限追加,文件會越來越大
如果aof文件大于64m,就會fork一個新的進程來將我們的文件進行重寫!
優缺點
aof配置文件
appendonly no #默認是不開啟aof模式的,默認是使用rdb方式持久化的,在大部分所有的情況下,rdb完全夠用
appendfilename "appendonly.aof" #持久化的文件的名字
sync:同步命令
# appendfsync always #每次修改都會sync。消耗性能
appendfsync everysec #每秒執行一次sync,可能會丟失這1s的數據
# appendfsync no #不執行sync 這個時候操作系統自己同步數據,速度最快
優點:
1、每一次修改都同步,文件的完整性會更好(數據一致性)
2、每秒同步一次,可能會丟失一秒的數據
3、從不同步,效率最高
缺點:
1、相對于數據文件來說,aof遠遠大于rdb,修復的速度也不rdb慢
2、aof運行效率要比rdb慢,所以我們redis默認的配置就是rdb持久化
擴展
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-K47M295p-1603332859298)(https://i.loli.net/2020/10/22/mUBTo48eitYd5jO.png)]
總結
RDB:
RDB是將支持當前數據的快照存成一個數據文件的持久化機制。
1.在生成快照時,將當前進程fork出一個子進程.
2.然后再子進程中循環所有的數據,將數據寫入到二進制文件中。
3.當子進程將快照寫入臨時文件完畢后,用臨時文件替換原來的快照文件,然后子進程退出。
優點:
1.一旦采用該方式,那么你的整個Redis數據庫將只包含一個文件,這樣非常方便進行備份。比如你可能打算每1天歸檔一些數據。
2.方便備份的同時,我們也很容易的將一個RDB文件移動到其他存儲物質上。
3.RDB 可以最大化 Redis 的性能:父進程在保存 RDB 文件時唯一要做的就是 fork 出一個子進程,然后這個子進程就會處理接下來的所有保存工作,父進程無須執行任何磁盤 I/O 操作。
劣勢:
如果你想在服務器上避免數據的丟失,那么RDB就不適合了,因為RDB文件需要保存整個數據集的狀態,因為你可能會在5分鐘才保存一次RDB文件,在這種情況下,一旦發生故障停機,你可能會損失好幾分鐘的數據。
每次在保存RDB的時候,Redis都要fork出一個子進程,并由子進程來進行實際的持久化工作,如果在數據集比較龐大時,fork可能會非常耗時,造成服務器在那么一瞬間會停止處理客戶端;雖然AOF重寫也需要進行fork,但AOF重寫的執行時間間隔有多長,數據的耐久性都不會有任何損失。
AOF:
AOF: Redis 默認不開啟。它的出現是為了彌補RDB的不足(數據的不一致性),所以它采用日志的形式來記錄每個寫操作,并追加到文件中。Redis 重啟的會根據日志文件的內容將寫指令從前到后執行一次以完成數據的恢復工作。AOF的工作原理就是是將寫操作追加到文件中,文件的冗余內容會越來越多。所以Redis 新增了重寫機制。當AOF文件的大小超過所設定的最大值時,Redis就會對AOF文件的內容壓縮。
優點:數據的完整性和一致性更高
缺點:因為AOF記錄的內容多,文件會越來越大,數據恢復也會越來越慢。
總結
Redis 默認開啟RDB持久化方式,在指定的時間間隔內,執行指定次數的寫操作,則將內存中的數據寫入到磁盤中。
RDB 持久化適合大規模的數據恢復但它的數據一致性和完整性較差。
Redis 需要手動開啟AOF持久化方式,默認是每秒將寫操作日志追加到AOF文件中。
AOF 的數據完整性比RDB高,但記錄內容多了,會影響數據恢復的效率。
Redis 針對 AOF文件大的問題,提供重寫的瘦身機制。
若只打算用Redis 做緩存,可以關閉持久化。
若打算使用Redis 的持久化。建議RDB和AOF都開啟。其實RDB更適合做數據的備份,留一后手。AOF出問題了,還有RDB。
Redis發布訂閱
命令
測試
訂閱端
127.0.0.1:6379> subscribe whshuo #訂閱一個頻道whshuo
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "whshuo"
3) (integer) 1
#等待讀取推送的消息
1) "message" #消息
2) "whshuo" #那個頻道的消息
3) "hello world" #消息具體內容
發送端
127.0.0.1:6379> publish whshuo "hello world" #發布者發布消息到頻道
(integer) 1
原理
使用場景
1、實時消息系統
2、實時聊天(頻道當做聊天室,將消息回顯給所有人即可)
3、訂閱,關注系統都是可以的
Redis的主從復制
概念
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-2kO4KJvZ-1603332859306)(https://i.loli.net/2020/10/22/tlXiBe4vALMobwz.png)]
作用
環境配置
只配置從庫,不配置主庫
127.0.0.1:6379> info replication #查看當前庫的信息
# Replication
role:master #角色
connected_slaves:0 #從機個數
master_replid:fb837b988c93cf6bc9dbc7914467ad134920e26d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
復制3個配置文件,然后修改對應的信息
- 端口
- pid名字
- log文件名字
- dump.rdb名字
啟動成功
一主二從
默認情況下,每臺Redis服務器都是主節點;一般情況下只用配置從機
認老大!一主(79)二從(80、81)
slaverof host port
細節
復制原理
層層鏈路
哨兵模式
概述
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-JFfCcbX6-1603332859320)(https://i.loli.net/2020/10/22/bg4LszwWSnD8xhc.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-7LikMiPG-1603332859321)(https://i.loli.net/2020/10/22/ij4ewY5lSWsRaIx.png)]
測試
優缺點
優點:
1、哨兵集群,基于主從復制模式,所有的主從配置優點,它全有
2、主從可以切換,故障可以轉移,系統的可用性就會更好
3、哨兵模式就是主從模式的升級,手動到自動,更加健壯
缺點:
1、Redis不好在線擴容,集群容量一旦達到上限,在線擴容就十分麻煩
2、實現哨兵模式的配置其實是很麻煩的,里面有很多選擇
哨兵模式的全部配置
緩存穿透和雪崩
緩存穿透
概念
解決方案
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-TNjlZxjy-1603332859343)(https://i.loli.net/2020/10/22/vrzajAdiH9MDoXw.png)]
緩存擊穿
概念
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-mkqT7FEq-1603332859344)(https://i.loli.net/2020/10/22/2nbkRUuS9ZpjD13.png)]
解決方案
緩存雪崩
概念
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-x4IGMjEq-1603332859346)(https://i.loli.net/2020/10/22/sz79dUvGYfSjJXw.png)]
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-gvyLXZW3-1603332859347)(https://i.loli.net/2020/10/22/Fweh4GkWag2AdNr.png)]
解決方案
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YG9pJiqY-1603332859350)(https://i.loli.net/2020/10/22/9TJbXV2DrAGkpyq.png)]
頻道的消息
3) “hello world” #消息具體內容
發送端
```bash
127.0.0.1:6379> publish whshuo "hello world" #發布者發布消息到頻道
(integer) 1
原理
[外鏈圖片轉存中…(img-5wVxg5UB-1603332859304)]
使用場景
1、實時消息系統
2、實時聊天(頻道當做聊天室,將消息回顯給所有人即可)
3、訂閱,關注系統都是可以的
Redis的主從復制
概念
[外鏈圖片轉存中…(img-2kO4KJvZ-1603332859306)]
作用
[外鏈圖片轉存中…(img-FOd9uNVY-1603332859307)]
[外鏈圖片轉存中…(img-3Kse2Rqq-1603332859308)]
環境配置
只配置從庫,不配置主庫
127.0.0.1:6379> info replication #查看當前庫的信息
# Replication
role:master #角色
connected_slaves:0 #從機個數
master_replid:fb837b988c93cf6bc9dbc7914467ad134920e26d
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
復制3個配置文件,然后修改對應的信息
- 端口
- pid名字
- log文件名字
- dump.rdb名字
啟動成功
[外鏈圖片轉存中…(img-fXMokjI7-1603332859309)]
一主二從
默認情況下,每臺Redis服務器都是主節點;一般情況下只用配置從機
認老大!一主(79)二從(80、81)
slaverof host port
[外鏈圖片轉存中…(img-ijpmNG9C-1603332859311)]
[外鏈圖片轉存中…(img-eYWBKeOi-1603332859312)]
[外鏈圖片轉存中…(img-2Qm8vPhI-1603332859312)]
細節
[外鏈圖片轉存中…(img-0UJZVEsD-1603332859314)]
[外鏈圖片轉存中…(img-lev7dHQX-1603332859314)]
復制原理
[外鏈圖片轉存中…(img-j4WJNZjs-1603332859316)]
層層鏈路
[外鏈圖片轉存中…(img-5JXQpnH7-1603332859317)]
[外鏈圖片轉存中…(img-rWT337r8-1603332859318)]
哨兵模式
概述
[外鏈圖片轉存中…(img-sQ0TCzg8-1603332859319)]
[外鏈圖片轉存中…(img-JFfCcbX6-1603332859320)]
[外鏈圖片轉存中…(img-7LikMiPG-1603332859321)]
[外鏈圖片轉存中…(img-dAXK3wdk-1603332859322)]
測試
[外鏈圖片轉存中…(img-hUZvOx50-1603332859323)]
[外鏈圖片轉存中…(img-kLJf5oAO-1603332859325)]
[外鏈圖片轉存中…(img-07gvVGFC-1603332859326)]
[外鏈圖片轉存中…(img-NQQOr5ng-1603332859327)]
[外鏈圖片轉存中…(img-QthCyIUg-1603332859329)]
[外鏈圖片轉存中…(img-5ab7ZXYp-1603332859330)]
[外鏈圖片轉存中…(img-SEkgz6tF-1603332859331)]
優缺點
優點:
1、哨兵集群,基于主從復制模式,所有的主從配置優點,它全有
2、主從可以切換,故障可以轉移,系統的可用性就會更好
3、哨兵模式就是主從模式的升級,手動到自動,更加健壯
缺點:
1、Redis不好在線擴容,集群容量一旦達到上限,在線擴容就十分麻煩
2、實現哨兵模式的配置其實是很麻煩的,里面有很多選擇
哨兵模式的全部配置
[外鏈圖片轉存中…(img-HB6S7cME-1603332859333)]
[外鏈圖片轉存中…(img-BX7j1boO-1603332859335)]
[外鏈圖片轉存中…(img-GHLeEyMD-1603332859336)]
[外鏈圖片轉存中…(img-qDwkxjni-1603332859337)]
緩存穿透和雪崩
緩存穿透
概念
[外鏈圖片轉存中…(img-JgbycBcY-1603332859339)]
解決方案
[外鏈圖片轉存中…(img-AXx1ep8f-1603332859340)]
[外鏈圖片轉存中…(img-pI76VR5X-1603332859341)]
[外鏈圖片轉存中…(img-TNjlZxjy-1603332859343)]
緩存擊穿
概念
[外鏈圖片轉存中…(img-mkqT7FEq-1603332859344)]
解決方案
[外鏈圖片轉存中…(img-6xZB1UOe-1603332859344)]
緩存雪崩
概念
[外鏈圖片轉存中…(img-x4IGMjEq-1603332859346)]
[外鏈圖片轉存中…(img-gvyLXZW3-1603332859347)]
解決方案
[外鏈圖片轉存中…(img-YG9pJiqY-1603332859350)]
智能推薦
freemarker + ItextRender 根據模板生成PDF文件
1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...
電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!
Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...
requests實現全自動PPT模板
http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...
猜你喜歡
Linux C系統編程-線程互斥鎖(四)
互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...
統計學習方法 - 樸素貝葉斯
引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...
styled-components —— React 中的 CSS 最佳實踐
https://zhuanlan.zhihu.com/p/29344146 Styled-components 是目前 React 樣式方案中最受關注的一種,它既具備了 css-in-js 的模塊化與參數化優點,又完全使用CSS的書寫習慣,不會引起額外的學習成本。本文是 styled-components 作者之一 Max Stoiber 所寫,首先總結了前端組件化樣式中的最佳實踐原則,然后在此基...
19.vue中封裝echarts組件
19.vue中封裝echarts組件 1.效果圖 2.echarts組件 3.使用組件 按照組件格式整理好數據格式 傳入組件 home.vue 4.接口返回數據格式...