好库文摘 http://doc.okbase.net/ TypeScript 看见未来的 JavaScript http://doc.okbase.net/tedeum/archive/265904.html tedeum 2018/1/19 10:21:18

TypeScript也可以看作是“更好的JavaScript”,TypeScript充分利用了JavaScript原有的对象模型并在此基础上做了扩充,添加了较为严格的类型检查机制,添加了模块支持和API导出的能力。比起JavaScript,TypeScript提供了更多在语言层面上的支持,使得程序员能够以更加标准化的语法来表达语义上的约束,从而降低了程序出错的机率;TypeScript也使得代码组织和复用变得更加有序,使得开发大型Web应用有了一套标准方法。

下面是完成同样功能的两段简单代码。

TypeScript Code:

class Greeter {
    greeting: string;
    constructor (message: string) {
        this.greeting = message;
    }
    greet() {
        return "Hello, " + this.greeting;
    }
}  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

JavaScript Code:

var Greeter = (function () {
    function Greeter(message) {
        this.greeting = message;
    }
    Greeter.prototype.greet = function () {
        return "Hello, " + this.greeting;
    };
    return Greeter;
})();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

在JavaScript中没有类,只有对象,要实现所谓的“类式操作”(classical operations),如封装、多态等,要通过若干基础设施,如原型、构造函数等来完成。这些对于非常熟悉JavaScript的程序员来说,也许都是可以完成的任务,但对于新手来说就困难重重了。并且,即使是高手,一段时间不写相关的代码也很容易遗忘和出错。但TypeScript却提供了标准的机制,将普通程序员熟悉的、C++和C#中常用的类概念映射到JavaScript中去,这样就大大降低了在JavaScript进行类式操作的难度。

严格ES6(ECMAScript 2015)开始支持类了,但TypeScript现在就可以运行在任何浏览器上、Node.js环境中和任何支持ECMAScript 3(或更高版本)的JavaScript引擎中。所以说TypeScript是JavaScript的未来,是更好的JavaScript

为什么要学习 TypeScript ,实际上 JavaScript 已经够用了,而且像我没学过 TypeScript 也照样开发得很好,我们来看看它的优势:

  • 更多的规则和类型限制,让代码预测性更高、可控性更高,易于维护和调试。
  • 对模块、命名空间和面向对象的支持,更容易组织代码开发大型复杂程序。
  • TypeScript 的编译步骤可以捕获运行之前的错误。
  • Angular 2+ 和 Ionic 2+默认使用 TypeScript(虽然我不会,我也在用。)、

TypeScript 是微软开发和控制的开源项目,我在应用 Anguar 2 + Ionic 2 + 框架开发系统时使用的就是 TypeScript,但我一直把 TypeScript 当作 JavaScript 来用的,甚至是照猫画虎,所以我想正儿八经的学习下 TypeScript,弄明白我糊里糊涂使用的东西,这篇 Chat分享算是我的一个学习笔记,如果你想和我一样,想知道以下内容,请来我的 Chat:

为什么要学习 TypeScript 
TypeScript 的类型有什么不同和用处 
使用 Webpack 搭建学习环境 
深入了解 TypeScript 中的类型

如果你想了解以上类容,欢迎加入TypeScript 快速入门 的Chat

原文地址

]]>
Yii2.0源码阅读-从路由到控制器 http://doc.okbase.net/skyfynn/archive/265903.html BigNerd 2018/1/19 10:15:40

之前的文章弄清了一次请求的开始到结束。主要讲了Yii Applicaton实例的创建、初始化,UrlManager如何返回Yii中的路由信息,到runAction,最后将Response发送给客户端。这其中略过了runAction($route)到底是如何找到以及调用对应的控制器中的方法的,下面继续从源码入手。

1、继承关系

首先我们弄清楚Yii几个重要类的继承关系:

  • yii\web\Application extends yii\base\Application
  • yii\base\Application extends yii\base\Module
  • yii\base\Module extends yii\di\ServiceLocator
  • yii\di\ServiceLocator extends yii\base\Component
  • yii\base\Component extends yii\base\Object

2、从runAction继续

找到yii\web\Application的handleRequest方法,这里对runAction进行了调用:

list ($route, $params) = $request->resolve();
$result = $this->runAction($route, $params);

runAction的定义位于父类的父类yii\base\Module中,这里已经获取到的信息为:通过yii\web\Request对当前url请求的解析,返回的一个路由。这个路由$route的格式为site/index 这种形式的,或者为附加模块信息Metting/attender/index 这种形式的。

//yii\base\Module
public function runAction($route, $params = [])
{
    $parts = $this->createController($route);
    if (is_array($parts)) {
        /* @var $controller Controller */
        list($controller, $actionID) = $parts;
        $oldController = Yii::$app->controller;
        Yii::$app->controller = $controller;
        $result = $controller->runAction($actionID, $params);
        Yii::$app->controller = $oldController;

        return $result;
    } else {
        $id = $this->getUniqueId();
        throw new InvalidRouteException('Unable to resolve the request "' . ($id === '' ? $route : $id . '/' . $route) . '".');
    }
}

可以看到,runAction的第一个操作就是根据$route创建controller实例。

3、创建controller实例对象

这个方法最终的目的就是创建一个控制器实例对象,要么是controllers中的一个控制器,要么是modules的controllers中的一个控制器。

createController的注释也写的比较明白,一共针对4种情况做处理:

  1. 如果$route为空,那么直接使用defaultRoute,可以看到defaultRoute在yii\web\Application中定义,默认值为site
  2. 如果$route的第一部分($id)匹配到了我们config/main.php modules中的某一项,那么会使用路由的剩余部分($route)作为参数递归调用createController($route)
  3. 如果controllerMap中发现了以第一部分($id)为key的项,那么会优先按照controllerMap中的配置来创建controller实例
  4. 因为模块可以无限的嵌套下去,yii2会递归的调用createController来创建实例

首先说一下方法中$route的处理,除去两侧的斜线/,对$route中是否存在双斜线进行判断,然后就是将$route按照/分为第一部分$id和第二部分,新的$route

public function createController($route)
{
    if (strpos($route, '/') !== false) {
        //将Metting/attender/index分为 $id='Metting'; $route='attender/index'
        list ($id, $route) = explode('/', $route, 2);
    } else {
        $id = $route;
        $route = '';
    }
    //这里就是上面说的controllerMap和模块会优先进行处理
    // module and controller map take precedence
    if (isset($this->controllerMap[$id])) {
        $controller = Yii::createObject($this->controllerMap[$id], [$id, $this]);
        return [$controller, $route];
    }
    $module = $this->getModule($id);
    if ($module !== null) {
        return $module->createController($route);
    }
    //下面代码暂时省略
}

【注】controllerMap说明:有的时候我们定义的路由可能不想跟控制器名一致,比如引入了第三方的库,那里面的控制器名你没有办法改变。而controllerMap是一个可配置项,在你的配置文件中可以自定义

如果controllerMap中不存在此id为key的项,那么这个id(比如Metting)会当做module来进行获取:$module = $this->getModule($id);看getModule如何处理:

//yii\base\Module
public function getModule($id, $load = true)
{
    //先不看子module的情况
    if (($pos = strpos($id, '/')) !== false) {
        // sub-module
        $module = $this->getModule(substr($id, 0, $pos));

        return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load);
    }
    //这里是$id(Metting)这个模块是否存在的判断逻辑
    //如果在_modules中找到了这个元素并且是Module的实例,那么直接返回
    //否则根据配置信息创建对象并返回
    if (isset($this->_modules[$id])) {
        if ($this->_modules[$id] instanceof Module) {
            return $this->_modules[$id];
        } elseif ($load) {
            Yii::trace("Loading module: $id", __METHOD__);
            /* @var $module Module */
            $module = Yii::createObject($this->_modules[$id], [$id, $this]);
            $module->setInstance($module);
            return $this->_modules[$id] = $module;
        }
    }

    return null;
}

