三歪看完了這篇Redis,有點飄
本文公眾號來源:Kerwin啊 作者:柯小賢 本文已收錄至我的GitHub
Windows Redis
安裝
鏈接: https://pan.baidu.com/s/1MJnzX_qRuNXJI09euzkPGA 提取碼: 2c6w 復制這段內(nèi)容后打開百度網(wǎng)盤手機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 ?(不修改配置的話默認即可)
redis-cli.exe -h 127.0.0.1 -p 6379 -a password
基本文件說明
| 可執(zhí)行文件 | 作用說明 |
|---|---|
| redis-server | redis服務 |
| redis-cli | redis命令行工具 |
| redis-benchmark | 基準性能測試工具 |
| redis-check-aof | AOF持久化文件檢測和修復工具 |
| redis-check-dump | RDB持久化文件檢測和修復工具 |
| redis-sentinel | 啟動哨兵 |
| redis-trib | cluster集群構建工具 |
基礎命令
| 命令 | 說明 |
|---|---|
| keys ?* | redis允許模糊查詢key 有3個通配符 ?*、?、[] |
| del ? ? ?key | 刪除key |
| exists kxm | 判斷是否存在 |
| expire key 20 | 設置過期時間 - 秒 |
| pexpire key 20000 | 設置過期時間 - 毫秒 |
| move kxm 2 | 移動key到指定位置庫中 ?2號庫 |
| persist key | 移除過期時間,key將會永久存在 ? 成功設置返回1 ?否則返回0 |
| pttl key | 以毫秒為單位返回 key 的剩余的過期時間 |
| ttl key | 以秒為單位,返回給定 key 的剩余生存時間 |
| randomkey | 從當前數(shù)據(jù)庫中隨機返回一個 key |
| rename key newkxy | 更改key的名字,如果重復了會覆蓋 |
| renamenx kxm key | 僅當 newkey 不存在時,將 key 改名為 newkey |
| type key | 返回 key 所儲存的值的類型 |
| select 0 | 選擇第一個庫 |
| ping | 返回PONG 表示連接正常 |
| quit | 關閉當前連接 |
字符串命令
| 命令 | 說明 |
|---|---|
| set key aaa | 設置指定 key 的值 |
| get key | 獲取指定 key 的值 |
| getrange key 0 1 | 返回 key 中字符串值的子字符 ?包含 0 和 1 包含關系 |
| getset key aaaaaaaa | 將給定 key 的值設為 value ,并返回 key 的舊值(old value) |
| mget key kxm | 獲取所有(一個或多個)給定 key 的值 |
| setex test 5 "this is my test" | 將值 value 關聯(lián)到 key ,并將 key 的過期時間設為 seconds (以秒為單位) |
| setnx test test | 只有在 key 不存在時設置 key 的值 (用于分布式鎖) |
| strlen test | 返回 key 所儲存的字符串值的長度 |
| mset key1 "1" key2 "2" | 同時設置一個或多個 key-value 對 |
| msetnx key3 "a" key2 "b" | 同時設置一個或多個 key-value 對,當且僅當所有給定 key 都不存在 ? 其中一個失敗則全部失敗 |
| incr key | 將 key 中儲存的數(shù)字值增一 -> ?key的值 比如為 數(shù)字類型字符串 ?返回增加后的結果 |
| incrby num 1000 | 將 key 中儲存的數(shù)字值增指定的值 -> ?key的值 比如為 數(shù)字類型字符串 ?返回增加后的結果 |
| 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 (域-值)對設置到哈希表 key 中 |
| hset hash name kxm | 將哈希表 key 中的字段 field 的值設為 value |
| hsetnx key field value | 只有在字段 field 不存在時,設置哈希表字段的值 |
| 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" | 通過索引設置列表元素的值 |
| ltrim list 1 5 | 對一個列表進行修剪(trim),就是說,讓列表只保留指定區(qū)間內(nèi)的元素,不在指定區(qū)間之內(nèi)的元素都將被刪除 |
| rpop list | 移除列表的最后一個元素,返回值為移除的元素 |
| rpush list newvalue3 | 從底部添加新值 |
| rpoplpush list list2 | 轉(zhuǎn)移列表的數(shù)據(jù) |
集合(Set)命令
Set 是 String 類型的無序集合。集合成員是唯一的,這就意味著集合中不能出現(xiàn)重復的數(shù)據(jù)
| 命令 | 說明 |
|---|---|
| sadd set java php c c++ python | 向集合添加一個或多個成員 |
| scard set | 獲取集合的成員數(shù) |
| sdiff key1 [key2] | 返回給定所有集合的差集 ?數(shù)學含義差集 |
| 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 | 隨機抽取兩個key (抽獎實現(xiàn)美滋滋) |
| smove set newtest java (smove source destination member) | 將 member 元素從 source 集合移動到 destination 集合 |
| sunion set newset | 返回所有給定集合的并集 |
| srem set java | 刪除 |
| spop set | 從集合中彈出一個元素 |
| sdiff | sinter | sunion | 操作:集合間運算:差集 |
有序集合(sorted set)命令
Redis 有序集合和集合一樣也是string類型元素的集合,且不允許重復的成員。
不同的是每個元素都會關聯(lián)一個double類型的分數(shù)。redis正是通過分數(shù)來為集合中的成員進行從小到大的排序。
有序集合的成員是唯一的,但分數(shù)(score)卻可以重復。
| 命令 | 說明 |
|---|---|
| zadd sort 1 java 2 python | 向有序集合添加一個或多個成員,或者更新已存在成員的分數(shù) |
| zcard sort | 獲取有序集合的成員數(shù) |
| zcount sort 0 1 | 計算在有序集合中指定區(qū)間分數(shù)的成員數(shù) |
| zincrby sort 500 java | 有序集合中對指定成員的分數(shù)加上增量 increment |
| zscore sort java | 返回有序集中,成員的分數(shù)值 |
| zrange sort 0 -1 | 獲取指定序號的值,-1代表全部 |
| zrangebyscore sort 0 5 | 分數(shù)符合范圍的值 |
| zrangebyscore sort 0 5 limit 0 1 | 分頁 limit ?0代表頁碼,1代表每頁顯示數(shù)量 |
| zrem sort java | 移除元素 |
| zremrangebyrank sort 0 1 | 按照排名范圍刪除元素 |
| zremrangebyscore sort 0 1 | 按照分數(shù)范圍刪除元素 |
| zrevrank sort c# | 返回有序集合中指定成員的排名,有序集成員按分數(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 | 查看當前redis ?有多少個頻道 |
| pubsub numsub chat1 | 查看某個頻道的訂閱者數(shù)量 |
| unsubscrible chat1 | 退訂指定頻道 |
| psubscribe java.* | 訂閱一組頻道 |
Redis 事務
Redis 事務可以一次執(zhí)行多個命令, 并且?guī)в幸韵氯齻€重要的保證:
批量操作在發(fā)送 EXEC 命令前被放入隊列緩存 收到 EXEC 命令后進入事務執(zhí)行,事務中任意命令執(zhí)行失敗,其余的命令依然被執(zhí)行 在事務執(zhí)行過程,其他客戶端提交的命令請求不會插入到事務執(zhí)行命令序列中
一個事務從開始到執(zhí)行會經(jīng)歷以下三個階段:
開始事務 命令入隊 執(zhí)行事務
注意:redis事務和數(shù)據(jù)庫事務不同,redis事務出錯后最大的特點是,一剩下的命令會繼續(xù)執(zhí)行,二出錯的數(shù)據(jù)不會回滾
| 命令 | 說明 |
|---|---|
| multi | 標記一個事務開始 |
| exec | 執(zhí)行事務 |
| discard | 事務開始后輸入命令入隊過程中,中止事務 |
| watch key | 監(jiān)視一個(或多個) key ,如果在事務執(zhí)行之前這個(或這些) key 被其他命令所改動,那么事務將被打斷 |
| unwatch | 取消 WATCH 命令對所有 key 的監(jiān)視 |
Redis 服務器命令
| 命令 | 說明 |
|---|---|
| flushall | 刪除所有數(shù)據(jù)庫的所有key |
| flushdb | 刪除當前數(shù)據(jù)庫的所有key |
| save | 同步保存數(shù)據(jù)到硬盤 |
Redis 數(shù)據(jù)備份與恢復
Redis SAVE 命令用于創(chuàng)建當前數(shù)據(jù)庫的備份
如果需要恢復數(shù)據(jù),只需將備份文件 (dump.rdb) 移動到 redis 安裝目錄并啟動服務即可。獲取 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值進行壓測
redis-benchmark?-h?127.0.0.1?-p?6379?-q?-d?100
Java Redis
Jedis
<dependency>
????<groupId>redis.clientsgroupId>
????<artifactId>jedisartifactId>
????<version>2.8.2version>
dependency>
Jedis配置
############# redis Config #############
# Redis數(shù)據(jù)庫索引(默認為0)
spring.redis.database=0
# Redis服務器地址
spring.redis.host=120.79.88.17
# Redis服務器連接端口
spring.redis.port=6379
# Redis服務器連接密碼(默認為空)
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);
????}
}
基礎使用
@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
<dependency>
????<groupId>org.springframework.bootgroupId>
????<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
????<groupId>org.apache.commonsgroupId>
????<artifactId>commons-pool2artifactId>
dependency>
############# redis Config #############
# Redis數(shù)據(jù)庫索引(默認為0)
spring.redis.database=0
# Redis服務器地址
spring.redis.host=120.79.88.17
# Redis服務器連接端口
spring.redis.port=6379
# Redis服務器連接密碼(默認為空)
spring.redis.password=123456
# 連接池最大連接數(shù)(使用負值表示沒有限制)
spring.redis.jedis.pool.max-active=200
# 連接池最大阻塞等待時間(使用負值表示沒有限制)
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),
????????????????//?默認策略,未配置的?key?會使用這個
????????????????this.getRedisCacheConfigurationWithTtl(15),
????????????????//?指定?key?策略
????????????????this.getRedisCacheConfigurationMap()
????????);
????}
????private?Map?getRedisCacheConfigurationMap()? {
????????Map?redisCacheConfigurationMap??=?new?HashMap<>(16);
????????redisCacheConfigurationMap.put("redisTest",?this.getRedisCacheConfigurationWithTtl(15));
????????return?redisCacheConfigurationMap;
????}
????private?RedisCacheConfiguration?getRedisCacheConfigurationWithTtl(Integer?seconds)?{
????????Jackson2JsonRedisSerializer //?RedisAutoConfiguration
@Configuration
@EnableCaching
public?class?RedisConfig?{
????@Bean
????@SuppressWarnings("all")
????public?RedisTemplate?redisTemplate(RedisConnectionFactory?factory)? {
????????RedisTemplate?template?=?new?RedisTemplate();
????????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;
????}
}
//?基礎使用
@Resource
RedisTemplate?redisTemplate;
redisTemplate.opsForList().rightPush("user:1:order",?dataList.get(3).get("key").toString());
//?注解使用
@Cacheable(value?=?"redisTest")
public?TestBean?testBeanAnnotation?()?{}
Redis使用場景
| 類型 | 適用場景 |
|---|---|
| String | 緩存,限流,計數(shù)器,分布式鎖,分布式session |
| Hash | 存儲用戶信息,用戶主頁訪問量,組合查詢 |
| List | 微博關注人時間軸列表,簡單隊列 |
| Set | 贊,踩,標簽,好友關系 |
| Zset | 排行榜 |
或者簡單消息隊列,發(fā)布訂閱實施消息系統(tǒng)等等
String - 緩存
//?1.Cacheable?注解
//?controller?調(diào)用?service?時自動判斷有沒有緩存,如果有就走redis緩存直接返回,如果沒有則數(shù)據(jù)庫然后自動放入redis中
//?可以設置過期時間,KEY生成規(guī)則?(KEY生成規(guī)則基于?參數(shù)的toString方法)
@Cacheable(value?=?"yearScore",?key?=?"#yearScore")
@Override
public?List?findBy?(YearScore?yearScore)? {}
//?2.手動用緩存
if?(redis.hasKey(???)?{
????return?....
}?
redis.set(find?from?DB)...
String - 限流 | 計數(shù)器
//?注:這只是一個最簡單的Demo 效率低,耗時舊,但核心就是這個意思
//?計數(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";
????????}
????}
????//?設置Key?起始請求為1,10秒過期??->??實際寫法肯定封裝過,這里就是隨便一寫
????jedis.set(uuid,?"1");
????jedis.expire(uuid,?10);
????return?"Success?Request";
}
String - 分布式鎖 (重點)
/***
?*?核心思路:
?*?????分布式服務調(diào)用時setnx,返回1證明拿到,用完了刪除,返回0就證明被鎖,等...
?*?????SET?KEY?value?[EX?seconds]?[PX?milliseconds]?[NX|XX]
?*?????EX?second:設置鍵的過期時間為second秒
?*?????PX?millisecond:設置鍵的過期時間為millisecond毫秒
?*???? NX:只在鍵不存在時,才對鍵進行設置操作
?*?????XX:只在鍵已經(jīng)存在時,才對鍵進行設置操作
?*
?*?1.設置鎖
?*?????A.?分布式業(yè)務統(tǒng)一Key
?*?????B.?設置Key過期時間
?*?????C.?設置隨機value,利用ThreadLocal?線程私有存儲隨機value
?*
?*?2.業(yè)務處理
?*?????...
?*
?*?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))?{
????????????????//?進入循環(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è)務處理成功";
}
String - 分布式Session(重點)
// 1.首先明白為什么需要分布式session -> nginx負載均衡?分發(fā)到不同的Tomcat,即使利用IP分發(fā),可以利用request獲取session,但是其中一個掛了,怎么辦???所以需要分布式session
注意理解其中的區(qū)別??A服務-用戶校驗服務??B服務-業(yè)務層
情況A:
A,B 服務單機部署:
cookie:登錄成功后,存儲信息到cookie,A服務自身通過request設置session,獲取session,B服務通過唯一key或者userid 查詢數(shù)據(jù)庫獲取用戶信息
cookie+redis:登錄成功后,存儲信息到cookie,A服務自身通過request設置session,獲取session,B服務通過唯一key或者userid 查詢redis獲取用戶信息
情況B:
A服務多節(jié)點部署,B服務多節(jié)點部署
B服務獲取用戶信息的方式其實是不重要的,必然要查,要么從數(shù)據(jù)庫,要么從cookie
A服務:登錄成功后,存儲唯一key到cookie,?與此同時,A服務需要把session(KEY-UserInfo)同步到redis中,不能存在單純的request(否則nginx分發(fā)到另一個服務器就完犢子了)
官方實現(xiàn):
spring-session-data-redis
有一個內(nèi)置攔截器,攔截request,session通過redis交互,普通使用代碼依然是request.getSession....??但是實際上這個session的值已經(jīng)被該組件攔截,通過redis進行同步了
List 簡單隊列-棧
//?說白了利用redis?-?list數(shù)據(jù)結構?支持從左從右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ù)嗎???多大的量啊... 我個人認為實際是數(shù)據(jù)庫存用戶id,然后用算法去處理,更省空間
Set 抽獎 | 好友關系(合,并,交集)
//?插入key?及用戶id
sadd?cat:1?001?002?003?004?005?006
//?返回抽獎參與人數(shù)
scard?cat:1
//?隨機抽取一個
srandmember?cat:1
//?隨機抽取一人,并移除
spop?cat:1
Zset 排行榜
根據(jù)分數(shù)實現(xiàn)有序列表
微博熱搜:每點擊一次?分數(shù)+1 即可
---?不用數(shù)據(jù)庫目的是因為避免order?by?進行全表掃描
常見面試題
Q1:為什么Redis能這么快
1.Redis完全基于內(nèi)存,絕大部分請求是純粹的內(nèi)存操作,執(zhí)行效率高。
2.Redis使用單進程單線程模型的(K,V)數(shù)據(jù)庫,將數(shù)據(jù)存儲在內(nèi)存中,存取均不會受到硬盤IO的限制,因此其執(zhí)行速度極快,另外單線程也能處理高并發(fā)請求,還可以避免頻繁上下文切換和鎖的競爭,同時由于單線程操作,也可以避免各種鎖的使用,進一步提高效率
3.數(shù)據(jù)結構簡單,對數(shù)據(jù)操作也簡單,Redis不使用表,不會強制用戶對各個關系進行關聯(lián),不會有復雜的關系限制,其存儲結構就是鍵值對,類似于HashMap,HashMap最大的優(yōu)點就是存取的時間復雜度為O(1)
5.C語言編寫,效率更高
6.Redis使用多路I/O復用模型,為非阻塞IO
7.有專門設計的RESP協(xié)議
針對第四點進行說明 ->
常見的IO模型有四種:
同步阻塞IO(Blocking IO):即傳統(tǒng)的IO模型。
同步非阻塞IO(Non-blocking IO):默認創(chuàng)建的socket都是阻塞的,非阻塞IO要求socket被設置為NONBLOCK。注意這里所說的NIO并非Java的NIO(New IO)庫。
IO多路復用(IO Multiplexing):即經(jīng)典的Reactor設計模式,有時也稱為異步阻塞IO,Java中的Selector和Linux中的epoll都是這種模型。
異步IO(Asynchronous IO):即經(jīng)典的Proactor設計模式,也稱為異步非阻塞IO
同步異步,阻塞非阻塞的概念:
假設Redis采用同步阻塞IO:
Redis主程序(服務端 單線程)-> 多個客戶端連接(真實情況是如開發(fā)人員連接redis,程序 redispool連接redis),這每一個都對應著一個客戶端,假設為100個客戶端,其中一個進行交互時候,如果采用同步阻塞式,那么剩下的99個都需要原地等待,這勢必是不科學的。
IO多路復用
Redis 采用 ?I/O 多路復用模型
I/O 多路復用模型中,最重要的函數(shù)調(diào)用就是
select,該方法的能夠同時監(jiān)控多個文件描述符的可讀可寫情況,當其中的某些文件描述符可讀或者可寫時,select方法就會返回可讀以及可寫的文件描述符個數(shù)
注:redis默認使用的是更加優(yōu)化的算法:epoll
select poll epoll 操作方式 遍歷 遍歷 回調(diào) 底層實現(xiàn) 數(shù)組 鏈表 哈希表 IO效率 每次調(diào)用都進行線性遍歷,時間復雜度為O(n) 每次調(diào)用都進行線性遍歷,時間復雜度為O(n) 事件通知方式,每當fd就緒,系統(tǒng)注冊的回調(diào)函數(shù)就會被調(diào)用,將就緒fd放到readyList里面,時間復雜度O(1) 最大連接數(shù) 1024(x86)或2048(x64) 無上限 無上限 所以我們可以說Redis是這樣的:服務端單線程毫無疑問,多客戶端連接時候,如果客戶端沒有發(fā)起任何動作,則服務端會把其視為不活躍的IO流,將其掛起,當有真正的動作時,會通過回調(diào)的方式執(zhí)行相應的事件
Q2:從海量Key里查詢出某一個固定前綴的Key
A. 笨辦法:KEYS [pattern] ?注意key很多的話,這樣做肯定會出問題,造成redis崩潰
B. SCAN cursor [MATCH pattern] [COUNT count] 游標方式查找
Q3:如何通過Redis實現(xiàn)分布式鎖
見上文
Q4:如何實現(xiàn)異步隊列
上文說到利用?redis-list?實現(xiàn)隊列
假設場景:A服務生產(chǎn)數(shù)據(jù)?-?B服務消費數(shù)據(jù),即可利用此種模型構造-生產(chǎn)消費者模型
1.?使用Redis中的List作為隊列
2.使用BLPOP?key?[key...]?timeout??->?LPOP?key?[key?...]?timeout:阻塞直到隊列有消息或者超時
(方案二:解決方案一中,拿數(shù)據(jù)的時,生產(chǎn)者尚未生產(chǎn)的情況)
3.pub/sub:主題訂閱者模式
基于reds的終極方案,上文有介紹,基于發(fā)布/訂閱模式
缺點:消息的發(fā)布是無狀態(tài)的,無法保證可達。對于發(fā)布者來說,消息是“即發(fā)即失”的,此時如果某個消費者在生產(chǎn)者發(fā)布消息時下線,重新上線之后,是無法接收該消息的,要解決該問題需要使用專業(yè)的消息隊列
Q5:Redis支持的數(shù)據(jù)類型?
見上文
Q6:什么是Redis持久化?Redis有哪幾種持久化方式?優(yōu)缺點是什么?
持久化就是把內(nèi)存的數(shù)據(jù)寫到磁盤中去,防止服務宕機了內(nèi)存數(shù)據(jù)丟失。
Redis 提供了兩種持久化方式:RDB(默認) 和AOF
RDB:
rdb是Redis DataBase縮寫
功能核心函數(shù)rdbSave(生成RDB文件)和rdbLoad(從文件加載內(nèi)存)兩個函數(shù)
RDB: ?把當前進程數(shù)據(jù)生成快照文件保存到硬盤的過程。分為手動觸發(fā)和自動觸發(fā)
手動觸發(fā) -> ?save (不推薦,阻塞嚴重) ?bgsave -> (save的優(yōu)化版,微秒級阻塞)
shutdowm 關閉服務時,如果沒有配置AOF,則會使用bgsave持久化數(shù)據(jù)bgsave - 工作原理
會從當前父進程fork一個子進程,然后生成rdb文件
缺點:頻率低,無法做到實時持久化
AOF:
Aof是Append-only file縮寫,AOF文件存儲的也是RESP協(xié)議
每當執(zhí)行服務器(定時)任務或者函數(shù)時flushAppendOnlyFile 函數(shù)都會被調(diào)用, 這個函數(shù)執(zhí)行以下兩個工作
aof寫入保存:
WRITE:根據(jù)條件,將 aof_buf 中的緩存寫入到 AOF 文件
SAVE:根據(jù)條件,調(diào)用 fsync 或 fdatasync 函數(shù),將 AOF 文件保存到磁盤中。
存儲結構:
內(nèi)容是redis通訊協(xié)議(RESP )格式的命令文本存儲
原理:
相當于存儲了redis的執(zhí)行命令(類似mysql的sql語句日志),數(shù)據(jù)的完整性和一致性更高
比較:
1、aof文件比rdb更新頻率高
2、aof比rdb更安全
3、rdb性能更好
PS:正確停止redis服務 應該基于連接命令 加再上 shutdown -> 否則數(shù)據(jù)持久化會出現(xiàn)問題
Q7:redis通訊協(xié)議(RESP)
Redis 即 REmote Dictionary Server (遠程字典服務);
而Redis的協(xié)議規(guī)范是 Redis Serialization Protocol (Redis序列化協(xié)議)
RESP 是redis客戶端和服務端之前使用的一種通訊協(xié)議;
RESP 的特點:實現(xiàn)簡單、快速解析、可讀性好
協(xié)議如下:
客戶端以規(guī)定格式的形式發(fā)送命令給服務器
set?key value 協(xié)議翻譯如下:
*?3????->??表示以下有幾組命令
$?3????->??表示命令長度是3
SET
$6?????->??表示長度是6
keykey
$5?????->??表示長度是5
value
完整即:
*?3
$?3
SET
$6
keykey
$5?
value
服務器在執(zhí)行最后一條命令后,返回結果,返回格式如下:
For Simple Strings the first byte of the reply is "+" 回復
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-服務端,監(jiān)聽??jedis發(fā)送的協(xié)議內(nèi)容
public?class?SocketApp?{
????
????/***
?????*?監(jiān)聽?6379?傳輸?shù)臄?shù)據(jù)
?????*?JVM端口需要進行設置
?????*/
????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架構有哪些
單節(jié)點
主從復制
Master-slave??主從賦值,此種結構可以考慮關閉master的持久化,只讓從數(shù)據(jù)庫進行持久化,另外可以通過讀寫分離,緩解主服務器壓力
哨兵
Redis sentinel 是一個分布式系統(tǒng)中監(jiān)控 redis 主從服務器,并在主服務器下線時自動進行故障轉(zhuǎn)移。其中三個特性:
監(jiān)控(Monitoring):??? Sentinel ?會不斷地檢查你的主服務器和從服務器是否運作正常。
提醒(Notification):?當被監(jiān)控的某個 Redis 服務器出現(xiàn)問題時, Sentinel 可以通過 API 向管理員或者其他應用程序發(fā)送通知。
自動故障遷移(Automatic failover):?當一個主服務器不能正常工作時, Sentinel 會開始一次自動故障遷移操作。
特點:
1、保證高可用
2、監(jiān)控各個節(jié)點
3、自動故障遷移
缺點:主從模式,切換需要時間丟數(shù)據(jù)
沒有解決?master?寫的壓力
集群
從redis 3.0之后版本支持redis-cluster集群,Redis-Cluster采用無中心結構,每個節(jié)點保存數(shù)據(jù)和整個集群狀態(tài),每個節(jié)點都和其他所有節(jié)點連接。
特點:
1、無中心架構(不存在哪個節(jié)點影響性能瓶頸),少了 proxy 層。
2、數(shù)據(jù)按照 slot 存儲分布在多個節(jié)點,節(jié)點間數(shù)據(jù)共享,可動態(tài)調(diào)整數(shù)據(jù)分布。
3、可擴展性,可線性擴展到 1000 個節(jié)點,節(jié)點可動態(tài)添加或刪除。
4、高可用性,部分節(jié)點不可用時,集群仍可用。通過增加 Slave 做備份數(shù)據(jù)副本
5、實現(xiàn)故障自動 failover,節(jié)點之間通過 gossip 協(xié)議交換狀態(tài)信息,用投票機制完成 Slave到 Master 的角色提升。
缺點:
1、資源隔離性較差,容易出現(xiàn)相互影響的情況。
2、數(shù)據(jù)通過異步復制,不保證數(shù)據(jù)的強一致性
Q9:Redis集群-如何從海量數(shù)據(jù)里快速找到所需?
分片
按照某種規(guī)則去劃分數(shù)據(jù),分散存儲在多個節(jié)點上。通過將數(shù)據(jù)分到多個Redis服務器上,來減輕單個Redis服務器的壓力。
一致性Hash算法
既然要將數(shù)據(jù)進行分片,那么通常的做法就是獲取節(jié)點的Hash值,然后根據(jù)節(jié)點數(shù)求模,但這樣的方法有明顯的弊端,當Redis節(jié)點數(shù)需要動態(tài)增加或減少的時候,會造成大量的Key無法被命中。所以Redis中引入了一致性Hash算法。該算法對2^32 取模,將Hash值空間組成虛擬的圓環(huán),整個圓環(huán)按順時針方向組織,每個節(jié)點依次為0、1、2...2^32-1,之后將每個服務器進行Hash運算,確定服務器在這個Hash環(huán)上的地址,確定了服務器地址后,對數(shù)據(jù)使用同樣的Hash算法,將數(shù)據(jù)定位到特定的Redis服務器上。如果定位到的地方?jīng)]有Redis服務器實例,則繼續(xù)順時針尋找,找到的第一臺服務器即該數(shù)據(jù)最終的服務器位置。

