UMC通过控制k8s集群中的容器控制公司产品,在进行控制之前要能监视产品的动态。在实例监视中原有的运行日志功能只能显示一个容器的运行日志,而产品在k8s集群中可能拥有多个容器,会产生日志信息显示不完整且不方便对比的问题,需要对运行日志功能进行改造升级。从前期思路和知识要点的确认到编写设计再到功能实现和功能完善,本篇文档将对整个过程进行详细介绍。

1.前期准备

在进行功能改造之前,我们首先要确定设计运行日志的改造思路;其次思路确定后需要补充知识,对需要用到的知识进行大体了解;最后撰写设计,明确如何改造功能。 

1.1总体思路 

修改功能的大致过程就是总体思路,也就是说,在改造之前要想到修改功能需要使用哪些以前功能中就有的内容,根据功能中已有内容思考还需要添加哪些没有的内容,最后将这些想法清晰的分步写出来。 

1.点击实例监视的产品节点,利用环境ID和产品ID获取当前环境下当前产品的所有容器ID

运行日志功能改造心得_html

2.利用容器的心跳时间查看是否存在该容器,并判断出所有容器ID中正在运行的容器ID。

3.如同一页面中显示多个日志,需要根据容器ID的数量将页面划分区域。

4.利用WebSocket获取容器的实时信息,并显示在不同区域中。

1.2知识补充 

根据总体思路学习改造功能的过程中可能会使用的知识点如下:

1.使用BootStrap中网格系统划分区域,网格系统可以根据class自动划分,最多划分12列

运行日志功能改造心得_运行日志_02

2.WebSocket 是 HTML5 提供的一种在单个 TCP 连接上进行全双工通讯的协议,使用WebSocket获取日志信息方法如下:

<%
if (pageBean.getStringValue("wsReqURL") != null){
%>
var websocket = null;
if('WebSocket' in window){
websocket = new WebSocket("<%=pageBean.getStringValue("wsReqURL")%>");
}
else{
alert('Not support websocket')
}

websocket.onerror = function(){
setMessageInnerHTML("error");
};

websocket.onopen = function(event){
setMessageInnerHTML("connected!");
}

websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}

websocket.onclose = function(){
setMessageInnerHTML("closed");
}

window.onbeforeunload = function(){
websocket.close();
}

function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
$('body').parent().scrollTop($('body')[0].scrollHeight);
}
<%}%>

1.3撰写设计

根据总体思路和补充的知识撰写设计,设计是功能改造的根本和方向。设计中至少要包含:实现思路、技术要点、实现步骤等。 

实现思路是总体思路的更加具体化,要比总体思路更加细化。

技术要点是知识补充的扩展,不仅扩展知识深度,还扩充知识的广度,罗列出可能使用到的知识点。

实现步骤包括页面设计和代码设计。

页面设计要至少包括页面中的重要展示信息和功能,如图所示:

运行日志功能改造心得_运行日志_03

代码设计中要包含原有代码,以及在原有代码上的改造思路。原代码如下:

public ViewRenderer prepareDisplay(DataParam param){
String instanceId = param.get("containerId");
if (StringUtil.isNotNullNotEmpty(instanceId)) {
CommandManager commandManager = (CommandManager)this.lookupService("commandManager");
ViewLogCommand viewLogCommand = new ViewLogCommand();
viewLogCommand.setActionType(ViewLogCommand.StartActionType);
CmdRequest cmdRequest = commandManager.buildCmdRequest(viewLogCommand);
commandManager.publishToCommand(instanceId, cmdRequest);

String serverName = this.getRequest().getServerName();
String scheme = this.getRequest().getScheme();
int port = this.getRequest().getServerPort();
String portSyntax = this.buildPortSyntax(port);

String wsReqURL = null;
if ("http".equals(scheme)) {
wsReqURL = "ws://"+serverName+portSyntax+"/UMC/monitor/" + instanceId + "/"+ MonitorResponse.ObjectTypes.RuntimeLog;
}
else {
wsReqURL = "wss://"+serverName+portSyntax+"/UMC/monitor/" + instanceId + "/"+ MonitorResponse.ObjectTypes.RuntimeLog;
}
this.setAttribute("wsReqURL", wsReqURL);
this.setAttribute("containerId", instanceId);
this.setAttribute("GP_CODE", param.getString("GP_CODE"));

ContainerManage containerManage = this.lookupService(ContainerManage.class);
DataRow holderConInfoRecord = containerManage.getHolderConInfoRecord(instanceId);
this.setAttributes(holderConInfoRecord);
}
return new LocalRenderer(getPage());
}

原代码中获取的是单个容器ID,并且构造WebSocket所需的wsReqURL,我们只需要获取该产品的多个容器ID,并构造多个wsReqURL,使得一一对应传输到前端。

2.功能实现 

功能实现是根据已有设计进行功能改造,在改造过程中,首先要确定对象,确定容器是获取当前产品节点下所有容器ID;其次划分区域,我们使用网格系统将页面划分成多个小区域,每个小区域代表一个容器;最后通过WebSockey使用wsReqURL动态获取各个容器的日志。 

2.1确定容器

如果页面中同时显示多个容器的日志,则需要获取多个容器ID。 

