赞
踩
系统有一个功能向别的系统多线程推送用户数据信息,前几天发现该推送功能报内部错误,经过查看后台日志文件,发现org.redisson.client.RedisConnectionException: Unable to connect to Redis server:,io.netty.channel.ChannelException: Unable to create Channel from class class io.netty.channel.socket.nio.NioSocketChannel。
错误日志输出特别清晰明了,显示redis的连接已经满了,不能创建新的NioSocketChannel了。
在该功能用户redis的地方无非就两个。
第一个场景是多线程请求其他系统的token值,这个过程中加了锁,确保多线程只有一个线程拿到token值并且将该token写入redis,其他线程复用这个线程获取的token进行用户数据推送,这样可以降低双方系统的资源消耗。
2、第二个场景就是在redis里面取出上一步的token值,进行下一步的业务处理。
关键代码:
- public String generateToken(RedissonClient redissonClient) {
-
- RLock lock = redissonClient.getLock(InterfacesConstants.REDISSON_LOCK);
-
- try {
-
- lock.lock(30, TimeUnit.SECONDS);
-
- RBucket<String> testBucket = redissonClient.getBucket(InterfacesConstants.PUSH_USER_TOKEN);
- //获取推送用户的token值
- String pushUserToken = testBucket.get();
-
- log.info("线程名为:{}的线程获取到「pushUserToken」为:{}", Thread.currentThread().getName(), pushUserToken);
-
- if (EmptyUtil.isEmpty(pushUserToken)) {
- //获取token
- IDasTokenResultDto tokenFromIDas = getTokenFromIDas();
-
- //获取IAM平台的token值
- pushUserToken = tokenFromIDas.getAccess_token();
- if (EmptyUtil.isEmpty(pushUserToken)) {
- log.info("未接收到IAM返回的token值!!");
- return null;
- }
- //重置token过期时间
- testBucket.set(pushUserToken, (Integer.parseInt(tokenFromIDas.getExpires_in())) / 1000 - 5, TimeUnit.SECONDS);
-
- }
-
- //拼接IAM所需要的token值
- String finalAccessToken = "Bearer " + pushUserToken;
-
- return finalAccessToken;
- } catch (Exception e) {
- log.error("获取IAM平台的token值错误,error message:{}", e.getMessage());
- } finally {
- lock.unlock();
- }
-
- return null;
-
- }
仔细检查上述场景的业务代码,也没有发现类似的问题。。。。。。
回过头又仔细分析了整个业务链条的场景,终于发现了问题所在。问题出在了RedissonClient实例化上,一般的项目会把这种公共的类交给spring容器去自动实例化,这个老项目因为架构的原因开发者自己去实例的该对象。
所以每次触发这个功能都会实例一次RedissonClient对象,从而导致redis的连接越来越来多并没有释放,一直到沾满。所以只要在RedissonClient实例的时候做点小动作就可以填上这个坑了。
原代码:
- @Slf4j
- public class RedissonConfig {
-
- private static final String REDIS_CONFIG_FILE = "Config/redis.properties";
-
- private static final String SINGLE = "standalone";
-
- private static final String CLUSTER = "cluster";
-
- private static final String HOST_PORT = "hostports";
-
- private RedissonConfig() {
- throw new IllegalStateException("Utility class");
- }
-
-
-
- public static RedissonClient initialRedissonClient() {
- Map<String, String> redisInfoMap = getHostAndPwd();
- if(EmptyUtil.isEmpty(redisInfoMap)){
- return null;
- }
-
- String runMode = redisInfoMap.get("runMode");
- RedissonClient redisson=null;
- //单机
- if(Objects.equals(SINGLE,runMode)){
-
- redisson = initialSingleRedisson();
- log.info("实例化单节点「RedissonClient」成功!!");
- //集群
- }else if (Objects.equals(CLUSTER,runMode)){
- redisson = initialClusterRedisson();
- log.info("实例集群「RedissonClient」成功!!");
- }
-
- return redisson;
-
-
- }
-
-
- public static RedissonClient initialSingleRedisson(){
-
- try {
- Map<String, String> redisInfoMap = getHostAndPwd();
- if(EmptyUtil.isEmpty(redisInfoMap)){
- return null;
- }
-
- String hostPort = redisInfoMap.get(HOST_PORT);
- String redisPwd = redisInfoMap.get("pwd");
- //单机
- Config config = new Config();
- config.useSingleServer().setAddress("redis://" + hostPort).setPassword(redisPwd);
- RedissonClient redisson = Redisson.create(config);
- log.info("「SingleRedisson」初始化成功!!");
- return redisson;
-
- }catch (Exception e){
- log.info("「SingleRedisson」初始化异常!!message:{}",e.getMessage());
- e.printStackTrace();
- }
- return null;
- }
-
-
- public static RedissonClient initialClusterRedisson(){
- //集群
- try {
- Map<String, String> redisInfoMap = getHostAndPwd();
- if(EmptyUtil.isEmpty(redisInfoMap)){
- return null;
- }
-
- String hostPort = redisInfoMap.get(HOST_PORT);
- String redisPwd = redisInfoMap.get("pwd");
- Config config = new Config();
- String[] hostPostArr = hostPort.split(",");
- for (String str : hostPostArr) {
- config.useClusterServers()
- //设置扫描间隔时间
- .setScanInterval(2000)
- .setPassword(redisPwd)
- .addNodeAddress("redis://" + str);
- }
- RedissonClient redisson = Redisson.create(config);
- return redisson;
-
- }catch (Exception e) {
- log.info("「ClusterRedisson」初始化异常!!message:{}",e.getMessage());
- e.printStackTrace();
- }
- return null;
- }
-
-
- public static Map<String,String> getHostAndPwd(){
- try {
- //加载连接池配置文件
- Properties props = PropsUtil.loadProps(REDIS_CONFIG_FILE);
- String runMode = props.getProperty("redis.runMode");
- String hostPort = props.getProperty("redis.hostports");
- String redisPwd = props.getProperty("redis.password");
- Map<String, String> map = new HashMap<>();
- map.put("runMode",runMode);
- map.put(HOST_PORT,hostPort);
- map.put("pwd",redisPwd);
-
- log.info("解析redis配置文件获取到的连接信息:{}",map);
- if(EmptyUtil.isEmpty(map)){
- log.warn("未解析到redis的配置信息!!");
- return Maps.newHashMap();
- }
-
- return map;
- }catch (Exception e) {
- log.info("redisson初始化获取结点和密码失败");
- e.printStackTrace();
- }
- return Maps.newHashMap();
- }
-
-
- }
知道问题在哪了,剩下的就好说了,我们只要保证该Java进程里面实例化有且只有一个RedissonClient对象就可以,spring的单例bean还用不了,这就得造一波轮子了。都到这了,单例模式走起。。。
优化后代码:
- @Slf4j
- public class RedissonConfig {
-
- private static final String REDIS_CONFIG_FILE = "Config/redis.properties";
-
- private static final String SINGLE = "standalone";
-
- private static final String CLUSTER = "cluster";
-
- private static final String HOST_PORT = "hostports";
- /**
- * volatile修饰(防止指令重排序)
- **/
- private volatile static RedissonClient instance;
-
- private RedissonConfig() {
- throw new IllegalStateException("Utility class");
- }
-
- public static RedissonClient initialRedissonClient() {
-
- //单例模式的双重校验
- if(instance == null){
- log.info("「RedissonClient」实例instance为空,进行实例化操作");
- synchronized (RedissonConfig.class) {
- //如果是空,就实例化对象
- if (instance == null) {
-
- Map<String, String> redisInfoMap = getHostAndPwd();
- if(EmptyUtil.isEmpty(redisInfoMap)){
- return null;
- }
-
- String runMode = redisInfoMap.get("runMode");
- //单机
- if(Objects.equals(SINGLE,runMode)){
-
- instance = initialSingleRedisson();
- log.info("实例化单节点「RedissonClient」成功!!");
- //集群
- }else if (Objects.equals(CLUSTER,runMode)){
- instance = initialClusterRedisson();
- log.info("实例集群「RedissonClient」成功!!");
- }
-
- }
- }
-
- }else {
- log.info("「RedissonClient」实例instance为:{},无需重新实例化「RedissonClient」",instance);
- }
-
-
- return instance;
- }
-
- public static RedissonClient initialSingleRedisson(){
-
- try {
- Map<String, String> redisInfoMap = getHostAndPwd();
- if(EmptyUtil.isEmpty(redisInfoMap)){
- return null;
- }
-
- String hostPort = redisInfoMap.get(HOST_PORT);
- String redisPwd = redisInfoMap.get("pwd");
- //单机
- Config config = new Config();
- config.useSingleServer().setAddress("redis://" + hostPort).setPassword(redisPwd);
- RedissonClient redisson = Redisson.create(config);
- log.info("「SingleRedisson」初始化成功!!");
- return redisson;
-
- }catch (Exception e){
- log.info("「SingleRedisson」初始化异常!!message:{}",e.getMessage());
- e.printStackTrace();
- }
- return null;
- }
-
-
- public static RedissonClient initialClusterRedisson(){
- //集群
- try {
- Map<String, String> redisInfoMap = getHostAndPwd();
- if(EmptyUtil.isEmpty(redisInfoMap)){
- return null;
- }
-
- String hostPort = redisInfoMap.get(HOST_PORT);
- String redisPwd = redisInfoMap.get("pwd");
- Config config = new Config();
- String[] hostPostArr = hostPort.split(",");
- for (String str : hostPostArr) {
- config.useClusterServers()
- //设置扫描间隔时间
- .setScanInterval(2000)
- .setPassword(redisPwd)
- .addNodeAddress("redis://" + str);
- }
- RedissonClient redisson = Redisson.create(config);
- return redisson;
-
- }catch (Exception e) {
- log.info("「ClusterRedisson」初始化异常!!message:{}",e.getMessage());
- e.printStackTrace();
- }
- return null;
- }
-
-
- public static Map<String,String> getHostAndPwd(){
- try {
- //加载连接池配置文件
- Properties props = PropsUtil.loadProps(REDIS_CONFIG_FILE);
- String runMode = props.getProperty("redis.runMode");
- String hostPort = props.getProperty("redis.hostports");
- String redisPwd = props.getProperty("redis.password");
- Map<String, String> map = new HashMap<>();
- map.put("runMode",runMode);
- map.put(HOST_PORT,hostPort);
- map.put("pwd",redisPwd);
-
- log.info("解析redis配置文件获取到的连接信息:{}",map);
- if(EmptyUtil.isEmpty(map)){
- log.warn("未解析到redis的配置信息!!");
- return Maps.newHashMap();
- }
-
- return map;
- }catch (Exception e) {
- log.info("redisson初始化获取结点和密码失败");
- e.printStackTrace();
- }
- return Maps.newHashMap();
- }
-
-
- }
对比以前的代码只需要实例化的时候改成单例模式就行了。
出现类似问题的原因可能一般开发者潜移默化的依赖spring架构的便利性,以至于一些特殊场景的实现不知觉中就按照以前的编码风格去开发,前期简单测试不会发现问题所在,系统压力上来了以后才能暴露出来。程序猿:我不管,反正是测试的锅,谁让他们压测做的不到位呢!!测试:我尼玛。。。。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。