龙浩的blog
用技术创造价值,用艺术塑造自我!
用技术创造价值,用艺术塑造自我!
Jan 11th
问题现象
Java进程存在,Servlet服务存在,cpu使用率低,未发现死锁。重启服务器,问题得到解决。
问题分析
根据tomcat的解压部署方式,发现jetty中设置为:-Djava.io.tmpdir=/tmp , 根据返回的错误搜索/tmp jetty,发现linux存在watchdong自动清理tmp目录。
[root@updata jetty]# cat /etc/cron.daily/tmpwatch
#! /bin/sh
flags=-umc
/usr/sbin/tmpwatch "$flags" -x /tmp/.X11-unix -x /tmp/.XIM-unix \
-x /tmp/.font-unix -x /tmp/.ICE-unix -x /tmp/.Test-unix \
-X '/tmp/hsperfdata_*' 10d /tmp
/usr/sbin/tmpwatch "$flags" 30d /var/tmp
for d in /var/{cache/man,catman}/{cat?,X11R6/cat?,local/cat?}; do
if [ -d "$d" ]; then
/usr/sbin/tmpwatch "$flags" -f 30d "$d"
fi
done
系统默认每10天清理一次/tmp目录。
Spring会在系统启动加载类到内存中,但是不会加载servlet的相关内容。查看jetty的文档,Temporary Directories 有如下说明:
【本段文字为翻译】Jetty 本身没有临时目录,每个应用在war解压的时候指定temp目录,JSPs在运行是编译。
确定一个应用临时目录的算法如下:
1:尝试使用这个应用明确指定的目录:
2:基于全局设置创建一个目录。这个目录中在war解压后产生的目录是这样的: "Jetty_"host""port""context""+virtualhost
一个重要的提示:webapp的临时目录只能在它停止的时候删除里面的内容。
一旦临时目录被分配,它将作为一个web应用的javax.servlet.context.tempdir的文件实例被设置和检索。
解决问题的方法:
方法1:在jetty.home目录下面创建一个可写的文件夹work,去掉davinci.sh中的-Djava.io.tmpdir=/tmp 设置。
方法2:修改davinci.sh中的-Djava.io.tmpdir=/tmp为-Djava.io.tmpdir=指定的目录 ,然后在davinci.sh的stop函数中设置删除该目录中的内容。重要提示:每个应用设置的目录不能相同
Jan 6th
Guava 中文是石榴的意思,该项目是 Google 的一个开源项目,包含许多 Google 核心的 Java 常用库。目前主要包含:
这里先介绍一下最常用的com.google.common.collect包中的最常用的一些API,仅仅讨论一下API的使用方法,没有讨论到实现细节。
1:Collections的构造方法
我们平时直接创建Collections对象的方法一般都是用new关键字,有泛型的情况下看起来会比较长:
Map<String , Map<String , String>> see = new HashMap<String, Map<String,String>>();
在java7中,这个初始化做了简化:
Map<String , Map<String , String>> see = new HashMap<>();
可以通过Guava的API来这样写:
Map<String , Map<String , String>> see = Maps.newHashMap();
得到一个有size的Map:
Map<String , Map<String , String>> see = Maps.newHashMapWithExpectedSize(32);
在JDK的collection类型,在Guava中都可以找到相关的static的构造方法,例如:Lists , Sets , Maps , Queues。新的colleciont类型提供了直接构造的方法,例如:HashMultimap<String, String> multiMap = HashMultimap.create();
2:有限功能的函数式编程
介绍2个重要的接口:
com.google.common.base.Function : 根据输入值来得到输出值
com.google.common.base.Predicate : 根据输入值得到 true 或者 false
拿Collections2中有2个函数式编程的接口:filter , transform ,例如 :在Collection<Integer>中过滤大于某数的内容:
Collection<Integer> filterList = Collections2.filter(collections
, new Predicate<Integer>(){
@Override
public boolean apply(Integer input) {
if(input > 4)
return false;
else
return true;
}
});
把Lis<Integer>中的Integer类型转换为String , 并添加test作为后缀字符:
List<String> c2 = Lists.transform(list, new Function<Integer , String>(){
@Override
public String apply(Integer input) {
return String.valueOf(input) + "test";
}
});
需要说明的是每次调用返回都是新的对象,同时操作过程不是线程安全的。
3:Multimap and BiMap
Map中一个key只能有一个,后续put进去的内容会覆盖前面的内容,有些业务需要有相同的key,但是有不同的内容,Guava中提供了
Multimaps 来解决这个问题。
Multimap<String, String> prosons = HashMultimap.create();
prosons.put("longhao", "hubei");
prosons.put("lilei" , "henan");
prosons.put("longhao", "shanxi");
prosons.put("liuxia", "beijing");
prosons.put("lilei", "hainan");
Iterator<String> it = prosons.get("longhao").iterator();
while(it.hasNext()){
System.out.println(it.next());
}
BiMap可以有相同的key,但是不能有相同的value,如果不同的key设置了相同的value,则会抛出IllegalArgumentException异常,可以通过inverse()来反转kv,values()来获取value的set。
public void biMapShouldOnlyHaveUniqueValues() {
BiMap<Integer, String> biMap = HashBiMap.create();
biMap.put(1, "a");
biMap.put(2, "b");
biMap.put(3, "a"); //argh! an exception
}
4:tables
给出一个columns, rows , values, 这个API和Map<K , Map<K , V>>形式差不多,多了一些封装。例子:
static void tables(){
Table<Integer , String , Integer> user = HashBasedTable.create();
user.put(1, "longhao", 29);
user.put(1, "shuaige", 29);
user.put(2, "xiaomi", 1);
user.put(3, "soso", 3);
System.out.println(user.containsColumn("soso"));//true
System.out.println(user.containsColumn("3"));//false
System.out.println(user.contains(1, "xiaomi"));//false
System.out.println(user.contains(1, "meinv"));//true
System.out.println(user.row(1));//{shuaige=29, longhao=29}
}
5:更简洁的判断
使用Preconditions中的方法来判断是否为空等操作,这个操作和spring,apache common-lang中的API类似
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
static void checkParam(String name , Integer passwd){
checkNotNull(name , passwd);
checkArgument("" != name , passwd > 0);
}
Common-lang,spring中的方法需要逐个调用。而Guava中支持。
6:约束
对Collection中的新加入元素做约束,只有符合条件的元素才能够被添加到Collection中,可以使用Constraint类来操作。
示例代码:
import static com.google.common.collect.Constraints.constrainedList;
static void constraintExam(){
Constraint<String> chkListStr = new Constraint<String>(){
@Override
public String checkElement(String element) {
if(element.startsWith("h")){
throw new IllegalArgumentException("不允许有h开头的内容");
}
return element;
}
};
List<String> list = Lists.newArrayList("li","hao","a");
List<String> conList = constrainedList(list, chkListStr);
conList.add("soso");
conList.add("hqb");// throw IllegalArgumentException
for(String str : list){
System.out.println(str);
}
}
参考资料
Dec 22nd
slideshare.net对微软雅黑字体支持有问题,感兴趣的请下载。
Nov 28th
1:Jetty的4种web应用部署方式(不包括嵌入启动):
1:直接修改${JETTY_HOME}/etc/jetty.xml的配置来部署应用;
2:把war包扔到${JETTY_HOME}/webapps目录中,自动被WebAppDeployer发现去部署;
3:在${JETTY_HOME}/contents中增加一个配置文件(模仿test.xml的配置),由ContextDeployer自动发现去部署;
4:定制部署启动配置文件;
前面3中部署方式在Jetty7的部署方式中讲述的较为清楚。
2:定制化启动Jetty:
由于我们每个应用需要不同端口启动,这样在停止A应用的情况下,B应用不会受到影响。同时,我们希望在每台服务器上只有一份jetty,每个应用的配置文件应该是独立的。
2.1:jetty容器配置文件
在start.ini中配置了etc/jetty.xml , etc/jetty-webapps.xml (WebAppDeployer),etc/jetty-contexts.xml(ContextDeployer)三个文件,代表在默认情况下启动jetty,web应用3种部署都是支持的。WebAppDeployer则是会扫描${JETTY_HOME}/webapps目录,寻找war包并部署;ContextDeployer会扫描${JETTY_HOME}/contents目录,找到可以部署的context配置文件并部署。
所以定制jetty之前,我们需要在start.ini中把这3行注释掉。
2.2:web应用配置
复制${JETTY_HOME}/etc/jetty.xml文件到bin目录下面,修改port和confidentialPort为自己应用的端口号。在<Array type="org.eclipse.jetty.server.Handler">中添加需要部署的war包得Item,如下注意(contextPath和war值):
<Item>
<New class="org.eclipse.jetty.webapp.WebAppContext">
<Set name="contextPath">/aaa</Set>
<Set name="war">/opt/longtask/aaa/webapps/aaa.war</Set>
<Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
<Set name="extractWAR">true</Set>
<Set name="copyWebDir">false</Set>
<Call name="addServlet">
<Arg>org.eclipse.jetty.servlet.DefaultServlet</Arg>
<Arg>/</Arg>
</Call>
</New>
</Item>
2.3:启动应用(省略相关sh脚本)
JETTY_CONFIG="${PWD}/jetty-${APP_NAME}.xml ${PWD}/jetty-logging.xml"
JETTY_OPTS="-jar ${JETTY_HOME}/start.jar –pre=${JETTY_CONFIG}"
java -server $JDK_OPTS $JETTY_OPTS >/dev/null 2>&1 &
2.4:OPTIONS的说明
在jetty8中通过java -jar start.jar –version命令可以查看到默认启动的OPTIONS:
Active Options: [Server, annotations, ext, jdbc, jmx, jsp, jta, plus, resources, websocket]
如果要启动其他OPTIONS,需要自己添加。
3:FAQ:
3.1:无法启动,报端口错误:
Caused by: java.net.BindException: Address already in use
at sun.nio.ch.Net.bind(Native Method)
at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:126)
at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:59)
at org.eclipse.jetty.server.nio.SelectChannelConnector.open(SelectChannelConnector.java:172)
at org.eclipse.jetty.server.AbstractConnector.doStart(AbstractConnector.java:300)
at org.eclipse.jetty.server.nio.SelectChannelConnector.doStart(SelectChannelConnector.java:250)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.server.Server.doStart(Server.java:273)
at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:59)
at org.eclipse.jetty.xml.XmlConfiguration$1.run(XmlConfiguration.java:1203)
at java.security.AccessController.doPrivileged(Native Method)
at org.eclipse.jetty.xml.XmlConfiguration.main(XmlConfiguration.java:1126)
通过lsof -i:port来查看端口是否被占用,如果被占用,需要修改jetty配置文件中的port来启动
3.2:在去掉start.ini中注释掉jetty.xml后,制定自己的jetty.xml后报错。
问题:
java.io.FileNotFoundException: Unable to find XML Config: etc/jetty.xml
at org.eclipse.jetty.start.Main.resolveXmlConfig(Main.java:671)
at org.eclipse.jetty.start.Main.resolveXmlConfigs(Main.java:888)
at org.eclipse.jetty.start.Main.start(Main.java:508)
at org.eclipse.jetty.start.Main.parseCommandLine(Main.java:265)
at org.eclipse.jetty.start.Main.main(Main.java:79)
需要在–pre中添加启动项,不是在-jar start.jar后面添加
3.3:网上说war包需要解压缩,是错误的,jetty8不需要解压的。
4:参考文档:
Nov 22nd

