Redis最全教程

Redis最全教程html

1. Redis安裝

1.1 從官網下載安裝包。

這裏咱們以Redis5.x爲例。下載好後使用xftp上傳到你的服務器上或者你的虛擬機中。目錄能夠任意,但建議把本身安裝的軟件放在/opt文件夾下。java

使用命令tar -zxvf redis-5.0.8.tar.gz把下載的軟件包解壓,後面的要寫你本身下載的包。git

1.2安裝Redis

進入到剛剛解壓的Redis的文件夾。由於Redis是用c寫的,因此要保證已經安裝gcc了。(注:最新版的6.x在使用make命令可能會報錯,須要升級下gcc就好。)面試

在Redis的文件夾中使用命令進行安裝。redis

make 
make PREFIX=/usr/local/redis install

第二行 命令是指定把軟件安裝的位置,若是不指定默認是安裝在/usr/local/bin目錄中。算法

安裝好以後咱們的Redis就安裝在/usr/local/redis這個文件夾下了。安裝好後,會發現沒有redis.conf這個Redis的配置文件,這個配置文件在解壓的時候的那個目錄,把它拷貝到安裝的目錄中。這樣每次啓動的時候給他指定配置文件就好,而自帶的配置文件不去動它。若是後期配置錯了能夠把這個刪除而後從新拷貝過來。你不作這步複製,redis也能正常啓動,只是的會用一套默認配置。spring

Redis安裝以後的bin目錄主要有一下的幾個功能:shell

img

1.3啓動測試Redis

首先修改一下拷貝過來的redis.conf文件。把後臺啓動打開,默認是前臺啓動。數據庫

image-20200919143554152

啓動的時候,指定使用配置文件:# ./bin/redis-server ./redis.conf 編程

出現以下的就說明啓動成功,不然會報錯。image-20200919144940574

咱們可使用Redis自帶的redis-cli去鏈接Redis。使用命令redis-cli -p 6379注意須要指定端口。

而後如ping,他會回一個pong,說明搭建是成功的。

image-20200919145512785

1.4 關閉redis

暴力的方式是查詢redis的進程號,而後使用kill命令。

正確的方式是,若是進入redis-cli能夠以下操做:

image-20200919145806875

若是沒有進入,則直接使用命令關閉./bin/redis-cli shutdown

1.5 redis的經常使用設置

redis的設置主要就是經過他的配置文件進行設置,目前主要能夠設置以下幾項:

  • 後臺啓動

    # 上面已經說了這裏,把這個改爲yes就能夠了。
    daemonize yes
  • 設置用戶名密碼

    ## 在默認的狀況下,redis是沒有密碼的,若是在測試的時候是沒問題的,可是若是項目要共享出去或者是真實的項目,那麼
    ## 就會有安全隱患,因此這種狀況下須要設置密碼。
    ## 設置的方式仍是在配置文件中 redis.conf,找到requirepass 標籤,把它前面的 `#`去掉,而後後面添加本身的密碼便可
    requirepass yourpassword
    ## 設置好以後重啓redis便可,重啓後若是不進行下面兩種方式認證,會發現你沒有權限在redis中進行任何操做
    ## 使用redis-cli 登陸的時候能夠添加參數 -a 後面添加密碼(這種是明文,可是會給警告,無論他)
    ## 第二種是按照原來的 redis-cli -p 6379 命令 登陸,登陸後 使用命令 `auth yourpassword` 進行驗證(也是明文)
  • 設置容許遠程鏈接

    ## bind字段默認爲: bind 127.0.0.1 這樣只能本機訪問redis
    ## 若容許遠程主機訪問,可註釋掉bind行   或者    將bind 127.0.0.1改成: bind 0.0.0.0

2.Redis的五大基本數據類型

基本操做

127.0.0.1:12138> keys * # 查看全部的key
(empty list or set)
127.0.0.1:12138> set user hello # 設置值
OK
127.0.0.1:12138> set age 18
OK
127.0.0.1:12138> EXISTS age  # 判斷鍵存不存在
(integer) 1
127.0.0.1:12138> move age 1  # 把鍵移動到指定的數據庫 ,redis有16個庫,默認使用第一個也就是0庫
(integer) 1
127.0.0.1:12138> keys *
1) "user"
127.0.0.1:12138> get user  # 根據key獲取value
"hello"
127.0.0.1:12138> type user # 判斷當前key對應的value的類型
string
127.0.0.1:12138> EXPIRE user 10 # 設置key的過時時間,單位是秒。-1表示沒有過時時間
(integer) 1
127.0.0.1:12138> TTL user # 查看key的剩餘時間
(integer) 6
127.0.0.1:12138> TTL user
(integer) 4
127.0.0.1:12138> TTL user # 時間爲-2表示已過時,key已經不存在
(integer) -2
127.0.0.1:12138> keys *
(empty list or set)
127.0.0.1:12138> 
127.0.0.1:12138> select 1 # 切換到指定的數據庫
OK
127.0.0.1:12138[1]> keys *
1) "age"
127.0.0.1:12138[1]> DBSIZE # 查看當前數據庫的大小
(integer) 1

2.1 String(字符串)

#################################################################################################################
基本操做
127.0.0.1:12138> set k1 v1 # 設置值
OK
127.0.0.1:12138> get k1  # 獲取值
"v1"
127.0.0.1:12138> keys * # 獲取全部的key
1) "k1"
127.0.0.1:12138> EXISTS k1 # 判斷某個key是否存在
(integer) 1
127.0.0.1:12138> APPEND k1 "append" # 追加字符串,若是當前key不存在,就至關於set key
(integer) 8
127.0.0.1:12138> get k1 
"v1append"
127.0.0.1:12138> APPEND k2 "not exist" 
(integer) 9
127.0.0.1:12138> keys *
1) "k1"
2) "k2"
127.0.0.1:12138> STRLEN k1 # 獲取key對應的value的長度
(integer) 8
#################################################################################################################
# i++ i-- 自增,自減  以及指定步長
127.0.0.1:12138> set views 0  # 初始瀏覽量爲0
OK
127.0.0.1:12138> incr views   # 自增一
(integer) 1
127.0.0.1:12138> incr views
(integer) 2
127.0.0.1:12138> get views
"2"
127.0.0.1:12138> decr views # 自減一
(integer) 1
127.0.0.1:12138> decr views 
(integer) 0
127.0.0.1:12138> decr views 
(integer) -1
127.0.0.1:12138> get views
"-1"
127.0.0.1:12138> INCRBY views 10 # 設置步長爲10
(integer) 9
127.0.0.1:12138> DECRBY views 15 # 設置步長爲15
(integer) -6
127.0.0.1:12138> get views
"-6"
#################################################################################################################
# 字符串範圍 range
127.0.0.1:12138> set key1 "you are good"
OK
127.0.0.1:12138> get key1
"you are good"
127.0.0.1:12138> GETRANGE key1 0 3 # 截取字符串 [0,3]
"you "
127.0.0.1:12138> 
127.0.0.1:12138> GETRANGE key1 0 -1 # 獲取所有的字符串 和 get key是同樣的
"you are good"
# 替換!
127.0.0.1:12138> set key2 abcdefg
OK
127.0.0.1:12138> get key2
"abcdefg"
127.0.0.1:12138> SETRANGE key2 1 123 # 替換指定位置開始的字符串!
(integer) 7
127.0.0.1:12138> get key2
"a123efg"
#################################################################################################################
# setex (set with expire) # 設置過時時間
# setnx (set if not exist) # 不存在在設置 (在分佈式鎖中會經常使用!)
127.0.0.1:12138> setex key3 10 "hello" # 設置key3 的值爲 hello,10秒後過時
OK
127.0.0.1:12138> ttl key3
(integer) 4
127.0.0.1:12138> ttl key3
(integer) 2
127.0.0.1:12138> ttl key3
(integer) -2
127.0.0.1:12138> get key3
(nil)
127.0.0.1:12138> setnx mykey "redis" # 若是mykey 不存在,建立mykey
(integer) 1
127.0.0.1:12138> setnx mykey "redisss" # 若是mykey存在,建立失敗!不會修改以前的value
(integer) 0
127.0.0.1:12138> get mykey
"redis"
#################################################################################################################
# mset mget 批量操做
127.0.0.1:12138> mset k1 v1 k2 v2 k3 v3 # 同時設置多個值
OK
127.0.0.1:12138> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:12138> mget k1 k2 k3 # 同時獲取多個值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:12138> mget k1 k2 k4 # 若是某個鍵不存在就返回nil空
1) "v1"
2) "v2"
3) (nil)
127.0.0.1:12138> msetnx k1 v1 k4 v4 # msetnx 是一個原子性的操做,要麼一塊兒成功,要麼一塊兒失敗!
(integer) 0
127.0.0.1:12138> get k4 
(nil)
127.0.0.1:12138> 
#################################################################################################################
getset # 先get而後在set
127.0.0.1:12138> getset db redis # 若是不存在值,則返回 nil,set是會執行的
(nil)
127.0.0.1:12138> get db
"redis"
127.0.0.1:12138> getset db kafka # 若是存在值,獲取原來的值,並設置新的值
"redis"
127.0.0.1:12138> get db
"kafka"
#################################################################################################################

