Redis入門

1、關於NOSQLjava

 要理解redis,首先得理解其歸屬於----NOSQL。linux

 一、什麼是NOSQLweb

 NoSQL,泛指非關係型的數據庫。隨着互聯網web2.0網站的興起,傳統的關係數據庫在應付web2.0網站,特別是超大規模和高併發的SNS類型的web2.0動態網站已經顯得力不從心,暴露了不少難以克服的問題,而非關係型的數據庫則因爲其自己的特色獲得了很是迅速的發展。NoSQL數據庫的產生就是爲了解決大規模數據集合多重數據種類帶來的挑戰,尤爲是大數據應用難題。                          面試

                                        -------百度百科redis

 二、從時代背景下考慮,爲何要使用NOSQL算法

  說到數據存儲,1)剛開始時單機的MySQL環境,由於大可能是靜態頁面,頁面訪問量並不大。隨着訪問量的增多,數據交互頻繁,2)便出現了Memcached+MySQL+垂直分佈。因爲緩存只能緩解讀壓力,當數據較多時,MySQL仍是存在寫的壓力,3)因而MySQL出現了主從讀寫分離。然後,高併發的環境,4)水平表分庫+水平拆分+MySQL集羣成爲了一種趨勢。可是這些仍是不能知足大數據時代須要,雖然關係型數據庫十分強大,可是它擴展性能較差。MySQL仍是存在一些瓶頸。而NoSQL數據庫的產生就是爲了解決大規模數據集合多重數據種類帶來的挑戰,尤爲是大數據應用難題,包括超大規模數據的存儲。spring

 三、區分傳統數據庫與非關係型數據庫特性數據庫

  MYSQL:原子性,一致性,獨立性,持久性編程

  NOSQL:強一致性,可用性,分區容錯性vim

2、什麼是Redis

      Redis(Remote Dictionary Server:遠程字典服務器)是一個key-value存儲系統。和Memcached相似,它支持存儲的value類型相對更多,包括string(字符串)、list(鏈表)、set(集合)、zset(sorted set --有序集合)和hash(哈希類型)。這些數據類型都支持push/pop、add/remove及取交集並集和差集及更豐富的操做,並且這些操做都是原子性的。在此基礎上,redis支持各類不一樣方式的排序。與memcached同樣,爲了保證效率,數據都是緩存在內存中。區別的是redis會週期性的把更新的數據寫入磁盤或者把修改操做寫入追加的記錄文件,而且在此基礎上實現了master-slave(主從)同步。          

                                                           --------百度百科

 3、Redis特性

 一、 Redis支持數據持久化。以往將數據存儲在內存中有個致命的問題就是當程序退出後內存中的數據會丟失,可是redis支持數據持久化,能將內存中的數據異步寫入硬盤中。

 二、支持複雜的數據類型。redis不只支持以鍵值對(key-value)形式的數據,還支持list、set、zset、hash等數據類型

4、如何開啓Redis

 一、下載相關jar包:下載

 二、解壓並安裝

  先利用tar命令進行解壓,進入解壓後的文件,執行make命令,執行完畢執行make install命令

 三、修改配置文件,啓動後臺運行功能 

  先拷貝其配置文件redis.conf,防止後期改動不影響原配置文件(最好將拷貝的文件放在新建的一個文件夾下),並在拷貝的文件中修改redis.conf文件將裏面的daemonize no 改爲 yes,讓服務在後臺啓動。

 三、啓動redis

[oracle@localhost myredis]$ redis-server redis.conf
2533:C 29 May 11:10:32.669 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
2533:C 29 May 11:10:32.671 # Redis version=4.0.9, bits=64, commit=00000000, modified=0, pid=2533, just started
2533:C 29 May 11:10:32.672 # Configuration loaded
[oracle@localhost myredis]$ redis-cli -p 6379
127.0.0.1:6379> ping
PONG

 四、驗證

  若是ping命令顯示pong表示成功

 五、關閉

127.0.0.1:6379> shutdown
(error) ERR Errors trying to SHUTDOWN. Check logs.
127.0.0.1:6379> exit

5、關於Redis初別


 一、Redis是單進程模型來處理客戶端的請求。對讀寫等事件的響應經過epoll函數進行包裝作到的。

 二、Redis總共有16個庫,默認下標從0開始,可經過select命令進行切換

 三、可經過dbsize查看當前數據庫key的數量

 四、清除數據庫的兩種方法:flushdb(清空當前數據庫);flushall(清空全部數據庫,慎用)

 五、默認端口爲6379

6、Redis相關數據類型命令

 1.String

Dbsize:查看當前數據庫的key的數量 set: 設置鍵值對,如 set jia haha 表示爲鍵jia設置值爲haha,注意:後面設置的可以覆蓋前面的值 Keys * :查看當前數據庫的全部的key get :精確獲取某個key ,若是沒有會報空 key k?:表示獲取以k開頭的鍵 flusthdb:清空當前數據庫 flushAll:清空全部的數據庫 exists key:判斷某個鍵是否存在 ,存在返回1,不存在返回0 如:exists k1 move key 下標值:將鍵轉移到某個數據庫:如 move k3 2 表示將k3轉移到3號庫中 Expire key time:表示爲指定鍵設置存活時間(防止某個不經常使用的數據常據內存)如:expire k2 10 表示爲k2設置存活時間爲10秒 ttl:表示查看某個值的存活時間:-1表示永遠不消失-2 表示已經消失,過時,沒法訪問 del key:表示刪除指定的key(redis語句執行成功爲1,失敗爲0) type key:表示查看指定key的數據類型 clear:(不屬於redis命令)用於清除以上全部的編輯數據記錄,用於長時間的編輯 append key value:用於在原有的字符串中疊加數據 Strlen key :查看指定的key的長度 incr key :每執行一次,key值增長1(注意:該key值必須 爲int型) decr: 與上相反 incrby key number:每執行一次 ,爲指定的key增加指定的值 Decrby key number:與上相反 getrange key number number:得到指定key的指定範圍內的值,如getrange k1 0 3 setrange key number number: 將指定key的指定範圍位置的覆蓋成指定的內容 Setex key number :設置指定的key存活時間 ,如 setex k1 10 v4表示建立k1而且設置k1存活時間爲10秒 Setnx key value:先判斷key是否存在,存在此語句無效;不存在就建立ky並賦值 mset key value key value..:給多個key賦值 mget key key key:同時查看多個key的值 Msetnx:同時設置一個或者多個key-value,當且僅當全部給定key都不存在,即便只有一個給定key已經存在,msetnx也會拒絕執行全部給定key的設置操做

 二、List

lpush list value value..:建立一個list的集合並向他左側壓入值,如:lpuh list01 1 2 3 4 5 lrange list01 0 -1 :(表示查看01集合,0表示從首位置查,-1表示到結尾 ) Rpush list value value:建立一個list的集合並向他右側壓入值 lrange list number number:從左側遍歷指定範圍的list集合 Lpop list:左出棧第一個值,出棧後集合不存在該值 rpop list:右出棧第一個值,出棧後集合不存在該值 lindex number 集合:從左開始遍歷並得到指定下表的值,沒有返回空 llen list:查看指定list長度 Lrem list number value:刪除指定集合的指定數量的值:如lrem list01 2 3 :表示刪除list01集合中的兩個3 Ltrim list number number:截取指定集合的指定範圍內的值,原來的集合被截取的集合所取代 Rpoplpush list list:將第一個集合的最右的一個數壓入第二個集合的最左邊 Lset key index value:從左開始修改指定位置的值 LINSERT list after value01 value02:在value01前插入指定的內容value02 Set:(方法與list類似,只是不容許有重複的值出現) Lset list number value:將指定集合的指定位置修改成指定值 Linsert list before value:在指定集合的指定值以前插入指定值

 三、Set

