BI项目扩展(附前后端项目地址以及线上地址)

最近在做智能BI项目 , 通过鱼总给出的一些扩展点内容结合自己项目开发的一些思路 , 进行了如下的扩展。


主要扩展点


  1. 通过MongoDB进行生成图表结果存储 , 原始数据量较大 , 通过对原始数据与图表结果的分库存储 , 一方面减小MySQL压力, 另一方面也可以提高原始数据的安全性(默认用户删除图表是删除生成的结果 , 对于原始数据需要走单独的删除接口)
  2. 通过策略模式以及反向压力思想, 根据当前系统负载进行策略选择
  3. 通过Spring-Retry进行失败重试
  4. 通过对生成图表JSON数据进行压缩 , 实测平均节约35+%空间
  5. 通过Docker进行项目部署, 同时通过Github Actions实现Docker镜像构建与推送到阿里云镜像仓库的自动化
  6. 通过WebSocket进行图表生成结果的实时推送
  7. 通过阿里云OSS进行用户图表存储(主要是头像)
  8. 更改用户注册登录方式为邮箱
  9. JWT + Redis双Token单点登录
  10. Logback日志配置以及基于AOP的日志处理



目前的主要业务流程

下面我简单描述下部分扩展点的实现细节


MongoDB


MongoDB是一个面向文档的NoSQL数据库,它以JSON样式的文档存储数据。这种灵活的数据模型使得可以轻松地存储和检索不同结构的数据

对于生成的图表, 主要的操作更多是查询 , 也就是 读>>写

这里使用MongoDB进行信息存储 , 数据库查询速度提高了 3~4倍 , 接口响应速度快了40%+ , 因此我认为这一点还是十分有必要的。

要点在于我们如果去做好图表生成的CRUD操作以及MySQL与图表之间的数据一致性

关于CRUD操作 , 建议参考spring-data-mongodb的官方文档 , 这里给出地址


https://docs.spring.io/spring-data/mongodb/docs/3.3.10/reference/html/


另外, 十分建议球友们直接使用docker去进行服务搭建 (只需要就记住一次命令 , 基本是属于一劳永逸)


还有一点需要注意的就是MongoDB的id , 这里我使用的是MongoDB自带的Object ID , 实际在查询的过程中仍然使用我们业务中的ChartId , 好处是这样用户生成图表会更加的方便

如果使用chartId作为主键 , 对于一些生成失败的图表就会占用主键 , 会大大增加编码的复杂性

只需要添加一个version字段用来标记即可


关于数据一致性 , 做法是在生成图表成功的时候进行数据的同步 , 这样也十分契合我们进行数据隔离存储的目的 , 也就是说我们的MySQL中不再存储图表生成的结果


反向压力与策略模式


反向压力的思想鱼总在直播的过程中已经介绍过了, 这里不再重复

详细内容可以查看 https://blog.csdn.net/weixin_41701290/article/details/119994997

使用的是java.lang.management包下的工具类

这里给出代码

 public class ServerMetricsUtil {
     private static OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class);
     private static MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
 ​
     private static ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
 ​
     /**
      * 获取当前服务器CPU使用占比
      *
      * @return CPU usage percentage.
      */
     public static double getCpuUsagePercentage() {
         return osBean.getProcessCpuLoad() * 100; // Convert to percentage
    }
 ​
     /**
      * 获取当前服务器内存使用占比
      *
      * @return Memory usage percentage.
      */
     public static double getMemoryUsagePercentage() {
         long usedMemory = memoryBean.getHeapMemoryUsage().getUsed();
         long maxMemory = memoryBean.getHeapMemoryUsage().getMax();
 ​
         return ((double) usedMemory / maxMemory) * 100; // Convert to percentage
    }
 ​
     /**
      * 判断当前使用同步还是异步进行服务
      * based on CPU and memory usage.
      *
      * @return true for synchronous, false for asynchronous.
      */
     public static boolean shouldProvideSync() {
         double cpuUsagePercentage = getCpuUsagePercentage();
         double memoryUsagePercentage = getMemoryUsagePercentage();
 ​
         // Threshold values for CPU and memory usage
         double cpuThreshold = 70.0; // Example threshold value
         double memoryThreshold = 80.0; // Example threshold value
 ​
         if (cpuUsagePercentage < cpuThreshold && memoryUsagePercentage < memoryThreshold) {
             return true; // Provide service synchronously
        } else {
             return false; // Provide service asynchronously
        }
    }
 ​
     public static ServerLoadInfo getLoadInfo() {
         double cpuUsagePercentage = getCpuUsagePercentage();
         double memoryUsagePercentage = getMemoryUsagePercentage();
         return new ServerLoadInfo(cpuUsagePercentage,memoryUsagePercentage);
    }
 }

