2012年12月3日星期一

Twitter Finagle-Scala语言的RPC框架

简介

Finagle是Twitter的一个开源scala框架,支持Java语言和Scala语言,支持的协议包括Apache Thrift, Kestrel, Memcached, http, redis, mysql等。由于国内的scala应用程度不高,而Twitter已经逐渐发展成为了Scala的重度使用用户。Finagle是基于NIO和netty的,主体的结构如下图:
本文的目的是实践Finagle框架的thrift协议,并在底层通过JPA,Hibernate与mysql进行交互,同时利用Spring framework管理Bean。

scrooge

scrooge也是twitter开源的一个项目,项目是用scala编写的一个thrift代码生成器。我们直接下载zip包使用scrooge脚本生成代码即可。
假设我们的thrift IDL代码如下:
struct User{
        1:i32 id,
        2:string name,
        3:i32 createdAt,
        4:i32 updated
}

service UserService{
        User getUser(1:i32 id),
        User saveUser(1:User user),
        User updateUser(1:User user)
}
使用下面的命令生成scala代码:
> scrooge -finagle demo.thrift
会生成一个thrift目录,scala代码就在目录下。

maven

通过eclipse新建一个scala项目,选择scala-archetype-simple,创建一个带maven支持的scala项目。
记得enable maven支持的同时,选中scala菜单的添加scala特性,否则不会编译。
需要调整pom中的scala版本和本地scala版本一致。

Finagle

拷贝scrooge生成的代码到工程中,并新建ThriftServer和ThriftClient两个scalaObject。
代码ThriftServer.scala
object ThriftServer {
  def main(args: Array[String]) {
   
    val protocol = new TBinaryProtocol.Factory()
    val ctx = new ClassPathXmlApplicationContext("applicationContext.xml")
    val userService = ctx.getBean("userService").asInstanceOf[UserService.FutureIface]
    val server = new UserService.FinagledService(userService, protocol)
    val address = new InetSocketAddress("localhost", 9000)
    var builder = ServerBuilder()
      .codec(ThriftServerFramedCodec())
      .name("UserService")
      .bindTo(address)
      .build(server)
  }
}
代码ThriftClient.scala:
object ThriftClient {
  def main(args: Array[String]) {
    val service = ClientBuilder()
      .hosts(new InetSocketAddress("localhost", 9000))
      .codec(ThriftClientFramedCodec())
      .hostConnectionLimit(100)
      .build()
    val client = new UserService.FinagledClient(service)
    val user = client.getUser(10)
    println("getUser-->"+user.apply())
    val user1 = client.saveUser(user.apply());
    println("save db-->"+user1.apply())
  }
}
ThriftServer中使用了spring管理了userService。
新建一个实现类,并调用了封装的dao层代码:
class UserServiceImpl extends FutureIface {
  
  @BeanProperty
  var dao:Dao = null
  
  def getUser(`id`: Int): Future[User] = {
    val currentTime = (System.currentTimeMillis() / 1000).asInstanceOf[Int];
    var user = if (id == 0) null else User.apply(id, "wenfeng", currentTime, currentTime)
    Future.value(user)
  }
  def saveUser(`user`: User): Future[User] = {
    var u = new com.scala.core.User(user._1, user._2, user._3, user._4)
    try{
     val id = dao.save(u)
     u.setId(id.asInstanceOf[Long]);
    }catch{
      case ex:DalException => ex.printStackTrace()
    }
    println("save user-->"+user)
    val res = User.apply(u.getId().asInstanceOf[Int], 
        u.getName(), (u.getCreatedAt() / 1000).asInstanceOf[Int], 
        (u.getUpdated() / 1000).asInstanceOf[Int])
    Future.value(res)
  }
  def updateUser(`user`: User): Future[User] = Future.value(user)
}
Spring的配置文件:
<beans xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:sca="http://www.springframework.org/schema/sca" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
            http://www.springframework.org/schema/sca http://www.osoa.org/xmlns/sca/1.0/spring-sca.xsd">
 <context:component-scan base-package="com.scala">
 <bean class="com.scala.dao.impl.DaoImpl" id="dao">
 <bean class="com.scala.service.impl.UserServiceImpl" id="userService">
  <property name="dao" ref="dao">
 </property></bean>
</bean></context:component-scan></beans>
新建一个JPA的User类,使用scala的@BeanProperty 生成getter和setters
@Entity
@Table(name="user")
class User extends java.io.Serializable{
 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 @BeanProperty 
 var id:Long = _
 
 @Column(name="name")
 @BeanProperty 
 var name:String = _
 
 @Column(name="created_at")
 @BeanProperty 
 var createdAt:Long = _
 
 @Column(name="updated")
 @BeanProperty 
 var updated:Long = _
 
 def this(id:Long, name:String, createdAt:Long, update:Long) = {
   this()
   this.id = id
   this.name = name
   this.createdAt = createdAt
   this.updated = updated
 }
 override def toString() = "User["+id+","+name+","+createdAt+","+updated+"]"
测试插入一条用户数据,查看log和DB后,发现数据已经成功写入DB。

2012年11月21日星期三

Memcached一致性Hash实测

使用的客户端

spy memcached

环境

先启动4个memcached服务,然后向这4个memcached实例写入1w个数据,测试数据分布情况;再增加一个实例,在5个实例的情况下,读取1w条记录,计算实际命中率

代码

启用一致性hash很容易,代码如下:
List servers = new ArrayList();
servers.add(new InetSocketAddress("192.168.12.167", 33001));
servers.add(new InetSocketAddress("192.168.12.167", 33002));
servers.add(new InetSocketAddress("192.168.12.167", 33003));
servers.add(new InetSocketAddress("192.168.12.167", 33004));
servers.add(new InetSocketAddress("192.168.12.167", 33005));
memCachedClient = new MemcachedClient(new KetamaConnectionFactory(), servers);

写入的分布比例


server1 2304
server2 2603
server3 2513
server4 2588

加入一个实例后的命中率

81.38%

2012年11月2日星期五

GOF设计模式-命令模式

command pattern

UML图如下:

命令模式分为四个角色,分别是Invoker,Command抽象接口,Command实现类和Receiver。
用一个Java与模式上的例子解释,玉皇大帝发圣旨,让太白金星招安孙悟空。这里玉皇大帝就是Client,圣旨有一个抽象接口,可以称作圣旨模板,圣旨则是实现类。而太白金星则是任务直接执行Invoker,孙悟空是任务接受者Receiver,真正的执行代码在孙悟空这里。
用Scala代码实现的命令模式如下:
首先是client代码:
object Client extends App {
 val receiver = new Receiver
 val command = new ConcreteCommand(receiver)
 val invoker = new Invoker(command)
 invoker.action
}
然后是Command代码:
trait Command {
 def execute
}
ConcerteCommand:
class ConcreteCommand(re:Receiver) extends Command{
 val receiver = re
 override def execute = receiver.action
}
Receiver:
class Receiver {

  def action = {
    println("Action has been taken.")
  }
}
Invoker:
class Invoker(com:Command) {
 val command = com
 
