您的当前位置:首页1. 搭个框架 | 重构ActiveMQ Web Console

1. 搭个框架 | 重构ActiveMQ Web Console

2024-12-14 来源:哗拓教育
框架

最近在自学Springboot,由于其轻量级的web开发特性,让我萌生了重新开始构建ActiveMQ Web Console的想法。记得去年尝试用nodejs+vue构建一个获取ActiveMQ监控数据的页面,但是中途搁置了,原因有很多,一方面是水平能力的不足,另一方面是nodejs用js让我一直觉得不舒服,毕竟我的主要语言还是java。

springboot用起来顺手多了,随着java水平的提升,这次也摆脱了上次用jolokia接口的约束,还是通过JMX接口来获取数据,大致架构就是通过一个webConsole来连接各个AMQ,获取监控数据,并支持集群能力计算。

大致架构是这样的

这样做的好处主要有几点:

  1. 摆脱需要登录多个ActivmeMQ页面的繁琐操作,一般如果需要通过页面操作集群所有的服务器,需要在web中同时开启N个标签,而如果要清理一个队列,还需要一个个页面进行操作。
  2. ActiveMQ不能支持集群的计算,在完全图的架构下,同一个队列的数据需要将所有ActiveMQ的数据进行一次相加统计并去除集群间转发的部分,无法从单个ActiveMQ上获取这个数据。
  3. 最主要的,我想找个东西练练手……

架构及技术要点

由于自己也是新手,所以有不少问题需要解决,对于一个网站来说,无非是前后端的设计和互动,总结起来就是以下的问题

  1. 前端要展示什么

前端分成两个模块,一个模块展示单个broker状态,另一个计算展示集群状态。

  1. 后端如何快速获取数据

后端与要展示状态的AMQ保持JMX端口的长连接,快速获取数据。连接设置超时机制,超时主动关闭。同时只能与单个集群的所有Broker建立连接,如果跳转到另一个集群则主动断开与上一个集群的所有连接。

  1. 如何保存数据

数据保存在内存中,每次前端调用接口时重新获取数据。所以这个网站只是对当前状态的展示,不涉及对历史数据的趋势视图展示。

  1. 前后端如何交互

前后端通过get,post等方式向后端请求数据,数据为json格式。

技术栈选择

后端毋庸置疑是springboot,前端原来想使用bootstrap,但是前几日看到了layui感觉弹窗很方便,所以这次就选择用layui来做吧。前端使用thymeleaf,主要是用来做router,js使用vue,主要是用vue-resource来替代jquery的ajax调用,通过vue-resource可以不用不停刷页面,只需要更新数据即可。数据的计算通过java写算法实现,图表使用echarts。

搭个框架

少说废话上代码吧。

Controller

package com.springdemo.controller;
import com.springdemo.entity.RespCode;
import com.springdemo.entity.RespEntity;
import com.springdemo.mqinfo.Broker;
import com.springdemo.mqinfo.BrokerInfo;
import org.springframework.boot.SpringApplication;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import javax.management.*;
import java.io.IOException;
import java.util.Date;

/**
 * @author Cadmean
 */
@Controller
public class AppController {
    Broker broker=null;

    @RequestMapping("/")
    public String indexMain(){
        return "index";
    }