这里我们对$_modules这个成员变量进行打印,可以看到结果就是我们在config/main.php中的modules这个数组中的配置:

//config/main.php
'modules' => [
    'Metting' => [
         'class' => 'backend\modules\Metting\Module',
     ],
]

所以,当找到这个module后,就是根据我们config的这个信息来实例化一个Module,比如上面的Metting/attender/index的例子:

  • 调用 $module = getModule('Metting')
  • 返回backend\modules\Metting\Module的实例
  • 然后$module->createController('attender/index')

这里是递归的调用,依然会检查attender是否是一个module,返回null,继续执行createController下面的代码:

//yii\base\Module createController($route) 代码简化
$controller = $this->createControllerByID($id);
if ($controller === null && $route !== '') {
    $controller = $this->createControllerByID($id . '/' . $route);
    $route = '';
}
return $controller === null ? false : [$controller, $route];

createControllerByID的操作就比较简单了,主要:

  • 检查传过来的这个controller id是否合法,满足/^[a-z][a-z0-9\\-_]*$/
  • 连字符(-)转为大写,拼接命名空间(controllerNamespace)信息,拼接'Controller'后缀
  • 调用Yii::createObject创建控制器对象

关于controllerNamespace,如果访问的不是模块,那么使用的就是默认的命名空间,即我们在config/main.php中配置的值,如:backend\controllers。如果是模块,$module是我们自己写的Module.php, 它继承了yii\base\Module,并且声明了controllerNamespace,如:

//backend\modules\Metting\Module.php
public $controllerNamespace = 'backend\modules\Metting\controllers';

所以可以看出createController,模块与非模块controller实例的创建,目的是找到正确的命名空间下的控制器。

最终createController返回[控制器实例, action名]

【疑问】存在一个问题没有说明的是$_modules里面的数据是何时以及如何初始化的?见下面:5、$_modules初始化说明。

4、run controller action

上面我们看到,已经根据路由找到并创建了controller实例对象,接下里的操作就是回到Module中的runAction继续执行,调用控制器里面的action了

//yii\base\Module runAction
$result = $controller->runAction($actionID,$params);

因为所有的controller类都继承了yii\web\Controller,所以我们到这里找到runAction方法,位于其父类yii\base\Controller中,通过源码可以看到这个runAction主要执行了:

  • 根据actionID创建一个InlineAction对象,内部是通过反射判断了这个方法是否存在,InlineActoin对象保存了当前的controller对象以及actionMethod信息
  • 执行生命周期函数$module->beforeAction($action)
  • beforeAction执行完毕调用$result = $action->runWithParams($params)
  • 执行生命周期函数$model->afterAction($action)

$action->runWithParams($params)就比较简单了,就是使用了我们之前保存在InlineAction对象中的信息,执行call_user_func_array([$this->controller, $this->actionMethod], $args);

至此,如何从url 到 Yii路由 到 controller 到 action的流程就分析清楚了。

【附】路由到控制器执行流程图
路由到控制器执行流程图

5、$_modules初始化说明

通过之前的Application的构造方法,以及init方法只是初始化了bootstrap、extension以及core components等,丝毫没有配置文件中module的初始化痕迹。然后注意到yii\base\Application中的构造方法的最后还有一句:Component::__construct($config),就是又主动的调用了Component的构造方法,事实上Component自己没有实现构造方法而是继承自他的父类:yii\base\Object

public function __construct($config = [])
{
    if (!empty($config)) {
        Yii::configure($this, $config);
    }
    $this->init();
}

重点在这个Yii::configure($this, $config),其实configure的操作很简单,就是遍历$config数组,以key作为对象的成员属性,对应的value作为属性的值进行初始化操作,这里的$this此时就是yii\web\Application对象了。

//yii\BaseYii
public static function configure($object, $properties)
{
    foreach ($properties as $name => $value) {
        $object->$name = $value;
    }
    return $object;
}

以上面我们讲的config中的modules为例,那就是:

$object->modules = [
    'Metting' => [
         'class' => 'backend\modules\Metting\Module',
     ],
]

但是我们找遍整个类以及继承的类,都没有找到$modules这个属性,那Yii是怎么做的呢?其实就是使用了PHP的魔术方法,打开yii\base\Component,可以看到yii2定义了__set __get __call __isset等常用的魔术方法,在属性赋值的时候,我们知道,如果属性不存在会执行__set($name,$value).

public function __set($name, $value)
{
    $setter = 'set' . $name;
    if (method_exists($this, $setter)) {
        // set property
        $this->$setter($value);
        return;
    } 
    //下面的代码暂时省略
}

根据代码我们知道,要对这些属性初始化,那么就要实现相应的setter方法,对于modules来说就是setmodules,由于PHP函数名,方法名,类名不区分大小写,所以找到了位于yii\web\Module中的:

public function setModules($modules)
{
    foreach ($modules as $id => $module) {
        $this->_modules[$id] = $module;
    }
}

终于,找了了$_modules中值得由来。

]]>
Git知识总览(三) 分支的创建、删除、切换、合并以及冲突解决 http://doc.okbase.net/ludashi/archive/265902.html 青玉伏案 2018/1/19 9:50:05

前两篇博客集中的聊了git的一些常用命令,具体请参见《Git知识总览(一) 从 git clone 和 git status 谈起》、《Git知识总览(二) git常用命令概览》。本篇博客主要涉及了在git版本管理中的分支的创建、切换以及合并。并且罗列了在merge分支使发生冲突时的解决方式。同时还介绍了如何删除本地分支以及远程分支。本篇博客除了参考ProGit中的内容,还参考了learngitbranching这个网站进行的git分支内容的学习和梳理。下方的示例有的给出了基于LearningBranching的示例,有的是基于真实分支操作的示例。本篇博客只是git分支管理的部分知识,下篇博客会继续总结git分支的相关操作。

 

一、分支创建与切换

1、创建新的分支并切换到该分支上进行提交

首先我们先从最简单的来,下方演示了git分支的创建和切换的最基本的操作,具体步骤如下:

  1. git branch <分支名> : 首先使用 git branch bugfix01,在当前分支也就是master分支上创建了一个名为bugfix01的新分支。
  2. git checkout <分支名> : 然后使用 git checkout bugfix01 命令来切换到我们新创建的bugfix01的新分支上。
  3. git commit : 最后使用 git commit 命令在新的分支上进行代码提交。

  

 

从上面的操作上我们不难看出,其中的星号*表示git的 HEAD 指针,指向当前所在分支。开始时 HEAD指针指向的是master分支,也就是用户当前所操作的分支是master。使用 git branch 创建一个新的分支后,HEAD的指向并没有改变,仍然指向的是master分支。当使用 git checkout 命令后,HEAD 指针就由 master 分支转向了 bugfix01 分支了,这样我们就可以对bugfix01进行操作了。

上面还需要注意的时,在创建好分支后,如果在新的分支上没有提交的话,那么 bugfix01 分支和 master 分支所指向的提交号是一致的,上面都是 C1。 这个 C1 表示的就是每次提交的哈希值,也就是提交号。当使用 git commit 时,说明在分支上有新的提交了,就会在之前的提交的基础上往下延伸一个提交,也就是 C2。后提交的 C2 会有一个指针指向上一个提交C1。

 

2、上述操作在终端的表现

接下来我们来看一下终端的具体操作。下方是最初的状态,目前所在的分支为master分支。从下方可以看出该分支上的Tag号,以及HEAD指针指向的master。

  

 

然后我们使用 git branch bugfix01 创建一个新的分支。从下方可以看出 bugfix01分支的指针指向的commit号和master的commit号一致,原因是我们从master中开出来的分支。不过当前所操作的分支仍然是master (HEAD -> master)。

  

 

因为我们从master分支上切换到了bugfix01分支上,所以此刻的HEAD指针指向的是bugfix01。 

  

 

