• Mongoid::StateBits

    在进行数据库设计的时候,我们经常会需要增加很多布尔类型字段,比如一篇文章是否公开、是否允许评论、是否原创等等。当只有一个布尔字段时,直接声明为布尔类型;当有很多个字段时,显然一一声明得不偿失,浪费空间且会带来很多重复代码。

    一个通常的做法是将这些布尔字段都整合进一个整型字段中,一个二进制位表示一个布尔字段。对于一篇文章,如果1(第一位)表示公开,2(第二位)表示允许评论,4(第三位)表示原创,那么一篇公开、允许评论、原创的文章就标记为7(1+2+4),公开、不允许评论、原创的文章则标记为5(1+0+4)。用一个整型字段就能标记多个布尔类型属性,显然是很好的,但这样做也会带来代码管理上的不便,程序员直接与这些标记位打交道是会很蛋疼的,而且代码看上去也不优雅。

    本文将介绍读远的一个解决方案,即对上述方法进行封装,实现了一个可公用的Mongoid::StateBits模块。

    # 使用方法
    # class Post
    #   include Mongoid::Document
    #   include Mongoid::StateBits
    #   state_bits %w{public commentable original}
    # end
    # ====================================
    
    module Mongoid
      module StateBits
        extend ActiveSupport::Concern
    
        included do
          field :state_bits, type: Integer, default: 0
          index :state_bits => -1
        end
        
        module ClassMethods
          def state_bits(bits)
            bits.each_with_index do |bit, index|
              define_method "#{bit}=" do |bool|
                if bool
                  self.state_bits |= 2**index
                else
                  self.state_bits -= (self.state_bits | 2**index)
                end
              end
              define_method "#{bit}?" do
                self.state_bits & 2**index != 0
              end
              alias_method("#{bit}", "#{bit}?")
              # then define scopes
              scope bit, ->{ where("this.state_bits & #{2**index}")}
            end
          end
        end
    
      end
    end

    三个布尔字段state_bits %w{public commentable original}的声明,会为每个字段生成三个方法,以public字段为例就是public/public?/public=();还会对应每个字段生成一个scope。

    如果需要增加字段,可以直接在数组%w{public commentable original}里面添加,所有字段默认值均为false。

  • Effective random selection in database

    数据库优化里面有一个常识是,不要使用ORDER BY RAND()来随机获取数据。因为使用ORDER BY RAND()就意味着在对数据表里的每一项都执行RAND()函数,这是非常低效的。一个通常的解决方案是在代码层面就决定随机行的id,然后根据id来从数据表里面取——但这只适用于取一行或者少数几行。虽然这已经能满足我大部分的需求了,好吧,我的一般需求是:从少量数据中,取出一行。

    直到昨天。因为生物智能算法课要搞个project,我做的是运用智能进化算法来实现一个高效的推荐系统(这个以后详细讲),使用的数据是之前利用学校研究生信息管理系统漏洞而扒下来的学生个人信息,有两万多条。初始化和更新种群分别要从数据表里面取1000和100条数据——显然上述的方法是不行的。然后我想到一个很好的随机取数据的办法。

    给数据表增加专门用来随机排序的一列——用冗余换性能是互联网应用的通常做法。这一列的值应该尽量随机并且简单,我使用的是数据表中的若干列的字符串连接的md5值。

    ALTER TABLE  `table` ADD  `md5` CHAR( 32 ) NULL
    UPDATE `table` SET md5 = MD5( CONCAT( `id`,`name` ) )

    然后就可以直接通过order by md5来实现随机查询了。例如:

    def random_candidates(n = 1000)
        User.limit(n).offset(rand(User.count - n)).order("md5")
    end

    就是这样。在Rails下用order(“RAND()”)耗时3105.4ms,而用order(“md5”)只用67.1ms。

  • 使用Git来管理VPS上的代码

    用Git有一段时间了,因为之前申请了Github的学生免费服务,5个私有项目基本够用,所以用Git结合Github来管理自己的代码还是用得非常爽。以至于没有机会使用Git作为服务器端的功能。而经过两个星期的开发,读远终于进入部署阶段。本来的想法是,使用Github作为代码版本管理中心服务器,开发机和生产环境VPS通过Giuhub中转实现代码同步。但这样还是太过于蛋疼了。每次代码更新都要从开发机push到Github上,再在vps上pull下来。

    于是开始折腾起Git服务器来。之前一直以为,要搭建一个Git服务器必须要额外搭建如gitolite的服务。结果发现不要,而且直接用Git同步代码非常简单。

    Git的remote操作如push/pull/clone都是通过给定的一个url在两台主机间进行,只要正确设置好这个url就能很轻易地在两个远程Git repo间同步了。

    首先设置ssh免验证登陆。先生成自己的RSA密钥:

    $ cd ~/.ssh
    $ ssh-keygen -t rsa -C “mail@domain.com”

    将生成的id_rsa.pub的内容加入到vps的~/.ssh/authorized_keys中去:

    $ scp ~/.ssh/id_rsa.pub user@vps:./ 
    $ ssh user@vps
    $ cat id_rsa.pub >> /home/user/.ssh/authorized_keys

    假设vps为vps的主机名或ip,user为vps上的用户。如果ssh的默认端口22不能用(原因你懂的)、指定了新的端口的话,ssh命令用-p、scp命令用-P选项来指定新端口号。

    然后在vps上创建一个新的git项目:

    $ ssh user@vps
    $ mkdir /var/web/readfar
    $ cd /var/web/readfar
    $ git init
    $ echo "hello" >> index.html
    $ git add index.html
    $ git commit -m "add index.html"
    $ exit

    最后在本机上把vps上的项目clone下来:

    $ git clone user@vps:/var/web/readfar

    若22端口不能用的话,新的端口是1280,用:

    $ cd ~/Documents/
    $ git clone ssh://user@vps:1280/var/web/readfar

    于是本机和vps的同步环境就搭好了。下面测试一下:

    $ git pull

    理论上index.html就会在你的~/Documents/readfar目录中了 :)

    但是到这里还没完。

    当你在本机上编辑index.html后commit在push到vps上,会出现“refusing to update checked out branch: refs/heads/master”的错误提示。因为当本机上提交到vps的branch为vps的当前branch时,vps的git会拒绝更新,个中缘由可以很轻易地猜测出来:当vps上也在编辑时,允许更新会带来混淆。当然,可以通过git reset –hard手动更新。现在的问题是,如何自动检测到提交请求然后允许git reset –hard命令。这里要用到git的钩子方法。

    首先在vps上设置接受push,运行 git config receive.denyCurrentBranch ignore。然后编辑vps端git钩子.git/hooks/post-receive文件,内容为:

    #!/bin/sh
    cd ..
    env -i git reset --hard

    将其设置为可执行:

    chmod a+x .git/hooks/post-receive

    然后vps就会接受push了。大功告成!

  • 转变技术思维

    昨天发现开源中国的Cloudfoundry应用比赛出结果了,果然还是没有中奖,换手机的愿望落空了。

    这个结果除了让我感叹小网站各种不靠谱以外,更多的是使自己开始反思起自己长期陷入的一个误区:总是唯技术论,把技术当成首要标准,而忽视了其他很多非常重要的方面。

    比如说运营和推广。

    每当自己学了新技术做了个新的产品,其中成就感会非常大,作为技术人员的收获也很多,但之后产品就总是不了了之了,没有后续的运营和推广,更没有产品的迭代改进。就算是聚城网,自己最完整、可能是代码量最大的项目,极不想做成烂尾工程,断断续续地开发了一年多,基本功能也都完成了,甚至还有500多注册用户,但自己从来就没有认真地推广过一次,运营聚城网的热度不会超过一天。我想原因还是自己骨子里对运营的抵制,因为运营没有技术含量,因为自己脸皮薄没好意思到处发广告。可是一个产品存在的意义就是被用户使用,没人使用的产品是没有意义的

    没错,这次参加比赛的其他应用大部分都是技术陈旧(95%是用Java,用Ruby的极少,没有Python)、界面丑陋(Bootstrap泛滥),但这不应该成为我嘲笑它们的理由,技术对用户而言无关紧要。“能获得用户认可才是一个产品的核心竞争力,这无关技术,而关乎产品的气质、具体而言就是用户体验设计”——我在微博上如是说。

    我应该走出这该死的技术思维误区,做一些真正有人用的产品。根据用户反馈来不断改进产品,而不是沉浸于使用到的新技术和开发的新功能,代码刚写完就被扔进了角落然后从此无人问津。

    1、做自己需要而且想用的产品,而不是YY出各种需求。产品如果开发者自己都不用的话别人就更不会用,要想其他人喜欢首先自己就应该喜欢它。

    2、不惮于运营。任何产品的起步阶段都不是轻松的,酒香仍怕巷子深,产品的成长是一个积累的过程。所以要勇于推广和运营,放下唯技术论,拿出耐心和信心。

    3、收集用户反馈不断迭代改进产品,这样产品才能朝着理想方向不断生长。

    (未完待续)

  • GridFS:基于MongoDB的分布式文件存储系统

    基本原理

    GridFS是MongoDB之上的分布式文件系统,其利用了MongoDB的分布式存储机制并通过MongoDB来存储文件数据和文件元数据,兼具文档型数据库和文件系统的优势。GridFS是当前大数据潮流和复杂数据分析需求的产物。

    简单地来说,GridFS通过将文件数据和文件元数据保存在MongoDB里来实现文件系统,通过复制(Replication)来应对故障切换、数据集成,还可以用来做读扩展、热备份或者作为离线批处理的数据源,通过分片来实现自动切分数据,实现大数据存储和负载均衡,通过数据库对集合中文档的管理和查询(包括MapReduce)实现轻量级文件系统接口和搜索与分析。

    GridFS的一个基本思想就是可以将大文件分成很多块,每一块作为一个单独的文档存储,这样就能存储大文件了。由于MongoDB支持在文档中存储二进制数据,可以最大限度减小块的存储开销。GridFS使用MongoDB的复制、分片等机制来实现分布式文件存储,使用MongoDB进行管理和复杂分析。

    GridFS使用两个文档来存储文件,一个用来存储文件本身的块,另外一个用来存储分块的信息和文件的元数据,默认对应的集合分别为fs.chunks和fs.files。

    chunks集合:

    {
      "_id" : <string>,
      "files_id" : <string>,
      "n" : <num>,
      "data" : <binary>
    }

    块集合中文档包含以下属性: chunks._id: 块ID。 chunks.files_id: 对应files集合中文档的_id。 chunks.n: 块的编号,由GridFS管理,从0开始。 chunks.data: 文件数据,是 BSON二进制类型。

    chunks集合使用files_id 和 n作为混合索引。 files集合:

    {
      "_id" : <ObjectID>,
      "length" : <num>,
      "chunkSize" : <num>
      "uploadDate" : <timestamp>
      "md5" : <hash>
    
      "filename" : <string>,
      "contentType" : <string>,
      "aliases" : <string array>,
      "metadata" : <dataObject>,
    }

    files集合中的文档包含以下属性,应用还可以创建额外任意的属性: files._id: 唯一的文件标识,MongoDB的默认值是BSON ObjectID。 files.length: 文件的字节数大小。 files.chunkSize: 每个块的大小,默认为256KB,GridFS根据这个值将文件分成多个块。 files.uploadDate: GridFS第一次存储此文件的时间,类型为ISODate。 files.md5: 文件的md5散列值,是字符串。 files.filename: 可选。人类可读的文件名。 files.contentType: 可选。合法的文件MIME类型。 files.aliases: 可选。别名的字符串数组。 files.metadata: 可选。自定义存储的文件元数据。

    可以通过mongofiles工具或者MongoDB驱动程序来使用GridFS。GridFS主要提供5种操作接口:

    list: 获取文件列表
    get: 获取文件
    put: 写入文件
    search: 根据文件名搜索文件
    delete: 删除文件

    主要技术和架构

    (1)复制

    分布式存储系统一个关键的功能就是复制(Replication),通过复制来应对故障切换、数据集成,还可以用来做读扩展、热备份或者作为离线批处理的数据源。总的来说,MongoDB的复制至少需要两个服务器或者节点。其中一个是注解点,负责处理客户端请求,其他的都是从节点,负责映射主节点的数据。主节点记录在其上执行的所有操作。从节点定期轮询主节点获得这些操作,然后对自己的数据副本执行这些操作。由于和主节点执行了相同的操作,从节点就能保持与主节点的数据同步。

    img

    主节点的操作记录被称为oplog,其存储在一个特殊的数据库中,叫做local。oplog就在其中的oplog.$main集合里面。oplog中的每一个文档都代表主节点上执行的一个操作。文档包含的键如下:

    ts: 操作的时间戳。时间戳是一种内部类型,用以跟踪操作执行的时间。由4字节的时间戳和4字节的递增计数器构成。
    op: 操作类型,只有1字节代码。
    ns: 执行操作的命名空间(集合名)。
    o: 进一步指定要执行的操作的文档。对插入来说,就是要插入的文档。

    img

    MongoDB有两种复制方式,分别为主从复制和副本集。主从复制是最常用的方式,基本的设置方式就是建立一个主节点和一个或多个从节点,每个从节点要知道主节点的地址。所有的从节点都从主节点复制内容。目前还没有能够从从节点复制的机制(菊花链),原因就是从节点并不保存自己的oplog。副本集中的活跃节点对应主节点,当活跃节点故障时,新的活跃节点由副本集中的大多数选举产生。

    从节点的主要作用是作为故障恢复机制,以防主节点数据丢失或者停止服务;其他用途包括:将查询放在从节点上、用从节点做数据处理——以减轻主节点负载、提高系统性能。

    (2)分片

    分片(Sharding)是指将数据拆分,将其分散存放在不同的机器上的过程。将数据分散到不同机器上,不需要功能强大的大型计算机就可以存储更多的数据,处理更大的负载。MongoDB支持自动分片,可以摆脱手动分片的管理困扰。集群自动切分数据,做负载均衡。MongoDB分片的基本思想就是将集合切分成小块。这些块分散到若干片里面,每个片只负责总数据的一部分。应用程序不必知道哪片对应哪些数据,甚至不需要知道数据已经被拆分了,所以在分片之前要运行一个路由进程,该进程为mongos。这个路由器知道所有数据的存放位置,所以应用可以连接它来正常发送请求。对应用来说,它仅知道连接了一个普通的mongod,也就是说分片对于应用来说是透明的。路由器知道数据和片的对应关系,能够转发请求到正确的片上。如果请求有了回应,路由器将其手机起来回送给应用。

    img

    设置分片时,需要从集合里面选一个键,用该键的值作为数据划分的依据。这个键称为片键,片键的选择决定了插入操作在片之间的分布。不论片键随机跳跃还是稳定增加,片键的变化至关重要。

    (3)查询和聚合

    因为GridFS文件的元数据存储在files集合中,因此GridFS可以非常方便地进行文件管理,比如根据文件名、上传时间、文件大小或者自定义的文件元数据进行查询,还可以利用MapReduce做复杂数据分析。这是GridFS把传统文件系统和数据库相结合得到的众多好处之一。

    对比传统文件系统的优势

    分布式:GridFS是基于MongoDB的分布式文件系统,可以直接使用MongoDB Replication和Sharding机制,数据可靠性和水平扩展性都得到保证。GridFS不产生磁盘碎片,因为MongoDB分配数据文件空间时以2GB为一块。

    MapReduce: 可以进行复杂管理和查询分析。

    索引和缓存:元数据存储在MongoDB中,非常方便索引。并且可以对文件和文件元数据进行索引,能提高系统效率。

    checksum:GridFS会为文件产生散列值,可用于校验文件以检查完整性。

    开发者友好: 利用Grid可以简化需求,减小开发成本。要是已经用了MongoDB,GridFS就可以不需要使用独立文件存储架构,并且使代码和数据真正分离,方便管理(尤其是对云计算平台)。

    其他:GridFS可以避免用于存储用户上传内容的文件系统出现的某些问题。例如,GridFS在同一个目录下放置大量的文件是没有任何问题的。GridFS不产生磁盘碎片,因为MongoDB分配数据文件空间时以2GB为一块。

    小结

    MongoDB是一个优秀的分布式基于文档的NoSQL数据库,正是基于MongoDB,GridFS才能将数据库与文件系统完美地结合在一起,兼具二者的优势。使用GridFS,开发者能在不怎么损失性能的情况下对系统进行扩展,并且极大地减小了开发成本和系统维护成本。可以预计,GridFS或者类似的分布式文件系统在未来一定会得到广泛地应用。事实上,以CloudFoundry为代表的云平台已经把MongoDB作为首选的数据库和文件管理系统了。

  • 折腾盛大CloudFoundry平台

    CloudFoundry与OSChina合办CloudFoundry应用开发大赛,前10名能奖一个Nexus 4,前100名有一件卫衣。鉴于之前室友参加CloudFoundry征文活动获奖得到了一个Cherry青轴键盘,于是这次我也有点蠢蠢欲动了:)。报名的要求不高,只要应用搭在CloudFoundry平台上就可以。想想自己实在没什么能拿得出手的Rails应用,只能继续吃老本,决定迁移自己酝酿许久且还没开始beta的PHP项目,搭在支持PHP的盛大CloudFoundry平台上。晚上开始然后结束了迁移,因为迁移成本非常低。实现了3种平台(local/vps, sae, cloudfoundry)同一份代码。

    因为支持本地IO(与SAE最大的区别),需要改动的地方基本上就是数据库的配置了。代码形如:

    //全局变量
    define('VCAP_SERVICES',getenv("VCAP_SERVICES"));
    if(VCAP_SERVICES !== false){
    	define('PLATFORM','cf');
    }elseif(defined('SAE_SECRETKEY')){
    	define('PLATFORM','sae');
    }else{
    	define('PLATFORM','local');
    }
    
    //数据库配置
    if(PLATFORM == 'cf'){
    	$vcap_service = json_decode(VCAP_SERVICES,true);
    	$mysql_config = $vcap_service["mysql-5.1"][0]["credentials"];
    	$db['host'] = $mysql_config["hostname"].':'.$mysql_config["port"];
    	$db['username'] = $mysql_config["user"];
    	$db['password'] = $mysql_config["password"];
    	$db['database'] = $mysql_config["name"];
    }else{
    	//...
    }

    嗯,上面的代码大致演示了一份代码同时运行在多种平台上的大概方式,系统可以根据PLATFORM常量的值来具体实现。

    盛大平台的代码部署有两种方式,一是上传代码zip压缩包,另外一种是通过gcat命令行工具(和vmc非常像)。显然使用gcat更方便,我用的也是gcat。gcat push创建应用,gcat update更新,最值得一提的是gcat logs用来查看日志,用来debug非常好用。我遇到的基本上是唯一的一个问题是,gcat不会上传隐藏文件,比如.htaccess文件,但.htaccess通常又是必须的——可以用来实现URL重写。我的解决办法是用代码来生成必需文件,然后将代码上传到服务器,通过浏览器请求执行。我用来生成.htaccess的代码:

    $ht = <<<EOT
    <IfModule !mod_rewrite.c>
    ErrorDocument 500 "mod_rewrite must be enabled"
    </IfModule>
    RewriteEngine on
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ index.php/$1 [L]
    EOT;
    $file = rtrim(dirname(__FILE__),'/').'/.htaccess';
    echo $file."<br/>";
    if(file_exists($file)){
    	echo 'Yes it exists,and content is ';
    	echo file_get_contents($file);
    }else{
    	echo 'File not exists!!';
    	file_put_contents($file, $ht);
    }

    大公告成!应用地址:jutown.sdapp.cn聚城马上要开始公测啦!)。然后我发现我用来写这篇日志的时间比用来迁移的时间还多,囧。

    12-26 updated

    发现目前的盛大cloudfoundry平台一个很大的问题就是虽然支持本地IO但不持久,也就是说只要应用被停止或重启,非代码包(droplet)里的文件即instance数据都会被清空,之前通过代码生成的文件就会被删除。官方文档里说下一版会推出文件系统服务,为应用提供持久的本地文件存储。现在系统服务里显示有描述为Persistent filesystem service的filesystem服务,我也开启了,可就是没用唉。看来使用云存储服务才是王道。

    12-28 updated

    用gcat files命令发现应用目录app里面多了一个cfs目录,看了盛大云引擎给的Rails文档才发现其实这就是持久化文件存储的目录。于是调整代码,修改文件存储目录后重试,文件终于能成功上传,迁移全部完成!

  • 台湾民主化实践对当前中国政治体制改革的启示

    这是之前政治课的一篇论文,本来是想认真针对这个自己选的主题研究下,因为非常有意义,但可惜的是,因为赶着要交,文章在写完摘要后便开始水了。。文章主要参考《转型:台湾民主化经验的实践》一书,还有很多内容不太敢写,比如开放党禁啥的,怕被查水表额。
    唉,希望中国尽快实现民主化。但我不寄希望于当局主动变革,而是寄希望于以微博为代表的互联网的发展、寄希望于人民的觉醒和人民对民主化的追求;并且努力从自身做起。中国加油。

    改革开放三十余年,改革已逐渐步入深水区,以往的“摸着石头过河”的不动根本的改革渐受掣肘且效果式微,最关键的政治体制改革迫在眉睫。台湾与中国大陆同根同源同种同持有中华文化,其政治改革的有益经验能为中国大陆的政治体制改革提供有效借鉴,不足之处亦能给我们以警醒。本文将以一个热爱自己祖国和人民、忧心国家前途的正直青年的视角,讨论台湾民主化实践对当前中国政治体制改革的启示。

    正文:

    中共十八大开幕在即,中国即将迎来新一届的中共中央领导人。回顾过去胡温执政的十年,总的来说,这十年是中国经济高速发展的十年,是中国加入世界贸易组织、全面参与全球化的十年,在这十年中,中国经济得到长远发展,GDP跃升全球第二,中国成为继美国之后的世界第二大经济体,人民的生活水平也得到较大提高,人均GDP翻了两番,中国成为名符其实的世界工厂,等等。中国取得的成就是有目共睹的。然而,随着中国经济禀赋的被挖掘与耗尽,中国经济或将减慢发展速度甚至停滞,比如经济最近连续7个季度持续下行,增长均速度低于传统的稳定线——8%,一系列长期被经济繁荣所遮掩住的问题将渐渐浮出水面,比如利益蛋糕的分配问题:当蛋糕不能持续变大,蛋糕分配的公平性就会成为焦点。

    胡温政府对于社会问题的解决办法是建设和谐社会,通过维稳来大事化小、小事化了,稳定成为压倒一切的决定因素。尽管维稳能一定程度上缓解社会矛盾,但是,这只是治标而不治本,维稳不能从根本上杜绝以利益冲突为主的社会矛盾,能顺畅表达民意的民主政治制度和良好的法律制度才是根本解决之道。异常可惜的是,在过去十年中,尽管温家宝多次在不同场合大力呼吁政治改革,但碍于既得利益集团的强大阻力,政治体制改革陷于停滞,二十余年来几无进展。

    当前中国的政治改革已经迫在眉睫。以刚在几天前发生的群体性事件——宁波PX事件为例。宁波PX事件是一个典型的“双输”结局,其大致过程为,政府上马重大化工项目,项目遭到民意反对,事件在社交媒体上发酵,博弈数日,地方政府作出全盘妥协,PX项目被宣告终止。地方政府上马PX项目无可厚非,PX项目能为地方带来数百亿的税收,地方政府的目的之一就是为了“造福当地民众”。民众反对PX项目是担心PX致癌,“再多的金钱也买不来健康”。但事实上,担心PX的毒性是非常可笑的事情,PX的毒性与汽油相当,远逊于烟草,而将PX项目建于沿海城市是有根据的,这也是沿袭国外的做法。公众对PX的担心更多的是源于其对PX的不了解,对政府缺乏基本的信任。宁波PX事件与之前发生的群体事件如什邡钼铜、启东排污事件等如出一辙,皆起因于普通民众对政府作为的不知情和对政府解释的不信任,加之互联网时代的信息高速流通,短时间内被广泛流传,大量民众上街,最后政府迫于压力做出妥协。假如政府能代表民众及其利益、政府决策被民众监督,这类事件就能够被杜绝。

    再说台湾的民主化政治改革。台湾与中国大陆同根同源同种同持有中华文化,其政治改革的有益经验能为中国大陆的政治体制改革提供有效借鉴,而不足之处亦能给我们以警醒。

    国民党政府1949年撤守台湾后,面对解放军解放台湾的压力以及二二八事件造成的影响,在台湾实行戒严和白色恐怖的统治,由此引发第一波自由思想运动。1970年代台湾退出联合国、中美建交等一系列事件,使国民党的威权统治面临危机,1977年的五项地方选举,国民党遭受迁台后最大的选举挫折,随后美丽岛事件爆发,其加速了军方舆情部门的衰弱和国民党中央领导结构的本土化与文人化。台湾自由化的关键事件是1986年9月民主进步党的成立和1987年7月来的解严、解禁。一方面,反对运动的突破党禁,使得当局认真考虑进行改革的可能性;随着日益升高的镇压成本,促使当局进一步思考政治自由化的因素。另一方面,戒严令的解除,被视为专制政权宣告修改原有的政治法则,以开放更大的空间,供给个人与团体,行使公民与政治的权利。1988年1月蒋经国逝世,李登辉继任“总统”,这是第一次台湾人担任“国家元首”。1989年发生郑南榕自焚事件,自此海外黑名单人世纷纷回台,并开启以台湾为主体的独立运动,且带给人民思索国会层次的政治改革的机会。1990年2月国民党高层爆发“主流”与“非主流”的斗争;3月发生数万名学生群众民主运动,李登辉同意提出的政治改革时间表,自此民主化成为社会共识。1991年第二届国大代表全面选举,1992年12月立法委员全面改选,1996年3月举行总统直接民选,李登辉当选首届中华民国民选总统。自此台湾完成了由威权体制到民主体制的转型。

    台湾的民主化驱动力主要有两个。一是经济的展和社会进步的影响。台湾经济在1970年代起飞,实现工业化,取得了举世瞩目的巨大成就,与香港、韩国、新加坡并称为“亚洲四小龙”。当经济发展达到特定水平时,民众就会寻求与其经济地位相匹配的政治权利,这就使执政当局被迫开始思考如何进一步开放自由并实行政治民主化。二是国民党的执政合法化危机。从大陆迁至台湾的国民党为维护其统治的合法性,将反攻大陆、光复中国作为其方针,并对内实行戒严;而随着中美建交、台湾在国际上被孤立以及反攻大陆的无望,国民党维持其威权统治的合法性逐渐消失。随着台湾本土意识的觉醒和民众对公民、社会权利的追求,国民党渐难承受政治民主化的压力,最终在一个本省党主席和总统的努力下,台湾终于实现总统和立委的普选,政治实现了民主化。

    归结台湾的民主化经验,主要有三点:一是经济的发展和人民素质的提高能促进政治改革的和平进行,可以避免经济的衰退、社会的动乱和严重的政治倾轧;二是政治改革时间表是非常有必要的,这有利于凝聚全党全社会的共识,并且逐步渐次地进行政治改革能将政治变迁带来的影响降至最低;三是推行政治改革的主要动力来自于民众对自身权益的主动追求,“改革须有人民觉醒和支持”(温家宝)。

    台湾民主化的教训主要是由于台湾的特定历史背景和在民主化过程中的失误,民主化使台湾内部严重分裂,蓝营和绿营在政治上几成死敌,相互之间的刻意阻饶也每每使各种决议流产,严重降低了政府的效率和反应速度。中国大陆实行政治体制改革,就一定要规避这种情况的发生。

    随着中国经济的发展和人民民主意识的觉醒,中国已经处于进行政治改革的历史时期,中国共产党应该把握住这一伟大的历史机遇和挑战,推动政治民主化,真正使人民能自己做主决定与自己切身利益相关的事情,而不是“被代表”。台湾的民主化经验启示我们:一定要设定好政治改革时间表,稳步推动民主化进程。而做法是:一是法治为先,实现司法独立,让执政党在法律的约束下行使权力,防止权力被滥用;二是全面开放报禁和言论自由,让政府和执政党接受人民的监督和批判;三是实现各级政府和机构的差额选举。

  • 做了个微信应用

    昨天晚上在V2EX看到有人分享一个微信失物招领的应用,顿时就被激起做个微信应用的热情。前几天还专门去翻微信开放平台的文档,没有找到很早之前就听说过的自动回复的API。发现原来是在公共帐号那里,而且只有自动回复的API。

    做了zju contact的微信版@zjucontact,利用之前采集到的数据;下午完成第一次代码重构。目前的功能还非常简单,关注者向公共帐号发送要搜索的人名,公共帐号返回其信息和联系方式;管理员还可以直接@别人发短信,利用SAE的短信接口。还挺好玩的,可以做很多有趣的功能。

    贡献一下基础代码,这些是可以共用的,具体应用的代码都删掉了:

    <?php 
    class Weixin{
    	private $token = 'xxxxx';
    	private $admins = array('xxxx');
    	private $text_tpl = "<xml>
    	                    	<ToUserName><![CDATA[%s]]></ToUserName>
    	                    	<FromUserName><![CDATA[%s]]></FromUserName>
    	                    	<CreateTime>%s</CreateTime>
    	                    	<MsgType><![CDATA[text]]></MsgType>
    	                    	<Content><![CDATA[%s]]></Content>
    	                    	<FuncFlag>0</FuncFlag>
    	                    </xml>";
    	private $pictxt_tpl = "<xml>
    	                         <ToUserName><![CDATA[%s]]></ToUserName>
    	                         <FromUserName><![CDATA[%s]]></FromUserName>
    	                         <CreateTime>%s</CreateTime>
    	                         <MsgType><![CDATA[news]]></MsgType>
    	                         <Content><![CDATA[%s]]></Content>
    	                         <ArticleCount>%s</ArticleCount>
    	                         <Articles>%s</Articles>
    	                         <FuncFlag>0</FuncFlag>
    	                     </xml> ";
    	private $pictxt_item = "<item>
    	                         <Title><![CDATA[%s]]></Title>
    	                         <Discription><![CDATA[%s]]></Discription>
    	                         <PicUrl><![CDATA[%s]]></PicUrl>
    	                         <Url><![CDATA[%s]]></Url>
    	                        </item>";
    
    	private $request = array();//当前的请求
    
    	private $precontent = '';//模板输出中内容前缀
    	//入口
    	public function main(){
    		if($this->check_signature()){
    		    $postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
    		    if (!empty($postStr)){
    		        $post_obj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
    		        $request['you'] = $post_obj->FromUserName;
    		        $request['i'] = $post_obj->ToUserName;
    		        $request['type'] = $post_obj->MsgType;//类型,现在只支持text和location
    		        $request['time'] = time();
    		        if($request['type'] == 'text'){
    		            $request['keyword'] = trim($post_obj->Content);
    		        }elseif($request['type'] == 'location'){
    		            $request['x'] = $post_obj->Location_X;
    		            $request['y'] = $post_obj->Location_Y;
    		            $request['scale'] = $post_obj->Scale;
    		            $request['label'] = $post_obj->Label;
    		        }
    		        $this->request = $request;
    		        
    		        $this->router();
    		    }else{
    		        echo $this->input->get('echostr');//第一步验证
    		    }
    		    return ;
    		}
    		echo 'bad request';
    	}
    
    	private function is_admin(){
    		return in_array($this->request['you'], $this->admins);
    	}
    
    	//分析输入,进行不同处理
    	private function router(){
    		//检查用户state
    
    		if($this->request['type'] == 'text'){
    		    $input = $this->request['keyword'];
    		    $matches = array();
    		    if(preg_match_all('/@([\S]*?)\s/', $input, $matches) > 0  && $this->is_admin()){
    		        $names = $matches[1];
    		        //
    		        $this->refer($names, $input."(通过微信公共帐号zjucontact联系.)");
    		    }
    		    if($this->request['keyword'])$this->search($this->request['keyword'],FALSE);
    		}else{
    		    echo $this->tmpl('text', '地理位置等输入还不支持呢');
    		}
    	}
    	//提到某人后用手机短信通知
    	private function refer($names,$message){}
    
    	public function test(){
    		$this->make_request();
    	}
    
    	private function make_request(){
    		$request = array();
    		$request['you'] = 'you';
    		$request['i'] = 'I';
    		$request['type'] = 'text';//类型,现在只支持text和location
    		$request['time'] = time();
    		$request['keyword'] = '米好呀';
    		$this->request = $request;
    	}
    
    	private function check_signature(){
    		$signature = $_GET["signature"];
    		
    		$timestamp = $_GET["timestamp"];
    		$nonce = $_GET["nonce"];
    		$token = $this->token;
    		$tmpArr = array($token, $timestamp, $nonce);
    		sort($tmpArr);
    		$tmpStr = implode( $tmpArr );
    		$tmpStr = sha1( $tmpStr );
    		if( $tmpStr == $signature ){
    		    return true;
    		}else{
    		    return false;
    		}
    	}
    
    
    	//应用模板
    	private function tmpl($type,$data){
    		$result = '';
    		if($type == 'text' && is_string($data)){
    		    //只有一个是可变变量,$data为字符串
    		    $content = $this->precontent.$data;
    		    $result = sprintf($this->text_tpl, $this->request['you'],$this->request['i'],  $this->request['time'],$content);
    		}
    		elseif ($type == 'pictxt' && is_array($data)){
    		    //两个变量data['items'] = array('title','description','picurl','url');data['content'] = ''
    		    $items_str = '';
    		    if(isset($data['items']) ){
    		        $items = $data['items'];
    		    }else{
    		        $items = $data;
    		    }
    		    $content = $this->precontent.(isset($data['content'])?trim($data['content']):'');
    		    $count = count($items);
    		    
    		    foreach ($items as $item)
    		        $items_str .= sprintf($this->pictxt_item, $item['title'], $item['description'], $item['picurl'], $item['url']);
    		    
    		    $result = sprintf($this->pictxt_tpl,$this->request['you'], $this->request['i'],$this->request['time'], $content, $count, $items_str);
    		}
    		header("Content-type: text/html; charset=utf-8");
    		return $result;
    	}
    	//查找一个用户
    	private function search($name){}
    
    	private function log($content){}
    	private function clear_text($string){
    		return htmlspecialchars(trim($string),ENT_QUOTES);
    	}
    }

  • 读《中国道教发展史略》

    就在近半个月前的九月二十九日,中国硕果仅存的国学泰斗南怀瑾先生于苏州驾鹤西去,享年九十五岁。值此之际,借写《道教文化专题》课程读书报告的机会,读先生《中国道教发展史略》,睹大师风采,聊以缅怀。

    先说说先生的生平。先生一九一八年三月十八日诞生于浙江温州乐清县一个世代书香之家,(其家世代都乐善好施,并且每一代都有人出家,在历史上亦出过几位高僧)。从孩提时起即接受严格的传统私塾教育。到十七岁时,除精研四书五经外,涉猎已遍及诸子百家,兼及拳术剑道等多种中国功夫,同时苦心研习文学书法、诗词曲赋、医药卜算、天文历法诸学,每得其精髓而以为乐焉。这位孜孜以求的好学青年,为深入探究宇宙人生的奥秘,不畏艰险,跋山涉水访求多位岩穴高隐之士,虚心求教,学到了许多不传的法门和秘学。(佚名《南怀瑾缘》)先生先后任教于中央军校、金陵大学研究院,报效国家和社会;在闲暇之余,先生遍历名山大川,尽访高僧奇士,与青城山灵岩禅寺住持传西法师、成都维摩精舍首座等结成至交。又曾三年闭关阅藏,于青灯古佛旁日夜苦读。诸如此类,不一而足。先生自此炼成深厚修为。

    先生著作多以演讲整理为主,如在大众读者中广为流传的《论语别裁》《老子他说》《金刚金说什么》等,内容往往兼述三教即儒、释、道三家,对三者进行对比。三教通常代表了中国的传统文化(在中国古代典籍中三教的说法很早就有了,如唐朝武则天曾召集过大批文人学士编撰《三教珠英》,白居易亦撰有《三教论衡》),先生综述三教,集国学之大成,实可谓国学大师。

    《中国道教发展史略》以道教发展史为中心,分八章分别讲述了道教的文化渊源、道教的建立、道教的成长、道教的扩张、道教的演变、宋元时期的道教、明清时期的道教以及二十世纪的道教。《中国道教发展史略》在讲历史的同时,还兼述历史发生的缘由,如社会、政治、文化等各方面因素,让人启发至深。下面将大致概述书中所讲的中国道教发展史。

    道家思想起源于上古中华民族对自然宇宙的认识,天地、神鬼以及万物皆一体同根,即所谓“道”的本原。随着社会文明的进步,渐次形成了天人合一的天道观念、天神人三位一体思想、鬼神崇拜,这些最终构成了道教的思想基础。道教起源于春秋战国时期的神仙方士,在高谈理论的各家学派之外,专门从事天文、地理、医药、养生研究等的被称为“方技之士”成为后世神仙思想的渊源,也是后来道教中心思想的精粹。秦汉时期由于秦始皇、汉武帝等官方力量的干预,神仙方士之术得以兴盛,但同时也引入了很多荒诞不经的巫蛊邪术和符咒图腾。

    道教初创于东汉明帝时期,而正式定型于北魏时代。道士即源自秦汉时期的方士,他们隐居各地名山大泽,修炼仙道,是道教形成的基础。汉末张道陵创立五斗米道,以鬼道教民,成为道教的原型。而东汉吴人魏伯阳把《周易》和老庄之学与修炼神仙丹道结合起来,使神仙丹道之学成为有体系、有哲学基础的理论,神仙丹道之学由此大行。到魏晋时代,道教相沿已蔚然成习了,知识分子十之八九志在博取功名官爵,要求富贵而兼神仙,而作为一种反激,空谈理论的玄学逐渐兴起,玄学作为一种哲学形成为后来道教的理论基础。道教自此建立。

    道佛之争。道教成型的同时作为外来文化的佛教也源源传入中国,晋室东渡之后社会不安与思想漫散绵延百年之久。道教和佛教互争雄长,佛教被国人所普遍接受,促使道教综合儒、道、墨、法、名等家精要为己所用,道教因而得以进一步发展。到李唐王朝,道教仰仗帝王政权的崇奉而取得优势,在政治上道教被尊为国教,民间信仰上也达到鼎盛,足以与当时的佛教分庭抗礼。

    禅道相融。唐末五代之后,华夷混杂、变像相仍的局面,又造成了历史的巨变。道教吸收禅宗身心性命之学,与道家自己的修炼生命之术相结合,形成性命双修的丹道之故,渐已调和的道佛相争归于一致。自张紫阳南宗丹道的崛起,禅道合一的途径已极其明朗。而南宋时期的北方人民长期受困于辽、夏、金、元的动荡局面,激发了王重阳、丘处机师徒的全真道的建立。全真道提倡敦品励行,修心养性的渐修教化,成为黄河南北声势烜赫的新兴道派,威名远布。

    到了明代,道教平添了武当张三丰一派,主要以身心内炼金丹、达成性命双修的丹法为主,其流风遗被,声名之隆,影响之大,绵延至今。明清两朝皇帝对道家思想的推崇,亦促进了道家思想的流传。及至清末义和团,假托符咒神鬼以动,应与道教无关。

    清朝中叶以后,中国文化与宗教,受西方文化思想传入的影响,一蹶至今,尚未重新振起。代表道教的胜地观宇,纵其荦荦大端者,均已奄奄一息、自顾不暇,无力做到承前启后、开展弘宗传教的事业了;道士众中,人才衰落,正统的神仙学术无以昌明,民间流传的道教思想,往往与巫蛊邪术不分。及至中共政权建立以后,对道教的革除整顿更是伤筋动骨,传承几千年的传统文化精髓,竟几无留存,令人扼腕。直至近年,中国大陆对传统文化才稍有所重视,民间层面亦兴起“国学热”,对传统文化的研究始被关注。可惜先生不及传统文化被弘扬光大,就溘然仙逝,让人万分惋惜。

    《中国道教发展史略》行文古朴、深入浅出,讲述了道教的整个发展史,从道教的思想根源讲起,直至当代道教的没落,时间幅度跨越五千余年。先生用平实的语言为我们讲述了一个具有清晰历史脉络的道教发展进程。

    掩卷沉思,心事飘扬。

    毫无疑问,中国传统知识分子的“内用黄老,外示儒术”塑造了中国人的性格,道教思想已经深深植根于整个中华民族。我们要保护好我们的传统文化,守住民族的根基。道教文化作为传统文化的一个代表,历经千年历史的陶涤,绵延至今,其核心部分,是中国传统文化的精髓,集结了先辈中国人的集体智慧,能为现代中国人安身立命提供指导,也可能会为我们以后遭遇未知困境指明解决之路。我们应该努力守护并传承我们优异的文化遗产。

    对于道教文化及其他优秀传统文化的传承,我们应该取其精华、弃其糟粕。纵观道教发展历史,我们不可否认道教的确存在一些文化糟粕如巫蛊邪术等,但其本质是好的、是非常值得现代人学习的。

    我们还要以史为鉴。文化的发展应该顺应自然,自由地发展,只有百家争鸣、百花齐放才能带来文化的繁荣,而一旦官方力量另怀目的地主动介入,就会将原本正常发展的文化引入歧途。道教文化的发展为我们当前社会的文化发展提供了前车之鉴,我们应该吸取教训。

  • Ruby中的多线程

    先看这段代码:

    def fetch_urls_with_archieve(archieve,min,max,url_like)
      urls = []
      count = max - min
      min.upto(max){ |i|
    	Thread.new{
    	  url = archieve.clone # reference value
    	  url["{num}"] = i.to_s
    	  content = open(url){|f| f.read }
    	  content.gsub(url_like){|u| urls.push(full_url(u,url)) }
    	  count -= 1
    	  print "."
    	}
      }
    
      while count >= 0
       Thread.pass # wait
      end
    
      urls.uniq!
      to_file("#{@site}_index",urls)
    end

    这是我正在写的Ruby爬虫类中的一个方法,功能是根据给定的博客索引页把所有文章的URL爬取下来。

    • 从上面可以看出,Ruby通过Thread.new{…}来新建线程,新线程在代码块中执行,在代码块结束时终止。

    • Ruby程序的主体流程由主线程运行,当主线程完成时解释器停止运行,不管其他线程有没有结束,故主线程要确保其他线程都结束时才退出。如上面的wait段所示,Thread.pass用来释放cpu给其他线程,因为计算密集型线程会饥饿同级的IO密集型线程,如果不主动释放,其他线程基本上不会被执行的。

    • 主线程还可以通过执行thread#join或thread#value(获取返回值)来等待线程执行完成。因此上面的wait段更好的实现方法是用thread#join来等待。

    • 对共享变量的存取应该设置互斥量,如m = Mutex.new;m.synchrinize{临界区},在临界区中存取共享变量。

    • Ruby中多线程的一种替代方案是使用协程。

    于是有改进版:

    def fetch_urls_with_archieve(archieve,min,max,url_like)
      urls = []
      total = {}
      lock = Mutex.new
      threads = {}
    
      @@concurrence.times{|i| # 线程数可设置
    	break if i > max-min
    	threads[i] = Thread.new(i+min){ |num|
    	  while(num &lt;= max) do
    	    url = archieve.clone # private copy of archieve
    	    url["{num}"] = num.to_s
    	    total[num] = url # record the open uri
    	    begin
    	      content = open(url,:proxy=>@@proxy){ |f| f.read } # 使用代理获取url内容
    	    rescue Exception=>e
    	      puts e.message
    	      puts "I'm going to sleep #{@@wait_http} seconds"
    	      sleep(@@wait_http) # http出错,等待后重试
    	    else
    	      lock.synchronize { # 共享变量锁
    	        content.gsub(url_like){|u| urls.push(full_url(u,url)) } # 提取url
    	        print "." # 很重要
    	        num += @@concurrence
    	      }
    	    end
    	  end
    	}
    	sleep(3) #
      }
    
      threads.each_pair{ |k,t|
    	begin
    	  t.join # 主线程等待其他线程完成
    	rescue Exception=>e
    	  puts "#{e.message},thread #{k} exits."
    	end
      }
      urls.uniq!
      to_file("#{@config[:name]}_index",urls) if urls.size>0 # 保存
    end

    这个是在爬虫中真实使用的代码,就可以说比较完整了,考虑了前面说的两点,还要考虑一些现实情况,比如并行线程数的可配置,通过代理访问,异常检测等等。

查看全部文章 →

About Me

My name is Zengbin, I'm now a postgraduate student of Zhejiang University, majoring in Computer Science. And I'm also the founder of Creatist Studio.

Feel free to contact me :)
Email

一位在MIT教数学的老师总结的十条经验,以为座右铭。

  1. 你能够做到每天七个小时坐在书桌前。
  2. 只有在学你觉得学不会的东西时才能学到东西。
  3. 总的来说,知其所以然比知其然重要得多。
  4. 在科学和工程方面,没有人能骗太久。
  5. 并不是天才才能做有创造性的工作。
  6. 你必须对自己高标准严要求。
  7. 世界变化很快,你最好选学那些坚实恒久的学科,少赶时髦。
  8. 你永远赶不上进度,别人也一样。
  9. 未来的计算机文化是正发生在你的身边,并不是计算机文化课上学的东西。
  10. 数学仍然是科学界的女皇。