2010-10-24 36 views
11

Bạn sử dụng mẫu/kiến ​​trúc nào trong ứng dụng 3 tầng sử dụng NHibernate cần hỗ trợ thử lại về lỗi giao dịch khi bạn đang sử dụng mẫu Phiên cho mỗi yêu cầu? (như ISession trở thành không hợp lệ sau khi một ngoại lệ, ngay cả khi đây là một bế tắc hoặc thời gian chờ hoặc ngoại lệ livelock).Làm cách nào để NHibernate thử lại các giao dịch bế tắc khi sử dụng phiên cho mỗi yêu cầu?

+0

Cách bạn xử lý sự cố của mình? –

+0

Xem câu trả lời của tôi .. – Henrik

Trả lời

33

Lưu ý 2 Ngày nay tôi sẽ không bao giờ đặt giao dịch ghi bên trong dự án web - mà thay vào đó sử dụng tin nhắn + hàng đợi và có nhân viên trong nền xử lý thư nhằm gây ra công việc giao dịch được thực hiện.

Tuy nhiên, tôi vẫn sử dụng giao dịch để đọc để nhận dữ liệu nhất quán; cùng với cách ly MVCC/Snapshot, từ các dự án web. Trong trường hợp đó, bạn sẽ thấy rằng phiên mỗi giao dịch mỗi giao dịch là hoàn toàn ổn.

Lưu ý 1 Ý tưởng của bài đăng này đã được đặt trong Castle Transactions frameworkNHibernate Facility mới của tôi.

OK, đây là ý tưởng chung. Giả sử bạn muốn tạo một đơn đặt hàng không được hoàn tất cho khách hàng. Bạn có một số loại GUI, ví dụ: một trình duyệt/ứng dụng MVC, tạo cấu trúc dữ liệu mới với thông tin có liên quan (hoặc bạn lấy cấu trúc dữ liệu này từ mạng):

[Serializable] 
class CreateOrder /*: IMessage*/ 
{ 
    // immutable 
    private readonly string _CustomerName; 
    private readonly decimal _Total; 
    private readonly Guid _CustomerId; 

    public CreateOrder(string customerName, decimal total, Guid customerId) 
    { 
     _CustomerName = customerName; 
     _Total = total; 
     _CustomerId = customerId; 
    } 

    // put ProtoBuf attribute 
    public string CustomerName 
    { 
     get { return _CustomerName; } 
    } 

    // put ProtoBuf attribute 
    public decimal Total 
    { 
     get { return _Total; } 
    } 

    // put ProtoBuf attribute 
    public Guid CustomerId 
    { 
     get { return _CustomerId; } 
    } 
} 

Bạn cần điều gì đó để xử lý. Có lẽ đây sẽ là một trình xử lý lệnh trong một loại xe buýt dịch vụ nào đó. Từ 'xử lý lệnh' là một trong nhiều và bạn cũng có thể gọi nó là 'dịch vụ' hoặc 'dịch vụ tên miền' hoặc 'trình xử lý tin nhắn'. Nếu bạn đang làm lập trình chức năng, nó sẽ được thực hiện hộp thư của bạn, hoặc nếu bạn đang làm Erlang hoặc Akka, nó sẽ là một diễn viên.

class CreateOrderHandler : IHandle<CreateOrder> 
{ 
    public void Handle(CreateOrder command) 
    { 
     With.Policy(IoC.Resolve<ISession>, s => s.BeginTransaction(), s => 
     { 
      var potentialCustomer = s.Get<PotentialCustomer>(command.CustomerId); 
      potentialCustomer.CreateOrder(command.Total); 
      return potentialCustomer; 
     }, RetryPolicies.ExponentialBackOff.RetryOnLivelockAndDeadlock(3)); 
    } 
} 

interface IHandle<T> /* where T : IMessage */ 
{ 
    void Handle(T command); 
} 

Ở trên cho biết cách sử dụng API bạn có thể chọn cho miền có vấn đề này (trạng thái ứng dụng/xử lý giao dịch).

Việc thực hiện Với:

static class With 
{ 
    internal static void Policy(Func<ISession> getSession, 
             Func<ISession, ITransaction> getTransaction, 
             Func<ISession, EntityBase /* abstract 'entity' base class */> executeAction, 
             IRetryPolicy policy) 
    { 
     //http://fabiomaulo.blogspot.com/2009/06/improving-ado-exception-management-in.html 

     while (true) 
     { 
      using (var session = getSession()) 
      using (var t = getTransaction(session)) 
      { 
       var entity = executeAction(session); 
       try 
       { 
        // we might not always want to update; have another level of indirection if you wish 
        session.Update(entity); 
        t.Commit(); 
        break; // we're done, stop looping 
       } 
       catch (ADOException e) 
       { 
        // need to clear 2nd level cache, or we'll get 'entity associated with another ISession'-exception 

        // but the session is now broken in all other regards will will throw exceptions 
        // if you prod it in any other way 
        session.Evict(entity); 

        if (!t.WasRolledBack) t.Rollback(); // will back our transaction 

        // this would need to be through another level of indirection if you support more databases 
        var dbException = ADOExceptionHelper.ExtractDbException(e) as SqlException; 

        if (policy.PerformRetry(dbException)) continue; 
        throw; // otherwise, we stop by throwing the exception back up the layers 
       } 
      } 
     } 
    } 
} 

