[转载]smarty 中fetch和display函数区别 - 宋红光的日志 - 网易博客

mikel阅读(1116)

[转载]smarty 中fetch和display函数区别 – 宋红光的日志 – 网易博客.在Smarty模板函数里面有这样一个方法:fetch(“template.htm”),他和 display(“template.htm”);最大的不同就是fetch()是把内容输出给一个变量,而display()是把内容输出给浏览器,这 样我们就可以用一个变量来接收fetch()的输出,然后把他写入到文件中去.

function insert_cache($file, $display=false) {

ob_start();

if(file_exists($file)) {

include $file;

}else {

exit();

}

ob_get_contents($file);

if($display) { echo $result}

else { return $result}

}

?>

[转载]ecshop中的Ajax.call定义及讲解_活在当下,展现自我。_百度空间

mikel阅读(903)

[转载]ecshop中的Ajax.call定义及讲解_活在当下,展现自我。_百度空间.

在ECShop中,经常看到这样的定义:

Ajax.call(‘api_tb_goods_upload.php?is_ajax=1&&act=get_goods_list’, filters1, queryGoodsResponse , ‘POST’, ‘JSON’);

其中,Ajax.call是ecshop中的非常重要的ajax回调函数。

 

该函数定义在:

js / transport.js

代码如下:

var Transport =
{
/* *
* 存储本对象所在的文件名。
*
* @static
*/
filename : "transport.js",

/* *
* 存储是否进入调试模式的开关,打印调试消息的方式,换行符,调试用的容器的ID。
*
* @private
*/
debugging :
{
    isDebugging : 0,
    debuggingMode : 0,
    linefeed : "",
    containerId : 0
},

/* *
* 设置调试模式以及打印调试消息方式的方法。
*
* @public
* @param   {int}   是否打开调试模式      0:关闭,1:打开
* @param   {int}   打印调试消息的方式    0:alert,1:innerHTML
*
*/
debug : function (isDebugging, debuggingMode)
{
    this.debugging =
    {
      "isDebugging" : isDebugging,
      "debuggingMode" : debuggingMode,
      "linefeed" : debuggingMode ? "<br />" : "\n",
      "containerId" : "dubugging-container" + new Date().getTime()
    };
},

/* *
* 传输完毕后自动调用的方法,优先级比用户从run()方法中传入的回调函数高。
*
* @public
*/
onComplete : function ()
{
},

/* *
* 传输过程中自动调用的方法。
*
* @public
*/
onRunning : function ()
{
},

/* *
* 调用此方法发送HTTP请求。
*
* @public
* @param   {string}    url             请求的URL地址
* @param   {mix}       params          发送参数
* @param   {Function} callback        回调函数
* @param   {string}    ransferMode     请求的方式,有"GET"和"POST"两种
* @param   {string}    responseType    响应类型,有"JSON"、"XML"和"TEXT"三种
* @param   {boolean}   asyn            是否异步请求的方式
* @param   {boolean}   quiet           是否安静模式请求
*/
run : function (url, params, callback, transferMode, responseType, asyn, quiet)
{
    /* 处理用户在调用该方法时输入的参数 */
    params = this.parseParams(params);
    transferMode = typeof(transferMode) === "string"
    && transferMode.toUpperCase() === "GET"
    ? "GET"
    : "POST";

    if (transferMode === "GET")
    {
      var d = new Date();

      url += params ? (url.indexOf("?") === - 1 ? "?" : "&") + params : "";
      url = encodeURI(url) + (url.indexOf("?") === - 1 ? "?" : "&") + d.getTime() + d.getMilliseconds();
      params = null;
    }

    responseType = typeof(responseType) === "string" && ((responseType = responseType.toUpperCase()) === "JSON" || responseType === "XML") ? responseType : "TEXT";
    asyn = asyn === false ? false : true;

    /* 处理HTTP请求和响应 */
    var xhr = this.createXMLHttpRequest();
    try
    {
      var self = this;

   /**/
      if (typeof(self.onRunning) === "function" && !quiet)
      {
        self.onRunning();
      }
       //alert(url);
      xhr.open(transferMode, url, asyn);
     
      if (transferMode === "POST")
      {
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      }
  
      if (asyn)
      {
        xhr.onreadystatechange = function (){
          
          if (xhr.readyState == 4){
    
            switch ( xhr.status ){
              case 0:
              case 200: // OK!
                if (typeof(self.onComplete) === "function"){
                  self.onComplete();
       //alert(1);
                }

                if (typeof(callback) === "function"){
                  callback.call(self, self.parseResult(responseType, xhr), xhr.responseText);
       //alert(2);
                }
              break;
              case 304: // Not Modified
              break;
              case 400: // Bad Request
                 alert("XmlHttpRequest status: [400] Bad Request");
              break;
              case 404: // Not Found
                alert("XmlHttpRequest status: [404] \nThe requested URL "+url+" was not found on this server.");
              break;
              case 409: // Conflict
              break;
              case 503: // Service Unavailable
                 alert("XmlHttpRequest status: [503] Service Unavailable");
              break;
              default:
                 alert("XmlHttpRequest status: [" + xhr.status + "] Unknow status.");
            }
            xhr = null;
          }
        }
        if (xhr != null) xhr.send(params);
      }
      else
      {
      /**/
        if (typeof(self.onRunning) === "function")
        {
          self.onRunning();
        }
        xhr.send(params);
       
        var result = self.parseResult(responseType, xhr);
        //xhr = null;
        if (typeof(self.onComplete) === "function")
        {
          self.onComplete();
        }
        if (typeof(callback) === "function")
        {
          callback.call(self, result, xhr.responseText);
        }

        return result;
      }
    }
    catch (ex)
    {
      if (typeof(self.onComplete) === "function")
      {
        self.onComplete();
      }

      alert(this.filename + "/run() error:" + ex.description);
    }
},

/* *
* 如果开启了调试模式,该方法会打印出相应的信息。
*
* @private
* @param   {string}    info    调试信息
* @param   {string}    type    信息类型
*/
displayDebuggingInfo : function (info, type)
{
    if ( ! this.debugging.debuggingMode)
    {
      alert(info);
    }
    else
    {

      var id = this.debugging.containerId;
      if ( ! document.getElementById(id))
      {
        div = document.createElement("DIV");
        div.id = id;
        div.style.position = "absolute";
        div.style.width = "98%";
        div.style.border = "1px solid #f00";
        div.style.backgroundColor = "#eef";
        var pageYOffset = document.body.scrollTop
        || window.pageYOffset
        || 0;
        div.style.top = document.body.clientHeight * 0.6
        + pageYOffset
        + "px";
        document.body.appendChild(div);
        div.innerHTML = "<div></div>"
        + "<hr style='height:1px;border:1px dashed red;'>"
        + "<div></div>";
      }

      var subDivs = div.getElementsByTagName("DIV");
      if (type === "param")
      {
        subDivs[0].innerHTML = info;
      }
      else
      {
        subDivs[1].innerHTML = info;
      }
    }
},

/* *
* 创建XMLHttpRequest对象的方法。
*
* @private
* @return      返回一个XMLHttpRequest对象
* @type    Object
*/
createXMLHttpRequest : function ()
{
    var xhr = null;

    if (window.ActiveXObject)
    {
      var versions = ['Microsoft.XMLHTTP', 'MSXML6.XMLHTTP', 'MSXML5.XMLHTTP', 'MSXML4.XMLHTTP', 'MSXML3.XMLHTTP', 'MSXML2.XMLHTTP', 'MSXML.XMLHTTP'];

      for (var i = 0; i < versions.length; i ++ )
      {
        try
        {
          xhr = new ActiveXObject(versions[i]);
          break;
        }
        catch (ex)
        {
          continue;
        }
      }
    }
    else
    {
      xhr = new XMLHttpRequest();
    }

    return xhr;
},

/* *
* 当传输过程发生错误时将调用此方法。
*
* @private
* @param   {Object}    xhr     XMLHttpRequest对象
* @param   {String}    url     HTTP请求的地址
*/
onXMLHttpRequestError : function (xhr, url)
{
    throw "URL: " + url + "\n"
    + "readyState: " + xhr.readyState + "\n"
    + "state: " + xhr.status + "\n"
    + "headers: " + xhr.getAllResponseHeaders();
},

/* *
* 对将要发送的参数进行格式化。
*
* @private
* @params {mix}    params      将要发送的参数
* @return 返回合法的参数
* @type string
*/
parseParams : function (params)
{
    var legalParams = "";
    params = params ? params : "";

    if (typeof(params) === "string")
    {
      legalParams = params;
    }
    else if (typeof(params) === "object")
    {
      try
      {
        legalParams = "JSON=" + obj2str(params);
      }
      catch (ex)
      {
        alert("Can't stringify JSON!");
        return false;
      }
    }
    else
    {
      alert("Invalid parameters!");
      return false;
    }

    if (this.debugging.isDebugging)
    {
      var lf = this.debugging.linefeed,
      info = "[Original Parameters]" + lf + params + lf + lf
      + "[Parsed Parameters]" + lf + legalParams;

      this.displayDebuggingInfo(info, "param");
    }
    return legalParams;
},

/* *
* 对返回的HTTP响应结果进行过滤。
*
* @public
* @params   {mix}   result   HTTP响应结果
* @return 返回过滤后的结果
* @type string
*/
preFilter : function (result)
{
    return result.replace(/\xEF\xBB\xBF/g, "");
},

/* *
* 对返回的结果进行格式化。
*
* @private
* @return 返回特定格式的数据结果
* @type mix
*/
parseResult : function (responseType, xhr)
{
    var result = null;
    switch (responseType)
    {
      case "JSON" :
     
        result = this.preFilter(xhr.responseText);
       
        try
        {
          result = result.parseJSON();
        }
        catch (ex)
        {
          throw this.filename + "/parseResult() error: can't parse to JSON.\n\n" + xhr.responseText;
        }
        break;
      case "XML" :
        result = xhr.responseXML;
        break;
      case "TEXT" :
        result = this.preFilter(xhr.responseText);
        break;
      default :
        throw this.filename + "/parseResult() error: unknown response type:" + responseType;
    }
   
    if (this.debugging.isDebugging)
    {
  
  
      var lf = this.debugging.linefeed,
      info = "[Response Result of " + responseType + " Format]" + lf
      + result;

      if (responseType === "JSON")
      {
        info = "[Response Result of TEXT Format]" + lf
        + xhr.responseText + lf + lf
        + info;
      }

      this.displayDebuggingInfo(info, "result");
    }

  
    return result;
}
};