注意:向set集合添加的數據都是非重複的
Sadd set(集合) value:建立集合,壓入值 如:sadd set01 1 2 3 Smembers set(集合):遍歷集合的元素並返回出來 Scard set(集合): 獲取集合中的有多少個元素 srem set(集合) value:刪除集合中的指定元素 srandmember set(集合) number:在指定集合中隨機抽取指定個數的元素 Spop set(集合):隨機出棧,個數爲一 Smove set(集合) set(集合) value:將第一個集合中的制定個值轉移到第二個集合中 Sdiff set(集合) set(集合) :取在第一個集合裏面而不在後面任何一個sest裏面的項 Sinter set(集合)set(集合) :去集合的交集 Sunion set(集合)set(集合 ):去集合的並集

 四、Hash

hash 是個鍵值對的模式,其中k-v中的v是個鍵值對 hset:建立一個hashset並賦值 如:hset user id 11 這裏的v爲 id-11,其中k爲id,value 爲11 Hget: 得到hashset的值,如:hget user id Hmset::給hashset多重賦值,如:hmset customer id 11 name lisi age 26 Hmget:查看hashset的多個值,如:hmget customer id name age Hgetall : 獲取一個key中全部值(鍵值對) 如:hgetall customer Hdel :刪除指定key中的指定值(鍵值對)如:hdel user name Hlen : 查看指定鍵的值的長度,如:hlen user Hexists : 查看指定鍵是否存在指定值 如:hexists user name Hkeys :查看指定鍵中的全部值(鍵值對)中的鍵,如:hkeys user Hvals :查看指定鍵中的全部值(鍵值對)中的值,如:hvals user Hincrby :每執行一次爲指定鍵中的值中指定的值增長指定的整數 如:hincrby user age 2 每執行一次,年齡增長2 Hincrbyfloat:每執行一次爲指定鍵中的值中指定的值增長指定的小數 Hsetnx: 若不存在,爲指定的鍵設置指定的值,不然執行失敗

 五、Zset

