存档

‘技术相关’ 分类的存档

漫谈Google的Native Client技术(一)–历史动力篇(Web本地计算发展史)

昨天在CB上看到一篇文章《最新Chrome Beta支持在浏览器内直接执行C/C++代码》,而实现这功能的最基本的技术就是Google几年前提出的Native Client技术,其实之前我在一个计算所师兄的博客里已经看过一篇介绍Native Client技术的文章,个人当时就感觉这东西挺有前途的,今天无聊,也了解了下,写篇博客总结下,其中不免有拾人牙慧之处。

Web计算本地化(前端技术)历史-HTML->CSS->Javascript

最初的网络上传输的内容是纯文本的,从网络上传输回来直接通过字符界面展示出来就够了,本地几乎不用计算。

后来,为了更直观,更层次化展现网络内容,人们在文本的基础上加上了什么<h1>之类的标签,于是出现了html,这时候,就出现了浏览器,浏览器是把这些标签解释,并且按照一定格式渲染,这时,就需要一定的本地计算来进行标签分析,字体渲染之类的。

再往后,出现了CSS,并且互联网有了富媒体元素,可以把网页展示的漂亮了,要求本地计算更多了。

但是,这还不够,除了将网页做得漂亮外,还需要减少用户的等待时间,提高用户体验。这时,有人发现,其实很多网络通信是可以避免的,比如,在用户登陆界面,用户输出email格式的用户名,之前是需要将这个信息发给服务器,服务器收到信息后,检查是否合法,不合法的话,返回一个页面,叫用户重新填写,这一来一往很花时间,用户体验非常不好。于是有人就想到,能不能把这些检查用户名之类的计算放到本地,于是,就出现了JavaScript,在用户提交信息时,JS可以在本地检查是否合法,不合法的话,提示原因并要求重新输入,这样用户体验就好了,并且减少了服务器计算以及通信(这貌似符合绿色通信的概念)。

再往后,有人发现,每次点击一个链接,在载入下一个页面的时候,出现空白页让用户很不爽,于是,有人想出了一个办法,那就是事实上,很多页面很大一部分是共同的,这部分其实不需要重新加载,只需要加载改变的部分,而这又可以通过JS实现,于是出现了Ajax技术。这个技术减少了空白等待,提高了用户体验,并且减少了网络通信量(有时绿色通信!)。

Web计算本地化进一步发展-Java-applet,Flash,html5

但是,用户在互联网上的需求还是没有得到满足,有人还想直接在网页上处理图片,玩游戏之类的,由于这类应用一方面计算量大,都放到服务器,服务器不堪重负,另一方面,这类应用都是实时生成画面的,假如通过服务器来计算生成画面(如游戏),再通过网络将每帧图像实时传送回来,对网络要求极高,几乎不可能实现。

于是,需要新的技术将更多的计算放到本地,这时候,出现Java applet,flash等。Java applet允许在网页中嵌入java程序,并在本地执行。Flash应该也是类似,只不过这些计算需要通过flash提供的api完成(这一点有点类似ActiveX通过COM来完成本地计算?)。

Flash的出现,一段时间内,几乎是一统互联网的视频播放以及网页游戏领域。这时,有人眼红也好,有人有更好的主意也好,结果就是有人抱怨flash不开放(还有不安全),有人就想到,我们做一个开放的标准,这标准里面就允许类似游戏等复杂的交互,于是出现了HTML5和CSS3。HTML5以及CSS3将很多JS以及Flash能完成的功能都已到了一个HTML标签里,其实,本质还是差不多,只是这时候这些渲染的计算不是通过flash程序来了,而是直接通过浏览器执行了。

云计算时代的本地计算-Native Client

再往后,随着网络带宽的增长,以及服务器存储能力的提高,出现了大量的数据中心,为了提高这些中心的利用率,出现了云计算。在云计算时代,用户可以把很多数据都放在云端,并且访问这些应用的方法都是统一的。开发者在云服务器开发程序的接口也是统一的(PaSS),这就可以很容易实现跨平台,给用户一个统一的体验。

在这个时代,由于访问云端的资源都是通过浏览器完成的,浏览器就成为一个很重要的平台,这也就是为什么Google会推出自己的浏览器以及ChromeOS的原因。在浏览器能做的事也越来越多,如玩游戏,但是,由于这些游戏是通过一个类似flash这样的东西执行的,效率很低,为了提高效率,并且进一步的将更多的计算密集的东西(如3D渲染)放到本地完成,Google提出Native Client技术,这种技术就是想直接在本地执行C/C++/Java/Python等代码,从而提高本地计算的效率。

从这里,也可以看出Google的野心,Google希望以后用户所有的需求都能在浏览器上完成,包括大型3D游戏,到时候,什么Windows,Linux,都是透明的了,只剩下一个Chrome Broswer,成为开发者的事实平台,得开发者得天下,Google就此一统江山,虽然不能千秋万代,但也能够衣食无忧上十年。