/* 定义两个别名 */
var Ajax = Transport;
Ajax.call = Transport.run;

[转载]高性能网站架构设计之缓存篇(1)- Redis的安装与使用 - linli8 - 博客园

mikel阅读(1004)

[转载]高性能网站架构设计之缓存篇(1)- Redis的安装与使用 – linli8 – 博客园.

一、什么 Redis

REmote DIctionary Server,简称 Redis,是一个类似于Memcached的Key-Value存储系统。相比Memcached,它支持更丰富的数据结构,包括string(字符串)、list(链表)、set(集合)、zset(sorted set –有序集合)和hash(哈希类型),并提供了数据持久化机制,在某些场景下,你完全可以把它当做非关系型数据库来使用。它是一个高性能的存储系统,能支持超过 100K+ 每秒的读写频率。同时还支持消息的发布/订阅,从而让你在构建高性能消息队列系统时多了另一种选择。

二、下载

点击这里下载

我在这里下载的是redis-3.0.0-beta5版,redis从3.0开始支持集群。

三、安装

以下安装均为 OSX 操作系统上的步骤:

1、找到你刚刚下载的 redis-3.0.0-beta5.tar.gz文件,解压这个文件。

2、将解压后的文件夹拷贝到你很容易找到的目录下,并修改文件夹名为redis,因为版本号太长了。

2、打开终端,进入redis文件夹,命令如下:

Last login: Fri May 30 21:33:25 on ttys000

zhaoguihuadediannao:~ zhaogh$ cd applications/dev/redis

zhaoguihuadediannao:redis zhaogh$

3、在命令提示符出输入 make 命令,稍等片刻,便能完成安装。如果系统提示找不到 make 命令,请参考这篇文章

装好了,是不是有点小激动。抽根烟,我们继续吧。

四、使用

1、启动服务,先进入 src 目录,然后执行 redis-server。

zhaoguihuadediannao:redis zhaogh$ cd src

zhaoguihuadediannao:src zhaogh$ ./redis-server

你将会看到:

2343:M 30 May 21:42:50.741 # Server started, Redis version 2.9.54

2343:M 30 May 21:42:50.741 * The server is now ready to accept connections on port 6379

我没看错吧,TMD我明明下载的3.0.0,怎么显示 Redis version 2.9.54 ? 好吧,这不是重点,我也懒得追究了。

6379 是 redis 默认端口,在后续的文章中你将知道如何修改这个默认端口。

2、客户端连接。

你会发现在执行了第四步之后,我们无法再执行其他命令了,怎么办?因为redis独占了此进程,后面告诉你们如果将它修改为后台运行。

我们暂且再打开一个终端吧。还是先进入 src 目录:

zhaoguihuadediannao:~ zhaogh$ cd applications/dev/redis/src

zhaoguihuadediannao:src zhaogh$

输入 ./redis-cli 命令:

zhaoguihuadediannao:src zhaogh$ ./redis-cli

127.0.0.1:6379>

已经连接上了,哈哈哈。

3、测试几个 redis 命令:

127.0.0.1:6379> set testkey001 testkey001

OK

127.0.0.1:6379> get testkey001

“testkey001”

127.0.0.1:6379> append testkey001 aaa

(integer) 13

127.0.0.1:6379> get testkey001

“testkey001aaa”

127.0.0.1:6379>

4、关闭连接,执行 quit 命令

127.0.0.1:6379> quit

zhaoguihuadediannao:src zhaogh$

5、关闭服务。

由于我们刚才已经退出了客户端,我们再次连接,并执行 shutdown 命令:

127.0.0.1:6379> shutdown

127.0.0.1:6379>

看看另外一个另外一个终端窗口中是不是显示 [进程已完成]。当然,你也可以通过 kill 命令来关闭服务。

今天就介绍这么多吧,下一篇你将看到如何使用c#客户端API操作redis

[转载]如何构建日均千万PV Web站点 三 Sharding - 姜明则 - 博客园

mikel阅读(1069)

转载如何构建日均千万PV Web站点 三 Sharding – 姜明则 – 博客园.

其实国内许多大型网站为了应对日益复杂的业务场景,通过使用分而治之的手段将整个网站业务分成不同的产品线,比如说国内那些大型购物交易网站它们都将自己的网站首页、商铺、订单、买家、卖家等拆分不同的产品线,分归不同的业务团队负责;

集体到技术,也会根据产品线划分,将一个网站拆分成许多不同的应用,每个应用用独立部署维护。应用之间可以通过一个超链接建立 关系(在首页上的导航链接每个都指向不同的应用地址),也可以通过消息队列进行数据分发,当然最多的还是通过访问同一个数据库存储系统来构成一个关联的完 整系统 此时的架构如下图所示:

分布式服务,随着业务拆分越来越小,存储系统越来越庞大,应用系统的整体复杂度呈指数级增加,部署维护越来越困难,由于所有应用要和所有数据库系统连接,在数万台服务器规模的网站中,这些连接的数目是服务器规模的平方,导致数据库连接资源不足,拒绝服务。

既然每一个应用系统都需要执行许多相同的业务操作,比如用户管理、商品管理等,那么可以将这些共用的业务提取出来,独立部署。由这些可复用的业务连接数据库,提供共用服务,而应用系统只需要管理用户界面,通过分布式服务调用共用业务服务完成具体业务操作

数据库如何sharding?

数据的切分(Sharding)根据其切分规则的类型,可以分为两种切分模式。一 种是按照 不同的表(或者 Schema)来切分到不同的数据库(主机)之上,这种切可以称之为数据的 垂直(纵向)切分;另外一种则是根据表中的数据的逻辑关系,将同一个表中的数据按照某 种条件拆分到多台数据库(主机)上面,这种切分称之为数据的水平(横向)切分。 垂直切分的最大特点就是规则简单,实施也更为方便,尤其适合各业务之间的耦合度非 常低,相互影响很小,业务逻辑非常清晰的系统。在这种系统中,可以很容易做到将不同业 务模块所使用的表分拆到不同的数据库中。根据不同的表来进行拆分,对应用程序的影响也 更小,拆分规则也会比较简单清晰。 水平切分于垂直切分相比,相对来说稍微复杂一些。因为要将同一个表中的不同数据拆 分到不同的数据库中,对于应用程序来说,拆分规则本身就较根据表名来拆分更为复杂, 后 期的数据维护也会更为复杂一些。 当我们某个(或者某些)表的数据量和访问量特别的大,通过垂直切分将其放在独立的 设备上后仍然无法满足性能要求,这时候我们就必须将垂直切分和水平切分相结合,先垂直 切分,然后再水平切分,才能解决这种超大型表的性能问题。 下面我们就针对垂直、水平以及组合切分这三种数据切分方式的架构实现及切分后数据 的整合进行相应的分析。 14.2 数据的垂直切分 我们先来看一下,数据的垂直切分到底是如何一个切分法的。数据的垂直切分,也可以 称之为纵向切分。将数据库想象成为由很多个一大块一大块的“数据块”(表)组成,我们 垂直的将这些 “数据块”切开,然后将他们分散到多台数据库主机上面。这样的切分方法就 是一个垂直(纵向)的数据切分。 一个架构设计较好的应用系统,其总体功能肯定是由很多个功能模块所组成的,而每一 个功能模块所需要的数据对应到数据库中就是一个或者多个表。而在架构设计中,各个功能 模块相互之间的交互点越统一越少,系统的耦合度就越低,系统各个模块的维护性以及扩展 性也就越好。这样的系统,实现数据的垂直切分也就越容易。 当我们的功能模块越清晰,耦合度越低,数据垂直切分的规则定义也就越容易。完全可 以根据功能模块来进行数据的切分,不同功能模块的数据存放于不同的数据库主机中,可以 很容易就避免掉跨数据库的 Join 存在,同时系统架构也非常的清晰.

