Transaction Context

Transaction context of Seata is managed by RootContext.

When application begins a global transaction, RootContext will bind the XID of the transaction automatically, at the end of transaction(commit or rollback), RootContext will unbind the XID automatically.

  1. // Bind XID
  2. RootContext.bind(xid);
  3. // Unbind XID
  4. String xid = RootContext.unbind();

Application retrieve the global transaction XID through the API of RootContext.

  1. // Retrieve XID
  2. String xid = RootContext.getXID();

Whether application is running a global transaction, just check if an XID bound to RootContext.

  1. public static boolean inGlobalTransaction() {
  2. return CONTEXT_HOLDER.get(KEY_XID) != null;
  3. }

Transaction propagation

The mechanism of the global transaction of Seata is the propagation of transaction context, primarily, it’s the propagation way of XID in runtime.

1. The propagation of transaction in the service

By default, RootContext is based on ThreadLocal, which is the XID is bound in the context of thread.

  1. public class ThreadLocalContextCore implements ContextCore {
  2. private ThreadLocal<Map<String, String>> threadLocal = new ThreadLocal<Map<String, String>>() {
  3. @Override
  4. protected Map<String, String> initialValue() {
  5. return new HashMap<String, String>();
  6. }
  7. };
  8. @Override
  9. public String put(String key, String value) {
  10. return threadLocal.get().put(key, value);
  11. }
  12. @Override
  13. public String get(String key) {
  14. return threadLocal.get().get(key);
  15. }
  16. @Override
  17. public String remove(String key) {
  18. return threadLocal.get().remove(key);
  19. }
  20. }

So the inner XID of service is tracing by the same thread naturally, do nothing to propagate the transaction by default.

If it hopes to hung up the transaction context, implement it by the API of RootContext:

  1. // Hung up(pause)
  2. String xid = RootContext.unbind();
  3. // TODO: Logic running out of the global transaction scope
  4. // recover the global transaction
  5. RootContext.bind(xid);

2. Transactional propagation across service calls

It’s easy to know by the basic idea preceding:

The transaction propagation across service calls, essentially, propagate the XID via service call to service provider, and bind it to RootContext.

As long as it can be done, Seata can support any microservice framework in theory.

Interpretation of supporting Dubbo

Let’s interpret the inner support for Dubbo RPC to illustrate how Seata supports a specific microservice framework in follows:

We use the org.apache.dubbo.rpc.Filter of Dubbo to support propagation of transaction.

  1. /**
  2. * The type Transaction propagation filter.
  3. */
  4. @Activate(group = { Constants.PROVIDER, Constants.CONSUMER }, order = 100)
  5. public class TransactionPropagationFilter implements Filter {
  6. private static final Logger LOGGER = LoggerFactory.getLogger(TransactionPropagationFilter.class);
  7. @Override
  8. public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
  9. String xid = RootContext.getXID(); // Get XID of current transaction
  10. String rpcXid = RpcContext.getContext().getAttachment(RootContext.KEY_XID); // Acquire the XID from RPC invoke
  11. if (LOGGER.isDebugEnabled()) {
  12. LOGGER.debug("xid in RootContext[" + xid + "] xid in RpcContext[" + rpcXid + "]");
  13. }
  14. boolean bind = false;
  15. if (xid != null) { // Consumer:Put XID into the attachment of RPC
  16. RpcContext.getContext().setAttachment(RootContext.KEY_XID, xid);
  17. } else {
  18. if (rpcXid != null) { // Provider:Bind the XID propagated by RPC to current runtime
  19. RootContext.bind(rpcXid);
  20. bind = true;
  21. if (LOGGER.isDebugEnabled()) {
  22. LOGGER.debug("bind[" + rpcXid + "] to RootContext");
  23. }
  24. }
  25. }
  26. try {
  27. return invoker.invoke(invocation); // Business method invoke
  28. } finally {
  29. if (bind) { // Provider:Clean up XID after invoke
  30. String unbindXid = RootContext.unbind();
  31. if (LOGGER.isDebugEnabled()) {
  32. LOGGER.debug("unbind[" + unbindXid + "] from RootContext");
  33. }
  34. if (!rpcXid.equalsIgnoreCase(unbindXid)) {
  35. LOGGER.warn("xid in change during RPC from " + rpcXid + " to " + unbindXid);
  36. if (unbindXid != null) { // if there is new transaction begin, can't do clean up
  37. RootContext.bind(unbindXid);
  38. LOGGER.warn("bind [" + unbindXid + "] back to RootContext");
  39. }
  40. }
  41. }
  42. }
  43. }
  44. }