这里我的实现并不好 , 每次服务都需要去查询负载 , 并且这个方法执行很慢 , 并且仅仅是通过CPU以及内存占用来进行负载判断

更好的应该是结合更多的参数, 比如磁盘I/O 网络I/O等

如果不熟悉策略模式 , 建议浏览 https://www.runoob.com/design-pattern/strategy-pattern.html

简单来讲就是通过一个接口统一方法的各项信息(参数 , 名称, 返回值等) , 然后我们定义不同的策略去实现接口中的执行方法

由于在进行图表生成的过程中会使用到大量的Spring控制的Bean , 于是我把所有的策略实现类都交给了Spring管理

通过@Compoennet(value="xxxx") 来指明Bean的名称 , 然后在 策略选择器的代码中通过注入Map<String, Strategy> 来获取具体的执行策略

通过枚举类枚举了策略的Bean名称 , 便于代码维护

 @Component
 public class StrategySelector {
 ​
     /**
      * Spring会自动将strategy接口的实现类注入到这个Map中,key为bean id,value值则为对应的策略实现类
      */
     @Resource
     Map<String, GenChartStrategy> strategyMap;
 ​
     /**
      * 选择对应的生成图表执行策略
      *
      * @param info 服务器当前负载信息
      * @return {@link GenChartStrategy}
      */
     public GenChartStrategy selectStrategy(ServerLoadInfo info) {
         if (info.isVeryHighLoad()) {
             return strategyMap.get(GenChartStrategyEnum.GEN_REJECT.getValue());
        } else if (info.isHighLoad()) {
             return strategyMap.get(GenChartStrategyEnum.GEN_MQ.getValue());
        } else if (info.isMediumLoad()) {
             return strategyMap.get(GenChartStrategyEnum.GEN_THREAD_POOL.getValue());
        } else {
             return strategyMap.get(GenChartStrategyEnum.GEN_SYNC.getValue());
        }
    }
 ​
 }

那么具体的执行策略也就是原本我们生成图表的方式

  1. 同步
  2. 线程池异步
  3. RabbitMQ异步

这里我新加了一条 拒绝策略 , 只会在服务器负载特别高的时候去执行


图表结果压缩


我们在开发的过程中大多经常与JSON打交道, 那么常用的JSON网站大家应该都有印象

图表生成的JSON数据中是有很多的制表符以及空格的 , 因此把这部分的空间省去可以极大地提高我们的空间利用效率

原本想着找个开源库直接压缩, 但是在测试的过程中发现 , 由于Java语言本身的原因 , JSON中字段的双引号会被吞掉

举个例子

 {
   "title": {
     "text": "资源消耗情况",
     "subtext": "数据来源:数据库"
  },
 }

在执行了压缩方法之后 , 就会变成下面这个样子

 {title: {text: 资源消耗情况,subtext: 数据来源:数据库},}

压缩确实是压缩了, 但是前端已经无法解析这段JSON了 , 于是自己写了个正则替换 , 也能达到目的

关键在于替换掉 制表符以及大量的空格 换行符

     public static String compressJson(String data) {
         data = data.replaceAll("\t+", "");
         data = data.replaceAll(" +", "");
         data = data.replaceAll("\n+", "");
         return data;
    }

效果

 {
   "title": {
     "text": "资源消耗情况",
     "subtext": "数据来源:数据库"
  },
   "tooltip": {
     "trigger": "axis",
     "axisPointer": {
       "type": "shadow"
    }
  },
   "legend": {
     "data": ["2020年", "2019年", "2018年"]
  },
   "grid": {
     "left": "3%",
     "right": "4%",
     "bottom": "3%",
     "containLabel": true
  },
   "xAxis": {
     "type": "value",
     "boundaryGap": [0, 0.01]
  },
   "yAxis": {
     "type": "category",
     "data": ["平均每天能源消费量(万吨标准煤)", "平均每天煤炭消费量(万吨)", "平均每天焦炭消费量(万吨)", "平均每天原油消费量(万吨)"]
  },
   "series": [
    {
       "name": "2020年",
       "type": "bar",
       "label": {
         "show": true,
         "position": "inside"
      },
       "emphasis": {
         "focus": "series"
      },
       "data": [1361.5, 1106.2, 132, 189.8]
    },
    {
       "name": "2019年",
       "type": "bar",
       "label": {
         "show": true,
         "position": "inside"
      },
       "emphasis": {
         "focus": "series"
      },
       "data": [1335.6, 1101.1, 127.2, 184.3]
    },
    {
       "name": "2018年",
       "type": "bar",
       "label": {
         "show": true,
         "position": "inside"
      },
       "emphasis": {
         "focus": "series"
      },
       "data": [1292.9, 1088.9, 119.8, 172.6]
    }
  ]
 }

