1. 程式人生 > >Redis有的值能存有的值不能存、Jedis不好使了?

Redis有的值能存有的值不能存、Jedis不好使了?

問題來了


  • 有的值可以存進去
  • 有的值存不進去

是不是redis壞了?是不是Jedis 客戶端不好使了。

本篇文章轉載自:程式碼飛:https://code.bywind.cn/2018/07/20/258/
另外有很多很不錯的文章大家可以參考下哦

本地測試一下:

同樣的redis.conf 檔案(window 和 centos)
centos 是線上
windows是本地測試

@Test
public void test03(){
    Jedis jedis = JedisUtil.getInstance().getJedis();
    jedis.hset
(Constants.GOODS_KANGO_AMOUNT,"wxg_1803271041153448","1"); jedis.hincrBy(Constants.GOODS_KANGO_AMOUNT,"wxg_1803271041153449",1L); JedisUtil.returnBrokenResource(jedis); }

結果:redis 看到了這個值。存進去了

為什麼 在線上 存不進去呢 ?

解決思路

就是想看下 客戶端到底幹了什麼

./redis-cli
#輸入你的密碼(如果需要)
auth ****
#開啟對客戶端的監控 
monitor

於是再次測試(真是邏輯。不是上面的testcase)
這裡寫圖片描述

可以看到, 我這裡是很明顯 執行了 我需要的所有的 命令
但是不知道 從哪裡冒出來了一個
FLUSHDB
FLUSHALL
這個是在select 1 (redis 切換 database 1)的時候 緊跟著呼叫的 ( redis 是序列的)

那麼好的 我為什麼要切換database ? 切換成 1
我想到了我用redis的地方

  • 一些頻繁操作,放redis 減少DB壓力
  • 一些資料查詢,用redis 做mybatis 二級快取。

就只有這兩個地方了。
查了下mybatis的配置,果真用的是 database 1
這裡寫圖片描述

那麼好的 我需要看下原始碼了。為什麼會執行
flushdb
flushall
這兩個命令呢?
如果你要自定義你的mybatis二級快取
你需要實現這個介面

package org.apache.ibatis.cache;

import java.util.concurrent.locks.ReadWriteLock;

public interface Cache {

  String getId();

  int getSize();

  void putObject(Object key, Object value);

  Object getObject(Object key);

  Object removeObject(Object key);

  void clear();

  ReadWriteLock getReadWriteLock();

}

我的程式碼是這樣實現的(這個也是一個網上找的類)
大家著重看下 clear() 方法
這就是造成此次問題的罪魁禍首了

public class RedisCache implements Cache {
    private static final Logger logger = LoggerFactory.getLogger(RedisCache.class);

    private static JedisConnectionFactory jedisConnectionFactory;

    private final String id;

    /**
     * The {@code ReadWriteLock}.
     */
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public RedisCache(final String id) {
        if (id == null) {
            throw new IllegalArgumentException("Cache instances require an ID");
        }
        logger.debug("MybatisRedisCache:id=" + id);
        this.id = id;
    }

    @Override
    public void clear() {
        JedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            connection.flushDb();
            connection.flushAll();
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public Object getObject(Object key) {
        Object result = null;
        JedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            result = serializer.deserialize(connection.get(serializer.serialize(key)));
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return this.readWriteLock;
    }

    @Override
    public int getSize() {
        int result = 0;
        JedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            result = Integer.valueOf(connection.dbSize().toString());
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }

    @Override
    public void putObject(Object key, Object value) {
        JedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            connection.set(serializer.serialize(key), serializer.serialize(value));
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

    @Override
    public Object removeObject(Object key) {
        JedisConnection connection = null;
        Object result = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            RedisSerializer<Object> serializer = new JdkSerializationRedisSerializer();
            result = connection.expire(serializer.serialize(key), 0);
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
        return result;
    }

    public static void setJedisConnectionFactory(JedisConnectionFactory jedisConnectionFactory) {
        RedisCache.jedisConnectionFactory = jedisConnectionFactory;
    }

}

那麼什麼時候clear()會被呼叫呢 ?
查看了一下 他的呼叫(全域性搜尋的)
這裡寫圖片描述
看到這些 好熟悉,這些不就是 mybatis 對應二級快取的一些處理策略嘛
fifo
lru

也就是說。我們的實現類 cn.bywind46.pc.common.RedisCache
會最終 被引入到這些 策略 中 然後執行我們的重寫方法
這裡面也包括了 clear();

我在一個mapper檔案中配置,當前檔案所有select都是用快取
配置如下
現在明白了

  • 快取清理:LRU
  • 失效時間:86400000 (毫秒)
  • 快取容量:2048
<cache type="cn.bywind46.pc.common.RedisCache"
           eviction="LRU"
           flushInterval="86400000"
           size="2048"
           readOnly="false"/>

OK 這很好理解了
不是時間到了
就是size到了唄
但是不對啊。時間是一天已清理。還沒有到時間呢
size 我才存了幾個SQL語句,不到2048個物件引用數啊。

那這是為什麼呢?
繼續找了一下clear()方法的呼叫發現了這個類
他也是呼叫了clear方法,在什麼時候呼叫的呢?在事務提交的時候。
這就很好理解了,因為有事務的時候,通常是執行 寫操作(insert 、update、delete)
這些時候回清快取。瞭解了。回頭看下程式碼,確實是在存入redis之前 我先做了一次update 和 insert DB 操作。
這裡寫圖片描述

本地DEBUG看了下呼叫鏈路
(通過這個過程,也知道了,會有那些類參與這個過程(DB寫操作,事務提交,快取清理))
這一路的設計如下
這裡寫圖片描述
好的到這裡總算是找到思路了

怎麼做

找到RedisCache這個類,改在下clear()方法
很簡單,直接註釋掉 connection.flushAll()
其他的保持不變
mybatis的快取策略還是按照他自己的來
其他的快取 我們放到 不同的庫,這樣大家都不會有干擾了

 @Override
    public void clear() {
        JedisConnection connection = null;
        try {
            connection = jedisConnectionFactory.getConnection();
            connection.flushDb();
//            connection.flushAll();
        } catch (JedisConnectionException e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                connection.close();
            }
        }
    }

重新部署下
看看結果
這裡寫圖片描述
儲存了下來。問題得以解決了

總結下

  • 第一步查詢日誌或者監控
  • 原始碼閱讀
  • 測試對比
  • 斷點除錯,找到呼叫鏈路中可能出現問題的地方