2.2 List(列表)

在redis裏面,咱們能夠把list玩成 ,棧、隊列、阻塞隊列! 消息隊列 (Lpush Rpop), 棧( Lpush Lpop)!
  • 他其實是一個鏈表,before Node after , left,right 均可以插入值
  • 若是key 不存在,建立新的鏈表
  • 若是key存在,新增內容
  • 若是移除了全部值,空鏈表,也表明不存在!
  • 在兩邊插入或者改動值,效率最高! 中間元素,相對來講效率會低一點~
#################################################################################################################
127.0.0.1:12138> LPUSH list one two  # 將一個值或者多個值,插入到列表頭部 (左)
(integer) 2
127.0.0.1:12138> LPUSH list three
(integer) 3
127.0.0.1:12138> LRANGE list 0 -1 # 獲取list中值!
1) "three"
2) "two"
3) "one"
127.0.0.1:12138> LRANGE list 0 1 # 經過區間獲取具體的值!
1) "three"
2) "two"
127.0.0.1:12138> RPUSH list right
(integer) 4
127.0.0.1:12138> LRANGE list 0 -1 # 將一個值或者多個值,插入到列表位部 (右)
1) "three"
2) "two"
3) "one"
4) "right"
#################################################################################################################
LPOP
RPOP
127.0.0.1:12138> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:12138> LPOP list # 移除list的第一個元素
"three"
127.0.0.1:12138> RPOP list # 移除list的最後一個元素
"right"
127.0.0.1:12138> LRANGE list 0 -1
1) "two"
2) "one"
#################################################################################################################
Lindex
127.0.0.1:12138> LRANGE list 0 -1
1) "two"
2) "one"
127.0.0.1:12138> LINDEX list 1 # 經過下標得到 list 中的某一個值!
"one"
127.0.0.1:12138> LINDEX list 0
"two"
#################################################################################################################
LLEN 
127.0.0.1:12138> LLEN list # 返回列表的長度
(integer) 2
#################################################################################################################
移除指定的值!
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"
## list裏面 沒有根據index去刪除的,只有根據值刪除的,若是想要刪除指定index,有這兩種方法
### 方法一 先把想刪的設置成本身的值,而後刪除本身的值
lset mylist index "del"
lrem mylist 1 "del"
### 方法二 也能夠用事務管道合併成一次請求
multi
lset mylist index "del"
lrem mylist 1 "del"
exec
#################################################################################################################
trim 修剪。; list 截斷!
127.0.0.1:6379> keys *
(empty list or set)
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 # 經過下標截取指定的長度,這個list已經被改變了,截斷了只剩下截取的元素!
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"
#################################################################################################################
rpoplpush # 移除列表的最後一個元素,將他移動到新的列表中!
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist # 移除列表的最後一個元素,將他移動到新的列表中!
"hello2"
127.0.0.1:6379> lrange mylist 0 -1 # 查看原來的列表
1) "hello"
2) "hello1"
127.0.0.1:6379> lrange myotherlist 0 -1 # 查看目標列表中,確實存在改值!
1) "hello2"
#################################################################################################################
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 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
#################################################################################################################
linsert # 將某個具體的value插入到列把你中某個元素的前面或者後面!
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush 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) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT mylist after world new
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"
#################################################################################################################

2.3 Set(集合)

set中的值是不能重複的!

應用場景:

微博,A用戶將全部關注的人放在一個set集合中!將它的粉絲也放在一個集合中!
共同關注,共同愛好,二度好友,推薦好友!(六度分割理論)

#################################################################################################################
127.0.0.1:12138> sadd myset are # set集合中添加value
(integer) 1
127.0.0.1:12138> sadd myset you
(integer) 1
127.0.0.1:12138> sadd myset ok
(integer) 1
127.0.0.1:12138> SMEMBERS myset # 查看指定set的全部值
1) "you"
2) "are"
3) "ok"
127.0.0.1:12138> SISMEMBER myset leijun  # 判斷某一個值是否是在set集合中!
(integer) 0
127.0.0.1:12138> SISMEMBER myset are
(integer) 1
#################################################################################################################
127.0.0.1:12138> SCARD myset # 獲取set集合中的內容元素個數!
(integer) 3

#################################################################################################################
刪除元素 srem
127.0.0.1:6379> srem myset you # 移除set集合中的指定元素
(integer) 1
127.0.0.1:6379> scard myset
(integer) 3
127.0.0.1:6379> SMEMBERS myset
1) "are"
2) "ok"
#################################################################################################################
set 無序不重複集合。抽隨機!
127.0.0.1:12138> sadd myset you
(integer) 1
127.0.0.1:12138> SMEMBERS myset
1) "you"
2) "are"
3) "ok"
127.0.0.1:12138> SRANDMEMBER myset # 隨機抽選出一個元素
"ok"
127.0.0.1:12138> SRANDMEMBER myset
"ok"
127.0.0.1:12138> SRANDMEMBER myset
"ok"
127.0.0.1:12138> SRANDMEMBER myset
"are"
127.0.0.1:12138> SRANDMEMBER myset
"ok"
127.0.0.1:12138> SRANDMEMBER myset 2 # 隨機抽選出指定個數的元素
1) "you"
2) "ok"
#################################################################################################################
隨機刪除key!
127.0.0.1:12138> SMEMBERS myset
1) "you"
2) "are"
3) "ok"
127.0.0.1:12138>  spop myset # 隨機刪除一個key
"you"
127.0.0.1:12138>  spop myset
"are"
127.0.0.1:12138> SMEMBERS myset 
1) "ok"
127.0.0.1:12138>  spop myset 2 # 隨機刪除指定個數的key,若是集合中的個數不夠就會有多少刪除多少
1) "ok"
127.0.0.1:12138> SMEMBERS myset
(empty list or set)
#################################################################################################################
將一個指定的值,移動到另一個set集合!
127.0.0.1:12138> sadd myset are
(integer) 1
127.0.0.1:12138> sadd myset you
(integer) 1
127.0.0.1:12138> sadd myset ok
(integer) 1
127.0.0.1:12138> sadd myset leijun
(integer) 1
127.0.0.1:12138> smove myset newmyset leijun # 將一個指定的值,移動到另一個set集合!
(integer) 1
127.0.0.1:12138> SMEMBERS myset
1) "you"
2) "are"
3) "ok"
127.0.0.1:12138> SMEMBERS newmyset
1) "leijun"
#################################################################################################################
微博,B站,共同關注!(並集)
數字集合類:
- 差集 SDIFF
- 交集
- 並集
127.0.0.1:12138> sadd k1 a b c d f e g 
(integer) 7
127.0.0.1:12138> sadd k2 a b c d h i j k  
(integer) 8
127.0.0.1:12138> SDIFF k1 k2 # 差集 相對於k1, k2不存在的值
1) "f"
2) "e"
3) "g"
127.0.0.1:12138> SINTER k1 k2 # 交集
1) "c"
2) "b"
3) "d"
4) "a"
127.0.0.1:12138> SUNION k1 k2  # 並集
 1) "j"
 2) "c"
 3) "g"
 4) "h"
 5) "e"
 6) "i"
 7) "f"
 8) "d"
 9) "b"
