Async源码分析

最近在使用到node js的async库的时候,对其waterfall的实现感觉很奇妙,于是看了一下源码:

    async.waterfall = function (tasks, callback) {
        callback = callback || function () {};
        if (!_isArray(tasks)) {
          var err = new Error('First argument to waterfall must be an array of functions');
          return callback(err);
        }
        if (!tasks.length) {
            return callback();
        }
        var wrapIterator = function (iterator) {
            return function (err) {
                if (err) {
                    callback.apply(null, arguments);
                    callback = function () {};
                }
                else {
                    var args = Array.prototype.slice.call(arguments, 1);
                    var next = iterator.next();
                    if (next) {
                        args.push(wrapIterator(next));
                    }
                    else {
                        args.push(callback);
                    }
                    async.setImmediate(function () {
                        iterator.apply(null, args);
                    });
                }
            };
        };
        wrapIterator(async.iterator(tasks))();
    };
 

技术的成长

最近因为一件事情,让自己突然对自己产生了巨大的怀疑。工作一年多,仔细想想貌似真的只是在积累项目经验,而在技术深度上却一直停滞不前。这其中确也有因为之前做产品没有太多空闲时间的缘故,但更多的还是自己一直不得章法。对nginx源码的学习,一拖再拖,想成为这方面的专家却也不知道努力挤出时间或者说没有好的方法让自己合理安排出时间。

毕业的时候自己选择这里,就是想安心的做技术,以求在技术上得到长足的进步。现在却发现在做着一些没那么有技术含量的东西,像某人所说:上学的时候给我一定的时间也能够做出来。虽然我觉得上学的时候,大部分进公司做的东西也能做出来(除非是那种需要基于一定的环境像大数据、高并发才能做的)。但其实我明白,那句话的意思主要强调的是应该潜心去研究一门技术,比如hadoop、storm等,成为一个领域的专家。这也的确是自己的软肋,也的确该好好加强。

Nginx源码分析之基本数据结构

引言

nginx实现中有很多结构体,一般命名为ngx_xxx_t。这些结构体分散在许多头文件中。src/core/ngx_core.h中把几乎所有的头文件都集合起来。也因此造成了nginx各部分源代码的耦合。但实际上nginx各个部分逻辑划分还是很明确的,整体上是一种松散的结构。

作者之所以重复造了这些轮子,无非是为了追求高效。查看这些数据结构的源码,的确是设计的比较精巧,也保证了对内存足够小的占用以及各种操作的高效。

谈谈系统架构这个东西

架构这个词在很多人看来都是很高大上的一个东西。事实上,搞架构的这些人却也都是些大神,至少都是在这个领域浸淫N久的专家级人物。现在很火的全栈工程师这个概念,就是架构师的另一种表现形式。

之于架构,其含义无非是从技术细节跳出来自上而下宏观地看待系统的一个思维,就好比建筑设计一样。架构师的角色和建筑设计师在某种意义上是相同的。在微博上看到蔡学镛分享过这么一个架构设计流程的图,从中或多或少能看出架构设计一个大概的流程。

arch-1

首当其冲的,肯定是需要对整个系统的业务进行拆分,进行业务设计,目的就是要捋清楚系统是干什么的,能提供什么功能,对系统的需求要做到详尽的分析和考虑。不过这部分,在我参与过的一些项目看来,尤其是对现在普遍使用的敏捷开发流程来说,无需考虑的太面面俱到,但至少不能太窄或者偏离正轨,后续的开发过程会不断的反馈回来进行调整。

接下来,系统的业务明确之后,交互设计和领域建模便可以同时执行。当然,这里我是觉得交互设计和架构师是没啥关系的,顶多就是两者要相辅相成。而领域建模这个就显得很重要了。领域建模是业务设计的主要逻辑,把现实中的业务转化成抽象的对象,这个确实是能力的体现了。我觉得这一部分很多出色的架构师相比其他人突出的一个很关键的地方。

技术模块设计则是在理解了系统的业务需求之后,对整体的一个技术框架上的设计。这里对于技术架构,我一直有一个分不太清楚的东西,就是软件架构和系统架构。说到底,这两者都是软件层面的含义,所不同的是前者到了代码层面,而系统架构则是到了软件层面。软件架构是位于系统架构之上的。一个系统,使用了Spring、Hibernater然后用了MVC设计模式,这就是软件架构;一个系统分成负载均衡模块、Link模块、队列模块、数据模块、推送模块等等则就是系统架构。再往下就应该是部署架构了,比如系统部署了几个结点、结点之间的关系、网络的规划结构、系统的高可用、可扩展等等。当然对于一个系统来说,数据的设计是可以拿出来重点进行的,毕竟对于互联网应用来说,数据 is all,系统的很多性能、效率问题是和数据的存储设计有密切关系的。

