Drupal与大型网站架构(译)- Large-Scale Web Site Infrastructure and Drupal
Linuxjournal 网站经典文章翻译,原文地址: Large-Scale Web Site Infrastructure and Drupal,由于主要按照内容翻译,非逐字翻译,不妥之处,请参考原文阅读。
时至今日,架设一个drupal网站已经是非常容易的了,直到网站规模开始变大, 那么你就需要花费很大的精力在查找和修复网站性能瓶颈上面。本文我们将谈到一些技术可以把你的drupal网站负载能力优化到你曾经希望它应该达到的那种程度。
Setting up a Drupal Web site is pretty simple these days, until it gets popular, then you need to bring out the big guns and start finding and fixing the performance bottlenecks. In this article, we show some of the techniques that can allow your Drupal Web site to scale to the grandiose levels you originally hoped for.
由于Twitter网站经常崩溃,所以Twitter网站的用户经常会经常碰到臭名昭著的“失败鲸”,提示:网站访客过多,请稍后再试。这种情况频繁的出现,因此用户经常能遇到。
大家记得不久以前,Facebook也网站经常down掉,这个也是由于网站的访问量异常大。但是在一些小网站上面,我们也经常碰到网站访问过多造成网站down掉问题。
When Twitter experiences an outage, users see the infamous “fail whale” error message, an illustration of twit-birds struggling to hoist a sleeping cartoon whale into the air along with the words “Too many tweets! Please wait a moment and try again.” It happens so often, Twitter has a much-heralded illustration for it.
Not too long ago, many readers may remember Facebook going down for days at a time. True, those sites are dealing with extraordinary levels of traffic, but smaller sites often face the same problems.
小网站怎么也会有这样的问题呢? 我们来找一下原因。
首先,现在的网站已经几乎没有静态页面的网站了。比如Nowadays,网站整合了社交网络的大部分功能,这就是说单单的一个网页会触发所有相关的服务器/程序运行。
其次,内容更新比较频繁并且类型更多,比如多媒体、广告、视频、手持设备等,而不仅仅只有一些简单的文本终端。这种复杂的载荷关联,是许多社交网站面临的很严重的危机,接下来的是找一些合理的方法来解决这“失败鲸”的问题。
其实在大多数大型高性能网站的解决方案,很多都是相同的,无论用的是什么技术开发什么样的网站。比如 Lullabot(本文的作者所在公司)是一个Drupal开发的公司,也就是我们常常见到的LAMP架构,但是技术都是相似的,很多性能相关的问题都是平台中立的。(下来我们就来谈几点).
How come? First, Web sites are no longer a collection of static pages. Nowadays, Web sites combine social-networking features with highly customized content for individual users, meaning most pages have to be assembled on the fly.
Second, content is changing—rich media, on-line advertising, video, telephony. There’s more than text forcing its way through the pipe, and network traffic only continues to grow. Addressing this tandem of complexity and load is the bane of many growing social-media Web sites’ existence. What follows are some clever ways to address this whale of a problem.
Surprisingly, the solutions to most scaling problems are frequently the same, regardless of the technology upon which the site was built. Lullabot (the parent company of this article’s authors) is a Drupal development company, meaning that most of our experience is centered around the typical LAMP stack (Linux, Apache, MySQL and PHP), although most techniques are universal, and some of the most advanced performance software is platform-neutral.
服务器架构
高性能网站架构的一个重要因素当然首要的是硬件(如下图)。如果有充足的资源,系统管理员一般喜欢用更多的硬件服务器来解决问题。其实很多服务可以放在一起,开发人员可以选择性的优化或者检查一些数据查询。 不过,当带宽或者用户达到某一个数量,我们就需要解决一些和硬件相关当问题。这就是为什么一个合理的硬件计划是非常重要,可以随意的增减或者筛减硬件,在需要的时候。
一个典型的架构,不管是虚拟机器还是独立机器,通常包含多个服务器,多个数据库,有时还会有多个缓存服务器,所有的这些都需要一个负载均衡服务器来分发请求,这个负载均衡器的配置也变的很关键,比如一台Web或者DB的均衡器配置通常比Cache服务器的均衡器要高。
服务器架构图
我们在处理分发处理请求到多个web服务器是非常简洁的做法,但是遇到上传文件处理,就会有问题了。一般我们的负载均衡是基于round-robin算法的,这种情况下,用户在上传操作中提交一个文件到一台web,当用户刷新页面之后,就被分发到另一台web,而这台web上面并没有新上传的文件。为了解决这个问题,我们需要一个文件服务器把几台Web服务器联合一下,通常情况下我们使用NAS(网络附件存储)或NFS(网络文件系统)mount到每一台web服务器上。这样用户上传和处理文件通过NFS就可以共享所有的文件地址了。
Server Infrastructure
One of the main factors in scaling a Web site is, of course, the hardware (Figure 1). System administrators always can throw more hardware at a problem and solve it at least temporarily, if they have the resources to do so. Quite a few services can be put in place before this needs to be done, and developers can selectively optimize the application by reducing or optimizing queries. Nevertheless, when it comes to sheer numbers of users and bandwidth over a short amount of time, there almost always comes a point where it’s necessary to include hardware in the mix. That’s why it is important to have your hardware infrastructure planned in a way that it rapidly can scale upward on a traffic spike, and back down when your traffic recedes.A typical setup, whether virtual or dedicated, usually includes multiple Web servers, multiple database servers and sometimes even separate caching servers, all behind a load balancer that distributes traffic between machines. Depending on its processor speed and the amount of available memory, a Web server or database often can double as the caching server, because caching services usually require less resources than Apache or MySQL.
Although distributing traffic across multiple Web servers, or Web heads, can be a quick win, it can introduce problems with managing file uploads. If requests are being distributed round-robin by the load balancer, a user may upload a file on one server but then be switched to a different Web server after the upload, which doesn’t have the newly uploaded file. To solve this problem, a file server also is added into the mix. The file server is usually some form of NAS (Network Attached Storage) or an NFS (Network File System) mount that allows the application to share files between machines. Each Web head will have a copy of the application stored in the Web root, but when it comes to the files that are uploaded or changed often by the users of the application, an NFS mount connects all the servers to a shared file location.
缓存策略
高性能网站构建中另外一个重要的因素,肯定是软件了。为了能以后可扩展、高并发的需求,缓存是一个重要的因素。缓存机制不是相互排斥的,比较优秀的网站都是联合多个缓存。大多数类型的缓存寻求减少所需的磁盘访问量,或者提供给编译成的字节码,使他们更快的运行更接近机器语言更好。
首先,(opcode缓存是非常重要的)APC以及其他opcode缓存组件的目的是减少web服务器读取、解析和编译PHP文件的次数。APC是免费、开源的opcode缓存,也成了PHP的默认标准。APC在PHP 6版本中是默认组件,但是也还有其他一些不同的可选方案。
现代内容管理系统,比如Drupal,一次页面请求通常需要非常多的数据库调用,由于数据库调用需要硬盘读写,因此这常常会成为网站的瓶颈。
Memcached 是一个缓存服务器,可以把数据库中的一些数据缓存到内存里面,因此可以极大的提高数据的存储能力,它就像一个巨大的哈希数据表一样,提供数据存储服务。
Memcached 是免费、开源并被广泛应用到很多站点的缓存服务器。Memcached经常和MySQL服务器安装在同台服务器上,但由于数据库服务器经常需要很大内存,而Memcached对内存的需求也很大,因此这里就会有内存的瓶颈。在一些情况下,Memcached其实是从数据库服务器上分离出来,运行在独立的服务器上的,这样就避免了和数据库服务器竞争内存的情况。
varnish 是一款优秀的高性能 Web(HTTP) 加速软件,术语 ‘varnish’ 的意思是 ‘逆向代理缓存’, 意味着它能处理网站的用户请求。如果说Apache是一个内科医师,varnish就好比一个分拣护士。当一个匿名用户发起请求,Varnish会把请求的页面做一份拷贝保存在一个高速缓存中以便下次请求可以重复使用,这样就避免了启动Apache、PHP、MySQL、Memcached以及其他任何相关的服务。如果Varnish不能处理这个请求,那么它就需要发送请求到Apache。但不管怎么说,Varnish在静态页面缓存方面,做得很不错。
Cache Techniques
The other main factor in scaling a web site is, of course, the software (Figure 2). To scale effectively, high-traffic Web sites require some flavor or flavors of caching. Caching mechanisms are not mutually exclusive, and most high-profile sites combine several. Most types of caching seek to reduce the amount of disk access necessary to render a page or compile higher-level languages into bytecode so they’re faster to run—the closer to machine language the better.APC (Alternative PHP Cache) and other opcode caches save the Web server from having to read, parse and compile PHP files on every request. APC is a free, open-source opcode cache and is pretty much the standard. It will come built in with PHP 6, but there are many different ones that perform differently.
Modern content management systems, like Drupal, can make a plethora of database calls on every page request. Because calls to the database hit the disk, it is often a bottleneck. Memcached is a service that allows entire database tables to be stored in memory, dramatically speeding up queries to those tables and alleviating strain on the database. It behaves as though it were a giant hash table and serves this data out of memory.
Memcached is free, open source and in use by a ton of high-traffic sites. Memcached is installed alongside MySQL on the database server in most typical setups. However, the database server needs to have a lot of RAM available if Memcached and MySQL are sharing this critical resource. There are occasions when Memcached is actually placed on its own server, completely decoupled from the database server, which precludes Memcached from using too much of the database server’s memory.
Varnish is an excellent high-performance, HTTP accelerator. The technical term for Varnish is a “reverse proxy cache”, meaning that it handles the requests when you visit a Web site. If Apache were a physician, Varnish would be the triage nurse. After each anonymous page request is made, Varnish makes a copy of the page in an ultra-fast storage so that the next time the page is requested, it returns it immediately, circumventing a bootstrap of Apache, PHP, MySQL, Memcached or any other technologies your Web site may require to serve pages. If Varnish doesn’t have a copy of the file or page being requested, it will send the request on to Apache. And, it’s really a huge win if you’re going to be serving static content.
独立的搜索服务
搜索通常是一个很消耗资源的功能。因此搜索的优化将会对网站的性能带来极大的提升,最好把搜索部分独立处理。Solr是一个开源的易用的搜索服务器,它来自Apache基金会,是Lucene项目的继任者,集成了Lucene的有点,快速的建立索引、搜索,而且它的API是基于HTTP的,如POST、GET操作,并提供较全的文档来供你使用查阅。
在Drupal中,Views模块很像一个可视化的SQL构建和搜索处理工具。在Views 3中,可以把Views的数据源指向到solr服务器上,这样就可以大大的减少数据库的压力和提高查询速度。
(参阅:DRUPAL北京聚会主题之 – APACHE SOLR)
Outsource Search
Search is resource-intensive. Optimizing search will contribute to overall site performance and is a great process to outsource to another box. Solr can help ease an over-burdened Web server. Solr is a project from the Apache Foundation that takes the power of Lucene, a fantastic indexer and searcher, and exposes it as a Web service. Using HTTP POST and GET requests, you can feed documents to Solr for indexing and issue queries for searching. In Drupal, the Views module serves as a visual query-builder and handles search. With Views 3, in Drupal, you can plug in Solr to handle the search heavy-lifting instead of having Drupal hit MySQL for this, alleviating a load on your database server best left to a document indexer like Lucene.
优化Apache
在Apache服务器设置中,MaxClients是一个限制并发请求的参数,如果达到了设置的限制,后来的用户就需要等待有请求被处理完成才能继续。但是,如果我们把这个数字设置的很大,这就会有服务器内存用尽的风险。这里有一个标准的公式来计算服务器内存和MaxClients之间的关系:
* 公式: 内存/平均单次请求所需内存 = #Max Clients (最大请求数)
* 举例: 2G(服务器) / 20M (每次) = 100 MaxClients
Apache模块 mod_expires 的设置控制着 HTTP 头的相关信息。如果一个文件被缓存在了用户的电脑上,这个设置可以告诉后续的请求直接用本地的缓存而不要向服务器发送请求,这个设置在Apache上面也很简单,请参考下面的设置。
4 |
ExpiresActive On |
KeepAlive 参数用来告诉Apache是否保持一个HTTP连接,在一定时间内重用该HTTP连接。对于一个包含多个图片的网页,这个设置一般可以减少50%的延迟时间。详细设置见下面的参数指令:
KeepAlive On
KeepAliveTimeout 2
Tune Apache
Apache’s MaxClients setting is a limit on the number of simultaneous requests that can be served. If this limit is reached, users have to wait until a child process is freed up until they can connect. If this number is increased too much, however, there is a risk that the Web head will run out of memory. There’s a standard formula for figuring out what this setting should be based upon the RAM available to the machine:
- formula: RAM/Average Apache Memory Size in Use = # max clients
- example: 2GB/20MB = 100 MaxClientsApache’s mod_expires setting controls the HTTP header information for anything served through Apache to your machine. If a resource has been cached on a user’s computer, this setting can tell any subsequent request to that resource if it has expired and needs to be downloaded again. It’s a good idea to have this turned on for text/HTML header types:
1
2
3 ExpiresActive On
ExpiresDefault A1209600
ExpiresByType text/html A1The KeepAlive setting is a way to tell Apache to keep an HTTP connection alive for a period of time so that it can be reused. This has been shown to result in an almost 50% speed increase in latency times for HTML documents with many images. Turn this on and set the KeepAliveTimeout to 2 seconds:
KeepAlive On
KeepAliveTimeout 2
优化MySQL
MySQL是应用最广泛的数据库之一,也是Drupal上使用最多的数据库,当然Drupal 6也支持Postgres。Drupal 7 有一个面向对象的数据抽象层,以便支持更多的底层数据库。在MySQL的优化中,有几点和性能相关的设置必须心里清楚。
MySQL有一个内建的查询缓存默认是被开启的,因此必须确保更多的查询使用这个缓存,我们需要增加这个设置。比如:
[mysqld] query_cache_size=32M
当网站上线了,经常统计慢查询是一件非常好的习惯,以便我们使用EXPLAIN等相关工具来优化相应的SQL。
log-slow-queries = /var/log/slow_query.log
long_query_time = 5
#log-queries-not-using-indexes
MySQL的EXPLAIN命令是一个很好的工具,它能帮助我们查找query在执行查询过程中的一些细节。我们需要查看的一个重要点就是,扫描的行数,它会告诉你什么时候需要,在合适的字段中建立索引。
我们看一下下面这段SQL查询,我们可以通过给其中有3个字段加上索引来减少查询时间。
具体的操作,使用ALTER TABLE指令来操作。
1 |
... |
‘status’, ‘type’ 和 ‘created’ 可以加上索引,如下:
mysql> ALTER TABLE node ADD INDEX (status, type, created);
锁表是数据库级别的另一个令人头疼的性能问题。默认情况下,Drupal的MySQL表使用的是MyISAM引擎。由于MyISAM在执行一个数据库操作时会锁住整个表,因此访问量大的网站经常出现数据库down掉的情况。如果你希望解决这个问题并试图寻求解决方案,InnoDB是一个比较好的解决办法。InnoDB是行级别的锁定而非表级别。在迁移到InnoDB的时候,注意数据库表的字段中是否有 auto_increment 的字段,因为这会导致在插入(INSERT)数据的时候性能会非常低,因为这是InnoDB也会锁住整个表以免出现重复的主键。
Optimize MySQL
MySQL is the most widely used database for Drupal, although Drupal 6 also supports Postgres. Drupal 7 has an object-oriented database abstraction layer that allows drivers to be written for many other database systems. There are some key things to keep in mind within MySQL’s configuration that can help optimize your application for performance.
MySQL has a built-in query cache that is turned on by default. Make sure to afford a liberal amount of memory to this cache:
[mysqld] query_cache_size=32MOnce your application is built, it’s a good idea to log slow queries for a short amount of time to get a list of queries that are taking a long time and can be examined with an EXPLAIN and then optimized:
log-slow-queries = /var/log/slow_query.log
long_query_time = 5
#log-queries-not-using-indexesMySQL’s EXPLAIN command is a great way to find out exactly what a particular query is doing in order to get some clues as to why it may be taking a long time to evaluate and return a result. One of the key things to look at is the number of rows that EXPLAIN tells you it had to search through. This may indicate that one of your tables, bursting at the seams, is a good candidate for a new index.
Taking a look at the following query, we see there are three fields that could have an index placed upon them in order to reduce the number of rows that a query has to search through in order to find the desired result:
…
FROM node node
WHERE node.status = 1
AND node.type IN (‘story’)
ORDER BY node.created DESCThe status, type and created fields are key to this query’s result and can be indexed so that they are seen as a group:
mysql> ALTER TABLE node ADD INDEX (status, type, created);Table locking can be a performance headache. By default, Drupal’s MySQL database tables are all set to MyISAM. Because MyISAM locks the entire table down during a query, high traffic may cause MySQL errors when a certain table is unavailable or locked. If you start seeing these errors, look at which tables are giving the error and evaluate whether they should be set to InnoDB instead. InnoDB does row locking instead of table locking. When evaluating, look to see if the table has any auto_increment fields, and keep in mind that converting this table may cause slow-downs on INSERTs, as InnoDB does a full table lock on INSERTs to avoid key duplication.
PHP
使用静态变量缓存是一个快速简单的缓存方式。这里有一个简单的示例。
1 |
function taxonomy_get_term($tid) { |
当页面加载的时候,这个函数可能被调用好多次,如果加上一个简单的静态变量缓存,那么就可以避免多次查询数据库,从而提高网站的性能。如下:
1 |
function taxonomy_get_term($tid) { |
PHP
Static variable caching is a quick-and-easy win in PHP. Here is an example of a simple function with a simple query to the database:
1
2
3
4 function taxonomy_get_term($tid) {
return db_fetch_object(
db_query('SELECT * FROM {term_data} WHERE tid = %d', $tid));
}This function can be given a simple static variable, so that if this function happens to be called more than once on a page load, it can skip over the call to the database and serve the result out of this static cache:
1
2
3
4
5
6
7
8
9 function taxonomy_get_term($tid) {
static $terms = array();
if (!isset($terms[$tid])) {
$terms[$tid] = db_fetch_object(
db_query('SELECT * from {term_data} WHERE tid = %d', $tid)
);
}
return $terms[$tid];
}
应用层-Drupal
Drupal是一个内容管理框架,Lullabot用它来作为大型站点的基础框架,Drupal是用PHP开发的,并且有相当多的第三方模块可以免费使用和扩展。它被比作是LEGOs就是因为它有很多的模块可以使用,你可以做很多代码检查,然后决定到底用那个模块到你的平台里面。如果一个模块能满足你的需求,你应该检查它是否使用了静态变量缓存、SQL是否被优化,还有代码是否符合标准。
通常的Drupal会有一些第三方的patch,这是因为模块在某些方面存在bug或者功能行缺陷,这些问题被报告在模块的问题列表里面(issue),你可以在该issue的页面找到这个patch。性能优化的基本要点是优化SQL查询,并且不要让数据库查询的同一个SQL语句在一次页面加载中运行多次。Devel模块是一个很好的调试工具,它可以帮你统计页面加载时间、内存使用以及页面的加载时间。
抛开我们的LAMP架构,缓存技术、硬件架构都是一些通用的网站开发最佳实践,但是在Drupal开发中,我们不仅需要减少在不同服务器的负载等待,并且还需要能很容易的管理数据以及代码的版本变化,(译者注:在Drupal中,很多设置是存在于数据库中的)。首先,最重要的是需要一个“可导出”的功能,这样数据就会变成可读的代码而不是数据库,并且在部署到不同的服务器后可以重用,而不需要重新设置。
“可导出”的概念来自于 Views模块,Eari Miles,他希望能找到一个方法来帮助开发人员调试他们遇到的问题,所以他开发了一个可导出的功能能让开发人员导出他们的Views到code文件,然后导入到本地的环境调试。这不仅仅给Views的开发者带来一个优秀的特性,而是让整个Drupal的思路变成了把数据库的设置可以变成结构化的文件,从而提高了性能。“可导出”的概念后来被Ctools和Panels模块的开发者借鉴并增强,其他的开发者也开始在各自的模块中实现这种”可导出“功能,目前已有相当多的模块已经实现了Ctools的 “可导出”功能。
最终,我们有了Features模块,它提供了一个界面可以选择和管理各种各样的“可导出”功能模块,它可以把Drupal的很多功能导出成一个自定义的”feature“模块,并且这个模块是可以被共享和重用的,当然我们不仅仅是为了共享和重用这些模块,更重要的是“Feature”已经变成了Drupal新建和部署流程中一个重要的工作流。
Application (Drupal)
Drupal is a content management framework that Lullabot uses to build high-performance Web sites on top of this infrastructure. Drupal is built with PHP as its primary programming language and has a ton of user-contributed modules freely available to extend its functionality. It has been compared to LEGOs because of this, and because the quality of modules vary, it’s a good idea to do full code reviews of any modules that are selected for inclusion into any platform build. If an existing module already does mostly what is needed, it should be reviewed to make sure static variable caching is utilized, queries are optimized and general coding standards are being used.Regularly contribute patches back to modules when a module is found lacking in any of these areas or if any general bugs are found through the module’s issue queue, which can be found on the same page where you download the module. Performance reviews also are a good idea once a site is built to ensure that queries are optimized and not run more than once per page load. The Devel module is a great resource for this, as it will give you stats on page load times, memory usage and can display every query executed on any given page load.
Beyond the regular LAMP configuration optimizations, caching techniques, and hardware infrastructure are some general Web development best practices available within Drupal that not only can reduce loads on various servers, but also make it easy to have some of your data structures in code that can be version-controlled to keep track of changes and to help with the deployment process of said changes. The first, and relatively new, paradigm of “exportables” is twofold, in that it gives you a way to read a data structure from code instead of the database, and it also can be deployed to different environments and reused.
Exportables started with the Views module by Earl (merlinofchaos) Miles who wanted a way to help debug the problems that his module users might encounter. So, he created a way for users to export the view they created into a readable data structure that he then could put on his own machine to help him debug. This not only had the awesome side effect of being able to share these “view recipes” with other users, but it also evolved into a method where the structure could replace what was read from the database and help increase the performance. Exportables then was extrapolated into a library dubbed Ctools (for Chaos Tools) and used for the Panels module. Other people started catching on and implementing exportables for their modules, and now there are a whole slew of modules that use the Ctools Exportables for this purpose.
This eventually led to a module called Features that provides a UI to choose the various exportable data structures within a Drupal installation and wrap them up into a custom “feature” module, which then can be shared. These features can be simple configuration options or complex features requiring many other contributed modules in order to provide feature-rich enhancements for any Drupal Web site. Not only can it be used to share such features, but it also has become an important part of the deployment process in creating modern Drupal Web sites.
还有一个工具也已经渐渐成熟,并且成为任何一个Drupal专家的必备工具,那就是Drush。 Drush的意思是Drupal Shell, 是一种通过命令行控制Drupal站点的一个途径。他不仅提供了强大的命令快速操控你的站点,其他第三方模块也提供了对Drush的集成,创建他们自己模块相关的命令。
比如:Features模块提供了一些Drush命令,比如 list、update、revert 来操作features。此外 Backup and Migrate 模块提供了一个简单的创建SQL备份的命令。还有其他的一些模块也提供了相关的命令来操作Drupal和Git。 当然Drush不是唯一可以让你快速管理Drupal的方法,但是它避免了你通过网页经Apache服务来管理Drupal的繁琐步骤。
可以肯定的是,没有任何一个专业网站可以脱离版本控制,Lullabot过去用CVS, SVN,最近开始向Git过渡,但是不管你用什么,有一个你工作的备份,并且为工作在相同项目的团队保留版本是非常重要的。版本控制的优点很多,一个高性能的网站通常需要很多人在一起工作,所以版本控制是必须要有的。
Another tool that has recently matured and become a necessity to any professional Drupalite is Drush. Drush stands for Drupal Shell and is a way to control your Drupal Web sites through the command line. Not only does it provide powerful commands to manipulate your Web site quickly, but other modules can provide integration with Drush as well, creating their own commands related to working with their particular module. For example: the Features module provides commands to Drush that allow you to list, update and revert any feature modules quickly that are part of a Drupal installation’s codebase. The Backup and Migrate module provides integration to allow you to create SQL backups of your Web site quickly with a simple command. Some modules even provide commands to work with Drupal and Git! So, not only does Drush allow you to work with your Drupal site quickly, but you also don’t have to load a huge page through Apache to do so.
And, of course, no professional Web site would be complete without revision control. Lullabot has used CVS (Concurrent Versions System), SVN (Subversion) and, most recently, made the move to Git. But no matter what you use, it’s important to have a backup of your work and versioning for teams working on the same project. The merits of versioning your code are many. Working on a high-performance Web site usually takes many people, so version control becomes a necessity.
Jerad Bitner has been using Drupal since the nightmare upgrades from 4.6 to 4.7 (that’s early 2005, if you’re asking). He started out as a Technical Illustrator with C/S Group and worked for three years with Photoshop, Illustrator, AutoCad and Macromedia products as well as PHP. When it came time to replicate a platform across the different locations of the company, Jerad found Drupal and hasn’t looked back since.
Nate Haug adds a dash of design to Lullabot. He received degrees in both a Fine Arts and Computer Science from Truman State University, creating the perfect bridge between the technical and aesthetic. Detail is his obsession, so if you know what you want, Nate will deliver your desire.
原文地址:http://www.linuxjournal.com/article/10842
原文作者:Jerad Bitner 和 Nate Haug
翻译作者:Robbin Zhao 赵高欣 和 Richard Yu 于志成