zeroMQ初體驗-26.可靠性-管家模式

 上一節末尾有說到協議,zeromq自然做了充沛的封裝,"管家模式"便由此而來。


是不是有點像簡化版的"偏執模式"?這裏的“broker”需要做到"承上啓下"。因爲這是"協議"的具體實現,自然,這裏以api形式給出各個角色的相應實現。

爲客戶端提供的api:
Java代碼 複製代碼 收藏代碼
  1. local setmetatable = setmetatable   
  2.   
  3. local mdp = require"mdp"  
  4.   
  5. local zmq = require"zmq"  
  6. local zpoller = require"zmq.poller"  
  7. local zmsg = require"zmsg"  
  8. require"zhelpers"  
  9.   
  10. local s_version_assert = s_version_assert   
  11.   
  12. local obj_mt = {}   
  13. obj_mt.__index = obj_mt   
  14.   
  15. function obj_mt:set_timeout(timeout)   
  16.     self.timeout = timeout   
  17. end   
  18.   
  19. function obj_mt:set_retries(retries)   
  20.     self.retries = retries   
  21. end   
  22.   
  23. function obj_mt:destroy()   
  24.     if self.client then self.client:close() end   
  25.     self.context:term()   
  26. end   
  27.   
  28. local function s_mdcli_connect_to_broker(self)   
  29.     -- close old socket.   
  30.     if self.client then   
  31.         self.poller:remove(self.client)   
  32.         self.client:close()   
  33.     end   
  34.     self.client = assert(self.context:socket(zmq.REQ))   
  35.     assert(self.client:setopt(zmq.LINGER, 0))   
  36.     assert(self.client:connect(self.broker))   
  37.     if self.verbose then   
  38.         s_console("I: connecting to broker at %s…", self.broker)   
  39.     end   
  40.     -- add socket to poller   
  41.     self.poller:add(self.client, zmq.POLLIN, function()   
  42.         self.got_reply = true  
  43.     end)   
  44. end   
  45.   
  46. --   
  47. -- Send request to broker and get reply by hook or crook   
  48. -- Returns the reply message or nil if there was no reply.   
  49. --   
  50. function obj_mt:send(service, request)   
  51.     -- Prefix request with protocol frames   
  52.     -- Frame 1: "MDPCxy" (six bytes, MDP/Client x.y)   
  53.     -- Frame 2: Service name (printable string)   
  54.     request:push(service)   
  55.     request:push(mdp.MDPC_CLIENT)   
  56.     if self.verbose then   
  57.         s_console("I: send request to '%s' service:", service)   
  58.         request:dump()   
  59.     end   
  60.   
  61.     local retries = self.retries   
  62.     while (retries > 0) do  
  63.         local msg = request:dup()   
  64.         msg:send(self.client)   
  65.         self.got_reply = false  
  66.   
  67.         whiletruedo  
  68.             local cnt = assert(self.poller:poll(self.timeout * 1000))   
  69.             if cnt ~= 0 and self.got_reply then   
  70.                 local msg = zmsg.recv(self.client)   
  71.                 if self.verbose then   
  72.                     s_console("I: received reply:")   
  73.                     msg:dump()   
  74.                 end   
  75.                 assert(msg:parts() >= 3)   
  76.   
  77.                 local header = msg:pop()   
  78.                 assert(header == mdp.MDPC_CLIENT)   
  79.                 local reply_service = msg:pop()   
  80.                 assert(reply_service == service)   
  81.                 return msg   
  82.             else  
  83.                 retries = retries - 1  
  84.                 if (retries > 0) then   
  85.                     if self.verbose then   
  86.                         s_console("W: no reply, reconnecting…")   
  87.                     end   
  88.                     -- Reconnect   
  89.                     s_mdcli_connect_to_broker(self)   
  90.                     break -- outer loop will resend request.   
  91.                 else  
  92.                     if self.verbose then   
  93.                         s_console("W: permanent error, abandoning request")   
  94.                     end   
  95.                     return nil -- Giving up   
  96.                 end   
  97.             end   
  98.         end   
  99.     end   
  100. end   
  101.   
  102. module(…)   
  103.   
  104. function new(broker, verbose)   
  105.     s_version_assert (2, 1);   
  106.     local self = setmetatable({   
  107.         context = zmq.init(1),   
  108.         poller = zpoller.new(1),   
  109.         broker = broker,   
  110.         verbose = verbose,   
  111.         timeout = 2500, -- msecs   
  112.         retries = 3,    -- before we abandon   
  113.     }, obj_mt)   
  114.   
  115.     s_mdcli_connect_to_broker(self)   
  116.     return self   
  117. end   
  118.   
  119. setmetatable(_M, { __call = function(self, …) returnnew(…) end })  
local setmetatable = setmetatable

local mdp = require"mdp"

local zmq = require"zmq"
local zpoller = require"zmq.poller"
local zmsg = require"zmsg"
require"zhelpers"

local s_version_assert = s_version_assert

local obj_mt = {}
obj_mt.__index = obj_mt

function obj_mt:set_timeout(timeout)
    self.timeout = timeout
end

function obj_mt:set_retries(retries)
    self.retries = retries
end

function obj_mt:destroy()
    if self.client then self.client:close() end
    self.context:term()
end

local function s_mdcli_connect_to_broker(self)
    -- close old socket.
    if self.client then
        self.poller:remove(self.client)
        self.client:close()
    end
    self.client = assert(self.context:socket(zmq.REQ))
    assert(self.client:setopt(zmq.LINGER, 0))
    assert(self.client:connect(self.broker))
    if self.verbose then
        s_console("I: connecting to broker at %s…", self.broker)
    end
    -- add socket to poller
    self.poller:add(self.client, zmq.POLLIN, function()
        self.got_reply = true
    end)
end

--
-- Send request to broker and get reply by hook or crook
-- Returns the reply message or nil if there was no reply.
--
function obj_mt:send(service, request)
    -- Prefix request with protocol frames
    -- Frame 1: "MDPCxy" (six bytes, MDP/Client x.y)
    -- Frame 2: Service name (printable string)
    request:push(service)
    request:push(mdp.MDPC_CLIENT)
    if self.verbose then
        s_console("I: send request to '%s' service:", service)
        request:dump()
    end

    local retries = self.retries
    while (retries > 0) do
        local msg = request:dup()
        msg:send(self.client)
        self.got_reply = false

        while true do
            local cnt = assert(self.poller:poll(self.timeout * 1000))
            if cnt ~= 0 and self.got_reply then
                local msg = zmsg.recv(self.client)
                if self.verbose then
                    s_console("I: received reply:")
                    msg:dump()
                end
                assert(msg:parts() >= 3)

                local header = msg:pop()
                assert(header == mdp.MDPC_CLIENT)
                local reply_service = msg:pop()
                assert(reply_service == service)
                return msg
            else
                retries = retries - 1
                if (retries > 0) then
                    if self.verbose then
                        s_console("W: no reply, reconnecting…")
                    end
                    -- Reconnect
                    s_mdcli_connect_to_broker(self)
                    break -- outer loop will resend request.
                else
                    if self.verbose then
                        s_console("W: permanent error, abandoning request")
                    end
                    return nil -- Giving up
                end
            end
        end
    end
end

module(…)

function new(broker, verbose)
    s_version_assert (2, 1);
    local self = setmetatable({
        context = zmq.init(1),
        poller = zpoller.new(1),
        broker = broker,
        verbose = verbose,
        timeout = 2500, -- msecs
        retries = 3,    -- before we abandon
    }, obj_mt)

    s_mdcli_connect_to_broker(self)
    return self
end

setmetatable(_M, { __call = function(self, …) return new(…) end })

