NodeJS源码解析 - HTTP Server模块 (2018-03-04)

 

NodeJS源码解析 - HTTP Server模块

http是nodejs中重要的模块之一,有必要了解它的运行原理

回到helloworld ,当node在收到一个http请求,会创建一个http.Server,注册并监听request。

var http = require('http');
http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('Hello World\n');
}).listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

HTTP模块

  1. 打开node-v8.9.3/lib/http.js

首先引入的是http模块,模块抛出公共方法调用createServer实际上是返回Server实例,

createServer里面的回调函数(参数requestListener)

直接作为了Server的参数requestListener,而这个Server实际上是require('_http_server')

'use strict';

const agent = require('_http_agent');
const { ClientRequest } = require('_http_client');
const common = require('_http_common');
const incoming = require('_http_incoming');
const outgoing = require('_http_outgoing');
//引入私有_http_server模块
const server = require('_http_server');

const { Server } = server;
//创建server, 将回调函数作为参数
function createServer(requestListener) {
  return new Server(requestListener);
}

function request(options, cb) {
  return new ClientRequest(options, cb);
}

function get(options, cb) {
  var req = request(options, cb);
  req.end();
  return req;
}
//http模块暴露的所有公共方法
module.exports = { 
  _connectionListener: server._connectionListener,
  METHODS: common.methods.slice().sort(),
  STATUS_CODES: server.STATUS_CODES,
  Agent: agent.Agent,
  ClientRequest,
  globalAgent: agent.globalAgent,
  IncomingMessage: incoming.IncomingMessage,
  OutgoingMessage: outgoing.OutgoingMessage,
  Server,
  ServerResponse: server.ServerResponse,
  createServer, 
  get,
  request
};

打开文件node-v8.9.3/lib/_http_server.js 260行

实际上是为这个requestListener函数与'request'事件绑定到了一起,而'request '是方法parserOnIncoming里面抛出的一个事件

function Server(requestListener) {
  if (!(this instanceof Server)) return new Server(requestListener);
  net.Server.call(this, { allowHalfOpen: true }); 
 
  //如果有回调函数,对当前实例进行监听,若request有事件触发则调用回调
  if (requestListener) {
    this.on('request', requestListener);
  }

  // Similar option to this. Too lazy to write my own docs.
  // http://www.squid-cache.org/Doc/config/half_closed_clients/
  // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F
  this.httpAllowHalfOpen = false;
  //当启动server实例时,观察者建立connect事件
  this.on('connection', connectionListener);

  this.timeout = 2 * 60 * 1000;
  this.keepAliveTimeout = 5000;
  this._pendingResponseData = 0;
  this.maxHeadersCount = null;
}

//net.Server继承Server
util.inherits(Server, net.Server);

res过程?

调用emit方法,将request事件发送给每一个监听的实例,并且传入req,res

server.emit('request', req, res); 这个事件也会同时抛出req和res两个对象

req变量与另一个叫做shouldKeepAlive的变量作参同时传入此函数parserOnIncoming

_http_server.js 592行 602行

//处理具体解析完毕的请求
function parserOnIncoming(server, socket, state, req, keepAlive) {
  resetSocketTimeout(server, socket, state);

  state.incoming.push(req);

  // If the writable end isn't consuming, then stop reading
  // so that we don't become overwhelmed by a flood of
  // pipelined requests that may never be resolved.
  if (!socket._paused) {
    var ws = socket._writableState;
    if (ws.needDrain || state.outgoingData >= ws.highWaterMark) {
      socket._paused = true;
      // We also need to pause the parser, but don't do that until after
      // the call to execute, because we may still be processing the last
      // chunk.
      socket.pause();
    }
  }
  //服务器通过ServerResponse实例,来个请求方发送数据。包括发送响应表头,发送响应主体
  var res = new ServerResponse(req);
  res._onPendingData = updateOutgoingData.bind(undefined, socket, state);

  res.shouldKeepAlive = keepAlive;
  DTRACE_HTTP_SERVER_REQUEST(req, socket);
  LTTNG_HTTP_SERVER_REQUEST(req, socket);
  COUNTER_HTTP_SERVER_REQUEST();

  if (socket._httpMessage) {
    // There are already pending outgoing res, append.
    state.outgoing.push(res);
  } else {
    res.assignSocket(socket);
  }

  // When we're finished writing the response, check if this is the last
  // response, if so destroy the socket.
  res.on('finish',
         resOnFinish.bind(undefined, req, res, socket, state, server));

  if (req.headers.expect !== undefined &&
      (req.httpVersionMajor === 1 && req.httpVersionMinor === 1)) {
    if (continueExpression.test(req.headers.expect)) {
      res._expect_continue = true;

      if (server.listenerCount('checkContinue') > 0) {
        server.emit('checkContinue', req, res);
      } else {
        res.writeContinue();
        //送给每一个监听器的实例并传入req&res
        server.emit('request', req, res);
      }
    } else if (server.listenerCount('checkExpectation') > 0) {
      server.emit('checkExpectation', req, res);
    } else {
      res.writeHead(417);
      res.end();
    }
  } else {
    //送给每一个监听器的实例并传入req&res
    // res实际上是ServerResponse的实例
    // var res = new ServerResponse(req);
    server.emit('request', req, res);
  }
  return false; // Not a HEAD response. (Not even a response!)
}

ServerResponse 实现了 Writable Stream interface,内部也是通过socket来发送信息。 res,发现为ServerResponse()的实例并传入req

function ServerResponse(req) {
  OutgoingMessage.call(this);

  if (req.method === 'HEAD') this._hasBody = false;

  this.sendDate = true;
  this._sent100 = false;
  this._expect_continue = false;

  if (req.httpVersionMajor < 1 || req.httpVersionMinor < 1) {
    this.useChunkedEncodingByDefault = chunkExpression.test(req.headers.te);
    this.shouldKeepAlive = false;
  }
}
//继承自OutgoingMessage,为OM的一个子类,所以回调函数里的res也是OM的一个实例
//来自_http_outgoing私有模块  
//const OutgoingMessage = require('_http_outgoing').OutgoingMessage;
util.inherits(ServerResponse, OutgoingMessage);

到此res线找到,res为ServerMessage的实例,也是OutgoingMessage的实例

