作者存档: 朱坤乾 - 第17页

一篇nosql内存数据库的文章

http://www.csdn.net/article/2013-04-11/2814850-877000-tps-with-erlang-and-voltdb

1、voltdb安装很简单,解压即可。

在官网上提到有几点影响性能的系统设置:

http://voltdb.com/docs/EnterpriseReleaseNotes/

 

1.1. Disable Swapping
Swapping is an operating system feature that optimizes memory usage when running multiple processes. However, memory is a critical component of the VoltDB server process. Any contention for memory, including swapping, will have a very negative impact on performance and functionality.

We recommend using dedicated servers and disabling swapping when running the VoltDB database server process. Use the swapoff command to disable swapping on Linux systems. If swapping cannot be disabled for any reason, you can reduce the likelihood of VoltDB being swapped out by setting the kernel parameter vm.swappiness to zero.

1.2. Turn off TCP segmentation offload and generic receive offload if cluster stability is a problem.
There is an issue where, under certain conditions, the use of TCP segmentation offload (TSO) and generic receive offload (GRO) can cause nodes to randomly drop out of a cluster. The symptoms of this problem are that nodes timeout — that is, the rest of the cluster thinks they have failed — although the node is still running and no other network issues (such as a network partition) are the cause.

Disabling TSO and GRO is recommended for any VoltDB clusters that experience such instability. The commands to disable offloading are the following, where N is replaced by the number of the ethernet card:

ethtool -K eth<span class="emphasis"><em>N</em></span> tso off
ethtool -K eth<span class="emphasis"><em>N</em></span> gro off

Note that these commands disable offloading temporarily. You must issue these commands every time the node reboots.

 

 

1.2. Turn off TCP segmentation offload and generic receive offload if cluster stability is a problem.
There is an issue where, under certain conditions, the use of TCP segmentation offload (TSO) and generic receive offload (GRO) can cause nodes to randomly drop out of a cluster. The symptoms of this problem are that nodes timeout — that is, the rest of the cluster thinks they have failed — although the node is still running and no other network issues (such as a network partition) are the cause.

Disabling TSO and GRO is recommended for any VoltDB clusters that experience such instability. The commands to disable offloading are the following, where N is replaced by the number of the ethernet card:

ethtool -K eth<span class="emphasis"><em>N</em></span> tso off
ethtool -K eth<span class="emphasis"><em>N</em></span> gro off

Note that these commands disable offloading temporarily. You must issue these commands every time the node reboots.

erlang gen_server 模版

今天终于理解了gen_server行为模式。

这里放一个模版,以方便自己使用:

-module(test8).

-behaviour(gen_server).

-export([start_link/0,stop_link/0]).

-export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2,code_change/3]).

-define(SERVER,?MODULE).

%% 不知道这个state的作用是什么
-record(state,{}).
%% 给外面调用的。

%%% ----------------------------------------------
%%% 公开的API
%%% 
%%% ----------------------------------------------

start_link()->
        gen_server:start_link({local,?SERVER},?MODULE,[],[]).
stop_link()->
        gen_server:cast(?SERVER,stop).

%%% ---------------------------------------------
%%% 私有函数
%%% ---------------------------------------------

%%% -------------------------------------------
%%% 以下是回调函数
%%% -------------------------------------------

%% Result = {ok,State} | {ok,State,Timeout} | {ok,State,hibernate}
%% | {stop,Reason} | ignore
%% 这里设置timeout为0,根据<<erlang并发编程>>描述,可以让init/1尽快结束,是一个众所周知的技巧。
init([])->
        {ok,#state{},0}.

%% Result = {reply,Reply,NewState} | {reply,Reply,NewState,Timeout}
%%  | {reply,Reply,NewState,hibernate}
%%  | {noreply,NewState} | {noreply,NewState,Timeout}
%%  | {noreply,NewState,hibernate}
%%  | {stop,Reason,Reply,NewState} | {stop,Reason,NewState}
handle_call(Request,From,State)->
        Reply=ok,
        {reply,Reply,State}.

%% Result = {noreply,NewState} | {noreply,NewState,Timeout}
%%  | {noreply,NewState,hibernate}
%%  | {stop,Reason,NewState}
handle_cast(Request,State)->
        case Request of
                stop    -> {stop,normal,State};
                _       -> {noreplay,State}
        end.

%% Result = {noreply,NewState} | {noreply,NewState,Timeout}
%%  | {noreply,NewState,hibernate}
%%  | {stop,Reason,NewState}
handle_info(Info,State)->
        {noreply,State}.
terminate(Reason,State)->
        io:format("server is stopped"),
        ok.
code_change(OldVsn,State,Extra)->
        {ok,State}.

erlang蜗牛学习中(六)

有几多日未写erlang学习代码了。

前一段时间主要做了项目的优化,有几点效果挺不错,这里略提一下。

io很耗时,日志通过udp远程写到其它服务器上,会大量减少cpu的消耗。

nosql概念在游戏开中很合适,使用json或protobuf直接存储在key-value数据库中非常方便,采用后基本上无需任何持久层的工作量。而且nosql的设计方式对某些特定场景的处理可大幅提高性能,比如:以前查询玩家的信件,直接使用sql查询,需要查询整张表,采用nosql的设计方式,在玩家中存储一个数组,里面是信件的id,可以直接根据id去查询数据。

好了,来看erlang test7

-module(test7).

-export([start/0]).

start()->
        {ok,ListenSocket} = gen_tcp:listen(8889,[binary,{packet,2},{reuseaddr,true},{active,true}]),
        listen_tcp(ListenSocket),
        gen_tcp:close(ListenSocket),
        io:format("start end.").

listen_tcp(ListenSocket)->
        {ok,Socket}=gen_tcp:accept(ListenSocket),
        spawn_link(fun()-> socket_rec(Socket) end),
        listen_tcp(ListenSocket).

socket_rec(Socket)->
        receive
                {tcp,Socket,Data}->
                        io:format("receive data ~n"),
                        socket_rec(Socket);
                {tcp_closed,Socket}->
                        io:format("tcp_closed ~n"),
                        gen_tcp:close(Socket);
                {tcp_error,Socket,Reason}->
                        io:format("tcp_error ~n"),
                        gen_tcp:close(Socket) 

        end.

test7,打开一个socket监听端口,然后开启一个线程去处理接收数据。

好了,继续测试半主动式:接一个包,设置socket为不接收状态,然后处理完包后,再继续读取数据包。

如果开启了新的线程来接收数据。别忘记调用:

controlling_process(Socket, Pid) -> ok | {error, Reason}

Types:

Socket = socket()
Pid = pid()
Reason = closed | not_owner | inet:posix()

 

Assigns a new controlling process Pid to Socket. The controlling process is the process which receives messages from the socket. If called by any other process than the current controlling process, {error, not_owner} is returned.

他的作用是把一个新的线程和这个socket绑定,这样新线程可以接收到socket的数据。

也让我困惑了两天为什么接收不到数据。

服务器性能优化

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

测试环境:

客户端:开始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>

flickr主键生成策略

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


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