| Roger Chen's profileLearningBlogLists | Help |
|
|
3/22/2006 LDAP的一些用途没使用LDAP时没感觉到它的好处,现在才觉得以前那真是生活在原始社会中……
你们是如何管理你们的服务器呢? 2/16/2006 Java SE 6 BetaSun发布了Java SE 6 Beta(以下简称6),查了查功能改进,没有发现什么激动人心的变化。挑了几条稍微感兴趣一点的评价一下。
6的新建连接的窗口有非常大的改变。在Local Process窗口列出了所有本地java进程(包括jconsole本身),不能通过Jconsole进行管理的进程处于disable状态。
但是6采用的是Java界面中最为难看的Window风格,而且还是实现的最差的一种(在新建连接这个对话框中倒感觉不出),还是5里面采用的Java风格更为美观一点。 个人认为jconsole中最难用的地方没有在这个版本中得到修改:MBean管理窗口无法复制。我甚至怀疑jconsole的开发人员有没有使用jconsole去真正管理过应用,难道他们都只是用眼睛看的么?每当需要一些MBean的属性当作参数去调用函数时,每当需要把方法的返回值记录下来留作参考时,我都狠的牙痒痒的对着屏幕把这些字符一个一个的移到需要的地方……
千呼万唤始出来啊,终于有方法可以拿到空闲磁盘空间了。在此之前只能用Apache commons io(非JNI方式,而是通过调用命令行得到)
对这个倒是很感兴趣,可惜描述的语焉不详,不知道到底加在了什么地方。找到的朋友请指点一下:) Update: 找到了,在com.sun.net.httpserver包下,网络方面的功能增强可见:http://java.sun.com/javase/6/docs/guide/net/enhancements-6.0.html
InterfaceAddress.getBroadcast() 得到该网络接口地址的广播地址 InterfaceAddress.getNetworkPrefixLength() 得到该网络接口地址的子网掩码 NetworkInterface.getMTU() 得到该网络接口的MTU的大小 NetworkInterface.getHardwareAddress() 得到该网络接口的MAC地址 等等一系列方法,全部在NetworkInterface/InterfaceAddress这两个类中,这些方法的增加对于大部分的网络应用是一个利好消息。
忘记以前运行java程序时要指定的-cp 1.jar;2.jar;3.jar吧,现在只需要-cp *即可匹配当前目录下的所有jar包。如果还要加上当前目录下的所有class,那就-cp .;*吧。
同步的开销被进一步降低,对于多线程应用来说,也算是一个利好消息。
改动还真不少,不过大部分改动对于我目前的应用而言没有什么影响。
至于那些新加入的JSR,一时半会还摸不清楚,有待研究:) 2/12/2006 修正对Sun Message Queue的错误认识感谢一位未署名的朋友指出,Sun的许多企业软件已经免费了,其中也包括Sun Message Queue Enterprise版本。
这样一来,该JMS产品应该会具有很强的竞争力。虽然我非常支持开源项目,但从我以前对Platform版的使用体验来看,众多开源JMS项目还需要再加把劲啊……
注:如果仅仅想下载Sun Message Queue Enterprise版本,可以从Solaris Enterprise System页面进去,选择Sun Java Application Platform Suite,再从后续的页面链接中单独下载Message Queue Enterprise Edition。
Update: 试用中发现Sun MQ一个Bug,发送的消息其String property长度不能超过65535字节,因为其采用DataObjectStream的writeUTF方法做字符串类型属性的编码,而该方法无法只保存两个字节的字符串长度,所以超出65535字节就会抛出异常。
居然都SP3了还没解决这种Bug,依照Sun的打补丁速度,那得再等一个季度了…… 2/9/2006 Concurrent 流量控制和Cindy 2.x版本一样,Cindy 3.0 a1中有一个比较大的毛病就是缺乏流量控制。如果生产者的速度超过消费者的速度,则产生的对象会一直堆在队列中,直到内存溢出。
不过幸运的是3.x采用了concurrent包,只需要很简单的几行代码就能实现流量控制。
Queue提供了一个offer方法,返回值是boolean类型。象LinkedBlockQueue这种类型的队列是没有容量限制的,所以其offer方法永远返回true;而ArrayBlockQueue这种类型的容量在构造时设置好的,超出容量后offer方法就会返回false。 如果要对Queue进行流量控制,只需要构造一个合适容量的ArrayBlockQueue实例,然后在offer方法返回false时可以做一些等待重试操作即可。
Executors提供了若干生成Executor的工厂方法,但是这些工厂方法中都没有做任何流量控制,所以如果要生成带流量控制的Executor,还是得直接构造java.lang.concurrent.ThreadPoolExecutor实例。 往ThreadPoolExecutor中添加任务的分派顺序如下:
ThreadPoolExecutor自带了四种RejectedExecutionHandler:AbortPolicy会抛出一个异常给应用,CallerRunsPolicy会直接在当前线程中执行该任务,DiscardPolicy会简单的放弃执行该任务,DiscardOldestPolicy会把当前队列中最老的一个任务给放弃掉,并尝试重新添加该任务。 如果我们仅仅想定时重试,可以实现一个RetryPolicy(注意:该简单实现没有判断当前线程是否为ThreadPoolExecutor中的工作线程,可能导致死循环):
用如下的代码可以构造一个单线程、队列中最多保存1000个任务的Executor:
1/17/2006 RMI and Full GC自定义JMX连接端口后,可以观察到一个很奇怪的现象:系统会周期性的做Full GC,即使当前Eden space和Survivor space还有充足的内存空间。
比如加上-verbose:gc参数运行如下的代码:
可以看到控制台会不停的输出:
在Sun的代码中不停的穿梭后终于发现,原来这是RMI实现上的问题。为了保证RMI原始对象在不被引用后尽量即时的销毁Stub,因此只要有未被释放的RMI实例存在,系统就会周期性的进行Full GC。如sun.rmi.transport.ObjectTable中的一段代码所示:
而gcInterval的值来自:
默认是每分钟做一次Full GC。我上面贴出来的运行结果是加了-Dsun.rmi.dgc.server.gcInterval=1000参数所得到的,即每秒钟做一次Full GC。
知道原理再改变就相对简单了:
比如如果绑定Sun的Java实现的话,可以用如下的代码:
在构造JMXConnectorServer前,对传入的env参数做如下设置:
UnicastServerRef2是Sun自己的实现,该实现exportObject方法的最后一个参数就是表示该对象是否会持久存在。
不过……这样实现后还是会进行一次Full GC(虽然也有办法避免,不过要更多的设置……)。呵呵,到此为止,因为解决了一个问题后总会来一个新问题的:) 1/16/2006 自定义JMX连接端口首先要从JMX URL说起。如下是一个典型的JMX URL:
这个JMX URL可以分为如下几个部分:
如果在服务器端,我们用该URL创建一个connector server,则大概流程如下:
可以看到,如果在服务器端创建connector server时,该URL的第三部分(即localhost:5000)如果省略的话,则connector server会随机任意选择一个可用的端口。 如果在客户端,我们通过该URL创建一个connector,则大概按照如下的流程:
可以看到,如果在客户端创建connector时,该URL的第三部分可以被省略掉。事实上我们也经常这么做,比如上面这个示例,在客户端建立connector所用的JMX URL可以被省略为:
从这个示例中我们也可以看到,如果采用rmi作为传输协议的话,客户端需要进行两个连接。首先客户端连接到rmiregistry上得到真实服务器的stub(如rmi://localhost:6000/rmxrmi),然后客户端再根据该stub连接到真实的服务器上(如rmi://localhost:5000)。
在Sun Java 5.0的实现当中,可以通过指定运行期属性来配置JMX。比如:
除开密码和权限控制的配置外,这段运行期相当于一个如下的JMX URL:
可以看到,通过指定com.sun.management.jmxremote.port属性,相当于指定了rmiregistry的运行端口,但是真正运行的服务器的stub export端口为0,会在运行期任意选择一个未被使用的端口号。如果该程序运行在防火墙后面,这个动态分配的端口号会让我们非常难以配置防火墙规则。 由于Sun Java 5.0实现的疏忽,并没有为我们提供一个运行期属性来配置stub export端口号,那么如何自定义一个JMX连接端口呢? 假设我们不传入任何运行期参数,仅通过代码来自定义一个connector server:
第一行代码指明在6000端口创建一个rmiregistry。 第二行代码创建了一个connector server。第一个URL参数传入指定的JMXServiceURL,比如new JMXServiceURL("service:jmx:rmi://localhost:5000/jndi/rmi://localhost:6000/jmxrmi"),值得注意的是红色部分标出的IP和端口号要和第一步中创建的rmiregistry一致。第二个参数指定创建时的环境,我们可以暂时传入一个空Map。第三个参数指定要创建连接的MBeanServer,比如在Java 5.0下可以是ManagementFactory.getPlatformMBeanServer()。 第三行代码开启了创建的connector server。 这三行代码创建的connector server工作的很好,除了一点:真实的服务器没有任何认证和授权机制,会允许所有人访问。 自定义权限控制可以通过刚才第二行代码的第二个参数来进行设置:
比如在MX4J中就提供了一个mx4j.tools.remote.PasswordAuthenticator的类,通过配置文件来匹配用户名和密码;Sun也提供了一个com.sun.jmx.remote.security.JMXPluggableAuthenticator的类通过JAAS来配置验证和权限。用户可以参照这两个类来提供自定义的实现。这里提供一个简单的示例(不带任何效验机制,仅用于示例) env.put(JMXConnectorServer.AUTHENTICATOR, new JMXAuthenticator() { public Subject authenticate(Object credentials) { } 加入这段代码后,真实的服务器必须要验证后才能访问,但又有了一个新问题:rmiregistry没有任何验证。这意味着其他人可以把一个不相干的stub绑定到同一地址,从而替换掉真实服务器stub。好在幸运的是,无法把一个服务器端类路径上不存在的stub类绑定到rmiregistry,否则真实的用户名和密码就有可能被泄漏出去。 如果直接通过rmi对外提供的接口,我们没有办法修正这个问题,除非将代码绑定于特定的Java实现,比如Sun就直接通过它自己的rmi实现类来做(所以通过运行期参数配置jmx生成的rmiregistry是只读的,没有该安全性问题)。具体可参见sun.management.Agent和sun.management.jmxremote包下相关类。 但是我们可以换一个思路,比如不绑定到rmiregistry而绑定到其他的实现了验证机制的jndi provider,例如ldap上。由于bind jndi的代码在相关connector server的实现类中,比如javax.management.remote.rmi.RMIConnectorServer中bind jndi的代码:
它在传入attributes时并没有特定的jndi的INITIAL_CONTEXT_FACTORY,所以我们可以通过两种方法来指定特定的INITIAL_CONTEXT_FACTORY:
其中第一种方式不需要额外的代码,但是可能会对系统中其他jndi的InitialContext产生影响;第二种方式稍微麻烦点,但是在实现上比较完美。 值得提到的是,如果采用rmiregistry,并且rmiregistry和server在同一个JVM内,stub和rmiregistry可以共享一个端口。比如可以使用如下的JMX URL:
但注意一定要在同一JVM内,比如先用LocateRegistry.createRegistry(6000)创建本地rmiregistry,再通过该URL开启connector server,则可以共享6000端口。 JMX最佳实践总结一下我目前认为的服务器端JMX最佳实践:
InitialContext 相关流程初始化属性优先级,排在前面的属性会覆盖后面的属性:
其中最后两个在加载后被缓存。 举个简单的例子,如果在jndi.properties中配置:
并且初始化InitialContext的代码是这样写的:
则实际采用的java.naming.factory.initial还是com.sun.jndi.fscontext.RefFSContextFactory。
如果初始化完成后,所采用的属性中仍然没有java.naming.factory.initial属性,则在通过JNDI绑定或查找时会继续查找。比如如果仅要简单的通过JNDI采用RMI来查找一个对象,可以:
可以不配置任何初始化属性,在查找时通过rmi://localhost/test这个URL会查找到:com.sun.jndi.url.rmi.rmiURLContextFactory类来初始化InitialContext。
我以前比较喜欢用无参数的构造函数构造InitialContext,将配置文件(jndi.properties)的路径加入到类路径中,这样感觉上最没有侵入性。不过现在发现在需要构造多个不同的InitialContext时,这种做法比较容易出问题。 比如构造JMX的connector server也许需要绑定到某rmiregistry,而取得JMS的ConnectionFactory和Destination也许需要连接到某LDAP,如果通过jndi.properties指定,其中一个肯定要出问题的。 1/11/2006 ActiveMQ 4 security setting该设置是基于ActiveMQ 4.0每天发布的snapshot版源代码分析得到,可能和正式版有所不同。不过看看Issues里该版本仅剩了若干不影响大局的Bug未修正,所以估计正式版在安全方面的设置和这里提到的应该没有太大变化。
相对3.x,最令我满意的是增加了JMX进行管理。虽然目前JMX管理仅仅是一个雏形,只可以start/stop以及查看一些统计信息,但是开了一个头,增加更多的管理方法就只是时间上的问题了。安全方面,4.0提供了一个基于Map的和一个基于JAAS的认证类,提供了一个基于Map的授权类,并且提供了若干基于JAAS的LoginModule(在源代码包的activemq-jaas目录下,并没有包含在二进制发行包中,需要自己编译),易用性相对3.x有所提高,但仍有很大不足。其他的一些改进可以看它的News in 4.0。
和以前版本一样,ActiveMQ默认的配置文件没有任何安全设置,这意味着如果没有在外部防火墙上做限制,任何人都可以连接到消息服务器上接收和发送消息。3.x的安全设置参见:simple ActiveMQ security setting。
如果通过代码来运行一个简单的消息服务器,可以:
BrokerService broker = new BrokerService(); broker.addConnector("tcp://localhost:61616"); broker.start(); 这段代码开启了一个Broker,监听本地61616端口,但是没有任何认证和授权功能。
如果仅要简单的用硬编码进行用户验证,可以在实例化Broker的时候改成:
BrokerService broker = new BrokerService() {
protected Broker addInterceptors(Broker broker) throws IOException {
broker = super.addInterceptors(broker); Map passwords = new HashMap(); passwords.put("name", "password"); broker = new SimpleAuthenticationBroker(broker, passwords, new HashMap()); return broker; } }; 这段代码只运行用户名为name,密码为password的用户访问JMS。SimpleAuthenticationBroker是BrokerFilter的子类,在Delegate前先进行用户验证。
如果要通过JAAS来进行用户验证,可以改为:
BrokerService broker = new BrokerService() {
protected Broker addInterceptors(Broker broker) throws IOException {
broker = super.addInterceptors(broker); broker = new JaasAuthenticationBroker(broker, "jassConfigName"); return broker; } }; 启动时通过-Djava.security.auth.login.config=配置文件 来指定JAAS的配置文件,比如如下的配置文件:
jassConfigName {
org.apache.activemq.jaas.PropertiesLoginModule required debug=true org.apache.activemq.jaas.properties.user="用户名和密码的配置文件,格式:用户名1=密码1\r\n用户名2=密码2" org.apache.activemq.jaas.properties.group="组名和用户名的配置文件,格式:组名1=用户名1,用户名2\r\n组名2=用户名1"; }; 如果在验证后还要进行授权,比如只允许某些用户访问一些特定的资源,则需要在上面的该方法重载中加入SimpleAuthorizationBroker实例。可参考activemq-core/org/apache/activemq/security以及activemq-jaas/org/apache/activemq/jaas中相关源代码和测试用例。
ActiveMQ 4.0改用xbean来从配置文件中加载配置,如果想通过配置文件的方式进行配置,则自己实现一个类来重载BrokerService,然后在配置文件中指定特定的的命名空间。比如如下一个典型设置:
Sun Message QueueSun Message Queue分为两个版本:Platform版(下面用P代替)和Enterprise版(下面用E代替)。
官方网站对两个版本的比较语焉不详,以下是我发现的主要区别:
其中最后一点最不厚道,P只允许两个用户同时接收某目的地上的消息,这不是用来玩的么……而且可恶的是自己网站上也不写明白,想骗用户先用上P,最好在发现限制后用户就直接花钱升级到E…… 软件做的还是不错的,稳定,管理方便,文档齐全。 感谢一位朋友指出了我的错误,Enterprise版本也是可以免费得到的。 12/21/2005 Direct vs non-direct ByteBuffer这两种类型的ByteBuffer相信大家都知道,但是两者的区别在什么地方呢?在不同的环境下采用哪种类型的ByteBuffer会更有效率呢?
先解释一下两者的区别: Non-direct ByteBuffer内存是分配在堆上的,直接由Java虚拟机负责垃圾收集,你可以把它想象成一个字节数组的包装类,如下伪码所示:
而Direct ByteBuffer是通过JNI在Java虚拟机外的内存中分配了一块(所以即使在运行时通过-Xmx指定了Java虚拟机的最大堆内存,还是可能实例化超出该大小的Direct ByteBuffer),该内存块并不直接由Java虚拟机负责垃圾收集,但是在Direct ByteBuffer包装类被回收时,会通过Java Reference机制来释放该内存块。如下伪码所示:
我相信大部分朋友们对上面的区别都应该很了解,那么还有什么其他的区别呢?嘿嘿,让我们稍微深入一点,翻到sun.nio.ch.IOUtil.java,绝大部分Channel类都是通过这个工具类和外界进行通讯的,如FileChannel/SocketChannel等等。我简单的用伪码把write方法给表达出来(read方法也差不多,就不多做说明了)
是的,在发送和接收前会把Non-direct ByteBuffer转换为Direct ByteBuffer,然后再进行相关的操作,最后更新原始ByteBuffer的position。这意味着什么?假设我们要从网络中读入一段数据,再把这段数据发送出去的话,采用Non-direct ByteBuffer的流程是这样的:
而采用Direct ByteBuffer的流程是这样的:
可以看到,除开构造和析构临时Direct ByteBuffer的时间外,起码还能节约两次内存拷贝的时间。那么是否在任何情况下都采用Direct Buffer呢? 不是。对于大部分应用而言,两次内存拷贝的时间几乎可以忽略不计,而构造和析构Direct Buffer的时间却相对较长。在JVM的实现当中,某些方法会缓存一部分临时Direct ByteBuffer,意味着如果采用Direct ByteBuffer仅仅能节约掉两次内存拷贝的时间,而无法节约构造和析构的时间。就用Sun的实现来说,write(ByteBuffer)和read(ByteBuffer)方法都会缓存临时Direct ByteBuffer,而write(ByteBuffer[])和read(ByteBuffer[])每次都生成新的临时Direct ByteBuffer。 根据这些区别,我会提出如下的建议:
基本上,采用Non-direct ByteBuffer总是对的!因为内存拷贝需要的开销对大部分应用而言都可以忽略不计。不过我做的是大规模的网络并发框架,因此对这些细节问题还是有必要有深入认识的,并且根据这些细节来调节自己的Buffer继承体系(再次抱怨,ByteBuffer无法扩展实在是一个非常非常非常费解的设计)
注:前面提到的“即使在运行时通过-Xmx指定了Java虚拟机的最大堆内存,还是可能实例化超出该大小的Direct ByteBuffer”中的可能是指可以通过-XX:MaxDirectMemorySize=<size>来指定Direct ByteBuffer实例最多可以使用的内存总数。如指定-XX:MaxDirectMemorySize=1024,则系统中所有存活的Direct ByteBuffer总内存数不能超过1024字节。 12/19/2005 java.lang.ref.Reference的糟糕实现今天在顺藤摸瓜的时候被java.lang.ref.Reference给恶心了一下……
大家经常接触的都是Reference的子类:SoftReference/WeakReference/PhantomReference,一般情况下都不会接触到Reference类。
今天顺着某根藤摸到了sun.mics.Cleaner这里,惊奇的发现Cleaner的clean方法居然会在回收前被调用,而Cleaner类只是简单的继承自PhantomReference,没有做任何的处理。本来是比较惊喜这个发现,因为没准就能实现透明的ByteBuffer缓存了,不过看了Reference的代码后,马上就被恶心了一下(自找的……)
Cleaner的继承体系是Reference-->PhantomReference-->Cleaner这样的,而居然在Reference的实现中会这样子实现Cleaner的clean,真是无语了…… 12/15/2005 Java Service WrapperJava Service Wrapper是一个可以将Java程序包装成服务的开源项目,支持各种主流平台,我一直使用其在服务器端进行项目部署。
不过最近发现我们一台服务器上包装的服务没过多久就会自动重启,日志中打印:JVM appears hung: Timed out waiting for signal from JVM. | JVM did not exit on request, terminated。看上去似乎是Java Service Wrapper发觉Java进程停止了响应。
一开始直觉认为是自己程序中某个地方隐藏的Bug导致了虚拟机停止响应,但是通过命令行方式来运行却不会发生这种情况,只不过运行了很长一段时间后会抛出OutOfMemory异常。但是出现这种情况时异常都会被记录在日志中,而通过Java Service Wrapper方式运行,自动重启前日志中却没有任何异常记录。
于是开始怀疑是Java Service Wrapper本身的问题。通过分析它的源代码,发现它是开了个单独的线程与它的C代码建立Socket连接读写数据,每隔若干时间,C代码会发PING请求过来,如果在一定时间内Java代码没有响应该请求,C代码就认为Java虚拟机停止了响应。
照理而言,就算其他的线程出现了什么异常,也不会影响这个线程,造成该线程停止响应。我在其Java代码中加入了若干调试代码,发现每次停止响应前该线程都卡在从Socket中读数据的地方。为了确保是Java Service Wrapper C部分的代码出现了问题,我又开启了另一个单独的线程,该线程仅仅是没过10s钟打印一条信息到控制台上。令人吃惊的是,居然在每次自动重启前,该线程也停止了打印!
迷惑中突然发现JMX中监测到的垃圾回收时间有点不对劲。居然每次轻量级回收都占用了1秒多钟,我尝试通过JMX让该进程来一次全面垃圾收集,OK,终于该现象重现了!控制台打印虚拟机停止了响应,然后又重新启动虚拟机……
原来罪魁祸首是垃圾回收。全面垃圾回收时暂停了所有线程,由于某次全面垃圾回收占用了太多的时间,导致Java Service Wrapper的C代码认为Java虚拟机停止了响应,于是把Java虚拟机给重新启动了。
唉,Java服务程序跑在512M内存的机器上还真是痛苦啊,短期内也不太会有增加的可能;本来加的参数就是-Xms512m -Xmx512m,这基本上已经是极限参数了。尝试指定通过-XX:MaxGCPauseMillis参数为15000,不过似乎没有任何改善,全面垃圾收集时间还是偶尔会突破30秒大关,导致虚拟机退出。
一方面还是要加内存,一方面要检查程序内存泄漏,还要进行垃圾回收参数调优……不过找到了原因,问题就算解决了一半了,幸好没错怪Java Service Wrapper:) 12/9/2005 OpenLDAP + MySql为啥要整个LDAP,这故事还真是源远流长啊……得从JMS说起。
上次嚷嚷着要换JMS Provider,一门心思等Active MQ 4.0 release,Roadmap上是标着2005年11月1号,可是看它的进度,能在明年3月份以前Release都算快的了。在QQ群里问了问,有人提到了Sun有一个Message Queue Platform版是免费的。找来研究了一下,做的的确不错,在易用性和稳定性方面要胜出我以前研究过的那些开源JMS产品(当然,也试过IBM Websphere MQ,做的更好,只不过价钱嘛……承受不起)。(Sun的这个MQ产品我会在以后列出其优缺点)
好用归好用,但有一个问题需要解决:Sun Message Queue没有带一个够用的JNDI Provider。其内置两种JNDI Provider,fscontext provider只能用于自己调程序用,对于分布式应用来说根本就是个玩具;LDAP provider块头又太大了些,我仅仅要存放些ConnectionFactory和Destination,或许将来要会存个DataSource啥的,在后台架个LDAP似乎有杀鸡用牛刀的感觉。
在网上搜索了一下也没找到什么好用的JNDI Provider,很多类似于fscontext的provider都是属于在自己机器上玩玩的玩具;自己写一个简单的实现倒也不麻烦,但是要实现认证和授权控制也不是一时半回就能搞定的事情。考虑了半天最后还是决定在后台架个LDAP来存放这些数据,能充分利用LDAP提供的认证和授权功能。并且如果不太麻烦的话,顺便把Window和Linux的用户验证也整合进去,这样比较物尽其用些。
Sun不知基于什么样的考虑,Message Qeuee虽然是Java写的,但在Window下只有3.6的标准版,Linux和Solaris下连SP3都出来了,Window下的补丁却迟迟没有发布,看来为了得到充分的支持只能选择Linux平台了。Message Queue可以通过文件和数据库来进行持久化,默认是用采用文件持久化,但是基于数据安全和备份的考虑,准备改成通过MySql数据库来持久化。
Linux下面免费的LDAP Server当然首选OpenLDAP了(Apache下面有个Directory子项目,半死不活的,不能用;Window的AD强则强矣,但是霸气十足,老是在标准上面搞自己的标准, 况且其不是免费的,虽然大部分人都把它当成免费的看:))。OpenLDAP支持各种各样的持久化方式,默认是用Berkeley DB做持久化的,也支持各种关系型数据库来进行持久化。我便希望把这些数据统一到MySql上来,一方面我并不会在LDAP中存放大量数据,虽然采用关系型数据库和目录服务结构不一样,但是小数据量应该不会有什么效率上的问题,况且统一管理数据会比较安全,也比较方便。
步骤如下:
SUSE带的OpenLDAP是没带--enable-sql参数编译出来的,不支持通过关系型数据库来持久化。我不想在部署时在服务器上安装一堆的开发库然后再编译,便下载了SUSE OpenLDAP RPM包源码,多加了一个back-sql模块,把对SQL持久化的编译成一个子模块,部署时只需要简单安装一下就可以了。(忍不住要抱怨一下,编译加测试花了半个小时后抛出一个错误,仔细一看原来多加的模块中有一个_不小心写成了-,结果又吭哧吭哧了半个小时才给我把所有的RPM包都生成好,在Window下哪会需要干这么麻烦的活……)编译好安装OpenLDAP-back-sql的RPM即可。
编译前先把需要的开发库都装好吧,有一堆要装的。还得提到的是不管是OSS还是GM版,SUSE Linux的5CD版本有很多开发库是不全的,请参照OpenSUSE网站上的文档,加入一个完整的Package Repositories,这样光盘上一些没有开发库可以直接从网上更新下来,比较方便。
安装MySQL和MySQL ODBC驱动,并开启MySQL服务。如果是采用unixODBC的话,编辑/etc/unixODBC/odbc.ini文件(SUSE带的unixODBC配置数据源管理工具编译的有问题,只能通过DataManager来查看,不能ODBCConfig来配置数据源,有兴趣的可以自行去下载新版本安装),配置格式可参考:http://www.unixodbc.org/odbcinst.html。
修改/etc/openldap/slapd.conf,可参照OpenLDAP源码包下servers/slapd/back-sql/rdbms_depend/mysql/slapd.conf。 同时用mysql的管理工具在mysql下建立OpenLDAP用的数据库,并用该目录下的backsql_create.sql来建立相关的表。如果不是用mysql做后台的数据库,可以参照rdbms_depend下的其他目录中的相关文件来修改配置和建立数据表。 对slapd还需要额外的修改: 加入include /etc/openldap/schema/java.schema,因为我们需要用LDAP来保存Java对象。 加入moudleload back_sql.la,加载我们在第一步编译好的支持sql持久化的模块。 加入dbname/dbuser/dbpasswd,指向第二步加入的MySQL数据源名称/用户名/密码。
运行/usr/lib/openldap/slapd -f /etc/openldap/slapd.conf -d 1,这样会打印出调试信息,如果中间出现异常,可以作为解决问题的参考。如果没有意外的话,应该在这一步不会出现问题的。 如果没有任何问题,以后可以通过shell script来启动服务,而不需要再打印调试信息了。
嘿嘿,其实这才是最关键的地方,OpenLDAP对sql的支持是非常有问题的,比如它根本无法MySQL数据库整合起来,在上面的示例中,可能能正常启动,但是做修改和查询操作均抛异常,或者启动都启动不起来就抛了异常了,因为它带的创建数据表的SQL有问题。并且用SQL做持久化,还需要手工把那些Schema都加入,麻烦的要命。 其maillist上开发人员说到他只在Postgre上做过测试,我已经没有兴趣再试了,就用默认的Berkeley DB算了。不过上面提到的这些步骤对于需要的人来说还是很有帮助。 最后罗嗦的提一句,LDAP和关系数据库的数据模型本来就是不一致的,用关系数据库来保存肯定会有一些限制和效率上的降低,我是基于小数据量和统一数据的考虑才会考虑选用关系数据库来持久。网上Google一下,OpenLDAP和MySQL整合的文章一大把,但是很少有文章提到为什么要整合以及整合的优劣所在。 咱不能为了整合而整合,所以在选择前,请基于自己的情况做充分的考虑。 11/25/2005 CAJ——推荐阅读的项目因为考虑了很久也没有想到优雅的实现Leader/Fellows模式的方法,所以在网上找相关的资料,看能不能在巨人的肩膀上有一些收获,结果就发现了这么一个项目:CAJ。
代码风格是我很欣赏的风格,简单清晰明了,在实现LF模型上的代码也是比较优美,推荐阅读:)
让我惊讶的是,实现Reactor的方式和我3.0a1版本里实现Reactor的方式相似度非常高(而且我受这些设计模式的影响,类名也换成了Reactor),呵呵,看来是想到一块去了:)
不过比较迷惑的是,我在网上找到了他去年在日本发表的一篇演讲,里面提到了CAJ在Linux 2.6上面能够支持epool,不过我在代码中没有看到相关的体现。如果说是nio支持epool,就我所知,似乎目前nio还没有这么做。没想明白…… 11/15/2005 fight against ByteBufferjava.nio.ByteBuffer应该算是nio中最常用的类之一,不过近来用的越来越不爽了,最为不爽的是无法构造基于ByteBuffer的缓存机制。 众所周知,ByteBuffer并不是接口,而是一个抽象类。最为关键的地方是其构造函数为包级私有,这意味着我们无法自己继承ByteBuffer来构造子类,而只能通过wrap或者allocate方法来得到一个ByteBuffer。 ByteBuffer本身并不持有内容,而仅仅是所持有内容的一个外部包装,多个不同的ByteBuffer可以共享同一份内容。比如可以通过slice、duplicate等方法构造一个新的ByteBuffer,新的ByteBuffer和原来的ByteBuffer共享内容。 如果有如下代码:
打印出来的都是false,而实际上两个ByteBuffer共享的是同一份数据。在不经意的情况下,可能发生应用把两个ByteBuffer返回缓冲池中,被缓冲池当成是不同的对象进行操作,破坏了数据完整性。 11/7/2005 Delphi开发ActiveForm的两个Bug这年头倒霉倒透了,到处碰Bug……
这个Bug倒是在Delphi本身的Bug Database里,不过似乎没有在Delphi 7里面得到解决,不知道在Delphi 2005中有没有得到解决。这个Bug本身倒也不太严重,最多也就是自己写个批处理来解决了。
说实话,这个Bug有点大,而且非常容易被发现(Delphi的测试团队似乎有些失职)。通过ActiveForm的生成向导生成了一个基本框架,可是在这个框架生成的Initialize方法中有如下代码:
这些XXXEvent方法也是框架自动生成的,其实现是把相应的事件通知给注册在ActiveX上的事件监听器。可是如果通过窗体设计器所生成的方法,却无法被绑定,因为顺序是这样的:
这样,虽然注册在ActiveX的事件监听器能得到相应的消息,可是Form本身却无法得到相应的消息了!在这种条件下,窗体设计器无法设计处理代码,只能手动将处理代码移入相应的XXXEvent处理方法中。例如可以移入ActivateEvent中:
红色的代码是手工加的,其他的代码都是框架生成的。 10/28/2005 simple ActiveMQ security setting目前ActiveMQ网站上没有相应的文档,通过其maillist和源代码分析出来的。ActiveMQ目前提供了一个安全方面的基本接口和用使用JAAS的默认实现,这里是针对JAAS的默认实现所做的设置。
在activemq的启动批处理中加入-Djava.security.auth.login.config=JAAS配置文件名称(这里设置为jaas.config)
新建jass.config,由于ActiveMQ未提供一个默认的LoginModule实现,所以在这里使用了一个第三方的类库Tagish(选用该类库仅仅由于其配置简单性,你可自行选择一个功能强大或者易用的实现)。编辑jass.config:
再建立一个passwd文件,输入:
即用户名和密码都是test1(密码是test1的MD5)
修改ActiveMQ的配置文件activemq.xml,在persistence元素后面加入:
如果要配置用户权限,可以参照org\activemq\security\jassjacc\PropertiesConfigLoader.java的javadoc来配置配置文件。
客户端要正常使用JMS,则需要在连接URL后指定userName和password,比如tcp://localhost:16161?userName=test1&password=test1,这样才能正常连接。 About JORAM and ActiveMQ由于我在公司的项目中使用JORAM已经半年多了,关于JORAM的经验相对比较丰富一些,就先从JORAM说起了。
JORAM的优点:
JORAM的缺点:
其中第一和第二个缺点都比较令我郁闷。尤其在前几天我们的一台服务器在尝试重连12个小时后,都还抛出The durable subscription XXXX has already been activated,直到重新启动JMS服务器才解决后,我就下定了决心,一定得找个更好的JMS服务器来把它给换掉。 当然,我的目光瞄准了ActiveMQ。ActiveMQ的社区还是比较活跃的,虽然我采用的是JORAM,但是ActiveMQ这个项目我还是一直关注着,从2.x到3.1版本,我都下载来试用过,看看有没有什么有意思的改动。这次下载了新发布的3.2版本,并且用它自带的示例试了试,感觉也还不错。 但当我刚开始进一步的尝试时,就碰到了一些问题。因为它默认是持久化到Derby数据库,基于方便管理和稳定性考虑,我准备试着改为使用Mysql持久化。结果发现它默认提供的配置文件里面(注释掉的)居然是错误的,需要把所有的XXXJDBCAdaptor改为XXXJDBCAdapter才可以。在这一步改好以后开启ActiveMQ,却抛了一个异常说Mysql索引不能超过1024字节。 接下来的测试都是风平浪静,在判断客户端是否在线上面似乎快过JORAM;速度方面,我上次就提到过了它的速度超出了JORAM;持久化比较优秀,并能够保持兼容性,我在3.1中保存的消息能够在3.2中正确的读入;但是权限控制方面,找遍它的网站都没有任何文档,但是从它的源代码里面找到了相关的接口和一个用JAAS的实现类(但却没有提供一个基本的JAAS LoginModule),从maillist和JAAS的文档里面找了半天,才摸索出基本的用法。(mailist里提到在11月份要发布的4.0版本似乎做了很大的改进,功能上强不强我倒无所谓,易用性上需要增强才是正解) 感觉上ActiveMQ构架上是比较先进,但还是存在我前几篇Blog中提到的老问题:文档缺乏。一些功能很强大,但是却没有文档来指导该如何使用,在研究阶段我是可以通过查maillist、看Javadoc、分析源代码来了解,但是没有一份稳定的支持是很难在企业中正式使用的。感觉上就是ActiveMQ基本弥补了JORAM的缺点,但是JORAM的优点,ActiveMQ也基本上没有。 不知道有没有正式使用过ActiveMQ的朋友?欢迎交流一下使用感受。 |
|
|