当然,很难有系统能够做到所有功能模块所使用的表完全独立,完全不需要访问对方的 表或者需要两个模块的表进行 Join 操作。这种情况下,我们就必须根据实际的应用场景进 行评估权衡。决定是迁就应用程序将需要 Join 的表的相关某快都存放在同一个数据库中, 还是让应用程序做更多的事情,也就是程序完全通过模块接口取得不同数据库中的数据, 然 后在程序中完成 Join 操作。 一般来说,如果是一个负载相对不是很大的系统,而且表关联又非常的频繁,那可能数 据库让步,将几个相关模块合并在一起减少应用程序的工作的方案可以减少较多的工作量, 是一个可行的方案。 当然,通过数据库的让步,让多个模块集中共用数据源,实际上也是简介的默许了各模 块架构耦合度增大的发展,可能会让以后的架构越来越恶化。尤其是当发展到一定阶段之后 , 发现数据库实在无法承担这些表所带来的压力,不得不面临再次切分的时候,所带来的架构 改造成本可能会远远大于最初的时候。 所以,在数据库进行垂直切分的时候,如何切分,切分到什么样的程度,是一个比较考 验人的难题。只能在实际的应用场景中通过平衡各方面的成本和收益,才能分析出一个真正 适合自己的拆分方案。 比如在本书所使用示例系统的 example 数据库,我们简单的分析一下,然后再设计一 个简单的切分规则,进行一次垂直垂直拆分。 系统功能可以基本分为四个功能模块:用户,群组消息,相册以及事件,

分别对应为如 下这些表:

1. 用户模块表:user,user_profile,user_group,user_photo_album

2. 群组讨论表:groups,group_message,group_message_content,top_message

3. 相册相关表:photo,photo_album,photo_album_relation,photo_comment

 

4. 事件信息表:event 初略一看,没有哪一个模块可以脱离其他模块独立存在,模块与模块之间都存在着关系 , 莫非无法切分? 当然不是,我们再稍微深入分析一下,可以发现,虽然各个模块所使用的表之间都有关 联,但是关联关系还算比较清晰,也比较简单。 群组讨论模块和用户模块之间主要存在通过用户或者是群组关系来进行关联。一般 关联的时候都会是通过用户的 id 或者 nick_name 以及 group 的 id 来进行关 联,通过模块之间的接口实现不会带来太多麻烦; 相册模块仅仅与用户模块存在通过用户的关联。这两个模块之间的关联基本就有通 过用户 id 关联的内容,简单清晰,接口明确; 事件模块与各个模块可能都有关联,但是都只关注其各个模块中对象的 ID 信 息 , 同样可以做到很容易分拆。 所以,我们第一步可以将数据库按照功能模块相关的表进行一次垂直拆分,每个模块所 涉及的表单独到一个数据库中,模块与模块之间的表关联都在应用系统端通过藉口来处理。 如下图所示: 通过这样的垂直切分之后,之前只能通过一个数据库来提供的服务,就被分拆成四个数 据库来提供服务,服务能力自然是增加几倍了。 垂直切分的优点 数据库的拆分简单明了,拆分规则明确; 应用程序模块清晰明确,整合容易; 数据维护方便易行,容易定位; 垂直切分的缺点 部分表关联无法在数据库级别完成,需要在程序中完成; 对于访问极其频繁且数据量超大的表仍然存在性能平静,不一定能满足要求; 事务处理相对更为复杂; 切分达到一定程度之后,扩展性会遇到限制; 过读切分可能会带来系统过渡复杂而难以维护。 针对于垂直切分可能遇到数据切分及事务问题,在数据库层面实在是很难找到一个较好 的处理方案。实际应用案例中,数据库的垂直切分大多是与应用系统的模块相对应,同一个 模块的数据源存放于同一个数据库中,可以解决模块内部的数据关联问题。而模块与模块之间,则通过应用程序以服务接口方式来相互提供所需要的数据。虽然这样 做在数据库的总体 操作次数方面确实会有所增加,但是在系统整体扩展性以及架构模块化方面,都是有益的。 可能在某些操作的单次响应时间会稍有增加,但是系统的整体性能很可能反而会有一定的提 升。而扩展瓶颈问题,就只能依靠下一节将要介绍的数据水平切分架构来解决了。

14.3 数据的水平切分 上面一节分析介绍了数据的垂直切分,这一节再分析一下数据的水平切分。数据的垂直 切分基本上可以简单的理解为按照表按照模块来切分数据,而水平切分就不再是按照表或者 是功能模块来切分了。一般来说,简单的水平切分主要是将某个访问极其平凡的表再按照某 个字段的某种规则来分散到多个表之中,每个表中包含一部分数据。 简单来说,我们可以将数据的水平切分理解为是按照数据行的切分,就是将表中的某些 行切分到一个数据库,而另外的某些行又切分到其他的数据库中。当然,为了能够比较容易的判定各行数据被切分到哪个数据库中了,切分总是都需要按照某种特定 的规则来进行的。 如根据某个数字类型字段基于特定数目取模,某个时间类型字段的范围,或者是某个字符类 型字段的 hash 值。如果整个系统中大部分核心表都可以通过某个字段来进行关联,那这个 字段自然是一个进行水平分区的上上之选了,当然,非常特殊无法使用就只能另选其他了。 一般来说,像现在互联网非常火爆的 Web2.0 类型的网站,基本上大部分数据都能够通 过会员用户信息关联上,可能很多核心表都非常适合通过会员 ID 来进行数据的水平切分。 而像论坛社区讨论系统,就更容易切分了,非常容易按照论坛编号来进行数据的水平切分。 切分之后基本上不会出现各个库之间的交互。 如我们的示例系统,所有数据都是和用户关联的,那么我们就可以根据用户来进行水平 拆分,将不同用户的数据切分到不同的数据库中。当然,唯一有点区别的是用户模块中的 groups 表和用户没有直接关系,所以 groups 不能根据用户来进行水平拆分。对于这种特 殊情况下的表,我们完全可以独立出来,单独放在一个独立的数据库中。其实这个做法可以 说是利用了前面一节所介绍的 “数据的垂直切分”方法,我将在下一节中更为详细的介绍这 种垂直切分与水平切分同时使用的联合切分方法。 所以,对于我们的示例数据库来说,大部分的表都可以根据用户 ID 来进行水平的切分 。 不同用户相关的数据进行切分之后存放在不同的数据库中。如将所有用户 ID 通过 2 取模 然后分别存放于两个不同的数据库中。每个和用户 ID 关联上的表都可以这样切分。这样, 基本上每个用户相关的数据,都在同一个数据库中,即使是需要关联,也可以非常简单的关 联上。 我们可以通过下图来更为直观的展示水平切分相关信息: 水平切分的优点 表关联基本能够在数据库端全部完成; 不会存在某些超大型数据量和高负载的表遇到瓶颈的问题; 应用程序端整体架构改动相对较少; 事务处理相对简单; 只要切分规则能够定义好,基本上较难遇到扩展性限制; 水平切分的缺点 切分规则相对更为复杂,很难抽象出一个能够满足整个数据库的切分规则; 后期数据的维护难度有所增加,人为手工定位数据更困难; 应用系统各模块耦合度较高,可能会对后面数据的迁移拆分造成一定的困难。

