from:http://blog.csdn.net/ugg/article/details/41894947


背景
在很多互联网产品应用中,有些场景需要加锁处理,比如:秒杀,全局递增ID,楼层生成等等。大部分的解决方案是基于DB实现的,Redis为单进程单线程模式,采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系。其次Redis提供一些命令SETNX,GETSET,可以方便实现分布式锁机制。

Redis命令介绍
使用Redis实现分布式锁,有两个重要函数需要介绍

SETNX命令(SET if Not eXists)
语法:
SETNX key value
功能:
当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

GETSET命令
语法:
GETSET key value
功能:
将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。

GET命令
语法:
GET key
功能:
返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。

DEL命令
语法:
DEL key [KEY …]
功能:
删除给定的一个或多个 key ,不存在的 key 会被忽略。

兵贵精,不在多。分布式锁,我们就依靠这四个命令。但在具体实现,还有很多细节,需要仔细斟酌,因为在分布式并发多进程中,任何一点出现差错,都会导致死锁,hold住所有进程。

加锁实现

SETNX 可以直接加锁操作,比如说对某个关键词foo加锁,客户端可以尝试
SETNX foo.lock <current unix time>

如果返回1,表示客户端已经获取锁,可以往下操作,操作完成后,通过
DEL foo.lock

命令来释放锁。
如果返回0,说明foo已经被其他客户端上锁,如果锁是非堵塞的,可以选择返回调用。如果是堵塞调用调用,就需要进入以下个重试循环,直至成功获得锁或者重试超时。理想是美好的,现实是残酷的。仅仅使用SETNX加锁带有竞争条件的,在某些特定的情况会造成死锁错误。

处理死锁

在上面的处理方式中,如果获取锁的客户端端执行时间过长,进程被kill掉,或者因为其他异常崩溃,导致无法释放锁,就会造成死锁。所以,需要对加锁要做时效性检测。因此,我们在加锁时,把当前时间戳作为value存入此锁中,通过当前时间戳和Redis中的时间戳进行对比,如果超过一定差值,认为锁已经时效,防止锁无限期的锁下去,但是,在大并发情况,如果同时检测锁失效,并简单粗暴的删除死锁,再通过SETNX上锁,可能会导致竞争条件的产生,即多个客户端同时获取锁。

C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,获得foo.lock的时间戳,通过比对时间戳,发现锁超时。
C2 向foo.lock发送DEL命令。
C2 向foo.lock发送SETNX获取锁。
C3 向foo.lock发送DEL命令,此时C3发送DEL时,其实DEL掉的是C2的锁。
C3 向foo.lock发送SETNX获取锁。

此时C2和C3都获取了锁,产生竞争条件,如果在更高并发的情况,可能会有更多客户端获取锁。所以,DEL锁的操作,不能直接使用在锁超时的情况下,幸好我们有GETSET方法,假设我们现在有另外一个客户端C4,看看如何使用GETSET方式,避免这种情况产生。

C1获取锁,并崩溃。C2和C3调用SETNX上锁返回0后,调用GET命令获得foo.lock的时间戳T1,通过比对时间戳,发现锁超时。
C4 向foo.lock发送GESET命令,
GETSET foo.lock <current unix time>
并得到foo.lock中老的时间戳T2

如果T1=T2,说明C4获得时间戳。
如果T1!=T2,说明C4之前有另外一个客户端C5通过调用GETSET方式获取了时间戳,C4未获得锁。只能sleep下,进入下次循环中。

现在唯一的问题是,C4设置foo.lock的新时间戳,是否会对锁产生影响。其实我们可以看到C4和C5执行的时间差值极小,并且写入foo.lock中的都是有效时间错,所以对锁并没有影响。
为了让这个锁更加强壮,获取锁的客户端,应该在调用关键业务时,再次调用GET方法获取T1,和写入的T0时间戳进行对比,以免锁因其他情况被执行DEL意外解开而不知。以上步骤和情况,很容易从其他参考资料中看到。客户端处理和失败的情况非常复杂,不仅仅是崩溃这么简单,还可能是客户端因为某些操作被阻塞了相当长时间,紧接着 DEL 命令被尝试执行(但这时锁却在另外的客户端手上)。也可能因为处理不当,导致死锁。还有可能因为sleep设置不合理,导致Redis在大并发下被压垮。最为常见的问题还有

GET返回nil时应该走那种逻辑?

第一种走超时逻辑
C1客户端获取锁,并且处理完后,DEL掉锁,在DEL锁之前。C2通过SETNX向foo.lock设置时间戳T0 发现有客户端获取锁,进入GET操作。
C2 向foo.lock发送GET命令,获取返回值T1(nil)。
C2 通过T0>T1+expire对比,进入GETSET流程。
C2 调用GETSET向foo.lock发送T0时间戳,返回foo.lock的原值T2
C2 如果T2=T1相等,获得锁,如果T2!=T1,未获得锁。

第二种情况走循环走setnx逻辑
C1客户端获取锁,并且处理完后,DEL掉锁,在DEL锁之前。C2通过SETNX向foo.lock设置时间戳T0 发现有客户端获取锁,进入GET操作。
C2 向foo.lock发送GET命令,获取返回值T1(nil)。
C2 循环,进入下一次SETNX逻辑

两种逻辑貌似都是OK,但是从逻辑处理上来说,第一种情况存在问题。当GET返回nil表示,锁是被删除的,而不是超时,应该走SETNX逻辑加锁。走第一种情况的问题是,正常的加锁逻辑应该走SETNX,而现在当锁被解除后,走的是GETST,如果判断条件不当,就会引起死锁,很悲催,我在做的时候就碰到了,具体怎么碰到的看下面的问题

GETSET返回nil时应该怎么处理?

C1和C2客户端调用GET接口,C1返回T1,此时C3网络情况更好,快速进入获取锁,并执行DEL删除锁,C2返回T2(nil),C1和C2都进入超时处理逻辑。
C1 向foo.lock发送GETSET命令,获取返回值T11(nil)。
C1 比对C1和C11发现两者不同,处理逻辑认为未获取锁。
C2 向foo.lock发送GETSET命令,获取返回值T22(C1写入的时间戳)。
C2 比对C2和C22发现两者不同,处理逻辑认为未获取锁。

此时C1和C2都认为未获取锁,其实C1是已经获取锁了,但是他的处理逻辑没有考虑GETSET返回nil的情况,只是单纯的用GET和GETSET值就行对比,至于为什么会出现这种情况?一种是多客户端时,每个客户端连接Redis的后,发出的命令并不是连续的,导致从单客户端看到的好像连续的命令,到Redis server后,这两条命令之间可能已经插入大量的其他客户端发出的命令,比如DEL,SETNX等。第二种情况,多客户端之间时间不同步,或者不是严格意义的同步。

