分类存档: java - 第3页

一篇介绍jsr用法的文章

一篇介绍jsr用法的文章:

https://today.java.net/pub/a/today/2008/04/10/source-code-analysis-using-java-6-compiler-apis.html#accessing-the-abstract-syntax-tree-the-compiler-tree-api

jsr可以将java source生成为一个TreePath,可以读取到该类中的所有信息。(除了备注 -.-!)。

可以了,现在转向eclipse adt看看能否获得带备注的ast。

maven中导出所依赖的第三方包

mvn dependency:copy-dependencies -DoutputDirectory=lib   -DincludeScope=compile

将所有的使用的第三方包导出于lib目录下。

 

把java安装为服务

将java安装为服务,强烈推荐common daemon:

http://commons.apache.org/proper/commons-daemon/procrun.html

apache上的多个java项目都使用这个安装为windows服务。

用法很简单:

java类中的main函数中使用start和stop来做为启动和停止指令:

public static void main(String[] args) {
		System.out.println("start Pdf2Swf Service main with args:" + args);

		String mode = "start";
		if (args != null && args.length > 0) {
			mode = args[0];
		}
		if ("start".equals(mode)) {
			// 启动主线程
			Pdf2SwfService pdf2SwfService = new Pdf2SwfService();
			pdf2SwfService.run();
			Pdf2SwfService.staticInstance = pdf2SwfService;
		} else if ("stop".equals(mode)) {
			if (Pdf2SwfService.staticInstance != null) {
				try {
					Pdf2SwfService.stop = true;
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

下面编译安装服务的脚本:

@echo off
cd /d %~dp0
set PR_PATH=%CD%
SET PR_SERVICE_NAME=Pdf2Swf
SET PR_JAR=pdf2swf.jar
SET START_CLASS=org.haifi.Pdf2SwfService
SET START_METHOD=main
SET STOP_CLASS=java.lang.System
SET STOP_METHOD=exit
rem ; separated values
SET STOP_PARAMS=0
rem ; separated values
SET JVM_OPTIONS=-Dapp.home=%PR_PATH% -out %CD%\stdout.log -err %CD%\stderr.log -current %CD%

echo %PR_PATH%

prunsrv.exe //IS//%PR_SERVICE_NAME% --DisplayName="%PR_SERVICE_NAME%" --Install=%PR_PATH%\prunsrv.exe --LogPath=%PR_PATH%\logs --LogLevel=Debug --StdOutput=auto --StdError=auto --StartMode=Java --StopMode=Java --Jvm=auto --StartMode=jvm --StartClass=%START_CLASS% ++StartParams=start --StopMode=jvm --StopClass=%STOP_CLASS% ++StopParams=stop --Classpath="%PR_PATH%\%PR_JAR%"  ++JvmOptions=%JVM_OPTIONS%
pause


上面的类换为自己写的主类名。

如果启动不起来时,注意一下如果jdk为32位版本时,需要使用32位的prunsrv。

如果使用64位的prunsrv,则必须使用64位的jdk版本。

不然会直接提示无法启动,但又不会有任何有用的提示信息。

服务器性能优化

今天完成了测试机器人,开始对服务器进行性能测试。

测试环境:

客户端:开始10000个连接,每个连接每秒发送一个包(业务逻辑只对内存操作)。

1、服务器内存1G,发现在连接达到1000个左右时,服务器接近假死状态。

查看gc: jstat -gcutil 21340 5000 999999

  S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT   
  0.00 100.00 100.00 100.00  38.29    908   12.490   927 1793.564 1806.054
  0.00 100.00 100.00 100.00  38.29    908   12.490   928 1797.939 1810.429
  0.00 100.00 100.00 100.00  38.29    908   12.490   930 1800.529 1813.019
  0.00 100.00 100.00 100.00  38.29    908   12.490   933 1809.093 1821.583
  0.00 100.00 100.00 100.00  38.29    908   12.490   935 1813.729 1826.219
  0.00 100.00 100.00 100.00  38.29    908   12.490   938 1818.452 1830.942
  0.00 100.00 100.00 100.00  38.29    908   12.490   939 1822.462 1834.952
  0.00 100.00 100.00 100.00  38.29    908   12.490   941 1827.227 1839.717
  0.00 100.00 100.00 100.00  38.29    908   12.490   944 1834.183 1846.673
  0.00 100.00 100.00 100.00  38.29    908   12.490   946 1836.594 1849.085
  0.00 100.00 100.00 100.00  38.29    908   12.490   948 1842.616 1855.106
  0.00  99.99 100.00 100.00  38.29    908   12.490   951 1849.040 1861.531
  0.00 100.00 100.00 100.00  38.29    908   12.490   953 1854.135 1866.625
  0.00 100.00 100.00 100.00  38.29    908   12.490   956 1858.858 1871.348
  0.00 100.00 100.00 100.00  38.29    908   12.490   957 1862.671 1875.161
  0.00 100.00 100.00 100.00  38.29    908   12.490   959 1867.481 1879.971

看出full gc 次数频繁。

2、把内存改为2G再次测试,同时把log日志关闭(udp syslog发送,对性能影响应该不是很大,可以在最后再测试未关闭日志的情况)

这次效果明显,达到2000连接,也未产生full gc,连接数继续加大。

连接已经上升到5000,依旧无压力。

再继续往上升时,发现连续全断开了。

查看log,发现是超时断开了,设置的是2分钟超时。

3、继续解决超时的问题。

机器人连接主要操作是两步,一是登陆(相对来说比较消耗时间),二是发送指令(1秒发送1个,异步发送,速度非常快)。

我这里是创建了一个连接池来处理每个机器人。

在开始运行时,即生成10000个线程,然后依次执行登陆、发送指令,然后继续将自己放入连接池。

而问题则出现在了第一步,在开始登陆时,10000个线程全部放入了连接池,所以连接池需要全部分执行完这10000个线程的登陆,才会进入循环,不停发送指令。而这10000个线程的耗时超过了2分钟,导致大量连接被服务器主动断开。

找到问题后,就好解决了。

再创建一个连接池,所有完成登陆的连接都放入第二个连接池。这样,所有的登陆操作在第一个连接池中,完成登陆的连接在第二个连接池中。顺利解决问题。

4、在测试中发现,虽然使用了udp往rsyslog发送日志,但日志量如果过大,依旧会影响到性能。

5、OK,现在的结果比较理想。最大2G内存,通过probe查看实际消耗不到1G内存,连接数还可以继续往上提升。

在后台查看状态时,又出现问题了,每秒处理一条指令,1万个连接,每秒需要处理1万条指令,我开启了20个线程,发现根本处理不过来。指令队列中的指令越堆越多。

尝试把线程最大数量修改为50。

奇怪,修改为50线程后,没有再出现堆积指令队列的现象,再改为20看下。

果真,发现慢的原因了,游戏使用定时存储,在存储时,1万个玩家对象都要保存进去,才会导致指令队列变慢了。找到原因就好解决了,把定时存储放在另一个线程时,不要占用玩家指令的线程。

2G内存测试的1万个连接,最终稳定在7900个,流量5M左右。其中2千多个连接失败,不知道是因为我在是自己本本上开启1万个连接引起的,还是有其它bug,等下再测试。先试下3G内存开5万个连接。

找了两台PC,每台分别开启5000个连接,非常稳定,没有出现掉线现象。另一台连接提升至1万。有10来个连接断掉。看来连接断开不是服务器的原因,应该是PC上的连接数限制之类的有关。只是还没有查到具体限制。

gc也是很稳定。fullgc不是很频繁,对了,在测试中还发现异步存储原来和用户处理线程都放在同一个线程池,导致的在存储时阻塞了用户处理线程,发现后,专门为异步存储指令开始一个连接池,解决掉阻塞用户处理线程的问题。

2013.09.23 mina2改为netty4,继续进行优化。

3G内存,1万个连接,idle78%左右。网络流量发送74k/s,接收4.4M/S(未开启no_delay)

在压力测试中打开tcpnodelay,结果很多连接直接创建失败。统计了下大概有180个连接创建失败。

这次只是在客户端上打开了tcp_nodelay,查看流量没有什么变化,可能是因为1秒发送一个包,所以只是降低了延迟,对于流量并没有变化。还有可能是因为我在系统注册中已经把no_delay打开了,所以这个开关并没有生效。

再测试下服务器端打开nodelay.服务器打开nodelay后性能并无下降,返回感觉有稍微提升。

使用jprofiler进行性能分析(jprofiler官网上直接下,可以免费用10天,10天对于性能分析足够了)。

查看发现以下几种对象在内存中占用过多:

protobuf对象数量在百万以上。占用的内存达到1.5G左右。

现在开始频繁的出现fgc.

强制结束掉客户端,查看对象是否被回收。好吧,现在完全感觉是内存泄露了。

修改服务器处理程序,每次接收包后,设置autoread为false,处理完包后再设置为true.先跑2个小时看看情况再说。因为上次查看时,指令队列长度最多有400多了,这样可以限制指令队列不会太长,也可以避免以后恶意用户不间断发包把服务器卡住。

找到原因了,是因为数据包太多了,处理不过来,要发送的数据没有发送出去,造成数据不断积累,从而耗尽内存。

2013.09.26记:

1、decoder中的byte[]已经改为使用theadlocal缓存,大量减少了字节数组的创建。

2、去除了中间对象,直接在decoder中解析出ActionRunnable对象,同时此对象使用对象缓冲池回收,大量减少了创建对象的数量。

3、动手修改protobuf,能够缓存protobuf对象。

  1. 先从网上找到protoc的源码,动手修改、
  2. 添加reInit方法,这个方法和init不同,他对集合对象只执行clear,不重新创建一个list赋值。
  3. 添加parseFrom2,内部调用reinit方法并执行parseFrom(byte[])的功能。
  4. 修改完成后,gc情况大为改善。

4、动手修改protobuf中的toByteArray().

  1. 查看protobuf代码,发现一个protobuf中共用一个byte[],因此也可以使用threadlocal缓存进行优化。
                  public byte[] toByteArray() {
    			try {
    				final byte[] result = cn.joylab.game.net.cache.ThreadLocalCache.getBytesProtobuf(getSerializedSize());
    				final CodedOutputStream output = CodedOutputStream
    						.newInstance(result);
    				writeTo(output);
    				output.checkNoSpaceLeft();
    				return result;
    			} catch (IOException e) {
    				throw new RuntimeException(
    						"Serializing to a byte array threw an IOException "
    								+ "(should never happen).", e);
    			}
    		}
  2. 发现这几个方法都提供公开调用,这样不必去修改protoc直接从外部生成byte[].
    /**
    	 * 使用了线程缓存,避免了byte[]的大量创建
    	 * @param messageLite
    	 * @return
    	 * @throws IOException
    	 */
    	private byte[] toByteArray(MessageLite messageLite) throws IOException{
    		byte[] result = cn.joylab.game.net.cache.ThreadLocalCache
    				.getBytesProtobuf(messageLite.getSerializedSize());
    		CodedOutputStream output = CodedOutputStream.newInstance(result);
    		messageLite.writeTo(output);
    
    		return result;
    	}

5、动手修改protobuf中的Builder对象。

  1. 这里比较麻烦一些。因为分为builder对象及message对象。message对象由builder进行生成。为了减少对象产生。这里builder和message对象都需要进行缓存。
  2. builder对象比较方便,本身自带clear()方法,从缓存中取出直接clear()后就可以供后续使用
  3. message对象需要手动调用reInit清理
  4. builder在newBuilder中从缓存中取,在builder()后放入缓存,放入时调用clear()
  5. message对象在builder中取,在decoder完成后放入缓存。取出时调用reInit清理。
  6. 在toByteArray因为用的byte[]是缓存的,所以会遇到checkNoSpaceLeft无法通过。这里

2013.10.07 真奇怪,感觉文章丢失了几段内容,记不起来是丢失了哪几部分了。

这里先记录下国庆前了解的内容。

在每秒1万个数据包的压力下,1小时左右,jvm内存耗尽,无限开始fgc。

这里使用了java自带的内存对象分析工具

jmap -dump:format=b,file=dump1.dump 12377

将内存保存为文件。

然后使用:

jhat dump1.dump 可以使用小型http server来查看内存。

这里就是使用了这个工具来发现了netty自带的内存缓存分配造成的问题。jvm分配了3G内存,netty给自己的缓存分配了2G内存导致内存不足。

AbstractByteBufAllocator中的属性:

MAX_CHUNK_SIZE (I) : 1073741824
MIN_PAGE_SIZE (I) : 4096
DEFAULT_MAX_ORDER (I) : 11
DEFAULT_PAGE_SIZE (I) : 8192
DEFAULT_NUM_DIRECT_ARENA (I) : 8
DEFAULT_NUM_HEAP_ARENA (I) : 8

PoolChunk中分配了具体的内存:

16M的byte[].属性为:memoryMap

 

chunkSize (I) : 16777216
freeBytes (I) : 0

freeBytes为0,说明已经被分配了,为什么没有被释放掉。难道没有被使用,这样的话,需要先输出netty的statistics来查看原因。

可能找到内存未释放的原因了。我在decoder中写了两句测试代码,未被注释掉,导致内存不断被消耗而没有被释放掉。

对了,jetty有一个continuation技术,可以实现http短连接的io与业务逻辑处理线程分离,实现时,可以借签(同时和netty的http技术比较一下)。

java性能监控/优化工具

来源自:jworks.idv.tw/java/?p=13

在igame项目中,一个一个测试以上工具,看看效果如何。

log日志远程统一记录

今天终于解决掉了所有遇到的问题,现在将解决方案及遇到的问题在此记录一下,以免遗忘。

这次花费了不少时间,最大的坑是centos6中自带的rsyslog是5.x版本,而官网上早已经发布到了7.x版本,而我一直按7.x文档来学习,真是坑爹啊。

好了,

下面是rsyslog的配置。

配置repo

在/etc/yum.repos.d下新建文件 rsyslog.repo

[rsyslog_v7]
name=Adiscon CentOS-$releasever - local packages for $basearch
baseurl=http://rpms.adiscon.com/v7-stable/epel-$releasever/$basearch
enabled=1
gpgcheck=0
gpgkey=http://rpms.adiscon.com/RPM-GPG-KEY-Adiscon
protect=1

1、打开udp,514端口

vi /etc/rsyslog.conf

注释掉下面两句前面的‘#’号

$ModLoad imudp
$UDPServerRun 514

2、在/etc/rsysconfig.d/下新建文件igame.conf

$EscapeControlCharactersOnReceive off

$template TraditionalFormat2,"%msg%\n"
$template TraditionalFormat3,"%syslogtag% %msg%\n"

        if re_match($syslogtag,'^moon[0-9]+-[a-zA-Z]+.log$') then {
                $ActionFileDefaultTemplate TraditionalFormat2
                $template DynFile2,"/opt/logs/router-log/%syslogtag:R,ERE,1,DFLT:(.*)-.*.log--end%/%timegenerated:1:10:date-rfc3339%/%syslogtag:R,ERE,1,DFLT:.*-(.*).log--end%.log"
                *.* -?DynFile2
                stop
        } else {
                if re_match($syslogtag,'^moon[0-9]+$') then {
                        $ActionFileDefaultTemplate TraditionalFormat3
                        $template DynFile3,"/opt/logs/router-log/moon-alert.log"
                        *.* -?DynFile3
                        stop
                }

        }

 

$EscapeControlCharactersOnReceive off

这句用来避免log中的’\t’被当做控制命令被转换为#011

logback中的配置

	<appender name="journal" class="ch.qos.logback.classic.net.SyslogAppender">
		<syslogHost>${logback.syslogHost}</syslogHost>
		<port>${logback.syslogPort}</port>
		<facility>LOCAL7</facility>
		<suffixPattern>${MODULE}-journal.log %date [%thread] %logger{0}\(%L\) %p %msg
		</suffixPattern>
	</appender>

appfuse中的远程认证

最近两天查看了appfuse中的源码,利用断点追踪了解里面的security内部实现。

如果玩家登陆成功后,会在session放一个securityContext对象,key为HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY=”SPRING_SECURITY_CONTEXT”;

以下为认证成功后登陆代码:

	protected void loginSuccess(User user) {
		HttpSession session = getSession();
		SecurityContext securityContext = SecurityContextHolder
				.createEmptyContext();
		UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
				user, "password", user.getAuthorities());
		// result.setDetails(user);
		securityContext.setAuthentication(result);

		session.setAttribute(SECURITY_CONTEXT_KEY, securityContext);
	}

log日志远程统一记录。

1、面临的问题

1、多个server后,日志分别存储在每个server的log目录下,管理起来不方便。
2、日志会引起的磁盘寻道及io会消耗系统性能

2、解决方案

初步设想了以下几种方案:
1、每个server生成的log每天定时上传至一个统一接收日志的服务器。但该方案无法解决以上中的问题2。
2、使用log4j2中的SocketAppender将日志输出到远程socket,在远程socket进行接收。看起来同时解决掉了问题1和2,但是未找到对应的远程接收服务器中间件,还需要自己来进行code。
3、在方案2的基础上,查找现成的可远程接收日志的中间件,有apache flume及linux系统自带的rsyslog入围。apache flume需要安装进行维护等,rsyslog在centos自带的组件,可以方便配置即可启用。而且rsyslog支持将日志写入文件或写入db中,配 置方便,如果以后需要日志存入db,修改也非常方便。

以上决定采用方案3。

3、具体操作过程

1、打开rsyslog的远程接收接口

vi rsyslog.conf

注释掉以下语句前面的’#’号:

$ModLoad imudp

$UDPServerRun 514

重启 rsyslog:
service rsyslog restart

2、配置日志发送至rsyslog。

最初采用的是log4j2,但发现log4j2中暂时不支持SyslogAppender中的pattern。
于是项目转为使用logback。

以下是一个logback的例子配置:

<appender name="default" class="ch.qos.logback.classic.net.SyslogAppender">
		<syslogHost>192.168.17.17</syslogHost>
		<port>514</port>
		<suffixPattern>A</suffixPattern>
		<facility>LOCAL7</facility>
		<suffixPattern>%date ${MODULE} igame.log [%thread] %logger{0}\(%L\) %p %msg
		</suffixPattern>
	</appender>

	<appender name="default_ASYNC" class="ch.qos.logback.classic.AsyncAppender">
		<queueSize>1024</queueSize>
		<appender-ref ref="default" />
	</appender>

需要注意的是结点suffixPattern,MODULE 是从环境变量取出的值,igame.log标志存储的文件名。
3、配置rsyslod对日志进行router

 $template DynFile,"/var/log/router-log/%msg:F,32:3%/%timegenerated:1:10:date-rfc3339%/%msg:F,32:4%"

:source , \!isequal , "localhost" ?DynFile

:source , \!isequal , "localhost" ~

以上代表接收到的日志存储在 /var/log/router-log/MODULE/日期/指定的文件名
重启rsyslog.
以上测试OK。

4、可改进的地方

tomcat7中的access log不支持直接发往rsyslog,改为logback输出access.log,成功,但用logback输出到rsyslog时出问题,一直无法接收到日志。
这里可以考虑下使用log4j2把日志输出到rsyslog中

现在遇到第二个问题了,我需要logback内的更多属性来router日志。现在的情况是exception日志,无法router.

5、小贴士

$EscapeControlCharactersOnReceive off

可以避免\t被转换为#011

2013.08.21记:centos中rsyslog是v5版本,最新版本是v7版本,怪不得遇到各种奇怪的问题,升级为最新的v7版本:http://www.rsyslog.com/rhelcentos-rpms/

tomcat7 下使用logback来记录access

1、先下载logback,可以在官网地址下载:

http://logback.qos.ch/download.html

2、解压后,将logback-access.jar与logback-core.jar复制到$TOMCAT_HOME/lib/目录下

修改$TOMCAT_HOME/conf/server.xml添加:

 

<Valve className="ch.qos.logback.access.tomcat.LogbackValve"/>

必须添加在<Engine>或<Host>结点下。

3、logback默认会在$TOMCAT_HOME/conf下查找文件 logback-access.xml

新建 logback-access.xml

<configuration>
  <!-- always a good activate OnConsoleStatusListener -->
  <statusListener class="ch.qos.logback.core.status.OnConsoleStatusListener" />  

  <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <file>access.log</file>
    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
      <fileNamePattern>access.%d{yyyy-MM-dd}.log.zip</fileNamePattern>
    </rollingPolicy>

    <encoder>
      <pattern>combined</pattern>
    </encoder>
  </appender>
 
  <appender-ref ref="FILE" />
</configuration>

OK,重启tomcat即可。

遇到的问题:未查找到如何将access.log输出到syslog中的方式。直接使用logback中的syslogappender,服务器接收不到日志。

参考:http://logback.qos.ch/access.html

log日志发送到远程服务器

前段时间看了一篇io对性能的影响的文章,记不起来在哪里看到的了。

里面对我印象比较深的是,硬盘操作(寻道、read、write)消耗的性能很可观。

这里试着把所有的日志通过内网直接输出到专用的一个日志服务器。这样可以避免在游戏服上记录日志,而且也方便统一管理日志。

在网上查找了一下相关文章,log4j2支持将日志写向远程socket或syslog。

如果用socket,还需要自己来写代码接收,所以决定使用syslog。

而且centos6以后都自带rsyslog(syslog的增强版),配置起来也方便。

以下是相关步骤:

rsyslog日志

1、编辑 /etc/rsyslog.conf

注释掉下两句最前面的#,这两句的作用是打开514端口,使用socket来接收日志。

# Provides TCP syslog reception
$ModLoad imtcp
$InputTCPServerRun 514

修改完后重启 rsyslog

service rsyslog restart

修改防火墙,对内网开放514端口

2、发愁啊,怎么格式化日志。

看来学艺不精,使用log4j2,竟然不知道如何格式化。

现在把log4j2改为logback ( logback的syslogappender使用udp发送,注意把第一步的tcp改为udp)。

以下需要查看rsyslog如何将日志按天进行分割及按来源分别存储在不同的目录下。

syslog可以使用%msg:F:1%来进行匹配fileds,fields中使用’\t’分隔的字符串,分隔符可以自定义。

比如要使用空格为分隔符即为:%msg:F,32:1%

除了使用分隔符rsyslog还支持正则表达式进行匹配,但匹配相对慢一些。

下面我们来看一个具体路由日志的例子:

$template DynFile,"/var/log/router-log/%msg:F,32:3%/%timegenerated:1:10:date-rfc3339%/%msg:F,32:4%"

:source , !isequal , "localhost" ?DynFile

:source , !isequal , "localhost" ~

以上面的代意思是:

将消息存储在 /var/log/router-log/第二个filed/当前日期(天)/第三个field

这样,我们在定义logback的日志时,使用以下格式:

	<appender name="job" class="ch.qos.logback.classic.net.SyslogAppender">
		<syslogHost>192.168.17.17</syslogHost>
		<port>514</port>
		<suffixPattern>A</suffixPattern>
		<facility>LOCAL7</facility>
		<suffixPattern>%date ${MODULE} job.log [%thread] %logger{0}\(%L\) %p %msg
		</suffixPattern>
	</appender>

<suffixPattern>中的 ${MODULE}指定存储所在的目录,是从环境变量中获取。

job.log是存储日志的具体文件名。

你可能会问第一个field为什么是日期,我试过了%msg:F,32:1%,但无论如何尝试都无法取到第一个field的内容。只好从第二个field开始取。

当一个临时解决方案吧。

 


Warning: Use of undefined constant XML - assumed 'XML' (this will throw an Error in a future version of PHP) in /opt/wordpress/wp-content/plugins/wp-syntaxhighlighter/wp-syntaxhighlighter.php on line 1048