客戶端調用:
Java代碼 複製代碼 收藏代碼
  1. require"mdcliapi"  
  2. require"zmsg"  
  3. require"zhelpers"  
  4.   
  5. local verbose = (arg[1] == "-v")   
  6. local session = mdcliapi.new("tcp://localhost:5555", verbose)   
  7.   
  8. local count=1  
  9. repeat   
  10.     local request = zmsg.new("Hello world")   
  11.     local reply = session:send("echo", request)   
  12.     if not reply then   
  13.         break    --  Interrupt or failure   
  14.     end   
  15.     count = count + 1  
  16. until (count == 100000)   
  17. printf("%d requests/replies processed\n", count)   
  18. session:destroy()  
require"mdcliapi"
require"zmsg"
require"zhelpers"

local verbose = (arg[1] == "-v")
local session = mdcliapi.new("tcp://localhost:5555", verbose)

local count=1
repeat
    local request = zmsg.new("Hello world")
    local reply = session:send("echo", request)
    if not reply then
        break    --  Interrupt or failure
    end
    count = count + 1
until (count == 100000)
printf("%d requests/replies processed\n", count)
session:destroy()

服務端api:
Java代碼 複製代碼 收藏代碼
  1. local HEARTBEAT_LIVENESS = 3  -- 3-5 is reasonable   
  2.   
  3. local setmetatable = setmetatable   
  4.   
  5. local mdp = require"mdp"  
  6.   
  7. local zmq = require"zmq"  
  8. local zpoller = require"zmq.poller"  
  9. local zmsg = require"zmsg"  
  10. require"zhelpers"  
  11.   
  12. local s_version_assert = s_version_assert   
  13.   
  14. local obj_mt = {}   
  15. obj_mt.__index = obj_mt   
  16.   
  17. function obj_mt:set_heartbeat(heartbeat)   
  18.     self.heartbeat = heartbeat   
  19. end   
  20.   
  21. function obj_mt:set_reconnect(reconnect)   
  22.     self.reconnect = reconnect   
  23. end   
  24.   
  25. function obj_mt:destroy()   
  26.     if self.worker then self.worker:close() end   
  27.     self.context:term()   
  28. end   
  29.   
  30. -- Send message to broker   
  31. -- If no msg is provided, create one internally   
  32. local function s_mdwrk_send_to_broker(self, command, option, msg)   
  33.     msg = msg or zmsg.new()   
  34.   
  35.     -- Stack protocol envelope to start of message   
  36.     if option then   
  37.         msg:push(option)   
  38.     end   
  39.     msg:push(command)   
  40.     msg:push(mdp.MDPW_WORKER)   
  41.     msg:push("")   
  42.   
  43.     if self.verbose then   
  44.         s_console("I: sending %s to broker", mdp.mdps_commands[command])   
  45.         msg:dump()   
  46.     end   
  47.     msg:send(self.worker)   
  48. end   
  49.   
  50. local function s_mdwrk_connect_to_broker(self)   
  51.     -- close old socket.   
  52.     if self.worker then   
  53.         self.poller:remove(self.worker)   
  54.         self.worker:close()   
  55.     end   
  56.     self.worker = assert(self.context:socket(zmq.XREQ))   
  57.     assert(self.worker:setopt(zmq.LINGER, 0))   
  58.     assert(self.worker:connect(self.broker))   
  59.     if self.verbose then   
  60.         s_console("I: connecting to broker at %s…", self.broker)   
  61.     end   
  62.     -- Register service with broker   
  63.     s_mdwrk_send_to_broker(self, mdp.MDPW_READY, self.service)   
  64.     -- If liveness hits zero, queue is considered disconnected   
  65.     self.liveness = HEARTBEAT_LIVENESS   
  66.     self.heartbeat_at = s_clock() + self.heartbeat   
  67.     -- add socket to poller   
  68.     self.poller:add(self.worker, zmq.POLLIN, function()   
  69.         self.got_msg = true  
  70.     end)   
  71. end   
  72.   
  73. --   
  74. -- Send reply, if any, to broker and wait for next request.   
  75. --   
  76. function obj_mt:recv(reply)   
  77.     -- Format and send the reply if we are provided one   
  78.     if reply then   
  79.         assert(self.reply_to)   
  80.         reply:wrap(self.reply_to, "")   
  81.         self.reply_to = nil   
  82.         s_mdwrk_send_to_broker(self, mdp.MDPW_REPLY, nil, reply)   
  83.     end   
  84.     self.expect_reply = true  
  85.   
  86.     self.got_msg = false  
  87.     whiletruedo  
  88.         local cnt = assert(self.poller:poll(self.heartbeat * 1000))   
  89.         if cnt ~= 0 and self.got_msg then   
  90.             self.got_msg = false  
  91.             local msg = zmsg.recv(self.worker)   
  92.             if self.verbose then   
  93.                 s_console("I: received message from broker:")   
  94.                 msg:dump()   
  95.             end   
  96.             self.liveness = HEARTBEAT_LIVENESS   
  97.             -- Don't try to handle errors, just assert noisily   
  98.             assert(msg:parts() >= 3)   
  99.   
  100.             local empty = msg:pop()   
  101.             assert(empty == "")   
  102.   
  103.             local header = msg:pop()   
  104.             assert(header == mdp.MDPW_WORKER)   
  105.   
  106.             local command = msg:pop()   
  107.             if command == mdp.MDPW_REQUEST then   
  108.                 -- We should pop and save as many addresses as there are   
  109.                 -- up to a null part, but for now, just save one…   
  110.                 self.reply_to = msg:unwrap()   
  111.                 return msg -- We have a request to process   
  112.             elseif command == mdp.MDPW_HEARTBEAT then   
  113.                 -- Do nothing for heartbeats   
  114.             elseif command == mdp.MDPW_DISCONNECT then   
  115.                 -- dis-connect and re-connect to broker.   
  116.                 s_mdwrk_connect_to_broker(self)   
  117.             else  
  118.                 s_console("E: invalid input message (%d)", command:byte(1,1))   
  119.                 msg:dump()   
  120.             end   
  121.         else  
  122.             self.liveness = self.liveness - 1  
  123.             if (self.liveness == 0) then   
  124.                 if self.verbose then   
  125.                     s_console("W: disconnected from broker - retrying…")   
  126.                 end   
  127.                 -- sleep then Reconnect   
  128.                 s_sleep(self.reconnect)   
  129.                 s_mdwrk_connect_to_broker(self)   
  130.             end   
  131.   
  132.             -- Send HEARTBEAT if it's time   
  133.             if (s_clock() > self.heartbeat_at) then   
  134.                 s_mdwrk_send_to_broker(self, mdp.MDPW_HEARTBEAT)   
  135.                 self.heartbeat_at = s_clock() + self.heartbeat   
  136.             end   
  137.         end   
  138.     end   
  139. end   
  140.   
  141. module(…)   
  142.   
  143. function new(broker, service, verbose)   
  144.     s_version_assert(2, 1);   
  145.     local self = setmetatable({   
  146.         context = zmq.init(1),   
  147.         poller = zpoller.new(1),   
  148.         broker = broker,   
  149.         service = service,   
  150.         verbose = verbose,   
  151.         heartbeat = 2500, -- msecs   
  152.         reconnect = 2500, -- msecs   
  153.     }, obj_mt)   
  154.   
  155.     s_mdwrk_connect_to_broker(self)   
  156.     return self   
  157. end   
  158.   
  159. setmetatable(_M, { __call = function(self, …) returnnew(…) end })  
local HEARTBEAT_LIVENESS = 3  -- 3-5 is reasonable

local setmetatable = setmetatable

local mdp = require"mdp"

local zmq = require"zmq"
local zpoller = require"zmq.poller"
local zmsg = require"zmsg"
require"zhelpers"

local s_version_assert = s_version_assert