单文件压力测试
准备用varnish来做图片缓冲服务器,用ab测试了一下单文件读取。发现nginx和varnish都可以把网卡写满(千兆网卡速度》110M/S)。调整了ab测试的并发数,在同样的配置的机器上,发现nginx的在1W并发情况下,依然可以运行;varnish在3600并发测试的时候,直接开始报错。不能不说nginx是神器,varnish不靠谱么?
多文件压力测试
结论
我们图片的平均大小为50K,varnish的3500的并发,最大处理为175MB,超过网卡125MB的峰值。所以还是用varnish比较靠谱。如果图片的大小为10K,直接用nginx做反向代理或许更好。
Nov 21st
在我们学习面向对象语言时,两个概念相当难理解:类必须高内聚,低耦合。今天的博文我们将解释这两个概念的重要性,同时展示Sonar如何帮助大家来评估类的内聚性。
几周前,我开始讨论如何《Fight Back Design Erosion by Breaking Cycles with Sonar》,我提到好的设计应该能够通过很小的努力编写的新代码块就可以替换系统中的相关部分代码。第一个层面需要从“宏观水平”来看,例如:确保你的“packages” 没有包的循环依赖;但得到任何水平的模块化设计,你也需要考虑“微观”,例如,你的类/方法要保证高内聚,低耦合。
耦合性是指每个类对其他类的依赖程度。换句话说,如果一个类是高耦合的,那么对这个类的修改将直接或间接影响到其他许多类:不是我所说的大模块!你可以通过反转控制来降低类之间的耦合。
内聚是指一个类中的方法的紧密程度。当一个类中的两个方法不使用一个共同属性或者方法,如果遵守单一职责原则,这意味着它们没有共用任何东西或者它们就不属于同一个类。换句话说,你应该把这个类分解为多个新类来达到类级别模块化的目的。
所以类应该尽可能的降低耦合关系,而方法需要刚好相反的来提高内聚性。这和一个灵活的组织非常相似:每个团队(class)应该非常自主(低耦合),但是团队中所有的成员(methods)应该彼此联系非常精密(高内聚)
Sonar2.0带来了实验性且有前途的度量:用LCOM4 (缺乏内聚性的方法)来衡量类的内聚性。解释这个度量是非常简单的,值1表示这个类只有一个职责(好),值X代码这个类有X个职责(差),值X得类应该重构/分割。
这里没有魔法,只有尝试。让我们用Driver这个类来举例。这个类有2个域:Car 和 Brain,包含5个方法:drive(), goTo(), stop(), getAngry() , drinkCoffee()。下面是这些组件之间的依赖关系图,这个类有3个不同的职责,所以LCOM4 = 3 ,这打破了单一职责的原则。

当进入Sonar,事情仍然非常简单。在项目面板的第一个窗口小部件就可以快速的查看:

在那里,你可以点击下面的链接去查看可疑类。了解一个类的LCOM4只是一个很好的起点,仅仅了解是绝对不够的。点击你感兴趣的类,你将会看到需要内聚的方法块。重构就此开始!

你可以查看Stuart Gunter的文章《Assessing the Single Responsibility Principle with LCOM4 with Sonar 2.0》。这个反馈是通过实践帮助我们改进相关的度量算法。
说明:翻译文章来源地址:《Clean Up Design at Class Level with Sonar》