14.4 垂直与水平联合切分的使用 上面两节内容中,我们分别,了解了“垂直”和“水平”这两种切分方式的实现以及切 分之后的架构信息,同时也分析了两种架构各自的优缺点。但是在实际的应用场景中,除了 那些负载并不是太大,业务逻辑也相对较简单的系统可以通过上面两种切分方法之一来解决 扩展性问题之外,恐怕其他大部分业务逻辑稍微复杂一点,系统负载大一些的系统,都无法 通过上面任何一种数据的切分方法来实现较好的扩展性,而需要将上述两种切分方法结合使 用,不同的场景使用不同的切分方法。 在这一节中,我将结合垂直切分和水平切分各自的优缺点,进一步完善我们的整体架构 , 让系统的扩展性进一步提高。 一般来说,我们数据库中的所有表很难通过某一个(或少数几个)字段全部关联起来, 所以很难简单的仅仅通过数据的水平切分来解决所有问题。而垂直切分也只能解决部分问 题,对于那些负载非常高的系统,即使仅仅只是单个表都无法通过单台数据库主机来承担其 负载。我们必须结合“垂直”和“水平”两种切分方式同时使用,充分利用两者的优点,避 开其缺点。 每一个应用系统的负载都是一步一步增长上来的,在开始遇到性能瓶颈的时候,大多数 架构师和 DBA 都会选择先进行数据的垂直拆分,因为这样的成本最先,最符合这个时期所追 求的最大投入产出比。然而,随着业务的不断扩张,系统负载的持续增长,在系统稳定一段 时期之后,经过了垂直拆分之后的数据库集群可能又再一次不堪重负,遇到了性能瓶颈。 这时候我们该如何抉择?是再次进一步细分模块呢,还是寻求其他的办法来解决?如果 我们再一次像最开始那样继续细分模块,进行数据的垂直切分,那我们可能在不久的将来, 又会遇到现在所面对的同样的问题。而且随着模块的不断的细化,应用系统的架构也会越来 越复杂,整个系统很可能会出现失控的局面。 这时候我们就必须要通过数据的水平切分的优势,来解决这里所遇到的问题。而且, 我 们完全不必要在使用数据水平切分的时候,推倒之前进行数据垂直切分的成果,而是在其基 础上利用水平切分的优势来避开垂直切分的弊端,解决系统复杂性不断扩大的问题。而水平 拆分的弊端(规则难以统一)也已经被之前的垂直切分解决掉了,让水平拆分可以进行的得 心应手。 对于我们的示例数据库,假设在最开始,我们进行了数据的垂直切分,然而随着业务的 不断增长,数据库系统遇到了瓶颈,我们选择重构数据库集群的架构。如何重构?考虑到之 前已经做好了数据的垂直切分,而且模块结构清晰明确。而业务增长的势头越来越猛,即使 现在进一步再次拆分模块,也坚持不了太久。我们选择了在垂直切分的基础上再进行水平拆 分。 在经历过垂直拆分后的各个数据库集群中的每一个都只有一个功能模块,而每个功能模 块中的所有表基本上都会与某个字段进行关联。如用户模块全部都可以通过用户 ID 进行切 分,群组讨论模块则都通过群组 ID 来切分,相册模块则根据相册 ID 来进切分,最后的事 件通知信息表考虑到数据的时限性(仅仅只会访问最近某个事件段的信息),则考虑按时间 来切分。 下图展示了切分后的整个架构: 实际上,在很多大型的应用系统中,垂直切分和水平切这两种数据的切分方法基本上都 是并存的,而且经常在不断的交替进行,以不断的增加系统的扩展能力。我们在应对不同的 应用场景的时候,也需要充分考虑到这两种切分方法各自的局限,以及各自的优势,在不同 的时期(负载压力)使用不同的结合方式。 联合切分的优点 可以充分利用垂直切分和水平切分各自的优势而避免各自的缺陷; 让系统扩展性得到最大化提升; 联合切分的缺点 数据库系统架构比较复杂,维护难度更大;应用程序架构也相对更复杂。

关于数据库如何sharding详情请参考<<MySQL性能调优与架构设计>>

[转载]sqlserver 构架与性能优化 - EchoSong - 博客园

mikel阅读(1221)

[转载]sqlserver 构架与性能优化 – EchoSong – 博客园.

一、SQLServer 构架结构

1、查询优化器三阶段

 

1)、找计划缓存如果找到直接使用

2)、简单语句生成0开销的执行计划

3)、正式优化 一般情况下优化到开销小于1.0就会停止

定义…

 

Select * from sys.dm_os_memory_cache_entries where type=’cachestore_phdr’

 

2、写入数据页

1)、惰性写入器(定期检验缓冲区的邻居l领进临界值,如果过小就会去检验很久么有的缓存(老化页)直接干掉,脏页写入到硬盘,然后把这个内存空间标注为空闲)

2)、Checkpoint 检验点进程只把脏页面写入到数据页,如果断电重启会从日志文件读取恢复

 

3)、内存配置的依据

select count(*)*8/1024 as ‘Buffer cached size(MB)’

, case database_id when 32767 THEN ‘ResourceDb’ else db_name(database_id)

end AS ‘Database’

 from sys.dm_os_buffer_descriptors group by db_name(database_id), database_id

order by ‘Buffer cached size(MB)’ desc

 

 

二、日志 备份和恢复

 

  1. 有序的文件事务日志(事务提交九写了事务日至)

描述记录包括 发生改变 数据页 和页 码。 增加或删除的数据,已经这些改变所属的事务 信息,还有事务的起止日期和时间信息

 

2、事务日志的数据恢复

在最后一个检查点之后发生的数据将被用作恢复

 

ldf ,

 

 

问题: 事务日志一直不断增长采取办法

恢复模式

简单,完整,大容量日志

 

1、  完整模式只有做了事务日志才会截断

2、  可以改成简单模式直接截断(强烈不建议使用)简单恢复模式只要checkpoint就会截断

3、  搜索

 

全备

差异备份

事务日志备份

 

三、深入理解索引

 

 

 

 

 

 

MsSQL 只有一种索引模式 B-Tree 模式

1、  聚集索引

 

2、  非聚集索索引

 

 

 

动作描述

使用聚集索引

使用非聚集索引

列经常被分组排序

返回某范围内的数据

不应

一个或极少不同值

不应

不应

小数目的不同值

不应

大数目的不同值

不应

频繁更新的列

不应

外键列

主键列

频繁修改索引列

不应

 

 

 

 

 

SELECT  * FROM dbo.PCE_Admin_Info WHERE Admin_Account=’admina’ AND Admin_Id = 1

 

 

 

四、优化工具使用

1、  执行计划– EXPLAIN

2、  查询优化顾问

3、  活动监视器

4、  SQL 分析器

 

索引查找 和索引扫描

 

USE db_30014

SELECT  * FROM dbo.PCE_Site_Mobile_Region_Bind

SELECT  * FROM dbo.PCE_Site_Mobile_Region_Bind WHERE Bind_MobileNo >1000

缓存体现

SELECT  * FROM dbo.PCE_Site_Mobile_Region_Bind WHERE Bind_MobileNo >1900000

SELECT  * FROM dbo.PCE_Site_Mobile_Region_Bind

[转载]Cocos2dx引擎优化(2) ---自定义资源包系统 - 宏波.王 - 博客园

mikel阅读(960)

[转载]Cocos2dx引擎优化(2) —自定义资源包系统 – 宏波.王 – 博客园.

把游戏中的资源文件(纹理,模型,材质,音乐,配置xml,json,脚本)打包有很多好处,也成为MMO开发的基本常识.对资源进行打包可以带来以下好处:

. 增加游戏资源和脚本破解的难度。大多游戏制作公司都不希望自己花高昂代价制作的资料被人全盘爬过去使用,更不忍受逻辑脚本完全暴露在同行的面前。

. 自定义的资源包比访问散文件资源有更快的查找和读取速度,消耗更少的系统资源,如文件句柄。

. 自定义资源包可以提供更简单可用的文件存取API、加解密和压缩方案。

. 一般来说,打包的资源也会比散文件形式的资源占用更小的磁盘存储空间。

但Cocos2dx引擎没有提供资源包的支持,所以我自己实现了一个。

    一、Cocos2dx资源存取分析

Cocos2dx的文件操作使用的是一个极简单的封装–CCFileUtils ,CCFileUtils代理了基本的文件和路径操作,包括查找文件,读取文件,获取文件路径等。查看CCFileUtils的实现,我们可以发现它是夸 平台的,它的夸平台是通过其子类来实现的。

. 在IOS下它的实现是CCFileUtilsIOS ,使用Cocoa 的相关API来获取app路径和拼凑资源完整路径,它的文件访问则是继承自CFileUtil使用CRT标准流函数进行访问

. 在Android它的实现为CCFileUtilsAndroid,其使用JNI从Android sdk里读取app的资源和可写路径,使用ZipFile从资源apk(zip文件)里访问资源文件

. 在Windows下它的实现为CCFileUitlsWin32,除了路径操作,和IOS一样使用CRT标准流进行文件访问

其基本结构如下所示:

CCFileUtil的主要接口有4个,但getFileDataFromZip从未被使用过。主要接口及功能如下:

//获取文件的全路径
virtual std::string fullPathForFilename(const char* pszFileName);

//查找文件是否存在
virtual bool isFileExist(const std::string&amp; strFilePath) = 0;

//读取文件,引擎的资源加载均使用此函数
virtual unsigned char* getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize);

//从zip里读取文件,在引擎里从未被使用
virtual unsigned char* getFileDataFromZip(const char* pszZipFilePath, const char* pszFileName, unsigned long * pSize);

  二、各平台上的文件操作权限和目录分析

操作系统 Android IOS PC
可写目录 /data/data/package name/files 目录为android上的app私有目录,具体目录可以

通过android sdk的Activity的getFilesDir来获取

app安装目录下的Documents子目录 任何有权限的目录
安装包资源存放目录 只有位于resource\raw和assets下面的资源会被原封不动地打包到apk,游戏内一般使用

assets,原因是raw不允许有目录层次结构。assets在apk被安装成功后,仍然是以资源

apk(zip压缩)的方式存在于存储设备的只读目录中,具体路径可以通过android sdk的

assetmanager获取。使用ZipFile进行访问和解压。

原封不动地把Xcode工程中的资源包括目录存放在app安装home目录下,

为只读目录,数据不可修改

由安装包定义
额外存储器可访问性 SD卡可读写 不可显示访问 可访问
权限申请 需要申请读,写和SD卡读写权限 不需要 不需要

 

    三、自定义资源包系统的实现

在考虑了Cocos2dx的文件操作实现和各系统平台的限制之后,我的自定义包系统基本实现思路如下:

1. 在获取自定义资源包的好处的时候,尽可能地提供与Cocos2dx引擎其它部分的兼容性。