function OutgoingMessage() {
  Stream.call(this);
  
  //返回一些与服务器有关的属性
  // Queue that holds all currently pending data, until the response will be
  // assigned to the socket (until it will its turn in the HTTP pipeline).
  this.output = []; 
  this.outputEncodings = []; 
  this.outputCallbacks = []; 

  // `outputSize` is an approximate measure of how much data is queued on this
  // response. `_onPendingData` will be invoked to update similar global
  // per-connection counter. That counter will be used to pause/unpause the
  // TCP socket and HTTP Parser and thus handle the backpressure.
  this.outputSize = 0;

  this.writable = true;

  this._last = false;
  this.upgrading = false;
  this.chunkedEncoding = false;
  this.shouldKeepAlive = true;
  this.useChunkedEncodingByDefault = true;
  this.sendDate = false;
  this._removedConnection = false;
  this._removedContLen = false;
  this._removedTE = false;

  this._contentLength = null;
  this._hasBody = true;
  this._trailer = ''; 

  this.finished = false;
  this._headerSent = false;

  this.socket = null;
  this.connection = null;
  this._header = null;
  this[outHeadersKey] = null;

  this._onPendingData = noopPendingOutput;
}
util.inherits(OutgoingMessage, Stream); //继承自Stream  

流程图演示:

image

req 过程

req,在parserOnIncoming()作为参数传入

parserOnIncoming()在哪里被调用?

// _http_server.js 345行
function connectionListener(socket) {
    ...
    var parser = parsers.alloc();
    parser.onIncoming = parserOnIncoming.bind(undefined, this, socket, state);
    ...
}

parsers在_http_common.js抛出 onIncoming在skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive)中调用

 function parserOnHeadersComplete(...) {      ...      //IncomingMessage的实例并将套接字作为参数传入 ,来自_http_common.js模块      parser.incoming = new IncomingMessage(parser.socket);      parser.incoming.httpVersionMajor = versionMajor;      parser.incoming.httpVersionMinor = versionMinor;      parser.incoming.httpVersion = `${versionMajor}.${versionMinor}`;      parser.incoming.url = url;      ...      //onIncoming 这里被调用 parser.incoming相当于req      skipBody = parser.onIncoming(parser.incoming, shouldKeepAlive);      ...  } 

流程图演示: image

Listen 过程

基于ner.js模块 Server Connection事件在net.Server.call(this, { allowHalfOpen: true })触发

connection会在onconnection中触发handle

function onconnection(err, clientHandle) {
  var handle = this;
  var self = handle.owner;

  debug('onconnection');

  if (err) {
    self.emit('error', errnoException(err, 'accept'));
    return;
  }

  if (self.maxConnections && self._connections >= self.maxConnections) {
    clientHandle.close();
    return;
  }

  var socket = new Socket({
    handle: clientHandle,
    allowHalfOpen: self.allowHalfOpen,
    pauseOnCreate: self.pauseOnConnect
  });  
  socket.readable = socket.writable = true;


  self._connections++;
  socket.server = self;
  socket._server = self;

  DTRACE_NET_SERVER_CONNECTION(socket);
  LTTNG_NET_SERVER_CONNECTION(socket);
  COUNTER_NET_SERVER_CONNECTION(socket);
  
  self.emit('connection', socket);
}

listen2调用setupListenHandle方法,注册onconnection

function setupListenHandle(address, port, addressType, backlog, fd) {
    ...
    this._handle.onconnection = onconnection
    ...
}

_listen2注册handle, 在listen里被调用

Server.prototype._listen2 = setupListenHandle;
server._listen2(address, port, addressType, backlog, fd);

listen在Server原型上,所以在代码里的http.createServer()实例上有listen()方法

Server.prototype.listen = function(...args) {
    ...
    if (options instanceof TCP) {
      this._handle = options;
      this[async_id_symbol] = this._handle.getAsyncId();
      listenInCluster(this, null, -1, -1, backlogFromArgs);
      return this;
    }
    ...
Socket.prototype.listen = function() {
  debug('socket.listen');
  this.on('connection', arguments[0]);
  listenInCluster(this, null, null, null);
};

Listen流程图:

 image

我的github地址:https://github.com/fzxa/NodeJS-Nucleus-Plus-Internals/blob/master/chapter1/chapter1-1.md

参考链接: https://yjhjstz.gitbooks.io/deep-into-node/chapter10/chapter10-1.html https://www.cnblogs.com/chyingp/p/node-learning-guide-http.html http://blog.csdn.net/sinat_22996989/article/details/51496010

点击查看原文阅读(8) | 评论(0) | 分类:Node.js

NodeJS源码分析-1 Hello world (2018-03-04)

 

NodeJS源码分析-1 Hello world

简要

Node已经如今发展很快,已经相对稳定和成熟,在某些时候有必要知道其内部运行原理以及运行处理过程。 种一棵树最好的时间是十年前 其次是现在。希望能坚持下去。

Nodejs当前最新版本 8.9.4

NodeJS官方网站下载源码 image

Node.js主要分为四大部分,Node Standard Library,Node Bindings,V8,Libuv

大体流程是这样的:

  1. 初始化 V8 、LibUV , OpenSSL

  2. 创建 Environment 环境

  3. 设置 Process 进程对象

  4. 执行 node.js 文件

解压包后代码结构如下:

├── AUTHORS ├── BSDmakefile   # bsd平台makefile文件 ├── BUILDING.md ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── COLLABORATOR_GUIDE.md ├── CONTRIBUTING.md ├── CPP_STYLE_GUIDE.md ├── GOVERNANCE.md ├── LICENSE ├── Makefile     # Linux平台makefile文件 ├── README.md ├── android-configure ├── benchmark ├── common.gypi ├── configure ├── deps          # Node底层核心依赖; 最核心的两块V8 Engine和libuv事件驱动的异步I/O模型库 ├── doc            ├── lib           # Node后端核心库 ├── node.gyp      # Node编译任务配置文件  ├── node.gypi ├── src           # C++内建模块 ├── test          # 测试代码 ├── tools         # 编译时用到的工具 └── vcbuild.bat   # Windows跨平台makefile文件 

Hello World 底层运行过程

官方Hello world代码

#app.js
const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader('Content-Type', 'text/plain');
  res.end('Hello World\n');
});

server.listen(port, hostname, () => {
  console.log(`Server running at http://${hostname}:${port}/`);
});

一个简单的Helloworld涉及到多个模块:

  • global
  • module
  • http
  • event
  • net

1.0 从main执行到js