然后我们在bugfix01上提了一些代码,此刻我们看到bugfix01指向最新的commit,但是HEAD一直是指向当前分支bugfix的。

  

 

上面这些操作所使用的命令如下所示:

  

  

3、创建并切换分支

我们可以使用一个简写的命令来创建并切换到该分支上,下方就演示了这一操作:

  1. git checkout -b <分支名> : 首先使用 git checkout -b bugfix02 命令在当前所在分支bugfix01上创建一个新的分支并且切换到新创建的bugfix02上。
  2. git commit : 然后就可以使用 git commit 在新的分支 bugfix02 上进行提交了。

  

上述命令在终端上的执行结果就不做过多赘述了,请参见第二部分。

 

4、切换到之前的分支并提交

下方的使用场景是切换到之前已经创建好的分支上,并在切换后的分支上进行提交。下方操作后就会形成分叉。

  1. git checkout <分支名> :目前所在分支是bugfix02, 然后使用 git checkout bugfix01 命令将分支切换到 bugfix01上。
  2. git commit : 切换后就可以在bugfix01上进行提交操作了。

  

 

上篇博客中也聊到了,在终端中,我们可以使用 git log --oneline --graph --all 来查看所有分支情况。具体如下所示:

  

 

 

二、分支的合并与删除以及冲突解决

上一部分是如何创建分支和在各个分支间进行切换,接下来我们就来看一下分支的合并与删除。

1、分支的合并-merge

我们还以上面那个示例来看一下分支的合并。下方的操作主要是分支的合并、当在两个分支 bugfix01 和 bugfix02 上修改了相关bug, 并且需要将修改后的代码合入到master分支上。下方就是这一系列的操作:

  • 首先使用 git checkout master 命令切换到master分支上。
  • 然后使用 git merge bugfix01 命令将 bugfix01 分支的修改合入到master分支上,在合入成功后会将合入后的新文件进行提交,此刻会有一个新的commit号,也就对应着下方的C9。
  • 然后使用 git merge bugfix02,在将 bugfix02上的修改合入到master分支上,merge 对应的commit号为C10。
  • 最后还是可以在master分支上进行正常提交的。

  

 

下方就是我们在真正的分支中进行的分支合并的操作,我们将 bugfix01 分支merge到了master分支上。从下方可以看到 bugfix02 还尚未合入Master分支。稍后我们会在处理冲突的示例中将bugfix02分支合入到master分支中。

  

 

2、分支的删除

上面可以看到,虽然 bugfix01 和 bugfix02 的分支已经被合入到master分支上了,但是这两个分支还是存在的。如果我们不需要这两个分支指针了,可以将两个分支指针进行删除:

  • 首先使用 git branch -d bugfix01 对分支 bugfix01 进行删除。
  • 然后使用 git branch -d bugfix02 对分支 bugfix02 进行删除。

从下方的操作上来看对分支的删除只是删除的指向该commit号的指针,并不会删除其相关的提交号, 在日志中仍然可以找到之前的commit记录,也仍然可以在该commit上创建新的分支。如果你想删除远端的分支的话,那么得使用 $ git push origin --delete <分支名> 了。

  

 

还是要依附于实例,下方对上一部分已经合入master分支的bugfix01分支执行了删除操作,删除成功后会提示 “Delete branch bugfix01 ( was 223aefb)”, 后边这个就是删除分支所对应的commit号的前7位。

  

 

从下方截图中可以看到,其中bugfix01这个分支被删除了,不过删除的只是指向该commit号的一个指针或者别名,其他的都没改变。

  

 

3、冲突解决

上面是不冲突时的正常流程,如果在分支合并时,两个分支同时修改了同一个文件的同一个地方。此刻分支合并时就会冲突,就需要人工介入来解决冲突的代码了。上面我们说留着 bugfix02 这个分支是有用的,现在就来看一下bugfix02 这个分支的用处。从下方的 log 中不难发现,bugfix02分支和 master 分支都修改该了README.md文件的第二行数据。接下来我们就将 bugfix02 合入 master分支上。

  

 

下方截图的内容就是我们将 bugfix02 分支合入到 master分支时所报的冲突。冲突的大概意思就是在合并 README.md 文件时产生了冲突,自动合并失败了,需要修复这个冲突,在修复之后再对结果进行提交。我们可以使用 git status来查看一下当前的状态(配置的别名 git st)。从 git status的提示中也可以看到,你可以修复该冲突,然后使用 git commit 进行提交,或者 使用 git merge --abort 命令放弃本次合并。如果放弃本次合并就会回到合并之前的状态,当然,这并不是我们想要的,下方会对冲突进行解决,并提交,

  

 

接下来我们就来看一下冲突的具体内容,从<<<<<<<<开始到>>>>>>>>>结尾是冲突的部分,两个分支的内容由========进行分割。上方是当前分支所修改的内容, 我们需要将冲突的内容进行合并,根据具体情况具体分析,看那些需要保留那些不需要保留。还是都需要保留。

  

 

下方就是我们修改冲突后的内容,修复策略是保留了master的修改,删除了bugfix02分支的修改。然后将修改的文件进行提交即可,在此就不做过多赘述了。

  

 

4、使用工具进行冲突解决

输入 git mergetool 然后根据提示输入opendiff, 在Mac下会打开Xcode自带的FileMerge工具。

  

 

下方就是启动的 FileMerge 工具, 在使用该工具进行文件merge时,可以选择几种文件合并策略。比如以左边为准,以右边为准等。

  

上述工具位于Xcode的开发工具中,如下所示:

  

 

 

三、分支的查看以及强制删除

首先我们使用 git log --oneline --graph --all 命令来可视化的看一下目前的分支状态。从下方的截图中我们可以看到,目前共有三个分支 master、bugfix02、bugfix03。并且我们可以看出bugfix02已经合入master分支,bugfix03尚未合入。

  

 

下方是一系列查看分支的一些方法:

  • git branch : 查看所有分支,其中前面有星号的是当前所在分支,下方即为master分支。
  • git branch -v : 查看所有分支和该分支上最后的一次提交。
  • git branch --merged : 查看已经合入当前分支的所有分支。
  • git branch --no-merged : 查看未被合入分支。

  

 

今天博客关于git分支管理的内容就先到这吧,下篇博客会详细介绍 rebase 以及 cherry-pick 等相关内容。 

]]>
OpenCASCADE构造一柱曲面 http://doc.okbase.net/eryar/archive/265901.html eryar 2018/1/19 9:35:50

OpenCASCADE构造一柱曲面

eryar@163.com

Abstract. 本文主要介绍常见的曲面如一般柱面(拉伸曲面)、旋转面在OpenCASCADE中的构造方法,由此思考一般放样算法的实现。

Key Words. Common Surface, Extrusion, Revolution

1.Introduction

实体Solid就是由面组成,面中包含几何曲面,常见的几何曲面有平面,柱面,旋转面等。对几何曲面有一些功能要求:如计算指定参数u,v处的点,切线等,即求指定参数u,v处的0阶导数、1阶导数,N阶导数;获取参数空间等。

图1. 放样曲面

由类图可以看出,放样曲面Swept Surface有两种形式:Geom_SurfaceOfLinearExtrusion和Geom_SurfaceOfRevolution。一种是线性拉伸成形的曲面,一种是旋转成形的曲面。

图2. 拉伸曲面

如上图1所示,可以将一条曲线沿一方向拉伸一定的距离来构造曲面,被拉伸的曲线称为准线。

图3. 旋转曲面

如上图2所示,将一条曲线绕指定的轴线旋转一定的角度得到一个旋转面。

本文结合OpenCASCADE中源码来说明拉伸曲面及旋转曲面原理。

2.The Surface of Extrusion

OpenCASCADE中线性拉伸曲面的类名为:Geom_SurfaceOfLinearExtrusion,其参数方程如下:

其中参数u的定义域决定准线C(u)的参数范围;参数v的取值范围是无穷的。即拉伸曲面是一个很长的柱面,如果在参数v上不加限制的话。其中一些计算功能代码如下所示:

//! Shift the point along direction to the given distance (theShift)
void Shift(const Standard_Real theShift, gp_Pnt& thePoint) const
{
thePoint.ChangeCoord() += myDirection.XYZ() * theShift;
}
void GeomEvaluator_SurfaceOfExtrusion::D0(
const Standard_Real theU, const Standard_Real theV,
gp_Pnt& theValue) const
{
if (!myBaseAdaptor.IsNull())
myBaseAdaptor->D0(theU, theValue);
else
myBaseCurve->D0(theU, theValue);
Shift(theV, theValue);
}

从计算曲面上在指定参数u,v处的点的函数D0()可以看出,先根据参数u计算出准线上的点,再将点沿拉伸方向移动拉伸向量模的距离。

图4. 拉伸多边形

如上图4所示为将多边形准线沿着Z方向拉伸得到一个拉伸曲面。

3.The Surface of Revolution

OpenCASCADE中旋转曲面的类名为:Geom_SurfaceOfRevolution,其参数方程如下:

其中计算旋转曲面上对应参数u,v的点的代码如下:

void GeomEvaluator_SurfaceOfRevolution::D0(
const Standard_Real theU, const Standard_Real theV,
gp_Pnt& theValue) const
{
if (!myBaseAdaptor.IsNull())
myBaseAdaptor->D0(theV, theValue);
else
myBaseCurve->D0(theV, theValue);
gp_Trsf aRotation;
aRotation.SetRotation(myRotAxis, theU);
theValue.Transform(aRotation);
}

根据代码可知,先根据参数v计算曲线上的点,再将点按指定的轴旋转变换。

图5. 旋转曲面

如上图5所示,将红色母线绕Y轴旋转90度得到的旋转曲面。线性拉伸曲面和旋转曲面都是特定条件下的放样曲面。拉伸曲面为沿直线放样得到的曲面,旋转曲面是沿圆放样得到的曲面。由此,可以思考一个问题,那就是如何将任意一个曲面沿任意路径来放样构造曲面呢?

图6. 放样曲面


为了方便大家在移动端也能看到我的博文,现已注册微信公众号,欢迎大家扫描下方二维码关注。

 

]]>
总结网络路由走向诊断方法 http://doc.okbase.net/qinqing1984/archive/265900.html qinqing1984 2018/1/19 9:35:43
由于traceroute只能诊断UDP通信的包路由,不能确定TCP通信的实际路由(可能变换),因此编写了本文。为方便描述,下面的IP、MAC和端口均为示例,实际诊断中可更换为具体的值

1. 如何判断客户端到服务器的TCP包,是否经过了网关
     在客户端执行 tcpdump -i eno16777728 ether dst b0:b9:8a:69:65:3e and host 192.168.0.26 and tcp port 80  抓取经过网关且往返服务器的TCP端口为80的包
     eno16777728 接口名称;ether 以太网链路,dst 目标(src表示源);b0:b9:8a:69:65:3e 网关MAC地址;192.168.0.26 服务器IP地址,80 监听端口

     输出结果分析
       ● 有输出,则表示经过了网关
       ● 有部分输出而TCP通信还在进行,则表示先前的包经过了网关,后来路由表项缓存被重定向更新,没经过网关了
       ● 不断输出,则表示一直经过网关

2. 如何判断路由表项缓存被重定向更新
     在客户端执行 tcpdump -i eno16777728 src 192.168.1.1 and dst 192.168.1.45 and icmp  抓取来自网关和到达客户端的所有icmp包
     192.168.1.1 网关IP;192.168.1.45 客户端(出口)IP

     输出结果分析
       ● 没有输出,则表示没有收到rerdirect包,路由表项缓存不变
       ● 有输出类似ICMP redirect 192.168.0.26 to host 192.168.0.26(前面一个IP表示到达服务器的直接路由IP,后一个表示服务器IP)
       ● 则表示收到了ICMP重定向包,内核会更新路由表项及缓存网关为192.168.0.26,下次通信时就直接发往192.168.0.26了

3. 如何控制接收ICMP重定向
      ● echo 0 | tee /proc/sys/net/ipv4/conf/*/accept_redirects    禁止所有网卡接收,可避免路由表项缓存被修改
      ● echo 1 | tee /proc/sys/net/ipv4/conf/*/accept_redirects    启用所有网卡接收ICMP重定向消息

4. 查看、刷新路由表项缓存
      ● ip route get 192.168.0.26    可以从输出中看到通住目标IP的实际路由
      ● ip route flush cache             清空路由表项缓存,下次通信时内核会查main表(即命令route输出的表)以确定路由
]]>
OpenCascade Ruled Surface http://doc.okbase.net/eryar/archive/265899.html eryar 2018/1/19 9:35:01

OpenCascade Ruled Surface

eryar@163.com

Abstract. A ruled surface is formed by moving a line connecting points of equal relative arc length or equal relative parametric value on two parametric curves from a start point to a terminate point on the curves. The paper focus on the ruled surface in opencascade.

Key words. OpenCascade, Ruled Surface,直纹面

1.Introduction

《解析几何》中有关于直纹面Ruled Surface的定义:一曲面S称为直纹面,如果存在一族直线使得这一族中的每一条直线全在S上。并且S上的每个点都在这一族的某一条直线上。这样一族直线称为S的一族直母线。其参数方程为:

即可以将直纹面看作是曲面对当v=0和1时得到的两个边界曲线之间进行线性插值得到的曲面。

Autodesk 3DS Max中的直纹面,图片来自:

https://knowledge.autodesk.com/support/3ds-max/learn-explore/caas/CloudHelp/cloudhelp/2015/ENU/3DSMax/files/GUID-364FE529-431B-448A-850B-DD9BBECAC90B-htm.html

直纹面是从两条曲线来构造曲面的方法,Coons曲面是由四边条界曲线来构造曲面,理解直纹面的构造原理,为进一步理解通用的放样Sweep造型打下基础。

2.Ruled Surface Parametric Equation

直纹面的参数方程也可以写成如下形式:

直接根据参数方程可以定义出相应的直纹面。在OpenCASCADE中话,可以从Geom_Surface派生新的类,并实现相应的虚函数。如实现计算对应参数u,v的值的虚函数D0()等。为了简单起见,用相应的函数计算直纹面上的点,并生成OpenCASCADE Draw Test Harness的命令脚本文件,方便在Draw中可视化。

如有名的Mobius Strip也是个直纹面:

Mobius Strip的参数方程为:

根据上述参数方程在OpenCASCADE的Draw生成Mobius Strip,代码如下所示:

const Standard_Real MOBIUS_RADIUS = 50.0;
void MobiusStrip(Standard_Real theU, Standard_Real theV, gp_Pnt& thePoint)
{
    thePoint.SetX((MOBIUS_RADIUS + theU * Cos(0.5 * theV)) * Cos(theV));
    thePoint.SetY((MOBIUS_RADIUS + theU * Cos(0.5 * theV)) * Sin(theV));
    thePoint.SetZ(theU * Sin(0.5 * theV));
}
void TestMobiusStrip()
{
    std::ofstream aTclFile("d:/mobius.tcl");
    aTclFile << "pload ALL" << std::endl;
    aTclFile << "vinit" << std::endl;
    Standard_Real aWidth = 10.0;
    Standard_Integer aN = 0;
    for (Standard_Real s = -aWidth; s < aWidth; s += 1.0)
    {
        aTclFile << "polyline p" << ++aN ;
        for (Standard_Real t = 0.0; t < M_PI * 2.0; t += 0.01)
        {
            gp_Pnt aPoint;
            MobiusStrip(s, t, aPoint);
            aTclFile << " " << aPoint.X() << " " << aPoint.Y() << " " << aPoint.Z();
        }
        aTclFile << "\n vdisplay p" << aN << std::endl;
    }
    for (Standard_Real t = 0.0; t < M_PI * 2.0; t += 0.2)
    {
        aTclFile << "polyline p" << ++aN;
        gp_Pnt aPoint;
        MobiusStrip(-aWidth, t, aPoint);
        aTclFile << " " << aPoint.X() << " " << aPoint.Y() << " " << aPoint.Z();
        MobiusStrip(aWidth, t, aPoint);
        aTclFile << " " << aPoint.X() << " " << aPoint.Y() << " " << aPoint.Z();
        aTclFile << "\n vdisplay p" << aN << std::endl;
    }
}
int main(int argc, char* argv[])
{
    TestMobiusStrip();
    return 0;
}

