nghttpx: Limit # of downstream connections per host when h2 proxy is used

This commit limits the number of concurrent HTTP/1 downstream
connections to same host.  By defualt, it is limited to 8 connections.
--backend-connections-per-frontend option was replaced with
--backend-http1-connections-per-host, which changes the maximum number
of connections per host.  This limitation only kicks in when h2 proxy
is used (-s option).
This commit is contained in:
Tatsuhiro Tsujikawa 2014-12-05 01:07:00 +09:00
parent f178b78816
commit 9614611969
9 changed files with 247 additions and 143 deletions

View File

@ -776,7 +776,7 @@ void fill_default_config() {
mod_config()->no_location_rewrite = false;
mod_config()->argc = 0;
mod_config()->argv = nullptr;
mod_config()->max_downstream_connections = 100;
mod_config()->downstream_connections_per_host = 8;
mod_config()->listener_disable_timeout = {0, 0};
}
} // namespace
@ -899,13 +899,12 @@ Performance:
Set maximum number of simultaneous connections
frontend accepts. Setting 0 means unlimited.
Default: 0
--backend-connections-per-frontend=<NUM>
Set maximum number of backend simultaneous
connections per frontend. This option is
meaningful when the combination of HTTP/2 or SPDY
frontend and HTTP/1 backend is used.
Default: )" << get_config()->max_downstream_connections
<< R"(
--backend-http1-connections-per-host=<NUM>
Set maximum number of backend concurrent HTTP/1
connections per host. This option is meaningful
when -s option is used.
Default: )"
<< get_config()->downstream_connections_per_host << R"(
Timeout:
--frontend-http2-read-timeout=<SEC>
@ -1276,7 +1275,7 @@ int main(int argc, char **argv) {
{"stream-read-timeout", required_argument, &flag, 60},
{"stream-write-timeout", required_argument, &flag, 61},
{"no-location-rewrite", no_argument, &flag, 62},
{"backend-connections-per-frontend", required_argument, &flag, 63},
{"backend-http1-connections-per-host", required_argument, &flag, 63},
{"listener-disable-timeout", required_argument, &flag, 64},
{"strip-incoming-x-forwarded-for", no_argument, &flag, 65},
{"accesslog-format", required_argument, &flag, 66},
@ -1568,8 +1567,8 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_NO_LOCATION_REWRITE, "yes");
break;
case 63:
// --backend-connections-per-frontend
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND,
// --backend-http1-connections-per-host
cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST,
optarg);
break;
case 64:

View File

