惊群问题的思考

什么是惊群

        举一个很简单的例子,当你往一群鸽子中间扔一块食物,虽然最终只有一个鸽子抢到食物,但所有鸽子都会被惊动来争夺,没有抢到食物的鸽子只好回去继续睡觉,等待下一块食物到来。这样,每扔一块食物,都会惊动所有的鸽子,即为惊群。对于操作系统来说,多个进程/线程在等待同一资源是,也会产生类似的效果,其结果就是每当资源可用,所有的进程/线程都来竞争资源,造成的后果:
1)系统对用户进程/线程频繁的做无效的调度、上下文切换,系统系能大打折扣。
2)为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销。

        最常见的例子就是对于socket描述符的accept操作,当多个用户进程/线程监听在同一个端口上时,由于实际只可能accept一次,因此就会产生惊群现象,当然前面已经说过了,这个问题是一个古老的问题,新的操作系统内核已经解决了这一问题。

linux内核解决惊群问题的方法

        对于一些已知的惊群问题,内核开发者增加了一个"互斥等待"选项。一个互斥等待的行为与睡眠基本类似,主要的不同点在于:
        1)当一个等待队列入口有 WQ_FLAG_EXCLUSEVE 标志置位, 它被添加到等待队列的尾部. 没有这个标志的入口项, 相反, 添加到开始.
        2)当 wake_up 被在一个等待队列上调用时, 它在唤醒第一个有 WQ_FLAG_EXCLUSIVE 标志的进程后停止。
        也就是说,对于互斥等待的行为,比如如对一个listen后的socket描述符,多线程阻塞accept时,系统内核只会唤醒所有正在等待此时间的队列的第一个,队列中的其他人则继续等待下一次事件的发生,这样就避免的多个线程同时监听同一个socket描述符时的惊群问题。

根据以上背景信息,我们来比较一下常见的Server端设计方案。
方案1:listen后,启动多个线程(进程),对此socket进行监听(仅阻塞accept方式不惊群)。
方案2:主线程负责监听,通过线程池方式处理连接。(通常的方法)
方案3:主线程负责监听,客户端连接上来后由主线程分配实际的端口,客户端根据此端口重新连接,然后处理数据。

先考虑客户端单连接的情况
方案1:每当有新的连接到来时,系统内核会从队列中以FIFO的方式选择一个监听线程来服务此连接,因此可以充分发挥系统的系能并且多线程负载均衡。对于单连接的场景,这种方案无疑是非常优越的。遗憾的是,对于select、epoll,内核目前无法解决惊群问题。(nginx对于惊群问题的解决方法)
方案2:由于只有一个线程在监听,其瞬时的并发处理连接请求的能力必然不如多线程。同时,需要对线程池做调度管理,必然涉及资源共享访问,相对于方案一来说管理成本要增加不少,代码复杂度提高,性能也有所下降。
方案3:与方案2有不少类似的地方,其优势是不需要做线程调度。缺点是增加了主线程的负担,除了接收连接外还需要发送数据,而且需要两次连接,孰优孰劣,有待测试。

再考虑客户端多连接的情况:
对于数据传输类的应用,为了充分利用带宽,往往会开启多个连接来传输数据,连接之间的数据有相互依赖性,因此Server端要想很好的维护这种依赖性,把同一个客户端的所有连接放在一个线程中处理是非常有必要的。
A、同一客户端在一个线程中处理
方案1:如果没有更底层的解决方案的话,Server则需要维护一个全局列表,来记录当前连接请求该由哪个线程处理。多线程需要同时竞争一个全局资源,似乎有些不妙。
方案2:主线程负责监听并分发,因此与单连接相比没有带来额外的性能开销。仅仅会造成主线程忙于更多的连接请求。
方案3:较单线程来说,主线程工作量没有任何增加,由于多连接而造成的额外开销由实际工作线程分担,因此对于这种场景,方案3似乎是最佳选择。

B、同一客户端在不同线程中处理
方案1:同样需要竞争资源。
方案2:没理由。
方案3:不可能。

另外:
(《UNIX网络编程》第三版是在第30章)
读《UNIX网络编程》第二版的第一卷时,发现作者在第27章"客户-服务器程序其它设计方法"中的27.6节"TCP预先派生子进程服务器程序,accept无上锁保护"中提到了一种由子进程去竞争客户端连接的设计方法,用伪码描述如下:

服务器主进程:

listen_fd = socket(...);
bind(listen_fd, ...);
listen(listen_fd, ...);
pre_fork_children(...);
close(listen_fd);
wait_children_die(...);


服务器服务子进程:

while (1) {
conn_fd = accept(listen_fd, ...);
do_service(conn_fd, ...);
}


初 识上述代码,真有眼前一亮的感觉,也正如作者所说,以上代码确实很少见(反正我读此书之前是确实没见过)。作者真是构思精巧,巧妙地绕过了常见的预先创建 子进程的多进程服务器当主服务进程接收到新的连接必须想办法将这个连接传递给服务子进程的"陷阱",上述代码通过共享的倾听套接字,由子进程主动地去向内 核"索要"连接套接字,从而避免了用UNIX域套接字传递文件描述符的"淫技"。

不过,当接着往下读的时候,作者谈到了"惊群" (Thundering herd)问题。所谓的"惊群"就是,当很多进程都阻塞在accept系统调用的时候,即使只有一个新的连接达到,内核也会唤醒所有阻塞在accept上 的进程,这将给系统带来非常大的"震颤",降低系统性能。

除了这个问题,accept还必须是原子操作。为此,作者在接下来的27.7节讲述了加了互斥锁的版本:

while (1) {
lock(...);
conn_fd = accept(listen_fd, ...);
unlock(...);
do_service(conn_fd, ...);
}


原 子操作的问题算是解决了,那么"惊群"呢?文中只是提到在Solaris系统上当子进程数由75变成90后,CPU时间显著增加,并且作者认为这是因为进 程过多,导致内存互换。对"惊群"问题回答地十分含糊。通过比较书中图27.2的第4列和第7列的内容,我们可以肯定"真凶"绝对不是"内存对换"。

"元凶"到底是谁?

仔 细分析一下,加锁真的有助于"惊群"问题么?不错,确实在同一时间只有一个子进程在调用accept,其它子进程都阻塞在了lock语句,但是,当 accept返回并unlock之后呢?unlock肯定是要唤醒阻塞在这个锁上的进程的,不过谁都没有规定是唤醒一个还是唤醒多个。所以,潜在的"惊 群"问题还是存在,只不过换了个地方,换了个形式。而造成Solaris性能骤降的"罪魁祸首"很有可能就是"惊群"问题。

崩溃了!这么说所有的锁都有可能产生惊群问题了?

似乎真的是这样,所以减少锁的使用很重要。特别是在竞争比较激烈的地方。

作者在27.9节所实现的"传递文件描述符"版本的服务器就有效地克服了"惊群"问题,在现实的服务器实现中,最常用的也是此节所提到的基于"分配"形式。

把"竞争"换成"分配"是避免"惊群"问题的有效方法,但是也不要忽视"分配"的"均衡"问题,不然后果可能更加严重哦!

using AF_UNIX address family

using AF_UNIX address family

Sockets that use the AF_UNIX or AF_UNIX_CCSID address family can be connection-oriented (type SOCK_STREAM) or connectionless (type SOCK_DGRAM).

Both types are reliable because there are no external communication functions connecting the two processes.

UNIX® domain datagram sockets act differently from UDP datagram sockets. With UDP datagram sockets, the client program does not need to call the bind() API because the system assigns an unused port number automatically. The server can then send a datagram back to that port number. However, with UNIX domain datagram sockets, the system does not automatically assign a path name for the client. Thus, all client programs using UNIX domain datagrams must call the bind() API. The exact path name specified on the client's bind() is what is passed to the server. Thus, if the client specifies a relative path name (that is, a path name that is not fully qualified by starting with /), the server cannot send the client a datagram unless it is running with the same current directory.

An example path name that an application might use for this address family is /tmp/myserver or servers/thatserver. With servers/thatserver, you have a path name that is not fully qualified (no / was specified). This means that the location of the entry in the file system hierarchy should be determined relative to the current working directory.
Note: Path names in the file system are NLS-enabled.

The following figure illustrates the client/server relationship of the AF_UNIX address family.

Socket flow of events used in server and client AF_UNIX address family example programs.

Socket flow of events: Server application that uses AF_UNIX address family

