1 簡介
Redis眼俊,REmote DIctionary Server拷橘,是一個由 Salvatore Sanfilippo 寫的 Key-Value 存儲系統(tǒng)躯护。
Redis 是一個開源的使用 ANSI C 語言編寫嚷堡、遵守 BSD 協(xié)議厘灼、支持網(wǎng)絡、可基于內(nèi)存亦可持久化的日志型栖忠、Key-Value 數(shù)據(jù)庫崔挖,并提供多種語言的API。
它通常被稱為數(shù)據(jù)結構服務器庵寞,因為值(Value)可以是字符串(String), 哈希(Map), 列表(list), 集合(sets)和有序集合(sorted sets)等類型狸相。
2 安裝
官網(wǎng)地址: https://redis.io
下載地址: https://github.com/antirez/redis/releases
2.1 Windows
安裝完成后在安裝目錄下執(zhí)行:
redis-server.exe redis.windows.conf
2.2 Linux
下載,解壓縮并編譯Redis最新穩(wěn)定版本:
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服務:
cd src
./redis-server ../redis.conf
3 配置
Redis 的配置文件捐川,Windows 是安裝目錄的 redis.windows.conf 文件脓鹃,Linux 是安裝目錄下的 redis.conf 文件。
在連接上 Redis 服務后古沥,可以通過 config 命令查看或者編輯配置項瘸右。
3.1 查看
redis 127.0.0.1:6379> config get ${name}
例:
127.0.0.1:6379> config get port
1) "port"
2) "6379"
3.2 編輯
redis 127.0.0.1:6379> config set ${name} ${value}
例:
127.0.0.1:6379> config set loglevel "notice"
OK
注:部分配置不能通過 config 命令動態(tài)編輯,需要直接修改配置文件對應內(nèi)容岩齿,例如端口 port太颤。
3.3 部分參數(shù)說明
3.3.1 daemonize
是否以守護線程運行,默認為 no盹沈,使用 yes 啟用守護線程龄章;(后臺啟動)
3.3.2 port
Redis監(jiān)聽端口,默認為 6379乞封;
注:作者曾解釋過 6379 的來歷瓦堵。6379 在手機按鍵對應的英文是 MERZ,意大利歌女 Alessia Merz 的名字歌亲。參考鏈接:http://oldblog.antirez.com/post/redis-as-LRU-cache.html
3.3.3 bind
指定客戶端連接地址,默認為 127.0.0.1澜驮,也就是只能本地連接陷揪,屏蔽該參數(shù)啟用遠程連接;
3.3.4 timeout
客戶端空閑多長時間(秒)關閉該連接杂穷,指定為 0 關閉該功能悍缠;
3.3.5 save
save <seconds> <changes>
指定在多長時間內(nèi),至少有多少次更新操作耐量,就將數(shù)據(jù)同步到數(shù)據(jù)文件飞蚓,可以多個條件配合使用;
Redis默認提供了三個條件:
save 900 1
save 300 10
save 60 10000
說明Redis在下列三種情況將會同步數(shù)據(jù)到文件中:
- 在 900 秒后至少 1 個 key 發(fā)生改變廊蜒;
- 在 300 秒后至少 10 個key發(fā)生改變趴拧;
- 在 60 秒后至少 10000 個key發(fā)生改變溅漾;
3.3.6 dbfilename
本地數(shù)據(jù)庫文件名,默認是dump.rdb著榴;
3.3.7 dir
本地數(shù)據(jù)庫文件存放路徑添履,默認是./(當前目錄);
3.3.8 replicaof
replicaof <masterip> <masterport>
當在主從復制中脑又,自己作為 slave暮胧,設置 master 的 ip 和端口,在該 slave 啟動時问麸,會自動從 master 進行數(shù)據(jù)同步往衷;
3.3.9 masterauth
當 master 設置了密碼后,slave 連接 master 的密碼严卖;
3.3.10 requirepass
設置 Redis 連接密碼席舍,默認關閉;
3.3.11 appendonly
開啟 Redis 數(shù)據(jù)持久化到日志中(AOF)妄田,默認為 no 未開啟俺亮;
由于默認的數(shù)據(jù)持久化方案(RDB),存儲到 dump.rdb 文件中疟呐,在斷電或服務突然掛掉的情況下會丟失數(shù)據(jù)脚曾,開啟日志持久化可以彌補該不足;
3.3.12 appendfilename
日志文件名启具,默認為 appendonly.aof本讥;
3.3.13 appendfsync
日志更新頻率,有3個可選值鲁冯;
- no拷沸,讓操作系統(tǒng)自己決定,速度最快薯演;
- always撞芍,每次操作都會寫更新日志,速度較慢但最安全跨扮;
- everysec序无,每秒更新一次日志,折中方案衡创;(默認)
3.4 淘汰策略
# 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
- LRU least recently used 最近使用帝嗡;
- LFU least frequently used 最少使用;
- noeviction 默認淘汰策略璃氢,return an error哟玷;
4 數(shù)據(jù)類型
Redis支持五種數(shù)據(jù)類型:string(字符串),hash(哈希)一也,list(列表)巢寡,set(集合)及 zset(sorted set:有序集合)喉脖。
4.1 string
最基本類型,二進制安全讼渊,也可以包含jpg或序列化后的對象动看,最大支持512M;
例:
127.0.0.1:6379> SET name "caojiantao"
OK
127.0.0.1:6379> GET name
"caojiantao"
4.2 hash
Key-Value鍵值對集合爪幻,適合用來存儲簡單對象菱皆;
例:
127.0.0.1:6379> hmset user name caojiantao age 18
OK
127.0.0.1:6379> hget user age
"18"
4.3 list
簡單的字符串列表,雙向鏈表的數(shù)據(jù)結構挨稿;
例:
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"
4.4 set
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
4.5 zset
同 set篷店,不過每個子元素會關聯(lián)一個 double 類型的分數(shù) score,zset 根據(jù) 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"
4.6 geo
geo 為地理位置類型疲陕,3.2+ 版本才開始支持,其底層實現(xiàn)仍是 zset钉赁,所以刪除成員命令同 zrem蹄殃;
重要命令一覽:
- geoadd 增加某個地理位置坐標
- geopos 獲取某個地理位置坐標
- geodist 獲取兩個地理位置的距離
- georadius 根據(jù)給定的地理位置坐標獲取指定范圍內(nèi)的地理位置集合
- geohash 獲取某個地理位置的 geohash 值
例:
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"
4.7 小結
類型 | 簡介 | 特性 | 場景 |
---|---|---|---|
String(字符串) | 二進制安全 | 可以包含任何數(shù)據(jù),比如jpg圖片或者序列化的對象,一個鍵最大能存儲512M | --- |
Hash(字典) | 鍵值對集合,即編程語言中的Map類型 | 適合存儲對象,并且可以像數(shù)據(jù)庫中update一個屬性一樣只修改某一項屬性值(Memcached中需要取出整個字符串反序列化成對象修改完再序列化存回去) | 存儲、讀取你踩、修改用戶屬性 |
List(列表) | 鏈表(雙向鏈表) | 增刪快,提供了操作某一段元素的API | 1,最新消息排行等功能(比如朋友圈的時間線) 2,消息隊列 |
Set(集合) | 哈希表實現(xiàn),元素不重復 | 1诅岩、添加、刪除,查找的復雜度都是O(1) 2带膜、為集合提供了求交集吩谦、并集、差集等操作 | 1膝藕、共同好友 2式廷、利用唯一性,統(tǒng)計訪問網(wǎng)站的所有獨立ip 3、好友推薦時,根據(jù)tag求交集,大于某個閾值就可以推薦 |
Sorted Set(有序集合) | 將Set中的元素增加一個權重參數(shù)score,元素按score有序排列 | 數(shù)據(jù)插入集合時,已經(jīng)進行天然排序 | 1芭挽、排行榜 |
geo | 經(jīng)緯度坐標類型 | 附近的人 |
5 特性
5.1 事務
multi
...(命令)
exec
一次執(zhí)行多條命令滑废,有以下特點:
- 發(fā)送exec指令前,所有的操作都會放入隊列緩存览绿;
- 執(zhí)行事務時,任何命令執(zhí)行失敗穗慕,其他命令正常被執(zhí)行饿敲,已操作的命令不會回滾(非原子性);
- 執(zhí)行過程中逛绵,其他客戶端的命令不會插入到該事務中怀各;
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
5.2 發(fā)布訂閱
Redis 支持一個發(fā)布訂閱的消息通信模式倔韭,發(fā)送者 pub 發(fā)送消息,訂閱者 sub 接受消息瓢对,可訂閱任意數(shù)量的頻道 channel寿酌;
三個客戶端都訂閱了 channel 這個頻道;
一旦有消息發(fā)布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
另開客戶端發(fā)布消息值talk頻道法焰;
127.0.0.1:6379> publish talk "hello world"
(integer) 1
此時客戶端收到消息秧荆;
1) "message"
2) "talk"
3) "hello world"
5.3 腳本
Redis 使用 Lua 解釋器執(zhí)行,執(zhí)行命令為eval埃仪;
eval script numkeys key [key ...] arg [arg ...]
- script乙濒,lua腳本內(nèi)容
- numkeys,key的個數(shù)
- key卵蛉,Redis中key屬性
- arg颁股,自定義參數(shù)
注:key 和 arg 在 lua 腳本占位符分別為 KEYS[] 和 ARGV[],必須大寫傻丝,數(shù)組下標從 1 開始甘有。
例:獲取腳本參數(shù)
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內(nèi)容如下:
return {KEYS[1],KEYS[2],ARGV[1]}
執(zhí)行這個lua腳本命令桑滩;
redis-cli.exe --eval test.lua "key1" "key2" , "argv1"
1) "key1"
2) "key2"
3) "argv1"
注意參數(shù)格式與之前有點出入梧疲,執(zhí)行l(wèi)ua腳本文件不需要numkeys,key和arg參數(shù)用逗號相隔运准;
6 數(shù)據(jù)結構
6.1 sorted set
兩種編碼實現(xiàn):ziplist 和 skiplist幌氮,當滿足下列條件采用 ziplist 編碼方式:
- 有序集合保存的元素數(shù)量小于128個 ;
- 有序集合保存的所有元素成員的長度小于64字節(jié) 胁澳;
同時 zset 還維護了一個字典该互,保存元素 member 到 分值 score 的映射,便于等值查找韭畸。
6.1.1 ziplist
壓縮列表宇智, 2 個緊挨在一起的節(jié)點組成一個元素,代表元素的實際值和分值大小胰丁。
6.1.2 skiplist
跳躍表随橘,有利于范圍查找,相比紅黑樹實現(xiàn)難度較為簡單得多锦庸。
7 為什么快
- 完全基于內(nèi)存机蔗;
- 數(shù)據(jù)結構簡單;
- 單線程避免上下文切換;
- 多路 I/0 復用模型萝嘁,非阻塞梆掸;
8 使用(Java)
8.1 客戶端
8.1.1 Jedis
github: https://github.com/xetorthio/jedis
阻塞 I/O 模型,調(diào)用方法都是同步的牙言,不支持異步調(dià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);
8.1.2 Lettuce
github: https://github.com/lettuce-io/lettuce-core
基于 Netty 框架卑硫,異步調(diào)用,線程安全庞钢;
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. 創(chuàng)建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();
8.1.3 Redission
github: https://github.com/redisson/redisson
實現(xiàn)了分布式和可擴展的 Java 數(shù)據(jù)結構拔恰;
maven依賴:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.11.5</version>
</dependency>
demo示例:
public static void main(String[] args) {
// 1. 創(chuàng)建連接配置
Config config = new Config();
config.useSingleServer().setAddress("redis://10.242.24.246:6379");
// 2. 創(chuàng)建 redisson 實例
RedissonClient client = Redisson.create(config);
// 操作數(shù)據(jù)
RBucket<Object> bucket = client.getBucket("name");
bucket.set("caojiantao");
System.out.println(bucket.get());
// 3. 關閉連接實例
client.shutdown();
}
8.2 springboot集成
8.2.1 maven 依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
注:springboot 2.x 之后使用了 Lettuce 替換掉了底層 Jedis 的依賴。
8.2.2 屬性配置
在 application.yml 添加下面屬性
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456
# 連接池配置(根據(jù)需要)
lettuce:
pool:
max-idle: 8
8.2.3 基本使用
springboot 默認注入了 RedisTemplate 和 StringRedisTemplate 兩個實例用來操作 Redis基括,前者 key 和 value 都是采用 JDK 序列化颜懊,后者只能操作 String 數(shù)據(jù)類型;
可直接注入使用风皿;
@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 類。
8.2.4 自定義 Template
默認注入的兩種 RedisTemplate 顯然不適用所有的業(yè)務場景桐款,自定義 Template 一般只需下列兩個步驟咸这;
- 自定義 RedisSerializer;
- 注入自定義 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;
}
}
9 分布式解決方案
9.1 主從同步
將所有數(shù)據(jù)存儲到單個 Redis 主要存在兩個問題朋凉;
- 數(shù)據(jù)備份州丹;
- 數(shù)據(jù)量過大降低性能;
主從模式很好的解決了以上問題杂彭。一個 Redis 實例作為主機 master墓毒,其他的作為從機 slave,主機主要用于數(shù)據(jù)的寫入亲怠,從機則主要提供數(shù)據(jù)的讀取所计。從機在啟動時會同步全量主機數(shù)據(jù),主機也會在寫入數(shù)據(jù)的時候同步到所有的從機团秽。
有兩種方式可以設置主從關系主胧;
- 在啟動配置文件指定 replicaof 參數(shù)钾腺;
- 啟動Redis實例后執(zhí)行
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 實例為主機吴旋,有三個從機损肛;
在當前主機寫入數(shù)據(jù);
127.0.0.1:6379> set msg "hello world"
OK
在其他任意從機執(zhí)行獲取操作荣瑟;
127.0.0.1:6382> get msg
"hello world"
已經(jīng)成功設置主從同步治拿。
9.2 哨兵模式
主從模式存在一定的弊端,master 一旦發(fā)生宕機笆焰,主從同步過程將會中斷劫谅。
Sentinel(哨兵)作為一個單獨的服務,用來監(jiān)控 master 主機嚷掠,間接監(jiān)控所有 slave 從機捏检,如下圖所示;
sentinel 主要有以下三個特點不皆;
- 監(jiān)控 Redis 實例是否正常運行贯城;
- 節(jié)點發(fā)生故障,能夠通知另外霹娄;
當master發(fā)生故障能犯,sentinel 會采用在當前 sentinel 集群中投票方式,從當前所有 slave 中犬耻,推舉一個作為新的master踩晶,從而保證了 Redis 的高可用性。
9.3 集群模式
在哨兵模式下香追,每個 Redis 實例都是存儲的全量數(shù)據(jù)合瓢。為了最大化利用內(nèi)存空間,采用集群模式透典,即分布式存儲晴楔,每臺 Redis 存儲不同的內(nèi)容。Redis 集群沒有使用一致性hash峭咒,而是引入了哈希槽的概念 税弃。
數(shù)據(jù)存儲在 16384 個 slot(插槽)中,所有的數(shù)據(jù)都是根據(jù)一定算法映射到某個 slot 中凑队;
為什么是 16384: https://github.com/antirez/redis/issues/2576
集群模式至少三個Redis節(jié)點则果,否則會提示:
./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目錄創(chuàng)建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示例,最后創(chuàng)建集群款青;
./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
控制臺輸出創(chuàng)建集群信息:
>>> 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這個節(jié)點,注意連接命令:
./redis-cli -c -p 7000
127.0.0.1:7000> get name
-> Redirected to slot [5798] located at 127.0.0.1:7001
(nil)
9.3.1 添加節(jié)點
假如當前集群為 7000, 7001, 7002 三個節(jié)點抡草,正確配置啟動新節(jié)點 7003 后執(zhí)行命令:
[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.
添加的新節(jié)點沒有分配 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
...
...
節(jié)點檢查:
[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.
9.3.2 刪除節(jié)點
首先將該節(jié)點的 slots 轉(zhuǎn)移(同新增),然后執(zhí)行刪除節(jié)點操作:
[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.
9.4 分布式鎖
場景:定時任務集群部署康震,Job 需要加鎖單次執(zhí)行燎含;
方案:基于 Redis 實現(xiàn)分布式鎖,以 Job 唯一標識為 key腿短,設置 expiration屏箍,在 Job 執(zhí)行前獲取鎖判定;
優(yōu)點:實現(xiàn)較為簡單橘忱,過期策略防止死鎖铣除,效率較高;
基于 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 通常用作標識加鎖請求的唯一性,只有對應的加鎖請求敲长,才能成功解鎖郎嫁。防止某個客戶端操作阻塞很久,鎖超時自動釋放被另外客戶端拿到祈噪,然后自己又執(zhí)行釋放鎖釋放掉其他客戶端當前持有的鎖泽铛。
解鎖:
/**
* 釋放鎖
*
* @param lockKey 加鎖的KEY
* @param requestId 加鎖客戶端唯一ID標識
* @return 是否釋放成功
*/
public boolean releaseLock(String lockKey, String requestId) {
// Lua代碼,一次性執(zhí)行保證原子性辑鲤,避免并發(fā)問題
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;
}
注意解鎖姿勢盔腔,保證操作原子性。
9.4.1 鎖超時
當鎖的持有時間無法估算月褥,存在鎖超時導致被自動釋放掉的可能弛随。可以在獲取鎖成功時宁赤,開啟一個定時線程詢問持有鎖狀況,若當前仍持有鎖狀態(tài)决左,則刷新過期時間走贪。
參考 Redisson 實現(xiàn):https://github.com/redisson/redisson/blob/master/redisson/src/main/java/org/redisson/RedissonLock.java (renewExpiration)
9.4.2 RedLock
主從復制時惑芭,獲取鎖成功還未同步 slave 時,master 宕機會出現(xiàn)數(shù)據(jù)不一致情況擦秽。
官方提供名為 RedLock 的算法思想:
- 獲取當前時間;
- 嘗試按順序在 N 個節(jié)點獲取鎖;
- 在大多數(shù)節(jié)點獲取鎖成功缩搅,則認為成功越败;
- 如果鎖獲取成功了,鎖有效時間就是最初的鎖有效時間減去之前獲取鎖所消耗的時間硼瓣;
- 如果鎖獲取失敗了究飞,將會嘗試釋放所有節(jié)點的鎖;
Redlock 算法: https://redis.io/topics/distlock
10 緩存雪崩堂鲤、緩存穿透和緩存擊穿
10.1 緩存雪崩
描述:同一時間緩存大面積失效亿傅,數(shù)量級的請求直接打到數(shù)據(jù)庫。
方案:給緩存失效時間加上一個隨機數(shù)瘟栖。
10.2 緩存穿透
描述:請求不符合緩存條件葵擎,直接打到數(shù)據(jù)庫。
方案:參數(shù)做好校驗半哟,null 值也可緩存酬滤。
10.3 緩存擊穿
描述:熱點數(shù)據(jù)失效瞬間,大量對該熱點數(shù)據(jù)的請求直接打到數(shù)據(jù)庫寓涨。
方案:設置緩存永不過期盯串,或者查詢引入互斥鎖。