有序set集合,在set的基礎上,加上了一個score的值,根據score值的大小達到排序 Zadd:將一個或多個 member 元素及其 score 值加入到有序集 key 當中。若是某個 member 已是有序集的成員,那麼更新這個 member 的 score 值,並經過從新插入這個 member 元素,來保證該 member 在正確的位置上。score 值能夠是整數值或雙精度浮點數。若是 key 不存在,則建立一個空的有序集並執行 ZADD 操做。當 key 存在但不是有序集類型時,返回一個錯誤 如:zadd zset01 60 v1 70 v2 80 v3 90 v4 100 v5 表示建立zset01集合,並壓入了v1 v2 v3 v4 v5鍵 Zrange :查看指定集合中的鍵 如 zrange zset01 0 -1 補充:zrange zset01 0 -1 withscores 返回攜帶分數的鍵 Zrangebyscore key :查找指定分數範圍內的鍵 如:zrangebyscore zset01 60 90 補充:分數中間加個「(」表示不包含,如:zrangebyscore zset01 60 (90 表示大於等於60小於90 zrangebyscore zset01 (60 (90 表示大於60小於90 Zrangebyscore zset01 60 90 limit 2 2 :表示在得到60 到90範圍內的數中從下表值爲2開始抽取兩個數 Zrem :刪除指定的key 如:zrem zset01 v5 Zcard:統計key的個數 如:zcard zset01 Zcount:統計指定分數範圍內的key個數 如:zcount zset01 60 80 Zrank:返回有序集合key中成員member的排名 Zscore:返回指定集合中的key的指定分數 如:zscore zset01 v4 Zrevrank:將指定的key反轉,並返回反轉後的下標值 如:zrevrank zset v4 Zrevrange:將指定的集合中的key反轉,並將它們遍歷出來:如 :zrevrange zset01 0 -1 zrevrangeByScore:將指定的集合的分數反轉:如 zrevrangeByScore zset01 90 60

7、Redis之配置文件詳解

 一、Units單位

  1)配置大小單位,開頭定義了一些基本的度量單位,支持bytes,不支持bit

  2)對大小寫不敏感

# Note on units: when memory size is needed, it is possible to specify
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes

 二、綁定本地地址與開啓自摸保護機制

  Redis綁定了本定地址:127.0.0.1而且開啓了保護機制,此保護機制可防止遠程客戶端訪問,若是想要取消該機制須要將默認值改成no

bind 127.0.0.1
protected-mode yes

 三、默認端口號爲6379

# Accept connections on the specified port, default is 6379 (IANA #815344).
# If port 0 is specified Redis will not listen on a TCP socket.
port 6379

 四、在高併發環境下你須要一個高backlog值來避免慢客戶端鏈接問題。注意Linux內核默默地將這個值減少

# TCP listen() backlog.
#
# In high requests-per-second environments you need an high backlog in order
# to avoid slow clients connections issues. Note that the Linux kernel
# will silently truncate it to the value of /proc/sys/net/core/somaxconn so
# make sure to raise both the value of somaxconn and tcp_max_syn_backlog
# in order to get the desired effect.
tcp-backlog 511

 五、Redis默認不是以守護進程的方式運行,能夠經過該配置項修改,使用yes啓用守護進程

################################# GENERAL #####################################

# By default Redis does not run as a daemon. Use 'yes' if you need it.
# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
daemonize no

 六、Redis以守護進程方式運行時,Redis默認會把pid寫入/var/run/redis——6379.pid文件,能夠經過pidfile指定

# If a pid file is specified, Redis writes it where specified at startup
# and removes it at exit.
#
# When the server runs non daemonized, no pid file is created if none is
# specified in the configuration. When the server is daemonized, the pid file
# is used even if not specified, defaulting to "/var/run/redis.pid".
#
# Creating a pid file is best effort: if Redis is not able to create it
# nothing bad happens, the server will start and run normally.
pidfile /var/run/redis_6379.pid

 七、指定日誌記錄級別,Redis總共支持四個級別:debug、verbose、notice、warning,默認爲notice

# 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 notic

 八、日誌記錄方式,默認爲標準輸出,若是配置Redis爲守護進程方式運行,而這裏又配置爲日誌記錄方式爲標準輸出,則日誌將會發送給/dev/null

# Specify the log file name. Also the empty string can be used to force
# Redis to log on the standard output. Note that if you use standard
# output for logging but daemonize, logs will be sent to /dev/null
logfile ""

 九、指定數據庫個數

databases 16

十、Redis快照機制。

  指定在多長時間內,有多少次更新操做,在指定的時間間隔內將內存中的數據集快照寫入磁盤,能夠多個條件配合(重點掌握),其三個條件能觸發同步,單位是秒即每1分鐘寫1萬次或者5分鐘寫10次或者沒15分鐘寫1次均可觸發同步

#   save ""

save 900 1
save 300 10
save 60 10000

 十一、序列化的時候是否中止寫操做

stop-writes-on-bgsave-error yes

 十二、指定存儲至本地數據庫時是否壓縮數據,默認爲yes,Redis採用LZF壓縮,若是爲了節省CPU時間,能夠關閉該選項,但會致使數據庫文件變的巨大

rdbcompression yes

 1三、經過消耗CPU資源對rdb數據進行校驗,默認爲yes

rdbchecksum yes

 1四、每次觸發同步條件時所生成的文件名

# The filename where to dump the DB
dbfilename dump.rdb

 1五、設置當本機爲slav服務時,設置master服務的IP地址及端口,在Redis啓動時,它會自動從master進行數據同步

    當master服務設置了密碼保護時,slav服務鏈接master的密碼

# slaveof <masterip> <masterport>
# masterauth <master-password>

 1七、當 slaves master 失去聯繫或者 複製數據工做仍然在進行。這個適合slave 會有兩種選擇
   當配置 yes(默認的) 意味着slave 會反饋 客戶端的請求
   當配置 no 客戶端會反饋一個error "SYNC with master in progress" ,若是master 沒法鏈接上,則會報"MASTERDOWN  Link with MASTER is down and slave-serve-  stale-data is set to 'no'."

slave-serve-stale-data yes

 1八、保護slave ,不讓它暴露在不受信任的客戶端上。通常用來當作保護層,尤爲在果斷的client的時候。

  當配置yes(默認)的時候,意味着客戶端無法給slave 節點寫入數據,一寫就會報錯"READONLY You can't write against a read only slave."

  當配置成 no 的時候,任何客戶端均可以寫入

slave-read-only yes

 十一、從主機的優先級,若是當主主機掛了的時候,將從從主機中選取一個做爲其餘從機的主,首先優先級的數字最低的將成爲主,0是一個特殊的級別,0將          永遠不會成爲主。默認值是100.

slave-priority 100

 十二、設置Redis鏈接密碼,若是配置了鏈接密碼,客戶端在鏈接Redis時須要經過AUTH <password>命令提供密碼,默認關閉

################################## SECURITY ###################################

# Require clients to issue AUTH <PASSWORD> before processing any other
# commands.  This might be useful in environments in which you do not trust
# others with access to the host running redis-server.
#
# This should stay commented out for backward compatibility and because most
# people do not need auth (e.g. they run their own servers).
#
# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
#
# requirepass foobared

 1三、設置同一時間最大客戶端鏈接數,默認無限制,Redis能夠同時打開的客戶端鏈接數爲Redis進程能夠打開的最大文件描述符數,若是設置 maxclients 0,表示不做          限制。當客戶端鏈接數到達限制時,Redis會關閉新的鏈接並向客戶端返回max number of clients reached錯誤信息

# maxclients 10000

 1四、指定Redis最大內存限制,Redis在啓動時會把數據加載到內存中,達到最大內存後,Redis會先嚐試清除已到期或即將到期的Key,當此方法處理 後,仍然到達最          大內存設置,將沒法再進行寫入操做,但仍然能夠進行讀取操做。Redis新的vm機制,會把Key存放內存,Value會存放在swap區

# In short... if you have slaves attached it is suggested that you set a lower
# limit for maxmemory so that there is some free RAM on the system for slave
# output buffers (but this is not needed if the policy is 'noeviction').
#
# maxmemory <bytes>

 1五、這是redis4.0新增的功能默認狀況是以阻塞方式刪除對象,若是想手動更改,代替以非阻塞的方式釋放內存,好比斷開連接已被調用,使用如下配置指令

lazyfree-lazy-eviction no       
lazyfree-lazy-expire no  
lazyfree-lazy-server-del no
slave-lazy-flush no

 1六、指定是否在每次更新操做後進行日誌記錄,Redis在默認狀況下是異步的把數據寫入磁盤,若是不開啓,可能會在斷電時致使一段時間內的數據丟失。由於 redis自己同步數據文件是按上面save條件來同步的,因此有的數據會         在一段時間內只存在於內存中。默認爲no;指定更新日誌文件名,默認爲appendonly.aof

appendonly no
appendfilename "appendonly.aof"

 1七、redis內存淘汰策略

# is reached. You can select among five behaviors:
#
# volatile-lru -> Evict using approximated LRU among the keys with an expire set.
# allkeys-lru -> Evict any key using approximated LRU.
# volatile-lfu -> Evict using approximated LFU among the keys with an expire set.
# allkeys-lfu -> Evict any key using approximated LFU.
# volatile-random -> Remove a random key among the ones with an expire set.
# allkeys-random -> Remove a random key, any key.
# volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
# noeviction -> Don't evict anything, just return an error on write operations.
#
# LRU means Least Recently Used
# LFU means Least Frequently Used
#
# Both LRU, LFU and volatile-ttl are implemented using approximated
# randomized algorithms.
#
# Note: with any of the above policies, Redis will return an error on write
#       operations, when there are no suitable keys for eviction.
#
#       At the date of writing these commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# The default is:
#
# maxmemory-policy noeviction

8、Redis之安全機制

   redis默認沒有設置密碼,每一個庫的密碼保持一致。之因此這樣是由於redis設計的初衷是在linux環境下運行,其環境是安全的,而且它最初只是用來作緩存的。可是並不表明它不能設置密碼。

127.0.0.1:6379> config get requirepass               #查看當前密碼
1) "requirepass"
2) ""                               #顯示空說明默認沒有  
127.0.0.1:6379> config set requirepass "123456"    #設置密碼
OK
127.0.0.1:6379> ping                     #驗證
(error) NOAUTH Authentication required.         #顯示此信息說明密碼設置成功
127.0.0.1:6379> auth 123456                 #用密碼登陸
OK
127.0.0.1:6379> ping                      #驗證
PONG

 9、Redis以內存淘汰策略

  Redis內存淘汰指的是用戶存儲的一些鍵被能夠被Redis主動地從實例中刪除,從而產生讀miss的狀況。此是Redis一個重要特徵。其中:

  1) volatile-lru :使用LRU算法移除Key,只針對設置可過時時間的鍵

  2) allKeys-lru :使用LRU算法移除Key

  3) volatile-random:在過時集合中移除隨機的key,只針對設置了過時時間鍵

  4) allKeys-random:移除隨機的key

  5) volatile-ttl:移除那些TTL(time-to-live)值最小的Key,即那些最近要過時的key

  6) noeviction:不進行移除。針對寫操做,只是返回錯誤信息

  7) volatile-lfu:使用LFU移除Key,只針對設置可過時時間的鍵

    8) allKeys-lfu :使用LFU算法移除Key

     默認淘汰機制是不過時,即noeivtion,配置文件以下:

# The default is:
#
# maxmemory-policy noeviction

10、關於SNAPSHOTTING快照(持久化) 

   Redis持久化包含RDB和AOF 

     一、RDB:在指定的時間間隔內將內存中的數據集快照寫入磁盤,配置文件每次當數據1分鐘改了1萬次,或者5分鐘改了10次或者15分鐘改了1此就會觸發保存,將信息保存到dump.rdb文件(注意:此文件在哪一個目錄下啓動redis,就在哪一個目錄下生成dump.rdb文件)

  二、RDB之模仿事故配置文件備份

  環境:由於手誤清空了數據庫,模擬數據恢復,注意真實環境中數據讀寫和數據備份不在同一臺機器,這裏是在同臺機器上演示。

   1) 先將配置文件中其中一個觸發保存條件將5分鐘內更改十次換成1分鐘更改10

save 900 1
save 300 10
save 60 10

  2) 開啓兩個終端,其中一個用來啓動redis,另一個用來查看當前目錄下生成的dump.rdb文件

   3) redis當前數據庫中建立11個數據,且這些操做須要在1分鐘內完成,此時刷新查看當前目錄就能看到新生成的dump.rdb

-rw-r--r-- 1 root root   186 5月  28 21:53 dump.rdb

   4) 拷貝dump.rdb文件,命名爲copydum.rdb(命名除了「dump.rdb」以外,隨意)