时间戳的问题

我们看到foo.lock的value值为时间戳,所以要在多客户端情况下,保证锁有效,一定要同步各服务器的时间,如果各服务器间,时间有差异。时间不一致的客户端,在判断锁超时,就会出现偏差,从而产生竞争条件。
锁的超时与否,严格依赖时间戳,时间戳本身也是有精度限制,假如我们的时间精度为秒,从加锁到执行操作再到解锁,一般操作肯定都能在一秒内完成。这样的话,我们上面的CASE,就很容易出现。所以,最好把时间精度提升到毫秒级。这样的话,可以保证毫秒级别的锁是安全的。

分布式锁的问题

1:必要的超时机制:获取锁的客户端一旦崩溃,一定要有过期机制,否则其他客户端都降无法获取锁,造成死锁问题。
2:分布式锁,多客户端的时间戳不能保证严格意义的一致性,所以在某些特定因素下,有可能存在锁串的情况。要适度的机制,可以承受小概率的事件产生。
3:只对关键处理节点加锁,良好的习惯是,把相关的资源准备好,比如连接数据库后,调用加锁机制获取锁,直接进行操作,然后释放,尽量减少持有锁的时间。
4:在持有锁期间要不要CHECK锁,如果需要严格依赖锁的状态,最好在关键步骤中做锁的CHECK检查机制,但是根据我们的测试发现,在大并发时,每一次CHECK锁操作,都要消耗掉几个毫秒,而我们的整个持锁处理逻辑才不到10毫秒,玩客没有选择做锁的检查。
5:sleep学问,为了减少对Redis的压力,获取锁尝试时,循环之间一定要做sleep操作。但是sleep时间是多少是门学问。需要根据自己的Redis的QPS,加上持锁处理时间等进行合理计算。
6:至于为什么不使用Redis的muti,expire,watch等机制,可以查一参考资料,找下原因。

锁测试数据

未使用sleep
第一种,锁重试时未做sleep。单次请求,加锁,执行,解锁时间 


可以看到加锁和解锁时间都很快,当我们使用

ab -n1000 -c100 'http://sandbox6.wanke.etao.com/test/test_sequence.php?tbpm=t'
AB 并发100累计1000次请求,对这个方法进行压测时。 


我们会发现,获取锁的时间变成,同时持有锁后,执行时间也变成,而delete锁的时间,将近10ms时间,为什么会这样?
1:持有锁后,我们的执行逻辑中包含了再次调用Redis操作,在大并发情况下,Redis执行明显变慢。
2:锁的删除时间变长,从之前的0.2ms,变成9.8ms,性能下降近50倍。
在这种情况下,我们压测的QPS为49,最终发现QPS和压测总量有关,当我们并发100总共100次请求时,QPS得到110多。当我们使用sleep时

使用Sleep时

单次执行请求时

我们看到,和不使用sleep机制时,性能相当。当时用相同的压测条件进行压缩时 

获取锁的时间明显变长,而锁的释放时间明显变短,仅是不采用sleep机制的一半。当然执行时间变成就是因为,我们在执行过程中,重新创建数据库连接,导致时间变长的。同时我们可以对比下Redis的命令执行压力情况 

上图中细高部分是为未采用sleep机制的时的压测图,矮胖部分为采用sleep机制的压测图,通上图看到压力减少50%左右,当然,sleep这种方式还有个缺点QPS下降明显,在我们的压测条件下,仅为35,并且有部分请求出现超时情况。不过综合各种情况后,我们还是决定采用sleep机制,主要是为了防止在大并发情况下把Redis压垮,很不行,我们之前碰到过,所以肯定会采用sleep机制。

参考资料

http://www.worlduc.com/FileSystem/18/2518/590664/9f63555e6079482f831c8ab1dcb8c19c.pdf
http://redis.io/commands/setnx
http://www.blogjava.net/caojianhua/archive/2013/01/28/394847.html

版权声明:本文为博主原创文章,未经博主允许不得转载。

posted @ 2015-08-31 15:22 小马歌 阅读(290) | 评论 (0)编辑 收藏
 

先描述一下问题,多个服务器实现的负载均衡,每个服务器存储在自己的硬盘里。但是现在需要对日志做统一的分析,在多个服务器上统计就麻烦了。思路是把日志统一到一台日志服务器上,再统一做统计分析。怎么统一到一台服务器上,说实话没有特别好的思路,最后尝试了log4j的SocketAppender。查了不少网络资源,都说的有些不明了,还是得亲自尝试之后才见分晓。

1、客户端的配置

客户端的配置比较简单,只需要告诉log4j需要监听哪个远程服务器的哪个端口即可。直接在log4j.properties里直接配置就好。

  1. <span style="font-size:12px;">log4j.appender.logs=org.apache.log4j.DailyRollingFileAppender  
  2. log4j.appender.logs.File = /data/logs/request/logs.log  
  3. log4j.appender.logs.layout = org.apache.log4j.PatternLayout  
  4. log4j.appender.logs.layout.ConversionPattern=%d [%t] - %m%n  
  5. log4j.appender.logs.DatePattern='.'yyyy-MM-dd'.log'  
  6.   
  7. log4j.appender.socket=org.apache.log4j.net.SocketAppender  
  8. log4j.appender.socket.RemoteHost=172.16.2.152  
  9. log4j.appender.socket.Port=4560  
  10. log4j.appender.socket.LocationInfo=true  
  11. #下面这两句感觉没用  
  12. log4j.appender.socket.layout=org.apache.log4j.PatternLayout  
  13. log4j.appender.socket.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%t%m%n  
  14.   
  15. #将日志写入本地和远程日志服务器  
  16. log4j.logger.com.test.core.filter =DEBUG,socket,logs</span>  
 

2、日志服务器的配置

日志服务器需要单独启动一个java进程,接收客户端给自己发送的socket请求。Log4j提供了org.apache.log4j.net.SocketServer类,直接运行其main函数就行了(当然也可以自己写啦)。

java -cp /log4jsocket/serverConfig/log4j-1.2.16.jarorg.apache.log4j.net.SocketServer 4560 /log4jsocket/log4jserver.properties /log4jsocket/clientConfig

/log4jsocket/serverConfig/log4j-1.2.16.jar是log4j jar包存放的位置,org.apache.log4j.net.SocketServer需要三个参数:

1)4560 是监听的端口号

2)/log4jsocket/log4jserver.properties 是记录日志服务器的日志的配置文件

3)/log4jsocket/clientConfig 是客户端配置文件所在的目录(注意是目录)。