    @GetMapping("/connectBroker.action")
    public String connectBroker(@RequestParam(value = "addr", required = false) String addr){
        try {
            if (this.broker!=null) {
                if (!this.broker.getAddr().equalsIgnoreCase(addr)) {
                    this.broker.closeConnection();
                    this.broker = new Broker(addr);
                }
            } else {
                this.broker=new Broker(addr);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "broker";
    }

    @RequestMapping(value = "/brokerInfo.json",method=RequestMethod.GET)
    @ResponseBody
    public RespEntity<BrokerInfo> getBrokerInfo() throws MalformedObjectNameException, InstanceNotFoundException, IOException, ReflectionException, AttributeNotFoundException, MBeanException {
        return new RespEntity<BrokerInfo>(RespCode.SUCCESS,this.broker.getBrokerInfo());
    }

}

设计点相关:

  • 有一个index.html主页,用来输入一个IP,跳转到broker.html页面,展示单个broker状态
  • connectBroker.action用于创建一个broker的JMX连接
  • brokerInfo.json用于获取broker的基本信息,json用一个RespEntity来封装,主要是封装上请求是否成功的信息。

数据

package com.springdemo.mqinfo;

/**
 * @author Cadmean
 */
public class BrokerInfo {
    private String brokerName;
    private String addr;
    private String brokerId;
    private String uptime;
    private int connection;
    private long memoryLimit;
    private long storeLimit;

    @Override
    public String toString() {
        return "BrokerInfo{" +
                "brokerName='" + brokerName + '\'' +
                ", addr='" + addr + '\'' +
                ", brokerId='" + brokerId + '\'' +
                ", uptime='" + uptime + '\'' +
                ", connection=" + connection +
                ", memoryLimit=" + memoryLimit +
                ", storeLimit=" + storeLimit +
                '}';
    }

    public String getBrokerName() {
        return brokerName;
    }

    public void setBrokerName(String brokerName) {
        this.brokerName = brokerName;
    }

    public String getAddr() {
        return addr;
    }

    public void setAddr(String addr) {
        this.addr = addr;
    }

    public String getBrokerId() {
        return brokerId;
    }

    public void setBrokerId(String brokerId) {
        this.brokerId = brokerId;
    }

    public int getConnection() {
        return connection;
    }

    public void setConnection(int connection) {
        this.connection = connection;
    }

    public long getMemoryLimit() {
        return memoryLimit;
    }

    public void setMemoryLimit(long memoryLimit) {
        this.memoryLimit = memoryLimit;
    }

    public String getUptime() {
        return uptime;
    }

    public void setUptime(String uptime) {
        this.uptime = uptime;
    }

    public BrokerInfo(String addr) {
        this.addr=addr;
    }

    public long getStoreLimit() {
        return storeLimit;
    }

    public void setStoreLimit(long storeLimit) {
        this.storeLimit = storeLimit;
    }
}

package com.springdemo.mqinfo;

import javax.management.*;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.IOException;

/**
 * @author Cadmean
 */
public class Broker {
    private String url;
    private JMXConnector connector;
    private MBeanServerConnection mbsc;
    private BrokerInfo brokerInfo;
    private String addr;

    public Broker(String addr) throws IOException {
        this.addr=addr;
        this.url="service:jmx:rmi:///jndi/rmi://"+addr+":1099/jmxrmi";
        this.brokerInfo=new BrokerInfo(this.addr);
        createConnector();
    }
    
    public String getAddr() {
        return addr;
    }

    private void createConnector() throws IOException {
        JMXServiceURL url = new JMXServiceURL(this.url);
        connector = JMXConnectorFactory.connect(url, null);
        connector.connect();
        mbsc = connector.getMBeanServerConnection();
    }

    public void closeConnection() throws IOException {
        this.connector.close();
    }
    public BrokerInfo getBrokerInfo() throws MalformedObjectNameException, AttributeNotFoundException, MBeanException, ReflectionException, InstanceNotFoundException, IOException {
        String objectName="org.apache.activemq:brokerName="+this.addr+",type=Broker";
        ObjectName name = new ObjectName(objectName);
        this.brokerInfo.setBrokerName((String) mbsc.getAttribute(name,"BrokerName"));
        this.brokerInfo.setBrokerId((String) mbsc.getAttribute(name,"BrokerId"));
        this.brokerInfo.setConnection((Integer) mbsc.getAttribute(name,"CurrentConnectionsCount"));
        this.brokerInfo.setMemoryLimit((Long) mbsc.getAttribute(name,"MemoryLimit"));
        this.brokerInfo.setStoreLimit((Long) mbsc.getAttribute(name,"StoreLimit"));
        this.brokerInfo.setUptime((String) mbsc.getAttribute(name,"Uptime"));
        return brokerInfo;
    }
}

Broker类用于存放Info信息和一些数据获取的操作,BrokerInfo用于保存Broker基本数据,相对应的后续还可以扩展出Queue、Topic、Connection等等数据信息类。

封装请求

package com.springdemo.entity;

/**
 * @author Cadmean
 */

public enum RespCode {

    SUCCESS(0, "ok"),
    WARN(-1, "fail");

    private int code;
    private String msg;

    RespCode(int code, String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }
    public String getMsg() {
        return msg;
    }
}
package com.springdemo.entity;

/**
 * @author Cadmean
 */
public class RespEntity<R> {
    private int code;
    private String msg;
    private R data;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public R getData() {
        return data;
    }

    public void setData(R data) {
        this.data = data;
    }

    public RespEntity(RespCode respCode) {
        this.code = respCode.getCode();
        this.msg = respCode.getMsg();
    }

    public RespEntity(RespCode respCode, R data) {
        this(respCode);
        this.data = data;
    }

    @Override
    public String toString() {
        return "RespEntity{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

使用RespEntity,并且加了个泛型来作为数据往外传输的通道,RespCode是用来存放返回码和具体返回说明的。在RespEntity中封装上这个就可以在前端得到返回的数据是否正常了。

前端

<!DOCTYPE HTML>
<html   >
<head>
    <meta content="text/html;charset=UTF-8"/>
    <meta name="viewport" content="width=device-width,initial-scale=1"/>
    <link rel="stylesheet" th:href="@{/layui/css/layui.css}">
    <script src="/webjars/vue/2.5.16/vue.min.js"></script>
    <script 
</head>
<body>
<div id="app" class="layui-container" style="margin-top:1%">
    <ul class="layui-nav" lay-filter="">
        <li class="layui-nav-item"><a href="">ActiveMQ Console</a></li>
    </ul>
    <table class="layui-table layui-anim layui-anim-scale" lay-skin="nob">
        <tr>
            <td>UpTime</td>
            <td id="times" v-on:click="get()">{{ msg.uptime }}</td>
        </tr>
        <tr>
            <td>BrokerName</td>
            <td>{{ msg.brokerName }}</td>
        </tr>
        <tr>
            <td>BrokerId</td>
            <td>{{ msg.brokerId }}</td>
        </tr>
        <tr>
            <td>Connections</td>
            <td>{{ msg.connection }}</td>
        </tr>
        <tr>
            <td>MemoryLimit</td>
            <td>{{ Math.floor(msg.memoryLimit/1024/1024) }} MB</td>
        </tr>
        <tr>
            <td>StoreLimit</td>
            <td>{{ Math.floor(msg.storeLimit/1024/1024/1024) }} GB</td>
        </tr>
    </table>
</div>
<script th:inline="javascript">
    var vm = new Vue({
        el:'#app',
        data:{
            msg:[],
        },
        methods:{
            get:function(){
                //发送get请求
                this.$http.get('/brokerInfo').then(function(res){
                        console.log(res.body.data);
                        this.$set(this,'msg',res.body.data);
                    },function(){
                        console.log('请求失败处理');
                });
            }
        }
    });
    window.onload=function(){
        vm.get();
    }
</script>
</body>
</html>

用到了layui的table,vue和vue-resource来进行数据渲染。

公司内部网络限制,主要还是在公司内部开发,初步成果差不多是这样。


显示全文