 def action = command.execute
}
执行结果:
Action has been taken.

命令模式的优点:

1. 新命令很容易加入到系统
2. 允许接受者一方决定是否要否决请求
3. 能较容易的设计一个命令队列
4. 也可以很容易实现对请求的undo和redo。
5. 在需要的情况下,可以很容易的将命令计入日志。
6. 将请求的操作对象和具体执行的操作对象分开;
7. 可以合成命令。

命令模式的使用场景

1. 作为回调,先定义好一个函数,在以后调用此函数;
2. 需要在不同的时间请求,将请求排队
3. 系统需要支持命令的撤销;
4. 如果系统需要记录命令日志,方便在系统崩溃后通过日志调用execute方法恢复系统在崩溃前的数据更新;
5. 一个系统需要支持交易;

2012年10月30日星期二

JVM原理简述-GC

GC

        GC-垃圾回收是一种动态存储管理技术,它可以自动释放那些不再被使用的对象,按照特定的算法来实现资源自动回收。

垃圾收集方式

引用计数

        堆中的每个对象对应一个引用计数器,每当创建或重新赋值的时候,引用计数器就+1,而当对象出了作用域之后,计数器-1,直到计数器为0,就达到了被回收的条件。
        引用计数方式速度较快,不会产生停顿。但是开销较大,因为每个对象都对应了一个计数器。

对象引用遍历

        从根集开始扫描,识别出哪些对象可达,哪些不可到达。并利用某种方式标记可达对象。

垃圾收集算法

复制

        找到活动对象并拷贝到新的空间。这种算法适合存活对象较少的情况,内存开销较大。

标记清除

        从根开始扫描,将活动对象标记。然后再扫描未标记的对象,一次清除。此方法的优点是不需要移动对象,仅对不存活对象操作,适合存活对象较多的情况,容易产生内存碎片。

标记压缩

        标记清除的基础上,移动活动对象。缺点是成本较高,优点是没有内存碎片。

分代回收