-rw-r--r-- 1 root root   186 5月  29 13:56 copydump.rdb
-rw-r--r-- 1 root root   186 5月  28 21:53 dump.rdb

   5) 清空數據庫,模仿事務,並關閉數據庫

   6) 再次啓動redis,發現數據庫中的數據是空的,緣由是當flushall或者shutdown關閉數據庫時都會會進行數據保存,生成新的dum.rdb來替換新的dum.rdb,               此時的dum.rdb是保存的沒有數據的文件,因此當重啓數據庫,從dum.rdb中拿取的數據時空的。

     7) 恢復數據:刪除dum.rdb,將原先拷貝的文件進行拷貝,而且命名爲dum.rdb(由於源碼解釋說數據庫默認從dum.rdb中抽取數據)

    8) 此時重啓動redis,數據便恢復了。若是想將數據立刻備份,可使用save命令

  如何關閉快照RDB功能:redis -cli config set save 「」

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

  四、開啓AOF功能

  redis默認是關閉aof功能的,由於邊寫邊拷貝不止消耗內存,也消耗cpu。可是卻保證了數據的完整性。可更改配置文件原配置將no改成yes

appendonly yes

  五、模擬事故之AOF數據恢復(正常恢復)  

  1) 修改配置文件,開啓aof配置生效

  2) 打開數據庫,此時在目錄中會生成appendonly.aof文件

-rw-r--r-- 1 root root  1802 5月  28 10:29 appendonly.aof

  3) 新建幾條數據,並flushall,shutdown數據庫,此時目錄中會生成dum.rdb文件,咱們要刪除它,防止其影響數據備份,由於它是份空的文件

  4) 編輯appendonly.aof文件,能夠看到全部新建的數據記錄都寫在了此文件中,固然最後的「flushall」語句也記錄在裏面了,要將此舉刪除,不然當數據庫             調用此日誌進行數據備時又會執行此句將數據刪除

  5) 重啓數據庫,數據從新備份了

  擴展:

  環境:模擬事故之數據書寫通常忽然電源關閉致使記錄了錯誤語法的日誌(異常恢復)

  分析:當appendonly.aof中若是含有錯誤語法的語句,數據庫將不能啓動,在剛生成的appendonly.aof文件中隨便添加幾個字符(模擬數據寫到一半由於           網絡延時或 者電源關閉致使數據備份時記錄了錯誤語法的語句)

        1) 關閉數據庫,此時會生成dum.rdb文件,重啓數據庫,數據庫將不能啓動,說明當aof開啓時而且appendonly.aof和dump.rdb文件同時存在時,會默認先執行                      appendonly.aof文件。

  2) 此時需修復appendonly.aof文件。在當前目錄(啓動redis目錄)下,有redis.check-aof命令

  3) 執行redis-check-aof,它會修復appendonly.aof備份文件

           Redis-check-aof  --fix appendonly.aof

  4) 重啓數據,數據庫啓動成功,數據成功備份

  六、aof數據追加(Appendfsync)分爲Always(每次數據變化就追加記錄,不推薦,性能不高,默認關閉)Everysec(每秒追加,異步操做,推薦,默認開啓)

# appendfsync always
appendfsync everysec

  七、AOF之重寫
   分析:AOF採用文件追加方式,文件會愈來愈大爲避免出現此種狀況,新增了重寫機制,當AOF文件的大小超過所設定的閾值時,Redis就會啓動                        AOF文件的內容壓縮,只保留能夠恢復數據的最小指令集.可使用命令bgrewriteaof

         原理:

  AOF文件持續增加而過大時,會fork出一條新進程來將文件重寫(也是先寫臨時文件最後再rename),遍歷新進程的內存中數據,每條記錄有一條的Set語           句。重寫aof文件的操做,並無讀取舊的aof文件,而是將整個內存中的數據庫內容用命令的方式重寫了一個新的aof文件,這點和快照有點相似觸發機     制:Redis會記錄上次重寫時的AOF大小,默認配置是當AOF文件大小是上次rewrite後大小的一倍且文件大於64M時觸發

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

  八、RDB和AOF優缺點

  RDB:

  優勢:RDB 是一個很是緊湊(compact)的文件,它保存了 Redis 在某個時間點上的數據集。 這種文件很是適合用於進行備份。RDB 能夠最大化 Redis 的性能:父進程在保存 RDB 文件時惟一要作的就是 fork 出一個子進程,而後這個子進程就會處理接下來的全部保存工做,父進程無須執行任何磁盤 I/O 操做。RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快。

  缺點:若是注重數據完整性,若是機器發生故障停機, 有可能會丟失好幾分鐘的數據。

  AOF:

  優勢:使用 AOF 持久化會讓 Redis 變得很是耐久,能保證數據的完整性。AOF 的默認策略爲每秒鐘 fsync 一次,在這種配置下,Redis 仍然能夠保持良好的性能,而且就算髮生故障停機,也最多隻會丟失一秒鐘的數據。

  缺點:對於相同的數據集來講,AOF 文件的體積一般要大於 RDB 文件的體積

11、Redis之事務

  一、相關命令

MULTI                  #標記一個事務的開始
WATCH Key         #監視一個或多個鍵,若在事務執行以前被監視的key改動,那麼事務將被打斷
EXEC            #執行全部事務快內命令
UNWATCH          #取消監視
DISCARD          #取消事務,放棄執行事務快內命令
 

   二、命令使用

開啓事務
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> EXEC
1) OK
2) OK
127.0.0.1:6379> KEYS *
1) "k3"
2) "k2"
3) "k1"

放棄事務
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k1 bb
QUEUED
127.0.0.1:6379> set k2 ss
QUEUED
127.0.0.1:6379> DISCARD
OK
127.0.0.1:6379> KEYS *
1) "k3"
2) "k2"
3) "k1"

一條語法出錯,其餘沒法執行
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> setget k1 
(error) ERR unknown command 'setget'
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> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> KEYS *
(empty list or set)

冤頭債主(哪條執行不合乎語意,但不是語法錯誤,能入列,不報錯,則只有它不執行)
K1爲字符串
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> INCR k1
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> EXEC
1) (error) ERR value is not an integer or out of range
2) OK
127.0.0.1:6379> get k4
"v4"
127.0.0.1:6379> KEYS *
1) "k1"
2) "k4"

  三、悲觀鎖與樂觀鎖

  悲觀所:預測操做時會出錯,將整張表給鎖起來,防止其餘操做者操做此表,直到解鎖。併發性差,一致性好

  樂觀鎖:不鎖整張表,在每條記錄下加個version版本號(數字),在操做該記錄後將版本號增長1,當其餘操做者在同一時間操做同條數據時,會由於版本號的不一樣致使提交失敗。併發性高,一致性也高

  四、事務監控

  在執行執行事務以前監控變量,在多併發狀況下能夠監控變量,事務會由於版本號的不一樣致使事務提交失敗

  用法:

  第一個客戶端:設置餘額和消費

127.0.0.1:6379> clear
127.0.0.1:6379> set balance 100
OK
127.0.0.1:6379> ste dept 0
(error) ERR unknown command 'ste'
127.0.0.1:6379> set dept 0
OK
127.0.0.1:6379> WATCH balance
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> DECRBY balance 20
QUEUED
127.0.0.1:6379> INCRBY dept 20
QUEUED

  第二個客戶端

127.0.0.1:6379> get balance
"80"
127.0.0.1:6379> set balance 800    #將balance變爲800
OK
127.0.0.1:6379> 

  第一個客戶端