10) "k"
11) "a"
#################################################################################################################

 2.4 Hash(哈希)

Map集合,key-map! 時候這個值是一個map集合! 本質和String類型沒有太大區別,仍是一個簡單的
key-vlaue!
set myhash myfield myvalue

hash 更適合於對象的存儲,String更加適合字符串存儲!

#################################################################################################################
127.0.0.1:12138> hset myhash field1 hello # set一個具體 key-vlaue
(integer) 1
127.0.0.1:12138> hget myhash field1 # 獲取一個字段值
"hello"
127.0.0.1:12138> hset hash f1 v1 f2 v2 # 目前測試的hset也能夠同時設置多個值,和hmset的惟一區別就是,他返回的是影響的條數,若是原來有則覆蓋,沒有則新增,然後者只會返回一個字符串OK
(integer) 2
127.0.0.1:12138> hmset hash f3 v3 f4 v4 # set多個 key-vlaue
OK
127.0.0.1:12138> HGETALL hash # 獲取所有的數據
1) "f1"
2) "v1"
3) "f2"
4) "v2"
5) "f3"
6) "v3"
7) "f4"
8) "v4"
127.0.0.1:12138> hdel hash f1 # 刪除hash指定key字段!對應的value值也就消失了!
(integer) 1
127.0.0.1:12138> hdel hash f2 f3 # 能夠一次刪除多個字段
(integer) 2
127.0.0.1:12138> hdel hash f2  # 刪除不存在的會返回影響的條數
(integer) 0 
127.0.0.1:12138> HGETALL hash
1) "f4"
2) "v4"
#################################################################################################################
hlen # 獲取hash的字段數量
127.0.0.1:12138> HGETALL hash
1) "f4"
2) "v4"
127.0.0.1:12138> hset hash f1 v1 f2 v2
(integer) 2
127.0.0.1:12138> HGETALL hash
1) "f4"
2) "v4"
3) "f1"
4) "v1"
5) "f2"
6) "v2"
127.0.0.1:12138> hlen hash # 獲取hash表的字段數量!
(integer) 3
#################################################################################################################
127.0.0.1:12138> HEXISTS hash f5 # 判斷hash中指定字段是否存在!
(integer) 0
127.0.0.1:12138> HEXISTS hash f1
(integer) 1
#################################################################################################################
# 只得到全部field
# 只得到全部value
127.0.0.1:12138> hkeys hash # 只得到全部field
1) "f4"
2) "f1"
3) "f2"
127.0.0.1:12138> HVALS hash # 只得到全部value
1) "v4"
2) "v1"
3) "v2"
#################################################################################################################
# incr decr hash 中只有HINCRBY實現增長的api,固然增量可使負的

127.0.0.1:12138> hset mytest age 5
(integer) 1
127.0.0.1:12138> HINCRBY myset age 1 #指定增量!
(integer) 1
127.0.0.1:12138> HINCRBY myset age 3
(integer) 4
127.0.0.1:12138> HINCRBY myset age -5 #指定增量!
(integer) -1
127.0.0.1:12138> hget myset age
"-1"
#################################################################################################################
# HSETNX 不爲空的時候設置
127.0.0.1:12138> HSETNX myset f6 v6 # 若是不存在則能夠設置
(integer) 1
127.0.0.1:12138> HSETNX myset f6 v5 # 若是存在則不能設置
(integer) 0
#################################################################################################################

2.5 Zset(有序集合)

在set的基礎上,增長了一個值,set k1 v1 zset k1 score1 v1

應用場景:set 排序 存儲班級成績表,工資表排序!
普通消息,1, 重要消息 2,帶權重進行判斷!
排行榜應用實現,取Top N 測試!

#################################################################################################################
127.0.0.1:6379> zadd myset 1 one # 添加一個值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多個值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
#################################################################################################################
排序如何實現
# zset中也可使用zrange 進行排序,和普通的range同樣。(默認是從小到大,range能指定索引,而rangebyscore能指定分數)
127.0.0.1:6379> zadd salary 2500 xiaohong # 添加三個用戶
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangsan
(integer) 1
127.0.0.1:6379> zadd salary 500 lisi
(integer) 1
# ZRANGEBYSCORE key min max
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 顯示所有的用戶 從小到大!
1) "lisi"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGE salary 0 -1 # 從大到進行排序!
1) "xiaohong"
2) "zhangsan"
3) "lisi"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 顯示所有的用戶而且附帶成
績
1) "lisi"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhangsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores # 顯示工資小於2500員工的升
序排序!
1) "lisi"
2) "500"
3) "xiaohong"
4) "2500"
#################################################################################################################
# 移除rem中的元素
127.0.0.1:6379> zrange salary 0 -1
1) "kaungshen"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> zrem salary xiaohong # 移除有序集合中的指定元素
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "kaungshen"
2) "zhangsan"
127.0.0.1:6379> zcard salary # 獲取有序集合中的個數
(integer) 2
#################################################################################################################
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world 3 kuangshen
(integer) 2
127.0.0.1:6379> zcount myset 1 3 # 獲取指定區間的成員數量!
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2
#################################################################################################################

3.Redis的三種特殊數據類型

3.1Geospatial 地理位置

朋友的定位,附近的人,打車距離計算?
Redis 的 Geo 在Redis3.2 版本就推出了! 這個功能能夠推算地理位置的信息,兩地之間的距離,方圓
幾裏的人!

一共就6個命令

補充