        GC是对JVM堆中的对象进行回收,在上一篇博客中,hotspot堆分为young,old和perm三部分。新生对象首先进入young区,随着young区不断的增加,一些老的对象将被移到old区。而perm区主要存储了java class的类型信息和静态变量等。

youngGC

即年轻代垃圾回收,当创建新对象,Eden空间不足时,触发youngGC。有三种选择
1. 串行GC
将Eden中存活的对象移到From->To->Old.适用于单线程,单cpu,新生代较小,要求不高的应用。在client模式或32位机器下默认选择。
启用:-XX:+UseSerialGC
2. 并行回收GC,Parallel Scavenge
多线程执行,适应多CPU,要求较高的应用。在server模式下是默认的。启用:-XX:+UseParallelGC, 在8核心以下的机器上默认为cpu核心数,大于8核,则默认为3+(核心数*5)/8.也可以通过-XX:ParallelGCThreads=8指定。
3. ParNew GC
需配合旧生代使用CMSGC。启用:-XX:+UseParNewGC.可以通过-XX:DisableExplicitGC禁用。

OldGC

1. 旧生代串行GC
        算法:标记清除和标记压缩。采用单线程执行,耗时较长,需要暂停应用。client模式或32位机默认采用这种模式。-XX:+PrintGCApplicationStoppedTime可以查看应用暂停时间。
2. 旧生代并行GC
        算法:标记压缩算法。采用多线程方式,暂停时间减少。server模式默认采用这种回收方式。
3. 旧生代并发GC
        采用标记清除算法。对GC进行并发执行,大大缩短应用暂停时间,但是整体GC时间会加长。启用方式:-XX:+UseConcMarkSweepGC。可以通过-XX:ParallelCMSThreads=10指定并发线程数。通过-XX:+CMSClassUnloadingEnabled来启用持久代CMS.
        垃圾整理:使用-XX:UseCMSCompactAtFullCollection每次fullGC后都会启动垃圾整理.-XX:CMSFullGCsBeforeCompaction=2表示2次fullGC后开始整理。

fullGC

旧生代和持久代GC时,即是对新生代,旧生代持久代都GC,FullGC。
执行时,先对新生代GC,可以通过-XX:ScanengeBeforeFullGC禁止fullGC时对新生代GC,然后对旧生代持久代进行GC。
触发时机:
1. system.gc,可以通过-XX:DisableExplicitGC禁用。
2. 旧生代空间不足
3. 持久代空间满
4. CMSGC时出现promotion failed
5. 统计youngGC后要移到旧生代的对象大于旧生代剩余空间

查看JVM使用的GC算法

找到java进程ID,使用jmap命令:
jmap -heap {pid}
会打印出JVM的基本信息,包括分区信息,回收算法等。
打印之前先看一下启动参数:
java -server -Xms512m -Xmx512m -XX:NewSize=100m -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=58 -XX:PermSize=64m -XX:MaxPermSize=64m -XX:ThreadStackSize=512

下面是jvm-heap信息:
Attaching to process ID 31410, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 20.8-b03

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 536870912 (512.0MB)
   NewSize          = 104857600 (100.0MB)
   MaxNewSize       = 283508736 (270.375MB)
   OldSize          = 314572800 (300.0MB)
   NewRatio         = 7
   SurvivorRatio    = 8
   PermSize         = 67108864 (64.0MB)
   MaxPermSize      = 67108864 (64.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 94371840 (90.0MB)
   used     = 7155464 (6.823982238769531MB)
   free     = 87216376 (83.17601776123047MB)
   7.582202487521702% used
Eden Space:
   capacity = 83886080 (80.0MB)
   used     = 6906936 (6.586967468261719MB)
   free     = 76979144 (73.41303253173828MB)
   8.233709335327148% used
From Space:
   capacity = 10485760 (10.0MB)
   used     = 248528 (0.2370147705078125MB)
   free     = 10237232 (9.762985229492188MB)
   2.370147705078125% used
To Space:
   capacity = 10485760 (10.0MB)
   used     = 0 (0.0MB)
   free     = 10485760 (10.0MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 432013312 (412.0MB)
   used     = 7523480 (7.174949645996094MB)
   free     = 424489832 (404.8250503540039MB)
   1.7414926325233238% used
Perm Generation:
   capacity = 67108864 (64.0MB)
   used     = 39960504 (38.10930633544922MB)
   free     = 27148360 (25.89069366455078MB)
   59.545791149139404% used

2012年10月24日星期三

Nginx做文件缓存原理

        关于反向代理不多赘述,Nginx是一个高性能的http服务器,可以通过配置Nginx缓存来实现静态文件访问加速。

http304原理

        在使用浏览器访问的时候,经常可以看到一个状态值304返回。304的英文表示是Not Modified,即文件未被修改,而这个值是通过http server返回给客户端的。以Nginx为例,当返回304时,并未产生任何的数据下载。
Request URL:http://s1.t.itc.cn/mblog/pic/20122_21_9/92542601162191005.jpg
Request Method:GET
Status Code:304 Not Modified

        从截图上可以看到只有228B字节的返回,这只相当于http header的大小,而图片则不需要被重新下载传输,也就是通过这个会加快网页的加载速度。

        实现304的原理关键在于两个http header。分别是Last-Modified和If-Modified-Since。
        http server在首次收到图片访问请求的时候,会向浏览器端返回Last-Modified这个header,标示当前文件的上次修改日期。
Cache-Control:max-age=86400
Connection:keep-alive
Date:Wed, 24 Oct 2012 08:56:16 GMT
Expires:Thu, 25 Oct 2012 08:56:16 GMT
Last-Modified:Wed, 24 Oct 2012 07:49:33 GMT
Server:nginx/0.8.54
        浏览器收到这个参数后,再次请求时,会带上If-Modified-Since参数传递给服务器。http server收到这个参数,会和本地缓存中的Last-Modified比较,如果一致则返回http304.
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Charset:GBK,utf-8;q=0.7,*;q=0.3
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-CN,zh;q=0.8
Cache-Control:max-age=0
Connection:keep-alive
Host:t.itc.cn
If-Modified-Since:Wed, 24 Oct 2012 07:49:33 GMT
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1

Nginx判断是否返回304的原理

nginx的缓存目录会缓存访问过的文件,这些文件的格式如下:
 [1m~JP^@^@^@^@……………………[1m~Y~GP^@^@^@^@~_^F [1m^@^@[1m^@[1m^A-cache
KEY: t.itc.cn/mblog/pic/20122_21_9/92542601162191005.jpg
HTTP/1.0 200 OK^M
Server: Resin/3.1.9^M
Last-Modified: 23 Oct 2012 09:40:38 GMT^M
ETag: BExxWmSgiYb1234^M
Accept-Ranges: bytes^M
Expires: Fri, 26 Oct 2012 07:33:00 GMT^M
Content-Type: image/jpeg^M
Content-Length: 7810^M
Date: Wed, 24 Oct 2012 07:32:59 GMT^M
^M
…[1m^@^PJFIF^@^A^A^A^@H^@H^@^@…[1m^@C^@^H^F^F^G^F^E^H^G^G^G               ^H
缓存文件中存储了缓存的主键key,和header值,通过匹配缓存中的Last-Modified来达到是否返回http 304的判断。如果没有缓存,则获取文件在源站的修改时间作为Last-Modified值。如果是接口处理后返回的文件,需要接口自己设置Last-Modified值。

简单配置Nginx缓存

proxy_temp_path /var/tmp/nginx/proxy;
proxy_cache_path /var/tmp/nginx/cache levels=1:2 keys_zone=cache_one:20m inactive=1d max_size=5g;
 
server {
 
 listen  80;
 server_name localhost;
 
 root /var/www;
 
 location ~ \.(jpg|png|jpeg|gif|css|js)$ {
  proxy_cache cache_one;
  proxy_cache_valid 200 304 12h;
  proxy_cache_key $host$uri$is_args$args;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $remote_addr;
  proxy_pass http://127.0.0.1:8181;
  proxy_set_header X-Forwarded_For $proxy_add_x_forwarded_for;
  expires 1d;
 }
}

2012年10月18日星期四

Java对象序列化

什么是序列化

        序列化是指将一个对象转换为一个一连串的字节描述的过程。而反序列化是将这个一连串的字节数据转化为对象的过程。在一个分布式的环境中,经常需要将一个Object从一端传递到网络的另外一端,此时就需要在发送端序列化之后,经过网络将字节流传递到另一端,对端再将字节流转换为对象。

场景

持久化存储,将对象持久化为流存储到对象或缓存中;
远程过程调用,将序列化之后的流经过网络传输解析;而对开发人员来说,底层的控制协议都被屏蔽了,它们看到了一个在同一个虚拟机内调用的效果。

如何序列化对象

        只要实现java.io.Serializable接口的对象都可以被JVM序列化,java.io.Serializable是一个标记接口,没有定义任何方法,它只是为了声明一个对象是可以被序列化的。例如:
public class User implements Serializable{
  private long id;
  private String name;
}
Java中,ObjectOutputStream和ObjectInputStream负责序列化java对象,通过ObjectOutputStream的writeObject方法来将一个对象写入到流中,通过ObjectInputStream的readObject方法将一个对象从流中读出,例如:
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bo);
out.writeObject(test);
byte[] bytes = bo.toByteArray();
out.flush();
   
ObjectInputStream input = new ObjectInputStream(new ByteArrayInputStream(bytes));
KryoTest s = (KryoTest)input.readObject();
AssertUtil.assertNotNull(s);
input.close();
默认对象所有的非静态和非瞬时的域都会被包含进来,而与域的声明没有关系。此时有两个办法来保证某些域不被序列化。第一种就是使用Transient关键字,将域声明为瞬时的,此时序列化将不会包含瞬时域。另外一种方式是通过serialPersistentFields来声明要被序列化的域,例如如果只序列化Person的name域:
private static final ObjectStreamField[] serialPersistentFields = { 
    new ObjectStreamField("name", String.class) 
};  

自定义序列化

自定义序列化是通过在对象中声明四个私有方法来完成的。这四个方法分别是:

private void writeObject(java.io.ObjectOutputStream out) throws IOException;
private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
private Object readResolve() throws ObjectStreamException;
private Object writeReplace() throws ObjectStreamException;
通过writeObject和readObject方法可以自定义序列化逻辑,例如额外序列化一个字段等。
而通过writeReplace和readResolve方法可以在写对象前和读取流之后对操作对象做自定义修改。这四个方法的执行顺序是writeReplace->writeObject->readObject->readResolve

其他序列化工具

java的序列化性能一直都不被恭维。而当前市面上也有很多开源的序列化工具,例如kyro,protostuff等,这些开源的序列化工具的性能大大超过了java本身的序列化性能。
以kyro举例,将同一个对象序列化反序列化50000次,得出的耗时时间比:

Output output = new Output(1, 4096);
kryo.writeObject(output, test);
byte[] bb = output.toBytes();
output.flush();
   
Input input = new Input(bb);
KryoTest s = (KryoTest) kryo.readObject(input, KryoTest.class);
AssertUtil.assertNotNull(s);
input.close();
耗时时间:654:2056(ms)

2012年10月9日星期二

JVM原理简述

什么是JVM?

         JVM是Java Virtual Machine(java虚拟机)的简称。它是一种规范。通过在实际计算机上仿真模拟各种计算功能来实现的。Java虚拟机有自己完善的硬件体系架构,例如处理器,堆栈,寄存器等,还有相应的指令系统。由于JAVA虚拟机屏蔽的具体操作系统平台的相关信息,使得java程序只需要生成在java虚拟机上运行的代码(字节码),就可以在多种不同平台上运行。而字节码又经过java虚拟机解释成具体平台的机器指令来执行。

什么是字节码?

          ByteCode指通过编译生成,但是与机器码无关,通常要经过解释器转义后才可以成为机器码的中间代码。编译器将特定的源码编译为字节码,而字节码又经过虚拟机转义成为可以直接运行的指令。

JVM的执行过程

编译

        由java编译器将源码编译为字节码(class文件)
        编译过程如下:
        1. 词法分析,扫描类名,变量和函数
        2. 语法分析
        3. 注解处理
        4. 语义分析,名字,表达式,变量,方法,类型,参数等一系列检查
        5. 生成字节码文件(class)

装载

        首先,由类装载器(classloader)装载class文件。
        1. BootStrapClassLoader装载jdk/lib下和Xbootclasspath指定的所有jar包;
        2. ExtClassLoader装载jdk/lib/ext下和-Djava.ext.dirs指定的所有jar包
        3. 由AppClassLoader装载-classpath和-Djava.class.path指定的目录和jar;
        4. 自定义类装载器,运行时动态加载class文件。
       注:装载时会进行验证,如果找不到class文件会抛出classNotFoundException
       装载完成后,会进行字节码校验:
       1. FileFormat-VerifyError
       2. 引用类:NoClassDefFoundError
       3. 变量:NoSuchFieldError
       4. 方法:NoSuchMethodError
       5. 权限
       检查完毕后,开始初始化class。
       jvm中执行代码有两种方式:1. 解释执行,即由jvm中的java解释器将字节码解释为不同操作系统平台的指令执行;2. 编译执行,将字节码编译为机器码执行。这个编译过程由JVM中的JIT完成。只有使用频率高的代码才会被JIT编译为机器码执行,以提高效率。

JVM内存区域

由图1中可以看出,JVM内存区分为1. 方法区,2. 堆,3. 栈,4. 程序计数器,5. 本地方法栈。

方法区(Perm区)

        由classloader加载的class的类型信息全部存储在方法区中。所有的静态变量也存在于方法区中。方法区是所有线程共享的,因此方法区是线程安全的。方法区是内存永久保存区域,当perm空间不足时,会引发full GC,GC之后还不足,会抛出permGen space不足的错误。-XX:PermSize, -XX:MaxPermSize来指定方法区的最小和最大值。
        主要存储内容:
        1. 此类型的全限定名
        2. 此类型的超类的全限定名
        3. 此类型是接口还是类
        4. 此类型的访问修饰符号(public,private等)
        5. 此类型的常量池,字段,方法信息
        6. 除常量意外的所有静态变量
        7. 一个到classloader的引用
        8. 一个到class的引用

        JVM的堆中存在了所有类的实例和所有数组。一个JVM只有一个堆空间,各个线程都共享这个空间。
        图中可以看出,堆分为young区和old区,一般young和old的配置比例为1:m。
        young区保存大部分新对象,便于高效回收。其中Eden区存储新创建的对象,Eden满的时候,会有一次小范围的youngGC,将Eden中依然存活的对象存储至from,当from满时,此区域的活动对象会移动到to,直到from/to区域都满的时候,依然存活的对象会移到old区。-Xmn设置新生代大小。-XX:NewSize和-XX:MaxNewSize设置young的最小值和最大值;-XX:NewRatio=m设置young:old=1:m。-XX:SurvivorRatio=8设置Eden和from/to的比例为8:1:1
        old区保存存活时间较长的对象,当整个新生代满的时候,会触发一次full GC,将新生代的存活对象移到old。通过-Xmx-Xmn设置旧生代大小。
-Xmx和-Xms来指定heap的最大和最小值,避免JVM动态调整则设置为同一个值即可。

程序计数器

每一个线程都有自己的程序计数器,里面存放了下一条被执行指令的地址。

        每一个线程被创建的时候,都会得到自己的程序计数器和java栈。java栈以帧为单位保存调用信息。当线程调用一个方法,JVM会向栈中压入一个新的栈帧,当方法调用结束时,栈帧会被弹出。java栈帧是线程私有的,不存在多线程的安全性问题。
        栈帧由3部分构成,局部变量区,操作数栈,帧数据区。
        1. 局部变量区
        编译器将调用方法的局部变量和参数按照声明顺序放入局部变量数组中,从0开始计数。如果是实例方法,this将被存储到数组的第一个位置。java中的对象传递都是按照引用传递的,因此java对象全部存储在堆中,而局部变量区和操作数栈中只存储了对象的引用。
        2. 操作数栈
        JVM把操作数栈作为它的工作区,大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈,然后等相关的指令将结果再次弹出。操作数栈扮演了暂存操作数的角色。
        3. 帧数据区
        栈帧还可以存储常量池的解析,正常方法返回和异常派发机制。这些都存在帧数据区。当方法要访问常量池时,会通过帧数据区的指向常量池的指针来访问。
        如果方法正常返回,jvm会将返回值压入调用方法的栈帧的操作数栈中。
        为了处理异常,帧数据区还保存了一个对异常表的引用。

本地方法栈

        主要是为JVM使用本地方法而服务的。线程调用本地方法时,JVM会保持java栈不变,不再压入新的栈帧。在sun的hotspotJVM中,本地方法栈和栈是合二为一的。

垃圾回收

放在下一篇文章中讨论。

2012年9月27日星期四

CAP设计原则

CAP设计原则,是Brewer教授在2000年提出的,它包括了三个方面:
         C(consistency)一致性:数据的一致性,多台服务器间的数据保持一致。
         A(Availability)可用性:即表示性能要求,响应速度快,效率高。
         P(Partition tolerance):分区容错性。一个服务需要在局部出错的情况下,没有出错的那部分被复制的数据分区仍然可以支持部分服务的操作,可以简单的理解为可以很容易的在线增减机器以达到更高的扩展性,即所谓的横向扩展能力。

以上的三点,同时最多只能满足两点。而分区容错是必须要被满足的。大部分情况都会在CA中间进行取舍。

        在海量数据访问环境下,会有一些基本的策略来提高服务质量,例如:

1. 数据sharding。数据分片能够将海量数据的单张数据表拆分,从而提高单表查询效率。但是由此带来的数据一致性维护开销会大大加大。
2. 缓存(cache)。增加cache会大大提高访问速度,但是cache的扩展又是一个头疼的问题,即使在使用一致性hash算法的前提下,扩展节点仍然会造成cache的失效问题。我们还会有一个监控系统,来监控每个节点的cache的状态,包括容量,命中率等。
3. 灰度发布:只开放部分用户验证系统的可用性,在服务稳定后再向外完全推出也是一个很好的策略方案。
4. 监控和报警。没有完美运行的服务,及时发现解决问题是必不可少的。良好的监控报警能够使服务不断的得到优化,从而越来越健壮。
5. 配置中心化:中心化的配置和监控也是很好的策略,能够帮助管理海量服务,并随时监控服务状态。
6. 自定义的协议。

2012年9月20日星期四

数据底层的多数据源和分布式事务

    SOA(面向服务的架构)在已经相当流行;在大数据处理的时候,分表,分区,分库等操作也一直伴随着一起发展,这里就涉及到了数个数据源,读写分离,事务等一系列的问题。
    本文主要记录了通过封装一个dao底层,来实现多数据源分库操作,通过分布式事务保持数据的强一致性。
    SOA的事务,也主要分为两个方向:
    1. 弱一致性,即单个的数据操作保持事务一致;这样可以保持高性能,在数据请求量很大的情况下对性能有保证;
    2. 强一致性,即一个service方法的数个操作在同一个事务中,由于数个操作的数据源可能不一样,也可能由多个dao service构成,这时就要通过分布式事务保持数据的一致,其中一个操作失败,所有的数据都回滚。这里适用于对数据一致性要求很强的场景。分布式事务会对性能有一定的影响。

    言归正传,本文主要使用了hibernate4,spring3.1,atomikos3.8来实现整体结构。设计的目的是一个方便的底层DAO的封装,而分布式事务会在上层的service控制,而不在DAO层控制,避免事务嵌套造成的问题。

第一部分:底层控制代码编写:

1. 配置dynamicDataSource,代码如下:
/**
 * 动态数据源
 * @author wenfengsun
 * @since 2012-9-19 下午3:47:15
 */
public class DynamicDataSource extends AbstractRoutingDataSource{
      @Override
      protected Object determineCurrentLookupKey() {
             String key = DataSourceContextHolder.getDataSource();
             if(key == null)
                   key = "ds0";
             return key;
      }
}
其中,DataSourceContextHolder利用了ThreadLocal为每个线程设置不同的变量值,DataSourceContextHolder的代码如下:
/**
 * 获取或设置当前线程的dataSource
 * @author wenfengsun
 * @since 2012-9-19 下午2:57:36
 */
public class DataSourceContextHolder {
     private static final ThreadLocal contextHolder = new ThreadLocal();  
     public static void setDataSource(String dsName){  
          contextHolder.set(dsName);  
     }  
     public static String getDataSource(){  
          return (String) contextHolder.get();  
     }  
     public static void clearDataSource(){  
          contextHolder.remove();  
     }  
}

解释:在每次请求来临时,根据不同的路由策略,设置不同的dataSource,从而达到分开存储的目的。
2. 配置sessionFactory和存储接口
public class Config {
       private static Config _instance;
       private SessionFactory sessionFactory;

       public static Config getInstance(){
              if(_instance == null){
                       synchronized (Config.class) {
                            _instance = new Config();
                       }
              }
              return _instance;
       }
       Config(){
              initSessionFactoryWithTransactionManager();
       }
}

我们定义了一个config,它是一个单例模式的实现,通过方法:initSessionFactoryWithTransactionManager来实现数据源和sessionFactory的初始化工作;
3. 方法initSessionFactoryWithTransactionManager的实现:
解释:首先建立了两个数据源,通过一个单例的map存储。并将数据源赋予给之前声明的dynamicDatasource,然后初始化sessionFactory,注意红色部分和删除的部分,不要写错,否则分布式事务会报错。
public void initSessionFactoryWithTransactionManager(){
    DynamicDataSource dds = new DynamicDataSource();
然后再增加一个save方法:
public Serializable save(Serializable id, Object obj) throws NotSupportedException, SystemException{
        long shardId = (Long)id;
        if(shardId%2==1){
              DataSourceContextHolder.setDataSource("ds1");
        }else
              DataSourceContextHolder.setDataSource("ds0");
        System.err.println("sessionFactory:"+sessionFactory);
        sessionFactory.openSession();
        Session session = sessionFactory.getCurrentSession();
     //  session.beginTransaction();
        Serializable ret = session.save(obj);
    //  session.getTransaction().commit();
        return ret;
}
此处,基数存储到数据源ds1,偶数存储到数据源ds2
请注意:删除的部分,由于要在service中控制事务,此处的session的事务代码务必删除,否则会报一个错误:

Cannot call method 'commit' while a global transaction is runing

好,至此我们底层的控制已经完成,现在新建一个接口Dao和一个实现DaoImpl,调用save方法存储对象;(代码掠过)

第二部分:上层service和分布式事务

第二部分的配置依赖于spring。
<bean id="dao" class="com.xx.dao.impl.DaoImpl"/>
<bean id="userService" class="com.xx.service.impl.UserServiceImpl">
          <property name="dao" ref="dao"/>
</bean>
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init"  destroy-method="close">
          <property name="forceShutdown">
                   <value>true</value>
          </property>
</bean>
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
          <property name="transactionTimeout" value="240"/>
</bean>
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
          <property name="transactionManager" ref="atomikosTransactionManager"/>
          <property name="userTransaction" ref="atomikosUserTransaction"/>
</bean>
<tx:annotation-driven  transaction-manager="transactionManager"/>


我们声明了Dao和Service,并且声明了transactionManager。在最后一行tx声明之后,可以在service的实现类中的方法加入@Transactional即可控制事务。例如:
@Transactional
public long save(User user){
       User u1 = new User();
       u1.setName("tttt---------------");
       long res = (Long)dao.save(10L, user);
       dao.save(11L, u1);
       return res;
}


当其中一个save失败时,整个事务会回滚;

Mac配置Dnsmasq自动获取被屏蔽网站的DNS

使用前提:有一个虚拟主机(EC2...);有openvpn,pptp等服务已经运行;有一台mac;
背景:GFW已经开始污染来自所有外部的DNS服务的返回结果,例如著名的8.8.8.8,8.8.6.6都已经无法针对twitter等网站返回正确的DNS。

1. 使用port安装dnsmasq(注:port的使用见之前的博客文章)
>sudo port install dnsmasq
2. dnsmasq默认安装在/opt/local/下,配置文件在/opt/local/etc/dnsmasq.conf;Dnsmasq启动请直接执行:sudo launchctl load -w /Library/LaunchDaemons/org.macports.dnsmasq.plist  (这个plist文件已经生成完毕)。启动之后netstat -na|grep LIST,发现53(dns)端口已经开始监听了。(注:修改配置文件需要unload之后在load一次)
3. 配置你的EC2主机,在EC2上安装dnsmasq。(我的是ubuntu,直接apt-get install即可)并且配置该DNS的解析走默认的amazon的DNSserver。这样我们就在EC2上启动了一个DNS服务;
4. 将你的EC2的主机的外网IP设置到你的vpn的路由表里,使53的DNS服务也从vpn路由出去,而不要通过直接连接的形式(直连也被污染)
5. 配置mac本地的Dnsmasq的配置文件,cp /etc/resolv.conf /etc/resolv.dnsmasq.conf拷贝你的本地DNS配置,然后修改dnsmasq的配置文件,设置:resolv-file=/etc/resolv.dnsmasq.conf
6. 设置你要解析的host:
server=/twitter.com/xxx.xxx.xxx.xxx(这里写你的EC2的外网IP)
...
7. 修改你当前网卡的DNS设置:修改到127.0.0.1
8. 删除之前配置的hosts
9. nslookup,可以看到twitter.com的host已经正常解析,无需配置hosts文件了。

2012年9月13日星期四

Ps拼接全景图

其实用PS拼接全景照片非常简单,File->Automate->Photomerge,选择要拼接的照片,然后选择默认的Auto模式即可,待拼接的照片需要是同一位置,同焦距拍摄的多张角度的照片,拼接会消耗一定的时间,拼接完成后是一张不规则形状的图片。剪裁一下,成为一张长方形图片即可。
附:厦门鼓浪屿日光岩拍摄的一组照片的合成全景图。

sed命令添加文字在行首或行尾

1. 在行首添加文字HEAD
sed 's/^/HEAD&/g' file
如果需要直接修改文件:
sed -i 's/^/HEAD&/g' file
在mac下稍有不同:
sed -i .bak 's/^/HEAD&/g' file
mac下会将原文件增加.bak后缀做备份,而普通linux不会。
2. 在行尾增加文字TAIL
sed 's/$/&TAIL/g' file
同理,加入-i参数会直接修改当前文件。

2012年9月7日星期五

UDP隧道打洞技术研究

        UDP在点对点通信中起到了很大的作用,比如skype在用户点对点通信的时候就使用了UDP打洞的技术;
        所谓打洞,实际就是绕过防火墙或路由器的拦截,实现用户终端的直接连接。这里就需要先介绍一下NAT(Network Address Translation)了。它的核心是在IP封包通过防火墙或路由器时,重写源IP地址或目的IP地址的技术;
        下图展示了一个带NAT的网络,路由器后的网络地址和路由器的网络地址是变化的。

       NAT设备有四种模式,还可以简化为两种类型,即对称模式和非对称模式。非对称模式还分为1. 自由模式 2. 受限IP模式 3. 受限IP和端口模式;
       1. 自由模式:凡是内网的一个IP+PORT发起的UDP广播,在经过路由器之后都会映射到一个固定的端口,并且外部所有的IP和端口都可以通过路由器映射的IP+Port回访数据;
       2. 受限IP模式:凡是内网的一个IP+PORT发起的UDP广播,在经过路由器之后都会映射到一个固定的端口,只有接收方的IP可以发送回访数据,端口不限;
       3. 受限IP+port模式:凡是内网的一个IP+PORT发起的UDP广播,在经过路由器之后都会映射到一个固定的端口,只有接收方的IP和指定的PORT发起的回访数据,会被接受;
       4. 对称模式:凡是内网的一个IP+PORT发起到指定IP+port的UDP广播,在经过路由器之后都会映射到一个固定的端口,只有接收方的IP和指定的PORT发起的回访数据,会被接受;

       换句话说,只有非对称模式的NAT设备才可以进行UDP打洞;

       UDP打洞原理:
       1. 有一个协调方,我们称之为server;
       2. A,B分别和S相连;S即得到了A,B在最外层路由的ip地址和端口号;
       3. A要向B发起连接,先向S请求连接,S将B的ip+端口发送给A;
       4. A向B的IP+端口发送UDP数据包,被B的防火墙或路由抛弃;
       5. A向S通知,已经向B发送过数据;
       6. S通知B,向B发送A的ip+port,并通知B向A发送数据;
       7. B向A发送数据,由于A向B发送过数据,因此A的NAT设备的session里已经记录了B的ip+port,B的数据过来后,会被A的设备放行,数据将被直接传递到A的终端;
       8. A收到B的数据后,回数据给B,此时B的设备也记录了A的ip+port,因此通信建立成功;
       
       测试了一下,发现公司的环境和家里的环境,全部是对称模式,抓包时发现端口号已变,无法打洞:(
     

2012年8月30日星期四

SecureCRT环境配置备忘(MAC)

1. 配置环境色彩

色彩和环境配置有关系,直接编辑~/.bash_profile,加入:

export CLICOLOR=1
export LSCOLORS=gxfxaxdxcxegedabagacad
重启secureCRT即可看到颜色;

2. 配置vim颜色

拷贝:sudo cp /usr/share/vim/vimrc ~/.vimrc
在最后加入:syntax on, 重启secureCRT或者注销用户重新登录即可;

3.乱码

首先,要设置secureCRT的环境为UTF-8, 在session option-->Terminal-->Apperence中设置。
然后,在终端输入locale 查看语言环境,一般会显示:

LANG=
LC_COLLATE="C"
LC_CTYPE="C"
LC_MESSAGES="C"
LC_MONETARY="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_ALL=
这时会发现中文是乱码。此时需要export这几个变量,编辑~/.bash_profile,加入:

export LANG="zh_CN.UTF-8"
export LC_COLLATE="zh_CN.UTF-8"
export LC_CTY=PE"zh_CN.UTF-8"
export LC_MESSAGES="zh_CN.UTF-8"
export LC_MONETARY="zh_CN.UTF-8"
export LC_NUMERIC="zh_CN.UTF-8"
export LC_TIME="zh_CN.UTF-8"
重启secureCRT或注销重进,发现以前的乱码消失了。

4 设置xterm

我们会发现,在vi或vim结束退出 后,有些term可以关闭之前编辑的内容,有些则不会,这其实是因为不同的term启用了不同的策略,关闭的情况是vim启用了另外一个显示区域,当退出后,这个区域就会关闭。
这个其实很容易设置:
session option-->Terminal-->Emulation中,把Terminal从ANSI或Linux修改为xterm即可。


2012年8月28日星期二

vi命令总结

插入字符

i  在光标前插入正文
I  在当前行开始处插入正文
a  在光标后插入正文
A  在当前行末尾插入正文
o  在当前行后插入一新行
O  在当前行前插入一新行

保存文件

:q!  放弃任何改动并退出 vi 系统
:w <filename>  保存当前的文件
:wq  保存当前的文件,并退出 vi 系统
:x  与 wq 类似
:zz  与 wq 的工作类似

删除字符

x  删除光标处字符(Character)
nx  删除光标处后 n 个字符
nX  删除光标处前 n 个字符
ndw  删除光标处下 n 个单词(word)
dd  删除整行
d$或 D  删除由光标至该行最末
U  恢复前一次所做的删除
dw 删除一个单词

替换字符

r char  由 char 代替光标处的字符
Rtext〈Esc〉 由 text 代替光标处的字符
cw  text〈Esc〉 由 text 取代光标处的单词
C text〈Esc〉 由 text 取代光标处至该行结尾处
Cc  使整行空白,但保留光标位置,让你开始输

文本搜索

/str〈Return〉 向前搜寻 str 直至文件结尾处
?str〈Return〉 往后搜寻 str 直至文件开首处集成开发环境的简介
n  同一方向上重复检索
N  相反方向上重复检索

其他

gg 去文本最开头
G 去文本最末尾
0(数字零) 去本行开头
$ 去本行结尾
:xx xx是你要跳转的行数
ctrl+f 下翻页
ctrl+b 上翻页

MAC做服务器开发环境笔记

1. 如何在term里高亮语法。
    a)先将vim的配置cp一份到用户的主目录,cp /user/share/vim/vimrc ~/.vimrc
    b)再chmod 777 ~/.vimrc
    c)编辑~/.vimrc,在最后一行加入syntax on,保存后退出即可。
2. 如何设置Open-vpn
    a)下载Tunnelblick,安装完成后,新建一份ovpn的配置文件;
    b)编辑文件,修改remote的ip地址和端口为你的openvpn的远程端口和ip
    c)设置ca.crt, client.key, client.crt为对应的存储目录
    d)配置Tunnelblick, DNS选为不选择域名服务;
    e)配置翻墙的hosts,例如twitter,fb
    f)配置路由脚本,拨号成功后要执行脚本,来实现指定路由;(目前还没找到全自动方案)
3. 如何查看路由表:netstat -r 原有的route -n 失效
4. 添加指定路由命令:sudo route add -net 74.0.0.0/8 x.x.x.x(x.x.x.x表示是你的openvpn的设备的ip段,选择ip段内任何一个ip即可)
5. mac下的apt-get:port 安装完成后,使用需要gcc,make命令,这些命令由xcode的command line tool提供。首先安装xcode,约1.4G左右,然后在xcode的偏好设置界面的downloads里可以安装command line tool。
------------------------
6. 自动openvpn翻墙(0916更新)
前置安装:port,openvpn2,xcode,gcc command line。
a)安装port,http://www.macports.org/install.php#pkg
b)安装xcode,可以直接通过appstore安装,免费
c)完成后,打开xcode,打开偏好设置,在下载一栏,安装command line tools
d)通过port安装openvpn2,sudo port install openvpn2
e)安装tuntap虚拟设备:http://tuntaposx.sourceforge.net/
f)将以前的ca.crt, client.key, client.crt, openvpn.conf拷贝到mac电脑上,执行:sudo openvpn2 --config openvpn.conf 可以看到成功拨号;
g)修改之前的conf文件:(增加红色项)其中,route2.set是设置vpn的脚本:sudo route add -net 74.0.0.0/8 x.x.x.x
client
dev tun
proto tcp
remote  remote_ip remote_port
resolv-retry infinite
nobind
mute-replay-warnings
ca ca.crt
cert client.crt
key client.key
comp-lzo
verb 4
status /etc/openvpn2/openvpn-status.log
script-security 2
up /etc/openvpn2/route2.set
h)增加openvpn2.plist到/Library/LaunchDaemons/,内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd";>
<plist version="1.0">