The first example uses the following sequence of API calls:

  1. The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies the UNIX address family with the stream transport (SOCK_STREAM) being used for this socket. You can also use the socketpair() API to initialize a UNIX socket.

    AF_UNIX or AF_UNIX_CCSID are the only address families to support the socketpair() API. The socketpair() API returns two socket descriptors that are unnamed and connected.

  2. After the socket descriptor is created, the bind() API gets a unique name for the socket.

    The name space for UNIX domain sockets consists of path names. When a sockets program calls the bind() API, an entry is created in the file system directory. If the path name already exists, the bind() fails. Thus, a UNIX domain socket program should always call an unlink() API to remove the directory entry when it ends.

  3. The listen() allows the server to accept incoming client connections. In this example, the backlog is set to 10. This means that the system queues 10 incoming connection requests before the system starts rejecting the incoming requests.
  4. The recv() API receives data from the client application. In this example, the client sends 250 bytes of data over. Thus, the SO_RCVLOWAT socket option can be used, which specifies that recv() is not required to wake up until all 250 bytes of data have arrived.
  5. The send() API echoes the data back to the client.
  6. The close() API closes any open socket descriptors.
  7. The unlink() API removes the UNIX path name from the file system.

Socket flow of events: Client application that uses AF_UNIX address family

The second example uses the following sequence of API calls:

  1. The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies the UNIX address family with the stream transport (SOCK_STREAM) being used for this socket. You can also use the socketpair() API to initialize a UNIX socket.

    AF_UNIX or AF_UNIX_CCSID are the only address families to support the socketpair() API. The socketpair() API returns two socket descriptors that are unnamed and connected.

  2. After the socket descriptor is received, the connect() API is used to establish a connection to the server.
  3. The send() API sends 250 bytes of data that are specified in the server application with the SO_RCVLOWAT socket option.
  4. The recv() API loops until all 250 bytes of the data have arrived.
  5. The close() API closes any open socket descriptors.

creating a connectionless socket

creating a connectionless socket

Connectionless sockets do not establish a connection over which data is transferred. Instead, the server application specifies its name where a client can send requests.

Connectionless sockets use User Datagram Protocol (UDP) instead of TCP/IP.

The following figure illustrates the client/server relationship of the socket APIs used in the examples for a connectionless socket design.

The client/server relationship of the socket APIs for a connectionless protocol

Socket flow of events: Connectionless server

The following sequence of the socket calls provides a description of the figure and the following example programs. It also describes the relationship between the server and client application in a connectionless design. Each set of flows contains links to usage notes on specific APIs. If you need more details on the use of a particular API, you can use these links. The first example of a connectionless server uses the following sequence of API calls:

  1. The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the Internet Protocol address family (AF_INET) with the UDP transport (SOCK_DGRAM) is used for this socket.
  2. After the socket descriptor is created, a bind() API gets a unique name for the socket. In this example, the user sets the s_addr to zero, which means that the UDP port of 3555 is bound to all IPv4 addresses on the system.
  3. The server uses the recvfrom() API to receive that data. The recvfrom() API waits indefinitely for data to arrive.
  4. The sendto() API echoes the data back to the client.
  5. The close() API ends any open socket descriptors.

Socket flow of events: Connectionless client

The second example of a connectionless client uses the following sequence of API calls.

  1. The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the Internet Protocol address family (AF_INET) with the UDP transport (SOCK_DGRAM) is used for this socket.
  2. In the client example program, if the server string that was passed into the inet_addr() API was not a dotted decimal IP address, then it is assumed to be the host name of the server. In that case, use the gethostbyname() API to retrieve the IP address of the server.
  3. Use the sendto() API to send the data to the server.
  4. Use the recvfrom() API to receive the data from the server.
  5. The close() API ends any open socket descriptors.

creating a connection-oriented socket

creating a connection-oriented socket

These server and client examples illustrate the socket APIs written for a connection-oriented protocol such as Transmission Control Protocol (TCP).

The following figure illustrates the client/server relationship of the sockets API for a connection-oriented protocol.

The client/server relationship of the sockets API for a connection-oriented design

Socket flow of events: Connection-oriented server

The following sequence of the socket calls provides a description of the figure. It also describes the relationship between the server and client application in a connection-oriented design. Each set of flows contains links to usage notes on specific APIs.

  1. The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the Internet Protocol address family (AF_INET) with the TCP transport (SOCK_STREAM) is used for this socket.
  2. The setsockopt() API allows the local address to be reused when the server is restarted before the required wait time expires.
  3. After the socket descriptor is created, the bind() API gets a unique name for the socket. In this example, the user sets the s_addr to zero, which allows connections to be established from any IPv4 client that specifies port 3005.
  4. The listen() API allows the server to accept incoming client connections. In this example, the backlog is set to 10. This means that the system queues 10 incoming connection requests before the system starts rejecting the incoming requests.
  5. The server uses the accept() API to accept an incoming connection request. The accept() call blocks indefinitely, waiting for the incoming connection to arrive.
  6. The select() API allows the process to wait for an event to occur and to wake up the process when the event occurs. In this example, the system notifies the process only when data is available to be read. A 30-second timeout is used on this select()call.
  7. The recv() API receives data from the client application. In this example, the client sends 250 bytes of data. Thus, the SO_RCVLOWAT socket option can be used, which specifies that recv() does not wake up until all 250 bytes of data have arrived.
  8. The send() API echoes the data back to the client.
  9. The close() API closes any open socket descriptors.

Socket flow of events: Connection-oriented client

The following sequence of APIs calls describes the relationship between the server and client application in a connection-oriented design.

  1. The socket() API returns a socket descriptor, which represents an endpoint. The statement also identifies that the Internet Protocol address family (AF_INET) with the TCP transport (SOCK_STREAM) is used for this socket.
  2. In the client example program, if the server string that was passed into the inet_addr() API was not a dotted decimal IP address, then it is assumed to be the host name of the server. In that case, use the gethostbyname() API to retrieve the IP address of the server.
  3. After the socket descriptor is received, the connect() API is used to establish a connection to the server.
  4. The send() API sends 250 bytes of data to the server.
  5. The recv() API waits for the server to echo the 250 bytes of data back. In this example, the server responds with the same 250 bytes that was just sent. In the client example, the 250 bytes of the data might arrive in separate packets, so the recv()API can be used over and over until all 250 bytes have arrived.
  6. The close() API closes any open socket descriptors.

viplugin2的破解

新版的加密程序在com.mbartl.viplugin.eclipse.layer_2.0.0/lib/licensechecker.jar中,你只需要将lib下的文件放在classhpath上,然后调用CheckLicenseFile.encrypt(name, email)方法就能得到一个注册码,将这个注册码保存到一个叫viPlugin2.lic的文件中放在workspace或者eclipse根目录下就可以了。

如果嫌麻烦的话,这里提供了一个可用的注册码如下:

q1MHdGlxh7nCyn_FpHaVa8ucV6QZwObn-_yM-8HcMu4

细数那些令人发狂的程序语言的特性

细数那些令人发狂的程序语言的特性:

这些最为奇怪的程序语言的特性,来自stackoverflow.com,原贴在这里。我摘选了一些例子,的确是比较怪异,让我们一个一个来看看。


1、C语言中的数组


在C/C++中,a[10] 可以写成 10[a]


“Hello World”[i] 也可以写成 i["Hello World"]


2、在Javascript中


’5′ + 3 的结果是:’53′


’5′ – 3 的结果是:2


3、C/C++中的Trigraphs


int main() {

cout << "LOL??!";

}

上面的这段程序会输出: “LOL|”,这是因为 ??! 被转成了 | ,关于Trigraphs,下面有个表格:









































??= #
??( [
??/ \
??) ]
??’ ^
??< {
??! |
??> }
??- ~

4、JavaScript 的条件表


看到下面这个表,不难理解为什么Javascript程序员为什么痛苦了。


''        ==   '0'          //false
0 == '' //true
0 == '0' //true
false == 'false' //false
false == '0' //true
false == undefined //false
false == null //false
null == undefined //true
" \t\r\n" == 0 //true

5、Java的Integer cache


Integer foo = 1000;
Integer bar = 1000;

foo <= bar; // true
foo >= bar; // true
foo == bar; // false

//然后,如果你的 foo 和 bar 的值在 127 和 -128 之间(包括)
//那么,其行为则改变了:

Integer foo = 42;
Integer bar = 42;

