2012年8月30日星期四

小记:python中用PIL进行图形合成

网站上需要展示一个类似如下的图:

整个图包含了三部分:背景,带透明度设置的阴影,以及封面。三个图通过程序合成成以上的样子。

用PIL实现极为简单,但摸索了差不多半小时才搞好,贴出来,或许对某人有用:


bg = Image.open(r'F:\temp\bg.png').convert('RGBA')
shadow = Image.open(r'F:\temp\shadow.png').convert('RGBA')
cover = Image.open(r'F:\temp\cover.png').convert('RGBA')
#下面这步是关键,蒙版的对象,采用自身即可根据透明度进行运算
bg.paste(shadow, ((bg.size[0]-shadow.size[0])/2,(bg.size[1]-shadow.size[1])/2), shadow)
bg.paste(book, ((bg.size[0]-book.size[0])/2,(bg.size[1]-book.size[1])/2))
bg.save(r'F:\temp\result.png', format='png')

2012年8月12日星期日

阿福技术BLOG所有文章打包下载

https://docs.google.com/open?id=0B2ZH5H4iY-oLdWYtMnlsblcya0U 看来,百度空间的业务对于百度来说是鸡肋,百度要放弃这个业务了。 不但故意改版得很难用,还提供文件打包下载的功能,潜台词是:东西都给你打包好了,滚吧,爷不伺候了……

2012年8月5日星期日

关于QQ音乐的一些看法

QQ音乐是我工作之余使用得比较多的一个腾讯的产品,因为已经使用了腾讯的其他很多服务,在选择音乐播放器上,也习惯性地选择了QQ音乐。 毕竟我自己不是音乐发烧友,仅仅只在闲暇时或者累了,放点音乐来舒缓的时候,才打开软件听歌。 使用QQ音乐有很长的时间了,但觉得这款软件时感觉越来越难用,老实说,它一直没让我爽过。 直到今天,偶然使用一款叫做飞乐电台的名不见经传的的播放器,惊奇的发现,里面推荐的歌我都非常喜欢。 为什么,一个亿级用户的公司,推荐的歌居然没有一款小软件的歌好听? 看看QQ音乐的新歌列表里,都是一些没见过的歌手,唱的一些不知所谓的烂歌,正常人一般都是听两首就听不下去的。 换了角度,假设我是QQ音乐的产品经理,我会怎么去考虑:坐拥上亿用户,不愁没流量;中国的盗版严重,从用户侧是收费很难;因为流量大,所以唱片公司很愿意接进来。 好吧,只要唱片公司给推广费,我就把你们的烂歌推到第一条,保证每天有百万次甚至千万次的曝光。 有钱赚,很好! 可用户不见得喜欢! 过度商业化,导致产品变成令用户讨厌的东西,而产品存在的意义则是对用户产生价值。这种背道而驰的例子,QQ音乐只是其中之一,如同百度,起家之时就是这么一路走过来的。 看来,创业小公司还是很有机会的,你们能抛开过度商业化去做用户真正喜欢的产品,直到赚钱的的目的胜过创造用户价值。

2012年8月1日星期三

遐想:微博的架构(一)

    不管是遐想还是瞎想,自己想试着从架构师的角度去试试,如果我自己去做一套微博系统,我会怎么去设计。于是想写一系列的文章,假想如何去构建一套微博系统。

1. 首先假想:在构建系统以前,一些基础服务已经完备,不必再重头再去一一构建。这些基础服务包括:

  日志服务:保存各种日志
  异步消息总线服务:用于系统间的解耦,进行异步消息通讯。
  监控服务:监控各种运营数据,便于发现问题。
  配置服务:提供一个容灾的集中的中心配置点,所有的相关配置都可以从配置中心里获取,简化运维,典型的比如开源的zoo_keeper
  日志计算服务:map-reduce, 典型的如hadoop,用于离线的各种数据分析。
  分布式key-value存储:由唯一的一个key快速定位到value,提供get/set/remove三个基本操作。实现为分布式系统,可自动扩容。
  分布式文件系统:可以存储海量的小文件,诸如图片服务等。
  CDN服务:做内容分发,把内容复制到离用户最近的访问点上去。
  短URL服务:把一个长URL转成极短的URL。
  以上服务都是逻辑上极度简单的,便于理解。但实现上有很不简单,比如分布式、比如容灾等,假想他们都是稳定而又可靠的。