在D盘生成一个mobius.tcl脚本文件,直接在Draw Test Harness中输入命令:

source d:/mobius.tcl

即可得到如下图所示的Mobius环:

其他的直纹面只要知道参数方程,都可以采用这种方法在OpenCASCADE Draw Test Harness中进行显示。有人也用POV-Ray根据直纹面的参数方程来绘制直纹面,效果更不错。

这是使用参数方程来表示直纹面的方法,如果知道直纹面的参数方程,可以从几何曲面来派生新类Geom_Surface,并实现几个相关虚函数,应该可以直接给OpenCASCADE显示了,这种方法没有测试。

3.Ruled Surface to B Spline Surface

如果已知直纹面的参数方程,如何用NURBS曲面来表示直纹面呢?在《非均匀有理B样条》一书中给出了一种将给定两条曲线C1,C2转换成直纹面的方法。他给出的限制条件是想要生成在v方向是直线的曲面,即是C1(u)和C2(u)之间的线性插值。而且还要求在两条曲线的等参数点之间进行插值。又由于曲面是张量各曲面,两条边界曲线C1和C2必须具有相同的次数,并定义在相同的节点矢量上,因此表示这样的直纹面的B样条转换过程为:

l 确保两条曲线定义在相同的参数区间内;

l 确保两条曲线的次数相同。如果不同,则将次数低的曲线升阶;

l 确保两条曲线有相同的节点矢量。

OpenCASCADE中生成直纹面的是类GeomFill的静态函数Surface(),其实现步骤与上述类似,具体实现的类是GeomFill_Profiler。GeomFill_Profiler是个更通用的类,它可以根据多条曲线来构造曲面。下面通过Draw Test Harness脚本来根据两条曲线构造直纹面。

3.1 根据两条直线构造直纹面

# Ruled surface between two lines.

vertex v1 0 0 0
vertex v2 0 8 8
vertex v3 8 0 8
vertex v4 8 8 0
edge e1 v1 v2
edge e2 v3 v4
pruled r1 e1 e2
vdisplay v1 v2 v3 v4 e1 e2 r1

生成直纹面是一个双线性曲面,如下图所示:

3.2 根据两个圆构造直纹面

# Ruled surface between circle and ellipse.

circle c1 0 0 0 5
circle c2 0 0 10 4
mkedge e3 c1
mkedge e4 c2
pruled r2 e3 e4
vdisplay e3 e4 r2

生成的直纹面是一个圆锥面,效果如下图所示:

当顶部的圆旋转时会得到如下图所示的直纹面:

相应的Draw脚本如下:

# Ruled surface between circle and ellipse.

circle c1 0 0 0 5
circle c2 0 0 10 4
mkedge e3 c1
mkedge e4 c2
pruled r2 e3 e4
vdisplay e3 e4 r2
wait 2
trotate e4 0 0 0 0 0 1 30
pruled r2 e3 e4 
vdisplay r2
wait 2
trotate e4 0 0 0 0 0 1 30
pruled r2 e3 e4 
vdisplay r2
wait 2
trotate e4 0 0 0 0 0 1 30
pruled r2 e3 e4 
vdisplay r2

生成的动画效果如下图所示:

4.Conclusion

根据直纹面的参数方程就可以绘制出相应的曲面,然后如何用B样条曲面来表示直纹面,需要满足一定的条件。IGES中定义的直纹面就给出了两种方式:等弧长和等参数构造。引用《非均匀有理B样条》书中对两种形式的说明如下:一般情况下,连接两条曲线上相对弧长相等的点会产生一个几何上不同的曲面,而这样的曲面不能通过NURBS来表示。因此,要在NURBS的直纹面和IGES的直纹面(Type 118 Form 0/1)之间进行数学上的精确转换是不可能的。

OpenCASCADE的直纹面也是使用的NURBS表示,所以其也是等参数形式的直纹面。希望在理解根据两条曲线来构造曲面的方法来理解更一般的造型算法,即通过多条曲线来构造曲面的造型方法。

5.References

1. Weisstein Eric W. “Ruled Surface”. http://mathworld.wolfram.com/RuledSurface.html

2. www.ms.uky.edu/~lee/visual05/gallery/ruledsurfaces.doc

3. The Initial Graphics Exchange Specification (IGES) Version 6.0

4. 赵罡, 穆国旺, 王拉柱. 非均匀有理B样条. 清华大学出版社. 2010

5. 丘维声. 解析几何. 北京大学出版社. 1996


为了方便大家在移动端也能看到我的博文和讨论交流,现已注册微信公众号,欢迎大家扫描下方二维码关注。

]]>
无法连接mysql案例汇总 http://doc.okbase.net/20730537/archive/265898.html w_y_b<em></em> 2018/1/19 9:34:43 与研发同学打交道很多年,也包含自己工作中遇到的问题。
现在讲无法连接mysql的情况汇总下,以免大家再犯。

第一种情况:端口错误,尤其是一些公司,使用的是否非标准的短口,但是程序段没有配置。
      错误:ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111)
      解决方法:遇到这种情况,有限检查应用程序是否配置对了端口。

第二种情况:
      错误:ERROR 1045 (28000): Access denied for user 'root'@'192.168.8.123' (using password: NO)
       解决方法:检查程序是否配置密码,没有使用密码,就会报这个错误。

第三种情况:
  
      错误:ERROR 1045 (28000): Access denied for user 'root'@'192.168.8.123' (using password: YES)
      解决方法:
      (a)、首先检查是否给相应的ip开放了权限。
       如果是按照ip授权的:show grants for   'abc_rw'@'192.168.8.123' 是否存在。
       如果是按照ip段授权的:show grants for   'abc_rw'@'192.168.8.%' ;——>show grants for   'abc_rw'@'192.168.%';  ---->show grants for   'abc_rw'@'192.%';---->show grants for   'abc_rw'@'%';  依次看是否存在。
       如果不存在,需要给授权;如果存在,那极有可能是密码的问题。
  
                 
      (b)、检查密码:
      select password('abc')  (abc是使用的明文密码) 这个值
      是否与数据库mysql.user 表中的password 字段一样。
      如果是mysql5.7+ 是mysql.user 表中的 authentication_string
      (c)、如果用户名、网段、密码都没问题,那有可能是权限没有刷新,在密码是通过update 的
     方式更的,需要执行flush privileges;
 
      (d)、如果还不行,那就要看看研发配置的ip是否是他访问的ip或者域名,这种情况,也是遇到       的。 






    
    
]]>
进程的处理器亲和性和vCPU的绑定 http://doc.okbase.net/24174632/archive/265897.html helpstudy 2018/1/19 9:34:36

http://smilejay.com/2012/08/kvm-vcpu-binding/
通常情况下,在SMP系统中,Linux内核的进程调度器根据自有的调度策略将系统中的一个进程调度到某个CPU上执行。一个进程在前一个执行时间是在cpuM(M为系统中的某CPU的ID)上运行,而在后一个执行时间是在cpuN(N为系统中另一CPU的ID)上运行。这样的情况在Linux中是很可能发生的,因为Linux对进程执行的调度采用时间片法则(即进行用完自己的时间片即被暂停执行),而默认情况下,一个普通进程或线程的处理器亲和性是在所有可用的CPU上,有可能在它们之中的任何一个CPU(包括超线程)上执行。