local obj_mt = {}
obj_mt.__index = obj_mt

function obj_mt:set_heartbeat(heartbeat)
    self.heartbeat = heartbeat
end

function obj_mt:set_reconnect(reconnect)
    self.reconnect = reconnect
end

function obj_mt:destroy()
    if self.worker then self.worker:close() end
    self.context:term()
end

-- Send message to broker
-- If no msg is provided, create one internally
local function s_mdwrk_send_to_broker(self, command, option, msg)
    msg = msg or zmsg.new()

    -- Stack protocol envelope to start of message
    if option then
        msg:push(option)
    end
    msg:push(command)
    msg:push(mdp.MDPW_WORKER)
    msg:push("")

    if self.verbose then
        s_console("I: sending %s to broker", mdp.mdps_commands[command])
        msg:dump()
    end
    msg:send(self.worker)
end

local function s_mdwrk_connect_to_broker(self)
    -- close old socket.
    if self.worker then
        self.poller:remove(self.worker)
        self.worker:close()
    end
    self.worker = assert(self.context:socket(zmq.XREQ))
    assert(self.worker:setopt(zmq.LINGER, 0))
    assert(self.worker:connect(self.broker))
    if self.verbose then
        s_console("I: connecting to broker at %s…", self.broker)
    end
    -- Register service with broker
    s_mdwrk_send_to_broker(self, mdp.MDPW_READY, self.service)
    -- If liveness hits zero, queue is considered disconnected
    self.liveness = HEARTBEAT_LIVENESS
    self.heartbeat_at = s_clock() + self.heartbeat
    -- add socket to poller
    self.poller:add(self.worker, zmq.POLLIN, function()
        self.got_msg = true
    end)
end

--
-- Send reply, if any, to broker and wait for next request.
--
function obj_mt:recv(reply)
    -- Format and send the reply if we are provided one
    if reply then
        assert(self.reply_to)
        reply:wrap(self.reply_to, "")
        self.reply_to = nil
        s_mdwrk_send_to_broker(self, mdp.MDPW_REPLY, nil, reply)
    end
    self.expect_reply = true

    self.got_msg = false
    while true do
        local cnt = assert(self.poller:poll(self.heartbeat * 1000))
        if cnt ~= 0 and self.got_msg then
            self.got_msg = false
            local msg = zmsg.recv(self.worker)
            if self.verbose then
                s_console("I: received message from broker:")
                msg:dump()
            end
            self.liveness = HEARTBEAT_LIVENESS
            -- Don't try to handle errors, just assert noisily
            assert(msg:parts() >= 3)

            local empty = msg:pop()
            assert(empty == "")

            local header = msg:pop()
            assert(header == mdp.MDPW_WORKER)

            local command = msg:pop()
            if command == mdp.MDPW_REQUEST then
                -- We should pop and save as many addresses as there are
                -- up to a null part, but for now, just save one…
                self.reply_to = msg:unwrap()
                return msg -- We have a request to process
            elseif command == mdp.MDPW_HEARTBEAT then
                -- Do nothing for heartbeats
            elseif command == mdp.MDPW_DISCONNECT then
                -- dis-connect and re-connect to broker.
                s_mdwrk_connect_to_broker(self)
            else
                s_console("E: invalid input message (%d)", command:byte(1,1))
                msg:dump()
            end
        else
            self.liveness = self.liveness - 1
            if (self.liveness == 0) then
                if self.verbose then
                    s_console("W: disconnected from broker - retrying…")
                end
                -- sleep then Reconnect
                s_sleep(self.reconnect)
                s_mdwrk_connect_to_broker(self)
            end

            -- Send HEARTBEAT if it's time
            if (s_clock() > self.heartbeat_at) then
                s_mdwrk_send_to_broker(self, mdp.MDPW_HEARTBEAT)
                self.heartbeat_at = s_clock() + self.heartbeat
            end
        end
    end
end

module(…)

function new(broker, service, verbose)
    s_version_assert(2, 1);
    local self = setmetatable({
        context = zmq.init(1),
        poller = zpoller.new(1),
        broker = broker,
        service = service,
        verbose = verbose,
        heartbeat = 2500, -- msecs
        reconnect = 2500, -- msecs
    }, obj_mt)

    s_mdwrk_connect_to_broker(self)
    return self
end

setmetatable(_M, { __call = function(self, …) return new(…) end })

服務端調用:
Java代碼 複製代碼 收藏代碼
  1. require"mdwrkapi"  
  2. require"zmsg"  
  3.   
  4. local verbose = (arg[1] == "-v")   
  5. local session = mdwrkapi.new("tcp://localhost:5555", "echo", verbose)   
  6.   
  7. local reply   
  8. whiletruedo  
  9.     local request = session:recv(reply)   
  10.     if not request then   
  11.         break              --  Worker was interrupted   
  12.     end   
  13.     reply = request        --  Echo is complex… :-)   
  14. end   
  15. session:destroy()  
require"mdwrkapi"
require"zmsg"

local verbose = (arg[1] == "-v")
local session = mdwrkapi.new("tcp://localhost:5555", "echo", verbose)

local reply
while true do
    local request = session:recv(reply)
    if not request then
        break              --  Worker was interrupted
    end
    reply = request        --  Echo is complex… :-)
end
session:destroy()