# geo沒有刪除命令,咱們可使用zrem去刪除,其實他底層使用的是 Zset!咱們可使用Zset命令來操做geo! 固然geo的最底層仍是跳躍鏈表
127.0.0.1:6379> ZRANGE china:city 0 -1 # 查看地圖中所有的元素
1) "chongqi"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city beijing # 移除指定元素! 這個經常使用
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqi"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
#################################################################################################################
# getadd 添加地理位置
# 命令:**GEOADD** key longitude latitude member [longitude latitude member ...]
# 命令描述:將指定的地理空間位置(經度、緯度、名稱)添加到指定的key中。
# 規則:北極和南極沒法直接添加,咱們通常會下載城市數據,直接經過java程序一次性導入!
# 有效的經度從-180度到180度。
# 有效的緯度從-85.05112878度到85.05112878度。
# 當座標位置超出上述指定範圍時,該命令將會返回一個錯誤。
# 127.0.0.1:6379> geoadd china:city 39.90 116.40 beijin
(error) ERR invalid longitude,latitude pair 39.900000,116.400000
# 參數 key 值()
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 106.50 29.53 chongqi 114.05 22.52 shengzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou 108.96 34.26 xian
(integer) 2
#################################################################################################################
# getpos 得到當前定位:必定是一個座標值!
27.0.0.1:6379> GEOPOS china:city beijing # 獲取指定的城市的經度和緯度!
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city beijing chongqi
1) 1) "116.39999896287918091"
2) "39.90000009167092543"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
#################################################################################################################
# GEODIST 兩個位置之間的距離 單位:
m 表示單位爲米。
km 表示單位爲公里。
mi 表示單位爲英里。
ft 表示單位爲英尺
127.0.0.1:6379> GEODIST china:city beijing shanghai km # 查看上海到北京的直線距離
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing chongqi km # 查看重慶到北京的直線距離
"1464.0708"
#################################################################################################################
# georadius 以給定的經緯度爲中心, 找出某一半徑內的元素
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km # 以110,30 這個經緯度爲中心,尋找方圓1000km內的城市
1) "chongqi"
2) "xian"
3) "shengzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqi"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist # 顯示到中間距離的位置
1) 1) "chongqi"
2) "341.9374"
2) 1) "xian"
2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord # 顯示他人的定位信息
1) 1) "chongqi"
2) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) 1) "108.96000176668167114"
2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1 #篩選出指定的結果!
1) 1) "chongqi"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqi"
2) "341.9374"
3) 1) "106.49999767541885376"
2) "29.52999957900659211"
2) 1) "xian"
2) "483.8340"
3) 1) "108.96000176668167114"
2) "34.25999964418929977"
#################################################################################################################
# GEORADIUSBYMEMBER 找出位於指定元素周圍的其餘元素!
127.0.0.1:6379> GEORADIUSBYMEMBER china:city beijing 1000 km
1) "beijing"
2) "xian"
127.0.0.1:6379> GEORADIUSBYMEMBER china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
#################################################################################################################
# GEOHASH 命令 - 返回一個或多個位置元素的 Geohash 表示
該命令將返回11個字符的Geohash字符串!
127.0.0.1:6379> geohash china:city beijing chongqi
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
#################################################################################################################

3.2 Hyperloglog

什麼是基數?

A {1,3,5,7,8,7}
B{1,3,5,7,8}

基數(不重複的元素) = 5,能夠接受偏差!

Redis Hyperloglog 基數統計的算法!(這個也能夠去學習學習)

優勢:佔用的內存是固定,2^64 不一樣的元素的技術,只須要廢佔用12KB內存!若是要從內存角度來比較的話 Hyperloglog 首選!

使用場景:網頁的 UV (一我的訪問一個網站屢次,可是仍是算做一我的!)
傳統的方式, set 保存用戶的id,而後就能夠統計 set 中的元素數量做爲標準判斷 !
這個方式若是保存大量的用戶id,就會比較麻煩!咱們的目的是爲了計數,而不是保存用戶id;
0.81% 錯誤率! 統計UV任務,能夠忽略不計的!

若是容許容錯,那麼必定可使用 Hyperloglog !
若是不容許容錯,就使用 set 或者本身的數據類型便可!

127.0.0.1:6379> PFadd mykey a b c d e f g h i j # 建立第一組元素 mykey
(integer) 1
127.0.0.1:6379> PFCOUNT mykey # 統計 mykey 元素的基數數量
(integer) 10
127.0.0.1:6379> PFadd mykey2 i j z x c v b n m # 建立第二組元素 mykey2
(integer) 1
127.0.0.1:6379> PFCOUNT mykey2
(integer) 9
127.0.0.1:6379> PFMERGE mykey3 mykey mykey2 # 合併兩組 mykey mykey2 => mykey3 並集
OK
127.0.0.1:6379> PFCOUNT mykey3 # 看並集的數量!
(integer) 15

3.3 Bitmap 位存儲

使用場景:

統計用戶信息,活躍,不活躍! 登陸 、 未登陸! 打卡,365打卡! 兩個狀態的,均可以使用
Bitmaps!
Bitmap 位圖,數據結構! 都是操做二進制位來進行記錄,就只有0 和 1 兩個狀態!
365 天 = 365 bit 1字節 = 8bit 46 個字節左右!

# 使用bitmap 來記錄 週一到週日的打卡!
# 週一:1 週二:0 週三:0 週四:1 ......
127.0.0.1:6379> setbit sign 0 1
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
127.0.0.1:6379> getbit sign 3 # 查看某一天是否有打卡!
(integer) 1
127.0.0.1:6379> getbit sign 6
(integer) 0
127.0.0.1:6379> bitcount sign # 統計這周的打卡記錄,就能夠看到是否有全勤!
(integer) 3

4.Redis中的事務

事務簡介

Redis 事務本質:一組命令的集合! 一個事務中的全部命令都會被序列化,在事務執行過程的中,會按照順序執行!
一次性、順序性、排他性!執行一些列的命令!
Redis事務沒有沒有隔離級別的概念!
全部的命令在事務中,並無直接被執行!只有發起執行命令的時候纔會執行!Exec
Redis單條命令式保存原子性的,可是事務不保證原子性!
redis的事務:
開啓事務(multi)
命令入隊(......)
執行事務(exec)

Redis的事務操做

#################################################################################################################
# 正常執行事務!
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 k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 執行事務
1) OK
2) OK
3) "v2"
4) OK
#################################################################################################################
# 放棄事務!
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> set k4 v4
QUEUED
127.0.0.1:6379> DISCARD # 取消事務
OK
127.0.0.1:6379> get k4 # 事務隊列中命令都不會被執行!
(nil)
#################################################################################################################
# 編譯型異常(代碼有問題! 命令有錯!) ,事務中全部的命令都不會被執行!
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> set k3 v3
QUEUED
127.0.0.1:6379> getset k3 # 錯誤的命令
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> set k5 v5
QUEUED
127.0.0.1:6379> exec # 執行事務報錯!
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5 # 全部的命令都不會被執行!
(nil)
#################################################################################################################
# 運行時異常(1/0), 若是事務隊列中存在語法性,那麼執行命令的時候,其餘命令是能夠正常執行的,錯誤命令拋出異常!
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"
#################################################################################################################

事務的樂觀鎖,不是另外一個線程,是另外一個客戶端,關於樂觀鎖能夠參考下這個:https://my.oschina.net/itommy...

https://www.cnblogs.com/marti...

證實:

127.0.0.1:12138> set money 100
OK
127.0.0.1:12138> set mon 0
OK
127.0.0.1:12138> watch money
OK
127.0.0.1:12138> incrby money 55
(integer) 155
127.0.0.1:12138> multi
OK
127.0.0.1:12138> decr money
QUEUED
127.0.0.1:12138> incr mon
QUEUED
127.0.0.1:12138> exec
(nil)
127.0.0.1:12138> get money
"155"
127.0.0.1:12138>

Redis 的監控機制(監控! Watch (面試常問!))

可使用Redis的監控機制實現樂觀鎖或者分佈式鎖

測試:

#################################################################################################################
# 正常執行成功!
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
#################################################################################################################
# 測試多線程修改值 , 使用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)
#################################################################################################################

若是修改失敗,獲取最新的值就好

image-20200926204958179

監控機制不會帶來ABA問題

redis是單線程的,在使用watch進行監控的時候,一旦修改就會被watch,由於修改的時候就是那個線程,因此redis的watch不存在aba問題。 使用watch監視一個或者多個key,跟蹤key的value修改狀況,若是有key的value值在 事務exec執行以前被修改了,整個事務被取消。exec返回提示信息,表示事務已經失敗。可是若是使用watch監視了一個帶過時時間的鍵,那麼即便這個鍵過時了,事務仍然能夠正常執行。

5.Java中使用Redis

5.1經過jedis鏈接Redis

Java操做redis最基礎的就是用jedis。Jedis 是 Redis 官方推薦的 java鏈接開發工具! 使用Java 操做Redis 中間件!若是你要使用
java操做redis,那麼必定要對Jedis 十分的熟悉!

