來自公眾號:是Kerwin啊
作者:柯小賢
Windows Redis
安裝
鏈接: https://pan.baidu.com/s/1MJnzX_qRuNXJI09euzkPGA 提取碼: 2c6w 復(fù)制這段內(nèi)容后打開百度網(wǎng)盤手機(jī)App巩那,操作更方便哦
無腦下一步即可
使用
出現(xiàn)錯誤:
creating server tcp listening socket 127.0.0.1:6379: bind No error
解決方案:
- redis-cli.exe
- shutdown
- exit
- redis-server.exe redis.windows.conf
啟動:redis-server.exe redis.windows.conf
客戶端啟動:redis-cli.exe (不修改配置的話默認(rèn)即可)
redis-cli.exe -h 127.0.0.1 -p 6379 -a password
基本文件說明
可執(zhí)行文件 | 作用說明 |
---|---|
redis-server | redis服務(wù) |
redis-cli | redis命令行工具 |
redis-benchmark | 基準(zhǔn)性能測試工具 |
redis-check-aof | AOF持久化文件檢測和修復(fù)工具 |
redis-check-dump | RDB持久化文件檢測和修復(fù)工具 |
redis-sentinel | 啟動哨兵 |
redis-trib | cluster集群構(gòu)建工具 |
基礎(chǔ)命令
命令 | 說明 |
---|---|
keys * | redis允許模糊查詢key 有3個通配符 *穴豫、?杨帽、[] |
del key | 刪除key |
exists kxm | 判斷是否存在 |
expire key 20 | 設(shè)置過期時間 - 秒 |
pexpire key 20000 | 設(shè)置過期時間 - 毫秒 |
move kxm 2 | 移動key到指定位置庫中 2號庫 |
persist key | 移除過期時間,key將會永久存在 成功設(shè)置返回1 否則返回0 |
pttl key | 以毫秒為單位返回 key 的剩余的過期時間 |
ttl key | 以秒為單位贡珊,返回給定 key 的剩余生存時間 |
randomkey | 從當(dāng)前數(shù)據(jù)庫中隨機(jī)返回一個 key |
rename key newkxy | 更改key的名字摹恨,如果重復(fù)了會覆蓋 |
renamenx kxm key | 僅當(dāng) newkey 不存在時,將 key 改名為 newkey |
type key | 返回 key 所儲存的值的類型 |
select 0 | 選擇第一個庫 |
ping | 返回PONG 表示連接正常 |
quit | 關(guān)閉當(dāng)前連接 |
字符串命令
命令 | 說明 |
---|---|
set key aaa | 設(shè)置指定 key 的值 |
get key | 獲取指定 key 的值 |
getrange key 0 1 | 返回 key 中字符串值的子字符 包含 0 和 1 包含關(guān)系 |
getset key aaaaaaaa | 將給定 key 的值設(shè)為 value 茫孔,并返回 key 的舊值(old value) |
mget key kxm | 獲取所有(一個或多個)給定 key 的值 |
setex test 5 "this is my test" | 將值 value 關(guān)聯(lián)到 key ,并將 key 的過期時間設(shè)為 seconds (以秒為單位) |
setnx test test | 只有在 key 不存在時設(shè)置 key 的值 (用于分布式鎖) |
strlen test | 返回 key 所儲存的字符串值的長度 |
mset key1 "1" key2 "2" | 同時設(shè)置一個或多個 key-value 對 |
msetnx key3 "a" key2 "b" | 同時設(shè)置一個或多個 key-value 對被芳,當(dāng)且僅當(dāng)所有給定 key 都不存在 其中一個失敗則全部失敗 |
incr key | 將 key 中儲存的數(shù)字值增一 -> key的值 比如為 數(shù)字類型字符串 返回增加后的結(jié)果 |
incrby num 1000 | 將 key 中儲存的數(shù)字值增指定的值 -> key的值 比如為 數(shù)字類型字符串 返回增加后的結(jié)果 |
decr key | 同 -> 減一 |
decrby num 500 | 同 -> 減指定值 |
append key 1123123 | 如果 key 已經(jīng)存在并且是一個字符串缰贝, APPEND 命令將指定的 value 追加到該 key 原來值(value)的末尾 返回字符串長度 |
哈希(Hash)命令
命令 | 說明 |
---|---|
hdel key field1 [field2] | 刪除一個或多個哈希表字段 |
hexistskey field | 查看哈希表 key 中,指定的字段是否存在 |
hget key field | 獲取存儲在哈希表中指定字段的值 |
hgetall key | 獲取在哈希表中指定 key 的所有字段和值 |
hincrby hash yeary 1 | 為哈希表 key 中的指定字段的整數(shù)值加上增量 increment |
hkeys hash | 獲取所有哈希表中的字段 |
hlen hash | 獲取哈希表中字段的數(shù)量 |
hmget hash name year | 獲取所有給定字段的值 |
hmset hash name "i am kxm" year 24 | 同時將多個 field-value (域-值)對設(shè)置到哈希表 key 中 |
hset hash name kxm | 將哈希表 key 中的字段 field 的值設(shè)為 value |
hsetnx key field value | 只有在字段 field 不存在時筐钟,設(shè)置哈希表字段的值 |
hvals hash | 獲取哈希表中所有值 |
hexists hash name | 是否存在 |
編碼: field value 值由 ziplist 及 hashtable 兩種編碼格式
字段較少的時候采用ziplist,字段較多的時候會變成hashtable編碼
列表(List)命令
Redis列表是簡單的字符串列表赋朦,按照插入順序排序篓冲。你可以添加一個元素到列表的頭部(左邊)或者尾部(右邊)
一個列表最多可以包含 232 - 1 個元素 (4294967295, 每個列表超過40億個元素)
容量 -> 集合,有序集合也是如此
命令 | 說明 |
---|---|
lpush list php | 將一個值插入到列表頭部 返回列表長度 |
lindex list 0 | 通過索引獲取列表中的元素 |
blpop key1 [key2 ] timeout | 移出并獲取列表的第一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止 |
brpop key1 [key2 ] timeout | 移出并獲取列表的最后一個元素宠哄, 如果列表沒有元素會阻塞列表直到等待超時或發(fā)現(xiàn)可彈出元素為止 |
linsert list before 3 4 | 在值 3 前插入 4 前即為頂 |
linsert list after 4 5 | 在值4 后插入5 |
llen list | 獲取列表長度 |
lpop list | 移出并獲取列表的第一個元素 |
lpush list c++ c | 將一個或多個值插入到列表頭部 |
lrange list 0 1 | 獲取列表指定范圍內(nèi)的元素 包含0和1 -1 代表所有 (lrange list 0 -1) |
lrem list 1 c | 移除list 集合中 值為 c 的 一個元素壹将, 1 代表count 即移除幾個 |
lset list 0 "this is update" | 通過索引設(shè)置列表元素的值 |
ltrim list 1 5 | 對一個列表進(jìn)行修剪(trim),就是說毛嫉,讓列表只保留指定區(qū)間內(nèi)的元素诽俯,不在指定區(qū)間之內(nèi)的元素都將被刪除 |
rpop list | 移除列表的最后一個元素,返回值為移除的元素 |
rpush list newvalue3 | 從底部添加新值 |
rpoplpush list list2 | 轉(zhuǎn)移列表的數(shù)據(jù) |
集合(Set)命令
Set 是 String 類型的無序集合承粤。集合成員是唯一的暴区,這就意味著集合中不能出現(xiàn)重復(fù)的數(shù)據(jù)
命令 | 說明 | ||
---|---|---|---|
sadd set java php c c++ python | 向集合添加一個或多個成員 | ||
scard set | 獲取集合的成員數(shù) | ||
sdiff key1 [key2] | 返回給定所有集合的差集 數(shù)學(xué)含義差集 | ||
sdiffstore curr set newset (sdiffstore destination key1 [key2]) | 把set和 newset的差值存儲到curr中 | ||
sinter set newset | 返回給定所有集合的交集 | ||
sinterstore curr set newset (sinterstoredestination key1 [key2]) | 同 | ||
sismember set c# | 判斷 member 元素是否是集合 key 的成員 | ||
smembers set | 返回集合中的所有成員 | ||
srandmember set 2 | 隨機(jī)抽取兩個key (抽獎實(shí)現(xiàn)美滋滋) | ||
smove set newtest java (smove source destination member) | 將 member 元素從 source 集合移動到 destination 集合 | ||
sunion set newset | 返回所有給定集合的并集 | ||
srem set java | 刪除 | ||
spop set | 從集合中彈出一個元素 | ||
sdiff | sinter | sunion | 操作:集合間運(yùn)算:差集 |
有序集合(sorted set)命令
Redis 有序集合和集合一樣也是string類型元素的集合,且不允許重復(fù)的成員。
不同的是每個元素都會關(guān)聯(lián)一個double類型的分?jǐn)?shù)辛臊。redis正是通過分?jǐn)?shù)來為集合中的成員進(jìn)行從小到大的排序仙粱。
有序集合的成員是唯一的,但分?jǐn)?shù)(score)卻可以重復(fù)。
命令 | 說明 |
---|---|
zadd sort 1 java 2 python | 向有序集合添加一個或多個成員彻舰,或者更新已存在成員的分?jǐn)?shù) |
zcard sort | 獲取有序集合的成員數(shù) |
zcount sort 0 1 | 計(jì)算在有序集合中指定區(qū)間分?jǐn)?shù)的成員數(shù) |
zincrby sort 500 java | 有序集合中對指定成員的分?jǐn)?shù)加上增量 increment |
zscore sort java | 返回有序集中伐割,成員的分?jǐn)?shù)值 |
zrange sort 0 -1 | 獲取指定序號的值,-1代表全部 |
zrangebyscore sort 0 5 | 分?jǐn)?shù)符合范圍的值 |
zrangebyscore sort 0 5 limit 0 1 | 分頁 limit 0代表頁碼刃唤,1代表每頁顯示數(shù)量 |
zrem sort java | 移除元素 |
zremrangebyrank sort 0 1 | 按照排名范圍刪除元素 |
zremrangebyscore sort 0 1 | 按照分?jǐn)?shù)范圍刪除元素 |
zrevrank sort c# | 返回有序集合中指定成員的排名隔心,有序集成員按分?jǐn)?shù)值遞減(從大到小)排序 |
發(fā)布訂閱
開啟兩個客戶端
A客戶端訂閱頻道:subscribe redisChat (頻道名字為 redisChat)
B客戶端發(fā)布內(nèi)容:publish redisChat "Hello, this is my wor" (內(nèi)容是 hello....)
A客戶端即為自動收到內(nèi)容, 原理圖如下:
命令 | 說明 |
---|---|
pubsub channels | 查看當(dāng)前redis 有多少個頻道 |
pubsub numsub chat1 | 查看某個頻道的訂閱者數(shù)量 |
unsubscrible chat1 | 退訂指定頻道 |
psubscribe java.* | 訂閱一組頻道 |
Redis 事務(wù)
Redis 事務(wù)可以一次執(zhí)行多個命令尚胞, 并且?guī)в幸韵氯齻€重要的保證:
- 批量操作在發(fā)送 EXEC 命令前被放入隊(duì)列緩存
- 收到 EXEC 命令后進(jìn)入事務(wù)執(zhí)行硬霍,事務(wù)中任意命令執(zhí)行失敗,其余的命令依然被執(zhí)行
- 在事務(wù)執(zhí)行過程笼裳,其他客戶端提交的命令請求不會插入到事務(wù)執(zhí)行命令序列中
一個事務(wù)從開始到執(zhí)行會經(jīng)歷以下三個階段:
- 開始事務(wù)
- 命令入隊(duì)
- 執(zhí)行事務(wù)
注意:redis事務(wù)和數(shù)據(jù)庫事務(wù)不同须尚,redis事務(wù)出錯后最大的特點(diǎn)是崖堤,一剩下的命令會繼續(xù)執(zhí)行,二出錯的數(shù)據(jù)不會回滾
命令 | 說明 |
---|---|
multi | 標(biāo)記一個事務(wù)開始 |
exec | 執(zhí)行事務(wù) |
discard | 事務(wù)開始后輸入命令入隊(duì)過程中耐床,中止事務(wù) |
watch key | 監(jiān)視一個(或多個) key 密幔,如果在事務(wù)執(zhí)行之前這個(或這些) key 被其他命令所改動,那么事務(wù)將被打斷 |
unwatch | 取消 WATCH 命令對所有 key 的監(jiān)視 |
Redis 服務(wù)器命令
命令 | 說明 |
---|---|
flushall | 刪除所有數(shù)據(jù)庫的所有key |
flushdb | 刪除當(dāng)前數(shù)據(jù)庫的所有key |
save | 同步保存數(shù)據(jù)到硬盤 |
Redis 數(shù)據(jù)備份與恢復(fù)
Redis SAVE 命令用于創(chuàng)建當(dāng)前數(shù)據(jù)庫的備份
如果需要恢復(fù)數(shù)據(jù)撩轰,只需將備份文件 (dump.rdb) 移動到 redis 安裝目錄并啟動服務(wù)即可胯甩。獲取 redis 目錄可以使用 CONFIG 命令
Redis 性能測試
redis 性能測試的基本命令如下:
redis目錄執(zhí)行:redis-benchmark [option] [option value]
// 會返回各種操作的性能報告(100連接,10000請求)
redis-benchmark -h 127.0.0.1 -p 6379 -c 100 -n 10000
// 100個字節(jié)作為value值進(jìn)行壓測
redis-benchmark -h 127.0.0.1 -p 6379 -q -d 100
Java Redis
Jedis
<!-- jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.2</version>
</dependency>
Jedis配置
############# redis Config #############
# Redis數(shù)據(jù)庫索引(默認(rèn)為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=120.79.88.17
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=123456
# 連接池中的最大空閑連接
spring.redis.jedis.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.jedis.pool.min-idle=0
JedisConfig
@Configuration
public class JedisConfig extends CachingConfigurerSupport {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.max-idle}")
private Integer maxIdle;
@Value("${spring.redis.min-idle}")
private Integer minIdle;
@Bean
public JedisPool redisPoolFactory(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxWaitMillis(3000L);
int timeOut = 3;
return new JedisPool(jedisPoolConfig, host, port, timeOut, password);
}
}
基礎(chǔ)使用
@RunWith(SpringRunner.class)
@SpringBootTest(classes = KerwinBootsApplication.class)
public class ApplicationTests {
@Resource
JedisPool jedisPool;
@Test
public void testJedis () {
Jedis jedis = jedisPool.getResource();
jedis.set("year", String.valueOf(24));
}
}
SpringBoot redis staeter RedisTemplate
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis 2.X 更換為commons-pool2 連接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
############# redis Config #############
# Redis數(shù)據(jù)庫索引(默認(rèn)為0)
spring.redis.database=0
# Redis服務(wù)器地址
spring.redis.host=120.79.88.17
# Redis服務(wù)器連接端口
spring.redis.port=6379
# Redis服務(wù)器連接密碼(默認(rèn)為空)
spring.redis.password=123456
# 連接池最大連接數(shù)(使用負(fù)值表示沒有限制)
spring.redis.jedis.pool.max-active=200
# 連接池最大阻塞等待時間(使用負(fù)值表示沒有限制)
spring.redis.jedis.pool.max-wait=1000ms
# 連接池中的最大空閑連接
spring.redis.jedis.pool.max-idle=8
# 連接池中的最小空閑連接
spring.redis.jedis.pool.min-idle=0
# 連接超時時間(毫秒)
spring.redis.timeout=1000ms
// Cache注解配置類
@Configuration
public class RedisCacheConfig {
@Bean
public KeyGenerator simpleKeyGenerator() {
return (o, method, objects) -> {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(o.getClass().getSimpleName());
stringBuilder.append(".");
stringBuilder.append(method.getName());
stringBuilder.append("[");
for (Object obj : objects) {
stringBuilder.append(obj.toString());
}
stringBuilder.append("]");
return stringBuilder.toString();
};
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 默認(rèn)策略堪嫂,未配置的 key 會使用這個
this.getRedisCacheConfigurationWithTtl(15),
// 指定 key 策略
this.getRedisCacheConfigurationMap()
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(16);
redisCacheConfigurationMap.put("redisTest", this.getRedisCacheConfigurationWithTtl(15));
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> 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);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
// RedisAutoConfiguration
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
@SuppressWarnings("all")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
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);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
// 基礎(chǔ)使用
@Resource
RedisTemplate<String,Object> redisTemplate;
redisTemplate.opsForList().rightPush("user:1:order", dataList.get(3).get("key").toString());
// 注解使用
@Cacheable(value = "redisTest")
public TestBean testBeanAnnotation () {}
Redis使用場景
類型 | 適用場景 |
---|---|
String | 緩存偎箫,限流,計(jì)數(shù)器皆串,分布式鎖淹办,分布式session |
Hash | 存儲用戶信息,用戶主頁訪問量恶复,組合查詢 |
List | 微博關(guān)注人時間軸列表怜森,簡單隊(duì)列 |
Set | 贊,踩谤牡,標(biāo)簽副硅,好友關(guān)系 |
Zset | 排行榜 |
或者簡單消息隊(duì)列,發(fā)布訂閱實(shí)施消息系統(tǒng)等等
String - 緩存
// 1.Cacheable 注解
// controller 調(diào)用 service 時自動判斷有沒有緩存翅萤,如果有就走redis緩存直接返回恐疲,如果沒有則數(shù)據(jù)庫然后自動放入redis中
// 可以設(shè)置過期時間,KEY生成規(guī)則 (KEY生成規(guī)則基于 參數(shù)的toString方法)
@Cacheable(value = "yearScore", key = "#yearScore")
@Override
public List<YearScore> findBy (YearScore yearScore) {}
// 2.手動用緩存
if (redis.hasKey(???) {
return ....
}
redis.set(find from DB)...
String - 限流 | 計(jì)數(shù)器
// 注:這只是一個最簡單的Demo 效率低套么,耗時舊培己,但核心就是這個意思
// 計(jì)數(shù)器也是利用單線程incr...等等
@RequestMapping("/redisLimit")
public String testRedisLimit(String uuid) {
if (jedis.get(uuid) != null) {
Long incr = jedis.incr(uuid);
if (incr > MAX_LIMITTIME) {
return "Failure Request";
} else {
return "Success Request";
}
}
// 設(shè)置Key 起始請求為1,10秒過期 -> 實(shí)際寫法肯定封裝過,這里就是隨便一寫
jedis.set(uuid, "1");
jedis.expire(uuid, 10);
return "Success Request";
}
String - 分布式鎖 (重點(diǎn))
/***
* 核心思路:
* 分布式服務(wù)調(diào)用時setnx,返回1證明拿到胚泌,用完了刪除漱凝,返回0就證明被鎖,等...
* SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
* EX second:設(shè)置鍵的過期時間為second秒
* PX millisecond:設(shè)置鍵的過期時間為millisecond毫秒
* NX:只在鍵不存在時诸迟,才對鍵進(jìn)行設(shè)置操作
* XX:只在鍵已經(jīng)存在時茸炒,才對鍵進(jìn)行設(shè)置操作
*
* 1.設(shè)置鎖
* A. 分布式業(yè)務(wù)統(tǒng)一Key
* B. 設(shè)置Key過期時間
* C. 設(shè)置隨機(jī)value,利用ThreadLocal 線程私有存儲隨機(jī)value
*
* 2.業(yè)務(wù)處理
* ...
*
* 3.解鎖
* A. 無論如何必須解鎖 - finally (超時時間和finally 雙保證)
* B. 要對比是否是本線程上的鎖,所以要對比線程私有value和存儲的value是否一致(避免把別人加鎖的東西刪除了)
*/
@RequestMapping("/redisLock")
public String testRedisLock () {
try {
for(;;){
RedisContextHolder.clear();
String uuid = UUID.randomUUID().toString();
String set = jedis.set(KEY, uuid, "NX", "EX", 1000);
RedisContextHolder.setValue(uuid);
if (!"OK".equals(set)) {
// 進(jìn)入循環(huán)-可以短時間休眠
} else {
// 獲取鎖成功 Do Somethings....
break;
}
}
} finally {
// 解鎖 -> 保證獲取數(shù)據(jù)阵苇,判斷一致以及刪除數(shù)據(jù)三個操作是原子的壁公, 因此如下寫法是不符合的
/*if (RedisContextHolder.getValue() != null && jedis.get(KEY) != null && RedisContextHolder.getValue().equals(jedis.get(KEY))) {
jedis.del(KEY);
}*/
// 正確姿勢 -> 使用Lua腳本,保證原子性
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Object eval = jedis.eval(luaScript, Collections.singletonList(KEY), Collections.singletonList(RedisContextHolder.getValue()));
}
return "鎖創(chuàng)建成功-業(yè)務(wù)處理成功";
}
String - 分布式Session(重點(diǎn))
// 1.首先明白為什么需要分布式session -> nginx負(fù)載均衡 分發(fā)到不同的Tomcat,即使利用IP分發(fā)绅项,可以利用request獲取session紊册,但是其中一個掛了,怎么辦?囊陡? 所以需要分布式session
注意理解其中的區(qū)別 A服務(wù)-用戶校驗(yàn)服務(wù) B服務(wù)-業(yè)務(wù)層
情況A:
A,B 服務(wù)單機(jī)部署:
cookie:登錄成功后芳绩,存儲信息到cookie,A服務(wù)自身通過request設(shè)置session撞反,獲取session妥色,B服務(wù)通過唯一key或者userid 查詢數(shù)據(jù)庫獲取用戶信息
cookie+redis:登錄成功后,存儲信息到cookie遏片,A服務(wù)自身通過request設(shè)置session嘹害,獲取session,B服務(wù)通過唯一key或者userid 查詢redis獲取用戶信息
情況B:
A服務(wù)多節(jié)點(diǎn)部署吮便,B服務(wù)多節(jié)點(diǎn)部署
B服務(wù)獲取用戶信息的方式其實(shí)是不重要的笔呀,必然要查,要么從數(shù)據(jù)庫髓需,要么從cookie
A服務(wù):登錄成功后许师,存儲唯一key到cookie, 與此同時僚匆,A服務(wù)需要把session(KEY-UserInfo)同步到redis中野来,不能存在單純的request(否則nginx分發(fā)到另一個服務(wù)器就完?duì)僮恿耍?
官方實(shí)現(xiàn):
spring-session-data-redis
有一個內(nèi)置攔截器医男,攔截request掏熬,session通過redis交互捻浦,普通使用代碼依然是request.getSession.... 但是實(shí)際上這個session的值已經(jīng)被該組件攔截粗卜,通過redis進(jìn)行同步了
List 簡單隊(duì)列-棧
// 說白了利用redis - list數(shù)據(jù)結(jié)構(gòu) 支持從左從右push屋确,從左從右pop
@Component
public class RedisStack {
@Resource
Jedis jedis;
private final static String KEY = "Stack";
/** push **/
public void push (String value) {
jedis.lpush(KEY, value);
}
/** pop **/
public String pop () {
return jedis.lpop(KEY);
}
}
@Component
public class RedisQueue {
@Resource
JedisPool jedisPool;
private final static String KEY = "Queue";
/** push **/
public void push (String value) {
Jedis jedis = jedisPool.getResource();
jedis.lpush(KEY, value);
}
/** pop **/
public String pop () {
Jedis jedis = jedisPool.getResource();
return jedis.rpop(KEY);
}
}
List 社交類APP - 好友列表
根據(jù)時間顯示好友,多個好友列表续扔,求交集攻臀,并集 顯示共同好友等等...
疑問:難道大廠真的用redis存這些數(shù)據(jù)嗎?纱昧?刨啸?多大的量啊... 我個人認(rèn)為實(shí)際是數(shù)據(jù)庫存用戶id,然后用算法去處理识脆,更省空間
Set 抽獎 | 好友關(guān)系(合设联,并,交集)
// 插入key 及用戶id
sadd cat:1 001 002 003 004 005 006
// 返回抽獎參與人數(shù)
scard cat:1
// 隨機(jī)抽取一個
srandmember cat:1
// 隨機(jī)抽取一人灼捂,并移除
spop cat:1
Zset 排行榜
根據(jù)分?jǐn)?shù)實(shí)現(xiàn)有序列表
微博熱搜:每點(diǎn)擊一次 分?jǐn)?shù)+1 即可
--- 不用數(shù)據(jù)庫目的是因?yàn)楸苊鈕rder by 進(jìn)行全表掃描
常見面試題
Q1:為什么Redis能這么快
1.Redis完全基于內(nèi)存离例,絕大部分請求是純粹的內(nèi)存操作,執(zhí)行效率高悉稠。
2.Redis使用單進(jìn)程單線程模型的(K,V)數(shù)據(jù)庫宫蛆,將數(shù)據(jù)存儲在內(nèi)存中,存取均不會受到硬盤IO的限制的猛,因此其執(zhí)行速度極快耀盗,另外單線程也能處理高并發(fā)請求想虎,還可以避免頻繁上下文切換和鎖的競爭,同時由于單線程操作叛拷,也可以避免各種鎖的使用舌厨,進(jìn)一步提高效率
3.數(shù)據(jù)結(jié)構(gòu)簡單,對數(shù)據(jù)操作也簡單胡诗,Redis不使用表邓线,不會強(qiáng)制用戶對各個關(guān)系進(jìn)行關(guān)聯(lián),不會有復(fù)雜的關(guān)系限制煌恢,其存儲結(jié)構(gòu)就是鍵值對骇陈,類似于HashMap,HashMap最大的優(yōu)點(diǎn)就是存取的時間復(fù)雜度為O(1)
5.C語言編寫瑰抵,效率更高
6.Redis使用多路I/O復(fù)用模型你雌,為非阻塞IO
7.有專門設(shè)計(jì)的RESP協(xié)議
針對第四點(diǎn)進(jìn)行說明 ->
常見的IO模型有四種:
- 同步阻塞IO(Blocking IO):即傳統(tǒng)的IO模型。
- 同步非阻塞IO(Non-blocking IO):默認(rèn)創(chuàng)建的socket都是阻塞的二汛,非阻塞IO要求socket被設(shè)置為NONBLOCK婿崭。注意這里所說的NIO并非Java的NIO(New IO)庫。
- IO多路復(fù)用(IO Multiplexing):即經(jīng)典的Reactor設(shè)計(jì)模式肴颊,有時也稱為異步阻塞IO氓栈,Java中的Selector和Linux中的epoll都是這種模型。
- 異步IO(Asynchronous IO):即經(jīng)典的Proactor設(shè)計(jì)模式婿着,也稱為異步非阻塞IO
同步異步授瘦,阻塞非阻塞的概念:
imageimage假設(shè)Redis采用同步阻塞IO:
Redis主程序(服務(wù)端 單線程)-> 多個客戶端連接(真實(shí)情況是如開發(fā)人員連接redis,程序 redispool連接redis)竟宋,這每一個都對應(yīng)著一個客戶端提完,假設(shè)為100個客戶端,其中一個進(jìn)行交互時候丘侠,如果采用同步阻塞式徒欣,那么剩下的99個都需要原地等待,這勢必是不科學(xué)的蜗字。
IO多路復(fù)用
Redis 采用 I/O 多路復(fù)用模型
I/O 多路復(fù)用模型中打肝,最重要的函數(shù)調(diào)用就是
select
,該方法的能夠同時監(jiān)控多個文件描述符的可讀可寫情況挪捕,當(dāng)其中的某些文件描述符可讀或者可寫時粗梭,select
方法就會返回可讀以及可寫的文件描述符個數(shù)
注:redis默認(rèn)使用的是更加優(yōu)化的算法:epoll
|
| select | poll | epoll |
| --- | --- | --- | --- |
| 操作方式 | 遍歷 | 遍歷 | 回調(diào) |
| 底層實(shí)現(xiàn) | 數(shù)組 | 鏈表 | 哈希表 |
| IO效率 | 每次調(diào)用都進(jìn)行線性遍歷,時間復(fù)雜度為O(n) | 每次調(diào)用都進(jìn)行線性遍歷担神,時間復(fù)雜度為O(n) | 事件通知方式楼吃,每當(dāng)fd就緒,系統(tǒng)注冊的回調(diào)函數(shù)就會被調(diào)用,將就緒fd放到readyList里面孩锡,時間復(fù)雜度O(1) |
| 最大連接數(shù) | 1024(x86)或2048(x64) | 無上限 | 無上限 |所以我們可以說Redis是這樣的:服務(wù)端單線程毫無疑問酷宵,多客戶端連接時候,如果客戶端沒有發(fā)起任何動作躬窜,則服務(wù)端會把其視為不活躍的IO流浇垦,將其掛起,當(dāng)有真正的動作時荣挨,會通過回調(diào)的方式執(zhí)行相應(yīng)的事件
Q2:從海量Key里查詢出某一個固定前綴的Key
A. 笨辦法:KEYS [pattern] 注意key很多的話男韧,這樣做肯定會出問題,造成redis崩潰
B. SCAN cursor [MATCH pattern] [COUNT count] 游標(biāo)方式查找
Q3:如何通過Redis實(shí)現(xiàn)分布式鎖
見上文
Q4:如何實(shí)現(xiàn)異步隊(duì)列
上文說到利用 redis-list 實(shí)現(xiàn)隊(duì)列
假設(shè)場景:A服務(wù)生產(chǎn)數(shù)據(jù) - B服務(wù)消費(fèi)數(shù)據(jù)默垄,即可利用此種模型構(gòu)造-生產(chǎn)消費(fèi)者模型
1. 使用Redis中的List作為隊(duì)列
2.使用BLPOP key [key...] timeout -> LPOP key [key ...] timeout:阻塞直到隊(duì)列有消息或者超時
(方案二:解決方案一中此虑,拿數(shù)據(jù)的時,生產(chǎn)者尚未生產(chǎn)的情況)
3.pub/sub:主題訂閱者模式
基于reds的終極方案口锭,上文有介紹朦前,基于發(fā)布/訂閱模式
缺點(diǎn):消息的發(fā)布是無狀態(tài)的,無法保證可達(dá)鹃操。對于發(fā)布者來說韭寸,消息是“即發(fā)即失”的,此時如果某個消費(fèi)者在生產(chǎn)者發(fā)布消息時下線荆隘,重新上線之后恩伺,是無法接收該消息的,要解決該問題需要使用專業(yè)的消息隊(duì)列
Q5:Redis支持的數(shù)據(jù)類型椰拒?
見上文
Q6:什么是Redis持久化晶渠?Redis有哪幾種持久化方式?優(yōu)缺點(diǎn)是什么耸三?
持久化就是把內(nèi)存的數(shù)據(jù)寫到磁盤中去乱陡,防止服務(wù)宕機(jī)了內(nèi)存數(shù)據(jù)丟失浇揩。
Redis 提供了兩種持久化方式:RDB(默認(rèn)) 和AOF
RDB:
rdb是Redis DataBase縮寫
功能核心函數(shù)rdbSave(生成RDB文件)和rdbLoad(從文件加載內(nèi)存)兩個函數(shù)
imageRDB: 把當(dāng)前進(jìn)程數(shù)據(jù)生成快照文件保存到硬盤的過程仪壮。分為手動觸發(fā)和自動觸發(fā)
手動觸發(fā) -> save (不推薦,阻塞嚴(yán)重) bgsave -> (save的優(yōu)化版胳徽,微秒級阻塞)
shutdowm 關(guān)閉服務(wù)時积锅,如果沒有配置AOF,則會使用bgsave持久化數(shù)據(jù)
bgsave - 工作原理
會從當(dāng)前父進(jìn)程fork一個子進(jìn)程养盗,然后生成rdb文件
缺點(diǎn):頻率低缚陷,無法做到實(shí)時持久化
AOF:
Aof是Append-only file縮寫,AOF文件存儲的也是RESP協(xié)議
image每當(dāng)執(zhí)行服務(wù)器(定時)任務(wù)或者函數(shù)時flushAppendOnlyFile 函數(shù)都會被調(diào)用往核, 這個函數(shù)執(zhí)行以下兩個工作
aof寫入保存:
WRITE:根據(jù)條件箫爷,將 aof_buf 中的緩存寫入到 AOF 文件
SAVE:根據(jù)條件,調(diào)用 fsync 或 fdatasync 函數(shù),將 AOF 文件保存到磁盤中虎锚。
存儲結(jié)構(gòu):
內(nèi)容是redis通訊協(xié)議(RESP )格式的命令文本存儲
原理:
相當(dāng)于存儲了redis的執(zhí)行命令(類似mysql的sql語句日志)硫痰,數(shù)據(jù)的完整性和一致性更高
比較:
1、aof文件比rdb更新頻率高
2窜护、aof比rdb更安全
3效斑、rdb性能更好
PS:正確停止redis服務(wù) 應(yīng)該基于連接命令 加再上 shutdown -> 否則數(shù)據(jù)持久化會出現(xiàn)問題
Q7:redis通訊協(xié)議(RESP)
Redis 即 REmote Dictionary Server (遠(yuǎn)程字典服務(wù));
而Redis的協(xié)議規(guī)范是 Redis Serialization Protocol (Redis序列化協(xié)議)
RESP 是redis客戶端和服務(wù)端之前使用的一種通訊協(xié)議柱徙;
RESP 的特點(diǎn):實(shí)現(xiàn)簡單缓屠、快速解析、可讀性好
協(xié)議如下:
客戶端以規(guī)定格式的形式發(fā)送命令給服務(wù)器
set key value 協(xié)議翻譯如下: * 3 -> 表示以下有幾組命令 $ 3 -> 表示命令長度是3 SET $6 -> 表示長度是6 keykey $5 -> 表示長度是5 value 完整即: * 3 $ 3 SET $6 keykey $5 value
服務(wù)器在執(zhí)行最后一條命令后护侮,返回結(jié)果敌完,返回格式如下:
For Simple Strings the first byte of the reply is "+" 回復(fù)
For Errors the first byte of the reply is "-" 錯誤
For Integers the first byte of the reply is ":" 整數(shù)
For Bulk Strings the first byte of the reply is "$" 字符串
For Arrays the first byte of the reply is "*" 數(shù)組
// 偽造6379 redis-服務(wù)端,監(jiān)聽 jedis發(fā)送的協(xié)議內(nèi)容
public class SocketApp {
/***
* 監(jiān)聽 6379 傳輸?shù)臄?shù)據(jù)
* JVM端口需要進(jìn)行設(shè)置
*/
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(6379);
Socket redis = serverSocket.accept();
byte[] result = new byte[2048];
redis.getInputStream().read(result);
System.out.println(new String(result));
} catch (Exception e) {
e.printStackTrace();
}
}
}
// jedis連接-發(fā)送命令
public class App {
public static void main(String[] args){
Jedis jedis = new Jedis("127.0.0.1");
jedis.set("key", "This is value.");
jedis.close();
}
}
// 監(jiān)聽命令內(nèi)容如下:
*3
$3
SET
$3
key
$14
Q8:redis架構(gòu)有哪些
單節(jié)點(diǎn)
主從復(fù)制
Master-slave 主從賦值羊初,此種結(jié)構(gòu)可以考慮關(guān)閉master的持久化蠢挡,只讓從數(shù)據(jù)庫進(jìn)行持久化,另外可以通過讀寫分離凳忙,緩解主服務(wù)器壓力
哨兵
Redis sentinel 是一個分布式系統(tǒng)中監(jiān)控 redis 主從服務(wù)器业踏,并在主服務(wù)器下線時自動進(jìn)行故障轉(zhuǎn)移。其中三個特性:
監(jiān)控(Monitoring): Sentinel 會不斷地檢查你的主服務(wù)器和從服務(wù)器是否運(yùn)作正常涧卵。
提醒(Notification): 當(dāng)被監(jiān)控的某個 Redis 服務(wù)器出現(xiàn)問題時勤家, Sentinel 可以通過 API 向管理員或者其他應(yīng)用程序發(fā)送通知。
自動故障遷移(Automatic failover): 當(dāng)一個主服務(wù)器不能正常工作時柳恐, Sentinel 會開始一次自動故障遷移操作伐脖。
特點(diǎn):
1、保證高可用
2乐设、監(jiān)控各個節(jié)點(diǎn)
3讼庇、自動故障遷移
缺點(diǎn):主從模式,切換需要時間丟數(shù)據(jù)
沒有解決 master 寫的壓力
集群
image從redis 3.0之后版本支持redis-cluster集群近尚,Redis-Cluster采用無中心結(jié)構(gòu)蠕啄,每個節(jié)點(diǎn)保存數(shù)據(jù)和整個集群狀態(tài),每個節(jié)點(diǎn)都和其他所有節(jié)點(diǎn)連接。
特點(diǎn):
1戈锻、無中心架構(gòu)(不存在哪個節(jié)點(diǎn)影響性能瓶頸)歼跟,少了 proxy 層。
2格遭、數(shù)據(jù)按照 slot 存儲分布在多個節(jié)點(diǎn)哈街,節(jié)點(diǎn)間數(shù)據(jù)共享,可動態(tài)調(diào)整數(shù)據(jù)分布拒迅。
3骚秦、可擴(kuò)展性她倘,可線性擴(kuò)展到 1000 個節(jié)點(diǎn),節(jié)點(diǎn)可動態(tài)添加或刪除作箍。
4帝牡、高可用性,部分節(jié)點(diǎn)不可用時蒙揣,集群仍可用靶溜。通過增加 Slave 做備份數(shù)據(jù)副本
5、實(shí)現(xiàn)故障自動 failover懒震,節(jié)點(diǎn)之間通過 gossip 協(xié)議交換狀態(tài)信息罩息,用投票機(jī)制完成 Slave到 Master 的角色提升。
缺點(diǎn):
1个扰、資源隔離性較差瓷炮,容易出現(xiàn)相互影響的情況。
2递宅、數(shù)據(jù)通過異步復(fù)制,不保證數(shù)據(jù)的強(qiáng)一致性
Q9:Redis集群-如何從海量數(shù)據(jù)里快速找到所需娘香?
-
分片
按照某種規(guī)則去劃分?jǐn)?shù)據(jù),分散存儲在多個節(jié)點(diǎn)上办龄。通過將數(shù)據(jù)分到多個Redis服務(wù)器上烘绽,來減輕單個Redis服務(wù)器的壓力。
-
一致性Hash算法
既然要將數(shù)據(jù)進(jìn)行分片俐填,那么通常的做法就是獲取節(jié)點(diǎn)的Hash值安接,然后根據(jù)節(jié)點(diǎn)數(shù)求模,但這樣的方法有明顯的弊端英融,當(dāng)Redis節(jié)點(diǎn)數(shù)需要動態(tài)增加或減少的時候盏檐,會造成大量的Key無法被命中。所以Redis中引入了一致性Hash算法驶悟。該算法對2^32 取模胡野,將Hash值空間組成虛擬的圓環(huán),整個圓環(huán)按順時針方向組織痕鳍,每個節(jié)點(diǎn)依次為0硫豆、1、2...2^32-1额获,之后將每個服務(wù)器進(jìn)行Hash運(yùn)算够庙,確定服務(wù)器在這個Hash環(huán)上的地址恭应,確定了服務(wù)器地址后抄邀,對數(shù)據(jù)使用同樣的Hash算法,將數(shù)據(jù)定位到特定的Redis服務(wù)器上昼榛。如果定位到的地方?jīng)]有Redis服務(wù)器實(shí)例境肾,則繼續(xù)順時針尋找剔难,找到的第一臺服務(wù)器即該數(shù)據(jù)最終的服務(wù)器位置。
image
Hash環(huán)的數(shù)據(jù)傾斜問題
Hash環(huán)在服務(wù)器節(jié)點(diǎn)很少的時候奥喻,容易遇到服務(wù)器節(jié)點(diǎn)不均勻的問題偶宫,這會造成數(shù)據(jù)傾斜,數(shù)據(jù)傾斜指的是被緩存的對象大部分集中在Redis集群的其中一臺或幾臺服務(wù)器上环鲤。
如上圖纯趋,一致性Hash算法運(yùn)算后的數(shù)據(jù)大部分被存放在A節(jié)點(diǎn)上,而B節(jié)點(diǎn)只存放了少量的數(shù)據(jù)冷离,久而久之A節(jié)點(diǎn)將被撐爆吵冒。引入虛擬節(jié)點(diǎn)
例如上圖:將NodeA和NodeB兩個節(jié)點(diǎn)分為Node A#1-A#3 NodeB#1-B#3。
Q10:什么是緩存穿透西剥?如何避免痹栖?什么是緩存雪崩?如何避免瞭空?什么是緩存擊穿揪阿?如何避免?
緩存穿透
一般的緩存系統(tǒng)咆畏,都是按照key去緩存查詢南捂,如果不存在對應(yīng)的value,就應(yīng)該去后端系統(tǒng)查找(比如DB)旧找。一些惡意的請求會故意查詢不存在的key,請求量很大黑毅,就會對后端系統(tǒng)造成很大的壓力。這就叫做緩存穿透钦讳。
如何避免矿瘦?
1:對查詢結(jié)果為空的情況也進(jìn)行緩存,緩存時間設(shè)置短一點(diǎn)愿卒,或者該key對應(yīng)的數(shù)據(jù)insert了之后清理緩存缚去。
2:對一定不存在的key進(jìn)行過濾∏砜可以把所有的可能存在的key放到一個大的Bitmap中易结,查詢時通過該bitmap過濾。
3:由于請求參數(shù)是不合法的(每次都請求不存在的參數(shù))柜候,于是我們可以使用布隆過濾器(Bloomfilter)或壓縮filter提前進(jìn)行攔截蕴忆,不合法就不讓這個請求進(jìn)入到數(shù)據(jù)庫層
緩存雪崩
當(dāng)緩存服務(wù)器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候惭蟋,會給后端系統(tǒng)帶來很大壓力廉嚼。導(dǎo)致系統(tǒng)崩潰。
如何避免辅柴?
1:在緩存失效后箩溃,通過加鎖或者隊(duì)列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量瞭吃。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待涣旨。
2:做二級緩存歪架,A1為原始緩存,A2為拷貝緩存霹陡,A1失效時和蚪,可以訪問A2,A1緩存失效時間設(shè)置為短期烹棉,A2設(shè)置為長期
3:不同的key惠呼,設(shè)置不同的過期時間,讓緩存失效的時間點(diǎn)盡量均勻峦耘。
4:啟用限流策略剔蹋,盡量避免數(shù)據(jù)庫被干掉
緩存擊穿
概念一個存在的key,在緩存過期的一刻辅髓,同時有大量的請求泣崩,這些請求都會擊穿到DB,造成瞬時DB請求量大洛口、壓力驟增矫付。
解決方案A. 在訪問key之前,采用SETNX(set if not exists)來設(shè)置另一個短期key來鎖住當(dāng)前key的訪問第焰,訪問結(jié)束再刪除該短期key
B. 服務(wù)層處理 - 方法加鎖 + 雙重校驗(yàn):
// 鎖-實(shí)例 private Lock lock = new ReentrantLock(); public String getProductImgUrlById(String id){ // 獲取緩存 String product = jedisClient.get(PRODUCT_KEY + id); if (null == product) { // 如果沒有獲取鎖等待3秒买优,SECONDS代表:秒 try { if (lock.tryLock(3, TimeUnit.SECONDS)) { try { // 獲取鎖后再查一次,查到了直接返回結(jié)果 product = jedisClient.get(PRODUCT_KEY + id); if (null == product) { // .... } return product; } catch (Exception e) { product = jedisClient.get(PRODUCT_KEY + id); } finally { // 釋放鎖(成功挺举、失敗都必須釋放杀赢,如果是lock.tryLock()方法會一直阻塞在這) lock.unlock(); } } else { product = jedisClient.get(PRODUCT_KEY + id); } } catch (InterruptedException e) { product = jedisClient.get(PRODUCT_KEY + id); } } return product; }
|
| 解釋 | 基礎(chǔ)解決方案 |
| --- | --- | --- |
| 緩存穿透 | 訪問一個不存在的key,緩存不起作用湘纵,請求會穿透到DB脂崔,流量大時DB會掛掉 | 1.采用布隆過濾器,使用一個足夠大的bitmap梧喷,用于存儲可能訪問的key砌左,不存在的key直接被過濾;2.訪問key未在DB查詢到值铺敌,也將空值寫進(jìn)緩存汇歹,但可以設(shè)置較短過期時間 |
| 緩存雪崩 | 大量的key設(shè)置了相同的過期時間,導(dǎo)致在緩存在同一時刻全部失效偿凭,造成瞬時DB請求量大产弹、壓力驟增,引起雪崩 | 可以給緩存設(shè)置過期時間時加上一個隨機(jī)值時間笔喉,使得每個key的過期時間分布開來取视,不會集中在同一時刻失效 |
| 緩存擊穿 | 一個存在的key硝皂,在緩存過期的一刻常挚,同時有大量的請求作谭,這些請求都會擊穿到DB,造成瞬時DB請求量大奄毡、壓力驟增 | 在訪問key之前折欠,采用SETNX(set if not exists)來設(shè)置另一個短期key來鎖住當(dāng)前key的訪問,訪問結(jié)束再刪除該短期key |
Q11:緩存與數(shù)據(jù)庫雙寫一致
如果僅僅是讀數(shù)據(jù)吼过,沒有此類問題
如果是新增數(shù)據(jù)锐秦,也沒有此類問題
當(dāng)數(shù)據(jù)需要更新時,如何保證緩存與數(shù)據(jù)庫的雙寫一致性盗忱?
三種更新策略:
- 先更新數(shù)據(jù)庫酱床,再更新緩存 ->
- 先刪除緩存,再更新數(shù)據(jù)庫
- 先更新數(shù)據(jù)庫趟佃,再刪除緩存
方案一:并發(fā)的時候扇谣,執(zhí)行順序無法保證,可能A先更新數(shù)據(jù)庫闲昭,但B后更新數(shù)據(jù)庫但先更新緩存
加鎖的話罐寨,確實(shí)可以避免,但這樣吞吐量會下降序矩,可以根據(jù)業(yè)務(wù)場景考慮
方案二:該方案會導(dǎo)致不一致的原因是鸯绿。同時有一個請求A進(jìn)行更新操作,另一個請求B進(jìn)行查詢操作簸淀。那么會出現(xiàn)如下情形:(1)請求A進(jìn)行寫操作瓶蝴,刪除緩存(2)請求B查詢發(fā)現(xiàn)緩存不存在(3)請求B去數(shù)據(jù)庫查詢得到舊值(4)請求B將舊值寫入緩存(5)請求A將新值寫入數(shù)據(jù)庫
因此采用:采用延時雙刪策略 即進(jìn)入邏輯就刪除Key,執(zhí)行完操作租幕,延時再刪除key
方案三:更新數(shù)據(jù)庫 - 刪除緩存 可能出現(xiàn)問題的場景:
(1)緩存剛好失效(2)請求A查詢數(shù)據(jù)庫囊蓝,得一個舊值(3)請求B將新值寫入數(shù)據(jù)庫(4)請求B刪除緩存(5)請求A將查到的舊值寫入緩存
先天條件要求:請求第二步的讀取操作耗時要大于更新操作,條件較為苛刻
但如果真的發(fā)生怎么處理令蛉?
A. 給鍵設(shè)置合理的過期時間
B. 異步延時刪除key
Q12:何保證Redis中的數(shù)據(jù)都是熱點(diǎn)數(shù)據(jù)
A. 可以通過手工或者主動方式聚霜,去加載熱點(diǎn)數(shù)據(jù)
B. Redis有其自己的數(shù)據(jù)淘汰策略:
redis 內(nèi)存數(shù)據(jù)集大小上升到一定大小的時候,就會施行數(shù)據(jù)淘汰策略(回收策略)珠叔。redis 提供 6種數(shù)據(jù)淘汰策略:
- volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
- volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
- volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
- allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選最近最少使用的數(shù)據(jù)淘汰
- allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
- no-enviction(驅(qū)逐):禁止驅(qū)逐數(shù)據(jù)
Q13:Redis的并發(fā)競爭問題如何解決?
即多線程同時操作統(tǒng)一Key的解決辦法:
Redis為單進(jìn)程單線程模式蝎宇,采用隊(duì)列模式將并發(fā)訪問變?yōu)榇性L問。Redis本身沒有鎖的概念祷安,Redis對于多個客戶端連接并不存在競爭姥芥,但是在Jedis客戶端對Redis進(jìn)行并發(fā)訪問時會發(fā)生連接超時、數(shù)據(jù)轉(zhuǎn)換錯誤汇鞭、阻塞凉唐、客戶端關(guān)閉連接等問題庸追,這些問題均是由于客戶端連接混亂造成
對此有多種解決方法:
A:條件允許的情況下,請使用redis自帶的incr命令,decr命令
B:樂觀鎖方式
watch price
get price $price
$price = $price + 10
multi
set price $price
exec
C:針對客戶端台囱,操作同一個key的時候淡溯,進(jìn)行加鎖處理
D:場景允許的話,使用setnx 實(shí)現(xiàn)
Q14:Redis回收進(jìn)程如何工作的? Redis回收使用的是什么算法?
Q12 中提到過簿训,當(dāng)所需內(nèi)存超過配置的最大內(nèi)存時咱娶,redis會啟用數(shù)據(jù)淘汰規(guī)則
默認(rèn)規(guī)則是:# maxmemory-policy noeviction
即只允許讀,無法繼續(xù)添加key
因此常需要配置淘汰策略强品,比如LRU算法
image
LRU算法最為精典的實(shí)現(xiàn)膘侮,就是HashMap+Double LinkedList,時間復(fù)雜度為O(1)
Q15:Redis大批量增加數(shù)據(jù)
參考文章:https://www.cnblogs.com/PatrickLiu/p/8548580.html
使用管道模式的榛,運(yùn)行的命令如下所示:
cat data.txt | redis-cli --pipe
data.txt文本:
SET Key0 Value0 SET Key1 Value1 ... SET KeyN ValueN # 或者是 RESP協(xié)議內(nèi)容 - 注意文件編碼G砹恕!夫晌! *8 $5 HMSET $8 person:1 $2 id $1 1
這將產(chǎn)生類似于這樣的輸出:
All data transferred. Waiting for the last reply... Last reply received from server. errors: 0, replies: 1000000
redis-cli實(shí)用程序還將確保只將從Redis實(shí)例收到的錯誤重定向到標(biāo)準(zhǔn)輸出
演示:
cat redis_commands.txt | redis-cli -h 192.168.127.130 -p 6379 [-a "password"] -n 0 --pipe All data transferred.Waiting for the last reply... Last reply received from server. errors:0雕薪,replies:10000000
mysql數(shù)據(jù)快速導(dǎo)入到redis 實(shí)戰(zhàn):
文件詳情:可見Redis-通道實(shí)戰(zhàn)博文:https://www.cnblogs.com/tommy-huang/p/4703514.html
# 1.準(zhǔn)備一個table create database if not exists `test`; use `test`; CREATE TABLE `person` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(200) NOT NULL, `age` varchar(200) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; # 2.插入七八萬條數(shù)據(jù) # 3.SQL查詢,將其轉(zhuǎn)化為 RESP協(xié)議命令 Linux 版本: -> 不要在windows環(huán)境試慷丽,沒啥意義 SELECT CONCAT( "*8\r\n", '/pre>,LENGTH(redis_cmd),'\r\n',redis_cmd,'\r\n', '/pre>,LENGTH(redis_key),'\r\n',redis_key,'\r\n', '/pre>,LENGTH(hkey1),'\r\n',hkey1,'\r\n','/pre>,LENGTH(hval1),'\r\n',hval1,'\r\n', '/pre>,LENGTH(hkey2),'\r\n',hkey2,'\r\n','/pre>,LENGTH(hval2),'\r\n',hval2,'\r\n', '/pre>,LENGTH(hkey3),'\r\n',hkey3,'\r\n','/pre>,LENGTH(hval3),'\r\n',hval3,'\r' )FROM( SELECT 'HMSET' AS redis_cmd, concat_ws(':','person', id) AS redis_key, 'id' AS hkey1, id AS hval1, 'name' AS hkey2, name AS hval2, 'age' AS hkey3, age AS hval3 From person )AS t # 4.如果用的就是線上數(shù)據(jù)庫+線上Linux -> 把sql存到 order.sql蹦哼,進(jìn)行執(zhí)行 mysql -uroot -p123456 test --default-character-set=utf8 --skip-column-names --raw < order.sql | redis-cli -h 127.0.0.1 -p 6379 -a 123456 --pipe # 5.本地?cái)?shù)據(jù)庫+線上redis 利用Navicat導(dǎo)出數(shù)據(jù) -> data.txt,清理格式(導(dǎo)出來的數(shù)據(jù)里面各種 " 符號)要糊,全局替換即可 cat data.txt | redis-cli -h 127.0.0.1 -p 6379 -a 123456 --pipe 81921條數(shù)據(jù) 一瞬間導(dǎo)入完成 注意事項(xiàng):RESP協(xié)議要求纲熏,不要有莫名其妙的字符,注意文件類型是Unix編碼類型
Q16:延申:布隆過濾器
數(shù)據(jù)結(jié)構(gòu)及算法篇 / 布隆過濾器
Redis 實(shí)現(xiàn)
redis 4.X 以上 提供 布隆過濾器插件
centos中安裝redis插件bloom-filter:https://blog.csdn.net/u013030276/article/details/88350641
語法:[bf.add key options]
語法:[bf.exists key options]
注意:
redis 布隆過濾器提供的是 最大內(nèi)存512M锄俄,2億數(shù)據(jù)局劲,萬分之一的誤差率
Q17:Lua腳本相關(guān)
使用Lua腳本的好處:
- 減少網(wǎng)絡(luò)開銷∧淘可以將多個請求通過腳本的形式一次發(fā)送鱼填,減少網(wǎng)絡(luò)時延
- 原子操作,redis會將整個腳本作為一個整體執(zhí)行毅戈,中間不會被其他命令插入苹丸。因此在編寫腳本的過程中無需擔(dān)心會出現(xiàn)競態(tài)條件,無需使用事務(wù)
- 復(fù)用苇经,客戶端發(fā)送的腳本會永久存在redis中赘理,這樣,其他客戶端可以復(fù)用這一腳本而不需要使用代碼完成相同的邏輯
@RequestMapping("/testLua")
public String testLua () {
String key = "mylock";
String value = "xxxxxxxxxxxxxxx";
// if redis.call('get', KEYS[1]) == ARGV[1]
// then
// return redis.call('del', KEYS[1])
// else
// return 0
// end
// lua腳本扇单,用來釋放分布式鎖 - 如果使用的較多商模,可以封裝到文件中, 再進(jìn)行調(diào)用
String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
Object eval = jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(value));
return eval.toString();
}
Q18:性能相關(guān) - Redis慢查詢分析
redis 命令會放在redis內(nèi)置隊(duì)列中,然后主線程一個個執(zhí)行,因此 其中一個 命令執(zhí)行時間過長施流,會造成成批量的阻塞
命令:
slowlog get 獲取慢查詢記錄slowlog len 獲取慢查詢記錄量(慢查詢隊(duì)列是先進(jìn)先出的响疚,因此新的值在滿載的時候,舊的會出去)Redis 慢查詢 -> 執(zhí)行階段耗時過長
conf文件設(shè)置:
slowlog-low-slower-than 10000 -> 10000微秒,10毫秒 (默認(rèn))0 -> 記錄所有命令-1 -> 不記錄命令slow-max-len 存放的最大條數(shù)
慢查詢導(dǎo)致原因: value 值過大瞪醋,解決辦法:
數(shù)據(jù)分段(更細(xì)顆粒度存放數(shù)據(jù))
Q19:如何提高Redis處理效率? 基于Jedis 的批量操作 Pipelined
Jedis jedis = new Jedis("127.0.0.1", 6379);
Pipeline pipelined = jedis.pipelined();
for (String key : keys) {
pipelined.del(key);
}
pipelined.sync();
jedis.close();
// pipelined 實(shí)際是封裝過一層的指令集 -> 實(shí)際應(yīng)用的還是單條指令忿晕,但是節(jié)省了網(wǎng)絡(luò)傳輸開銷(服務(wù)端到Redis環(huán)境的網(wǎng)絡(luò)開銷)
最后
本篇是一篇大合集,中間肯定參考了許多其他人的文章內(nèi)容或圖片趟章,但由于時間比較久遠(yuǎn)杏糙,當(dāng)時并沒有一一記錄慎王,為此表示歉意蚓土,如果有作者發(fā)現(xiàn)了自己的文章或圖片,可以私聊我赖淤,我會進(jìn)行補(bǔ)充蜀漆。
如果你發(fā)現(xiàn)寫的還不錯,可以搜索公眾號「是Kerwin啊」咱旱,一起進(jìn)步确丢!
也可以查看Kerwin的GitHub主頁[1]
參考資料
[1]
Kerwin的GitHub主頁: https://github.com/kkzhilu