存档

‘技术’ 分类的存档

Consistent Hashing算法

2010年3月12日

网络应用中经常会用到缓存机制,例如memcached。当需要缓存的数据量较大,一个memcached实例存不下的时候,就需要一定的策略将不同的对象分配到不同的memcached实例上。比较常见的方法是对对象的key值做hash,再求余,根据余数分配到不同的cache中。这种简单的方法的扩展性相当差一些,主要体现在cache结点的增加和删除上。假设本来有a个cache结点,因数据量扩大而扩大至b个结点,可以计算得到有 min(a, b) / [a, b]比例的对象的余数没有发生变化,也就是说这些对象仍可以存在于原来的cache结点上。如果每次cache结点数量的扩展都是翻番的,即b=2a,则有50%的对象的缓存失效。但是如果是原有5个结点,增加了2个结点,则会有86%的对象缓存失效,这将对系统的性能造成较大冲击。

所以出现了consistent hashing算法,它可以减少节点变动带来的缓存失效。consistent hashing的实现方法是把缓存对像的key和cache结点本身,分别用一定的算法映射到一个相同的hash空间上,例如一个32位的整数。然后将这个空间想像成一个圆,然后将各key及各cache结点都画在这个圆上。对于每个key,它顺时针碰到的最近的一个cache结点就是它应该存在的地方。如下图所示:

为了防止由于对cache结点的hash算法结果的不均匀,导致cache结点在圆环上的分布过于不均,使得每个cache的负载不同,consistent hashing算法还引用了virtual node的概念。也就是在对cache结点计算hash的时候,通过对hash过程的微调,使每个结点都算出很多(例如200个)hash值,这些值以virtual node的形式添加到环上,所有属于这些virtual node的缓存对象都映射到该实际结点上,这样就基本能保持每个cache结点在hash空间中cover住基本同样多的对象了。

这样,在保证结点基本均匀分布的情况下,将cache结点数从a提高到b,只有(b-a)/b比例的对象缓存失效。对于上文提到的从5个结点添加2个结点的情况,缓存失效率为28%,相比传统算法的86%,性能提高了不少。

不少新的memcached client都已经支持这种算法,如libketama等。但是不同语言的客户端的实现依赖于该语言对对象的serialization实现,可能分配的结果也不一致。我最近做的项目需要同时从Java和C客户端访问同一组cache server,所以我还是自己实现了一个简单的consistent hashing机制。其中,算法中的环结构对应一个SortedMap,每个cache结点均为该树中的一个点,某key顺时针碰到的第一个结点的问题转化成排序树上大于该key的最小结点。(无关代码已略去)

public class ConsistentHashingPartitionStrategy <TTarget, TKey> {

    public static final int                    DEFAULT_REPLICA = 64;

    private final SortedMap <Integer, TTarget> circle          = new TreeMap <Integer, TTarget> ();

    /**
     * Number of virtual nodes we create for a target
     */
    private final int                          replica;

    public void addTarget (TTarget target) {

        for (int i = 0; i < replica; i++) {
            circle.put (this.getTargetHashingFunction ().hash (target.toString() + i), target);
        }
    }

    public void removeTarget (TTarget target) {

        for (int i = 0; i < replica; i++) {
            circle.remove (this.getTargetHashingFunction ().hash (target.toString() + i));
        }
    }

    /**
     * Find the nearest target next to the key hash on the circle.
     */
    public TTarget getTargetForKey (TKey key) {

        if (circle.isEmpty ()) {
            return null;
        }

        int hash = getKeyHashingFunction ().hash (key);

        if (!circle.containsKey (hash)) {
            SortedMap <Integer, TTarget> tailMap = circle.tailMap (hash);
            hash = tailMap.isEmpty () ? circle.firstKey () : tailMap.firstKey ();
        }

        return circle.get (hash);
    }
}

编程 ,

glibc中malloc()的空间overhead

2010年2月13日

在linux下调用malloc()分配内存的时候,实际占用的内存与请求的内存尺寸的关系是什么呢,这个需要研究一下glibc中malloc()的实现。现在常见linux发行版中带的glibc中采用的都是Doug Lea的实现,下面的分析取自他的2.8.4版本的malloc.c

