1. 程式人生 > 其它 >JVM入門(二)——類的大小

JVM入門(二)——類的大小

一、事務

Redis事務:redis可以保證一條命令原子性,但是不保證多條命令的原子性

Redis隔離級別:不存在隔離級別的概念!

Redis事務的操作:

  • 開啟事務
  • 命令入隊
  • 執行事務
# example
127.0.0.1:6379> multi  # 開啟事務
OK
127.0.0.1:6379> mset k1 v1 k2 v2 # 設定多個值
QUEUED   # 進入佇列
127.0.0.1:6379> set k3 v3  # 設定一個值
QUEUED
127.0.0.1:6379> get k2  # 獲取一個值
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec  # 執行這個事務
1) OK  # 設定成功
2) OK # 設定成功
3) "v2"  # 獲取到了k2的值
4) OK  # 設定成功

###################################################

# 取消事務
127.0.0.1:6379> flushdb  # 清空redis 
OK
127.0.0.1:6379> multi  # 開啟事務
OK
127.0.0.1:6379> set key1 value1
QUEUED
127.0.0.1:6379> mset k2 v2 k3 v3 k4 v4 # 設定值
QUEUED
127.0.0.1:6379> DISCARD  # 取消事務
OK
127.0.0.1:6379> get key1  # 因為事務的取消  導致set設定的值沒有
(nil)


###################################################
#  Redis事務(多條語句下)不支援原子性
127.0.0.1:6379> set k1 "v1"   # 設定一個字串
OK
127.0.0.1:6379> multi # 開啟事務
OK
127.0.0.1:6379> incr k1 # 對字串進行自增,明顯會報錯
QUEUED
127.0.0.1:6379> set k2 c2  # 設定一個k2
QUEUED
127.0.0.1:6379> set k4 v5 # 設定k4
QUEUED
127.0.0.1:6379> exec  # 執行事務
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
127.0.0.1:6379> get k4  # 即使自增報錯,依舊可以拿到後面設定的值
"v5"


Redis多執行緒下樂觀鎖實現

# 執行緒1
127.0.0.1:6379> set flower 100  # 設定一個值
OK
127.0.0.1:6379> get flower   # 檢視
"100"
127.0.0.1:6379> watch flower  # 監視這個key
OK
127.0.0.1:6379> multi   # 開啟事務
OK
127.0.0.1:6379> DECRBY flower 10  # 對flower總體進行減法操作
QUEUED
127.0.0.1:6379> INCRBY out 10     # 既然flower減法操作了,那麼就對應有加法操作
QUEUED
127.0.0.1:6379> exec    # 執行事務
(nil)


# 執行緒2
127.0.0.1:6379> get flower  # 線上程1還未執行事務的時候,就搶佔了資源,先查看了flower
"100"
127.0.0.1:6379> set flower 1000  # 再對flower進行了操作, 那麼執行緒1的flower值肯定不對了,這就導致了樂觀鎖的失敗!!
OK

解決方案

# 執行緒1
127.0.0.1:6379> exec
1) (integer) 700
2) (integer) 300
127.0.0.1:6379> unwatch   # 放棄監視
OK
127.0.0.1:6379> watch flower  # 再次監視
OK
127.0.0.1:6379> multi  
OK
127.0.0.1:6379> INCRBY flower 300  # 加上300
QUEUED
127.0.0.1:6379> DECRBY out 300   # 減少300
QUEUED
127.0.0.1:6379> exec
1) (integer) 1000
2) (integer) 0

# 執行緒2 只獲取了key,並沒有修改,而執行緒1就不斷的嘗試,只要執行緒2不對資料進行修改就可以達到成功!!
127.0.0.1:6379> get flower
"700"
127.0.0.1:6379> get flower
"700"
127.0.0.1:6379> get flower
"1000"
  • 樂觀鎖:樂觀鎖是一種不實際加鎖的過程,在執行過程中,就以為它就是不會改變,在redis中樂觀鎖就可以用watch和unwatch實現,通過watch監視,當樂觀鎖失敗時,就使用unwatch取消監視然後又再次watch監視,再執行相同的操作,可以自己設定重試的次數,當某一次線上程1執行事務中,未被執行緒2搶佔時間片去修改,那麼執行緒1的事務就可以執行成功!

Redis 多執行緒

二、Springboot整合Redis

  • spring-boot-starter-data-redis版本在2.xxx之後採用的是lettuce作為redis的操作型別,它很好的支援了多執行緒併發下的執行緒安全問題(底層通訊採用了netty),而原來的jedis在多執行緒情況下會出現執行緒不安全的情況

匯入依賴

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.5.5</version>
        </dependency>

RedisTemplate

  • 如果不自定義RedisTemplate,springboot就會自動使用他裝配的預設RedisTemplate
@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
    public RedisAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean(
        name = {"redisTemplate"}
    )
    
    // ConditionalOnSingleCandidate表示如果沒有自定義RedisTemplate這個類那麼springboot自帶的RedisTemplate就會生效!
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean
    @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }
}
  • 自定義RedisTemplate