<dict>
        <key>Label</key>
        <string>org.openvpn2</string>
        <key>OnDemand</key>
        <false/>
        <key>Program</key>
        <string>/opt/local/sbin/openvpn2</string>

        <key>ProgramArguments</key>
        <array>
                <string>openvpn2</string>
                <string>--config</string>
                <string>client.conf</string>
        </array>
        <key>RunAtLoad</key>

<true/>
        <key>TimeOut</key>
        <integer>90</integer>
        <key>WorkingDirectory</key>
        <string>/etc/openvpn2</string>
</dict>
</plist>
i)执行:sudo launchctl load -w /Library/LaunchDaemons/openvpn2.plist 即可拨号,每次开机启动自动拨号;断线自动拨号;
done;
备注:启动plist的日志在/var/log/system.log,如果启动有问题,日志会打印:(org.openvpn2[5586]): Exited with code: 1
(org.openvpn2): Throttling respawn: Will start in 10 seconds
则可以修改plist文件:

<key>StandardOutPath</key>
<string>/var/log/openvpn-out.log</string>
<key>StandardErrorPath</key>
<string>/var/log/openvpn-err.log</string>
增加错误日志,可以在错误日志中查看具体的错误。

2012年8月23日星期四

hadoop的五个典型场景

Hadoop作为大数据存储及计算领域的一颗明星,目前已经得到越来越广泛的应用。下面PPT主要分析了Hadoop的一些典型应用场景,并对其进行了深入分析,主要包括下面几个方面:
  • 日志处理: Hadoop擅长这个 
  • 抓住本拉登: 并行计算
  • ETL: 每个人几乎都在做ETL(Extract-Transform-Load)ETL负责将分布的、异构数据源中的数据如关系数据、平面数据文件等抽取到临时中间层后进行清洗、转换、集成,最后加载到数据仓库或数据集市中,成为联机分析处理、数据挖掘的基础。 
  • 使用HBase做数据分析: 用扩展性应对大量的写操作—Facebook构建了基于HBase的实时数据分析系统
  • 机器学习: 比如Apache Mahout项目