入口 src/node_main.cc 106行 通过 src/node.cc 调用 node::Start(argc, argv); node_main.cc

namespace node {
  extern bool linux_at_secure;
}  // namespace node

int main(int argc, char *argv[]) {
#if defined(__linux__)
  char** envp = environ;
  while (*envp++ != nullptr) {}
  Elf_auxv_t* auxv = reinterpret_cast<Elf_auxv_t*>(envp);
  for (; auxv->a_type != AT_NULL; auxv++) {
    if (auxv->a_type == AT_SECURE) {
      node::linux_at_secure = auxv->a_un.a_val;
      break;
    }   
  }
#endif
  // Disable stdio buffering, it interacts poorly with printf()
  // calls elsewhere in the program (e.g., any logging from V8.)
  setvbuf(stdout, nullptr, _IONBF, 0); 
  setvbuf(stderr, nullptr, _IONBF, 0); 
  // main作为入口调用node::Start
  return node::Start(argc, argv);
}
#endif

1.1 node::Start 加载js

调用顺序:

Start() -> LoadEnviroment() -> ExecuteString()

最终在LoadEnvrioment()里面加载node.js文件,调用ExecuteString() 解析执行node.js文件,返回值是一个f_value

并且在ExecuteString()调用V8的 Script::Compile() 和 Script::Run()两个接口去解析执行js代码。

node.cc

# Nodejs启动入口, 
inline int Start(Isolate* isolate, IsolateData* isolate_data,
                 int argc, const char* const* argv,
                 int exec_argc, const char* const* exec_argv) {
  HandleScope handle_scope(isolate);
  Local<Context> context = Context::New(isolate);
  Context::Scope context_scope(context);
  Environment env(isolate_data, context);
  CHECK_EQ(0, uv_key_create(&thread_local_env));
  uv_key_set(&thread_local_env, &env);
  env.Start(argc, argv, exec_argc, exec_argv, v8_is_profiling);

  const char* path = argc > 1 ? argv[1] : nullptr;
  StartInspector(&env, path, debug_options);

  if (debug_options.inspector_enabled() && !v8_platform.InspectorStarted(&env))
    return 12;  // Signal internal error.

  env.set_abort_on_uncaught_exception(abort_on_uncaught_exception);

  if (force_async_hooks_checks) {
    env.async_hooks()->force_checks();
  }

  {
    Environment::AsyncCallbackScope callback_scope(&env);
    env.async_hooks()->push_async_ids(1, 0);
    
    //加载nodejs文件后调用ExecuteString()
    LoadEnvironment(&env); 
    env.async_hooks()->pop_async_id(1);
  }

  env.set_trace_sync_io(trace_sync_io);
  //事件循环池
  {
    SealHandleScope seal(isolate);
    bool more;
    PERFORMANCE_MARK(&env, LOOP_START);
    do {
      uv_run(env.event_loop(), UV_RUN_DEFAULT);

      v8_platform.DrainVMTasks();

      more = uv_loop_alive(env.event_loop());
      if (more)
        continue;

      EmitBeforeExit(&env);

      // Emit `beforeExit` if the loop became alive either after emitting
      // event, or after running some callbacks.
      more = uv_loop_alive(env.event_loop());
    } while (more == true);
    PERFORMANCE_MARK(&env, LOOP_EXIT);
  }

  env.set_trace_sync_io(false);

  const int exit_code = EmitExit(&env);
  RunAtExit(&env);
  uv_key_delete(&thread_local_env);

  v8_platform.DrainVMTasks();
  WaitForInspectorDisconnect(&env);
#if defined(LEAK_SANITIZER)
  __lsan_do_leak_check();
#endif

  return exit_code;
}

核心运行流程

整体运行流程图 image

  1. 核心数据结构 default_loop_struct 结构体为struct uv_loop_s 当加载js文件时,如果代码有io操作,调用lib模块->底层C++模块->LibUV(deps uv)->拿到系统返回的一个fd(文件描述符),和 js代码传进来的回调函数callback,封装成一个io观察者(一个uv__io_s类型的对象),保存到default_loop_struct.

  2. 进入事件池, default_loop_struct保存对应io观察着,V8 Engine处理js代码, main函数调用libuv进入uv_run(), node进入事件循环 ,判断是否有存活的观察者

  • 如果也没有io, Node进程退出
  • 如果有io观察者, 执行uv_run()进入epoll_wait()线程挂起,io观察者检测是否有数据返回callback, 没有数据则会一直在epoll_wait()等待执行 server.listen(3000)会挂起一直等待。

Module对象

根据CommonJS规范,每一个文件就是一个模块,在每个模块中,都会有一个module对象,这个对象就指向当前的模块。 module对象具有以下属性:

  • id:当前模块的bi
  • exports:表示当前模块暴露给外部的值
  • parent: 是一个对象,表示调用当前模块的模块
  • children:是一个对象,表示当前模块调用的模块
  • filename:模块的绝对路径
  • paths:从当前文件目录开始查找node_modules目录;然后依次进入父目录,查找父目录下的node_modules目录;依次迭代,直到根目录下的node_modules目录
  • loaded:一个布尔值,表示当前模块是否已经被完全加载

示例:

module.exports = { 
    name: 'fzxa',
    getAge: function(age){
            console.log(age)
    }   
}
console.log(module)

执行node module.js 返回如下

Module {
  id: '.',
  exports: { name: 'fzxa', getAge: [Function: getAge] },
  parent: null,
  filename: '/Users/fzxa/Documents/study/module.js',
  loaded: false,
  children: [],
  paths: 
   [ '/Users/fzxa/Documents/study/node_modules',
     '/Users/fzxa/Documents/node_modules',
     '/Users/fzxa/node_modules',
     '/Users/node_modules',
     '/node_modules' ] }

module对象具有一个exports属性,该属性就是用来对外暴露变量、方法或整个模块的。当其他的文件require进来该模块的时候,实际上就是读取了该模块module对象的exports属性

exports对象

exports和module.exports都是引用类型的变量,而且这两个对象指向同一块内存地址

exports = module.exports = {}; 

例子:

var module = {
    exports: {}
}

var exports = module.exports

function change(exports) {
    //为形参添加属性,是会同步到外部的module.exports对象的
    exports.name = "fzxa"
    //在这里修改了exports的引用,并不会影响到module.exports
    exports = {
        age: 24
    }
    console.log(exports) //{ age: 24 }
}

change(exports)
console.log(module.exports) //{exports: {name: "fzxa"}}

