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

一、需求

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

二、解决方案

1、不太好的方案

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

2、改进后的方案

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

3、再一次改进?

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

三、代码实现

1、前台页面实现

a、数据展示
数据展示使用了echarts的柱状图显示。echarts是个很常用的前端图表插件。不过作为一个非专业前端码农,会用就行了。具体怎么用就直接看官方的文档吧,官方文档就已经很详细了。
b、长连接发起
我们通过ajax向后台发起异步请求,并保持长连接。代码如下:
[javascript] view plain copy
  1. //区划统计长连接查询  
  2.         function longPolling() {  
  3.             $.ajax({  
  4.                 url: "${root!}/accept/statistics/wisdomdata/regionQueryLongPolling?regionCode=" + regionCode,  
  5.                 data: {"timed"new Date().getTime()},  
  6.                 dataType: "json",  
  7.                 type: "POST",  
  8.                 timeout: 20000,//设置为20s后断开连接  
  9.                 error: function (XMLHttpRequest, textStatus, errorThrown) {//请求失败  
  10.                     //如果返回错误,根据错误信息进行相应的处理  
  11.                     //再次发起长连接  
  12.                     longPolling();  
  13.                 },  
  14.                 success: function (data) {//请求成功  
  15.                     //根据后台返回的数据对页面数据进行刷新  
  16.                     refresh(data)  
  17.                     longPolling();//刷新成功后发起新的长连接请求  
  18.                 }  
  19.             });  
  20.         }  
长连接成功返回数据后,我们根据获取的数据对页面数据进行刷新。因为使用的是echarts的柱状图,所以只需要重新封装echarts的数据,然后调用myChart的setOption方法将数据重新装载,就可以完成图表的刷新了。其实因为只是数据在变化,我们只需要将后台返回的数据放到一个数组里,然后修改series的data值即可。代码如下:
[javascript] view plain copy
  1. function refresh(data) {  
  2.             //后台是将各列的数据用【,】隔开返回到前台,所以可以通过split(",")来获取series中的data所需要的数组  
  3.             var regionTotal = data.regionTotal.split(",");  
  4.             var regionCodeArray = data.regionCodeArray.split(",");  
  5.             var regionNameArray = data.regionNameArray.split(",");  
  6.             //这里的series,option和myChart都是页面初始化时创建的变量,在此不表  
  7.             series[0]["data"] = regionTotal;//动态刷新  
  8.             option.series = series;  
  9.             myChart.setOption(option);  
  10.         }  

2、后台逻辑实现

后台逻辑实现氛围两部分。一部分是响应前台请求,返回改变后的数据。另一部分是单独的一个线程,不断的对数据库进行查询获取最新的数据。
a、数据查询
我们先来看看后台与数据库交互的部分,这部分需要实现下面的功能:
要把实时查询出来的数据保存在后台以供前台随时获取。解决方法是使用一个内部类对数据进行保存。
代码如下:
[java] view plain copy
  1. class RegionQuery{  
  2.       private boolean isOpen = false;//查询线程是否已经开启  
  3.       public List<String> regionCodelist = new ArrayList<String>();//需要动态查询的区划code  
  4.       public List<String> regionNamelist = new ArrayList<String>();//需要动态查询的区划name  
  5.       public List<String> loadingList = new ArrayList<String>();//当前线程已经加入动态查询的区划  
  6.       public Map<String, JSONObject> regionCountMap = new HashMap<String, JSONObject>();//动态保存最新的区划业务数量  
  7.       public RegionQuery(){  
  8.           String[] regionArray = {"00001","00002","00003","00004"};  
  9.           String[] regionNameArray = {"北京市","上海市","广州市","深圳市"};  
  10.           regionCodelist = Arrays.asList(regionArray);  
  11.           regionNamelist = Arrays.asList(regionNameArray);  
  12.           this.isOpen= false;  
  13.       }  
  14.       public boolean isOpen() {  
  15.           return isOpen;  
  16.       }  
  17.       public void setOpen(boolean open) {  
  18.           isOpen = open;  
  19.       }  
  20.   }  