RedisTemplate需要進行序列化,不然儲存的物件或者中文都無法進行正常的讀取,可能會出現亂碼行為

  /**
     * RedisTemplate通用模板
     * @param factory
     * @return
     */
    @Bean(name = "redisTemplate")
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        //(1)Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //過期方法
        //om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        /**
         * Jackson ObjectMapper 中的 enableDefaultTyping 方法由於安全原因,
         * 從 2.10.0 開始標記為過期,建議用 activateDefaultTyping 方法代替,
         * 所以如果繼續使用 enableDefaultTyping 會有警告出現,我們現在要消除這個警告,
         * 去看一下 enableDefaultTyping 原始碼
         */
        om.activateDefaultTyping(
                LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL,
                JsonTypeInfo.As.WRAPPER_ARRAY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //(2)String的序列化
        RedisSerializer<String> stringRedisSerializer = new StringRedisSerializer();

        //序列化設定,這樣為了儲存操作物件時正常顯示的資料,也能正常儲存和獲取
        //key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key採用String序列化
        template.setHashKeySerializer(stringRedisSerializer);
        //value採用Json序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的value採用json序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

自定義RedisUtil實現對RedisTemplate的再次封裝

  • 使用RedisTemplate<String, Object> 底層api比較繁瑣,可以自定義一個Redis工具類來實現並達到像操作redis命令列的操作!
// 通用模板RedisTemplate<String, Object>

package com.example.utils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.DataType;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Connection;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * @author XiaZh
 * @version v1.0
 * @Description :
 * Created on 2021/10/11.
 */
@Component
public class RedisUtil {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    /**
     * 設定過期時間
     * @param key
     * @param time
     * @param unit
     * @return
     */
    public Boolean expire(String key, Long time, TimeUnit unit) {
        return redisTemplate.expire(key, time, unit);
    }

    /**
     * 刪除一個key
     * @param key
     */
    public void delete(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 批量刪除key
     * @param keys
     */
    public void delete(Collection<String> keys) {
        redisTemplate.delete(keys);
    }

    /**
     * 序列化key
     * @param key
     * @return
     */
    public byte[] dump(String key) {
        return redisTemplate.dump(key);
    }


    /**
     * 是否存在key
     *
     * @param key
     * @return
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 設定過期時間
     *
     * @param key
     * @param timeout
     * @param unit
     * @return
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }

    /**
     * 設定過期時間
     *
     * @param key
     * @param date
     * @return
     */
    public Boolean expireAt(String key, Date date) {
        return redisTemplate.expireAt(key, date);
    }

    /**
     * 查詢匹配的key
     *
     * @param pattern
     * @return
     */
    public Set<String> keys(String pattern) {
        return redisTemplate.keys(pattern);
    }

    /**
     * 將當前資料庫的 key 移動到給定的資料庫 db 當中
     *
     * @param key
     * @param dbIndex
     * @return
     */
    public Boolean move(String key, int dbIndex) {
        return redisTemplate.move(key, dbIndex);
    }

    /**
     * 移除 key 的過期時間,key 將持久保持
     *
     * @param key
     * @return
     */
    public Boolean persist(String key) {
        return redisTemplate.persist(key);
    }

    /**
     * 返回 key 的剩餘的過期時間
     *
     * @param key
     * @param unit
     * @return
     */
    public Long getExpire(String key, TimeUnit unit) {
        return redisTemplate.getExpire(key, unit);
    }

    /**
     * 返回 key 的剩餘的過期時間
     *
     * @param key
     * @return
     */
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    /**
     * 從當前資料庫中隨機返回一個 key
     *
     * @return
     */
    public String randomKey() {
        return redisTemplate.randomKey();
    }

    /**
     * 修改 key 的名稱
     *
     * @param oldKey
     * @param newKey
     */
    public void rename(String oldKey, String newKey) {
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * 僅當 newkey 不存在時,將 oldKey 改名為 newkey
     *
     * @param oldKey
     * @param newKey
     * @return
     */
    public Boolean renameIfAbsent(String oldKey, String newKey) {
        return redisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 返回 key 所儲存的值的型別
     *
     * @param key
     * @return
     */
    public DataType type(String key) {
        return redisTemplate.type(key);
    }

    /** -------------------string相關操作--------------------- */

    /**
     * 設定指定 key 的值
     * @param key
     * @param value
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 獲取指定 key 的值
     * @param key
     * @return
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 返回 key 中字串值的子字元
     * @param key
     * @param start
     * @param end
     * @return
     */
    public String getRange(String key, long start, long end) {
        return redisTemplate.opsForValue().get(key, start, end);
    }

    /**
     * 將給定 key 的值設為 value ,並返回 key 的舊值(old value)
     *
     * @param key
     * @param value
     * @return
     */
    public Object getAndSet(String key, Object value) {
        return redisTemplate.opsForValue().getAndSet(key, value);
    }

    /**
     * 對 key 所儲存的字串值,獲取指定偏移量上的位(bit)
     *
     * @param key
     * @param offset
     * @return
     */
    public Boolean getBit(String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * 批量獲取
     *
     * @param keys
     * @return
     */
    public List<Object> multiGet(Collection<String> keys) {
        return redisTemplate.opsForValue().multiGet(keys);
    }

    /**
     * 設定ASCII碼, 字串'a'的ASCII碼是97, 轉為二進位制是'01100001', 此方法是將二進位制第offset位值變為value
     *
     * @param key
     * @param value
     *            值,true為1, false為0
     * @return
     */
    public boolean setBit(String key, long offset, boolean value) {
        return redisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * 將值 value 關聯到 key ,並將 key 的過期時間設為 timeout
     *
     * @param key
     * @param value
     * @param timeout
     *            過期時間
     * @param unit
     *            時間單位, 天:TimeUnit.DAYS 小時:TimeUnit.HOURS 分鐘:TimeUnit.MINUTES
     *            秒:TimeUnit.SECONDS 毫秒:TimeUnit.MILLISECONDS
     */
    public void setEx(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }

    /**
     * 只有在 key 不存在時設定 key 的值
     *
     * @param key
     * @param value
     * @return 之前已經存在返回false,不存在返回true
     */
    public boolean setIfAbsent(String key, Object value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 用 value 引數覆寫給定 key 所儲存的字串值,從偏移量 offset 開始
     *
     * @param key
     * @param value
     * @param offset
     *            從指定位置開始覆寫
     */
    public void setRange(String key, String value, long offset) {
        redisTemplate.opsForValue().set(key, value, offset);
    }

    /**
     * 獲取字串的長度
     *
     * @param key
     * @return
     */
    public Long size(String key) {
        return redisTemplate.opsForValue().size(key);
    }

    /**
     * 批量新增
     *
     * @param maps
     */
    public void multiSet(Map<String, String> maps) {
        redisTemplate.opsForValue().multiSet(maps);
    }

    /**
     * 同時設定一個或多個 key-value 對,當且僅當所有給定 key 都不存在
     *
     * @param maps
     * @return 之前已經存在返回false,不存在返回true
     */
    public boolean multiSetIfAbsent(Map<String, String> maps) {
        return redisTemplate.opsForValue().multiSetIfAbsent(maps);
    }

    /**
     * 增加(自增長), 負數則為自減
     *
     * @param key
     * @return
     */
    public Long incrBy(String key, long increment) {
        return redisTemplate.opsForValue().increment(key, increment);
    }

    /**
     * 增加(自增長), 負數則為自減
     * @param key
     * @return
     */
    public Double incrByFloat(String key, double increment) {
        return redisTemplate.opsForValue().increment(key, increment);
    }

    /**
     * 追加到末尾
     *
     * @param key
     * @param value
     * @return
     */
    public Integer append(String key, String value) {
        return redisTemplate.opsForValue().append(key, value);
    }

    /** -------------------hash相關操作------------------------- */

    /**
     * 獲取儲存在雜湊表中指定欄位的值
     *
     * @param key
     * @param field
     * @return
     */
    public Object hGet(String key, String field) {
        return redisTemplate.opsForHash().get(key, field);
    }

    /**
     * 獲取所有給定欄位的值
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 獲取所有給定欄位的值
     *
     * @param key
     * @param fields
     * @return
     */
    public List<Object> hMultiGet(String key, Collection<Object> fields) {
        return redisTemplate.opsForHash().multiGet(key, fields);
    }

    public void hPut(String key, String hashKey, String value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }

    public void hPutAll(String key, Map<String, String> maps) {
        redisTemplate.opsForHash().putAll(key, maps);
    }

    /**
     * 僅當hashKey不存在時才設定
     *
     * @param key
     * @param hashKey
     * @param value
     * @return
     */
    public Boolean hPutIfAbsent(String key, String hashKey, String value) {
        return redisTemplate.opsForHash().putIfAbsent(key, hashKey, value);
    }

    /**
     * 刪除一個或多個雜湊表字段
     *
     * @param key
     * @param fields
     * @return
     */
    public Long hDelete(String key, Object... fields) {
        return redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 檢視雜湊表 key 中,指定的欄位是否存在
     *
     * @param key
     * @param field
     * @return
     */
    public boolean hExists(String key, String field) {
        return redisTemplate.opsForHash().hasKey(key, field);
    }

    /**
     * 為雜湊表 key 中的指定欄位的整數值加上增量 increment
     *
     * @param key
     * @param field
     * @param increment
     * @return
     */
    public Long hIncrBy(String key, Object field, long increment) {
        return redisTemplate.opsForHash().increment(key, field, increment);
    }

    /**
     * 為雜湊表 key 中的指定欄位的整數值加上增量 increment
     *
     * @param key
     * @param field
     * @param delta
     * @return
     */
    public Double hIncrByFloat(String key, Object field, double delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    /**
     * 獲取所有雜湊表中的欄位
     *
     * @param key
     * @return
     */
    public Set<Object> hKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }

    /**
     * 獲取雜湊表中欄位的數量
     *
     * @param key
     * @return
     */
    public Long hSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }

    /**
     * 獲取雜湊表中所有值
     *
     * @param key
     * @return
     */
    public List<Object> hValues(String key) {
        return redisTemplate.opsForHash().values(key);
    }

    /**
     * 迭代雜湊表中的鍵值對
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<Map.Entry<Object, Object>> hScan(String key, ScanOptions options) {
        return redisTemplate.opsForHash().scan(key, options);
    }

    /** ------------------------list相關操作---------------------------- */

    /**
     * 通過索引獲取列表中的元素
     *
     * @param key
     * @param index
     * @return
     */
    public Object lIndex(String key, long index) {
        return redisTemplate.opsForList().index(key, index);
    }

    /**
     * 獲取列表指定範圍內的元素
     *
     * @param key
     * @param start
     *            開始位置, 0是開始位置
     * @param end
     *            結束位置, -1返回所有
     * @return
     */
    public List<Object> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    /**
     * 儲存在list頭部
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushAll(String key, Object... value) {
        return redisTemplate.opsForList().leftPushAll(key, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushAll(String key, Collection<Object> value) {
        return redisTemplate.opsForList().leftPushAll(key, value);
    }

    /**
     * 當list存在的時候才加入
     *
     * @param key
     * @param value
     * @return
     */
    public Long lLeftPushIfPresent(String key, Object value) {
        return redisTemplate.opsForList().leftPushIfPresent(key, value);
    }

    /**
     * 如果pivot存在,再pivot前面新增
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public Long lLeftPush(String key, String pivot, Object value) {
        return redisTemplate.opsForList().leftPush(key, pivot, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushAll(String key, Object... value) {
        return redisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushAll(String key, Collection<Object> value) {
        return redisTemplate.opsForList().rightPushAll(key, value);
    }

    /**
     * 為已存在的列表新增值
     *
     * @param key
     * @param value
     * @return
     */
    public Long lRightPushIfPresent(String key, Object value) {
        return redisTemplate.opsForList().rightPushIfPresent(key, value);
    }

    /**
     * 在pivot元素的右邊新增值
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public Long lRightPush(String key, String pivot, Object value) {
        return redisTemplate.opsForList().rightPush(key, pivot, value);
    }

    /**
     * 通過索引設定列表元素的值
     *
     * @param key
     * @param index
     *            位置
     * @param value
     */
    public void lSet(String key, long index, Object value) {
        redisTemplate.opsForList().set(key, index, value);
    }

    /**
     * 移出並獲取列表的第一個元素
     *
     * @param key
     * @return 刪除的元素
     */
    public Object lLeftPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

    /**
     * 移出並獲取列表的第一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止
     *
     * @param key
     * @param timeout
     *            等待時間
     * @param unit
     *            時間單位
     * @return
     */
    public Object lBLeftPop(String key, long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().leftPop(key, timeout, unit);
    }

    /**
     * 移除並獲取列表最後一個元素
     *
     * @param key
     * @return 刪除的元素
     */
    public Object lRightPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    /**
     * 移出並獲取列表的最後一個元素, 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止
     *
     * @param key
     * @param timeout
     *            等待時間
     * @param unit
     *            時間單位
     * @return
     */
    public Object lBRightPop(String key, long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().rightPop(key, timeout, unit);
    }

    /**
     * 移除列表的最後一個元素,並將該元素新增到另一個列表並返回
     *
     * @param sourceKey
     * @param destinationKey
     * @return
     */
    public Object lRightPopAndLeftPush(String sourceKey, String destinationKey) {
        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                destinationKey);
    }

    /**
     * 從列表中彈出一個值,將彈出的元素插入到另外一個列表中並返回它; 如果列表沒有元素會阻塞列表直到等待超時或發現可彈出元素為止
     *
     * @param sourceKey
     * @param destinationKey
     * @param timeout
     * @param unit
     * @return
     */
    public Object lBRightPopAndLeftPush(String sourceKey, String destinationKey,
                                        long timeout, TimeUnit unit) {
        return redisTemplate.opsForList().rightPopAndLeftPush(sourceKey,
                destinationKey, timeout, unit);
    }

    /**
     * 刪除集合中值等於value得元素
     *
     * @param key
     * @param index
     *            index=0, 刪除所有值等於value的元素; index>0, 從頭部開始刪除第一個值等於value的元素;
     *            index<0, 從尾部開始刪除第一個值等於value的元素;
     * @param value
     * @return
     */
    public Long lRemove(String key, long index, Object value) {
        return redisTemplate.opsForList().remove(key, index, value);
    }

    /**
     * 裁剪list
     *
     * @param key
     * @param start
     * @param end
     */
    public void lTrim(String key, long start, long end) {
        redisTemplate.opsForList().trim(key, start, end);
    }

    /**
     * 獲取列表長度
     *
     * @param key
     * @return
     */
    public Long lLen(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /** --------------------set相關操作-------------------------- */

    /**
     * set新增元素
     *
     * @param key
     * @param values
     * @return
     */
    public Long sAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }

    /**
     * set移除元素
     *
     * @param key
     * @param values
     * @return
     */
    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }

    /**
     * 移除並返回集合的一個隨機元素
     *
     * @param key
     * @return
     */
    public Object sPop(String key) {
        return redisTemplate.opsForSet().pop(key);
    }

    /**
     * 將元素value從一個集合移到另一個集合
     *
     * @param key
     * @param value
     * @param destKey
     * @return
     */
    public Boolean sMove(String key, Object value, String destKey) {
        return redisTemplate.opsForSet().move(key, value, destKey);
    }

    /**
     * 獲取集合的大小
     *
     * @param key
     * @return
     */
    public Long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 判斷集合是否包含value
     *
     * @param key
     * @param value
     * @return
     */
    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    /**
     * 獲取兩個集合的交集
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<Object> sIntersect(String key, String otherKey) {
        return redisTemplate.opsForSet().intersect(key, otherKey);
    }

    /**
     * 獲取key集合與多個集合的交集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<Object> sIntersect(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().intersect(key, otherKeys);
    }

    /**
     * key集合與otherKey集合的交集儲存到destKey集合中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sIntersectAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().intersectAndStore(key, otherKey,
                destKey);
    }

    /**
     * key集合與多個集合的交集儲存到destKey集合中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sIntersectAndStore(String key, Collection<String> otherKeys,
                                   String destKey) {
        return redisTemplate.opsForSet().intersectAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 獲取兩個集合的並集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<Object> sUnion(String key, String otherKeys) {
        return redisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * 獲取key集合與多個集合的並集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<Object> sUnion(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().union(key, otherKeys);
    }

    /**
     * key集合與otherKey集合的並集儲存到destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sUnionAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     * key集合與多個集合的並集儲存到destKey中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sUnionAndStore(String key, Collection<String> otherKeys,
                               String destKey) {
        return redisTemplate.opsForSet().unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 獲取兩個集合的差集
     *
     * @param key
     * @param otherKey
     * @return
     */
    public Set<Object> sDifference(String key, String otherKey) {
        return redisTemplate.opsForSet().difference(key, otherKey);
    }

    /**
     * 獲取key集合與多個集合的差集
     *
     * @param key
     * @param otherKeys
     * @return
     */
    public Set<Object> sDifference(String key, Collection<String> otherKeys) {
        return redisTemplate.opsForSet().difference(key, otherKeys);
    }

    /**
     * key集合與otherKey集合的差集儲存到destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long sDifference(String key, String otherKey, String destKey) {
        return redisTemplate.opsForSet().differenceAndStore(key, otherKey,
                destKey);
    }

    /**
     * key集合與多個集合的差集儲存到destKey中
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long sDifference(String key, Collection<String> otherKeys,
                            String destKey) {
        return redisTemplate.opsForSet().differenceAndStore(key, otherKeys,
                destKey);
    }

    /**
     * 獲取集合所有元素
     *
     * @param key
     * @return
     */
    public Set<Object> setMembers(String key) {
        return redisTemplate.opsForSet().members(key);
    }

    /**
     * 隨機獲取集合中的一個元素
     *
     * @param key
     * @return
     */
    public Object sRandomMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }

    /**
     * 隨機獲取集合中count個元素
     *
     * @param key
     * @param count
     * @return
     */
    public List<Object> sRandomMembers(String key, long count) {
        return redisTemplate.opsForSet().randomMembers(key, count);
    }

    /**
     * 隨機獲取集合中count個元素並且去除重複的
     *
     * @param key
     * @param count
     * @return
     */
    public Set<Object> sDistinctRandomMembers(String key, long count) {
        return redisTemplate.opsForSet().distinctRandomMembers(key, count);
    }

    /**
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<Object> sScan(String key, ScanOptions options) {
        return redisTemplate.opsForSet().scan(key, options);
    }

    /**------------------zSet相關操作--------------------------------*/

    /**
     * 新增元素,有序集合是按照元素的score值由小到大排列
     *
     * @param key
     * @param value
     * @param score
     * @return
     */
    public Boolean zAdd(String key, String value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    /**
     *
     * @param key
     * @param values
     * @return
     */
    public Long zAdd(String key, Set<ZSetOperations.TypedTuple<Object>> values) {
        return redisTemplate.opsForZSet().add(key, values);
    }

    /**
     *
     * @param key
     * @param values
     * @return
     */
    public Long zRemove(String key, Object... values) {
        return redisTemplate.opsForZSet().remove(key, values);
    }

    /**
     * 增加元素的score值,並返回增加後的值
     *
     * @param key
     * @param value
     * @param delta
     * @return
     */
    public Double zIncrementScore(String key, Object value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    /**
     * 返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
     *
     * @param key
     * @param value
     * @return 0表示第一位
     */
    public Long zRank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    /**
     * 返回元素在集合的排名,按元素的score值由大到小排列
     *
     * @param key
     * @param value
     * @return
     */
    public Long zReverseRank(String key, Object value) {
        return redisTemplate.opsForZSet().reverseRank(key, value);
    }

    /**
     * 獲取集合的元素, 從小到大排序
     *
     * @param key
     * @param start
     *            開始位置
     * @param end
     *            結束位置, -1查詢所有
     * @return
     */
    public Set<Object> zRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    /**
     * 獲取集合元素, 並且把score值也獲取
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start,
                                                                   long end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    /**
     * 根據Score值查詢集合元素
     *
     * @param key
     * @param min
     *            最小值
     * @param max
     *            最大值
     * @return
     */
    public Set<Object> zRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().rangeByScore(key, min, max);
    }

    /**
     * 根據Score值查詢集合元素, 從小到大排序
     *
     * @param key
     * @param min
     *            最小值
     * @param max
     *            最大值
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key,
                                                                          double min, double max) {
        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max);
    }

    /**
     *
     * @param key
     * @param min
     * @param max
     * @param start
     * @param end
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> zRangeByScoreWithScores(String key,
                                                                          double min, double max, long start, long end) {
        return redisTemplate.opsForZSet().rangeByScoreWithScores(key, min, max,
                start, end);
    }

    /**
     * 獲取集合的元素, 從大到小排序
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<Object> zReverseRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().reverseRange(key, start, end);
    }

    /**
     * 獲取集合的元素, 從大到小排序, 並返回score值
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeWithScores(String key,
                                                                          long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeWithScores(key, start,
                end);
    }

    /**
     * 根據Score值查詢集合元素, 從大到小排序
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<Object> zReverseRangeByScore(String key, double min,
                                            double max) {
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
    }

    /**
     * 根據Score值查詢集合元素, 從大到小排序
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Set<ZSetOperations.TypedTuple<Object>> zReverseRangeByScoreWithScores(
            String key, double min, double max) {
        return redisTemplate.opsForZSet().reverseRangeByScoreWithScores(key,
                min, max);
    }

    /**
     *
     * @param key
     * @param min
     * @param max
     * @param start
     * @param end
     * @return
     */
    public Set<Object> zReverseRangeByScore(String key, double min,
                                            double max, long start, long end) {
        return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max,
                start, end);
    }

    /**
     * 根據score值獲取集合元素數量
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long zCount(String key, double min, double max) {
        return redisTemplate.opsForZSet().count(key, min, max);
    }

    /**
     * 獲取集合大小
     *
     * @param key
     * @return
     */
    public Long zSize(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 獲取集合大小
     *
     * @param key
     * @return
     */
    public Long zZCard(String key) {
        return redisTemplate.opsForZSet().zCard(key);
    }

    /**
     * 獲取集合中value元素的score值
     *
     * @param key
     * @param value
     * @return
     */
    public Double zScore(String key, Object value) {
        return redisTemplate.opsForZSet().score(key, value);
    }

    /**
     * 移除指定索引位置的成員
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Long zRemoveRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * 根據指定的score值的範圍來移除成員
     *
     * @param key
     * @param min
     * @param max
     * @return
     */
    public Long zRemoveRangeByScore(String key, double min, double max) {
        return redisTemplate.opsForZSet().removeRangeByScore(key, min, max);
    }

    /**
     * 獲取key和otherKey的並集並存儲在destKey中
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long zUnionAndStore(String key, String otherKey, String destKey) {
        return redisTemplate.opsForZSet().unionAndStore(key, otherKey, destKey);
    }

    /**
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long zUnionAndStore(String key, Collection<String> otherKeys,
                               String destKey) {
        return redisTemplate.opsForZSet()
                .unionAndStore(key, otherKeys, destKey);
    }

    /**
     * 交集
     *
     * @param key
     * @param otherKey
     * @param destKey
     * @return
     */
    public Long zIntersectAndStore(String key, String otherKey,
                                   String destKey) {
        return redisTemplate.opsForZSet().intersectAndStore(key, otherKey,
                destKey);
    }

    /**
     * 交集
     *
     * @param key
     * @param otherKeys
     * @param destKey
     * @return
     */
    public Long zIntersectAndStore(String key, Collection<String> otherKeys,
                                   String destKey) {
        return redisTemplate.opsForZSet().intersectAndStore(key, otherKeys,
                destKey);
    }

    /**
     *
     * @param key
     * @param options
     * @return
     */
    public Cursor<ZSetOperations.TypedTuple<Object>> zScan(String key, ScanOptions options) {
        return redisTemplate.opsForZSet().scan(key, options);
    }

}

三、Redis.config

網路

  • bind 127.0.0.1 // 繫結的ip地址
  • protected-mode yes // 保護模式
  • port 6379 // 埠設定

通用的GENERAL

  • daemonize // 以守護程序的方式執行 ,預設為no,需要手動開啟(如果不開啟的話,客戶端退出 就會導致斷開連線)

  • pidfile /var/run/redis_6379.pid // 後臺自動執行,須指定一個pid檔案

  • loglevel notice // 日誌級別 (預設生產環境)

  • logfile “ ” // 日誌的檔案位置

  • database 16 // 資料庫的個數

  • always-show-logo yes // 總是顯示logo

快照

  • 持久化 -> 在規定的時間內,執行了多少次操作,會持久化到.rdb .aof
save 300 1   # 如果在300s內進行了1次操作,就會執行持久化
save 100 5   # 如果在100s內進行了5次操作,就會執行持久化
save 60 10   # 如果在60s內進行了10次操作,就會執行持久化

stop-writes-on-bgsave-error  yes # 持久化如果出錯 設定為yes就是繼續工作,改為no就取消工作

rdbcompression yes  # 是否壓縮rdb檔案,需要消耗cpu資源
rdbchecksum yes # 儲存rdb檔案 並進行錯誤校驗
dir ./ # rdb預設儲存的目錄(當前目錄)

Security 密碼設定

127.0.0.1:6379> config get requirepass # 獲取密碼,預設為空1) "requirepass"2) ""127.0.0.1:6379> config set requirepass "ahui"  # 設定密碼OK127.0.0.1:6379> ping  # 當設定密碼 再去ping就需要進行登入校驗(error) NOAUTH Authentication required.127.0.0.1:6379> auth ahui  # 登入校驗成功OK127.0.0.1:6379> ping # 再次ping就會顯示為通的PONG127.0.0.1:6379> config get requirepass  # 獲取redis登入密碼1) "requirepass"2) "ahui"

append only 模式 aof配置

  • appendonly no // 預設不開啟aof模式,預設是rdb方式持久化

  • appendfilename "appendonly.aof" // 持久化的檔名字

  • appendfsync always // 每次修改都會sync 消耗效能

  • appendfsync everysec // 每秒執行一次sync 可能會丟失這1s的資料 (預設)

  • appendfsync no //不執行sync,這個時候作業系統自己同步資料,速度最快

四、Redis持久化

RDB理論理解:

rdb的持久化方式:在一定的時間間隔內將資料集的記憶體快照(snapshotting)儲存到硬碟中去(下面的RDB觸發規則)。

rdb的原理:他其實是通過fork一個子程序來專門進行持久化,會先將資料寫入到一個臨時檔案裡面,然後待持久化全部結束之後,再將這個臨時檔案去替換上次已經持久化好的檔案(dump.rdb)。這個全程他的服務程序是不用去操作的,所以啟動效率很高,效能也比較好,但是會在redis突然掛掉的時候,會出現最後一次資料丟失的問題。至於原因下面會講到

參考大佬部落格

RDB 觸發規則

  • 1、save
    • save 300 1 # 如果在300s內進行了1次操作,就會執行持久化
      save 100 5 # 如果在100s內進行了5次操作,就會執行持久化
      save 60 10 # 如果在60s內進行了10次操作,就會執行持久化
  • 2、執行flushall命令,也會觸發rdb規則
  • 3、退出redis(shutdown),也會觸發rdb規則
  • 4、bgsave(bgsave命令會建立一個子程序,由子程序來負責建立RDB檔案,父程序(即Redis主程序)則繼續處理請求)
bgsave命令執行過程中,只有fork子程序時會阻塞伺服器,而對於save命令,整個過程都會阻塞伺服器,因此save已基本被廢棄,線上環境要杜絕save的使用;後文中也將只介紹bgsave命令。此外,在自動觸發RDB持久化時,Redis也會選擇bgsave而不是save來進行持久化;下面介紹自動觸發RDB持久化的條件。

恢復rdb檔案

  • 只需要將rdb檔案放在redis的啟動目錄下,redis啟動時會自動檢查並恢復資料

  • 檢視需要存放的位置

    config get dir
    

只使用redis的快取功能,而不使用redis 的持久化功能

redis-cli  config set save " "

AOF理論理解:

AOF預設是不開啟的,是需要在redis.config中進行開啟

Redis的AOF會像log日誌一樣的格式在.aof檔案中進行追加操作的命令

Redis先將寫命令追加到緩衝區,而不是直接寫入檔案,主要是為了避免每次有寫命令都直接寫入硬碟,導致硬碟IO成為Redis負載的瓶頸。
命令追加的格式是Redis命令請求的協議格式,它是一種純文字格式,具有相容性好、可讀性強、容易處理、操作簡單避免二次開銷等優點;具體格式略。在AOF檔案中,除了用於指定資料庫的select命令(如select 0 為選中0號資料庫)是由Redis新增的,其他都是客戶端傳送來的寫命令。

五、釋出訂閱

使用場景:實時聊天系統.. 訂閱關注系統....

模型:

栗子:

釋出方

127.0.0.1:6379> PUBLISH ahui  hello  # 釋出的頻道及內容
(integer) 1

127.0.0.1:6379> PUBLISH ahui  sayredis!  
(integer) 1

127.0.0.1:6379> PUBLISH ahui test!!!
(integer) 1

訂閱方(一個或多個)

127.0.0.1:6379> SUBSCRIBE ahui
Reading messages... (press Ctrl-C to quit)
1) "subscribe"  # 訂閱此頻道ahui
2) "ahui"      
3) (integer) 1
1) "message"   # 當釋出方傳送資料到佇列中時,可以及時收到
2) "ahui"
3) "hello"

