Archive for the ‘互联网’ Category

Nginx的epoll类非阻塞事件模型以及被说的烂了,但这只是它高性能的一角,细节决定成败。这次重操旧业,希望能真正了解nginx。

请求处理的不同阶段

在使用ngx_lua时,经常看到可以在access、rewrite等各阶段插入lua代码,那一个请求的处理过程中,会经过多少阶段,是不是每个阶段都能够插入callback呢?filter所需的参数是如何传递的呢?filter与nginx worker之间又是如何调度并表达返回值的呢?

 

 

先收集一些资料,后续写体会:

nginx中upstream的设计和实现

使用异步 I/O 大大提高应用程序的性能

使用 Nginx 提升网站访问速度 (较浅)

利用 squid 反向代理提高网站性能

I have marked with a * those which I think are absolutely essential
Items for each section are sorted by oldest to newest. Come back soon for more!

BASH
* In bash, 'ctrl-r' searches your command history as you type
- Add "set -o vi" in your ~/.bashrc to make use the vi keybindings instead
  of the Emacs ones. Takes some time to get used to, but it's fantastic!
- Input from the commandline as if it were a file by replacing 
  'command < file.in' with 'command <<< "some input text"'
- '^' is a sed-like operator to replace chars from last command 
  'ls docs; ^docs^web^' is equal to 'ls web'. The second argument can be empty.
* '!!:n' selects the nth argument of the last command, and '!$' the last arg
  'ls file1 file2 file3; cat !!:1-2' shows all files and cats only 1 and 2
- More in-line substitutions: http://tiny.cc/ecv0cw http://tiny.cc/8zbltw
- 'nohup ./long_script &' to leave stuff in background even if you logout
- 'cd -' change to the previous directory you were working on
- 'ctrl-x ctrl-e' opens an editor to work with long or complex command lines
* Use traps for cleaning up bash scripts on exit http://tiny.cc/traps
* 'shopt -s cdspell' automatically fixes your 'cd folder' spelling mistakes

PSEUDO ALIASES FOR COMMONLY USED LONG COMMANDS
- function lt() { ls -ltrsa "$@" | tail; }
- function psgrep() { ps axuf | grep -v grep | grep "$@" -i --color=auto; }
- function fname() { find . -iname "*$@*"; }

VIM
- ':set spell' activates vim spellchecker. Use ']s' and '[s' to move between
  mistakes, 'zg' adds to the dictionary, 'z=' suggests correctly spelled words
- check my .vimrc http://tiny.cc/qxzktw and here http://tiny.cc/kzzktw for more

TOOLS
* 'htop' instead of 'top'
- 'ranger' is a nice console file manager for vi fans
- Use 'apt-file' to see which package provides that file you're missing
- 'dict' is a commandline dictionary
- Learn to use 'find' and 'locate' to look for files
- Compile your own version of 'screen' from the git sources. Most versions
  have a slow scrolling on a vertical split or even no vertical split at all
* 'trash-cli' sends files to the trash instead of deleting them forever. 
  Be very careful with 'rm' or maybe make a wrapper to avoid deleting '*' by
  accident (e.g. you want to type 'rm tmp*' but type 'rm tmp *')
- 'file' gives information about a file, as image dimensions or text encoding
- 'sort | uniq' to check for duplicate lines
- 'echo start_backup.sh | at midnight' starts a command at the specified time
- Pipe any command over 'column -t' to nicely align the columns
* Google 'magic sysrq' and learn how to bring you machine back from the dead
- 'diff --side-by-side fileA.txt fileB.txt | pager' to see a nice diff
* 'j.py' http://tiny.cc/62qjow remembers your most used folders and is an 
  incredible substitute to browse directories by name instead of 'cd' 
- 'dropbox_uploader.sh' http://tiny.cc/o2qjow is a fantastic solution to 
  upload by commandline via Dropbox's API if you can't use the official client
- learn to use 'pushd' to save time navigating folders (j.py is better though)
- if you liked the 'psgrep' alias, check 'pgrep' as it is far more powerful
* never run 'chmod o+x * -R', capitalize the X to avoid executable files. If
  you want _only_ executable folders: 'find . -type d -exec chmod g+x {} \;'
- 'xargs' gets its input from a pipe and runs some command for each argument

NETWORKING
- Don't know where to start? SMB is usually better than NFS for most cases.
  'sshfs_mount' is not really stable, any network failure will be troublesome
- 'python -m SimpleHTTPServer 8080' shares all the files in the current 
  folder over HTTP, port 8080
- 'ssh -R 12345:localhost:22 server.com "sleep 1000; exit"' forwards 
  server.com's port 12345 to your local ssh port, even if you machine 
  is not externally visible on the net. 
  Now you can 'ssh localhost -p 12345' from server.com and you will 
  log into your machine. 
  'sleep' avoids getting kicked out from server.com for inactivity
* Read on 'ssh-keygen' to avoid typing passwords every time you ssh
- 'socat TCP4-LISTEN:1234,fork TCP4:192.168.1.1:22' forwards your port
  1234 to another machine's port 22. Very useful for quick NAT redirection.
* Configure postfix to use your personal Gmail account as SMTP:
  http://tiny.cc/n5k0cw. Now you can send emails from the command line.
  'echo "Hello, User!" | mail user@domain.com'
- Some tools to monitor network connections and bandwith:
  'lsof -i' monitors network connections in real time
  'iftop' shows bandwith usage per *connection*
  'nethogs' shows the bandwith usage per *process*
* Use this trick on .ssh/config to directly access 'host2' which is on a private 
  network, and must be accessed by ssh-ing into 'host1' first
  Host host2
      ProxyCommand ssh -T host1 'nc %h %p'
  	  HostName host2
* Pipe a compressed file over ssh to avoid creating large temporary .tgz files
  'tar cz folder/ | ssh server "tar xz"' or even better, use 'rsync'

                                     -~-

(CC) by-nc, Carles Fenollosa <carles.fenollosa@bsc.es> 
Retrieved from http://mmb.pcb.ub.es/~carlesfe/unix/tricks.txt
Last modified: vie 08 mar 2013 12:42:06  CET

zz from: http://mmb.pcb.ub.es/~carlesfe/unix/tricks.txt

最近一个项目中,发现打开多个页面后,会发生登录状态的丢失。通过fiddler抓包发现,两个连续类似页面的http请求有cookie丢失的情况:

  1. 最后一个正常的页面的http request header中,包含phpsessionid等cookie值,response里没有对cookie的操作
  2. 第一个异常的页面的http request header 中,缺少phpsessionid等cookie值
  3. 这时尝试进入其他需要登录才能访问的系统,登录状态也丢失了,这些系统是其他域且功能无关的登录系统

