项目小结
前言:准备了大半年的东西,在上一个星期准备交付给客户使用。怎么交付给他们使用呢?就是把我们程序员写的的代码发布到客户那边的服务器环境中去。当然对于项目的功能我们已经测试多次了,本以为很简单的事情却苦苦折磨了我们一个星期多才算有个头绪,而且还把之前一些功能屏蔽了。下面就把我这个星期的bug简单记录一下,这其中有我自己代码的问题也有客户环境那边导致的问题。
零:开发环境
测试环境
第一阶段 LINUX服务器 + tomcat war项目直接部署测试
第二阶段 k8s + docker
生产环境
腾讯云平台 docker + tsf平台
一:虚惊一场
其中我有个项目是用spring boot开发的,而且还是个war项目,其他的都是jar。偏偏就是这个项目在部署到客户那边经常出现异常重启的现象。当时还是测试阶段,数据量还很小,每天就会重启个8-10+次。这一现象可惊呆了鄙人(因为我第一感觉就是死循环,内存泄漏导致的<严重而且低级>,因为这会严重影响我的声誉^_^),故要急需解决。因为有一块我用到了设计模式中的思想(单例+枚举),来实现模板消息的发送。及恐慌是这一块导致的。后经过排查虽然有些地方可以优化。但不至于把项目搞死。无奈只好把问题抛出来,大家一起想办法解决,虽都提出了很多想法但都没有最终解决问题,后让客户那边帮我们查看容器重启的原因,他告诉我们cup占用率过高。后来我们把项目的启动参数增加到2核+内存2014M(之前1核+512M),问题解决。
尝试过程
1.检测是否因内存泄漏导致
工具 postman + jvisualvm.exe 通过postman连续发送5000次请求,并且通过查看 jvisualvm 界面查看内存使用情况(好像是512M会自动触发java的回收GC机制)
postman 设置连续发送多次请求
jvisualvm( 目录 **\jdk1.8.0_121\bin ) 查看内存占用情况
备注:可通过此工具来查看内存泄漏,cup占用等情况。512M触发java回收机制
备注:因为垃圾回收机制的影响,如果如果给项目分配了较少的资源,就会频繁调用GC,反而影响性能。而如果分配的核数较少更会影响项目的稳健型
2.单例的思考
单例对象应该是作为工具对象,即该对象的属性不应该全部能修改(要不然就失去的单例的意义),然对于不经常修改的属性不应该影响其他其他人(其他线程)使用。故我总结一下几点设计单例的规则(已我的消息模板为例)
* > 可选属性(参数) —— 消息的内容,比如你中的那个奖品。可选属性应该是每次使用都会被重新赋值,即对于一个可选属性都应该有一个默认值。
*> 不可选属性(参数 ) —— 消息发送的URL,和消息发送的应用APPID(必须有不然就失去单例的意义)
两类属性应该分来创建
有人说设计单例时应该考虑内存泄漏,我认为是有道理的,应为单例对象是用static关键字修饰,故其不会被垃圾回收机制GC回收到。如果其单例对象的属性引用了大量的资源如一个map。就会导致该map也不会被回收到
二:图片下载
由于涉及IO流问题,就少不了编码方式,即常出现的中文乱码问题。针对这一块,其实有出现多个问题,但总结起来就两类问题,一类就是中文乱码,另一类就是客户端那边的网络限制。真对问题的出现原因,一方面就是编写代码的时候我欠思考,当然这是很难避免的,对于IO流的这一块的复杂程度大家也是有目共睹的^_^。另一方便就是我们设计的时候没有想打客户那边的网络限制,当然就算想到了估计也要这么做^_^。下面对该问题的详细描述。
客户网络限制
不管需求先给介绍一下发生场景,我做了一个编辑器(uedit百度的),客户可以利用该编辑器编辑文章,并生成HTML页面。其中该编辑器中的大部分图片可能都来自网络,而我需要把网络图片下载到本地,下次显示的时候就是我们自己资源库中的图片。步骤如下
步骤一》一个正则表达式获取该文章中的所有图片地址
public static List<String> searchPicUrlInString(String html) { List<String> urlList = new ArrayList<String>(); // 图片网址正则 String reg = "(http:|https:){1}[\\//]{2}[A-Za-z0-9.\\/\\-\\_\\%]+(bmp|jpeg|gif|png|jpg)"; Pattern pattern = Pattern.compile(reg); Matcher matcher = null; matcher = pattern.matcher(html); while (matcher.find()) { String imageUrl = matcher.group(0); // 如果图片已在chinatopfine,不返回 if (imageUrl.contains("chinatopfine")) { continue; } // 如果图片tobacco,不返回 if (imageUrl.contains("tobacco")) { continue; } // mmbiz.qpic.cn"服务器不返回 if (imageUrl.contains("mmbiz.qpic.cn")) { continue; } urlList.add(imageUrl); } return urlList; }
步骤二》通过网络图片URL下载图片,并返回我们图片地址(我们自己的)
/** * * @param picUrl * 网络图片地址 * @param savePath * 保存路径 * @return 返回图片名称 */ public static String savePictureByUrl(String picUrl, String savePath) { // 生成图片名 String imgType = picUrl.substring(picUrl.lastIndexOf(".")); String imgName = UUID.randomUUID().toString().replaceAll("-", "") .toString() + imgType; File f = new File(savePath); if (!f.exists()) { f.mkdirs(); } String imgPath = savePath + File.separator + imgName; File file = new File(imgPath); URL url = null; try { url = new URL(picUrl); DataInputStream dataInputStream = new DataInputStream( url.openStream()); FileOutputStream fileOutputStream = new FileOutputStream(file); ByteArrayOutputStream output = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int length; while ((length = dataInputStream.read(buffer)) > 0) { output.write(buffer, 0, length); } fileOutputStream.write(output.toByteArray()); dataInputStream.close(); fileOutputStream.close(); } catch (IOException e) { e.printStackTrace(); log.info("本地备份图片失败:" + picUrl); } // 返回图片名 return imgName; }
但由于客户那边限制的访问外网的能力,最终这个功能被我们舍弃了
中文乱码
其实中文乱码发生在两处,第一处就是前面说的编辑器中的文章难免会有中文,当时就是中文乱码。当时我用的是FileOutputStream,简单查看了一下该类不能指定编码格式,但其的包装类PrintStream可以指定,这两个问题也就很快解决了
代码如下 new PrintStream(new FileOutputStream(file),false, "UTF-8");
public static String saveHtmlByString2(String htmlName,String htmlDir, String content, File templateHtml) throws IOException { // 生成html名和地址 htmlName = htmlName + ".html"; String htmlPath = htmlDir + htmlName; Document doc; try { doc = Jsoup.parse(templateHtml, "UTF-8"); } catch (IOException e) { log.info("Html 模板解析错误:" + templateHtml.getAbsolutePath()); throw e; } // 获取模板中要 替换的标签 Element div = doc.getElementById("isme"); // content替换为content div.html(content); // 创建文件 File filedir = new File(htmlDir); if (!filedir.exists()) { filedir.mkdirs(); } // 创建文件 File file = new File(htmlPath); file.createNewFile(); PrintStream ps = new PrintStream(new FileOutputStream(file),false, "UTF-8"); ps.println(doc.html()); ps.flush(); ps.close(); return htmlName; }
另一处就是上传带有中文名的图片(当然可以直接修改图片名称把中文名替换,但因为某种业务原因需要使用原图片的名称),我直接把war包发布到我们的测试环境即linix服务器是可以上传中文名图片的为什么放到客户的环境就不好用了呢?
方法一 首先想到的方法就是和上面的解决思路一样,通过包装流指定编码方式(不好用)
方法二 指定服务器编码格式(echo $LANG查看),当然也是不好用(其实关键点,就是这)
方法三 网上有人说文件的内容和System.getProperty(“file.encoding”)这个属性有关而文件名和System.getProperty(“sun.jnu.encoding”)有关,当然测试结果还是不好用。
方法四 修改docker容器内的编码格式,有这个想法时,我首先是进入了我们自己的容器通过(echo $LANG)命令发现没有输出,当然容器内的中文名图片也是乱码。感觉一下子问题就找到了根源。即我的项目的真正运行环境是容器。然后就重新修改创建镜像的DockerFile如下问题解决^_^
WORKDIR /home ADD wxcms.war /home/tomcat/webapps/ ADD myhosts /home/ ADD startup.sh /home/tomcat/bin/startup.sh # 安装必要的工具 RUN chmod -R 777 /home/tomcat/bin && \ mkdir -p /qyhupload/wxkf && \ mkdir -p /qyhupload/cyxx && \ mkdir -p /qyhupload/information && \ mkdir -p /qyhupload/company/ && \ mkdir -p /qyhupload/cigarette/ && \ mkdir -p /qyhupload/article && \ mkdir -p /qyhupload/tuwenmessage && \ ln -snf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo Asia/Shanghai > /etc/timezone RUN locale RUN localedef -i zh_CN -c -f UTF-8 zh_CN.UTF-8 RUN echo "export LC_ALL=zh_CN.UTF-8" >> /etc/profile && source /etc/profile ENV LANG zh_CN.UTF-8 ENV LC_CTYPE zh_CN.UTF-8
三:入坑多次,屡教不改,哀伤
这块内容才是我最想总结的对自己的教训, 有时候我为了图一时之便,把原本设计的规则弃置不管,也许当时自己还可以控制但时间久了,或遇到自己无法把控的问题出现。就会焦头烂额,无法下手。这一次有一个问题就狠狠的敲击了我。
长哎一声,以至我当时的哀伤。事情简单介绍一下,之前为了方便自己查看bug,经常会把测试环境的代码连接到自己的本地项目。虽然在当时时时段段会有点影响,但我图方便就临时启动一下本地项目,也就问题解决了(当时就两套环境),自己感觉还挺方便。所以就侥幸一直这样。但这次上真正环境,出现了一个问题惊动了我们的小团队,需要快熟解决..................不说了,浪费了很多时间也无从下手,之前侥幸获取的方便也一下子全还了回去。