• 基于 JSch 实现服务的自定义监控解决方案


    一、基于 JSch 实现服务的自定义监控

    JSchSSH2 的一个纯 Java 实现。它允许你连接到一个 sshd 服务器,使用端口转发X11转发,文件传输等等。你可以将它的功能集成到你自己的 程序中。

    既然可以通过 SSH 连接到服务器,那就可以执行一些 命令 ,例如我们要监控一个服务是否正在运行,或者服务有无僵死,可以通过查看服务进程是否存在,访问接口是否正常来判断,如果不正常,我们可以通过 JSch 连接到该服务器中,执行重启的脚本。

    现在对于新的项目相信大家都已经放在 k8s 中部署了,在 k8s 中有完善的服务检测机制,可以实现服务宕机的重起,服务僵死重启等功能,那为什么博主还要写这篇博客呢,相信大家应该都遇到过一些比较老的项目吧,由于项目比较老不容易打包成镜像放在 k8s 中运行,但是这种项目还依然运行着一些比较重要的功能,对其可用性还是需要保障。或者公司中有项目并不是放在容器中运行的,一时也不想迁移,针对这种情况下就可以参考本篇文章的内容。

    下面我们将实现一个简单的场景,服务器中运行着一个普通的 java 项目,我们需要保障其运行的可用性,如果出现宕机需要及时的进行启动。

    这里博主就有两种实现方案了:

    第一种是我们在另一台服务器中准备一个监控服务,该服务通过JSch 连接到服务器中,定时查看是否存在 java 项目进程,如果进程不存在则进行重启指令。同时也可以通过指令监控服务的内存、磁盘等使用大小。

    第二种就是借助 zookeeper 的事件通知机制,第一种方式通过定时的方式,势必会有一定的时差。如果需要服务一宕机,监控服务立马可以检测到的话,可以借助 zookeeper 的事件通知机制,被检测的项目在运行的时候去 zookeeper 建立一个属于该服务的临时节点,如果服务宕机则会因为 session 连接终断,临时节点自动移除,此时监控该节点的 session 会立马收到通知。但这种方式需要被检测服务操作 zookeeper 有一定的侵入性,另外有可能因为网络的震荡,导致连接中断,因此在收到删除事件通知时,建议再去服务中查看下进程是否真的宕机。

    以上两种方案都是简单的说了下思路,具体实施还是有很多注意点,本篇文章就基于第一种方式实现自定义监控。

    环境准备:

    首先准备一个 java 项目,这里为了方便我准备了一个 SpringBoot 项目,并运行在了服务器中:
    在这里插入图片描述

    下面开始监控服务的搭建:

    二、监控服务搭建

    首先新建一个 SpringBoot 项目,在 pom 中引入 JSch 的依赖:

    
        com.jcraft
        jsch
        0.1.55
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    创建一个 SSHVO 存放主机信息:

    @Data
    public class SSHVO {
        private String host;
        private String userName;
        private String password;
        private Integer port;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    下面便是本篇文章的主要代码, 通过 JSch 进行远程主机的连接获得连接 Session ,并通过 Session 执行命令:

    @Slf4j
    @Data
    public class SSHUtil {
    
        private SSHVO sshVo;
        private Session session;
    
        public SSHUtil(SSHVO sshVo) {
            this.sshVo = sshVo;
        }
    
        public void connect() throws Exception {
            ValidResult validResult = ValidationUtil.fastFailValidate(sshVo);
            if (validResult.isHasError()) {
                throw new Exception(validResult.getErrMessage());
            }
            JSch jsch = new JSch();
            session = jsch.getSession(sshVo.getUserName(), sshVo.getHost(), (sshVo.getPort() == null || sshVo.getPort() == 0) ? 22 : sshVo.getPort());
            session.setPassword(sshVo.getPassword());
            java.util.Properties config = new java.util.Properties();
            config.put("StrictHostKeyChecking", "no");
            session.setConfig(config);
            session.setTimeout(3000);
            session.connect();
            log.info("SSH 连接成功 > {}", sshVo.toString());
        }
    
        public boolean isConnect() {
            if (session == null) {
                return false;
            }
            return session.isConnected();
        }
    
        public String command(String command) throws Exception {
            if (session == null){
                connect();
            }
            log.info("SSH 执行命令 > {} , {} ", sshVo.toString(), command);
            Channel channel = null;
            try {
                channel = session.openChannel("exec");
                ((ChannelExec) channel).setCommand(command);
                channel.setInputStream(null);
                ((ChannelExec) channel).setErrStream(System.err);
                channel.connect();
                InputStream in = channel.getInputStream();
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8")))) {
                    StringBuilder stringBuffer = new StringBuilder();
                    String buf = "";
                    while ((buf = reader.readLine()) != null) {
                        stringBuffer.append(buf);
                    }
                    log.info("SSH 执行命令 > {} ,返回 >>  {} ", sshVo.toString(), stringBuffer.toString());
                    return stringBuffer.toString();
                }
            } catch (Exception e) {
                throw e;
            } finally {
                if (channel != null) {
                    channel.disconnect();
                }
            }
        }
    
        public boolean close() {
            if (session == null) {
                return false;
            }
            session.disconnect();
            return true;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    上面实现了对远程主机的连接和执行命令操作,如果每次都创建一个新的 Session 肯定会造成资源浪费,下面我们做一个对 Session 缓存的操作,这里使用了 guava 中的缓存 Api,需要引入 guava 的依赖:

     
         com.google.guava
         guava
         18.0
     
    
    • 1
    • 2
    • 3
    • 4
    • 5

    下面使用 Cache 缓存连接对象:

    @Slf4j
    @Service
    public class SSHService {
    
        private Cache sshConnectCache = CacheBuilder.newBuilder()
                .maximumSize(2000)
                .expireAfterWrite(2, TimeUnit.DAYS).build();
    
        /**
         * 执行命令
         */
        public String command(SSHVO sshVo, String command) {
            try {
                if (StringUtils.isEmpty(sshVo.getHost())) {
                    throw new Exception("host is null !");
                }
                SSHUtil tool = sshConnectCache.getIfPresent(sshVo.getHost());
                if (tool == null) {
                    tool = new SSHUtil(sshVo);
                    tool.connect();
                    sshConnectCache.put(sshVo.getHost(), tool);
                }
                return tool.command(command);
            } catch (Exception e) {
                log.error("执行SSH 失败!", e);
                return null;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    下面创建一个 定时任务,定时获取进程 ID,如果ID不存在则进行启动:

    @Slf4j
    @Component
    @EnableScheduling
    public class SSHTask {
        @Autowired
        SSHService sshService;
    
        @Scheduled(cron = "0/5 * * * * *")
        public void sshTask() throws InterruptedException {
            SSHVO sshvo = new SSHVO();
            sshvo.setHost("192.168.40.170");
            sshvo.setUserName("root");
            sshvo.setPassword("bxc");
            sshvo.setPort(22);
            String command = "ps aux |grep java-server-0.0.1.jar |grep -v grep | awk '{print $2}'";
            // 执行命令
            String resCommand = sshService.command(sshvo, command);
            log.info("主机:{},指令:{},返回:{}", sshvo.getHost(), command, resCommand);
            if (StringUtils.isEmpty(resCommand)) {
                //重启
                String startCommand = "nohup java -jar /opt/java/java-server-0.0.1.jar > /opt/java/nohup.out 2>&1 &";
                String resstartCommand = sshService.command(sshvo, startCommand);
                log.info("主机:{},指令:{},返回:{}", sshvo.getHost(), startCommand, resstartCommand);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    上面每5秒进行检测一次,启动项目查看打印日志:

    在这里插入图片描述
    可以看到打印出服务的进程 ID ,下面我们手动去服务中将该进程杀掉:

    在这里插入图片描述
    下面再观看打印的日志:

    在这里插入图片描述
    可以看到服务已经自动启动起来了,进入主机中查看下进程:

    在这里插入图片描述

    服务已经成功被启动起来。

  • 相关阅读:
    八、Gateway
    分布式系统的发展历程
    微信小程序之授权登录以及授权登录流程讲解
    Nginx 面试题
    APS排产考虑生产动态合并优化
    Linux 系统烧写实操
    ClickHouse用户路径分析原理及实现
    java入坑之注解
    C/C++:双重循环中的break
    王江涛十天搞定考研词汇
  • 原文地址:https://blog.csdn.net/qq_43692950/article/details/126211105