于是猜测cookie的丢失应该是发生在浏览器端,而且应该不是代码导致的,因为js代码肯定无权访问其他域名的cookie。

最终的结论是:

该类型页面中 cookie个数会进行累加,导致cookie个数超过浏览器限制,浏览器会清除cookie,从而导致登录状态的丢失。IE、FF、chrome都有该现象,但是不同版本有区别。单个域名cookie上限个数有的是20、50,甚至还有对cookie总数的限制。清除策略有的是随机清除,有的是清除最老的cookie。

 

 

无锁队列的实现

by 陈皓

关于无锁队列的实现,网上有很多文章,虽然本文可能和那些文章有所重复,但是我还是想以我自己的方式把这些文章中的重要的知识点串起来和大家讲一讲这个技术。下面开始正文。

关于CAS等原子操作

在开始说无锁队列之前,我们需要知道一个很重要的技术就是CAS操作——Compare & Set,或是 Compare & Swap,现在几乎所有的CPU指令都支持CAS的原子操作,X86下对应的是 CMPXCHG 汇编指令。有了这个原子操作,我们就可以用其来实现各种无锁(lock free)的数据结构。

这个操作用C语言来描述就是下面这个样子:(代码来自Wikipedia的Compare And Swap词条)意思就是说,看一看内存*reg里的值是不是oldval,如果是的话,则对其赋值newval。

int compare_and_swap (int* reg, int oldval, int newval)
{
  int old_reg_val = *reg;
  if (old_reg_val == oldval)
     *reg = newval;
  return old_reg_val;
}

这个操作可以变种为返回bool值的形式(返回 bool值的好处在于,可以调用者知道有没有更新成功):
bool compare_and_swap (int *accum, int *dest, int newval)
{
  if ( *accum == *dest ) {
      *dest = newval;
      return true;
  }
  return false;
}

与CAS相似的还有下面的原子操作:(这些东西大家自己看Wikipedia吧)

注:在实际的C/C++程序中,CAS的各种实现版本如下:

 

1)GCC的CAS

GCC4.1+版本中支持CAS的原子操作(完整的原子操作可参看 GCC Atomic Builtins

bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

2)Windows的CAS

在Windows下,你可以使用下面的Windows API来完成CAS:(完整的Windows原子操作可参看MSDN的InterLocked Functions

 InterlockedCompareExchange ( __inout LONG volatile *Target,
                                 __in LONG Exchange,
                                 __in LONG Comperand);

3) C++11中的CAS

C++11中的STL中的atomic类的函数可以让你跨平台。(完整的C++11的原子操作可参看 Atomic Operation Library

template< class T >
bool atomic_compare_exchange_weak( std::atomic<T>* obj,
                                   T* expected, T desired );
template< class T >
bool atomic_compare_exchange_weak( volatile std::atomic<T>* obj,
                                   T* expected, T desired );

无锁队列的链表实现

下面的东西主要来自John D. Valois 1994年10月在拉斯维加斯的并行和分布系统系统国际大会上的一篇论文——《Implementing Lock-Free Queues》。

我们先来看一下进队列用CAS实现的方式:

EnQueue(x) //进队列
{
    //准备新加入的结点数据
    q = new record();
    q->value = x;
    q->next = NULL;

    do {
        p = tail; //取链表尾指针的快照
    } while( CAS(p->next, NULL, q) != TRUE); //如果没有把结点链上,再试

    CAS(tail, p, q); //置尾结点
}

我们可以看到,程序中的那个 do- while 的 Re-Try-Loo。就是说,很有可能我在准备在队列尾加入结点时,别的线程已经加成功了,于是tail指针就变了,于是我的CAS返回了false,于是程序再试,直到试成功为止。这个很像我们的抢电话热的不停重播的情况。

你会看到,为什么我们的“置尾结点”的操作不判断是否成功,因为:

  1. 如果有一个线程T1,它的while中的CAS如果成功的话,那么其它所有的 随后线程的CAS都会失败,然后就会再循环,
  2. 此时,如果T1 线程还没有更新tail指针,其它的线程继续失败,因为tail->next不是NULL了。
  3. 直到T1线程更新完tail指针,于是其它的线程中的某个线程就可以得到新的tail指针,继续往下走了。

这里有一个潜在的问题——如果T1线程在用CAS更新tail指针的之前,线程停掉了,那么其它线程就进入死循环了。下面是改良版的EnQueue()

EnQueue(x) //进队列改良版
{
    q = new record();
    q->value = x;
    q->next = NULL;

    p = tail;
    oldp = p
    do {
        while (p->next != NULL)
            p = p->next;
    } while( CAS(p.next, NULL, q) != TRUE); //如果没有把结点链上,再试

    CAS(tail, oldp, q); //置尾结点
}

我们让每个线程,自己fetch 指针 p 到链表尾。但是这样的fetch会很影响性能。而通实际情况看下来,99.9%的情况不会有线程停转的情况,所以,更好的做法是,你可以接合上述的这两个版本,如果retry的次数超了一个值的话(比如说3次),那么,就自己fetch指针。

好了,我们解决了EnQueue,我们再来看看DeQueue的代码:(很简单,我就不解释了)

DeQueue() //出队列
{
    do{
        p = head;
        if (p->next == NULL){
            return ERR_EMPTY_QUEUE;
        }
    while( CAS(head, p, p->next) != TRUE );
    return p->next->value;
}

我们可以看到,DeQueue的代码操作的是 head->next,而不是head本身。这样考虑是因为一个边界条件,我们需要一个dummy的头指针来解决链表中如果只有一个元素,head和tail都指向同一个结点的问题,这样EnQueue和DeQueue要互相排斥了

注:上图的tail正处于更新之前的装态。

CAS的ABA问题

所谓ABA(见维基百科的ABA词条),问题基本是这个样子:

  1. 进程P1在共享变量中读到值为A
  2. P1被抢占了,进程P2执行
  3. P2把共享变量里的值从A改成了B,再改回到A,此时被P1抢占。
  4. P1回来看到共享变量里的值没有被改变,于是继续执行。

虽然P1以为变量值没有改变,继续执行了,但是这个会引发一些潜在的问题。ABA问题最容易发生在lock free 的算法中的,CAS首当其冲,因为CAS判断的是指针的地址。如果这个地址被重用了呢,问题就很大了。

比如上述的DeQueue()函数,因为我们要让head和tail分开,所以我们引入了一个dummy指针给head,当我们做CAS的之前,如果head的那块内存被回收并被重用了,而重用的内存又被EnQueue()进来了,这会有很大的问题。(内存管理中重用内存基本上是一种很常见的行为

这个例子你可能没有看懂,维基百科上给了一个活生生的例子——

你拿着一个装满钱的手提箱在飞机场,此时过来了一个火辣性感的美女,然后她很暖昧地挑逗着你,并趁你不注意的时候,把用一个一模一样的手提箱和你那装满钱的箱子调了个包,然后就离开了,你看到你的手提箱还在那,于是就提着手提箱去赶飞机去了。

这就是ABA的问题。

解决ABA的问题

维基百科上给了一个解——使用double-CAS(双保险的CAS),例如,在32位系统上,我们要检查64位的内容

1)一次用CAS检查双倍长度的值,前半部是指针,后半部分是一个计数器。

2)只有这两个都一样,才算通过检查,要吧赋新的值。并把计数器累加1。

这样一来,ABA发生时,虽然值一样,但是计数器就不一样(但是在32位的系统上,这个计数器会溢出回来又从1开始的,这还是会有ABA的问题)

当然,我们这个队列的问题就是不想让那个内存重用,这样明确的业务问题比较好解决,论文《Implementing Lock-Free Queues》给出一这么一个方法——使用结点内存引用计数refcnt

SafeRead(q)
{
    loop:
        p = q->next;
        if (p == NULL){
            return p;
        }

        Fetch&Add(p->refcnt, 1);

        if (p == q->next){
            return p;
        }else{
            Release(p);
        }
    goto loop;
}

其中的 Fetch&Add和Release分是是加引用计数和减引用计数,都是原子操作,这样就可以阻止内存被回收了。

用数组实现无锁队列

本实现来自论文《Implementing Lock-Free Queues

使用数组来实现队列是很常见的方法,因为没有内存的分部和释放,一切都会变得简单,实现的思路如下:

1)数组队列应该是一个ring buffer形式的数组(环形数组)