2012年6月8日星期五

memcached启动参数


-p <num>     指定监听的TCP端口号,默认是11211
-U <num>     指定监听的TCP端口号 (默认: 11211, 0 表示关闭)
-s <file>     UNIX socket path to listen on (disables network support)
-a <mask>     access mask for UNIX socket, in octal (default: 0700)
-l <addr>    指定绑定的IP地址,多个地址用逗号分隔。
-d            以后台进程运行
-r            maximize core file limit
-u <username> 指定运行的帐号(只有root时有效)
-m <num>      最大内存使用值,单位是M(default: 64 MB)
-M            return error on memory exhausted (rather than removing items)
-c <num>      最大并发连接数 (默认: 1024)
-k            lock down all paged memory.  Note that there is a
              limit on how much memory you may lock.  Trying to
              allocate more than that would fail, so be sure you
              set the limit correctly for the user you started
              the daemon with (not for -u <username> user;
              under sh this is done with 'ulimit -S -l NUM_KB').
-v            日志模式 (在事件循环中打印错误/警告)
-vv           强日志模式 (还打印客户端的命令/响应)
-vvv        最大日志模式 (打印内部状态交互信息)
-h            打印帮助信息
-i            打印 memcached 和 libevent 的版权信息
-P <file>     -d参数下,指定pid file
-f <factor>   chunk size growth factor (default: 1.25)
-n <bytes>    为 key+value+flags  分配的最小空间(字节)(default: 48)
-L            Try to use large memory pages (if available). Increasing
              the memory page size could reduce the number of TLB misses
              and improve the performance. In order to get large pages
              from the OS, memcached will allocate the total item-cache
              in one large chunk.
