一些小問題
Redis一共有幾種數據類型?(注意是數據類型
不是數據結構)
一共是八種,String、Hash、Set、List、Zset、Hyperloglog、Geo、Streams。
為什么要把數據放在內存中?
內存的速度更快,10W QPS
減少計算的時間,減輕數據庫壓力
如果是用內存的數據結構作為緩存,為什么不用HashMap或者Memcached?
更豐富的數據類型
支持多種編程語言
功能豐富:持久化機制、內存淘汰策略、事務、發布訂閱、pipeline、LUA
支持集群、分布式
Memcached和redis的區別是什么?
Memcached只能存儲KV、沒有持久化機制、不支持主從復制、是多線程的。
Redis Key的最大長度限制是512M,值的限制不同,有的是用長度限制的,有的是用個數限制的。
關於其他有趣的 Reids 的內容可以移步這兒:https://www.cnblogs.com/zwtblog/tag/Redis/
String
get和set命令就是String的操作命令,Redis的字符串被叫做二進制安全的字符串(Binary-safe strings)。
String可以存儲三種類型,INT(整數)、float(單精度浮點數)、string(字符串)
操作命令
# 存值(如果對同一個key set多次會直接覆蓋舊值)
set jack 2673
# 取值
get jack
# 查看所有鍵
keys *
# 獲取鍵總數(生產環境數據量大,慎用)
dbsize
# 查看鍵是否存在
exists jack
# 刪除鍵
del jack tonny
# 重命名鍵
rename jack tonny
# 查看類型
type jack
# 獲取指定范圍的字符
getrange jack 0 1
# 獲取值長度
strlen jack
# 字符串追加內容
append jack good
# 設置多個值(批量操作,原子性)
mset jack 2673 tonny 2674
# 獲取多個值
mget jack tonny
# 設置值,如果key存在,則不成功
setnx jack shuaige
# 基於此實現分布式鎖
set key value [expiration EX seconds|PX milliseconds][NX|XX]
# (整數)值遞增(值不存在會得到1)
incr jack
incrby jack 100
# (整數)值遞減
decr jack
decrby jack 100
# 浮點數增量
set mf 2.6
incrbyfloat mf 7.3
應用場景
1、緩存
String類型,這是最常用的,可以緩存一些熱點數據,比如首頁新聞,可以顯著提升熱點數據的訪問速度,同時減輕DB壓力。
2、分布式數據共享
String 類型,因為Redis是分布式的獨立服務,可以在多個應用之間共享。
例如:分布式Session
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
3、分布式鎖
詳情見博客:https://www.cnblogs.com/zwtblog/p/15185894.html
4、全局ID
INT類型,INCRBY,利用原子性
incrby userid 1000(分表分庫的場景,一次性拿一段)
5、計數器
INT類型,INCR方法
例如:文章的閱讀量,微博點贊數,允許一定的延遲,先寫入Redis再定時同步到數據庫
6、限流
INT類型,INCR方法
以訪問者的IP和其他信息作為key,訪問一次增加一次計數,超過次數則返回false。
Hash
這樣也便於集中管理,划分的粒度不同,可以按照實際場景,key 的過期時間,靈活度考慮選取哪一種存儲方式。
Hash用來存儲多個無序的鍵值對,最大存儲數量2^32-1(40億左右)。
優點:
- 把所有相關的值聚集到一個Key中,節省內存空間
- 只使用一個Key,減少Key沖突
- 當需要批量獲取值的時候,只需要使用一個命令,減少內存/IO/CPU的消耗
缺點:
- Field不能單獨設置過期時間
- 需要考慮數據量分布的問題(field非常多的時候,無法分布到多個節點)
操作命令
# 設置、批量設置值
hset h1 f 6
hset h1 e 5
hmset h1 a 1 b 2 c 3 d 4
# 取值
hget h1 a
# 批量取值
hmget h1 a b c d
# 獲取所有field
hkeys h1
# 獲取所有field的值
hvals h1
# 返回哈希表中,所有的字段和值
hgetall h1
# 刪除field
hdel h1 a
# 獲取哈希表中字段的數量
hlen h1
應用場景
String可以做的事情,Hash都可以做。
再補充一個場景,購物車
List
存儲有序的字符串(從左到右),元素可以重復,最大存儲數量2^32-1(40億左右)。
操作命令
# 左推
lpush queue a
lpush queue b c
# 右推
rpush queue d e
# 左邊移除並返回列表的第一個元素
lpop queue
# 右邊移除並返回列表的第一個元素
rpop queue
# 通過索引獲取列表中的元素
lindex queue 0
# 返回列表中指定區間內的元素
lrange queue 0 -1
應用場景
1、列表
例如用戶的消息列表、網站的公告列表、活動列表、博客的文章列表、評論列表等,通過 LRANGE 取出一頁,按順序顯示。
2、隊列/棧
List還可以當做分布式環境的 隊列 / 棧 使用。
隊列:先進先出,rpush 和 blpop
棧:先進后出,rpush 和 brpop
這里介紹兩個阻塞的彈出操作:blpop/brpop,可以設置超時時間(單位:秒)
blpop:blpop key1 timeout,
移出並獲取列表的第一個元素,
如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。
brpop:brpop key1 timeout,
移出並獲取列表的最后一個元素,
如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止。
Set
Set 存儲 String 類型的無序集合,最大存儲數量 2^32-1(40億左右)。
操作命令
# 添加一個或多個元素
sadd myset a b c d e f g
# 獲取所有元素
smembers myset
# 統計元素個數
scard myset
# 隨機獲取一個元素
srandmember myset
# 隨機彈出一個元素
spop myset
# 移除一個或者多個元素
srem myset d e f
# 查看元素是否存在
sismember myset a
# 獲取差集
sdiff set1 set2
# 獲取交集
sinter set1 set2
# 獲取並集
sunion set1 set2
應用場景
1、抽獎
隨機獲取元素:spop myset
2、點贊、簽到、打卡
我們以微博舉例子,假設這條微博的ID是t1001,用戶ID是u6001,
用 dianzan:t1001 來維護 t1001 這條微博的所有點贊用戶。
點贊了這條微博:sadd dianzan:t1001 u6001
取消點贊:srem dianzan:t1001 u6001
是否點贊:sismember dianzan:t1001 u6001
點贊的所有用戶:smembers dianzan:t1001
點贊數:scard dianzan:t1001
比關系型數據庫簡單了許多。
3、商品標簽
用 tags : i8001 來維護商品所有的標簽。
sadd tags:i8001 畫面清晰細膩
sadd tags:i8001 真彩清晰顯示屏
sadd tags:i8001 流暢至極
4、商品篩選
華為P50上線了,支持民族品牌,加到各個標簽中去。
sadd brand:huawei p50
sadd os:android p50
sadd screensize:6.0-6.24 p50
買的時候篩選,牌子是華為,操作系統是安卓,屏幕大小在6.0-6.24之間的,取交集:
sinter brand:huawei os:android screensize:6.0-6.24
ZSet
sorted set 存儲有序的元素。每個元素都有個 score,按照 score 從小到大排序。
score 相同時,按照 key 的ASCII碼排序。
操作命令
# 添加元素
zadd myzset 10 java 20 php 30 ruby 40 cpp 50 python
# 獲取全部元素
zrange myset 0 -1 withscores
zrevrange myzset 0 -1 withscores
# 根據分數區間獲取元素
zrangebyscore myzset 20 30
# 移除元素(也可以根據score rank刪除)
zrem myzset php cpp
# 統計元素個數
zcard myzset
# 分值增加
zincrby myzset 5 python
# 根據分值min和max統計個數
zcount myzset 20 60
# 獲取python排名
zrank myzset python
# 獲取元素分數
zscore myzset python
應用場景
1、排行榜
今天是2021年5月23號,建一個 key 為 hotSearch:20210523 的 zset。
這條新聞的id是n1234,每點擊一下:zincrby hotSearch:20210523 1 n1234
獲取熱搜排行榜前十條:zrevrange hotSearch:20210523 0 10 withscores
BitMaps
BitMaps是在字符串類型上定義的位操作,一個字節由8個二進制位組成。
操作命令
# 設置字符串key為k1,value為mic
set k1 mic
# 取k1的第七位,結果是0
getbit k1 6
# 取k1的第八位為0,此時的ASCII碼是108,對應字母是l
setbit k1 7 0
# 所以取出來值為lic
get k1
# 統計二進制中1的個數,一共是12個
bitcount k1
# 獲取第一個1或者0的位置
bitpos k1 1
bitpos k1 1
BITOP AND destkey key [key ...],對一個或多個key求邏輯並,並將結果保存到 destkey。
BITOP OR destkey key [key ...],對一個或多個key求邏輯或,並將結果保存到 destkey。
BITOP XOR destkey key [key ...],對一個或多個key求邏輯異或,並將結果保存到 destkey。
BITOP NOT destkey key,對給定key求邏輯非,並將結果保存到 destkey。
應用場景
1、連續在線用戶
setbit firstday 0 1 //設置第一天uid是0的用戶登錄
setbit firstday 1 0 //設置第一天uid是1的用戶未登錄
setbit firstday 2 1 //設置第一天uid是2的用戶登錄
...
setbit secondday 0 0 //設置第二天uid是0的用戶未登錄
setbit secondday 1 1 //設置第二天uid是1的用戶登錄
setbit secondday 2 1 //設置第二天uid是2的用戶登錄
... //以此類推
那么在算連續七天在線用戶就是:
BITOP AND 7_both_online_users firstday secondday thirdday fourthday fifthday sixthday seventhday
2、應用訪問統計
3、在線用戶統計
Hyperloglog
Hyperloglog 提供了一種不太精確的基數統計方法,用來統計一個集合中不重復的元素個數,
在 Redis 中實現的 Hyperloglog,只需要12k內存就能統計2^64個數據。
public static void main(String[] args) {
Jedis jedis = new Jedis("39.103.144.86", 6379);
float size = 100000;
for (int i = 0; i < size; i++) {
jedis.pfadd("hll", "hll-" + i);
}
long total = jedis.pfcount("hll");
System.out.println(String.format("統計個數: %s", total));
System.out.println(String.format("正確率: %s", (total / size)));
System.out.println(String.format("誤差率: %s", 1 - (total / size)));
jedis.close();
}
操作命令
PFADD key element [element ...]
添加指定元素到 HyperLogLog 中。
PFCOUNT key [key ...]
返回給定 HyperLogLog 的基數估算值。
PFMERGE destkey sourcekey [sourcekey ...]
將多個 HyperLogLog 合並為一個 HyperLogLog
應用場景
1、統計網站的UV,或者應用的日活、月活,存在一定的誤差。
Geo
操作命令
# 存經緯度
geoadd location 121.445 31.213 shanghai
# 取經緯度
geopos location shanghai
應用場景
1、增加地址位置信息、獲取地址位置信息
2、計算兩個位置的距離
3、獲取指定范圍內的地理位置集合
public static void main(String[] args) {
Jedis jedis = new Jedis("39.103.144.86", 6379);
Map<String, GeoCoordinate> geoMap = new HashMap<>();
GeoCoordinate coordinate = new GeoCoordinate(121.445, 31.213);
geoMap.put("shanghai", coordinate);
jedis.geoadd("positions", geoMap);
System.out.println(jedis.geopos("positions", "shanghai"));
jedis.close();
}
Streams
Redis Stream 是 Redis 5.0 版本新增加的數據結構。
Redis Stream 主要用於消息隊列(MQ,Message Queue),Redis 本身是有一個 Redis 發布訂閱 (pub/sub) 來實現消息隊列的功能,但它有個缺點就是消息無法持久化,如果出現網絡斷開、Redis 宕機等,消息就會被丟棄。
簡單來說發布訂閱 (pub/sub) 可以分發消息,但無法記錄歷史消息。
而 Redis Stream 提供了消息的持久化和主備復制功能,可以讓任何客戶端訪問任何時刻的數據,並且能記住每一個客戶端的訪問位置,還能保證消息不丟失。
Redis Stream 的結構如下所示,它有一個消息鏈表,將所有加入的消息都串起來,每個消息都有一個唯一的 ID 和對應的內容:
每個 Stream 都有唯一的名稱,它就是 Redis 的 key,在我們首次使用 xadd 指令追加消息時自動創建。
上圖解析:
- Consumer Group :消費組,使用 XGROUP CREATE 命令創建,一個消費組有多個消費者(Consumer)。
- last_delivered_id :游標,每個消費組會有個游標 last_delivered_id,任意一個消費者讀取了消息都會使游標 last_delivered_id 往前移動。
- pending_ids :消費者(Consumer)的狀態變量,作用是維護消費者的未確認的 id。 pending_ids 記錄了當前已經被客戶端讀取的消息,但是還沒有 ack (Acknowledge character:確認字符)。
操作命令
消息隊列相關命令:
XADD - 添加消息到末尾
XTRIM - 對流進行修剪,限制長度
XDEL - 刪除消息
XLEN - 獲取流包含的元素數量,即消息長度
XRANGE - 獲取消息列表,會自動過濾已經刪除的消息
XREVRANGE - 反向獲取消息列表,ID 從大到小
XREAD - 以阻塞或非阻塞方式獲取消息列表
消費者組相關命令:
XGROUP CREATE - 創建消費者組
XREADGROUP GROUP - 讀取消費者組中的消息
XACK - 將消息標記為"已處理"
XGROUP SETID - 為消費者組設置新的最后遞送消息ID
XGROUP DELCONSUMER - 刪除消費者
XGROUP DESTROY - 刪除消費者組
XPENDING - 顯示待處理消息的相關信息
XCLAIM - 轉移消息的歸屬權
XINFO - 查看流和消費者組的相關信息;
XINFO GROUPS - 打印消費者組的信息;
XINFO STREAM - 打印流信息
應用場景
應用場景小結
緩存======提升熱點數據的訪問速度
共享數據======數據的存儲和共享的問題
全局ID======分布式全局ID的生成方案(分庫分表)
分布式鎖======進程間共享數據的原子操作保證
在線用戶統計和計數
隊列、棧======跨進程的隊列/棧
消息隊列======異步解耦的消息機制
服務注冊與發現======RPC通信機制的服務協調中心(Dubbo支持Redis)
購物車
新浪用戶消息時間線
抽獎邏輯(禮物、轉發)
點贊、簽到、打卡
商品標簽
用戶(商品)關注(推薦)模型
電商產品篩選
排行榜