Redis,REmote DIctionary Server,是一個由 Salvatore Sanfilippo 寫的 Key-Value 存儲系統。html
Redis 是一個開源的使用 ANSI C 語言編寫、遵照 BSD 協議、支持網絡、可基於內存亦可持久化的日誌型、Key-Value 數據庫,並提供多種語言的API。java
它一般被稱爲數據結構服務器,由於值(Value)能夠是字符串(String), 哈希(Map), 列表(list), 集合(sets)和有序集合(sorted sets)等類型。node
官網地址: https://redis.iogit
下載地址: https://github.com/antirez/redis/releasesgithub
安裝完成後在安裝目錄下執行:redis
redis-server.exe redis.windows.conf
下載,解壓縮並編譯Redis最新穩定版本:算法
wget http://download.redis.io/releases/redis-5.0.3.tar.gz tar xzf redis-5.0.3.tar.gz cd redis-5.0.3 make
啓動Redis服務:spring
cd src ./redis-server ../redis.conf
Redis 的配置文件,Windows 是安裝目錄的 redis.windows.conf 文件,Linux 是安裝目錄下的 redis.conf 文件。shell
在鏈接上 Redis 服務後,能夠經過 config 命令查看或者編輯配置項。數據庫
redis 127.0.0.1:6379> config get ${name}
例:
127.0.0.1:6379> config get port 1) "port" 2) "6379"
redis 127.0.0.1:6379> config set ${name} ${value}
例:
127.0.0.1:6379> config set loglevel "notice" OK
注:部分配置不能經過 config 命令動態編輯,須要直接修改配置文件對應內容,例如端口 port。
是否以守護線程運行,默認爲 no,使用 yes 啓用守護線程;(後臺啓動)
Redis監聽端口,默認爲 6379;
注:做者曾解釋過 6379 的來歷。6379 在手機按鍵對應的英文是 MERZ,意大利歌女 Alessia Merz 的名字。參考連接: http://oldblog.antirez.com/po...
指定客戶端鏈接地址,默認爲 127.0.0.1,也就是隻能本地鏈接,屏蔽該參數啓用遠程鏈接;
客戶端空閒多長時間(秒)關閉該鏈接,指定爲 0 關閉該功能;
save <seconds> <changes>
指定在多長時間內,至少有多少次更新操做,就將數據同步到數據文件,能夠多個條件配合使用;
Redis默認提供了三個條件:
save 900 1 save 300 10 save 60 10000
說明Redis在下列三種狀況將會同步數據到文件中:
本地數據庫文件名,默認是dump.rdb;
本地數據庫文件存放路徑,默認是./(當前目錄);
replicaof <masterip> <masterport>
當在主從複製中,本身做爲 slave,設置 master 的 ip 和端口,在該 slave 啓動時,會自動從 master 進行數據同步;
當 master 設置了密碼後,slave 鏈接 master 的密碼;
設置 Redis 鏈接密碼,默認關閉;
開啓 Redis 數據持久化到日誌中(AOF),默認爲 no 未開啓;
因爲默認的數據持久化方案(RDB),存儲到 dump.rdb 文件中,在斷電或服務忽然掛掉的狀況下會丟失數據,開啓日誌持久化能夠彌補該不足;
日誌文件名,默認爲 appendonly.aof;
日誌更新頻率,有3個可選值;
# maxmemory <bytes> # MAXMEMORY POLICY: how Redis will select what to remove when maxmemory # 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支持五種數據類型:string(字符串),hash(哈希),list(列表),set(集合)及 zset(sorted set:有序集合)。
最基本類型,二進制安全,也能夠包含jpg或序列化後的對象,最大支持512M;
例:
127.0.0.1:6379> SET name "caojiantao" OK 127.0.0.1:6379> GET name "caojiantao"
Key-Value鍵值對集合,適合用來存儲簡單對象;
例:
127.0.0.1:6379> hmset user name caojiantao age 18 OK 127.0.0.1:6379> hget user age "18"
簡單的字符串列表,雙向鏈表的數據結構;
例:
127.0.0.1:6379> lpush months 1 (integer) 1 127.0.0.1:6379> lpush months 2 (integer) 2 127.0.0.1:6379> rpush months 3 (integer) 3 127.0.0.1:6379> lrange months 0 10 1) "2" 2) "1" 3) "3" 127.0.0.1:6379> lpop months "2" 127.0.0.1:6379> rpop months "3"
string 類型的無序集合(惟一性),hash 結構,操做複雜度爲 O(1);
例:
127.0.0.1:6379> sadd team zhangsan lisi (integer) 2 127.0.0.1:6379> smembers team 1) "zhangsan" 2) "lisi" 127.0.0.1:6379> sadd team lisi (integer) 0
同 set,不過每一個子元素會關聯一個 double 類型的分數 score,zset 根據 score 排序;
例:
127.0.0.1:6379> zadd days 1 one (integer) 1 127.0.0.1:6379> zadd days 0 zero (integer) 1 127.0.0.1:6379> zadd days 2 two (integer) 1 127.0.0.1:6379> zrangebyscore days 0 10 1) "zero" 2) "one" 3) "two"
geo 爲地理位置類型,3.2+ 版本纔開始支持,其底層實現還是 zset,因此刪除成員命令同 zrem;
重要命令一覽:
例:
127.0.0.1:6379> geoadd positions 116.407258 39.991496 olympics 116.403909 39.915547 tiananmen 116.333374 40.009645 qinghua (integer) 3 127.0.0.1:6379> geodist positions tiananmen qinghua "12070.5091" 127.0.0.1:6379> georadiusbymember positions tiananmen 20 km 1) "qinghua" 2) "tiananmen" 3) "olympics" 127.0.0.1:6379> georadiusbymember positions tiananmen 10 km 1) "tiananmen" 2) "olympics"
類型 | 簡介 | 特性 | 場景 |
---|---|---|---|
String(字符串) | 二進制安全 | 能夠包含任何數據,好比jpg圖片或者序列化的對象,一個鍵最大能存儲512M | --- |
Hash(字典) | 鍵值對集合,即編程語言中的Map類型 | 適合存儲對象,而且能夠像數據庫中update一個屬性同樣只修改某一項屬性值(Memcached中須要取出整個字符串反序列化成對象修改完再序列化存回去) | 存儲、讀取、修改用戶屬性 |
List(列表) | 鏈表(雙向鏈表) | 增刪快,提供了操做某一段元素的API | 1,最新消息排行等功能(好比朋友圈的時間線) 2,消息隊列 |
Set(集合) | 哈希表實現,元素不重複 | 一、添加、刪除,查找的複雜度都是O(1) 二、爲集合提供了求交集、並集、差集等操做 | 一、共同好友 二、利用惟一性,統計訪問網站的全部獨立ip 三、好友推薦時,根據tag求交集,大於某個閾值就能夠推薦 |
Sorted Set(有序集合) | 將Set中的元素增長一個權重參數score,元素按score有序排列 | 數據插入集合時,已經進行自然排序 | 一、排行榜 |
geo | 經緯度座標類型 | 附近的人 |
multi ...(命令) exec
一次執行多條命令,有如下特色:
127.0.0.1:6379> multi OK 127.0.0.1:6379> set a 1 QUEUED 127.0.0.1:6379> set b 2 QUEUED 127.0.0.1:6379> get a QUEUED 127.0.0.1:6379> del a QUEUED 127.0.0.1:6379> exec 1) OK 2) OK 3) "1" 4) (integer) 1
Redis 支持一個發佈訂閱的消息通訊模式,發送者 pub 發送消息,訂閱者 sub 接受消息,可訂閱任意數量的頻道 channel;
三個客戶端都訂閱了 channel 這個頻道;
一旦有消息發佈pub到channel中,以前訂閱該channel的三個客戶端都會收到這個message;
例:
客戶端訂閱talk頻道;
127.0.0.1:6379> subscribe talk Reading messages... (press Ctrl-C to quit) 1) "subscribe" 2) "talk" 3) (integer) 1
另開客戶端發佈消息值talk頻道;
127.0.0.1:6379> publish talk "hello world" (integer) 1
此時客戶端收到消息;
1) "message" 2) "talk" 3) "hello world"
Redis 使用 Lua 解釋器執行,執行命令爲eval;
eval script numkeys key [key ...] arg [arg ...]
注:key 和 arg 在 lua 腳本佔位符分別爲 KEYS[] 和 ARGV[],必須大寫,數組下標從 1 開始。
例:獲取腳本參數
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1]}" 2 "key1" "key2" "argv1" 1) "key1" 2) "key2" 3) "argv1"
一般會將腳本存儲到一個lua文件中,假如test.lua內容以下:
return {KEYS[1],KEYS[2],ARGV[1]}
執行這個lua腳本命令;
redis-cli.exe --eval test.lua "key1" "key2" , "argv1" 1) "key1" 2) "key2" 3) "argv1"
注意參數格式與以前有點出入,執行lua腳本文件不須要numkeys,key和arg參數用逗號相隔;
兩種編碼實現:ziplist 和 skiplist,當知足下列條件採用 ziplist 編碼方式:
同時 zset 還維護了一個字典,保存元素 member 到 分值 score 的映射,便於等值查找。
壓縮列表, 2 個緊挨在一塊兒的節點組成一個元素,表明元素的實際值和分值大小。
跳躍表,有利於範圍查找,相比紅黑樹實現難度較爲簡單得多。
參考: https://segmentfault.com/a/11...
github: https://github.com/xetorthio/jedis
阻塞 I/O 模型,調用方法都是同步的,不支持異步調用,而且 Jedis 客戶端非線程安全,須要結合鏈接池使用;
maven依賴:
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.9.0</version> </dependency>
demo示例:
String host = "127.0.0.1"; int port = 6379; // 鏈接本地的 Redis 服務 Jedis jedis = new Jedis(host, port); // 查看服務是否運行 System.out.println("服務正在運行: " + jedis.ping()); // 基本操做 String key = "welcome"; jedis.set(key, "hello world"); System.out.println(jedis.get(key)); // 鏈接池配置 GenericObjectPoolConfig config = new GenericObjectPoolConfig(); config.setMaxTotal(1); // 鏈接池操做 JedisPool pool = new JedisPool(config, host, port); Jedis a = pool.getResource(); // a.close(); System.out.println(a); Jedis b = pool.getResource(); System.out.println(b);
github: https://github.com/lettuce-io/lettuce-core
基於 Netty 框架,異步調用,線程安全;
maven依賴:
<dependency> <groupId>io.lettuce</groupId> <artifactId>lettuce-core</artifactId> <version>5.0.3.RELEASE</version> </dependency>
demo示例:
// 1. 構造uri RedisURI uri = RedisURI.builder() .withHost("127.0.0.1") .withPort(6379) .build(); // 2. 建立client RedisClient client = RedisClient.create(uri); // 3. 鏈接redis StatefulRedisConnection<String, String> connect = client.connect(); // 4. 獲取操做命令(同步) RedisCommands<String, String> commands = connect.sync(); String key = "welcome"; System.out.println(commands.get(key)); connect.close();
github: https://github.com/redisson/redisson
實現了分佈式和可擴展的 Java 數據結構;
maven依賴:
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>3.11.5</version> </dependency>
demo示例:
public static void main(String[] args) { // 1. 建立鏈接配置 Config config = new Config(); config.useSingleServer().setAddress("redis://10.242.24.246:6379"); // 2. 建立 redisson 實例 RedissonClient client = Redisson.create(config); // 操做數據 RBucket<Object> bucket = client.getBucket("name"); bucket.set("caojiantao"); System.out.println(bucket.get()); // 3. 關閉鏈接實例 client.shutdown(); }
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
注:springboot 2.x 以後使用了 Lettuce 替換掉了底層 Jedis 的依賴。
在 application.yml 添加下面屬性
spring: redis: host: 127.0.0.1 port: 6379 password: 123456 # 鏈接池配置(根據須要) lettuce: pool: max-idle: 8
springboot 默認注入了 RedisTemplate 和 StringRedisTemplate 兩個實例用來操做 Redis,前者 key 和 value 都是採用 JDK 序列化,後者只能操做 String 數據類型;
可直接注入使用;
@Autowired @Qualifier("redisTemplate") private RedisTemplate redisTemplate; @Autowired @Qualifier("stringRedisTemplate") private StringRedisTemplate stringRedisTemplate; public void test() { String key = "welcome"; Object o = redisTemplate.opsForValue().get(key); // 此處爲null,因爲key序列化方式爲JDK System.out.println(o); String s = stringRedisTemplate.opsForValue().get(key); System.out.println(s); }
注:Redis 默認注入原理可參考 RedisAutoConfiguration 類。
默認注入的兩種 RedisTemplate 顯然不適用全部的業務場景,自定義 Template 通常只需下列兩個步驟;
參考第三方序列化框架 protostuff,序列化後體積較小,速度快;
import io.protostuff.*; import io.protostuff.runtime.RuntimeSchema; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; /** * @author caojiantao */ public class ProtoStuffSerializer<T> implements RedisSerializer<T> { private Class<T> clazz; public ProtoStuffSerializer(Class<T> clazz) { this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } Schema<T> schema = RuntimeSchema.getSchema(clazz); return ProtostuffIOUtil.toByteArray(t, schema, LinkedBuffer.allocate()); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null) { return null; } Schema<T> schema = RuntimeSchema.getSchema(clazz); T t = schema.newMessage(); ProtostuffIOUtil.mergeFrom(bytes, t, schema); return t; } }
而後手動注入到spring容器中;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; @Configuration public class RedisConfig { @Bean("customTemplate") public RedisTemplate<String, Student> customTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Student> template = new RedisTemplate<>(); // 注入redis鏈接工廠實例 template.setConnectionFactory(factory); ProtoStuffSerializer<Student> serializer = new ProtoStuffSerializer<>(Student.class); // 設置key、value序列化方式 template.setKeySerializer(RedisSerializer.string()); template.setValueSerializer(serializer); template.afterPropertiesSet(); return template; } }
將全部數據存儲到單個 Redis 主要存在兩個問題;
主從模式很好的解決了以上問題。一個 Redis 實例做爲主機 master,其餘的做爲從機 slave,主機主要用於數據的寫入,從機則主要提供數據的讀取。從機在啓動時會同步全量主機數據,主機也會在寫入數據的時候同步到全部的從機。
有兩種方式能夠設置主從關係;
replicaof ip port
命令;簡單測試,複製 redis.conf 文件,主要配置以下:
master:
port 6379 logfile "6379.log" dbfilename "dump-6379.rdb"
slave_1:
port 6380 logfile "6380.log" dbfilename "dump-6380.rdb" replicaof 127.0.0.1 6379
slave_2:
port 6381 logfile "6381.log" dbfilename "dump-6381.rdb" replicaof 127.0.0.1 6379
slave_3:
port 6382 logfile "6382.log" dbfilename "dump-6382.rdb" replicaof 127.0.0.1 6379
依次啓動上述四個Redis實例;
./redis-server 6379.conf ./redis-server 6380.conf ./redis-server 6381.conf ./redis-server 6382.conf
鏈接6379主機master,查看replication信息;
127.0.0.1:6379> info replication # Replication role:master connected_slaves:3 slave0:ip=127.0.0.1,port=6380,state=online,offset=322,lag=0 slave1:ip=127.0.0.1,port=6381,state=online,offset=322,lag=1 slave2:ip=127.0.0.1,port=6382,state=online,offset=322,lag=0 master_replid:417b1e3811a2d9b3465876d65c67a36949de8f9f master_replid2:0000000000000000000000000000000000000000 master_repl_offset:322 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:322
說明了當前 Redis 實例爲主機,有三個從機;
在當前主機寫入數據;
127.0.0.1:6379> set msg "hello world" OK
在其餘任意從機執行獲取操做;
127.0.0.1:6382> get msg "hello world"
已經成功設置主從同步。
主從模式存在必定的弊端,master 一旦發生宕機,主從同步過程將會中斷。
Sentinel(哨兵)做爲一個單獨的服務,用來監控 master 主機,間接監控全部 slave 從機,以下圖所示;
sentinel 主要有如下三個特色;
當master發生故障,sentinel 會採用在當前 sentinel 集羣中投票方式,從當前全部 slave 中,推舉一個做爲新的master,從而保證了 Redis 的高可用性。
在哨兵模式下,每一個 Redis 實例都是存儲的全量數據。爲了最大化利用內存空間,採用集羣模式,即分佈式存儲,每臺 Redis 存儲不一樣的內容。Redis 集羣沒有使用一致性hash,而是引入了哈希槽的概念 。
數據存儲在 16384 個 slot(插槽)中,全部的數據都是根據必定算法映射到某個 slot 中;
爲何是 16384: https://github.com/antirez/re...
集羣模式至少三個Redis節點,不然會提示:
./redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 *** ERROR: Invalid configuration for cluster creation. *** Redis Cluster requires at least 3 master nodes. *** This is not possible with 2 nodes and 0 replicas per node. *** At least 3 nodes are required.
在src目錄建立confs文件夾,複製redis.conf文件6分,三主三從;
主要配置以下;
port 7000 cluster-enabled yes cluster-node-timeout 15000 cluster-config-file "nodes-7000.conf" pidfile /var/run/redis-7000.pid logfile "cluster-7000.log" dbfilename dump-cluster-7000.rdb appendfilename "appendonly-cluster-7000.aof"
順序啓動相關Redis示例,最後建立集羣;
./redis-server confs/7000.conf ./redis-server confs/7001.conf ./redis-server confs/7002.conf ./redis-server confs/7003.conf ./redis-server confs/7004.conf ./redis-server confs/7005.conf ./redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1
控制檯輸出建立集羣信息:
>>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 10.242.24.246:7003 to 10.242.24.246:7000 Adding replica 10.242.24.246:7004 to 10.242.24.246:7001 Adding replica 10.242.24.246:7005 to 10.242.24.246:7002 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: bbb45e488e5679b79dd077f97803304534793420 10.242.24.246:7000 slots:[0-5460] (5461 slots) master M: b490121213e22451a9b788755b0be0d3bf158cda 10.242.24.246:7001 slots:[5461-10922] (5462 slots) master M: 000f55716f8e9f2c635744999a49425bcc65595d 10.242.24.246:7002 slots:[10923-16383] (5461 slots) master S: 17611ff6f3dffbfab60ce4ae7b7991a9ae280bcd 10.242.24.246:7003 replicates b490121213e22451a9b788755b0be0d3bf158cda S: 950a5a467ccb6af3280b67a3f2ce2e3fa7510bd8 10.242.24.246:7004 replicates 000f55716f8e9f2c635744999a49425bcc65595d S: c15ce5e96e69a1d93c5a71953ee044af6b2bd560 10.242.24.246:7005 replicates bbb45e488e5679b79dd077f97803304534793420 Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join ........... >>> Performing Cluster Check (using node 10.242.24.246:7000) M: bbb45e488e5679b79dd077f97803304534793420 10.242.24.246:7000 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: c15ce5e96e69a1d93c5a71953ee044af6b2bd560 10.242.24.246:7005 slots: (0 slots) slave replicates bbb45e488e5679b79dd077f97803304534793420 M: b490121213e22451a9b788755b0be0d3bf158cda 10.242.24.246:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) S: 950a5a467ccb6af3280b67a3f2ce2e3fa7510bd8 10.242.24.246:7004 slots: (0 slots) slave replicates 000f55716f8e9f2c635744999a49425bcc65595d M: 000f55716f8e9f2c635744999a49425bcc65595d 10.242.24.246:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) S: 17611ff6f3dffbfab60ce4ae7b7991a9ae280bcd 10.242.24.246:7003 slots: (0 slots) slave replicates b490121213e22451a9b788755b0be0d3bf158cda [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered.
集羣部署成功後,鏈接7000這個節點,注意鏈接命令:
./redis-cli -c -p 7000 127.0.0.1:7000> get name -> Redirected to slot [5798] located at 127.0.0.1:7001 (nil)
假如當前集羣爲 7000, 7001, 7002 三個節點,正確配置啓動新節點 7003 後執行命令:
[root@localhost redis-conf]# redis-cli --cluster add-node 127.0.0.1:7003 127.0.0.1:7000 >>> Adding node 127.0.0.1:7003 to cluster 127.0.0.1:7000 >>> Performing Cluster Check (using node 127.0.0.1:7000) M: 9de886a23be8bc92bbe51a4e73ad27d2fb96df8d 127.0.0.1:7000 slots:[0-5460] (5461 slots) master M: cba883e361f23f1415e4d94148c7c26900c28111 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master M: 3855e27b1ec68d6481d6d308101fb28dd6ed21df 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. >>> Send CLUSTER MEET to node 127.0.0.1:7003 to make it join the cluster. [OK] New node added correctly.
添加的新節點沒有分配 slots,須要手動分配:
[root@localhost redis-conf]# redis-cli --cluster reshard 127.0.0.1:7000 >>> Performing Cluster Check (using node 127.0.0.1:7000) M: 9de886a23be8bc92bbe51a4e73ad27d2fb96df8d 127.0.0.1:7000 slots:[0-5460] (5461 slots) master M: cba883e361f23f1415e4d94148c7c26900c28111 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master M: 28eedd55e0fd8e35d36766055b720418c14fa04a 127.0.0.1:7003 slots: (0 slots) master M: 3855e27b1ec68d6481d6d308101fb28dd6ed21df 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. How many slots do you want to move (from 1 to 16384)? 100 What is the receiving node ID? 28eedd55e0fd8e35d36766055b720418c14fa04a Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1: 9de886a23be8bc92bbe51a4e73ad27d2fb96df8d Source node #2: done Ready to move 100 slots. Source nodes: M: 9de886a23be8bc92bbe51a4e73ad27d2fb96df8d 127.0.0.1:7000 slots:[0-5460] (5461 slots) master Destination node: M: 28eedd55e0fd8e35d36766055b720418c14fa04a 127.0.0.1:7003 slots: (0 slots) master Resharding plan: ... ... Do you want to proceed with the proposed reshard plan (yes/no)? yes ... ...
節點檢查:
[root@localhost redis-conf]# redis-cli --cluster info 127.0.0.1:7000 127.0.0.1:7000 (9de886a2...) -> 0 keys | 5361 slots | 0 slaves. 127.0.0.1:7001 (cba883e3...) -> 0 keys | 5462 slots | 0 slaves. 127.0.0.1:7003 (28eedd55...) -> 0 keys | 100 slots | 0 slaves. 127.0.0.1:7002 (3855e27b...) -> 0 keys | 5461 slots | 0 slaves.
首先將該節點的 slots 轉移(同新增),而後執行刪除節點操做:
[root@localhost redis-conf]# redis-cli --cluster del-node 127.0.0.1:7003 28eedd55e0fd8e35d36766055b720418c14fa04a >>> Removing node 28eedd55e0fd8e35d36766055b720418c14fa04a from cluster 127.0.0.1:7003 >>> Sending CLUSTER FORGET messages to the cluster... >>> SHUTDOWN the node.
場景:定時任務集羣部署,Job 須要加鎖單次執行;
方案:基於 Redis 實現分佈式鎖,以 Job 惟一標識爲 key,設置 expiration,在 Job 執行前獲取鎖斷定;
優勢:實現較爲簡單,過時策略防止死鎖,效率較高;
基於 springboot 2.x 項目,參考代碼以下;
加鎖:
/** * 嘗試加鎖 * * @param lockKey 加鎖的KEY * @param requestId 加鎖客戶端惟一ID標識 * @param expireTime 過時時間 * @param timeUnit 時間單位 * @return 是否加鎖成功 */ public Boolean tryLock(String lockKey, String requestId, long expireTime, TimeUnit timeUnit) { RedisConnection connection = connectionFactory.getConnection(); Boolean result = connection.set(lockKey.getBytes(StandardCharsets.UTF_8), requestId.getBytes(StandardCharsets.UTF_8), Expiration.from(expireTime, timeUnit), RedisStringCommands.SetOption.SET_IF_ABSENT); connection.close(); return result; }
requestId 一般用做標識加鎖請求的惟一性,只有對應的加鎖請求,才能成功解鎖。防止某個客戶端操做阻塞好久,鎖超時自動釋放被另外客戶端拿到,而後本身又執行釋放鎖釋放掉其餘客戶端當前持有的鎖。
解鎖:
/** * 釋放鎖 * * @param lockKey 加鎖的KEY * @param requestId 加鎖客戶端惟一ID標識 * @return 是否釋放成功 */ public boolean releaseLock(String lockKey, String requestId) { // Lua代碼,一次性執行保證原子性,避免併發問題 String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end"; RedisConnection connection = connectionFactory.getConnection(); byte[][] keysAndArgs = new byte[2][]; keysAndArgs[0] = lockKey.getBytes(StandardCharsets.UTF_8); keysAndArgs[1] = requestId.getBytes(StandardCharsets.UTF_8); Long result = connection.scriptingCommands().eval(script.getBytes(StandardCharsets.UTF_8), ReturnType.INTEGER, 1, keysAndArgs); connection.close(); return result != null && result > 0; }
注意解鎖姿式,保證操做原子性。
當鎖的持有時間沒法估算,存在鎖超時致使被自動釋放掉的可能。能夠在獲取鎖成功時,開啓一個定時線程詢問持有鎖情況,若當前仍持有鎖狀態,則刷新過時時間。
參考 Redisson 實現:https://github.com/redisson/redisson/blob/master/redisson/src/main/java/org/redisson/RedissonLock.java (renewExpiration)
主從複製時,獲取鎖成功還未同步 slave 時,master 宕機會出現數據不一致狀況。
官方提供名爲 RedLock 的算法思想:
Redlock 算法: https://redis.io/topics/distlock
描述:同一時間緩存大面積失效,數量級的請求直接打到數據庫。
方案:給緩存失效時間加上一個隨機數。
描述:請求不符合緩存條件,直接打到數據庫。
方案:參數作好校驗,null 值也可緩存。
描述:熱點數據失效瞬間,大量對該熱點數據的請求直接打到數據庫。
方案:設置緩存永不過時,或者查詢引入互斥鎖。