直接给exports赋值,会改变当前模块内部的形参exports对象的引用,也就是说当前的exports已经跟外部的module.exports对象没有任何关系了,所以这个改变是不会影响到module.exports的

module.exports就是为了解决上述exports直接赋值,会导致抛出不成功的问题而产生的。有了它,我们就可以这样来抛出一个模块了.

require方法

Node中引入模块的机制步骤

  1. 路径分析
  2. 文件定位
  3. 编译执行 Node对引入过的模块也会进行缓存。不同的地方是,node缓存的是编译执行之后的对象而不是静态文件

Module._load的源码:

Module._load = function(request, parent, isMain) {

  //  计算绝对路径
  var filename = Module._resolveFilename(request, parent);

  //  第一步:如果有缓存,取出缓存
  var cachedModule = Module._cache[filename];
  if (cachedModule) {
    return cachedModule.exports;

  // 第二步:是否为内置模块
  if (NativeModule.exists(filename)) {
    return NativeModule.require(filename);
  }

  // 第三步:生成模块实例,存入缓存
  var module = new Module(filename, parent);
  Module._cache[filename] = module;

  // 第四步:加载模块
  try {
    module.load(filename);
    hadException = false;
  } finally {
    if (hadException) {
      delete Module._cache[filename];
    }
  }

  // 第五步:输出模块的exports属性
  return module.exports;
};

在Module._load方法的内部调用了Module._findPath这个方法,这个方法是用来返回模块的绝对路径的,源码如下:

Module._findPath = function(request, paths) {

  // 列出所有可能的后缀名:.js,.json, .node
  var exts = Object.keys(Module._extensions);

  // 如果是绝对路径,就不再搜索
  if (request.charAt(0) === '/') {
    paths = [''];
  }

  // 是否有后缀的目录斜杠
  var trailingSlash = (request.slice(-1) === '/');

  // 第一步:如果当前路径已在缓存中,就直接返回缓存
  var cacheKey = JSON.stringify({request: request, paths: paths});
  if (Module._pathCache[cacheKey]) {
    return Module._pathCache[cacheKey];
  }

  // 第二步:依次遍历所有路径
  for (var i = 0, PL = paths.length; i < PL; i++) {
    var basePath = path.resolve(paths[i], request);
    var filename;

    if (!trailingSlash) {
      // 第三步:是否存在该模块文件
      filename = tryFile(basePath);

      if (!filename && !trailingSlash) {
        // 第四步:该模块文件加上后缀名,是否存在
        filename = tryExtensions(basePath, exts);
      }
    }

    // 第五步:目录中是否存在 package.json 
    if (!filename) {
      filename = tryPackage(basePath, exts);
    }

    if (!filename) {
      // 第六步:是否存在目录名 + index + 后缀名 
      filename = tryExtensions(path.resolve(basePath, 'index'), exts);
    }

    // 第七步:将找到的文件路径存入返回缓存,然后返回
    if (filename) {
      Module._pathCache[cacheKey] = filename;
      return filename;
    }
 }

  // 第八步:没有找到文件,返回false 
  return false;
};

当我们第一次引入一个模块的时候,require的缓存机制会将我们引入的模块加入到内存中,以提升二次加载的性能。但是,如果我们修改了被引入模块的代码之后,当再次引入该模块的时候,就会发现那并不是我们最新的代码,这是一个麻烦的事情。如何解决呢 require有如下方法: require(): 加载外部模块 require.resolve():将模块名解析到一个绝对路径 require.main:指向主模块 require.cache:指向所有缓存的模块 require.extensions:根据文件的后缀名,调用不同的执行函数

//删除指定模块的缓存 delete require.cache[require.resolve('/*被缓存的模块名称*/')]  // 删除所有模块的缓存 Object.keys(require.cache).forEach(function(key) {      delete require.cache[key]; }) 

HTTP_Server

首先需要创建一个 http.Server 类的实例,然后监听它的 request 事件

requestListener 回调函数作为观察者,监听了 request 事件, 默认超时时间为2分

lib/_http_server.js

function Server(requestListener) {
  if (!(this instanceof Server)) return new Server(requestListener);
  net.Server.call(this, { allowHalfOpen: true }); 

  if (requestListener) {
    this.on('request', requestListener);
  }

  // Similar option to this. Too lazy to write my own docs.
  // http://www.squid-cache.org/Doc/config/half_closed_clients/
  // http://wiki.squid-cache.org/SquidFaq/InnerWorkings#What_is_a_half-closed_filedescriptor.3F
  this.httpAllowHalfOpen = false;

  this.on('connection', connectionListener);

  this.timeout = 2 * 60 * 1000;
  this.keepAliveTimeout = 5000;
  this._pendingResponseData = 0;
  this.maxHeadersCount = null;
}

观察者 connectionListener 处理 connection 事件。 这时,则需要一个 HTTP parser 来解析通过 TCP 传输过来的数据:

lib/_http_server.js

function connectionListener(socket) {
  debug('SERVER new http connection');

  httpSocketSetup(socket);

  // Ensure that the server property of the socket is correctly set.
  // See https://github.com/nodejs/node/issues/13435
  if (socket.server === null)
    socket.server = this;

  // If the user has added a listener to the server,
  // request, or response, then it's their responsibility.
  // otherwise, destroy on timeout by default
  if (this.timeout)
    socket.setTimeout(this.timeout);
  socket.on('timeout', socketOnTimeout);

  var parser = parsers.alloc();
  parser.reinitialize(HTTPParser.REQUEST);
  parser.socket = socket;
  socket.parser = parser;
  parser.incoming = null;

  // Propagate headers limit from server instance to parser
  if (typeof this.maxHeadersCount === 'number') {
    parser.maxHeaderPairs = this.maxHeadersCount << 1;
  } else {
    // Set default value because parser may be reused from FreeList
    parser.maxHeaderPairs = 2000;
  }

  var state = { 
    onData: null,
    onEnd: null,
    onClose: null,
    onDrain: null,
    outgoing: [], 
    incoming: [], 
    // `outgoingData` is an approximate amount of bytes queued through all
    // inactive responses. If more data than the high watermark is queued - we
    // need to pause TCP socket/HTTP parser, and wait until the data will be
    // sent to the client.
    outgoingData: 0,
    keepAliveTimeoutSet: false
  };  
  state.onData = socketOnData.bind(undefined, this, socket, parser, state);
  state.onEnd = socketOnEnd.bind(undefined, this, socket, parser, state);
  state.onClose = socketOnClose.bind(undefined, socket, state);
  state.onDrain = socketOnDrain.bind(undefined, socket, state);
  socket.on('data', state.onData);
  socket.on('error', socketOnError);
  socket.on('end', state.onEnd);
  socket.on('close', state.onClose);
  socket.on('drain', state.onDrain);
  parser.onIncoming = parserOnIncoming.bind(undefined, this, socket, state);

  // We are consuming socket, so it won't get any actual data
  socket.on('resume', onSocketResume);
  socket.on('pause', onSocketPause);

  // Override on to unconsume on `data`, `readable` listeners
  socket.on = socketOnWrap;

  // We only consume the socket if it has never been consumed before.
  var external = socket._handle._externalStream;
  if (!socket._handle._consumed && external) {
    parser._consumed = true;
    socket._handle._consumed = true;
    parser.consume(external);
  }
  parser[kOnExecute] =
    onParserExecute.bind(undefined, this, socket, parser, state);

  socket._paused = false;
}

github: https://github.com/fzxa/NodeJS-Nucleus-Plus-Internals/blob/master/chapter1/chapter1-0.md

参考链接:

https://yjhjstz.gitbooks.io/deep-into-node/chapter1/ http://blog.csdn.net/wuji3390/article/details/71276849 https://feclub.cn/post/content/wq_node

 

点击查看原文阅读(11) | 评论(0) | 分类:Node.js

Consul 使用手册 (2017-09-06)

 

介绍

Consul包含多个组件,但是作为一个整体,为你的基础设施提供服务发现和服务配置的工具.他提供以下关键特性:

  • 服务发现 Consul的客户端可用提供一个服务,比如 api 或者mysql ,另外一些客户端可用使用Consul去发现一个指定服务的提供者.通过DNS或者HTTP应用程序可用很容易的找到他所依赖的服务.
  • 健康检查 Consul客户端可用提供任意数量的健康检查,指定一个服务(比如:webserver是否返回了200 OK 状态码)或者使用本地节点(比如:内存使用是否大于90%). 这个信息可由operator用来监视集群的健康.被服务发现组件用来避免将流量发送到不健康的主机.
  • Key/Value存储 应用程序可用根据自己的需要使用Consul的层级的Key/Value存储.比如动态配置,功能标记,协调,领袖选举等等,简单的HTTP API让他更易于使用.
  • 多数据中心: Consul支持开箱即用的多数据中心.这意味着用户不需要担心需要建立额外的抽象层让业务扩展到多个区域.

Consul面向DevOps和应用开发者友好.是他适合现代的弹性的基础设施.

consul-cluster

基础架构

Consul是一个分布式高可用的系统. 这节将包含一些基础,我们忽略掉一些细节这样你可以快速了解Consul是如何工作的.如果要了解更多细节,请参考深入的架构描述.

每个提供服务给Consul的阶段都运行了一个Consul agent . 发现服务或者设置和获取 key/value存储的数据不是必须运行agent.这个agent是负责对节点自身和节点上的服务进行健康检查的.

Agent与一个和多个Consul Server 进行交互.Consul Server 用于存放和复制数据.server自行选举一个领袖.虽然Consul可以运行在一台server , 但是建议使用3到5台来避免失败情况下数据的丢失.每个数据中心建议配置一个server集群.

你基础设施中需要发现其他服务的组件可以查询任何一个Consul 的server或者 agent.Agent会自动转发请求到server .

每个数据中运行了一个Consul server集群.当一个跨数据中心的服务发现和配置请求创建时.本地Consul Server转发请求到远程的数据中心并返回结果.

更多介绍查看官网点击前往

安装Consul

安装Consul,找到适合你系统的包下载他.Consul打包为一个’Zip’文件.前往下载

下载后解开压缩包.拷贝Consul到你的PATH路径中,在Unix系统中~/bin/usr/local/bin是通常的安装目录.根据你是想为单个用户安装还是给整个系统安装来选择.在Windows系统中有可以安装到%PATH%的路径中.

验证安装

完成安装后,通过打开一个新终端窗口检查consul安装是否成功.通过执行 consul你应该看到类似下面的输出

  1. [root@dhcp-10-201-102-248 ~]# consul
  2. usage: consul [--version] [--help] <command> [<args>]
  3.  
  4. Available commands are:
  5. agent Runs a Consul agent
  6. configtest Validate config file
  7. event Fire a new event
  8. exec Executes a command on Consul nodes
  9. force-leave Forces a member of the cluster to enter the "left" state
  10. info Provides debugging information for operators
  11. join Tell Consul agent to join cluster
  12. keygen Generates a new encryption key
  13. keyring Manages gossip layer encryption keys
  14. kv Interact with the key-value store
  15. leave Gracefully leaves the Consul cluster and shuts down
  16. lock Execute a command holding a lock
  17. maint Controls node or service maintenance mode
  18. members Lists the members of a Consul cluster
  19. monitor Stream logs from a Consul agent
  20. operator Provides cluster-level tools for Consul operators
  21. reload Triggers the agent to reload configuration files
  22. rtt Estimates network round trip time between nodes
  23. snapshot Saves, restores and inspects snapshots of Consul server state
  24. version Prints the Consul version
  25. watch Watch for changes in Consul

如果你得到一个consul not be found的错误,你的PATH可能没有正确设置.请返回检查你的consul的安装路径是否包含在PATH中.

运行Agent

完成Consul的安装后,必须运行agent. agent可以运行为serverclient模式.每个数据中心至少必须拥有一台server . 建议在一个集群中有3或者5个server.部署单一的server,在出现失败时会不可避免的造成数据丢失.

其他的agent运行为client模式.一个client是一个非常轻量级的进程.用于注册服务,运行健康检查和转发对server的查询.agent必须在集群中的每个主机上运行.

查看启动数据中心的细节请查看这里.

启动 Consul Server

  1. consul agent -server -bootstrap-expect 3 -data-dir /tmp/consul -node=s1 -bind=10.201.102.198 -ui-dir ./consul_ui/ -rejoin -config-dir=/etc/consul.d/ -client 0.0.0.0

运行cosnul agent以server模式,

  • -server : 定义agent运行在server模式
  • -bootstrap-expect :在一个datacenter中期望提供的server节点数目,当该值提供的时候,consul一直等到达到指定sever数目的时候才会引导整个集群,该标记不能和bootstrap共用
  • -bind:该地址用来在集群内部的通讯,集群内的所有节点到地址都必须是可达的,默认是0.0.0.0
  • -node:节点在集群中的名称,在一个集群中必须是唯一的,默认是该节点的主机名
  • -ui-dir: 提供存放web ui资源的路径,该目录必须是可读的
  • -rejoin:使consul忽略先前的离开,在再次启动后仍旧尝试加入集群中。
  • -config-dir::配置文件目录,里面所有以.json结尾的文件都会被加载
  • -client:consul服务侦听地址,这个地址提供HTTP、DNS、RPC等服务,默认是127.0.0.1所以不对外提供服务,如果你要对外提供服务改成0.0.0.0
  1. [root@dhcp-10-201-102-198 consul]# consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=s1 -bind=10.201.102.198 -ui-dir ./consul_ui/ -rejoin -config-dir=/etc/consul.d/ -client 0.0.0.0
  2. ==> WARNING: Expect Mode enabled, expecting 3 servers
  3. ==> Starting Consul agent...
  4. ==> Starting Consul agent RPC...
  5. ==> Consul agent running!
  6. Version: 'v0.7.4'
  7. Node ID: '422ec677-74ef-8f29-2f22-01effeed6334'
  8. Node name: 's1'
  9. Datacenter: 'dc1'
  10. Server: true (bootstrap: false)
  11. Client Addr: 0.0.0.0 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400)
  12. Cluster Addr: 10.201.102.198 (LAN: 8301, WAN: 8302)
  13. Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false
  14. Atlas: <disabled>
  15.  
  16. ==> Log data will now stream in as it occurs:
  17.  
  18. 2017/03/17 18:03:08 [INFO] raft: Restored from snapshot 139-352267-1489707086023
  19. 2017/03/17 18:03:08 [INFO] raft: Initial configuration (index=6982): [{Suffrage:Voter ID:10.201.102.199:8300 Address:10.201.102.199:8300} {Suffrage:Voter ID:10.201.102.200:8300 Address:10.201.102.200:8300} {Suffrage:Voter ID:10.201.102.198:8300 Address:10.201.102.198:8300}]
  20. 2017/03/17 18:03:08 [INFO] raft: Node at 10.201.102.198:8300 [Follower] entering Follower state (Leader: "")
  21. 2017/03/17 18:03:08 [INFO] serf: EventMemberJoin: s1 10.201.102.198
  22. 2017/03/17 18:03:08 [INFO] serf: Attempting re-join to previously known node: s2: 10.201.102.199:8301
  23. 2017/03/17 18:03:08 [INFO] consul: Adding LAN server s1 (Addr: tcp/10.201.102.198:8300) (DC: dc1)
  24. 2017/03/17 18:03:08 [INFO] consul: Raft data found, disabling bootstrap mode
  25. 2017/03/17 18:03:08 [INFO] serf: EventMemberJoin: s2 10.201.102.199
  26. 2017/03/17 18:03:08 [INFO] serf: EventMemberJoin: s3 10.201.102.200
  27. 2017/03/17 18:03:08 [INFO] serf: Re-joined to previously known node: s2: 10.201.102.199:8301
  28. 2017/03/17 18:03:08 [INFO] consul: Adding LAN server s2 (Addr: tcp/10.201.102.199:8300) (DC: dc1)
  29. 2017/03/17 18:03:08 [INFO] consul: Adding LAN server s3 (Addr: tcp/10.201.102.200:8300) (DC: dc1)
  30. 2017/03/17 18:03:08 [INFO] serf: EventMemberJoin: s1.dc1 10.201.102.198
  31. 2017/03/17 18:03:08 [INFO] consul: Adding WAN server s1.dc1 (Addr: tcp/10.201.102.198:8300) (DC: dc1)
  32. 2017/03/17 18:03:08 [WARN] serf: Failed to re-join any previously known node
  33. 2017/03/17 18:03:14 [INFO] agent: Synced service 'consul'
  34. 2017/03/17 18:03:14 [INFO] agent: Deregistered service 'consul01'
  35. 2017/03/17 18:03:14 [INFO] agent: Deregistered service 'consul02'
  36. 2017/03/17 18:03:14 [INFO] agent: Deregistered service 'consul03'
  • 查看集群成员

