用js写一个zookeeper简易web控制台

使用node.js下的zookeeper客户端访问组件库,写一个 web的控制台,非常简单。比起java的ZooInspector,还有其他一些node下的zk的web控制台,有哪些优势?轻量,部署依赖少,可以单机,可以扔在服务器上多用户访问;依赖少,早起一些node下的zk的控制台,使用了node-zookeeper,这是是一个包装c实现库;这里使用了node-zookeeper-client库,这是一个纯js的zk客户端组件库,更为轻量。

使用express搭建web服务

环境安装,库安装略过;
app.js

const express = require('express')
const bodyParser = require('body-parser')
const api = require('./src/api')

const port = 8123

app = express()
app.use(bodyParser.json({limit: '50mb'}))
app.use(express.static('public'))
app.use('/api', api)


app.listen(port, '127.0.0.1', () => console.log(`Server running: http://localhost:${port}`))

访问zk的API

const express = require('express')
const zookeeper = require('node-zookeeper-client')
const Long = require('long')
const router = express.Router()

let zkConnections = {};

router.post('/zk', async (req, res) => {
    switch(req.body.act){
        case 'connect': {
            let client = await zkClient(req.body.address)
            zkListChildren(client, '/', res)
            break
        }
        case 'list': {
            let client = await zkClient(req.body.address)
            zkListChildren(client, req.body.path, res)
            break
        }
        case 'getData': {
            let client = await zkClient(req.body.address)
            zkGetData(client, req.body.path, res)
            break
        }
        case 'addNode': {
            let client = await zkClient(req.body.address)
            zkAddNode(client, req.body.path, res)
            break
        }
        case 'removeNode': {
            let client = await zkClient(req.body.address)
            zkRemoveNode(client, req.body.path, res)
            break
        }
        case 'setData':
            let client = await zkClient(req.body.address)
            zkSetData(client, req.body.path, req.body.data, res)
            break
        default:
            res.send('')
    }
})

async function zkClient(address){
    let client = zkConnections[address]
    if(client){
        let state = client.getState()
        if(state === zookeeper.State.SYNC_CONNECTED){
            return Promise.resolve(client)
         }else{
             client.close()
             delete zkConnections[address]
             return await zkClient(address)
         }
    }else{
        client = zookeeper.createClient(address)
        zkConnections[address] = client
        console.log('new connetion: %s', address)
        client.connect()
        client.on('state', state => {
            console.log(state)
        })
        return new Promise((resolve, reject) => {
            client.once('connected', ()=>{
                resolve(client)
            })
        })
    }
}

function zkGetData(client, path, res) {
    client.getData(path, (err, data, stat) => {
        if(err){
            console.log('%s', err)
        }
        let s = {}
        stat.specification.forEach(i => {
            s[i.name] = i.type === 'long' ? Long.fromBytes(stat[i.name]).toString() : stat[i.name] 
        });
        res.send({data: (data && data.length > 0) ? data.toString('utf8') : '', stat: s})
    })
}

function zkListChildren(client, path, res) {
    client.getChildren(path, (err, children, stat) => {
        if(err){
            console.log('%s', err)
        }
        res.send(children)
    })
}

function zkRemoveNode(client, path, res) {
    client.remove(path, -1, (err)=>{
        if(err){
            console.log('%s', err)
            res.send('fail')
        }else{
            res.send('success')
        }
    })
}

function zkAddNode(client, path, res){
    client.create(path, null, null, zookeeper.CreateMode.PERSISTENT, (err) =>{
        if(err){
            console.log('%s', err)
            res.send('fail')
        }else{
            res.send('success')
        }
    })
}

function zkSetData(client, path, data, res){
    client.setData(path, Buffer.from(data), -1, (error, stat) => {
        if (error) {
            console.log(error.stack);
            res.send('fail')
        }else{
            res.send('success')
        }
    })
}

module.exports = router

使用Vue.js搭建前端

这里vue使用简单的方式,可以参考不使用Webpack的Vue传统前端使用方式(一)

说说主要的几个点。

使用CDN引入js库

<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js" integrity="sha256-ngFW3UnAN0Tnm76mDuu7uUtYEcG3G5H1+zioJw3t+68=" crossorigin="anonymous"></script>

使用了以下前端组件:

  • boostrap前端css/html框架
  • nanoscroller 滚动条
  • vue 前端mvvm框架
  • axios http请求库

使用js加载js

这个方法可以实现使用js加载js到html中,而且是同步的;

