Memcache 调研

官网:http://memcached.org/

文档:http://code.google.com/p/memcached/wiki/NewStart?tm=6

这位仁兄收集了些文章:

分布式缓存系统Memcached简介与实践

Memcached深度分析 [好文,必读]

自己实现memcached客户端库

memcached server LRU 深入分析[这个针对实际问题阐述memcached内存存储的调整,回头再看Memcached深度分析 ]

Memcached 原理和使用详解(PPT/PDF) [这个写的很好,必读]

Memcached使用点滴 [这个需要对memcache有一定了解之后,打算自己实现client可做借鉴]

memcached全面剖析–PDF总结篇 [very good,日本mixi网站工程师写的,国人翻译的]

 

api client 列表 http://code.google.com/p/memcached/wiki/Clients

参见http://kazge.com/archives/753.html 

memcached 安装见http://www.ccvita.com/257.html

以下是我的安装命令[需要root权限]:

------------------------------------------------------------------------------------------------------------

#安装libevent

cd /tmp

wget http://memcached.googlecode.com/files/memcached-1.4.13.tar.gz

wget --no-check-certificate https://github.com/downloads/libevent/libevent/libevent-2.0.17-stable.tar.gz

tar zxvf libevent-2.0.17-stable.tar.gz

cd libevent-2.0.17-stable

./configure --prefix=/usr

make

make install

#检查是否安装

ls -al /usr/lib | grep libevent

#安装memcached

tar zxvf memcached-1.4.13.tar.gz

cd memcached-1.4.13

./configure --with-libevent=/usr

make

make install

#测试是否成功安装memcached:

ls -al /usr/local/bin/mem*

#查看12000端口是否被占用

netstat -tl | grep 12000

#启动并将进程号存入memcached.pid

/usr/local/bin/memcached -d -m 10 -u root -l 192.168.0.200 -p 12000 -c 256 -P memcached.pid

#查看配置状态

echo "stats settings" | nc localhost 12000

#查看内存分块状态

echo "stats slabs" | nc localhost 12000

#查看健康状况

echo "stats" | nc localhost 12000

详细参数列表:

-p <num>      TCP port number to listen on (default: 11211)
-U <num>      UDP port number to listen on (default: 11211, 0 is off)
-s <file>     UNIX socket path to listen on (disables network support)
-a <mask>     access mask for UNIX socket, in octal (default: 0700)
-l <addr>     interface to listen on (default: INADDR_ANY, all addresses)
              <addr> may be specified as host:port. If you don't specify
              a port number, the value you specified with -p or -U is
              used. You may specify multiple addresses separated by comma
              or by using -l multiple times
-d            run as a daemon
-r            maximize core file limit
-u <username> assume identity of <username> (only when run as root)
-m <num>      max memory to use for items in megabytes (default: 64 MB)
-M            return error on memory exhausted (rather than removing items)
-c <num>      max simultaneous connections (default: 1024)
-k            lock down all paged memory.  Note that there is a
              limit on how much memory you may lock.  Trying to
              allocate more than that would fail, so be sure you
              set the limit correctly for the user you started
              the daemon with (not for -u <username> user;
              under sh this is done with 'ulimit -S -l NUM_KB').
-v            verbose (print errors/warnings while in event loop)
-vv           very verbose (also print client commands/reponses)
-vvv          extremely verbose (also print internal state transitions)
-h            print this help and exit
-i            print memcached and libevent license
-P <file>     save PID in <file>, only used with -d option
-f <factor>   chunk size growth factor (default: 1.25)
-n <bytes>    minimum space allocated for key+value+flags (default: 48)
-L            Try to use large memory pages (if available). Increasing
              the memory page size could reduce the number of TLB misses
              and improve the performance. In order to get large pages
              from the OS, memcached will allocate the total item-cache
              in one large chunk.
-D <char>     Use <char> as the delimiter between key prefixes and IDs.
              This is used for per-prefix stats reporting. The default is
              ":" (colon). If this option is specified, stats collection
              is turned on automatically; if not, then it may be turned on
              by sending the "stats detail on" command to the server.
-t <num>      number of threads to use (default: 4)
-R            Maximum number of requests per event, limits the number of
              requests process for a given connection to prevent 
              starvation (default: 20)
-C            Disable use of CAS
-b            Set the backlog queue limit (default: 1024)
-B            Binding protocol - one of ascii, binary, or auto (default)
-I            Override the size of each slab page. Adjusts max item size
              (default: 1mb, min: 1k, max: 128m)
-o            Comma separated list of extended or experimental options
              - (EXPERIMENTAL) maxconns_fast: immediately close new
                connections if over maxconns limit
              - hashpower: An integer multiplier for how large the hash
                table should be. Can be grown at runtime if not big enough.
                Set this based on "STAT hash_power_level" before a 
                restart.

注意-f  增量 -n chunck块初始大小[默认1024b] -I slab page大小[不能超过slab大小,1k—128M,默认1M]

------------------------------------------------------------------------------------------------------------

以下摘自: Memcached 原理和使用详解(PPT/PDF) 

数据存储方式:Slab Allocation

Slab Alloction 构造图:

image

Slab Allocator的基本原理是按照预先规定的大小,将分配的内存分割成特定长度的块,以完全解决内存碎片问题。

Slab Allocation的原理相当简单。 将分配的内存分割成各种尺寸的块(chunk),并把尺寸相同的块分成组(chunk的集合)

Slab Classes 分配图

image

Page:分配给Slab的内存空间,默认是1MB。分配给Slab之后根据slab的大小切分成chunk。

Chunk:用于缓存记录的内存空间。

注意:摘自http://y.jmeye.com/?aid=185

Slab是一个内存块,它是memcached一次申请内存的最小单位。 在启动memcached的时候一般会使用参数-m指定其可用内存,但是并不是在启动的那一刻所有的内存就全部分配出去了,只有在需要的时候才会去申请, 而且每次申请一定是一个slab。Slab的大小固定为1M(1048576 Byte),一个slab由若干个大小相等的chunk组成。每个chunk中都保存了一个item结构体、一对key和value。

注意:

chunk中不仅保持了缓存对象的value,而且保存了缓存对象的key,expire time, flag等详细信息

参见: http://tank.blogs.tkiicpp.com/category/programming/memcache/

Slab Class:特定大小的chunk的组。

memcached根据收到的数据的大小,选择最适合数据大小的slab。

memcached中保存着slab内空闲chunk的列表,根据该列表选择chunk,然后将数据缓存于其中。

Slab Alloction 缺点

image

这个问题就是,由于分配的是特定长度的内存,因此无法有效利用分配的内存。例如,将100字节的数据缓存到128字节的chunk中,剩余的28字节就浪费了。

 

以下摘自: http://kazge.com/archives/750.html

在1.2中,chunk大小表示为初始大小*f^n,f为factor,在memcached.c中定义,n为classid,同时,201个头不是全部 都要初始化的,因为factor可变,初始化只循环到计算出的大小达到slab大小的一半为止,而且它是从id1开始的,即:id为1的slab,每 chunk大小80字节,id为2的slab,每chunk大小80*f,id为3的slab,每chunk大小80*f^2,初始化大小有一个修正值 CHUNK_ALIGN_BYTES,用来保证n-byte排列 (保证结果是CHUNK_ALIGN_BYTES的整倍数)。这样,在标准情况下,memcached1.2会初始化到id40,这个slab中每个 chunk大小为504692,每个slab中有两个chunk。最后,slab_init函数会在最后补足一个id41,它是整块的,也就是这个 slab中只有一个1MB大的chunk
 

以下摘自:

http://blog.csdn.net/jarfield/article/details/4322953

http://blog.csdn.net/jarfield/article/details/4336035

http://blog.csdn.net/jarfield/article/details/4341819

所有的被发送到memcached的单个命令是完全原子的。如果您针对同一份数据同时发送了一个set命令和一个get命令,它们不会影响对方。它们将被串行化、先后执行。

通常,基于memcached中item的值来修改item,是一件棘手的事情。除非您很清楚自己在做什么,否则请不要做这样的事情。

 
以下摘自: http://www.iteye.com/topic/225692
memcached中新的value过来存放的地址是该value的大小决定的,value总是会被选择存放到chunk与其最接近的一个slab中,比如上面的例子,如果我的value是80b,那么我这所有的value总是会被存放到1号slab中,而1号slab中的free_chunks已经是0了,怎么办呢,如果你在启动memcached的时候没有追加-M(禁止LRU,这种情况下内存不够时会out of memory),那么memcached会把这个slab中最近最少被使用的chunk中的数据清掉,然后放上最新的数据。这就解释了为什么我的内存还有40%的时候LRU就执行了,因为我的其他slab中的chunk_size都远大于我的value,所以我的value根本不会放到那几个slab中,而只会放到和我的value最接近的chunk所在的slab中(而这些slab早就满了,郁闷了)。这就导致了我的数据被不停的覆盖,后者覆盖前者。
 