-D <char>     Use <char> as the delimiter between key prefixes and IDs.
              This is used for per-prefix stats reporting. The default is
              ":" (colon). If this option is specified, stats collection
              is turned on automatically; if not, then it may be turned on
              by sending the "stats detail on" command to the server.
-t <num>      线程数 (default: 4)
-R            Maximum number of requests per event, limits the number of
              requests process for a given connection to prevent
              starvation (default: 20)
-C          禁用 CAS
-b            Set the backlog queue limit (default: 1024)
-B            Binding protocol - one of ascii, binary, or auto (default)
-I            Override the size of each slab page. Adjusts max item size
              (default: 1mb, min: 1k, max: 128m)
-o            Comma separated list of extended or experimental options
              - (EXPERIMENTAL) maxconns_fast: immediately close new
                connections if over maxconns limit
              - hashpower: An integer multiplier for how large the hash
                table should be. Can be grown at runtime if not big enough.
                Set this based on "STAT hash_power_level" before a
                restart.

2012年6月4日星期一

在Amazon的EC2上搭建pptp-vpn和openvpn

        首先要去申请一个amazon的账户,这个阶段你可能需要一个visa信用卡来激活你的帐号,另外会有一个印度的呼叫中心用蹩脚的英语来核实你的基本信息。
        好,假设我们认为你已经有了个amazon的帐号,你需要登录到amazon的后台console,选择上边的EC2的tab,然后注意选择左上角的大区,这取决于你的server位于哪个大陆,我们这里选择的是tokyo东京。
        我们要申请一个EC2的实例,这里推荐的是ubuntu11.04 32位的系统(RHEL5.x,6.x的tiny instance也是收费的,慎用)。这里面有一系列的步骤,之需要选择最低配置即可,中间会有一个生成安全私钥的过程,会直接下载的本地电脑中,假设这个密钥是sec.pem。我们保存好这个文件,之后的ssh,scp等操作都需要用这个文件。
        启动你的console。并设置安全拦截(Security groups)放行443,1194(openvpn)和1723(pptp)