127.0.0.1:6379> exec
(nil)                     #由於中途有人更改過值,事務執行失敗

  此時客戶端一隻能執行unwatch命令後從新監視balance對數據進行操做

12、Redis之發佈訂閱

  一、發佈訂閱是指一個端口號監聽到另外一個端口號後(如同訂閱),被監聽的機器發佈消息,訂閱的能及時接收到發佈的消息

  二、經常使用命令

PSUBSCRIBE pattern [pattern..]                 #訂閱一個或多個符合給定模式的頻道
PUBSUB subcommand [argument [argument..]]    #查看訂閱與發佈系統狀態
PUBLISH channel message               #將信息發送到指定的頻道
PUNSUBSCRIBE [pattern[pattern..]]         #退訂全部給定模式的頻道
SUBSCRIBE channel [channel..]           #訂閱給定的一個或多個頻道的信息
UNSUBBSCRIBE [channel[channel]]          #指退訂給的頻道

  三、用法

   1) 同時開啓兩個客戶端。其中一個客戶端用來訂閱消息,另外一個用來發送消息,訂閱消息的客戶端可以接受發送端發過的消息

   2)  訂閱端先訂閱消息

127.0.0.1:6379> SUBSCRIBE c1 c2 c3 #模擬訂閱了c1,c2,c3頻道
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "c1"
3) (integer) 1
1) "subscribe"
2) "c2"
3) (integer) 2
1) "subscribe"
2) "c3"
3) (integer) 3

  3)發送端發送消息

127.0.0.1:6379> PUBLISH c2 hello-redis   
(integer) 1

  4)此時訂閱端能接收到發送端發送的消息

2) "c2"
3) "hello-redis"

  擴展,能夠用「*」模糊匹配訂閱多個消息

  如訂閱端

  127.0.0.1:6379>PSUBSCIBE new*

  發送端

127.0.0.1:6379> PUBLISH new1 redis2015
(integer) 1

十3、Redis的複製機制

 一、什麼是redis的複製

   也就是咱們所說的主從複製,主機數據更新後根據配置和策略,自動同步到備機的master/slaver機制,Master以寫爲主,Slave以讀爲主

 二、能幹嗎?

 讀寫分離 容災恢復

  用法

  1) 同時開啓三個終端,而且分別命名爲7980,81端口

  2) 複製redis的配置文件,而且命名爲redis79.confredis80.confredis;81.conf

  3) 更改三個配置文件的內容

    3.1)pidfile /var/run/redis6379.pid       //命名根據指定的端口號爲準,如80端口就是  redis_6380.pid

    3.2)port 6379                    //命名根據指定的端口號爲準,如80端口就是6380

    3.3)logfile "6379.log"

    3.4)dbfilename dump6379.rdb

  4) 重用三招

   4.1)一主二僕

  1.啓動三臺終端的redis,注意啓動時的端口號
  redis-server redis80.conf   //如啓動80端口的客戶端
  redis-cli -p 6380

     2.三臺redis同時輸入 info replication,此時三者角色的身份都是master

       3.若是要想玩主從複製,將79端口定義爲主(master)8081端口的定義爲從(slave),主具備讀寫權限,而從只有讀權限,每次主更新了內容,從都能遍歷到主更新的內容

     4.設置

   79端口:不作處理  

  80端口:SLAVEOF 127.0.0.1 6379  //監聽79端口

   81端口:SLAVEOF 127.0.0.1 6379  //監聽79端口

  此時79端口身份仍是master8081端口身份變成了slave

  每次79端口set一個新值,8081端口均可以get這個新值

  可是8081端口不能set新值

   注意:每次與master斷開以後,都須要從新鏈接而後監聽主機端口,除非你配置進redis.conf文件,可是主機斷開沒事,只要從機沒關,主機從新登陸後從機繼續監聽主機

   4.2)薪火相傳
  一主二僕的缺點是當主機掛時,其餘從機就只能等待主機從新鏈接上才能運做
分析:將79端口做爲主機,80端口仍是監聽79端口,可是81端口再也不監聽79端口,而是80端口,此時80端口角色相對於79端口是slave,相對於81端口倒是master

      雖然81端口監聽的是80端口,可是依然能接收到來自79端口新存進的值

      若是79端口關閉,由80端口做爲新主機(master)

      79端口:不作處理

      80端口:SLAVEOF 127.0.0.1 6379

      81端口:SLAVEOF 127.0.0.1 6380

  上一個Slave能夠是下一個slaveMasterSlave一樣能夠接收其餘

  slaves的鏈接和同步請求,那麼該slave做爲了鏈條中下一個的master,

  能夠有效減輕master的寫壓力

  中途變動轉向:會清除以前的數據,從新創建拷貝最新的

  4.3)反客爲主

   一、使當前數據庫中止與其餘數據庫的同步,轉成主數據庫(也就是說此時環境是80端口和81端口同時監聽79端口,當主機79端口斷開後,80端口反客爲主,將本身身份設置爲主機替代79端口)

   二、作法
  環境開始前:

      79端口:不作處理

      80端口:SLAVEOF 127.0.0.1 6379

      81端口:SLAVEOF 127.0.0.1 6379

      環境開始:

      79端口:shutdown

      80端口:SLAVEOF no one

      81端口:SLAVEOF 127.0.0.1 6380   //此時81端口有兩個選擇,1選擇等待主機號開    啓,2是從新選擇主機號。此時80端口有寫的    權限,80端口更新值後81端口能夠遍歷到

十4、redis的哨兵模式(sentinel)

  一、分析:反客爲主確實能解決主機掛斷後替補問題,可是採用手動方式仍是顯得有點笨重不方便。而哨兵模式即是自動方式。它是反客爲主的自動版,可以後臺監控主機是否故障,若是故障了根據投票數自動將從庫轉換爲主庫。

  二、用法

  1)環境:80端口和81端口同時監聽79端口

  2)在啓動redis目錄下新建sentinel.conf文件,名字毫不能錯

  [root@localhost myredis]# touch sentinel.conf

  3) 編輯sentinel.conf文件,配置如今主機爲79端口號,當主機端口端口時採用 投票方式從新選取主機

sentinel monitor host6379 127.0.0.1 6379 1   #最後一個數字1,表示主機掛掉後salve投票看讓誰接替成爲主機,得票數多少後成爲主機

  4) 開啓哨兵模式

 [root@localhost myredis]# redis-sentinel sentinel.conf

  5) 此時關閉79端口號,哨兵會監控到79掛失,從新選取新的主機號,選取方式採 用投票方式,不能認爲控制

 

  此時能夠看到81端口號被選舉爲主機,80端口再也不監控79端口(即便79端口重啓),監聽81端口號

十5、Jedis

 Jedis是java原生操做redis,經過java代碼操做redis數據庫中的數據

 一、部署環境

  1)先引入maven依賴

<!--引入java訪問redis客戶端:Jedis-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.7.3</version>
</dependency>

  2) 編寫測試類,測試連通性

