2009-03-28

[感想] 平台,语言,库

早期的时候,要发展一门语言,需要考虑两件事情,语言本身,以及这个语言所用到的库,这时候的层次结构比较简单,就是 "操作系统 | 语言 | 库 | 程序"。

稍后出现的 Java 引入了 JVM 平台的概念,现在的层次结构就变成了 "操作系统 | 平台 | 语言 | 库 | 程序"。

这还不够, .NET 上的库已经不再依赖于语言,所以现在又变成了 "操作系统 | 平台+库 | 语言 | 程序"

这样做的好处在于:

  • 创建一门新语言不再是一门庞大的工程,你无须创建库,语言的推广不再被你所需的库没有准备好,库没有稳定这类事情的拖累。

  • 当你想革新你的项目,采用一种新的语言时,不用在意如何移植旧项目,只要新旧语言能采用同一种平台即可



事实上 JVM 平台早就有这种能力,不过却没有好好推广这种能力,早期只有 Java 和 JSP 这两种语言工作在 JVM 平台上(JSP的应用范围还严重受限),Jython, Groovy, JRuby 之类语言很晚才得到一定程度的应用,这个时候 .NET 平台已经发展了很久了。

这是我为什么不看好 D 语言的原因,他基于 C 语言的平台,却不能反馈给 C 平台(C++ 比他稍好,能用 extern "C" 的方式反馈),当然这跟 C 平台本身也有一点关系,仅有 "栈", "堆", "函数" 这样几个概念,难免有些难用。

同样我也不看好函数式语言成为一门通用的语言,他们与现有的平台的概念离得太远,同时又没有建设好自己的平台。

2009-03-17

尝试了一下直接写 gobject

先说结论吧: 1. 痛苦的体验,2. 用面向过程的语言来做面向对象的事时你需要处理很多繁琐的事情 3. 用 gob2 之类的工具可以改善体验。

基本结构


这部分有模板可以套(30-40行),你只需要把里面的名字换成自己的类的名字, 不过由于存在全大写, 首字母大写,用下划线分割的小写三种情况,所以你需要替换三次。

在这一步,你定义了 6 个宏,两个 struct 和两个函数 xxx_init 和 xxx_class_init.


处理析构


如果你的对象引用了其他对象,或者有动态分配的内存(比如字符串), 那么你就需要处理析构的问题。 gobject 为了解决循环引用的问题,所以析构分为两步, 第一步是解除对其他对象的引用(这一步叫做 dispose), 第二步是释放动态分配的内存(叫做 finialize)。在这一步中,你需要添加一个全局静态变量保存 parent_class, 定义 xxx_dispose 和 xxx_finalize (记得通过 parent_class 调用父类的对应的析构函数), 在 xxx_class_init 代码中初始化 parent_class, 已经把 xxx_dispose 和 xxx_finalize 关联到这个类。

私有成员


封装很重要,所以私有成员不应当放到头文件中,所以类 Xxx 的定义应该如下所示。

struct _Xxx {
GObject parent_instance;
XxxPrivate* priv;
}

gobject 用了一个小技巧来防止生成一个对象时分配两次内存,他可以让生成对象时申请的内存大小为 sizeof(Xxx) + sizeof(XxxPrivate),当然这个是可选的。

通常,你为了支持私有成员,你需要添加一个宏,同时在 xxx_init 和 xxx_class_init 中加入必要的初始化代码。

属性


gobject 的属性主要功能是为了实现事件监听,你可以在属性上挂回调函数来监听属性的改变。如果你不需要的监听的话,那么简单的一个私有成员,再加上 set 和 get 函数即可。实现属性是一件很郁闷的事,你首先要定义一个 enum 来给每个属性一个顺序号, 然后定义 xxx_get_property 和 xxx_set_property 来覆盖基类的实现,然后在 xxx_class_init 中为没个属性定义一个包含属性元数据的结构,最后依次用 g_object_class_install_property 安装。

仅有这些似乎还不够,你一般还需要定义一些辅助函数或辅助宏来降低用户使用的难度,包括 set, get, signal_connect 等。

信号


其实属性的大部分的功能都可以用信号来实现(在属性的 set 函数上 emit 一个 foo-changed 信号),而且 signal 的写法比较简单,无须重写 xxx_set_property 和 xx_get_property。

总之


