TCP Proxy dynamic routing scheme

Currently tcp long connection used by the application program is nginx (ngx_stream_core_module), static configuration; built-in health check can only be based on port, but the application program errors, or sometimes suspended animation, the port is OK, cause the client a lot of error, so the new dynamically configurable routing scheme may be required, by the monitoring node checker dynamic configuration management backend.

http dynamic routing (also known as dynamic upstream) program more, there are ready-made plug-ins, or lua support for this piece was also good; but relatively few tcp programs under the full study, summarized under the following two fly program .

1. Nginx, lua library under openresty, write your own script lua
realize the function:

  • When the configuration server Nginx startup to initialize redis, hash structure: {ip1: 0, ip2: 0, ip3: 0, ......} (0: OK, 1: not available);
  • Each request is acquired from the server nginx, the manner in rotation, Redis supports connection pooling;
  • Redis access connection fails, or no available access server key (hash is empty, or all value 1), with the local static configuration;
  • Can not access a server (ip port or unreasonable, too busy or inaccessible), the other nodes will try, try number can be equipped;
  • Unrealized and static ports configured as health check mechanism automatically kicked out (for two reasons: 1, it will conflict with the external independent monitoring inspection procedures; 2, from the timing of tasks required in nginx init stage, but does not support socket module init stage access redis, the right solution is not found)

Script Description:

High concurrency scenarios need to focus on the use of variables (as far as possible not to use global variables) and processing logic, especially in rotation, failure retry two functions.

stream {
    lua_package_path "/usr/local/lib/lua/?.lua;;";
    lua_shared_dict  dict_ups 4m;
    lua_shared_dict  dict_try 10m;
    lua_shared_dict  rotate_lock 100k;
    lua_add_variable $dhq_proxypass;
    lua_add_variable $backend_server;
    lua_add_variable $try_cnt;
    lua_add_variable $dhq_conn_cnt;

    log_format  main  '$remote_addr [$time_local] $server_addr:$server_port $dhq_proxypass $dhq_conn_cnt $try_cnt $backend_server $status';
    access_log  logs/access.log  main;

    upstream mytcp_static {        
        server 10.40.20.201:22 max_fails=3 fail_timeout=5s;
        server 10.40.20.202:22 max_fails=3 fail_timeout=5s;
        server 10.40.20.203:22 max_fails=3 fail_timeout=5s;
        server 10.40.20.204:22 max_fails=3 fail_timeout=5s;
    }

    upstream mytcp_lua {
        # just a place holder,not work
        server 1.1.1.1:1111;
    
        balancer_by_lua_block {
            local backend_port = 22
            local try_cnt = ngx.shared.dict_try:get("conn" .. ngx.var.dhq_conn_cnt)
            if try_cnt > 16 then
                return
            end
            local balancer = require "ngx.balancer"
            balancer.set_timeouts(3, 3, 3)
            balancer.set_more_tries(4)
            if g_ups_cur_dhq then
                local state_name, status_code = balancer.get_last_failure()
                if state_name == nil then
                    balancer.set_current_peer(g_ups_cur_dhq, backend_port)
                    ngx.var.backend_server = g_ups_cur_dhq .. ":" .. backend_port
                else
                    local table_len = table.getn(g_ups_dhq_active_table)
                    local ups_cur_dhq = g_ups_dhq_active_table[(try_cnt - 1) % table_len + 1]
                    balancer.set_current_peer(ups_cur_dhq, backend_port)
                    ngx.var.backend_server = ups_cur_dhq .. ":" .. backend_port
                    try_cnt = try_cnt + 1
                    ngx.shared.dict_try:set("conn" .. ngx.var.dhq_conn_cnt, try_cnt)
                end
                ngx.var.try_cnt = try_cnt
            else
                ngx.log(ngx.ERR, "[error]: no server in upstream. ")
                return
            end
        }
    }

    server {
        listen  12345;
        proxy_connect_timeout  3s;
        proxy_timeout  120s;
        proxy_next_upstream_tries 5;

        preread_by_lua_block {
            ups_dhq_table = {}
            ups_dhq_table["10.40.20.201"] = 0
            ups_dhq_table["10.40.20.202"] = 0
            ups_dhq_table["10.40.20.203"] = 0
            ups_dhq_table["10.40.30.204"] = 0

            local ups_name = "mytcp"
            local redis_ups_key = "upstream_denghaoqi"
            ngx.var.dhq_proxypass = ups_name .. "_lua"

            function func_get_redis()
                local Redis = require "resty.redis"
                local redis = Redis:new()
                local pool_options = { pool_size = 300, blck_log = 20000 }
                redis:set_timeout(3000)
                local ok, err = redis:connect("10.40.16.45", 36379, pool_options)
                if not ok then
                    ngx.var.dhq_proxypass = ups_name .. "_static"
                    ngx.log(ngx.ERR, "connect to redis failed, ", err)
                    return
                end
                return redis
            end

            local dhq_conn_cnt, err = ngx.shared.dict_ups:incr("dhq_conn_cnt",1,0,0)
            ngx.var.dhq_conn_cnt = dhq_conn_cnt

            -- sync to redis when nginx start
            if (dhq_conn_cnt == 1) then
                local redis = func_get_redis()
                if redis == nil then 
                    return 
                end
                local ok, err = redis:del(redis_ups_key)
                local ok, err = redis:hmset(redis_ups_key, ups_dhq_table)
                -- local ok, err = redis:close()
                redis:set_keepalive(30000, 300)
            end
 
            -- get a server in rotation
            local redis = func_get_redis()
            if redis == nil then 
                return
            end
            local ok, err = redis:array_to_hash(redis:hgetall(redis_ups_key))
            if not ok then
                ngx.var.dhq_proxypass = ups_name .. "_static"
                ngx.log(ngx.ERR, "get redis key failed. ")
                return
            end
            redis:set_keepalive(30000, 300)
            if type(ok) == "table" then
                if ok[1] == false then
                    ngx.log(ngx.ERR, "error: ", ok[2])
                else
                    local ups_dhq_active_table = {}
                    for key, value in pairs(ok) do
                        if value == "0" then
                            table.insert(ups_dhq_active_table,key)
                        end
                    end
                    if table.getn(ups_dhq_active_table) == 0 then
                        ngx.var.dhq_proxypass = ups_name .. "_static"
                        ngx.log(ngx.ERR, "redis key has no valid server. ")
                        return
                    end
                    table.sort(ups_dhq_active_table)
                    local ind = (dhq_conn_cnt - 1) % table.getn(ups_dhq_active_table) + 1
                    g_ups_cur_dhq = ups_dhq_active_table[ind]
                    g_ups_dhq_active_table = ups_dhq_active_table
                    ngx.shared.dict_try:set("conn" .. dhq_conn_cnt, 1)
                end
            end
        }
        proxy_pass $dhq_proxypass;
    }
}