到最后,业务之上的这些设计会反作用于业务,将系统的关键点反馈回来,从而对业务进行调整,进而再推进整个架构的流程。现在很火的敏捷开发,某种角度看来就是一个不断迭代、反馈的过程,是传统架构设计的一种演化形式。

谈到架构,那么如何才能具有架构能力呢?借鉴在知乎上看到一个回答:

  • 视野开阔,知道可以直接用哪个开源项目来满足这样那样的需求。多数时候其实我们并不需要重复造轮子。视野窄的架构师会放着捷径不走,不断让团队重复造轮子,直至把项目拖死。
  • 精通设计模式,但又不泛用。不设计过度,不在各种细节问题上需求蔓延。所有架构设计都是为了满足产品需求的,不满足需求或者过度设计都是菜鸟行为。
  • 把系统拆分成多个子系统或模块,模块之间尽量松耦合,使得原先只能串行的开发任务,可以并行开展,也就是说良好的设计可以通过投入更多人力来缩短工期。反之拙劣的设计需要一个人维护一大坨代码,无法通过加人并行开发来缩短工期。
  • 能清楚地知道系统的瓶颈在什么地方,不断地定位技术难度、研发进度、性能、内存等各方面的瓶颈,不断调整骨干力量解决瓶颈,在风险爆发之前就消除隐患。
  • 行业经验带来的直觉和预见性,可以预先需求可能产生怎样的变化,提前把可扩展性、后向兼容性设计好。但仍然不要过度设计

以上对架构的一些理解,很多地方自认还是有点迷糊。在进行系统设计的时候,也经常摸不着头脑,把不同层次的东西混为一谈。记得蔡学镛大神之前还分享过一张图片,对架构讲的挺透彻的。不明白的时候看看这个,会有种茅塞顿开的感觉。

arch-2

《白帽子讲Web安全》读书笔记

最近一直在忙着易信公众平台的开发工作,一直没能抽出空来总结一下。周末终于有了一些空闲,就把这本书的笔记写了一下。

整本书四篇十八章,包括世界观安全、客户端脚本安全、服务端应用安全以及互联网公司安全运营四大部分。

一、世界观安全

  1. 黑帽子和白帽子这两个概念,前者指的是利用安全技术进行破坏的哪一类黑客,后者则指的是工作在反黑客领域的安全技术专家。
  2. 安全问题的本质是信任的问题。并且安全是一个持续的过程,并不存在所谓的银弹。
  3. 安全三要素:机密性、完整性、可用性
  4. 一个安全评估的过程可以分为4个阶段:资产等级划分、威胁分析、风险分析、确认解决方案。其中威胁分析的一种建模方法是微软提出的STRIDE模型;风险分析则是DREAD模型,Risk = Probability * Damage Potenial。
  5. 白帽子并发有以下几个原则:Secure By Default原则;纵深防御原则(Defense in Depth);数据与代码分离原则;不可预测性原则。

杂感一篇

最近两部关于青春的片子《致青春》和《中国合伙人》,对于前者这种爱情的东西,从某个时间段开始,我早已不再关注。后者讲述的奋斗、梦想,却是我特别想看一看的。虽然我知道成功学这东西,完全是一群成功了的人在吹嘘当年自己有多苦逼,后来变得多NB。但是他们的成功却的确能触动自己心里的那根神经,唤起自己当初的激情。

十几年前,自己无意之中说出的一句话,到现在也算一直坚持了下来。虽然没有功成名就,但到现在还是不至于令自己失望的。平淡的日子往往让自己忘了自己当初的梦想,也忘了走向它的这条路。今天看《中国合伙人》这部影片又像当初看《社交网络》一样让自己全身瞬间充满了能量,同时也让自己拷问着自己:还坚持当初的梦想吗?正在为梦想努力吗?努力有收获吗?其实,现实中大多数人在说自己梦想的时候都能侃侃而谈,而真正能付诸于努力的人却寥寥可数。为梦想而努力,结果不一定是成功,但没有为此而努力过,那也许会成为一辈子最大的憾事。

