遇见sharon ’ 的文章存档

redis超时问题分析

Redis在分布式应用中占据着越来越重要的地位,短短的几万行代码,实现了一个高性能的数据存储服务。最近dump中心的cm8集群出现过几次redis超时的情况,但是查看redis机器的相关内存都没有发现内存不够,或者内存发生交换的情况,查看redis源码之后,发现在某些情况下redis会出现超时的状况,相关细节如下。

1. 网络。Redis的处理与网络息息相关,如果网络出现闪断则容易发生redis超时的状况。如果出现这种状况首先应查看redis机器网络带宽信息,判断是否有闪断情况发生。

阅读全文

grunt快速上手

这篇文章的目标是帮助大家快速上手grunt,适用的grunt版本为0.4.x,本文只是大致介绍,如果想做深入了解请阅读grunt官方文档

安装grunt命令行工具

首先确保你的node版本在0.8以上(暂时不建议适用0.10.0),命令:

node -v

然后安装grunt命令行工具grunt-cli

npm install -g grunt-cli

可能需要前面加上sudo(例如 OSX, *nix)。

如果你只前装过grunt的老版本的话则需要卸载:

npm uninstall -g grunt

安装grunt模板

grunt模板的作用是帮助你生成初始的Gruntfile.js文件,当然你也可以直接把其它项目的Gruntfile.js文件拷贝过来使用。

首先要安装命令行工具grunt-init:

npm install -g grunt-init

然后安装模板,目前有三种模板是由grunt官方做维护的,还有别的可在github上找到,或者你自己实现一个。 官方模板的安装命令如下:

然后安装模板,目前有三种模板是由grunt官方做维护的,还有别的可在github上找到,或者你自己实现一个。 官方模板的安装命令如下:

git clone git://github.com/gruntjs/grunt-init-gruntfile.git $HOME/.grunt-init/
git clone git://github.com/gruntjs/grunt-init-jquery.git $HOME/.grunt-init/
git clone git://github.com/gruntjs/grunt-init-node.git $HOME/.grunt-init/

三种分别对应默认grunt模板,jquery插件的grunt模板,node包的grunt模板。

然后就可以适用grunt-init命令来初始化你的Gruntfile.js文件了,例如你要安装默认模板:

grunt-init grunt-init-gruntfile #最后一个参数也可以是模板所在的文件夹

它会问你一些问题,然后根据你的答案创建当前项目的Gruntfile.js文件。

安装所需node包

首先是创建package.json文件(npm的包管理配置文件)。
你可以使用npm init命令创建这个文件或者直接拷贝其它项目的。

然后在项目的根目录下安装你开发依赖的各种grunt包,首先是grunt:

npm install grunt --save-dev

--save-dev可以将你所安装的包自动保存到package.json文件中的devDependencies属性中去,如果你的项目使用时(不仅仅是开发)也需要用到某个包,
你应该使用--save将其保存在dependcies属性中。

你肯定要用到需要其它grunt模块来帮你完成构建任务,这时你就可以用这种方法把他们都加到项目中,例如最常用的concat jshint uglify模块:

npm install grunt-contrib-concat --save-dev
npm install grunt-contrib-jshint --save-dev
npm install grunt-contrib-uglify --save-dev

还有种方式就是先把依赖的包写到package.json中的devDependencies中去,然后直接使用npm nistall来安装。

编辑Gruntfile.js文件使之工作

我们以本站当前使用的Gruntfile.js为例来了解一下grunt设置中最重要的部分。

var path = require('path');
var snippet = require('grunt-contrib-livereload/lib/utils').livereloadSnippet;