2)数组的元素应该有三个可能的值:HEAD,TAIL,EMPTY(当然,还有实际的数据)

3)数组一开始全部初始化成EMPTY,有两个相邻的元素要初始化成HEAD和TAIL,这代表空队列。

4)EnQueue操作。假设数据x要入队列,定位TAIL的位置,使用double-CAS方法把(TAIL, EMPTY) 更新成 (x, TAIL)。需要注意,如果找不到(TAIL, EMPTY),则说明队列满了。

5)DeQueue操作。定位HEAD的位置,把(HEAD, x)更新成(EMPTY, HEAD),并把x返回。同样需要注意,如果x是TAIL,则说明队列为空。

算法的一个关键是——如何定位HEAD或TAIL?

1)我们可以声明两个计数器,一个用来计数EnQueue的次数,一个用来计数DeQueue的次数。

2)这两个计算器使用使用Fetch&ADD来进行原子累加,在EnQueue或DeQueue完成的时候累加就好了。

3)累加后求个模什么的就可以知道TAIL和HEAD的位置了。

如下图所示:

 小结

以上基本上就是所有的无锁队列的技术细节,这些技术都可以用在其它的无锁数据结构上。

1)无锁队列主要是通过CAS、FAA这些原子操作,和Retry-Loop实现。

2)对于Retry-Loop,我个人感觉其实和锁什么什么两样。只是这种“锁”的粒度变小了,主要是“锁”HEAD和TAIL这两个关键资源。而不是整个数据结构。

还有一些和Lock Free的文章你可以去看看:

注:我配了一张look-free的自行车,寓意为——如果不用专门的车锁,那么自行得自己锁自己!

淘宝开放平台架构组件体系初探

作者 岑文初 发布于 2009年9月29日

zz from: http://www.infoq.com/cn/news/2009/09/top-arch-components

淘宝开放平台(TaoBao Open Platform,简称TOP)的整个架构体系是组件化体系架构,可以是很少的几个基础组件构成的Skeleton,也可以是融入了商业想象的Amazing Architecture。这里就通过对于这些组件的罗列,描述出在TOP这个大体系中,各个组件所处的地位及作用。TOP的“兵器谱”是在现阶段商业需求及平台非业务性需求指导下形成的,未来TOP将继续发展,“兵器谱”也会不断演进。

下图是整个TOP当前的一个组件结构图:

图中,红色虚线就是TOP的Skeleton。TOP当前从业务模块功能角度来划分,可以分成三个层次:基础平台组件层,基础业务组件层,普通业务组件层。基础平台组件层,倾向于平台级别功能满足及对平台稳定性,可用性的支持。基础业务组件层,是介于平台服务于普通业务服务之间的组件,部分利用了平台基础组件层的组件,来抽象出一层公用业务服务组件,为业务组件提供通用的基础支持。

安全组件

安全组件主要从四个角色去考虑整体的安全策略及具体的实施方案,这四个角色是:用户,应用,平台,服务。

平台本身的安全主要是基于在大并发和大流量的情况下,保证平台自身稳定性和可用性,同时也要兼顾在平台开放的服务不相互干扰和影响。因此采取服务分流隔离机制,通过虚拟配置及软负载方式将服务请求动态分流和隔离,保证了服务之间相互的独立性,同时也充分利用TOP的能力。频率控制及流量控制除了保护TOP自身不受到攻击,也为后端服务提供者作了天然的一个保护屏障,保证服务请求压力可以在TOP上可控,防止流量直接压倒服务提供者。用户隐私安全在淘宝尤为重要,用户信息的安全性也在淘宝开放的过程中被放到了首位。在开放平台设计中,除了采用普通开放平台的认证模式以外(OAuth类似流程),还在服务调用过程中通过区分应用角色来限制对于用户信息的获取和使用。同时针对不同的应用类型(插件,Web应用,客户端应用,手机应用)都有各自不同的用户授权方式,保证用户的知情权。App的安全其实也是为了保证对服务的请求及对用户信息的获取不被不法的应用信息盗取者所利用,根据应用角色及自己对于安全的需求,采取多种方式或者组合的方式来实现App信息的保密性,保护App自身安全,也保证了平台服务的数据安全。服务安全指的是对于服务来说分成了几个层级,不同层级的服务对于安全级别的要求不同(不需要交验应用身份,需要交验应用身份,需要用户授权,用户可选择授权等),在应用访问服务的时候,就会需要根据服务级别的不同采用不同的访问控制流程。根据上述的四个角色对于安全的考虑,通过应用角色的定义,服务虚拟组的编排,黑名单(频率控制及流量控制),多模式用户令牌等手段,形成了多种模式的安全控制流程。

服务路由组件

服务路由是开放平台最基本的功能,如果排除商业因素,那么对于TOP最基本上来看可以看作一个服务路由器,服务路由主要的功能如下图展示:

服务路由组件需要支持多服务类型的服务接入,不同服务类型主要表现在两个维度:1.服务对外的展现方式(REST OR RPC),这两种形态的服务没有任何好坏之分,只是根据各自的系统形态来选择采用哪一种模式来对外暴露,RPC比较符合过去应用开放的风格,REST比较适合面向资源的架构。同时对于同步,异步,通知,大数据量的服务,都会有不同的接入方式和调用方式支持,满足各种业务场景的需求。多通信协议支持,表示服务请求到了TOP以后,TOP负责将请求继续发送给服务提供者,不论服务提供者采用什么方式和TOP交互,最终将得到的结果返回给客户,服务调用者将会对后端的服务请求过程透明,同时可以使TOP很容易接入一些传统遗留系统的服务,或者是对通信有特殊需求的服务。特性支持主要是会有对内容的一些特殊处理,例如压缩,在CS或者手机应用交互过程中,就会需要对数据量有所压缩,满足业务需求。

监控告警组件

下图是监控告警组件的基本功能图

监控和告警模块在TOP中起到越来越重要的作用,访问量逐日膨胀,运行期TOP是一个黑盒,无法知晓当前系统实际的健康状况,当出现问题以后比较难以定位。服务监控主要是服务质量(响应时间),短时间段内的服务请求峰值,和阶段性的趋势。系统和平台主要是对底层基础组件的监控,同时及时地通知TOP负责人处理线上即将要发生的事情。对于应用的监控通常就是从客户端和服务端两面对于应用当前的情况作汇总分析。当监控发现异常以后,就交由告警部分按照一定的发送策略给相关的负责人,在第一时间将问题解决。

日志组件

日志组件和其他系统的日志组件基本没有太大的区别,只是在对于海量数据写出和获取的方法做了优化(例如异步分页批量输出等)。日志组件主要负责,打点,收集,分析,数据库记录,归档。

协议转换组件

这里谈到的协议转换指的是对于请求和返回的协议,TOP可以做适配,来满足服务调用者和服务发布者之间在服务协议失配的情况下还是能够正常通信。当前支持JSON,XML,Atom,二进制协议之间的转换,当然转换描述文档将会配置在TOP。同时返回的数据内容,也可以通过协议转换,返回给客户端常规的xml或者json类型的数据。

服务流程化组件

服务流程化指的是将离散的服务通过流程描述文档能够虚拟的串联成为一个新的服务,这样更加适合调用者使用,同时将服务的一些内部逻辑隐藏起来。这很类似于SOA中的服务编排,同时也可以参看Yahoo的Pipe,那就是一种典型的服务串联,同时还提供了方便的界面直接交由用户通过手动拖拉的方式来使用服务串联。

服务流程化最大的特点就是将不同类型的服务能够根据业务场景的需求组合成简单的流程性服务,极大降低了服务开发者由于对服务流程不熟悉而犯错的几率,同时也为服务开发者提高了开发效率。

计费组件

当前计费模型主要是按流量收费和插件分成两种模式,因此计费组件还比较简单,当前就是基于日志做分析,未来会考虑在流量上的各种特殊模式(打包,优惠等等)。

容器组件(TBML)

产生原因:

  • 数据隐私性
  • 开发便利性
  • 业务升级透明化
  • 监控全局化
  • 开发标准化

作用:

  • 数据操作可控,保护终端用户隐私(结合cookie和标签,控制ISV业务数据操作尺度,提高数据安全性)
  • 提供标准业务流程标签,简化开发者对于业务流程理解过程。
  • 标签化接口方式,完成数据获取和页面渲染,后台业务升级对ISV透明化。
  • 标签获取客户端信息,将监控扩展到整个业务请求过程。制定行业化标签库,形成统一开发标准。

TBML首先需要根据业务需求及场景定义出对应的标签库,也就是制定Taobao的标签标准,最简单的获取用户信息标签,到最复杂的业务操作流程标签都会成为标签库中的一部分。同时在服务端需要有解释引擎来翻译标签,解释引擎一方面需要去了解标签内容和含义,同时需要请求后台多个API,串联成为流程化的服务,从应用的输入,得到最后的输出,当然期间也需要处理异常的情况。最后还需要关注的就是安全控制,在交验标签传递来的数据时,需要对数据作完整性及合法性的交验,防止通过标签数据的特殊性攻击后台服务接口。

TBQL组件

TBQL其实是一种服务调用的方式,也是通过一种程序员和开发者习惯的方式,将对资源的REST请求转换成一种类似QL的请求,对于面向资源性的架构体系来说是十分有利的。同时对于API来说,使用者会更加自然的去采用连接和过滤得方式得到需要的数据。

QL解释引擎负责对于TBQL的翻译工作,数据存储的MetaData保存在数据库中,可以指导QL解释引擎翻译。需要支持不同数据来源的连接和过滤,在获得结果以后需要做格式转换返回给服务调用者(通常就是xml)。与容器一样,需要着重考虑安全性问题,对于传统的SQL注入就是典型攻击QL系统的案例,需要谨慎处理解析中对于字符的翻译工作。在流程中出现异常,需要制定策略来判断是否直接返回错误还是支持部分容错。

TOPID组件

TOPID组件有点类似于Facebook的Connect,需要在淘宝和淘宝的合作开发者之间建立起双向的用户互通的标准和流程,同时也为服务互通打好基础,毕竟业务的互动需要基于可以互通的用户体系。

总结

以上仅仅只是简单的罗列了一下TOP技术体系架构中的一些组件化的内容,同时在这些组件的背后有着更多的基础性项目的支持,例如统一配置中心对于系统动态扩容的支持,分布式缓存对于监控告警的支持,分布式文件系统对于海量小文件保存和获取的支持等等。同时以上每一个模块都有各自特殊的定制和优化,例如路由模块就需要有Lazy的服务参数解析模式来提高处理性能,安全体系中需要有动态密钥机制来保证高安全性等等。

TOP从萌芽走向成熟,不论从技术架构还是商业规划都处于不断摸索和改进的过程,当前的技术体系仅仅是现阶段的一个需求缩影,未来在市场不断成熟,开发者不断介入和反馈的情况下,TOP会走得更快更远,TOP的“兵器谱”会更加丰富,更加出彩。

========================

http://www.csdn.net/article/2012-07-26/2807765

SDCC讲师专访:淘宝岑文初谈开放平台架构

CSDN:请您对自己做个简单介绍,尤其是在开放平台方面的技术经验和积累。您目前最关注哪些技术领域?