使用步驟:

  • 導入相關的依賴

    <!--導入jedis的包-->
    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
    <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>
    • 編寫測試代碼
    public class JedisDemo {
        public static void main(String[] args) {
        // 一、 new Jedis 對象便可
            Jedis jedis = new Jedis("www.njitzyd.com",12138);
            // 若是設置了密碼須要進行驗證
            jedis.auth("zydredis");
         // jedis 全部的命令就是咱們以前學習的全部指令!因此以前的指令學習很重要!
            System.out.println(jedis.ping());
        }
    }
    • 查看結果

      image-20200926215422152

能夠看到鏈接成功!

Jedis中經常使用的API

#################################################################################################################
# 全部的api命令,就是咱們對應的上面學習的指令,一個都沒有變化!全部的均可以經過Jedis對象完成操做,和以前的命令行中的命令徹底一致!
String
List
Set
Hash
Zset
#################################################################################################################
# 事務 也是和以前在命令行中的一致
public class TestTX {
public static void main(String[] args) {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.flushDB();
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello","world");
jsonObject.put("name","leijun");
// 開啓事務
Transaction multi = jedis.multi();
String result = jsonObject.toJSONString();
// jedis.watch(result)
try {
multi.set("user1",result);
multi.set("user2",result);
int i = 1/0 ; // 代碼拋出異常事務,執行失敗!
multi.exec(); // 執行事務!
} catch (Exception e) {
multi.discard(); // 放棄事務
e.printStackTrace();
} finally {
System.out.println(jedis.get("user1"));
System.out.println(jedis.get("user2"));
jedis.close(); // 關閉鏈接
}
}
}
#################################################################################################################

5.2 SpringBoot整合Redis

在SpringBoot 2.x 版本中,使用lettuce替代了jedis來操做Redis。

jedis : 採用的直連,多個線程操做的話,是不安全的,若是想要避免不安全的,使用 jedis pool 鏈接
池! 更像 BIO 模式
lettuce : 採用netty,實例能夠再多個線程中進行共享,不存在線程不安全的狀況!能夠減小線程數據
了,更像 NIO 模式

源碼解析
@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;
}
SpringBoot中使用案例

使用的步驟:

  • 添加依賴(是springboot項目)

    <!-- 操做redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
  • 配置鏈接

    在spring.properties中配置以下

    # 配置redis
    spring.redis.host=127.0.0.1
    spring.redis.port=6379
  • 測試

    @SpringBootTest
    class Redis02SpringbootApplicationTests {
    @Autowired
    private 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("mykey","myvalue");
    System.out.println(redisTemplate.opsForValue().get("mykey"));
    }
    }
自定義RedisTemplate

自帶的RedisTemplate的問題:

  1. 默認的序列化方式是jdk自帶的,當直接存入沒有實現Serializable的類會直接報錯序列化失敗。

    image-20200926225941986

  2. 自帶的兩個泛型都是Object,而咱們常用的是key爲string類型。
  3. 咱們沒法指定序列化的方式,而實際開發中常用fastjson或者Jackson來實現序列化。

自定義以下,基本知足需求:

// 聲明爲一個配置類
@Configuration
public class RedisConfig {
// 本身定義了一個 RedisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory
factory) {
// 咱們爲了本身開發方便,通常直接使用 <String, Object>
RedisTemplate<String, Object> template = new RedisTemplate<String,
Object>();
template.setConnectionFactory(factory);
// Json序列化配置
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String 的序列化
StringRedisSerializer stringRedisSerializer = new
StringRedisSerializer();
// key採用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也採用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式採用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式採用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}

這樣就能夠實現自定義的RedisTemplate,當咱們自定義時,系統自帶的就不會初始化。(springboot的starter機制,在自帶的RedisTemplate中有這個註解@ConditionalOnMissingBean(name = "redisTemplate")

自定義redis工具類

就是對上面自定義的RedisTemplate以後還能夠再次封裝,從而使得redis操做更方便。

// 在咱們真實的分發中,或者大家在公司,通常均可以看到一個公司本身封裝RedisUtil
@Component
public final class RedisUtil {

    // 這裏要注意,注入的是咱們剛剛自定義的RedisTemplate,而不是官方默認的!
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    
    // =============================common============================
    /**
     * 指定緩存失效時間
     * @param key  鍵
     * @param time 時間(秒)
     */
    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)
     */
    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)
     */
    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
     */
    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 對應多個鍵值
     */
    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)
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }


    /**
     * hash遞減
     * @param key  鍵
     * @param item 項
     * @param by   要減小記(小於0)
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }


    // ============================set=============================

    /**
     * 根據key獲取Set中的全部值
     * @param key 鍵
     */
    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 鍵
     */
    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表明全部值
     */
    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 鍵
     */
    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倒數第二個元素,依次類推
     */
    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 值
     */
    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  時間(秒)
     */
    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;
        }
    }
}

5.3 jedis和lu的對比

6.Redis的配置文件redis.conf詳解

7.Redis持久化

Redis 是內存數據庫,若是不將內存中的數據庫狀態保存到磁盤,那麼一旦服務器進程退出,服務器中
的數據庫狀態也會消失。因此 Redis 提供了持久化功能!

7.1 RDB(Redis DataBase)

7.1.1簡介

image-20200928205610092

在指定的時間間隔內將內存中的數據集快照寫入磁盤,也就是行話講的Snapshot快照,它恢復時是將快照文件直接讀到內存裏。
Redis會單首創建(fork)一個子進程來進行持久化,會先將數據寫入到一個臨時文件中,待持久化過程都結束了,再用這個臨時文件替換上次持久化好的文件。整個過程當中,主進程是不進行任何IO操做的。這就確保了極高的性能。若是須要進行大規模數據的恢復,且對於數據恢復的完整性不是很是敏感,那RDB方式要比AOF方式更加的高效。RDB的缺點是最後一次持久化後的數據可能丟失。咱們默認的就是RDB,通常狀況下不須要修改這個配置!

rdb保存的文件是默認的dump.rdb 就是在咱們的配置文件中快照中進行配置的!具體配置就是上面所描述的redis.conf文件。

7.1.2 觸發機制

默認的save的規則:

image-20200928210407847

# 若是900s內,若是至少有一個1 key進行了修改,咱們及進行持久化操做
save 900 1
# 若是300s內,若是至少10 key進行了修改,咱們及進行持久化操做
save 300 10
# 若是60s內,若是至少10000 key進行了修改,咱們及進行持久化操做
save 60 10000

當知足下面的條件時就會觸發生成dump.rdb:

  1. save的規則知足的狀況下,會自動觸發rdb規則(上述的規則是針對bgsave命令的,是對bgsave命令生效的)
  2. 當執行save或者bgsave命令的時候,會觸發rdb生成rdb文件(save和bgsave的區別)
  3. 執行 flushall 命令,也會觸發咱們的rdb規則!
  4. 退出redis(使用shutdown命令會觸發,shutdown nosave 命令不會觸發,使用kill命令強制退出也不會觸發),也會產生 rdb 文件!

7.1.3 RDB文件保存過程

  • redis調用fork,如今有了子進程和父進程。
  • 父進程繼續處理client請求,子進程負責將內存內容寫入到臨時文件。因爲os的寫時複製機制(copy on write)父子進程會共享相同的物理頁面,當父進程處理寫請求時os會爲父進程要修改的頁面建立副本,而不是寫共享的頁面。因此子進程的地址空間內的數據是fork時刻整個數據庫的一個快照。
  • 當子進程將快照寫入臨時文件完畢後,用臨時文件替換原來的快照文件,而後子進程退出。

7.1.4數據恢復

一、只須要將rdb文件放在咱們redis啓動目錄就能夠,redis啓動的時候會自動檢查dump.rdb 恢復其中的數據!(默認就是在這個位置)
二、查看須要存在的位置