Web计算本地化的问题-安全性

由于web计算本地化是在本地执行服务器上下下来的代码,因此,服务器上要是发给用户的恶意代码,在本地执行的话,会出现严重的后果,因此,这些技术(包括JS,Java applet,Flash)的安全性非常重要。由于Flash应用最多,因此,Flash也被经常爆漏洞。

而对于Native Client技术,如何保证安全则是更大的挑战,为了保证安全,感觉这技术最好用于计算密集型应用,而尽量不让远程代码执行文件读写操作。为了保证文件读写的安全性,个人觉得有两个办法,一个是不读写本地文件,将文件直接写到云端的云存储(但是计算还是本地);另外一个就是在本地沙盒里面读写,不过这只适合临时数据,永久的还是要写到云端。

最后,附一张图,这张图反映了个人计算机应用平台的进化方向:

Redis实现之虚拟存储系统(VM)实现(四)–线程式虚存(Threaded VM)交换

非阻塞虚存的几种实现选择

由于阻塞式虚存实现有各种缺点,因此,Redis实现了非阻塞式的虚存系统。实现非阻塞式虚存系统有以下几种方法:

  1. 最显然的方法,就是把整个redis做成多线程的,每个数据库操作都是自动分配到某个线程(或者fork一个新的线程),这样就不存在阻塞的问题。但是这对redis不是一个好的解决方案,因为为了实现提高操作速度,redis没有锁,保证原子性是通过顺序执行来保证的,因此整个redis引入多线程要增加锁,导致速度变慢(也许多核上能快点),并且极大增加代码量(现在redis只有1万行代码)。
  2. 通过非阻塞式IO实现,由于redis是基于事件循环的(event-loop based),可以采用非阻塞IO。但redis的作者基于以下两点把这个否了:①非阻塞文件操作不像sockets,会导致很多不兼容的问题,这和操作系统紧密相关,要做很多和操作系统相关的工作。②另外一个原因就是,在虚存中,文件IO操作仅仅是一部分,还有很多事件花在数据的编解码上面(编解码成交换文件swap file)。
  3. 于是就剩下最后一种方法,通过增加IO线程来完成数据交换。

下面,具体介绍第三种实现,即IO线程:

IO线程

IO线程的设计目标依重要性排列如下:

  • 实现简单:尽量减少数据冲突(race)的可能性;用简单的锁;VM的代码要能够和Redis的其他代码分离(个人感觉这符合设计正交性原则)。
  • 高性能,对于客户端访问数据不会造成锁(等待)。
  • IO线程要能够编解码交换文件(针对第二种实现的缺点2)。

鉴于以上目标,Redis 的非阻塞VM实现的方式为,实现了一个redis主线程(实际和客户端交互的线程,处理请求),以及一些通过一个简单的信号量(mutex)和任务队列进行通信的IO进程。简单的说,主线程将IO请求交给IO线程进行后台处理(通过将一个(IO)任务结构交给server.io_newjobs队列)。假如没有IO进程,则新建一个。有些IO进程完成IO任务后,结果将发送到server.io_processed队列。IO进程将通过UNIX管道给主进程一个信号,以便告知已经完成,可以继续新的任务。

阅读全文…

Redis实现之虚拟存储系统(VM)实现(三)–阻塞式虚存(Blocking VM)交换

个人理解,所谓阻塞式虚存(Blocking VM),是指redis发生数据交换时,进程被阻塞,不能干其他的事情(后面一篇文章将讲到Threaded VM相反,有独立VM线程)。

在Redis中,启用阻塞式虚存需要在配置文件中将server.vm_max_threads设成0(即不为Threaded VM),和VM另一个相关配置变量是sever.vm_max_memory,它表示Redis中数数据所占用的最大内存(实际上运行过程中可能大于这个值),只有数据量占用内存大于这个值时,才会出现数据交换到disk。

交换到硬盘

数据交换是通过一个计划任务函数(cron function)实现的。这个函数每隔一定的时间会检查是否超出了内存最大值(out of memory)。假如发现超出了最大值,则通过循环调用vmSwapOneObject(这个函数只有一个参数,0表示阻塞式,1表示线程式)来将数据交换到硬盘。

vmSwapOneObject实现如下:

  1. 和Cache替换一样,很关键的一点就是需要找到需要交换出去的Object,使得交换代价最小(后面会讲redis是如何决定的)。
  2. 被交换出去的object关联的值(value)通过阻塞式被交换到硬盘。
  3. key的storage域被设置成REDIS_VM_SWAPPED,vm域也将改写,包括页表索引值,以及所占用的页数。
  4. 释放被swap的object的内存,在哈希表(hash table)中将对应的value的入口设置成NULL。

这个函数一直被调用,直到如下情况:swap空间已满,或者数据对象占用的内存小于sever.vm_max_memory。

阅读全文…

Redis实现之虚拟存储系统(VM)实现(二)–数据交换(Swap)的实现

