JAVA树形结构数据优化

项目背景

1.由于当时这个是从0-1的新项目,为了开快速开发功能,我们第一版接口,直接从数据库中查询组织数据,组装成组织树,然后返回给前端。
2.通过这种方式,简化了数据流程,快速把整个页面功能调通了。

组织树优化

目前优化方案

1.代码部署IDC环境,刚开始没啥问题;
2.随着4A全量组织数据导入,很快就暴露出性能瓶颈;
3.我们不得不做优化了,我们第一个想到的是代码层面进行优化;
4.代码优化如下图,目前已经符合上线条件。

/**
  * 查询组织树
  * @param arkSysDept
  * @return
  */
 @Override
 public List<DeptTreeNode> findDeptTreeByDept(ArkSysDept arkSysDept) {
     List<DeptTreeNode> treeList = new ArrayList<DeptTreeNode>();
     arkSysDept.setDelFlag("0");
     Long startTime = System.currentTimeMillis();
     List<DeptTree> deptList = new ArrayList<DeptTree>();
     if(redisUtils.hasKey(DEPT_TREE_NODE)){
         deptList = (List<DeptTree>) redisUtils.get(DEPT_TREE_NODE);
         log.info("查询redis总耗时 :" + (System.currentTimeMillis()-startTime));
     }else{
         deptList = arkSysDeptDao.findDeptTreeByDept(arkSysDept);
         log.info("查询数据库总耗时 :" + (System.currentTimeMillis()-startTime));
         redisUtils.set(DEPT_TREE_NODE,deptList);
     }
     Long buildTreeStartTime = System.currentTimeMillis();
     treeList = buildTree(deptList);
     log.info("封装组织树总耗时 :" + (System.currentTimeMillis()-buildTreeStartTime));
     log.info("查询组织树总耗时 :" + (System.currentTimeMillis()-startTime));
     return treeList;
 }

/**
  * 封装组织树结构list
  * @param deptList
  * @return
  */
 private List<DeptTreeNode> buildTree(List<DeptTree> deptList) {
     if(null == deptList || deptList.isEmpty()){
         return new LinkedList<DeptTreeNode>();
     }
     // 最终要返回的只保留根节点的集合,增删比较多,使用LinkedList
     List<DeptTreeNode> respTreeNodeList = new LinkedList<DeptTreeNode>();
     // id集合,后面用来判断parentId是否在deptList存在,如果存在则说明是子节点,不存在则认为是根节点
     Set<String> idSet = new HashSet<>(deptList.size());
     DeptTreeNode deptTreeNode;
     for (DeptTree deptTree : deptList) {
         deptTreeNode = new DeptTreeNode();
         deptTreeNode.setId(deptTree.getDeptId().toString());
         deptTreeNode.setLabel(deptTree.getDeptName());
         deptTreeNode.setFreeze("Y");
         deptTreeNode.setParentId(deptTree.getParentId().toString());
         // 遍历的时候顺便保存id集合
         idSet.add(deptTreeNode.getId());
         respTreeNodeList.add(deptTreeNode);
     }
     // 获取由parentId作为key的map,同一个父节点的数据已经被汇总成一个list,通过key就能获取
     Map<String, List<DeptTreeNode>> parentIdMap = respTreeNodeList.stream().collect(Collectors.groupingBy(item -> item.getParentId() != null ? item.getParentId() : "nonParent"));
     // 设置children,并从根节点移出不是父节点的数据
     Iterator<DeptTreeNode> iterator = respTreeNodeList.iterator();
     DeptTreeNode node;
     while (iterator.hasNext()) {
         node = iterator.next();
         // 获取当前结点的子节点集合
         node.setChildren(parentIdMap.get(node.getId()));
         // 如果父id在id集合中存在,则说明他不是根节点,移除
         if (idSet.contains(node.getParentId())) {
             iterator.remove();
         }else{
             String pId = node.getParentId();
             if(!pId.equals("-1")){
                 iterator.remove();
             }
         }
     }
     return respTreeNodeList;
 }

后期优化法案一

1.组织相关数据实时性要求不是太高;
2.我们第一个想到的是:加Redis缓存;
3.用户访问接口获取组织树时,先从Redis中查询数据;
4.如果Redis中有数据,则直接数据;
5.如果Redis中没有数据,则再从数据库中查询数据,拼接成组织树返回;
6.将从数据库中查到的组织树的数据,保存到Redis中,设置过期时间5分钟;
7.将组织树返回给用户;
8.流程图如下:
在这里插入图片描述

后期优化法案二

1.随着数据量不断增加,可以添加一层内存缓存;
2.使用Job定期异步更新组织树到Redis中;
3.内存缓存是保存在服务器节点上的,不同的服务器节点更新的频率可能有点差异,这样可能会导致数据的不一致性;
4.但组织本身是更新频率比较低的数据,对于用户来说不太敏感,即使在短时间内,用户看到的组织树有些差异,也不会对用户造成太大的影响;
5.因此,组织树这种业务场景,是可以使用内存缓存的;
6.建议使用了Spring推荐的caffine作为内存缓存;
7.流程图如下:
在这里插入图片描述

优化后业务逻辑说明

1.用户访问接口时改成先从本地缓存分类数查询数据;
2.如果本地缓存有,则直接返回;
3.如果本地缓存没有,则从Redis中查询数据;
4.如果Redis中有数据,则将数据更新到本地缓存中,然后返回数据;
5.如果Redis中也没有数据(说明Redis挂了),则从数据库中查询数据,更新到Redis中(万一Redis恢复了呢),然后更新到本地缓存中,返回返回数据;
6.需要注意的是,需要改本地缓存设置一个过期时间,这里设置的5分钟,不然的话,没办法获取新的数据。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值