Redis入門


一、關於NOSQL

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

 1、什么是NOSQL

 NoSQL,泛指非關系型的數據庫。隨着互聯網web2.0網站的興起,傳統的關系數據庫在應付web2.0網站,特別是超大規模和高並發的SNS類型的web2.0動態網站已經顯得力不從心,暴露了很多難以克服的問題,而非關系型的數據庫則由於其本身的特點得到了非常迅速的發展。NoSQL數據庫的產生就是為了解決大規模數據集合多重數據種類帶來的挑戰,尤其是大數據應用難題。                          

                                        -------百度百科

 2、從時代背景下考慮,為什么要使用NOSQL

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

 3、區分傳統數據庫與非關系型數據庫特性

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

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

二、什么是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(主從)同步。          

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

 三、Redis特性

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

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

四、如何開啟Redis

 1、下載相關jar包:下載

 2、解壓並安裝

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

 3、修改配置文件,啟動后台運行功能 

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

 3、啟動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

 4、驗證

  如果ping命令顯示pong表示成功

 5、關閉

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

五、關於Redis初別


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

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

 3、可通過dbsize查看當前數據庫key的數量

 4、清除數據庫的兩種方法:flushdb(清空當前數據庫);flushall(清空所有數據庫,慎用)

 5、默認端口為6379

六、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的設置操作

 2、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:在指定集合的指定值之前插入指定值

 3、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(集合 ):去集合的並集

 4、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: 若不存在,為指定的鍵設置指定的值,否則執行失敗

 5、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

七、Redis之配置文件詳解

 1、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

 2、綁定本地地址與開啟自摸保護機制

  Redis綁定了本定地址:127.0.0.1並且開啟了保護機制,此保護機制可防止遠程客戶端訪問,如果想要取消該機制需要將默認值改為no

bind 127.0.0.1
protected-mode yes

 3、默認端口號為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

 4、在高並發環境下你需要一個高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

 5、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

 6、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

 7、指定日志記錄級別,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

 8、日志記錄方式,默認為標准輸出,如果配置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 ""

 9、指定數據庫個數

databases 16

10、Redis快照機制。

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

#   save ""

save 900 1
save 300 10
save 60 10000

 11、序列化的時候是否停止寫操作

stop-writes-on-bgsave-error yes

 12、指定存儲至本地數據庫時是否壓縮數據,默認為yes,Redis采用LZF壓縮,如果為了節省CPU時間,可以關閉該選項,但會導致數據庫文件變的巨大

rdbcompression yes

 13、通過消耗CPU資源對rdb數據進行校驗,默認為yes

rdbchecksum yes

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

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

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

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

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

 17、當 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

 18、保護slave ,不讓它暴露在不受信任的客戶端上。一般用來當做保護層,尤其在果斷的client的時候。

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

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

slave-read-only yes

 11、從主機的優先級,如果當主主機掛了的時候,將從從主機中選取一個作為其他從機的主,首先優先級的數字最低的將成為主,0是一個特殊的級別,0將          永遠不會成為主。默認值是100.

slave-priority 100

 12、設置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

 13、設置同一時間最大客戶端連接數,默認無限制,Redis可以同時打開的客戶端連接數為Redis進程可以打開的最大文件描述符數,如果設置 maxclients 0,表示不作          限制。當客戶端連接數到達限制時,Redis會關閉新的連接並向客戶端返回max number of clients reached錯誤信息

# maxclients 10000

 14、指定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>

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

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

 16、指定是否在每次更新操作后進行日志記錄,Redis在默認情況下是異步的把數據寫入磁盤,如果不開啟,可能會在斷電時導致一段時間內的數據丟失。因為 redis本身同步數據文件是按上面save條件來同步的,所以有的數據會         在一段時間內只存在於內存中。默認為no;指定更新日志文件名,默認為appendonly.aof

appendonly no
appendfilename "appendonly.aof"

 17、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