新开一个终端窗口运行consul members, 你可以看到Consul集群的成员.

  1. [root@dhcp-10-201-102-198 ~]# consul members
  2. Node Address Status Type Build Protocol DC
  3. s1 10.201.102.198:8301 alive server 0.7.4 2 dc1
  4. s2 10.201.102.199:8301 alive server 0.7.4 2 dc1
  5. s3 10.201.102.200:8301 alive server 0.7.4 2 dc1

启动 Consul Client

  1. consul agent -data-dir /tmp/consul -node=c1 -bind=10.201.102.248 -config-dir=/etc/consul.d/ -join 10.201.102.198

运行cosnul agent以client模式,-join 加入到已有的集群中去。

  1. [root@dhcp-10-201-102-248 ~]# consul agent -data-dir /tmp/consul -node=c1 -bind=10.201.102.248 -config-dir=/etc/consul.d/ -join 10.201.102.198
  2. ==> Starting Consul agent...
  3. ==> Starting Consul agent RPC...
  4. ==> Joining cluster...
  5. Join completed. Synced with 1 initial agents
  6. ==> Consul agent running!
  7. Version: 'v0.7.4'
  8. Node ID: '564dc0c7-7f4f-7402-a301-cebe7f024294'
  9. Node name: 'c1'
  10. Datacenter: 'dc1'
  11. Server: false (bootstrap: false)
  12. Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400)
  13. Cluster Addr: 10.201.102.248 (LAN: 8301, WAN: 8302)
  14. Gossip encrypt: false, RPC-TLS: false, TLS-Incoming: false
  15. Atlas: <disabled>
  16.  
  17. ==> Log data will now stream in as it occurs:
  18.  
  19. 2017/03/17 15:35:16 [INFO] serf: EventMemberJoin: c1 10.201.102.248
  20. 2017/03/17 15:35:16 [INFO] agent: (LAN) joining: [10.201.102.198]
  21. 2017/03/17 15:35:16 [INFO] serf: EventMemberJoin: s2 10.201.102.199
  22. 2017/03/17 15:35:16 [INFO] serf: EventMemberJoin: s3 10.201.102.200
  23. 2017/03/17 15:35:16 [INFO] serf: EventMemberJoin: s1 10.201.102.198
  24. 2017/03/17 15:35:16 [INFO] agent: (LAN) joined: 1 Err: <nil>
  25. 2017/03/17 15:35:16 [INFO] consul: adding server s2 (Addr: tcp/10.201.102.199:8300) (DC: dc1)
  26. 2017/03/17 15:35:16 [INFO] consul: adding server s3 (Addr: tcp/10.201.102.200:8300) (DC: dc1)
  27. 2017/03/17 15:35:16 [INFO] consul: adding server s1 (Addr: tcp/10.201.102.198:8300) (DC: dc1)
  28. 2017/03/17 15:35:16 [INFO] agent: Synced node info
  • 查看集群成员

