diff --git a/doc/configuration.txt b/doc/configuration.txt index 030a9a660..9e129de09 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -465,6 +465,7 @@ The following keywords are supported in the "global" section : - maxcomprate - maxcompcpuusage - maxpipes + - maxsessrate - maxsslconn - noepoll - nokqueue @@ -733,6 +734,16 @@ maxpipes The splice code dynamically allocates and releases pipes, and can fall back to standard copy, so setting this value too low may only impact performance. +maxsessrate + Sets the maximum per-process number of sessions per second to . + Proxies will stop accepting connections when this limit is reached. It can be + used to limit the global capacity regardless of each frontend capacity. It is + important to note that this can only be used as a service protection measure, + as there will not necessarily be a fair share between frontends when the + limit is reached, so it's a good idea to also limit each frontend to some + value close to its expected share. Also, lowering tune.maxaccept can improve + fairness. + maxsslconn Sets the maximum per-process number of concurrent SSL connections to . By default there is no SSL-specific limit, which means that the @@ -12598,6 +12609,12 @@ set rate-limit http-compression global passed in number of kilobytes per second. The value is available in the "show info" on the line "CompressBpsRateLim" in bytes. +set rate-limit sessions global + Change the process-wide session rate limit, which is set by the global + 'maxsessrate' setting. A value of zero disables the limitation. This limit + applies to all frontends and the change has an immediate effect. The value + is passed in number of sessions per second. + set table key [data. ]* Create or update a stick-table entry in the table. If the key is not present, an entry is inserted. See stick-table in section 4.2 to find all possible diff --git a/include/types/global.h b/include/types/global.h index 7d78d207d..393ddc457 100644 --- a/include/types/global.h +++ b/include/types/global.h @@ -80,9 +80,11 @@ struct global { char *connect_default_ciphers; #endif struct freq_ctr conn_per_sec; + struct freq_ctr sess_per_sec; struct freq_ctr comp_bps_in; /* bytes per second, before http compression */ struct freq_ctr comp_bps_out; /* bytes per second, after http compression */ int cps_lim, cps_max; + int sps_lim, sps_max; int comp_rate_lim; /* HTTP compression rate limit */ int maxpipes; /* max # of pipes */ int maxsock; /* max # of sockets */ diff --git a/src/cfgparse.c b/src/cfgparse.c index e11730e5a..cab9d6ebc 100644 --- a/src/cfgparse.c +++ b/src/cfgparse.c @@ -875,6 +875,19 @@ int cfg_parse_global(const char *file, int linenum, char **args, int kwm) } global.cps_lim = atol(args[1]); } + else if (!strcmp(args[0], "maxsessrate")) { + if (global.sps_lim != 0) { + Alert("parsing [%s:%d] : '%s' already specified. Continuing.\n", file, linenum, args[0]); + err_code |= ERR_ALERT; + goto out; + } + if (*(args[1]) == 0) { + Alert("parsing [%s:%d] : '%s' expects an integer argument.\n", file, linenum, args[0]); + err_code |= ERR_ALERT | ERR_FATAL; + goto out; + } + global.sps_lim = atol(args[1]); + } else if (!strcmp(args[0], "maxcomprate")) { if (*(args[1]) == 0) { Alert("parsing [%s:%d] : '%s' expects an integer argument in kb/s.\n", file, linenum, args[0]); diff --git a/src/dumpstats.c b/src/dumpstats.c index 227abc546..b6a4dc4db 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -1146,6 +1146,7 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) } global.cps_max = 0; + global.sps_max = 0; return 1; } else if (strcmp(args[1], "table") == 0) { @@ -1424,6 +1425,43 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) return 1; } } + else if (strcmp(args[2], "sessions") == 0) { + if (strcmp(args[3], "global") == 0) { + int v; + + if (s->listener->bind_conf->level < ACCESS_LVL_ADMIN) { + appctx->ctx.cli.msg = stats_permission_denied_msg; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + if (!*args[4]) { + appctx->ctx.cli.msg = "Expects an integer value.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + v = atoi(args[4]); + if (v < 0) { + appctx->ctx.cli.msg = "Value out of range.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + global.sps_lim = v; + + /* Dequeues all of the listeners waiting for a resource */ + if (!LIST_ISEMPTY(&global_listener_queue)) + dequeue_all_listeners(&global_listener_queue); + + return 1; + } + else { + appctx->ctx.cli.msg = "'set rate-limit sessions' only supports 'global'.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + } else if (strcmp(args[2], "http-compression") == 0) { if (strcmp(args[3], "global") == 0) { int v; @@ -1444,7 +1482,7 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) } } else { - appctx->ctx.cli.msg = "'set rate-limit' supports 'connections' and 'http-compression'.\n"; + appctx->ctx.cli.msg = "'set rate-limit' supports 'connections', 'sessions', and 'http-compression'.\n"; appctx->st0 = STAT_CLI_PRINT; return 1; } @@ -2182,6 +2220,9 @@ static int stats_dump_info_to_buffer(struct stream_interface *si) "ConnRate: %d\n" "ConnRateLimit: %d\n" "MaxConnRate: %d\n" + "SessRate: %d\n" + "SessRateLimit: %d\n" + "MaxSessRate: %d\n" "CompressBpsIn: %u\n" "CompressBpsOut: %u\n" "CompressBpsRateLim: %u\n" @@ -2209,6 +2250,7 @@ static int stats_dump_info_to_buffer(struct stream_interface *si) #endif global.maxpipes, pipes_used, pipes_free, read_freq_ctr(&global.conn_per_sec), global.cps_lim, global.cps_max, + read_freq_ctr(&global.sess_per_sec), global.sps_lim, global.sps_max, read_freq_ctr(&global.comp_bps_in), read_freq_ctr(&global.comp_bps_out), global.comp_rate_lim, #ifdef USE_ZLIB diff --git a/src/listener.c b/src/listener.c index 836ca70a4..95a11998f 100644 --- a/src/listener.c +++ b/src/listener.c @@ -263,13 +263,31 @@ void listener_accept(int fd) return; } - if (global.cps_lim && !(l->options & LI_O_UNLIMITED)) { - int max = freq_ctr_remain(&global.conn_per_sec, global.cps_lim, 0); + if (!(l->options & LI_O_UNLIMITED) && global.sps_lim) { + int max = freq_ctr_remain(&global.sess_per_sec, global.sps_lim, 0); + int expire; if (unlikely(!max)) { /* frontend accept rate limit was reached */ limit_listener(l, &global_listener_queue); - task_schedule(global_listener_queue_task, tick_add(now_ms, next_event_delay(&global.conn_per_sec, global.cps_lim, 0))); + expire = tick_add(now_ms, next_event_delay(&global.sess_per_sec, global.sps_lim, 0)); + task_schedule(global_listener_queue_task, tick_first(expire, global_listener_queue_task->expire)); + return; + } + + if (max_accept > max) + max_accept = max; + } + + if (!(l->options & LI_O_UNLIMITED) && global.cps_lim) { + int max = freq_ctr_remain(&global.conn_per_sec, global.cps_lim, 0); + int expire; + + if (unlikely(!max)) { + /* frontend accept rate limit was reached */ + limit_listener(l, &global_listener_queue); + expire = tick_add(now_ms, next_event_delay(&global.conn_per_sec, global.cps_lim, 0)); + task_schedule(global_listener_queue_task, tick_first(expire, global_listener_queue_task->expire)); return; } @@ -411,6 +429,13 @@ void listener_accept(int fd) return; } + /* increase the per-process number of cumulated connections */ + if (!(l->options & LI_O_UNLIMITED)) { + update_freq_ctr(&global.sess_per_sec, 1); + if (global.sess_per_sec.curr_ctr > global.sps_max) + global.sps_max = global.sess_per_sec.curr_ctr; + } + } /* end of while (max_accept--) */ /* we've exhausted max_accept, so there is no need to poll again */