@ -132,8 +132,8 @@ const char SHRPX_OPT_ADD_RESPONSE_HEADER[] = "add-response-header";
const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[] =
"worker-frontend-connections";
const char SHRPX_OPT_NO_LOCATION_REWRITE[] = "no-location-rewrite";
const char SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND[] =
"backend-connections-per-frontend";
const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[] =
"backend-http1-connections-per-host";
const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[] = "listener-disable-timeout";
namespace {
@ -1006,7 +1006,7 @@ int parse_config(const char *opt, const char *optarg) {
return 0;
}
if (util::strieq(opt, SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND)) {
if (util::strieq(opt, SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST)) {
int n;
if (parse_uint(&n, opt, optarg) != 0) {
@ -1019,7 +1019,7 @@ int parse_config(const char *opt, const char *optarg) {
return -1;
}
mod_config()->max_downstream_connections = n;
mod_config()->downstream_connections_per_host = n;
return 0;
}

View File

@ -123,7 +123,7 @@ extern const char SHRPX_OPT_ALTSVC[];
extern const char SHRPX_OPT_ADD_RESPONSE_HEADER[];
extern const char SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS[];
extern const char SHRPX_OPT_NO_LOCATION_REWRITE[];
extern const char SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND[];
extern const char SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST[];
extern const char SHRPX_OPT_LISTENER_DISABLE_TIMEOUT[];
union sockaddr_union {
@ -222,7 +222,7 @@ struct Config {
size_t http2_downstream_window_bits;
size_t http2_upstream_connection_window_bits;
size_t http2_downstream_connection_window_bits;
size_t max_downstream_connections;
size_t downstream_connections_per_host;
// actual size of downstream_http_proxy_addr
size_t downstream_http_proxy_addrlen;
size_t read_rate;

View File

@ -25,12 +25,18 @@
#include "shrpx_downstream_queue.h"
#include <cassert>
#include <limits>
#include "shrpx_downstream.h"
namespace shrpx {
DownstreamQueue::DownstreamQueue() {}
DownstreamQueue::HostEntry::HostEntry() : num_active(0) {}
DownstreamQueue::DownstreamQueue(size_t conn_max_per_host)
: conn_max_per_host_(conn_max_per_host == 0
? std::numeric_limits<size_t>::max()
: conn_max_per_host) {}
DownstreamQueue::~DownstreamQueue() {}
@ -44,49 +50,146 @@ void DownstreamQueue::add_failure(std::unique_ptr<Downstream> downstream) {
failure_downstreams_[stream_id] = std::move(downstream);
}
DownstreamQueue::HostEntry &
DownstreamQueue::find_host_entry(const std::string &host) {
auto itr = host_entries_.find(host);
if (itr == std::end(host_entries_)) {
std::tie(itr, std::ignore) = host_entries_.emplace(host, HostEntry());
}
return (*itr).second;
}
void DownstreamQueue::add_active(std::unique_ptr<Downstream> downstream) {
auto &ent = find_host_entry(downstream->get_request_http2_authority());
++ent.num_active;
auto stream_id = downstream->get_stream_id();
active_downstreams_[stream_id] = std::move(downstream);
}
std::unique_ptr<Downstream> DownstreamQueue::remove(int32_t stream_id) {
auto kv = pending_downstreams_.find(stream_id);
void DownstreamQueue::add_blocked(std::unique_ptr<Downstream> downstream) {
auto &ent = find_host_entry(downstream->get_request_http2_authority());
auto stream_id = downstream->get_stream_id();
ent.blocked.insert(stream_id);
blocked_downstreams_[stream_id] = std::move(downstream);
}
if (kv != std::end(pending_downstreams_)) {
auto downstream = std::move((*kv).second);
pending_downstreams_.erase(kv);
return downstream;
bool DownstreamQueue::can_activate(const std::string &host) const {
auto itr = host_entries_.find(host);
if (itr == std::end(host_entries_)) {
return true;
}
auto &ent = (*itr).second;
return ent.num_active < conn_max_per_host_;
}
kv = active_downstreams_.find(stream_id);
namespace {
std::unique_ptr<Downstream>
pop_downstream(DownstreamQueue::DownstreamMap::iterator i,
DownstreamQueue::DownstreamMap &downstreams) {
auto downstream = std::move((*i).second);
downstreams.erase(i);
return downstream;
}
} // namespace
namespace {
bool remove_host_entry_if_empty(const DownstreamQueue::HostEntry &ent,
DownstreamQueue::HostEntryMap &host_entries,
const std::string &host) {
if (ent.blocked.empty() && ent.num_active == 0) {
host_entries.erase(host);
return true;
}
return false;
}
} // namespace
std::unique_ptr<Downstream> DownstreamQueue::pop_pending(int32_t stream_id) {
auto itr = pending_downstreams_.find(stream_id);
if (itr == std::end(pending_downstreams_)) {
return nullptr;
}
return pop_downstream(itr, pending_downstreams_);
}
std::unique_ptr<Downstream>
DownstreamQueue::remove_and_pop_blocked(int32_t stream_id) {
auto kv = active_downstreams_.find(stream_id);
if (kv != std::end(active_downstreams_)) {
auto downstream = std::move((*kv).second);
active_downstreams_.erase(kv);
return downstream;
auto downstream = pop_downstream(kv, active_downstreams_);
auto &host = downstream->get_request_http2_authority();
auto &ent = find_host_entry(host);
--ent.num_active;
if (remove_host_entry_if_empty(ent, host_entries_, host)) {
return nullptr;
}
if (ent.blocked.empty() || ent.num_active >= conn_max_per_host_) {
return nullptr;
}
auto next_stream_id = *std::begin(ent.blocked);
ent.blocked.erase(std::begin(ent.blocked));
auto itr = blocked_downstreams_.find(next_stream_id);
assert(itr != std::end(blocked_downstreams_));
auto next_downstream = pop_downstream(itr, blocked_downstreams_);
remove_host_entry_if_empty(ent, host_entries_, host);
return next_downstream;
}
kv = blocked_downstreams_.find(stream_id);
if (kv != std::end(blocked_downstreams_)) {
auto downstream = pop_downstream(kv, blocked_downstreams_);
auto &host = downstream->get_request_http2_authority();
auto &ent = find_host_entry(host);
ent.blocked.erase(stream_id);
remove_host_entry_if_empty(ent, host_entries_, host);
return nullptr;
}
kv = pending_downstreams_.find(stream_id);
if (kv != std::end(pending_downstreams_)) {
pop_downstream(kv, pending_downstreams_);
return nullptr;
}
kv = failure_downstreams_.find(stream_id);
if (kv != std::end(failure_downstreams_)) {
auto downstream = std::move((*kv).second);
failure_downstreams_.erase(kv);
return downstream;
pop_downstream(kv, failure_downstreams_);
return nullptr;
}
return nullptr;
}
Downstream *DownstreamQueue::find(int32_t stream_id) {
auto kv = pending_downstreams_.find(stream_id);
auto kv = active_downstreams_.find(stream_id);
if (kv != std::end(pending_downstreams_)) {
if (kv != std::end(active_downstreams_)) {
return (*kv).second.get();
}
kv = active_downstreams_.find(stream_id);
kv = blocked_downstreams_.find(stream_id);
if (kv != std::end(active_downstreams_)) {
if (kv != std::end(blocked_downstreams_)) {
return (*kv).second.get();
}
kv = pending_downstreams_.find(stream_id);
if (kv != std::end(pending_downstreams_)) {
return (*kv).second.get();
}
@ -99,41 +202,14 @@ Downstream *DownstreamQueue::find(int32_t stream_id) {
return nullptr;
}
std::unique_ptr<Downstream> DownstreamQueue::pop_pending() {
auto i = std::begin(pending_downstreams_);
if (i == std::end(pending_downstreams_)) {
return nullptr;
}
auto downstream = std::move((*i).second);
pending_downstreams_.erase(i);
return downstream;
}
Downstream *DownstreamQueue::pending_top() const {
auto i = std::begin(pending_downstreams_);
if (i == std::end(pending_downstreams_)) {
return nullptr;
}
return (*i).second.get();
}
size_t DownstreamQueue::num_active() const {
return active_downstreams_.size();
}
bool DownstreamQueue::pending_empty() const {
return pending_downstreams_.empty();
}
const std::map<int32_t, std::unique_ptr<Downstream>> &
const DownstreamQueue::DownstreamMap &
DownstreamQueue::get_active_downstreams() const {
return active_downstreams_;
}
const DownstreamQueue::DownstreamMap &
DownstreamQueue::get_blocked_downstreams() const {
return blocked_downstreams_;
}
} // namespace shrpx

View File

@ -30,6 +30,7 @@
#include <stdint.h>
#include <map>
#include <set>
#include <memory>
namespace shrpx {
@ -38,39 +39,67 @@ class Downstream;
class DownstreamQueue {
public:
DownstreamQueue();
typedef std::map<int32_t, std::unique_ptr<Downstream>> DownstreamMap;
struct HostEntry {
// Set of stream ID that blocked by conn_max_per_host_.
std::set<int32_t> blocked;
// The number of connections currently made to this host.
size_t num_active;
HostEntry();
};
typedef std::map<std::string, HostEntry> HostEntryMap;
// conn_max_per_host == 0 means no limit for downstream connection.
DownstreamQueue(size_t conn_max_per_host = 0);
~DownstreamQueue();
void add_pending(std::unique_ptr<Downstream> downstream);
void add_failure(std::unique_ptr<Downstream> downstream);
// Adds |downstream| to active_downstreams_, which means that
// downstream connection has been started.
void add_active(std::unique_ptr<Downstream> downstream);
// Removes |downstream| from either pending_downstreams_,
// active_downstreams_ or failure_downstreams_ and returns it
// wrapped in std::unique_ptr.
std::unique_ptr<Downstream> remove(int32_t stream_id);
// Adds |downstream| to blocked_downstreams_, which means that
// download connection was blocked because conn_max_per_host_ limit.
void add_blocked(std::unique_ptr<Downstream> downstream);
// Returns true if we can make downstream connection to given
// |host|.
bool can_activate(const std::string &host) const;
// Removes pending Downstream object whose stream ID is |stream_id|
// from pending_downstreams_ and returns it.
std::unique_ptr<Downstream> pop_pending(int32_t stream_id);
// Removes Downstream object whose stream ID is |stream_id| from
// either pending_downstreams_, active_downstreams_,
// blocked_downstreams_ or failure_downstreams_. If a Downstream
// object is removed from active_downstreams_, this function may
// return Downstream object with the same target host in
// blocked_downstreams_ if its connection is now not blocked by
// conn_max_per_host_ limit.
std::unique_ptr<Downstream> remove_and_pop_blocked(int32_t stream_id);
// Finds Downstream object denoted by |stream_id| either in
// pending_downstreams_, active_downstreams_ or
// failure_downstreams_.
// pending_downstreams_, active_downstreams_, blocked_downstreams_
// or failure_downstreams_.
Downstream *find(int32_t stream_id);
// Returns the number of active Downstream objects.
size_t num_active() const;
// Returns true if pending_downstreams_ is empty.
bool pending_empty() const;
// Pops first Downstream object in pending_downstreams_ and returns
// it.
std::unique_ptr<Downstream> pop_pending();
// Returns first Downstream object in pending_downstreams_. This
// does not pop the first one. If queue is empty, returns nullptr.
Downstream *pending_top() const;
const std::map<int32_t, std::unique_ptr<Downstream>> &
get_active_downstreams() const;
const DownstreamMap &get_active_downstreams() const;
const DownstreamMap &get_blocked_downstreams() const;
HostEntry &find_host_entry(const std::string &host);
// Maximum number of concurrent connections to the same host.
size_t conn_max_per_host_;
private:
// Per target host structure to keep track of the number of
// connections to the same host.
std::map<std::string, HostEntry> host_entries_;
// Downstream objects, not processed yet
std::map<int32_t, std::unique_ptr<Downstream>> pending_downstreams_;
// Downstream objects in use, consuming downstream concurrency limit
std::map<int32_t, std::unique_ptr<Downstream>> active_downstreams_;
DownstreamMap pending_downstreams_;
// Downstream objects, failed to connect to downstream server
std::map<int32_t, std::unique_ptr<Downstream>> failure_downstreams_;
DownstreamMap failure_downstreams_;
// Downstream objects, downstream connection started
DownstreamMap active_downstreams_;
// Downstream objects, blocked by conn_max_per_host_
DownstreamMap blocked_downstreams_;
};
} // namespace shrpx

View File

@ -352,35 +352,24 @@ int on_request_headers(Http2Upstream *upstream, Downstream *downstream,
downstream->set_request_state(Downstream::MSG_COMPLETE);
}
upstream->maintain_downstream_concurrency();
upstream->start_downstream(downstream);
return 0;
}
} // namespace
void Http2Upstream::maintain_downstream_concurrency() {
while (get_config()->max_downstream_connections >
downstream_queue_.num_active()) {
if (downstream_queue_.pending_empty()) {
break;
}
void Http2Upstream::start_downstream(Downstream *downstream) {
auto next_downstream =
downstream_queue_.pop_pending(downstream->get_stream_id());
assert(next_downstream);
{
auto downstream = downstream_queue_.pending_top();
if (downstream->get_request_state() != Downstream::HEADER_COMPLETE &&
downstream->get_request_state() != Downstream::MSG_COMPLETE) {
break;
}
}
auto downstream = downstream_queue_.pop_pending();
if (!downstream) {
break;
}
initiate_downstream(std::move(downstream));
if (downstream_queue_.can_activate(
downstream->get_request_http2_authority())) {
initiate_downstream(std::move(next_downstream));
return;
}
downstream_queue_.add_blocked(std::move(next_downstream));
}
void
@ -610,7 +599,10 @@ uint32_t infer_upstream_rst_stream_error_code(uint32_t downstream_error_code) {
} // namespace
Http2Upstream::Http2Upstream(ClientHandler *handler)
: handler_(handler), session_(nullptr), settings_timerev_(nullptr) {
: downstream_queue_(get_config()->http2_proxy
? get_config()->downstream_connections_per_host
: 0),
handler_(handler), session_(nullptr), settings_timerev_(nullptr) {
reset_timeouts();
int rv;
@ -1154,9 +1146,12 @@ void Http2Upstream::remove_downstream(Downstream *downstream) {
handler_->write_accesslog(downstream);
}
downstream_queue_.remove(downstream->get_stream_id());
auto next_downstream =
downstream_queue_.remove_and_pop_blocked(downstream->get_stream_id());
maintain_downstream_concurrency();
if (next_downstream) {
initiate_downstream(std::move(next_downstream));
}
}
Downstream *Http2Upstream::find_downstream(int32_t stream_id) {
@ -1400,6 +1395,11 @@ void Http2Upstream::on_handler_delete() {
handler_->write_accesslog(ent.second.get());
}
}
for (auto &ent : downstream_queue_.get_blocked_downstreams()) {
if (ent.second->accesslog_ready()) {
handler_->write_accesslog(ent.second.get());
}
}
}
} // namespace shrpx

View File

@ -88,7 +88,7 @@ public:
int consume(int32_t stream_id, size_t len);
void log_response_headers(Downstream *downstream,
const std::vector<nghttp2_nv> &nva) const;
void maintain_downstream_concurrency();
void start_downstream(Downstream *downstream);
void initiate_downstream(std::unique_ptr<Downstream> downstream);
nghttp2::util::EvbufferBuffer sendbuf;

View File

@ -232,7 +232,7 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
downstream->set_request_state(Downstream::MSG_COMPLETE);
}
upstream->maintain_downstream_concurrency();
upstream->start_downstream(downstream);
break;
}
@ -242,29 +242,18 @@ void on_ctrl_recv_callback(spdylay_session *session, spdylay_frame_type type,
}
} // namespace
void SpdyUpstream::maintain_downstream_concurrency() {
while (get_config()->max_downstream_connections >
downstream_queue_.num_active()) {
if (downstream_queue_.pending_empty()) {
break;
}
void SpdyUpstream::start_downstream(Downstream *downstream) {
auto next_downstream =
downstream_queue_.pop_pending(downstream->get_stream_id());
assert(next_downstream);
{
auto downstream = downstream_queue_.pending_top();
if (downstream->get_request_state() != Downstream::HEADER_COMPLETE &&
downstream->get_request_state() != Downstream::MSG_COMPLETE) {
break;
}
}
auto downstream = downstream_queue_.pop_pending();
if (!downstream) {
break;
}
initiate_downstream(std::move(downstream));
if (downstream_queue_.can_activate(
downstream->get_request_http2_authority())) {
initiate_downstream(std::move(next_downstream));
return;
}
downstream_queue_.add_blocked(std::move(next_downstream));
}
void SpdyUpstream::initiate_downstream(std::unique_ptr<Downstream> downstream) {
@ -425,7 +414,10 @@ uint32_t infer_upstream_rst_stream_status_code(uint32_t downstream_error_code) {
} // namespace
SpdyUpstream::SpdyUpstream(uint16_t version, ClientHandler *handler)
: handler_(handler), session_(nullptr) {
: downstream_queue_(get_config()->http2_proxy
? get_config()->downstream_connections_per_host
: 0),
handler_(handler), session_(nullptr) {
// handler->set_bev_cb(spdy_readcb, 0, spdy_eventcb);
reset_timeouts();
@ -875,9 +867,12 @@ void SpdyUpstream::remove_downstream(Downstream *downstream) {
handler_->write_accesslog(downstream);
}
downstream_queue_.remove(downstream->get_stream_id());
auto next_downstream =
downstream_queue_.remove_and_pop_blocked(downstream->get_stream_id());
maintain_downstream_concurrency();
if (next_downstream) {
initiate_downstream(std::move(next_downstream));
}
}
Downstream *SpdyUpstream::find_downstream(int32_t stream_id) {
@ -1094,6 +1089,11 @@ void SpdyUpstream::on_handler_delete() {
handler_->write_accesslog(ent.second.get());
}
}
for (auto &ent : downstream_queue_.get_blocked_downstreams()) {
if (ent.second->accesslog_ready()) {
handler_->write_accesslog(ent.second.get());
}
}
}
} // namespace shrpx

View File

@ -81,7 +81,7 @@ public:
int consume(int32_t stream_id, size_t len);
void maintain_downstream_concurrency();
void start_downstream(Downstream *downstream);
void initiate_downstream(std::unique_ptr<Downstream> downstream);
nghttp2::util::EvbufferBuffer sendbuf;