问题来了
- 有的值可以存进去
- 有的值存不进去
是不是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();
}
}
}
重新部署下
看看结果
存储了下来。问题得以解决了
总结下
- 第一步查找日志或者监控
- 源码阅读
- 测试对比
- 断点调试,找到调用链路中可能出现问题的地方