电影里三个人的友情是让我感触颇深的另一个地方。人的一生会不停地结交到各种朋友,但最后真正能称得上朋友的人其实就那么几个人。和他们在一起,你会很确定不管你怎么样,他们都不会嫌弃你;不管你跟他们说了什么话,他们也不会介意。心情不好的时候,一个电话,他们不管有多忙也会立马赶到你的身旁。其实,以前的自己挺自私的,有点像孟晓峻,自从经历了某些事情之后,我才体会到友情是多么的珍贵,也才弥足珍惜现在的朋友们。

无题

5.11-5.12,176公里,千岛湖镇-汾口-千岛湖镇,环湖一周。相信这将会是我未来永远难忘的一段经历。

开始的计划是5.11中午从千岛湖镇出发,历经96公里前往汾口,18:00左右到达目的地。没想到走了大约20公里之后遇到了各种上坡路,尤其一段一公里的大上坡,让我们实在无法保持速度。全身各种疼,大腿完全是无知觉地在做机械运动,有无数次想放弃的冲动。如此艰难的挣扎,造成的后果就是从枫树岭开始的20公里山路只能摸黑前进,中间好几次撞到石头差点栽到地上。所幸的是有一辆自行车是有车灯的,不然还真不知道如何前行。最后到达汾口的时候已经是晚上快9点了。虽然远远晚于原定计划,但顺利到达目的地的喜悦还是让我们挺开心的。

5.12的路程相对来说是比较容易的,一路都是大缓坡,咬咬牙就都过了。中间在界首农庄吃了顿午饭。之后连续10个隧道,一路都还是挺舒服的。下午4点多,终于回到了出发点。算是完成了这次环千岛湖骑行。

这一段行程,真的感觉是对自己的一种历练+超越。在很多次想要放弃的时候,我都告诉自己,人只有超越自己、超越极限,才能成为一个无比强大的个体。和别人比较是没有任何意义的,能够战胜自己的人才是最优秀的。

PS:一路上各种哈雷机车,那叫一个帅气。。。据说是哈雷机车110周年纪念日,全国的机车爱好者都过来环湖庆祝。望而兴叹,啥时候自己也能成为一个哈雷机车车友呢?

Android异步加载图片

对于Android中的异步加载图片,自己总结了两种方式,如下:

1.

/*
 * 异步读取图片,需要传递三个参数:Imageview imageView,String imagePath,int maxNumpixels
* @author Bryant
/
public class AsyncLoadImage extends AsyncTask<Object, Object, Void> {
@Override
protected Void doInBackground(Object… params) {

    try {  
        ImageView imageView=(ImageView) params[0];  
        String path=(String) params[1];  
        int maxNumOfPixels = (Integer)params[2];

        Bitmap bm = CompressPicture.compress(path,maxNumOfPixels);

        publishProgress(new Object[] {imageView, bm});  
    }catch (Exception e) {  
        e.printStackTrace();  
    }  

    return null;
}  

protected void onProgressUpdate(Object... progress) {  
    ImageView imageView = (ImageView) progress[0];  
    Bitmap bm = (Bitmap) progress[1];
    if(bm != null){
        imageView.setImageBitmap(bm); 
    }

}  

}

/**
 * 图片异步加载
 * @author Bryant
 *
 */
public class AsyncImageLoader {
    private Map> imageCache=new HashMap>();
    private int maxNumPixels;
    
    public Bitmap loadBitmap(final String imagePath,int maxNumPixels,final ImageCallback callback){
        this.maxNumPixels = maxNumPixels;
        
        if(imageCache.containsKey(imagePath)){
            SoftReference softReference=imageCache.get(imagePath);
            if(softReference.get()!=null){
                return softReference.get();
            }
        }
        final Handler handler=new Handler(){
            @Override
            public void handleMessage(Message msg) {
                callback.imageLoaded((Bitmap) msg.obj, imagePath);
            }
        };
        new Thread(){
            public void run() {
                Bitmap bitmap=loadImageFromPath(imagePath);
                imageCache.put(imagePath, new SoftReference(bitmap));
                handler.sendMessage(handler.obtainMessage(0,bitmap));
            };
        }.start();
        return null;
    }
    