127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 若是在這個目錄下存在 dump.rdb 文件,啓動就會自動恢復其中的數據

7.1.4 RDB的優缺點

優點

  • 一旦採用該方式,那麼你的整個Redis數據庫將只包含一個文件,這樣很是方便進行備份。好比你可能打算沒1天歸檔一些數據。
  • 方便備份,咱們能夠很容易的將一個一個RDB文件移動到其餘的存儲介質上
  • RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。
  • RDB 能夠最大化 Redis 的性能:父進程在保存 RDB 文件時惟一要作的就是 fork 出一個子進程,而後這個子進程就會處理接下來的全部保存工做,父進程無須執行任何磁盤 I/O 操做。

劣勢

  • 若是你須要儘可能避免在服務器故障時丟失數據,那麼 RDB 不適合你。 雖然 Redis 容許你設置不一樣的保存點(save point)來控制保存 RDB 文件的頻率, 可是, 由於RDB 文件須要保存整個數據集的狀態, 因此它並非一個輕鬆的操做。 所以你可能會至少 5 分鐘才保存一次 RDB 文件。 在這種狀況下, 一旦發生故障停機, 你就可能會丟失好幾分鐘的數據。
  • 每次保存 RDB 的時候,Redis 都要 fork() 出一個子進程,並由子進程來進行實際的持久化工做。 在數據集比較龐大時, fork() 可能會很是耗時,形成服務器在某某毫秒內中止處理客戶端; 若是數據集很是巨大,而且 CPU 時間很是緊張的話,那麼這種中止時間甚至可能會長達整整一秒。 雖然 AOF 重寫也須要進行 fork() ,但不管 AOF 重寫的執行間隔有多長,數據的耐久性都不會有任何損失。

7.2 AOF(Append Only File)

將咱們的全部命令都記錄下來,history,恢復的時候就把這個文件所有在執行一遍!

image-20200928224944407

以日誌的形式來記錄每一個寫操做,將Redis執行過的全部指令記錄下來(讀操做不記錄),只許追加文件但不能夠改寫文件,redis啓動之初會讀取該文件從新構建數據,換言之,redis重啓的話就根據日誌文件的內容將寫指令從前到後執行一次以完成數據的恢復工做
Aof保存的是 appendonly.aof 文件

image-20200928225027558

默認是不開啓的,咱們須要手動進行配置!咱們只須要將 appendonly 改成yes就開啓了 aof!其餘的默認就好,能夠參考上面配置文件部分的講解。重啓,redis 就能夠生效了!

若是若是aof文件被破壞,好比手動修改裏面的內容,可使用redis-check-aof來進行修復,具體命令是:redis-check-aof --fix appendonly.aof(可能會把錯誤的那條數據給刪除,會形成丟失數據。可是這種手動修改數據的場景很少見)

7.2.1 AOF 簡介

redis會將每個收到的寫命令都經過write函數追加到文件中(默認是 appendonly.aof)。

當redis重啓時會經過從新執行文件中保存的寫命令來在內存中重建整個數據庫的內容。固然因爲os會在內核中緩存 write作的修改,因此可能不是當即寫到磁盤上。這樣aof方式的持久化也仍是有可能會丟失部分修改。不過咱們能夠經過配置文件告訴redis咱們想要 經過fsync函數強制os寫入到磁盤的時機。有三種方式以下(默認是:每秒fsync一次)

appendonly yes              //啓用aof持久化方式
# appendfsync always      //每次收到寫命令就當即強制寫入磁盤,最慢的,可是保證徹底的持久化,不推薦使用
appendfsync everysec     //每秒鐘強制寫入磁盤一次,在性能和持久化方面作了很好的折中,推薦
# appendfsync no    //徹底依賴os,性能最好,持久化沒保證

7.2.2 AOF的rewrite機制

aof 的方式也同時帶來了另外一個問題。持久化文件會變的愈來愈大。例如咱們調用incr test命令100次,文件中必須保存所有的100條命令,其實有99條都是多餘的。由於要恢復數據庫的狀態其實文件中保存一條set test 100就夠了。

爲了壓縮aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis將使用與快照相似的方式將內存中的數據 以命令的方式保存到臨時文件中,最後替換原來的文件。具體過程以下

  • redis調用fork ,如今有父子兩個進程
  • 子進程根據內存中的數據庫快照,往臨時文件中寫入重建數據庫狀態的命令
  • 父進程繼續處理client請求,除了把寫命令寫入到原來的aof文件中。同時把收到的寫命令緩存起來。這樣就能保證若是子進程重寫失敗的話並不會出問題。
  • 當子進程把快照內容寫入已命令方式寫到臨時文件中後,子進程發信號通知父進程。而後父進程把緩存的寫命令也寫入到臨時文件。
  • 如今父進程可使用臨時文件替換老的aof文件,並重命名,後面收到的寫命令也開始往新的aof文件中追加。

須要注意到是重寫aof文件的操做,並無讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點相似。

7.2.3 AOF的優缺點

優點

  • 使用 AOF 持久化會讓 Redis 變得很是耐久(much more durable):你能夠設置不一樣的 fsync 策略,好比無 fsync ,每秒鐘一次 fsync ,或者每次執行寫入命令時 fsync 。 AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,Redis 仍然能夠保持良好的性能,而且就算髮生故障停機,也最多隻會丟失一秒鐘的數據( fsync 會在後臺線程執行,因此主線程能夠繼續努力地處理命令請求)。
  • AOF 文件是一個只進行追加操做的日誌文件(append only log), 所以對 AOF 文件的寫入不須要進行 seek , 即便日誌由於某些緣由而包含了未寫入完整的命令(好比寫入時磁盤已滿,寫入中途停機,等等), redis-check-aof 工具也能夠輕易地修復這種問題。
    Redis 能夠在 AOF 文件體積變得過大時,自動地在後臺對 AOF 進行重寫: 重寫後的新 AOF 文件包含了恢復當前數據集所需的最小命令集合。 整個重寫操做是絕對安全的,由於 Redis 在建立新 AOF 文件的過程當中,會繼續將命令追加到現有的 AOF 文件裏面,即便重寫過程當中發生停機,現有的 AOF 文件也不會丟失。 而一旦新 AOF 文件建立完畢,Redis 就會從舊 AOF 文件切換到新 AOF 文件,並開始對新 AOF 文件進行追加操做。
  • AOF 文件有序地保存了對數據庫執行的全部寫入操做, 這些寫入操做以 Redis 協議的格式保存, 所以 AOF 文件的內容很是容易被人讀懂, 對文件進行分析(parse)也很輕鬆。 導出(export) AOF 文件也很是簡單: 舉個例子, 若是你不當心執行了 FLUSHALL 命令, 但只要 AOF 文件未被重寫, 那麼只要中止服務器, 移除 AOF 文件末尾的 FLUSHALL 命令, 並重啓 Redis , 就能夠將數據集恢復到 FLUSHALL 執行以前的狀態。

劣勢

  • 對於相同的數據集來講,AOF 文件的體積一般要大於 RDB 文件的體積。
  • 由於redis是單線程的,每次aof文件同步寫入都要等(能夠參考下面AOF持久化詳解文章中的文件寫入和同步模塊)。根據所使用的 fsync 策略,AOF 的速度可能會慢於 RDB 。 在通常狀況下, 每秒 fsync 的性能依然很是高, 而關閉 fsync 可讓 AOF 的速度和 RDB 同樣快, 即便在高負荷之下也是如此。 不過在處理巨大的寫入載入時,RDB 能夠提供更有保證的最大延遲時間(latency)。
  • AOF 在過去曾經發生過這樣的 bug : 由於個別命令的緣由,致使 AOF 文件在從新載入時,沒法將數據集恢復成保存時的原樣。 (舉個例子,阻塞命令 BRPOPLPUSH 就曾經引發過這樣的 bug 。) 測試套件裏爲這種狀況添加了測試: 它們會自動生成隨機的、複雜的數據集, 並經過從新載入這些數據來確保一切正常。 雖然這種 bug 在 AOF 文件中並不常見, 可是對比來講, RDB 幾乎是不可能出現這種 bug 的。