module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    // Task configuration.
    concat: {
      dist: {
        src: ['javascripts/common.js','javascripts/respond.js'],
        dest: 'javascripts/main.js'
      }
    },
    uglify: {
      options: {
        banner: '/*script for site: http://chemzqm.me*/'
      },
      dist: {
        src: '<%= concat.dist.dest %>',
        dest: 'javascripts/main.min.js'
      }
    },
    cssmin: {
      compress:{
        files:{
          "stylesheets/styles.min.css":["stylesheets/styles.css"]
        }
      }
    },
    jshint: {
      options: {
        curly: true,
        eqeqeq: true,
        immed: true,
        latedef: true,
        newcap: true,
        noarg: true,
        sub: true,
        undef: true,
        unused: false,
        boss: true,
        eqnull: true,
        browser: true,
        node: true,
        globals: {
          jQuery: true
        }
      },
      all: ['javascripts/common.js','Gruntfile.js']
    },
    regarde: {
      livereload: {
        files: ['_posts/*.md', 'javascripts/common.js', 'stylesheets/styles.css', '*.html'],
        tasks: ['default', 'livereload']
      }
    },
    connect: {
      livereload: {
        options: {
          port: 8000,
          middleware: function(connect, options) {
            return [snippet, connect.static(path.resolve(process.cwd(), '_site'))];
          }
        }
      }
    },
    bgShell:{
      jekyll:{
        cmd: 'jekyll'
      }
    }
  });

  // These plugins provide necessary tasks.
  grunt.loadNpmTasks('grunt-contrib-cssmin');
  grunt.loadNpmTasks('grunt-contrib-concat');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-regarde');
  grunt.loadNpmTasks('grunt-bg-shell');
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-contrib-livereload');

  // Default task.
  grunt.registerTask('default', ['jshint', 'concat', 'uglify', 'cssmin', 'bgShell:jekyll']);
  // start livereload server
  grunt.registerTask('server', ['livereload-start', 'connect', 'regarde']);

};
  • module.exports指向调用该模块时将返回的对象,而当你使用grunt命令的时候这个名为Gruntfile.js的文件就会被grunt找到(0.4.0以上会找Gruntfile.js,而之前是grunt.js,具体的找法也很简单,就是从运行命令的目录开始一直往上找,直到找到为止),然后以grunt对象为参数执行该模块返回的函数。
  • grunt.initConfig方法接受一个Plain Object做为参数,该对象的每个属性名代表一个顶层task名字,而值则是具体的配置。配置任务时要注意不能把子任务名配置成任务要读取的配置属性,例如uglify需要options做为配置属性,这时就不能把子任务名也设置成options。如果任务是multitask则可以为一个任务配置多个子任务,例如配置多个合并文件的任务:
    concat: {
      dist: {
        src: ['javascripts/common.js','javascripts/respond.js'],
        dest: 'javascripts/main.js'
      },  
      dist2:{
        src: ['javascripts/*.js'],
        dest:'javascripts/all.js'
      }   
    },

    这样你就可以通过grunt concat来顺序执行两个合并任务,或者grunt concat:dist只执行dist任务。上面用到的任务都是multitask,具体实践的时候从文档或者源码都可以判断。此外,grunt还支持文件名的模式匹配和模板替换的功能,例如匹配所有js文件可以用*.js,想引用concat:dist任务的dist属性可以用<%= concat.dist.dest %>, 详细的说明还请阅读官方文档

  • grunt.loadNpmTasks负责载入npm模块定义的任务,grunt 0.4.0之后没有了核心任务,所以每个任务都需要开发者写代码载入(还有一个grunt.task.loadTasks方法用来加载指定目录task文件,具体参阅task官方文档)。
  • grunt.registerTask用来注册新的任务,它最简单的形式如下:
    grunt.task.registerTask(taskName, taskList)
    名为taskname的任务出发后,tasklist数组(grunt 0.4.0之前是空格分格的字符串)中的任务将被顺序执行, 比较特殊的是名为default的任务,它将在命令行输入grunt后被调用。grunt.registerTaskgrunt.registerMultiTask还可通过传入函数的方式创建新的任务,具体参阅创建task文档