1) "message"
2) "ahui"
3) "sayredis!"

1) "message"
2) "ahui"
3) "test!!!"

命令用法

SUBSCRIBE ahui   # 訂閱方訂閱頻道

PUBLISH ahui  hello [...channel]  # 釋出方釋出頻道及內容(可以同時訂閱多個)

PUBSUB channels   # 檢視訂閱與釋出系統狀態

PUNSUBSCRIBE ahui [...channel]   #  退訂所以給定模式的頻道資訊
1) "punsubscribe"
2) "ahui"
3) (integer) 0

UNSUBSCRIBE ahui   # 退訂指定的頻道
1) "unsubscribe"
2) "ahui"
3) (integer) 0

六、主從複製

概述

主從複製:在大資料的資訊量下,單臺數據庫的讀寫壓力很大,以至於到現在推出的讀寫分離模式,即使用主從複製實現讀寫分離,在Redis中一般超過20G的話,redis的讀寫壓力就很大了,需要做讀寫分離來實現對資料庫的優化,主從複製就可以很好的實現讀寫分離,主機主要用於寫的操作,而從機就可以實現讀取的功能!

模型

主從配置命令(暫時性)

replicaof <masterip> <masterport>  # 在從機中配置主機ip + port 

info replication  # 檢視主從機複製的資訊