Như bạn có thể thấy, chúng ta cần một đơn vị làm việc mới; các ISession mỗi khi một cái gì đó đi sai. Đó là lý do tại sao vòng lặp nằm ở bên ngoài các câu lệnh/khối Sử dụng. Có các hàm tương đương với các cá thể của nhà máy, ngoại trừ chúng ta đang gọi trực tiếp trên một cá thể đối tượng, thay vì gọi một phương thức trên nó. Nó làm cho một imho caller-API đẹp hơn.

Chúng tôi muốn xử lý khá trơn tru về cách chúng tôi thực hiện thử lại, vì vậy chúng tôi có một giao diện có thể được triển khai bởi các trình xử lý khác nhau, được gọi là IRetryHandler. Nó có thể chuỗi những điều này cho mọi khía cạnh (có, nó rất gần với AOP) bạn muốn thực thi dòng điều khiển. Tương tự như cách AOP hoạt động, giá trị trả về được sử dụng để kiểm soát luồng điều khiển, nhưng chỉ trong một thời trang đúng/sai, đó là yêu cầu của chúng tôi.

interface IRetryPolicy 
{ 
    bool PerformRetry(SqlException ex); 
} 

The AggregateRoot, PotentialCustomer là một thực thể có tuổi thọ. Đó là những gì bạn sẽ được ánh xạ với các tệp * .hbm.xml của bạn/FluentNHibernate.

Nó có phương thức tương ứng 1: 1 với lệnh được gửi. Điều này làm cho các trình xử lý lệnh hoàn toàn rõ ràng để đọc.

Hơn nữa, với một ngôn ngữ động với kiểu gõ vịt, nó sẽ cho phép bạn ánh xạ các tên kiểu lệnh thành các phương thức, tương tự như cách Ruby/Smalltalk thực hiện nó.

Nếu bạn đang thực hiện tìm nguồn cung ứng sự kiện, xử lý giao dịch sẽ giống nhau, ngoại trừ giao dịch sẽ không giao diện của NHibernate như vậy. Hệ quả là bạn sẽ lưu các sự kiện được tạo ra bằng cách gọi CreateOrder (thập phân) và cung cấp cho thực thể của bạn một cơ chế để đọc lại các sự kiện đã lưu từ cửa hàng.

Điểm cuối cùng cần lưu ý là tôi ghi đè ba phương pháp mà tôi đã tạo. Đây là một yêu cầu từ phía NHibernate, vì nó cần một cách để biết khi một thực thể là bằng nhau, nên họ được trong bộ/túi. Thông tin thêm về việc triển khai của tôi here. Trong bất kỳ cách nào, đây là mã mẫu và tôi không quan tâm đến khách hàng của tôi ngay bây giờ, vì vậy tôi không thực hiện chúng:

sealed class PotentialCustomer : EntityBase 
{ 
    public void CreateOrder(decimal total) 
    { 
     // validate total 
     // run business rules 

     // create event, save into event sourced queue as transient event 
     // update private state 
    } 

    public override bool IsTransient() { throw new NotImplementedException(); } 
    protected override int GetTransientHashCode() { throw new NotImplementedException(); } 
    protected override int GetNonTransientHashCode() { throw new NotImplementedException(); } 
} 

Chúng tôi cần một phương pháp để tạo chính sách thử lại. Tất nhiên chúng ta có thể làm điều này theo nhiều cách. Ở đây tôi kết hợp một giao diện thông thạo với một thể hiện của cùng một đối tượng cùng kiểu mà kiểu của phương thức tĩnh là. Tôi thực hiện giao diện một cách rõ ràng để không có phương thức nào khác hiển thị trong giao diện thông thạo. Giao diện này chỉ sử dụng triển khai 'ví dụ' của tôi bên dưới.

internal class RetryPolicies : INonConfiguredPolicy 
{ 
    private readonly IRetryPolicy _Policy; 

    private RetryPolicies(IRetryPolicy policy) 
    { 
     if (policy == null) throw new ArgumentNullException("policy"); 
     _Policy = policy; 
    } 

    public static readonly INonConfiguredPolicy ExponentialBackOff = 
     new RetryPolicies(new ExponentialBackOffPolicy(TimeSpan.FromMilliseconds(200))); 

