2440 发表于 2016-12-28 10:59:15

Nginx模块开发 从url变量中key的值 来确定upstream服务器

  源文件(从Miller的文件修改而来)
  /*
  * Hash a variable to choose an upstream server.
  *
  * Copyright (C) Evan Miller
  *
  * This module can be distributed under the same terms as Nginx itself.
  */
  #include <ngx_config.h>
  #include <ngx_core.h>
  #include <ngx_http.h>
  ngx_int_t ngx_http_hi_upstream_module_init(ngx_conf_t *cf);
  #define ngx_bitvector_index(index) index / (8 * sizeof(uintptr_t))
  #define ngx_bitvector_bit(index) (uintptr_t) 1 << index % (8 * sizeof(uintptr_t))
  typedef struct {
  struct sockaddr                *sockaddr;
  socklen_t                       socklen;
  ngx_str_t                       name;
  } ngx_http_upstream_hash_peer_t;
  typedef struct {
  ngx_uint_t                        number;
  ngx_http_upstream_hash_peer_t     peer;
  } ngx_http_upstream_hash_peers_t;
  typedef struct {
  ngx_http_upstream_hash_peers_t   *peers;
  ngx_uint_t                        hash;
  ngx_str_t                         current_key;
  ngx_str_t                         original_key;
  ngx_uint_t                        try_i;
  uintptr_t                         tried;
  } ngx_http_upstream_hash_peer_data_t;
  static ngx_int_t ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
  ngx_http_upstream_srv_conf_t *us);
  static ngx_int_t ngx_http_upstream_get_hash_peer(ngx_peer_connection_t *pc,
  void *data);
  static void ngx_http_upstream_free_hash_peer(ngx_peer_connection_t *pc,
  void *data, ngx_uint_t state);
  static char *ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd,
  void *conf);
  static ngx_int_t ngx_http_upstream_init_hash(ngx_conf_t *cf,
  ngx_http_upstream_srv_conf_t *us);
  static ngx_uint_t ngx_http_upstream_hash_crc32(u_char *keydata, size_t keylen);
  // 获得URL参数
  static ngx_int_t  ngx_http_key_index;
  static ngx_str_t  ngx_http_key_value = ngx_string("key");
  ngx_http_variable_value_t      *key_vv;
  static ngx_int_t
  ngx_http_hi_upstream_module_variable_not_found(ngx_http_request_t *r,
  ngx_http_variable_value_t *v, uintptr_t data)
  {
  v->not_found = 1;
  return NGX_OK;
  }
  static ngx_int_t
  ngx_http_hi_upstream_module_add_variable(ngx_conf_t *cf, ngx_str_t *name) {
  ngx_http_variable_t         *v;
  v = ngx_http_add_variable(cf, name, NGX_HTTP_VAR_CHANGEABLE);
  if (v == NULL) {
  return NGX_ERROR;
  }
  v->get_handler = ngx_http_hi_upstream_module_variable_not_found;
  return ngx_http_get_variable_index(cf, name);
  }
  // 指令声明,既确定了他们在哪里生效又确定了一旦流程遇到指令将要调用什么函数。
  // load-balancer的指令需要置NGX_HTTP_UPS_CONF标志位,
  // 以便让Nginx知道这个指令只会在upstream块中有效。
  // 同时它需要提供一个指向注册函数的指针。下面列出的是upstream_hash模块的指令声明
  // 第一步
  static ngx_command_t  ngx_http_upstream_hash_commands[] = {
  { ngx_string("hash"),
  NGX_HTTP_UPS_CONF|NGX_CONF_TAKE1,
  ngx_http_upstream_hash,
  0,
  0,
  NULL },
  ngx_null_command
  };
  static ngx_http_module_t  ngx_http_hi_upstream_module_ctx = {
  NULL,                                  /* preconfiguration */
  ngx_http_hi_upstream_module_init,               /* postconfiguration 从配置文件中读取key定义 */
  NULL,                                  /* create main configuration */
  NULL,                                  /* init main configuration */
  NULL,                                  /* create server configuration */
  NULL,                                  /* merge server configuration */
  NULL,                                  /* create location configuration */
  NULL                                   /* merge location configuration */
  };
  //////////////////////
  //     定义模块              //
  //////////////////////
  ngx_module_t  ngx_http_hi_upstream_module = {
  NGX_MODULE_V1,
  &ngx_http_hi_upstream_module_ctx,    /* module context */
  ngx_http_upstream_hash_commands,       /* module directives */
  NGX_HTTP_MODULE,                       /* module type */
  NULL,                                  /* init master */
  NULL,                                  /* init module */
  NULL,                                  /* init process */
  NULL,                                  /* init thread */
  NULL,                                  /* exit thread */
  NULL,                                  /* exit process */
  NULL,                                  /* exit master */
  NGX_MODULE_V1_PADDING
  };
  // upstream 初始化函数的目的是,解析主机名,为socket分配空间,分配(另一个)回调函数。
  // 下面是upstream_hash:
  // 第三步
  static ngx_int_t
  ngx_http_upstream_init_hash(ngx_conf_t *cf, ngx_http_upstream_srv_conf_t *us)
  {
  ngx_uint_t                       i, j, n;
  ngx_http_upstream_server_t      *server;
  ngx_http_upstream_hash_peers_t  *peers;
  us->peer.init = ngx_http_upstream_init_hash_peer; // 回调函数 去第四步
  if (!us->servers) {
  return NGX_ERROR;
  }
  server = us->servers->elts;
  for (n = 0, i = 0; i < us->servers->nelts; i++) {
  n += server.naddrs;
  }
  peers = ngx_pcalloc(cf->pool, sizeof(ngx_http_upstream_hash_peers_t)
  + sizeof(ngx_http_upstream_hash_peer_t) * n);
  if (peers == NULL) {
  return NGX_ERROR;
  }
  peers->number = n;
  /* 同一个主机名可能对应多个IP地址 */
  for (n = 0, i = 0; i < us->servers->nelts; i++) {
  for (j = 0; j < server.naddrs; j++, n++) {
  peers->peer.sockaddr = server.addrs.sockaddr;
  peers->peer.socklen = server.addrs.socklen;
  peers->peer.name = server.addrs.name;
  }
  }
  us->peer.data = peers;
  return NGX_OK;
  }
  // 对端初始化函数每个请求调用一次。它会构造一个数据结构,模块会用这个数据结构来选择合适的后端服务器;
  // 这个数据结构保存着和后端交互的重试次数,通过它可以很容易的跟踪链接失败次数或者是计算好的哈希值。
  // 这个结构体习惯性地被命名为ngx_http_upstream_<module name>_peer_data_t。
  // 另外,对端初始化函数还会构建两个回调函数:
  // * get: load-balancing 函数
  // * free: 对端释放函数 (通常只是在连接完成后更新一些统计信息)
  // 似乎还不止这些,它同时还初始化了一个叫做tries的变量。只要tries是正数,Nginx将继续重试当前的load-banlancer。
  // 当tries变为0时,Nginx将放弃重试。一切都取决于get 和 free 如何设置合适的tries。
  // 下面是upstream_hash中对端初始化函数的例子:
  static ngx_int_t
  ngx_http_upstream_init_hash_peer(ngx_http_request_t *r,
  ngx_http_upstream_srv_conf_t *us)
  {
  ////////////////////////////////////////////
  //        读取URL里面具体的key变量的值
  ////////////////////////////////////////////
  key_vv = ngx_http_get_indexed_variable(r,
  ngx_http_key_index);
  ngx_http_upstream_hash_peer_data_t     *uhpd;
  ngx_str_t val;
  // 获得hash变量对应的本次请求的值
  if (ngx_http_script_run(r, &val, us->lengths, 0, us->values) == NULL) {
  return NGX_ERROR;
  }
  uhpd = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_hash_peer_data_t)
  + sizeof(uintptr_t) *
  ((ngx_http_upstream_hash_peers_t *)us->peer.data)->number /
  (8 * sizeof(uintptr_t)));
  if (uhpd == NULL) {
  return NGX_ERROR;
  }
  r->upstream->peer.data = uhpd;
  uhpd->peers = us->peer.data;
  r->upstream->peer.get = ngx_http_upstream_get_hash_peer; // 回调函数 去第五步
  r->upstream->peer.free = ngx_http_upstream_free_hash_peer; // 回调函数 去第六步
  r->upstream->peer.tries = us->retries + 1;
  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
  "upstream_hash: hashing \"%V\"", &val);
  uhpd->hash = ngx_http_upstream_hash_crc32(val.data, val.len); // 准备hash的数值
  return NGX_OK;
  }
  static ngx_int_t
  ngx_http_upstream_get_hash_peer(ngx_peer_connection_t *pc, void *data)
  {
  ngx_http_upstream_hash_peer_data_t  *uhpd = data;
  ngx_http_upstream_hash_peer_t       *peer;
  ngx_uint_t                           peer_index;
  ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  "upstream_hash: get upstream request hash peer try %ui", pc->tries);
  pc->cached = 0;
  pc->connection = NULL;
  //    peer_index = uhpd->hash % uhpd->peers->number;
  peer_index = atoi((char *) key_vv->data) % uhpd->peers->number;
  peer = &uhpd->peers->peer;
  ngx_log_debug2(NGX_LOG_DEBUG_HTTP, pc->log, 0,
  "upstream_hash: chose peer %ui w/ hash %ui", peer_index, uhpd->hash);
  pc->sockaddr = peer->sockaddr;
  pc->socklen = peer->socklen;
  pc->name = &peer->name;
  return NGX_OK;
  }
  /* 不进行重试 */
  static void
  ngx_http_upstream_free_hash_peer(ngx_peer_connection_t *pc, void *data,
  ngx_uint_t state)
  {
  pc->tries = 0;
  }
  /* bit-shift, bit-mask, and non-zero requirement are for libmemcache compatibility */
  static ngx_uint_t
  ngx_http_upstream_hash_crc32(u_char *keydata, size_t keylen)
  {
  ngx_uint_t crc32 = (ngx_crc32_short(keydata, keylen) >> 16) & 0x7fff;
  return crc32 ? crc32 : 1;
  }
  // 回调函数ngx_http_upstream_hash就是所谓的注册函数。
  // 之所以这样叫(我起得名字)是因为它注册了把upstream初始化函数和周边的upstream配置注册到了一块。
  // 另外,注册函数还定义了特定upstream块中的server指令的一些选项(如weight=, fail_timeout=),
  // 下面是upstream_hash模块的注册函数:
  // 第二步
  static char *
  ngx_http_upstream_hash(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
  {
  ngx_http_upstream_srv_conf_t  *uscf;
  ngx_http_script_compile_t      sc;
  ngx_str_t                     *value;
  ngx_array_t                   *vars_lengths, *vars_values;
  value = cf->args->elts; // 获得nginx.conf中的hash配置的值
  ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
  vars_lengths = NULL;
  vars_values = NULL;
  sc.cf = cf;
  sc.source = &value;
  sc.lengths = &vars_lengths;
  sc.values = &vars_values;
  sc.complete_lengths = 1;
  sc.complete_values = 1;
  if (ngx_http_script_compile(&sc) != NGX_OK) {
  return NGX_CONF_ERROR;
  }
  uscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_upstream_module);
  uscf->peer.init_upstream = ngx_http_upstream_init_hash; // 回调函数 去第三步
  uscf->flags = NGX_HTTP_UPSTREAM_CREATE;
  uscf->values = vars_values->elts;
  uscf->lengths = vars_lengths->elts;
  if ((ngx_http_key_index = ngx_http_hi_upstream_module_add_variable(
  cf, &ngx_http_key_value)) == NGX_ERROR)
  {
  return NGX_CONF_ERROR;
  }
  return NGX_CONF_OK;
  }
  //////////////////////
  // 从配置文件中读取key定义
  //////////////////////
  ngx_int_t
  ngx_http_hi_upstream_module_init(ngx_conf_t *cf)
  {
  printf("called:ngx_http_hi_upstream_module_init\n");
  // 读key参数
  if ((ngx_http_key_index = ngx_http_hi_upstream_module_add_variable(
  cf, &ngx_http_key_value)) == NGX_ERROR)
  {
  return NGX_ERROR;
  }
  return NGX_OK;
  }
  config文件
  ngx_addon_name=ngx_http_hi_upstream_module
  HTTP_MODULES="$HTTP_MODULES ngx_http_hi_upstream_module"
  NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_hi_upstream_module.c"


  Nginx配置文件
  worker_processes  1;
  daemon off; 
  master_process  off;
  error_log  /tmp/error.log debug;
  pid /tmp/nginx_demo.pid;
  events {
  worker_connections  1024;
  }
  http {
  #include       /etc/nginx/mime.types;
  sendfile        on;
  keepalive_timeout  65;
  tcp_nodelay        on;
  upstream backend {
  server 127.0.0.1:8889;
  server 127.0.0.1:8890;
  hash   $request_uri;
  }
  server {
  listen   8100;
  server_name  localhost;
  access_log  /tmp/access.log;
  error_log  /tmp/error.log debug;
  location /hello {
  set $key $arg_key;
  #echo "abcd";
      proxy_pass      http://backend;
  }
  }
  }
页: [1]
查看完整版本: Nginx模块开发 从url变量中key的值 来确定upstream服务器