我总结一下:
逻辑上的item 存于chunk中,相同大小chunk组成slab(chunk初始值由-n配置), 各个slab大小是一致的(-I配置),但是slab1和slab2其内部的chunk大小是不同的,slab2内部的chunk大小是slab1内部chunk大小的 f增量子倍(-f配置),另外slab page是将slab划分的内存块,一个slab可划分为多个page,每个page下存储多个chunk
逻辑元素: slab,item
内存元素:page,chunk
这些可通过memcached的状态打印输出看出来

STAT 1:chunk_size 80

STAT 1:chunks_per_page 13107

STAT 1:total_pages 1

STAT 1:total_chunks 13107

STAT 1:used_chunks 13107

STAT 1:free_chunks 0

STAT 1:free_chunks_end 13107


•在 Memcached 中可以保存的item数据量是没有限制的,只有内存足够

• 最大键长为250字节,大于该长度无法存储,常量代码中KEY_MAX_LENGTH 250 控制

• 单个item最大数据默认是1MB,超过1MB数据不予存储,常量代码POWER_BLOCK 1048576 进行控制,参数-I控制的是slab page大小,因此它不能超过slab大小,slab大小只能通过编译源码来改。

32位系统下Memcached单进程最大使用内存为2G,要使用更多内存,可以分多个端口开启多个Memcached进程或改为64位系统。

-----------------------------------------------------------------------------

它的接口文档见http://code.google.com/p/memcached/wiki/NewCommands

其中有些还没被Memcached-Java-Client实现,比如cas,可能是新加的接口。

memcached提供的接口目标就是简单,其用起来可以说简单,但是不简单的地方是什么呢,当然他的内部机理比较复杂,最主要的是实际应用场景的相关问题。

你做数据缓存,那么哪些数据应该缓存?怎么样缓存?过期策略?

做session集群共享,要不要保证可靠性?

很多时候不该memcached做的事情也让它来做了,这时可考虑下nosql地盘的东东了。

可参考转载的这篇文章:http://kazge.com/archives/757.html

Continue reading Memcache 调研

ThreadLocal 使用搞明白

我最初使用ThreadLocal是copy Myfaces中的源码,为了保存Httprequest对象,现在想一想,总是听说ThreadLocal是解决并发安全性的好方法,这其中有个道理还是没想清楚:

ThreadLocal只是维护资源副本,因此对本线程资源的改变是不会对其他线程起作用的。

那么我们为什么用它呢,最常用的情况是维护Singleton对象,即一个线程内所有对此对象的访问都可以做到是一个对象,这与类静态变量维护的Sington对象是不同的。

可参考:Hibernate的SessionFactory,IBatis的SqlClient

参见文章:

ThreadLocal如何解决并发安全性 http://www.java3z.com/cwbwebhome/article/article8/81086.html

    前面我们介绍了Java当中多个线程抢占一个共享资源的问题。但不论是同步还是重入锁,都不能实实在在的解决资源紧缺的情况,这些方案只是靠制定规则来约束线程的行为,让它们不再拼命的争抢,而不是真正从实质上解决他们对资源的需求。
在JDK 1.2当中,引入了java.lang.ThreadLocal。它为我们提供了一种全新的思路来解决线程并发的问题。但是他的名字难免让我们望文生义:本地线程?
什么是本地线程?
本地线程开玩笑的说:不要迷恋哥,哥只是个传说。
   其实ThreadLocal并非Thread at Local,而是LocalVariable in a Thread。
根据WikiPedia上的介绍,ThreadLocal其实是源于一项多线程技术,叫做Thread Local Storage,即线程本地存储技术。不仅仅是Java,在C++、C#、.NET、Python、Ruby、Perl等开发平台上,该技术都已经得以实现。

   当使用ThreadLocal维护变量时,它会为每个使用该变量的线程提供独立的变量副本。也就是说,他从根本上解决的是资源数量的问题,从而使得每个线程持有相对独立的资源。这样,当多个线程进行工作的时候,它们不需要纠结于同步的问题,于是性能便大大提升。但资源的扩张带来的是更多的空间消耗,ThreadLocal就是这样一种利用空间来换取时间的解决方案。
说了这么多,来看看如何正确使用ThreadLocal。
通过研究JDK文档,我们知道,ThreadLocal中有几个重要的方法:get()、set()、remove()、initailValue(),对应的含义分别是:
返回此线程局部变量的当前线程副本中的值、将此线程局部变量的当前线程副本中的值设置为指定值、移除此线程局部变量当前线程的值、返回此线程局部变量的当前线程的“初始值”。
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
还记得我们在第三篇的上半节引出的那个例子么?几个线程修改同一个Student对象中的age属性。为了保证这几个线程能够工作正常,我们需要对Student的对象进行同步。
下面我们对这个程序进行一点小小的改造,我们通过继承Thread来实现多线程:

/**
*
* @author x-spirit
*/
public class ThreadDemo3 extends Thread{
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>();
public ThreadDemo3(Student stu){
        stuLocal.set(stu);
    }
public static void main(String[] args) {
        Student stu = new Student();
        ThreadDemo3 td31 = new ThreadDemo3(stu);
        ThreadDemo3 td32 = new ThreadDemo3(stu);
        ThreadDemo3 td33 = new ThreadDemo3(stu);
        td31.start();
        td32.start();
        td33.start();
    }
    @Override
public void run() {
        accessStudent();
    }
public void accessStudent() {
        String currentThreadName = Thread.currentThread().getName();
        System.out.println(currentThreadName + " is running!");
        Random random = new Random();
int age = random.nextInt(100);
        System.out.println("thread " + currentThreadName + " set age to:" + age);
        Student student = stuLocal.get();
        student.setAge(age);
        System.out.println("thread " + currentThreadName + " first  read age is:" + student.getAge());
try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
    }
}

转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
貌似这个程序没什么问题。但是运行结果却显示:这个程序中的3个线程会抛出3个空指针异常。读者一定感到很困惑。我明明在构造器当中把Student对象set进了ThreadLocal里面阿,为什么run起来之后居然在调用stuLocal.get()方法的时候得到的是NULL呢?
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
带着这个疑问,让我们深入到JDK的代码当中,去一看究竟。
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
原来,在ThreadLocal中,有一个内部类叫做ThreadLocalMap。这个ThreadLocalMap并非java.util.Map的一个实现,而是利用java.lang.ref.WeakReference实现的一个键-值对应的数据结构其中,key是ThreadLocal类型,而value是Object类型,我们可以简单的视为HashMap<ThreadLocal,Object>。
而在每一个Thread对象中,都有一个ThreadLocalMap的引用,即Thread.threadLocals。而ThreadLocal的set方法就是首先尝试从当前线程中取得ThreadLocalMap(以下简称Map)对象。如果取到的不为null,则以ThreadLocal对象自身为key,来取Map中的value。如果取不到Map对象,则首先为当前线程创建一个ThreadLocalMap,然后以ThreadLocal对象自身为key,将传入的value放入该Map中。

    ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
    }   
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
if (map != null)
            map.set(this, value);
else
            createMap(t, value);
    }

而get方法则是首先得到当前线程的ThreadLocalMap对象,然后,根据ThreadLocal对象自身,取出相应的value。当然,如果在当前线程中取不到ThreadLocalMap对象,则尝试为当前线程创建ThreadLocalMap对象,并以ThreadLocal对象自身为key,把initialValue()方法产生的对象作为value放入新创建的ThreadLocalMap中。

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
        }
return setInitialValue();
    }
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
if (map != null)
            map.set(this, value);
else
            createMap(t, value);
return value;
    }
protected T initialValue() {
return null;
    }

这样,我们就明白上面的问题出在哪里:我们在main方法执行期间,试图在调用ThreadDemo3的构造器时向ThreadLocal置入Student对象,而此时,以ThreadLocal对象为key,Student对象为value的Map是被放入当前的活动线程内的。也就是Main线程。而当我们的3个ThreadDemo3线程运行起来以后,调用get()方法,都是试图从当前的活动线程中取得ThreadLocalMap对象,但当前的活动线程显然已经不是Main线程了,于是,程序最终执行了ThreadLocal原生的initialValue()方法,返回了null。
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
讲到这里,我想不少朋友一定已经看出来了:ThreadLocal的initialValue()方法是需要被覆盖的。
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
于是,ThreadLocal的正确使用方法是:将ThreadLocal以内部类的形式进行继承,并覆盖原来的initialValue()方法,在这里产生可供线程拥有的本地变量值。
这样,我们就有了下面的正确例程:

/**
*
* @author x-spirit
*/
public class ThreadDemo3 extends Thread{
private ThreadLocal<Student> stuLocal = new ThreadLocal<Student>(){
        @Override
protected Student initialValue() {
return new Student();
        }
    };
public ThreadDemo3(){
    }
public static void main(String[] args) {
        ThreadDemo3 td31 = new ThreadDemo3();
        ThreadDemo3 td32 = new ThreadDemo3();
        ThreadDemo3 td33 = new ThreadDemo3();
        td31.start();
        td32.start();
        td33.start();
    }
    @Override
public void run() {
        accessStudent();
    }
public void accessStudent() {
        String currentThreadName = Thread.currentThread().getName();
        System.out.println(currentThreadName + " is running!");
        Random random = new Random();
int age = random.nextInt(100);
        System.out.println("thread " + currentThreadName + " set age to:" + age);
        Student student = stuLocal.get();
        student.setAge(age);
        System.out.println("thread " + currentThreadName + " first  read age is:" + student.getAge());
try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
        System.out.println("thread " + currentThreadName + " second read age is:" + student.getAge());
    }
}

可见,要正确使用ThreadLocal,必须注意以下几点:
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
1. 总是对ThreadLocal中的initialValue()方法进行覆盖。
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
2. 当使用set()或get()方法时牢记这两个方法是对当前活动线程中的ThreadLocalMap进行操作,一定要认清哪个是当前活动线程!
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
3. 适当的使用泛型,可以减少不必要的类型转换以及可能由此产生的问题。
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
运行该程序,我们发现:程序的执行过程只需要5秒,而如果采用同步的方法,程序的执行结果相同,但执行时间需要15秒。以前是多个线程为了争取一个资源,不得不在同步规则的制约下互相谦让,浪费了一些时间。
转载注明出处:http://x- spirit.javaeye.com/、http: //www.blogjava.net/zhangwei217245/
现在,采用ThreadLocal机制以后,可用的资源多了,你有我有全都有,所以,每个线程都可以毫无顾忌的工作,自然就提高了并发性,线程安全也得以保证。
当今很多流行的开源框架也采用ThreadLocal机制来解决线程的并发问题。比如大名鼎鼎的 Struts 2.x 和 Spring 等。
把ThreadLocal这样的话题放在我们的同步机制探讨中似乎显得不是很合适。但是ThreadLocal的确为我们解决多线程的并发问题带来了全新的思路。它为每个线程创建一个独立的资源副本,从而将多个线程中的数据隔离开来,避免了同步所产生的性能问题,是一种“以空间换时间”的解决方案。
但这并不是说ThreadLocal就是包治百病的万能药了。如果实际的情况不允许我们为每个线程分配一个本地资源副本的话,同步还是非常有意义的。

Continue reading ThreadLocal 使用搞明白

什么时候锻炼好?

最近国内流行锻炼的说法:下午4点到7点最好。

这不不是和我们一贯以来早上锻炼的习惯相反吗?

处于对国内砖家叫兽的了解,我特地查了一下国外的资料,有如下结论:

科学研究表明,下午4点到7点确实是人体较活跃的时间段,但是并不是说这个时间段锻炼就是最适合你的,实际上,你觉得什么时候锻炼适合你,那就是你锻炼的最好时间,贵在坚持!

国外研究表明,经常早上锻炼得人早上肌肉有较好的状态,晚上锻炼得人,晚上肌肉有较好的状态。

锻炼一个小时以内,可吃可不吃,但是一定要喝点水。

一个小时以上者,则一定要吃点食物,喝些水。

 

以下是我个人观点:

从我对中医的领悟来讲,冬天还是太阳出来一小时以后,太阳落山半小时之前这段时间锻炼较好,冬天主藏,不要将自己的身体暴露在肃杀寒气之中。也对应古人“日出而作日入而息”的生活规律。

参见:

http://www.naturallyintense.net/blog/personal-trainer-nyc-articles/when-is-the-best-time-to-exercise/

http://goaskalice.columbia.edu/best-time-day-exercise

Continue reading 什么时候锻炼好?

【转】Apache Rewrite 规则详解(很不错)

转自:http://www.iocblog.net/blog/site/apache-rewrite-all.html

1、Rewrite规则简介:

Rewirte主要的功能就是实现URL的跳转,它的正则表达式是基于Perl语言。可基于服务器级的(httpd.conf)和目录级的 (.htaccess)两种方式。如果要想用到rewrite模块,必须先安装或加载rewrite模块。方法有两种一种是编译apache的时候就直接安装rewrite模块,别一种是编译apache时以DSO模式安装apache,然后再利用源码和apxs来安装rewrite模块。

基于服务器级的(httpd.conf)有两种方法,一种是在httpd.conf的全局下直接利用RewriteEngine on来打开rewrite功能;另一种是在局部里利用RewriteEngine on来打开rewrite功能,下面将会举例说明,需要注意的是,必须在每个virtualhost里用RewriteEngine on来打开rewrite功能。否则virtualhost里没有RewriteEngine on它里面的规则也不会生效。

基于目录级的(.htaccess),要注意一点那就是必须打开此目录的FollowSymLinks属性且在.htaccess里要声明RewriteEngine on。

细讲每一条规则:

RewriteRule

Syntax: RewriteRule Pattern Substitution [flags]

  一条RewriteRule指令,定义一条重写规则,规则间的顺序非常重要。对Apache1.2及以后的版本,模板(pattern)是一个POSIX正则式,用以匹配当前的URL。当前的URL不一定是用记最初提交的URL,因为可能用一些规则在此规则前已经对URL进行了处理。

  对mod_rewrite来说,!是个合法的模板前缀,表示“非”的意思,这对描述“不满足某种匹配条件”的情况非常方便,或用作最后一条默认规则。当使用!时,不能在模板中有分组的通配符,也不能做后向引用。

  当匹配成功后,Substitution会被用来替换相应的匹配,它除了可以是普通的字符串以外,还可以包括:

$N,引用RewriteRule模板中匹配的相关字串,N表示序号,N=0..9

%N,引用最后一个RewriteCond模板中匹配的数据,N表示序号

%{VARNAME},服务器变量

${mapname:key|default},映射函数调用

这些特殊内容的扩展,按上述顺序进行。

  一个URL的全部相关部分都会被Substitution替换,而且这个替换过程会一直持续到所有的规则都被执行完,除非明确地用L标志中断处理过程。

  当susbstitution有”-”前缀时,表示不进行替换,只做匹配检查。

  利用RewriteRule,可定义含有请求串(Query String)的URL,此时只需在Sustitution中加入一个?,表示此后的内容放入QUERY_STRING变量中。如果要清空一个QUERY_STRING变量,只需要以?结束Substitution串即可。

  如果给一个Substitution增加一个http://thishost[:port]的前缀,则mod_rewrite会自动将此前缀去掉。因此,利用http://thisthost做一个无条件的重定向到自己,将难以奏效。要实现这种效果,必须使用R标志。

  Flags是可选参数,当有多个标志同时出现时,彼此间以逗号分隔。

'redirect|R [=code]' (强制重定向)

  给当前的URI增加前缀http://thishost[:thisport]/, 从而生成一个新的URL,强制生成一个外部重定向(external redirection,指生的URL发送到客户端,由客户端再次以新的URL发出请求,虽然新URL仍指向当前的服务器). 如果没有指定的code值,则HTTP应答以状态值302 (MOVED TEMPORARILY),如果想使用300-400(不含400)间的其它值可以通过在code的位置以相应的数字指定,也可以用标志名指定: temp (默认值), permanent, seeother.

  注意,当使用这个标志时,要确实substitution是个合法的URL,这个标志只是在URL前增加http://thishost[:thisport]/前缀而已,重写操作会继续进行。如果要立即将新URL重定向,用L标志来中重写流程。

'forbidden|F' (强制禁止访问URL所指的资源)

  立即返回状态值403 (FORBIDDEN)的应答包。将这个标志与合适的RewriteConds 联合使用,可以阻断访问某些URL。

'gone|G' (强制返回URL所指资源为不存在(gone))

  立即返回状态值410 (GONE)的应答包。用这个标志来标记URL所指的资源永久消失了.