2. 在Android平台上优先把资源包创建在SD卡上–大部分低端Android手机的自带存储非常有限

3. 在Android平台上做资源冗余,以提高资源读取速度,具体的为安装后的第一次运行时,从只读的资源APk里把资源包解压到可写目录或SD卡一份,这样在资源创建的时候就不用每次从zip里解压文件了

4. 包文件的读取API提供线程安全支持,为异步资源管理器的资源加载提供基础设施支持

5. 提供可订制的加密和压缩接口,以方便外部配置压缩和加密方式

6. 为后续的资源升级做准备,在提供资源读取API的同时提求资源更新,删除和校验方法

7. 提供打包、解包和包查看工具为制作流程和自动化构建提供支持

8. 修改cocos2dx以全路径读取文件的方式改为相对路径–打包只需要把相对Resource目录下的子路径截下来就可以做为唯一路径使用。

资源包系统架构图如下所示

对游戏资源加载来说,主要使用的接口为IPackageSystem定义的:

//获取文件描述信息,如文件长度,md5等
virtual bool getFileDesc(BlockDesc&amp; desc,const std::string&amp; filepath) = 0 ;

//查找文件是否存在
virtual bool isFileExist(const std::string&amp; filepath) const = 0 ;

//读取文件到buffer里,返回值为读取到的文件长度
virtual size_t readFile(const std::string&amp; filepath ,u8* buffer ,size_t bufferLen) = 0 ;

这些接口PackageSystem通过扫描它加载的所有资源包,并调用资源包的相应接口来代理实现。

 

 

 

    四、集成资源包系统到Cocos2dx

 

要把资源包集成到cocos2dx,还需要做如下改动:

 

.Hack CCFileUtils的getFileData函数,使之转而从资源包内读取数据

 

. 提供另一个函数来实现getFileData原来实现的从apk读取文件的功能,我这儿实现为readFileFromInstallPackage

这两个修改应用之后的CCFileUtils::geFileData实现如下:

unsigned char* CCFileUtils::getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize)
{
    string lowerFileName = pszFileName;
    std::transform(lowerFileName.begin(),lowerFileName.end(),lowerFileName.begin(),::tolower) ;
    if(m_packageSystem && m_packageSystem->isFileExist(lowerFileName))
    {
        ext::u8* buffer = null;
        ext::BlockDesc desc ;
        if(m_packageSystem->getFileDesc(desc,lowerFileName) &&
            desc.length>0)
        {
            buffer = new ext::u8[desc.length] ;
            if(buffer)
            {
                if(m_packageSystem->readFile(lowerFileName,buffer,desc.length)==desc.length)
                {
                    *pSize = desc.length ;                   
                }
                else
                {
                    delete[] buffer ;
                    buffer = null ;
                }
            }
        }
        return buffer ;
    }
    return readFileFromInstallPackage(pszFileName,pszMode,pSize) ;
}

. cocos2dx对zip文件的读取(具体在ZipUtil.cpp里)也依赖了CCFileUtils::getFileData,这儿需要把getFileData修改为readFileFromInstallPackage

. 修改CCFileUtil中的fullPathForFilename和isFileExist,使其用PackageSytem代理实现

. app初始化代码里添加资源包管理系统的初始化代码

[转载]编写高质量代码改善C#程序的157个建议[用抛异常替代返回错误、不要在不恰当的场合下引发异常、重新引发异常时使用inner Exception] - aehyok - 博客园

mikel阅读(1082)

[转载]编写高质量代码改善C#程序的157个建议[用抛异常替代返回错误、不要在不恰当的场合下引发异常、重新引发异常时使用inner Exception] – aehyok – 博客园.

前言

  自从.NET出现后,关于CLR异常机制的讨论就几乎从未停止过。迄今为止,CLR异常机制让人关注最多的一点就是“效率”问题。其实,这里存 在认识上的误区,因为正常控制流程下的代码运行并不会出现问题,只有引发异常时才会带来效率问题。基于这一点,很多开发者已经达成共识:不应将异常机制用 于正常控制流中。达成的另一个共识是:CLR异常机制带来的“效率”问题不足以“抵消”它带来的巨大收益。CLR异常机制至少有一下几个优点:

1、正常控制流会倍立即中止,无效值或状态不会在系统中继续传播。

2、提供了统一处理错误的方法。

3、提供了在构造函数、操作符重载及属性中报告异常的便利机制。

4、提供了异常堆栈,便于开发者定位异常发生的位置。

另外,“异常”其名称本身就说明了它的发生是一个小概率事件。所以,因异常带来的效率问题会倍限制在一个很小的范围内。实际上,try catch所带来的效率问题几乎忽略的。在某些特定的场合,如Int32的Parse方法中, 确实存在这因为滥用而导致的效率问题。在这种情况下,我们就应该考虑提供一个TryParse方法,从设计的角度让用户选择让程序运行得更快。另一种规避 因为异常而影响效率的方法是:Tester-doer模式,下文将详细阐述。

本章将给出一些在C#中处理CLR异常方面的通用建议,一帮助大家构建和开发一个运行良好和可靠的应用系统。

本文已同步到http://www.cnblogs.com/aehyok/p/3624579.html。本文主要来学习以下几点建议

建议58、用抛出异常代替返回错误代码

建议59、不要在不恰当的场合下引发异常

建议60、重新引发异常时使用inner Exception

58、用抛出异常代替返回错误代码

  在异常机制出现之前,应用程序普遍采用返回错误代码的方式来通知调用者发生了异常。本建议首先阐述为什么要用抛出异常的方式来代替返回错误代码的方式。

对于一个成员方法来说,它要么执行成功,要么执行失败。成员方法成功的情况很容易理解。但是如果执行失败了却没有那么简单,因为我们需要将导致执行失败的原因通知调用者。抛出异常和返回错误代码都是用来通知调用者的手段。

假设我们要实现这样一个简单的功能:应用程序需要完成一次保存新建用户的操作。这是一个分布式的操作,保存动作除了需要将用户保存在本地外,还需要通过WCF在远程服务器上保存数据。负责保存用户的成员方法如下:

public int SaveUser(User user)
{
if (!SaveToFile(user))
{
return 1;
}
if (!SaveToDataBase(user))
{
return 2;
}
return 0;
}

public bool SaveToFile(User user)
{
return true;
}

public bool SaveToDataBase(User user)
{
return true;
}

如果单纯的看SaveUser方法,似乎一切都还不错,在约定好了错误代码后,调用者只要接收到1或2,就知道到底是那里出现了问题。但仔细研究会发现,如果方法执行失败,似乎还可以挖掘出更多的原因。

假设在SaveToFile方法中,我们可能会遇到:

1、程序无数据存储文件写权限导致的失败。

2、硬盘空间不足导致的失败。

在SaveToDataBase方法中,我们可能会遇到:

1、服务不存在导致的失败。

2、网络连接不正常导致的失败。

当我们想要告诉调用者更多的细节的时候,就需要与调用者约定更多的错误代码。于是我们很快就会发现,错误代码飞速膨胀,直到看起来似乎无法维护。因为我们总在查找并确认错误代码。

采用接下来的方法,可能会省略很大一部分的错误代码:

public bool SaveUser1(User user,ref string errorMessage)
{
if (!SaveToFile(user))
{
errorMessage = "本地保存失败";
return false;
}
if (!SaveToDataBase(user))
{
errorMessage = "远程保存失败";
return false;
}
return true;
}

这看上去不错,即使存在更多的错误也可以将失败信息呈现给调用者或者上层用户。然后仅仅呈现失败信息就可以了吗?我们来看看这样一种情况:给失败通知增加稍微复杂一点的功能。

如果本地保存失败,要完成“通知运行本段代码的客户机管理员”的功能。通常情况下,仅仅只需要显示类似的信息:“本地保存失败,请检查用户权限”。如果远程保存失败,应用程序需要“发送一封邮件给远程服务器的系统管理员”。总金额个增加的功能导致我们不能像处理“本地保存失败”那样来处理“远程保存失败”。

一切仿佛又回到了起点,在没有异常处理机制之前,我们只能返回错误代码,但是现在有了另一种选择,即使用异常机制。如果使用异常机制,那么最终的代码看起来应该是下面这样的:

static void Main(string[] args)
{
try
{
SaveUser(new User());
}
catch (IOException e)
{
///IO异常,通知当前用户
}
catch (UnauthorizedAccessException e)
{
////权限异常,通知客户端管理员
}
catch (CommunicationException e)
{
///网络异常,通知发送给网络管理员
}
}

public static void SaveUser(User user)
{
SaveToFile(user);

SaveToDataBase(user);
}

使用CLR异常机制后,我们会发现代码变得更清晰、更易于理解了。至于效率问题,还可以重新审视“效率”的立足点:throw exception产生的那点效率损耗与等待网络连接异常相比,简直微不足道,而CLR异常机制带来的好处却是显而易见的。

这里需要稍加强调的是,在catch(CommunicationException)这个代码块中,代码所完成的功能是“通知发送”而不是“发送”本身,因为我们要确保在catch和finally中所执行的代码是可以倍执行的。换句话说,尽量不要在catch和finally中再让代码“出错”,那么让异常堆栈信息变得复杂和难以理解。

