「查缺補漏」鞏固你的Redis知識體系

來自公眾號:是Kerwin啊
作者:柯小賢

Windows Redis

安裝

鏈接: https://pan.baidu.com/s/1MJnzX_qRuNXJI09euzkPGA 提取碼: 2c6w 復(fù)制這段內(nèi)容后打開百度網(wǎng)盤手機App酌摇,操作更方便哦

無腦下一步即可

使用

出現(xiàn)錯誤:

creating server tcp listening socket 127.0.0.1:6379: bind No error

解決方案:

  1. redis-cli.exe
  2. shutdown
  3. exit
  4. 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ù)庫中隨機返回一個 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 對一個列表進行修剪(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 隨機抽取兩個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類型元素的集合,且不允許重復(fù)的成員。

不同的是每個元素都會關(guān)聯(lián)一個double類型的分?jǐn)?shù)户誓。redis正是通過分?jǐn)?shù)來為集合中的成員進行從小到大的排序饼灿。

有序集合的成員是唯一的,但分?jǐn)?shù)(score)卻可以重復(fù)。

命令 說明
zadd sort 1 java 2 python 向有序集合添加一個或多個成員帝美,或者更新已存在成員的分?jǐn)?shù)
zcard sort 獲取有序集合的成員數(shù)
zcount sort 0 1 計算在有序集合中指定區(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)容, 原理圖如下:

image
image
命令 說明
pubsub channels 查看當(dāng)前redis 有多少個頻道
pubsub numsub chat1 查看某個頻道的訂閱者數(shù)量
unsubscrible chat1 退訂指定頻道
psubscribe java.* 訂閱一組頻道

Redis 事務(wù)

Redis 事務(wù)可以一次執(zhí)行多個命令舰褪, 并且?guī)в幸韵氯齻€重要的保證:

  • 批量操作在發(fā)送 EXEC 命令前被放入隊列緩存
  • 收到 EXEC 命令后進入事務(wù)執(zhí)行漆枚,事務(wù)中任意命令執(zhí)行失敗,其余的命令依然被執(zhí)行
  • 在事務(wù)執(zhí)行過程抵知,其他客戶端提交的命令請求不會插入到事務(wù)執(zhí)行命令序列中

一個事務(wù)從開始到執(zhí)行會經(jīng)歷以下三個階段:

  • 開始事務(wù)
  • 命令入隊
  • 執(zhí)行事務(wù)

注意:redis事務(wù)和數(shù)據(jù)庫事務(wù)不同墙基,redis事務(wù)出錯后最大的特點是软族,一剩下的命令會繼續(xù)執(zhí)行,二出錯的數(shù)據(jù)不會回滾

命令 說明
multi 標(biāo)記一個事務(wù)開始
exec 執(zhí)行事務(wù)
discard 事務(wù)開始后輸入命令入隊過程中残制,中止事務(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值進行壓測
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 緩存,限流碌燕,計數(shù)器折汞,分布式鎖倔幼,分布式session
Hash 存儲用戶信息,用戶主頁訪問量爽待,組合查詢
List 微博關(guān)注人時間軸列表损同,簡單隊列
Set 贊,踩鸟款,標(biāo)簽膏燃,好友關(guān)系
Zset 排行榜

或者簡單消息隊列,發(fā)布訂閱實施消息系統(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 - 限流 | 計數(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";
        }
    }

    // 設(shè)置Key 起始請求為1,10秒過期  ->  實際寫法肯定封裝過,這里就是隨便一寫
    jedis.set(uuid, "1");
    jedis.expire(uuid, 10);
    return "Success Request";
}

String - 分布式鎖 (重點)