foo <= bar; // true
foo >= bar; // true
foo == bar; // true

为什么会这样呢?你需要了解一下Java Interger Cache,下面是相关的程序,注意其中的注释。


/**
* Returns a <tt>Integer</tt> instance representing the specified
* <tt>int</tt> value.

* If a new <tt>Integer</tt> instance is not required, this method

* should generally be used in preference to the constructor
* <a href="mailto:{@link">{@link</a> #Integer(int)}, as this method is likely to yield
* significantly better space and time performance by caching
* frequently requested values.
*
* @param i an <code>int</code> value.
* @return a <tt>Integer</tt> instance representing <tt>i</tt>.
* @since 1.5
*/
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}

其所有的这些怪异的变量请参看:http://www.kichwa.com/quik_ref/spec_variables.html


6、Java的异常返回


请看下面这段程序,你觉得其返回true还是false?


try {
return true;
} finally {
return false;
}

在 javascript 和python下,其行为和Java的是一样的。


7、C语言中的Duff device


下面的这段程序你能看得懂吗?这就是所谓的Duff Device,相当的怪异。


void duff_memcpy( char* to, char* from, size_t count ) {
size_t n = (count+7)/8;
switch( count%8 ) {
case 0: do{ *to++ = *from++;
case 7: *to++ = *from++;
case 6: *to++ = *from++;
case 5: *to++ = *from++;
case 4: *to++ = *from++;
case 3: *to++ = *from++;
case 2: *to++ = *from++;
case 1: *to++ = *from++;
}while(--n>0);
}
}

8、PHP中的字符串当函数用


PHP中的某些用法也是很怪异的


$x = "foo";
function foo(){ echo "wtf"; }
$x();

9、在C++中,你可以使用空指针调用静态函数


class Foo {
public:
static void bar() {
std::cout << "bar()" << std::endl;
}
};

呵呵。的确是挺怪异的。

关于Linux系统性能调优需要了解的几点

关于Linux系统性能调优需要了解的几点:

1. 引言


实践证明Linux是高性能、稳定可靠而又相当灵活的操作系统,近年来,世界上许多大软件公司纷纷推出各种Linux服务器系统及Linux下的应用软件。目前,Linux已可以与各种传统的商业操作系统分庭抗礼,在服务器市场,占据了相当大的份额。Linux的服务器系统多种多样,可用作web服务器,邮件服务器,ftp服务器,文件服务器以及数据库服务器等等。针对不同的系统以及具体的应用环境,可以对Linux的性能进行相应的调优。下面分别从磁盘调优,文件系统,内存管理以及编译优化等方面来论述Linux系统的优化调优策略。


2.磁盘调优


2.1 优化的分区


在安装Linux系统之初,就应该考虑怎样使linux系统得到最好的性能。在Linux系统中,我们可以自由地组织磁盘分区。一个优化的分区策略,可以很好地改进Linux系统的性能,减少磁盘碎片,提高磁盘I/O能力。


根据磁盘的特点,我们知道越是靠磁盘外部的柱面,旋转越快,而且每次旋转时,磁盘读写头可以覆盖较多的区域,也就意味着靠外部的柱面可以得到较好的性能。所以在分区时,我们应该考虑将访问频率高的,对系统性能影响相对较大的分区置于磁盘的靠外部分。同时,为了减少磁盘碎片,应将内容经常改变的目录放在单独的分区。从方便备份数据的角度考虑,因为很多备份工具对整个分区进行备份的效率要高,所以我们应将Linux系统的几个主要的目录作为单独的文件系统,为它们各自分配一个区。推荐的分区策略如图一:


图(一) 优化的分区


在上图中,磁盘还有一部分未分区的空间,为什么不在安装系统时,将整个磁盘分区呢?现在的硬盘空间比较大,而在安装系统时,也许你所安装的文件加上以后增长所需要的空间用不到整个磁盘存储空间。保留一部分未分区空间,当以后有新的需要时,就可以用fdisk重新将之分区。在分区时,需要注意的是,根据系统将来运行的需要或过去的经验来估计各个分区的大小,以免将来空间不够。


如果你的系统有多块硬盘,可以考虑使用多个交换分区。每个磁盘上划分一个交换分区。通过在/etc/fstab文件中设置pri 选项,可使多个交换分区具有同样的优先级。Linux系统即可并行地使用它们。这样即可提高交换分区的性能。


/dev/hda5 swap swap defaults,pri=5 0 0
/dev/hdc5 swap swap defaults,pri=5 0 0

当然,如果你的系统内存足够大,而你的系统又远远用不了那么多的内存,那也就用不到什么虚存了。分区时你就可以考虑去掉交换分区。不过作为一个Linux服务器,即使你的内存足够大,还是应该设置交换分区。


2.2 使用hdparm改善Linux系统性能


如果你的Linux系统运行于IDE硬盘,可以使用hdparm工具来提高磁盘I/O的性能。不过使用hdparm要小心,因为可能破坏硬盘上的数据。所以在使用hdparm之前,仔细阅读你的硬盘手册。根据你具体的硬盘规格来使用相应的hdparm开关参数。对一块UltraATA/66 EIDE 硬盘,其控制芯片支持多 PIO 模式和DMA,我们使用以下命令来调优磁盘性能:


# /sbin/hdparm -x66 -d1 -u1 -m16 -c3 /dev/hda

选项说明:



  • c3 :就是把硬盘的16位格式转换为32位模式(32-bit mode w/sync)。控制数据如何从pci总线传递到控制器。

  • m16 :改变硬盘的多路扇区的读功能,-m16可以使得硬盘在一次i/o中断中读入16个扇区的数据(据具体硬盘而定)。

  • d1:打开DMA模式。

  • x66 :在支持UDMA-capable的硬盘中,这个参数可以支持双DMA通道的数据传输模式。

  • u1 :Linux在处理磁盘中断时,可以unmask其他的中断或者响应其他中断相关的任务。


查看以上的更改情况可以使用命令:


# /sbin/hdparm /dev/hda

测试磁盘I/O性能可以使用命令:


# /sbin/hdparm -tT /dev/hda

如果磁盘的性能有改进的话,可以使用以下命令来保存设置:


# /sbin/hdparm -k1 /dev/hda

2.3 Linux下使用软RAID


RAID(磁盘冗余阵列)是通过将数据分布在多个磁盘上来增强磁盘性能和可靠性的技术。如果你的系统没有硬件RAID控制器的话,在Linux下可以通过软件来实现RAID。RAID的有很多的级别,不同的级别对硬件有不同的要求,相应也会得到不同的性能和可靠性。RAID0为数据分割,数据块被交替写到磁盘,可以得到最好的读写性能,但是不提供数据冗余。RAID1为磁盘镜像,写到磁盘1中的一切也写到磁盘2中,从任何一个磁盘都可以读取。RAID3除了数据分割之外,还指定一个磁盘驱动器来存储奇偶信息。


在LINUX下通过软件来实现RAID, 需要LINUX的内核支持,可以通过编译新内核来添加对RAID的支持。然后还需编译并安装 raidtools 软件包。raidtools 是可以初始化、启动、停止及控制 RAID 阵列的用户级工具。下面是我们用两块IDE硬盘在LINUX内核2.4上实现RAID0的方法。


设置分区 使用RAID0至少需要两个分区,它们位于不同的磁盘。最好做RAID0的两个分区有相同的大小。创建分区时,将分区类型指定为 "FD"。以使 Linux 内核能将它们识别为 RAID 分区,这样,这些分区就可在每次引导时自动被检测并启动。如果您没有以这种方式标记 RAID 分区,则在每次引导之后,必须首先输入 "raidstart --all" 才能挂载 RAID 阵列。我们做成RAID0的两个分区为hda5和hdc5。


编辑/etc/raidtab文件 创建/etc/raidtab文件以描述RAID阵列的配置信息。Linux 内核使用这些信息在引导时自动检测并启动 RAID 阵列,因此必须对创建的每个 RAID 阵列进行这种配置。将分区hda5和hdc5组成md0 RAID0阵列的配置文件如下:


 raiddev /dev/md0
raid-level 0
nr-raid-disks 2
persistent-superblock 1
chunk-size 32
device /dev/hda5
raid-disk 0
device /dev/hdc5
raid-disk 1

在raidtab文件中,"raiddev" 条目指明要创建的 RAID 阵列;"nr-raid-disks" 指定阵列中的磁盘数目;"persistent-superblock" 设置为 1,告诉 raid 工具何时创建该卷;"chunk-size" 以K字节指定 RAID0 使用的块大小;最后,要指定的是组成该阵列的设备即分区。


mkraid 和创建文件系统 使用命令"# mkraid /dev/md0" 命令初始化/dev/md0分区,同时将启动md0 RAID0阵列。接下来,即可在md0上创建你想要得文件系统。我们在Linux服务器上使用的是Reiserfs日志文件系统,创建的命令为"# mkreiserfs /dev/md0"。


这样,就可以像其它文件系统那样来加载新创建的基于RAID0的文件系统了。


2.4 使用elvtune调谐磁盘I/O


在Linux内核2.4以后的版本中,可以通过磁盘I/O的调度操作,来控制磁盘I/O的响应时间和吞吐量。通过调整I/O请求在队列中的最大等待时间,可以在响应时间和吞吐量之间调优。如果要求较少的响应时间,那么吞吐量将降低,反之,较长的响应时间则可以得到较大的吞吐量。可以使用工具"/sbin/elvtune"来改变最大的响应时间值。使用方法如下:


查看当前的设置


# /sbin/elvtune /dev/hda1

修改当前的配置


# /sbin/elvtune -r 2000 -w 4000 /dev/hda

其中-r参数针对读操作,-w参数针对写操作.


可以通过命令"iostat -d -x /dev/hda1"得到的平均信息(包括平均请求大小和平均队列长度)来监视以上I/O配置的效果,并调整配置,以得到最佳的性能。一般来讲,对于读写频繁,但操作的数据量较少的Linux服务器,且对实时性要求较高,那么可以将参数调小。反之如果对于读写不频繁,但要求具有较大的吞吐量的Linux服务器,可以将参数调大,以获得较大的吞吐量。


3. 文件及文件系统调优


3.1 块大小


创建文件系统时,可以指定块的大小。如果将来在你的文件系统中是一些比较大的文件的话,使用较大的块大小将得到较好的性能。将ext2文件系统的块大小调整为4096byte而不是缺省的1024byte,可以减少文件碎片,加快fsck扫描的速度和文件删除以及读操作的速度。另外,在ext2的文件系统中,为根目录保留了5%的空间,对一个大的文件系统,除非用作日志文件,5%的比例有些过多。可以使用命令"# mke2fs -b 4096 -m 1 /dev/hda6"将它改为1%并以块大小4096byte创建文件系统。


使用多大的块大小,需要根据你的系统综合考虑,如果系统用作邮件或者新闻服务器,使用较大的块大小,虽然性能有所提高,但会造成磁盘空间较大的浪费。比如文件系统中的文件平均大小为2145byte,如果使用4096byte的块大小,平均每一个文件就会浪费1951byte空间。如果使用1024byte的块大小,平均每一个文件会浪费927byte空间。在性能和磁盘的代价上如何平衡,要看具体应用的需要。


3.2 不使用atime属性


当文件被创建,修改和访问时,Linux系统会记录这些时间信息。记录文件最近一次被读取的时间信息,当系统的读文件操作频繁时,将是一笔不少的开销。所以,为了提高系统的性能,我们可以在读取文件时不修改文件的atime属性。可以通过在加载文件系统时使用notime选项来做到这一点。当以noatime选项加载(mount)文件系统时,对文件的读取不会更新文件属性中的atime信息。设置noatime的重要性是消除了文件系统对文件的写操作,文件只是简单地被系统读取。由于写操作相对读来说要更消耗系统资源,所以这样设置可以明显提高服务器的性能。注意wtime信息仍然有效,任何时候文件被写,该信息仍被更新。


比如在你的系统中,要为/home文件系统设置notime选项,可以修改/etc/fstab文件相应的行如下:


LABEL=/home /home ext2 noatime 1 2

要使该设置立即生效,可运行命令"#mount -oremount /home"。这样以后系统读取/home下的文件时将不会再修改atime属性。


3.3 调整缓冲区刷新参数


Linux内核中,包含了一些对于系统运行态的可设置参数。缓冲刷新的参数可以通过调整 /proc/sys/vm/bdflush文件来完成,这个文件的格式是这样的:


# cat /proc/sys/vm/bdflush
30 64 64 256 500 3000 60 0 0

每一栏是一个参数,其中最重要的是前面几个参数。第一个数字是在"dirty"缓冲区达到多少的时候强制唤醒bdflush进程刷新硬盘,第二个数字是每次让bdflush进程刷新多少个dirty块。所谓dirty块是必须写到磁盘中的缓存块。接下来的参数是每次允许bd flush将多少个内存块排入空闲的缓冲块列表。 以上值为RedHat Linux 7.1中的缺省值。如何修改它们呢?对不同的系统有以下两种方法


(1)# echo "100 128 128 512 5000 3000 60 0 0">/proc/sys/vm/bdflush 并将这条命令加到/etc/rc.d/rc.local文件中去。
(2)在/etc/sysctl.conf 文件中加入如下行: vm.bdflush = 100 128 128 512 5000 3000 60 0 0

以上的设置加大了缓冲区大小,降低了bdflush被启动的频度,同时也增加了万一系统崩溃丢失数据的危险性。VFS的缓冲刷新是Linux文件系统高效的重要原因之一,如果性能对你真的很重要,应该考虑调整这个参数。


3.4 调整文件句柄数和i-节点数


在一个大型的网站服务器其中,可能Linux默认的同时可打开最大文件数不能满足系统需要,我们可以通过调整文件句柄数和i-节点数来增加系统的缺省的限制。不同的Linux内核版本有不同的调整方法。


在Linux内核2.2.x中可以用如下命令修改:


# echo '8192' > /proc/sys/fs/file-max
# echo '32768' > /proc/sys/fs/inode-max

并将以上命令加到/etc/rc.c/rc.local文件中,以使系统每次重新启动时配置以上值。


在Linux内核2.4.x中需要修改源代码,然后重新编译内核才生效。编辑Linux内核源代码中的 include/linux/fs.h文件,将 NR_FILE 由8192改为 65536,将NR_RESERVED_FILES 由10 改为 128。编辑fs/inode.c 文件将 MAX_INODE 由16384改为262144。


一般情况下,最大打开文件数比较合理的设置为每4M物理内存256,比如256M内存可以设为16384,而最大的使用的i节点的数目应该是最大打开文件数目的3倍到4倍。


3.5 使用内存文件系统


在Linux中可以将一部分内存当作分区来使用,我们称之为RamDisk。对于一些经常被访问的文件,而它们又不会被更改,可以将它们通过RamDisk放在内存中,即可明显地提高系统的性能。当然你的内存可要足够大了。RamDisk有两种,一种可以格式化,加载,在Linux内核2.0/2.2就已经支持,其不足之处是大小固定。另一种是内核2.4才支持的,通过Ramfs或者tmpfs来实现,它们不能被格式化,但是用起来灵活,其大小随所需要的空间而增加或减少。这里主要介绍一下Ramfs和Tmpfs。


Ramfs顾名思义是内存文件系统,它工作于虚拟文件系统(VFS)层。不能格式化,可以创建多个,在创建时可以指定其最大能使用的内存大小。如果你的Linux已经将Ramfs编译进内核,你就可以很容易地使用Ramfs了。创建一个目录,加载Ramfs到该目录即可。


# mkdir -p /RAM1
# mount -t ramfs none /RAM1

缺省情况下,Ramfs被限制最多可使用内存大小的一半。可以通过maxsize(以kbyte为单位)选项来改变。


# mkdir -p /RAM1
# mount -t ramfs none /RAM1 -o maxsize=10000

以上即创建了一个限定了最大使用内存大小为10M的ramdisk。


Tmpfs是一个虚拟内存文件系统,它不同于传统的用块设备形式来实现的ramdisk,也不同于针对物理内存的Ramfs。Tmpfs可以使用物理内存,也可以使用交换分区。在Linux内核中,虚拟内存资源由物理内存(RAM)和交换分区组成,这些资源是由内核中的虚拟内存子系统来负责分配和管理。Tmpfs就是和虚拟内存子系统来"打交道"的,它向虚拟内存子系统请求页来存储文件,它同Linux的其它请求页的部分一样,不知道分配给自己的页是在内存中还是在交换分区中。Tmpfs同Ramfs一样,其大小也不是固定的,而是随着所需要的空间而动态的增减。使用tmpfs,首先你编译内核时得选择"虚拟内存文件系统支持(Virtual memory filesystem support)" ,然后就可以加载tmpfs文件系统了。


# mkdir -p /mnt/tmpfs
# mount tmpfs /mnt/tmpfs -t tmpfs

为了防止tmpfs使用过多的内存资源而造成系统的性能下降或死机,可以在加载时指定tmpfs文件系统大小的最大限制。


# mount tmpfs /mnt/tmpfs -t tmpfs -o size=32m

以上创建的tmpfs文件系统就规定了其最大的大小为32M。不管是使用ramfs还是tmpfs,必须明白的是,一旦系统重启,它们中的内容将会丢失。所以那些东西可以放在内存文件系统中得根据系统的具体情况而定。


3.6 使用日志文件系统


如果Linux系统由于意外情况而没有正常关机,则可能引起文件系统中某些文件的元数据(meta-data即和文件有关的信息,例如:权限、所有者以及创建和访问时间)遭到破坏。文件系统需要维护文件的元数据来保证文件的可组织和可存取,如果元数据处于不合理或不一致的状态,那么就不能访问和存取文件。当系统重新启动时,fsck将扫描/etc/fstab文件中所列出的所有文件系统,确保它们的元数据处于可用的状态。如果发现元数据不一致,fsck将扫描和检测元数据,并纠正错误。如果文件系统很大,这个过程将需要很长的时间。为解决这个问题,可以使用日志文件系统。日志文件系统用独立的日志文件跟踪磁盘内容的变化,在写入文件内容的同时写入文件的元数据。每次修改文件的元数据时,都要先向称为"日志"的数据结构登记相应的条目。这样,日志文件系统就维护了最近更改的元数据的记录。当加载日志文件系统时,如果发现了错误,不会扫描整个文件系统的元数据,而是根据日志检查最近被更改的元数据。所以相对于传统的文件系统(如ext2),日志文件系统大大地加快了扫描和检测的时间。


Linux下可用的日志文件系统很多,如XFS,JFS,Reiserfs,ext3等等。日志文件系统主要被设计为服务器环境提供出色性能和高可用性。当然, Linux 工作站和家用机器也可从具有高性能的可靠日志文件系统中获益。安装日志文件系统,一般需要下载相应的压缩包、为内核打补丁、重新配置和重新编译内核。 详细的安装过程可访问相应文件系统的官方网站。


4.其它方面的调优


4.1 调优buffermem


文件buffermen和内核虚拟内存子系统密切相关。文件/proc/sys/vm/buffermem控制多大内存被用于缓冲区(百分数表示)。内核2.4的缺省值为:: "2 10 60"。可以按如下方法修改:


# echo "70 10 60" >/proc/sys/vm/buffermem

并将之加到脚本文件/etc/rc.d/rc.local 中。或者在/etc/sysctl.conf文件中加入


vm.buffermem = 70 10 60

第一个参数 70意味着至少分配70%的内存用作缓冲区。后两个参数保持系统的默认值。第一个参数设置为多大合适,依赖于系统的内存大小和系统高负载时内存的使用情况(可用free监视)。


4.2 进程限制


Linux对于每个用户,系统限制其最大进程数。为提高性能,可以设置超级用户root的最大进程数为无限。编辑.bashrc文件(vi /root/.bashrc)增加行"ulimit -u unlimited"即可消除超级用户的进程限制。


核心和系统对用户进程其它的一些限制也可以通过ulimit命令来查看和更改。"ulimit -a" 用来显示当前的各种用户进程限制。一些更改用户限制的例子如下:



  • ulimit -n 4096 将每个进程可以打开的文件数目加大到4096,缺省为1024

  • ulimit -m 4096 限制每个进程使用的内存数。


4.3 优化gcc编译


将优化标志放在/etc/profile文件中。在pentium III级的处理器上使用以下优化标志将得到最优的应用程序:


CFLAGS=-O9 -funroll-loops -ffast-math -malign-double -mcpu=pentiumpro
-march=pentiumpro -fomit-frame-pointer -fno-exceptions

再将如下行加到/etc/profile 更靠后的位置:


export PATH PS1 HOSTNAME HISTSIZE HISTFILESIZE USER LOGNAME MAIL
INPUTRC CFLAGS LANG LESSCHARSET

使用以上的优化,gcc或者egcs编译的程序将得到最佳的性能。


4.4 编译内核优化


编辑/usr/src/linux/Makefile文件,可以根据具体的cpu优化内核编译。以下的参数设置将得到优化的内核性能。


① vi +18 /usr/src/linux/Makefile ,将HOSTCC =gcc改为 HOSTCC =egcs.


② vi +25 /usr/src/linux/Makefile

将CC =$(CROSS_COMPILE)gcc D__KERNEL__ -I$(HPATH)

改为 CC =$(CROSS_COMPILE)egcs D__KERNEL__ -I$(HPATH).


③ vi +90 /usr/src/linux/Makefile

将CFLAGS = -Wall -Wstrict-prototypes -O2 -fomit-frame-pointer

改为CFLAGS = -Wall -Wstrict-prototypes -O9 -funroll-loops -ffast-math -malign-double -mcpu=pentiumpro -march=pentiumpro -fomit-frame-pointer -fno-exceptions


④ vi +19 /usr/src/linux/Makefile

将HOSTCFLAGS =-Wall -Wstrict-prototypes -O2 -fomit-frame-pointer

改为HOSTCFLAGS =-Wall -Wstrict-prototypes -O9 -funroll-loops -ffast-math -malign-double -mcpu=pentiumpro -march=pentiumpro -fomit-frame-pointer -fno-exceptions


根据以上修改后的makefile文件重新编译内核将得到较优的性能。


5. 结束语


Linux是一个灵活而又开放的系统。用户可以针对具体的应用环境,从系统的外围到系统的 内核进行调优。系统的外围的调优包括系统硬件的配置到系统安装和系统服务的优化等。系统内核的调优包括参数的修改和改进系统的源代码。我们在针对用作db2数据库服务器的Linux系统的调优中,针对db2数据库的特点,按照本文的各个调优方面,另外还包括网络的调优,对系统性能进行了综合调优。在对调优后的系统的综合测试来看,系统的性能有很大的改进。


关于作者


邓延军,男,湖南永州人,西安电子科技大学软件工程研究所硕士研究生,主要研究方向多播协议,系统调优。您可以通过电子邮件 deng.yanjun@163.com和他联系。

Web开发中需要了解的东西

Web开发中需要了解的东西:

在StackExchange上有人问了这样一个问题:What should every programmer know about web development?(关于Web开发,什么是所有程序员需要知道的?)里面给出的答案非常不错,所以,我翻译转载过来。 顺便说一下,StackExchange真是非常好,大家可以对同一个答案做贡献和修订,看看这个问题的修订过程你就知道了——专业的问答网站应该怎么去做。这就是我在这篇文章中也说过真正的用户体验是什么样的


好了,下面是正文(我对原文做了一些批注,也许不对或有误导,请大家指正)


下面的这些东西可能对于大多数人并不陌生,但是可能会有些东西你以前并没有看过,或是没有完全搞懂,甚至都没有听说过。(陈皓注:我相信当你看完这个列表后,你会觉得对于我国的Web开发有点弱了,还是那句话,表面上的东西永远是肤浅的)


接口和用户体验



  • 小心浏览器的实现标准上的不一致,确信让你的网站能够适当地跨浏览器。至少,你的网站需要测试一下下面的浏览器:



