「查缺補(bǔ)漏」鞏固你的Redis知識體系

來自公眾號:是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

解決方案:

  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ù)庫中隨機(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)容, 原理圖如下:

image
image
命令 說明
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

同步異步授瘦,阻塞非阻塞的概念:

image
image

假設(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ù)

image

RDB: 把當(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ù)器上环鲤。

image

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

image

例如上圖:將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ù)庫的雙寫一致性盗忱?

三種更新策略:

  1. 先更新數(shù)據(jù)庫酱床,再更新緩存 ->
  2. 先刪除緩存,再更新數(shù)據(jù)庫
  3. 先更新數(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ù)淘汰策略:

  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為單進(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市吐限,隨后出現(xiàn)的幾起案子鲜侥,更是在濱河造成了極大的恐慌,老刑警劉巖诸典,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件描函,死亡現(xiàn)場離奇詭異,居然都是意外死亡狐粱,警方通過查閱死者的電腦和手機(jī)舀寓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來肌蜻,“玉大人互墓,你說我怎么就攤上這事〗眩” “怎么了篡撵?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長豆挽。 經(jīng)常有香客問我育谬,道長,這世上最難降的妖魔是什么祷杈? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任斑司,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宿刮。我一直安慰自己互站,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布僵缺。 她就那樣靜靜地躺著胡桃,像睡著了一般。 火紅的嫁衣襯著肌膚如雪磕潮。 梳的紋絲不亂的頭發(fā)上翠胰,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天,我揣著相機(jī)與錄音自脯,去河邊找鬼之景。 笑死,一個胖子當(dāng)著我的面吹牛膏潮,可吹牛的內(nèi)容都是我干的锻狗。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼焕参,長吁一口氣:“原來是場噩夢啊……” “哼轻纪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起叠纷,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤刻帚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后涩嚣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體崇众,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年缓艳,在試婚紗的時候發(fā)現(xiàn)自己被綠了校摩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡阶淘,死狀恐怖衙吩,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情溪窒,我是刑警寧澤坤塞,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布,位于F島的核電站澈蚌,受9級特大地震影響摹芙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宛瞄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一浮禾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦盈电、人聲如沸蝴簇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽熬词。三九已至,卻和暖如春吸重,著一層夾襖步出監(jiān)牢的瞬間互拾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工嚎幸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留颜矿,地道東北人。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓鞭铆,卻偏偏與公主長得像或衡,于是被迫代替她去往敵國和親焦影。 傳聞我的和親對象是個殘疾皇子车遂,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容