要不间断的对数据库中的数据进行查询。解决方法是在第一次访问这个页面时,启动一个线程对数据库进行循环查询,并将数据保存在后台内部类。
代码片段如下:
[java] view plain copy
  1. //开启一个线程来获取regionQuery类的数据,这个线程是写在刚进入展示页面的方法中  
  2.             if (!regionQuery.isOpen()) {//如果未开启线程,就进行开启  
  3.                 new Thread(){  
  4.                     public void run(){  
  5.                         regionQuery.setOpen(true);//标志进程已开启  
  6.                         while (true) {  
  7.                             try {  
  8.                                 //循环查询当前loading列表的区划数据  
  9.                                 for(int i=0;i<regionQuery.loadingList.size();i++){  
  10.                                     String code = regionQuery.loadingList.get(i);//当前区划  
  11.                                     String name = regionQuery.regionNamelist.get(regionQuery.regionCodelist.indexOf(code));//当前区划名  
  12.                                     JSONObject jsonObject = wisdomRegionQuery(code,name,"month");//wisdomRegionQuery是对数据库进行业务量查询的方法,在这里就不放代码了。返回的是个JSONObject,其中各区的业务量数字是放在key为regionTotal的键值对中  
  13.                                     regionQuery.regionCountMap.put(code, jsonObject);  
  14.                                 }  
  15.                                 Thread.sleep(5000);//等待5秒钟继续进行查询  
  16.                             } catch (ParseException e) {  
  17.                                 e.printStackTrace();  
  18.                                 logger.error(e.getMessage());  
  19.                             } catch (InterruptedException e) {  
  20.                                 e.printStackTrace();  
  21.                                 logger.error(e.getMessage());  
  22.                             }  
  23.                         }  
  24.                     }  
  25.                 }.start();  
  26.             }  
b、响应前台请求返回数据
在数据发生变化时,向保持长连接的前台请求返回最新的数据。解决方法是,我们将本次查询的数据保存在session中,在后续的查询时将新的数据与session中的数据做对比。如果数据发生变化,则将新数据返回前台,同时更新session中的数据。
代码如下:
[java] view plain copy
  1. /** 
  2.     * 长连接动态获取当前区划统计值 
  3.     * @param response 
  4.     */  
  5.    @RequestMapping(value = "/regionQueryLongPolling", method = RequestMethod.POST)  
  6.    public void regionQueryLongPolling(HttpServletResponse response) {  
  7.        String regionCode = this.getPara("regionCode");  
  8.        //判断session是否有当前查询区划的数据  
  9.        HttpSession session = request.getSession();  
  10.        if (session.getAttribute(regionCode) == null) {  
  11.            JSONObject tempJson = new JSONObject();  
  12.            tempJson.put("regionTotal""0");  
  13.            session.setAttribute(regionCode,tempJson);//以json格式保存区划名称业务量等相关信息  
  14.        }  
  15.        JSONObject result = new JSONObject();  
  16.        result.put("code", SYSTEM_ERROR);  
  17.        try {  
  18.            for(int i=0;i<21;i++){//设置21秒后退出循环  
  19.                JSONObject sessionJson = (JSONObject) session.getAttribute(regionCode);  
  20.                JSONObject regionCountMapJson = (JSONObject) regionQuery.regionCountMap.get(regionCode);  
  21.                if(!sessionJson.getString("regionTotal").equals(regionCountMapJson.getString("regionTotal"))){  
  22.                    //如果数据有变化则返回值并跳出循环  
  23.                    result.put("code", SYSTEM_SUCCESS);  
  24.                    result.putAll(regionCountMapJson);  
  25.                    session.setAttribute(regionCode,regionCountMapJson);  
  26.                    break;  
  27.                }  
  28.                Thread.sleep(1000);//等待一秒钟保持连接  
  29.            }  
  30.        } catch (Exception e) {  
  31.            e.printStackTrace();  
  32.            logger.error(e.getMessage());  
  33.            result.put("code", SYSTEM_ERROR);  
  34.        }  
  35.        this.renderJson(response, result.toString());// 返回数据  
  36.    }  

四、总结

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

参考资料:
长连接长轮询:http://www.cnblogs.com/hoojo/p/longPolling_comet_jquery_iframe_ajax.html
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值