最后重点介绍一下livereload模块,这个模块分两个任务liveload-startliveload ,前一个任务启动livereload服务,后一个任务负责通知浏览器进行刷新。livereload本身不提供文件的web服务,所以我们需要connect任务插件来提供。然后通过require('grune-contrib-livereload/lib/utils').livereloadSnippet获取到livereload插件提供的中间件函数(实质上就是参数为req, res, next的函数),然后配置到connectmiddleware参数中,这时请求connect服务
(http://localhost:8000) 得到的html就会被动态的加入负责与livereload服务端通讯的一段代码。grunt-regarde模块负责监控文件变化(不用grunt-watch是因为那个获得不了变化的文件名),这里我们根据需要配置成每当文件变化后就执行default任务和livereload任务。最后,配置

grunt.registerTask('server', ['livereload-start', 'connect', 'regarde']);

就可以通过grunt server命令同时启用liveload, connect, 和regard服务(顺序可以任意)

grunt已经开始被越来越多的知名项目所使用了(例如jquery和AngularJS),而且它的任务插件也在不断的扩展之中(例如coffeescript, jade, stylus)。虽说相比Makefile笨拙了一些,但更容易满足团队中各种操作系统下开发的需要。

 

文章来自:segmentfault

linux 内核软中断详解

中断的作用:当一个中断信号到达时,CPU必须停止它当前正做的工作,转而去做中断要求其做的事情。

中断分为同步中断和异步中断两种。

1、同步中断又称异常,是由CPU执行指令时由CPU控制单元产生的。异常又分两种:

(1)、 一种是由程序执行出错造成的,内核通过发送一个unix的信号来处理异常。

(2)、一种是由内核必须处理的异常条件产生的,比如缺页异常,内核执行恢复异常的所有步骤。
2、异步中断,通常我们就叫中断。由其他硬件设备按照CPU时钟信号随机产生。
中断处理程序的一般步骤:
一个中断处理程序的几个中断服务例程之间是串行执行的,并且在一个中断处理程序结束前,不应该再次出现这个中断,所以一般中断处理程序是先禁止该中断,然后处理中断,处理完成后在使能该中断。

有一些中断是可以延迟处理的,这种可延迟中断可以在开中断的情况下执行,执行时允许其他中断抢占他。把可延迟中断从中断处理程序中抽出来有助于使内核保持较短的响应时间。

Linux内核使用三种方法来处理这种可延迟的中断任务:可延迟函数(软中断和tasklets)以及工作队列。工作队列是工作在进程上下文中,可以睡眠,软中断和tasklets 是工作在中断上下文,不可以睡眠。本节只讨论软中断和tasklets。

软中断:
Linux 2.6 版本使用如下几个软中断,不同版本之间略有差异。但一下几个不同版本都包含。

HI_SOFTIRQ=0, 处理高优先级的tasklet

TIMER_SOFTIRQ, 时钟中断相关的tasklet.

NET_TX_SOFTIRQ, 内核把数据报文传送给网卡。

NET_RX_SOFTIRQ, 内核从网卡接收数据报文。

TASKLET_SOFTIRQ, 处理常规tasklet。

低下标代表高优先级。

内核中定义了softirq_vec数组来存放各种软中断。定义如下:

static struct softirq_action softirq_vec[32]__cacheline_aligned_in_smp;

数组元素为 softirq_action,一个元素代码一个软中断。不同的软中断号对应不同的数组的下标。

struct softirq_action
{
void (*action)(struct softirq_action *); //软中断发生时执行软中断的处理函数。
void *data; //软中断的处理函数的参数指针。
};

 
初始化软中断时调用函数 open_softirq().如下代码。

void open_softirq(int nr, void (*action)(struct softirq_action*),void *data)
{
softirq_vec[nr].data = data;
softirq_vec[nr].action = action;

}

 

另外一个跟软中断相关的关键字段是 32 位的 preempt_counte字段,用它来跟踪内核抢占和内核控制路径的嵌套,该字段放在每个进程描述符的 thread_info 字段中。用函数preempt_count()来返回该字段的值。

preempt_count字段
描述
0~7 抢占计数器,记录显示禁用本地cpu内核抢占的次数,值为0时代表内核允许抢占。
8~15 软中断计数器。记录软中断被禁用的次数,0表示软中断被激活。
16~27 硬中断计数器。记录硬中断嵌套的层数。irq_entry()增加它的值,irq_exit()递减它的值。
28

 

当内核明确不允许发生抢占或内核正在中断上下文中运行时,必须禁止内核的抢占功能。为了确定当前进程是否能够被抢占,内核快速检查preempt_counte字段是否等于零。

另一个跟软中断相关的字段是每个CPU都有一个32位掩码的字段

typedef struct {

unsigned int __softirq_pending;

} ____cacheline_aligned irq_cpustat_t;

他描述挂起的软中断。每一位对应相应的软中断。比如0位代表HI_SOFTIRQ.

宏local_softirq_pending()来获取该字段的值。

使用函数raise_softirq()来激活软中断。即把响应的软中断号对应的__softirq_pending中的位置1.表示该软中断被挂起。如果当前CPU不在中断上下文中,唤醒内核线程ksoftirqd来检查被挂起的软中断,然后执行相应软中断处理函数。

内核在如下几个点上检查被挂起的软中断:

1、当调用local_bh_enable()函数激活本地CPU的软中断时。条件满足就调用do_softirq() 来处理软中断。

2、当do_IRQ()完成硬中断处理时调用irq_exit()时调用do_softirq()来处理软中断。

3、当一个特殊内核线程ksoftirq/n被唤醒时,处理软中断。

软中断处理函数详解:

asmlinkage void do_softirq(void)

{

    __u32 pending;

    unsigned long flags;

    /*如果当前处于硬中断中,在硬中断处理函数退出时会调用irq_exit()函数来处理软中断,
      或当前软中断被禁用.所以in_interrupt()返回不为1 就没必要处理软中断,直接返回*/

    if (in_interrupt())

       return;

    /*保持中断寄存器的状态并禁用本地CPU的中断*/

    local_irq_save(flags);

    /*取得当前cpu上__softirq_pending字段,获取本地CPU上挂起的软中断*/

    pending = local_softirq_pending();

    /*如果当前CPU上有挂起的软中断,执行__do_softirq()来处理软中断*/

    if (pending)

    {

        __do_softirq();

}

    /*恢复中断寄存器的状态*/

    local_irq_restore(flags);

}

asmlinkage void __do_softirq(void)

{

    struct softirq_action *h;

    __u32 pending;

    int max_restart = MAX_SOFTIRQ_RESTART; //10

    int cpu;

  /*取得当前cpu上__softirq_pending字段,获取本地CPU上挂起的软中断*/

    pending = local_softirq_pending();

    /*debug 用,不讨论*/

    account_system_vtime(current);

    /*禁止本地cpu的软中断,现在本地cpu上挂起的软中断已经存入pending临时变量中了*/

    __local_bh_disable((unsigned long)__builtin_return_address(0));

    /*debug 用,不讨论*/

    trace_softirq_enter();

    /*取本地cpu id 号*/

    cpu = smp_processor_id();

restart:

    /* Reset the pending bitmask before enabling irqs */

    /*清空本地cpu的__softirq_pending字段*/

    set_softirq_pending(0);

    /*开启本地cpu的硬中断*/

    local_irq_enable();

    /*循环执行被挂起的软中断处理函数。相应的软中断的处理函数存在数组softirq_ver[nr]中的元素 softirq_action->action中*/

    h = softirq_vec;

    do {

       if (pending & 1) {
         h->action(h);
         rcu_bh_qsctr_inc(cpu);
       }

         h++;

         pending >>= 1;

    } while (pending);

    /*禁止本地CPU的硬中断*/

    local_irq_disable();

    /*取本地CPU的__softirq_pending,查看是否还有新的被挂起的软中断并且检查被挂起软中断的次数小于10次,如果条件满足,
      继续处理新的被挂起的软中断*/

    pending = local_softirq_pending();

    if (pending && --max_restart)

      goto restart;

    /*如果有新的挂起的软中断并且处理循环次数已经够了10次,
      唤醒ksoftirq内核线程来处理软中断*/

    if (pending)

      wakeup_softirqd();

    /*debug 用,不讨论*/

  trace_softirq_exit();

    account_system_vtime(current);

    /*使能本地CPU的软中断*/

  _local_bh_enable();

}

 

文章来自:耀洋的博客

 

 

Heartbeat+DRBD+MySQL Replication故障处理

不久前的一次机房网络故障,再一次对我们在Heartbeat+DRBD+MySQL数据库架构运维水平的一个考验,之前不止一次的测试与线上部署,还有之后大言不惭的关于该架构组件的所谓深入理解,在这一次不经意的意外面前又是“很囧”的收场,慌张呀!这次断网导致H-D-M全线异常,真是千载难逢,都让我们赶上啦lol: 下面就把这次的小幸运小幸福和大家分享下,以下是按照问题处理的先后顺序依次讲述。

- MySQL Replication同步异常

当发生网络故障一个小时后,从库io_thread和主库的连接被中断,抛出错误提示:[ERROR] Error reading packet from server: Client requested master to start replication from impossible position ( server_errno=1236),没想到竟遇到了一个古董级的Bug,有点喜出望外了(心想,我也能遇到bug)。最后解决办法,只能拿备份重新做一遍主从。后来,好奇想查查,究竟是怎么导致这个问题,竟发现,从库relay log中的记录比主库binlog中的记录多了2条insert和1条update(0_0!!!…不合逻辑呀?!!)。
阅读全文

优化临时表使用,SQL语句性能提升100倍

【问题现象】

线上mysql数据库爆出一个慢查询,DBA观察发现,查询时服务器IO飙升,IO占用率达到100%, 执行时间长达7s左右
SQL语句如下:
SELECT DISTINCT g.*, cp.name AS cp_name, c.name AS category_name, t.name AS type_name FROMgm_game g LEFT JOIN gm_cp cp ON cp.id = g.cp_id AND cp.deleted = 0 LEFT JOIN gm_category c ON c.id = g.category_id AND c.deleted = 0 LEFT JOIN gm_type t ON t.id = g.type_id AND t.deleted = 0 WHERE g.deleted = 0 ORDER BY g.modify_time DESC LIMIT 20 ;

【问题分析】

使用explain查看执行计划,结果如下:
阅读全文

MYSQL数据丢失讨论

1.  概述

很多企业选择MySQL都会担心它的数据丢失问题,从而选择Oracle,但是其实并不十分清楚什么情况下,各种原因导致MySQL会丢失部分数据。本文不讨论Oracle和MySQL的优劣,仅仅关注MySQL丢失数据的几种情况。希望能够抛砖引玉,让各位MySQL大牛们梳理出MySQL最安全或者性价比合适的适合各种应用场景的方案。

2.  问题定义

一般我们希望把一系列的数据作为一个原子操作,这样的话,这一系列操作,要么提交,要么全部回滚掉。

当我们提交一个事务,数据库要么告诉我们事务提交成功了,要么告诉我们提交失败。

数据库为了效率等原因,数据只保存在内存中,没有真正的写入到磁盘上去。如果数据库响应为“提交成功”,但是由于数据库挂掉,操作系统,数据库主机等任何问题导致这次“提交成功”的事务对数据库的修改没有生效,那么我们认为这个事务的数据丢失了。这个对银行或者支付宝这种业务场景来说是不能接受的。所以,保证数据不丢失也是数据库选择的一个重要衡量指标
阅读全文

理解OAuth 2.0

作者: 阮一峰

日期: 2014年5月12日

OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。

本文对OAuth 2.0的设计思路和运行流程,做一个简明通俗的解释,主要参考材料为RFC 6749

b1

 

一、应用场景

为了理解OAuth的适用场合,让我举一个假设的例子。

有一个”云冲印”的网站,可以将用户储存在Google的照片,冲印出来。用户为了使用该服务,必须让”云冲印”读取自己储存在Google上的照片。

b2

 

问题是只有得到用户的授权,Google才会同意”云冲印”读取这些照片。那么,”云冲印”怎样获得用户的授权呢?

传统方法是,用户将自己的Google用户名和密码,告诉”云冲印”,后者就可以读取用户的照片了。这样的做法有以下几个严重的缺点。

(1)”云冲印”为了后续的服务,会保存用户的密码,这样很不安全。

(2)Google不得不部署密码登录,而我们知道,单纯的密码登录并不安全。

(3)”云冲印”拥有了获取用户储存在Google所有资料的权力,用户没法限制”云冲印”获得授权的范围和有效期。

(4)用户只有修改密码,才能收回赋予”云冲印”的权力。但是这样做,会使得其他所有获得用户授权的第三方应用程序全部失效。

(5)只要有一个第三方应用程序被破解,就会导致用户密码泄漏,以及所有被密码保护的数据泄漏。

OAuth就是为了解决上面这些问题而诞生的。

二、名词定义

在详细讲解OAuth 2.0之前,需要了解几个专用名词。它们对读懂后面的讲解,尤其是几张图,至关重要。

(1) Third-party application:第三方应用程序,本文中又称”客户端”(client),即上一节例子中的”云冲印”。

(2)HTTP service:HTTP服务提供商,本文中简称”服务提供商”,即上一节例子中的Google。

(3)Resource Owner:资源所有者,本文中又称”用户”(user)。

(4)User Agent:用户代理,本文中就是指浏览器。

(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。

(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。

知道了上面这些名词,就不难理解,OAuth的作用就是让”客户端”安全可控地获取”用户”的授权,与”服务商提供商”进行互动。

三、OAuth的思路

OAuth在”客户端”与”服务提供商”之间,设置了一个授权层(authorization layer)。”客户端”不能直接登录”服务提供商”,只能登录授权层,以此将用户与客户端区分开来。”客户端”登录授权层所用的令牌(token),与用户的密码不同。用户可以在登录的时候,指定授权层令牌的权限范围和有效期。

“客户端”登录授权层以后,”服务提供商”根据令牌的权限范围和有效期,向”客户端”开放用户储存的资料。

四、运行流程

OAuth 2.0的运行流程如下图,摘自RFC 6749。

vvv

 

(A)用户打开客户端以后,客户端要求用户给予授权。

(B)用户同意给予客户端授权。

(C)客户端使用上一步获得的授权,向认证服务器申请令牌。

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

(E)客户端使用令牌,向资源服务器申请获取资源。

(F)资源服务器确认令牌无误,同意向客户端开放资源。

不难看出来,上面六个步骤之中,B是关键,即用户怎样才能给于客户端授权。有了这个授权以后,客户端就可以获取令牌,进而凭令牌获取资源。

下面一一讲解客户端获取授权的四种模式。

五、客户端的授权模式

客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

六、授权码模式

授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与”服务提供商”的认证服务器进行互动。

b3

 

它的步骤如下:

(A)用户访问客户端,后者将前者导向认证服务器。

(B)用户选择是否给予客户端授权。

(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的”重定向URI”(redirection URI),同时附上一个授权码。

(D)客户端收到授权码,附上早先的”重定向URI”,向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。

(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

下面是上面这些步骤所需要的参数。

A步骤中,客户端申请认证的URI,包含以下参数:

  • response_type:表示授权类型,必选项,此处的值固定为”code”
  • client_id:表示客户端的ID,必选项
  • redirect_uri:表示重定向URI,可选项
  • scope:表示申请的权限范围,可选项
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

下面是一个例子。

GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com

C步骤中,服务器回应客户端的URI,包含以下参数:

  • code:表示授权码,必选项。该码的有效期应该很短,通常设为10分钟,客户端只能使用该码一次,否则会被授权服务器拒绝。该码与客户端ID和重定向URI,是一一对应关系。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

下面是一个例子。

HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
          &state=xyz

 

D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:

  • grant_type:表示使用的授权模式,必选项,此处的值固定为”authorization_code”。
  • code:表示上一步获得的授权码,必选项。
  • redirect_uri:表示重定向URI,必选项,且必须与A步骤中的该参数值保持一致。
  • client_id:表示客户端ID,必选项。

下面是一个例子。

POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb

 

E步骤中,认证服务器发送的HTTP回复,包含以下参数:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。

下面是一个例子。

 HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }

 

从上面代码可以看到,相关参数使用JSON格式发送(Content-Type: application/json)。此外,HTTP头信息中明确指定不得缓存。

七、简化模式

简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了”授权码”这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。

b3

它的步骤如下:

(A)客户端将用户导向认证服务器。

(B)用户决定是否给于客户端授权。

(C)假设用户给予授权,认证服务器将用户导向客户端指定的”重定向URI”,并在URI的Hash部分包含了访问令牌。

(D)浏览器向资源服务器发出请求,其中不包括上一步收到的Hash值。

(E)资源服务器返回一个网页,其中包含的代码可以获取Hash值中的令牌。

(F)浏览器执行上一步获得的脚本,提取出令牌。

(G)浏览器将令牌发给客户端。

下面是上面这些步骤所需要的参数。

A步骤中,客户端发出的HTTP请求,包含以下参数:

  • response_type:表示授权类型,此处的值固定为”token”,必选项。
  • client_id:表示客户端的ID,必选项。
  • redirect_uri:表示重定向的URI,可选项。
  • scope:表示权限范围,可选项。
  • state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。

下面是一个例子。

 


    GET /authorize?response_type=token&client_id=s6BhdRkqt3&state=xyz
        &redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
    Host: server.example.com

 

C步骤中,认证服务器回应客户端的URI,包含以下参数:

  • access_token:表示访问令牌,必选项。
  • token_type:表示令牌类型,该值大小写不敏感,必选项。
  • expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
  • scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
  • state:如果客户端的请求中包含这个参数,认证服务器的回应也必须一模一样包含这个参数。

下面是一个例子。

 


     HTTP/1.1 302 Found
     Location: http://example.com/cb#access_token=2YotnFZFEjr1zCsicMWpAA
               &state=xyz&token_type=example&expires_in=3600

 

在上面的例子中,认证服务器用HTTP头信息的Location栏,指定浏览器重定向的网址。注意,在这个网址的Hash部分包含了令牌。

根据上面的D步骤,下一步浏览器会访问Location指定的网址,但是Hash部分不会发送。接下来的E步骤,服务提供商的资源服务器发送过来的代码,会提取出Hash中的令牌。

八、密码模式

密码模式(Resource Owner Password Credentials Grant)中,用户向客户端提供自己的用户名和密码。客户端使用这些信息,向”服务商提供商”索要授权。

在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而认证服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式

b7

 

它的步骤如下:

(A)用户向客户端提供用户名和密码。

(B)客户端将用户名和密码发给认证服务器,向后者请求令牌。

(C)认证服务器确认无误后,向客户端提供访问令牌。

B步骤中,客户端发出的HTTP请求,包含以下参数:

  • grant_type:表示授权类型,此处的值固定为”password”,必选项。
  • username:表示用户名,必选项。
  • password:表示用户的密码,必选项。
  • scope:表示权限范围,可选项。

下面是一个例子。

 POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded

     grant_type=password&username=johndoe&password=A3ddj3w

 

C步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。


     HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
       "example_parameter":"example_value"
     }

上面代码中,各个参数的含义参见《授权码模式》一节。

整个过程中,客户端不得保存用户的密码。

九、客户端模式

客户端模式(Client Credentials Grant)指客户端以自己的名义,而不是以用户的名义,向”服务提供商”进行认证。严格地说,客户端模式并不属于OAuth框架所要解决的问题。在这种模式中,用户直接向客户端注册,客户端以自己的名义要求”服务提供商”提供服务,其实不存在授权问题。

b8

它的步骤如下:

(A)客户端向认证服务器进行身份认证,并要求一个访问令牌。

(B)认证服务器确认无误后,向客户端提供访问令牌。

A步骤中,客户端发出的HTTP请求,包含以下参数:

  • granttype:表示授权类型,此处的值固定为”clientcredentials”,必选项。
  • scope:表示权限范围,可选项。

     POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded

     grant_type=client_credentials

认证服务器必须以某种方式,验证客户端身份。

B步骤中,认证服务器向客户端发送访问令牌,下面是一个例子。


     HTTP/1.1 200 OK
     Content-Type: application/json;charset=UTF-8
     Cache-Control: no-store
     Pragma: no-cache

     {
       "access_token":"2YotnFZFEjr1zCsicMWpAA",
       "token_type":"example",
       "expires_in":3600,
       "example_parameter":"example_value"
     }

上面代码中,各个参数的含义参见《授权码模式》一节。

十、更新令牌

如果用户访问的时候,客户端的”访问令牌”已经过期,则需要使用”更新令牌”申请一个新的访问令牌。

客户端发出更新令牌的HTTP请求,包含以下参数:

  • granttype:表示使用的授权模式,此处的值固定为”refreshtoken”,必选项。
  • refresh_token:表示早前收到的更新令牌,必选项。
  • scope:表示申请的授权范围,不可以超出上一次申请的范围,如果省略该参数,则表示与上一次一致。

下面是一个例子。


     POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded

     grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA

(完)

文章来自:阮一峰的网络日志

IT考古:1984年的鼠标使用指南

我们这一代人从小就开始和电脑打交道。我们已经熟练掌握该如何通过鼠标来指挥电脑完成各项工作。但是鼠标作为一种人机交互工具,不是在电脑诞生时就出现,未来也不会一直存在(00后最先熟练掌握的估计是iPad)。1984年,Macintosh 128K(麦金托)首次亮相时,鼠标对人们来说是一个绝对陌生的概念。因此Macintosh必须通过寓教于乐的方法,来教用户如果通过鼠标实现指向、点击、拖动等功能。

Apple的Lisa计算机是首款使用图形用户界面(GUI)的个人电脑,但是Macintosh 128K才是真正的把GUI以及鼠标广泛推向市场的产品。对于很多人来说,Macintosh 128K是他们的首台电脑和首次遇见鼠标。Apple自家的Macintosh使用指南由两部分组成,分别是软盘以及盒式磁带,配有同步音频说明。为了让用户对鼠标这个外形奇怪的新奇设备有更深入的了解,Apple在使用指南中特别列出一章节来指导用户使用鼠标,这一章的名字也很诡异叫“捕鼠”(MOUSING AROUND)
Apple和微软的Windows都有一份详细的使用指南,里面都有教你如何使用鼠标。如果感兴趣你可以装个Windows 95看看,又或者拿部Lisa来玩玩。现在我们就来看看1984年Lisa的鼠标使用指南。
阅读全文

PHP的性能优化

第一部分:PHP程序部分
按需执行:
A:以正则为例, 与普通字符串函数相比,其效率是非常低的([php正则性能]).
很多情况下都可以避免的。
B:按需加载类似Bingo2(spl_autoload_register)
避免重复计算
A:for($i=0;$i<getTotal();$i++) //getTotal没有必要被重复调用
B:比如使用 memcached、redis 来减轻数据库压力。
阅读全文

同工不同酬,年薪 50 万美金的工程师到底有什么神本事?

他们究竟是作哪些事情,或是拥有哪些技术,让他们如此值钱?这些东西有办法用『学』吗?

这位叫 Amin Ariana 的创业家就上 Quora 写了一则被赞到破表的回答,我自己非常同意,也受到很多启发,因此跟 Amin 联络,获得允许,分享他的文章如下。

hhh

以下正文开始

声明:我之前也是 Google 的员工,但是我的回答不代表 Google 的观点。

首先,这问题问得有点奇怪,有点误导人,好像只要工程师做了哪几点,或是获得哪些技能以后,就可以挂到年薪 50 万的保证。其实 Business Insider 那边说得很清楚了,50 万美金其实是薪水跟股票的总和。

  高昂报酬背后的条件:贡献不同等级的能力

要了解高昂报酬背后的条件,我先先来打个比方。

假设你是村子里面非常重要的,负责水源供给的劳工。我们分为:一类劳工,与二类劳工。

一类劳工会拎起一到两个水桶,冲到水源旁边,装满它们,把他们两个挑回来。水大概够 20 个人喝,如此一来有水喝的村民就皆大欢喜了。这个劳工挑水的过程可能会喝掉一些水,然后回到村中,他也可能可以分一些水回家作他的报酬。

二类劳工不太理所谓『公平分水』的概念,他会拿起一把铲子,带上一只水杯,然后忽然间就消失了。他跑到水源处,挖一条可以通到村庄的小溪,希望可以把水源引过来。每当他拖着疲惫的身躯,拎着空杯子回到村庄的时候,总会引起一阵失望,但是不知道为什么那村中的长老相信他,相信他在做的事情(还丢根骨头给他啃,让他不会饿肚子)。

某天,他直挺挺的站在村庄前面,他身后白涔涔地流躺着一条饮用水的小溪。这条小溪立刻把一类专门经营『水快递』的劳工赶出市场,他们只好转行,加入别的团队。这个二类劳工呢,看他对这条小溪拥有多少的控制权,一般而言,他有小溪很大部分的拥有权。

后来村庄决定要把小溪整个买下来,整进整个村庄的供水系统,于是村庄拿了他们一部分的财产去换,比如说土地啊什么的,这个二类劳工于是瞬间升级变成地主了。

村子里面的媒体注意到村子给这个二类劳工的薪水奇高,别村的人根本挖不动他(他应该是有跟村子签订协议,比如要在村子里留两年,才能领完全额的报酬之类的),于是出了一篇报导,写得好像别村出高价挖角,却因为村子给的薪水太好,以致于这个二类劳工根本不会考虑。
阅读全文