-----------------------------
22 (SSH)0.0.0.0/0
443 (HTTPS)0.0.0.0/0
1194 0.0.0.0/0
1723 0.0.0.0/0
-----------------------------
        ssh:ssh -i sec.pem ubuntu@{dynamic_Dns_name}连接到你启动的ubuntu实例。
        ssh成功后,查看一下虚拟主机的配置,是613M内存,8G硬盘空间;
        安装pptpd,apt-get install pptpd;
        修改一下/etc/pptpd.conf 打开localip和remoteip选项;
        修改/etc/ppp/pptpd-options, 设置dns ms-dns 8.8.8.8 ms-dns 8.8.4.4;
        修改/etc/ppp/chap-secrets 设置客户端的用户名密码 pptpd * pptpd *;
        重启pptpd:pptpd restart;
        修改/etc/sysctl.conf,打开注释的net.ipve.ip_forward=1,然后运行sudo sysctl -p
        修改iptables,设置网络地址翻译:sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;/sbin/iptables -A INPUT -p gre -j ACCEPT
        设置到/etc/rc.local避免重启丢失:在exit 0之前添加:iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE;iptables –table nat –append POSTROUTING –jump MASQUERADE;
        好,上面的一系列步骤帮助 你设置好了一个pptp的server,下面要在ubuntu的环境下设置client。
        首先create一个pptp连接,pptpsetup --create pptpd --server ec2_dynamic_DNS --username [帐号] --password [密码] --encypt,以后连接和取消vpn执行pon pptpd和poff pptpd即可;