修改主從配置

  • 在.conf 中有一個replicaof,預設是註釋的,可以進行開啟,然後配置主機的相關資訊

主從複製注意事項

  • 在主機宕機的情況下,這個主從複製就沒有了寫入的操作,但是還可以進行讀取。
  • 在主從複製中,從機如果進行寫的操作,就會報錯
  • 在主機宕機後,修復好再次啟動並進行寫,從機依舊可以拿到主機的寫入操作

複製方式及原理

  • 主庫 master 和從庫 slave 之間通過複製 id 進行匹配,避免 slave 掛到錯誤的 master
  • slave啟動後,會向主機master發出sync同步命令
  • master會將傳送整個資料檔案傳輸到slave從機,這就完成了一次同步
  • 全量複製:slave第一次連線會進行全量複製,把master的資料全部同步,並載入到記憶體中
  • 增量複製:master會將寫入的操作,依次傳遞到從機slave上,完成同步
  • master 在分發寫請求時,同時會將寫指令複製一份存入複製積壓緩衝,這樣當 slave 短時間斷開重連時

主從複製的優缺點

  • 優點
    • 高可靠性:可以保證資料的完整性,也就是主機宕機了會有從機保證資料,從機宕機也可以採用sync同步
    • 讀寫分離策略:主機主要負責資料的寫入操作,而從機負責主機的同步,以及讀取操作。
  • 缺點
    • 故障恢復複雜,如果主機宕機了且沒有哨兵模式的話,就需要手動去重新選舉一個主機來替代原來的主機。
    • 主機的寫入能力受限於單機操作,當寫入操作較多時,主機可以考慮分片操作
    • 主機的儲存能力受限於單機操作,當儲存較多時,可以採用Pika