# 'proxy|P' (强制将当前URL送往代理模块(proxy module))

  这个标志,强制将substitution当作一个发向代理模块的请求,并立即将共送往代理模块。因此,必须确保substitution串是一个合法的URI (如, 典型的情况是以http://hostname开头),否则会从代理模块得到一个错误. 这个标志,是ProxyPass指令的一个更强劲的实现,将远程请求(remote stuff)映射到本地服务器的名字空间(namespace)中来。

  注意,使用这个功能必须确保代理模块已经编译到Apache 服务器程序中了. 可以用“httpd -l ”命令,来检查输出中是否含有mod_proxy.c来确认一下。如果没有,而又需要使用这个功能,则需要重新编译``httpd''程序并使用mod_proxy有效。

'last|L' (最后一条规则)

  中止重写流程,不再对当前URL施加更多的重写规则。这相当于perl的last命令或C的break命令。

'next|N' (下一轮)

  重新从第一条重写规则开始执行重写过程,新开的过程中的URL不应当与最初的URL相同。 这相当于Perl的next命令或C的continue命令. 千万小心不要产生死循环。

# 'chain|C' (将当前的规则与其后续规则捆绑(chained))

  当规则匹配时,处理过程与没有捆绑一样;如果规则不匹配,则捆绑在一起的后续规则也不在检查和执行。

'type|T=MIME-type' (强制MIME类型)

  强制将目标文件的MIME-type为某MIME类型。例如,这可用来模仿mod_alias模块对某目录的ScriptAlias指定,通过强制将该目录下的所有文件的类型改为 “application/x-httpd-cgi”.

'nosubreq|NS' (used only if no internal sub-request )

  这个标志强制重写引擎跳过为内部sub-request的重写规则.例如,当mod_include试图找到某一目录下的默认文件时 (index.xxx),sub-requests 会在Apache内部发生. Sub-requests并非总是有用的,在某些情况下如果整个规则集施加到它上面,会产生错误。利用这个标志可排除执行一些规则。

'nocase|NC' (模板不区分大小写)

  这个标志会使得模板匹配当前URL时忽略大小写的差别。

'qsappend|QSA' (追加请求串(query string))

  这个标志,强制重写引擎为Substitution的请求串追加一部分串,则不是替换掉原来的。借助这个标志,可以使用一个重写规则给请求串增加更多的数据。

'noescape|NE' (不对输出结果中的特殊字符进行转义处理)

  通常情况下,mod_write的输出结果中,特殊字符(如'%', '$', ';', 等)会转义为它们的16进制形式(如分别为'%25', '%24', and '%3B')。这个标志会禁止mod_rewrite对输出结果进行此类操作。 这个标志只能在 Apache 1.3.20及以后的版本中使用。

'passthrough|PT' (通过下一个处理器)

  这个标志强制重写引擎用filename字段的值来替换内部request_rec数据结构中uri字段的值。. 使用这个标志,可以使后续的其它URI-to-filename转换器的Alias、ScriptAlias、Redirect等指令,也能正常处理RewriteRule指令的输出结果。用一个小例子来说明它的语义:如果要用mod_rewrite的重写引擎将/abc转换为/def,然后用mod_alas将/def重写为ghi,则要:

RewriteRule ^/abc(.*) /def$1 [PT]

Alias /def /ghi

如果PT标志被忽略,则mod_rewrite也能很好完成工作,如果., 将 uri=/abc/... 转换为filename=/def/... ,完全符合一个URI-to-filename转换器的动作。接下来 mod_alias 试图做 URI-to-filename 转换时就会出问题。

注意:如果要混合都含有URL-to-filename转换器的不同的模块的指令,必须用这个标志。最典型的例子是mod_alias和mod_rewrite的使用。

'skip|S=num' (跳过后面的num个规则)

  当前规则匹配时,强制重写引擎跳过后续的num个规则。用这个可以来模仿if-then-else结构:then子句的最后一条rule的标志是skip=N,而N是else子句的规则条数。

'env|E=VAR:VAL' (设置环境变量)

  设置名为VAR的环境变量的值为VAL,其中VAL中可以含有正则式的后向引用($N或%N)。这个标志可以使用多次,以设置多个环境变量。这儿设置的变量,可以在多种情况下被引用,如在XSSI或CGI中。另外,也可以在RewriteCond模板中以%{ENV:VAR}的形式被引用。

注意:一定不要忘记,在服务器范围内的配置文件中,模板(pattern)用以匹配整个URL;而在目录范围内的配置文件中,目录前缀总是被自动去掉后再进行模板匹配的,且在替换完成后自动再加上这个前缀。这个功能对很多种类的重写是非常重要的,因为如果没有去前缀,则要进行父目录的匹配,而父目录的信息并不是总能得到的。一个例外是,当substitution中有http://打头时,则不再自动增加前缀了,如果P标志出现,则会强制转向代理。

注意:如果要在某个目录范围内启动重写引擎,则需要在相应的目录配置文件中设置“RewriteEngine on”,且目录的“Options FollowSymLinks”必须设置。如果管理员由于安全原因没有打开FollowSymLinks,则不能使用重写引擎。

2、举例说明:

下面是在一个虚拟主机里定义的规则。功能是把client请求的主机前缀不是www.colorme.com和203.81.23.202都跳转到主机前缀为http://www.colorme.com.cn,避免当用户在地址栏写入http://colorme.com.cn时不能以会员方式登录网站。

NameVirtualHost 192.168.100.8:80

ServerAdmin [email protected]
DocumentRoot "/web/webapp"
ServerName www.colorme.com.cn
ServerName colorme.com.cn
RewriteEngine on #打开rewirte功能
RewriteCond %{HTTP_HOST} !^www.colorme.com.cn [NC] #声明Client请求的主机中前缀不是www.colorme.com.cn,[NC]的意思是忽略大小写
RewriteCond %{HTTP_HOST} !^203.81.23.202 [NC] #声明Client请求的主机中前缀不是203.81.23.202,[NC]的意思是忽略大小写
RewriteCond %{HTTP_HOST} !^$ #声明Client请求的主机中前缀不为空,[NC]的意思是忽略大小写
RewriteRule ^/(.*) http://www.colorme.com.cn/ [L]
#含义是如果Client请求的主机中的前缀符合上述条件,则直接进行跳转到http://www.colorme.com.cn/,[L]意味着立即停止重写操作,并不再应用其他重写规则。这里的.*是指匹配所有URL中不包含换行字符,()括号的功能是把所有的字符做一个标记,以便于后面的应用.就是引用前面里的(.*)字符。

例二.将输入 folio.test.com 的域名时跳转到profile.test.com

listen 8080
NameVirtualHost 10.122.89.106:8080
ServerAdmin [email protected]
DocumentRoot "/usr/local/www/apache22/data1/"
ServerName profile.test.com
RewriteEngine on
RewriteCond %{HTTP_HOST} ^folio.test.com [NC]
RewriteRule ^/(.*) http://profile.test.com/ [L]

3.Apache mod_rewrite规则重写的标志一览

1) R[=code](force redirect) 强制外部重定向
强制在替代字符串加上http://thishost[:thisport]/前缀重定向到外部的URL.如果code不指定,将用缺省的302 HTTP状态码。
2) F(force URL to be forbidden)禁用URL,返回403HTTP状态码。
3) G(force URL to be gone) 强制URL为GONE,返回410HTTP状态码。
4) P(force proxy) 强制使用代理转发。
5) L(last rule) 表明当前规则是最后一条规则,停止分析以后规则的重写。
6) N(next round) 重新从第一条规则开始运行重写过程。
7) C(chained with next rule) 与下一条规则关联

如果规则匹配则正常处理,该标志无效,如果不匹配,那么下面所有关联的规则都跳过。

8) T=MIME-type(force MIME type) 强制MIME类型
9) NS (used only if no internal sub-request) 只用于不是内部子请求
10) NC(no case) 不区分大小写
11) QSA(query string append) 追加请求字符串
12) NE(no URI escaping of output) 不在输出转义特殊字符
例如:RewriteRule /foo/(.*) /bar?arg=P1\%3d$1 [R,NE] 将能正确的将/foo/zoo转换成/bar?arg=P1=zed
13) PT(pass through to next handler) 传递给下一个处理
例如:
   RewriteRule ^/abc(.*) /def$1 [PT] # 将会交给/def规则处理
   Alias /def /ghi
14) S=num(skip next rule(s)) 跳过num条规则
15) E=VAR:VAL(set environment variable) 设置环境变量