    IRetryPolicy INonConfiguredPolicy.RetryOnLivelockAndDeadlock(int retries) 
    { 
     return new ChainingPolicy(new[] {new SqlServerRetryPolicy(retries), _Policy}); 
    } 
} 

Chúng tôi cần giao diện cho lời gọi hoàn toàn một phần đến giao diện thông thạo. Điều này cho chúng ta loại an toàn. Do đó, chúng tôi cần hai toán tử dereference (tức là 'toàn dừng' - (.)), Cách xa loại tĩnh của chúng ta, trước khi hoàn thành việc cấu hình chính sách.

internal interface INonConfiguredPolicy 
{ 
    IRetryPolicy RetryOnLivelockAndDeadlock(int retries); 
} 

Chính sách chuỗi có thể được giải quyết. Việc thực hiện của nó kiểm tra xem tất cả các con của nó có tiếp tục trở lại và khi nó kiểm tra điều đó, nó cũng thực hiện logic trong chúng.

internal class ChainingPolicy : IRetryPolicy 
{ 
    private readonly IEnumerable<IRetryPolicy> _Policies; 

    public ChainingPolicy(IEnumerable<IRetryPolicy> policies) 
    { 
     if (policies == null) throw new ArgumentNullException("policies"); 
     _Policies = policies; 
    } 

    public bool PerformRetry(SqlException ex) 
    { 
     return _Policies.Aggregate(true, (val, policy) => val && policy.PerformRetry(ex)); 
    } 
} 

Chính sách này cho phép luồng hiện tại ngủ một khoảng thời gian; đôi khi cơ sở dữ liệu bị quá tải, và có nhiều độc giả/nhà văn liên tục cố gắng đọc sẽ là một cuộc tấn công DOS trên cơ sở dữ liệu (xem những gì đã xảy ra một vài tháng trước đây khi facebook bị hỏng vì máy chủ cache của họ đều truy vấn cơ sở dữ liệu của họ thời gian).

internal class ExponentialBackOffPolicy : IRetryPolicy 
{ 
    private readonly TimeSpan _MaxWait; 
    private TimeSpan _CurrentWait = TimeSpan.Zero; // initially, don't wait 

    public ExponentialBackOffPolicy(TimeSpan maxWait) 
    { 
     _MaxWait = maxWait; 
    } 

    public bool PerformRetry(SqlException ex) 
    { 
     Thread.Sleep(_CurrentWait); 
     _CurrentWait = _CurrentWait == TimeSpan.Zero ? TimeSpan.FromMilliseconds(20) : _CurrentWait + _CurrentWait; 
     return _CurrentWait <= _MaxWait; 
    } 
} 

Tương tự, trong bất kỳ hệ thống dựa trên SQL nào, chúng tôi cần xử lý deadlocks. Chúng tôi không thể thực sự lập kế hoạch cho những điều này trong chiều sâu, đặc biệt là khi sử dụng NHibernate, ngoài việc giữ một chính sách giao dịch nghiêm ngặt - không có giao dịch ngầm; và cẩn thận với Phiên mở trong chế độ xem. Ngoài ra còn có vấn đề sản phẩm Descartes/N + 1 chọn vấn đề bạn cần phải ghi nhớ nếu bạn đang tìm nạp rất nhiều dữ liệu. Thay vào đó, bạn có thể có từ khóa 'Tìm nạp' Multi-Query hoặc HQL.

internal class SqlServerRetryPolicy : IRetryPolicy 
{ 
    private int _Tries; 
    private readonly int _CutOffPoint; 

    public SqlServerRetryPolicy(int cutOffPoint) 
    { 
     if (cutOffPoint < 1) throw new ArgumentOutOfRangeException("cutOffPoint"); 
     _CutOffPoint = cutOffPoint; 
    } 

    public bool PerformRetry(SqlException ex) 
    { 
     if (ex == null) throw new ArgumentNullException("ex"); 
     // checks the ErrorCode property on the SqlException 
     return SqlServerExceptions.IsThisADeadlock(ex) && ++_Tries < _CutOffPoint; 
    } 
} 

Lớp trợ giúp để làm cho mã đọc tốt hơn.

internal static class SqlServerExceptions 
{ 
    public static bool IsThisADeadlock(SqlException realException) 
    { 
     return realException.ErrorCode == 1205; 
    } 
} 

Đừng quên xử lý các lỗi mạng trong IConnectionFactory (bằng cách ủy nhiệm có thể thông qua triển khai kết nối).


PS: Phiên cho mỗi yêu cầu là mẫu bị hỏng nếu bạn không chỉ đọc. Đặc biệt là nếu bạn đang đọc với cùng một ISession mà bạn đang viết với và bạn không đặt hàng các lần đọc như vậy mà họ là tất cả, luôn luôn, trước khi viết.

Các vấn đề liên quan