主機宕機,手動設定更改從機為主機

  • slaveof no one # 在主節點宕機後,從機使用此命令可以讓自己升級為主機,當原來主節點修復好了,再次連線,它也不再是主節點!!!

七、哨兵模式

概念:哨兵模式就是來解決主機宕機後,主機的自動重新選舉!!

具體參考

主從複製中單哨兵模式的應用

#配置埠
port 76379
#以守護程序模式啟動
daemonize yes
#日誌檔名
logfile "sentinel_76379.log"
#存放備份檔案以及日誌等檔案的目錄
dir "/opt/redis/data"
#監控的IP 埠號 名稱 sentinel通過投票後認為mater宕機的數量,此處為至少2個
sentinel monitor mymaster 192.168.14.101 6379 2
#30秒ping不通主節點的資訊,主觀認為master宕機
sentinel down-after-milliseconds mymaster 30000
#故障轉移後重新主從複製,1表示序列,>1並行
sentinel parallel-syncs mymaster 1
#故障轉移開始,三分鐘內沒有完成,則認為轉移失敗
sentinel failover-timeout mymaster 180000

Linux上啟動哨兵

redis-sentinel sentinel.conf

單哨兵弊端

  • 如果哨兵宕機了,也就代表著主機宕機的話,無法再自動的選取從機取代主機