4.Apache rewrite例子集合

   在 httpd 中将一个域名转发到另一个域名虚拟主机世界近期更换了域名,新域名为 www.wbhw.com, 更加简短好记。这时需要将原来的域名webhosting-world.com, 以及论坛所在地址 webhosting-world.com/forums/定向到新的域名,以便用户可以找到,并且使原来的论坛 URL 继续有效而不出现 404 未找到,比如原来的http://www.webhosting-world.com/forums/-f60.html, 让它在新的域名下继续有效,点击后转发到http://bbs.wbhw.com/-f60.html, 这就需要用 apache 的 Mod_rewrite 功能来实现。

在中添加下面的重定向规则:

RewriteEngine On
# Redirect webhosting-world.com/forums to bbs.wbhw.com
RewriteCond %{REQUEST_URI} ^/forums/
RewriteRule /forums/(.*) http://bbs.wbhw.com/$1 [R=permanent,L]
# Redirect webhosting-world.com to wbhw.com
RewriteCond %{REQUEST_URI} !^/forums/
RewriteRule /(.*) http://www.wbhw.com/$1 [R=permanent,L]

添加了上面的规则以后, 里的全部内容如下:

ServerAlias webhosting-world.com
ServerAdmin [email protected]
DocumentRoot /path/to/webhosting-world/root
ServerName www.webhosting-world.com
RewriteEngine On
# Redirect webhosting-world.com/forums to bbs.wbhw.com
RewriteCond %{REQUEST_URI} ^/forums/
RewriteRule /forums/(.*) http://bbs.wbhw.com/$1 [R=permanent,L]
# Redirect webhosting-world.com to wbhw.com
RewriteCond %{REQUEST_URI} !^/forums/
RewriteRule /(.*) http://www.wbhw.com/$1 [R=permanent,L]

URL重定向

例子一:

1.http://www.zzz.com/xxx.php-> http://www.zzz.com/xxx/
2.http://yyy.zzz.com-> http://www.zzz.com/user.php?username=yyy 的功能
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.zzz.com
RewriteCond %{REQUEST_URI} !^user\.php$
RewriteCond %{REQUEST_URI} \.php$
RewriteRule (.*)\.php$ http://www.zzz.com/$1/ [R]
RewriteCond %{HTTP_HOST} !^www.zzz.com
RewriteRule ^(.+) %{HTTP_HOST} [C]
RewriteRule ^([^\.]+)\.zzz\.com http://www.zzz.com/user.php?username=$1

例子二:

/type.php?typeid=*   --> /type*.html
/type.php?typeid=*&page=*   --> /type*page*.html
RewriteRule ^/type([0-9]+).html$ /type.php?typeid=$1 [PT]
RewriteRule ^/type([0-9]+)page([0-9]+).html$ /type.php?typeid=$1&page=$2 [PT]

5.使用Apache的URL Rewrite配置多用户虚拟服务器

   要实现这个功能,首先要在DNS服务器上打开域名的泛域名解析(自己做或者找域名服务商做)。比如,我就把 *.semcase.com和 *.semcase.cn全部解析到了我的这台Linux Server上。

然后,看一下我的Apache中关于*.semcase.com的虚拟主机的设定。

#*.com,*.osall.net

ServerAdmin [email protected]
DocumentRoot /home/www/www.semcase.com
ServerName dns.semcase.com
ServerAlias dns.semcase.com semcase.com semcase.net *.semcase.com *.semcase.net
CustomLog /var/log/httpd/osa/access_log.log" common
    ErrorLog /var/log/httpd/osa/error_log.log"
AllowOverride None
Order deny,allow
#AddDefaultCharset GB2312   
RewriteEngine on      
RewriteCond %{HTTP_HOST}        ^[^.]+\.osall\.(com|net)$      
RewriteRule ^(.+)     %{HTTP_HOST}$1   [C]      
RewriteRule ^([^.]+)\.osall\.(com|net)(.*)$
/home/www/www.semcase.com/sylvan$3?un=$1&%{QUERY_STRING} [L]

在这段设定中,我把*.semcase.net和*.semcase.com 的Document Root都设定到了 /home/www/www.semcase.com

但是,继续看下去,看到...配置了吗?在这里我就配置了URL Rewrite规则。
RewriteEngine on #打开URL Rewrite功能
RewriteCond %{HTTP_HOST} ^[^.]+.osall.(com|net)$ #匹配条件,如果用户输入的URL中主机名是类似 xxxx.semcase.com 或者 xxxx.semcase.cn 就执行下面一句
RewriteRule ^(.+) %{HTTP_HOST}$1 [C] #把用户输入完整的地址(GET方式的参数除外)作为参数传给下一个规则,[C]是Chain串联下一个规则的意思
RewriteRule ^([^.]+).osall.(com|net)(.*)$ /home/www/dev.semcase.com/sylvan$3?un=$1&%{QUERY_STRING} [L]
# 最关键的是这一句,使用证则表达式解析用户输入的URL地址,把主机名中的用户名信息作为名为un的参数传给/home/www /dev.semcase.com目录下的脚本,并在后面跟上用户输入的GET方式的传入参数。并指明这是最后一条规则([L]规则)。注意,在这一句中指明的重写后的地址用的是服务器上的绝对路径,这是内部跳转。如果使用http://xxxx这样的URL格式,则被称为外部跳转。使用外部跳转的话,浏览着的浏览器中的URL地址会改变成新的地址,而使用内部跳转则浏览器中的地址不发生改变,看上去更像实际的二级域名虚拟服务器。

这样设置后,重启Apache服务器,测试一下,就大功告成了!

Continue reading 【转】Apache Rewrite 规则详解(很不错)

【转】Tumblr:150亿月浏览量背后的架构挑战

转自:http://cloud.csdn.net/a/20120214/311806.html

导读:和许多新兴的网站一样,著名的轻博客服务Tumblr在急速发展中面临了系统架构的瓶颈。每天5亿次浏览量,峰值每秒4万次请求,每天3TB新的数据存储,超过1000台服务器,这样的情况下如何保证老系统平稳运行,平稳过渡到新的系统,Tumblr正面临巨大的挑战。近日,HighScalability网站的Todd Hoff采访了该公司的分布式系统工程师Blake Matheny,撰文系统介绍了网站的架构,内容很有价值。我们也非常希望国内的公司和团队多做类似分享,贡献于社区的同时,更能提升自身的江湖地位,对招聘、业务发展都好处多多。欢迎通过@CSDN云计算的微博向我们投稿。

以下为译文的第一部分第二部分点这里。(括号内小号字为CSDN编辑所注)

Tumblr每月页面浏览量超过150亿次,已经成为火爆的博客社区。用户也许喜欢它的简约、美丽,对用户体验的强烈关注,或是友好而忙碌的沟通方式,总之,它深得人们的喜爱。

每月超过30%的增长当然不可能没有挑战,其中可靠性问题尤为艰巨。每天5亿次浏览量,峰值每秒4万次请求,每天3TB新的数据存储,并运行于超过1000台服务器上,所有这些帮助Tumblr实现巨大的经营规模。

创业公司迈向成功,都要迈过危险的迅速发展期这道门槛。寻找人才,不断改造基础架构,维护旧的架构,同时要面对逐月大增的流量,而且曾经只有4位工程师。这意味着必须艰难地选择应该做什么,不该做什么。这就是Tumblr的状况。好在现在已经有20位工程师了,可以有精力解决问题,并开发一些有意思的解决方案。

Tumblr最开始是非常典型的LAMP应用。目前正在向分布式服务模型演进,该模型基于ScalaHBaseRedis(著名开源K-V存储方案)、Kafka(Apache项目,出自LinkedIn的分布式发布-订阅消息系统)、Finagle(由Twitter开源的容错、协议中立的RPC系统),此外还有一个有趣的基于Cell的架构,用来支持Dashboard(CSDN注:Tumblr富有特色的用户界面,类似于微博的时间轴)。

Tumblr目前的最大问题是如何改造为一个大规模网站。系统架构正在从LAMP演进为最先进的技术组合,同时团队也要从小的创业型发展为全副武装、随时待命的正规开发团队,不断创造出新的功能和基础设施。下面就是Blake Matheny对Tumblr系统架构情况的介绍。

网站地址

http://www.tumblr.com/

