changeset 6:bef88ba0b378

Keepalive: distinguish between upstream servers by default. Option 'single' may be used if single pool of connections desired for some reason.
author Maxim Dounin <mdounin@mdounin.ru>
date Fri, 24 Oct 2008 02:03:39 +0400
parents e7d1b49e0611
children c1a2ef20a2a7
files ngx_http_upstream_keepalive_module.c t/memcached-keepalive.t
diffstat 2 files changed, 149 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/ngx_http_upstream_keepalive_module.c
+++ b/ngx_http_upstream_keepalive_module.c
@@ -12,11 +12,16 @@
 typedef struct {
     ngx_queue_t                        queue;
     ngx_connection_t                  *connection;
+
+    socklen_t                          socklen;
+    struct sockaddr_storage            sockaddr;
+
 } ngx_http_upstream_keepalive_cache_t;
 
 
 typedef struct {
     ngx_uint_t                         max_cached;
+    ngx_uint_t                         single;       /* unsigned:1 */
 
     ngx_queue_t                        cache;
     ngx_queue_t                        free;
@@ -56,7 +61,7 @@ static char *ngx_http_upstream_keepalive
 static ngx_command_t  ngx_http_upstream_keepalive_commands[] = {
 
     { ngx_string("keepalive"),
-      NGX_HTTP_UPS_CONF|NGX_CONF_NOARGS|NGX_CONF_TAKE1,
+      NGX_HTTP_UPS_CONF|NGX_CONF_TAKE12,
       ngx_http_upstream_keepalive,
       0,
       0,
@@ -179,15 +184,16 @@ ngx_http_upstream_get_keepalive_peer(ngx
     ngx_http_upstream_keepalive_peer_data_t  *kp = data;
     ngx_http_upstream_keepalive_cache_t      *item;
 
-    ngx_queue_t       *q;
+    ngx_int_t          rc;
+    ngx_queue_t       *q, *cache;
     ngx_connection_t  *c;
 
     ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pc->log, 0,
                    "get keepalive peer");
 
-    /* XXX single pool of cached connections */
+    /* single pool of cached connections */
 
-    if (!ngx_queue_empty(&kp->conf->cache)) {
+    if (kp->conf->single && !ngx_queue_empty(&kp->conf->cache)) {
 
         q = ngx_queue_head(&kp->conf->cache);
         ngx_queue_remove(q);
@@ -208,7 +214,43 @@ ngx_http_upstream_get_keepalive_peer(ngx
         return NGX_DONE;
     }
 
-    return kp->original_get_peer(pc, kp->data);
+    rc = kp->original_get_peer(pc, kp->data);
+
+    if (kp->conf->single || rc != NGX_OK) {
+        return rc;
+    }
+
+    /* search cache for suitable connection */
+
+    cache = &kp->conf->cache;
+
+    for (q = ngx_queue_head(cache);
+         q != ngx_queue_sentinel(cache);
+         q = ngx_queue_next(q))
+    {
+        item = ngx_queue_data(q, ngx_http_upstream_keepalive_cache_t, queue);
+        c = item->connection;
+
+        if (ngx_memn2cmp((u_char *) &item->sockaddr, (u_char *) pc->sockaddr,
+                         item->socklen, pc->socklen)
+            == 0)
+        {
+            ngx_queue_remove(q);
+            ngx_queue_insert_head(&kp->conf->free, q);
+
+            c->idle = 0;
+            c->log = pc->log;
+            c->read->log = pc->log;
+            c->write->log = pc->log;
+
+            pc->connection = c;
+            pc->cached = 1;
+
+            return NGX_DONE;
+        }
+    }
+
+    return NGX_OK;
 }
 
 
@@ -268,6 +310,9 @@ ngx_http_upstream_free_keepalive_peer(ng
         c->log = ngx_cycle->log;
         c->read->log = ngx_cycle->log;
         c->write->log = ngx_cycle->log;
+
+        item->socklen = pc->socklen;
+        ngx_memcpy(&item->sockaddr, pc->sockaddr, pc->socklen);
     }
 
     return kp->original_free_peer(pc, kp->data, state);
@@ -367,16 +412,22 @@ ngx_http_upstream_keepalive(ngx_conf_t *
 
     value = cf->args->elts;
 
-    for (i = 1; i < cf->args->nelts; i++) {
-
-        if (ngx_strncmp(value[i].data, "cached=", 7) == 0) {
-            n = ngx_atoi(&value[i].data[7], value[i].len - 7);
+    n = ngx_atoi(value[1].data, value[1].len);
 
-            if (n == NGX_ERROR || n == 0) {
-                goto invalid;
-            }
+    if (n == NGX_ERROR || n == 0) {
+        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                           "invalid value \"%V\" in \"%V\" directive",
+                           &value[1], &cmd->name);
+        return NGX_CONF_ERROR;
+    }
 
-            kcf->max_cached = n;
+    kcf->max_cached = n;
+
+    for (i = 2; i < cf->args->nelts; i++) {
+
+        if (ngx_strcmp(value[i].data, "single") == 0) {
+
+            kcf->single = 1;
 
             continue;
         }
--- a/t/memcached-keepalive.t
+++ b/t/memcached-keepalive.t
@@ -20,7 +20,7 @@ select STDOUT; $| = 1;
 eval { require Cache::Memcached; };
 plain(skip_all => 'Cache::Memcached not installed') if $@;
 
-my $t = Test::Nginx->new()->has('rewrite')->has_daemon('memcached')->plan(7)
+my $t = Test::Nginx->new()->has('rewrite')->has_daemon('memcached')->plan(10)
 	->write_file_expand('nginx.conf', <<'EOF');
 
 master_process off;
@@ -39,7 +39,25 @@ http {
 
     upstream memd {
         server 127.0.0.1:8081;
-        keepalive;
+        keepalive 1;
+    }
+
+    upstream memd2 {
+        server 127.0.0.1:8081;
+        server 127.0.0.1:8082;
+        keepalive 1 single;
+    }
+
+    upstream memd3 {
+        server 127.0.0.1:8081;
+        server 127.0.0.1:8082;
+        keepalive 1;
+    }
+
+    upstream memd4 {
+        server 127.0.0.1:8081;
+        server 127.0.0.1:8082;
+        keepalive 10;
     }
 
     server {
@@ -56,20 +74,39 @@ http {
             memcached_next_upstream  not_found;
             memcached_pass memd;
         }
+
+        location /memd2 {
+            set $memcached_key "/";
+            memcached_pass memd2;
+        }
+
+        location /memd3 {
+            set $memcached_key "/";
+            memcached_pass memd3;
+        }
+
+        location /memd4 {
+            set $memcached_key "/";
+            memcached_pass memd4;
+        }
     }
 }
 
 EOF
 
 $t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8081');
+$t->run_daemon('memcached', '-l', '127.0.0.1', '-p', '8082');
 $t->run();
 
 ###############################################################################
 
-my $memd = Cache::Memcached->new(servers => [ '127.0.0.1:8081' ]);
-$memd->set('/', 'SEE-THIS');
+my $memd1 = Cache::Memcached->new(servers => [ '127.0.0.1:8081' ]);
+my $memd2 = Cache::Memcached->new(servers => [ '127.0.0.1:8082' ]);
 
-my $total = $memd->stats()->{total}->{total_connections};
+$memd1->set('/', 'SEE-THIS');
+$memd2->set('/', 'SEE-THIS');
+
+my $total = $memd1->stats()->{total}->{total_connections};
 
 like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request');
 like(http_get('/notfound'), qr/404/, 'keepalive memcached not found');
@@ -79,6 +116,48 @@ like(http_get('/'), qr/SEE-THIS/, 'keepa
 like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request again');
 like(http_get('/'), qr/SEE-THIS/, 'keepalive memcached request again');
 
-is($memd->stats()->{total}->{total_connections}, $total + 1, 'keepalive used');
+is($memd1->stats()->{total}->{total_connections}, $total + 1,
+	'only one connection used');
+
+# two backends with 'single' option - should establish only one connection
+
+$total = $memd1->stats()->{total}->{total_connections} +
+	$memd2->stats()->{total}->{total_connections};
+
+http_get('/memd2');
+http_get('/memd2');
+http_get('/memd2');
+
+is($memd1->stats()->{total}->{total_connections} +
+	$memd2->stats()->{total}->{total_connections}, $total + 1,
+	'only one connection with two backends and single');
+
+$total = $memd1->stats()->{total}->{total_connections} +
+	$memd2->stats()->{total}->{total_connections};
+
+# two backends without 'single' option and maximum number of cached
+# connections set to 1 - should establish new connection on each request
+
+http_get('/memd3');
+http_get('/memd3');
+http_get('/memd3');
+
+is($memd1->stats()->{total}->{total_connections} +
+	$memd2->stats()->{total}->{total_connections}, $total + 3,
+	'3 connections should be established');
+
+# two backends without 'single' option and maximum number of cached
+# connections set to 10 - should establish only two connections (1 per backend)
+
+$total = $memd1->stats()->{total}->{total_connections} +
+        $memd2->stats()->{total}->{total_connections};
+
+http_get('/memd4');
+http_get('/memd4');
+http_get('/memd4');
+
+is($memd1->stats()->{total}->{total_connections} +
+	$memd2->stats()->{total}->{total_connections}, $total + 2,
+	'connection per backend');
 
 ###############################################################################