岑文初:从2007年底开始构建阿里系最早的开放平台,2009年正式加入淘宝开始负责淘宝开放平台基础架构建设,2012年负责整个淘宝的开放平台技术和产品团队。

开放平台发展的过程中技术也不断的在发展,从最早的网站服务化(SCA,OSGI),平台授权(OAuth),服务流控与服务隔离(Web容器异步化服务处理),服务生命周期管理(服务接入与文档,SDK自动化等),到后来的平台透明化(海量流式数据即时分析),多样化服务(ATS异步大任务处理服务,Streaming API流式数据推送服务,支持并行和串行化的QL服务),无线客户端的安全及服务易用(IOS、Android),JS组件(支持网站接入)。

CSDN:作为电商开放平台的代表,您觉得淘宝开放平台在架构方面有哪些特点?尤其在系统稳定和数据安全性方面,淘宝用到了哪些技术、经历了哪些磨历?

岑文初:淘宝开放平台架构关键词:透明,核心模块小,按需简化设计,多层次设计配合(js,client,server),服务模式创新。

系统稳定:1.Web容器异步化支持服务流控和隔离。同步的http服务处理模式在初期后端服务不多,服务质量差异不大的情况下能够满足需求,但是当后端服务越来越多(最初30个公开服务到今天300多个公开服务),服务质量和响应参差不齐的情况下,开放平台自身的稳定性直接受制于后端某一个服务,因此当后端某一个服务出现不稳定,就会直接使得淘宝开放平台服务全线无法服务,因此将同步的http服务中转改造为异步中转并设置了权重线程池来差异化对待不同重要度的服务,最后实现服务之间质量影响隔离,服务与平台稳定性隔离。

2.基于流式数据分析可以每一分钟产出业务和系统的统计数据。对比一些基线和告警阀值,最快速度的定位问题,并且采取运行期控制的方式来避免问题的扩大。

数据安全:

1.淘宝开放平台是所有开放平台中对于数据安全要求最高最细的开放平台,当前除了使用业界标准的OAuth2,同时还将安全控制从服务纬度细化到了服务字段级别,同时对于数据安全性分成了四个级别,结合应用形态和应用安全指标差异化控制。

2.通过JS组件化集成实现对用户发起服务和isv应用服务端发起服务的差别验证,对于电子商务买家类服务开放提高了安全性。3.提供无线端SDK静态类库方式,平衡无线用户的安全性和用户体验。

CSDN:在淘宝11.11活动中,面对这种突发的海量访问请求,淘宝开放平台采取了哪些应对措施?

岑文初:淘宝的11.11访问量其实一直在预期以内,因为平台自身对于数据流解析,异步化都保证了高效的系统处理能力,同时可以卸载的一些服务控制保证了服务控制流程如果出现问题可以随时降级处理,保证业务正常支持。另一方面,对于海量订单的对外输出,提供了Streaming api,降低轮询服务带来的压力,同时能够给开发者和商家最快速的消息投递。

CSDN:定义设计API接口有哪些学问和经验可以分享的?

岑文初:对于数据获取类服务非常重要的一点就是保留fields字段,也就是可以支持有选择的数据返回,这样对于服务升级(字段增加或者减少)都会非常主动,同时也非常适合做QL映射。

另一方面,对外提供服务必须要先定义服务处理所对应的资源定义,这样可以避免服务过程化设计开放(服务要设计为面向资源开放),保证业务内部逻辑变更对外透明。对于操作型服务(更新,删除,新增),需要防范重放攻击,业务需要支持一些主键字段或者随机一次性会话字段。

上文分析了《sina微博计数器的设计》一文提到的Mysql/cache实现方式,这里继续其他方案的学习,包括redis和微博自己开发的couter计数器。

Redis实现计数器

对于Redis,我也只是耳闻未曾亲用,但NoSQL的原理大致相似吧。它作为一种NoSQL数据库,一般认为其读写速度都优于传统关系型数据库。按照原文分析,存储64位key+32位value,还需要其他辅助存储空间,故每个key-value对需要76字节的空间(该估算,需要了解Redis数据结构)。如果是百万级别的数据,需要百M级别的存储空间;千万级别,是G级别的存储空间,都还可以接受。但是对于微博这种千亿级别的数据,7600G的存储空间就难以接受了。更何况还要考虑主从备份的问题呢?

即使按照原文下面的分析,0评论0转发的微博占多数,故不存储这些0数据了呢?也只是存储空间降低到3040G。分析该方法,读写性能和复杂度应该都是可以接受的,但问题出在内存消耗过多。这里有一个疑问,之前用tokyo cabinet的hash类型数据库时,是可以指定需要缓存多少数据在内存中,而整体数据持久化在文件里。这样的话,内存其实不会真的消耗到那么多。Redis应该也支持吧?如果不支持,换其他NoSQL呢?其实原文下面的counter计数器也是基于这种思路,并简化了存储struct。

(这里也激发一个兴趣:PHP存储一个int、字符、数组、对象都需要多少空间呢?我虽然会去了解其源码,但是没有这么细致的分析过。)

开发Counter实现计数器

先列举其优化方法如下:

  1. 应对稀疏数据
  2. 合并转发、评论数目为一个结构体,定长
  3. 初始化时开辟大内存空间,采用二次hash的方式解决碰撞
  4. 转发、评论数 32位 -> 16位
  5. 改变数据结构,压缩value,定长->变长
  6. 优化key
  7. 批量查询
  8. 内存+磁盘,冷热数据分别处理
  9. 定期备份内存数据,配合append log,应对故障
  10. 分布式

1. 应对稀疏数据:

不存储转发、评论数为0的微博,该思路虽然简单,但是很重要!我们之前做推荐算法时,也是使用类似的思路过滤了不活跃用户,从而将数据量降低了一个数量级。(再罗嗦一下,为什么cache+存储时,cache里就必须存储0呢?是因为如果不存储,会导致cache不命中,进而继续访问DB。)

2. 合并转发、评论数目为一个结构体,定长:

这个是结合业务需求的直观想法,既然转发、评论数经常一起获取,那就存储在一起。写操作也是原子的,转发数+1应该是:偏移至weibo_id对应的item->ptr.repost_num++。

3. 初始化时开辟大内存空间,采用二次hash的方式解决碰撞

数组偏移是最快的操作。一次读取请求,处理流程可能是:计算其整数类型的hash key,偏移数组下标,读取,比较key是否一致,若一致则返回,否则处理碰撞。写操作类似。

hash碰撞有两种解决思路:不断探测;hash链表。原文采用的是“不断探测”的方法。到这里,存储40G条有效微博数据,其内存使用空间是640GB=16B×40G。