进程的处理器亲和性(Processor Affinity),即是CPU的绑定设置,是指将进程绑定到特定的一个或多个CPU上去执行,而不允许调度到其他的CPU上。Linux内核对进程的调度算法也是遵守进程的处理器亲和性设置的。设置进程的处理器亲和性带来的好处是可以减少进程在多个CPU之间交换运行带来的缓存命中失效(cache missing),从该进程运行的角度来看,可能带来一定程度上的性能提升。换个角度来看,对进程亲和性的设置也可能带来一定的问题,如破坏了原有SMP系统中各个CPU的负载均衡(load balance),这可能会导致整个系统的进程调度变得低效。特别是在多处理器、多核、多线程技术使用的情况下,在NUMA(Non-Uniform Memory Access)[3]结构的系统中,如果不能基于对系统的CPU、内存等有深入的了解,对进程的处理器亲和性进行设置是可能导致系统的整体性能的下降而非提升。

每个vCPU都是宿主机中的一个普通的QEMU线程,可以使用taskset工具对其设置处理器亲和性,使其绑定到某一个或几个固定的CPU上去调度。尽管Linux内核的进程调度算法已经非常高效了,在多数情况下不需要对进程的调度进行干预,不过,在虚拟化环境中有时却有必要对客户机的QEMU进程或线程绑定到固定的逻辑CPU上。下面举一个云计算应用中需要绑定vCPU的实例。

作为IAAS(Infrastructure As A Service)类型的云计算提供商的A公司(如Amazon、Google、阿里云、盛大云等),为客户提供一个有2个逻辑CPU计算能力的一个客户机。要求CPU资源独立被占用,不受宿主机中其他客户机的负载水平的影响。为了满足这个需求,可以分为如下两个步骤来实现。

第一步,启动宿主机时隔离出两个逻辑CPU专门供一个客户机使用。在Linux内核启动的命令行加上“isolcpus=”参数,可以实现CPU的隔离,让系统启动后普通进程默认都不会调度到被隔离的CPU上执行。例如,隔离了cpu2和cpu3的grub的配置文件如下:

title Red Hat Enterprise Linux Server (3.5.0)

root (hd0,0)

kernel /boot/vmlinuz-3.5.0 ro root=UUID=1a65b4bb-cd9b-4bbf-97ff-7e1f7698d3db isolcpus=2,3

initrd /boot/initramfs-3.5.0.img

系统启动后,在宿主机中检查是否隔离成功,命令行如下:

[root@jay-linux ~]# ps -eLo psr | grep 0 | wc -l

106

[root@jay-linux ~]# ps -eLo psr | grep 1 | wc -l

107

[root@jay-linux ~]# ps -eLo psr | grep 2 | wc -l

4

[root@jay-linux ~]# ps -eLo psr | grep 3 | wc -l

4

[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==2) print $0}’

root        10     2    10   2 [migration/2]

root        11     2    11   2 [kworker/2:0]

root        12     2    12   2 [ksoftirqd/2]

root       245     2   245   2 [kworker/2:1]

[root@jay-linux ~]# ps –eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==3) print $0}’

root        13     2    13   3 [migration/3]

root        14     2    14   3 [kworker/3:0]

root        15     2    15   3 [ksoftirqd/3]

root       246     2   246   3 [kworker/3:1]

从上面的命令行输出信息可知,cpu0和cpu1上分别有106和107个线程在运行,而cpu2和cpu3上都分别只有4个线程在运行。而且,根据输出信息中cpu2和cpu3上运行的线程信息(也包括进程在内),分别有migration进程(用于进程在不同CPU间迁移)、两个kworker进程(用于处理workqueues)、ksoftirqd进程(用于调度CPU软中断的进程),这些进程都是内核对各个CPU的一些守护进程,而没有其他的普通进程在cup2和cpu3上运行,说明对其的隔离是生效的。

另外,简单解释一下上面的一些命令行工具及其参数的意义。ps命令显示当前系统的进程信息的状态,它的“-e”参数用于显示所有的进程,“-L”参数用于将线程(LWP,light-weight process)也显示出来,“-o”参数表示以用户自定义的格式输出(其中“psr”这列表示当前分配给进程运行的处理器编号,“lwp”列表示线程的ID,“ruser”表示运行进程的用户,“pid”表示进程的ID,“ppid”表示父进程的ID,“args”表示运行的命令及其参数)。结合ps和awk工具的使用,是为了分别将在处理器cpu2和cpu3上运行的进程打印出来。

第二步,启动一个拥有2个vCPU的客户机并将其vCPU绑定到宿主机中两个CPU上。此操作过程的命令行如下:

#(启动一个客户机)

[root@jay-linux kvm_demo]# qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize

VNC server running on ‘::1:5900’

 

#(查看代表vCPU的QEMU线程)