2. 第二个部分是微博的存储:

   先看微博本身的特点,最长140个字,假设使用UTF-8编码,则每条微博最多140*3=420字节。因此1kb的存储空间可以存储两条微博,加上微博本身的一些元数据,以每条微博512字节来进行存储吧。
    已经定义了每条微博固定占512字节,则微博可以顺序存储,把所有微博放在一个连续的大文件里,根据偏移量就可以随机定位到任意一条微博。单个文件肯定是不行的,因此一个机器上可以放多个文件,多个机器分别存储不同内容,再按群集部署,一定的群集放在某个机房内,再进一步扩展,不同的城市再建立多个机房……
    城市,机房,群集,机器,文件,偏移量……以上的信息都用一个数字来编号的话,每条微博最终都能得到一条唯一的ID。 不过,城市和机房等因素一般是用来做异地容灾的,与ID无关。
    再者,由于微博的完全开放的,因此就算其他人猜出任意一条微博的ID,也不存在隐私方面的问题。就算用同样的存储结构存储私信等内容,仍可以在微博的额外的92个字节的meta区域加上权限和类型等信息,然后接入服务器读取微博的内容后,判断当前的读取者无权获得此条微博,再返回错误就行。
    决定了微博的存储结构和存储方式,下一步是确定微博的数据如何分布到多个文件,多个机器,甚至多个群集上。
    从群集上看,按用户进行分区是好办法。假设当前有1000万用户,则可以每个群集服务100万用户,一共十个群集即可。用户量增加后,再按群集进行扩展。
    微博的特性也决定了,微博是按时间顺序来进行读取的。因此第二个分区的维度是时间:简单的把当前发表的微博顺序存储在大文件中,然后返回微博的ID即可。一个文件写满后写另一个,一台机器写满后再写另一台……这样也带来了缺点:群集中存储最近的微博内容的机器总是IO最高的,其他的则比较闲。因此也可以在写微博的时候,前端随即分发给所有群集内的服务器,每个服务器内部进行切换文件。当所有机器都达到一定量级后,在群集内增加机器,把比较老的文件迁移到新的机器上,然后写分发从原来的N台机器变为N+M台。


    顺序存储带来的另一个问题是:修改和删除怎么办?好在微博的特点就是,不支持修改,且删除极少。对于删除的情况,修改92字节的meta部分,把微博标记为已经删除即可。前端的调用者读到微博已经删除的标记后,丢弃到这条数据。删除仅仅只是在文件内留下512字节的尸体,毫无压力。
    这样的存储不仅仅存储微博:转播,评论,私信,短URL等内容都可以以这样的方式保存下来。
    存储量的问题:TWITTER现在的发表量每天3.4亿条(参考:《 用户达1.4亿 每天Tweets发布量达3.4亿条 》, http://web2.iresearch.cn/87/20120324/167421.shtml ),按照这样的存储方式,则每天增加的存储空间为162Gb。sina微博每天发表1亿条微博: http://www.bjnews.com.cn/finance/2012/05/16/199252.html,存储空间占用为47.7G。考虑异地容灾,存储两份或者三份的话,压力也不算太大。
    关于存储的另一个考虑是:如果不固定分配512字节,按照紧凑存储的话,会更省空间。假设已经统计到平均每个用户的微博长度为200字节,加上头信息和预留存储位,可能最多300字节就能存储一条。因此,这里可以折中一下,把存储单元按128字节划分为一个块,一条微博最多占用4个块,也可以只占用一个块。假设平均的微博长度仅占用两个块,则上面计算出的3.4亿条微博每天的存储量减半,达到81.1G。



3. 存储之上:近期热点数据,CACHE,传输,聚合拉取等问题

    有了存储后,不可能用户读取微博的时候,都从磁盘去读取,一定是要有CACHE的。
    并且,微博的访问特点与时间相关性很大,比如,最近一两天的微博是最热的;最近一两个星期的内容,还有人会去看看;超过一个月以前的内容,可能几乎没人去访问了。
    根据这个特点,我们构建一个三级的存储:所有微博的最终落地是磁盘,容量大,成本低;最近一个月的微博,采用SSD存储,性价比适中;最近一周内的微博,全部存储在内存中。
    假设微博新增量有twitter那么大,那需要的容量为:(按最大存储容量计算,按照平均长度计算的话,容量减半)
        内存:162G*7 = 1134G
        SSD: 162*30 = 4860G
        磁盘:162*365 = 57.7T/每年
    内存,SSD,磁盘,其存储的格式可以都保持一致,仍可以以唯一的微博ID索引到。一条最新的微博会同时存在与内存,SSD和磁盘中;内存和SSD中的内容,相关的服务进程会定期对超时的数据进行淘汰。存储层的接口服务器来决定把请求转发到内存,SSD还是磁盘。
    另一个可能是,在SSD或磁盘中的某条微博突然死灰复燃,又火起来了。在架构上,可以在SSD存储和磁盘存储之上再加自己的CACHE层,把访问得多的数据CACHE住,避免偶发性的热点数据的情况。
    从这样的设计也可以看出,总体上采用了有损服务的策略:仅保证了近期的微博有较好的读取速度,晚一点的就会越来越慢。

    再一个问题是传输:每条微博最多512字节,你想到了什么?小数据量的高性能传输,是使用UDP通讯的极佳场合:
    1. 从性能上而言,网络上的IO次数,TCP的极限是2万次/s,而UDP的极限是10万次/s;
    2. 一条微博完全可以放在一个UDP包内,甚至放多条也是可以的;
    3. 丢包的问题:极端情况下,承载一条微博内容的UDP包丢了,会怎么样?那就让它丢吧,让用户在某个时间点少看一条微博又不会死,更不会怀孕……没什么大不了!
    4. 在系统内部的复杂网络环境里,可以做到点对点通讯。或许你认为点对点通讯没什么大不了的,我来举个例子:假设使用TCP协议,服务器A请求服务器B,服务器B再请求服务器C,服务器C响应给服务器B,服务器B再响应给服务器A,类似代理一样,司空见惯,没什么异常的;但是,如果使用了UDP,则可以有这样的效果:服务器A请求服务器B,并在包体中带上自己的IP和端口,服务器B转发请求给服务器C,服务器C响应的时候,根据包体内的地址,直接响应给服务器A。P2P通讯的好处就是每个代理服务器负责转发就好,不必等待请求的返回,代理本身的吞吐量提高了,且缩短了回路的路径长度,效率更高。

   下一个问题是微博主页的聚合拉取。经过前面的介绍,我们了解到如下信息:
       1. 微博有唯一ID,可以快速定位到确定的一条微博;
       2. 近期的微博都在内存中,访问极快;
       3. 机器间的微博使用UDP传输,性能极其快……
   有了上面的铺垫,下面看起来不可能实现的功能也就得以实现了:每个人收听了N个人,N个人发了M条微博,则进入微博主页的时候,需要拉取N*M条数据,然后按时间排序,显示给用户。
    计算方法很简单,但数据量非常大,要做到快速显示出结果,只有一个方法:并行计算。
    之前有提到,服务器群集按用户来划分,某部分用户存储在某个群集中。首先假设每个用户收听的人,还有每个人发表微博的ID列表这两种数据是已经存储好了的。拉取首页的流程如下:
    1. 主服务器拉取当前用户的收听的人的列表;(花费时间T1)
    2. 主服务器根据用户分区的规则,把获取这个人微博的请求分别发到对应的群集上,然后等待一定的时间;(发出请求只花费很少的时间,忽略不计,自身最多等待T2时间)
    3. 群集服务器收到请求后,请求用户创建微博的ID列表,返回给主服务器;(花费时间T3)
    4. 主服务器对N个用户的M条微博ID按时间排序,确定分页,取某个ID段;(花费时间T4)
    5. 主服务器把微博ID发到多个微博存储服务器,等待时间T5;
    6. 微博存储服务器根据ID返回微博,花费时间T6;
    7. 主服务器输出内容……完成!
    按照最慢的时间:T1+T2+T4+T5,T2,T5这个等待时间段内,可能都没返回,可能返回了一部分;都没返回就让用户重试,返回了一部分就显示这部分。有损服务,总比让用户一直傻等的好。
    最快的情况是T3<T2, T6<T5,数据都拉到了,皆大欢喜。
    在T3, T5这两个步骤,请求是分布在多个机器上完成的,庞大的数据分摊给了整个群集,而非顺序地逐个执行,因而性能得以提高。