Redis的虚拟存储系统(VM subsystem)的目的是实现将Redis对象(Redis Objects)方便的在主存(Memory)和硬盘(Disk)之间交换。在Redis虚拟存储系统中,Redis仅仅会将和值(Values)关联的对象交换(swap)到硬盘上。在前一篇日志中,对VM涉及的数据结构进行简要介绍,在这篇日志中,将介绍数据交换的具体过程。

在具体讲Redis数据交换的具体实现之前,有必要讲下swap文件,swap文件是由若干页(page)组成的,每页包含着给定字节数的数据。由于不同的redis实例的最优配置不一样(取决于你实际存储数据的大小),这些参数可以通过redis.conf文件进行修改。下面是默认的大小:

 vm-page-size 32
vm-pages 134217728
 

Redis使用位向量(英文为bitmap,这是一些连续的位,每位的值为0或1)来表示在硬盘中page是否被使用。假如一个给定位是1,则表示该页被使用(有swap文件存在上面),为0,表示该页未被使用。

通过在内存中使用位向量(下面将称为页表位向量),可以在很少的内存消耗情况下取得巨大的性能提升,因为我们仅仅只需要为每页提供一位。对于上面的默认配置,总共有4G的虚存,但是只需要16M的内存给页表位向量。

为了将主存上的数据交换到交换空间,我们需要做如下步骤(假设没有使用虚存线程,仅仅是块实现)。

  1. 找出需要多少页来存储交换文件。这仅仅需要通过调用rdbSavedObjectPages函数即可返回需要的页数。
  2. 知道需要的页数后,我们需要找到交换空间中一些连续的页来存储。这是通过vmFindContiguousPages函数实现的。这个函数有可能因为内存满了而失败,也有可能因为找不到连续的页失败。当这种情况发生时,交换将被取消,数据将继续保存在内存中。
  3. 最终,我们只需要调用vmWriteObjectOnSwap函数即可以将数据交换到对应的位置。数据交换完成后,对应的主存被释放,对应的key也被标记为REDIS_VM_SWAPPED,而对应的页表位向量也被标记为使用中。
    而将交换空间的数据切回到主存中,则很简单,由于知道对象存的位置以及占用页数。只需要调用vmLoadObject即可以完成。

注:本文内容翻译自redis源码包doc目录中的相关文档。

Redis实现之虚拟存储系统(VM)实现(一)–Swap file数据结构

Redis的虚拟存储系统(VM subsystem)的目的是实现将Redis对象(Redis Objects)方便的在主存(Memory)和硬盘(Disk)之间交换。在Redis虚拟存储系统中,Redis仅仅会将和值(Values)关联的对象交换(swap)到硬盘上。

在Redis的顶层的Hash 表中,将一部分的Redis对象(键Key)映射到另一部分Redis对象(值Value)。从而实现可以只将value交换到硬盘,而key对象不交换到硬盘,这保证了Redis非常好的查找性能(一般查找都是通过Key实现)。Redis虚存系统设计目标就是保证有虚存的Redis系统和没有虚存的Redis系统性能相差不大。

当一个对象(包括Key 和 Value)被交换到硬盘,在Hash表中:

  • Key还在内存中,保存着一个代表着Kye的Redis对象。
  • Value被设成了NULL。

到这里,你也许会问,在哪里存储被交换出去的Value信息(这Value和某个Key关联着)。事实上,在Redis中,就在Key对象中。

下面是Redis对象robj的数据结构:

  /* The actual Redis Object */
typedef struct redisObject {
    void *ptr;
    unsigned char type;
    unsigned char encoding;
    unsigned char storage;  /* If this object is a key, where is the value?
                             * REDIS_VM_MEMORY, REDIS_VM_SWAPPED, ... */
    unsigned char vtype; /* If this object is a key, and value is swapped out,
                          * this is the type of the swapped out object. */
    int refcount;
    /* VM fields, this are only allocated if VM is active, otherwise the
     * object allocation function will just allocate
     * sizeof(redisObjct) minus sizeof(redisObjectVM), so using
     * Redis without VM active will not have any overhead. */
    struct redisObjectVM vm;
} robj;
 

正如你所看到的,这个数据结构中有一些域是和虚拟存储相关的。最重要的域是storage,其取值可能有如下几个:

  • REDIS_VM_MEMORY: 对应的Value就在内存中
  • REDIS_VM_SWAPPED: 对应的Value已被交换, 并且hash表中入口刚被设成了NULL.
  • REDIS_VM_LOADING: 对应的Value已被交换, hash表中入口是NULL, 并且对象正从硬盘Load到主存中(这个值仅仅在线程VM/ThreadedVM激活时有效,具体什么事threaded VM,详见我的下一篇博文).
  • REDIS_VM_SWAPPING: 对应的Value在主存中, hash表中入口是一个指向redis对象的指针, 但是系统存在一个把这些Value交换到硬盘的IO任务.

阅读全文…

无觅相关文章插件,快速提升流量