[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | grep qemu | grep -v grep

root      3963     1  3963   0 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize

root      3963     1  3967   0 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize

root      3963     1  3968   1 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 –daemonize

 

#(绑定代表整个客户机的QEMU进程,使其运行在cpu2上)

[root@jay-linux ~]# taskset -p 0×4 3963

pid 3963′s current affinity mask: 3

pid 3963′s new affinity mask: 4

#(绑定第一个vCPU的线程,使其运行在cpu2上)

[root@jay-linux ~]# taskset -p 0×4 3967

pid 3967′s current affinity mask: 3

pid 3967′s new affinity mask: 4

#(绑定第二个vCPU的线程,使其运行在cpu3上)

[root@jay-linux ~]# taskset -p 0×8 3968

pid 3968′s current affinity mask: 4

pid 3968′s new affinity mask: 8

 

#(查看QEMU线程的绑定是否生效,如下的第5列为处理器亲和性)

[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | grep qemu | grep -v grep

root      3963     1  3963   2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize

root      3963     1  3967   2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize

root      3963     1  3968   3 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 –daemonize

#(执行vCPU的绑定后,查看在cpu2上运行的线程)

[root@jay-linux ~]# ps -eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==2) print $0}’

root        10     2    10   2 [migration/2]

root        11     2    11   2 [kworker/2:0]

root        12     2    12   2 [ksoftirqd/2]

root       245     2   245   2 [kworker/2:1]

root      3963     1  3963   2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize

root      3963     1  3967   2 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize

#(执行vCPU的绑定后,查看在cpu3上运行的线程)

[root@jay-linux ~]# ps –eLo ruser,pid,ppid,lwp,psr,args | awk ‘{if($5==3) print $0}’

root        13     2    13   3 [migration/3]

root        14     2    14   3 [kworker/3:0]

root        15     2    15   3 [ksoftirqd/3]

root       246     2   246   3 [kworker/3:1]

root      3963     1  3968   3 qemu-system-x86_64 rhel6u3.img -smp 2 -m 512 -daemonize

由上面的命令行及其输出信息可知,CPU绑定之前,代表这个客户机的QEMU进程和代表各个vCPU的QEMU线程分别被调度到cpu0和cpu1上。使用taskset命令将QEMU进程和第一个vCPU的线程绑定到cpu2,将第二个vCPU线程绑定到cpu3上。绑定之后,即可查看到绑定的结果是生效的,代表两个vCPU的QEMU线程分别运行在cpu2和cpu3上(即使再过一段时间后,它们也不会被调度到其他CPU上去)。

对taskset命令解释一下,此处使用的语法是:taskset -p [mask] pid 。其中,mask是一个代表了处理器亲和性的掩码数字,转化为二进制表示后,它的值从最低位到最高位分别代表了第一个逻辑CPU到最后一个逻辑CPU,进程调度器可能将该进程调度到所有标为“1”的位代表的CPU上去运行。根据上面的输出,taskset运行之前,QEMU线程的处理器亲和性mask值是0×3(其二进制值为:0011),可知其可能会被调度到cpu0和cpu1上运行;而运行“taskset -p 0×4 3967”命令后,提示新的mask值被设为0×4(其二进制值为:0100),所以该进程就只能被调度到cpu2上去运行,即通过taskset工具实现了vCPU进程绑定到特定的CPU上。

上面命令行中,根据ps命令可以看到QEMU的线程和进程的关系,但如何查看vCPU与QEMU线程之间的关系呢?可以切换(“Ctrl+Alt+2”快捷键)到QEMU monitor中进行查看,运行“info cpus”命令即可(还记得3.6节中运行过的“info kvm”命令吧),其输出结果如下:

(qemu) info cpus

* CPU #0: pc=0xffffffff810375ab thread_id=3967

CPU #1: pc=0xffffffff812b2594 thread_id=3968

从上面的输出信息可知,客户机中的cpu0对应的线程ID为3967,cpu1对应的线程ID为3968。另外,“CPU #0”前面有一个星号(*),是标识cpu0是BSP(Boot Strap Processor,系统最初启动时在SMP生效前使用的CPU)。

总的来说,在KVM环境中,一般并不推荐手动地人为设置QEMU进程的处理器亲和性来绑定vCPU,但是,在非常了解系统硬件架构的基础上,根据实际应用的需求,是可以将其绑定到特定的CPU上去从而提高客户机中的CPU执行效率或者实现CPU资源独享的隔离性。

[2013.03.31] 添加几个关于CPU亲和性的小知识点:
1. 限制CPU亲和性的原因一般有如下3个:
1.1 任务中有大量计算存在;
1.2 测试复杂的应用程序(随着CPU个数的正常,程序的处理能力可以线性地扩展);
1.3 运行时间敏感的进程(实时性要求很高)。
2. 子进程会继承父进程的affinity属性(其实用taskset方式启动一个进程就是一次fork+exec)。
3. 在进程的代码中,使用sched_setaffinity函数可以设置该进程的CPU亲和性。
#include 
int sched_setaffinity(pid_t pid, unsigned int len, unsigned long *mask);
int sched_getaffinity(pid_t pid, unsigned int len, unsigned long *mask);
4. 使用Nginx时,其配置文件conf/nginx.conf中支持一个名为worker_cpu_affinity的配置项,也就是说,nginx可以为每个工作进程绑定CPU。
如下配置:
worker_processes 3;
worker_cpu_affinity 0010 0100 1000;
这里0010 0100 1000是掩码,分别代表第2、3、4颗CPU核心(或超线程)。
重启nginx后,3个工作进程就可以各自用各自的CPU了。
5. 在Windows系统中的“任务管理器”中,也可以对一个进程设置CPU亲和性“set affinity”。

逻辑CPU个数:

逻辑CPU个数是指cat /proc/cpuinfo 所显示的processor的个数
# cat /proc/cpuinfo | grep "processor" | wc -l

物理CPU个数
物理CPU个数,是指physical id(的值)的数量
# cat /proc/cpuinfo | grep "physical id" | sort | uniq | wc -l

每个物理CPU中Core的个数:

每个相同的physical id都有其对应的core id。如core id分别为1、2、3、4,则表示是Quad-Core CPU,若core id分别是1、2,则表示是Dual-Core。
# cat /proc/cpuinfo | grep "cpu cores" | wc -l

是否为超线程?
如果有两个逻辑CPU具有相同的"core id",那么超线程是打开的。

每个物理CPU中逻辑CPU(可能是core, threads或both)的个数:
# cat /proc/cpuinfo | grep "siblings"

逻辑cpu既可能是cores的个数,也可能是core的倍数。当它和core的个数相等时,表示每一个core就是一个逻辑CPU,若它时core的2倍时,表示每个core又enable了超线程(Hyper-Thread)。比如:一个双核的启用了超线程的物理cpu,其core id分别为1、2,但是sibling是4,也就是如果有两个逻辑CPU具有相同的"core id",那么超线程是打开的。


]]>
闲聊PostgreSQL的oid http://doc.okbase.net/20726500/archive/265896.html skykiker<em></em 2018/1/19 9:34:30

闲聊PostgreSQL的oid

oid为何物?

PostgreSQL的系统表中大多包含一个叫做OID的隐藏字段,这个OID也是这些系统表的主键。

所谓OID,中文全称就是"对象标识符"。what?还有“对象”?

如果对PostgreSQL有一定了解,应该知道PostgreSQL最初的设计理念就是"对象关系数据库"。也就是说,系统表中储存的那些元数据,比如表,视图,类型,操作符,函数,索引,FDW,甚至存储过程语言等等这些统统都是对象。具体表现就在于这些东西都可以扩展,可以定制。不仅如此,PostgreSQL还支持函数重载,表继承等这些很OO的特性。

利用PostgreSQL的这些特性,用户可以根据业务场景从应用层到数据库层做一体化的优化设计,获得极致的性能与用户体验。一些用惯了MySQL的互联网架构师推崇"把数据库当存储",这一设计准则用在MySQL上也许合适,但如果硬要套在PostgreSQL上,就有点暴殄天物了!

扯得有点远了^_^,下面举几个栗子看下oid长啥样。

使用示例

先随便创建一张表

postgres=# create table tb1(id int);
CREATE TABLE 

再看下这张表对应的oid

postgres=# select oid from pg_class where relname='tb1';
  oid  
-------
 32894
(1 row) 

这个oid是隐藏字段,因此必须在select列表里明确指定oid列名,光使用select *是不输出oid的。

postgres=# select *from pg_class where relname='tb1';
-[ RECORD 1 ]-------+------
relname             | tb1
relnamespace        | 2200
reltype             | 32896
reloftype           | 0
relowner            | 10
relam               | 0
relfilenode         | 32894
reltablespace       | 0
relpages            | 0
reltuples           | 0
relallvisible       | 0
reltoastrelid       | 32897
relhasindex         | f
relisshared         | f
relpersistence      | p
relkind             | r
relnatts            | 2
relchecks           | 0
relhasoids          | f
relhaspkey          | f
relhasrules         | f
relhastriggers      | f
relhassubclass      | f
relrowsecurity      | f
relforcerowsecurity | f
relispopulated      | t
relreplident        | d
relispartition      | f
relfrozenxid        | 596
relminmxid          | 2
relacl              | 
reloptions          | 
relpartbound        | 

不同对象对应于不同的对象标识符类型,比如表对象对应的对象标识符类型就是regclass, 通过对象标识符类型可以实现,对象标识符的数字值和对象名称之间的自由转换。

比如,上面那条SQL可以改写成以下的形式。

postgres=# select 'tb1'::regclass::int;
 int4  
-------
 32894
(1 row) 

反过来当然也是可以的,在PostgreSQL里就是一个普通的类型转换。

postgres=# select 32894::regclass;
 regclass 
----------
 tb1
(1 row) 

表的数据类型

作为OO的体现之一,PostgreSQL中每个表都是一个新的数据类型,即有一个相应的数据类型对象。

通过pg_class可以查出刚才创建的表对应的数据类型对象的oid

postgres=# select reltype from pg_class where relname='tb1';
 reltype 
---------
   32896
(1 row) 

在定义数据类型的系统表pg_type中保存了这个类型相关的信息。

postgres=# select * from pg_type where oid=32896;
-[ RECORD 1 ]--+------------
typname        | tb1
typnamespace   | 2200
typowner       | 10
typlen         | -1
typbyval       | f
typtype        | c
typcategory    | C
typispreferred | f
typisdefined   | t
typdelim       | ,
typrelid       | 32894
typelem        | 0
typarray       | 32895
typinput       | record_in
typoutput      | record_out
typreceive     | record_recv
typsend        | record_send
typmodin       | -
typmodout      | -
typanalyze     | -
typalign       | d
typstorage     | x
typnotnull     | f
typbasetype    | 0
typtypmod      | -1
typndims       | 0
typcollation   | 0
typdefaultbin  | 
typdefault     | 
typacl         | 