在本例的catch代码块中,不要真得编写发送邮件的代码,因为发送邮件这个行为可能会产生更多的异常,而“通知发送”这个行为稳定性更高(即不“出错”)。

以上通过实际的案例阐述了抛出异常相比于返回错误代码的优越性,以及在某些情况下错误代码将无用武之地,如构造函数、操作符重载及属性。语法特性决定了其不能具备任何返回值,于是异常机制倍当作取代错误代码的首要选择。

59、不要在不恰当的场合下引发异常

  最常见不易引发异常的情况是对在可控范围内的输入和输出引发异常。如下面的代码所示:

public void SaveUser2(User user)
{
if (user.Age &lt; 0)
{
throw new ArgumentOutOfRangeException("Age不能为负数");
}
}

暂时可以发现此方法有两处不妥:

1、判断Age为负数。这是一个正常的业务逻辑,它不应该倍处理为一个异常。

2、应该采用Tester-Doer来验证输入。

我们现在来添加一个Tester方法

public static bool CheckAge(int age,ref string errorMessage)
{
if (age &lt; 0) { errorMessage = "Age不能为负数"; return false; } else if (age &gt; 100)
{
errorMessage = "Age不能大于100";
return false;
}
return true;
}

而调用的地方看起来是这样的

string errorMessage = string.Empty;
if (CheckAge(30, ref errorMessage))
{
SaveUser(new User());
}

程序员,尤其是类库开发程序员,要掌握的两条首要原则是:

正常的业务流程不应使用异常来处理。

不要总是尝试去捕获异常或引发异常,而应该允许异常向调用堆栈往上传播。

那么到底应该在什么情况下引发异常呢?

第一种情况 如果运行代码后会造成内存泄漏、资源不可用,或者应用程序状态不可恢复,则引发异常。

第二种情况 在捕获异常的时候,如果需要包装一些更有用的信息, 则引发异常。

这类异常的引发在UI层特别有用。系统引发的异常所带的信息往往更倾向于技术性的描述;而在UI层,面对异常的很可能是最终的用户。如果需要将异常信息呈现给用户,更好的做法是先包装异常,然后引发一个包含友好信息的新异常。

第三种情况 如果底层异常在高层操作的上下文中没有意义,则可以考虑捕获这些底层异常,并引发新的有意义的异常。

例如下面的代码中:

public void CaseSample(object o)
{
if (o == null)
{
throw new ArgumentNullException("o");
}
User user = null;
try
{
user = (User)o;
}
catch (InvalidCastException)
{
throw new ArgumentException("输入参数不是一个User", "o");
}
}

如果抛出InvalidCastException则没有任何意义,甚至会造成误解,所以更好的方式是抛出一个ArgumentException。

需要重点介绍的正确引发异常的典型例子就是捕获底层API错误代码,并抛出。查看如下代码:

public void Test()
{
int errorCode=Marshal.GetLastWin32Error();
if (errorCode == 6)
{
throw new InvalidOperationException("具体错误");
}
}

很显然当需要调用WIndows API或第三方API提供的接口时,如果对方的异常报告机制使用的是错误代码,最好重新引发该接口提供的错误,因为你需要让自己的团队更好地理解这些错误。

建议60、重新引发异常时使用inner Exception

  当捕获了某个异常,将其包装或重新引发异常的时候,如果其中包含了Inner Exception,则有助于程序员分析内部信息,方便调试。

可以先来查看以下代码

static void Main(string[] args)
{
    try
    {
        Test();
    }
    catch (Exception err)
    {
        Console.WriteLine(err.Message);
        if (err.InnerException != null)
        {
            Console.WriteLine(err.InnerException.Message);
        }
    }
}
 
 
public static void Test()
{
    try
    {
        SaveUser(new User());
    }
    catch (Exception err)
    {
        var ex = new Exception("网络链接失败,请稍后再试",err);
        //throw err; //这样抛出异常会丢掉异常原有的堆栈信息
        throw ex;
    }
}

如果不想使用Inner Exception,可以使用如下方式

static void Main(string[] args)
        {
            try
            {
                Test();
            }
            catch (Exception err)
            {
                Console.WriteLine(err.Data["SockInfo"].ToString());
            }
        }


        public static void Test()
        {
            try
            {
                SaveUser(new User());
            }
            catch (Exception err)
            {
                err.Data.Add("SockInfo", "网络链接失败,请稍后再试");
                throw err;
            }       
        }

相当于把Test方法中的异常当作Inner Exception,然后向上抛出。

意思其实也就是将异常进行简单的封装,然后继续向上抛出,让上层来捕获异常信息。

[转载]新浪微博登录接口(PHP版) - 宇智波-鼬 - 博客园

mikel阅读(1082)

[转载]新浪微博登录接口(PHP版) – 宇智波-鼬 – 博客园.                              CI框架下 新浪微博登录接口完整版
说明:本贴只适合CI框架。功能实现:登录接口跳转链接成功,获取用户信息(包括最重要的u_id)成功,将用户于本地平台连接起来,用户登录成功后信息的存储,本地数据库第三方登录表的设计。总之接口流程已全部完成。每个关键步骤我几乎都有注释,讲解详细。

首先来看下流程:
流程原理:
     1.通过code获得access_token通过授权,并获取用户的信息(包括用户u_id)(这个u_id在后面的第三方登录表里面叫sina_id,那个表是需要自己建的)
     2.查询第三方登录表,如果不存在用户sina_id,分2种情况,一:用户在平台已经有帐号,这时需要把平台(比如:平台的用户表是:user_reg)用户id绑定到第三方登录表(比如是:third_login表),然后就让客户登录;
                                                                                         二:用户在平台没有帐号,跳转至注册页面注册,注册的同时,信息写入uer_reg表,同时也把用户sina_id写入第三方登录表进行绑定;
     3.查询第三方登录表(third_login),如果存在用户sina_id,再查询用户表(user_reg),如果邮箱已经激活,就直接登录,如果没有激活,提示用户去邮箱激活帐号。

下面开始详讲步骤:
第一步:申请App key和App secret申请地址:http://open.weibo.com/ 在页面点击网站接入WEB,进去申请就好了,通过后会得到App Key 和 App Secret如下:
App Key:1428003339
App Sercet:f1c6177a38b39f764c76a1690720a6dc
回调地址:http://test.com/callback.php

说明:申请下来后,那你的这个新浪帐号就是测试帐号,你在开发的时候可以用这个帐号来调试,其他帐号是无法登录,无法返回信息的。开发前,最好上官网看下开发流程,流程是最重要的。只要思路理清楚了,剩下就是用代码实现你的所思所想。

第二步:下载SDK,下载php版的,下载地址(官网):http://code.google.com/p/libweibo/downloads/list,下载下来有5个文件,其中一个是saetv2.ex.class.php,我只需要这个文件。

第三步:代码
1.建立一个第三方登录表,以便存储第三方登录的信息(新浪是u_id,QQ是openid,他们都是唯一的,用来标识用户,我们根据这个来存储):

CREATE TABLE IF NOT EXISTS `third_login` (
`user_id` INT(6) NOT NULL,
`sina_id` BIGINT(16) NULL,
`qq_id` varchar(64) NULL,
PRIMARY KEY (`user_id`),
UNIQUE INDEX `user_id_UNIQUE` (`user_id` ASC),
INDEX `sina_id` (`sina_id` ASC),
INDEX `index4` (`qq_id` ASC))
ENGINE = MyISAM
DEFAULT CHARACTER SET = utf8
COLLATE = utf8_bin
COMMENT = '第三方登录表'

说明:平台返回的是u_id,他是用户的唯一标识,我把他存为 sina_id,user_id是关联平台用户表user_reg的id的,user_reg表我这里不列出,你可以按实际项目需求来建表,推荐的操作工 具有phpmyadmin,MySQL Workbench,操作方便。
如果你只需要做新浪登录接口,那可以把qq_id这个字段去掉。
2.写配置文件,在application下新建一个文件sina_conf.php,把刚申请到的App Key 和 App Secret写进去,代码如下:

<!--?php $config["sina_conf"] = array(     "App_Key" =--> '1428003339',
"App_Secret" =&gt;'f1c6177a38b39f764c76a1690720a6dc',
"WB_CALLBACK_URL" =&gt; 'http://test.com/callback.php'
);

保存

3.oauth认证类,把刚下载下来的saetv2.ex.class.php文件复制到application/libraries下。
说明:这是非常重要的类,登录,授权,获取用户信息都要用到这个类中的方法,没他就没法玩下去了,原封不动的粘到application/libraries下。

4.写新浪微博登录类(QQ登录也可用,我这里QQ登录的也封装在一起了,就算只做新浪登录接口,也不影响),在application/models下建一个文件third_login_model.php,代码:

