2010-10-24 7 views
11

जब आप सत्र-प्रति-अनुरोध पैटर्न का उपयोग कर रहे हैं, तो लेन-देन विफलताओं पर रीट्रीज़ का समर्थन करने के लिए एनएचबर्ननेट का उपयोग करके 3-स्तरीय एप्लिकेशन में आप किस पैटर्न/आर्किटेक्चर का उपयोग करते हैं? (चूंकि अपवाद के बाद इशारा अमान्य हो जाता है, भले ही यह एक डेडलॉक या टाइमआउट या लाइफेलॉक अपवाद है)। वेब परियोजना के अंदरसत्र प्रति अनुरोध का उपयोग करते समय NHibernate को डेडलॉक लेनदेन को फिर से प्रयास करने दें?

+0

आपने अपनी समस्या को कैसे संभाला? –

+0

मेरा जवाब देखें .. – Henrik

उत्तर

33

नोट 2 आजकल मैं कभी नहीं रखा लिखने लेनदेन - लेकिन इसके बजाय मैसेजिंग + कतारों का उपयोग और पृष्ठभूमि व्यवहार काम पैदा करने के लिए लक्ष्य कर संदेशों को संभालने में एक कार्यकर्ता है किया जाएगा।

हालांकि, मैं अभी भी लगातार डेटा प्राप्त करने के लिए पढ़ने के लिए लेनदेन का उपयोग करता हूं; वेब परियोजनाओं से एमवीसीसी/स्नैपशॉट अलगाव के साथ। उस स्थिति में आप पाएंगे कि सत्र-प्रति-अनुरोध-प्रति-लेनदेन पूरी तरह से ठीक है।

नोट 1 इस पोस्ट के विचार Castle Transactions framework और मेरे नए NHibernate Facility में दिए गए हैं।

ठीक है, यहां सामान्य विचार है। मान लीजिए कि आप एक ग्राहक के लिए एक गैर-अंतिम आदेश बनाना चाहते हैं। आपके पास कुछ प्रकार का जीयूआई है, उदा। एक ब्राउज़र/MVC अनुप्रयोग, कि प्रासंगिक जानकारी के साथ एक नया डेटा संरचना बनाने के (या आप नेटवर्क से इस डेटा संरचना मिल):

[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; } 
    } 
} 

आप इसे संभाल करने के लिए कुछ की जरूरत है। शायद यह किसी प्रकार की सेवा बस में कमांड हैंडलर होगा। 'कमांड हैंडलर' शब्द कई में से एक है और आप इसे 'सेवा' या 'डोमेन सेवा' या 'संदेश हैंडलर' भी कह सकते हैं। यदि आप कार्यात्मक प्रोग्रामिंग कर रहे थे, तो यह आपका संदेश बॉक्स कार्यान्वयन होगा, या यदि आप एरलांग या अक्का कर रहे थे, तो यह एक अभिनेता होगा।

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); 
} 

उपर्युक्त एक एपीआई उपयोग दिखाता है जो आप इस दिए गए समस्या डोमेन (एप्लिकेशन स्थिति/लेनदेन हैंडलिंग) के लिए चुन सकते हैं।

साथ के कार्यान्वयन:

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 
       } 
      } 
     } 
    } 
} 

आप देख सकते हैं, हम काम की एक नई इकाई की जरूरत है; हर बार कुछ गलत हो जाता है। यही कारण है कि लूप उपयोग कथन/ब्लॉक के बाहर है। फ़ंक्शन होने के कारण कारखाने के उदाहरण होने के बराबर हैं, सिवाय इसके कि हम उस पर एक विधि कॉल करने के बजाए सीधे ऑब्जेक्ट इंस्टेंस पर आवेदक कर रहे हैं। यह एक अच्छा कॉलर-एपीआई imho के लिए बनाता है।

हम रीट्रीज़ करने के तरीके के बारे में काफी चिकनी हैंडलिंग चाहते हैं, इसलिए हमारे पास एक इंटरफ़ेस है जिसे विभिन्न हैंडलर द्वारा कार्यान्वित किया जा सकता है, जिसे इरेट्री हैंडलर कहा जाता है। इन्हें हर पहलू के लिए चेन करना संभव होना चाहिए (हाँ, यह एओपी के बहुत करीब है) आप नियंत्रण प्रवाह को लागू करना चाहते हैं। एओपी कैसे काम करता है, रिटर्न वैल्यू का उपयोग नियंत्रण-प्रवाह को नियंत्रित करने के लिए किया जाता है, लेकिन केवल एक सच्चे/झूठी फैशन में, जो हमारी आवश्यकता है।

interface IRetryPolicy 
{ 
    bool PerformRetry(SqlException ex); 
} 

समेकित रूट, संभावित ग्राहक जीवन भर के साथ एक इकाई है। यह वही है जो आप अपने * .hbm.xml फ़ाइलों/FluentNHibernate के साथ मैपिंग करेंगे।

इसमें एक विधि है जो प्रेषित आदेश के साथ 1: 1 से मेल खाती है। यह कमांड हैंडलर को पढ़ने के लिए पूरी तरह से स्पष्ट बनाता है।

