您的位置:首页 > 编程语言 > Java开发

长连接与echarts实现动态数据实时展示

2017-07-15 17:46 609 查看

一、需求

项目上提出了一个需求,说是需要做一个简单大气的页面,上方一排方块显示各个市区的当日业务数量,下方是一个柱状图表,动态的显示当日的业务数量。所谓动态就是要实时的显示业务数量,如果有业务的增加,数字会跳动,而且柱状图也会增长。

二、解决方案

1、不太好的方案

按照正常的想法,可以通过异步加载不断的向后台发起请求,对业务数量进行查询,将查询结果返回到前台后刷新数据。但是如果采用这种方式,就会有以下几个问题:
a、间断的持续异步访问后台会对服务器造成压力,占用过多的资源。
b、结合业务情况,数据量并不是每分每秒都在变化。如果在一段时间内业务量并没有变化,前台却做了多次查询请求,那么这些请求相当于是无意义的请求,进而也浪费了服务器资源。
其实这个方案的优点在于,实现方式简单。缺点是会对服务器造成压力,消耗不必要的资源。

2、改进后的方案

经过讨论之后,部门的大神提出这样的方案。在后台定时查询数据库得到业务数量的统计结果,并将结果保存在一个变量里。前台通过ajax向后台轮询请求,保持一个长连接。如果变量中的数据有了变化,则向前台返回结果,前台刷新数据,之后发起下一个长连接请求。这样,对于后台的访问次数会减小,几乎是只有数据变动的时候才会发起新的请求(之所以说是几乎是因为长连接尽量不要真的一直保持下去,所以在业务量没有变化的情况下,也会定时的关闭长连接并发起下一次链接)。我们将查询业务量的访问压力放在后台与数据库之间。而这部分压力对于服务器来说要比前台多次发起请求要小得多。

3、再一次改进?

就算是查询数据库的压力完全放到了后台和数据库之间的交互,但是,还是会存在数据量没有变化的多余查询。如果是在业务办理的同时向后台的变量写入记录,就可以让数据从之前的主动查询变成被动接受,由拉变推,这样可以真正的保证数据是实时变化的。但是问题又来了,我们要改造业务生成的方法,同时要做一个整体的变量以便于获取。但是这样实现的工作量太大,而且这个页面的数据展示只不过是一个简单的功能而已,因此我们没有考虑这种方法。所以最终选择使用第二种方案来实现这个功能。

三、代码实现

1、前台页面实现

a、数据展示
数据展示使用了echarts的柱状图显示。echarts是个很常用的前端图表插件。不过作为一个非专业前端码农,会用就行了。具体怎么用就直接看官方的文档吧,官方文档就已经很详细了。
b、长连接发起
我们通过ajax向后台发起异步请求,并保持长连接。代码如下:
//区划统计长连接查询
function longPolling() {
$.ajax({
url: "${root!}/accept/statistics/wisdomdata/regionQueryLongPolling?regionCode=" + regionCode,
data: {"timed": new Date().getTime()},
dataType: "json",
type: "POST",
timeout: 20000,//设置为20s后断开连接
error: function (XMLHttpRequest, textStatus, errorThrown) {//请求失败
//如果返回错误,根据错误信息进行相应的处理
//再次发起长连接
longPolling();
},
success: function (data) {//请求成功
//根据后台返回的数据对页面数据进行刷新
refresh(data)
longPolling();//刷新成功后发起新的长连接请求
}
});
}

长连接成功返回数据后,我们根据获取的数据对页面数据进行刷新。因为使用的是echarts的柱状图,所以只需要重新封装echarts的数据,然后调用myChart的setOption方法将数据重新装载,就可以完成图表的刷新了。其实因为只是数据在变化,我们只需要将后台返回的数据放到一个数组里,然后修改series的data值即可。代码如下:
function refresh(data) {
//后台是将各列的数据用【,】隔开返回到前台,所以可以通过split(",")来获取series中的data所需要的数组
var regionTotal = data.regionTotal.split(",");
var regionCodeArray = data.regionCodeArray.split(",");
var regionNameArray = data.regionNameArray.split(",");
//这里的series,option和myChart都是页面初始化时创建的变量,在此不表
series[0]["data"] = regionTotal;//动态刷新
option.series = series;
myChart.setOption(option);
}

2、后台逻辑实现