<!--?php /**  * Description of third_login_model  *第三方接口授权,登录model  * @author  */ class third_login_model extends CI_Model{     //put your code here     private $sina=array();     private $qq  =array();     private $users ='';     private $third='';     public function __construct() {         parent::__construct(); //        $this--->l = DIRECTORY_SEPARATOR;
$this-&gt;load-&gt;database();
$this-&gt;load-&gt;library('session');
include_once APPPATH."/libraries"."/saetv2.ex.class.php";
$this-&gt;third = $this-&gt;db-&gt;'third_login';//第三方登录表
$this-&gt;users = $this-&gt;db-&gt;'user_reg';//本项目用户表
$this-&gt;config-&gt;load("sina_conf");
$this-&gt;sina= $this-&gt;config-&gt;item("sina_conf");

}

/**
* @uses : 新浪微博登录
* @param :
* @return : $sina_url----登录地址
*/
public function sina_login(){
$obj = new SaeTOAuthV2($this-&gt;sina['App_Key'],$this-&gt;sina['App_Secret']);
$sina_url = $obj-&gt;getAuthorizeURL( $this-&gt;sina['WB_CALLBACK_URL'] );
return $sina_url;
}

/**
* @uses : 登录后,通过返回的code值,获取token,实现授权完成,然后获取用户信息
* @param : $code
* @return : $user_message--用户信息
*/
public function sina_callback($code){
$obj = new SaeTOAuthV2($this-&gt;sina['App_Key'],$this-&gt;sina['App_Secret']);
if (isset($code)) {
$keys = array();
$keys['code'] = $code;
$keys['redirect_uri'] = $this-&gt;sina['WB_CALLBACK_URL'];
try {
$token = $obj-&gt;getAccessToken( 'code', $keys ) ;//完成授权
} catch (OAuthException $e) {
}
}
$c = new SaeTClientV2($this-&gt;sina['App_Key'], $this-&gt;sina['App_Secret'], $token['access_token']);
$ms =$c-&gt;home_timeline();
$uid_get = $c-&gt;get_uid();//获取u_id
$uid = $uid_get['uid'];
$user_message = $c-&gt;show_user_by_id($uid);//获取用户信息
return $user_message;
}

/**
* @uses : 查询第三方登录表
* @param : $where
* @return : 第三方登录用户记录结果集
*/
public function select_third($where) {
$result = false;
$this-&gt;db-&gt;select();
$this-&gt;db-&gt;from($this-&gt;third);
$this-&gt;db-&gt;where($where);
$query = $this-&gt;db-&gt;get();
if($query){
$result = $query-&gt;row_array();
}
return $result;
}

/*-
* @uses : sina---查询用户表和第三方登录表
* @param : $where
* @return : 第三方登录用户记录结果集
*/
public function select_user_name($where) {
$field ="user.id,user.password,user.username,utl.*";
$sql = "select {$field} from {$this-&gt;third} as utl "
." left join {$this-&gt;users} as user on user.id=utl.user_id"
. " where utl.sina_id={$where}";
$query = $this-&gt;db-&gt;query($sql);
$result = $query-&gt;row_array();
return $result;
}

/**
* @uses : qq---查询用户表和第三方登录表
* @param : $where
* @return : 第三方登录用户记录结果集
*/
public function select_user_qqname($where) {
$field ="user.id,user.password,user.username,utl.*";
$sql = "select {$field} from {$this-&gt;third} as utl "
." left join {$this-&gt;users} as user on user.id=utl.user_id"
. " where utl.qq_id='{$where}'";
$query = $this-&gt;db-&gt;query($sql);
$result = $query-&gt;row_array();
return $result;
}

/**
* @uses : 将用户和第三方登录表信息绑定
* @param : $datas
* @return :
*/
public function binding_third($datas) {
if (!is_array($datas)) show_error ('wrong param');
if($datas['sina_id']==0 &amp;&amp; $datas['qq_id']==0) return;

$resa ='';
$resb ='';
$resa = $this-&gt;select_third(array("user_id"=&gt;$datas['user_id']));
$temp =array(
"user_id"=&gt;$datas['user_id'],
"sina_id"=&gt;$resa['sina_id']!=0 ? $resa['sina_id'] : $datas['sina_id'],
"qq_id" =&gt; $resa['qq_id']!=0 ? $resa['qq_id'] : $datas['qq_id'],
);
if($resa){
$resb = $this-&gt;db-&gt;update($this-&gt;third, $temp,array("user_id"=&gt;$datas['user_id']));
}else{
$resb = $this-&gt;db-&gt;insert($this-&gt;third,$temp);
}
if($resb) {
$this-&gt;session-&gt;unset_userdata('sina_id');//注销
$this-&gt;session-&gt;unset_userdata('qq_id');//注销
}
return $resb;
}
}

保存
说明:这个code是由入口文件callback.php传过来的,第7步会有他的详细代码。
现在配置文件,model,数据表都有了,接下来就是控制器和视图文件了。

5.写登录控制器  在application/controllers下,建立login.php文件(名字你可以自己取),代码:

<!--?php   if ( ! defined('BASEPATH')) exit('No direct script access allowed'); /**  * Description of index  * @author victory  */ class Login extends CI_Controller {          public function __construct() {         parent::__construct();         $this--->load-&gt;model('login_model','login');//这个类是本项目的用户登录类,本贴不提供原代码,因为不同的项目,需求不同,可根据你项目需求可以自己封装
$this-&gt;load-&gt;model("third_login_model","third");
$this-&gt;load-&gt;library('session');
}

public function index() {
header("content-type: text/html; charset=utf-8");
$this-&gt;load-&gt;model("third_login_model","third");//加载新浪登录接口类
$datas['sina_url'] = $this-&gt;third-&gt;sina_login();//调用类中的sina_login方法
$this-&gt;load-&gt;view("index.php",$datas);//调取视图文件,并传入数据

}

public function callback(){
header("content-type: text/html; charset=utf-8");
$this-&gt;load-&gt;model("user_reg_model","user_reg");
$code = $_REQUEST['code'];//code值由入口文件callback.php传过来
$arr =array();
$arr = $this-&gt;third-&gt;sina_callback($code);//通过授权并获取用户信息(包括u_id)
$res = $this-&gt;third-&gt;select_third(array("sina_id"=&gt;$arr['id']));
if(!empty($res)){//用户已有帐号记录,先判断帐号是否激活
$user_info = $this-&gt;user_reg-&gt;user_detect(array("id"=&gt;$res['user_id']));//查询用户表邮箱状态,user_detect方法就是查询用户信息的方法,上面也说了,login_model.php这个类本贴不提供,需要大家自己去封装。
if($user_info['status']){//根据status的状态判断用户帐号是否激活,user_reg表中的字段status,1为未激活,0为已激活
echo "<script>// <![CDATA[
alert('您的账号未激活,请去邮箱激活!');location='/login/index';
// ]]></script>";die();
}
$datas = $this-&gt;third-&gt;select_user_name($arr['id']);//激活后,把信息写入用户表和第三方登录表
$uname = $datas['username'];//username,password都是user_reg表的字段,user_reg数据表的构建本帖也不提供,因为每个项目都不一样,需要根据实际项目来
$password = $datas['password'];
$this-&gt;load-&gt;model("login_model","login");
$this-&gt;login-&gt;validation($uname,$password);//validation方法是登录的主要方法,这里主要是在登录的时候,将用户信息写入第三方登录表,下面仅提供写入第三方登录表的代码
echo "<script>// <![CDATA[
alert('登录成功!');location='/user_center'
// ]]></script>";die();
}else{//用户第三方表没有记录,询问用户是否在平台有过帐号,没有跳转注册,有跳转登录
$this-&gt;session-&gt;set_userdata('sina_id',$arr['id']);
echo "<script>// <![CDATA[
if(!confirm('是否在平台注册过用户?')){location='/register/index'}else{location='/login'};
// ]]></script>";
}
}

public function login_validation(){
//第三方登录用户id ,sina_id,qq_id的记录增改
$third_info =array(
"user_id" =&gt; $user_ser['id'],
"sina_id" =&gt; $this-&gt;session-&gt;userdata('sina_id'),
"qq_id" =&gt;$this-&gt;session-&gt;userdata('qq_id'),
);
if($third_info['sina_id']||$third_info['qq_id']) $this-&gt;third-&gt;binding_third($third_info); // 绑定
}

保存

//在注册控制器里,用户信息写入user_reg表,同时也把sina_id写入third_login表,我这里只展示第三方登录接口用户id存入数据表的代码
class Register extends CI_Controller {

public function __construct() {
parent::__construct();
$this-&gt;load-&gt;library('session');
}
public function reg() {
$haha =array(
"user_id" =&gt; $rs,
"sina_id" =&gt; $this-&gt;session-&gt;userdata('sina_id'),
"qq_id" =&gt;$this-&gt;session-&gt;userdata('qq_id'),
);
if($haha['sina_id']||$haha['qq_id']) $this-&gt;third-&gt;binding_third($haha);
}
}

6.视图文件布置新浪微博登录按钮,在application/view下建立index.php文件,代码:



新浪微博登录接口
<div><a href="&lt;?=$sina_url?&gt;"><img src="http://images.cnblogs.com/weibo_login.png" alt="" width="110" /></a></div>

说明:这是个图片按钮,图片你可在官网下载,下载地址:http://open.weibo.com/widget/loginbutton.php
7.回调地址
前 面在第1步配置文件文件的时候,设置了回调地址:http://test.com/callback.php ,那这个callback.php放在什么地方呢,它需要放在和入口index.php同级的位置,它和application也是同级的。所在在开始的 目录下新建文件callback.php。代码:

<?php

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
//新浪微博登录回调入口文件,将路径转移到login/callback方法里,并将code值传过去
$code ='';
$url = '';
$str ='';
$code = $_REQUEST['code'];
$url  = "/login/callback";

$str = "<!doctype html>
<html>
    <head>
    <meta charset=\"UTF-8\">
    <title>自动跳转</title>
    </head>
<body>";
$str .="<form action=\"{$url}\" method=\"post\" id=\"form\" autocomplete='off'>";
$str .="<input type='hidden' name='code' value='{$code}'>";
$str .="</form>
        </body>
        </html>
        <script type=\"text/javascript\">
           document.getElementById('form').submit();
        </script>";
echo $str;

这个时候,你用浏览器访问index.php文件的时候,会看到一个用微博帐号登录的登录按钮,点击按钮,会跳转到微博登录页面,要你输入新浪微博用户名密码,他会做不同的操作。具体流程我在上面也说过了。

本贴仅供参考学习,不涉及任何商业范围。
如要转载,请说明来源和原文地址,谢谢!
如有不对,或者可以改良之处,请在下方评论指出,谢谢!

[转载]必知-网站常用度量指标(2/2) - 产品经理西蒙 - 博客园

mikel阅读(1191)

[转载]必知-网站常用度量指标(2/2) – 产品经理西蒙 – 博客园.

有好友对我说,你的文章太书面了,定义太多了,理解门槛太高了,会导致很多读者只是点开看看,哎,有这么一个东西,挺好挺好,但不会细读。我当时这 么回复他:“有些书是给看懂的人阅读的,有些书是给最广泛的群体阅读的。”西蒙认为,知识体系是有门槛的,比如相对论的学术体系,看懂的人真的不多。产品 经理的职业目前鱼目混杂,耐不住寂寞,经不住诱惑,浮躁的心太多。这个职业的种种乱象是因为产品经理这个职业并没有一个标准的知识体系手册。
西蒙没有大才,只希望在这个博客中写一些东西,为这个标准的知识体系做一些微薄的贡献。
西蒙一直认为,产品经理是一个严肃的职业,正如西蒙在简历中给产品经理的定义一样:

PM是以挖掘和满足用户需求为主导,对产品进行输出和管理。TA以产品体验为检验标准,用系统的实践知识体系研究用户,是一门科学的践行者和传道者。

任道而重远,希望诸君共勉之。

__________________________________________

正文:
跳出率(Bounce Rate)
是指仅浏览了一个页面的访问者占全部访问者的比例。例如,假设用户在浏览网页时点击了一个网站广告,在进入该网站的的广告链接页面后,发现并不感兴趣,在没有点击该页面中任何链接的情况下,直接关闭了网页,这对于该广告网站来说就是一个跳出。
对于网站营销而言,跳出率是一个非常重要的指标,它有助于判断网站主页或登录页能否激发访问者的浏览兴趣。如果访问者只是打开了网页,并没有点击起初希望他们点击的链接,那么说明网站设计存在一定的问题。

广告显示数、点击数和点击率
许多知名网站上通常放置有多个广告插图,这也是网站很重要的一部分收入来源。但是,广告放置后的效果怎样一般是通过广告显示数、点击数和点击率来判断。
广告显示数是指广告被显示的次数。当打开放置广告的网站时,显示数量就被记录了一次。如果进行了页面刷新操作,则显示数量又被记录了一次。一些知名网站每天的访问量很大,因此,广告的显示数量也很巨大。
广告点击数是指用户点击广告的次数。在点击广告后,会链接到广告商的网站。如果广告仅显示而没有点击,或者点击数量很小,说明广告效果一般,没有引起访问者的兴趣。因此,通常使用点击率来评判广告的效果,点击率就是点击数除以显示数。

转换率(Conversions Rates)
是指用户进行了相应目标行为的访问次数与总访问次数的比 率,也叫转化率。这些行为可以是用户登录、用户注册、用户订阅、用户下载、用户购买等一系列行为。例如,以用户登录为例,如果每100次访问中有10个登 录网站,则网站的登录转化率为10%。而最后有5个用户订购,则订购转化率为5%。

回访者比率
回放者比率是回访者次数与独立访问者次数的比率。该指标用于衡量网站内容对访问者的吸引程度和网站的实用性,也就是说,网站是否有令人感兴趣的内容使访问者再次访问网站。
需要注意的是,基于访问时长的设定和产生报告的时间段,这个指标可能会有很大不同,因此,一旦选定了一个时长和时间段,前后之间就要使用相同的参数来产生报告,否则就失去了比较的意义。

积极访问者比率
积极访问者比率是访问超过一定页数的用户与总访问用户数的比例。该指标用户衡量有多少访问者是对网站的内容高度感兴趣的。如果网站能够针对正确目标受众且使用方便,那么该指标应该是不断上升的。如果网站是内容型的,可以针对不同类别的内容来区分不同的积极访问者。
需要注意的是,页数的设定会直接影响积极访问者的数量,因此,在前后比较时,要确保页数标准一致。

忠实访问者比率
忠实访问者比率是指访问时间超过一定限定的用户数与总访问用户的比例。该指标与积极访问者比率意义相同,只是使用驻留时间代替了浏览页数。选择积极访问者比率还是忠实访问者比率取决于网站的目标,可以选择两个中的一个使用,或是结合使用。
同样,访问者时长的设定会直接影响忠实访问者的数量,因此,在前后比较时,时长设定应当一致。

小结:
这个小系列介绍了指标与用户行为的关系,以及各项指标的计算方法和优缺点。在进行网站分析时应当 综合运用,才能做出正确的改进措施建议。例如,假如一项产品关注度不高,但是转化率很高,我们应当怎样分析;而另一项产品关注度很高,但是转化率很低,我 们又应怎样分析。通过这个案例就可以看出来,但看某一项指标往往并不能看清问题的全貌。

我们设定度量指标的目的,是为了从浩淼的用户群中甄别出普通用户、潜在用户、付费用户,根据用户群与内容的关系作出判断,策划和检验运营效果,促进 产品的购买转化率。在负责具体产品时,各位可以自行创造度量指标,比如百合网用户中心登录的时候,页面会直接弹出产品广告,这种行为无异于强暴用户的产品 体验。但我们从另外一个角度想,如果我们设定了一个时间段,比如半个小时,半个小时内,用户对弹出广告的浏览时间明显大于一个伐值(比如0.8秒),那么 是否可以判定为是潜在用户呢?
___________________________________________________________________

写字不易,且读且珍惜,转载请注明出处:SimonK @http://www.cnblogs.com/simonk/

更博时间:

周一到周五每天一更,时间22:00-23:40之间

周六/周日/法定节假日 不定期

欢迎订阅!

下节预报:有了数据,有了指标,我们下一步进入解决问题的第三个阶段:分析阶段

[转载]Eclipse 项目管理控制软件svn - 承影剑 - 博客园

mikel阅读(1173)

[转载]Eclipse 项目管理控制软件svn – 承影剑 – 博客园.

^_^太开心了,之前以为eclipse只有tortoiseSVN而没有类似 Visual Studio 2010里面的cvs的版本控制软件,不是我讨厌tortoiseSVN,而是我实在不习惯使用这个软件(感觉 太麻烦了)。好了,废话不多说了,Eclipse里的项目管理软件叫做:subclipse。做java开发的同学应该都知道这个软件,不过我是初学者, 所以今天才知道。

通过网上查找的资料,知道了怎么下载和配置这个软件了:点击这里查看如何下载和安装点击这里查看如何配置这个软件

通过上面两个链接相信大家都安装和配置好了,我现在就总结下如何使用这个软件:

1:首先你要有托管的服务器,我使用的是GoogleCode,国内好多大牛使用的都是GitHub,不过我不太会使用,所以暂时不考虑这个托管网站

2:然后在你的Eclipse中添加svn(添加你的托管服务器地址下的项目文件),如图:

QQ截图20140527102659

3:在你的项目文件上点击右键,如图:

QQ截图20140527104028

4:选择svn,点击next,如图:

QQ截图20140527104121

5:接下来就是选择你的托管服务器和选择放在哪个文件夹下了,如图所示:

QQ截图20140527104210

QQ截图20140527104223

6:点击finish就完成了项目的签入。

7:最后你就可以提交你的项目文件到托管服务器了,如图所示:

QQ截图20140527104959

QQ截图20140527102640

 

QQ截图20140527102940

总结一下:

习 惯使用VisualStudio2010的同学,现在可以直接转移到Eclipse了,对了,我在这里也讲一下,配置Jdk和tomcat的心得,在环境 变量里,配置Jdk时首先要在Path里添加到Jdk的bin文件夹下的路径,然后添加JAVA_HOME这个路径只添加到JDK的根目录,不需要到bin目录下,同理,Tomcat也是这样配置的。