首先通俗的介绍下redis:可以把redis当做一个数据库,像mysql这样,只是存放的方式和地方不一样。
redis将数据存放在内存中,所有读取的速度肯定比mysql快。
同时redis的数据类型是key-value型。和Map<String,Object>相似。所以对于java项目来说,很方便对redis 的数据操作。redis的value值可以支持 String,List,Set,ZSet(有序),Hash.
项目需求:项目需要统计每个使用者(大概每天1w人登录操作)的操作内容,操作了多少次(总共400个接口)。每天最多400w条数据。
方案:1.采用HashMap来存数据,数据内存太大。系统吃不消
2.接入监控平台,添加埋点即可。但是花费太大,每年11w
3.接入redis。费用大减
具体操作: 1.引入jar 由于项目比较老,用的jdk6,并且内网限制。采用了jedis 2.1.0 和commons-pool 1.5.6
jedis用于操作redis,常见的set,get,expire,其他对象的操作等。
现在基本使用jedis 2.9.0+ 和commons2-pool 2.4.2+ 并且会接入Spring(封装了一层)
2.具体代码逻辑,2.1 写好redis工具类,这个可以百度。然后注意连接池的配置。 JedisPoolConfig的配置。
2.2 写个拦截器。对所有的请求,获取用户编码 a,请求url b,当天时间 c. 以 a_b_c为key,value放次 数。第一次为1,然后 2. 可以用到redis的incrby方法。因为有了当前日期,并且我们打算存两天 的数据,所有得设 置有效期,不然以后垃圾数据就爆了。设置有效期为两天。
2.3 写个定时任务,每天凌晨将缓存的数据读取出来,批量插入到oracle中,然后定期进行归档(将次 数进行汇总,相同的key的数据累加,只保留前7天的数据和月份数据,后期归档到年数据中)
至此就完成了: 后面再根据oracle中的数据查询即可。
出现的问题:
1.JedisPoolConfig 的问题,很多参数只是百度然后没有认真思考。
1.1 whenExhaustedAction(当取不到jedis链接时的操作):默认是 阻塞等待。千万不要这样,这样会导致线上大量线程阻塞。 最好是设置为0.即超过等待时间还没获取到就抛异常。这个问题导致线上紧张的一批。
同时设置下max_wait的值。比如 3000,单位 毫秒
1.2. testOnBorrow 和testOnReturn 即 测试 取链接和还链接 是测试 链接时ok的,但是我出现了问题,并设置了后并没有起作 用。
2. 异常 JedisConnectionException
正常测试开发环境都没有问题,一旦并发,就出现问题了。。。。
后来经过不断的找问题,终于发现是对于异常处理的问题:
之前 的代码:
long result=0;
Jedis jedis=getJedis();
try{
result=jedis.incrBy(key,count)// key 为a_b_c 字符串,count 为1,累加1
if(result==1) jedis.expire(key,expire_time)//只在第一次设置过期时间
}catch(Exception e){
log.error(......)
}finally{
jedisPool.returnResource(jedis);//释放资源,将连接放回连接池
}
上述代码好像没有问题,可以一旦并发起来,jedis.incrBy 这个代码会有异常,
由于异常后没有将异常链接销毁掉,导致异常链接一直被用,从而一直报有错误信息。
所以在Catch那里需要有针对性的catch异常。
catch(JedisConnectionException e){
jedisPool.returnBrokenResource(jedis);//销毁异常链接
}
异常二:
是由于自己不小心造成的,后果非常严重。我在取链接的方法中的finally中添加了returnResource方法,在实际操作链接的地方,比如incrby(),get(),set()方法的finally也添加了returnResource方法。导致连接池中的一个参数 numActive为负数。应为每拿了一个链接,我返回的两次,导致现存的链接时负数。而 whenExhaustedAction 这参数的判断条件是当前链接大于最大链接时才会起作用,所以whenExhaustedAction 这个参数不起作用。可想而知。最后都阻塞了。