glibc对内存的管理是以chunk为单位的,未分配的chunk之间用双向链表连接成一个环,遍历的时候用指针遍历,已分配的chunk在其首部有chunk的大小,并以此字节数做遍历。每个chunk的首部要放置一个叫做malloc_chunk的struct,故每个chunk的大小至少是这个struct的大小,如果分配的空间或者free的空间大于这个大小,则struct的后面是raw的数据或者是空白空间。chunk的定义如下所示:

struct malloc_chunk {
  size_t               prev_foot;  /* Size of previous chunk (if free).  */
  size_t               head;       /* Size and inuse bits. */
  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
};

对于已分配的和未分配的chunk,使用的都这个结构,只是用到的成员不一样。对于这两种情况,head变量存放的都是本chunk的尺寸,由于chunk的大小有对齐的规定(见下文),所以head变量的最后三位一定是0的,用不到,所以这三位被做为三个标志位使用,其实本文涉及的是最低位P,它表示当前chunk紧跟着的上一个chunk是不是一个free的chunk,以及倒数第三位A,它表明当前chunk是否被使用中。如果上一个chunk是free的,prev_foot变量就是有用的,它表明了上一个chunk的尺寸,如果上一个chunk也是分配了的,prev_foot变量就是不使用的(在内存上直接和前一个chunk重叠起来,见下文的图示)。如果当前chunk是个空chunk,那么fd和bk两个指针就会分别指向空闲chunk环中的上一个和下一个,如果当前chunk已经分配了,fd和bk所在的内存存放的就是用户实际申请到的数据空间了。

Doug Lea在malloc.c的源代码中画了图来描述实际分配中的内存使用情况,首先是对于已分配的chunk:

chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | Size of previous chunk (if P = 0)                             |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | Size of this chunk                                     |1| |P|+
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                                                               |
        +-                                                             -+
        |                                                               |
        +-                                                             -+
        |                                                               :
        +-      size - sizeof(size_t) available payload bytes          -+
        :                                                               |
chunk-> +-                                                             -+
        |                                                               |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | Size of next chunk (may or may not be in use)              |1|+
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图中,chunk箭头所指的位置是malloc_chunk struct的起始位置,mem箭头所指的位置是malloc()等函数返回给用户的指针的位置,也就是实际数据的位置,mem和chunk箭头之前的2*size_t字节的空间可认为是overhead。但是注意第二个chunk,它的上一个chunk也就是第一个chunk是已经分配的,所以它的head字段的P标志位为1,并且它的prev_foot字段其实是不存在的,因为它和第一个chunk的payload数据的最后size_t个字节是重叠的。如果第二个chunk也被分配了的话,它的overhead就是第二个mem箭头上面的那一条当前chunk尺寸(也就是head变量)的那size_t字节的空间。

对于空闲的chuck是这样的:

chunk-> +-                                                             -+
        | User payload (must be in use, or we would have merged!)       |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | Size of this chunk                                     |0| |P|+
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | Next pointer                                                  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | Prev pointer                                                  |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                                                               :
        +-      size - sizeof(struct chunk) unused bytes               -+
        :                                                               |
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | Size of this chunk                                            |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        | Size of next chunk (must be in use, or we would have merged|0|+
  mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
        |                                                               :
        +- User payload                                                -+
        :                                                               |
        +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

如图中注释所说,两个free的chunk如果是相邻的,会被合并,所以不会存在两个free的chunk相连的情况。(对于较小的刚刚被free的chunk,glibc中存在一种调度算法,把这种小free chunk加入一个叫fastbins的队列管理,这时尽管被free,它们的A标志位会保持为1,因此就算相邻也不会被合并)。

注意图中的第二个chunk,它是一个被分配了的chunk,它的第一个字段prev_foot表示的是第一个free的chunk的大小。其实这个prev_foot变量也是位于上一个chunk的尾部。真正属于第二个chunk的空间还是从“size of the next chunk”开始的,它距离用户数据的空间mem是size_t字节。

综合上面两个例子可以看出,对于一个已分配了的chunk,如果它的上一个chunk也是分配了的,它的prev_foot就是完全被前一个chunk覆盖的;如果它的上一个chunk是未分配的,它的prev_foot存的是该空间chunk的大小,且prev_foot变量位于前一个chunk的最后size_t个字节。这样看来,已分配的chunk的第一个成员prev_foot总是位于别的chunk内的,所以overhead就是head一个变量的大小,即site_t字节。这样一种chunk之间重叠的设计,使得prev_foot与其说是当前chunk的第一个变量,不如说是上一个chunk结尾尺寸标记(从它的名字foot就可以看出这一点)。这使得对于一个空闲chunk来说,其头(自身的header变量)和尾(下一个chunk的prev_foot)都是此空闲chunk的尺寸,这使得从正向两个方向按字节数遍历都很容易,是个不错的设计。

除了上面讨论的overhead以外,chunk的存储还涉及对齐的问题,glibc中规定chunk的以size_t的两倍大小对齐。与此相关的一些代码如下:

#define MALLOC_ALIGNMENT       (2 * SIZE_SZ)  //在我下载的malloc.c中不是这样定义的,而是分情况讨论的
#define CHUNK_ALIGN_MASK    (MALLOC_ALIGNMENT - SIZE_T_ONE)

#define CHUNK_OVERHEAD      (SIZE_T_SIZE)

#define MCHUNK_SIZE         (sizeof(mchunk))

/* The smallest size we can malloc is an aligned minimal chunk */
#define MIN_CHUNK_SIZE\
  ((MCHUNK_SIZE + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)

/* pad request bytes into a usable size */
#define pad_request(req) \
   (((req) + CHUNK_OVERHEAD + CHUNK_ALIGN_MASK) & ~CHUNK_ALIGN_MASK)

/* pad request, checking for minimum (but not maximum) */
#define request2size(req) \
  (((req) < MIN_REQUEST)? MIN_CHUNK_SIZE : pad_request(req))

request2size这个宏汇合了上文的各个因素,给出一个要申请的空间大小,它返回的就是实际分配的内存大小。只申请1个字节的时候,实际至少也要分配malloc_chunk的大小那么多空间,当申请的字节更多直到一个malloc_chunk的空间放不下后,实际空间会以2倍的size_t为步长增长。

这里写了一个程序验证:

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>

#define N 1

int main() {
    int i = 0;
    char *p;
    char *p0, *p1;

    size_t *psize;

    for (i = 0; i < 1000000; i++) {
        p = (char*) malloc(N);
        if (i == 0) p0 = p;
        if (i == 1) p1 = p;
    }
    printf("%d\n", p1 - p0);    //看两个chunk的mem指针相差多少

    psize = (size_t*)(p1 - sizeof(size_t));
    printf("%d\n", (*psize) & ~7);    //看head变量的值,& ~7的作用是把后三个标志位过滤掉

    getchar();
    return 0;

}

在32系统下,size_t和指针均为4字节,当N为1至12时,程序的内存占用为16M,当N为13时,程序占用内存涨到24M。
在64系统下,size_t和指针均为8字节,当N为1至24时,程序的内存占用为32M,当N为25时,程序占用内存涨到48M。

上述程序在centos 5.3下,gcc 4.1.2下测试通过。另外,由于C++的new操作底层调用的也是malloc,所以上述讨论对new同样适用。

最后附上本文主要的参考材料,一个讲述glibc的pdf和malloc.c源代码。

Understanding the heap by breaking it
malloc.c

编程

c语言中struct的内存对齐

2010年2月8日

为了让CPU能够更舒服地访问到变量,struct中的各成员变量的存储地址有一套对齐的机制。这个机制概括起来有两点:第一,每个成员变量的首地址,必须是它的类型的对齐值的整数倍,如果不满足,它与前一个成员变量之间要填充(padding)一些无意义的字节来满足;第二,整个struct的大小,必须是该struct中所有成员的类型中对齐值最大者的整数倍,如果不满足,在最后一个成员后面填充。

各种类型的变量的align值如下,参考的是wikipedia的页面:Data structure alignment

The following typical alignments are valid for compilers from MicrosoftBorland, and GNU when compiling for 32-bit x86:

  • char (one byte) will be 1-byte aligned.
  • short (two bytes) will be 2-byte aligned.
  • An int (four bytes) will be 4-byte aligned.
  • float (four bytes) will be 4-byte aligned.
  • double (eight bytes) will be 8-byte aligned on Windows and 4-byte aligned on Linux.
  • long double (twelve bytes) will be 4-byte aligned on Linux.
  • Any pointer (four bytes) will be 4-byte aligned on Linux. (eg: char*, int*)

The only notable difference in alignment for a 64-bit linux system when compared to a 32 bit is:

  • double (eight bytes) will be 8-byte aligned.
  • long double (Sixteen bytes) will be 16-byte aligned.
  • Any pointer (eight bytes) will be 8-byte aligned.

这里写了个程序来验证这些事:

#include <stdio.h>

struct s {
    char a;
    short b;
    char c;
    double d;
    char e;
};

int main() {

    struct s s1;

    printf("%d, %d, %d, %d, %d\n",
        (char*)(&s1.a) - (char*)(&s1),
        (char*)(&s1.b) - (char*)(&s1),
        (char*)(&s1.c) - (char*)(&s1),
        (char*)(&s1.d) - (char*)(&s1),
        (char*)(&s1.e) - (char*)(&s1));
    printf("%d\n", sizeof(struct s));

    return 0;
}

在64位linux下面运行这段代码的结果是:

0, 2, 4, 8, 16
24

由于对齐机制的存在,实际上上面的struct在内存中是长这个样子的,共计24个字节:

struct s {
    char a;             //在地址为0的位置
    char padding1[1];   //由于下面一个元素是short,对齐字节数为2的位数,需要补1字节
    short b;            //对齐到了地址为2的位置
    char c;             //在地址为4的位置
    char padding2[3];   //由于下面一个元素是double,对齐字节数为8的倍数,需要补3字节
    double d;           //对齐到了地址为8的位置
    char e;             //在地址为16的位置
    char padding3[7];   //整个struct的大小需要是对齐数最大者,也就是double的8字节的整数倍
};

如果是在32位的linux下跑上面的程序,由于double的长度还是8字节,但是对齐是4字节的了,所以前面几个成员的位置不变,而最后的padding只需要补3个字节就可以了,所以输出的结果是0, 2, 4, 8, 16及20.

对于windows,其32位和64位下double都是8字节对齐的,所以在32位和64位下跑这个程序结果都是0, 2, 4, 8, 16及24.

最后,整个struct的大小的要求是对齐值最大者的整数倍,没有什么默认的4或者8的倍数一说。如果把上面程序中的a,b,c,d,e的类型全变成char,那么最后的他们的地址会是0,1,2,3,4,整个struct的大小 sizeof(struct s)的值是5,没有任何padding发生。

以上程序实验的环境在64位centos x64上的gcc 4.1.2(32位结果加-m32参数)及Visual Studio 2008上得出。

编程

Facebook神作: HipHop for PHP

2010年2月4日

PHP很容易开发,它的弱类型的变量和近乎万能的array,可以节约很多开发的时间,当然这也导致它的运行效率不高。有一些扩展例如APC 和 eAccelerator 致力于缓存PHP编译成的opcode来提高速度,不过facebook花了一年半的时间做了更彻底的事情,他们不想容忍连$a=1; $b=1; $c = $a + $b这样简单的事情都要去判断zval的类型,于是他们写出了HipHop for PHP——直接把PHP往C++上转换。

主创人员的blog文章:HipHop for PHP: Move Fast
一个关于HipHop for PHP的talk的视频:http://www.ustream.tv/recorded/4409735

PHP的语法大致被分为两类,一种是传统的,几乎可以和C++直接对应的语法,一种是所谓的”magic”语法。magic语法包括诸如:

$$$$$foo = 1;(动态变量)
$$$$foo();  (动态函数)
extract(array(‘a’ => 1, ‘b’ => 2));

从上面的视频可以看到,动态变量,动态函数乃至extract()都被支持了,不过他们没说是怎么实现的,好奇中。而且对于下面这种code,会是如何编译的呢,a到底要声明为一个int还是查表呢,或者莫非只要scope中有extract()的存在一切变量都要查表?

$a = 1;
$arr['a'] = “2″;
extract($arr);
$b = $a + 1;

另外还有明确说了不支持的,包括eval()的功能,还有is_function_exists()这样的运行时的判断,到是不太用得着。另外在视频中的Q&A环节,看意思是PHP的已有extension都要rewrite一下,感觉这个的代价是不是太大了?

还不知道啥时能用上这个技术,facebook说他们已经有90%的服务已经在用了,用了6个月部署。不清楚它的开源度如何,其它人能不能随便用。HTTP Server方面,facebook方面用了一个自已写的http server以及non-multithreading的apache 1.3,multithreading的暂时还不可用的样子,所以说短期内至少未名是不能用上这个技术的。

ps. 这个技术如果真的发达了,是不是广大C++民工身价会小跌呢。。。

编程

Anti-Spam插件换成了akismet

2010年2月3日

原来用的Spam Karma 2的作者说从09年开始就不维护它了,而且blog搬来dreamhost后SK2好像对中文处理有各种问题,中文comment都被咔嚓了,设置了WPLANG=zh_CN也不见好转,于是决定试用一下akismet,反正服务器已经在墙外了,嗯。

SK2给了一系列复杂的参数来鉴别spam,而akismet一点能设置的东西都没有,完全靠它的server上的策略,不知道效果如何,过一阵上来总结吧。

技术

Puppet笔记:介绍及基本用法

2009年6月5日

Puppet是一个可以管理大量linux主机的系统,它的中心思想是让主机上的某个资源与配置文件(manifest)对该资源的设计一致,这里资源可以指一个文件、一个用户、一个软件包、一条cron中的任务等实体。例如下面的一段manifest:

 

file { “/etc/sudoers”:
    owner => root,
    group => root,
    mode => 440
}

它的作用就是指明了:/etc/sudoers这个文件,其owner应为root,group应为root,mod应为440。执行的时候,客户端会判断这三点是否符合,如果不符合,它会去设置user、group、mode等的值为设置的值。

安装

Puppet的结构也是一个唯一(以后会有failover冗余)的中心Server与所有的Client组成。

Puppet是基于Ruby写成的,所以安装前要准备好Ruby环境,并且需要安装RubyGems以用来安装。Server与Client的安装没有区别,都在相同的包内,只是中心的server上运行的是puppetmasterd,被管理机上运行的是puppetd。另外,每台主机上一定要配置好自己的hostname,之后每台机器要以hostname区分。

如果ruby环境和gems都装好了,执行下面几句就可以安装好puppet了:

wget puppet.proximic.cnpuppet/facter-1.5.5.gem
gem install facter-1.5.5.gem
wget puppet.proximic.cn/puppet/puppet-0.24.8.gem
gem install puppet-0.24.8.gem

使被管理机与中心机连接

server端首次运行前,编辑/etc/puppet/manifests/site.pp文件,内容可以用最基本的:

 

# site.pp
file { "/etc/sudoers":
    owner => root, group => root, mode => 440
}

 

启动server,执行:puppetmasterd –mkusers,这个参数是为了生成puppet运行所用用户,以后执行就不需要了。

然后在客户机上执行:puppetd –server <你的服务器地址> –waitforcert 60 –test,这时客户机会去连server,但是由于连接是在ssl上的,而Server还没有sign过客户的cert,这时客户机被被断开。到server上执行:puppetca –list,会显示出刚才这个客户的hostname为等待签名的客户,执行:puppetca –sign <客户hostname> 即可为其签名。这时再到客户机上执行上面的puppetd命令,即可看到客户在正常地连接server,并且检测上面关于sudoers文件的策略了。

向被管理机分发文件

通过puppet可以向被管理机上推送文件,方法是使用file类型的source属性,例如:

file {
    ”/etc/profile.d/java.sh”:
    source => “puppet://puppet.domain.com/profiles/javaprofile.sh”,
    owner => root,
    group => root,
    mode => 755
}

这里的文件来源是什么呢?puppetmasterd自带一个简易的文件服务器,配置方法是创建并编辑/etc/puppet/fileserver.conf ,其中每个section是一个module,module就是指上述puppet://路径中域名之后的第一段(即“profiles”),下面可以配置该module对应的server上的物理路径以及acl等,例如:

[profiles]
    path /home/misc/profiles
    allow *

这样如果客户端上该文件不存在,或者内容不同(通过checksum),就会从server上把这个文件拉过去。

执行脚本

很复杂的部署任务肯定不能完全靠puppet完成,这时我会写一个部署用的脚本。Puppet有一种“资源”叫exec,通过它可以执行程序,还可以设置执行时的path,环境变量,user/group,是否记录output等,很方便,例如:

exec { “/root/puppet/nagiosclient/nrpe.sh”:
    cwd => “/root/puppet”,
    timeout => 7200,
    logoutput => on_failure,
    user => root,
    path => ["/sbin", "/usr/sbin", "/usr/local/sbin", "/root/bin", "/usr/local/bin", "/usr/bin", "/bin", "/usr/lib64/jvm/jre/bin"],
    require => File["/root/puppet/nagiosclient/nrpe.sh"]
}

想在各客户机上执行时,我一般就是先用上述file资源的配置把该脚本推送到客户机上,然后用空上exec资源去执行它,注意exec中要配置好require 该file,否则无法保证exec在file之类在客户上执行。

运维 ,

活动目录中时间日期格式的总结

2009年5月3日

活动目录中关于日期和时间的存储还是比较复杂的,如果是用ldap直接操作的话,需要了解得比较清楚的。这里查了一些文档,做一个笔记。

AD中关于时间的属性有两种类型:

1.NT时间戳型:

以lastLogon、pwdLastSet、accountExpires等属性为代表,这类属性的类型(syntax)其实就是一个大整数。NT时间戳与Unix时间戳的思想是一样的,只是前者的定义更bt一些:从1601年1月1日0时起经过的1E-7秒(即100纳秒)的个数(时间是GMT的)。

官方文档可以参考:How to convert date/time attributes in Active Directory to standard time format

如果用PHP之类的语言操作AD,可以先求出标准的Unix时间戳,然后乘上1E7,再加上1970年1月1日00:00:00的NT时间戳(常量:116445312000000000)即得到了NT时间戳。这需要用到计算大数值的BC库,如果没有的话也可以改用字符串,不乘那个1E7,而是直接转成字符串接上7个”0″

2. Generalized Time/Universal time 类型

这种以whenCreated、whenChanged属性为代表,这种属性的类型是真正的时间类型。

这种格式的说明在MSDN上的这个页面:http://msdn.microsoft.com/en-us/library/cc223266(PROT.10).aspx

格式的核心是由ISO8601定义的,W3上的 Date and Time Formats一文介绍得比较易读。不过实际使用的格式不包括横线、冒号等分隔符,具体格式是由ITUX680规定的(在64~65页)。

概括起来,这种格式就是年月日时分秒连续地写在一起,加上一个小数点后的秒值(一般为0),再加上时区,小数点和时区都不可省略。
对于UTC时间,它就形如:20090502173227.0Z 
对于本地时间,它就形如:20090503013227.0+0800 (这两个时间是同一个)

一般来说,向AD中写入时,可以用本地的表示也可以用UTC的表示,但是读出来的时候,都是UTC表示(也就是以Z结尾的) 

 

如果你是PHP用户,可以参照下面的代码实现DateTime对象与Generalized Time格式的相互转化:

 

    function DateTimeToGeneralizedTime($datetime) {
        return $datetime->format(‘YmdHis.0O’);
    }

    function GeneralizedTimeToDateTime($str) {
        $ret = strptime($str, ‘%Y%m%d%H%M%S.0%z’);
        if (!$ret) $ret = strptime($str, ‘%Y%m%d%H%M%S.0%Z’);

        $gmtTimestamp = gmmktime($ret['tm_hour'], $ret['tm_min'], $ret['tm_sec'], $ret['tm_mon'] + 1, $ret['tm_mday'], $ret['tm_year'] + 1900);

        $ret = getdate($gmtTimestamp);

        $datetime = new DateTime();
        $datetime->setDate($ret['year'], $ret['mon'], $ret['mday']);
        $datetime->setTime($ret['hours'], $ret['minutes'], $ret['seconds']);

        return $datetime;
    }

 

 

编程, 技术 , ,

OpenSUSE下安装VMWare Server遇到的问题

2009年4月10日

今天准备在一台OpenSUSE 11.0的服务器上安装VMWare Server 2。VMWare Server 2的全binary版其实是支持OpenSUSE的,不过目前只支持到10.1版,所以还是用一般linux上的安装过程。

由于非binary支持,有关网络设备及server的核心的部分需要编译,结果安装程序提示:

Your kernel was built with “gcc” version “4.3.1″, while you are trying to use ”/usr/bin/gcc” version “4.3″. This configuration is not recommended and VMware Server may crash if you’ll continue. Please try to use exactly same compiler as one used for building your kernel. Do you want to go with compiler ”/usr/bin/gcc” version “4.3″ anyway? [no] 

网上查了一下,这个是vmware安装程序的bug,可以直接yes继续。之后的步骤会问kernel源码的位置:

What is the location of the directory of C header files that match your 
running
kernel? [/usr/src/linux/include]

在OpenSUSE上默认是不安装kernel源码的,如果上面这个默认目录不存在,就可以去yast中装一下,包的名字叫“kernel-source”,不过版本不一定是对的,例如我的系统内核是2.6.25.5-1.1,而今天yast中要装的是2.6.25.20.0.1,这样就不能用,需要上网去找到与实际内核版本号完全相同的kernel-source的rpm包。(PS.如果装的时候/usr/src/linux已经是其它版本的kernel source了,还是要先进yast把kernel-source御掉再装rpm)。

多数人遇到的问题是下面这个提示:

The path “/usr/src/linux/include” is a kernel header file directory, but it
does not contain the file “linux/version.h” as expected. This can happen if
the kernel has never been built, or if you have invoked the “make mrproper”
command in your kernel directory. In any case, you may want to rebuild your
kernel
What is the location of the directory of C header files that match your 
running
kernel? [/usr/src/linux/include]

这是因为只是装了kernel源码却没用过它,有些文件是没有生成的,当然不是说你必须用自己编译的内核才能装vmware。只要做过以下几步操作就可以了:

cd /usr/src/linux
make cloneconfig (当然如果你是想自己编个内核,这步就是通常的make config)
make scripts
make prepare

技术

长期陷入PHP这类动态语言的思维定式中

2009年3月19日

长期陷入PHP这类动态语言的思维定式中,一个实际的页面就对应着一个物理的文件,有时还要特意借助什么rewrite之类的功能来实现文件结构不暴露给用户。

其实像python这类的语言做web server时先天的就不是这样做的,人家就是用一个url dispatcher来分配什么url交给哪个函数去处理,你访问 /foo/bar,其实根本没有bar那个文件在那里,这很方便嘛。甚至这些python的代码都不需要放在httpd的document目录里了。事实上,The Django Book里赫然就鄙视了原来PHP的这种方法来着:

Where Should This Directory Live?

If your background is in PHP, you’re probably used to putting code under the Web server’s document root (in a place such as /var/www). With Django, you don’t do that. It’s not a good idea to put any of this Python code within your Web server’s document root, because in doing so you risk the possibility that people will be able to view your raw source code over the Web. That’s not good.

Put your code in some directory outside of the document root.

中译版

这个目录应该放哪儿?

 

有过 PHP 编程背景的话,你可能习惯于将代码都放在 Web 服务器的文档根目录 (例如 /var/www 这样的地方)。而在 Django 中,你不能这样做。把任何 Python 代码放到 Web 服务器的文档根目录中都不是个好主意,因为这样一来,你就要冒着别人透过页面直接看到代码的风险。这对于安全可不是件好事。

 

把代码放置在文档根目录 之外 的某些目录中。

编程 ,

推荐一下EMS SQL Manager for MySQL

2009年3月12日

原来操作mysql一直使用两个工具:phpMyAdmin和Mysql的官方GUI客户端

前者是服务器端的,不提了,后者功能虽然挺全(至少比phpMyAdmin全),可是用着就是别扭,各种功能的摆放不是很有条理,需要大量输入的东西比如query和脚本时也没有什么人性化的帮助。

这两天才发现了EMS SQL Manager for MySQL,初步用了用感觉不错,发到这里推荐一下吧。

技术