总之,写的很烦,而且写了半天还没有写到业务代码,每增加一个属性或者信号都需要不少的工作量。看了一下 python 的 gobject 封装感觉很漂亮(见下), gob2 也不错,能让你开发得开心一点。


import gobject

class MyObject(gobject.GObject):

foo = gobject.property(type=str, default='bar')
boolprop = gobject.property(type=bool, default=False)

def __init__(self):
gobject.GObject.__init__(self)

@gobject.property
def readonly(self):
return 'readonly'



最后补一句,虽然 C 语言有些繁琐,但 C 平台真的非常成功,有空再补一下我对平台,库,语言三者关系的观点。

2009-03-13

在 Java 服务端使用 syslog 作为日志系统

1. 为什么要用 syslog?


使用 syslog 的最大理由就是可以集中处理日志,通常一个中小型的服务端10来台机器,如果日志集中到一台服务器处理起来就非常方便。

其次,你可以很方便地利用 syslog-ng 这一类的服务器来对日志进行分流,在只需要跟踪某一种类型的日志时就比较方便。

除此之外,用文件做日志在部署时总会出现目录不存在,目录没有权限的情况,而用 syslog 就没有这个问题。

2. 配置 log4j


把如下的一段加入 log4j.properties 即可把日志重定向到 syslog(同时保留了终端,方便调试)

log4j.appender.SYSLOG=org.apache.log4j.net.SyslogAppender
log4j.appender.SYSLOG.syslogHost=192.168.101.101
log4j.appender.SYSLOG.facility=local3
log4j.appender.SYSLOG.facilityPrinting=false
log4j.appender.SYSLOG.layout=org.apache.log4j.PatternLayout
log4j.appender.SYSLOG.layout.ConversionPattern=%d{dd-MM-yyyy HH:mm:ss} %-5p (%C:%M:%L) - %m%n

log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
log4j.appender.CONSOLE.layout.ConversionPattern=%d{dd-MM-yyyy HH:mm:ss} %-5p (%C:%M:%L) - %m%n

log4j.rootLogger=info, CONSOLE, SYSLOG


syslog 的每一条记录有两个属性,一个是 facility(可用于日志分类), 另外一个是 priority.

facility 就在上面的 log4j.appender.SYSLOG.facility 属性中配置,缺省值是 user, 对于服务器,我觉得在 local0 到 local7 之间选一个比较好,便于将日志分类。所有可用的 facility 可以参考log4j的文档

priority 在我们用 log.error, log.warn 之类地命令进行日志操作时就已经指定了,syslog 有 8 个优先级,log4j 用了其中的 5 个,对应关系如下(syslog的 ALERT, CRIT, NOTICE 3个等级未被使用)

log4j.FATAL = 0 = syslog.EMERG
log4j.ERROR = 3 = syslog.ERR
log4j.WARN = 4 = syslog.WARNING
log4j.INFO = 6 = syslog.INFO
log4j.DEBUG = 7 = syslog.DEBUG


log4j.appender.SYSLOG.syslogHost 这个配置选项用于配置 syslog 机的 IP, log4j.appender.SYSLOG.facilityPrinting 设为 true 时会在每条日志前打印 "local3:", 在日志没有分流时打开此选项可以用 tail -f /var/log/syslog | grep local3 来只跟踪从你的服务器传来的日志。

syslog 服务端


我一般使用 syslog-ng 来作为 syslog 服务器,安装后打开 /etc/syslog-ng/syslog-ng.conf,把 "udp()" 这一行取消掉注释后重启 syslog-ng 服务即可正常使用,所有的日志会被输出到 /var/log/syslog。(注意,如果日志服务器在外网,建议修改 /etc/hosts.deny 和 /etc/hosts.allow, 防止被人灌数据灌着玩)

一般我们会把服务器的日志定向一个单独的文件来进一步处理,最简单的情况就是把如下的一段追加到 /etc/syslog-ng/syslog-ng.conf, 然后重启 syslog-ng 服务。

filter f_foo { facility(local3); };
destination df_foo { file("/var/log/foo.log" owner("foo")); };
log {
source(s_all);
filter(f_foo);
filter(f_at_least_warn);
destination(df_foo);
};

syslog-ng 也支持把日志的输出重定向到你的程序,具体可以参见 syslog-ng 的文档。

最后你可能需要把你的日志文件加入 logrotate 的配置, 这样方便日志滚动。