新开一个终端窗口运行consul members, 你可以看到Consul集群的成员.

  1. [root@dhcp-10-201-102-248 ~]# consul members
  2. Node Address Status Type Build Protocol DC
  3. c1 10.201.102.248:8301 alive client 0.7.4 2 dc1
  4. s1 10.201.102.198:8301 alive server 0.7.4 2 dc1
  5. s2 10.201.102.199:8301 alive server 0.7.4 2 dc1
  6. s3 10.201.102.200:8301 alive server 0.7.4 2 dc1
  • 加入集群
  1. [root@dhcp-10-201-102-248 ~]# consul join 10.201.102.198
  2. Node Address Status Type Build Protocol DC
  3. c1 10.201.102.248:8301 alive client 0.7.4 2 dc1
  4. s1 10.201.102.198:8301 alive server 0.7.4 2 dc1
  5. s2 10.201.102.199:8301 alive server 0.7.4 2 dc1
  6. s3 10.201.102.200:8301 alive server 0.7.4 2 dc1

停止Agent

你可以使用Ctrl-C 优雅的关闭Agent. 中断Agent之后你可以看到他离开了集群并关闭.

在退出中,Consul提醒其他集群成员,这个节点离开了.如果你强行杀掉进程.集群的其他成员应该能检测到这个节点失效了.当一个成员离开,他的服务和检测也会从目录中移除.当一个成员失效了,他的健康状况被简单的标记为危险,但是不会从目录中移除.Consul会自动尝试对失效的节点进行重连.允许他从某些网络条件下恢复过来.离开的节点则不会再继续联系.