哨兵叢集

  • 主節點引數
port 6377
daemonize yes
logfile "6377.log"
dbfilename "dump-6377.rdb"

  • 從節點1引數
port 6379
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"
slaveof 127.0.0.1 6377
  • 從節點2引數
port 6380
daemonize yes
logfile "6380.log"
dbfilename "dump-6380.rdb"
slaveof 127.0.0.1 6377

多哨兵模式缺點

  • 在多哨兵模式下,效率和安全得到了很大的提升,但是多哨兵模式的配置就很繁瑣,主哨兵宕機也會像主從複製機制那樣,重新選舉一個主哨兵來進行監控

八、快取穿透和雪崩(大面積查詢且查不到)

正常查詢資料庫

導致快取穿透

原因

  • 1、併發情況下,10000+使用者不斷請求快取,但快取都沒有這個資料
  • 2、使用者又不斷去請求mysql資料庫,mysql也沒有這個資料就導致了快取穿透

快取擊穿

  • 併發使用者同時請求同一個地址,而快取又剛好過期!快取過期,就會查詢mysql資料庫,而此時Mysql就會承受大量的查詢請求,就會導致快取擊穿

快取擊穿解決方案

  • 設定熱點資料永不過期, 這就有點浪費資源了
  • 加分散式鎖,在快取和mysql之間加分散式鎖,同一時間只能有一個執行緒進入mysql查詢

快取雪崩

原因:

  • 斷電情況下,伺服器正在寫的操作,突然終止,就可能導致快取雪崩
  • 伺服器宕機
  • 快取失效時間到,且沒有加上過濾器等措施

雪崩解決方案

  • Redis高可用:採用類似叢集模式的Redis伺服器,一臺宕機,另一臺接上
  • 限流降級:在快取失效後,採用加鎖或者過濾等措施來保證同一時間只有一個執行緒操作
  • 資料預熱:先測試資料到快取中,防止大量資料一下載入到快取,並設定不同的過期時間,讓快取失效的時間均勻分配

具體視訊參考狂神說!