4. 转发、评论数 32位 -> 16位

这个思路很直接,内存还是大,就再想办法,这里是结合业务需求,搞了value,将32位正整数变成16位short。对于超出16位存储的数据,另外开辟内存存储。虽然略微增加了复杂度,但是内存变成了480G=12B×40G。(所以,优化一方面是技术,另一方面也是对业务需求的深入了解和思考)

5. 改变数据结构,压缩value,定长->变长

这一条还是在减少内存,采取的是压缩数据的思路。我对压缩算法和压缩比不了解,只能推测(学无止境啊!)。其原先的思路是针对每个item做变长压缩:

struct直接压缩

在这种思路下,为了保证可直接偏移数组下标,就要求每个item定长,所以如其所述,节省出来的空闲空间也没什么用。而将一维变成二维甚至更多维,并针对value做压缩后,效果就不一样了:

变长压缩value

我的理解是,针对“item”分片,比如1024个item作为一个mini block。将item里的key和value分别存储,repost_num和comment_num可以拆开也可以不拆开。这时,key仍然是连续hash存储的,当定位到key之后,需要获取到其对应的value block指针。一个key block中,该指针值是相同的,所以可以共享。那么怎样获取到这个共享数据的位置呢?我能想到的方案是,将指针存储在每个key block的顶部,故该位置=当前key指针-(当前key指针-初始位置)%(1024+1)。不知道有没有更好的方案?

这种方式,假设value的压缩比是50%,则所需内存为:(8B+2B)*40G=400G是存储key数组和value数组的空间。还需要header的存储空间,假设repost_num和comment_num一起存储,每个分片是1k个item,则=8B*(40G/1k)=320M。如果多拆分列,则会增加header的存储空间,但相对而言,还是很小的。

6. 优化key

以上都是针对value的压缩,其实key也是可以优化的。以上本来key也是被分片了,如果我们能使每个block中,key的高位或者低位有重合,那么这个重合部分,就可以存储到block的header里!更进一步,如果先对weibo_id分区,再hash,一来分区之后便于后面做分布式;二来对hash函数没什么特殊要求;三来,每个分区内数据量减少,也可以降低碰撞的几率。这时,只需要将每个分区内的weibo_id的高位存储在table header里,就可以将weibo_id从64位降低到32位,在不压缩value时内存空间=(4+2+2)*40G=320G;若同时做value压缩,并假设压缩比为0.5,则其内存空间=(4+2)*40G=240G。其数据结构为:

 

7.批量查询:根据原文给出的数据推测,counter会把一次批量查询的10条weibo_id拆分为20次key请求(为什么是20次??即使将repost和comment拆开,也不会影响到key的查询吧?),如果这些key对应value在相同的block里,则解压的操作是可以省略的。

8. 内存+磁盘,冷热数据分别处理

原文在分析热点数据时,提到了LRU导致必须cache 0数据,为什么呢?因为使用LRU意味着,当我们访问cache不命中时,有可能是数据从来没被写入cache,或者超时被expire了,或者由于cache不足根据LRU策略被踢出了。所以,不命中cache,还得再访问持久化存储获取数据。

为了避免存储0值,原文采用的是根据weibo_id区分,半年内的weibo_id肯定在cache中,如果没有则代表其值=0;半年前的weibo_id肯定在持久化存储里,不访问cache,直接去读持久化存储。

一次涉及到持久化存储的写过程是:根据weibo_id判断是否在硬盘,若是,读取内存里的硬盘block索引获取硬盘文件位置,读硬盘取整个block,存储在内存的cold block里,返回数据。

那么写过程呢?由于写之前,肯定得先读(微博页面得先显示,然后才能操作),所以写时数据应该在cold block里(cold block的溢出机制不知道什么样?如果打开页面很久之后再操作呢?)那么根据原文推测,这时仅写cold block。而之后cold block与硬盘文件的merge应该是由其他进程延时/触发独立完成的。

在这种优化思路下,可以设置最大内存,若超过,则flush数据到硬盘,并修改作为标识的weibo_id即可。

9. 定期备份内存数据,配合append log,应对故障:

这里应该是有一个后台进程or线程,定期地把内存里的数据保存到硬盘文件,结合其所说的append log,猜测这个进程可能是根据log来持久化数据,而不一定直接从内存读取吧?因为如果读取内存的话,是否就需要加全局锁呢?即使是分table持久化,也需要对table加锁。

还提到了二级索引。这里可能是对内存分table、分block做二级索引,也有可能是针对硬盘文件的。不过考虑到硬盘文件读取较少,可能不会费这个功夫吧?

(针对分散的旧数据做攻击,会不会造成计数器性能问题?有什么防护措施呢?)

10. 分布式:

跟分table的思路一样,只是粒度会更大,每台服务器负责多个table。既然是master-slave架构,那么主从间如何同步,append log是一种思路。

 

 

 

这两天,有一篇很技术的博客《微博计数器的设计12》,逐步介绍了大并发、大数据量时,sina微博计数器的实现原理,详情请 移步原文欣赏。

作为一个架构初学者,看到很多似曾相识的名词和未透彻的技术,这里对名词进行解释,对每一种设计方式尝试剖析。

使用MySql数据库实现计数器

这种是最简单的方法,可能的表设计是:

create table weibo_ext(

weibo_id bigint not null,

repost_num integer default 0,

comment_num integer default 0,

UNIQUE KEY `weibo_id_idx` (weibo_id)

) ENGINE=innodb;

当数据量在百万甚至千万时,单表都还可能支撑。(to myself:这里,如果能给出预估的并发量就更好了)但是,由于微博的转发、评论数变化较快,即读写频率都很高,所以可能会出现读写锁竞争,导致访问变慢。

当数据量到达500w时,就得为分表做准备了,如原文所述:

  1. 按id取模,把数据拆分到N个表当中去
  2. 按id的时间来分段拆表,满了就建新表

第一种方法,比如我们建了8张weibo_ext_N表,结构都与weibo_ext一致。在代码端,weibo_id%8依次分散到各个表进行读写。这样当读写一条微博时,表较小会较快。批量查询的话,则可能要做1-8次表查询。更严重的情况是,当所有表都膨胀到千万数据量时,再添加新表会很痛苦,几乎所有数据都需要重新散列到各表中。

有没有更好的散列方法了呢?分布式应用中,有一种叫一致性hash算法。上面取模仅仅散列了key,而一致性hash还会额外散列服务器,两种散列方式需要保证数据空间一致,从而使散列后每个服务器节点负责一片散列空间里的数据。当新增服务器时,只会影响到原先一台服务器的一部分数据。针对一致性hash,还有一种升级方案,即将每个真实的服务器实为多个虚拟服务器,并均匀散列到数据空间里。采用这种方式,对mysql按id随机的分表,会有一定帮助。

