引入:最近的项目中,要用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 staticT 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,除非你能力很强可能解决它会出现的问题