数据类型的对象标识符类型是regtype,通过regtype转换可以看到新创建的数据类型对象的名字也叫tb1。

postgres=# select 32896::regtype;
 regtype 
---------
 tb1
(1 row) 

tb1类型在使用上和内置的int,text这些常见的数据类型几乎没有区别。

所以,你可以把一个字符串的值转换成tb1类型。

postgres=# select $$(999,'abcd')$$::text::tb1;
     tb1      
--------------
 (999,'abcd')
(1 row) 

可以使用.取出表类型里面的1个或所有字段

postgres=# select ($$(999,'abcd')$$::text::tb1).id;
 id  
-----
 999
(1 row)

postgres=# select ($$(999,'abcd')$$::text::tb1).*;
 id  |   c1   
-----+--------
 999 | 'abcd'
(1 row) 

当然,还可以用这个类型去创建新的表

postgres=# create table tb2(id int, c1 tb1);
CREATE TABLE 

如果你其实是想要创建一个像表一样的数据类型(即多个字段的组合),也可以单独创建这个数据类型。 'g, postgres=# create type ty1 as (id int,c1 text); CREATE TYPE

表文件

每个表的数据存储在文件系统中单独的文件中(实际不止一个文件),文件路径可以通过系统函数查询

postgres=# select pg_relation_filepath('tb1');
 pg_relation_filepath 
----------------------
 base/13211/32894
(1 row) 

上面的base对应的是缺省表空间,除此以外还有global表空间。

postgres=# select oid,* from pg_tablespace ;
 oid  |  spcname   | spcowner | spcacl | spcoptions 
------+------------+----------+--------+------------
 1663 | pg_default |       10 |        | 
 1664 | pg_global  |       10 |        | 
(2 rows) 

用户等全局对象存储在global表空间

postgres=# select relname,reltablespace from pg_class where relkind='r' and reltablespace<>0;
        relname        | reltablespace 
-----------------------+---------------
 pg_authid             |          1664
 pg_subscription       |          1664
 pg_database           |          1664
 pg_db_role_setting    |          1664
 pg_tablespace         |          1664
 pg_pltemplate         |          1664
 pg_auth_members       |          1664
 pg_shdepend           |          1664
 pg_shdescription      |          1664
 pg_replication_origin |          1664
 pg_shseclabel         |          1664
(11 rows) 

表文件路径的第2部分13211是表所在数据库的oid

postgres=# select oid,datname from pg_database;
  oid  |  datname  
-------+-----------
 13211 | postgres
     1 | template1
 13210 | template0
(3 rows) 

第3部分就是表对象的oid。

oid如何分配?

oid的分配来自一个实例的全局变量,每分配一个新的对象,对这个全局变量加一。 当分配的oid超过4字节整形最大值的时候会重新从0开始分配,但这并不会导致类似于事务ID回卷那样严重的影响。

系统表一般会以oid作为主键,分配oid时,PostgreSQL会通过主键索引检查新的oid是否在相应的系统表中已经存在, 如果存在则尝试下一个oid。

相关代码如下:

Oid
GetNewOidWithIndex(Relation relation, Oid indexId, AttrNumber oidcolumn)
{
    Oid         newOid;
    SnapshotData SnapshotDirty;
    SysScanDesc scan;
    ScanKeyData key;
    bool        collides;

    InitDirtySnapshot(SnapshotDirty);

    /* Generate new OIDs until we find one not in the table */
    do
    {
        CHECK_FOR_INTERRUPTS();

        newOid = GetNewObjectId();

        ScanKeyInit(&key,
                    oidcolumn,
                    BTEqualStrategyNumber, F_OIDEQ,
                    ObjectIdGetDatum(newOid));

        /* see notes above about using SnapshotDirty */
        scan = systable_beginscan(relation, indexId, true,
                                  &SnapshotDirty, 1, &key);

        collides = HeapTupleIsValid(systable_getnext(scan));

        systable_endscan(scan);
    } while (collides);

    return newOid;
} 

因此,oid溢出不会导致系统表中出现oid冲突(2个不同的系统表可能存在oid相同的对象)。 但重试毕竟会使分配有效的oid花费较多的时间,因此不建议用户为普通的用户表使用oid(使用with oids)从而导致oid过早的耗尽。 而且,使用oid的用户表如果未给oid创建唯一索引,oid溢出时,可能这个用户表中可能出现重复oid。以下是一个简单的演示:

创建一个with oids的表,并插入2条记录

postgres=# create table tb3(id int) with oids;
CREATE TABLE
postgres=# insert into tb3 values(1);
INSERT 32912 1
postgres=# insert into tb3 values(2);
INSERT 32913 1 

此时,下一个全局oid是32914

[postgres@node1 ~]$ pg_ctl -D data stop
waiting for server to shut down.... done
server stopped
[postgres@node1 ~]$ pg_controldata data
pg_control version number:            1002
Catalog version number:               201707211
Database system identifier:           6500386650559491472
Database cluster state:               shut down
pg_control last modified:             Sun 07 Jan 2018 11:14:58 PM CST
Latest checkpoint location:           0/9088930
Prior checkpoint location:            0/9073988
Latest checkpoint's REDO location:    0/9088930
Latest checkpoint's REDO WAL file:    000000010000000000000009
Latest checkpoint's TimeLineID:       1
Latest checkpoint's PrevTimeLineID:   1
Latest checkpoint's full_page_writes: on
Latest checkpoint's NextXID:          0:602
Latest checkpoint's NextOID:          32914
Latest checkpoint's NextMultiXactId:  2
Latest checkpoint's NextMultiOffset:  3
Latest checkpoint's oldestXID:        548
Latest checkpoint's oldestXID's DB:   1
Latest checkpoint's oldestActiveXID:  0
Latest checkpoint's oldestMultiXid:   1
Latest checkpoint's oldestMulti's DB: 1
Latest checkpoint's oldestCommitTsXid:0
Latest checkpoint's newestCommitTsXid:0
Time of latest checkpoint:            Sun 07 Jan 2018 11:14:58 PM CST
Fake LSN counter for unlogged rels:   0/1
Minimum recovery ending location:     0/0
Min recovery ending loc's timeline:   0
Backup start location:                0/0
Backup end location:                  0/0
End-of-backup record required:        no
wal_level setting:                    replica
wal_log_hints setting:                off
max_connections setting:              100
max_worker_processes setting:         8
max_prepared_xacts setting:           0
max_locks_per_xact setting:           64
track_commit_timestamp setting:       off
Maximum data alignment:               8
Database block size:                  8192
Blocks per segment of large relation: 131072
WAL block size:                       8192
Bytes per WAL segment:                16777216
Maximum length of identifiers:        64
Maximum columns in an index:          32
Maximum size of a TOAST chunk:        1996
Size of a large-object chunk:         2048
Date/time type storage:               64-bit integers
Float4 argument passing:              by value
Float8 argument passing:              by value
Data page checksum version:           0
Mock authentication nonce:            5b060aed93e061d3d1ad2dccdfe3336b1ac844f94872e068d86587c48c7d394a 

篡改下一个全局oid为32912

[postgres@node1 ~]$ pg_resetwal -D data -o 32912
Write-ahead log reset
[postgres@node1 ~]$ pg_ctl -D data start 

再插入3条记录,oid存在重复分配。

postgres=# insert into tb3 values(3);
INSERT 32912 1
postgres=# insert into tb3 values(4);
INSERT 32913 1
postgres=# insert into tb3 values(5);
INSERT 32914 1
postgres=# select oid,* from tb3;
  oid  | id 
-------+----
 32912 |  1
 32913 |  2
 32912 |  3
 32913 |  4
 32914 |  5
(5 rows)
]]>
使用rt_tables巧妙配置Linux多网卡多路由实现策略路由 http://doc.okbase.net/7934175/archive/265895.html zzjlzx<em></em> 2018/1/19 9:34:28