获取当前环境下所有容器的信息
List<DataRow> containerRows = containerManage.findContainerRecords("", "", envId, "");
区分不同产品的容器信息
if ((gpCode.equals(productCode))) {
}
使用心跳时间区分运行中的容器
HeartBeatResult heartBeatResult = (HeartBeatResult)commandManager.retrieveCmdResult(HeartBeatCommand.class.getSimpleName(), containerId);
if (heartBeatResult != null) {
long heartBeatTime = heartBeatResult.getTimestamp();
containerRow.put("CON_HEART_BEAT_TIME", DateUtil.getDateByType(DateUtil.YYMMDDHHMISS_HORIZONTAL, new Date(heartBeatTime)));
} else {
if ("stop".equals(containerRow.getString("CON_STATE"))) {
containerRow.put("CON_STATE", "unknown");
}
}

根据获取的容器ID拼接wsReqURL,一个容器ID对应一条wdReqURL。

wsReqURL = "ws://"+serverName+portSyntax+"/UMC/monitor/" + instanceId + "/"+ MonitorResponse.ObjectTypes.RuntimeLog;

2.2区域划分

由于容器数量是动态的,每个产品下的容器个数可能都不相同,所以在区域划分时不能使用静态的,需要根据容器数量创建不同数量的div。

根据容器日志的数量循环创建div,以区分不同容器
<%for(int i=0;i<gpContainerLogs.size();i=i+2){
if(i+1 >= gpContainerLogs.size()){
break;
}
%>
var childDiv1 = $('<div id="child<%=i%>" class="col-md-6" style="overflow-y:scroll;height: '+bodyHeight+'px;padding: 0px;border-bottom-style: solid;border-bottom-width:2px;border-right-style: solid;border-right-width:1px;border-top-style: solid;border-top-width:2px;border-left-style: solid;border-left-width:2px"></div>');
parentDiv.append(childDiv1);
}

2.3动态获取

在划分好区域后,需要根据容器ID区分不同WebSocket,WebSocket获取对应容器的日志信息。

<%
if (gpContainerLogs.get(i+1).getString("wsReqURL") != null){
%>
var websocket<%=i+1%> = null;
if('WebSocket' in window){
websocket<%=i+1%> = new WebSocket("<%=gpContainerLogs.get(i+1).getString("wsReqURL")%>");
}
else{
alert('Not support websocket')
}

websocket<%=i+1%>.onerror = function(){
setMessageInnerHTML<%=i+1%>("error");
};

websocket<%=i+1%>.onopen = function(event){
setMessageInnerHTML<%=i+1%>("connected!");
}

websocket<%=i+1%>.onmessage = function(event){
setMessageInnerHTML<%=i+1%>(event.data);
}

websocket<%=i+1%>.onclose = function(){
setMessageInnerHTML<%=i+1%>("closed");
}

window.onbeforeunload = function(){
websocket<%=i+1%>.close();
}

function setMessageInnerHTML<%=i+1%>(innerHTML){
document.getElementById('son<%=i+1%>').innerHTML += innerHTML + '<br/>';
$('#son<%=i+1%>').scrollTop($('#son<%=i+1%>').scrollHeight);
}
<%}%>

3.功能完善

在改造功能后,可能会有许多bug,需要多测试,发现其中的功能问题并完善。

3.1功能问题 

测试时发现的问题如下: 

1.当容器个数为奇数时,jsp页面会报错,错误描述:index超出界限异常。

2.点击清空按钮,整个页面都清空了。

3.2补充完善 

当容器个数为奇数时,jsp页面报错。这个问题是因为判断奇数和偶数时,js中的判断无法限制js中的java代码,即使容器个数是奇数,也会运行偶数情况下的java代码,导致超限。在js中的java代码添加奇数和偶数判断即可。

点击清空按钮,整个页面都清空了。这个问题是因为在使用text(“”)清空页面内容时将动态创建的div也清空了。需要设定好清空的元素范围,如果将动态创建好的元素清空了,就使得页面中创建WebSocket也被清空。

4.心得体会 

在改造运行日志的过程中,在知识方面收获了WebSocket、网格系统和动态创建页面元素的相关知识;在意识方面了解到做事要反复确认、反复修改,争取做到更好。 

4.1知识收获 

知识方面收获了很多,了解到WebSocket的简单使用方法和原理,以及WebSocket相对于轮询和Ajax在实时获取信息方面的优势;学会使用BootStrap的网格系统对div分区,在js和html方面多了一份积累。

4.2意识方法 

意识方面,深切体会到设计在功能开发中的重要性,设计是功能开发的方向,无论做什么工作都应该先制定一个设计,避免在工作过程中走弯路;在做一项工作时要多和领导交互,避免走错方向,在时间允许的情况下多做测试和完善。

4.3工作总结 

运行日志功能改造是我第一次从设计开始做的工作任务,在这次工作中收获了许多,有很多内容是我以前没有思考过的。在编写设计时就发生了准备的技术要点不充足,做到一半再去查找资料的问题,在之后的工作中还是要多思考,即使由别人写设计,也要思考设计中内容是否正确,同时在考虑问题时要发散思维,每次发散一点点到最后考虑问题时就会很全面。

本篇文档对于改造功能做简单阐述,文档的总体思路不仅适用于功能开发还适用于其他工作,不过最重要的还是在做工作量较大的工作时要先做设计,避免自己在工作过程中跑偏。