Jedis jedis=new Jedis("127.0.0.1",6379);
System.out.println(jedis.ping());

  若不顯示PONG,錯誤分析以下

  1. Connection refused

     分析緣由:這是由於redis默認啓動的是保護措施,綁定了127.0.0.1,而且開啓了保護措施

     1)編輯redis.Conf文件Vim redis.conf

     2)註銷掉127.0.0.1    #127.0.0.1

       protected-mode yes改成no
    二、Connect time out

    分析緣由:鏈接時間超時,是由於防火牆沒開放此端口號,解決方式要麼關閉防火牆(不推薦),要麼在iptables中配置6379端口號,並重啓防火牆

     1) vim /etc/sysconfig/iptables

    #reids
    -A INPUT -p TCP --dport 6379 -j ACCEPT
  2. [root@localhost myredis]#service iptables restart

 二、測試Jedis API

 測試1、

 Jedis jedis=new Jedis("127.0.0.1",6379);
         jedis.set("k1","v1");
         jedis.set("k2","v2");
         jedis.set("k3","v3");
         System.out.println(jedis.get("k3"));
         Set<String> set=jedis.keys("*");
         System.out.println(set.size());

 測試2、

public class Test02{
  public static void main(String[] args){

     Jedis jedis new Jedis("127.0.0.1",6379);
     //key
     Set<String> keys = jedis.keys("*");
     for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
       String key = (String) iterator.next();
       System.out.println(key);
     }
     System.out.println("jedis.exists====>"+jedis.exists("k2"));
     System.out.println(jedis.ttl("k1"));
     //String
     //jedis.append("k1","myreids");
     System.out.println(jedis.get("k1"));
     jedis.set("k4","k4_redis");
     System.out.println("----------------------------------------");
     jedis.mset("str1","v1","str2","v2","str3","v3");
     System.out.println(jedis.mget("str1","str2","str3"));
     //list
     System.out.println("----------------------------------------");
     //jedis.lpush("mylist","v1","v2","v3","v4","v5");
     List<String> list = jedis.lrange("mylist",0,-1);
     for (String element : list) {
       System.out.println(element);
     }
     //set
     jedis.sadd("orders","jd001");
     jedis.sadd("orders","jd002");
     jedis.sadd("orders","jd003");
     Set<String> set1 = jedis.smembers("orders");
     for (Iterator iterator = set1.iterator(); iterator.hasNext();) {
       String string = (String) iterator.next();
       System.out.println(string);
     }
     jedis.srem("orders","jd002");
     System.out.println(jedis.smembers("orders").size());
     //hash
     jedis.hset("hash1","userName","lisi");
     System.out.println(jedis.hget("hash1","userName"));
     Map<String,String> map = new HashMap<String,String>();
     map.put("telphone","13811814763");
     map.put("address","atguigu");
     map.put("email","abc@163.com");
     jedis.hmset("hash2",map);
     List<String> result = jedis.hmget("hash2", "telphone","email");
     for (String element : result) {
       System.out.println(element);
     }
     //zset
     jedis.zadd("zset01",60d,"v1");
     jedis.zadd("zset01",70d,"v2");
     jedis.zadd("zset01",80d,"v3");
     jedis.zadd("zset01",90d,"v4");
     
     Set<String> s1 = jedis.zrange("zset01",0,-1);
     for (Iterator iterator = s1.iterator(); iterator.hasNext();) {
       String string = (String) iterator.next();
       System.out.println(string);
     }       
  }
}

  測試事務

 Jedis jedis=new Jedis("127.0.0.1",6379);
          //開啓事務
          Transaction transaction=jedis.multi();
          transaction.set("k4", "v44");
          transaction.set("k5", "v55");
        //  transaction.exec();
          //放棄事務
          transaction.discard();

  測試事務監控

public boolean transMethod() throws InterruptedException{
        Jedis jedis=new Jedis("127.0.0.1",6379);
        int balance;//餘額
        int debt;//欠額
        int amtToSubtract=10;//實刷額度
        jedis.watch("balance");//加入監控
        //模擬異常時,將屏蔽的代碼打開
        //Thread.sleep(7000); //模擬網絡擁堵
        //在擁堵過時間,在redis客戶端將balance的值設置爲7,模擬兩人同時操做一個資源
        balance=Integer.parseInt(jedis.get("balance"));
        if(balance<amtToSubtract){
            jedis.unwatch();
            System.out.println("modify");//已經有人修改
            return false;//提交失敗,必須得從新得到版本號,從新設置事務而且進行提交
        }else{
            System.out.println("**************transaction");
            Transaction transaction=jedis.multi();
            transaction.decrBy("balance", amtToSubtract);//消費
            transaction.incrBy("debt", amtToSubtract);//欠下
            transaction.exec();//提交,進行批處理
            balance=Integer.parseInt(jedis.get("balance"));
            debt=Integer.parseInt(jedis.get("debt"));
            System.out.println("******"+balance);
            System.out.println("******"+debt);
            return true;
        }
        
    }
   /**
    * 通俗的講,watch命令就是標記一個鍵,若是標記了一個鍵
    * 在提交事務前若是該鍵被別人標記修改過,那事務就會失敗,這種狀況一般能夠再程序中
    * 從新嘗試
    * 首先標記了鍵balance,而後檢查餘額是否只夠,不足取消標記,並不作扣減;
    * 足夠的話,就啓動事務進行更行操做
    * 若是在此期間 鍵balance被其餘人修改過,那麼在提交 事務(執行exec)時就會報錯
    * 程序中一般能夠捕獲這類錯誤再從新執行一次,直到成功。
 * @throws InterruptedException 
    * 
    */
    public static void main(String[] args) throws InterruptedException{
        TestDemoTX test=new TestDemoTX();
        boolean retValue=test.transMethod();
        System.out.println("main retValue----"+retValue);
    }

  測試主從複製

Jedis jedis_M=new Jedis("127.0.0.1",6379);
        Jedis jedis_S=new Jedis("127.0.0.1",6380);
        jedis_S.slaveof("127.0.0.1", 6379);
        jedis_M.set("class", "1122");
        String result=jedis_S.get("class");
        System.out.println(result );

  測試Jedis_Pool

將new Jedis的操做交給jedis_Pool,減小內存損耗
/**
 * Jedis工具類,利用單列模式懶漢模式
 * @author Administrator
 *
 */
public class JedisPoolUtil {
    //一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾以後,那麼就具有了兩層語義:
    //1)保證了不一樣線程對這個變量進行操做時的可見性,即一個線程修改了某個變量的值,這新值對其餘線程來講是當即可見的。
    //2)禁止進行指令重排序。
    private static volatile JedisPool jedisPool=null; 
    private JedisPoolUtil(){} 
    public static JedisPool getJedisPoolInstance(){
        if(null==jedisPool){
            synchronized (JedisPoolUtil.class) {
                if(null==jedisPool){
                    JedisPoolConfig poolConfig=new JedisPoolConfig();
                    poolConfig.setMaxActive(1000);//設置最大鏈接數
                    poolConfig.setMaxIdle(32);//設置最大空閒數
                    poolConfig.setMaxWait(100*1000);//最大的等待時間,若是超過等待時間,則直接拋出JedisConnectionException
                    poolConfig.setTestOnBorrow(true);//設置是否檢查鏈接成功
                    jedisPool=new JedisPool(poolConfig,"127.0.0.1",6379);
                }
            }
        }
        return jedisPool;
    }
    public static void release(JedisPool jedisPool,Jedis jedis){
        if(null!=jedis){
            jedisPool.returnResourceObject(jedis);
        }
    }
}
/**
 * 測試應用鏈接池工具
 * @author Administrator
 *
 */
public class TestConnect {
    public static void main(String[] args){
        JedisPool jedisPool=JedisPoolUtil.getJedisPoolInstance();

        Jedis jedis=null;
        try{
            jedis=jedisPool.getResource();
            jedis.set("aa","bb");
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            JedisPoolUtil.release(jedisPool, jedis);
        }
    }
}