着重说一下org.apache.log4j.net.SocketServer的第三个参数,这个文件夹下配置的是各个客户端的日志的配置。配置文件以.lcf结尾,文件名可以用客户端的IP命名,log4j会自己找发送请求的客户端IP对应的那个配置文件,如172.16.2.46服务器发送的socket请求会寻找172.16.2.46.lcf配置文件,并根据配置将日志写入对应的文件。

  1. <span style="font-size:12px;">#注意logger后面的值要与client的值相同  
  2. log4j.logger.com.test.core.filter=DEBUG,localLogs  
  3.    
  4. log4j.appender.localLogs=org.apache.log4j.DailyRollingFileAppender  
  5. log4j.appender.localLogs.File=/data/logs/request/172.16.2.46/logs.log  
  6. log4j.appender.localLogs.layout=org.apache.log4j.PatternLayout  
  7. log4j.appender.localLogs.layout.ConversionPattern=%d [%t] - %m%n  
  8. log4j.appender.localLogs.DatePattern='.'yyyy-MM-dd'.log'  
  9. </span>  


这样做的好处是可以根据不同客户端,将日志写入不同的文件夹下的。

其实,配置过程就这么简单,但是当你这么做之后,你会发现运行org.apache.log4j.net.SocketServer后,客户端向日志服务器发送请求时,会报找不到.lcf文件的错误,得不到想要的结果。原因出在org.apache.log4j.net.SocketServer代码中的一个小bug。 

  1. <span style="font-size:12px;">LoggerRepository configureHierarchy(InetAddress inetAddress)  
  2.   {  
  3.     cat.info("Locating configuration file for " + inetAddress);  
  4.   
  5.     String s = inetAddress.toString();  
  6.     int i = s.indexOf("/");  
  7.     if (i == -1) {  
  8.       cat.warn("Could not parse the inetAddress [" + inetAddress + "]. Using default hierarchy.");  
  9.   
  10.       return genericHierarchy();  
  11.     }  
  12.     String key = s.substring(0,i);  
  13.   
  14.     File configFile = new File(this.dir, key + CONFIG_FILE_EXT);  
  15.     if (configFile.exists()) {  
  16.       Hierarchy h = new Hierarchy(new RootLogger(Level.DEBUG));  
  17.       this.hierarchyMap.put(inetAddress, h);  
  18.   
  19.       new PropertyConfigurator().doConfigure(configFile.getAbsolutePath(), h);  
  20.   
  21.       return h;  
  22.     }  
  23.     cat.warn("Could not find config file [" + configFile + "].");  
  24.     return genericHierarchy();  
  25.   }</span>  

String key = s.substring(0, i);换成String key = s.substring(i+1);就好了。这段代码是解析IP地址,然后寻找对应IP命名的.lcf配置文件;如果找不到,则解析默认的generic.lcf。由于截取的错误,导致找不到172.16.2.46.lcf,文件夹下又没有generic.lcf,所以会抛异常。

org.apache.log4j.net.SocketServer代码中的另外一个bug是,只能接收来自一台客户端的日志请求,一旦客户端停止运行,SocketServer也将关闭。查看代码:

  1. public static void main(String[] argv)  
  2.   {     
  3.       if (argv.length == 3)  
  4.       init(argv[0], argv[1], argv[2]);  
  5.     else  
  6.       usage("Wrong number of arguments.");  
  7.     try  
  8.     {  
  9.         cat.info("Listening on port " + port);  
  10.         ServerSocket serverSocket = new ServerSocket(port);  
  11.         cat.info("Waiting to accept a new client.");  
  12.     Socket socket = serverSocket.accept();  
  13.     InetAddress inetAddress = socket.getInetAddress();  
  14.     cat.info("Connected to client at " + inetAddress);  
  15.       
  16.     LoggerRepository h = (LoggerRepository)server.hierarchyMap.get(inetAddress);  
  17.     if (h == null) {  
  18.             h = server.configureHierarchy(inetAddress);  
  19.     }  
  20.       
  21.     cat.info("Starting new socket node.");  
  22.     new Thread(new SocketNode(socket, h)).start();  
  23.       }  
  24.     catch (Exception e)  
  25.     {  
  26.       e.printStackTrace();  
  27.     }  
  28.   }  

问题出在只建立了一个socket连接就不在accept了,加上while循环问题就解决了。

  1. ServerSocket serverSocket = new ServerSocket(port);  
  2. while(true){  
  3.  cat.info("Waiting to accept a new client.");  
  4.  Socket socket = serverSocket.accept();  
  5.  InetAddress inetAddress = socket.getInetAddress();  
  6.  cat.info("Connected to client at " + inetAddress);  
  7.   
  8.  LoggerRepository h = (LoggerRepository)server.hierarchyMap.get(inetAddress);  
  9.  if (h == null) {  
  10.    h = server.configureHierarchy(inetAddress);  
  11.  }  
  12.   
  13.  cat.info("Starting new socket node.");  
  14.  new Thread(new SocketNode(socket, h)).start();  
  15. }  



 

 

好了。Log4j的配置到此结束。

最后一个问题,日志服务器是linux,需要有一个统一的start、shutdown命令来启动和关闭org.apache.log4j.net.SocketServer。那就需要些shell命令了,下面这段代码参考了http://www.cnblogs.com/baibaluo/archive/2011/08/31/2160934.html