八、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

 九、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

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

   Redis持久化包含RDB和AOF 

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

  2、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 “”

  3、AOF:以日志的形式來記錄每個寫操作,將Redis執行過的所有寫指令記錄下來(讀操作不記錄),只許追加文件但不可以改寫文件,redis啟動之初會讀取該文件重新構建數據,換言之,redis重啟的話就根據日志文件的內容將寫指令從前到后執行一次以完成數據的恢復工作(記錄每個寫的操作,以日志的形式,但不記錄讀操作)

  4、開啟AOF功能

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

appendonly yes

  5、模擬事故之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) 重啟數據,數據庫啟動成功,數據成功備份

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

# appendfsync always
appendfsync everysec

  7、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

  8、RDB和AOF優缺點

  RDB:

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

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

  AOF:

  優點:使用 AOF 持久化會讓 Redis 變得非常耐久,能保證數據的完整性。AOF 的默認策略為每秒鍾 fsync 一次,在這種配置下,Redis 仍然可以保持良好的性能,並且就算發生故障停機,也最多只會丟失一秒鍾的數據。

  缺點:對於相同的數據集來說,AOF 文件的體積通常要大於 RDB 文件的體積

十一、Redis之事務

  1、相關命令

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

   2、命令使用

開啟事務
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"

  3、悲觀鎖與樂觀鎖

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

  樂觀鎖:不鎖整張表,在每條記錄下加個version版本號(數字),在操作該記錄后將版本號增加1,當其他操作者在同一時間操作同條數據時,會因為版本號的不同導致提交失敗。並發性高,一致性也高

  4、事務監控

  在執行執行事務之前監控變量,在多並發情況下可以監控變量,事務會因為版本號的不同導致事務提交失敗

  用法:

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

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對數據進行操作

十二、Redis之發布訂閱

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

  2、常用命令

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

  3、用法

   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

十三、Redis的復制機制

 1、什么是redis的復制

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

 2、能干嘛?

 讀寫分離 容災恢復

  用法

  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)反客為主

   1、使當前數據庫停止與其他數據庫的同步,轉成主數據庫(也就是說此時環境是80端口和81端口同時監聽79端口,當主機79端口斷開后,80端口反客為主,將自己身份設置為主機替代79端口)

   2、做法
  環境開始前:

      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端口可以遍歷到

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

  1、分析:反客為主確實能解決主機掛斷后替補問題,但是采用手動方式還是顯得有點笨重不方便。而哨兵模式便是自動方式。它是反客為主的自動版,能夠后台監控主機是否故障,如果故障了根據投票數自動將從庫轉換為主庫。

  2、用法

  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端口號

十五、Jedis

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

 1、部署環境

  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
    2、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

 2、測試Jedis API

 測試一、

 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());

 測試二、

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);
        }
    }
}

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

  當緩存中沒有數據,從(關系型)數據庫中拿取數據,然后放進緩存,有就直接從緩存中拿取

      1、引入jedis依賴

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

  2、引入序列化依賴(此依賴是個高效的序列化,相對於類繼承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>

  3、創建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;
    }
}

  4、注入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>

  5、編寫測試類

@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);

            }
        }
    }

  其架構圖為:

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

十七、客戶端與Redis  

 1、哨兵功能:

  告訴客戶端,哪台redis服務器可以用

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

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

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

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

  意思是一台哨兵掛掉不一定認為掛掉,需要和其他哨兵溝通,如果都認為掛掉了,那就是掛掉了

        

2、哨兵啟動原理

       

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

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

  

4、哨兵模式客戶端

  每個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();
                }
            }
        }
        
    }
}

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

 1、自己先選一個最小的,RunID,再看別人選的是什么 

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

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

     Slave priority

     Replication offset processed

     Run ID

  

6、哨兵原理--選舉master

 1、選擇非掛失

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

 3、同步情況

 4、最小run id

7、關於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

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

十八、Redis擴展

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

   1、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);

  2、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();
    }
}

  執行結果

  

  3、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(驅逐):禁止驅逐數據

 

 

 

 

 

 

    

 

 

 

  

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM