存档:网站架构

中庸之道的newsfeed的设计

四月 25, 2010 | 数据结构算法, 网站架构 | RSS 2.0

follows大道至简,道道相同,其实世界本没有那么复杂,不过人们把它描写的多样化了,他就那么复杂了,抽象到一定程度发现互不相关的东西都有他的共性,无论在任何领域都少不了change,有change都有质变,质变点就有极值点,min,max,在数学中尤为常见。但有时需要结合最大和最小结合运用才能创造出合理,譬如在web2.0时代颇为流行的newsfeed。twitter,facebook,其实googlereader等都一样,一个生产者,一个消费者。设计往往经常是大家谈论的两种,一种是存储一份,消费者就登陆时候就pull过来,例如你登陆豆瓣,就会看到你好多好友的信息,都是pull过来的,如果你比较强大可以把生产者的内容给每一个消费者发一份。但是也会面临瞬间分发的压力,虽然在发的时候有一定的延时性。但用户读取的时候就很容易,读跟自己相关的就可以了。目前好多有这种应用的都是这种操作。以下是facebooke的。

facebook newsfeed

facebook newsfeed

在看看一篇有关校内的http://www.54chen.com/uncategorized/net-new-to-live-for-all-to-share.html

用的是一些key value的数据库。也是push的思想,即给所有的相关的好友dispatch一份。 但是想twitter这样的有些突发事件或者名人

follows过多的话会有多大的压力无论是写还是读。  后来躲到一篇follows的论文,好像一个普林斯顿大学大学的人写的,专门解决这种臭名昭著的棘手问题,没看的太明白,但是大概思想是把生产者和消费者分类,因为有些生成者是高产者,而有些消费者是高消费者,无论单一的产用pull的方式

或者是push的方式都不是太合适,他们会根据用户的生产效率和登录比率来判断到底给用pull的方式或者是push的方式,当然作者也说类似follows的应用时臭名昭著的应用

难于设计扩展等,作者也做了性能优化方面的分析,用了yahoo的开源的pnuts的key value的一个系统。把用户的好友关系抽象成一张图,然后利用一定的算法来优化这种follows的应用。而且作者通过调用twitter的api进行了一些测试,测试效果还不错。

仔细想想也是,以前总以为设计这种系统要么是是push给用户,要么是pull给用户,但是其实两者是可以结合的,相互弥补。

follows的pnuts应用

follows的pnuts应用

看来世事没有绝对性有时候需要结合两个极端来思考问题,中庸也许是一种更好的解决办法。就像老子的思想那样,物极必反。事不能要一个极端。我会附加上作者的论文,本身我也

不全明白,多读几篇follows pdf

1条评论 »

mysql replication 报告

十二月 8, 2009 | mysql, 网站架构 | RSS 2.0

整理的一份mysql replication的报告,主要包括基本原理,一些常见的架构和replication的历史,不能同步等问题
Mysql Replication
View more documents from liufabin 66688.

没有评论 »

speeding up your nginx server with memcached

五月 8, 2009 | 网站架构 | RSS 2.0

可以看这篇文章http://wiki.nginx.org/NginxChsHttpProxyModule ,说的是nginx和memcache可以提升你4倍的性能,请看他们   的图

speeding up your nginx server with memcached - 玉树临风 - 小和尚真情无限
nginx在前端可以通过重写规则写到app servrs 还可以通过原生的memcache去查看一些东西是否存在,比如一些小的用户头像,存到cache里,最近看qconbeijing2009的daouban的架构的演化好像经历过这一步,不过他们好像是lighhttd服务器,内置的memache的东西,看看他们的配置文件
user  usr usr;
worker_processes  2;

http {
types {
text/javascript    js;
application/xml    xml;
}

# By default, return content sa
default_type  application/xml;

access_log  /home/api/logs/nginx.log  main;

sendfile       on;
tcp_nopush     on;

keepalive_timeout  65;
tcp_nodelay        on;

# app. server(s) / cluster definition
upstream dynamic_srv { server 127.0.0.1:9020; }

server {
listen       9000;
server_name  srv;
root        /home/usr;

# Match any request that begins with /dynamic_request
location /dynamic_request {

# Append a file-extension to every request
if ($args ~* format=json) { rewrite ^/dynamic_request/?(.*)$ /dynamic_request.js$1 break; }
if ($args ~* format=xml)  { rewrite ^/dynamic_request/?(.*)$ /dynamic_request.xml$1 break; }

# Check if local memcached server can answer this request
memcached_pass 127.0.0.1:11211;

# Send to app. server if Memcached could not ansewr the request
error_page 404 = @dynamic_request;
}

location @dynamic_feed_id {
# only internal requests can reach this endpoint
internal;

# dispatch to our app_server cluster / instance
proxy_pass http://dynamic_srv;
}

}
}

然后在学习的过程中,发现一篇更好的文章http://lserinol.blogspot.com/2009/03/speeding-up-your-nginx-server-with.htmlnginx 的memcache的modual,豆瓣就是用的这个缓存小图片,减少磁盘的io操作,Serving content from memory will be faster than serving it from disk首先需要定义一个反向代理请求的的server,92.168.2.3 的性能要比92.168.2.4的好,所以我们他的权重写一些。

upstream backend {     server 192.168.2.3 weight=2;     server 192.168.2.4;} 

然后写他的配置文件 :

