一次项目使用redis单机的坑

引入:最近的项目中,要用redis来做缓存,优化一些与数据库的交互,所以本人也开始了第一次正式的使用。

      因为框架中已经把spring的一些特性关掉(例如注入等),然后做了封装(为了避免一些无用的注入导致浪费资源以及其他一些特性),所以我当时想到的是没法用spring管理,那就先用连接池jedisPool ,也在网上查阅了一些资料,直接开始着手,用法很普遍,一查一大堆,都是先初始化配置以及连接池,然后每次都从连接池中取出资源来和redis进行交互。

上代码:

public void init() throws FAPBusinessException{
    Log.info("开始初始化jedis连接池");
    try {
        if(jedisPool==null){
            JedisPoolConfig config = new JedisPoolConfig();
            config.setMaxTotal(MAX_ACTIVE);
            config.setMaxIdle(MAX_IDLE);
            config.setMaxWaitMillis(MAX_WAIT);
            config.setTestOnBorrow(TEST_ON_BORROW);
            if(!"-1".equals(PASSWORD)){
                jedisPool = new JedisPool(config, HOST, PORT, TIMEOUT,PASSWORD);
            }else{
                jedisPool = new JedisPool(config, HOST, PORT, TIMEOUT);
            }

        }
    } catch (Exception e) {
        Log.error("JedisInit.init: {}", e);
        throw new FAPBusinessException("初始化redis配置异常");
    }

    Log.info("redis配置加载完毕");
}

public   static Jedis getJedis() {
    if (jedisPool == null) {
        Log.info("redis初始化失败");
    }
    Jedis jedis = null;
    try {
        if (jedisPool != null) {
            jedis = jedisPool.getResource();
        }
    } catch (Exception e) {
        Log.error("getJedis() 方法出错:" + e.getMessage());
    } finally {
        close(jedis);
    }
    return jedis;
}

/**
 * 释放jedis资源
 * @param jedis
 */
public  static void close(final Jedis jedis) {
    try {
        if (jedis != null && jedisPool != null) {
            jedis.close();
        }
    } catch (Exception e) {
        Log.error("close() 方法出错:" + e.getMessage());
    }
}

     这个是很普遍的,在网上可以一查一大堆,然后按照自己的需要改就可以。

    我的用法也是很基础,在业务层每次都直接getResource拿到一个Jedis对象进行操作原生方法,在撸代码的过程中还是很和谐的。but,当进行压测的时候,问题开始一个又一个的出了,java.lang.ClassCastException: [B cannot be cast to java.lang.Long,要么就是一些转化异常,当时看到转化异常,又对着redis库中存的内容发现是数据拿乱了,然后就开始想解决办法,那我可不可以用完以后就关掉连接,然后让他重新去连接池中拿,这样不就不会有拿错的问题了?于是实践,我就把一些常用的原生方法进行封装,执行完成之后就close掉。问题更多,大部分都是socket Closed这些问题,然后去确认了一下,连接池是不需要关闭的,只不过是用完就放回去等待下一个线程调用,然后就开始查资料,原因也查到了,说是多个线程同时调用了同一个jedis对象,导致内存数据被多个线程竞争,产生数据混乱 于是开始想解决办法,如果是因为多个竞争的话,当前首先想到的是同步了呗。于是就把所有调用的方法加上了同步锁。

上代码:

public synchronized static String hgetValue(String key,String field) {
    if (getJedis() == null || !getJedis().exists(key)) {
        return null;
    }
    return getJedis().hget(key,field);
}

public synchronized static void hsetValue(String key,String field,String value) {
    if(getJedis()!=null){
        getJedis().hset(key,field,value);
    }
}

public synchronized static String getValue(String key) {
    if (getJedis() == null || !getJedis().exists(key)) {
        return null;
    }
    return getJedis().get(key);
}

public synchronized static void setValue(String key,String value) {
    if(getJedis()!=null){
        getJedis().set(key,value);
    }
}

public synchronized static Long llen(String key) {
    if(getJedis()!=null){
        return   getJedis().llen(key);
    }
    return null;
}

进行测试,哎?发现有效果,于是兴致冲冲的去压测,果然,问题少了很多,等等,嗯?怎么回事,用户量越大怎么又不行了,感觉自己都要炸掉了,开始不停的查资料,咨询。。。。,中间还试过了用JedisClustor集群,想着用一台机器来假装集群试下,之后就是按照集群的方式运作,不仅数据没有存进去,还把redis服务给搞宕机了。。。。。

经过一天的摧残,早已不省人事,忽然脑门一亮,想到了RedisTemplate这个东西,项目中没有spring的注入,我该怎么使用呢?费尽九牛二虎之力终于把这个东西拿到了。不多说直接甩代码

新加类:
public class JedisTempLate {

    private RedisTemplate redisTemplate;

    private RedisConnectionFactory redisConnectionFactory;

    public void Init(){
        redisTemplate= new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
    }

    public RedisTemplate getRedisTemplate(){
        return redisTemplate;
    }

    public void setRedisConnectionFactory(RedisConnectionFactory redisConnectionFactory) {
        this.redisConnectionFactory = redisConnectionFactory;
    }

}
public class SpringCtxHolder implements ApplicationContextAware {

    protected static ApplicationContext ctx;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringCtxHolder.ctx = applicationContext;
    }

    public static  T getBean(Class cls) {
        if (SpringCtxHolder.ctx == null) {
            Log.warn("sss");
            return null;
        }
        return ctx.getBean(cls);
    }
}

spring配置:


   
   
   
   
   



   
   
   
   
   



   
   



 

 

业务层在spring启动的时候初始化template:

public class IJedisUtilServiceImpl implements InitializingBean,IJedisUtilService {

    private RedisTemplate redisTemplate;

    @Override
    public void afterPropertiesSet() throws Exception {
        this.redisTemplate= SpringCtxHolder.getBean(RedisTemplate.class);
    }


    @Override
    public String hgetValue(String key, String field) {
        if (redisTemplate== null) {
            return null;
        }
        return (String) redisTemplate.opsForHash().get(key,field);
    }

}

 

     思路也很简单,SpringCtxHolder 继承一个spring上下文,然后自己封装一个可以获取bean的方法,在配置文件中初始化,以及初始化自己的这个JedisTemplate方法,对外一共一个方法用来获取RedisTemplate。IJedisUtilServiceImpl 用来提供一些操作redis的方法。

最后解决了这个问题,直接抛弃了JedisPool连接池,之后查资料发现RedisTemplate内部对连接池进行了封装,用ThreadLocal对线程的资源进行了绑定,解决这个多线程调用的问题,所以告诫大家如果是有这种并发场景的时候尽量不要去直接使用RedisPool,除非你能力很强可能解决它会出现的问题

你可能感兴趣的:(一次项目使用redis单机的坑)