一致性Hash算法
Hash環(huán)的數(shù)據(jù)傾斜問題
Hash環(huán)在服務器節(jié)點很少的時候,容易遇到服務器節(jié)點不均勻的問題,這會造成數(shù)據(jù)傾斜,數(shù)據(jù)傾斜指的是被緩存的對象大部分集中在Redis集群的其中一臺或幾臺服務器上。

如上圖,一致性Hash算法運算后的數(shù)據(jù)大部分被存放在A節(jié)點上,而B節(jié)點只存放了少量的數(shù)據(jù),久而久之A節(jié)點將被撐爆。引入虛擬節(jié)點

例如上圖:將NodeA和NodeB兩個節(jié)點分為Node A#1-A#3 NodeB#1-B#3。
Q10:什么是緩存穿透?如何避免?什么是緩存雪崩?如何避免?什么是緩存擊穿?如何避免?
緩存穿透
一般的緩存系統(tǒng),都是按照key去緩存查詢,如果不存在對應的value,就應該去后端系統(tǒng)查找(比如DB)。一些惡意的請求會故意查詢不存在的key,請求量很大,就會對后端系統(tǒng)造成很大的壓力。這就叫做緩存穿透。
如何避免?
1:對查詢結果為空的情況也進行緩存,緩存時間設置短一點,或者該key對應的數(shù)據(jù)insert了之后清理緩存。
2:對一定不存在的key進行過濾??梢园阉械目赡艽嬖诘膋ey放到一個大的Bitmap中,查詢時通過該bitmap過濾。
3:由于請求參數(shù)是不合法的(每次都請求不存在的參數(shù)),于是我們可以使用布隆過濾器(Bloomfilter)或壓縮filter提前進行攔截,不合法就不讓這個請求進入到數(shù)據(jù)庫層
緩存雪崩
當緩存服務器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候,會給后端系統(tǒng)帶來很大壓力。導致系統(tǒng)崩潰。
如何避免?
1:在緩存失效后,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待。
2:做二級緩存,A1為原始緩存,A2為拷貝緩存,A1失效時,可以訪問A2,A1緩存失效時間設置為短期,A2設置為長期
3:不同的key,設置不同的過期時間,讓緩存失效的時間點盡量均勻。
4:啟用限流策略,盡量避免數(shù)據(jù)庫被干掉
緩存擊穿
概念 一個存在的key,在緩存過期的一刻,同時有大量的請求,這些請求都會擊穿到DB,造成瞬時DB請求量大、壓力驟增。
解決方案 A. 在訪問key之前,采用SETNX(set if not exists)來設置另一個短期key來鎖住當前key的訪問,訪問結束再刪除該短期key
B. 服務層處理 - 方法加鎖 + 雙重校驗:
//?鎖-實例
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?{
????????????????????//?獲取鎖后再查一次,查到了直接返回結果
????????????????????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;
}
| 解釋 | 基礎解決方案 | |
|---|---|---|
| 緩存穿透 | 訪問一個不存在的key,緩存不起作用,請求會穿透到DB,流量大時DB會掛掉 | 1.采用布隆過濾器,使用一個足夠大的bitmap,用于存儲可能訪問的key,不存在的key直接被過濾;2.訪問key未在DB查詢到值,也將空值寫進緩存,但可以設置較短過期時間 |
| 緩存雪崩 | 大量的key設置了相同的過期時間,導致在緩存在同一時刻全部失效,造成瞬時DB請求量大、壓力驟增,引起雪崩 | 可以給緩存設置過期時間時加上一個隨機值時間,使得每個key的過期時間分布開來,不會集中在同一時刻失效 |
| 緩存擊穿 | 一個存在的key,在緩存過期的一刻,同時有大量的請求,這些請求都會擊穿到DB,造成瞬時DB請求量大、壓力驟增 | 在訪問key之前,采用SETNX(set if not exists)來設置另一個短期key來鎖住當前key的訪問,訪問結束再刪除該短期key |
Q11:緩存與數(shù)據(jù)庫雙寫一致
如果僅僅是讀數(shù)據(jù),沒有此類問題
如果是新增數(shù)據(jù),也沒有此類問題
當數(shù)據(jù)需要更新時,如何保證緩存與數(shù)據(jù)庫的雙寫一致性?
三種更新策略:
先更新數(shù)據(jù)庫,再更新緩存 ?-> 先刪除緩存,再更新數(shù)據(jù)庫 先更新數(shù)據(jù)庫,再刪除緩存 方案一:并發(fā)的時候,執(zhí)行順序無法保證,可能A先更新數(shù)據(jù)庫,但B后更新數(shù)據(jù)庫但先更新緩存
加鎖的話,確實可以避免,但這樣吞吐量會下降,可以根據(jù)業(yè)務場景考慮
方案二:該方案會導致不一致的原因是。同時有一個請求A進行更新操作,另一個請求B進行查詢操作。那么會出現(xiàn)如下情形: (1)請求A進行寫操作,刪除緩存 (2)請求B查詢發(fā)現(xiàn)緩存不存在 (3)請求B去數(shù)據(jù)庫查詢得到舊值 (4)請求B將舊值寫入緩存 (5)請求A將新值寫入數(shù)據(jù)庫
因此采用:采用延時雙刪策略 ? 即進入邏輯就刪除Key,執(zhí)行完操作,延時再刪除key
方案三:更新數(shù)據(jù)庫 - 刪除緩存 ?可能出現(xiàn)問題的場景:
(1)緩存剛好失效 (2)請求A查詢數(shù)據(jù)庫,得一個舊值 (3)請求B將新值寫入數(shù)據(jù)庫 (4)請求B刪除緩存 (5)請求A將查到的舊值寫入緩存
先天條件要求:請求第二步的讀取操作耗時要大于更新操作,條件較為苛刻
但如果真的發(fā)生怎么處理?
A. 給鍵設置合理的過期時間
B. 異步延時刪除key
Q12:何保證Redis中的數(shù)據(jù)都是熱點數(shù)據(jù)
A. 可以通過手工或者主動方式,去加載熱點數(shù)據(jù)
B. Redis有其自己的數(shù)據(jù)淘汰策略:
redis 內(nèi)存數(shù)據(jù)集大小上升到一定大小的時候,就會施行數(shù)據(jù)淘汰策略(回收策略)。redis 提供 6種數(shù)據(jù)淘汰策略:
volatile-lru:從已設置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰 volatile-ttl:從已設置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰 volatile-random:從已設置過期時間的數(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為單進程單線程模式,采用隊列模式將并發(fā)訪問變?yōu)榇性L問。Redis本身沒有鎖的概念,Redis對于多個客戶端連接并不存在競爭,但是在Jedis客戶端對Redis進行并發(fā)訪問時會發(fā)生連接超時、數(shù)據(jù)轉(zhuǎn)換錯誤、阻塞、客戶端關閉連接等問題,這些問題均是由于客戶端連接混亂造成
對此有多種解決方法:
A:條件允許的情況下,請使用redis自帶的incr命令,decr命令
B:樂觀鎖方式
watch?price
get?price?$price
$price?=?$price?+?10
multi
set?price?$price
exec
C:針對客戶端,操作同一個key的時候,進行加鎖處理
D:場景允許的話,使用setnx 實現(xiàn)
Q14:Redis回收進程如何工作的? Redis回收使用的是什么算法?
Q12 中提到過,當所需內(nèi)存超過配置的最大內(nèi)存時,redis會啟用數(shù)據(jù)淘汰規(guī)則
默認規(guī)則是:# maxmemory-policy noeviction
即只允許讀,無法繼續(xù)添加key
因此常需要配置淘汰策略,比如LRU算法
LRU算法最為精典的實現(xiàn),就是HashMap+Double LinkedList,時間復雜度為O(1)
Q15:Redis大批量增加數(shù)據(jù)
參考文章:https://www.cnblogs.com/PatrickLiu/p/8548580.html
使用管道模式,運行的命令如下所示:
cat?data.txt?|?redis-cli?--pipedata.txt文本:
SET?Key0?Value0
SET?Key1?Value1
...
SET?KeyN?ValueN
#?或者是 RESP協(xié)議內(nèi)容?-?注意文件編碼?。?!
*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:?1000000redis-cli實用程序還將確保只將從Redis實例收到的錯誤重定向到標準輸出
演示:
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ù)快速導入到redis 實戰(zhàn):?文件詳情:可見Redis-通道實戰(zhàn)博文:https://www.cnblogs.com/tommy-huang/p/4703514.html
#?1.準備一個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",
???'$',LENGTH(redis_cmd),'\r\n',redis_cmd,'\r\n',
???'$',LENGTH(redis_key),'\r\n',redis_key,'\r\n',
???'$',LENGTH(hkey1),'\r\n',hkey1,'\r\n','$',LENGTH(hval1),'\r\n',hval1,'\r\n',
???'$',LENGTH(hkey2),'\r\n',hkey2,'\r\n','$',LENGTH(hval2),'\r\n',hval2,'\r\n',
???'$',LENGTH(hkey3),'\r\n',hkey3,'\r\n','$',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,進行執(zhí)行
mysql?-uroot?-p123456?test?--default-character-set=utf8?--skip-column-names?--raw?
|
redis-cli?-h?127.0.0.1?-p?6379?-a?123456?--pipe
#?5.本地數(shù)據(jù)庫+線上redis
利用Navicat導出數(shù)據(jù)?->?data.txt,清理格式(導出來的數(shù)據(jù)里面各種?"?符號),全局替換即可
cat?data.txt?|?redis-cli?-h?127.0.0.1?-p?6379?-a?123456??--pipe
81921條數(shù)據(jù)?一瞬間導入完成
注意事項:RESP協(xié)議要求,不要有莫名其妙的字符,注意文件類型是Unix編碼類型
Q16:延申:布隆過濾器
數(shù)據(jù)結構及算法篇 / 布隆過濾器
Redis 實現(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腳本相關
使用Lua腳本的好處:
減少網(wǎng)絡開銷??梢詫⒍鄠€請求通過腳本的形式一次發(fā)送,減少網(wǎng)絡時延 原子操作,redis會將整個腳本作為一個整體執(zhí)行,中間不會被其他命令插入。因此在編寫腳本的過程中無需擔心會出現(xiàn)競態(tài)條件,無需使用事務 復用,客戶端發(fā)送的腳本會永久存在redis中,這樣,其他客戶端可以復用這一腳本而不需要使用代碼完成相同的邏輯
@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腳本,用來釋放分布式鎖?-?如果使用的較多,可以封裝到文件中,?再進行調(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:性能相關 - Redis慢查詢分析
redis 命令會放在redis內(nèi)置隊列中,然后主線程一個個執(zhí)行,因此 其中一個 命令執(zhí)行時間過長,會造成成批量的阻塞
命令:slowlog get 獲取慢查詢記錄 slowlog len 獲取慢查詢記錄量 (慢查詢隊列是先進先出的,因此新的值在滿載的時候,舊的會出去)Redis 慢查詢 -> 執(zhí)行階段耗時過長
conf文件設置:slowlog-low-slower-than 10000 -> 10000微秒,10毫秒 (默認) 0 -> 記錄所有命令 -1 -> 不記錄命令 slow-max-len 存放的最大條數(shù)
慢查詢導致原因: value 值過大,解決辦法:數(shù)據(jù)分段(更細顆粒度存放數(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?實際是封裝過一層的指令集?->??實際應用的還是單條指令,但是節(jié)省了網(wǎng)絡傳輸開銷(服務端到Redis環(huán)境的網(wǎng)絡開銷)各類知識點總結
下面的文章都有對應的原創(chuàng)精美PDF,在持續(xù)更新中,可以來找我催更~
掃碼或者微信搜Java3y?免費領取原創(chuàng)思維導圖、精美PDF。在公眾號回復「888」領取,PDF內(nèi)容純手打有任何不懂歡迎來問我。
原創(chuàng)電子書
原創(chuàng)思維導圖
![]() |
|










