月度存档: 八月 2013

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>

flickr主键生成策略

http://code.flickr.net/2010/02/08/ticket-servers-distributed-unique-primary-keys-on-the-cheap/

 

测试一下主键生成的性能,通过代码调用生成10万次主键为例。

测试的机器:8G内存,自身跑着7个tomcat,每个分配512M内存。mysql也安装在此机器上。

1、innodb无事务(never)

单线程:746秒,每个请求7.4秒

10线程:540秒,每个请求5.4秒

50线程:耗时太长,2449,每个请求24秒,经过查找,发现是数据库连接池最大数量限制为20影响了性能。把最大连接数改为100,更恶性的事件产生了,DB中的线程竟然开始互相竞争起来,导致严重影响性能。

2、innodb事务

3、MYISAM无事务

单线程:476秒,平均每次请求4.7ms

10线程: 244秒,平均每次请求2.4ms

50线程:237秒,平均每秒2.3ms

100线程:235秒,平均每秒2.3ms

4、MYISAM有事务(MYISAM本身不支持事务,只是测试下spring aop的影响)

单线程:在aop事务中,503秒,每个请求5ms,不在aop事务中,446秒,每秒请求4.4

10线程:在aop事务中,238秒,每个请求2.3秒,不在aop事务中,234秒,每个请求2.3秒

结论:InnoDB是并发数达到一定数据后,DB中锁的竞争厉害。但是myisam表倒没有这种问题,并发数在100时也没有遇到问题。

在aop事务中多少会有一点影响,但本身是myisam表,影响在可接受范围内。

可改进的地方:这次测试只是使用一个表,在flicker中,也介绍了如何分表来分担负载。但对这个项目来说,已经足够满足需求了。暂不考虑分表。

 

——————————————————————————————

2015.03.09

附下项目中使用的代码:

package cn.joylab.game.service.impl;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import cn.joylab.game.dao2.Ticket32Dao;
import cn.joylab.game.dao2.Ticket64Dao;
import cn.joylab.game.model.Ticket32;
import cn.joylab.game.model.Ticket64;
import cn.joylab.game.service.TicketManager;

@Component("ticketManager")
public class TicketManagerImpl implements TicketManager {

	private static final int NUM = 1000;

	private AtomicInteger a32 = new AtomicInteger();
	private AtomicInteger a32_ = new AtomicInteger();
	private AtomicInteger b32 = new AtomicInteger();
	private AtomicInteger b32_ = new AtomicInteger();
	private AtomicInteger c32 = new AtomicInteger();
	private AtomicInteger c32_ = new AtomicInteger();
	private AtomicInteger d32 = new AtomicInteger();
	private AtomicInteger d32_ = new AtomicInteger();
	private AtomicInteger e32 = new AtomicInteger();
	private AtomicInteger e32_ = new AtomicInteger();
	private AtomicInteger f32 = new AtomicInteger();
	private AtomicInteger f32_ = new AtomicInteger();
	private AtomicInteger g32 = new AtomicInteger();
	private AtomicInteger g32_ = new AtomicInteger();
	private AtomicInteger h32 = new AtomicInteger();
	private AtomicInteger h32_ = new AtomicInteger();

	private AtomicLong a64 = new AtomicLong();
	private AtomicLong a64_ = new AtomicLong();
	private AtomicLong b64 = new AtomicLong();
	private AtomicLong b64_ = new AtomicLong();
	private AtomicLong c64 = new AtomicLong();
	private AtomicLong c64_ = new AtomicLong();
	private AtomicLong d64 = new AtomicLong();
	private AtomicLong d64_ = new AtomicLong();
	private AtomicLong e64 = new AtomicLong();
	private AtomicLong e64_ = new AtomicLong();
	private AtomicLong f64 = new AtomicLong();
	private AtomicLong f64_ = new AtomicLong();
	private AtomicLong g64 = new AtomicLong();
	private AtomicLong g64_ = new AtomicLong();
	private AtomicLong h64 = new AtomicLong();
	private AtomicLong h64_ = new AtomicLong();

	public int getNewId32(String stub) {
		if ("a".equals(stub)) {
			return getNewId32_1(stub, a32, a32_);
		} else if ("b".equals(stub)) {
			return getNewId32_1(stub, b32, b32_);
		} else if ("c".equals(stub)) {
			return getNewId32_1(stub, c32, c32_);
		} else if ("d".equals(stub)) {
			return getNewId32_1(stub, d32, d32_);
		} else if ("e".equals(stub)) {
			return getNewId32_1(stub, e32, e32_);
		} else if ("f".equals(stub)) {
			return getNewId32_1(stub, f32, f32_);
		} else if ("g".equals(stub)) {
			return getNewId32_1(stub, g32, g32_);
		} else if ("h".equals(stub)) {
			return getNewId32_1(stub, h32, h32_);
		} else {
			log.error("未解析的字符串:" + stub);
		}
		return -1;
	}

	private int getNewId32_1(String stub, AtomicInteger a, AtomicInteger b) {
		if (a.get() == 0) {
			int id = getNewId32_2(stub);
			a.set(id);
			b.set(NUM);
			return id * NUM;
		}
		int i = b.decrementAndGet();
		if (i > 0) {
			return a.get() * NUM + (NUM - i);
		} else {
			int id = getNewId32_2(stub);
			a.set(id);
			b.set(NUM);
			return id * NUM;
		}
	}