第二种方法,是按id区间分表,很简单,不重复。

这两种方法,千亿条微博需要生成上千张表,为了稳定性和效率还需要做一主多从的数据库,成本较高。

使用Mysql+memcached实现计数器

假设采取了mysql实现计数器的方案,并姑且不考虑存储成本,但是当访问量提升之后,Mysql还是会成为性能瓶颈,尤其是写操作也很频繁时,数据库缓存可能频繁失效,也有可能发生读写锁竞争使访问堆积,再引起mysql connection不足的问题,进而导致web server响应缓慢、可用进程数不足,使前端用户感觉到页面刷新慢甚至无法打开。

那么惯性思维,我们会想到添加memcached之类的缓存,来减轻mysql的负担。由于需要保证计数的原子性,即转发、评论数的增加需要在缓存内部完成,而非先get再set,可能会将转发和评论数分表存储,即weibo_id=>repost_num和weibo_id=>comment_num两条。如原文所述,有两个弊端:空数据也得Cache浪费内存, Cache频繁失效。针对cache频繁失效,还需要考虑,是超时失效,还是每次写操作都强制delete使失效呢?前者会造成数据不一致,后者会增加写操作的复杂度。

这种方式,不但要接受mysql的存储成本,还增加了memcached的成本。

结合牛B硬件实现计数器

我对硬件没什么了解,只能google到一些资料。猜测其实现方式是:

  1. mysql作为持久化存储,但是不使用传统的sql访问方式,而是handlesocket,免去了sql优化解析等步骤,直接操作数据,提高速度
  2. 固态硬盘,提高硬盘读写效率
  3. cache缓存访问量高的数据

我一向小成本开发,不太了解这种方案会消耗多少money、提速到何种程度?

学习《sina微博计数器的设计》二

记录log,对于很多人而言是很简单或者低级的事情。但是,随着项目经验的增长,遇到生产环境中bug数的增多,至少对于我来说,日志的重要性日益增加。

这次,需要对项目中log类进行重构,主要希望实现4个目的:

  1. 建立日志监控机制,第一时间发现问题
  2. 协助定位问题,帮助快速解决问题
  3. 记录用户行为,协助解答客户疑问
  4. 记录用户行为,协助制定安全与个性化等策略

除了这些功能性的目的,由于log类在一次请求中的调用频率相对较高,且与基本业务无关,如果性能方面有问题的话,就太本末颠倒了,所以先从性能说起。

log一般记录在文件里,所以其本质上是写文件,使用php作为开发语言的话,主要考虑了3个方面:

  1. 选择fwrite还是file_put_contents?
  2. 是否使用debug_backtrace函数获取文件名和行号?
  3. 怎样保证并发写的原子性?

选择fwrite还是file_put_contents?

php有多种写文件的函数,fwrite需要先fopen获取到文件句柄,而file_put_contents直接使用文件名即可,且传入的data参数可以是字符串,也可以是数组,甚至stream,使用较简单。

zend框架的Zend_Log_Writer_Stream类,使用的是fwrite函数;而公司内部多个team的log封装都使用了file_put_contents函数。首先考虑,我们的log类,给谁用?内部使用,暂时没考虑开源。传入的参数,是否需要支持string,array or stream?记录log而已,支持string即可,而且log的基本样式是每次记录一行。所以,比较两者在写入多行数据之间的性能区别即可:

$str = "abc\n";
$n = 10000;
$filename = 'test_write.txt';

$start_time = mytime();
write_with_open($filename, $str, $n);
$used_time = mytime() - $start_time;
printf("%20s%s\n","fwrite","Time used: $used_time ");

$start_time = mytime();
write_without_open($filename, $str, $n);
$used_time = mytime() - $start_time;
printf("%20s%s\n","file_put_contents","Time used: $used_time ");

$start_time = mytime();
write_with_errorlog($filename, $str, $n);
$used_time = mytime() - $start_time;
printf("%20s%s\n","error_log","Time used: $used_time ");

function write_with_open($filename, $str, $n){
        $fp = fopen($filename, 'a') or die("can't open $filename for write");

        while($n--){
                fwrite($fp, $str);
        }

        fclose($fp);
}

function write_without_open($filename, $str, $n){
        while($n--){
                file_put_contents($filename, $str, FILE_APPEND);
        }
}

function write_with_errorlog($filename, $str, $n){
        while($n--){
                error_log($str, 3, $filename);
        }
}

执行该测试脚本的结果是:

fwriteTime used: 0.018175840377808
file_put_contentsTime used: 0.22816514968872
error_logTime used: 0.2338011264801

可见fwrite的性能要远远大于另外两者,直观上看,fwrite仅关注于写,而文件句柄的获取仅由fopen做一次,关闭操作也尽有一次。如果修改write_with_open函数,把fopen和fclose函数放置到while循环里,则3者的性能基本持平。

以上结论,也可以通过查看PHP源代码得知,fwrite和file_put_contents的源码都在php-5.3.10/ext/standard/file.c里,file_put_contents不但逻辑较为负载,还牵涉到open/锁/close操作,对于只做一次写操作的请求来说,file_put_contents可能较适合,因为其减少了函数调用,使用起来较为方便。而对于log操作来说,fwrite从性能角度来说,较为适合。

2012-06-22补充:

以上只是单纯从“速度”角度考虑,但是在web的生产环境里,如果并发数很高,导致系统的open files数目成为瓶颈的话,情况就不同了!fwrite胜在多次写操作只用打开一次文件,持有file handler的时间较长;而file_put_contents每次都在需要的时候打开文件,写完就立即关闭,持有file handler的时间较短。如果open files的值已无法调高,那么使用file_put_contents在这种情况下,就会是另外一种选择了。

是否使用debug_backtrace函数获取文件名和行号?

在gcc,标准php出错信息里,都会给出出错的文件名和行号,我们也希望在log里加上这两个信息,那么是否使用debug_backstrace函数呢?

class A1{
        public static $_use_debug=true;
        function run(){
                # without debug_backtrace
                if (!self::$_use_debug){
                        #echo "Quit\n";
                        return;
                }

                # with debug_backtrace
                $trace = debug_backtrace();
                $depth = count($trace) - 1;
                if ($depth > 1)
                        $depth = 1;
                $_file = $trace[$depth]['file'];
                $_line = $trace[$depth]['line'];

                #echo "file: $_file, line: $_line\n";
        }
}

class A2{
        function run(){
                $obj = new A1();
                $obj->run();
        }
}