ppp/peers/下建立了一个vpn_name的连接,info如下:
----------vpn_name--------
# written by pptpsetup
pty "pptp xxx.xxx.xx.xxx --nolaunchpppd"
lock
noauth
nobsdcomp
nodeflate
name xxx
remotename pptpd
ipparam pptpd
require-mppe-128
--------------------------------
最后一行表示使用mppe128位加密;(即--encypt)
        执行sudo pon即可拨号,这里有一个debug模式,命令是:pon xxx debug dump logfd 2 nodetach (xxx为你设置的vpn名称)
拨号成功后,可以通过增加路由策略实现有选择的翻墙;
增加一个路由策略:route add -net 199.59.0.0/16 dev ppp0,即可把twitter加入到vpn策略中。
错误分析:
如果在debug模式下出现多行sent [LCP ConfReq ...] 这时说明你的GRE协议穿透有问题;你需要重点关注协议在哪一方被拦截了;
可以通过抓包来判断,分别在server和client对1723端口抓包,可以轻易的判别是server拦截了GRE还是client端的环境拦截了GRE。server你需要配置好防火墙规则,client你需要检查你的网络防火墙设置。
====PPTP结束====

2012年4月19日星期四

JVM命令行分析工具

先附上一张JVM参数大全:http://kenwublog.com/docs/java6-jvm-options-chinese-edition.htm
1. jstat-是JDK自带的一个轻量级小工具。全称“Java Virtual Machine statistics monitoring tool”,它位于java的bin目录下,主要利用JVM内建的指令对Java应用程序的资源和性能进行实时的命令行的监控,包括了对Heap size和垃圾回收状况的监控。
命令行格式:

jstat [ generalOption | outputOptions vmid [interval[s|ms] [count]] ]


jstatOptions:
-gcutil 打印gc的统计信息,例如 jstat -gcutil 1234 1000 100 表示每隔1秒打印一次pid为1234的gc统计信息,打印100次;

S0     S1     E      O      P     YGC     YGCT    FGC    FGCT     GCT
100.00   0.00   7.86  96.32  99.36 124512  329.588   346  161.916  491.504
  0.00  97.27  21.61  96.33  99.36 124513  329.591   346  161.916  491.507
  0.00  91.02  99.64  96.38  99.36 124515  329.597   346  161.916  491.513
E:Eden区使用的百分比;
O:Old区使用的百分比;
P:perm区使用的百分比;
YGC:youngGC的次数;
YGCT:youngGC总共耗时(秒)
FGC:fullGC的次数
FGCT:fullGC的总耗时(秒)
GCT:GC总共耗时
-----------
其他选项:

-class Option 显示class的数量和占用空间等情况
-compiler Option 显示VM实时编译的数量等信息;
-gc Option 显示GC的一些信息,包含各个区所占大小和GC的次数以及时间统计
-gccapacity Option
-gccause Option
-gcnew Option
-gcnewcapacity Option
-gcold Option
-gcoldcapacity Option
-gcpermcapacity Option
-printcompilation Option
2.jstack 打印JVM堆栈信息,可以通过这个命令查询cpu过高或死锁一类的问题。
例如:jstack -l 1234 打印pid为1234的JVM的堆栈信息;
如果cpu占用过高,可以关注那些runnable的进程;
如果停止响应,可以关注那写locked的进程信息;
3.jmap-对JVM的内存进行查询或dump操作的命令;例如:jmap -histo pid 可以打印出JVM中占用内存按排名的java对象个数和所占内存大小;
命令行格式:

       jmap [ option ] pid
       jmap [ option ] executable core
       jmap [ option ] [server-id@]remote-hostname-or-IP

options:
executable Java executable from which the core dump was produced.
(可能是产生core dump的java可执行程序)
core 将被打印信息的core dump文件
remote-hostname-or-IP 远程debug服务的主机名或ip
server-id 唯一id,假如一台主机上多个远程debug服务
基本参数:
-dump:[live,]format=b,file=<filename> 使用hprof二进制形式,输出jvm的heap内容到文件=. live子选项是可选的,假如指定live选项,那么只输出活的对象到文件.
-finalizerinfo 打印正等候回收的对象的信息.
-heap 打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情况.
-histo[:live] 打印每个class的实例数目,内存占用,类全名信息. VM的内部类名字开头会加上前缀”*”. 如果live子参数加上后,只统计活的对象数量.
-permstat 打印classload和jvm heap长久层的信息. 包含每个classloader的名字,活泼性,地址,父classloader和加载的class数量. 另外,内部String的数量和占用内存数也会打印出来.
-F 强迫.在pid没有相应的时候使用-dump或者-histo参数. 在这个模式下,live子参数无效.
-h | -help 打印辅助信息
-J 传递参数给jmap启动的jvm.
pid 需要被打印配相信息的java进程id,创业与打工的区别 - 博文预览,可以用jps查问.
常用命令:
jmap -histo pid 打印内存中的对象信息;
jmap -dump:format=b file=heap.bin pid 将JVM以2进制文件的形式dump到磁盘上;
heap.bin可以通过mat或jhat进行进一步分析;
4. jhat&mat
分析jvm-dump的二进制文件的两种方法,其中mat是eclipse插件,jhat是命令行工具;
命令格式:
jhat [ options ] <heap-dump-file>


例如:jhat -J-Xmx512m heap.bin 可以在本地的7000端口启动一个服务,可以通过浏览器来访问获取统计信息;

2012年4月13日星期五

zookeeper笔记

zookeeper是hadoop的开源子项目。地址:http://zookeeper.apache.org
1. zookeeper是分布式的;
2. zookeeper是开源的;
3. zookeeper是一个协调服务,用于协调分布式应用的;
4. 典型应用:同步,配置维护,集群和命名服务;
设计目标:
1. 简单易用
2. 互相复制
3. 有序操作
4. 性能强大,处理速度快;
命名空间被设计成很类似一个文件系统,只不过无论是文件还是文件夹,都会有它自己的value。
zookeeper保证:
  • 顺序一致性 - Updates from a client will be applied in the order that they were sent.
  • 原子性 - Updates either succeed or fail. No partial results.
  • 单一的系统视图 - A client will see the same view of the service regardless of the server that it connects to.
  • 可靠性 - Once an update has been applied, it will persist from that time forward until a client overwrites the update.
  • 及时性 - The clients view of the system is guaranteed to be up-to-date within a certain time bound.

zookeeper的简单API:

create  creates a node at a location in the tree


delete  deletes a node
exists  tests if a node exists at a location
get data  reads the data from a node
set data writes data to a node
get children retrieves a list of children of a node
sync  waits for data to be propagated


更多可以查看zookeeper的官方文档:http://zookeeper.apache.org/doc/r3.3.2/zookeeperOver.html
zookeeper的应用场景:
1. 实时配置信息(client有一个watcher监控变化,实时改变)
2. 全局变量(是分布式应用级的,而非某单独事例)
3. 动态数据获取和更新(动态数据获取,以及人为更新,可以不通过接口方式实现)
4. 命名服务(Naming)
5. push的心跳检测,可以通过配置检测服务与zookeeper交互,而不直接与server交互)
6. 分布式锁(有点类似memcached的mutex模式中的add)
7. 集群, 例如分布式服务管理,动态增加或删除一个分布式服务,而无需重启。
8. 分布式队列
一个使用zookeeper作为协调RMI服务的例子。
目的:
         使用zookeeper作为RMI协调服务。在使用spring管理RMI服务的基础上,修改spring加载RMI的代码,加入zookeeper,使得spring由固定代理一个指定的RMI stub,变为动态加载多个RMI stub并且可以动态增减。

2012年4月12日星期四