location ~* .(jpg|png|gif)$ {access_log   off;expires      max;add_header   Last-Modified "Thu, 26 Mar 2000 17:35:45 GMT";set $memcached_key $uri;memcached_pass     127.0.0.1:11211;error_page         404 = /fetch;}把图片文件重定向过来,然后查找在memcache中是否存在,不存在调到404,然后404错误页面会找到fetchlocation /fetch {internal;access_log   off;expires      max;add_header   Last-Modified "Thu, 26 Mar 2000 17:35:45 GMT";proxy_pass http://backend;break;}然后fetch就会反向代理到后端backend后面。这时候就需要负载均衡到相应的server。可以看出他的原生的memcache只能获取,而不能设置,所以我们需要自己手工完成如对图片存贮到memcache ,写个对应的脚本:

<?php

function rscandir($base='', &$data=array()) {$array = array_diff(scandir($base), array('.', '..'));

foreach($array as $value) :  if (is_dir($base.$value)) {    $data = rscandir($base.$value.'/', $data);

  }  elseif (is_file($base.$value)) {   $rest = substr($value, -4);   if ((!strcmp($rest,'.jpg')) || (!strcmp($rest,'.png'))                                || (!strcmp($rest,'.gif')) ){         $data[] = $base.$value;   } }

endforeach;return $data;}

$mylist=rscandir("/var/www/mysite");

$srch = array('/var/www/mysite');$newval = array('');

$memcache_obj = memcache_connect("192.168.2.1", 11211);

while (list($key, $val) = each($mylist)) {  $url=str_replace($srch,$newval,$val);  echo "$key => $val -> ".filesize($val)."";  $value = file_get_contents($val);  memcache_add($memcache_obj, $url, $value, false, 0);}

以上操作就应该能完成你所需要的功能。 以上文字主要来源于作者的文章,算我我的一个理解和翻译。

没有评论 »

c获取文件的大小的两种方法和行读取

五月 6, 2009 | c/c++, linux, 网站架构 | RSS 2.0

1 fseek移动指针获取
#include <stdio.h>
#include <stdlib.h>

long filesize( FILE *fp )
{
    long int save_pos;
    long size_of_file;

    /* Save the current position. */
    save_pos = ftell( fp );

    /* Jump to the end of the file. */
    fseek( fp, 0L, SEEK_END );

    /* Get the end position. */
    size_of_file = ftell( fp );

    /* Jump back to the original position. */
    fseek( fp, save_pos, SEEK_SET );

    return( size_of_file );
}

int main( void )
{
    FILE *fp;

    fp = fopen( “aa.txt”, “r” );

    if( fp != NULL ) {
        printf( “File size=%ld”, filesize( fp ) );
        fclose( fp );

        return EXIT_SUCCESS;
    }

    return EXIT_FAILURE;
}

2 stat获取

#include <stdio.h>#include <stdlib.h>#include <sys/stat.h>

int main( void )  {    struct stat buf;

    if( stat( "file", &buf ) != -1 ) {      printf( "File size = %d", buf.st_size );    }    return EXIT_SUCCESS;

  }

但是是有区别的,如
参考fseek()。
  通过fseek()、ftell()两个函数,我们就可以随意访问文件的任何位置了,想了想好像操作文件就这么easy,实在也没有更多可说的了。对了,fseek()和ftell()存在一个潜在的问题就是他们限制文件的大小只能在long类型的表示范围以内,也就是说通过这种方式,只能打开2,000,000,000字节的文件,不过在绝大多数情况下似乎也已经够用了。如果需要打开更大的文件,你需要用到fgetpos()、fsetpos()函数了,那是另一个命题了。

#include <stdio.h>
#include <string.h>

int main()
{
    int  i;
    long k;
    char buf[100];
    long int save_pos;

    FILE *fp;

    fp=fopen(”aa.txt”,”rb”);
    for(i=1;i<10;i++)
    {
        fscanf(fp,”%[^]“,buf);
        //这句读取一行,直到遇到,停止,并把读到的内容放到buf里,而此时文件指针的位置指向
        printf(”%s”,buf);
        //k=strlen(buf);
        fseek(fp,1,SEEK_CUR);
    //跳过,则从下一行的开始读。这里主要是跳过换行符。如果为k的话,就是从第二行跳过k个字符的位置开始读,当然是不对的了。
        buf[0]=’0′;

    }
    fclose(fp);
    return 0;
}

没有评论 »

使用ETags减少Web应用带宽和负载(转)

四月 22, 2009 | linux, 网站架构 | RSS 2.0

转自http://www.infoq.com/cn/articles/etags

介绍

最近,大众对于REST风格应用架构表现出强烈兴趣,这表明Web的优雅设计开始受到人们的注意。现在,我们逐渐理解了“3W架构(Architectureof the World WideWeb)”内在所蕴含的可伸缩性和弹性,并进一步探索运用其范式的方法。本文中,我们将探究一个可被Web开发者利用的、鲜为人知的工具,不引人注意的“ETag响应头(ETag ResponseHeader)”,以及如何将它集成进基于Spring和Hibernate的动态Web应用,以提升应用程序性能和可伸缩性。

我们将要使用的Spring框架应用是基于“宠物诊所(petclinic)”的。下载文件中包含了关于如何增加必要的配置及源码的说明,你可以自己尝试。

什么是“ETag”?

HTTP协议规格说明定义ETag为“被请求变量的实体值” (参见 http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html —— 章节 14.19)。 另一种说法是,ETag是一个可以与Web资源关联的记号(token)。典型的Web资源可以一个Web页,但也可能是JSON或XML文档。服务器单独负责判断记号是什么及其含义,并在HTTP响应头中将其传送到客户端。

ETag如何帮助提升性能?

聪明的服务器开发者会把ETags和GET请求的“If-None-Match”头一起使用,这样可利用客户端(例如浏览器)的缓存。因为服务器首先产生ETag,服务器可在稍后使用它来判断页面是否已经被修改。本质上,客户端通过将该记号传回服务器要求服务器验证其(客户端)缓存。

其过程如下:

  1. 客户端请求一个页面(A)。
  2. 服务器返回页面A,并在给A加上一个ETag。
  3. 客户端展现该页面,并将页面连同ETag一起缓存。
  4. 客户再次请求页面A,并将上次请求时服务器返回的ETag一起传递给服务器。
  5. 服务器检查该ETag,并判断出该页面自上次客户端请求之后还未被修改,直接返回响应304(未修改——Not Modified)和一个空的响应体。

本文的其余部分将展示在基于Spring框架的Web应用中利用ETag的两种方法,该应用使用SpringMVC。首先我们将使用Servlet 2.3 Filter,利用展现视图(renderedview)的MD5校验和(checksum)以实现生成ETag的方法(一个“浅显的”ETag实现)。 第二种方法使用更为复杂的方法追踪view中所使用的model,以确定ETag有效性(一个“深入的”ETag实现)。尽管我们使用的是Spring MVC,但该技术可以应用于任何MVC风格的Web框架。

在我们继续之前,强调一下这里所展现的是提升动态产生页面性能的技术。已有的优化技术也应作为整体优化和应用性能特性调整分析的一部分来考虑。(见下)。

自顶向下的Web缓存

本文主要涉及对动态生成页面使用HTTP缓存技术。当考虑提升Web应用的性能的时候,应采取一个整体的、自顶向下的方法。为了这一目的,理解HTTP请求经过的各层是很重要的,应用哪些适当的技术取决于你所关注的热点。例如:

  • 将Apache作为Servlet容器的前端,来处理如图片和javascript脚本这样的静态文件,而且还可以使用FileETag指令创建ETag响应头。
  • 使用针对javascript文件的优化技术,如将多个文件合并到一个文件中以及压缩空格。
  • 利用GZip和缓存控制头(Cache-Control headers)。
  • 为确定你的Spring框架应用的痛处所在,可以考虑使用 JamonPerformanceMonitorInterceptor
  • 确信你充分利用ORM工具的缓存机制,因此对象不需要从数据库中频繁的再生。花时间确定如何让查询缓存为你工作是值得的。
  • 确保你最小化数据库中获取的数据量,尤其是大的列表。如果每个页面只请求大列表的一个小子集,那么大列表的数据应由其中某个页面一次获得。
  • 使放入到HTTP session中的数据量最小。这样内存得到释放,而且当将应用集群的时候也会有所帮助。
  • 使用数据库明细(database profiling)工具来查看在查询的时候使用了什么索引,在更新的时候整个表没有被上锁。

当然,应用性能优化的至理名言是:两次测量,一次剪裁(measure twice, cut once)。哦,等等,这是对木工而言的!没错,但是它在这里也很适用!

ETag Filter内容体

我们要考虑的第一种方法是创建一个Servlet Filter,它将基于页面(MVC中的“View”)的内容产生其ETag记号。乍一看,使用这种方法所获得的任何性能提升看起来都是违反直觉的。我们仍然不得不产生页面,而且还增加了产生记号的计算时间。然而,这里的想法是减少带宽使用。在大的响应时间情形下,如你的主机和客户端分布在这个星球的两端,这很大程度上是有益的。我曾见过东京办公室使用纽约服务器上托管的应用,其响应时间达到了 350 ms。随着并发用户数的增长,这将变成巨大的瓶颈。

代码

我们用来产生记号的技术是基于从页面内容计算MD5哈希值。这通过在响应之上创建一个包装器来实现。该包装器使用字节数组来保存所产生的内容,在filter链处理完成之后我们利用数组的MD5哈希值计算记号。

doFilter方法的实现如下所示。

 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {  HttpServletRequest servletRequest = (HttpServletRequest) req;  HttpServletResponse servletResponse = (HttpServletResponse) res;

   ByteArrayOutputStream baos = new ByteArrayOutputStream();  ETagResponseWrapper wrappedResponse = new ETagResponseWrapper(servletResponse, baos);  chain.doFilter(servletRequest, wrappedResponse);

   byte[] bytes = baos.toByteArray();

   String token = '"' + ETagComputeUtils.getMd5Digest(bytes) + '"';  servletResponse.setHeader("ETag", token); // always store the ETag in the header

   String previousToken = servletRequest.getHeader("If-None-Match");  if (previousToken != null && previousToken.equals(token)) { // compare previous token with current one   logger.debug("ETag match: returning 304 Not Modified");   servletResponse.sendError(HttpServletResponse.SC_NOT_MODIFIED);   // use the same date we sent when we created the ETag the first time through   servletResponse.setHeader("Last-Modified", servletRequest.getHeader("If-Modified-Since"));  } else  {   // first time through - set last modified time to now    Calendar cal = Calendar.getInstance();   cal.set(Calendar.MILLISECOND, 0);   Date lastModified = cal.getTime();   servletResponse.setDateHeader("Last-Modified", lastModified.getTime());

    logger.debug("Writing body content");   servletResponse.setContentLength(bytes.length);   ServletOutputStream sos = servletResponse.getOutputStream();   sos.write(bytes);   sos.flush();   sos.close();  } } 

清单 1:ETagContentFilter.doFilter

你需注意到,我们还设置了Last-Modified头。这被认为是为服务器产生内容的正确形式,因为其迎合了不认识ETag头的客户端。

下面的例子使用了一个工具类EtagComputeUtils来产生对象所对应的字节数组,并处理MD5摘要逻辑。我使用了javax.security MessageDigest来计算MD5哈希码。

public static byte[] serialize(Object obj) throws IOException {  byte[] byteArray = null;  ByteArrayOutputStream baos = null;  ObjectOutputStream out = null;  try {   // These objects are closed in the finally.   baos = new ByteArrayOutputStream();   out = new ObjectOutputStream(baos);   out.writeObject(obj);   byteArray = baos.toByteArray();  } finally {   if (out != null) {    out.close();   }  }  return byteArray; }

  public static String getMd5Digest(byte[] bytes) {  MessageDigest md;  try {   md = MessageDigest.getInstance("MD5");  } catch (NoSuchAlgorithmException e) {   throw new RuntimeException("MD5 cryptographic algorithm is not available.", e);  }  byte[] messageDigest = md.digest(bytes);  BigInteger number = new BigInteger(1, messageDigest);  // prepend a zero to get a "proper" MD5 hash value  StringBuffer sb = new StringBuffer('0');  sb.append(number.toString(16));  return sb.toString(); }

清单 2:ETagComputeUtils

直接在web.xml中配置filter。

    <filter>       <filter-name>ETag Content Filter</filter-name>       <filter-class>org.springframework.samples.petclinic.web.ETagContentFilter</filter-class>     </filter>

      <filter-mapping>       <filter-name>ETag Content Filter</filter-name>       <url-pattern>/*.htm</url-pattern>     </filter-mapping>

清单 3:web.xml中配置filter。

每个.htm文件将被EtagContentFilter过滤,如果页面自上次客户端请求后没有改变,它将返回一个空内容体的HTTP响应。

我们在这里展示的方法对特定类型的页面是有用的。但是,该方法有两个缺点:

  • 我们是在页面已经被展现在服务器之后计算ETag的,但是在返回客户端之前。如果有Etag匹配,实际上并不需要再为model装进数据,因为要展现的页面不需要发送回客户端。
  • 对于类似于在页脚显示日期时间这样的页面,即使内容实际上并没有改变,每个页面也将是不同的。

下一节,我们将着眼于另一种方法,其通过理解更多关于构造页面的底层数据来克服这些问题的某些限制。

ETag拦截器(Interceptor)

Spring MVC HTTP请求处理途径中包括了在一个controller前插接拦截器(Interceptor)的能力,因而有机会处理请求。这儿是应用我们ETag比较逻辑的理想场所,因此如果我们发现构建一个页面的数据没有发生变化,我们可以避免进一步处理。

这儿的诀窍是你怎么知道构成页面的数据已经改变了?为了达到本文的目的,我创建了一个简单的ModifiedObjectTracker,它通过Hibernate事件侦听器清楚地知道插入、更新和删除操作。该追踪器为应用程序的每个view维护一个唯一的号码,以及一个关于哪些Hibernate实体影响每个view的映射。每当一个POJO被改变了,使用了该实体的view的计数器就加1。我们使用该计数值作为ETag,这样当客户端将ETag送回时我们就知道页面背后的一个或多个对象是否被修改了。

代码

我们就从ModifiedObjectTracker开始吧:

public interface ModifiedObjectTracker {  void notifyModified(> String entity); }

够简单吧?这个实现还有一点更有趣的。任何时候一个实体改变了,我们就更新每个受其影响的view的计数器:

public void notifyModified(String entity) {  // entityViewMap is a map of entity -> list of view names  List views = getEntityViewMap().get(entity);

   if (views == null) {   return; // no views are configured for this entity  }

   synchronized (counts) {   for (String view : views) {    Integer count = counts.get(view);    counts.put(view, ++count);   }  } }

一个“改变”就是插入、更新或者删除。这里给出的是侦听删除操作的处理器(配置为Hibernate 3 LocalSessionFactoryBean上的事件侦听器):

public class DeleteHandler extends DefaultDeleteEventListener {  private ModifiedObjectTracker tracker;

   public void onDelete(DeleteEvent event) throws HibernateException {   getModifiedObjectTracker().notifyModified(event.getEntityName());  }

  public ModifiedObjectTracker getModifiedObjectTracker() {   return tracker;  }   public void setModifiedObjectTracker(ModifiedObjectTracker tracker) {   this.tracker = tracker;  } }

ModifiedObjectTracker通过Spring配置被注入到DeleteHandler中。还有一个SaveOrUpdateHandler来处理新建或更新POJO。

如果客户端发送回当前有效的ETag(意味着自上次请求之后我们的内容没有改变),我们将阻止更多的处理,以实现我们的性能提升。在Spring MVC里,我们可以使用HandlerInterceptorAdaptor并覆盖preHandle方法:

public final boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException {  String method = request.getMethod();  if (!"GET".equals(method))   return true;

   String previousToken = request.getHeader("If-None-Match");  String token = getTokenFactory().getToken(request);

   // compare previous token with current one  if ((token != null) && (previousToken != null && previousToken.equals('"' + token + '"'))) {   response.sendError(HttpServletResponse.SC_NOT_MODIFIED);   // re-use original last modified timestamp   response.setHeader("Last-Modified", request.getHeader("If-Modified-Since"))   return false; // no further processing required  }

   // set header for the next time the client calls  if (token != null) {    response.setHeader("ETag", '"' + token + '"');

    // first time through - set last modified time to now   Calendar cal = Calendar.getInstance();   cal.set(Calendar.MILLISECOND, 0);   Date lastModified = cal.getTime();   response.setDateHeader("Last-Modified", lastModified.getTime());  }

   return true; }

我们首先确信我们正在处理GET请求(与PUT一起的ETag可以用来检测不一致的更新,但其超出了本文的范围。)。如果该记号与上次我们发送的记号相匹配,我们返回一个“304未修改”响应并“短路”请求处理链的其余部分。否则,我们设置ETag响应头以便为下一次客户端请求做好准备。

你需注意到我们将产生记号逻辑抽出到一个接口中,这样可以插接不同的实现。该接口有一个方法:

public interface ETagTokenFactory {  String getToken(HttpServletRequest request); }

为了把代码清单减至最小,SampleTokenFactory的简单实现还担当了ETagTokenFactory的角色。本例中,我们通过简单返回请求URI的更改计数值来产生记号:

public String getToken(HttpServletRequest request) {  String view = request.getRequestURI();  Integer count = counts.get(view);  if (count == null) {   return null;  }

   return count.toString(); }

大功告成!

会话

这里,如果什么也没改变,我们的拦截器将阻止任何搜集数据或展现view的开销。现在,让我们看看HTTP头(借助于LiveHTTPHeaders),看看到底发生了什么。下载文件中包含了配置该拦截器的说明,因此owner.htm“能够使用ETag”:

我们发起的第一个请求说明该用户已经看过了这个页面:

----------------------------------------------------------  http://localhost:8080/petclinic/owner.htm?ownerId=10  

 GET /petclinic/owner.htm?ownerId=10 HTTP/1.1 Host: localhost:8080 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4 Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5 Accept-Language: en-us,en;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8 X-lori-time-1: 1182364348062 If-Modified-Since: Wed, 20 Jun 2007 18:29:03 GMT If-None-Match: "-1"

 HTTP/1.x 304 Not Modified Server: Apache-Coyote/1.1 Date: Wed, 20 Jun 2007 18:32:30 GMT

我们现在应该做点修改,看看ETag是否改变了。我们给这个物主增加一个宠物:

----------------------------------------------------------
http://localhost:8080/petclinic/addPet.htm?ownerId=10

GET /petclinic/addPet.htm?ownerId=10 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.4) Gecko/20070515 Firefox/2.0.0.4
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://localhost:8080/petclinic/owner.htm?ownerId=10
Cookie: JSESSIONID=13D2E0CB63897F4EDB56639E46D2BBD8
X-lori-time-1: 1182364356265

HTTP/1.x 200 OK
Server: Apache-Coyote/1.1
Pragma: No-cache
Expires: Thu, 01 Jan 1970 00:00:00 GMT
Cache-Control: no-cache, no-store
Content-Type: text/html;charset=ISO-8859-1
Content-Language: en-US
Content-Length: 2174
Date: Wed, 20 Jun 2007 18:32:57 GMT
----------------------------------------------------------
http://localhost:8080/petclinic/addPet.htm?ownerId=10

POST /petclinic/addPet.htm?ownerId=10 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1 ...

没有评论 »

socket笔记

四月 1, 2009 | c/c++, linux, 网站架构 | RSS 2.0

1什么是socket 利用标准的unix文件表述夫和别的程序进行交流的方式

unix中一切都是文件,unix程序i/o交换时通过多谢文件表述夫,一个文件表述夫就是一个简单的整形的和打开文件相关的东西。 thatfile can be a network connection, a

FIFO,a pipe, a terminal, a real on-the-disk file, or just about anything

else.Everything in Unix is a file! So when you want to communicate with

anotherprogram over the Internet you’re gonna do it through a file

descriptor,you’d better believeit.你可能要问,在网络交换中我如何得到文件表述夫呢?调用socket,它将返回一个文件夫,你和它进行交流调用通过专门的socketsendrecv。但是你现在可能会说,既然是文件表述夫,那我为什么不能用writeread呢?简单的说,可以,复杂的,但是在数据传输中sendrecv能更好的得到控制。

有多种socketdarpainternetsocket网络socket,路径名在本地节点,x25地址。。。。可能还支持多种unix支持的。本文主要说的是网络socket。两种网络socket,其实是在说谎,为了怕吓着你,这里只介绍两种情况,介绍一种简单的状态协议的socket。流套接字,可靠如果你输出的socket按照顺序是12,到达的则是1,2的相反,你听说过telnet吧,它用的就是流套接字,你可以模仿http协议在telnet,的两边连接,在某些方面类似,标准的输入输出流,提供一个有序的可靠的,双字节流的链接,发送的数据确保不会丢失,复制和乱序到到,并且在这一过程中发生的错误也不会显示出来,大的消息背分片,传输,再重组。这很像一个文件流,接受大的数据,然后以笑数据块的格式将他们写入底层硬盘。流套接字的行为是可以遇见的。有类型sock_stream指定,在af_inet域中通过tcp/ip连接实现的。SOCK_DGRAM数据包套接字有时被称为无连接的socket.流套接字用的是传输控制协议,网际协议。数据包套接字也用ip路由,但是他们不用tcp协议,他们用udp协议。什么是无连接的?基本上是你在进行流套接字时不用去维持一个打开的连接,你建立一个包,分片,ip头,在目的信息上,发出去,不需要连接了,包包传简单的应用如:tftpbootp等。

你可能惊奇,如果数据包丢失,程序将如如何工作,myhumanfriend,在udp上层也有自己的协议,tftp协议每个包发出,接受方返回说我得到了,如果没有回复,5秒后,它将继续传输,直到得到了ack

网际七层协议:

*Application

*Presentation

*Session

*Transport

*Network

*Data Link

  • Physical

  • 四层协议

    * Application Layer (telnet, ftp, etc.)

  • * Host-to-Host Transport Layer (TCP, UDP)

  • * Internet Layer (IP and routing)

  • * Network Access Layer (was Network, Data Link, and Physical)

多种struct

structsockaddr {

unsignedshort sa_family; /* address family, AF_xxx */

char sa_data[14]; /* 14 bytes of protocol address */

};

af_inet域中:

structsockaddr_in (”in” for “Internet”.)

structsockaddr_in {

shortint sin_family; /* Address family */

unsignedshort int sin_port; /* Port number */

structin_addr sin_addr; /* Internet address */

unsignedchar sin_zero[8]; /* Same size as struct sockaddr */

};

/*Internet address (a structure for historical reasons) */

structin_addr {

unsignedlong s_addr;

};

字节顺序:

*htons()–”Host to Network Short”

*htonl()–”Host to Network Long”

*ntohs()–”Network to Host Short”

  • ntohl()–”Network to Host Long”

  • 为什么地址协议不用网络字节顺序?

  • A final point: why do sin_addr and sin_port need to be in Network Byte Order

  • in a struct sockaddr_in, but sin_family does not? The answer: sin_addr and

  • sin_port get encapsulated in the packet at the IP and UDP layers,

  • respectively. Thus, they must be in Network Byte Order. However, the

  • sin_family field is only used by the kernel to determine what type of

  • address the structure contains, so it must be in Host Byte Order. Also,

  • since sin_family does not get sent out on the network, it can be in Host

  • Byte Order.

库函数介绍

    inet_addr(), converts an IP address in numbers-and-dots notation

  • into an unsigned long. The assignment can be made as follows:

  • ina.sin_addr.s_addr = inet_addr(”132.241.5.10″);

  • Notice that inet_addr() returns the address in Network Byte Order

  • already–you don’t have to call htonl(). Swell!

  • 不要忘记了错误检查

  • 相反你也可以有iplong得到ip地址:

  • printf(”%s”,inet_ntoa(ina.sin_addr));

  • 注意事项

  • That will print the IP address. Note that inet_ntoa() takes a struct in_addr

  • as an argument, not a long. Also notice that it returns a pointer to a char.

  • This points to a statically stored char array within inet_ntoa() so that

  • each time you call inet_ntoa() it will overwrite the last IP address you

  • asked for. For example:

  • char *a1, *a2;

  • .

  • .

  • a1 = inet_ntoa(ina1.sin_addr); /* this is 198.92.129.1 */

  • a2 = inet_ntoa(ina2.sin_addr); /* this is 132.241.5.10 */

  • printf(”address 1: %s”,a1);

  • printf(”address 2: %s”,a2);

  • will print:

  • address 1: 132.241.5.10

  • address 2: 132.241.5.10

  • If you need to save the address, strcpy() it to your own character array.

  • That’s all on this topic for now. Later, you’ll learn to convert a string

  • like “whitehouse.gov” into its corresponding IP address

创建socket

#include<sys/types.h>

#include<sys/socket.h>

intsocket(int domain, int type, int protocol);

Butwhat are these arguments? First, domain should be set to “AF_INET”,just

likein the struct sockaddr_in (above.) Next, the type argument tells the

kernelwhat kind of socket this is: SOCK_STREAM or SOCK_DGRAM. Finally,just

setprotocol to “0″. (Notes: there are many more domains thanI’ve listed.

Thereare many more types than I’ve listed. See the socket() man page.Also,

there’sa “better” way to get the protocol. See thegetprotobyname() man

page.)

socket()simply returns to you a socket descriptor that you can use in later

systemcalls, or -1 on error. The global variable errno is set to the

error’svalue (see the perror() man page.)

bind

#include<sys/types.h>

#include<sys/socket.h>

intbind(int sockfd, struct sockaddr *my_addr, int addrlen);

例子:

#include<string.h>

#include<sys/types.h>

#include<sys/socket.h>

#defineMYPORT 3490

main()

{

intsockfd;

structsockaddr_in my_addr;

sockfd= socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */

my_addr.sin_family= AF_INET; /* host byte order */

my_addr.sin_port= htons(MYPORT); /* short, network byte order */

my_addr.sin_addr.s_addr= inet_addr(”132.241.5.10″);

bzero(&(my_addr.sin_zero),8); /* zero the rest of the struct */

/*don’t forget your error checking for bind(): */

bind(sockfd,(struct sockaddr *)&my_addr, sizeof(struct sockaddr));

注意:

my_addr.sin_port= 0; /* choose an unused port at random */

my_addr.sin_addr.s_addr= INADDR_ANY; /* use my IP address */

See,by setting my_addr.sin_port to zero, you are telling bind() tochoose

theport for you. Likewise, by setting my_addr.sin_addr.s_addr to

INADDR_ANY,you are telling it to automatically fill in the IP address of

themachine the process is running on.

.Theconnect() call is as follows:

#include<sys/types.h>

没有评论 »

开源数据库Sharding技术

二月 25, 2009 | linux, mysql, 网站架构 | RSS 2.0

  Sharding不是一个某个特定数据库软件附属的功能,而是在具体技术细节之上的抽象处理,是水平扩展(Scale Out,亦或横向扩展、向外扩展)的解决方案,其主要目的是为突破单节点数据库服务器的 I/O 能力限制,解决数据库扩展性问题。
‘);

   从 Shard Sharding

  ”Shard” 这个词英文的意思是”碎片”,而作为数据库相关的技术用语,似乎最早见于大型多人在线角色扮演游戏(MMORPG)中。”Sharding” 姑且称之为”分片”。

  Sharding 不是一门新技术,而是一个相对简朴的软件理念。如您所知,MySQL 5 之后才有了数据表分区功能,那么在此之前,很多 MySQL 的潜在用户都对 MySQL的扩展性有所顾虑,而是否具备分区功能就成了衡量一个数据库可扩展性与否的一个关键指标(当然不是唯一指标)。数据库扩展性是一个永恒的话题,MySQL的推广者经常会被问到:如在单一数据库上处理应用数据捉襟见肘而需要进行分区化之类的处理,是如何办到的呢? 答案是:Sharding。

  Sharding 不是一个某个特定数据库软件附属的功能,而是在具体技术细节之上的抽象处理,是水平扩展(Scale Out,亦或横向扩展、向外扩展)的解决方案,其主要目的是为突破单节点数据库服务器的 I/O 能力限制,解决数据库扩展性问题。

  事关数据库扩展性

  说起数据库扩展性,这是个非常大的话题。目前的商业数据都有自己的扩展性解决方案,在过去相对来说比较成熟,但是随着互联网的高速发展,不可避免的会带来一些计算模式上的演变,这样很多主流商业系统也难免暴露出一些不足之处。比如 Oracle 的 RAC 是采用共享存储机制,对于 I/O 密集型的应用,瓶颈很容易落在存储上,这样的机制决定后续扩容只能是 Scale Up(向上扩展) 类型,对于硬件成本、开发人员的要求、维护成本都相对比较高。

  Sharding 基本上是针对开源数据库的扩展性解决方案,很少有听说商业数据库进行 Sharding 的。目前业界的趋势基本上是拥抱 Scale Out,逐渐从 Scale Up 中解放出来。

  Sharding 的应用场景

  任何技术都是在合适的场合下能发挥应有的作用。 Sharding 也一样。联机游戏、IM、BSP 都是比较适合 Sharding的应用场景。其共性是抽象出来的数据对象之间的关联数据很小。比如IM,每个用户如果抽象成一个数据对象,完全可以独立存储在任何一个地方,数据对象是 Share Nothing 的;再比如 Blog服务提供商的站点内容,基本为用户生成内容(UGC),完全可以把不同的用户隔离到不同的存储集合,而对用户来说是透明的。

  这个 “Share Nothing” 是从数据库集群中借用的概念,举例来说,有些类型的数据粒度之间就不是 “ShareNothing”的,比如类似交易记录的历史表信息,如果一条记录中既包含卖家信息与买家信息,如果随着时间推移,买、卖家会分别与其它用户继续进行交易,这样不可避免的两个买卖家的信息会分布到不同的 Sharding DB 上,而这时如果针对买卖家查询,就会跨越更多的 Sharding ,开销就会比较大。

  Sharding 并不是数据库扩展方案的银弹,也有其不适合的场景,比如处理事务型的应用就会非常复杂。对于跨不同DB的事务,很难保证完整性,得不偿失。所以,采用什么样的 Sharding 形式,不是生搬硬套的。

  Sharding与数据库分区(Partition)的区别

  有的时候,Sharding 也被近似等同于水平分区(Horizontal Partitioning),网上很多地方也用水平分区来指代 Sharding,但我个人认为二者之间实际上还是有区别的。的确,Sharding的思想是从分区的思想而来,但数据库分区基本上是数据对象级别的处理,比如表和索引的分区,每个子数据集上能够有不同的物理存储属性,还是单个数据库范围内的操作,而 Sharding 是能够跨数据库,甚至跨越物理机器的。(见对比表格)

Sharding.png

Sharding 策略

  数据 Sharding 的策略与分区表的方式有很多类似的地方,有基于表、ID 范围、数据产生的时间或是SOA 下理念下的基于服务等众多方式可选择。而与传统的表分区方式不同的是,Sharding 策略和业务结合的更为紧密,成功的 Sharding 必须对自己的业务足够熟悉,进行众多可行性分析的基础上进行,”业务逻辑驱动”。

  Sharding 实现案例分析:Digg 网站

  作为风头正劲的 Web 2.0 网站之一的Digg.com,虽然用户群庞大,但网站数据库数据并非海量,去年同期主数据大约只有 30GB的样子,现在应该更大一些,但应该不会出现数量级上增长,数据库软件采用 MySQL 5.x。Digg.com的 IO压力非常大,而且是读集中的应用(98%的 IO是读请求)。因为提供的是新闻类服务,这类数据有其自身特点,最近时间段的数据往往是读压力最大的部分。

  根据业务特点,Digg.com 根据时间范围对主要的业务数据做 Sharding,把不到 10%的”热”数据有效隔离开来,同时对这部分数据用以更好的硬件,提供更好的用户体验。而另外 90%的数据因用户很少访问,所以尽管访问速度稍慢一点,对用户来说,影响也很小。通过 Sharding,Digg 达到了预期效果。

  现有的 Sharding 软件简介

  现在 Sharding 相关的软件实现其实不少,基于数据库层、DAO 层、不同语言下也都不乏案例。限于篇幅,作一下简要的介绍。

  MySQL Proxy + HSCALE

  一套比较有潜力的方案。其中 MySQL Proxy(http://forge.mysql.com/wiki/MySQL_Proxy) 是用 Lua 脚本实现的,介于客户端与服务器端之间,扮演Proxy 的角色,提供查询分析、失败接管、查询过滤、调整等功能。目前的 0.6 版本还做不到读、写分离。HSCALE 则是针对 MySQLProxy 插件,也是用 Lua 实现的,对 Sharding 过程简化了许多。需要指出的是,MySQL Proxy 与 HSCALE各自会带来一定的开销,但这个开销与集中式数据处理方式单条查询的开销还是要小的。

  Hibernate Shards

  这是 Google 技术团队贡献的项目(http://www.hibernate.org/414.html),该项目是在对Google 财务系统数据 Sharding 过程中诞生的。因为是在框架层实现的,所以有其独特的特性:标准的 Hibernate编程模型,会用 Hibernate 就能搞定,技术成本较低;相对弹性的 Sharding 策略以及支持虚拟 Shard 等。

  Spock Proxy

  这也是在实际需求中产生的一个开源项目。Spock(http://www.spock.com/)是一个人员查找的 Web 2.0网站。通过对自己的单一 DB 进行有效 Sharding化 而产生了SpockProxy(http://spockproxy.sourceforge.net/ ) 项目,Spock Proxy 算得上 MySQLProxy 的一个分支,提供基于范围的 Sharding 机制。Spock 是基于 Rails 的,所以Spock Proxy 也是基于Rails 构建,关注 RoR 的朋友不应错过这个项目。 HiveDB

  上面介绍了 RoR 的实现,HiveDB (http://www.hivedb.org/)则是基于Java 的实现,另外,稍有不同的是,这个项目背后有商业公司支持。

  PL/Proxy

  前面几个都是针对 MySQL 的 Sharding 方案,PL/Proxy 则是针对 PostgreSQL 的,设计思想类似Teradata 的 Hash 机制,数据存储对客户端是透明的,客户请求发送到 PL/Proxy 后,由这里分布式存储过程调用,统一分发。PL/Proxy 的设计初衷就是在这一层充当”数据总线”的职责,所以,当数据吞吐量支撑不住的时候,只需要增加更多的 PL/Proxy服务器即可。大名鼎鼎的 Skype 用的就是 PL/Proxy 的解决方案。

  Pyshards

  http://code.google.com/p/pyshards/wiki/Pyshards 这是个基于 Python的解决方案。该工具的设计目标还有个 Re-balancing 在里面,这倒是个比较激进的想法。目前只支持 MySQL 数据库。

  结束语

  Sharding 是一项仍处于高速发展中的”老”技术,随着 Web 2.0的发展,Sahrding逐渐从比较”虚”的概念变成比较”实”的运用思路,开放源代码软件大潮也给 Sharding注入新的活力,相信会有越来越多的项目采用 Sharding 技术,也会有更多成熟的 Sharding 方案和数据库附加软件涌现。

  你的站点 Sharding 了么?

  –EOF–

  另,本周末我讲参加这个活动:体验基于OpenSolaris的Web/企业应用,做一个题为《设计可扩展的面向互联网应用的MySQL数据库》的简单分享。欢迎杭州朋友光临指导。

没有评论 »

高内聚,松耦合

二月 16, 2009 | 网站架构, 软件工程/编程技巧/设计模式 | RSS 2.0

耦合关系直接决定着软件面对变化时候
    1 模块与模块之间的紧耦合使得软件面对变化时,相关的模块都要随之
更改。    
    2模块与模块之间的松耦合使得软件变化时,一些模块更容易替换或者更改
但其它模块保持不变

大自然是个天生松耦合,高内聚的
高内聚,松耦合 - 玉树临风 - 玉树临风真情无限
顺便学习下高内聚,松耦合
内聚:一个模块内各个元素彼此结合的紧密程度

耦合:一个软件结构内不同模块之间互连程度的度量

一个完整的系统,模块与模块之间,尽可能的使其独立存在。

也就是说,让每个模块,尽可能的独立完成某个特定的子功能。

模块与模块之间的接口,尽量的少而简单。

如果某两个模块间的关系比较复杂的话,最好首先考虑进一步的模块划分。

这样有利于修改和组合。

在一个模块内,让每个元素之间都尽可能的紧密相连。

也就是充分利用每一个元素的功能,各施所能,以最终实现某个功能。

如果某个元素与该模块的关系比较疏松的话,可能该模块的结构还不够完善,或者是该元素是多余的。

内聚和耦合,包含了横向和纵向的关系。功能内聚和数据耦合,是我们需要达成的目标。横向的内聚和耦合,通常体现在系统的各个模块、类之间的关系,而纵向的耦合,体现在系统的各个层次之间的关系。

对于我在编码中的困惑,我是这样想的,用面向对象的思想去考虑一个类的封装。
一个方法,如何封装,拿到现实生活中来看,看这种能力(方法)是否是属于这类事物(类)的本能。
如果是,就封装在这个类里。
如果不是,则考虑封装在其它类里。
如果这种能力,很多事物都具有,则一定要封装在这类事物的总类里。
如果这种能力,很多事物都会经常用到,则可以封装成一个总类的静态方法

一个优秀软件开发人员的必修课:高内聚

高内聚 Java 软件工程 软件模式    

一个重要的模式:高内聚。

2.  高内聚(High Cohesion

高内聚是另一个普遍用来评判软件设计质量的标准。内聚,更为专业的说法叫功能内聚,是对软件系统中元素职责相关性和集中度的度量。如果元素具有高度相关的职责,除了这些职责内的任务,没有其它过多的工作,那么该元素就具有高内聚性,反之则为低内聚性。高内聚要求软件系统中的各个元素具有较高的协作性,因为在我们在完成软件需求中的一个功能,可能需要做各种事情,但是具有高内聚性的一个元素,只完成它职责内的事情,而把那些不在它职责内的事情拿去请求别人来完成。这就好像,如果我是一个项目经理,我的职责是监控和协调我的项目各个阶段的工作。当我的项目进入需求分析阶段,我会请求需求分析员来完成;当我的项目进入开发阶段,我会请求软件开发人员来完成;当我的项目需要测试的时候,我会请求测试人员。。。。。。如果我参与了开发,我就不是一个高内聚的元素,因为开发不是我的职责。我们的项目为什么要高内聚呢?我觉得可以从可读性、复用性、可维护性和易变更性四个方面来理解。

1.可读性

一个人写文章、讲事情,条理清晰才能易于理解,这同样发生在读写软件代码上。如果一堆代码写得一团乱麻,东一个跳转西一个调用,读它的人会感觉非常头疼。这种事情也许一直在写程序的你我都曾经有过经历。如果一段程序条理非常清晰,每个类通过名称或说明都能清楚明白它的意义,类的每个属性、函数也都是易于理解的它所应当完成的任务和行为,这段程序的可读性必然提高。在软件产业越来越密集,软件产业中开发人员协作越来越紧密、分工越来越细的今天,软件可读性的要求相信也越来越为人们所重视。

2.复用性

在软件开发中,最低等级的复用是代码拷贝,然后是函数的复用、对象的复用、组件的复用。软件开发中最懒的人是最聪明的人,他们总是想到复用。在代码编写的时候突然发现某个功能是曾经实现过的功能,直接把它拷贝过来就ok了。如果这段代码在同一个对象中,那么就提出来写一个函数到处调用就行了。如果不是在同一个对象中呢,就将其抽象成一个对象到处调用吧。如果不在一个项目中呢,那就做成组件给各个项目引用吧。代码复用也使我们的代码在复用的过程中不断精化、不断健壮、提高代码质量。代码的复用的确给我们的开发带来了不少便利,但是一段代码能否在各个需要的地方都能复用呢?这给我们的软件开发质量提出了新的要求:好的代码可以复用,不好的则不行。软件中的一个对象如果能保证能完成自己职能范围内的各项任务,同时又不去理会与自己职能无关的其它任务,那么它就能够保证功能的相对独立性,也就可以脱离自己所处的环境而复用到其它环境中,这是一个具有内聚性的对象。

3.可维护性和易变更性

在前面《如何在struts+spring+hibernate的框架下构建低耦合高内聚的软件》中我提到,我们现在的软件是在不断变更的,这种变更不仅来自于我们的客户,更来自于我们的市场。如果我们的软件通过变更能及时适应我们的市场需求,我们就可以在市场竞争中获胜。如何能及时变更以适应我们的市场呢,就是通过调整软件的结构,使我们每次的变更付出的代价最小,耗费的人力最小,这种变更才最快最经济。高内聚的软件,每个系统、模块、类的任务都高度相关,就使每一次的变更涉及的范围缩小到最小。比如评审表发生了变更,只会与评审表对象有关,我们不会去更改其它的对象。如果我们能做到这一点,我们的系统当然是可维护性好、易变更性好的系统。

那么,我们如何做到高内聚呢?就拿前面我提到的评审项目举例。我现在要为“评审表”对象编写一段填写并保存评审表的代码。评审表对象的职责是更新和查询评审表的数据,但是在显示一个要填写的评审表的时候,我需要显示该评审计划的名称、该评审计划有哪些评审对象需要评审。现在我如何编写显示一个要填写的评审表的代码?我在评审表对象的这个相应的函数中编写一段查询评审计划和评审对象的代码吗?假如你这样做了,你的代码就不是高内聚的,因为查询评审计划和评审对象的数据不是它的职责。正确的方法应当去请求“评审计划”对象和“评审对象”对象来完成这些工作,而“评审表”对象只是获取其结果。

另外,如果一个对象要完成一个虽然在自己职责范围内,但过程非常复杂的任务时,也应当将该任务分解成数个功能相对独立的子函数来完成。我曾经看见一个朋友写的数百行的一个函数,让人读起来非常费劲。同时这样的函数中一些相对独立的代码,本可以复用到其它代码中,也变成了不可能。所以我给大家的建议是,不要写太长的函数,超过一百行就可以考虑将一些功能分解出去。

与“低耦合”一样,高内聚也不是一个绝对,而是一个相对的指标,应当适当而不能过度。正如我们在现实生活中,如果在一个十来人的小公司,每个人的分工可能会粗一些,所分配的职责会广一些杂一些,因为其总体的任务少;而如果在一个一两百人的大公司,每个人的分工会细一些,所分配的任务会更加专一些,因为总体任务多,更需要专业化的分工来提高效率。软件开发也是一样,如果“评审计划”对象完成的业务功能少,并且不复杂,它完全可以代理它的子表“评审对象”和“评审者”的管理。但是“评审计划”对象需要完成的“对评审计划表的管理”这个基本职责包含的业务功能繁多或者复杂,它就应当将“对评审对象表的管理”交给“评审对象”对象,将“对评审者表的管理”交给“评审者”对象。同样,高内聚的可维护性好、易变更性好只能是一个相对的指标。如果一个变更的确是大范围的变更,你永远不可能通过内聚就不进行大范围的变更了。同时内聚也是要付出代价的,所以你也不必要去为了一个不太可能的变更去进行过度设计,应当掌握一个度。过度的内聚必将增加系统中元素之间的依赖,提高耦合度。所以“高内聚”与“低耦合”是矛盾的,必须权衡利弊,综合地去处理。在李洋等人翻译的《UML和模式应用》中,将内聚和耦合翻译为软件工程中的阴与阳,是中国人对内聚和耦合的最佳解释。

综上所述,“高内聚”给软件项目带来的优点是:可读性强、易维护和变更、支持低耦合、移植和重用性强。

 

一个优秀软件开发人员的必修课:GRASP2)低耦合

关键字: 设计模式       

我偶然在googleyahoo这样的搜索引擎搜索GRASP发现,除了国外的网站,国内网站多介绍和讨论GoF而很少介绍GRASP,即使这少量的文章也讲解非常粗略。个人认为作为优秀的开发人员,理解GRASPGoF更重要,故写此文章。前面我在《 (原创)一个优秀软件开发人员的必修课:GRASP软件开发模式浅析》中介绍了使用GRASP的目的,今天允许我调换一下顺序,先从低耦合讲起,因为诸如创建者模式、信息专家模式的根本目的就是降低耦合。

1.    低耦合(Low Coupling

“低耦合”这个词相信大家已经耳熟能详,我们在看spring的书籍、MVC的数据、设计模式的书籍,无处不提到“低耦合、高内聚”,它已经成为软件设计质量的标准之一。那么什么是低耦合?耦合就是对某元素与其它元素之间的连接、感知和依赖的量度。这里所说的元素,即可以是功能、对象(类),也可以指系统、子系统、模块。假如一个元素A去连接元素B,或者通过自己的方法可以感知B,或者当B不存在的时候就不能正常工作,那么就说元素A与元素B耦合。耦合带来的问题是,当元素B发生变更或不存在时,都将影响元素A的正常工作,影响系统的可维护性和易变更性。同时元素A只能工作于元素B存在的环境中,这也降低了元素A的可复用性。正因为耦合的种种弊端,我们在软件设计的时候努力追求“低耦合”。低耦合就是要求在我们的软件系统中,某元素不要过度依赖于其它元素。请注意这里的“过度”二字。系统中低耦合不能过度,比如说我们设计一个类可以不与JDK耦合,这可能吗?除非你不是设计的Java程序。再比如我设计了一个类,它不与我的系统中的任何类发生耦合。如果有这样一个类,那么它必然是低内聚(关于内聚的问题我随后讨论)。耦合与内聚常常是一个矛盾的两个方面。最佳的方案就是寻找一个合适的中间点。

哪些是耦合呢?

1.元素B是元素A的属性,或者元素A引用了元素B的实例(这包括元素A调用的某个方法,其参数中包含元素B)。

2.元素A调用了元素B的方法。

3.元素A 没有评论 »

epoll

十二月 11, 2008 | linux, 网站架构 | RSS 2.0

inux下Epoll的简单介绍

Q:网络服务器的瓶颈在哪?
A:IO效率。
在大家苦苦的为在线人数的增长而导致的系统资源吃紧上的问题正在发愁的时候,Linux 2.6内核中提供的System Epoll为我们提供了一套完美的解决方案。传统的select以及poll
的效率会因为在线人数的线形递增而导致呈二次乃至三次方的下降,这些直接导致了网络服务器可以支持的人数有了个比较明显的限制。
自从Linux提供了/dev/epoll的设备以及后来2.6内核中对/dev/epoll设备的访问的封装(SystemEpoll)之后,这种现象得到了大大的缓解,如果说几个月前,大家还对epoll不熟悉,那么现在来说的话,epoll的应用已经得到了大范围的普及。
那么究竟如何来使用epoll呢?其实非常简单。
通过在包含一个头文件#include 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。
 
首先通过create_epoll(int maxfds)来创建一个epoll的句柄,
其中maxfds为你epoll所支持的最大句柄数。
这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。
在用完之后,记得用close()来关闭这个创建出来的epoll句柄。
之后在你的网络主循环里面,
每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。
基本的语法为: nfds = epoll_wait(kdpfd, events, maxevents, -1);
其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件范围,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则范围。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。
epoll_wait范围之后应该是一个循环,遍利所有的事件:

for(n = 0; n < nfds; ++n)
{
    if(events[n].data.fd == listener) {
//如果是主socket的事件的话,则表示有新连接进入了,进行新连接的处理。

        client = accept(listener, (struct sockaddr *) &local, &addrlen);
        if(client < 0){
            perror("accept");
            continue;
        }
        setnonblocking(client);
// 将新连接置于非阻塞模式 
        ev.events = EPOLLIN | EPOLLET;
// 并且将新连接也加入EPOLL的监听队列。 注意,这里的参数EPOLLIN | EPOLLET并没有设置对写socket的监听,如果有写操作的话,这个时候epoll是不会返回事件的,如果要对写操作也监听的话,应该是EOLLIN | EPOLLOUT | EPOLLET
        ev.data.fd = client;
        if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) {
            /*设置好event之后,将这个新的event通过epoll_ctl加入到epoll的监听队列里面,这里用EPOLL_CTL_ADD来加一个新的epoll事件,通过POLL_CTL_DEL来减少一个epoll事件,通过EPOLL_CTL_MOD来改变一个事件的监听方式。 */
            fprintf(stderr, "epoll set insertion error: fd=%d0,
    client);
   return -1;
  }
 }
 else // 如果不是主socket的事件的话,则代表是一个用户socket的事件,则来处理这个用户socket的事情,比如说read(fd,xxx)之类的,或者一些其他的处理。
  do_use_fd(events[n].data.fd);
}

对,epoll的操作就这么简单,总共不过4个API:epoll_create, epoll_ctl, epoll_wait和close。

没有评论 »

Unix,进程,线程,同步,互斥,通信

十一月 12, 2008 | 网站架构 | RSS 2.0

Unix,进程,线程,同步,互斥,通信
2008年11月09日 星期日 11:49

Unix线程同步:
* 在POSIX线程(pthread)情况下

   1. 互斥量:pthread_mutex_t

          相关函数:    PTHREAD_MUTEX_INITIALIZER
                              phread_mutex_init
                             phread_mutex_destroy
                             phread_mutex_lock
                             phread_mutex_trylock
                             phread_mutex_unlock


   2. 条件变量:pthread_cond_t

          相关函数:    PTHREAD_COND_INITIALIZER
                              pthread_cond_init
                            pthread_cond_destroy
                             pthread_cond_wait
                             pthread_cond_timedwait
                             pthread_cond_signal
                             pthread_cond_broadcast


   3. 读写锁:pthread_rwlock_t      

          相关函数:   pthread_rwlock_init
                             pthread_rwlock_destroy
                             pthread_rwlock_wrlock
                             pthread_rwlock_rdlock
                             pthread_rwlock_trywrlock
                             pthread_rwlock_tryrdlock
                             pthread_rwlock_unlock

Unix进程/线程同步:

      1. 信号量/信号灯semaphore
              特点: 信号量主要作为进程间以及同进程不同线程之间的同步手段。
              相关函数: semget
                                semctl
                               semop

Unix间通信:

       1. 管道

               特点: a. 历史上是半双工的;
                           b. 只能在具有公共祖先的进程之间使用;
                            c. 没有名字;
                           d. 只能承载无格式字节流;
                            e. 缓冲区大小受限.

               相关函数:   int pipe(int filedes[2] );
                                   FILE * popen(const char * cmdstring, const char * type);
                                   int pclose(FILE * fp);

         
      2. FIFO

                特点:a. 能在不同的进程之间使用;
                          b.有名字,在文件系统中有对应的文件名。

                  相关函数: mkfifo();

      3. socket
             
更为一般的进程间通信机制,可用于不同机器之间的进程间通信。

      4. XSI IPC
                * XSI IPC源自system V的IPC功能
                 * XSI IPC不使用文件系统名字空间,而是构造了它们自己的名字空间。
                * XSI IPC不使用文件描述符,所以不能对它们使用多路转接I/O函数select和poll。

             (1)消息队列             
                           特点: a.消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
                b.用户可以自定义消息结构

                           相关函数:msgget
                                            msgsnd
                                            msgctl
                                            msgrcv          

             (2)信号量semaphore

                           相关函数:semget
                                            semop
                                            semctl

             (3)共享存储(共享内存)
                    
    特点: a. 最快的IPC形式;
                                     b. 针对其他通信机制运行效率较低而设计的;
                                    c. 往往与其他通信机制(如信号量)结合使用,以达到进程间的同步及互斥;

                         相关函数: shmget                                          
                                           shmat                          
                                           shmdt
                                          shmctl

       5. STREAMS

       6. 信号signal
              a. 信号是软件中断            
              b.
承载信息量少
              c. 信号提供了一种处理异步事件的方法

没有评论 »