class A3{
        function run(){
                $obj = new A2();
                $obj->run();
        }
}
class A4{
        function run(){
                $obj = new A3();
                $obj->run();
        }
}
class A5{
        function run(){
                $obj = new A4();
                $obj->run();
        }
}
class A6{
        function run(){
                $obj = new A5();
                $obj->run();
        }
}

$n = 1000;
$start_time = mytime();
A1::$_use_debug = true;
$obj = new A6();
while($n--){
        $obj->run();
}
$used_time = mytime() - $start_time;
printf("%30s:%s\n", "With Debug Time used", $used_time);

$n = 1000;
$start_time = mytime();
A1::$_use_debug = false;
$obj = new A6();
while($n--){
        $obj->run();
}
$used_time = mytime() - $start_time;
printf("%30s:%s\n", "Without Debug Time used", $used_time);

function mytime(){
        list($utime, $time) = explode(' ', microtime());
        return $time+$utime;
}

运行的结果是:

flykobe@desktop test $ php test_debug_trace.php
With Debug Time used:0.005681037902832
Without Debug Time used:0.0021991729736328

但是若多次运行,少数情况下,with debug版本甚至与快于without版本。综合考虑,还是决定不用debug_backtrace,而由调用者传入__FILE__和__LINE__值。

怎样保证并发写的原子性?

写文件时,大致有这3种情况:

  1. 一次写一行,中间被插入其他内容
  2. 一次写多行,行与行之间被插入其他内容
  3. 多次写多行,行与行之间被插入其他内容

对于web访问这种高并发的写日志而言,一条日志一般就是一行,中间绝不允许被截断,覆盖或插入其他数据。但行与行之间,是否被插入其他内容,并不care。既然之前是决定采用fwrite,php手册上说的很清楚:

If handle was fopen()ed in append mode, fwrite()s are atomic (unless the size of string exceeds the filesystem’s block size, on some platforms, and as long as the file is on a local filesystem). That is, there is no need to flock() a resource before callingfwrite(); all of the data will be written without interruption.

即当fwrite使用的handler是由fopen在append模式下打开时,其写操作是原子性的。不过也有例外,如果一次写操作的长度超过了文件系统的块大小,或者使用到NFS之类的非local存储时,则可能出问题。其中文件系统的块大小(在我的centos5虚拟机上是4096 bytes)可以通过以下命令查看:

sudo /sbin/tune2fs -l /dev/mapper/VolGroup00-LogVol00 | grep -i block

这同样可以通过模拟多种不同情况的fwrite操作来验证,由于比较简单不再赘述代码。

————————-

2012-07-03添加

《unix环境高级编程》里说:Unix系统提供了一种方法使这种操作成为原子操作,即打开文件时,设置O_APPEND操作,就使内核每次对这种文件进行读写之前,都将进程的当前偏移量设置到该文件的尾端处。

而php手册中,指的某些platform应该不包含*nix实现。故可认为,在我们的环境下,php的fwrite函数是原子性的。

在以上几种比较的基础上,初步完成了我们的Log类封装,进行了千行和万行级别的log写入测试,性能提高了3倍,但应该仍然有优化余地。

国内开放平台泛滥,大多使用的是OAuth或其变形,但对于openID则了解较少。

当前有一个需求,核心其实是用户的身份验证,从目前看来,并没有后续资源访问了,使用oauth感觉小题大做。而如果自己定义协议,又增加了用户的学习成本,且有安全性等疏漏的隐患。

OpenID和OAuth的区别及第三方登录的安全隐患分析一文中,作者分析了openID与OAuth的区别:

OpenID和OAuth完全是为了两种不同的需求而生

OpenID的目标是为了帮助网站确认一个用户的身份
OAuth的目标是为了授权第三方在可控范围下访问用户资源

同时,http://www.slideshare.net/gongjt/oauthopenid 里也比对比了两者,并给出国内目前可用的openID案例:比如http://www.lepu.com/login使用了google的openID.

—————————

综合以上的分析,对于需要开放资源的平台而言,oauth更合适。而仅提供用户身份验证的平台,openID就足够用了。考虑到我们的需求,以下详细研究OAuth2.0。

具体的计划是:

  1. 泛读OAuth2.0 draft协议
  2. 泛读百度内部文档中,关于OAuth的部分,关注有哪些部门选用了OAuth,是哪个版本或者变种,实现机制是什么?
  3. 结合步骤2,以及网络上关于OAuth实现版本的分析,快速浏览两到三种OAuth2.0 server端的php开源代码,并进行搭建,分析其性能/安全性/可扩展性/协议实现的完整程度
  4. 选用一种实现代码,进行必要的修改
  5. 选择对应的OAuth client SDK,完成授权流程
  6. 结合业务需求,开发open api,完成一期api开发流程

出于时间的考虑,目前仅focus在web server scenarios上,不考虑js/mobile等场景。

大多数OAuth2.0实现的授权过程如下:

1. app方,根据appkey/return_url(授权成功后返回的url)等,拼成授权登录的url,header user 到这个url(该url部署在授权服务器上)

2.user在这个url页面里,登录,并同意授权给该app

3.授权服务器验证用户登录和授权成功,生成request_token/request_token_secret,拼在return_url后面,header user到该return_url(加密和签名后的)

4.app方,根据该request_token/appkey,生成获取access_token的api url(该url也部署在授权服务器上),调用该api;授权服务器检查requset_token和appkey是否匹配,生成access_token/access_token_secret,作为返回值;app方,获取返回值中的access_token/access_token_secret,并保存。

5.app方,利用access_token,调用资源服务器提供的api

资料:

1. oauth2-11 版本中文译文 http://wenku.baidu.com/view/b37ed7260722192e4536f66e.html

2.oauth2-26 英文版 http://tools.ietf.org/html/draft-ietf-oauth-v2-26

3.Using OAuth 2.0 to Access Google APIs https://developers.google.com/accounts/docs/OAuth2?hl=zh-CN

4.OAuth官网 http://oauth.net/

nginx能够通过upstream的方式,把请求分配到不同的phpcgi服务上。

#phpcgi/etc/php-fpm.conf
listen = 10.241.133.144:9000   # 本地ip,非127.0.0.1
listen.allowed_clients = 10.241.133.137 # nginx server ip,非127.0.0.1

#nginx/conf/nginx.conf
upstream multiphp{
  server 10.241.133.144:9000 weight=1;
  server 127.0.0.1:9000 weight=1;
}
server{
  ……
  location ~ .*\.php{
    fastcgi_pass  multiphp;
    #fastcgi_pass  127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
    include fastcgi.conf;
  }
}