catalina.sh

  1. <span style="font-size:12px;">#!/bin/bash  
  2. #端口  
  3. LISTEN_PORT=4560  
  4. #服务端log4j配置文件  
  5. SERVER_CONFIG=/log4jsocket/server.properties  
  6. #客户端的配置  
  7. CLIENT_CONFIG_DIR=/log4jsocket/clientConfig  
  8.   
  9. #Java程序所在的目录(classes的上一级目录)  
  10. APP_HOME=/opt/log4jsocket/serverConfig   
  11. #需要启动的Java主程序(main方法类)  
  12. APP_MAINCLASS=org.apache.log4j.net.SocketServer  
  13.    
  14. #拼凑完整的classpath参数,包括指定lib目录下所有的jar  
  15. CLASSPATH=$APP_HOME  
  16. for i in "$APP_HOME"/*.jar; do     
  17.     CLASSPATH="$CLASSPATH":"$i"  
  18. done  
  19.   
  20. #JDK所在路径  
  21. JAVA_HOME="/opt/jdk1.6.0_30"   
  22. #执行程序启动所使用的系统用户,考虑到安全,推荐不使用root帐号  
  23. RUNNING_USER=root  
  24.    
  25. #java虚拟机启动参数  
  26. JAVA_OPTS="-ms512m -mx512m -Xmn256m -Djava.awt.headless=true -XX:MaxPermSize=128m"   
  27.   
  28. #初始化psid变量(全局)  
  29. psid=0  
  30.    
  31. checkpid() {  
  32.    javaps=`$JAVA_HOME/bin/jps -l | grep $APP_MAINCLASS`  
  33.    
  34.    if [ -n "$javaps" ]; then  
  35.       psid=`echo $javaps | awk '{print $1}'`  
  36.    else  
  37.       psid=0  
  38.    fi  
  39. }  
  40.   
  41. start() {  
  42.    checkpid  
  43.    
  44.    if [ $psid -ne 0 ]; then  
  45.       echo "================================"  
  46.       echo "warn: $APP_MAINCLASS already started! (pid=$psid)"  
  47.       echo "================================"  
  48.    else  
  49.       echo -n "Starting $APP_MAINCLASS ..."  
  50.       JAVA_CMD="nohup $JAVA_HOME/bin/java -classpath $CLASSPATH $APP_MAINCLASS $LISTEN_PORT $SERVER_CONFIG $CLIENT_CONFIG_DIR >/dev/null 2>&1 &"  
  51.       su - $RUNNING_USER -c "$JAVA_CMD"  
  52.       checkpid  
  53.       if [ $psid -ne 0 ]; then  
  54.          echo "(pid=$psid) [OK]"  
  55.       else  
  56.          echo "[Failed]"  
  57.       fi  
  58.    fi  
  59. }  
  60.   
  61. stop() {  
  62.    checkpid  
  63.    
  64.    if [ $psid -ne 0 ]; then  
  65.       echo -n "Stopping $APP_MAINCLASS ...(pid=$psid) "  
  66.       su - $RUNNING_USER -c "kill -9 $psid"  
  67.       if [ $? -eq 0 ]; then  
  68.          echo "[OK]"  
  69.       else  
  70.          echo "[Failed]"  
  71.       fi  
  72.    
  73.       checkpid  
  74.       if [ $psid -ne 0 ]; then  
  75.          stop  
  76.       fi  
  77.    else  
  78.       echo "================================"  
  79.       echo "warn: $APP_MAINCLASS is not running"  
  80.       echo "================================"  
  81.    fi  
  82. }  
  83.   
  84. status() {  
  85.    checkpid  
  86.    
  87.    if [ $psid -ne 0 ];  then  
  88.       echo "$APP_MAINCLASS is running! (pid=$psid)"  
  89.    else  
  90.       echo "$APP_MAINCLASS is not running"  
  91.    fi  
  92. }  
  93. info() {  
  94.    echo "System Information:"  
  95.    echo "****************************"  
  96.    echo `head -n 1 /etc/issue`  
  97.    echo `uname -a`  
  98.    echo  
  99.    echo "JAVA_HOME=$JAVA_HOME"  
  100.    echo `$JAVA_HOME/bin/java -version`  
  101.    echo  
  102.    echo "APP_HOME=$APP_HOME"  
  103.    echo "APP_MAINCLASS=$APP_MAINCLASS"  
  104.    echo "****************************"  
  105. }  
  106. case "$1" in  
  107.   
  108.    'start')  
  109.       start  
  110.       ;;  
  111.    'stop')  
  112.      stop  
  113.      ;;  
  114.    'restart')  
  115.      stop  
  116.      start  
  117.      ;;  
  118.    'status')  
  119.      status  
  120.      ;;  
  121.    'info')  
  122.      info  
  123.      ;;  
  124.   *)  
  125.      echo "Usage: $0 {start|stop|restart|status|info}"   
  126.      exit 0   
  127. esac  
  128. </span>  
startup.sh

  1. <span style="font-size:12px;">#!/bin/sh  
  2. EXECUTABLE=/log4jsocket/catalina.sh  
  3. exec "$EXECUTABLE" start "$@"</span>  
shutdown.sh

  1. <span style="font-size:12px;">EXECUTABLE=/log4jsocket/catalina.sh  
  2. exec "$EXECUTABLE" stop "$@"</span>  


PS: csdn博客啥时可以上传附件啊


版权声明:本文为博主原创文章,未经博主允许不得转载。

posted @ 2015-08-17 11:37 小马歌 阅读(383) | 评论 (0)编辑 收藏
 
from:http://javatar.iteye.com/blog/981787
关于Dubbo服务框架的分布式事务,虽然现在不急着做,但可以讨论一下。 

我觉得事务的管理不应该属于Dubbo框架, 
Dubbo只需实现可被事务管理即可, 
像JDBC和JMS都是可被事务管理的分布式资源, 
Dubbo只要实现相同的可被事务管理的行为,比如可以回滚, 
其它事务的调度,都应该由专门的事务管理器实现。 

在Java中,分布式事务主要的规范是JTA/XA, 
其中:JTA是Java的事务管理器规范, 
XA是工业标准的X/Open CAE规范,可被两阶段提交及回滚的事务资源定义, 
比如某数据库实现了XA规范,则不管是JTA,还是MSDTC,都可以基于同样的行为对该数据库进行事务处理。 

在JTA/XA中,主要有两个扩展点: 

(1) TransactionManager 
JTA事务管理器接口,实现该接口,即可完成对所有XA资源的事务调度,比如BEA的Tuxedo,JBossJTA等。 

(2) XAResource 
XA资源接口,实现该接口,即可被任意TransactionManager调度,比如:JDBC的XAConnection,JMS的XAMQ等。 

而Dubbo的远程服务,也应该是一个XAResource,比如:XAInvoker和XAExporter, 
Dubbo只需在第一次提交时,将请求发到服务提供方进行缓存和写盘, 
在第二次提交时,再基于缓存调用服务的Impl实现, 
当然一些健状性分支流程要考虑清楚。 

JTA/XA的基本原理如下: 

 

1. 用户启动一个事务: 
Java代码  收藏代码
  1. transactionManager.begin();   


2. 事务管理器在当前线程中初始化一个事务实例: 
Java代码  收藏代码
  1. threadLocal.set(new TransactionImpl());  


3. 用户调用JDBC或JMS或Dubbo请求,请求内部初始化一个XAResource实例: 
Java代码  收藏代码
  1. XAResource xaResource = new XAResourceImpl(); // 比如:XAConnection  


4. JDBC或JMS或Dubbo内部从当前线程获取事务: 
Java代码  收藏代码
  1. Transaction transaction = transactionManager.getTransaction(); // 其内部为:threadLocal.get();  


5. 将当前XAResource注册到事务中: 
Java代码  收藏代码
  1. transaction.enlistResource(xaResource);  


6. 用户提交一个事务: 
Java代码  收藏代码
  1. transactionManager.commit(); // 其内部为:getTransaction().commit();  


7. 事务for循环调用所有注册的XAResource的两阶段提交: 
Java代码  收藏代码
  1. Xid xid = new XidImpl();  
  2. for (XAResource xaResource: xaResources) {  
  3. xaResource.prepare(xid);  
  4. xaResource.commit(xid, true);  
  5. xaResource.commit(xid, false);  
  6. }  


8. 当然,还有一些异常流程,比如rollback和forget等。 

举例: 
Java代码  收藏代码
  1. TransactionManager transactionManager = ...; // 从JNDI进行lookup等方式获取  
  2. transactionManager.begin(); // 启动事务  
  3. try {  
  4.     jdbcConn.executeUpdate(sql); // 执行SQL语句,DB写入binlog,但不更新表  
  5.     jmsMQ.send(message); // 发送消息,MQ记录消息,但不进入队列  
  6.     dubboService.invoke(parameters); // 调用远程服务,Provider缓存请求信息,但不执行  
  7.     transactionManager.commit(); // 提交事务,数据库,消息队列,远程服务同时提交  
  8. catch(Throwable t) {  
  9.     transactionManager.rollback(); // 回滚事务,数据库,消息队列,远程服务同时回滚  
  10. }  
    posted @ 2015-08-04 11:19 小马歌 阅读(3232) | 评论 (0)编辑 收藏
     
    转自:http://javatar.iteye.com/blog/1056664

    最近一直担心Dubbo分布式服务框架后续如果维护人员增多或变更,会出现质量的下降, 
    我在想,有没有什么是需要大家共同遵守的, 
    根据平时写代码时的一习惯,总结了一下在写代码过程中,尤其是框架代码,要时刻牢记的细节, 
    可能下面要讲的这些,大家都会觉得很简单,很基础,但要做到时刻牢记, 
    在每一行代码中都考虑这些因素,是需要很大耐心的, 
    大家经常说,魔鬼在细节中,确实如此。 

    1. 防止空指针和下标越界 
    这是我最不喜欢看到的异常,尤其在核心框架中,我更愿看到信息详细的参数不合法异常, 
    这也是一个健状的程序开发人员,在写每一行代码都应在潜意识中防止的异常, 
    基本上要能确保一次写完的代码,在不测试的情况,都不会出现这两个异常才算合格。 

    2. 保证线程安全性和可见性 
    对于框架的开发人员,对线程安全性和可见性的深入理解是最基本的要求, 
    需要开发人员,在写每一行代码时都应在潜意识中确保其正确性, 
    因为这种代码,在小并发下做功能测试时,会显得很正常, 
    但在高并发下就会出现莫明其妙的问题,而且场景很难重现,极难排查。 

    3. 尽早失败和前置断言 
    尽早失败也应该成为潜意识,在有传入参数和状态变化时,均在入口处全部断言, 
    一个不合法的值和状态,在第一时间就应报错,而不是等到要用时才报错, 
    因为等到要用时,可能前面已经修改其它相关状态,而在程序中很少有人去处理回滚逻辑, 
    这样报错后,其实内部状态可能已经混乱,极易在一个隐蔽分支上引发程序不可恢复。 

    4. 分离可靠操作和不可靠操作 
    这里的可靠是狭义的指是否会抛出异常或引起状态不一致, 
    比如,写入一个线程安全的Map,可以认为是可靠的, 
    而写入数据库等,可以认为是不可靠的, 
    开发人员必须在写每一行代码时,都注意它的可靠性与否, 
    在代码中尽量划分开,并对失败做异常处理, 
    并为容错,自我保护,自动恢复或切换等补偿逻辑提供清晰的切入点, 
    保证后续增加的代码不至于放错位置,而导致原先的容错处理陷入混乱。 

    5. 异常防御,但不忽略异常 
    这里讲的异常防御,指的是对非必须途径上的代码进行最大限度的容忍, 
    包括程序上的BUG,比如:获取程序的版本号,会通过扫描Manifest和jar包名称抓取版本号, 
    这个逻辑是辅助性的,但代码却不少,初步测试也没啥问题, 
    但应该在整个getVersion()中加上一个全函数的try-catch打印错误日志,并返回基本版本, 
    因为getVersion()可能存在未知特定场景异常,或被其他的开发人员误修改逻辑(但一般人员不会去掉try-catch), 
    而如果它抛出异常会导致主流程异常,这是我们不希望看到的, 
    但这里要控制个度,不要随意try-catch,更不要无声无息的吃掉异常。 

    6. 缩小可变域和尽量final 
    如果一个类可以成为不变类(Immutable Class),就优先将它设计成不变类, 
    不变类有天然的并发共享优势,减少同步或复制,而且可以有效帮忙分析线程安全的范围, 
    就算是可变类,对于从构造函数传入的引用,在类中持有时,最好将字段final,以免被中途误修改引用, 
    不要以为这个字段是私有的,这个类的代码都是我自己写的,不会出现对这个字段的重新赋值, 
    要考虑的一个因素是,这个代码可能被其他人修改,他不知道你的这个弱约定,final就是一个不变契约。 

    7. 降低修改时的误解性,不埋雷 
    前面不停的提到代码被其他人修改,这也开发人员要随时紧记的, 
    这个其他人包括未来的自己,你要总想着这个代码可能会有人去改它, 
    我应该给修改的人一点什么提示,让他知道我现在的设计意图, 
    而不要在程序里面加潜规则,或埋一些容易忽视的雷, 
    比如:你用null表示不可用,size等于0表示黑名单, 
    这就是一个雷,下一个修改者,包括你自己,都不会记得有这样的约定, 
    可能后面为了改某个其它BUG,不小心改到了这里,直接引爆故障。 
    对于这个例子,一个原则就是永远不要区分null引用和empty值。 

    8. 提高代码的可测性 
    这里的可测性主要指Mock的容易程度,和测试的隔离性, 
    至于测试的自动性,可重复性,非偶然性,无序性,完备性(全覆盖),轻量性(可快速执行), 
    一般开发人员,加上JUnit等工具的辅助基本都能做到,也能理解它的好处,只是工作量问题, 
    这里要特别强调的是测试用例的单一性(只测目标类本身)和隔离性(不传染失败), 
    现在的测试代码,过于强调完备性,大量重复交叉测试, 
    看起来没啥坏处,但测试代码越多,维护代价越高, 
    经常出现的问题是,修改一行代码或加一个判断条件,引起100多个测试用例不通过, 
    时间一紧,谁有这个闲功夫去改这么多形态各异的测试用例? 
    久而久之,这个测试代码就已经不能真实反应代码现在的状况,很多时候会被迫绕过, 
    最好的情况是,修改一行代码,有且只有一行测试代码不通过, 
    如果修改了代码而测试用例还能通过,那也不行,表示测试没有覆盖到, 
    另外,可Mock性是隔离的基础,把间接依赖的逻辑屏蔽掉, 
    可Mock性的一个最大的杀手就是静态方法,尽量少用。 
    posted @ 2015-08-04 11:11 小马歌 阅读(440) | 评论 (0)编辑 收藏
     
         摘要: 2015-06-30 (点击上方蓝字,可快速关注我们)通常来说,一个优化良好的 Nginx Linux 服务器可以达到 500,000 – 600,000 次/秒 的请求处理性能,然而我的 Nginx 服务器可以稳定地达到 904,000 次/秒 的处理性能,并且我以此高负载测试超过 12 小时,服务器工作稳定。这里需要特别说明的是,本文中所有列出来的配置都是在我的测试环境验...  阅读全文
    posted @ 2015-07-10 10:23 小马歌 阅读(354) | 评论 (0)编辑 收藏
     

    from:http://blog.csdn.net/yanzi1225627/article/details/42040629

    由于Google官方已经不提供Adt-Bundle下载了,主推AndroidStudio。可以从这个链接下载http://www.androiddevtools.cn。上面不光有adt-bundle,还有最新的AndroidStudio。由于对OS X还不是很熟悉,本次采用adt-bundle安装。

    一,下载JDK

    下载方式有两种,其一是从链接http://www.androiddevtools.cn处下载,选择Mac OSX的1.8u5版本即可。截图如下:


    其二是从JDK的官网下载,文件名为jdk-8u25-macosx-x64.dmg,大小219.3M。这个稍后我上传至百度网盘供大家下载。我就是通过这种方式下载的。下载完毕后,点击打开,接着出现如下:


    再点击一下就开始安装了,中间会让输入用户名和密码。安装完毕后,打开个终端,输入javac -version查看是否安装成功。


    像上图所示就是安装成功了。哈哈,其实OSX没什么神秘的,全当它是linux就好了。

    二,下载Adt-Bundle

    从http://www.androiddevtools.cn处下载,选择Mac OSX的64位23.0.2即可。


    文件名为adt-bundle-mac-x86_64-20140702.zip,大小320.6M。是个zip格式的压缩包,打开解压后看到根windows上一样是熟悉的eclipse和sdk文件夹。这就表示安装完毕了。

    三,打开Eclipse

    打开之后就傻眼了:


    为此,我研究了N种解决办法:

    1,http://www.cnblogs.com/zhouyinhui/p/3750836.html 让修改Info.plist,没有任何效果。

    2,还有的让配置jdk环境变量,sudo vim /etc/profile,之后输入:

    JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home

    export JAVA_HOME

    然后source /etc/profile,没有效果。注意此处,jdk的环境变量设置一定要使用sudo vim,退出后用wq! 否则是保存不了的。JDK的路径貌似高低版本的OSX还不太一样,应以终端里输入:/usr/libexec/java_home 打印出来的变量为准。可以参考:http://han.guokai.blog.163.com/blog/static/136718271201301183938165/

    3,最终找到了链接:http://java.com/zh_CN/download/faq/java_mac.xml#mac1010 


    上面的链接也就是打开eclipse,弹窗上的“更多信息”,http://support.apple.com/kb/DL1572?viewlocale=zh_CN&locale=en_US,截图如下:


    下载的文件名为JavaForOSX2014-001.dmg,60多M。点击安装。终于Eclipse启动起来了,后面的就不啰嗦了。


    总结:一定要先在苹果官网上下载java1.6的安装包,然后java官网上下载安装jdk8u25. 后者是否是必须我没有测试,我mac上安装了这两个东西未见不良现象。

    备注:最新的jdk8u25已经可以顺利在OSX10.10上安装了,无需按照http://www.krislq.com/2014/07/mac-x-yosemide10-10-update-jdk-7-jdk-8/ 进行处理。

    posted @ 2015-06-15 09:01 小马歌 阅读(382) | 评论 (0)编辑 收藏
     
    <?xml version="1.0" encoding="UTF-8" ?>
    <!--
           Copyright 2009-2013 the original author or authors.
           Licensed under the Apache License, Version 2.0 (the "License");
           you may not use this file except in compliance with the License.
           You may obtain a copy of the License at
              http://www.apache.org/licenses/LICENSE-2.0
           Unless required by applicable law or agreed to in writing, software
           distributed under the License is distributed on an "AS IS" BASIS,
           WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
           See the License for the specific language governing permissions and
           limitations under the License.
    -->
    <!ELEMENT mapper (cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* )+>
    <!ATTLIST mapper
    xmlns:fo CDATA #IMPLIED
    namespace CDATA #IMPLIED
    >
    <!ELEMENT cache-ref EMPTY>
    <!ATTLIST cache-ref
    namespace CDATA #REQUIRED
    >
    <!ELEMENT cache (property*)>
    <!ATTLIST cache
    type CDATA #IMPLIED
    eviction CDATA #IMPLIED
    flushInterval CDATA #IMPLIED
    size CDATA #IMPLIED
    readOnly CDATA #IMPLIED
    blocking CDATA #IMPLIED
    >
    <!ELEMENT parameterMap (parameter+)?>
    <!ATTLIST parameterMap
    id CDATA #REQUIRED
    type CDATA #REQUIRED
    >
    <!ELEMENT parameter EMPTY>
    <!ATTLIST parameter
    property CDATA #REQUIRED
    javaType CDATA #IMPLIED
    jdbcType CDATA #IMPLIED
    mode (IN | OUT | INOUT) #IMPLIED
    resultMap CDATA #IMPLIED
    scale CDATA #IMPLIED
    typeHandler CDATA #IMPLIED
    >
    <!ELEMENT resultMap (constructor?,id*,result*,association*,collection*, discriminator?)>
    <!ATTLIST resultMap
    id CDATA #REQUIRED
    type CDATA #REQUIRED
    extends CDATA #IMPLIED
    autoMapping (true|false) #IMPLIED
    >
    <!ELEMENT constructor (idArg*,arg*)>
    <!ELEMENT id EMPTY>
    <!ATTLIST id
    property CDATA #IMPLIED
    javaType CDATA #IMPLIED
    column CDATA #IMPLIED
    jdbcType CDATA #IMPLIED
    typeHandler CDATA #IMPLIED
    >
    <!ELEMENT result EMPTY>
    <!ATTLIST result
    property CDATA #IMPLIED
    javaType CDATA #IMPLIED
    column CDATA #IMPLIED
    jdbcType CDATA #IMPLIED
    typeHandler CDATA #IMPLIED
    >
    <!ELEMENT idArg EMPTY>
    <!ATTLIST idArg
    javaType CDATA #IMPLIED
    column CDATA #IMPLIED
    jdbcType CDATA #IMPLIED
    typeHandler CDATA #IMPLIED
    select CDATA #IMPLIED
    resultMap CDATA #IMPLIED
    >
    <!ELEMENT arg EMPTY>
    <!ATTLIST arg
    javaType CDATA #IMPLIED
    column CDATA #IMPLIED
    jdbcType CDATA #IMPLIED
    typeHandler CDATA #IMPLIED
    select CDATA #IMPLIED
    resultMap CDATA #IMPLIED
    >
    <!ELEMENT collection (constructor?,id*,result*,association*,collection*, discriminator?)>
    <!ATTLIST collection
    property CDATA #REQUIRED
    column CDATA #IMPLIED
    javaType CDATA #IMPLIED
    ofType CDATA #IMPLIED
    jdbcType CDATA #IMPLIED
    select CDATA #IMPLIED
    resultMap CDATA #IMPLIED
    typeHandler CDATA #IMPLIED
    notNullColumn CDATA #IMPLIED
    columnPrefix CDATA #IMPLIED
    resultSet CDATA #IMPLIED
    foreignColumn CDATA #IMPLIED
    autoMapping (true|false) #IMPLIED
    fetchType (lazy|eager) #IMPLIED
    >
    <!ELEMENT association (constructor?,id*,result*,association*,collection*, discriminator?)>
    <!ATTLIST association
    property CDATA #REQUIRED
    column CDATA #IMPLIED
    javaType CDATA #IMPLIED
    jdbcType CDATA #IMPLIED
    select CDATA #IMPLIED
    resultMap CDATA #IMPLIED
    typeHandler CDATA #IMPLIED
    notNullColumn CDATA #IMPLIED
    columnPrefix CDATA #IMPLIED
    resultSet CDATA #IMPLIED
    foreignColumn CDATA #IMPLIED
    autoMapping (true|false) #IMPLIED
    fetchType (lazy|eager) #IMPLIED
    >
    <!ELEMENT discriminator (case+)>
    <!ATTLIST discriminator
    column CDATA #IMPLIED
    javaType CDATA #REQUIRED
    jdbcType CDATA #IMPLIED
    typeHandler CDATA #IMPLIED
    >
    <!ELEMENT case (constructor?,id*,result*,association*,collection*, discriminator?)>
    <!ATTLIST case
    value CDATA #REQUIRED
    resultMap CDATA #IMPLIED
    resultType CDATA #IMPLIED
    >
    <!ELEMENT property EMPTY>
    <!ATTLIST property
    name CDATA #REQUIRED
    value CDATA #REQUIRED
    >
    <!ELEMENT typeAlias EMPTY>
    <!ATTLIST typeAlias
    alias CDATA #REQUIRED
    type CDATA #REQUIRED
    >
    <!ELEMENT select (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST select
    id CDATA #REQUIRED
    parameterMap CDATA #IMPLIED
    parameterType CDATA #IMPLIED
    resultMap CDATA #IMPLIED
    resultType CDATA #IMPLIED
    resultSetType (FORWARD_ONLY | SCROLL_INSENSITIVE | SCROLL_SENSITIVE) #IMPLIED
    statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
    fetchSize CDATA #IMPLIED
    timeout CDATA #IMPLIED
    flushCache (true|false) #IMPLIED
    useCache (true|false) #IMPLIED
    databaseId CDATA #IMPLIED
    lang CDATA #IMPLIED
    resultOrdered (true|false) #IMPLIED
    resultSets CDATA #IMPLIED 
    >
    <!ELEMENT insert (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST insert
    id CDATA #REQUIRED
    parameterMap CDATA #IMPLIED
    parameterType CDATA #IMPLIED
    timeout CDATA #IMPLIED
    flushCache (true|false) #IMPLIED
    statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
    keyProperty CDATA #IMPLIED
    useGeneratedKeys (true|false) #IMPLIED
    keyColumn CDATA #IMPLIED
    databaseId CDATA #IMPLIED
    lang CDATA #IMPLIED
    >
    <!ELEMENT selectKey (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST selectKey
    resultType CDATA #IMPLIED
    statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
    keyProperty CDATA #IMPLIED
    keyColumn CDATA #IMPLIED
    order (BEFORE|AFTER) #IMPLIED
    databaseId CDATA #IMPLIED
    >
    <!ELEMENT update (#PCDATA | selectKey | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST update
    id CDATA #REQUIRED
    parameterMap CDATA #IMPLIED
    parameterType CDATA #IMPLIED
    timeout CDATA #IMPLIED
    flushCache (true|false) #IMPLIED
    statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
    keyProperty CDATA #IMPLIED
    useGeneratedKeys (true|false) #IMPLIED
    keyColumn CDATA #IMPLIED
    databaseId CDATA #IMPLIED
    lang CDATA #IMPLIED
    >
    <!ELEMENT delete (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST delete
    id CDATA #REQUIRED
    parameterMap CDATA #IMPLIED
    parameterType CDATA #IMPLIED
    timeout CDATA #IMPLIED
    flushCache (true|false) #IMPLIED
    statementType (STATEMENT|PREPARED|CALLABLE) #IMPLIED
    databaseId CDATA #IMPLIED
    lang CDATA #IMPLIED
    >
    <!-- Dynamic -->
    <!ELEMENT include (property+)?>
    <!ATTLIST include
    refid CDATA #REQUIRED
    >
    <!ELEMENT bind EMPTY>
    <!ATTLIST bind
     name CDATA #REQUIRED
     value CDATA #REQUIRED
    >
    <!ELEMENT sql (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST sql
    id CDATA #REQUIRED
    lang CDATA #IMPLIED
    databaseId CDATA #IMPLIED
    >
    <!ELEMENT trim (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST trim
    prefix CDATA #IMPLIED
    prefixOverrides CDATA #IMPLIED
    suffix CDATA #IMPLIED
    suffixOverrides CDATA #IMPLIED
    >
    <!ELEMENT where (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ELEMENT set (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ELEMENT foreach (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST foreach
    collection CDATA #REQUIRED
    item CDATA #IMPLIED
    index CDATA #IMPLIED
    open CDATA #IMPLIED
    close CDATA #IMPLIED
    separator CDATA #IMPLIED
    >
    <!ELEMENT choose (when* , otherwise?)>
    <!ELEMENT when (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST when
    test CDATA #REQUIRED
    >
    <!ELEMENT otherwise (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ELEMENT if (#PCDATA | include | trim | where | set | foreach | choose | if | bind)*>
    <!ATTLIST if
    test CDATA #REQUIRED
    >
    posted @ 2015-06-05 11:36 小马歌 阅读(537) | 评论 (0)编辑 收藏
     
    看了dataguru网站的介绍,这个 《大型分布式系统案例实战学习》貌似还可以,2015.5.6开课,已经报名,系统理一下知识点。

    也作为使用了mycat开源工具的回报:) 另外这个逆向学习收费模式还挺有意思的

    如果对Dataguru的课程有兴趣,报名的时候可以填写我的优惠码 V5M5,能立减50%的固定学费!

    课程大纲:
    第1课 大型分布式系统原理概述
    结合业界主流的那些开源软件,介绍和分析分布式系统的基本架构,组成部分,和实现原理。几款常用的软件以及功能功能性对比。

    第2课 分布式系统之网络篇
    Zookeeper入门
    Zookeeper原理: Zookeeper原理介绍
    Curator客户端 : 对Zookeeper知名客户端Curator进行介绍,初步掌握其编程方式和用法。
    迷你P2P网络服务案例: 采用Zookeeper打造一个迷你P2P网络系统,节点之间相互交换名片,并且实现动态路由(节点宕机后其他节点自动感知并更新链路状态),

    第3课  分布式存储-文件系统篇
    传统的分布式文件系统:Lustre、GlusterFS等经典分布式文件系统分析
    新型分布式文件系统:介绍Ceph以及它跟Openstack的关系
    互联网领域中的小文件系统:GridFS、FastDFS、TFS等分析学习

    第4课 分布式存储-内存篇
    Hazelcast 详解与分析
    GridGain详解与分析
    MemCache详解与分析
    案例集锦:分布式系统存储之基于内存的两表Join演示

    第5课 分布式存储-数据库篇
    分布式数据库原理 :介绍分布式数据库的实现原理,特性、优缺点、以及难点、热点问题
    Mycat前世今生:介绍目前基于MYSQL的热门开源数据库血统,包括Cobar、tddl、Amoeba、以及目前很火的Mycat
    案例集锦:某大型网站每天1亿数据处的案例剖析

    第6课 分布式系统之云计算篇
    主机虚拟化:介绍主机虚拟化的技术
    网络虚拟化:介绍网络虚拟化的技术
    存储虚拟化:介绍存储虚拟化的技术
    云计算实践:VirtualBox虚机集群搭建
    Openstack原理介绍:介绍Openstack的体系、架构、以及基本功能
    案例集锦:基于RDO实现Openstack的安装、部署等。

    第7课 分布式计算框架
    Map-Reduce原理:介绍Map-Reduce的原理以及限制问题
    Apache Storm应用:学习Storm的原理并搭建测试环境,掌握基本编程
    案例集锦:实现基于Storm的1000万×1000万的SQL Join和排序分页

    第8课 通信机制的设计与实现
    分布式通信机制概述:讲解分布式通信的几种常见机制,RPC调用、共享远程数据、消息队列等。
    RPC通信机制的原理 讲解RPC通信机制的原理和实现方式:
    案例集锦:设计并实现一个XML-RPC框架 动手设计和实现一个简单的XML-RPC框架

    第9课 消息队列
    消息队列机制介绍: 介绍古典的和新型的消息队列机制的相同点和不同点
    消息队列通信的案例分析: 对一些采用消息队列通信的系统做分析,掌握消息队列用作分布式通信的一般设计原则
    案例集锦:对知名开源消息中间件Kafka做一个入门学习,并动手完成一个实际编程案例

    第10课 打造高可用系统(上)
    高可用系统常规方案:介绍高可用系统的一些原理、实现机制、常规实现方案,包括基于硬件、软件中间件、系统架构等一些典型方案的实现
    HA Proxy入门:介绍业界常规的HA Proxy的原理以及用法
    实践篇:Java开发一个类似HA Proxy的代理中间件

    第11课 打造高可用系统(下)
    高可用集群套件中间件:介绍基于Corosync+Pacemaker的高可用集群套件中间件系统的原理、配置以及常见案例
    Corosync技术; 
    Pacemaker技术;
    Pacemaker实践:实现基于Pacemaker的MYSQL高可用方案。

    第12课 Mycat架构的分布式演进背后的秘密
    配置文件的分布式访问问题:为什么最终选择了Zookeeper
    Mycat负载均衡的特殊性:为什么标准的HA Proxy还无法满足Mycat的负载均衡要求
    大数据Join背后的难题:数据、网络、内存和计算能力的矛盾和调和

    第13课 Java分布式系统中的高性能难题
    高性能网络框架的难题:AIO,NIO,Netty还是自己开发框架
    堆内和堆外存储:堆内与堆外存储的差别,开源的堆外存储组件为何凤毛麟角
    高性能事件派发机制:线程池模型的性能问题以及不为人知的Disruptor模型

    第14课 挑战自我——全栈架构师实践
    本节课程的目标,是挑战自我,开发一个基于Zeroc ICE+Zookeeper+Mycat+Android App+ Web系统的“身边购”平台,目标是支持1亿用户,每天交易订单为1亿,商家自己在手机上通过Appp注册自己的店铺,店铺包括地理位置信息,后台审批通过,然后可以拍照上架自己的货物,定价,发售。用户登录App以后,根据其地理位置信息,显示附近的(默认3公里)新品、热门商品、二手商品等,并可以下单。

    授课时间:
    第一期课程预计2015年5月6日开课,预计课程持续时间为16周。
    posted @ 2015-04-13 10:23 小马歌 阅读(906) | 评论 (0)编辑 收藏
     
         摘要: from:http://blog.csdn.net/myrainblues/article/details/25881535最近研究redis-cluster,正好搭建了一个环境,遇到了很多坑,系统的总结下,等到redis3 release出来后,换掉memCache 集群.一:关于redis cluster1:redis cluster的现状reids-cluster计划在redis3.0中推出...  阅读全文
    posted @ 2015-04-03 11:00 小马歌 阅读(903) | 评论 (0)编辑 收藏
     
    from :
    http://git.oschina.net/yeetrack/weixin-alert

    很简单的步骤:

    1. 登录web微信,打开开发者工具,切到http请求tab
    2. 给要报警的人、或者群组发送一条微信
    3. 在开发者工具中,copy出两条curl请求,一个是发送心跳的,一个是发送消息内容的。
    4. 编译jar包,mvn clean pakcage
    5. 将心跳curl保存成**weixin-heart.sh**,将发送curl保存成**weixin-send.sh**。
    6. 将jar包和两个shell文件放在统一路径中,执行java -jar weixinalert-1.0-SNAPSHOT-jar-with-dependencies.jar "Hello world!"即可。

    ps:心跳的那个请求,最好有个定时任务,crontab之类,一分钟一次,防止服务器踢掉该会话。缺点:不能退出手机上的微信(断网可以,只要不手动退出),不能再使用该微信号登录其他客户端的微信,如windows,mac,网页微信等,也就是说一个手机上的微信账号,最多额外产生一个sid,多了会踢掉之前的sid。

    posted @ 2015-04-03 10:50 小马歌 阅读(386) | 评论 (0)编辑 收藏
    仅列出标题
    共95页: First 上一页 10 11 12 13 14 15 16 17 18 下一页 Last