/***
 * 核心思路:
 *     分布式服務(wù)調(diào)用時setnx,返回1證明拿到霍比,用完了刪除幕袱,返回0就證明被鎖,等...
 *     SET KEY value [EX seconds] [PX milliseconds] [NX|XX]
 *     EX second:設(shè)置鍵的過期時間為second秒
 *     PX millisecond:設(shè)置鍵的過期時間為millisecond毫秒
 *     NX:只在鍵不存在時悠瞬,才對鍵進行設(shè)置操作
 *     XX:只在鍵已經(jīng)存在時们豌,才對鍵進行設(shè)置操作
 *
 * 1.設(shè)置鎖
 *     A. 分布式業(yè)務(wù)統(tǒng)一Key
 *     B. 設(shè)置Key過期時間
 *     C. 設(shè)置隨機value,利用ThreadLocal 線程私有存儲隨機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)) {
                // 進入循環(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(重點)

// 1.首先明白為什么需要分布式session -> nginx負(fù)載均衡 分發(fā)到不同的Tomcat,即使利用IP分發(fā)凌外,可以利用request獲取session辩尊,但是其中一個掛了,怎么辦康辑?摄欲? 所以需要分布式session

注意理解其中的區(qū)別  A服務(wù)-用戶校驗服務(wù)  B服務(wù)-業(yè)務(wù)層

情況A:
A,B 服務(wù)單機部署:
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é)點部署智袭,B服務(wù)多節(jié)點部署
B服務(wù)獲取用戶信息的方式其實是不重要的奔缠,必然要查,要么從數(shù)據(jù)庫吼野,要么從cookie

A服務(wù):登錄成功后校哎,存儲唯一key到cookie, 與此同時箫锤,A服務(wù)需要把session(KEY-UserInfo)同步到redis中贬蛙,不能存在單純的request(否則nginx分發(fā)到另一個服務(wù)器就完犢子了)

官方實現(xiàn):
spring-session-data-redis
有一個內(nèi)置攔截器雨女,攔截request谚攒,session通過redis交互,普通使用代碼依然是request.getSession....  但是實際上這個session的值已經(jīng)被該組件攔截氛堕,通過redis進行同步了

List 簡單隊列-棧

// 說白了利用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ù)據(jù)庫存用戶id,然后用算法去處理赠摇,更省空間

Set 抽獎 | 好友關(guān)系(合固逗,并,交集)

// 插入key 及用戶id
sadd cat:1 001 002 003 004 005 006

// 返回抽獎參與人數(shù)
scard cat:1

// 隨機抽取一個
srandmember cat:1

// 隨機抽取一人藕帜,并移除
spop cat:1

Zset 排行榜

根據(jù)分?jǐn)?shù)實現(xiàn)有序列表
微博熱搜:每點擊一次 分?jǐ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ù)結(jié)構(gòu)簡單,對數(shù)據(jù)操作也簡單串结,Redis不使用表哑子,不會強制用戶對各個關(guān)系進行關(guān)聯(lián),不會有復(fù)雜的關(guān)系限制肌割,其存儲結(jié)構(gòu)就是鍵值對卧蜓,類似于HashMap,HashMap最大的優(yōu)點就是存取的時間復(fù)雜度為O(1)
5.C語言編寫把敞,效率更高
6.Redis使用多路I/O復(fù)用模型弥奸,為非阻塞IO
7.有專門設(shè)計的RESP協(xié)議

針對第四點進行說明 ->

常見的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è)計模式耽装,有時也稱為異步阻塞IO愤炸,Java中的Selector和Linux中的epoll都是這種模型。
  • 異步IO(Asynchronous IO):即經(jīng)典的Proactor設(shè)計模式掉奄,也稱為異步非阻塞IO

同步異步规个,阻塞非阻塞的概念:

image
image

假設(shè)Redis采用同步阻塞IO:

Redis主程序(服務(wù)端 單線程)-> 多個客戶端連接(真實情況是如開發(fā)人員連接redis,程序 redispool連接redis)姓建,這每一個都對應(yīng)著一個客戶端诞仓,假設(shè)為100個客戶端,其中一個進行交互時候速兔,如果采用同步阻塞式墅拭,那么剩下的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) |
| 底層實現(xiàn) | 數(shù)組 | 鏈表 | 哈希表 |
| IO效率 | 每次調(diào)用都進行線性遍歷,時間復(fù)雜度為O(n) | 每次調(diào)用都進行線性遍歷掸宛,時間復(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實現(xiàn)分布式鎖

見上文

Q4:如何實現(xiàn)異步隊列

上文說到利用 redis-list 實現(xiàn)隊列
假設(shè)場景:A服務(wù)生產(chǎn)數(shù)據(jù) - B服務(wù)消費數(shù)據(jù)最疆,即可利用此種模型構(gòu)造-生產(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)的,無法保證可達(dá)获诈。對于發(fā)布者來說仍源,消息是“即發(fā)即失”的,此時如果某個消費者在生產(chǎn)者發(fā)布消息時下線舔涎,重新上線之后笼踩,是無法接收該消息的,要解決該問題需要使用專業(yè)的消息隊列

Q5:Redis支持的數(shù)據(jù)類型终抽?

見上文

Q6:什么是Redis持久化戳表?Redis有哪幾種持久化方式桶至?優(yōu)缺點是什么昼伴?

持久化就是把內(nèi)存的數(shù)據(jù)寫到磁盤中去,防止服務(wù)宕機了內(nèi)存數(shù)據(jù)丟失镣屹。

Redis 提供了兩種持久化方式:RDB(默認(rèn)) 和AOF

RDB:

rdb是Redis DataBase縮寫

功能核心函數(shù)rdbSave(生成RDB文件)和rdbLoad(從文件加載內(nèi)存)兩個函數(shù)

image

RDB: 把當(dāng)前進程數(shù)據(jù)生成快照文件保存到硬盤的過程圃郊。分為手動觸發(fā)和自動觸發(fā)

手動觸發(fā) -> save (不推薦,阻塞嚴(yán)重) bgsave -> (save的優(yōu)化版女蜈,微秒級阻塞)

shutdowm 關(guān)閉服務(wù)時持舆,如果沒有配置AOF,則會使用bgsave持久化數(shù)據(jù)

bgsave - 工作原理

會從當(dāng)前父進程fork一個子進程伪窖,然后生成rdb文件

缺點:頻率低逸寓,無法做到實時持久化

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 的特點:實現(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端口需要進行設(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é)點

主從復(fù)制

Master-slave  主從賦值疯汁,此種結(jié)構(gòu)可以考慮關(guān)閉master的持久化牲尺,只讓從數(shù)據(jù)庫進行持久化,另外可以通過讀寫分離幌蚊,緩解主服務(wù)器壓力

哨兵

Redis sentinel 是一個分布式系統(tǒng)中監(jiān)控 redis 主從服務(wù)器谤碳,并在主服務(wù)器下線時自動進行故障轉(zhuǎn)移。其中三個特性:

監(jiān)控(Monitoring):    Sentinel  會不斷地檢查你的主服務(wù)器和從服務(wù)器是否運作正常溢豆。

提醒(Notification): 當(dāng)被監(jiān)控的某個 Redis 服務(wù)器出現(xiàn)問題時蜒简, Sentinel 可以通過 API 向管理員或者其他應(yīng)用程序發(fā)送通知。

自動故障遷移(Automatic failover): 當(dāng)一個主服務(wù)器不能正常工作時漩仙, Sentinel 會開始一次自動故障遷移操作搓茬。

特點:
1、保證高可用
2队他、監(jiān)控各個節(jié)點
3卷仑、自動故障遷移

缺點:主從模式,切換需要時間丟數(shù)據(jù)
沒有解決 master 寫的壓力

集群

image

從redis 3.0之后版本支持redis-cluster集群麸折,Redis-Cluster采用無中心結(jié)構(gòu)锡凝,每個節(jié)點保存數(shù)據(jù)和整個集群狀態(tài),每個節(jié)點都和其他所有節(jié)點連接。

特點:

1垢啼、無中心架構(gòu)(不存在哪個節(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ù)通過異步復(fù)制,不保證數(shù)據(jù)的強一致性

Q9:Redis集群-如何從海量數(shù)據(jù)里快速找到所需犁跪?

  • 分片

    按照某種規(guī)則去劃分?jǐn)?shù)據(jù),分散存儲在多個節(jié)點上歹袁。通過將數(shù)據(jù)分到多個Redis服務(wù)器上坷衍,來減輕單個Redis服務(wù)器的壓力。

  • 一致性Hash算法

    既然要將數(shù)據(jù)進行分片条舔,那么通常的做法就是獲取節(jié)點的Hash值枫耳,然后根據(jù)節(jié)點數(shù)求模,但這樣的方法有明顯的弊端孟抗,當(dāng)Redis節(jié)點數(shù)需要動態(tài)增加或減少的時候迁杨,會造成大量的Key無法被命中。所以Redis中引入了一致性Hash算法凄硼。該算法對2^32 取模铅协,將Hash值空間組成虛擬的圓環(huán),整個圓環(huán)按順時針方向組織帆喇,每個節(jié)點依次為0警医、1、2...2^32-1坯钦,之后將每個服務(wù)器進行Hash運算,確定服務(wù)器在這個Hash環(huán)上的地址侈玄,確定了服務(wù)器地址后婉刀,對數(shù)據(jù)使用同樣的Hash算法,將數(shù)據(jù)定位到特定的Redis服務(wù)器上序仙。如果定位到的地方?jīng)]有Redis服務(wù)器實例突颊,則繼續(xù)順時針尋找,找到的第一臺服務(wù)器即該數(shù)據(jù)最終的服務(wù)器位置。

    image

Hash環(huán)的數(shù)據(jù)傾斜問題

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

image

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

image

例如上圖:將NodeA和NodeB兩個節(jié)點分為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é)果為空的情況也進行緩存,緩存時間設(shè)置短一點侠鳄,或者該key對應(yīng)的數(shù)據(jù)insert了之后清理緩存埠啃。

2:對一定不存在的key進行過濾∥岸瘢可以把所有的可能存在的key放到一個大的Bitmap中碴开,查詢時通過該bitmap過濾。

3:由于請求參數(shù)是不合法的(每次都請求不存在的參數(shù))博秫,于是我們可以使用布隆過濾器(Bloomfilter)或壓縮filter提前進行攔截潦牛,不合法就不讓這個請求進入到數(shù)據(jù)庫層

緩存雪崩

當(dāng)緩存服務(wù)器重啟或者大量緩存集中在某一個時間段失效,這樣在失效的時候挡育,會給后端系統(tǒng)帶來很大壓力巴碗。導(dǎo)致系統(tǒng)崩潰。

如何避免即寒?

1:在緩存失效后橡淆,通過加鎖或者隊列來控制讀數(shù)據(jù)庫寫緩存的線程數(shù)量召噩。比如對某個key只允許一個線程查詢數(shù)據(jù)和寫緩存,其他線程等待逸爵。

2:做二級緩存具滴,A1為原始緩存,A2為拷貝緩存师倔,A1失效時构韵,可以訪問A2,A1緩存失效時間設(shè)置為短期溯革,A2設(shè)置為長期

3:不同的key贞绳,設(shè)置不同的過期時間,讓緩存失效的時間點盡量均勻致稀。

4:啟用限流策略冈闭,盡量避免數(shù)據(jù)庫被干掉

緩存擊穿

概念一個存在的key,在緩存過期的一刻抖单,同時有大量的請求萎攒,這些請求都會擊穿到DB,造成瞬時DB請求量大矛绘、壓力驟增耍休。

解決方案A. 在訪問key之前,采用SETNX(set if not exists)來設(shè)置另一個短期key來鎖住當(dāng)前key的訪問货矮,訪問結(jié)束再刪除該短期key

B. 服務(wù)層處理 - 方法加鎖 + 雙重校驗:

// 鎖-實例
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查詢到值定铜,也將空值寫進緩存阳液,但可以設(shè)置較短過期時間 |
| 緩存雪崩 | 大量的key設(shè)置了相同的過期時間,導(dǎo)致在緩存在同一時刻全部失效宿稀,造成瞬時DB請求量大趁舀、壓力驟增,引起雪崩 | 可以給緩存設(shè)置過期時間時加上一個隨機值時間祝沸,使得每個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ù)庫的雙寫一致性痊硕?

三種更新策略:

  1. 先更新數(shù)據(jù)庫赊级,再更新緩存 ->
  2. 先刪除緩存,再更新數(shù)據(jù)庫
  3. 先更新數(shù)據(jù)庫岔绸,再刪除緩存

方案一:并發(fā)的時候理逊,執(zhí)行順序無法保證,可能A先更新數(shù)據(jù)庫盒揉,但B后更新數(shù)據(jù)庫但先更新緩存

加鎖的話晋被,確實可以避免,但這樣吞吐量會下降刚盈,可以根據(jù)業(yè)務(wù)場景考慮

方案二:該方案會導(dǎo)致不一致的原因是羡洛。同時有一個請求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. 給鍵設(shè)置合理的過期時間

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ù)淘汰策略:

  1. volatile-lru:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選最近最少使用的數(shù)據(jù)淘汰
  2. volatile-ttl:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中挑選將要過期的數(shù)據(jù)淘汰
  3. volatile-random:從已設(shè)置過期時間的數(shù)據(jù)集(server.db[i].expires)中任意選擇數(shù)據(jù)淘汰
  4. allkeys-lru:從數(shù)據(jù)集(server.db[i].dict)中挑選最近最少使用的數(shù)據(jù)淘汰
  5. allkeys-random:從數(shù)據(jù)集(server.db[i].dict)中任意選擇數(shù)據(jù)淘汰
  6. 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)換錯誤斋射、阻塞育勺、客戶端關(guā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 中提到過南蓬,當(dāng)所需內(nèi)存超過配置的最大內(nèi)存時纺非,redis會啟用數(shù)據(jù)淘汰規(guī)則

默認(rèn)規(guī)則是:# maxmemory-policy noeviction

即只允許讀,無法繼續(xù)添加key

因此常需要配置淘汰策略赘方,比如LRU算法

image

LRU算法最為精典的實現(xiàn)烧颖,就是HashMap+Double LinkedList,時間復(fù)雜度為O(1)

Q15:Redis大批量增加數(shù)據(jù)

參考文章:https://www.cnblogs.com/PatrickLiu/p/8548580.html

使用管道模式窄陡,運行的命令如下所示:

cat data.txt | redis-cli --pipe

data.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: 1000000

redis-cli實用程序還將確保只將從Redis實例收到的錯誤重定向到標(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 實戰(zhàn): 文件詳情:可見Redis-通道實戰(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乘综,進行執(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.本地數(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)入完成

注意事項:RESP協(xié)議要求卡辰,不要有莫名其妙的字符,注意文件類型是Unix編碼類型

Q16:延申:布隆過濾器

數(shù)據(jù)結(jié)構(gòu)及算法篇 / 布隆過濾器

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腳本相關(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腳本蠢莺,用來釋放分布式鎖 - 如果使用的較多寒匙,可以封裝到文件中, 再進行調(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)置隊列中,然后主線程一個個執(zhí)行躏将,因此 其中一個 命令執(zhí)行時間過長锄弱,會造成成批量的阻塞

命令:slowlog get 獲取慢查詢記錄slowlog len 獲取慢查詢記錄量(慢查詢隊列是先進先出的考蕾,因此新的值在滿載的時候,舊的會出去)

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 實際是封裝過一層的指令集 ->  實際應(yīng)用的還是單條指令辕翰,但是節(jié)省了網(wǎng)絡(luò)傳輸開銷(服務(wù)端到Redis環(huán)境的網(wǎng)絡(luò)開銷)

最后

本篇是一篇大合集夺衍,中間肯定參考了許多其他人的文章內(nèi)容或圖片狈谊,但由于時間比較久遠(yuǎn),當(dāng)時并沒有一一記錄沟沙,為此表示歉意河劝,如果有作者發(fā)現(xiàn)了自己的文章或圖片,可以私聊我矛紫,我會進行補充赎瞎。

如果你發(fā)現(xiàn)寫的還不錯,可以搜索公眾號「是Kerwin啊」颊咬,一起進步务甥!

也可以查看Kerwin的GitHub主頁[1]

參考資料

[1]

Kerwin的GitHub主頁: https://github.com/kkzhilu

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喳篇,隨后出現(xiàn)的幾起案子敞临,更是在濱河造成了極大的恐慌,老刑警劉巖麸澜,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挺尿,死亡現(xiàn)場離奇詭異,居然都是意外死亡炊邦,警方通過查閱死者的電腦和手機编矾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來馁害,“玉大人窄俏,你說我怎么就攤上這事糙箍≌八蹋” “怎么了注竿?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵极景,是天一觀的道長卖毁。 經(jīng)常有香客問我特纤,道長铺韧,這世上最難降的妖魔是什么赤炒? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任吊骤,我火速辦了婚禮缎岗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘白粉。我一直安慰自己传泊,他們只是感情好鼠渺,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著眷细,像睡著了一般拦盹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上溪椎,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天普舆,我揣著相機與錄音,去河邊找鬼校读。 笑死沼侣,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的歉秫。 我是一名探鬼主播蛾洛,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼雁芙!你這毒婦竟也來了轧膘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤兔甘,失蹤者是張志新(化名)和其女友劉穎谎碍,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裂明,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡椿浓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了闽晦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扳碍。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖仙蛉,靈堂內(nèi)的尸體忽然破棺而出笋敞,到底是詐尸還是另有隱情,我是刑警寧澤荠瘪,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布夯巷,位于F島的核電站,受9級特大地震影響哀墓,放射性物質(zhì)發(fā)生泄漏趁餐。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一篮绰、第九天 我趴在偏房一處隱蔽的房頂上張望后雷。 院中可真熱鬧,春花似錦、人聲如沸臀突。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽候学。三九已至藕筋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梳码,已是汗流浹背隐圾。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留边翁,地道東北人翎承。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像符匾,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子瘩例,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348