    protected Bitmap loadImageFromPath(String imagePath) {
        try {
            return CompressPicture.compress(imagePath, maxNumPixels);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public interface ImageCallback{
        public void imageLoaded(Bitmap imageBitmap,String imagePath);
    }
}

经过实际使用,AsyncImageLoader的效率比较高,但是调用比较麻烦。 调用示例:

private AsyncImageLoader imageLoader = new AsyncImageLoader();
imageLoader.loadBitmap(imagePath,maxNumPixels, new ImageCallback() {
    public void imageLoaded(Bitmap imageBitmap, String imagePath) {
        imageView.setImageBitmap(imageBitmap);
    }
}); 

AsyncLoadImage调用较简单,调用示例如下:

new AsyncLoadImage().execute(new Object[]{imageView,imagePath,maxNumPixels});

《七周七语言》读后感

在微博上看到Robbin老大推荐《七周七语言》一书,去豆瓣上查了一下发现大家对这本书的评价也是非常高,于是就想着去读一下。书买了之后由于找工作一直没有来得及看,随着前段时间把工作定了,就着手对这本书的学习了。从书名来看,顾名思义就是讲了七种不同的编程语言。自己浏览了一下目录,心里也大致衡量了一下,对于这七种语言,我觉得我以后会用到的或者说是能去学的应该也就Ruby、Scala两个,所以就计划大体地浏览一下这七门语言,先有个大概的了解就好。这样每天利用上午的两个小时,一共大约两周的时间,大体把这本书看完了。

这本书一共讲了四种编程范型:面向对象编程、原型编程、约束-逻辑编程、函数式编程。面向对象编程有Ruby和Scala;原型编程是Io;约束-逻辑编程是Prolog;函数式编程有Scala、Erlang、Clojure、Haskell。对于这几种编程范型,书里面最后一章进行了很好的总结,在这里也就不重复了。

在阅读这本书的过程中,经常会在看到某个语言的某个特性时让自己有种狂喜的感觉,让自己感到特别兴奋,有一股强烈的冲动去学习它、去运用到实际中。自己在这过程中也产生了一些想法,思考了一些问题。

  • 书的一开始是Ruby那一章。其实几年前就曾听闻Ruby on rails的威名,也知道ItEye就是使用的这个技术,知道Ruby以注重程序员效率为主。之前花了点时间进行了学习,但一直没有一个练手的项目,所以也就停留在似懂非懂的地步。这次看这一章,第一感觉仍然是惊喜于Ruby语法的灵活。写Ruby代码就觉得是在说话一样,而且各种方式都可以,让人非常舒服。另外,Ruby的纯面向对象和动态类型特性都极大地提高了编程效率。对于Ruby在性能、并发方面的弱点,随着现在硬件性能的提高以及Ruby自身的改进,已经越来越不是问题。

  • 对于Io这个语言,自己很陌生,是第一次听说。不过它的原型编程,自己曾在JavaScript中接触过。Io的另一个特色是万事万物皆消息,这个东西确实挺新颖,在处理并发的时候使用actor发送消息、处理消息能达到不错的效果。此外,它的future让我想起了Java中的FutureTask和Future,不知道Java是不是参考Io的。这个机制在多线程环境中也是个很不错的方案。

  • Prolog,也是第一次听说,更是第一次听说声明式编程语言和逻辑编程语言。貌似只要描述一个事实和推论,就能写出解决很多难题的程序来。里面对于数独和八皇后问题的编码解决,让我真的是叹为观止。此外,自己也第一次知道了尾递归优化这个技术,对于递归调用,这个优化的确能够大大节省内存占用。但是真的对于这种语言很不适应,也就没太仔细地看。不过,不得不承认的是DSL语言在特定领域确实是无可比拟的。

  • Scala,一门混合编程语言,算是面向对象语言与函数式语言之间的桥梁。其运行于JVM之上,加上对函数式编程的支持,使得很多基于Java开发的系统可以在之前的基础上进行改进,极大地提高了编程效率。而且其使用val关键字声明值不可变的变量以及其actor使用了线程池的方式都是对并发不错的解决方案。看到这个语言,我强烈地感觉Scala是一个能够取代Java的高级语言,也特别想去学习一下。也有了计划去进一步接触和使用。

  • Erlang,这个语言之前看到过,知道是一个针对并发的编程语言。不过看了书才知道其是基于Prolog而来的。其“就让它崩溃”的错误处理策略显得非常健壮,另外并发方面采用的是开销极小的轻量级进程也是一个优势。最让我惊叹的是它的列表解析功能,一个典型的例子就是能够轻易的向一个列表中加入新的属性。

  • Clojure,又一个陌生的语言,一个JVM上的Lisp实现,也就是一个Lisp方言。对于Lisp自己也是从未接触过。不过从Lisp字面上的意思来看就是一个列表语言,即取列表第一个元素作函数,其余元素做参数。另外就是在Lisp中数据即代码、代码即数据。这里最让我惊异的是其采用事务内存STM和原子来解决并发问题,算是一个亮点。此外,使用延迟计算解决斐波那契数列和阶乘问题以及Clojure-Java互操作对Java语言的扩展也都是很突出的优势。

  • 最后一个语言Haskell是一个纯函数编程语言,也是本书一个让自己看了很久也没搞明白的语言。可能是对于函数式编程自己就是个小白的缘故吧。不过好歹是弄明白了柯里化的意思,也看到了惰性计算的好处。此外,对其中的monad这个为了弥补函数式编程在命令式风格编程方面的缺陷采用的技术,自己看了好几遍还是很迷糊。书上说这是Haskell的精髓,弄懂这个能学到很多东西。自己打算以后有时间再回过头来好好研究一下这个东西。

总之,这本书确实是一本不可多得的好书,值得好好研读一下。强烈推荐!

关于terracotta在tomcat集群中做session共享的问题

手头上的一个项目“陕西省专业技术人员继续教育学习与管理平台”是服务于全陕西省130万专技人员的高并发且事务逻辑较复杂的一个系统,不管是编码上还是系统架构部署上都具有一定的挑战性。在考虑了现有设备以及系统负载的基础上,整个系统架构采用了六台服务器,分别是:WEB门户服务器(WEB门户+Oracle数据库)、学习管理平台服务器、系统核心数据库、资源下载服务器、在线点播服务器、数据库备份服务器。

系统运行了数月,WEB门户采用的apache+mod_jk+memcached+4tomcat能够正常应付目前的访问。管理学习系统虽然由于一些在线课程还未上线,最大的并发还未开始,但目前课程文件的上传、人员的表单录入等对服务器的造成的负载经常使服务器响应缓慢甚至不响应。考虑到开始采用的nginx+4tomcat+ip_hash,ip_hash本身策略的缺陷使负载不能很好地均衡,于是使用memcached做集群的session共享。此时出现了一个到现在还让我搞不清楚的现象:系统session中存入一些timestamp的Date对象,读取的时候,竟然会读成UNIX原始时间戳。一直没办法解决这个问题,只能采用ip_hash的方式运行着,一旦出现服务器不响应的情况就重启tomcat。

这几天上网查资料的时候,发现terracotta可以做tomcat集群的session共享,由于其是jvm级别的cluster解决方案且采用find-gained changes机制,因此在性能要优于memcached,关键是其是jvm堆级别的复制,储存任何值都没任何问题。按照官网的说明把所有东西都配置好了,却发现session无法正常共享。写了个测试的jsp,打印出session id,单独访问某一个tomcat,发现id在不停的换,我一开始怀疑是terracotta没安好的问题,就又重装了一遍,问题依旧。这时突然发现在IE下session是正常的,其他浏览器下session就不正常,总是不停地换,按说没有关闭浏览器是不会重新生成session的。使用fiddler来检测请求和响应的header,发现一个奇怪的问题,chrome请求一个页面时比使用IE时,多了一个对/favicon.ico的请求,而这个请求得到的响应(404)中会产生新的session id使session发生变化。(什么原因,怎么解释?)在网站的根目录下上传一个favicon.ico即可解决此问题。为了证实是favicon.ico不存在造成的问题,我在页面中请求了一个不存在的js文件,结果session id又开始不停地变。把所有不存在的文件的引用都删掉能够解决此问题,但是在没有使用terracotta前根本不存在此问题,我猜想是不是terraccota在检测到404错误的时候,就会重新生成session的缘故啊。查了一下tomcat的文档,发现context有个属性sessionCookiePath,尝试着把这个值设置为了”/“,结果一切正常了,即使再有404错误,session也不会再变了。这是为什么?在文档中对sessionCookiePath的解释是:

The path to be used for all session cookies created for this context. If set, this overrides any path set by the web application. If not set, the value specified by the web application will be used, or the context path used if the web application does not explicitly set one. To configure all web application to use an empty path (this can be useful for portlet specification implementations) set this attribute to / in the global CATALINA_BASE/conf/context.xml file.

不指定此值的时候,是使用的web app中的或者是直接使用context path。我猜测terracotta是接受了此值,但在处理404资源的时候,这个path无法识别,故以为是新的一个会话,造成session改变的缘故。

不管咋样,暂且算是解决了此问题,原因也是猜测的。有空得看一下terracotta的源代码,看看究竟是什么原因造成的。

其实这个项目在部署运行中,出现过各种问题,因此也做过很多架构的变动。到目前还是不能让人满意,只能在后续的维护中继续改进了。