इसके अलावा, बतख टाइपिंग के साथ गतिशील भाषा के साथ, यह आपको कमांड के प्रकार के नामों को तरीकों से मैप करने की अनुमति देगा, जैसे रुबी/स्मॉलटाक यह करता है।

यदि आप ईवेंट सोर्सिंग कर रहे थे, लेनदेन हैंडलिंग समान होगी, लेनदेन को छोड़कर एनएचबेर्नेट के इंटरफ़ेस को इंटरफ़ेस नहीं किया जाएगा। अनुशासनिक यह है कि आप CreateOrder (दशमलव) को आमंत्रित करने के माध्यम से बनाई गई घटनाओं को सहेज लेंगे, और स्टोर से सहेजी गई घटनाओं को फिर से पढ़ने के लिए अपनी इकाई को एक तंत्र के साथ प्रदान करेंगे।

नोटिस के लिए एक अंतिम बिंदु यह है कि मैं अपने द्वारा बनाई गई तीन विधियों को ओवरराइड कर रहा हूं। यह NHibernate की तरफ से एक आवश्यकता है, क्योंकि यह जानने का एक तरीका है कि जब कोई इकाई दूसरे के बराबर होती है, तो क्या उन्हें सेट/बैग में होना चाहिए। मेरे कार्यान्वयन here के बारे में अधिक जानकारी। किसी भी तरह से, यह कोड का नमूना है और मैं अपने ग्राहकों के बारे में अभी कोई परवाह नहीं है, इसलिए मैं उन्हें लागू नहीं कर रहा हूँ:

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(); } 
} 

हम नीतियों पुन: प्रयास बनाने के लिए विधि की जरूरत है। बेशक हम इसे कई तरीकों से कर सकते हैं। यहां मैं उसी प्रकार के उसी ऑब्जेक्ट के उदाहरण के साथ एक धाराप्रवाह इंटरफ़ेस को संयोजित कर रहा हूं, जिसमें स्थिर विधि का प्रकार है। मैं इंटरफ़ेस को स्पष्ट रूप से कार्यान्वित करता हूं ताकि धाराप्रवाह इंटरफ़ेस में कोई अन्य विधि दिखाई न दे। यह इंटरफेस केवल नीचे 'मेरे' उदाहरणों का उपयोग करता है।

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}); 
    } 
} 

हमें धाराप्रवाह इंटरफ़ेस में आंशिक रूप से पूर्ण आमंत्रण के लिए एक इंटरफ़ेस की आवश्यकता है। यह हमें टाइप-सुरक्षा देता है। इसलिए हमें पॉलिसी को कॉन्फ़िगर करने से पहले, हमारे स्थिर प्रकार से दूर दो ड्रेफरेंस ऑपरेटरों (यानी 'पूर्ण स्टॉप' - (।)) की आवश्यकता होती है।

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

चेनिंग नीति का समाधान किया जा सकता है। इसका कार्यान्वयन यह जांचता है कि उसके सभी बच्चे जारी रहते हैं और जैसा कि यह जांचता है, यह भी उनमें तर्क करता है।

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)); 
    } 
} 

यह नीति वर्तमान धागे को कुछ समय सोती है; कभी-कभी डेटाबेस ओवरलोड हो जाता है, और कई पाठकों/लेखकों को लगातार पढ़ने की कोशिश कर रहे हैं डेटाबेस पर एक वास्तविक तथ्य डीओएस-हमला होगा (देखें कि कुछ महीनों पहले क्या हुआ जब फेसबुक क्रैश हो गया क्योंकि उनके कैश सर्वर सभी ने अपने डेटाबेस पर क्वेरी की थी पहर)।

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; 
    } 
} 

इसी तरह, किसी भी अच्छे एसक्यूएल आधारित प्रणाली में हम गतिरोध को संभालने की जरूरत है। हम वास्तव में गहराई से इनके लिए योजना नहीं बना सकते हैं, खासकर एनएचबीर्नेट का उपयोग करते समय, सख्त लेनदेन नीति को रखने के अलावा - कोई अंतर्निहित लेनदेन नहीं; और ओपन-सत्र-इन-व्यू से सावधान रहें। कार्टेशियन उत्पाद समस्या/एन + 1 समस्या का चयन भी करती है, यदि आपको बहुत अधिक डेटा मिल रहा है तो आपको ध्यान में रखना होगा। इसके बजाय, आपके पास बहु-क्वेरी, या एचक्यूएल का 'fetch' कीवर्ड हो सकता है।

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; 
    } 
} 

कोड को बेहतर पढ़ने के लिए एक सहायक वर्ग।

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

रूप में अच्छी तरह (IConnection को लागू करने के माध्यम से शायद सौंपने के द्वारा) IConnectionFactory में नेटवर्क विफलताओं को संभालने के लिए मत भूलना।


पीएस: सत्र-प्रति-अनुरोध एक टूटा हुआ पैटर्न है यदि आप केवल पढ़ने नहीं कर रहे हैं। विशेष रूप से यदि आप उसी ISession के साथ पढ़ रहे हैं जिसके साथ आप लिख रहे हैं और आप इस तरह के पढ़ने का आदेश नहीं दे रहे हैं कि वे सभी लिखते हैं, हमेशा लिखते हैं।

संबंधित मुद्दे