Redis 学习笔记-NoSQL数据库 常用五大数据类型 Redis配置文件介绍 Redis的发布和订阅 Redis_事务_锁机制_秒杀 Redis应用问题解决 分布式锁
1.NoSQL數據庫
1.1 NoSQL數據庫概述
NoSQL(NosQL = Not Only sQL ),意即“不僅僅是sQL”,泛指非關系型的數據庫。NoSQL不依賴業務邏輯方式存儲,而以簡單的 key-value模式存儲。因此大大的增加了數據庫的擴展能力
?不遵循SQL標準。
?不支持ACID。
?遠超于SQL的性能。
1.2NoSQL適用場景
?對數據高并發的讀寫
?海量數據的讀寫
?對數據高可擴展性的
1.3.NoSQL不適用場景
?需要事務支持
?基于sql的結構化查詢存儲,處理復雜的關系,需要即席查詢。
?用不著sql的和用了sql也不行的情況,請考慮用NoSql
2. Redis 6
2.1.Redis 6安裝
docker 安裝
2.2.Redis介紹相關知識
端口默認==6379 ==
默認16個數據庫,類似數組下標從0開始,初始默認使用0號庫
使用命令 select dbid 來切換數據庫。如: select 8
統一密碼管理,所有庫同樣密碼。
dbsize查看當前數據庫的key的數量
flushdb清空當前庫
flushall通殺全部庫
Redis是單線程+多路IO復用技術
多路復用是指使用一個線程來檢查多個文件描述符(Socket)的就緒狀態,比如調用select和poll函數,傳入多個文件描述符,如果有一個文件描述符就緒,則返回,否則阻塞直到超時。得到就緒狀態后進行真正的操作可以在同一個線程里執行,也可以啟動線程執行(比如使用線程池)
3.常用五大數據類型
哪里去獲得redis常見數據類型操作命令http://www.redis.cn/commands.html
3.0 庫的操作命令
| select | 命令切換數據庫 |
| dbsize | 查看當前數據庫的key的數量 |
| flushdb | 清空當前庫 |
| flushall | 通殺全部庫 |
3.1.Redis鍵(key)
| keys * | 查看當前庫所有key (匹配:keys *1) |
| exists key | 判斷某個key是否存在 |
| type key | 查看你的key是什么類型 |
| del key | 刪除指定的key數據 |
| unlink key | 根據value選擇非阻塞刪除,僅將keys從keyspace元數據中刪除,真正的刪除會在后續異步操作。 |
| expire key 10 | 10秒鐘:為給定的key設置過期時間 |
| ttl key | 查看還有多少秒過期,-1表示永不過期,-2表示已過期 |
3.2.Redis字符串(String)
3.2.1.簡介
String是Redis最基本的類型,你可以理解成與Memcached一模一樣的類型,一個key對應一個value。
String類型是二進制安全的。意味著Redis的string可以包含任何數據。比如jpg圖片或者序列化的對象。
String類型是Redis最基本的數據類型,一個Redis中字符串value最多可以是512M
3.2.2.常用命令
| set <key><value> | 添加鍵值對 |
| get <key> | 查詢對應鍵值 |
| append <key><value> | 將給定的<value> 追加到原值的末尾 |
| strlen <key> | 獲得值的長度 |
| setnx <key><value> | 只有在 key 不存在時 設置 key 的值 |
| incr <key> | 將 key 中儲存的數字值增1,只能對數字值操作,如果為空,新增值為1 原子操作 |
| decr <key> | 將 key 中儲存的數字值減1,只能對數字值操作,如果為空,新增值為-1 原子操作 |
| incrby / decrby <key><步長> | 將 key 中儲存的數字值增減。自定義步長。 |
| mset <key1><value1><key2><value2> … | 同時設置一個或多個 key-value對 |
| mget <key1><key2><key3> … | 同時獲取一個或多個 value |
| msetnx <key1><value1><key2><value2> … | 同時設置一個或多個 key-value 對,當且僅當所有給定 key 都不存在。原子性,有一個失敗則都失敗 |
| getrange <key><起始位置><結束位置> | 獲得值的范圍,類似java中的substring,前包,后包 |
| setrange <key><起始位置><value> | 用 <value> 覆寫<key>所儲存的字符串值,從<起始位置>開始(索引從0開始)。 |
| setex <key><過期時間><value> | 設置鍵值的同時,設置過期時間,單位秒。 |
| getset <key><value> | 以新換舊,設置了新值同時獲得舊值。 |
3.2.2.1所謂原子操作是指不會被線程調度機制打斷的操作;
這種操作一旦開始,就一直運行到結束,中間不會有任何 context switch (切換到另一個線程)。
(1)在單線程中, 能夠在單條指令中完成的操作都可以認為是"原子操作",因為中斷只能發生于指令之間。
(2)在多線程中,不能被其它進程(線程)打斷的操作就叫原子操作。
Redis單命令的原子性主要得益于Redis的單線程。
案例:
java中的i++是否是原子操作?
答:不是
i=0;兩個線程分別對i進行++100次,值是多少?
3.2.2.2 set擴展 <key><value>命令
*NX:當數據庫中key不存在時,可以將key-value添加數據庫
*XX:當數據庫中key存在時,可以將key-value添加數據庫,與NX參數互斥
*EX:key的超時秒數
*PX:key的超時毫秒數,與EX互斥
3.2.3.數據結構
String的數據結構為簡單動態字符串(Simple Dynamic String,縮寫SDS)。是可以修改的字符串,內部結構實現上類似于Java的ArrayList,采用預分配冗余空間的方式來減少內存的頻繁分配.
如圖中所示,內部為當前字符串實際分配的空間capacity一般要高于實際字符串長度len。當字符串長度小于1M時,擴容都是加倍現有的空間,如果超過1M,擴容時一次只會多擴1M的空間。需要注意的是字符串最大長度為512M。
3.3.Redis列表(List)
3.3.1.簡介
單鍵多值
Redis 列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)。
它的底層實際是個雙向鏈表,對兩端的操作性能很高,通過索引下標的操作中間的節點性能會較差。
3.3.2.常用命令
| lpush/rpush <key><value1><value2><value3> … | 從左邊/右邊插入一個或多個值。 |
| lpop/rpop <key> | 從左邊/右邊吐出一個值。值在鍵在,值光鍵亡。 |
| rpoplpush <key1><key2> | 從<key1>列表右邊吐出一個值,插到<key2>列表左邊。 |
| lrange <key><start><stop> | 按照索引下標獲得元素(從左到右) |
| lrange <key> 0 -1 | 0左邊第一個,-1右邊第一個,(0-1表示獲取所有) |
| lindex <key><index> | 按照索引下標獲得元素(從左到右) |
| llen <key> | 獲得列表長度 |
| linsert <key> before <value><newvalue> | 在<value>的后面插入<newvalue>插入值 |
| lrem <key><n><value> | 從左邊刪除n個value(從左到右) |
| lset<key><index><value> | 將列表key下標為index的值替換成value |
3.3.3.數據結構
List的數據結構為快速鏈表quickList。
首先在列表元素較少的情況下會使用一塊連續的內存存儲,這個結構是ziplist,也即是壓縮列表。
它將所有的元素緊挨著一起存儲,分配的是一塊連續的內存。
當數據量比較多的時候才會改成quicklist。
因為普通的鏈表需要的附加指針空間太大,會比較浪費空間。比如這個列表里存的只是int類型的數據,結構上還需要兩個額外的指針prev和next。
Redis將鏈表和ziplist結合起來組成了quicklist。也就是將多個ziplist使用雙向指針串起來使用。這樣既滿足了快速的插入刪除性能,又不會出現太大的空間冗余。
3.4.Redis集合(Set)
3.4.1.簡介
Redis set對外提供的功能與list類似是一個列表的功能,特殊之處在于set是可以自動排重的,當你需要存儲一個列表數據,又不希望出現重復數據時,set是一個很好的選擇,并且set提供了判斷某個成員是否在一個set集合內的重要接口,這個也是list所不能提供的。
Redis的Set是string類型的無序集合。它底層其實是一個value為null的hash表,所以添加,刪除,查找的復雜度都是O(1)。
一個算法,隨著數據的增加,執行時間的長短,如果是O(1),數據增加,查找數據的時間不變
3.4.2.常用命令
| sadd <key><value1><value2> … | 將一個或多個 member 元素加入到集合 key 中,已經存在的 member 元素將被忽略 |
| smembers <key> | 取出該集合的所有值。 |
| sismember <key><value> | 判斷集合<key>是否為含有該<value>值,有1,沒有0 |
| scard<key> | 返回該集合的元素個數。 |
| srem <key><value1><value2> … | 刪除集合中的某個元素。 |
| spop <key> | 隨機從該集合中吐出一個值。 |
| srandmember <key><n> | 隨機從該集合中取出n個值。不會從集合中刪除 。 |
| smove <source><destination>value | 把集合中一個值從一個集合移動到另一個集合 |
| sinter <key1><key2> | 返回兩個集合的交集元素。 |
| sunion <key1><key2> | 返回兩個集合的并集元素。 |
| sdiff <key1><key2> | 返回兩個集合的差集元素(key1中的,不包含key2中的) |
3.4.3.數據結構
Set數據結構是dict字典,字典是用哈希表實現的。
Java中HashSet的內部實現使用的是HashMap,只不過所有的value都指向同一個對象。Redis的set結構也是一樣,它的內部也使用hash結構,所有的value都指向同一個內部值。
3.5.Redis哈希(Hash)
3.5.1.簡介
Redis hash 是一個鍵值對集合。
Redis hash是一個string類型的field和value的映射表,hash特別適合用于存儲對象。
類似Java里面的Map<String,Object>
用戶ID為查找的key,存儲的value用戶對象包含姓名,年齡,生日等信息,如果用普通的key/value結構來存儲
主要有以下2種存儲方式:
第一種:
每次修改用戶的某個屬性需要,先反序列化改好后再序列化回去。開銷較大。
第二種:
用戶ID數據冗余
第三種hash:
通過 key(用戶ID) + field(屬性標簽) 就可以操作對應屬性數據了,既不需要重復存儲數據,也不會帶來序列化和并發修改控制的問題
3.5.2.常用命令
| hset <key><field><value> | 給<key>集合中的 <field>鍵賦值<value> |
| hget <key1><field> | 從<key1>集合<field>取出 value |
| hmset <key1><field1><value1><field2><value2>… | 批量設置hash的值 |
| hexists<key1><field> | 查看哈希表 key 中,給定域 field 是否存在。 |
| hkeys <key> | 列出該hash集合的所有field |
| hvals <key> | 列出該hash集合的所有value |
| hincrby <key><field><increment> | 為哈希表 key 中的域 field 的值加上增量increment 值 1 -1 |
| hsetnx <key><field><value> | 將哈希表 key 中的域 field 的值設置為 value ,當且僅當域 field 不存在 . |
3.5.3.數據結構
Hash類型對應的數據結構是兩種:ziplist(壓縮列表),hashtable(哈希表)。當field-value長度較短且個數較少時,使用ziplist,否則使用hashtable。
3.6.Redis有序集合Zset(sorted set)
3.6.1.簡介
Redis有序集合zset與普通集合set非常相似,是一個沒有重復元素的字符串集合。
不同之處是有序集合的每個成員都關聯了一個評分(score),這個評分(score)被用來按照從最低分到最高分的方式排序集合中的成員。集合的成員是唯一的,但是評分可以是重復了 。
因為元素是有序的, 所以你也可以很快的根據評分(score)或者次序(position)來獲取一個范圍的元素。
訪問有序集合的中間元素也是非常快的,因此你能夠使用有序集合作為一個沒有重復成員的智能列表。
3.6.2.常用命令
| zadd <key><score1><value1><score2><value2>… | 將一個或多個 member 元素及其 score 值加入到有序集 key 當中。 |
| zrange <key><start><stop> [WITHSCORES] | 返回有序集 key 中,下標在<start><stop>之間的元素,帶WITHSCORES,可以讓分數一起和值返回到結果集。 |
| zrangebyscore key minmax [withscores] [limit offset count] | 返回有序集 key 中,所有 score 值介于 min 和 max 之間(包括等于 min 或 max )的成員。有序集成員按 score 值遞增(從小到大)次序排列。 |
| zrevrangebyscore key maxmin [withscores] [limit offset count] | 同上,改為從大到小排列。 |
| zincrby <key><increment><value> | 為元素的score加上增量 |
| zrem <key><value> | 刪除該集合下,指定值的元素 |
| zcount <key><min><max> | 統計該集合,分數區間內的元素個數 |
| zrank <key><value> | 返回該值在集合中的排名,從0開始。 |
案例:如何利用zset實現一個文章訪問量的排行榜?
3.6.3.數據結構
SortedSet(zset)是Redis提供的一個非常特別的數據結構,一方面它等價于Java的數據結構Map<String, Double>,可以給每一個元素value賦予一個權重score,另一方面它又類似于TreeSet,內部的元素會按照權重score進行排序,可以得到每個元素的名次,還可以通過score的范圍來獲取元素的列表。
zset底層使用了兩個數據結構
(1)hash,hash的作用就是關聯元素value和權重score,保障元素value的唯一性,可以通過元素value找到相應的score值。
(2)跳躍表,跳躍表的目的在于給元素value排序,根據score的范圍獲取元素列表。
3.6.4.跳躍表(跳表)
1、簡介
有序集合在生活中比較常見,例如根據成績對學生排名,根據得分對玩家排名等。對于有序集合的底層實現,可以用數組、平衡樹、鏈表等。數組不便元素的插入、刪除;平衡樹或紅黑樹雖然效率高但結構復雜;鏈表查詢需要遍歷所有效率低。Redis采用的是跳躍表。跳躍表效率堪比紅黑樹,實現遠比紅黑樹簡單。
2、實例
對比有序鏈表和跳躍表,從鏈表中查詢出51
(1)有序鏈表
要查找值為51的元素,需要從第一個元素開始依次查找、比較才能找到。共需要6次比較。
(2)跳躍表
從第2層開始,1節點比51節點小,向后比較。
21節點比51節點小,繼續向后比較,后面就是NULL了,所以從21節點向下到第1層
在第1層,41節點比51節點小,繼續向后,61節點比51節點大,所以從41向下
在第0層,51節點為要查找的節點,節點被找到,共查找4次。
從此可以看出跳躍表比有序鏈表效率要高
4.Redis配置文件介紹
自定義目錄:/myredis/redis.conf
4.1.###Units單位###
配置大小單位,開頭定義了一些基本的度量單位,只支持bytes,不支持bit
大小寫不敏感
4.2.###INCLUDES包含###
類似jsp中的include,多實例的情況可以把公用的配置文件提取出來
4.3.###網絡相關配置
4.3.1.bind
默認情況bind=127.0.0.1只能接受本機的訪問請求
不寫的情況下,無限制接受任何ip地址的訪問
生產環境肯定要寫你應用服務器的地址;服務器是需要遠程訪問的,所以需要將其注釋掉
如果開啟了protected-mode,那么在沒有設定bind ip且沒有設密碼的情況下,Redis只允許接受本機的響應
保存配置,停止服務,重啟啟動查看進程,不再是本機訪問了。
4.3.2.protected-mode
將本機訪問保護模式設置no
4.3.3.Port
端口號,默認 6379
4.3.4. tcp-backlog
設置tcp的backlog,backlog其實是一個連接隊列,backlog隊列總和=未完成三次握手隊列 + 已經完成三次握手隊列。
在高并發環境下你需要一個高backlog值來避免慢客戶端連接問題。
注意Linux內核會將這個值減小到/proc/sys/net/core/somaxconn的值(128),所以需要確認增大/proc/sys/net/core/somaxconn和/proc/sys/net/ipv4/tcp_max_syn_backlog(128)兩個值來達到想要的效果
4.3.5. timeout
一個空閑的客戶端維持多少秒會關閉,0表示關閉該功能。即永不關閉。
4.3.6.tcp-keepalive
對訪問客戶端的一種心跳檢測,每個n秒檢測一次。
單位為秒,如果設置為0,則不會進行Keepalive檢測,建議設置成60
4.4.###GENERAL通用###
4.4.1.daemonize
是否為后臺進程,設置為yes
守護進程,后臺啟動
4.4.2.pidfile
存放pid文件的位置,每個實例會產生一個不同的pid文件
4.4.3.loglevel
指定日志記錄級別,Redis總共支持四個級別:debug、verbose、notice、warning,默認為notice
四個級別根據使用階段來選擇,生產環境選擇notice 或者warning
4.4.4.logfile
日志文件名稱
4.4.5.databases 16
設定庫的數量 默認16,默認數據庫為0,可以使用SELECT 命令在連接上指定數據庫id
4.5.###SECURITY安全###
4.5.1.設置密碼
訪問密碼的查看、設置和取消
在命令中設置密碼,只是臨時的。重啟redis服務器,密碼就還原了。
永久設置,需要再配置文件中進行設置。
4.6.#### LIMITS限制
4.6.1.maxclients
?設置redis同時可以與多少個客戶端進行連接。
?默認情況下為10000個客戶端。
?如果達到了此限制,redis則會拒絕新的連接請求,并且向這些連接請求方發出“max number of clients reached”以作回應。
4.6.2. maxmemory
?建議必須設置,否則,將內存占滿,造成服務器宕機
?設置redis可以使用的內存量。一旦到達內存使用上限,redis將會試圖移除內部數據,移除規則可以通過maxmemory-policy來指定。
?如果redis無法根據移除規則來移除內存中的數據,或者設置了“不允許移除”,那么redis則會針對那些需要申請內存的指令返回錯誤信息,比如SET、LPUSH等。
?但是對于無內存申請的指令,仍然會正常響應,比如GET等。如果你的redis是主redis(說明你的redis有從redis),那么在設置內存使用上限時,需要在系統中留出一些內存空間給同步隊列緩存,只有在你設置的是“不移除”的情況下,才不用考慮這個因素。
4.6.3.maxmemory-policy
?volatile-lru:使用LRU算法移除key,只對設置了過期時間的鍵;(最近最少使用)
?allkeys-lru:在所有集合key中,使用LRU算法移除key
?volatile-random:在過期集合中移除隨機的key,只對設置了過期時間的鍵
?allkeys-random:在所有集合key中,移除隨機的key
?volatile-ttl:移除那些TTL值最小的key,即那些最近要過期的key
?noeviction:不進行移除。針對寫操作,只是返回錯誤信息
4.6.4.maxmemory-samples
?設置樣本數量,LRU算法和最小TTL算法都并非是精確的算法,而是估算值,所以你可以設置樣本的大小,redis默認會檢查這么多個key并選擇其中LRU的那個。
?一般設置3到7的數字,數值越小樣本越不準確,但性能消耗越小。
5.Redis的發布和訂閱
5.1.什么是發布和訂閱
Redis 發布訂閱 (pub/sub) 是一種消息通信模式:發送者 (pub) 發送消息,訂閱者 (sub) 接收消息。
Redis 客戶端可以訂閱任意數量的頻道。
5.2.Redis的發布和訂閱
1、客戶端可以訂閱頻道如下圖
2、當給這個頻道發布消息后,消息就會發送給訂閱的客戶端
5.3.發布訂閱命令行實現
1、打開一個客戶端訂閱channel1
SUBSCRIBE channel1
2、打開另一個客戶端,給channel1發布消息hello
publish channel1 hello
返回的1是訂閱者數量
3、打開第一個客戶端可以看到發送的消息
注:發布的消息沒有持久化,如果在訂閱的客戶端收不到hello,只能收到訂閱后發布的消息
6.Redis新數據類型
6.1.Bitmaps
6.1.1.簡介
現代計算機用二進制(位) 作為信息的基礎單位, 1個字節等于8位, 例如“abc”字符串是由3個字節組成, 但實際在計算機存儲時將其用二進制表示, “abc”分別對應的ASCII碼分別是97、 98、 99, 對應的二進制分別是01100001、 01100010和01100011,如下圖
合理地使用操作位能夠有效地提高內存使用率和開發效率。
(1)Bitmaps本身不是一種數據類型, 實際上它就是字符串(key-value) , 但是它可以對字符串的位進行操作。
(2)Bitmaps單獨提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一個以位為單位的數組, 數組的每個單元只能存儲0和1, 數組的下標在Bitmaps中叫做偏移量。
6.1.2.命令
1、setbit
(1)格式
setbit<key><offset><value>設置Bitmaps中某個偏移量的值(0或1)
offset:偏移量從0開始
2、getbit
(1)格式
getbit<key><offset>獲取Bitmaps中某個偏移量的值
獲取鍵的第offset位的值(從0開始算)
3、bitcount
統計字符串被設置為1的bit數。一般情況下,給定的整個字符串都會被進行計數,通過指定額外的 start 或 end 參數,可以讓計數只在特定的位上進行。start 和 end 參數的設置,都可以使用負數值:比如 -1 表示最后一個位,而 -2 表示倒數第二個位,start、end 是指bit組的字節的下標數,二者皆包含。
(1)格式
bitcount<key>[start end] 統計字符串從start字節到end字節比特值為1的數量
4、bitop
(1)格式
bitop and(or/not/xor) <destkey> [key…]
bitop是一個復合操作, 它可以做多個Bitmaps的and(交集) 、 or(并集) 、 not(非) 、 xor(異或) 操作并將結果保存在destkey中。
6.1.3.Bitmaps與set對比
假設網站有1億用戶, 每天獨立訪問的用戶有5千萬, 如果每天用集合類型和Bitmaps分別存儲活躍用戶可以得到表
很明顯, 這種情況下使用Bitmaps能節省很多的內存空間, 尤其是隨著時間推移節省的內存還是非常可觀的
但Bitmaps并不是萬金油, 假如該網站每天的獨立訪問用戶很少, 例如只有10萬(大量的僵尸用戶) , 那么兩者的對比如下表所示, 很顯然, 這時候使用Bitmaps就不太合適了, 因為基本上大部分位都是0。
6.2.HyperLogLog
6.2.1.簡介
在工作當中,我們經常會遇到與統計相關的功能需求,比如統計網站PV(PageView頁面訪問量),可以使用Redis的incr、incrby輕松實現。
但像UV(UniqueVisitor,獨立訪客)、獨立IP數、搜索記錄數等需要去重和計數的問題如何解決?這種求集合中不重復元素個數的問題稱為基數問題。
解決基數問題有很多種方案:
(1)數據存儲在MySQL表中,使用distinct count計算不重復個數
(2)使用Redis提供的hash、set、bitmaps等數據結構來處理
以上的方案結果精確,但隨著數據不斷增加,導致占用空間越來越大,對于非常大的數據集是不切實際的。
能否能夠降低一定的精度來平衡存儲空間?Redis推出了HyperLogLog。
Redis HyperLogLog 是用來做基數統計的算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定的、并且是很小的。
在 Redis 里面,每個 HyperLogLog 鍵只需要花費 12 KB 內存,就可以計算接近 2^64 個不同元素的基數。這和計算基數時,元素越多耗費內存就越多的集合形成鮮明對比。
但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。
什么是基數?
比如數據集 {1, 3, 5, 7, 5, 7, 8}, 那么這個數據集的基數集為 {1, 3, 5 ,7, 8}, 基數(不重復元素)為5。 基數估計就是在誤差可接受的范圍內,快速計算基數。
6.2.2.命令
1、pfadd
(1)格式
pfadd <key>< element> [element …] 添加指定元素到 HyperLogLog 中
2、pfcount
(1)格式
pfcount<key> [key …] 計算HLL的近似基數,可以計算多個HLL,比如用HLL存儲每天的UV,計算一周的UV可以使用7天的UV合并計算即可
3、pfmerge
(1)格式
pfmerge<destkey><sourcekey> [sourcekey …] 將一個或多個HLL合并后的結果存儲在另一個HLL中,比如每月活躍用戶可以使用每天的活躍用戶來合并計算可得
6.3.Geospatial
6.3.1.簡介
Redis 3.2 中增加了對GEO類型的支持。GEO,Geographic,地理信息的縮寫。該類型,就是元素的2維坐標,在地圖上就是經緯度。redis基于該類型,提供了經緯度設置,查詢,范圍查詢,距離查詢,經緯度Hash等常見操作。
6.3.2.命令
1、geoadd
(1)格式
geoadd<key>< longitude><latitude><member> [longitude latitude member…] 添加地理位置(經度,緯度,名稱)
2、geopos
(1)格式
geopos <key><member> [member…] 獲得指定地區的坐標值
3、geodist
(1)格式
geodist<key><member1><member2> [m|km|ft|mi ] 獲取兩個位置之間的直線距離
4、georadius
(1)格式
georadius<key>< longitude><latitude>radius m|km|ft|mi 以給定的經緯度為中心,找出某一半徑內的元素
7.Redis_Jedis_測試
Jedis所需要的jar包
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>3.2.0</version> </dependency>7.2.連接Redis注意事項
禁用Linux的防火墻:Linux(CentOS7)里執行命令
systemctl stop/disable firewalld.service
redis.conf中注釋掉bind 127.0.0.1 ,然后 protected-mode no
9.Redis與Spring Boot整合
Spring Boot整合Redis非常簡單,只需要按如下步驟整合即可
9.1.整合步驟
1、在pom.xml文件中引入redis相關依賴
<!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency><!-- spring2.X集成redis所需common-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version>2.6.0</version> </dependency>2、application.properties配置redis配置
#Redis服務器地址 spring.redis.host=192.168.140.136 #Redis服務器連接端口 spring.redis.port=6379 #Redis數據庫索引(默認為0) spring.redis.database= 0 #連接超時時間(毫秒) spring.redis.timeout=1800000 #連接池最大連接數(使用負值表示沒有限制) spring.redis.lettuce.pool.max-active=20 #最大阻塞等待時間(負數表示沒限制) spring.redis.lettuce.pool.max-wait=-1 #連接池中的最大空閑連接 spring.redis.lettuce.pool.max-idle=5 #連接池中的最小空閑連接 spring.redis.lettuce.pool.min-idle=03、添加redis配置類
@EnableCaching @Configuration public class RedisConfig extends CachingConfigurerSupport {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {RedisTemplate<String, Object> template = new RedisTemplate<>();RedisSerializer<String> redisSerializer = new StringRedisSerializer();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);template.setConnectionFactory(factory); //key序列化方式template.setKeySerializer(redisSerializer); //value序列化template.setValueSerializer(jackson2JsonRedisSerializer); //value hashmap序列化template.setHashValueSerializer(jackson2JsonRedisSerializer);return template;}@Beanpublic CacheManager cacheManager(RedisConnectionFactory factory) {RedisSerializer<String> redisSerializer = new StringRedisSerializer();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); // 配置序列化(解決亂碼的問題),過期時間600秒RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600)).serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(factory).cacheDefaults(config).build();return cacheManager;} }測試一下
RedisTestController中添加測試方法
10.Redis_事務_鎖機制_秒殺
10.1.Redis的事務定義
Redis事務是一個單獨的隔離操作:事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。
Redis事務的主要作用就是串聯多個命令防止別的命令插隊。
10.2.Multi、Exec、discard
從輸入Multi命令開始,輸入的命令都會依次進入命令隊列中,但不會執行,直到輸入Exec后,Redis會將之前的命令隊列中的命令依次執行。
組隊的過程中可以通過discard來放棄組隊。
案例:
組隊成功,提交成功
組隊階段報錯 ERR,提交失敗
組隊成功,提交有成功有失敗情況
10.3.事務的錯誤處理
組隊中某個命令出現了報告錯誤,執行時整個的所有隊列都會被取消。
如果執行階段某個命令報出了錯誤,則只有報錯的命令不會被執行,而其他的命令都會執行,不會回滾。
10.4.為什么要做成事務
想想一個場景:有很多人有你的賬戶,同時去參加雙十一搶購
10.5.事務沖突的問題
10.5.1.例子
一個請求想給金額減8000
一個請求想給金額減5000
一個請求想給金額減1000
10.5.2.悲觀鎖
悲觀鎖(Pessimistic Lock), 顧名思義,就是很悲觀,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。
10.5.3.樂觀鎖
樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量。Redis就是利用這種check-and-set機制實現事務的。
10.5.4.WATCH key [key …]
在執行multi之前,先執行watch key1 [key2],可以監視一個(或多個) key ,如果在事務執行之前這個(或這些) key 被其他命令所改動,那么事務將被打斷。
10.5.5.unwatch
取消 WATCH 命令對所有 key 的監視。
如果在執行 WATCH 命令之后,EXEC 命令或DISCARD 命令先被執行了的話,那么就不需要再執行UNWATCH 了。
http://doc.redisfans.com/transaction/exec.html
10.6.Redis事務三特性
?單獨的隔離操作 ?事務中的所有命令都會序列化、按順序地執行。事務在執行的過程中,不會被其他客戶端發送來的命令請求所打斷。 ?沒有隔離級別的概念 ?隊列中的命令沒有提交之前都不會實際被執行,因為事務提交前任何指令都不會被實際執行?不保證原子性 ?事務中如果有一條命令執行失敗,其后的命令仍然會被執行,沒有回滾11.Redis應用問題解決
11.1.緩存穿透
11.1.1.問題描述
key對應的數據在數據源并不存在,每次針對此key的請求從緩存獲取不到,請求都會壓到數據源,從而可能壓垮數據源。比如用一個不存在的用戶id獲取用戶信息,不論緩存還是數據庫都沒有,若黑客利用此漏洞進行攻擊可能壓垮數據庫。
11.1.2.解決方案
一個一定不存在緩存及查詢不到的數據,由于緩存是不命中時被動寫的,并且出于容錯考慮,如果從存儲層查不到數據則不寫入緩存,這將導致這個不存在的數據每次請求都要到存儲層去查詢,失去了緩存的意義。
解決方案:(1)對空值緩存:如果一個查詢返回的數據為空(不管是數據是否不存在),我們仍然把這個空結果(null)進行緩存,設置空結果的過期時間會很短,最長不超過五分鐘(2)設置可訪問的名單(白名單):使用bitmaps類型定義一個可以訪問的名單,名單id作為bitmaps的偏移量,每次訪問和bitmap里面的id進行比較,如果訪問id不在bitmaps里面,進行攔截,不允許訪問。(3)采用布隆過濾器:(布隆過濾器(Bloom Filter)是1970年由布隆提出的。它實際上是一個很長的二進制向量(位圖)和一系列隨機映射函數(哈希函數)。布隆過濾器可以用于檢索一個元素是否在一個集合中。它的優點是空間效率和查詢時間都遠遠超過一般的算法,缺點是有一定的誤識別率和刪除困難。)將所有可能存在的數據哈希到一個足夠大的bitmaps中,一個一定不存在的數據會被 這個bitmaps攔截掉,從而避免了對底層存儲系統的查詢壓力。(4)進行實時監控:當發現Redis的命中率開始急速降低,需要排查訪問對象和訪問的數據,和運維人員配合,可以設置黑名單限制服務11.2.緩存擊穿
11.2.1.問題描述
key對應的數據存在,但在redis中過期,此時若有大量并發請求過來,這些請求發現緩存過期一般都會從后端DB加載數據并回設到緩存,這個時候大并發的請求可能會瞬間把后端DB壓垮。
11.2.2.解決方案
key可能會在某些時間點被超高并發地訪問,是一種非?!盁狳c”的數據。這個時候,需要考慮一個問題:緩存被“擊穿”的問題。
解決問題:
(1)預先設置熱門數據:在redis高峰訪問之前,把一些熱門數據提前存入到redis里面,加大這些熱門數據key的時長
(2)實時調整:現場監控哪些數據熱門,實時調整key的過期時長
(3)使用鎖:
(1)就是在緩存失效的時候(判斷拿出來的值為空),不是立即去load db。
先使用緩存工具的某些帶成功操作返回值的操作(比如Redis的SETNX)
(2)去set一個mutex key
(3)當操作返回成功時,再進行load db的操作,并回設緩存,最后刪除mutex key;
(4)當操作返回失敗,證明有線程在load db,當前線程睡眠一段時間再重試整個get緩存的方法。
11.3.緩存雪崩
11.3.1.問題描述
key對應的數據存在,但在redis中過期,此時若有大量并發請求過來,這些請求發現緩存過期一般都會從后端DB加載數據并回設到緩存,這個時候大并發的請求可能會瞬間把后端DB壓垮。
緩存雪崩與緩存擊穿的區別在于這里針對很多key緩存,前者則是某一個key
正常訪問
緩存失效瞬間
11.3.2.解決方案
緩存失效時的雪崩效應對底層系統的沖擊非??膳?#xff01;
解決方案:
(1)構建多級緩存架構:nginx緩存 + redis緩存 +其他緩存(ehcache等)
(2)使用鎖或隊列:
用加鎖或者隊列的方式保證來保證不會有大量的線程對數據庫一次性進行讀寫,從而避免失效時大量的并發請求落到底層存儲系統上。不適用高并發情況
(3)設置過期標志更新緩存:
記錄緩存數據是否過期(設置提前量),如果過期會觸發通知另外的線程在后臺去更新實際key的緩存。
(4)將緩存失效時間分散開:
比如我們可以在原有的失效時間基礎上增加一個隨機值,比如1-5分鐘隨機,這樣每一個緩存的過期時間的重復率就會降低,就很難引發集體失效的事件。
11.4.分布式鎖
11.4.1.問題描述
隨著業務發展的需要,原單體單機部署的系統被演化成分布式集群系統后,由于分布式系統多線程、多進程并且分布在不同機器上,這將使原單機部署情況下的并發控制鎖策略失效,單純的Java API并不能提供分布式鎖的能力。為了解決這個問題就需要一種跨JVM的互斥機制來控制共享資源的訪問,這就是分布式鎖要解決的問題!
11.4.2.解決方案:使用redis實現分布式鎖
redis:命令
set sku:1:info “OK” NX PX 10000
EX second :設置鍵的過期時間為 second 秒。 SET key value EX second 效果等同于 SETEX key second value 。
PX millisecond :設置鍵的過期時間為 millisecond 毫秒。 SET key value PX millisecond 效果等同于 PSETEX key millisecond value 。
NX :只在鍵不存在時,才對鍵進行設置操作。 SET key value NX 效果等同于 SETNX key value 。
XX :只在鍵已經存在時,才對鍵進行設置操作。
11.4.3.編寫代碼
Redis: set num 0
@GetMapping("testLock") public void testLock(){//1獲取鎖,setneBoolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111");//2獲取鎖成功、查詢num的值if(lock){Object value = redisTemplate.opsForValue().get("num");//2.1判斷num為空returnif(StringUtils.isEmpty(value)){return;}//2.2有值就轉成成intint num = Integer.parseInt(value+"");//2.3把redis的num加1redisTemplate.opsForValue().set("num", ++num);//2.4釋放鎖,delredisTemplate.delete("lock");}else{//3獲取鎖失敗、每隔0.1秒再獲取try {Thread.sleep(100);testLock();} catch (InterruptedException e) {e.printStackTrace();}} }問題: setnx剛好獲取到鎖,業務邏輯出現異常,導致鎖無法釋放
解決: 設置過期時間,自動釋放鎖。
11.4.4.優化之設置鎖的過期時間
設置過期時間有兩種方式:
設置過期時間:
壓力測試肯定也沒有問題。自行測試
問題:可能會釋放其他服務器的鎖。
場景:如果業務邏輯的執行時間是7s。執行流程如下
1.index1業務邏輯沒執行完,3秒后鎖被自動釋放。
2.index2獲取到鎖,執行業務邏輯,3秒后鎖被自動釋放。
3.index3獲取到鎖,執行業務邏輯
4.index1業務邏輯執行完成,開始調用del釋放鎖,這時釋放的是index3的鎖,導致index3的業務只執行1s就被別人釋放。
最終等于沒鎖的情況。
解決:setnx獲取鎖時,設置一個指定的唯一值(例如:uuid);釋放前獲取這個值,判斷是否自己的鎖
11.4.5.優化之UUID防誤刪
問題:刪除操作缺乏原子性。
場景:
1.index1執行刪除時,查詢到的lock值確實和uuid相等
uuid=v1
set(lock,uuid);
2.index1執行刪除前,lock剛好過期時間已到,被redis自動釋放
在redis中沒有了lock,沒有了鎖。
3.index2獲取了lock
index2線程獲取到了cpu的資源,開始執行方法
uuid=v2
set(lock,uuid);
4.index1執行刪除,此時會把index2的lock刪除
index1 因為已經在方法中了,所以不需要重新上鎖。index1有執行的權限。index1已經比較完成了,這個時候,開始執行
刪除的index2的鎖!
注意:
為了確保分布式鎖可用,我們至少要確保鎖的實現同時滿足以下四個條件:
- 互斥性。在任意時刻,只有一個客戶端能持有鎖。
- 不會發生死鎖。即使有一個客戶端在持有鎖的期間崩潰而沒有主動解鎖,也能保證后續其他客戶端能加鎖。
- 解鈴還須系鈴人。加鎖和解鎖必須是同一個客戶端,客戶端自己不能把別人加的鎖給解了。
- 加鎖和解鎖必須具有原子性。
總結
以上是生活随笔為你收集整理的Redis 学习笔记-NoSQL数据库 常用五大数据类型 Redis配置文件介绍 Redis的发布和订阅 Redis_事务_锁机制_秒杀 Redis应用问题解决 分布式锁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 前端学习笔记之-VUE框架学习-Vue核
- 下一篇: 数据库引起的性能瓶颈应如何优化?