Archive for February, 2012

作者:AngryFox 分类: Uncategorized February 27th, 2012 暂无评论

Youtube首页中有一块区域是recommended video,这些video都是通过后台的推荐系统提供的,那么youtube后台是怎样推荐这些video的呢?

youtube会把以下两类数据作为整个推荐系统的输入数据,所有的推荐都是基于这些数据进行的,他们是:(1)视频的元数据,如标题、类型、长度、描述、观看次数等等(2)用户的交互数据,这些数据分为显示和隐式的,显示的包含收藏、标注等等,隐式的包含点击观看等等。

youtube第一步会计算出所有video的相似video集合,这个相似video集合是通过关联挖掘计算出来的。youtube把同一个用户一段时间内看的所有video看做一个session,属于同一个session内的video被认为是相似的,具体计算公式如下:s(v1,v2) = c12 / f(v1,v2),其中c12为同时包含v1和v2 的session的个数,f(v1,v2)为归一化因子,通常取c1*c2,即包含v1的session个数乘以包含v2的session的个数。由于某个vide的相似video可能很多,所以在实际计算时取一阈值。

计算了所有的video的相似video集合后,将这些相似关系建立成一个有向图,例如v的相似video集合R,则v节点到R中每个元素vi存在一条指向该元素的边。

推荐时,对于某个用户,先利用该用户的交互数据解析出用户seed video集合,作为推荐的依据。然后从上一步的图中取出所有的seed video的所有相似video集合,作为候选集。

选择出候选集后,youtube采用一些rank算法,对候选集中的video排序。排序要考虑相似性 和多样性,特别是多样性,若果只考虑相似度,推荐的video可能非常单一,用户不能发现新的内容。通常youtube采取一个类别只推荐一个video,一个主题只推荐一个video等方式来保证推荐结果的多样性。

除了推荐算法,值得一提的是youtube乃至google的评估方法。google同样采用邀请用户测试和线上测试结合的方法进行测试评估新加入的功能。通过google测试,有足够理由表明新加入的功能能提高性能后才会被最终采纳。

作者:AngryFox 分类: Uncategorized February 27th, 2012 暂无评论

一、基于内容的推荐
基于内容的推荐系统的data包含一个user的集合,一个item的集合。每个item都有property,通常property用一个向量表示。基于内容的推荐的基本原理是基于用户已经喜欢的item,找到与这些item相似的item(相似的计算是基于item的property的),然后将这些item推荐给用户。

基于内容的推荐给用户的item都是用户已有item同一类的,可能导致推荐聚集。另外,如果同一类的item过多,必然导致推荐不够准确。

基于内容推荐的优点是没有数据稀疏的问题,但是冷启动的问题依然存在。

二、协同过滤
协同过滤是指利用其他人的行为来做推荐。协同过滤技术分为两类,memory based和model based。memory based分为user based和item based,这些方法都是neighbor based方法。model based通常是假定user或item有个latent variable,通过training学习出这些variable,然后基于这些variable做推荐。

1. user based 推荐
user based推荐是先找到topK个相似的user(将user的已有行为作为user的profile计算user间的相似度),然后把这些相似的user的item推荐个这个user,这些item组成候选集,每个item有个权值,该权值的计算为所有包含该item的相似user的相似度加和。从这些item的候选集中选出权值最大的若干个推荐给用户。

CF 基于已有用户的行为规律来推荐item,在积累了一定数据量的情况下有很好的推荐效果。但是CF有很严重的冷启动与数据稀疏问题。

2. random walk推荐
本质上和user based相同

3. model based推荐
假设user有一个d维的隐变量表示user的profile,item也有一个d维的隐变量表示item的property。user与item的rating是这两个向量的点乘。

这种方法比较适合rating的预测,由于rating的预测问题中训练集中已经有了所有可能出现的label。而对于购买、收藏行为等情况下,由于训练集中只有一类label,另外一类label数据没有出现,因此模型的效果可能会很差。

这类方法非常适合处理大规模数据,且推荐效率较高。

三、常见的推荐系统应用

实际的推荐系统一般按照推荐的item来分类

1. 推荐视频的
Youtube, YouKu, Tudou,Qiyi, Hulu等等所有的视频网站
这种推荐需要考虑推荐结果的多样性、时效性。多样性是为了避免推荐聚集到一个很小的集合里,时效性是某些视频再刚出来时有很高的接受度,而过而一段时间后用户的喜欢程度将明显下降。

2. 推荐音乐的
Pandora, 亦歌, last.fm
CF技术非常适合应用于这种类型的推荐,用户的爱好比较固定,通过CF的方法一般可以推荐出符合用户喜欢习惯的item。 需要注意的是CF技术只有在有一定data的情况下才有较好的效果。

3. 图片、电影、新闻
和视频类似,有多样性和时效性要求

4. 电子商务网站
电子商务网站的推荐一般有两种类型: online recommendation,用户在购买了某几个商品之后马上推荐给用户若干个新的item。 promoted recommendation,类似于广告,向用户推荐若干个item。

5. 餐厅等本地服务
大众点评、yelp

作者:AngryFox 分类: Uncategorized February 19th, 2012 暂无评论

问题:
最近遇到一个问题就是根据需求需要对所有的用户根据积分,显示他的全站排名,数据量大概200万左右,积分是实时更改的,怎么能让这个排名尽量的实时呢?一直很困惑,之前用过db的count查,速度很慢,并且高峰时候db也容易堵住,大家有没有什么好的方法,推荐一下。
方案1:

实时性排序要求高,不建议用db做,db只是恢复数据用,排序及前端获取排行列表,我推荐用redis,数据结构采用其内置的sorted list,性能非常好,redis只保存最简单的id+积分这么一个数据,关闭实体化选项,全部用内存来保存数据,搭建主从防止宕机,然后自己实现一个从db到redis的数据恢复过程即可。
数据保存的话,根据你实际并发量考虑,并发不高的话可以定时写入db,并发很高的话,中间需要再做一个缓冲队列。

方案2:

通过搜索引擎。
积分变更 动态更新索引文件。

作者:AngryFox 分类: Uncategorized February 13th, 2012 暂无评论

php的sessionid的产生由远程地址,时间等md5,sha1算法后等,然后再Hash算出,是唯一性的参数。

PHPAPI char *php_session_create_id(PS_CREATE_SID_ARGS)
{
    PHP_MD5_CTX md5_context;
    PHP_SHA1_CTX sha1_context;
    unsigned char digest[21];
    int digest_len;
    int j;
    char *buf;
    struct timeval tv;
    zval **array;
    zval **token;
    char *remote_addr = NULL;

    gettimeofday(&tv, NULL);

    if (zend_hash_find(&EG(symbol_table), "_SERVER",
                sizeof("_SERVER"), (void **) &array) == SUCCESS &&
            Z_TYPE_PP(array) == IS_ARRAY &&
            zend_hash_find(Z_ARRVAL_PP(array), "REMOTE_ADDR",
                sizeof("REMOTE_ADDR"), (void **) &token) == SUCCESS) {
        remote_addr = Z_STRVAL_PP(token);
    }

    buf = emalloc(100);

    /* maximum 15+19+19+10 bytes */
    sprintf(buf, "%.15s%ld%ld%0.8f", remote_addr ? remote_addr : "",
    switch (PS(hash_func)) {
    case PS_HASH_FUNC_MD5:
        PHP_MD5Init(&md5_context);
        PHP_MD5Update(&md5_context, buf, strlen(buf));
        digest_len = 16;
        break;
    case PS_HASH_FUNC_SHA1:
        PHP_SHA1Init(&sha1_context);
        PHP_SHA1Update(&sha1_context, buf, strlen(buf));
        digest_len = 20;
        break;
    default:
        php_error_docref(NULL TSRMLS_CC, E_ERROR, "Invalid session hash function");
        efree(buf);
        return NULL;
    }

    if (PS(entropy_length) > 0) {
        int fd;

        fd = VCWD_OPEN(PS(entropy_file), O_RDONLY);
        if (fd >= 0) {
            unsigned char rbuf[2048];
            int n;
            int to_read = PS(entropy_length);

            while (to_read > 0) {
                n = read(fd, rbuf, MIN(to_read, sizeof(rbuf)));
                if (n <= 0) break;

                switch (PS(hash_func)) {
                case PS_HASH_FUNC_MD5:
                    PHP_MD5Update(&md5_context, rbuf, n);
                    break;
                case PS_HASH_FUNC_SHA1:
                    PHP_SHA1Update(&sha1_context, rbuf, n);
                    break;
                }
                to_read -= n;
            }
            close(fd);
        }
    }

    switch (PS(hash_func)) {
    case PS_HASH_FUNC_MD5:
        PHP_MD5Final(digest, &md5_context);
        break;
    case PS_HASH_FUNC_SHA1:
        PHP_SHA1Final(digest, &sha1_context);
        break;
    }

    if (PS(hash_bits_per_character) < 4
            || PS(hash_bits_per_character) > 6) {
        PS(hash_bits_per_character) = 4;

        php_error_docref(NULL TSRMLS_CC, E_WARNING, "The ini setting hash_bits_per_character is out of range (should be 4, 5, or 6)
- using 4 for now");
    }
    j = (int) (bin_to_readable(digest, digest_len, buf, PS(hash_bits_per_character)) - buf);

    if (newlen)
        *newlen = j;
    return buf;
}
作者:AngryFox 分类: Uncategorized February 10th, 2012 暂无评论
通常我们在建立服务器的处理模型的时候,主要是下面集中模型;

 (1)    a new Connection 进来,用 fork() 产生一个 Process 处理。
  (2)   a new Connection 进来,用 pthread_create() 产生一个 Thread 处理。
  (3)   a new Connection 进来,丢入 Event-based Array,由 Main Process 以 Nonblocking 的方式处理所有的 I/O。
这三种方法当然也都有各自的缺点:
 用 fork() 的问题在于每一个 Connection 进来时的成本太高,如果同时接入的并发连接数太多容易进程数量很多,进程之间的切换开销会很大,同时对于老的内核(Linux)会产生雪崩效应。
 用 Multi-thread 的问题在于 Thread-safe 与 Deadlock 问题难以解决,另外有 Memory-leak 的问题要处理,这个问题对于很多程序员来说无异于恶梦,尤其是对于连续服务器的服务器程序更是不可以接受。 如果才用 Event-based 的方式在于实做上不好写,尤其是要注意到事件产生时必须 Nonblocking,于是会需要实做 Buffering 的问题,而 Multi-thread 所会遇到的 Memory-leak 问题在这边会更严重。而在多 CPU 的系统上没有办法使用到所有的 CPU resource。 

     针对上面存在的问题,通常采用的方法有: 以 Poll 的方式解决:当一个 Process 处理完一个 Connection 后,不直接死掉,而继续回到 accept() 的状态继续处理,但这样会遇到 Memory-leak 的问题,于是采用这种方式的人通常会再加上「处理过 N 个 Connection 后死掉,由 Parent Process 再 fork() 一只新的」。最有名的例子是 Apache 1.3服务器,大家可以参考其源代码的实现。 hread-safe 的问题可以寻找其他 Thread-safe Library 直接使用。Memory-leak 的问题可以试着透过 Garbage Collection Library 分析出来。Apache 2.0 的 Thread MPM 就是使用这个模式。
     然而,目前高效率的 Server 都偏好采用 Event-based,一方面是没有 Create Process/Thread 所造成的 Overhead,另外一方面是不需要透过 Shared Memory 或是 Mutex 在不同的 Process/Thread 之间交换资料。然而,Event-based 在实做上的几个复杂的地方在于:
 select() 与 poll() 的效率过慢,造成每次要判断「有哪些 Event 发生」这件事情的成本很高,这在 BSD 支援 kqueue()、Linux 支援 epoll()、Solaris 支援 /dev/poll 后就解决了,在Windows平台上通过完成端口的方式解决了.但这两组 Function 都不是 Standard,于是在不同的平台上就必须再改一次。

对于非阻塞的IO模型, 因为 Nonblocking,所以在 write() 或是 send() 时满了需要自己 Buffering。  因为 Nonblocking,所以不能使用 fgets() 或是其他类似的 function,于是需要自己刻一个 Nonblocking 的 fgets()。但是使用者所丢过来的资料又不能保证在一次 read() 或 recv() 就有一行,于是要自己做 Buffering。实际上这三件事情在 libevent 都有 Library 处理掉了.

   libevent 是一个事件触发的网络库,适用于windows、linux、bsd等多种平台,内部使用select、epoll、kqueue等系统调用管理事件机 制。著名的用于apache的php缓存库memcached据说也是libevent based,而且libevent在使用上可以做到跨平台,如果你将要开发的应用程序需要支持以上所列出的平台中的两个以上,那么强烈建议你采用这个库,即使你的应用程序只需要支持一个平台,选择libevent也是有好处的,因为它可以根据编译/运行环境切换底层的事件驱动机制,这既能充分发挥系统的性能,又增加了软件的可移植性。它封装并且隔离了事件驱动的底层机制,除了一般的文件描述符读写操作外,它还提供有读写超时、定时器和信号回调,另外,它还允许为事件设定不同的优先级,当前版本的libevent还提供dns和http协议的异步封装,这一切都让这个库尤其适合于事件驱动应用程序的开发。

   下面介绍libevent实现的框架

原文请参考:libevent官方网址:   http://www.monkey.org/~provos/libevent/
比较好的文档:

http://unx.ca/log/category/libevent/

http://tb.blog.csdn.net/TrackBack.aspx?PostId=1808095

libenvent库的代码结构可以大概分成几个模块:
    事件处理框架
   事件引擎模块
   Buffer管理模块
 信号处理模块

  1. 事件处理框架 

1.1 event_init() 初始化
  首先要隆重介绍event_base对象:

struct event_base {
    const struct eventop *evsel;
    void *evbase;
    int event_count;        /* counts number of total events */
    int event_count_active; /* counts number of active events */

    int event_gotterm;      /* Set to terminate loop */

    /* active event management */
    struct event_list **activequeues;
    int nactivequeues;
    struct event_list eventqueue;
    struct timeval event_tv;
    RB_HEAD(event_tree, event) timetree;
};

   event_base对象整合了事件处理的一些全局变量,  角色是event对象的"总管家", 他包括了事件引擎函数对象(evsel, evbase), 当前入列事件列表(event_count, event_count_active, eventqueue), 全局终止信号(event_gotterm), 活跃事件列表(avtivequeues), 事件队列树(timetree)...初始化时创建event_base对象, 选择 当前OS支持的事件引擎(epoll, poll, select...)并初始化, 创建全局信号队列(signalqueue), 活跃队列的内存分配( 根据设置的priority个数,默认为1).
 1.2 event_set() 事件定义
    event_set来设置event对象,包括所有者event_base对象, fd, 事件(EV_READ| EV_WRITE), 回掉函数和参数,事件优先级是当前event_base的中间级别(current_base->nactivequeues/2). event对象的定义见下:

struct event {
    TAILQ_ENTRY (event) ev_next;
    TAILQ_ENTRY (event) ev_active_next;
    TAILQ_ENTRY (event) ev_signal_next;
    RB_ENTRY (event) ev_timeout_node;
    struct event_base *ev_base;
    int ev_fd;
    short ev_events;
    short ev_ncalls;
    short *ev_pncalls;  /* Allows deletes in callback */
    struct timeval ev_timeout;
    int ev_pri;     /* smaller numbers are higher priority */
    void (*ev_callback)(int, short, void *arg);
    void *ev_arg;
    int ev_res;     /* result passed to event callback */
    int ev_flags;
};

1.3 event_add() 事件添加:
   int event_add(struct event *ev, struct timeval *tv)
   这个接口有两个参数, 第一个是要添加的事件, 第二个参数作为事件的超时值(timer). 如果该值非NULL, 在添加本事件的同时添加超时事件(EV_TIMEOUT)到时间队列树(timetree), 根据事件类型处理如下:
   EV_READ  =>  EVLIST_INSERTED  => eventqueue
   EV_WRITE  =>  EVLIST_INSERTED  => eventqueue
   EV_TIMEOUT => EVLIST_TIMEOUT => timetree
  EV_SIGNAL  => EVLIST_SIGNAL => signalqueue
1.4 event_base_loop() 事件处理主循环
   这里是事件的主循环,只要flags不是设置为EVLOOP_NONBLOCK, 该函数就会一直循环监听事件/处理事件.
   每次循环过程中, 都会处理当前触发(活跃)事件:
   (a). 检测当前是否有信号处理(gotterm, gotsig), 这些都是全局参数,不适合多线程
   (b). 时间更新,找到离当前最近的时间事件, 得到相对超时事件tv
   (c). 调用事件引擎的dispatch wait事件触发, 超时值为tv, 触发事件添加到activequeues
   (d). 处理活跃事件, 调用caller的callbacks (event_process_acitve)
2. 事件引擎模块 :

   Linux下有多种I/O复用机制, .来处理多路事件监听, 常见的有epoll, poll, select, 按照优先级排下来为:
 evport
 kqueue
 epoll
 devpoll
 rtsig
 poll
 select
   在event_init()选择事件引擎时,按照优先级从上向下检测, 如果检测成功,当前引擎被选中.每个引擎需要定义几个处理函数,以epoll为例:

struct eventop epollops = {
    "epoll",
    epoll_init,
    epoll_add,
    epoll_del,
    epoll_recalc,
    epoll_dispatch,
    epoll_dealloc
};

3. Buffer管理模块: 

   libevent定义了自己的buffer管理机制evbuffer, 支持多种类型数据的read/write功能, 包括不定长字符串,buffer中内存采用预分配/按需分配结合的方式, 可以比较方便的管理多个数据结构映射到内存buffer.
   需要拉出来介绍的是evbuffer_expand()函数, 当内部内存不够时,需要expand, 这里采用预分配的方式,如果需要长度<256字节,预分配256字节, 同时内存成倍增长,一直到大于需要的长度.
4.  信号处理模块

   信号处理单独提出来,主要是libevent的信号处理比较轻巧, 从而很好融合到event机制.
   singal模块初始化(evsignal_init)时, 创建了UNIX域socket ( pipe)作为内部消息传递桥梁:

    if (socketpair(AF_UNIX, SOCK_STREAM, 0, ev_signal_pair) == -1)
        event_err(1, "%s: socketpair", __func__);
    FD_CLOSEONEXEC(ev_signal_pair[0]);
    FD_CLOSEONEXEC(ev_signal_pair[1]);
    fcntl(ev_signal_pair[0], F_SETFL, O_NONBLOCK);
    event_set(&ev_signal, ev_signal_pair[1], EV_READ,
        evsignal_cb, &ev_signal);
    ev_signal.ev_flags |= EVLIST_INTERNAL;

   evsignal_add(), 添加信号事件, 关联信号处理方法(sigaction)
   实际运行过程中,如果某singal发生, 对应的信号处理方法被调用, write a character to pipe
   同时pipe的另一端被激活, 添加信号到singalqueue, 在事件循环中evsignal_process处理信号callbacks.

libevent库的具体使用方法

   直接写一个很简单的 Time Server 来当作例子:当你连上去以后 Server 端直接提供时间,然后结束连线。event_init() 表示初始化 libevent 所使用到的变数。event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev) 把 s 这个 File Description 放入 ev (第一个参数与第二个参数),并且告知当事件 (第三个参数的 EV_READ) 发生时要呼叫 connection_accept() (第四个参数),呼叫时要把 ev 当作参数丢进去 (第五个参数)。其中的 EV_PERSIST 表示当呼叫进去的时候不要把这个 event 拿掉 (继续保留在 Event Queue 里面),这点可以跟 connection_accept() 内在注册 connection_time() 的代码做比较。而 event_add(&ev, NULL) 就是把 ev 注册到 event queue 里面,第二个参数指定的是 Timeout 时间,设定成 NULL 表示忽略这项设定。

注:这段代码来自于网络,虽然很粗糙,但是对libevent的使用方法已经说明的很清楚了.

附源码:使用方法

 #include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <event.h>
#include <stdio.h>
#include <time.h>

void connection_time(int fd, short event, struct event *arg)
{
    char buf[32];
    struct tm t;
    time_t now;

    time(&now);
    localtime_r(&now, &t);
    asctime_r(&t, buf);

    write(fd, buf, strlen(buf));
    shutdown(fd, SHUT_RDWR);

    free(arg);
}

void connection_accept(int fd, short event, void *arg)
{
    /* for debugging */
    fprintf(stderr, "%s(): fd = %d, event = %d.\n", __func__, fd, event);

    /* Accept a new connection. */
    struct sockaddr_in s_in;
    socklen_t len = sizeof(s_in);
    int ns = accept(fd, (struct sockaddr *) &s_in, &len);
    if (ns < 0) {
        perror("accept");
        return;
    }

    /* Install time server. */
    struct event *ev = malloc(sizeof(struct event));
    event_set(ev, ns, EV_WRITE, (void *) connection_time, ev);
    event_add(ev, NULL);
}

int main(void)
{
    /* Request socket. */
    int s = socket(PF_INET, SOCK_STREAM, 0);
    if (s < 0) {
        perror("socket");
        exit(1);
    }

    /* bind() */
    struct sockaddr_in s_in;
    bzero(&s_in, sizeof(s_in));
    s_in.sin_family = AF_INET;
    s_in.sin_port = htons(7000);
    s_in.sin_addr.s_addr = INADDR_ANY;
    if (bind(s, (struct sockaddr *) &s_in, sizeof(s_in)) < 0) {
        perror("bind");
        exit(1);
    }

    /* listen() */
    if (listen(s, 5) < 0) {
        perror("listen");
        exit(1);
    }

    /* Initial libevent. */
    event_init();

    /* Create event. */
    struct event ev;
    event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev);

    /* Add event. */
    event_add(&ev, NULL);

    event_dispatch();

    return 0;
}

在写 Nonblocking Network Program 通常要处理 Buffering 的问题,但并不好写,主要是因为 read() 或 recv() 不保证可以一次读到一行的份量进来。

在 libevent 里面提供相当不错的 Buffer Library 可以用,完整的说明在 man event 的时候可以看到,最常用的应该就是以 evbuffer_add()、evbuffer_readline() 这两个 Function,其他的知道存在就可以了,需要的时候再去看详细的用法。

下面直接提供 libevent-buff.c 当作范例,编译后看执行结果,再回头来看 source code 应该就有感觉了:

#include <sys/time.h>
#include <event.h>
#include <stdio.h>

void printbuf(struct evbuffer *evbuf)
{
    for (;;) {
        char *buf = evbuffer_readline(evbuf);
        printf("* buf = %p, the string = \"\e[1;33m%s\e[m\"\n", buf, buf);
        if (buf == NULL)
            break;
        free(buf);
    }
}

int main(void)
{
    struct evbuffer *evbuf;

    evbuf = evbuffer_new();
    if (evbuf == NULL) {
        fprintf(stderr, "%s(): evbuffer_new() failed.\n", __func__);
        exit(1);
    }

    /* Add "gslin" into buffer. */
    u_char *buf1 = "gslin";
    printf("* Add \"\e[1;33m%s\e[m\".\n", buf1);
    evbuffer_add(evbuf, buf1, strlen(buf1));
    printbuf(evbuf);

    u_char *buf2 = " is reading.\nAnd he is at home.\nLast.";
    printf("* Add \"\e[1;33m%s\e[m\".\n", buf2);
    evbuffer_add(evbuf, buf2, strlen(buf2));
    printbuf(evbuf);

    evbuffer_free(evbuf);
}

最后的 event_dispatch() 表示进入 event loop,当 Queue 里面的任何一个 File Description 发生事件的时候就会进入 callback function 执行。
作者:AngryFox 分类: Uncategorized February 9th, 2012 暂无评论

OAUTH相关术语

在弄清楚OAUTH流程之前,我们先了解下OAUTH的一些术语的定义:

OAUTH相关的三个URL:
Request Token URL: 获取未授权的Request Token服务地址;
User Authorization URL: 获取用户授权的Request Token服务地址;
Access Token URL: 用授权的Request Token换取Access Token的服务地址;

OAUTH相关的参数定义:
oauth_consumer_key: 使用者的ID,OAUTH服务的直接使用者是开发者开发出来的应用。所以该参数值的获取一般是要去OAUTH服务提供商处注册一个应用,再获取该应用的oauth_consumer_key。
oauth_consumer_secret:oauth_consumer_key对应的密钥。
oauth_signature_method: 请求串的签名方法,应用每次向OAUTH三个服务地址发送请求时,必须对请求进行签名。签名的方法有:HMAC-SHA1、RSA-SHA1与PLAINTEXT等三种。
oauth_signature: 用上面的签名方法对请求的签名。
oauth_timestamp: 发起请求的时间戳,其值是距1970 00:00:00 GMT的秒数,必须是大于0的整数。本次请求的时间戳必须大于或者等于上次的时间戳。
oauth_nonce: 随机生成的字符串,用于防止请求的重放,防止外界的非法攻击。
oauth_version: OAUTH的版本号,可选,其值必须为1.0。

OAUTH HTTP响应代码:

HTTP 400 Bad Request 请求错误
Unsupported parameter 参数错误
Unsupported signature method 签名方法错误
Missing required parameter 参数丢失
Duplicated OAuth Protocol Parameter 参数重复
HTTP 401 Unauthorized 未授权
Invalid Consumer Key 非法key
Invalid / expired Token 失效或者非法的token
Invalid signature 签名非法
Invalid / used nonce 非法的nonce

作者:AngryFox 分类: Uncategorized February 7th, 2012 暂无评论

围绕技术方面如何应对高并发

根据官方微博透露出售的书籍信息,及订单数量级,我们可以再大胆猜测大概有30W-50W的网络用户在三个小时促销期内参与购买,而且是分二天完成的,相信一天的促销时间段参与购买者的数字会更低一些,不过这对于一个B2C网站而言确实并发压力不小,假设一台Web服务器能支撑5000个并发,所有用户同时间点添加书籍到购物车中,那么需要600-1000台Web服务器。不过这只是个极端假设,真实的情况肯定不是有几个点肯定容易成为瓶颈:购物车服务、MemCached服务、订单管理服务、支付服务、数据库服务,不清楚内部具体情况,只能进行猜测性分析:

(1). 京东商城采用购物车系统,方便网购客进行物品挑选,然后集中完成订单的模式,有区别于淘宝网的模式,为此购物车系统的压力将会非常大。购物车系统服务涉及大量的数据添加和显示操作,甚至修改和删除,数据必须考虑先要你用内存(例如:MemCached、Redis等)方式支持物品数据的增加、修改、删除,并异步更新到数据库中,因此购物车系统需要操作的内存缓存区最好能支持分布式部署和提供数据服务的模式,否则容易成为瓶颈,若是无法水平分布式的方式扩容,那必须进行垂直扩容,不得不考虑更换内存更大及性能更优秀的服务器支持;

(2). MemCached提供读服务是必须的且压力也会不小,但还必须考虑提供写操作,即数据先写入MemCached再队列方式同步到数据库;

(3).队列服务,涉及到一些数据的异步执行和排队等待等事件,在电子商务领域一样非常流行和成熟的技术,一个分布式队列项目的研发,达到可以在线部署、停止、调度、监控等功能,尤其是这种网购促销活动队列尤其重要,若是能有此类技术服务,会对整个系统的有序控制非常有帮助;

(4). 提供数据服务的数据库产品为SQL Server,这并不是意味着性能就差,关键是要做到:操作系统不需要的服务必须关闭,磁盘RAID要合理,数据库的设计要合理,索引组织结构要优化好,SQL Server 服务器端参数要配置和优化,重点借助Windows自带的工具就足够监控和分析数据库服务器的性能和瓶颈。主要是数据库服务器往往不容易进行拆分,而实现水平方向的分布式部署(注释:SQL Server有一个分布式分区视图功能,可以轻易实现数据的拆分,且不需要应用做任何修改,只是性能相对而言会降低一点)。故可对于一些静态的速度提前预存到后端的缓存系统中,减少数据库服务器的压力,提高对网购者物品信息查询的响应速度;

(5). 提供Web服务的程序为.net编写的,一般做到水平扩展,然后再搭配LVS或F5(注释:既说京东商城使用F5)实现负载均衡设备即可,Web服务层面不会成为瓶颈。若是.net程序代码质量不高,则可能成为瓶颈,就需要更多的机器支持;

(6). 订单管理系统将会成为压力非常大的服务,需要合并订单和检查库存,分配订单到不同的仓库所在地等,建议只做订单常规的检查,至于订单分配到不同地方仓库处理等事情可以等活动结束之后的1-2个小时内再后台集中处理的模式;

(7).支付服务方面,京东商场有自己的物流配送服务,且提供在线支付和货到付款的模式,具体数字不详,网络上获得的京东商城货代付款比率高达90%;