2. Haproxy, dataplaneapi haproxy ecosystem
Description:
Dataplaneapi achieve a restful api, through a friendly interface to delete server, increased server, dataplaneapi and haproxy deployed on a single server, is one relationship, it is necessary for each node haproxy api of operation;

After haproxy manually edit the configuration file, execute the following command strong brush dataplaneapi cache: kill -SIGUSR2 dataplaneapi process, or restart dataplaneapi

Api use:
when to add or delete server, you need to open the transaction;
after the transaction is committed, haproxy automatically reload, modify configuration files automatically

dataplaneapi service starts

/root/dataplaneapi/dataplaneapi-master/build/dataplaneapi --host 10.40.20.203 --port 5555 -b /usr/local/haproxy/sbin/haproxy -c /usr/local/haproxy/conf/haproxy.cfg -d 5 -r "/usr/local/haproxy/haproxy_mgr.sh restart" -s "/usr/local/haproxy/haproxy_mgr.sh reload" -u api -t /tmp/haproxy

The main steps:
 

查询信息,获取当前version
# curl -X GET -u admin:admin \
> -H "Content-Type: application/json" \
> "http://10.40.20.203:5555/v2/services/haproxy/configuration/servers?backend=test-proxy-srv"
{
    "_version":1,
    "data":[
        {"address":"10.40.20.208","check":"enabled","name":"10.40.20.208","port":222},
        {"address":"10.45.0.10","check":"enabled","name":"10.45.0.10","port":22,"weight":80},
        {"address":"10.45.0.11","check":"enabled","name":"10.45.0.11","port":22,"weight":80}
    ]
}
 
开启事务,获取事务id
参数version根据上述步骤结果递增
# curl -X POST -u admin:admin \
> -H "Content-Type: application/json" \
> http://10.40.20.203:5555/v2/services/haproxy/transactions?version=2
{"_version":1,"id":"c69fa5fe-8dc7-4c85-8912-0ac86b3ad59d","status":"in_progress"}
 
删除server
curl -X DELETE -u admin:admin \
-H "Content-Type: application/json" \
"http://10.40.20.203:5555/v2/services/haproxy/configuration/servers/10.40.20.207?backend=test-proxy-srv&transaction_id=c69fa5fe-8dc7-4c85-8912-0ac86b3ad59d"

增加server
curl -X POST -u admin:admin \
-H "Content-Type: application/json" \
--data '{"address": "10.45.0.11", "check": "enabled", "max-connections": 500, "name": "10.45.0.11", "port": 22, "weight": 80}' \
"http://10.40.20.203:5555/v2/services/haproxy/configuration/servers?backend=test-proxy-srv&transaction_id=c69fa5fe-8dc7-4c85-8912-0ac86b3ad59d "

 
提交事务
curl -X PUT -u admin:admin \
-H "Content-Type: application/json" \
http://10.40.20.203:5555/v2/services/haproxy/transactions/c69fa5fe-8dc7-4c85-8912-0ac86b3ad59d

3. Compare program

 

Nginx + lua

Haproxy + dataplaneapi

stability

Write your own lua, through a wide range of functional testing and performance testing, stability needs to be verified online

Own characteristics, stability is more assured

Monitoring program requirements

Redis can adjust a simple program for

Need to know api, the need for separately processing nodes N

Operation and maintenance requirements

Lua introducing complex programming (in particular failure or performance problems in high concurrency), subsequent to further optimize

The newly introduced haproxy, operation and maintenance to further study

 

 

Published 24 original articles · won praise 25 · views 20000 +

Guess you like

Origin blog.csdn.net/sdmei/article/details/103773014