主要数据
  •     每天5亿次PV(页面访问量)
  •     每月超过150亿PV
  •     约20名工程师
  •     峰值请求每秒近4万次
  •     每天超过1TB数据进入Hadoop集群
  •     MySQL/HBase/Redis/memcache每天生成若干TB数据
  •     每月增长30%
  •     近1000硬件节点用于生产环境
  •     平均每位工程师每月负责数以亿计的页面访问
  •     每天上传大约50GB的文章,每天跟帖更新数据大约2.7TB(CSDN注:这两个数据的比例看上去不太合理,据Tumblr数据科学家Adam Laiacano在Twitter上解释,前一个数据应该指的是文章的文本内容和元数据,不包括存储在S3上的多媒体内容)
软件环境
  •     开发使用OS X,生产环境使用Linux(CentOS/Scientific)
  •     Apache
  •     PHP, Scala, Ruby
  •     Redis, HBase, MySQL
  • Varnish, HAProxy, nginx
  •     memcache, Gearman(支持多语言的任务分发应用框架), Kafka, Kestrel(Twitter开源的分布式消息队列系统), Finagle
  •     Thrift, HTTP
  • Func——一个安全、支持脚本的远程控制框架和API
  •     Git, Capistrano(多服务器脚本部署工具), Puppet, Jenkins
硬件环境
  •     500台Web服务器
  •     200台数据库服务器(47 pool,20 shard)
  •     30台memcache服务器
  •     22台Redis服务器
  •     15台Varnish服务器
  •     25台HAproxy节点
  •     8台nginx服务器
  •     14台工作队列服务器(Kestrel + Gearman)
架构

    1. 相对其他社交网站而言,Tumblr有其独特的使用模式:

  •     每天有超过5千万篇文章更新,平均每篇文章的跟帖又数以百计。用户一般只有数百个粉丝。这与其他社会化网站里少数用户有几百万粉丝非常不同,使得Tumblr的扩展性极具挑战性。
  •     按用户使用时间衡量,Tumblr已经是排名第二的社会化网站。内容的吸引力很强,有很多图片和视频,文章往往不短,一般也不会太长,但允许写得很长。文章内容往往比较深入,用户会花费更长的时间来阅读。
  •     用户与其他用户建立联系后,可能会在Dashboard上往回翻几百页逐篇阅读,这与其他网站基本上只是部分信息流不同。
  •     用户的数量庞大,用户的平均到达范围更广,用户较频繁的发帖,这些都意味着有巨量的更新需要处理。

    2. Tumblr目前运行在一个托管数据中心中,已在考虑地域上的分布性。

    3. Tumblr作为一个平台,由两个组件构成:公共Tumblelogs和Dashboard

  •     公共Tumblelogs与博客类似(此句请Tumblr用户校正),并非动态,易于缓存
  •     Dashboard是类似于Twitter的时间轴,用户由此可以看到自己关注的所有用户的实时更新。与博客的扩展性不同,缓存作用不大,因为每次请求都不同,尤其是活跃的关注者。而且需要实时而且一致,文章每天仅更新50GB,跟帖每天更新2.7TB,所有的多媒体数据都存储在S3上面。
  •     大多数用户以Tumblr作为内容浏览工具,每天浏览超过5亿个页面,70%的浏览来自Dashboard。
  •     Dashboard的可用性已经不错,但Tumblelog一直不够好,因为基础设施是老的,而且很难迁移。由于人手不足,一时半会儿还顾不上。
老的架构

Tumblr最开始是托管在Rackspace上的,每个自定义域名的博客都有一个A记录。当2007年Rackspace无法满足其发展速度不得不迁移时,大量的用户都需要同时迁移。所以他们不得不将自定义域名保留在Rackspace,然后再使用HAProxy和Varnish路由到新的数据中心。类似这样的遗留问题很多。

开始的架构演进是典型的LAMP路线:

  • 最初用PHP开发,几乎所有程序员都用PHP
  • 最初是三台服务器:一台Web,一台数据库,一台PHP
  • 为了扩展,开始使用memcache,然后引入前端cache,然后在cache前再加HAProxy,然后是MySQL sharding(非常奏效)
  • 采用“在单台服务器上榨出一切”的方式。过去一年已经用C开发了两个后端服务:ID生成程序Staircar(用Redis支持Dashboard通知)

Dashboard采用了“扩散-收集”方式。当用户访问Dashboard时将显示事件,来自所关注的用户的事件是通过拉然后显示的。这样支撑了6个月。由于数据是按时间排序的,因此sharding模式不太管用。

新的架构

由于招人和开发速度等原因,改为以JVM为中心。目标是将一切从PHP应用改为服务,使应用变成请求鉴别、呈现等诸多服务之上的薄层。

这其中,非常重要的是选用了Scala和Finagle

  • 在团队内部有很多人具备Ruby和PHP经验,所以Scala很有吸引力。
  • Finagle是选择Scala的重要因素之一。这个来自Twitter的库可以解决大多数分布式问题,比如分布式跟踪、服务发现、服务注册等。
  • 转到JVM上之后,Finagle提供了团队所需的所有基本功能(Thrift, ZooKeeper等),无需再开发许多网络代码,另外,团队成员认识该项目的一些开发者。
  • Foursquare和Twitter都在用Finagle,Meetup也在用Scala。
  • 应用接口与Thrift类似,性能极佳。
  • 团队本来很喜欢Netty(Java异步网络应用框架,2月4日刚刚发布3.3.1最终版),但不想用Java,Scala是不错的选择。
  • 选择Finagle是因为它很酷,还认识几个开发者。

之所以没有选择Node.js,是因为以JVM为基础更容易扩展。Node的发展为时尚短,缺乏标准、最佳实践以及大量久经测试的代码。而用Scala的话,可以使用所有Java代码。虽然其中并没有多少可扩展的东西,也无法解决5毫秒响应时间、49秒HA、4万每秒请求甚至有时每秒40万次请求的问题。但是,Java的生态链要大得多,有很多资源可以利用。

内部服务从C/libevent为基础正在转向Scala/Finagle为基础。

开始采用新的NoSQL存储方案如HBase和Redis。但大量数据仍然存储在大量分区的MySQL架构中,并没有用HBase代替MySQL。HBase主要支持短地址生产程序(数以十亿计)还有历史数据和分析,非常结实。此外,HBase也用于高写入需求场景,比如Dashboard刷新时一秒上百万的写入。之所以还没有替换HBase,是因为不能冒业务上风险,目前还是依靠人来负责更保险,先在一些小的、不那么关键的项目中应用,以获得经验。MySQL和时间序列数据sharding(分片)的问题在于,总有一个分片太热。另外,由于要在slave上插入并发,也会遇到读复制延迟问题。

此外,还开发了一个公用服务框架

  • 花了很多时间解决分布式系统管理这个运维问题。
  • 为服务开发了一种Rails scaffolding,内部用模板来启动服务。
  • 所有服务从运维的角度来看都是一样的,所有服务检查统计数据、监控、启动和停止的方式都一样。
  • 工具方面,构建过程围绕SBT(一个Scala构建工具),使用插件和辅助程序管理常见操作,包括在Git里打标签,发布到代码库等等。大多数程序员都不用再操心构建系统的细节了。

200台数据库服务器中,很多是为了提高可用性而设,使用的是常规硬件,但MTBF(平均故障间隔时间)极低。故障时,备用充足。

为了支持PHP应用有6个后端服务,并有一个小组专门开发后端服务。新服务的发布需要两到三周,包括Dashboard通知、Dashboard二级索引、短地址生成、处理透明分片的memcache代理。其中在MySQL分片上耗时很多。虽然在纽约本地非常热,但并没有使用MongoDB,他们认为MySQL的可扩展性足够了。

Gearman用于会长期运行无需人工干预的工作。

可用性是以达到范围(reach)衡量的。用户能够访问自定义域或者Dashboard吗?也会用错误率。

历史上总是解决那些最高优先级的问题,而现在会对故障模式系统地分析和解决,目的是从用户和应用的角度来定成功指标。(后一句原文似乎不全)

最开始Finagle是用于Actor模型的,但是后来放弃了。对于运行后无需人工干预的工作,使用任务队列。而且Twitter的util工具库中有Future实现,服务都是用Future(Scala中的无参数函数,在与函数关联的并行操作没有完成时,会阻塞调用方)实现的。当需要线程池的时候,就将Future传入Future池。一切都提交到Future池进行异步执行。

Scala提倡无共享状态。由于已经在Twitter生产环境中经过测试,Finagle这方面应该是没有问题的。使用Scala和Finagle中的结构需要避免可变状态,不使用长期运行的状态机。状态从数据库中拉出、使用再写回数据库。这样做的好处是,开发人员不需要操心线程和锁。