7.3 二者對比

二者的開啓並非衝突的,若是都開啓,系統默認是優先加載aof的文件來進行恢復。那個兩種持久化方式如何關閉,能夠參考下面的方法。RDB和AOF的關閉方法

AOF持久化詳解

參考

7.4擴展:

一、RDB 持久化方式可以在指定的時間間隔內對你的數據進行快照存儲
二、AOF 持久化方式記錄每次對服務器寫的操做,當服務器重啓的時候會從新執行這些命令來恢復原始的數據,AOF命令以Redis 協議追加保存每次寫的操做到文件末尾,Redis還能對AOF文件進行後臺重寫,使得AOF文件的體積不至於過大。
三、只作緩存,若是你只但願你的數據在服務器運行的時候存在,你也能夠不使用任何持久化
四、同時開啓兩種持久化方式在這種狀況下,當redis重啓的時候會優先載入AOF文件來恢復原始的數據,由於在一般狀況下AOF文件保存的數據集要比RDB文件保存的數據集要完整。RDB 的數據不實時,同時使用二者時服務器重啓也只會找AOF文件,那要不要只使用AOF呢?做者建議不要,由於RDB更適合用於備份數據庫(AOF在不斷變化很差備份),快速重啓,並且不會有AOF可能潛在的Bug,留着做爲一個萬一的手段。
五、性能建議
由於RDB文件只用做後備用途,建議只在Slave上持久化RDB文件,並且只要15分鐘備份一次就夠了,只保留 save 900 1 這條規則。若是Enable AOF ,好處是在最惡劣狀況下也只會丟失不超過兩秒數據,啓動腳本較簡單隻load本身的AOF文件就能夠了,代價一是帶來了持續的IO,二是AOF rewrite 的最後將 rewrite 過程當中產生的新數據寫到新文件形成的阻塞幾乎是不可避免的。只要硬盤許可,應該儘可能減小AOF rewrite的頻率,AOF重寫的基礎大小默認值64M過小了,能夠設到5G以上,默認超過原大小100%大小重寫能夠改到適當的數值。若是不Enable AOF ,僅靠 Master-Slave Repllcation 實現高可用性也能夠,能省掉一大筆IO,也減小了rewrite時帶來的系統波動。代價是若是Master/Slave 同時倒掉,會丟失十幾分鐘的數據,啓動腳本也要比較兩個 Master/Slave 中的 RDB文件,載入較新的那個,微博就是這種架構。

8.Redis發佈訂閱(暫時簡單瞭解)

發佈訂閱的命令

image-20200929214049296

8.1 測試

訂閱端:

127.0.0.1:6379> SUBSCRIBE mychannel # 訂閱一個頻道 mychannel
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "mychannel"
3) (integer) 1
# 等待讀取推送的信息
1) "message" # 消息
2) "mychannel" # 那個頻道的消息
3) "hello,channel" # 消息的具體內容
1) "message"
2) "mychannel"
3) "hello,redis"

發送端:

127.0.0.1:6379> PUBLISH mychannel "hello,channel" # 發佈者發佈消息到頻道!
(integer) 1
127.0.0.1:6379> PUBLISH mychannel "hello,redis" # 發佈者發佈消息到頻道!
(integer) 1

8.2 原理

Redis是使用C實現的,經過分析 Redis 源碼裏的 pubsub.c 文件,瞭解發佈和訂閱機制的底層實現,籍此加深對 Redis 的理解。

Redis 經過 PUBLISH 、SUBSCRIBE 和 PSUBSCRIBE 等命令實現發佈和訂閱功能。
經過 SUBSCRIBE 命令訂閱某頻道後,redis-server 裏維護了一個字典,字典的鍵就是一個個 頻道!而字典的值則是一個鏈表,鏈表中保存了全部訂閱這個 channel 的客戶端。SUBSCRIBE 命令的關鍵,就是將客戶端添加到給定 channel 的訂閱鏈表中。

經過 PUBLISH 命令向訂閱者發送消息,redis-server 會使用給定的頻道做爲鍵,在它所維護的 channel字典中查找記錄了訂閱這個頻道的全部客戶端的鏈表,遍歷這個鏈表,將消息發佈給全部訂閱者。

Pub/Sub 從字面上理解就是發佈(Publish)與訂閱(Subscribe),在Redis中,你能夠設定對某一個key值進行消息發佈及消息訂閱,當一個key值上進行了消息發佈後,全部訂閱它的客戶端都會收到相應的消息。這一功能最明顯的用法就是用做實時消息系統,好比普通的即時聊天,羣聊等功能。

9.Redis主從複製

9.1 概念

主從複製,是指將一臺Redis服務器的數據,複製到其餘的Redis服務器。前者稱爲主節點(master/leader),後者稱爲從節點(slave/follower);數據的複製是單向的,只能由主節點到從節點。
Master以寫爲主,Slave 以讀爲主。

可使用命令 info replication查看redis的相關信息以下:

# Replication
role:master
connected_slaves:0
master_replid:a63afb2b95c60ac0a9d20b5cb76d1505a54bac58
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

默認狀況下,每臺獨立Redis服務器都是主節點;
且一個主節點能夠有多個從節點(或沒有從節點),但一個從節點只能有一個主節點。

主從複製的做用主要包括:
一、數據冗餘:主從複製實現了數據的熱備份,是持久化以外的一種數據冗餘方式。
二、故障恢復:當主節點出現問題時,能夠由從節點提供服務,實現快速的故障恢復;其實是一種服務的冗餘。
三、負載均衡:在主從複製的基礎上,配合讀寫分離,能夠由主節點提供寫服務,由從節點提供讀服務(即寫Redis數據時應用鏈接主節點,讀Redis數據時應用鏈接從節點),分擔服務器負載;尤爲是在寫少讀多的場景下,經過多個從節點分擔讀負載,能夠大大提升Redis服務器的併發量。
四、高可用(集羣)基石:除了上述做用之外,主從複製仍是哨兵和集羣可以實施的基礎,所以說主從複製是Redis高可用的基礎。

通常來講,要將Redis運用於工程項目中,只使用一臺Redis是萬萬不能的(宕機),緣由以下:
一、從結構上,單個Redis服務器會發生單點故障,而且一臺服務器須要處理全部的請求負載,壓力較大;
二、從容量上,單個Redis服務器內存容量有限,就算一臺Redis服務器內存容量爲256G,也不能將全部內存用做Redis存儲內存,通常來講,單臺Redis最大使用內存不該該超過20G。
電商網站上的商品,通常都是一次上傳,無數次瀏覽的,說專業點也就是"多讀少寫"。

<img src="https://gitee.com/jsnucrh/blog-sharding_1/raw/master/img/20201219225402.png" alt="image-20200929224316529" style="zoom:80%;" />

9.2 環境配置

若是資源比較少,只有一臺服務器的話,能夠複製3個配置文件,而後修改對應的信息
一、端口
二、pid 名字
三、log文件名字
四、dump.rdb 名字
修改完畢以後,經過啓動命令指定不一樣的配置文件實現主從複製。

daemonize yes
port 6379
pidfile /var/run/redis_6379.pid
logfile "redis_6379.log"
dbfilename 6379.rdb

9.3 一主二從