此外,如果一个agent作为一个服务器,一个优雅的离开是很重要的,可以避免引起潜在的可用性故障影响达成一致性协议.

查看这里了解添加和移除server.

更新服务

服务定义可以通过配置文件并发送SIGHUP给agent来进行更新.这样你可以让你在不关闭服务或者保持服务请求可用的情况下进行更新.

  1. consul reload

另外 HTTP API可以用来动态的添加,移除和修改服务.

注册服务

搭建好conusl集群后,用户或者程序就能到consul中去查询或者注册服务。可以通过提供服务定义文件或者调用HTTP API来注册一个服务.

首先,为Consul配置创建一个目录.Consul会载入配置文件夹里的所有配置文件.在Unix系统中通常类似 /etc/consul.d (.d 后缀意思是这个路径包含了一组配置文件).

  1. mkdir /etc/consul.d

然后,我们将编写服务定义配置文件.假设我们有一个名叫web的服务运行在 80端口.另外,我们将给他设置一个标签.这样我们可以使用他作为额外的查询方式:

  1. echo '{"service": {"name": "web", "tags": ["rails"], "port": 80}}' >/etc/consul.d/web.json

现在重启agent , 设置配置目录:

  1. $ consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=s1 -bind=10.201.102.198 -rejoin -config-dir=/etc/consul.d/ -client 0.0.0.0
  2.  
  3. ...
  4. [INFO] agent: Synced service 'web'
  5. ...
  • -data-dir:提供一个目录用来存放agent的状态,所有的agent允许都需要该目录,该目录必须是稳定的,系统重启后都继续存在

你可能注意到了输出了 “synced” 了 web这个服务.意思是这个agent从配置文件中载入了服务定义,并且成功注册到服务目录.

如果你想注册多个服务,你应该在Consul配置目录创建多个服务定义文件.

HTTP API注册服务,curl命令或者postman 以PUT方式请求consul HTTP API更多细节点击查看

  1. curl -X PUT -d '{"Datacenter": "dc1", "Node": "c2", "Address": "10.155.0.106", "Service": {"Service": "MAC", "tags": ["lianglian", "Mac"], "Port": 22}}' http://127.0.0.1:8500/v1/catalog/register

查询服务

一旦agent启动并且服务同步了.我们可以通过DNS或者HTTP的API来查询服务.

  • DNS API

让我们首先使用DNS API来查询.在DNS API中,服务的DNS名字是 NAME.service.consul. 虽然是可配置的,但默认的所有DNS名字会都在consul命名空间下.这个子域告诉Consul,我们在查询服务,NAME则是服务的名称.

对于我们上面注册的Web服务.它的域名是 web.service.consul :

  1. [root@dhcp-10-201-102-198 ~]# dig @127.0.0.1 -p 8600 web.service.consul
  2.  
  3. ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6 <<>> @127.0.0.1 -p 8600 web.service.consul
  4. ; (1 server found)
  5. ;; global options: +cmd
  6. ;; Got answer:
  7. ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 39468
  8. ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
  9. ;; WARNING: recursion requested but not available
  10.  
  11. ;; QUESTION SECTION:
  12. ;web.service.consul. IN A
  13.  
  14. ;; ANSWER SECTION:
  15. web.service.consul. 0 IN A 10.201.102.198
  16.  
  17. ;; Query time: 0 msec
  18. ;; SERVER: 127.0.0.1#8600(127.0.0.1)
  19. ;; WHEN: Tue Mar 28 16:10:24 2017
  20. ;; MSG SIZE rcvd: 52
  21.  
  22. [root@dhcp-10-201-102-198 ~]#