后台逻辑实现氛围两部分。一部分是响应前台请求,返回改变后的数据。另一部分是单独的一个线程,不断的对数据库进行查询获取最新的数据。
a、数据查询
我们先来看看后台与数据库交互的部分,这部分需要实现下面的功能:
要把实时查询出来的数据保存在后台以供前台随时获取。解决方法是使用一个内部类对数据进行保存。
代码如下:
class RegionQuery{
private boolean isOpen = false;//查询线程是否已经开启
public List<String> regionCodelist = new ArrayList<String>();//需要动态查询的区划code
public List<String> regionNamelist = new ArrayList<String>();//需要动态查询的区划name
public List<String> loadingList = new ArrayList<String>();//当前线程已经加入动态查询的区划
public Map<String, JSONObject> regionCountMap = new HashMap<String, JSONObject>();//动态保存最新的区划业务数量
public RegionQuery(){
String[] regionArray = {"00001","00002","00003","00004"};
String[] regionNameArray = {"北京市","上海市","广州市","深圳市"};
regionCodelist = Arrays.asList(regionArray);
regionNamelist = Arrays.asList(regionNameArray);
this.isOpen= false;
}
public boolean isOpen() {
return isOpen;
}
public void setOpen(boolean open) {
isOpen = open;
}
}

要不间断的对数据库中的数据进行查询。解决方法是在第一次访问这个页面时,启动一个线程对数据库进行循环查询,并将数据保存在后台内部类。
代码片段如下:
//开启一个线程来获取regionQuery类的数据,这个线程是写在刚进入展示页面的方法中
if (!regionQuery.isOpen()) {//如果未开启线程,就进行开启
new Thread(){
public void run(){
regionQuery.setOpen(true);//标志进程已开启
while (true) {
try {
//循环查询当前loading列表的区划数据
for(int i=0;i<regionQuery.loadingList.size();i++){
String code = regionQuery.loadingList.get(i);//当前区划
String name = regionQuery.regionNamelist.get(regionQuery.regionCodelist.indexOf(code));//当前区划名
JSONObject jsonObject = wisdomRegionQuery(code,name,"month");//wisdomRegionQuery是对数据库进行业务量查询的方法,在这里就不放代码了。返回的是个JSONObject,其中各区的业务量数字是放在key为regionTotal的键值对中
regionQuery.regionCountMap.put(code, jsonObject);
}
Thread.sleep(5000);//等待5秒钟继续进行查询
} catch (ParseException e) {
e.printStackTrace();
logger.error(e.getMessage());
} catch (InterruptedException e) {
e.printStackTrace();
logger.error(e.getMessage());
}
}
}
}.start();
}

b、响应前台请求返回数据
在数据发生变化时,向保持长连接的前台请求返回最新的数据。解决方法是,我们将本次查询的数据保存在session中,在后续的查询时将新的数据与session中的数据做对比。如果数据发生变化,则将新数据返回前台,同时更新session中的数据。
代码如下:
/**
* 长连接动态获取当前区划统计值
* @param response
*/
@RequestMapping(value = "/regionQueryLongPolling", method = RequestMethod.POST)
public void regionQueryLongPolling(HttpServletResponse response) {
String regionCode = this.getPara("regionCode");
//判断session是否有当前查询区划的数据
HttpSession session = request.getSession();
if (session.getAttribute(regionCode) == null) {
JSONObject tempJson = new JSONObject();
tempJson.put("regionTotal", "0");
session.setAttribute(regionCode,tempJson);//以json格式保存区划名称业务量等相关信息
}
JSONObject result = new JSONObject();
result.put("code", SYSTEM_ERROR);
try {
for(int i=0;i<21;i++){//设置21秒后退出循环
JSONObject sessionJson = (JSONObject) session.getAttribute(regionCode);
JSONObject regionCountMapJson = (JSONObject) regionQuery.regionCountMap.get(regionCode);
if(!sessionJson.getString("regionTotal").equals(regionCountMapJson.getString("regionTotal"))){
//如果数据有变化则返回值并跳出循环
result.put("code", SYSTEM_SUCCESS);
result.putAll(regionCountMapJson);
session.setAttribute(regionCode,regionCountMapJson);
break;
}
Thread.sleep(1000);//等待一秒钟保持连接
}
} catch (Exception e) {
e.printStackTrace();
logger.error(e.getMessage());
result.put("code", SYSTEM_ERROR);
}
this.renderJson(response, result.toString());// 返回数据
}

四、总结

这个案例的核心在于两点,第一点,是使用长连接保持前台与后台之间的连接,只有数据改变时再返回数据。第二点,是在后台抛出一个线程不断的对数据库进行查询,并将结果保存到一个内部类里。整体实现了查询数据和展示动态数据的分离,减少前台页面对服务器的访问压力。毕竟自己的技术不够成熟,对于方案的实现,依旧会有考虑欠缺的地方,希望有大神能够指正。
同时,这个案例也有很多需要改进的地方,比如,抛出的用来查询数据的线程没有结束的时候,就会一直跑下去,这一点应该会造成隐患。也许我们可以根据,是否还有人保留这个数据展示页面,来控制这个线程的状态。这就等以后有时间的时候,再去研究啦。

参考资料:
echarts官网实例:http://echarts.baidu.com/examples.html
长连接长轮询:http://www.cnblogs.com/hoojo/p/longPolling_comet_jquery_iframe_ajax.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java web 异步