Urus niaga yang diedarkan melibatkan berbilang perkhidmatan mikro, di mana setiap perkhidmatan melaksanakan sebahagian daripada transaksi. Sebagai contoh, platform e-dagang mungkin melibatkan perkhidmatan seperti pembayaran, inventori dan pengurusan pesanan. Perkhidmatan ini perlu bekerjasama untuk menyelesaikan transaksi. Namun, apa yang berlaku jika salah satu perkhidmatan ini gagal?
Bayangkan aplikasi e-dagang di mana langkah berikut berlaku semasa penempatan pesanan:
Jika perkhidmatan inventori gagal selepas pembayaran ditolak tetapi sebelum pesanan dibuat, sistem akan berada dalam keadaan tidak konsisten. Pelanggan dikenakan bayaran, tetapi tiada pesanan dibuat.
Untuk mengendalikan kegagalan sedemikian, seseorang mungkin mempertimbangkan untuk menggunakan transaksi yang diedarkan dengan protokol komit dua fasa. Walau bagaimanapun, ini memperkenalkan beberapa isu:
Dalam sistem yang diedarkan, urus niaga selalunya merangkumi berbilang perkhidmatan mikro. Memastikan semua perkhidmatan sama ada selesai dengan jayanya atau tiada langsung adalah mencabar. Cara tradisional mengendalikan perkara ini—menggunakan urus niaga teragih dengan komitmen dua fasa—boleh menjadi masalah disebabkan isu seperti kependaman tinggi, gandingan yang ketat dan ketersediaan yang berkurangan.
Corak Saga menawarkan pendekatan yang lebih fleksibel. Daripada cuba melaksanakan urus niaga sebagai satu unit, corak Saga membahagikan urus niaga kepada langkah yang lebih kecil dan terpencil yang boleh dilakukan secara bebas. Setiap langkah ialah transaksi setempat yang mengemas kini pangkalan data dan kemudian mencetuskan langkah seterusnya. Jika langkah gagal, sistem melakukan tindakan pampasan untuk membuat asal perubahan yang dibuat oleh langkah sebelumnya, memastikan sistem boleh kembali kepada keadaan yang konsisten.
Corak Saga pada asasnya ialah urutan transaksi yang lebih kecil yang dilaksanakan satu demi satu. Begini cara ia berfungsi:
Terdapat dua cara utama untuk melaksanakan corak Saga: Koreografi dan Orkestrasi.
Dalam Saga Koreografi, tiada penyelaras pusat. Sebaliknya, setiap perkhidmatan yang terlibat dalam Saga mendengar acara dan memutuskan masa untuk bertindak berdasarkan keputusan langkah sebelumnya. Pendekatan ini tidak berpusat dan membolehkan perkhidmatan beroperasi secara bebas. Begini cara ia berfungsi:
Kelebihan Koreografi:
Cabaran Koreografi:
Dalam Orkestrasi Saga, orkestra pusat mengawal aliran transaksi. Orkestra menentukan urutan langkah dan mengendalikan komunikasi antara perkhidmatan. Begini cara ia berfungsi:
Kelebihan Orkestrasi:
Cabaran Orkestrasi:
Mari kita pertimbangkan senario e-dagang dan laksanakannya menggunakan corak Saga.
Dalam senario pembelian kopi kami, setiap perkhidmatan mewakili transaksi tempatan. Perkhidmatan Kopi bertindak sebagai pengatur kisah ini, menyelaraskan perkhidmatan lain untuk menyelesaikan pembelian.
Berikut ialah pecahan cara saga itu mungkin berfungsi:
Dalam pelaksanaan saga saya, setiap SagaItemBuilder mewakili satu langkah dalam aliran transaksi teragih kami. ActionBuilder mentakrifkan tindakan yang akan dilakukan, termasuk tindakan utama dan tindakan rollback yang akan dilaksanakan jika ralat berlaku. ActionBuilder merangkumi tiga maklumat:
komponen : Contoh kacang di mana kaedah yang akan digunakan berada.
kaedah : Nama kaedah yang akan dipanggil.
args : Hujah-hujah yang akan dihantar kepada kaedah.
Pembina Tindakan
public class ActionBuilder { private Object component; private String method; private Object[] args; public static ActionBuilder builder() { return new ActionBuilder(); } public ActionBuilder component(Object component) { this.component = component; return this; } public ActionBuilder method(String method) { this.method = method; return this; } public ActionBuilder args(Object... args) { this.args = args; return this; } public Object getComponent() { return component; } public String getMethod() { return method; } public Object[] getArgs() { return args; } }
SagaItemBuilder
import java.util.HashMap; import java.util.Map; import java.util.Objects; public class SagaItemBuilder { private ActionBuilder action; private Map<Class<? extends Exception>, ActionBuilder> onBehaviour; public static SagaItemBuilder builder() { return new SagaItemBuilder(); } public SagaItemBuilder action(ActionBuilder action) { this.action = action; return this; } public SagaItemBuilder onBehaviour(Class<? extends Exception> exception, ActionBuilder action) { if (Objects.isNull(onBehaviour)) onBehaviour = new HashMap<>(); onBehaviour.put(exception, action); return this; } public ActionBuilder getAction() { return action; } public Map<Class<? extends Exception>, ActionBuilder> getBehaviour() { return onBehaviour; } }
Senario
import java.util.ArrayList; import java.util.List; public class Scenarios { List<SagaItemBuilder> scenarios; public static Scenarios builder() { return new Scenarios(); } public Scenarios scenario(SagaItemBuilder sagaItemBuilder) { if (scenarios == null) scenarios = new ArrayList<>(); scenarios.add(sagaItemBuilder); return this; } public List<SagaItemBuilder> getScenario() { return scenarios; } }
Di bawah ialah bagaimana saya boleh melakukan transaksi pengedaran.
package com.example.demo.saga; import com.example.demo.saga.exception.CanNotRollbackException; import com.example.demo.saga.exception.RollBackException; import com.example.demo.saga.pojo.ActionBuilder; import com.example.demo.saga.pojo.SagaItemBuilder; import com.example.demo.saga.pojo.Scenarios; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Map; import java.util.Set; @Component public class DTC { public boolean commit(Scenarios scenarios) throws Exception { validate(scenarios); for (int i = 0; i < scenarios.getScenario().size(); i++) { SagaItemBuilder scenario = scenarios.getScenario().get(i); ActionBuilder action = scenario.getAction(); Object bean = action.getComponent(); String method = action.getMethod(); Object[] args = action.getArgs(); try { invoke(bean, method, args); } catch (Exception e) { rollback(scenarios, i, e); return false; } } return true; } private void rollback(Scenarios scenarios, Integer failStep, Exception currentStepFailException) { for (int i = failStep; i >= 0; i--) { SagaItemBuilder scenario = scenarios.getScenario().get(i); Map<Class<? extends Exception>, ActionBuilder> behaviours = scenario.getBehaviour(); Set<Class<? extends Exception>> exceptions = behaviours.keySet(); ActionBuilder actionWhenException = null; if (failStep == i) { for(Class<? extends Exception> exception: exceptions) { if (exception.isInstance(currentStepFailException)) { actionWhenException = behaviours.get(exception); } } if (actionWhenException == null) actionWhenException = behaviours.get(RollBackException.class); } else { actionWhenException = behaviours.get(RollBackException.class); } Object bean = actionWhenException.getComponent(); String method = actionWhenException.getMethod(); Object[] args = actionWhenException.getArgs(); try { invoke(bean, method, args); } catch (Exception e) { throw new CanNotRollbackException("Error in %s belong to %s. Can not rollback transaction".formatted(method, bean.getClass())); } } } private void validate(Scenarios scenarios) throws Exception { for (int i = 0; i < scenarios.getScenario().size(); i++) { SagaItemBuilder scenario = scenarios.getScenario().get(i); ActionBuilder action = scenario.getAction(); if (action.getComponent() == null) throw new Exception("Missing bean in scenario"); if (action.getMethod() == null) throw new Exception("Missing method in scenario"); Map<Class<? extends Exception>, ActionBuilder> behaviours = scenario.getBehaviour(); Set<Class<? extends Exception>> exceptions = behaviours.keySet(); if (exceptions.contains(null)) throw new Exception("Exception can not be null in scenario has method %s, bean %s " .formatted(action.getMethod(), action.getComponent().getClass())); if (!exceptions.contains(RollBackException.class)) throw new Exception("Missing default RollBackException in scenario has method %s, bean %s " .formatted(action.getMethod(), action.getComponent().getClass())); } } public String invoke(Object bean, String methodName, Object... args) throws Exception { try { Class<?>[] paramTypes = new Class[args.length]; for (int i = 0; i < args.length; i++) { paramTypes[i] = parameterType(args[i]); } Method method = bean.getClass().getDeclaredMethod(methodName, paramTypes); Object result = method.invoke(bean, args); return result != null ? result.toString() : null; } catch (Exception e) { throw e; } } private static Class<?> parameterType (Object o) { if (o instanceof Integer) { return int.class; } else if (o instanceof Boolean) { return boolean.class; } else if (o instanceof Double) { return double.class; } else if (o instanceof Float) { return float.class; } else if (o instanceof Long) { return long.class; } else if (o instanceof Short) { return short.class; } else if (o instanceof Byte) { return byte.class; } else if (o instanceof Character) { return char.class; } else { return o.getClass(); } } }
Saya mempunyai 3 perkhidmatan yang memanggil perkhidmatan luar: BillingService , OrderService , PaymentService.
Perkhidmatan Pesanan
package com.example.demo.service; import org.springframework.stereotype.Service; @Service public class OrderService { public String prepareOrder(String name, int number) { System.out.println("Prepare order for %s with order id %d ".formatted(name, number)); return "Prepare order for %s with order id %d ".formatted(name, number); } public void Rollback_prepareOrder_NullPointException() { System.out.println("Rollback prepareOrder because NullPointException"); } public void Rollback_prepareOrder_RollBackException() { System.out.println("Rollback prepareOrder because RollBackException"); } }
Perkhidmatan Pengebilan
package com.example.demo.service; import org.springframework.stereotype.Service; @Service public class BillingService { public String prepareBilling(String name, int number) { System.out.println("Prepare billing for %s with order id %d ".formatted(name, number)); return "Prepare billing for %s with order id %d ".formatted(name, number); } public String createBilling(String name, int number) { System.out.println("Create billing for %s with order id %d ".formatted(name, number)); return "Create billing for %s with order id %d ".formatted(name, number); } public void Rollback_prepareBilling_NullPointException() { System.out.println("Rollback prepareBilling because NullPointException"); } public void Rollback_prepareBilling_ArrayIndexOutOfBoundsException() { System.out.println("Rollback prepareBilling because ArrayIndexOutOfBoundsException"); } public void Rollback_prepareBilling_RollBackException() { System.out.println("Rollback prepareBilling because RollBackException"); } public void Rollback_createBilling_NullPointException() { System.out.println("Rollback createBilling because NullPointException"); } public void Rollback_createBilling_ArrayIndexOutOfBoundsException() { System.out.println("Rollback createBilling because ArrayIndexOutOfBoundsException"); } public void Rollback_createBilling_RollBackException() { System.out.println("Rollback createBilling because RollBackException"); } }
Perkhidmatan Pembayaran
package com.example.demo.service; import org.springframework.stereotype.Service; @Service public class PaymentService { public String createPayment() { System.out.println("Create payment"); return "Create payment"; } public void Rollback_createPayment_NullPointException() { System.out.println("Rollback createPayment because NullPointException"); } public void Rollback_createPayment_RollBackException() { System.out.println("Rollback createPayment because RollBackException"); } }
Dan dalam Perkhidmatan Kopi, saya melaksanakannya seperti berikut, saya mencipta senario dan kemudian melaksanakannya.
package com.example.demo.service; import com.example.demo.saga.DTC; import com.example.demo.saga.exception.RollBackException; import com.example.demo.saga.pojo.ActionBuilder; import com.example.demo.saga.pojo.SagaItemBuilder; import com.example.demo.saga.pojo.Scenarios; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class CoffeeService { @Autowired private OrderService orderService; @Autowired private BillingService billingService; @Autowired private PaymentService paymentService; @Autowired private DTC dtc; public String test() throws Exception { Scenarios scenarios = Scenarios.builder() .scenario( SagaItemBuilder.builder() .action(ActionBuilder.builder().component(orderService).method("prepareOrder").args("tuanh.net", 123)) .onBehaviour(NullPointerException.class, ActionBuilder.builder().component(orderService).method("Rollback_prepareOrder_NullPointException").args()) .onBehaviour(RollBackException.class, ActionBuilder.builder().component(orderService).method("Rollback_prepareOrder_RollBackException").args()) ).scenario( SagaItemBuilder.builder() .action(ActionBuilder.builder().component(billingService).method("prepareBilling").args("tuanh.net", 123)) .onBehaviour(NullPointerException.class, ActionBuilder.builder().component(billingService).method("Rollback_prepareBilling_NullPointException").args()) .onBehaviour(RollBackException.class, ActionBuilder.builder().component(billingService).method("Rollback_prepareBilling_RollBackException").args()) ).scenario( SagaItemBuilder.builder() .action(ActionBuilder.builder().component(billingService).method("createBilling").args("tuanh.net", 123)) .onBehaviour(NullPointerException.class, ActionBuilder.builder().component(billingService).method("Rollback_createBilling_ArrayIndexOutOfBoundsException").args()) .onBehaviour(RollBackException.class, ActionBuilder.builder().component(billingService).method("Rollback_createBilling_RollBackException").args()) ).scenario( SagaItemBuilder.builder() .action(ActionBuilder.builder().component(paymentService).method("createPayment").args()) .onBehaviour(NullPointerException.class, ActionBuilder.builder().component(paymentService).method("Rollback_createPayment_NullPointException").args()) .onBehaviour(RollBackException.class, ActionBuilder.builder().component(paymentService).method("Rollback_createPayment_RollBackException").args()) ); dtc.commit(scenarios); return "ok"; } }
Apabila saya membuat pengecualian dalam membuat pengebilan.
public String createBilling(String name, int number) { throw new NullPointerException(); }
Keputusan
2024-08-24T14:21:45.445+07:00 INFO 19736 --- [demo] [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/' 2024-08-24T14:21:45.450+07:00 INFO 19736 --- [demo] [main] com.example.demo.DemoApplication : Started DemoApplication in 1.052 seconds (process running for 1.498) 2024-08-24T14:21:47.756+07:00 INFO 19736 --- [demo] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2024-08-24T14:21:47.756+07:00 INFO 19736 --- [demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2024-08-24T14:21:47.757+07:00 INFO 19736 --- [demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms Prepare order for tuanh.net with order id 123 Prepare billing for tuanh.net with order id 123 Rollback createBilling because RollBackException Rollback prepareBilling because RollBackException Rollback prepareOrder because RollBackException
Lihat Repositori GitHub saya
Ringkasnya, corak Saga menyediakan penyelesaian yang mantap untuk mengurus urus niaga yang diedarkan dengan memecahkannya kepada langkah yang lebih kecil dan boleh diurus. Pilihan antara Koreografi dan Orkestrasi bergantung pada keperluan khusus dan seni bina sistem anda. Koreografi menawarkan gandingan dan daya tahan yang longgar, manakala Orkestrasi menyediakan kawalan terpusat dan pemantauan yang lebih mudah. Dengan mereka bentuk sistem anda dengan teliti dengan corak Saga, anda boleh mencapai ketekalan, ketersediaan dan fleksibiliti dalam seni bina perkhidmatan mikro teragih anda.
Jangan ragu untuk mengulas di bawah jika anda mempunyai sebarang soalan atau memerlukan penjelasan lanjut tentang melaksanakan corak Saga dalam sistem anda!
Baca siaran lebih lanjut di : Bagaimana Corak Saga Menyelesaikan Isu Transaksi Teragih: Kaedah dan Contoh Dunia Nyata
Atas ialah kandungan terperinci Cara Corak Saga Menyelesaikan Isu Transaksi Teragih: Kaedah dan Contoh Dunia Nyata. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!