22台Redis服务器,每台的都有8-32个实例,因此线上同时使用了100多个Redis实例。

  • Redis主要用于Dashboard通知的后端存储。
  • 所谓通知就是指某个用户like了某篇文章这样的事件。通知会在用户的Dashboard中显示,告诉他其他用户对其内容做了哪些操作。
  • 高写入率使MySQL无法应对。
  • 通知转瞬即逝,所以即使遗漏也不会有严重问题,因此Redis是这一场景的合适选择。
  • 这也给了开发团队了解Redis的机会。
  • 使用中完全没有发现Redis有任何问题,社区也非常棒。
  • 开发了一个基于Scala Futures的Redis接口,该功能现在已经并入了Cell架构。
  • 短地址生成程序使用Redis作为一级Cache,HBase作为永久存储。
  • Dashboard的二级索引是以Redis为基础开发的。
  • Redis还用作Gearman的持久存储层,使用Finagle开发的memcache代理。
  • 正在缓慢地从memcache转向Redis。希望最终只用一个cache服务。性能上Redis与memcache相当。

(先到这里吧,敬请期待下篇,包括如何用Kafaka、Scribe、Thrift实现内部活动流,Dashboard的Cell架构,开发流程和经验教训等精彩内容。)

翻译:包研,张志平,刘江;审校:刘江

 

---

内部的firehose(通信管道)

  • 内部的应用需要活跃的信息流通道。这些信息包括用户创建/删除的信息,liking/unliking的提示,等等。挑战在于这些数据要实时的分布式处理。我们希望能够检测内部运行状况,应用的生态系统能够可靠的生长,同时还需要建设分布式系统的控制中心。
  • 以前,这些信息是基于Scribe(Facebook开源的分布式日志系统。)/Hadoop的分布式系统。服务会先记录在Scribe中,并持续的长尾形式写入,然后将数据输送给应用。这种模式可以立即停止伸缩,尤其在峰值时每秒要创建数以千计的信息。不要指望人们会细水长流式的发布文件和grep。
  • 内部的firehose就像装载着信息的大巴,各种服务和应用通过Thrift与消防管线沟通。(一个可伸缩的跨语言的服务开发框架。)
  • LinkedIn的Kafka用于存储信息。内部人员通过HTTP链接firehose。经常面对巨大的数据冲击,采用MySQL显然不是一个好主意,分区实施越来越普遍。
  • firehose的模型是非常灵活的,而不像Twitter的firehose那样数据被假定是丢失的。
  •     firehose的信息流可以及时的回放。他保留一周内的数据,可以调出这期间任何时间点的数据。
  •     支持多个客户端连接,而且不会看到重复的数据。每个客户端有一个ID。Kafka支持客户群,每个群中的客户都用同一个ID,他们不会读取重复的数据。可以创建多个客户端使用同一个ID,而且不会看到重复的数据。这将保证数据的独立性和并行处理。Kafka使用ZooKeeper(Apache推出的开源分布式应用程序协调服务。)定期检查用户阅读了多少。