	private int getNewId32_2(String stub) {
		Ticket32 ticket32 = new Ticket32();
		ticket32.setStub(stub);
		if ("a".equals(stub)) {
			ticket32Dao.getNewId_a(ticket32);
		} else if ("b".equals(stub)) {
			ticket32Dao.getNewId_b(ticket32);
		} else if ("c".equals(stub)) {
			ticket32Dao.getNewId_c(ticket32);
		} else if ("d".equals(stub)) {
			ticket32Dao.getNewId_d(ticket32);
		} else if ("e".equals(stub)) {
			ticket32Dao.getNewId_e(ticket32);
		} else if ("f".equals(stub)) {
			ticket32Dao.getNewId_f(ticket32);
		} else if ("g".equals(stub)) {
			ticket32Dao.getNewId_g(ticket32);
		} else if ("h".equals(stub)) {
			ticket32Dao.getNewId_h(ticket32);
		} else {
			log.error("未解析的字符串:" + stub);
		}
		return ticket32.getId();
	}

	public long getNewId64(String stub) {
		if ("a".equals(stub)) {
			return getNewId64_1(stub, a64, a64_);
		} else if ("b".equals(stub)) {
			return getNewId64_1(stub, b64, b64_);
		} else if ("c".equals(stub)) {
			return getNewId64_1(stub, c64, c64_);
		} else if ("d".equals(stub)) {
			return getNewId64_1(stub, d64, d64_);
		} else if ("e".equals(stub)) {
			return getNewId64_1(stub, e64, e64_);
		} else if ("f".equals(stub)) {
			return getNewId64_1(stub, f64, f64_);
		} else if ("g".equals(stub)) {
			return getNewId64_1(stub, g64, g64_);
		} else if ("h".equals(stub)) {
			return getNewId64_1(stub, h64, h64_);
		} else {
			log.error("未解析的字符串:" + stub);
		}
		return -1;
	}

	private long getNewId64_1(String stub, AtomicLong a, AtomicLong b) {
		if (a.get() == 0) {
			long id = getNewId64_2(stub);
			a.set(id);
			b.set(NUM);
			return id * NUM;
		}
		long i = b.decrementAndGet();
		if (i > 0) {
			return a.get() * NUM + (NUM - i);
		} else {
			long id = getNewId64_2(stub);
			a.set(id);
			b.set(NUM);
			return id * NUM;
		}
	}

	private long getNewId64_2(String stub) {
		Ticket64 ticket64 = new Ticket64();
		ticket64.setStub(stub);
		if ("a".equals(stub)) {
			ticket64Dao.getNewId_a(ticket64);
		} else if ("b".equals(stub)) {
			ticket64Dao.getNewId_b(ticket64);
		} else if ("c".equals(stub)) {
			ticket64Dao.getNewId_c(ticket64);
		} else if ("d".equals(stub)) {
			ticket64Dao.getNewId_d(ticket64);
		} else if ("e".equals(stub)) {
			ticket64Dao.getNewId_e(ticket64);
		} else if ("f".equals(stub)) {
			ticket64Dao.getNewId_f(ticket64);
		} else if ("g".equals(stub)) {
			ticket64Dao.getNewId_g(ticket64);
		} else if ("h".equals(stub)) {
			ticket64Dao.getNewId_h(ticket64);
		} else {
			log.error("未解析的字符串:" + stub);
		}
		return ticket64.getId();
	}

	@Autowired
	private Ticket32Dao ticket32Dao;
	@Autowired
	private Ticket64Dao ticket64Dao;
	private static final Log log = LogFactory.getLog(TicketManagerImpl.class);
}

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开始取。

当一个临时解决方案吧。

 

erlang工具

1、https://github.com/beamspirit/bigwig

erlang蜗牛学习中(五)

非阻塞tcp学习:

参考以下文档:

http://www.trapexit.org/Building_a_Non-blocking_TCP_server_using_OTP_principles

erlang蜗牛学习中(四)

socket,主动模式

被动模式,需要server调用recv时才读取数据。

主动模式,server使用receive来接收消息。

-module(test4).

-export([ttt/0]).

ttt()->
        {ok,Listen}=gen_tcp:listen(8888,[{packet,0},{active,true}]),
        {ok,Socket}=gen_tcp:accept(Listen),
        gen_tcp:close(Listen),
        loop(Socket).

loop(Socket)->
        receive
                {tcp,Socket,Bin} ->
                        io:format("hello tcp,~p~n",[Bin]),
                        loop(Socket);
                {tcp_closed,Socket}->
                        io:format("hello tcp_closed~n")
        end.

erlang蜗牛学习中(三)

test3.erl

理清楚了gen_server和gen_tcp之间的关系。

gen_server仅仅是一个通用模型。如果编写socket,还是需要gen_tcp。

gen_server不仅仅处理c/s,而且module之间也可以使用gen_server来进行交互处理。

 

-module(test3).

-export([ttt/0]).

ttt()->
        start_server().

start_server()->
        {ok,ListenSocket}= gen_tcp:listen(8888,[{packet,0},{active,false}]),
        {ok,Socket}=gen_tcp:accept(ListenSocket),
        {ok,_Bin}=loop(Socket,[]),
        gen_tcp:close(Socket),
        io:format("~p~n",[_Bin]).
loop(Socket,Bs)->
        case gen_tcp:recv(Socket,0) of
                {ok,Packet}->
                        io:format("ok read~p~n",[Packet]),
                        loop(Socket,[Bs,Packet]);
                {error,closed}->
                        io:format("error closed~n"),
                        {ok,Bs};
                {erorr,_Reason}->
                        io:format("error Reason~n"),
                        {ok,Bs}

        end.