最后,你可以使用一下这个工具 来看看你的网页在不同的浏览器下是怎么被显示出来的(陈皓注:这个工具就是以前本站介绍过的在不同浏览器和平台上检查你的网站的兼容性



  • 多考虑一下人们是怎么来访问你的网站而不是那些主流的浏览器:手机,读屏软件和搜索引擎,例如:一些Accessibility的东西: WAISection508, 移动设备开发:MobiForge.



  • 部署Staging:怎么部署网站的更新而不会影响用户的访问。 Ed Lucas的答案 可以让你了解一些(陈皓注:Ed说了一些如版本控制,自动化build,备份,回滚等机制)。



  • 千万不要直接给用户显示不友好的错误信息。




  • 千万不要把用户的邮件地址以明文显示出来,这样会被爬虫爬走并被让用户的邮箱被垃圾邮件搞死。



  • 为用户的链接加上 rel="nofollow" 的属性以 避免垃圾网站的干扰。(陈皓注:nofollow是HTML的一个属性,用于通知搜索引擎“这个链接所指向的网页非我所能控制,对其内容不予置评”,或者简单地说,该链接不是对目标网站或网页的“投票”,这样搜索引擎不会再访问这个链接。这个是用来减少一些特定垃圾页面对原网站的影响,从而可以改善搜索结果的质量,并且防止垃圾链接的蔓延。)



  • 为网站建立一些的限制 - 这个属于安全性的范畴。(陈皓注:比如你在Google注册邮箱时,你一口气注册超过两个以上的邮箱,gmail要求给你发短信或是给你打电话认证,比如Discuz论坛的会限制你发贴或是搜索的间隔时间等等,更多的网站会用CAPTCHA来确认是人为的操作。 这些限制都是为了防止垃圾和恶意攻击)



  • 学习如何做 Progressive Enhancement. (陈皓注:Progressive Enhancement是一个Web Design的理念,如:1)基础的内容和功能应该可以被所有的浏览器存取,2)页面布局的应该使用外部的CSS链接,3)Javascript也应该是外部链接还应该是 unobtrusive 的,4)应该让用户可以设置他们的偏好)




  • 严重关注Accessibility。因为这是法律上的需求(陈皓注:Section 508是美国的508法案,其是美国劳工复健法的改进,它是一部联邦法律,这个法律要求所有技术要考虑到残障人士的应用,如果某个大众信息传播网站,如果某些用户群体(如残疾人)浏览该网站获取信息时,如果他们无法正常获得所期望的信息(如无法正常浏览),那可以依据相关法规,可以对该网站依法起诉)。 WAI-ARIA 为这方面的事提供很不错的资源.


安全



  • 在网上有很多关于安全的文章,但是 OWASP 开发指导 涵盖了几乎所有关于Web站点安全的东西。(陈皓注:OWASP(开放Web应用安全项目- Open Web Application Security Project)是一个开放的非营利性组织,目前全球有130个分会近万名会员,其主要目标是研议协助解决Web软体安全之标准、工具与技术文件,长期 致力于协助政府或企业了解并改善网页应用程式与网页服务的安全性。OWASP被视为Web应用安全领域的权威参考。2009年下列发布的美国国家和国际立法、标准、准则、委员会和行业实务守则参考引用了OWASP。美国联邦贸易委员会(FTC)强烈建议所有企业需遵循OWASP十大WEB弱点防护守则)




  • 永远不要相信用户的输入(包括Cookies,因为那也是用户的输入)



  • 对用户的口令进行Hash,并使用salt,以防止Rainbow 攻击(陈皓注:Hash算法可用MD5或SHA1等,对口令使用salt的意思是,user 在设定密码时,system 产生另外一个random string(salt)。在datbase 存的​​是与salt + passwd 产的md5sum 及salt。 当要验证密码时就把user 输入的string 加上使用者的salt,产生md5s​​um 来比对。 理论上用salt 可以大幅度让密码更难破解,相同的密码除非刚好salt 相同,最后​​存在database 上的内容是不一样的。google一下md5+salt你可以看到很多文章。关于Rainbow 攻击,其意思是很像密码字典表,但不同的是,Rainbow Table存的是已经被Hash过的密码了,而且其查找密码的速度更快,这样可以让攻击非常快)。使用慢一点的Hash算法来保存口令,如 bcrypt (被时间检证过了) 或是 scrypt (更强,但是也更新一些) (1, 2)。你可以阅读一下 How To Safely Store A Password(陈皓注:酷壳以前曾介绍过bcrypt这个算法,这里,我更建议我们应该让用户输入比较强的口令,比如Apple ID注册的过程需要用户输入超过8位,需要有大小写和数字的口令,或是做出类似于这样的用户体验的东西)。





  • 使用 SSL/HTTPS 来加密传输登录页面或是任可有敏感信息的页面,比如信用卡号等。



  • 知道如何对付session 劫持。(陈皓注:请参看wikipedia的这Session Hijacking,)





  • 保持你的系统里的所有软件更新到最新的patch。



  • 确保你的数据库连接是安全的。



  • 确保你能了解最新的攻击技术,以及你系统的脆弱处。





性能




  • 优化页面 —— 不要使用20KB图片来平铺网页背景。(陈皓注:还有很多网页页面优化性的文章,你可以STFG – Search The Fucking Google一下。如果你要调试的话,你可以使用firebug或是chrome内置的开发人员的工具来查看网页装载的性能)

  • 学习如何 gzip/deflate 网页 (deflate 更好).



  • 把多个CSS文件和Javascript文件合并成一个,这样可以减少浏览器的网络连数,并且使用gzip压缩被反复用到的文件。




  • 为那些小的图片使用 CSS Image Sprites,就像工具条一样。 (参看 “最小化 HTTP 请求” ) (陈皓注:把所有的小图片合并成一个图片,然后用CSS把显示其中的一块,这样,这些小图片只用传输一次,酷壳的Wordpress样式的那个RSS订阅列表中的小图标就是这样做的)



  • 繁忙的网络应该考虑把网页的内容分开存放在不同的域名下。(陈皓注:比如有专门的图片服务器——图片相当耗带宽,或是专门的Ajax服务器)



  • 静态网页 (如,图片,CSS,JavaScript,以及一些不需要访问cookies的网页) 应该放在一个不使用cookies的独立的域名下,因为所有在同一个域名或子域名下的cookie会被这个域名下的请求一同发送。另一个好的选择是使用 Content Delivery Network (CDN).



  • 使用单个页面的HTTP请求数最小化。



  • 为Javascript使用 Google Closure Compiler 或是 其它压缩工具(陈皓注:压缩Javascript代码可以让你的页面减少网络传输从而可以得到很快的喧染。注意,并不是所有的工具都可以正确压缩Javascript的,Google的这个工具甚至还可以帮你优化你的代码)



  • 确认你的网站有一个 favicon.ico 文件放在网站的根下,如 /favicon.ico. 浏览器会自动请求这个文件,就算这个图标文件没有在你的网页中明显说明,浏览器也会请求。如果你没有这个文件,就会出大量的404错误,这会消耗你的服务器带宽。(陈皓注:服务器返回404页面会比这个ico文件可能还大)


SEO (搜索引擎优化)



  • 使用 “搜索引擎喜欢的” URL,如:使用 example.com/pages/45-article-title 而不是 example.com/index.php?page=45 (陈皓注:这里的URL是说Wordpress的,后者是默认的)



  • 如果你的动态页面要使用 # ,那么请把其改成 #! ,而在服务端,你需要处理$_REQUEST["_escaped_fragment_"] 这是Google搜索引擎需要的。换句话说,./#!page=1 会被Google搜索引擎转成 ./?_escaped_fragments_=page=1。 (陈皓注:通常来说URL中的#后的东西都不会被传到服务器上,所以,为了要让Google可以抓取AJAX的东西,你需要使用#!,而Google会把“#!”转成“_escaped_fragment_”来向服务器发请求,Twitter的大量的链接者是#!的,比如:https://twitter.com/#!/your_activity)。另外,用户也许会使用Firefox 或 Chromium, history.pushState({"foo":"bar"}, "About", "./?page=1"); 是一个很不错的命令。所以,就算是我们的地址栏上的地址改变了,页面也不会重新装载。这可以让你使用 ? 而不是 #! 也能无刷地保住当前的动态的页面,这可以让AJAX的请求被浏览器记住。



  • 别使用 “click here” 这样的链接。这样一来,无法SEO,而且对于一些需要使用读屏人来说很不友好(陈皓注:关于读屏软件,可参看本站的“如果看不见你还能编程吗”)






  • 了解 robots.txt 和搜索引擎爬虫是如何工作的。



  • 重定向请求 (使用 301 重定向网站) ,如果你要把 www.example.com 定向到 example.com(或是其它的变更) 这样可以防止Google的rank因为域名的变化发生改变。(陈皓注:301重定向一般用作域名变更)



  • 知道并不是所有的爬虫都是好的,有些爬虫的行为并不好。(陈皓注:比如向你的网站发大量的请求导致服务器性能低下)



  • 如果你有一些非文本的内容需要在 Google’s sitemap 中,比如视频什么的。Tim Farley的答案,可以让你看到很多有价值的东西。