注意:
這裏的api全部都是單線程的,不會做心跳,並且不會做錯誤報告(這裏可以根據具體需要修正)。確定連接通路就任務分配的是“管家”:
Java代碼 複製代碼 收藏代碼
  1. require"zmq"  
  2. require"zmq.poller"  
  3. require"zmsg"  
  4. require"zhelpers"  
  5. require"mdp"  
  6.   
  7. local tremove = table.remove   
  8.   
  9. --  We'd normally pull these from config data   
  10.   
  11. local HEARTBEAT_LIVENESS   = 3       --  3-5 is reasonable   
  12. local HEARTBEAT_INTERVAL   = 2500    --  msecs   
  13. local HEARTBEAT_EXPIRY     = HEARTBEAT_INTERVAL * HEARTBEAT_LIVENESS   
  14.   
  15. --  ---------------------------------------------------------------------   
  16. --  Constructor for broker object   
  17.   
  18. --  ---------------------------------------------------------------------   
  19. --  Broker object's metatable.   
  20. local broker_mt = {}   
  21. broker_mt.__index = broker_mt   
  22.   
  23. function broker_new(verbose)   
  24.     local context = zmq.init(1)   
  25.     --  Initialize broker state   
  26.     return setmetatable({   
  27.         context = context,   
  28.         socket = context:socket(zmq.XREP),   
  29.         verbose = verbose,   
  30.         services = {},   
  31.         workers = {},   
  32.         waiting = {},   
  33.         heartbeat_at = s_clock() + HEARTBEAT_INTERVAL,   
  34.     }, broker_mt)   
  35. end   
  36.   
  37. --  ---------------------------------------------------------------------   
  38. --  Service object   
  39. local service_mt = {}   
  40. service_mt.__index = service_mt   
  41.   
  42. --  Worker object   
  43. local worker_mt = {}   
  44. worker_mt.__index = worker_mt   
  45.   
  46. -- helper list remove function   
  47. local function zlist_remove(list, item)   
  48.     for n=#list,1,-1do  
  49.         if list[n] == item then   
  50.             tremove(list, n)   
  51.         end   
  52.     end   
  53. end   
  54.   
  55. --  ---------------------------------------------------------------------   
  56. --  Destructor for broker object   
  57.   
  58. function broker_mt:destroy()   
  59.     self.socket:close()   
  60.     self.context:term()   
  61.     for name, service in pairs(self.services) do  
  62.         service:destroy()   
  63.     end   
  64.     for id, worker in pairs(self.workers) do  
  65.         worker:destroy()   
  66.     end   
  67. end   
  68.   
  69. --  ---------------------------------------------------------------------   
  70. --  Bind broker to endpoint, can call this multiple times   
  71. --  We use a single socket for both clients and workers.   
  72.   
  73. function broker_mt:bind(endpoint)   
  74.     self.socket:bind(endpoint)   
  75.     s_console("I: MDP broker/0.1.1 is active at %s", endpoint)   
  76. end   
  77.   
  78. --  ---------------------------------------------------------------------   
  79. --  Delete any idle workers that haven't pinged us in a while. Workers   
  80. --  are oldest to most recent, so we stop at the first alive worker.   
  81.   
  82. function broker_mt:purge_workers()   
  83.     local waiting = self.waiting   
  84.     for n=1,#waiting do  
  85.         local worker = waiting[n]   
  86.         if (not worker:expired()) then   
  87.             return             --  Worker is alive, we're done here   
  88.         end   
  89.         if (self.verbose) then   
  90.             s_console("I: deleting expired worker: %s", worker.identity)   
  91.         end   
  92.   
  93.         self:worker_delete(worker, false)   
  94.     end   
  95. end   
  96.   
  97. --  ---------------------------------------------------------------------   
  98. --  Locate or create new service entry   
  99.   
  100. function broker_mt:service_require(name)   
  101.     assert (name)   
  102.     local service = self.services[name]   
  103.     if not service then   
  104.         service = setmetatable({   
  105.             name = name,   
  106.             requests = {},   
  107.             waiting = {},   
  108.             workers = 0,   
  109.         }, service_mt)   
  110.         self.services[name] = service   
  111.         if (self.verbose) then   
  112.             s_console("I: received message:")   
  113.         end   
  114.     end   
  115.     return service   
  116. end   
  117.   
  118. --  ---------------------------------------------------------------------   
  119. --  Destroy service object, called when service is removed from   
  120. --  broker.services.   
  121.   
  122. function service_mt:destroy()   
  123. end   
  124.   
  125. --  ---------------------------------------------------------------------   
  126. --  Dispatch requests to waiting workers as possible   
  127.   
  128. function broker_mt:service_dispatch(service, msg)   
  129.     assert (service)   
  130.     local requests = service.requests   
  131.     if (msg) then               --  Queue message if any   
  132.         requests[#requests + 1] = msg   
  133.     end   
  134.   
  135.     self:purge_workers()   
  136.     local waiting = service.waiting   
  137.     while (#waiting > 0 and #requests > 0) do  
  138.         local worker = tremove(waiting, 1) -- pop worker from service's waiting queue.   
  139.         zlist_remove(self.waiting, worker) -- also remove worker from broker's waiting queue.   
  140.         local msg = tremove(requests, 1) -- pop request from service's request queue.   
  141.         self:worker_send(worker, mdp.MDPW_REQUEST, nil, msg)   
  142.     end   
  143. end   
  144.   
  145. --  ---------------------------------------------------------------------   
  146. --  Handle internal service according to 8/MMI specification   
  147.   
  148. function broker_mt:service_internal(service_name, msg)   
  149.     if (service_name == "mmi.service") then   
  150.         local name = msg:body()   
  151.         local service = self.services[name]   
  152.         if (service and service.workers) then   
  153.             msg:body_set("200")   
  154.         else  
  155.             msg:body_set("404")   
  156.         end   
  157.     else  
  158.         msg:body_set("501")   
  159.     end   
  160.   
  161.     --  Remove & save client return envelope and insert the   
  162.     --  protocol header and service name, then rewrap envelope.   
  163.     local client = msg:unwrap()   
  164.     msg:wrap(mdp.MDPC_CLIENT, service_name)   
  165.     msg:wrap(client, "")   
  166.   
  167.     msg:send(self.socket)   
  168. end   
  169.   
  170. --  ---------------------------------------------------------------------   
  171. --  Creates worker if necessary   
  172.   
  173. function broker_mt:worker_require(identity)   
  174.     assert (identity)   
  175.   
  176.     --  self.workers is keyed off worker identity   
  177.     local worker = self.workers[identity]   
  178.     if (not worker) then   
  179.         worker = setmetatable({   
  180.             identity = identity,   
  181.             expiry = 0,   
  182.         }, worker_mt)   
  183.         self.workers[identity] = worker   
  184.         if (self.verbose) then   
  185.             s_console("I: registering new worker: %s", identity)   
  186.         end   
  187.     end   
  188.     return worker   
  189. end   
  190.   
  191. --  ---------------------------------------------------------------------   
  192. --  Deletes worker from all data structures, and destroys worker   
  193.   
  194. function broker_mt:worker_delete(worker, disconnect)   
  195.     assert (worker)   
  196.     if (disconnect) then   
  197.         self:worker_send(worker, mdp.MDPW_DISCONNECT)   
  198.     end   
  199.     local service = worker.service   
  200.     if (service) then   
  201.         zlist_remove (service.waiting, worker)   
  202.         service.workers = service.workers - 1  
  203.     end   
  204.     zlist_remove (self.waiting, worker)   
  205.     self.workers[worker.identity] = nil   
  206.     worker:destroy()   
  207. end   
  208.   
  209. --  ---------------------------------------------------------------------   
  210. --  Destroy worker object, called when worker is removed from   
  211. --  broker.workers.   
  212.   
  213. function worker_mt:destroy(argument)   
  214. end   
  215.   
  216. --  ---------------------------------------------------------------------   
  217. --  Process message sent to us by a worker   
  218.   
  219. function broker_mt:worker_process(sender, msg)   
  220.     assert (msg:parts() >= 1)     --  At least, command   
  221.   
  222.     local command = msg:pop()   
  223.     local worker_ready = (self.workers[sender] ~= nil)   
  224.     local worker = self:worker_require(sender)   
  225.   
  226.     if (command == mdp.MDPW_READY) then   
  227.         if (worker_ready) then          --  Not first command in session then   
  228.             self:worker_delete(worker, true)   
  229.         elseif (sender:sub(1,4) == "mmi.") then  --  Reserved service name   
  230.             self:worker_delete(worker, true)   
  231.         else  
  232.             --  Attach worker to service and mark as idle   
  233.             local service_name = msg:pop()   
  234.             local service = self:service_require(service_name)   
  235.             worker.service = service   
  236.             service.workers = service.workers + 1  
  237.             self:worker_waiting(worker)   
  238.         end   
  239.     elseif (command == mdp.MDPW_REPLY) then   
  240.         if (worker_ready) then   
  241.             --  Remove & save client return envelope and insert the   
  242.             --  protocol header and service name, then rewrap envelope.   
  243.             local client = msg:unwrap()   
  244.             msg:wrap(mdp.MDPC_CLIENT, worker.service.name)   
  245.             msg:wrap(client, "")   
  246.   
  247.             msg:send(self.socket)   
  248.             self:worker_waiting(worker)   
  249.         else  
  250.             self:worker_delete(worker, true)   
  251.         end   
  252.     elseif (command == mdp.MDPW_HEARTBEAT) then   
  253.         if (worker_ready) then   
  254.             worker.expiry = s_clock() + HEARTBEAT_EXPIRY   
  255.         else  
  256.             self:worker_delete(worker, true)   
  257.         end   
  258.     elseif (command == mdp.MDPW_DISCONNECT) then   
  259.         self:worker_delete(worker, false)   
  260.     else  
  261.         s_console("E: invalid input message (%d)", command:byte(1,1))   
  262.         msg:dump()   
  263.     end   
  264. end   
  265.   
  266. --  ---------------------------------------------------------------------   
  267. --  Send message to worker   
  268. --  If pointer to message is provided, sends & destroys that message   
  269.   
  270. function broker_mt:worker_send(worker, command, option, msg)   
  271.     msg = msg and msg:dup() or zmsg.new()   
  272.   
  273.     --  Stack protocol envelope to start of message   
  274.     if (option) then                 --  Optional frame after command   
  275.         msg:push(option)   
  276.     end   
  277.     msg:push(command)   
  278.     msg:push(mdp.MDPW_WORKER)   
  279.     --  Stack routing envelope to start of message   
  280.     msg:wrap(worker.identity, "")   
  281.   
  282.     if (self.verbose) then   
  283.         s_console("I: sending %s to worker", mdp.mdps_commands[command])   
  284.         msg:dump()   
  285.     end   
  286.     msg:send(self.socket)   
  287. end   
  288.   
  289. --  ---------------------------------------------------------------------   
  290. --  This worker is now waiting for work   
  291.   
  292. function broker_mt:worker_waiting(worker)   
  293.     --  Queue to broker and service waiting lists   
  294.     self.waiting[#self.waiting + 1] = worker   
  295.     worker.service.waiting[#worker.service.waiting + 1] = worker   
  296.     worker.expiry = s_clock() + HEARTBEAT_EXPIRY   
  297.     self:service_dispatch(worker.service, nil)   
  298. end   
  299.   
  300. --  ---------------------------------------------------------------------   
  301. --  Return 1if worker has expired and must be deleted   
  302.   
  303. function worker_mt:expired()   
  304.     return (self.expiry < s_clock())   
  305. end   
  306. --  ---------------------------------------------------------------------   
  307. --  Process a request coming from a client   
  308.   
  309. function broker_mt:client_process(sender, msg)   
  310.     assert (msg:parts() >= 2)     --  Service name + body   
  311.   
  312.     local service_name = msg:pop()   
  313.     local service = self:service_require(service_name)   
  314.     --  Set reply return address to client sender   
  315.     msg:wrap(sender, "")   
  316.     if (service_name:sub(1,4) == "mmi.") then   
  317.         self:service_internal(service_name, msg)   
  318.     else  
  319.         self:service_dispatch(service, msg)   
  320.     end   
  321. end   
  322.   
  323. --  ---------------------------------------------------------------------   
  324. --  Main broker work happens here   
  325.   
  326. local verbose = (arg[1] == "-v")   
  327.   
  328. s_version_assert (2, 1)   
  329. s_catch_signals ()   
  330. local self = broker_new(verbose)   
  331. self:bind("tcp://*:5555")   
  332.   
  333. local poller = zmq.poller.new(1)   
  334.   
  335. --  Process next input message, if any   
  336. poller:add(self.socket, zmq.POLLIN, function()   
  337.     local msg = zmsg.recv(self.socket)   
  338.     if (self.verbose) then   
  339.         s_console("I: received message:")   
  340.         msg:dump()   
  341.     end   
  342.     local sender = msg:pop()   
  343.     local empty  = msg:pop()   
  344.     local header = msg:pop()   
  345.   
  346.     if (header == mdp.MDPC_CLIENT) then   
  347.         self:client_process(sender, msg)   
  348.     elseif (header == mdp.MDPW_WORKER) then   
  349.         self:worker_process(sender, msg)   
  350.     else  
  351.         s_console("E: invalid message:")   
  352.         msg:dump()   
  353.     end   
  354. end)   
  355.   
  356. --  Get and process messages forever or until interrupted   
  357. while (not s_interrupted) do  
  358.     local cnt = assert(poller:poll(HEARTBEAT_INTERVAL * 1000))   
  359.     --  Disconnect and delete any expired workers   
  360.     --  Send heartbeats to idle workers if needed   
  361.     if (s_clock() > self.heartbeat_at) then   
  362.         self:purge_workers()   
  363.         local waiting = self.waiting   
  364.         for n=1,#waiting do  
  365.             local worker = waiting[n]   
  366.             self:worker_send(worker, mdp.MDPW_HEARTBEAT)   
  367.         end   
  368.         self.heartbeat_at = s_clock() + HEARTBEAT_INTERVAL   
  369.     end   
  370. end   
  371. if (s_interrupted) then   
  372.     printf("W: interrupt received, shutting down…\n")   
  373. end   
  374. self:destroy()  
require"zmq"
require"zmq.poller"
require"zmsg"
require"zhelpers"
require"mdp"

local tremove = table.remove

--  We'd normally pull these from config data

local HEARTBEAT_LIVENESS   = 3       --  3-5 is reasonable
local HEARTBEAT_INTERVAL   = 2500    --  msecs
local HEARTBEAT_EXPIRY     = HEARTBEAT_INTERVAL * HEARTBEAT_LIVENESS

--  ---------------------------------------------------------------------
--  Constructor for broker object

--  ---------------------------------------------------------------------
--  Broker object's metatable.
local broker_mt = {}
broker_mt.__index = broker_mt

function broker_new(verbose)
    local context = zmq.init(1)
    --  Initialize broker state
    return setmetatable({
        context = context,
        socket = context:socket(zmq.XREP),
        verbose = verbose,
        services = {},
        workers = {},
        waiting = {},
        heartbeat_at = s_clock() + HEARTBEAT_INTERVAL,
    }, broker_mt)
end

--  ---------------------------------------------------------------------
--  Service object
local service_mt = {}
service_mt.__index = service_mt

--  Worker object
local worker_mt = {}
worker_mt.__index = worker_mt

-- helper list remove function
local function zlist_remove(list, item)
    for n=#list,1,-1 do
        if list[n] == item then
            tremove(list, n)
        end
    end
end

--  ---------------------------------------------------------------------
--  Destructor for broker object

function broker_mt:destroy()
    self.socket:close()
    self.context:term()
    for name, service in pairs(self.services) do
        service:destroy()
    end
    for id, worker in pairs(self.workers) do
        worker:destroy()
    end
end

--  ---------------------------------------------------------------------
--  Bind broker to endpoint, can call this multiple times
--  We use a single socket for both clients and workers.

function broker_mt:bind(endpoint)
    self.socket:bind(endpoint)
    s_console("I: MDP broker/0.1.1 is active at %s", endpoint)
end

--  ---------------------------------------------------------------------
--  Delete any idle workers that haven't pinged us in a while. Workers
--  are oldest to most recent, so we stop at the first alive worker.

function broker_mt:purge_workers()
    local waiting = self.waiting
    for n=1,#waiting do
        local worker = waiting[n]
        if (not worker:expired()) then
            return             --  Worker is alive, we're done here
        end
        if (self.verbose) then
            s_console("I: deleting expired worker: %s", worker.identity)
        end

        self:worker_delete(worker, false)
    end
end

--  ---------------------------------------------------------------------
--  Locate or create new service entry

function broker_mt:service_require(name)
    assert (name)
    local service = self.services[name]
    if not service then
        service = setmetatable({
            name = name,
            requests = {},
            waiting = {},
            workers = 0,
        }, service_mt)
        self.services[name] = service
        if (self.verbose) then
            s_console("I: received message:")
        end
    end
    return service
end

--  ---------------------------------------------------------------------
--  Destroy service object, called when service is removed from
--  broker.services.

function service_mt:destroy()
end

--  ---------------------------------------------------------------------
--  Dispatch requests to waiting workers as possible

function broker_mt:service_dispatch(service, msg)
    assert (service)
    local requests = service.requests
    if (msg) then               --  Queue message if any
        requests[#requests + 1] = msg
    end

    self:purge_workers()
    local waiting = service.waiting
    while (#waiting > 0 and #requests > 0) do
        local worker = tremove(waiting, 1) -- pop worker from service's waiting queue.
        zlist_remove(self.waiting, worker) -- also remove worker from broker's waiting queue.
        local msg = tremove(requests, 1) -- pop request from service's request queue.
        self:worker_send(worker, mdp.MDPW_REQUEST, nil, msg)
    end
end

--  ---------------------------------------------------------------------
--  Handle internal service according to 8/MMI specification

function broker_mt:service_internal(service_name, msg)
    if (service_name == "mmi.service") then
        local name = msg:body()
        local service = self.services[name]
        if (service and service.workers) then
            msg:body_set("200")
        else
            msg:body_set("404")
        end
    else
        msg:body_set("501")
    end

    --  Remove & save client return envelope and insert the
    --  protocol header and service name, then rewrap envelope.
    local client = msg:unwrap()
    msg:wrap(mdp.MDPC_CLIENT, service_name)
    msg:wrap(client, "")

    msg:send(self.socket)
end

--  ---------------------------------------------------------------------
--  Creates worker if necessary

function broker_mt:worker_require(identity)
    assert (identity)

    --  self.workers is keyed off worker identity
    local worker = self.workers[identity]
    if (not worker) then
        worker = setmetatable({
            identity = identity,
            expiry = 0,
        }, worker_mt)
        self.workers[identity] = worker
        if (self.verbose) then
            s_console("I: registering new worker: %s", identity)
        end
    end
    return worker
end

--  ---------------------------------------------------------------------
--  Deletes worker from all data structures, and destroys worker

function broker_mt:worker_delete(worker, disconnect)
    assert (worker)
    if (disconnect) then
        self:worker_send(worker, mdp.MDPW_DISCONNECT)
    end
    local service = worker.service
    if (service) then
        zlist_remove (service.waiting, worker)
        service.workers = service.workers - 1
    end
    zlist_remove (self.waiting, worker)
    self.workers[worker.identity] = nil
    worker:destroy()
end

--  ---------------------------------------------------------------------
--  Destroy worker object, called when worker is removed from
--  broker.workers.

function worker_mt:destroy(argument)
end

--  ---------------------------------------------------------------------
--  Process message sent to us by a worker

function broker_mt:worker_process(sender, msg)
    assert (msg:parts() >= 1)     --  At least, command

    local command = msg:pop()
    local worker_ready = (self.workers[sender] ~= nil)
    local worker = self:worker_require(sender)

    if (command == mdp.MDPW_READY) then
        if (worker_ready) then          --  Not first command in session then
            self:worker_delete(worker, true)
        elseif (sender:sub(1,4) == "mmi.") then  --  Reserved service name
            self:worker_delete(worker, true)
        else
            --  Attach worker to service and mark as idle
            local service_name = msg:pop()
            local service = self:service_require(service_name)
            worker.service = service
            service.workers = service.workers + 1
            self:worker_waiting(worker)
        end
    elseif (command == mdp.MDPW_REPLY) then
        if (worker_ready) then
            --  Remove & save client return envelope and insert the
            --  protocol header and service name, then rewrap envelope.
            local client = msg:unwrap()
            msg:wrap(mdp.MDPC_CLIENT, worker.service.name)
            msg:wrap(client, "")

            msg:send(self.socket)
            self:worker_waiting(worker)
        else
            self:worker_delete(worker, true)
        end
    elseif (command == mdp.MDPW_HEARTBEAT) then
        if (worker_ready) then
            worker.expiry = s_clock() + HEARTBEAT_EXPIRY
        else
            self:worker_delete(worker, true)
        end
    elseif (command == mdp.MDPW_DISCONNECT) then
        self:worker_delete(worker, false)
    else
        s_console("E: invalid input message (%d)", command:byte(1,1))
        msg:dump()
    end
end

--  ---------------------------------------------------------------------
--  Send message to worker
--  If pointer to message is provided, sends & destroys that message

function broker_mt:worker_send(worker, command, option, msg)
    msg = msg and msg:dup() or zmsg.new()

    --  Stack protocol envelope to start of message
    if (option) then                 --  Optional frame after command
        msg:push(option)
    end
    msg:push(command)
    msg:push(mdp.MDPW_WORKER)
    --  Stack routing envelope to start of message
    msg:wrap(worker.identity, "")

    if (self.verbose) then
        s_console("I: sending %s to worker", mdp.mdps_commands[command])
        msg:dump()
    end
    msg:send(self.socket)
end

--  ---------------------------------------------------------------------
--  This worker is now waiting for work

function broker_mt:worker_waiting(worker)
    --  Queue to broker and service waiting lists
    self.waiting[#self.waiting + 1] = worker
    worker.service.waiting[#worker.service.waiting + 1] = worker
    worker.expiry = s_clock() + HEARTBEAT_EXPIRY
    self:service_dispatch(worker.service, nil)
end

--  ---------------------------------------------------------------------
--  Return 1 if worker has expired and must be deleted

function worker_mt:expired()
    return (self.expiry < s_clock())
end
--  ---------------------------------------------------------------------
--  Process a request coming from a client

function broker_mt:client_process(sender, msg)
    assert (msg:parts() >= 2)     --  Service name + body

    local service_name = msg:pop()
    local service = self:service_require(service_name)
    --  Set reply return address to client sender
    msg:wrap(sender, "")
    if (service_name:sub(1,4) == "mmi.") then
        self:service_internal(service_name, msg)
    else
        self:service_dispatch(service, msg)
    end
end

--  ---------------------------------------------------------------------
--  Main broker work happens here

local verbose = (arg[1] == "-v")

s_version_assert (2, 1)
s_catch_signals ()
local self = broker_new(verbose)
self:bind("tcp://*:5555")

local poller = zmq.poller.new(1)

--  Process next input message, if any
poller:add(self.socket, zmq.POLLIN, function()
    local msg = zmsg.recv(self.socket)
    if (self.verbose) then
        s_console("I: received message:")
        msg:dump()
    end
    local sender = msg:pop()
    local empty  = msg:pop()
    local header = msg:pop()

    if (header == mdp.MDPC_CLIENT) then
        self:client_process(sender, msg)
    elseif (header == mdp.MDPW_WORKER) then
        self:worker_process(sender, msg)
    else
        s_console("E: invalid message:")
        msg:dump()
    end
end)

--  Get and process messages forever or until interrupted
while (not s_interrupted) do
    local cnt = assert(poller:poll(HEARTBEAT_INTERVAL * 1000))
    --  Disconnect and delete any expired workers
    --  Send heartbeats to idle workers if needed
    if (s_clock() > self.heartbeat_at) then
        self:purge_workers()
        local waiting = self.waiting
        for n=1,#waiting do
            local worker = waiting[n]
            self:worker_send(worker, mdp.MDPW_HEARTBEAT)
        end
        self.heartbeat_at = s_clock() + HEARTBEAT_INTERVAL
    end
end
if (s_interrupted) then
    printf("W: interrupt received, shutting down…\n")
end
self:destroy()


這裏的“管家”基本上做了所有他能做的事:心跳,代理髮送信息,合理利用多服務資源。
或許,效能上還有些問題,那麼試試"異步"?
Java代碼 複製代碼 收藏代碼
  1. require"zmq"  
  2. require"zmq.threads"  
  3. require"zmsg"  
  4.   
  5. local common_code = [[   
  6.     require"zmq"  
  7.     require"zmsg"  
  8.     require"zhelpers"  
  9. ]]   
  10.   
  11. local client_task = common_code .. [[   
  12.     local context = zmq.init(1)   
  13.     local client = context:socket(zmq.XREQ)   
  14.     client:setopt(zmq.IDENTITY, "C", 1)   
  15.     client:connect("tcp://localhost:5555")   
  16.   
  17.     printf("Setting up test…\n")   
  18.     s_sleep(100)   
  19.   
  20.     local requests   
  21.     local start   
  22.   
  23.     printf("Synchronous round-trip test…\n")   
  24.     requests = 10000  
  25.     start = s_clock()   
  26.     for n=1,requests do  
  27.         local msg = zmsg.new("HELLO")   
  28.         msg:send(client)   
  29.         msg = zmsg.recv(client)   
  30.     end   
  31.     printf(" %d calls/second\n",   
  32.         (1000 * requests) / (s_clock() - start))   
  33.   
  34.     printf("Asynchronous round-trip test…\n")   
  35.     requests = 100000  
  36.     start = s_clock()   
  37.     for n=1,requests do  
  38.         local msg = zmsg.new("HELLO")   
  39.         msg:send(client)   
  40.     end   
  41.     for n=1,requests do  
  42.         local msg = zmsg.recv(client)   
  43.     end   
  44.     printf(" %d calls/second\n",   
  45.         (1000 * requests) / (s_clock() - start))   
  46.   
  47.     client:close()   
  48.     context:term()   
  49. ]]   
  50.   
  51. local worker_task = common_code .. [[   
  52.     local context = zmq.init(1)   
  53.     local worker = context:socket(zmq.XREQ)   
  54.     worker:setopt(zmq.IDENTITY, "W", 1)   
  55.     worker:connect("tcp://localhost:5556")   
  56.   
  57.     whiletruedo  
  58.         local msg = zmsg.recv(worker)   
  59.         msg:send(worker)   
  60.     end   
  61.     worker:close()   
  62.     context:term()   
  63. ]]   
  64.   
  65. local broker_task = common_code .. [[   
  66.     --  Prepare our context and sockets   
  67.     local context = zmq.init(1)   
  68.     local frontend = context:socket(zmq.XREP)   
  69.     local backend  = context:socket(zmq.XREP)   
  70.     frontend:bind("tcp://*:5555")   
  71.     backend:bind("tcp://*:5556")   
  72.   
  73.     require"zmq.poller"  
  74.     local poller = zmq.poller(2)   
  75.     poller:add(frontend, zmq.POLLIN, function()   
  76.         local msg = zmsg.recv(frontend)   
  77.         --msg[1] = "W"  
  78.         msg:pop()   
  79.         msg:push("W")   
  80.         msg:send(backend)   
  81.     end)   
  82.     poller:add(backend, zmq.POLLIN, function()   
  83.         local msg = zmsg.recv(backend)   
  84.         --msg[1] = "C"  
  85.         msg:pop()   
  86.         msg:push("C")   
  87.         msg:send(frontend)   
  88.     end)   
  89.     poller:start()   
  90.     frontend:close()   
  91.     backend:close()   
  92.     context:term()   
  93. ]]   
  94.   
  95. s_version_assert(2, 1)   
  96.   
  97. local client = zmq.threads.runstring(nil, client_task)   
  98. assert(client:start())   
  99. local worker = zmq.threads.runstring(nil, worker_task)   
  100. assert(worker:start(true))   
  101. local broker = zmq.threads.runstring(nil, broker_task)   
  102. assert(broker:start(true))   
  103.   
  104. assert(client:join())  
require"zmq"
require"zmq.threads"
require"zmsg"

local common_code = [[
    require"zmq"
    require"zmsg"
    require"zhelpers"
]]

local client_task = common_code .. [[
    local context = zmq.init(1)
    local client = context:socket(zmq.XREQ)
    client:setopt(zmq.IDENTITY, "C", 1)
    client:connect("tcp://localhost:5555")

    printf("Setting up test…\n")
    s_sleep(100)

    local requests
    local start

    printf("Synchronous round-trip test…\n")
    requests = 10000
    start = s_clock()
    for n=1,requests do
        local msg = zmsg.new("HELLO")
        msg:send(client)
        msg = zmsg.recv(client)
    end
    printf(" %d calls/second\n",
        (1000 * requests) / (s_clock() - start))

    printf("Asynchronous round-trip test…\n")
    requests = 100000
    start = s_clock()
    for n=1,requests do
        local msg = zmsg.new("HELLO")
        msg:send(client)
    end
    for n=1,requests do
        local msg = zmsg.recv(client)
    end
    printf(" %d calls/second\n",
        (1000 * requests) / (s_clock() - start))

    client:close()
    context:term()
]]

local worker_task = common_code .. [[
    local context = zmq.init(1)
    local worker = context:socket(zmq.XREQ)
    worker:setopt(zmq.IDENTITY, "W", 1)
    worker:connect("tcp://localhost:5556")

    while true do
        local msg = zmsg.recv(worker)
        msg:send(worker)
    end
    worker:close()
    context:term()
]]

local broker_task = common_code .. [[
    --  Prepare our context and sockets
    local context = zmq.init(1)
    local frontend = context:socket(zmq.XREP)
    local backend  = context:socket(zmq.XREP)
    frontend:bind("tcp://*:5555")
    backend:bind("tcp://*:5556")

    require"zmq.poller"
    local poller = zmq.poller(2)
    poller:add(frontend, zmq.POLLIN, function()
        local msg = zmsg.recv(frontend)
        --msg[1] = "W"
        msg:pop()
        msg:push("W")
        msg:send(backend)
    end)
    poller:add(backend, zmq.POLLIN, function()
        local msg = zmsg.recv(backend)
        --msg[1] = "C"
        msg:pop()
        msg:push("C")
        msg:send(frontend)
    end)
    poller:start()
    frontend:close()
    backend:close()
    context:term()
]]

s_version_assert(2, 1)

local client = zmq.threads.runstring(nil, client_task)
assert(client:start())
local worker = zmq.threads.runstring(nil, worker_task)
assert(worker:start(true))
local broker = zmq.threads.runstring(nil, broker_task)
assert(broker:start(true))

assert(client:join())


如此這般,效能倒是大大降低了(官網說法是降了近20倍),分析了下原因,由於異步需要管理各條任務,不斷輪詢之類的原因,反倒降低了性能,那麼I/O的異步呢?

異步的客戶端api:
Java代碼 複製代碼 收藏代碼
  1. local setmetatable = setmetatable   
  2.   
  3. local mdp = require"mdp"  
  4.   
  5. local zmq = require"zmq"  
  6. local zpoller = require"zmq.poller"  
  7. local zmsg = require"zmsg"  
  8. require"zhelpers"  
  9.   
  10. local s_version_assert = s_version_assert   
  11.   
  12. local obj_mt = {}   
  13. obj_mt.__index = obj_mt   
  14.   
  15. function obj_mt:set_timeout(timeout)   
  16.     self.timeout = timeout   
  17. end   
  18.   
  19. function obj_mt:destroy()   
  20.     if self.client then self.client:close() end   
  21.     self.context:term()   
  22. end   
  23.   
  24. local function s_mdcli_connect_to_broker(self)   
  25.     -- close old socket.   
  26.     if self.client then   
  27.         self.poller:remove(self.client)   
  28.         self.client:close()   
  29.     end   
  30.     self.client = assert(self.context:socket(zmq.XREQ))   
  31.     assert(self.client:setopt(zmq.LINGER, 0))   
  32.     assert(self.client:connect(self.broker))   
  33.     if self.verbose then   
  34.         s_console("I: connecting to broker at %s…", self.broker)   
  35.     end   
  36.     -- add socket to poller   
  37.     self.poller:add(self.client, zmq.POLLIN, function()   
  38.         self.got_reply = true  
  39.     end)   
  40. end   
  41.   
  42. --   
  43. -- Send request to broker and get reply by hook or crook   
  44. --   
  45. function obj_mt:send(service, request)   
  46.     -- Prefix request with protocol frames   
  47.     -- Frame 0: empty (REQ emulation)   
  48.     -- Frame 1: "MDPCxy" (six bytes, MDP/Client x.y)   
  49.     -- Frame 2: Service name (printable string)   
  50.     request:push(service)   
  51.     request:push(mdp.MDPC_CLIENT)   
  52.     request:push("")   
  53.     if self.verbose then   
  54.         s_console("I: send request to '%s' service:", service)   
  55.         request:dump()   
  56.     end   
  57.     request:send(self.client)   
  58.     return0  
  59. end   
  60.   
  61. --  Returns the reply message or NULL if there was no reply. Does not   
  62. --  attempt to recover from a broker failure, this is not possible   
  63. --  without storing all unanswered requests and resending them all…   
  64. function obj_mt:recv()   
  65.     self.got_reply = false  
  66.   
  67.     local cnt = assert(self.poller:poll(self.timeout * 1000))   
  68.     if cnt ~= 0 and self.got_reply then   
  69.         local msg = zmsg.recv(self.client)   
  70.         if self.verbose then   
  71.             s_console("I: received reply:")   
  72.             msg:dump()   
  73.         end   
  74.         assert(msg:parts() >= 3)   
  75.   
  76.         local empty = msg:pop()   
  77.         assert(empty == "")   
  78.   
  79.         local header = msg:pop()   
  80.         assert(header == mdp.MDPC_CLIENT)   
  81.   
  82.         return msg   
  83.     end   
  84.     if self.verbose then   
  85.         s_console("W: permanent error, abandoning request")   
  86.     end   
  87.     return nil -- Giving up   
  88. end   
  89.   
  90. module(…)   
  91.   
  92. function new(broker, verbose)   
  93.     s_version_assert (2, 1);   
  94.     local self = setmetatable({   
  95.         context = zmq.init(1),   
  96.         poller = zpoller.new(1),   
  97.         broker = broker,   
  98.         verbose = verbose,   
  99.         timeout = 2500, -- msecs   
  100.     }, obj_mt)   
  101.   
  102.     s_mdcli_connect_to_broker(self)   
  103.     return self   
  104. end   
  105.   
  106. setmetatable(_M, { __call = function(self, …) returnnew(…) end })  
local setmetatable = setmetatable

local mdp = require"mdp"

local zmq = require"zmq"
local zpoller = require"zmq.poller"
local zmsg = require"zmsg"
require"zhelpers"

local s_version_assert = s_version_assert

local obj_mt = {}
obj_mt.__index = obj_mt

function obj_mt:set_timeout(timeout)
    self.timeout = timeout
end

function obj_mt:destroy()
    if self.client then self.client:close() end
    self.context:term()
end

local function s_mdcli_connect_to_broker(self)
    -- close old socket.
    if self.client then
        self.poller:remove(self.client)
        self.client:close()
    end
    self.client = assert(self.context:socket(zmq.XREQ))
    assert(self.client:setopt(zmq.LINGER, 0))
    assert(self.client:connect(self.broker))
    if self.verbose then
        s_console("I: connecting to broker at %s…", self.broker)
    end
    -- add socket to poller
    self.poller:add(self.client, zmq.POLLIN, function()
        self.got_reply = true
    end)
end

--
-- Send request to broker and get reply by hook or crook
--
function obj_mt:send(service, request)
    -- Prefix request with protocol frames
    -- Frame 0: empty (REQ emulation)
    -- Frame 1: "MDPCxy" (six bytes, MDP/Client x.y)
    -- Frame 2: Service name (printable string)
    request:push(service)
    request:push(mdp.MDPC_CLIENT)
    request:push("")
    if self.verbose then
        s_console("I: send request to '%s' service:", service)
        request:dump()
    end
    request:send(self.client)
    return 0
end

--  Returns the reply message or NULL if there was no reply. Does not
--  attempt to recover from a broker failure, this is not possible
--  without storing all unanswered requests and resending them all…
function obj_mt:recv()
    self.got_reply = false

    local cnt = assert(self.poller:poll(self.timeout * 1000))
    if cnt ~= 0 and self.got_reply then
        local msg = zmsg.recv(self.client)
        if self.verbose then
            s_console("I: received reply:")
            msg:dump()
        end
        assert(msg:parts() >= 3)

        local empty = msg:pop()
        assert(empty == "")

        local header = msg:pop()
        assert(header == mdp.MDPC_CLIENT)

        return msg
    end
    if self.verbose then
        s_console("W: permanent error, abandoning request")
    end
    return nil -- Giving up
end

module(…)

function new(broker, verbose)
    s_version_assert (2, 1);
    local self = setmetatable({
        context = zmq.init(1),
        poller = zpoller.new(1),
        broker = broker,
        verbose = verbose,
        timeout = 2500, -- msecs
    }, obj_mt)

    s_mdcli_connect_to_broker(self)
    return self
end

setmetatable(_M, { __call = function(self, …) return new(…) end })


異步的客戶端:
Java代碼 複製代碼 收藏代碼
  1. require"mdcliapi2"  
  2. require"zmsg"  
  3. require"zhelpers"  
  4.   
  5. local verbose = (arg[1] == "-v")   
  6. local session = mdcliapi2.new("tcp://localhost:5555", verbose)   
  7.   
  8. local count=100000  
  9. for n=1,count do  
  10.     local request = zmsg.new("Hello world")   
  11.     session:send("echo", request)   
  12. end   
  13. for n=1,count do  
  14.     local reply = session:recv()   
  15.     if not reply then   
  16.         break   --  Interrupted by Ctrl-C   
  17.     end   
  18. end   
  19. printf("%d replies received\n", count)   
  20. session:destroy()  
require"mdcliapi2"
require"zmsg"
require"zhelpers"

local verbose = (arg[1] == "-v")
local session = mdcliapi2.new("tcp://localhost:5555", verbose)

local count=100000
for n=1,count do
    local request = zmsg.new("Hello world")
    session:send("echo", request)
end
for n=1,count do
    local reply = session:recv()
    if not reply then
        break   --  Interrupted by Ctrl-C
    end
end
printf("%d replies received\n", count)
session:destroy()


噹噹噹當:
$ time mdclient
同步的:
real    0m14.088s
user    0m1.310s
sys     0m2.670s
異步的:
real    0m8.730s
user    0m0.920s
sys     0m1.550s
10個服務端的異步:
real    0m3.863s
user    0m0.730s
sys     0m0.470s

經過測試,4核的話起8個服務端就算飽和了。不過,就效率而言,應該是足夠了。
值得注意到是,"異步管家模式"並非全能。由於他沒有做管家的連接重試,所以一旦“管家”崩潰了,那自然一切都say goodbye了。

爲了服務更靠譜,或許還需要一個叫做"發現服務"的系統,來確認到底有哪些服務可用。
Java代碼 複製代碼 收藏代碼
  1. require"mdcliapi"  
  2. require"zmsg"  
  3. require"zhelpers"  
  4.   
  5. local verbose = (arg[1] == "-v")   
  6. local session = mdcliapi.new("tcp://localhost:5555", verbose)   
  7.   
  8. --  This is the service we want to look up   
  9. local request = zmsg.new("echo")   
  10.   
  11. --  This is the service we send our request to   
  12. local reply = session:send("mmi.service", request)   
  13.   
  14. if (reply) then   
  15.     printf ("Lookup echo service: %s\n", reply:body())   
  16. else  
  17.     printf ("E: no response from broker, make sure it's running\n")   
  18. end   
  19.   
  20. session:destroy()  
require"mdcliapi"
require"zmsg"
require"zhelpers"

local verbose = (arg[1] == "-v")
local session = mdcliapi.new("tcp://localhost:5555", verbose)

--  This is the service we want to look up
local request = zmsg.new("echo")

--  This is the service we send our request to
local reply = session:send("mmi.service", request)

if (reply) then
    printf ("Lookup echo service: %s\n", reply:body())
else
    printf ("E: no response from broker, make sure it's running\n")
end

session:destroy()



注:以上皆爲lua代碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章