async function loadjs(src) {
  if(src instanceof Array){
    return Promise.all(src.map(i => loadjs(i)))
  }
  let script = document.createElement('script')
  script.setAttribute('type', 'text/javascript')
  script.src = src;
  document.getElementsByTagName('body')[0].appendChild(script)
  return new Promise(resolve => {
    script.onload = () => {
      return resolve('ok')
    }
  })
}

用法:

await loadjs([
  'js/vue_com.js',
  'js/vue_zk.js'
])

vue实现tree组件

Vue.component('tree', {
  template: `
    <ul v-show="show">
      <li v-for="node in nodes">
        <span :class="[node.leaf ? 'leaf' : (node.show ? 'down-arrow' : 'right-arrow'), 'hand']" @click="loadAndToggleExpand(node)"></span>
        <a href="javascript:;" @click="nodeClick($event, node)">{{node.text}}</a>
        <tree :nodes="node.children" :show="node.show" @nodeClick="childNodeClick" @load="childNodeLoad">
      </li>
    </ul>
  `,
  props: ['nodes', 'show'],
  data(){
    return {}
  },
  methods:{
    loadAndToggleExpand(node){
      if(node.show === null){
        this.$emit('load', node)
      }else{
        node.show = !node.show
      }
    },
    nodeClick(e, node){
      this.$emit('nodeClick', node)
    },
    childNodeClick(node){
      this.$emit('nodeClick', node)
    },
    childNodeLoad(node){
      this.$emit('load', node)
    }
  }
  
});

主界面及操作表单

详细代码不贴了,可以直接访问仓库: https://github.com/ccor/zk-browser

主界面主要是左侧树形目录,右侧的数据显示和操作区;

主要实现了经典的程序员的四门功课:增删改查。一般来说,基本够用了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ZooKeeper中,心跳是通过ZooKeeper客户端与ZooKeeper服务器之间的连接保持活动状态的机制。当客户端与服务器之间的连接断开时,ZooKeeper服务器会认为该客户端已经下线。因此,我们可以通过监听客户端与服务器之间的心跳来判断客户端是否在线。 下面是使用Java编一个ZooKeeper心跳监听示例: ```java import org.apache.zookeeper.*; import org.apache.zookeeper.Watcher.Event.*; import org.apache.zookeeper.data.Stat; import java.util.concurrent.TimeUnit; public class ZooKeeperHeartbeatListener implements Watcher, Runnable { private ZooKeeper zooKeeper; private String znodePath; private int sessionTimeout; private volatile boolean isRunning = true; public ZooKeeperHeartbeatListener(String connectString, String znodePath, int sessionTimeout) throws Exception { this.znodePath = znodePath; this.sessionTimeout = sessionTimeout; zooKeeper = new ZooKeeper(connectString, sessionTimeout, this); // 创建一个临时节点,当客户端与服务器之间的连接断开时,该节点会被删除 zooKeeper.create(znodePath, "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); } @Override public void process(WatchedEvent event) { if (event.getType() == EventType.None && event.getState() == KeeperState.SyncConnected) { System.out.println("ZooKeeper session established."); } else if (event.getType() == EventType.NodeDeleted && event.getPath().equals(znodePath)) { System.out.println("Connection lost."); } } @Override public void run() { while (isRunning) { try { TimeUnit.SECONDS.sleep(sessionTimeout / 2); // 检查临时节点是否存在,如果不存在,则表示客户端与服务器之间的连接已经断开 Stat stat = zooKeeper.exists(znodePath, false); if (stat == null) { System.out.println("Connection lost."); } } catch (Exception e) { e.printStackTrace(); } } } public void stop() throws Exception { isRunning = false; zooKeeper.close(); } public static void main(String[] args) throws Exception { ZooKeeperHeartbeatListener listener = new ZooKeeperHeartbeatListener("localhost:2181", "/heartbeat", 5000); new Thread(listener).start(); System.in.read(); listener.stop(); } } ``` 在上面的示例中,我们创建了一个临时节点,当客户端与服务器之间的连接断开时,该节点会被删除。我们通过在ZooKeeper服务器上检查该节点是否存在来判断客户端是否在线。在run方法中,我们每隔sessionTimeout/2秒就检查一次节点是否存在,如果不存在,则表示客户端已经下线。在process方法中,我们监听节点是否被删除,如果被删除,则表示客户端与服务器之间的连接已经断开。当客户端下线时,我们可以在process方法中执行一些自定义的操作,比如重新连接ZooKeeper服务器等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值