十6、高併發環境下秒殺相關代碼之引用redis案例  

  當緩存中沒有數據,從(關係型)數據庫中拿取數據,而後放進緩存,有就直接從緩存中拿取

      一、引入jedis依賴

<!--引入java訪問redis客戶端:Jedis-->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>2.7.3</version>
</dependency>

  二、引入序列化依賴(此依賴是個高效的序列化,相對於類繼承seriliazible,它更高效)

<!--使用開源社區高效序列化插件-->
<!--protostuff序列化依賴-->
<!-- https://mvnrepository.com/artifact/com.dyuproject.protostuff/protostuff-core -->
<dependency>
  <groupId>com.dyuproject.protostuff</groupId>
  <artifactId>protostuff-core</artifactId>
   <version>1.0.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.dyuproject.protostuff/protostuff-runtime -->
<dependency>
  <groupId>com.dyuproject.protostuff</groupId>
  <artifactId>protostuff-runtime</artifactId>
  <version>1.0.8</version>
</dependency>

  三、建立redis dao層

public class RedisDao {
     private JedisPool jedisPool;
    //自定義序列化(推薦用類實現serilazible,由於性能不高),此須要加入protostuff
    private RuntimeSchema<Seckill> schema=RuntimeSchema.createFrom(Seckill.class);
    public RedisDao(String ip,int port){
        jedisPool=new JedisPool(ip,port);
    }
    //獲取秒殺對象
    public Seckill getSeckill(long seckillId){
        //redis操做邏輯
        try{
            Jedis jedis=jedisPool.getResource();
            try {  //快速生成try/catch:Ctr+Alt+T
                String key="seckill:"+seckillId;
                //redis對於類的操做並無實現內部序列化操做,它存儲的都是一個二進制數組,必須經過反序列化將二進制轉化爲類
                // 思路:1.seckill實現serilizable(不高效,jdk內部方法);2.採用開源社區較高效的
                //get->byte[] ->反序列化->Object(Seckill)
                //採用自定義序列化
                byte[] bytes=jedis.get(key.getBytes());
                if(bytes!=null){
                    //空對象
                    Seckill seckill=schema.newMessage();
                    //將數據傳送到空對象中
                    ProtobufIOUtil.mergeFrom(bytes,seckill,schema);
                    //seckill被序列化
                    return seckill;
                }
            }finally {
                jedis.close();
            }

        }catch(Exception e){
            e.printStackTrace();
        }
        return null;
    }
    //將seckill對象存進redis中,存取秒殺對象
    public String putSeckill(Seckill seckill){
        //set Object(Seckill) -->序列化-->byte[]-->redis
        try {
            Jedis jedis=jedisPool.getResource();
            try {
                String key="seckill:"+seckill.getSeckillId();
                //LinkedBuffer緩存器,當數據較大時會有必定緩存
                byte[] bytes=ProtobufIOUtil.toByteArray(seckill,schema,
                        LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
                //超時緩存
                int timeout=60*60;//緩存一個小時
                //返回結果,若是錯誤返回錯誤信息,正確則返回「ok」
                String result=jedis.setex(key.getBytes(),timeout,bytes);
                return result;
            } finally {
                jedis.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

  四、注入dao層

<!--注入RedisDao-->
<bean id="redisDao" class="org.seckill.dao.cache.RedisDao">
     <!--配置該Dao層的構造方法中的兩個參數,不然沒法使用-->
     <!--配置IP-->
     <constructor-arg index="0" value="localhost"/>
     <!--配置port端口號,redis端口號默認爲6379-->
     <constructor-arg index="1" value="6379"/>
</bean>

  五、編寫測試類

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:spring/spring-dao.xml"})
public class RedisDaoTest{
    private long id=1001;
    @Autowired
    private RedisDao redisDao;
    @Autowired
    private SeckillDao seckillDao;
    @Test
    public void testSeckill() throws Exception {
        //get and put
        Seckill seckill=redisDao.getSeckill(id);
        if(seckill==null){
            //若是緩存沒有,從數據庫中拿取數據
            seckill=seckillDao.queryById(id);
            if(seckill!=null){
                //將數據庫中拿取的數據放進緩存中
                String result=redisDao.putSeckill(seckill);
                System.out.println(result);
                seckill=redisDao.getSeckill(id);
                System.out.println(seckill);

            }
        }
    }

  其架構圖爲:

  讀多寫少用緩存,讀少寫多用隊列

十7、客戶端與Redis  

 一、哨兵功能:

  告訴客戶端,哪臺redis服務器能夠用

  維護redis主從,當主服務器掛失,主從自動切換,將從變成主

  哨兵溝通渠道:主redis上面的專屬通信
  命令:subscribe_sentinel_:hello

  Master掛掉:
  sdown 主觀的認爲master掛掉(掛掉的哨兵)

  odown 客觀的認爲master,超過半數的哨兵認爲master掛掉了

  意思是一臺哨兵掛掉不必定認爲掛掉,須要和其餘哨兵溝通,若是都認爲掛掉了,那就是掛掉了

        

二、哨兵啓動原理

       

  配置哨兵須要sentinel.conf文件
  此配置文件須要告訴哨兵哪一個是主
  當主掛失,文件會自動更新內容,哨兵能自動更改配置

三、哨兵原理:Redis狀態檢測

  

四、哨兵模式客戶端

  每一個1秒中訪問一次redis

  能夠手動停掉其中一臺機器掛失,查看打印的信息

  當主機掛失後再次啓動,它已經變成了從,沒法執行寫 操做,只有讀操做

public class TestDemo {
    public static void main(String[] args) {
        Set<String> set=new HashSet<String>();
        set.add("192.168.174.130:6379");
        set.add("192.168.174.130:6380");
        set.add("192.168.174.130:6381");
        JedisSentinelPool pool=new JedisSentinelPool("myMaster", set);
        //沒隔1秒,訪問一次redis
        while(true){
            Jedis jedis=null;
            try {
                jedis=pool.getResource();
                String value=jedis.get("hello");
                System.out.println(System.currentTimeMillis()+"-從redis中抽取的結果-");
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }finally{
                if(jedis!=null){
                    jedis.close();
                }
            }
        }
        
    }
}

五、哨兵原理--選舉leader過程

 一、本身先選一個最小的,RunID,再看別人選的是什麼 

   相關代碼:SENTINEL is-master-down-by-addr 查詢其餘哨兵的選舉結果/查詢master情況

     二、官方文檔顯示:
     Disconnection time from the master

     Slave priority

     Replication offset processed

     Run ID

  

六、哨兵原理--選舉master

 一、選擇非掛失

 二、選擇優先級高的(在redis.conf文件中有優先級設置slave-priority)

 三、同步狀況

 四、最小run id

七、關於Redis客戶端底層

  客戶端和Redis交互,其實就是Socket編程

 代碼一:手寫客戶端與Redis交互底層原理

/**
 * Redis客戶端底層
 * 底層就是Socket編程
 * @author Administrator
 *
 */
public class RedisClient {

    public static void main(String[] args) throws Exception {
        Socket client=new Socket("192.168.174.133",6379);
        /*發送語句一:
         * 發包\r\n表示一段數據的結束(不配置不然會報錯)
         * 結果:-ERR unknown command 'hello-redis'(由於語法,須要遵照redis相關協議)
        client.getOutputStream().write("hello-redis\r\n".getBytes());*/
        /*發送語句二:
         * 結果::10
         */    
        client.getOutputStream().write("dbsize\r\n".getBytes());
        //接受redis server響應
        byte[] response=new byte[1024];
        client.getInputStream().read(response);
        System.out.println(new String(response));
    }
}

  代碼二:手寫客戶端存儲數據於Redis底層原理

/**
 * 手寫客戶端
 * 
 * @author Administrator
 * 
 */
public class RedisClient2 {
    // 每段數據 分隔 \r\n
    // *數組
    // $多行字符串
    // +單行信息
    // -錯誤信息
    // :整形數字
    private OutputStream writer;
    private InputStream reader;

    public RedisClient2(String host, int port) throws Exception {
        Socket client = new Socket(host, port);
        writer = client.getOutputStream();
        reader = client.getInputStream();
    }

    // set key value
    public String set(String Key, String value) throws Exception {
        // 組裝一個請求報文 -RESP
        StringBuffer command = new StringBuffer();
        command.append("*3").append("\r\n");// 開頭,報文包含幾個部分
        command.append("$3").append("\r\n");// 第一部分命令的類型是多行字符串,長度爲3
        command.append("set").append("\r\n");// 第一部分命令的數據值
        // 爲何要轉爲字節數組由於要將中文轉化爲字節數組才能識別
        command.append("$").append(Key.getBytes().length).append("\r\n");// 第二部分數據的長度
        command.append(Key).append("\r\n");// 第二部分數據的值

        command.append("$").append(value.length()).append("\r\n");// 第二部分數據的長度
        command.append(value).append("\r\n");// 第二部分數據的值

        // 發送一個命令報文到redis服務器
        writer.write(command.toString().getBytes());

        // 接受redis執行結果
        byte[] response = new byte[1024];
        reader.read(response);
        return new String(response);
    }

    // get key
    public String get(String key) throws Exception {
        // 組成一個請求報文RESP
        StringBuffer command = new StringBuffer();
        command.append("*2").append("\r\n");

        command.append("$3").append("\r\n");
        command.append("get").append("\r\n");

        command.append("$").append(key.getBytes().length).append("\r\n");
        command.append(key).append("\r\n");

        // 發送一個命令報文到redis服務器
        writer.write(command.toString().getBytes());

        // 接受redis執行結果
        byte[] response = new byte[1024];
        reader.read(response);
        return new String(response);
    }

    public static void main(String[] args) throws Exception {
        RedisClient2 redis = new RedisClient2("192.168.174.133", 6379);
        String info = redis.set("liyiling", "iloveu");
        System.out.println(info);// 結果:+OK
        String result=redis.get("liyiling");
        System.out.println(result);//結果:$6 iloveu
    }
}

  代碼三:手寫訂閱消息底層

/**
 * 訂閱機制的實現
 * @author Administrator
 *
 */
public class Subscribe {
    private OutputStream writer;
    private InputStream reader;
    //支持訂閱的程序
    public Subscribe(OutputStream writer,InputStream reader){
        this.writer=writer;
        this.reader=reader;
    }
    //訂閱一個消息頻道
    public void news(String myNew) throws Exception{
        //和redis-server通訊
        //subscribe channel
        //組裝一個請求報文 -RESP
        StringBuffer command = new StringBuffer();
        command.append("*2").append("\r\n");

        command.append("$9").append("\r\n");
        command.append("subscribe").append("\r\n");

        command.append("$").append(myNew.getBytes().length).append("\r\n");
        command.append(myNew).append("\r\n");
        writer.write(command.toString().getBytes());
        //實現實時接收
        while(true){
            byte[] dontai=new byte[1024];
            reader.read(dontai);
            System.out.println(myNew+"訂閱的消息有動態了");
            System.out.println(new String(dontai));
        }
    }
    public static void main(String[] args) throws Exception {
        RedisClient2 redis = new RedisClient2("192.168.174.133", 6379);
        Subscribe subscribe=redis.subscribe();
        subscribe.news("liyiling");
    }
}

  此時能夠再redis一臺機器中發佈一個消息

127.0.0.1:6379> publish liyiling smile
(integer) 1

  控制檯能顯示接受到的消息

十8、Redis擴展

    如下是在網上找到的一些資源

   一、Redis 管道(pipeline)

  在插入多條數據時,使用Redis管道可以增快redis執行速度。redis的pipeline(管道)功能在命令行中沒有,可是redis是支持管道的,在java的客戶端(jedis)中是可使用的。

  測試以下:

  1) 不使用管道,執行時間爲372

        long currentTimeMills=System.currentTimeMillis();
        Jedis jedis=new Jedis("192.168.174.133",6379);
        for(int i=0;i<1000;i++){
            jedis.set("test"+i,"test"+i);
        }
        long endTimeMills=System.currentTimeMillis();
        System.out.println(endTimeMills-currentTimeMills);

  2)  使用管道,執行時間爲83

    long currentTimeMills=System.currentTimeMillis();
        Jedis jedis=new Jedis("192.168.174.133",6379);
        Pipeline pipeline=jedis.pipelined();
        for(int i=0;i<1000;i++){
            pipeline.set("test"+i,"test"+i);
        }
        pipeline.sync();
        long endTimeMills=System.currentTimeMillis();
        System.out.println(endTimeMills-currentTimeMills);

  二、Redis應用場景

     限制網站訪客訪問頻率

    進行各類數據統計的用途是很是普遍的,好比想知道何時封鎖一個IP地址。INCRBY命令讓這個變得很容易,經過原子遞增保持計數;GETSET用來重置計數器;過時expire用來肯定一個關鍵字何時應該刪除。

         代碼以下:

public class TestDemo2 {
    
        String host="192.168.174.133";
        int port=6379;
        Jedis jedis=new Jedis(host,port);
        /**
         * 限制網站訪問頻率,一分鐘以內最多訪問10次
         */
        public void test3() throws Exception{
            //模擬用戶頻繁請求
            for(int i=0;i<20;i++){
                boolean result=testLogin("192.168.174.133");
                if(result){
                    System.out.println("正常訪問");
                }else{
                    System.out.println("訪問受限制");
                }
            }
        }
        public boolean testLogin(String ip){
            String value=jedis.get(ip);
            if(value==null){
                //初始化時設置IP訪問此時
                jedis.set(ip,"1");
                //設置IP的生存時間爲60秒,60秒內IP的訪問次數由程序控制
                jedis.expire(ip,60);
            }else{
                int parsetInt=Integer.parseInt(value);
                //若是60秒內IP的訪問次數超過10,返回false,實現了超過10次禁止分的功能
                if(parsetInt>10){
                    return false;
                }else{
                    //若是沒有10次。能夠自增
                   jedis.incr(ip);
                }
            }
            return true;
        }
    public static void main(String[] args) throws Exception {
        TestDemo2 t=new TestDemo2();
        t.test3();
    }
}

  執行結果

  

  三、Redis經典面試題

   MySQL裏有2000w數據,redis中只存20w的數據,如何保證redis中的數據都是熱點數據

   相關知識:redis 內存數據集大小上升到必定大小的時候,就會施行數據淘汰策略。redis 提供 6種數據淘汰策略:

  voltile-lru:從已設置過時時間的數據集(server.db[i].expires)中挑選最近最少使用的數據淘汰

  volatile-ttl:從已設置過時時間的數據集(server.db[i].expires)中挑選將要過時的數據淘汰

  volatile-random:從已設置過時時間的數據集(server.db[i].expires)中任意選擇數據淘汰

  allkeys-lru:從數據集(server.db[i].dict)中挑選最近最少使用的數據淘汰

  allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰

  no-enviction(驅逐):禁止驅逐數據

相關文章
相關標籤/搜索