默認狀況下,每臺Redis服務器都是主節點; 咱們通常狀況下只用配置從機就行了!可使用命令 SLAVEOF ip port來實現主從,可是這樣是臨時的。若是要永久生效就要在配置文件中配置,在REPLICATION下有個slaveof,配置主機 和 端口 就好,若是主有密碼就把密碼也配置上就行了。

從機只能讀不能寫

使用SLAVEOF命令進行主從設置的時候,若是中途從機斷了,而後從機再寫入數據,那麼再次進行slaveof的時候,從機獨有的數據會被清除,也就是從機的數據在進行主從的時候會同步和主機的數據徹底一致,很少很多。

9.4 哨兵模式

主從切換技術的方法是:當主服務器宕機後,須要手動把一臺從服務器切換爲主服務器,這就須要人工干預,費事費力,還會形成一段時間內服務不可用。這不是一種推薦的方式,更多時候,咱們優先考慮哨兵模式。Redis從2.8開始正式提供了Sentinel(哨兵) 架構來解決這個問題。
哨兵模式可以後臺監控主機是否故障,若是故障了根據投票數自動將從庫轉換爲主庫。哨兵模式是一種特殊的模式,首先Redis提供了哨兵的命令,哨兵是一個獨立的進程,做爲進程,它會獨立運行。其原理是哨兵經過發送命令,等待Redis服務器響應,從而監控運行的多個Redis實例。

img

這裏的哨兵有兩個做用

  • 經過發送命令,讓Redis服務器返回監控其運行狀態,包括主服務器和從服務器。
  • 當哨兵監測到master宕機,會自動將slave切換成master,而後經過發佈訂閱模式通知其餘的從服務器,修改配置文件,讓它們切換主機。

然而一個哨兵進程對Redis服務器進行監控,可能會出現問題,爲此,咱們可使用多個哨兵進行監控。各個哨兵之間還會進行監控,這樣就造成了多哨兵模式。

img

假設主服務器宕機,哨兵1先檢測到這個結果,系統並不會立刻進行failover過程,僅僅是哨兵1主觀的認爲主服務器不可用,這個現象成爲主觀下線。當後面的哨兵也檢測到主服務器不可用,而且數量達到必定值時,那麼哨兵之間就會進行一次投票,投票的結果由一個哨兵發起,進行failover[故障轉移]操做。切換成功後,就會經過發佈訂閱模式,讓各個哨兵把本身監控的從服務器實現切換主機,這個過程稱爲客觀下線

9.5 哨兵模式的設置

  • 配置哨兵配置文件 sentinel.conf

    # sentinel monitor 被監控的名稱 host port 1
    # 哨兵模式的最後的一個 1 的意思是哨兵判斷該節點多少次纔算死亡,就是幾個哨兵都獲得他死了纔算死(即幾個哨兵認爲他死了他纔算死)
    sentinel monitor myredis 127.0.0.1 6379 1
  • 指定本身的配置文件啓動

    ./bin/redis-sentinel myconfig/sentinel.conf

  • 觀察日誌便可

9.6 哨兵模式總結

優勢:
一、哨兵集羣,基於主從複製模式,全部的主從配置優勢,它全有
二、 主從能夠切換,故障能夠轉移,系統的可用性就會更好
三、哨兵模式就是主從模式的升級,手動到自動,更加健壯!
缺點:
一、Redis 很差啊在線擴容的,集羣容量一旦到達上限,在線擴容就十分麻煩!
二、實現哨兵模式的配置實際上是很麻煩的,裏面有不少選擇!

哨兵模式的所有配置!

# Example sentinel.conf
# 哨兵sentinel實例運行的端口 默認26379
port 26379
# 哨兵sentinel的工做目錄
dir /tmp
# 哨兵sentinel監控的redis主節點的 ip port
# master-name 能夠本身命名的主節點名字 只能由字母A-z、數字0-9 、這三個字符".-_"組成。
# quorum 配置多少個sentinel哨兵統一認爲master主節點失聯 那麼這時客觀上認爲主節點失聯了
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
sentinel monitor mymaster 127.0.0.1 6379 2
# 當在Redis實例中開啓了requirepass foobared 受權密碼 這樣全部鏈接Redis實例的客戶端都要提供
密碼
# 設置哨兵sentinel 鏈接主從的密碼 注意必須爲主從設置同樣的驗證密碼
# sentinel auth-pass <master-name> <password>
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd
# 指定多少毫秒以後 主節點沒有應答哨兵sentinel 此時 哨兵主觀上認爲主節點下線 默認30秒
# sentinel down-after-milliseconds <master-name> <milliseconds>
sentinel down-after-milliseconds mymaster 30000
# 這個配置項指定了在發生failover主備切換時最多能夠有多少個slave同時對新的master進行 同步,這個數字越小,完成failover所需的時間就越長,可是若是這個數字越大,就意味着越 多的slave由於replication而不可用。能夠經過將這個值設爲 1 來保證每次只有一個slave 處於不能處理命令請求的狀態。
# sentinel parallel-syncs <master-name> <numslaves>
sentinel parallel-syncs mymaster 1
# 故障轉移的超時時間 failover-timeout 能夠用在如下這些方面:
#1. 同一個sentinel對同一個master兩次failover之間的間隔時間。
#2. 當一個slave從一個錯誤的master那裏同步數據開始計算時間。直到slave被糾正爲向正確的master那裏同步數據時。
#3.當想要取消一個正在進行的failover所須要的時間。
#4.當進行failover時,配置全部slaves指向新的master所需的最大時間。不過,即便過了這個超時,
slaves依然會被正確配置爲指向master,可是就不按parallel-syncs所配置的規則來了
# 默認三分鐘
# sentinel failover-timeout <master-name> <milliseconds>
sentinel failover-timeout mymaster 180000
# SCRIPTS EXECUTION
#配置當某一事件發生時所須要執行的腳本,能夠經過腳原本通知管理員,例如當系統運行不正常時發郵件通知
相關人員。
#對於腳本的運行結果有如下規則:
#若腳本執行後返回1,那麼該腳本稍後將會被再次執行,重複次數目前默認爲10
#若腳本執行後返回2,或者比2更高的一個返回值,腳本將不會重複執行。
#若是腳本在執行過程當中因爲收到系統中斷信號被終止了,則同返回值爲1時的行爲相同。
#一個腳本的最大執行時間爲60s,若是超過這個時間,腳本將會被一個SIGKILL信號終止,以後從新執行。
#通知型腳本:當sentinel有任何警告級別的事件發生時(好比說redis實例的主觀失效和客觀失效等等),將會去調用這個腳本,這時這個腳本應該經過郵件,SMS等方式去通知系統管理員關於系統不正常運行的信息。調用該腳本時,將傳給腳本兩個參數,一個是事件的類型,一個是事件的描述。若是sentinel.conf配置文件中配置了這個腳本路徑,那麼必須保證這個腳本存在於這個路徑,而且是可執行的,不然sentinel沒法正常啓動成功。
#通知腳本
# shell編程
# sentinel notification-script <master-name> <script-path>
sentinel notification-script mymaster /var/redis/notify.sh
# 客戶端從新配置主節點參數腳本
# 當一個master因爲failover而發生改變時,這個腳本將會被調用,通知相關的客戶端關於master地址已
經發生改變的信息。
# 如下參數將會在調用腳本時傳給腳本:
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>
# 目前<state>老是「failover」,
# <role>是「leader」或者「observer」中的一個。
# 參數 from-ip, from-port, to-ip, to-port是用來和舊的master和新的master(即舊的slave)通訊的
# 這個腳本應該是通用的,能被屢次調用,不是針對性的。
# sentinel client-reconfig-script <master-name> <script-path>
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh # 通常都是由運維來配置!

10.Redis緩存擊穿、穿透和雪崩

這部份內容能夠看我以前的博客

相關文章
相關標籤/搜索