如你所见,一个A记录返回了一个可用的服务所在的节点的IP地址.`A记录只能设置为IP地址. 有也可用使用 DNS API 来接收包含 地址和端口的 SRV记录:

  1. [root@dhcp-10-201-102-198 ~]# dig @127.0.0.1 -p 8600 web.service.consul SRV
  2.  
  3. ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6 <<>> @127.0.0.1 -p 8600 web.service.consul SRV
  4. ; (1 server found)
  5. ;; global options: +cmd
  6. ;; Got answer:
  7. ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 13331
  8. ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
  9. ;; WARNING: recursion requested but not available
  10.  
  11. ;; QUESTION SECTION:
  12. ;web.service.consul. IN SRV
  13.  
  14. ;; ANSWER SECTION:
  15. web.service.consul. 0 IN SRV 1 1 80 s1.node.dc1.consul.
  16.  
  17. ;; ADDITIONAL SECTION:
  18. s1.node.dc1.consul. 0 IN A 10.201.102.198
  19.  
  20. ;; Query time: 0 msec
  21. ;; SERVER: 127.0.0.1#8600(127.0.0.1)
  22. ;; WHEN: Tue Mar 28 16:10:56 2017
  23. ;; MSG SIZE rcvd: 84
  24.  
  25. [root@dhcp-10-201-102-198 ~]#

SRV记录告诉我们 web 这个服务运行于节点dhcp-10-201-102-198 的80端口. DNS额外返回了节点的A记录.

最后,我们也可以用 DNS API 通过标签来过滤服务.基于标签的服务查询格式为TAG.NAME.service.consul. 在下面的例子中,我们请求Consul返回有 rails标签的 web服务.我们成功获取了我们注册为这个标签的服务:

  1. [root@dhcp-10-201-102-198 ~]# dig @127.0.0.1 -p 8600 rails.web.service.consul SRV
  2.  
  3. ; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.17.rc1.el6 <<>> @127.0.0.1 -p 8600 rails.web.service.consul SRV
  4. ; (1 server found)
  5. ;; global options: +cmd
  6. ;; Got answer:
  7. ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 37307
  8. ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
  9. ;; WARNING: recursion requested but not available
  10.  
  11. ;; QUESTION SECTION:
  12. ;rails.web.service.consul. IN SRV
  13.  
  14. ;; ANSWER SECTION:
  15. rails.web.service.consul. 0 IN SRV 1 1 80 s1.node.dc1.consul.
  16.  
  17. ;; ADDITIONAL SECTION:
  18. s1.node.dc1.consul. 0 IN A 10.201.102.198
  19.  
  20. ;; Query time: 0 msec
  21. ;; SERVER: 127.0.0.1#8600(127.0.0.1)
  22. ;; WHEN: Tue Mar 28 16:11:45 2017
  23. ;; MSG SIZE rcvd: 90
  24.  
  25. [root@dhcp-10-201-102-198 ~]#
  • HTTP API

除了DNS API之外,HTTP API也可以用来进行服务查询:

  1. [root@dhcp-10-201-102-198 ~]# curl -s 127.0.0.1:8500/v1/catalog/service/web | python -m json.tool
  2. [
  3. {
  4. "Address": "10.201.102.198",
  5. "CreateIndex": 492843,
  6. "ID": "422ec677-74ef-8f29-2f22-01effeed6334",
  7. "ModifyIndex": 492843,
  8. "Node": "s1",
  9. "NodeMeta": {},
  10. "ServiceAddress": "",
  11. "ServiceEnableTagOverride": false,
  12. "ServiceID": "web",
  13. "ServiceName": "web",
  14. "ServicePort": 80,
  15. "ServiceTags": [
  16. "rails"
  17. ],
  18. "TaggedAddresses": {
  19. "lan": "10.201.102.198",
  20. "wan": "10.201.102.198"
  21. }
  22. }
  23. ]

目录API给出所有节点提供的服务.稍后我们会像通常的那样带上健康检查进行查询.就像DNS内部处理的那样.这是只查看健康的实例的查询方法:

  1. [root@dhcp-10-201-102-198 ~]# curl -s 127.0.0.1:8500/v1/catalog/service/web?passing | python -m json.tool
  2. [
  3. {
  4. "Address": "10.201.102.198",
  5. "CreateIndex": 492843,
  6. "ID": "422ec677-74ef-8f29-2f22-01effeed6334",
  7. "ModifyIndex": 492843,
  8. "Node": "s1",
  9. "NodeMeta": {},
  10. "ServiceAddress": "",
  11. "ServiceEnableTagOverride": false,
  12. "ServiceID": "web",
  13. "ServiceName": "web",
  14. "ServicePort": 80,
  15. "ServiceTags": [
  16. "rails"
  17. ],
  18. "TaggedAddresses": {
  19. "lan": "10.201.102.198",
  20. "wan": "10.201.102.198"
  21. }
  22. }
  23. ]

WEB管理界面

Consul同时提供了一个漂亮的功能齐全的WEB界面,开箱即用.界面可以用来查看所有的节点,可以查看健康检查和他们的当前状态.可以读取和设置K/V 存储的数据.UI自动支持多数据中心.点击前往下载

UI_Download

下载完后上传至服务器,建议所有server角色都使用WebUI,。

  1. consul agent -server -bootstrap-expect 1 -data-dir /tmp/consul -node=s1 -bind=10.201.102.198 -ui-dir ./consul_ui/ -rejoin -config-dir=/etc/consul.d/ -client 0.0.0.0
  • -ui-dir: 提供存放web ui资源的路径,指向该目录必须是可读的
  • -client:consul服务侦听地址,这个地址提供HTTP、DNS、RPC等服务,默认是127.0.0.1所以不对外提供服务,如果你要对外提供服务改成0.0.0.0

可通过http://10.201.102.198:8500访问WEB管理界面。

UI_Download

健康检查

我们现在看到Consul运行时如此简单.添加节点和服务,查询节点和服务.在这一节.我们将继续添加健康检查到节点和服务.健康检查是服务发现的关键组件.预防使用到不健康的服务.

这一步建立在前一节的Consul集群创建之上.目前你应该有一个包含多个节点的Consul集群.

  • 自定义检查

和服务注册类似,一个检查可以通过检查定义或HTTP API请求来注册.

我们将使用和检查定义来注册检查.和服务类似,因为这是建立检查最常用的方式.

在第二个节点的配置目录建立定义文件:

/etc/consul.d/web.json

  1. {"service": {
  2. "name": "Faceid",
  3. "tags": ["extract", "verify", "compare", "idcard"],
  4. "address": "10.201.102.198",
  5. "port": 9000,
  6. "check": {
  7. "name": "ping",
  8. "script": "curl -s localhost:9000",
  9. "interval": "3s"
  10. }
  11. }
  12. }

or

/etc/consul.d/web.json

  1. {"service": {
  2. "name"