技术



  • 理解什么是 HTTP 比如 GET, POST, sessions, cookies等,了解什么是 “stateless” 无状态。



  • 让你的 XHTML/HTMLCSS 符合 W3C 规范,并确认他们都是 合格的。我们的目标是避免浏览器的 “quirks mode”,并且可以让其更容易地能和非标准的浏览器工作,比如读屏器或移动设备。



  • 理解浏览器是怎么处理 JavaScript 的。(陈皓:你会看到有些Javascript代码在页面上前面,有些则是在后面,所以你需要对其了解清楚为什么是这样)



  • 了解浏览器是怎么装载 JavaScript,CSS和其它资源的,了解其对视觉上的影响。(陈皓注:10年前我做网页的时候因为HTML还很弱,所以只能使用table来布局,使用table布局的问题就是整个table读完后页面才会显示,用户的视觉体验并不好)。在某些情况下,你可能需要把你的脚本放在页面的后面



  • 理解 JavaScript 的 sandbox 是怎么怎么工作的,尤其是你想使用iframes。



  • 请注意 JavaScript 可能会被禁止,这样会让你的AJAX失效。就算是大多数用户都开启了Javascript功能,但是也可能在一些情况下脚本是不被运行的,比如移动终端上,搜索引擎抓网页的时候也并不会执行你的脚本。




  • 尽可能多地学习你的部署平台。(比如:操作系统,Web Server:Apache/Nginx,防火墙,数据库,等等)





  • 把视觉效果和JS框架合在一起讨论,考虑使用一个Service,如:Google Libraries API 来装载框架,这样可以让浏览器可能早就把这个JS框架已经cache了而不需要再从你的网站上下载了。


Bug fixing



  • 明白你会花20%的时间写代码,而80%的时候在维护,所以你要小心编码。(陈皓注:参看本站的“多些时间可以少些代码”一文)



  • 设计一个好的错误报告机制。



  • 设计一个入口可以让人们联系到你并给你建议和批评。



  • 为你开发的东西形成文档,这样可以让后来的人容易维护你的软件和系统。



  • 频繁备份(也可确保你的这些备份功能正常) Ed Lucas 的回答 有一些忠告。你还需要有一个恢复策略,而不只是一个备份策略。



  • 使用一个版本控制系统来保存你的代码,如: SubversionGit.



  • 别忘了做Acceptance Testing,使用 Selenium 能帮到你。



  • 确保你有足够的日志,你可以使用 log4j, log4n 或 log4r。如果出了问题,这是可以让你快速找到问题的方式。



  • 当你写日志的时候,确保你记录了你捕获了处理和未处理异常。报告和分析日志可以让你知道你网站的问题。


这里有多的东西被省略了,并不是因为那些可能不是有帮助的答案,而是因为那些东西都太细节了,超出了这个问题的范围,因为这本来就是一个Web开发需要了解东西的Overview。我想你可以去看一下其它人的答案,我有时间,我也会补充别人的答案进来。请随意编辑这个答案,因为可能有些东西忘了,也有可能有些东西不对。


(全文完)



















您可能也喜欢:




Web开发人员速查卡





开源中最好的Web开发的资源





30+ Web下拉菜单





如何在低速率网络中测试 Web 应用





Web中的省略号



无觅

相关文章

MySQL主从服务器配置的一些总结

一、做了MySQL主从也有一段时间了,这两天检查磁盘空间情况,发现放数据库的分区磁盘激增了40多G,一路查看下来,发现配置好主从复制以来到现在的binlog就有40多G,原来根源出在这里,查看了一下my.cnf,看到binlog的 size是1G就做分割,但没有看到删除的配置,在MySQL里show了一下variables:


mysql>show variables like '%log%';

查到了:


| expire_logs_days | 0 |


这个默认是0,也就是logs不过期,这个是一个global的参数,所以需要执行


set global expire_logs_days=8;

这样8天前的log就会被删除了,如果有回复的需要,请做好备份工作,但这样设置还不行,下次重启mysql了,配置又恢复默认了,所以需在my.cnf中设置,


expire_logs_days = 8

这样重启也不怕了。现在我在生产环境下的做法是将此时间设为0,然后备份mysql日志文件,然后再手动清理此文件。想要恢复数据库以前的资料,执行:


mysql>show binlog events;

由于数据量很多,查看起来很麻烦,光打开个文件就要闪半天,所以应该适当删除部分可不用的日志。并且如果使用的时间足够长的话,会把我的硬盘空间都给吃掉。


1、登录系统,/usr/bin/mysql


使用mysql查看日志:


mysql>show binary logs;
+—————-+———–+
| Log_name | File_size |
+—————-+———–+
| ablelee.000001 | 150462942 |
| ablelee.000002 | 120332942 |
| ablelee.000003 | 141462942 |
+—————-+———–+

2、删除bin-log(删除ablelee.000003之前的而没有包含ablelee.000003):


mysql> purge binary logs to ′ablelee.000003′;
Query OK, 0 rows affected (0.16 sec)

3、查询结果(现在只有一条记录了):


mysql> show binlog eventsG
*************************** 1. row ***************************
Log_name: ablelee.000003
Pos: 4
Event_type: Format_desc
Server_id: 1
End_log_pos: 106
Info: Server ver: 5.1.26-rc-log, Binlog ver: 4
1 row in set (0.01 sec)
(ablelee.000001和ablelee.000002已被删除)
mysql> show binary logs;
+—————-+———–+
| Log_name | File_size |
+—————-+———–+
| ablelee.000003 | 106 |
+—————-+———–+
1 row in set (0.00 sec)
(删除的其它格式运用!)
PURGE {MASTER | BINARY} LOGS TO ‘log_name’
PURGE {MASTER | BINARY} LOGS BEFORE ‘date’

用于删除列于在指定的日志或日期之前的日志索引中的所有二进制日志。这些日志也会从记录在日志索引文件中的清单中被删除,这样被给定的日志成为第一个。


例如:


PURGE MASTER LOGS TO 'mysql-bin.010';
PURGE MASTER LOGS BEFORE '2008-06-22 13:00:00';

二、现在手上蛮多项目的数据库用的是MySQL,由于权限等原因,暂时不方便部署Nagios监控MySQL主从复制,所以我一般在从机上配置了SHELL脚本用来监控MySQL的主从状态(设置为每十分钟运行一次),并且每次出问题时将确切日期写进错误日志,方便事后排查原因,脚本内容如下:


#!/bin/bash
#check MySQL_Slave Status
#crontab time 00:10
MYSQLPORT=`netstat -na|grep "LISTEN"|grep "3306"|awk -F[:" "]+ '{print $4}'`
MYSQLIP=`ifconfig eth0|grep "inet addr" | awk -F[:" "]+ '{print $4}'`
STATUS=$(/usr/local/webserver/mysql/bin/mysql -u yuhongchun -pyuhongchun101 -S /tmp/mysql.sock -e "show slave statusG" | grep -i "running")
IO_env=`echo $STATUS | grep IO | awk ' {print $2}'`
SQL_env=`echo $STATUS | grep SQL | awk '{print $2}'`


if [ "$MYSQLPORT" == "3306" ]
then
echo "mysql is running"
else
mail -s "warn!server: $MYSQLIP mysql is down" yuhongchun027@163.com
fi

if [ "$IO_env" = "Yes" -a "$SQL_env" = "Yes" ]
then
echo "Slave is running!"
else
echo "####### $date #########">> /data/data/check_mysql_slave.log
echo "Slave is not running!" >> /data/data/check_mysql_slave.log
mail -s "warn! $MySQLIP_replicate_error" yuhongchun027@163.com << /data/data/check_mysql_slave.log
fi

建议每十分钟运行一次。


*/10 * * * * root /bin/sh /root/mysql_slave.sh

记得在每台MySQL从机上分配一个yuhongchun的用户,权限大些也没关系,只限定在本地运行,如下所示:


grant all privileges on *.* to "yuhongchun"@"127.0.0.1" identified by "yuhongchun101";
grant all privileges on *.* to "yuhongchun"@"localhost" identified by "yuhongchun101";

脚本设计思路:



  1. 此脚本应该能适应各种各样不同的内外网环境,即IP不同的环境;

  2. 让脚本也顺便监控下MySQL是否正常运行;


三、innodb_buffer_pool_size的设置。