结果

 {"title":{"text":"资源消耗情况","subtext":"2020-2018"},"tooltip":{"trigger":"axis","axisPointer":{"type":"shadow"}},"legend":{"data":["平均每天能源消费量(万吨标准煤)","平均每天煤炭消费量(万吨)","平均每天焦炭消费量(万吨)","平均每天原油消费量(万吨)"]},"toolbox":{"show":true,"orient":"vertical","left":"right","top":"center","feature":{"mark":{"show":true},"dataView":{"show":true,"readOnly":false},"magicType":{"show":true,"type":["line","bar","stack","tiled"]},"restore":{"show":true},"saveAsImage":{"show":true}}},"xAxis":{"type":"category","data":["2020年","2019年","2018年"]},"yAxis":{"type":"value","name":"消费量(万吨)"},"series":[{"name":"平均每天能源消费量(万吨标准煤)","type":"bar","stack":"总量","data":[1361.5,1335.6,1292.9]},{"name":"平均每天煤炭消费量(万吨)","type":"bar","stack":"总量","data":[1106.2,1101.1,1088.9]},{"name":"平均每天焦炭消费量(万吨)","type":"bar","stack":"总量","data":[132,127.2,119.8]},{"name":"平均每天原油消费量(万吨)","type":"bar","stack":"总量","data":[189.8,184.3,172.6]}]}

这段数据中空间占用从 2.66kb 减小到了1.67kb , 效果还是十分可观的

其他的几个扩展点更多的是偏向于通用的一些代码 , 这里由于篇幅原因不再详细介绍了 , 有疑问的球友可以在评论区提出。


效果


前端很丑 , 轻喷QAQ





这里个人中心右边的板块还需要修改







关于项目部署


后端这里配置了workflow的自动化Docker镜像部署 , 同时推送到阿里云的私有镜像仓库

这里我专门编写了两篇文章来详细介绍部署的流程以及Github Actions的配置过程, 欢迎感兴趣的小伙伴前往阅读:

  1. GithubAction与阿里云镜像仓库自动化实现Docker镜像构建与推送 | dhx_'blog
  2. Docker部署Springboot+React项目 | dhx_'blog

如果你并不熟悉Docker , 那么建议你先阅读:

  1. docker常用容器部署命令总结 | dhx_'blog


结尾


目前项目还有一些遗留的问题没有解决

  1. 前端部署到Nginx之后在填写生成图表的表单的时候上传文件显示错误(因为Nginx默认是禁止通过POST来访问静态资源的) , 不过在实际的测试过程中我发现后端是可以接收到文件的 (奇怪的bug)
  2. 目前还没有统计用户生成图表的调用结果等数据(我的想法是通过统计这个去实时的显示在个人中心页面中 , 使得可以更加直观的看到调用的结果)
  3. 前端有很多展示上的问题 : 比如部分页面没有loading , 以及页面展示原始数据的方式并不一致
  4. 用户无法修改原始数据
  5. 虽然图表引入了版本号, 但是前端在展示的时候还是有问题 , Spring-data-MongoDB分页查询返回了全部的元素数量
  6. 后端通过java.util.managment包来获取服务器的负载 , 在测试的过程中发现获取负载数据十分消耗时间
  7. 执行拒绝策略没有对之后的图表进行处理(这里的想法是存入到Redis集合中 , 通过定时任务去再次生成)

这上面的一些问题对于很多大佬来说应该非常容易 , 在这里也非常希望能够得到大佬们的帮助T_T


最后!!!

线上地址 : http://bi.dhx.icu

测试用户:

邮箱 testuser@163.com

密码 adorabled4


项目地址 :

  1. 后端 : https://github.com/adorabled4/hxBI
  2. 前端: https://github.com/adorabled4/hxBI-frontend

觉得做的还凑合的球友可以给俺点一个Star (跪谢.jpg)



0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
adorabled4
下载 APP