問題描述
WCF 服務僅在客戶端收到結果時才寫入日誌 (WCF service writes log only if client receives results)
I'm working on a WCF service to help our new code interoperate with a legacy system. The process goes like this:
- Client calls the service with a request for the legacy system.
- Service writes the request into a database.
- Legacy system services request from the DB in its own time and writes results back into the DB (updating a status flag to say results are ready).
- Client retrieves results by calling a second service method, which polls the DB until the ready flag is set.
- Just before returning the results, the service updates the status flag to client has results, so that the related DB rows can be deleted.
My concern is the race condition at the last step. I can see this happening:
- Service updates status to client has results.
- Client times out after waiting for the service to poll the DB.
- Service tries to return results. Hilarity ensues.
One way to solve this would be to have three service calls instead of two: the second call retrieves results, and the last one is an explicit acknowledgement by the client that it has them. I'd like to know whether there is a way which doesn't impose this extra "protocol" burden on the client though.
I've looked briefly into using transactions in WCF, and it sounds like they might be able to do what I need. The client (optionally) starts a transaction, flows it to the service, which uses it if it's there, and commits it when done. This seems as if it implicitly does the "third call".
Does this idea have any merit? Any disadvantages that you can see? Are there any other avenues I could explore?
參考解法
方法 1:
Why not reduce the likelihood of the client timing out by doing this instead:
- Client calls service with a request for the legacy system.
- Service writes the request into a database.
- Legacy system services request from the DB in its own time and writes results back into the DB (updating a status flag to say results are ready).
- Client calls a service to find out whether the results are ready. NB. no polling: just returns with an immediate yes or no.
- If the results are NOT ready, client waits a bit and then goes back to step 4.
- If the results ARE ready, call the service to retrieve the results. The service can update the status to "Client has results" at that point.
By doing this, the client won't be waiting for the service call in step 4. to return for a prolonged period, and the chances of a timeout should be minimal.
However, you're never going to be 100% certain that the client has received the results unless the client makes a final service call to say so. (What if, for example, the client dies after making the very last request?)
方法 2:
Using transaction flow is possible but flowing transaction in polling scenario (in each poll call) is terrible architecture. What you generally need is transaction flow for the real read operation where service modifies the record and returns data back to the client. The client will commit the transaction and it will commit changes performed by the service.
Using transactional processing places some additional requirements on your service and clients.
Another approach can be transactional MSMQ:
- Client calls the service with a request for the legacy system = client sends a message to the service's queue
- Service writes the request into a database = service processes the message from its queue
- Legacy system services request from the DB in its own time and writes results back into the DB (updating a status flag to say results are ready).
- Service polls the database and places messages to correct client queues. Placing the message and modifying database records runs in transaction
- Client processes incoming message
Transactional queue allows transactional reading (the message is removed from the queue only if transaction is committed) and writing (the message is added to the queue only if transactions is committed). That will allow deleting records before the client reads the message because the message will remain in the queue until he successfully reads it (or until it timeouts and even after that it can be passed to some error queues).
In both cases you should think about clients who will consume the service. Transaction flowing can be interoperable but not every web service stack supports it. MSMQ is not interoperable.
(by anton.burger、razlebe、Ladislav Mrnka)