这个参数定义了InnodDB存储引擎的表数据和索引数据的最大内存缓冲区大小。和MyISAM存储引擎不同,MyISAM的key_buffer_size只缓存索引键,而innodb_buffer_pool_size却是同时为数据块和索引块 做缓存,这个特征和Oracle是一样的,这个值设得越高,访问表中数据需求的I/O就越少。在一个专用的数据库服务器,可以设置这个参数达机器物理内存的80%,我现在一般的做法是配置成物理内存的 1/4,比如8G内存的生产数据库,我一般会配置成2G左右。


四、测试了很长一段时间的MySQL的负载均衡,最后综合了老男孩和其它技术高手的意见,最终决定还是用LVS+Keepalived来作为MySQL的负载均衡,这是因为后端机器超过10台时,LVS的性能还是最好的;如果在3-5台左右,HAProxy也可以很轻松的搞定工作。


五、大家都很清,磁盘I/O总会成为数据库的性能瓶颈,这时候我们应该如何在生产环境下选择合适的RAID级别呢?



  1. 如果数据读写都很频繁,可靠性要求也很高,最好选择RAID10;

  2. 如果数据读很频繁,写相对较少,对可靠性有一定要求,可以选择RAID5;

  3. 如果数据读写都很频繁,但可靠性要求不高,可以选择RAID0。

  4. 对于核心业务的数据库主从同步,建议从机的备份时间往后延迟一段时间,通常的做法是延迟一天左右。


作者介绍:


余洪春(抚琴煮酒),《构建高可用Linux服务器》一书作者,一拍网系统架构师、资深项目管理工程师,ChinaUnix集群和高可用版版主。

优化Linux的内核参数来提高服务器并发处理能力

在服务器硬件资源额定有限的情况下,最大的压榨服务器的性能,提高服务器的并发处理能力,是很多运维技术人员思考的问题。要提高Linux系统下的负载能力,可以使用Nginx等原生并发处理能力就很强的Web服务器,如果使用Apache的可以启用其Worker模式,来提高其并发处理能力。除此之外,在考虑节省成本的情况下,可以修改Linux的内核相关TCP参数,来最大的提高服务器性能。当然,最基础的提高负载问题,还是升级服务器硬件了,这是最根本的。


Linux系统下,TCP连接断开后,会以TIME_WAIT状态保留一定的时间,然后才会释放端口。当并发请求过多的时候,就会产生大量的TIME_WAIT状态的连接,无法及时断开的话,会占用大量的端口资源和服务器资源。这个时候我们可以优化TCP的内核参数,来及时将TIME_WAIT状态的端口清理掉。


本文介绍的方法只对拥有大量TIME_WAIT状态的连接导致系统资源消耗有效,如果不是这种情况下,效果可能不明显。可以使用netstat命令去查TIME_WAIT状态的连接状态,输入下面的组合命令,查看当前TCP连接的状态和对应的连接数量:


# netstat -n | awk ‘/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}’


这个命令会输出类似下面的结果:


LAST_ACK 16

SYN_RECV 348

ESTABLISHED 70

FIN_WAIT1 229

FIN_WAIT2 30

CLOSING 33

TIME_WAIT 18098



我们只用关心TIME_WAIT的个数,在这里可以看到,有18000多个TIME_WAIT,这样就占用了18000多个端口。要知道端口的数量只有65535个,占用一个少一个,会严重的影响到后继的新连接。这种情况下,我们就有必要调整下Linux的TCP内核参数,让系统更快的释放TIME_WAIT连接。


用vim打开配置文件:


# vim /etc/sysctl.conf


在这个文件中,加入下面的几行内容:


net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30

输入下面的命令,让内核参数生效:


# sysctl -p


简单的说明上面的参数的含义:


net.ipv4.tcp_syncookies = 1
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;

net.ipv4.tcp_tw_reuse = 1
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

net.ipv4.tcp_tw_recycle = 1
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭;

net.ipv4.tcp_fin_timeout
#修改系統默认的 TIMEOUT 时间。

在经过这样的调整之后,除了会进一步提升服务器的负载能力之外,还能够防御小流量程度的DoS、CC和SYN攻击。


此外,如果你的连接数本身就很多,我们可以再优化一下TCP的可使用端口范围,进一步提升服务器的并发能力。依然是往上面的参数文件中,加入下面这些配置:


net.ipv4.tcp_keepalive_time = 1200
net.ipv4.ip_local_port_range = 10000 65000
net.ipv4.tcp_max_syn_backlog = 8192
net.ipv4.tcp_max_tw_buckets = 5000
#这几个参数,建议只在流量非常大的服务器上开启,会有显著的效果。一般的流量小的服务器上,没有必要去设置这几个参数。

net.ipv4.tcp_keepalive_time = 1200
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。

net.ipv4.ip_local_port_range = 10000 65000
#表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为10000到65000。(注意:这里不要将最低值设的太低,否则可能会占用掉正常的端口!)

net.ipv4.tcp_max_syn_backlog = 8192
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。

net.ipv4.tcp_max_tw_buckets = 6000
#表示系统同时保持TIME_WAIT的最大数量,如果超过这个数字,TIME_WAIT将立刻被清除并打印警告信息。默 认为180000,改为6000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量,但是对于 Squid,效果却不大。此项参数可以控制TIME_WAIT的最大数量,避免Squid服务器被大量的TIME_WAIT拖死。

内核其他TCP参数说明:


net.ipv4.tcp_max_syn_backlog = 65536
#记录的那些尚未收到客户端确认信息的连接请求的最大值。对于有128M内存的系统而言,缺省值是1024,小内存的系统则是128。

net.core.netdev_max_backlog = 32768
#每个网络接口接收数据包的速率比内核处理这些包的速率快时,允许送到队列的数据包的最大数目。

net.core.somaxconn = 32768
#web应用中listen函数的backlog默认会给我们内核参数的net.core.somaxconn限制到128,而nginx定义的NGX_LISTEN_BACKLOG默认为511,所以有必要调整这个值。

net.core.wmem_default = 8388608
net.core.rmem_default = 8388608
net.core.rmem_max = 16777216
#最大socket读buffer,可参考的优化值:873200

net.core.wmem_max = 16777216
#最大socket写buffer,可参考的优化值:873200

net.ipv4.tcp_timestsmps = 0
#时间戳可以避免序列号的卷绕。一个1Gbps的链路肯定会遇到以前用过的序列号。时间戳能够让内核接受这种“异常”的数据包。这里需要将其关掉。

net.ipv4.tcp_synack_retries = 2
#为了打开对端的连接,内核需要发送一个SYN并附带一个回应前面一个SYN的ACK。也就是所谓三次握手中的第二次握手。这个设置决定了内核放弃连接之前发送SYN+ACK包的数量。

net.ipv4.tcp_syn_retries = 2
#在内核放弃建立连接之前发送SYN包的数量。

#net.ipv4.tcp_tw_len = 1
net.ipv4.tcp_tw_reuse = 1
# 开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接。

net.ipv4.tcp_wmem = 8192 436600 873200
# TCP写buffer,可参考的优化值: 8192 436600 873200

net.ipv4.tcp_rmem = 32768 436600 873200
# TCP读buffer,可参考的优化值: 32768 436600 873200

net.ipv4.tcp_mem = 94500000 91500000 92700000
# 同样有3个值,意思是:
net.ipv4.tcp_mem[0]:低于此值,TCP没有内存压力。
net.ipv4.tcp_mem[1]:在此值下,进入内存压力阶段。
net.ipv4.tcp_mem[2]:高于此值,TCP拒绝分配socket。
上述内存单位是页,而不是字节。可参考的优化值是:786432 1048576 1572864


net.ipv4.tcp_max_orphans = 3276800
#系统中最多有多少个TCP套接字不被关联到任何一个用户文件句柄上。
如果超过这个数字,连接将即刻被复位并打印出警告信息。
这个限制仅仅是为了防止简单的DoS攻击,不能过分依靠它或者人为地减小这个值,
更应该增加这个值(如果增加了内存之后)。


net.ipv4.tcp_fin_timeout = 30
#如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间。对端可以出错并永远不关闭连接,甚至意外当机。缺省值是60秒。2.2 内核的通常值是180秒,你可以按这个设置,但要记住的是,即使你的机器是一个轻载的WEB服务器,也有因为大量的死套接字而内存溢出的风险,FIN- WAIT-2的危险性比FIN-WAIT-1要小,因为它最多只能吃掉1.5K内存,但是它们的生存期长些。

经过这样的优化配置之后,你的服务器的TCP并发处理能力会显著提高。以上配置仅供参考,用于生产环境请根据自己的实际情况。