为Dashboard收件箱设计的Cell架构

  • 现在支持Dashboard的功能的分散-集中架构非常受限,这种状况不会持续很久。
  •     解决方法是采用基于Cell架构的收件箱模型,与Facebook Messages非常相似。
  •     收件箱与分散-集中架构是对立的。每一位用户的dashboard都是由其追随者的发言和行动组成的,并按照时间顺序存储。
  •     就因为是收件箱就解决了分散-集中的问题。你可以会问到底在收件箱中放了些什么,让其如此廉价。这种方式将运行很长时间。
  • 重写Dashboard非常困难。数据已经分布,但是用户局部升级产生的数据交换的质量还没有完全搞定。
  •     数据量是非常惊人的。平均每条消息转发给上百个不同的用户,这比Facebook面对的困难还要大。大数据+高分布率+多个数据中心。
  •     每秒钟上百万次写入,5万次读取。没有重复和压缩的数据增长为2.7TB,每秒百万次写入操作来自24字节行键。
  •     已经流行的应用按此方法运行。
  • cell
  •     每个cell是独立的,并保存着一定数量用户的全部数据。在用户的Dashboard中显示的所有数据也在这个cell中。
  •     用户映射到cell。一个数据中心有很多cell。
  •     每个cell都有一个HBase的集群,服务集群,Redis的缓存集群。
  •     用户归属到cell,所有cell的共同为用户发言提供支持。
  •     每个cell都基于Finagle(Twitter推出的异步的远程过程调用库),建设在HBase上,Thrift用于开发与firehose和各种请求与数据库的链接。(请纠错)
  •     一个用户进入Dashboard,其追随者归属到特定的cell,这个服务节点通过HBase读取他们的dashboard并返回数据。
  •     后台将追随者的dashboard归入当前用户的table,并处理请求。
  •     Redis的缓存层用于cell内部处理用户发言。
  • 请求流:用户发布消息,消息将被写入firehose,所有的cell处理这条消息并把发言文本写入数据库,cell查找是否所有发布消息追随者都在本cell内,如果是的话,所有追随者的收件箱将更新用户的ID。(请纠错
  • cell设计的优点:
  •     大规模的请求被并行处理,组件相互隔离不会产生干扰。 cell是一个并行的单位,因此可以任意调整规格以适应用户群的增长。
  •     cell的故障是独立的。一个Cell的故障不会影响其他cell。
  •     cell的表现非常好,能够进行各种升级测试,实施滚动升级,并测试不同版本的软件。
  • 关键的思想是容易遗漏的:所有的发言都是可以复制到所有的cell。
  •     每个cell中存储的所有发言的单一副本。 每个cell可以完全满足Dashboard呈现请求。应用不用请求所有发言者的ID,只需要请求那些用户的ID。(“那些用户”所指不清,请指正。)他可以在dashboard返回内容。每一个cell都可以满足Dashboard的所有需求,而不需要与其他cell进行通信。
  •     用到两个HBase table :一个table用于存储每个发言的副本,这个table相对较小。在cell内,这些数据将与存储每一个发言者ID。第二个table告诉我们用户的dashboard不需要显示所有的追随者。当用户通过不同的终端访问一个发言,并不代表阅读了两次。收件箱模型可以保证你阅读到。
  •     发言并不会直接进入到收件箱,因为那实在太大了。所以,发言者的ID将被发送到收件箱,同时发言内容将进入cell。这个模式有效的减少了存储需求,只需要返回用户在收件箱中浏览发言的时间。而缺点是每一个cell保存所有的发言副本。令人惊奇的是,所有发言比收件箱中的镜像要小。(请纠错)每天每个cell的发言增长50GB,收件箱每天增长2.7TB。用户消耗的资源远远超过他们制造的。
  •     用户的dashboard不包含发言的内容,只显示发言者的ID,主要的增长来自ID。(请Tumblr用户纠错)
  •     当追随者改变时,这种设计方案也是安全的。因为所有的发言都保存在cell中了。如果只有追随者的发言保存在cell中,那么当追随者改变了,将需要一些回填工作。
  •     另外一种设计方案是采用独立的发言存储集群。这种设计的缺点是,如果群集出现故障,它会影响整个网站。因此,使用cell的设计以及后复制到所有cell的方式,创建了一个非常强大的架构。
  • 一个用户拥有上百万的追随者,这带来非常大的困难,有选择的处理用户的追随者以及他们的存取模式(见Feeding Frenzy
  •     不同的用户采用不同并且恰当的存取模式和分布模型,两个不同的分布模式包括:一个适合受欢迎的用户,一个使用大众。
  •     依据用户的类型采用不同的数据处理方式,活跃用户的发言并不会被真正发布,发言将被有选择的体现。(果真如此?请Tumblr用户纠错)
  •     追随了上百万用户的用户,将像拥有上百万追随者的用户那样对待。
  • cell的大小非常难于决定。cell的大小直接影响网站的成败。每个cell归于的用户数量是影响力之一。需要权衡接受怎样的用户体验,以及为之付出多少投资。
  • 从firehose中读取数据将是对网络最大的考验。在cell内部网络流量是可管理的。
  • 当更多cell被增添到网络中来,他们可以进入到cell组中,并从firehose中读取数据。一个分层的数据复制计划。这可以帮助迁移到多个数据中心。

在纽约启动运作

  • 纽约具有独特的环境,资金和广告充足。招聘极具挑战性,因为缺乏创业经验。
  • 在过去的几年里,纽约一直致力于推动创业。纽约大学和哥伦比亚大学有一些项目,鼓励学生到初创企业实习,而不仅仅去华尔街。市长建立了一所学院,侧重于技术。

团队架构

  • 团队:基础架构,平台,SRE,产品,web ops,服务;
  • 基础架构:5层以下,IP地址和DNS,硬件配置;
  • 平台:核心应用开发,SQL分片,服务,Web运营;
  • SRE:在平台和产品之间,侧重于解决可靠性和扩展性的燃眉之急;
  • 服务团队:相对而言更具战略性,
  • Web ops:负责问题检测、响应和优化。

软件部署

  • 开发了一套rsync脚本,可以随处部署PHP应用程序。一旦机器的数量超过200台,系统便开始出现问题,部署花费了很长时间才完成,机器处于部署进程中的各种状态。
  • 接下来,使用Capistrano(一个开源工具,可以在多台服务器上运行脚本)在服务堆栈中构建部署进程(开发、分期、生产)。在几十台机器上部署可以正常工作,但当通过SSH部署到数百台服务器时,再次失败。
  • 现在,所有的机器上运行一个协调软件。基于Redhat Func(一个安全的、脚本化的远程控制框架和接口)功能,一个轻量级的API用于向主机发送命令,以构建扩展性。
  • 建立部署是在Func的基础上向主机发送命令,避免了使用SSH。比如,想在组A上部署软件,控制主机就可以找出隶属于组A的节点,并运行部署命令。
  • 部署命令通过Capistrano实施。

        Func API可用于返回状态报告,报告哪些机器上有这些软件版本。

  • 安全重启任何服务,因为它们会关闭连接,然后重启。
  • 在激活前的黑暗模式下运行所有功能。

展望

  • 从哲学上将,任何人都可以使用自己想要的任意工具。但随着团队的发展壮大,这些工具出现了问题。新员工想要更好地融入团队,快速地解决问题,必须以他们为中心,建立操作的标准化。
  • 过程类似于Scrum(一种敏捷管理框架),非常敏捷。
  • 每个开发人员都有一台预配置的开发机器,并按照控制更新。
  • 开发机会出现变化,测试,分期,乃至用于生产。
  • 开发者使用VIM和TextMate。
  • 测试是对PHP程序进行代码审核。
  • 在服务方面,他们已经实现了一个与提交相挂钩的测试基础架构,接下来将继承并内建通知机制。

招聘流程

  • 面试通常避免数学、猜谜、脑筋急转弯等问题,而着重关注应聘者在工作中实际要做什么。
  • 着重编程技能。
  • 面试不是比较,只是要找对的人。
  • 挑战在于找到具有可用性、扩展性经验的人才,以应对Tumblr面临的网络拥塞。
  • 在Tumblr工程博客(Tumblr Engineering Blog),他们对已过世的Dennis Ritchie和John McCarthy予以纪念。

经验及教训

  • 自动化无处不在
  • MySQL(增加分片)规模,应用程序暂时还不行
  • Redis总能带给人惊喜
  • 基于Scala语言的应用执行效率是出色的
  • 废弃项目——当你不确定将如何工作时
  • 不顾用在他们发展经历中没经历过技术挑战的人,聘用有技术实力的人是因为他们能适合你的团队以 及工作。
  • 选择正确的软件集合将会帮助你找到你需要的人
  • 建立团队的技能
  • 阅读文档和博客文章。
  • 多与同行交流,可以接触一些领域中经验丰富的人,例如与在Facebook、Twitter、LinkedIn的工程师 多交流,从他们身上可以学到很多
  • 对技术要循序渐进,在正式投入使用之前他们煞费苦心的学习HBase和Redis。同时在试点项目中使用 或将其控制在有限损害范围之内。

翻译:包研,张志平

Continue reading 【转】Tumblr:150亿月浏览量背后的架构挑战

【转】调用Sina股票数据

转自 调用Sina股票数据

sina股票数据接口
以大秦铁路(股票代码:601006)为例,如果要获取它的最新行情,只需访问新浪的股票数据
接口:http://hq.sinajs.cn/list=sh601006这个url会返回一串文本,例如:
var hq_str_sh601006="大秦铁路, 27.55, 27.25, 26.91, 27.55, 26.20, 26.91, 26.92,
22114263, 589824680, 4695, 26.91, 57590, 26.90, 14700, 26.89, 14300,
26.88, 15100, 26.87, 3100, 26.92, 8900, 26.93, 14230, 26.94, 25150, 26.95,
15220, 26.96, 2008-01-11, 15:05:32";
这个字符串由许多数据拼接在一起,不同含义的数据用逗号隔开了,按照程序员的思路,顺序号从0开始。
0:”大秦铁路”,股票名字;
1:”27.55″,今日开盘价;
2:”27.25″,昨日收盘价;
3:”26.91″,当前价格;
4:”27.55″,今日最高价;
5:”26.20″,今日最低价;
6:”26.91″,竞买价,即“买一”报价;
7:”26.92″,竞卖价,即“卖一”报价;
8:”22114263″,成交的股票数,由于股票交易以一百股为基本单位,所以在使用时,通常把该值除以一百;
9:”589824680″,成交金额,单位为“元”,为了一目了然,通常以“万元”为成交金额的单位,所以通常把该值除以一万;
10:”4695″,“买一”申请4695股,即47手;
11:”26.91″,“买一”报价;
12:”57590″,“买二”
13:”26.90″,“买二”
14:”14700″,“买三”
15:”26.89″,“买三”
16:”14300″,“买四”
17:”26.88″,“买四”
18:”15100″,“买五”
19:”26.87″,“买五”
20:”3100″,“卖一”申报3100股,即31手;
21:”26.92″,“卖一”报价
(22, 23), (24, 25), (26,27), (28, 29)分别为“卖二”至“卖四的情况”
30:”2008-01-11″,日期;
31:”15:05:32″,时间;
这个接口对于JavaScript程序非常方便,通常的使用方式为,静态或动态地在页面中插入:
<script src="http://hq.sinajs.cn/list=sh601006" type="text/javascript"><!--mce:0--></script>
<script type="text/javascript"><!--mce:1--></script>
这段代码输出大秦铁路(股票代码:601006)的当前股价
current price:14.20
如果你要同时查询多个股票,那么在URL最后加上一个逗号,再加上股票代码就可以了;比如你要一次查询大秦铁路(601006)和大同煤业(601001)的行情,就这样使用URL:
http://hq.sinajs.cn/list=sh601003,sh601001
但如果你要查询大盘指数,情况会有不同,比如查询上证综合指数(000001),使用如下URL:
http://hq.sinajs.cn/list=s_sh000001 服务器返回的数据为:
var hq_str_s_sh000001=”上证指数,3094.668,-128.073,-3.97,436653,5458126″;
数据含义分别为:指数名称,当前点数,当前价格,涨跌率,成交量(手),成交额(万元);
查询深圳成指的URL为:
http://hq.sinajs.cn/list=s_sz399001
对于股票的K线图,日线图等的获取可以通过请求http://image.sinajs.cn/…./…/*.gif此URL获取,其中*代表股票代码,详见如下:
查看日K线图:
http://image.sinajs.cn/newchart/daily/n/sh601006.gif
分时线的查询:
http://image.sinajs.cn/newchart/min/n/sh000001.gif
日K线查询:
http://image.sinajs.cn/newchart/daily/n/sh000001.gif
周K线查询:
http://image.sinajs.cn/newchart/weekly/n/sh000001.gif
月K线查询:
http://image.sinajs.cn/newchart/monthly/n/sh000001.gif

深成指:<script type="text/javascript" src="http://hq.sinajs.cn/list=sz399001" charset="gb2312"></script>
上证指:<script type="text/javascript" src="http://hq.sinajs.cn/list=sh000001" charset="gb2312"></script>
道琼斯:<script type="text/javascript" src="http://hq.sinajs.cn/list=int_dji" charset="gb2312"></script>
纳斯达克:<script type="text/javascript" src="http://hq.sinajs.cn/list=int_nasdaq" charset="gb2312"></script>
恒生指:<script type="text/javascript" src="http://hq.sinajs.cn/list=int_hangseng" charset="gb2312"></script>
日经指数:<script type="text/javascript" src="http://hq.sinajs.cn/list=int_nikkei" charset="gb2312"></script>
台湾加权:<script type="text/javascript" src="http://hq.sinajs.cn/list=b_TWSE" charset="gb2312"></script>
新加坡:<script type="text/javascript" src="http://hq.sinajs.cn/list=b_FSSTI" charset="gb2312"></script>
转自:http://nerrsoft.com/259.html

Continue reading 【转】调用Sina股票数据

Pagination


Total views.

© 2013 - 2019. All rights reserved.

Powered by Hydejack v6.6.1