一 Redis簡介
Redis是一個開源(BSD許可)的,用C語言編寫的基于內存的數據結構存儲系統(tǒng)(是一個高性能的 key-value存儲系統(tǒng))。而且會周期性的把更新的數據寫入磁盤或者把修改操作寫入追加的記錄文件,實現數據的持久化。Redis可以用在數據庫,緩存和消息中間件。
Redis官網:https://redis.io/
Redis中文官網:http://www.redis.cn/
1.1 Redis的特點
- 性能極高,Redis讀取的速度是110000次/s,寫的速度是81000次/s。
- 豐富的數據類型,支持多種數據結構:string(字符串);list(列表);hash(哈希),set(集合);zset(有序集合)。
- 原子性,Redis的所有操作都是原子性的,要么成功,要么失敗完全不執(zhí)行。多個操作也支持事務,即原子性,通過MULTI和EXEC指令包起來。
- 豐富的特性 – Redis還支持 publish/subscribe, 通知, key 過期等等特性。
- 支持持久化,主從復制,集群。
1.2 Redis的應用場景
數據緩存(熱數據):將一些在短時間內不會發(fā)生變化的數據,并且還要被頻繁訪問的數據保存在Redis中。從而提高我們的請求速度,降低數據庫的讀寫次數。
會話緩存:seccsion cache,保存web的會話信息。
位操作:用于數據量上億的場景下,例如幾億用戶系統(tǒng)的簽到,去重登錄次數統(tǒng)計,某用戶是否在線狀態(tài)等等。
消息隊列:相當于消息系統(tǒng),ActiveMQ,RocketMQ等工具類似,但是個人覺得簡單用一下還行,如果對于數據一致性要求高的話還是用RocketMQ等專業(yè)系統(tǒng)。
分布式鎖:秒殺系統(tǒng),全局增量ID的生產。
最新列表:例如新聞列表頁面最新的新聞列表,如果總數量很大的情況下,盡量不要使用select a from A limit 10這種low貨,嘗試redis的 LPUSH命令構建List,一個個順序都塞進去就可以啦。不過萬一內存清掉了咋辦?也簡單,查詢不到存儲key的話,用mysql查詢并且初始化一個List到redis中就好了。
二 Redis安裝
官網下載redis安裝包,這里我下載的是我目前最高的版本 redis-6.2.4.tar.gz
-
解壓(將redis解壓到/usr/local/目錄下)
# 將redis解壓到/usr/local/目錄下 tar -zxf redis-6.2.4.tar.gz -C /usr/local/ -
安裝
# 進入到解壓后的目錄/usr/local/redis-6.2.4/ cd /usr/local/redis-6.2.4/ # 編譯安裝 make make install # 修改配置文件redis.conf,當然你也可以直接手動去修改,為了方便一點這里直接用命令修改了 # 把bind 127.0.0.1 改為 bind 0.0.0.0 # daemonize no 改為 daemonize yes # logfile “” 改為logfile “/var/log/redis.log” sed -i "s/^bind 127.0.0.1/bind 0.0.0.0/g" redis.conf sed -i "s/^daemonize no/daemonize yes/g" redis.conf sed -i 's/^logfile \"\"/logfile \"\/var\/log\/redis.log\"/g' redis.conf -
把redis做成服務,方便開機啟動
添加/etc/init.d/redis文件并且輸入相應的內容,這里為了方便大家,我提供兩種方式
-
第一種,新建/etc/init.d/redis文件添加如下內容
# /etc/init.d/redis 輸入如下內容,保存退出(注意,EXEC,CLIEXEC幾個參數的修改) vi /etc/init.d/redis#!/bin/sh # chkconfig: 2345 10 90 # description:redis # processname:redis # Simple Redis init.d script conceived to work on Linux systems # as it does use of the /proc filesystem. REDISPORT=6379 EXEC=/usr/local/redis-6.2.4/src/redis-server CLIEXEC=/usr/local/redis-6.2.4/src/redis-cli PIDFILE=/var/run/redis_${REDISPORT}.pid CONF="/usr/local/redis-6.2.4/redis.conf" case "$1" in start) if [ -f $PIDFILE ] then echo "$PIDFILE exists, process is already running or crashed" else echo "Starting Redis server..." $EXEC $CONF & fi ;; stop) if [ ! -f $PIDFILE ] then echo "$PIDFILE does not exist, process is not running" else PID=$(cat $PIDFILE) echo "Stopping ..." $CLIEXEC -p $REDISPORT shutdown while [ -x /proc/${PID} ] do echo "Waiting for Redis to shutdown ..." sleep 1 done echo "Redis stopped" fi ;; status) if [ -f $PIDFILE ] then echo "Redis running" else echo "Redis stopped" fi ;; restart) if [ -f $PIDFILE ] then PID=$(cat $PIDFILE) echo "Stopping ..." $CLIEXEC -p $REDISPORT shutdown while [ -x /proc/${PID} ] do echo "Waiting for Redis to shutdown ..." sleep 1 done echo "Redis stopped" echo "Starting Redis server..." $EXEC $CONF & else echo "Redis stopped" echo "Starting Redis server..." $EXEC $CONF & fi ;; *) echo "require start|stop|status|restart" ;; esac
-
-
第二種,直接在命令行里面完成
# 直接用命令行創(chuàng)建/etc/init.d/redis文件,注意我這里是redis-6.2.4,如果你的不是的話記得替換掉 # 下面的命令你可以直接在命令行里面執(zhí)行。自動生成/etc/init.d/redis,并且寫入了相應的內容 echo '#!/bin/sh' >/etc/init.d/redis echo '# chkconfig: 2345 10 90' >>/etc/init.d/redis echo '# description:redis' >>/etc/init.d/redis echo '# processname:redis' >>/etc/init.d/redis echo '# Simple Redis init.d script conceived to work on Linux systems' >>/etc/init.d/redis echo '# as it does use of the /proc filesystem.' >>/etc/init.d/redis echo 'REDISPORT=6379' >>/etc/init.d/redis echo 'EXEC=/usr/local/redis-6.2.4/src/redis-server' >>/etc/init.d/redis echo 'CLIEXEC=/usr/local/redis-6.2.4/src/redis-cli' >>/etc/init.d/redis echo 'PIDFILE=/var/run/redis_${REDISPORT}.pid' >>/etc/init.d/redis echo 'CONF="/usr/local/redis-6.2.4/redis.conf"' >>/etc/init.d/redis echo 'case "$1" in' >>/etc/init.d/redis echo ' start)' >>/etc/init.d/redis echo ' if [ -f $PIDFILE ]' >>/etc/init.d/redis echo ' then' >>/etc/init.d/redis echo ' echo "$PIDFILE exists, process is already running or crashed"' >>/etc/init.d/redis echo ' else' >>/etc/init.d/redis echo ' echo "Starting Redis server..."' >>/etc/init.d/redis echo ' $EXEC $CONF &' >>/etc/init.d/redis echo ' fi' >>/etc/init.d/redis echo ' ;;' >>/etc/init.d/redis echo ' stop)' >>/etc/init.d/redis echo ' if [ ! -f $PIDFILE ]' >>/etc/init.d/redis echo ' then' >>/etc/init.d/redis echo ' echo "$PIDFILE does not exist, process is not running"' >>/etc/init.d/redis echo ' else' >>/etc/init.d/redis echo ' PID=$(cat $PIDFILE)' >>/etc/init.d/redis echo ' echo "Stopping ..."' >>/etc/init.d/redis echo ' $CLIEXEC -p $REDISPORT shutdown' >>/etc/init.d/redis echo ' while [ -x /proc/${PID} ]' >>/etc/init.d/redis echo ' do' >>/etc/init.d/redis echo ' echo "Waiting for Redis to shutdown ..."' >>/etc/init.d/redis echo ' sleep 1' >>/etc/init.d/redis echo ' done' >>/etc/init.d/redis echo ' echo "Redis stopped"' >>/etc/init.d/redis echo ' fi' >>/etc/init.d/redis echo ' ;;' >>/etc/init.d/redis echo ' status)' >>/etc/init.d/redis echo ' if [ -f $PIDFILE ]' >>/etc/init.d/redis echo ' then' >>/etc/init.d/redis echo ' echo "Redis running"' >>/etc/init.d/redis echo ' else' >>/etc/init.d/redis echo ' echo "Redis stopped"' >>/etc/init.d/redis echo ' fi' >>/etc/init.d/redis echo ' ;;' >>/etc/init.d/redis echo ' restart)' >>/etc/init.d/redis echo ' if [ -f $PIDFILE ]' >>/etc/init.d/redis echo ' then' >>/etc/init.d/redis echo ' PID=$(cat $PIDFILE)' >>/etc/init.d/redis echo ' echo "Stopping ..."' >>/etc/init.d/redis echo ' $CLIEXEC -p $REDISPORT shutdown' >>/etc/init.d/redis echo ' while [ -x /proc/${PID} ]' >>/etc/init.d/redis echo ' do' >>/etc/init.d/redis echo ' echo "Waiting for Redis to shutdown ..."' >>/etc/init.d/redis echo ' sleep 1' >>/etc/init.d/redis echo ' done' >>/etc/init.d/redis echo ' echo "Redis stopped"' >>/etc/init.d/redis echo ' echo "Starting Redis server..."' >>/etc/init.d/redis echo ' $EXEC $CONF &' >>/etc/init.d/redis echo ' else' >>/etc/init.d/redis echo ' echo "Redis stopped"' >>/etc/init.d/redis echo ' echo "Starting Redis server..."' >>/etc/init.d/redis echo ' $EXEC $CONF &' >>/etc/init.d/redis echo ' fi' >>/etc/init.d/redis echo ' ;;' >>/etc/init.d/redis echo ' *)' >>/etc/init.d/redis echo ' echo "require start|stop|status|restart"' >>/etc/init.d/redis echo ' ;;' >>/etc/init.d/redis echo 'esac' >>/etc/init.d/redis
修改 /etc/init.d/redis 文件權限
chmod 755 /etc/init.d/redis
-
添加redis服務
# 添加到服務列表 chkconfig --add redis # 設置開啟啟動 chkconfig redis on -
啟動redis
# 啟動redis # 如果報錯,env: “/etc/init.d/redis”: 權限不夠,記得修改/etc/init.d/redis文件的權限 # 如果報錯,/var/run/redis_6379.pid exists, process is already running or crashed,那么刪掉 rm -rf /var/run/redis_6379.pid 在試下 service redis start
三 Redis數據類型
Redis是key-value的存儲服務器(數據結構服務器),Redis的keys是二進制安全的,這一意味著你可以用任何二級制序列作為key值。Redis的value更加的強大,它支持不同類型的值。你不必僅僅可以把字符串當作value對應的值。下列這些數據類型都可作為value的類型:
- Strings:二進制安全的字符串。
- Lists:按插入順序排序的字符串元素的集合。他們基本上就是鏈表(linked lists)。
- Hashes:就是我們經常使用的Map。field和value都是字符串。
- Sets:不重復且無序的字符串元素的集合。
- Zsets(Sorted sets):類似Sets,但是每個字符串元素都關聯(lián)到一個叫score浮動數值(floating number value)。里面的元素總是通過score進行排序,所以不同的是,它可以對元素進行排序。(比如你可能會問:給我前面10個或者后面10個元素)。
- Bitmap(或者說 simply bitmaps):二進制數組。通過特殊的命令,我們可以將 String 值當作一系列 bits 處理,可以設置和清除單獨的 bits,數出所有設為 1 的 bits 的數量,找到最前的被設為 1 或 0 的 bit,等等。Bit arrays屬于Strings類型。
- HyperLogLogs(基數):HyperLogLogs是一種基數估算算法,所謂基數估算,就是估算在一批數據中,不重復元素的個數有多少。
- Geospatial(地理空間):Redis提供的地理空間以及索引半徑查詢的功能的數據結構。這在需要地理位置的應用上或許可以一展身手。
二進制安全的字符串:不僅可以保存文本,還可以保存任意格式的二進制數據。
Strings,Lists,Hashes,Sets,Zsets是Redis里面的五大基本數據類型。Bitmap,HyperLogLogs,Geospatial數據擴展數據類型。
3.1 Strings(二進制安全的字符串)
Redis Strings 二進制安全的字符串。意味著Redis的Strings可以包含任何數據。比如jpg圖片或者序列化對象。Strings類型的值最大能存儲512MB。
3.1.1 Strings常用操作
# 字符串常用操作
set key value # 設置字符串鍵值對
mset key value [key value ...] # 批量存入字符串鍵值對
mget key [key ...] # 批量獲取字符串鍵值
setnx key value # 存入一個不存在的字符串鍵值對
get key # 獲取一個字符串鍵值
del key [key ...] # 刪除一個鍵
expire key seconds # 設置一個鍵的過期時間
# 原子加減操作
incr key # 將key中存儲的數字值加1
decr key # 將key中存儲的數字值減1
incrby key increment # 將key中存儲的數字值加increment
decrby key decrement # 將key中存儲的數字值加decrement
incrbyfloat key increment # 將key中存儲的值加上指定的浮點數
apend key value # 在尾部追加
getrange key start end # 獲取指定key中存儲的value范圍內的值
setrange key offset value # 設置指定key中存儲的value從索引位置開始設置后面的值
strlen key # 返回指定key對應value的長度
bitcount key [start end] # 統(tǒng)計指定位區(qū)間上值為1的個數
getbit key offset # 指定key對應value,獲取指定位置的二進制位的值
setbit key offset value # 指定key對應value,設置指定位置的二進制位的值
bitop operation destkey key [key ...] # 對一個或多個保存二進制位的字符串key進行位元操作,并將結果保存到destkey上,operation可以是AND、OR、NOT、XOR這四種操作中的任意一種
3.1.2 Strings應用場景
- 存儲對象:利用JSON強大的兼容性、可讀性和易用性,將對象轉換為JSON字符串,再存儲在string類型中,是個不錯的選擇。
- 分布式鎖:String類型的setnx的作用是“當key不存在時,設值并返回1,當key已經存在時,不設值并返回0”,“判斷key是否存在”和“設值”兩個操作是原子性地執(zhí)行的,因此可以用String類型作為分布式鎖,返回1表示獲得鎖,返回0表示沒有獲得鎖。
- 計數器:String類型的incr和decr命令的作用是將key中儲存的數字值加一/減一,這兩個操作具有原子性,總能安全地進行加減操作,因此可以用String類型進行計數,如微博的評論數、點贊數、分享數,抖音作品的收藏數,京東商品的銷售量、評價數等。
3.2 Lists(列表)
Redis Lists 列表,列表是簡單的字符串列表,按照插入順序排序。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)。Lists類型經常會被用于消息隊列的服務,以完成多程序之間的消息交換。列表最多可存儲-1 元素 (4294967295, 每個列表可存儲40多億)。Reids里面Lists是基于Linked Lists實現。這意味著即使在一個List中有數百萬個元素,在頭部或尾部添加一個元素的操作,其時間復雜度也是常數級別的。當然也有缺點,因為是基于Linked Lists實現的,所以訪問元素就沒那么快(數組的實現,訪問元素是很快的)。
3.2.1 Lists常用操作
lpush key element [element ...] # 將一個或多個值插入到列表頭部(最左邊)
rpush key element [element ...] # 將一個或多個值插入到列表的尾部(最右邊)
lrange key start stop # 返回列表中指定區(qū)間內的元素
lindex key index # 用于通過索引獲取列表中的元素
lpop key [count] # 用于移除列表的第一個元素(左邊),返回值為移除的元素
rpop key [count] # 用于移除列表的最后一個元素(右邊),返回值為移除的元素
llen key # 返回列表的長度
lrem key count element # 根據參數 COUNT 的值,移除列表中與參數 element 相等的元素。count > 0 : 從表頭開始向表尾搜索,移除與 element 相等的元素,數量為 COUNT ;count < 0 : 從表尾開始向表頭搜索,移除與 element 相等的元素,數量為 COUNT 的絕對值;count = 0 : 移除表中所有與 VALUE 相等的值
blpop key [key ...] timeout # 移出并獲取列表的第一個元素(左邊), 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現可彈出元素為止
brpop key [key ...] timeout # 移出并獲取列表的最后一個元素(右邊), 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現可彈出元素為止
rpoplpush source destination # 用于移除列表的最后一個元素,并將該元素添加到另一個列表并返回
brpoplpush source destination timeout # 從列表中取出最后一個元素,并插入到另外一個列表的頭部; 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現可彈出元素為止。
3.2.2 Lists應用場景
- 消息隊列。list類型的lpop和rpop能實現隊列的功能。不過如果系統(tǒng)比較復雜要求比較高的話,還是推薦用Kafka,RabbitMQ等成熟的消息隊列。
- 文章列表或者數據分頁展示的應用。比如,我們常用的博客網站的文章列表,當用戶量越來越多時,而且每一個用戶都有自己的文章列表,而且當文章多時,都需要分頁展示,這時可以考慮使用redis的列表,列表不但有序同時還支持按照范圍內獲取元素,可以完美解決分頁查詢功能。大大提高查詢效率。
3.3 Hashes(哈希)
Redis Hashes 哈希。Hash是由鍵值對組成的Map。是string 類型的 field 和 value 的映射表,Hash特別適合用于存儲對象。每個 hash 可以存儲-1 鍵值對(40多億)。
3.3.1 Hashes常用操作
hset key field value [field value ...] # 用于為哈希表中的字段賦值
hmset key field value [field value ...] # 用于同時將多個 field-value (字段-值)對設置到哈希表中
hget key field # 用于返回哈希表中指定字段的值
hmget key field [field ...] # 用于返回哈希表中,一個或多個給定字段的值
hgetall key # 用于返回哈希表中,所有的字段和值
hdel key field [field ...] # 用于刪除哈希表 key 中的一個或多個指定字段,不存在的字段將被忽略
hlen key # 用于返回hask里面指定key對應值的長度
hexists key field # 用于查看哈希表的指定字段是否存在
hkeys key # 用于獲取哈希表中的所有域
hvals key # 返回哈希表所有的值
hincrby key field increment # 用于為哈希表中的字段值加上指定增量值
hincrbyfloat key field increment # 用于為哈希表中的字段值加上指定浮點數增量值
hsetnx key field value # 用于為哈希表中不存在的的字段賦值
3.3.2 Hashes應用場景
- 存儲對象。因為對象有多個屬性,正好可以用Map表示。
3.4 Sets(集合)
Redis Set是string類型的無序集合。和列表一樣,在執(zhí)行插入和刪除和判斷是否存在某元素時,效率是很高的。集合最大的優(yōu)勢在于可以進行交集并集差集操作。Set可包含的最大元素數量是-1(4294967295)。集合是通過哈希表實現的,所以添加,刪除,查找的復雜度都是O(1)。
3.4.1 Sets常用操作
sadd key member [member ...] # 將一個或多個成員元素加入到集合中,已經存在于集合的成員元素將被忽略
smembers key # 返回集合中的所有的成員。 不存在的集合 key 被視為空集合
sismember key member # 判斷成員元素是否是集合的成員
srem key member [member ...] # 用于移除集合中的一個或多個成員元素,不存在的成員元素會被忽略
scard key
srandmember key [count] # 用于返回集合中的一個隨機元素
spop key [count] # 用于移除集合中的指定 key 的一個或多個隨機元素,移除后會返回移除的元素
smove source destination member # 將指定成員 member 元素從 source 集合移動到 destination 集合
sdiff key [key ...] # 返回第一個集合與其他集合之間的差異
sdiffstore destination key [key ...] # 將給定集合之間的差集存儲在指定的集合中。如果指定的集合 key 已存在,則會被覆蓋
sinter key [key ...] # 返回給定所有給定集合的交集。 不存在的集合 key 被視為空集。 當給定集合當中有一個空集時,結果也為空集(根據集合運算定律)
sinterstore destination key [key ...] # 將給定集合之間的交集存儲在指定的集合中。如果指定的集合已經存在,則將其覆蓋
sunion key [key ...] # 返回給定集合的并集。不存在的集合 key 被視為空集
sunionstore destination key [key ...] # 將給定集合的并集存儲在指定的集合 destination 中。如果 destination 已經存在,則將其覆蓋
3.4.2 Sets應用場景
- 隨機展示。
- 好友/關注/粉絲/感興趣的人集合。(交集,并集,合集,差集的特性)。
- 黑名單/白名單。通用需要判斷用戶是否在白名單或者黑名單中。
3.5 Zsets(Sorted sets有序集合)
Redis Zsets和 Sets一樣也是string類型元素的集合,且不允許重復的成員。不同的是Zsets每個元素都會關聯(lián)一個double類型的分數。Redis正是通過分數來為集合中的成員進行從小到大的排序。
3.5.1 Zsets常用操作
zadd key [NX|XX] [GT|LT] [CH] [INCR] score member [score member ...] # 用于將一個或多個成員元素及其分數值加入到有序集當中
zrange key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] # 返回有序集中,指定區(qū)間內的成員
zrangebyscore key min max [WITHSCORES] [LIMIT offset count] # 返回有序集合中指定分數區(qū)間的成員列表。有序集成員按分數值遞增(從小到大)次序排列
zrem key member [member ...] # 刪除指定的一個或多個成員
zcard key # 查看集合的成員個數
zincrby key increment member # 對指定元素的分數進行增減操作,負數為減,正數為加
zcount key min max # 用于計算有序集合中指定分數區(qū)間的成員數量
zrank key member # 獲取指定成員的小標位置(排序)
zscore key member # 獲取指定成員的分數
zrevrank key member # 返回有序集中成員的排名。其中有序集成員按分數值遞減(從大到小)排序
zrevrange key start stop [WITHSCORES] # 倒序,從高到底排序輸出指定范圍的數據
zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count] # 返回有序集中指定分數區(qū)間內的所有的成員。有序集成員按分數值遞減(從大到小)的次序排列。具有相同分數值的成員按字典序的逆序(reverse lexicographical order )排列
zremrangebyrank key start stop # 根據坐標范圍刪除數據
zremrangebyscore key min max # 根據分數范圍刪除數據
zinterstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] #計算給定的一個或多個有序集的交集,其中給定 key 的數量必須以 numkeys 參數指定,并將該交集(結果集)儲存到 destination
zunionstore destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] # 計算給定的一個或多個有序集的并集,其中給定 key 的數量必須以 numkeys 參數指定,并將該并集(結果集)儲存到 destination
3.5.2 Zsets應用場景
延時隊列,zset會按score進行排序,如果score代表想要執(zhí)行的時間戳。在某個時間將它插入zset集合中,它變會按照時間戳大小進行排序,也就是對執(zhí)行時間前后進行排序。
排行榜
限流。滑動窗口是限流常見的一種策略。如果我們把一個用戶的 ID 作為 key 來定義一個 zset ,member 或者 score 可以都為訪問時的時間戳。我們只需統(tǒng)計某個 key 下在指定時間戳區(qū)間內的個數,就能得到這個用戶滑動窗口內訪問頻次,與最大通過次數比較,來決定是否允許通過。
3.6 Bitmap(二進制位數組)
Redis Bitmap(二進制位數組)屬于Strings類型的一個擴展功能。可以設置和清除單獨的 bits,數出所有設為 1 的 bits 的數量,找到最前的被設為 1 或 0 的 bit,等等。
Bitmap有非常多的優(yōu)點:
- 基于最小的單位bit進行存儲,所以非常省空間。
- 設置時候時間復雜度O(1)、讀取時候時間復雜度O(n),操作是非??斓摹?/li>
- 二進制數據的存儲,進行相關計算的時候非常快。
- 方便擴容
Bitmap缺點:
Redis中的bit映射被限制在512MB內,所以最大是位。
3.6.1 Bitmap常用操作
bitcount key [start end] # 統(tǒng)計指定位區(qū)間上值為1的個數
getbit key offset # 指定key對應value,獲取指定位置的二進制位的值
setbit key offset value # 指定key對應value,設置指定位置的二進制位的值
bitop operation destkey key [key ...] # 對一個或多個保存二進制位的字符串key進行位元操作,并將結果保存到destkey上,operation可以是AND、OR、NOT、XOR這四種操作中的任意一種
3.6.2 Bitmap應用場景
- 用戶在線狀態(tài),用戶id為偏移量offset,如果在線就設置為1,不在線就設置為0。
- 統(tǒng)計活躍用戶。
- 用戶簽到。
3.7 HyperLogLogs(基數)
Redis HyperLogLogs HyperLogLogs是一種基數估算算法,所謂基數估算,就是估算在一批數據中,不重復元素的個數有多少。HyperLogLog 的優(yōu)點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、并且是很小的。在 Redis 里面,每個 HyperLogLog 鍵只需要花費 12 KB 內存,就可以計算接近 個不同元素的基 數。這和計算基數時,元素越多耗費內存就越多的集合形成鮮明對比。但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。
什么是基數:比如數據集 {1, 3, 5, 7, 5, 7, 8}, 那么這個數據集的基數集為 {1, 3, 5 ,7, 8}, 基數(不重復元素)為5。 基數估計就是在誤差可接受的范圍內,快速計算基數。
HyperLoglog是Redis新支持的兩種類型中的另外一種(上一種是位圖類型Bitmaps)。主要適用場景是海量數據的計算。特點是速度快。占用空間小。HyperLoglog在適用場景方面與Bitmaps方面有什么不同呢。我個人的理解是,Bitmaps更適合用于驗證的大數據,比如簽到,記錄某用戶是不是當天進行了簽到,簽到了多少天的時候。也就是說,你不光需要記錄數據,還需要對數據進行驗證的時候使用Bitmaps。HyperLoglog則用于只記錄的時候,比如訪問的uv統(tǒng)計。
3.7.1 HyperLogLogs常用操作
pfadd key element [element ...] # 添加指定元素到 HyperLogLog 中
pfcount key [key ...] # 返回給定 HyperLogLog 的基數估算值
pfmerge destkey sourcekey [sourcekey ...] # 將多個 HyperLogLog 合并為一個 HyperLogLog
3.7.1 HyperLogLogs應用場景
- UV(訪客數),指定時間內的訪客數量,使用HyperLogLogs就可以很方便的做到了,每個訪客都有一個獨立的id。訪問一次則往HyperLogLogs里面加一個。
3.8 Geospatial(地理空間)
Redis Geospatial 是Redis提供的地理空間以及索引半徑查詢的功能的數據結構。這在需要地理位置的應用上或許可以一展身手。
Geospatial底層的實現原理其實就是Zsets!我們可以使用Zset命令來操作Geospatial。
3.8.1 Geospatial常用操作
geoadd key [NX|XX] [CH] longitude latitude member [longitude latitude member ...] # 添加一個或多個地理位置元素到一個key中
geodist key member1 member2 [m|km|ft|mi] # 返回一個key中指定兩個位置之間的距離
geohash key member [member ...] # 返回一個或多個位置元素的 Geohash 表示,Geohash是一種經緯度散列算法
geopos key member [member ...] # 返回一個或多個位置的經緯度信息,由于采用了geohash算法,返回的經緯度和添加時的數據可能會有細小誤差
georadius key longitude latitude radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key]
# 以給定位置為中心,半徑不超過給定半徑的附近所有位置
georadiusbymember key member radius m|km|ft|mi [WITHCOORD] [WITHDIST] [WITHHASH] [COUNT count [ANY]] [ASC|DESC] [STORE key] [STOREDIST key] # 和GEORADIUS相似,只是中心點不是指定經緯度,而是指定已添加的某個位置作為中心
zrange key min max [BYSCORE|BYLEX] [REV] [LIMIT offset count] [WITHSCORES] # 獲得指定key中所有坐標信息
zrem key member [member ...] # 刪除指定key下指定目標的數據
3.8.2 Geospatial應用場景
- 朋友圈定位。
- 附件的人。
- 打車距離計算,兩個位置之間的距離。
四 Redis持久化
Redis是一個內存數據庫,數據保存在內存中,但是我們都知道內存的數據變化是很快的,也容易發(fā)丟失。幸好Redis還為我們提供了持久化的機制,分別是RDB(Rdis DataBase)和AOF(Apend Only File)。
4.1 RDB
RDB其實就是把數據以快照的形式保存在磁盤上。什么是快照呢,你可以理解成把當前時刻的數據拍成一張照片保存下來。
RDB持久化是指在指定的時間間隔內將內存中的數據集快照寫入磁盤。這也是Redis默認的持久化方式,這種方式是將內存中的數據以快照的形式寫入到二進制文件中。默認的文件命名為dump.rdb。
-
自動觸發(fā)
自動觸發(fā)是由我們的配置文件來完成的。在redis.conf配置文件中,里面有如下配置:
save: 這里是用來配置觸發(fā)Redis的RDB持久化條件,也就是什么時候將內存中的數據保存到硬盤。比如“save m n”。表示m秒內數據集存在n次修改時,自動觸發(fā)bgsave。 #save 900 1 表示900秒內如果至少有1個key的值變化 #save 300 10 表示300秒內如果至少有10個 key的值變化 #save 60 10000 表示60秒內如果至少有10000個key 的值變化 stop-writes-on-bgsave-error: 默認值為yes。當啟用了RDB且最后一次后臺保存數據失敗,Redis是否停止接收數據。這會讓用戶意識到數據沒有正確持久化到磁盤上,否則沒有人會注意到災難(disaster)發(fā)生了。如果Redis重啟了,那么又可以重新開始接收數據了 rdbcompression: 默認值是yes。對于存儲到磁盤中的快照,可以設置是否進行壓縮存儲 rdbchecksum: 默認值是yes。在存儲快照后,我們還可以讓redis使用CRC64算法來進行數據校驗,但是這樣做會增加大約10%的性能消耗,如果希望獲取到最大的性能提升,可以關閉此功能 dbfilename: 設置快照的文件名,默認是 dump.rdb dir: 設置快照文件的存放路徑,這個配置項一定是個目錄,而不能是文件名 -
save觸發(fā)方式
save命令會阻塞當前Redis服務器,執(zhí)行save命令期間,Redis不能處理其他命令,知道RDB過程完成為止。執(zhí)行完成時候如果存在老的RDB文件,就把新的替換掉舊的。我們的客戶端可能都是幾萬或者幾十萬,這種方式顯然是不可取的。
-
bgsave觸發(fā)方式
bgsave命令,Redis會在后臺異步執(zhí)行快照操作,快照的同事還可以相應客戶端請求。bgsave操作就是fork操作創(chuàng)建子線程,RDB持久化過程由子進程負責,完成后自動接收。阻塞只發(fā)生在fork階段,一般時間很短?;旧蟁edis內部所有的RDB操作都是采用的bgsave命令。
RDB優(yōu)點
- RDB文件緊湊,全量備份,非常適合用于進行備份和災難恢復
- 生成RDB文件的時候,redis主進程會fork()一個子進程來處理所有保存工作,主進程不需要進行任何磁盤IO操作
- RDB 在恢復大數據集時的速度比 AOF 的恢復速度要快
RDB缺點
- RDB快照是一次全量備份,存儲的是內存數據的二進制序列化形式,存儲上非常緊湊。當進行快照持久化時,會開啟一個子進程專門負責快照持久化,子進程會擁有父進程的內存數據,父進程修改內存子進程不會反應出來,所以在快照持久化期間修改的數據不會被保存,可能丟失數據
4.2 AOF
RDB全量備份總是耗時的,Redis提供了一種更加高效的方式AOF,工作機制很簡單Redis會將每一個收到的寫命令都通過write函數追加到文件中。通俗的理解就是日志記錄。
AOF也有三種觸發(fā)方式:always、everysec、no
appendfsync everysec # always: 每修改同步同步,缺點就是IO開銷比較大; everysec: 每秒同步:異步操作,每秒記錄 如果一秒內宕機,有數據丟失; no: 從不同步
AOF優(yōu)點
- AOF可以更好的保護數據不丟失,一般AOF會每隔1秒,通過一個后臺線程執(zhí)行一次fsync操作,最多丟失1秒鐘的數據
- AOF日志文件沒有任何磁盤尋址的開銷,寫入性能非常高,文件不容易破損
- AOF日志文件即使過大的時候,出現后臺重寫操作,也不會影響客戶端的讀寫
- AOF日志文件的命令通過非??勺x的方式進行記錄,這個特性非常適合做災難性的誤刪除的緊急恢復。比如某人不小心用flushall命令清空了所有數據,只要這個時候后臺rewrite還沒有發(fā)生,那么就可以立即拷貝AOF文件,將最后一條flushall命令給刪了,然后再將該AOF文件放回去,就可以通過恢復機制,自動恢復所有數據
AOF缺點
- 對于同一份數據來說,AOF日志文件通常比RDB數據快照文件更大
- AOF開啟后,支持的寫QPS會比RDB支持的寫QPS低,因為AOF一般會配置成每秒fsync一次日志文件,當然,每秒一次fsync,性能也還是很高的
- 以前AOF發(fā)生過bug,就是通過AOF記錄的日志,進行數據恢復的時候,沒有恢復一模一樣的數據出來
五 Redis可能存在的問題
Redis經常會被我們用作緩存層。緩存層是為了緩解持久層數據庫的壓力而添加的一層保護層。如果數據在緩存(Redis)里面查詢不到,我們就需要去持久層數據庫里面查詢。主要步驟如下:
- 根據所傳入的key去緩存層Redis里面查詢對應的值。
- 如果緩存層Redis里面查詢到了,則直接返回。
- 如果緩存層Redis里面沒有查到,則到持久成數據庫里面去查詢。
- 如果持久層數據庫里面有查詢到,則緩存到緩存層Redis里面,并且返回。
- 如果持久層數據庫里面沒有查詢到,則返回沒有查詢到。
下面我們針對Redis作為緩沖層使用過程中可能會出現的三個問題:緩存穿透,緩存擊穿,緩存雪崩,做一個簡單的了解。
5.1 緩存穿透
緩存穿透是指需要查詢的key對應的數據,在緩存層Redis和持久層數據庫里面都沒有,但是用戶又在不斷的發(fā)起這個key對應的請求。比如正常情況下用戶id一般都是大于0的。但是在某一時刻由于某些黑客的攻擊,一直發(fā)送id < 0的請求,去查詢用戶信息。因為緩存層Redis里面沒有,所有的請求都會打到持久層數據庫中去。在這一時刻,會給持久層數據庫造成很大的壓力,此時緩存層Redis好像被“穿透”了一樣,起不到任何作用。
緩存穿透的原因:
- 自身業(yè)務代碼或者數據出現問題。
- 一些惡意攻擊,爬蟲等造成大量空命中。
緩存穿透的解決方法:
接口校驗:在正常業(yè)務流程中可能會存在少量訪問不存在 key 的情況,但是一般不會出現大量的情況,所以這種場景最大的可能性是遭受了非法攻擊。可以在最外層先做一層校驗:用戶鑒權、數據合法性校驗等,例如商品查詢中,商品的ID是正整數,則可以直接對非正整數直接過濾等等。
緩存空對象:當訪問緩存層和持久層數據庫都沒有查詢到值的時候,可以將空值寫進緩存。并且設置較短過期時間。
布隆過濾器:使用布隆過濾器存儲所有可能訪問的key,不存在的key直接過濾掉,存在的key則在進一步查詢。當布隆過濾器說某個值存在時,這個值可能不存在;當它說某個值不存在時,那這個值肯定不存在。
5.2 緩存擊穿
緩存擊穿是指某一個熱點key(這個key非常的熱門,很多人都需要對它進行查詢),在緩存過期的一瞬間,同時有大量的請求打進來。因為這個key在緩存層的過期時間過了,所以這一瞬間所有的請求都打到了持久層數據庫中,引起持久層數據庫壓力瞬間增大,造成持久層數據庫過大的壓力。(注意:是指同一個key,緩存過期的一瞬間有大量的請求打進來)
緩存擊穿的原因:
- 設置了過期時間的key,承載著高并發(fā),是一種熱點數據。從這個key過期到重新從MySQL加載數據放到緩存的一段時間,大量的請求過來。
緩存擊穿的解決方法:
- 設置熱點數據永不過期。直接將緩存設置不過期。
- 加互斥鎖,同一個key的請求互斥。
5.3 緩存雪崩
緩存雪崩是指大量的熱點key設置了相同的過期時間,導致緩存層Redis有大量的key在同一時刻全部失效,造成瞬間持久層數據庫的壓力過大。當然了如果在某一時刻Redis掛了也會出現緩存雪崩的情況。
緩存雪崩的原因:
- 大量的熱點key設置了相同的過期時間。
- Redis崩潰了,徹底掛了。
緩存雪崩的解決辦法:
- 過期時間打散,緩存數據的過期時間設置隨機,防止同一時間大量數據過期現象發(fā)生。
- 設置熱點數據永不過期。
- 如果緩存數據庫是分布式部署,將熱點數據均勻分布在不同的緩存數據庫中。
- 數據預熱:數據預熱的含義就是在正式部署之前,我們先把可能的數據先預先訪問一遍,這樣部分可能大量訪問的數據都會加載到緩存中。在即將發(fā)生大并發(fā)訪問前手動觸發(fā)加載緩存不同的key,設置不同的過期時間,讓緩存失效的時間點盡量均勻。
緩存擊穿和緩存雪崩的區(qū)別在:緩存擊穿是一個key的失效情況,而緩存雪崩是大批量key的失效情況。
六 Redis開發(fā)規(guī)范
6.1 鍵值設計
6.1.1 key設計
- 可讀性和可管理性:以業(yè)務名(或數據庫名)為前綴(防止key沖突),用冒號分隔,比如業(yè)務名:表名:id。
- 簡潔性:保證語義的前提下,控制key的長度,當key較多時,內存占用也不容忽視。
- 不要包含特殊字符:不能包含空格、換行、單雙引號以及其他轉義字。
6.1.2 value設計
拒絕大key:防止網卡流量、慢查詢,string類型控制在10KB以內,hash、list、set、zset元素個數不要超過5000。
選擇合適的數據類型。
-
控制key的生命周期:redis不是垃圾桶,建議使用expire設置過期時間(條件允許可以打散過期時間,防止集中過期),不過期的數據重點關注idletime。
項目里一般強制要求所有都設置過期時間,避免由于redis問題導致服務不可用。
6.2 Redis命令的使用
- O(N)命令關注N的數量:例如hgetall、lrange、smembers、zrange、sinter等并非不能使用,但是需要明確N的值。有遍歷的需求可以使用hscan、sscan、zscan代替。
- 禁用命令:禁止線上使用keys、flushall、flushdb等,通過redis的rename機制禁掉命令,或者使用scan的方式漸進式處理。
- 合理使用select命令:redis的多數據庫較弱,使用數字進行區(qū)分,很多客戶端支持較差,同時多業(yè)務用多數據庫實際還是單線程處理,會有干擾。
- 推薦使用批量操作命令提高效率:原生命令:例如mget、mset。 非原生命令:可以使用pipeline提高效率。
- 不建議過多使用Redis的事務功能:Redis的事務功能較弱(不支持回滾),而且集群版本(自研和官方)要求一次事務操作的key必須在一個slot上(可以使用hashtag功能解決)。
6.3 Redis客戶端的使用
- 避免多個應用使用一個Redis實例:不相干的業(yè)務拆分,公共數據做服務化。
- 使用連接池:可以有效控制連接,同時提高效率。
- 熔斷功能:高并發(fā)下建議客戶端添加熔斷功能(例如netflix hystrix)。
- 合理加密:設置合理的密碼,如有必要可以使用SSL加密訪問。
- 淘汰策略:根據自身業(yè)務類型,選好maxmemory-policy(最大內存淘汰策略),設置好過期時間。默認策略是volatile-lru,即超過最大內存后,在過期鍵中使用lru算法進行key的剔除,保證不過期數據不被刪除,但是可能會出現OOM問題。
七 Redis相關工具
Redis可視化工具 :RedisDesktopManager
Redis數據同步工具:redis間數據同步可以使用:redis-port。redis-port通過解析 rdb 文件,實現 Redis 之間的數據同步以及數據恢復。
大key搜索工具:阿里云 redis大key搜索工具,big key搜索工具。讓我們可以快速的找到大key。
Redis性能優(yōu)化:
- 修改linux中TCP監(jiān)聽的最大容納數量
- 修改linux內核內存分配策略