Dinamik proxy Mock dubbo hizmetine dayalı uygulama şeması

Arka plana genel bakış

Şirketin mevcut Java proje hizmetleri Dubbo çerçevesine dayanmaktadır ve Dubbo çerçevesi, çoğu yerli İnternet şirketi tarafından seçilen temel bir bileşen haline gelmiştir.

Günlük proje işbirliği sürecinde, aslında istikrarsız hizmetler ve tatmin edici olmayan talep senaryoları gibi durumlar olacaktır.Çoğu geliştirme, Mocktio gibi tek test araçlarını yerel olarak kendi kendine test yardımı olarak kullanacaktır. Öyleyse, ortak hata ayıklama, test etme ve diğer işbirlikçi süreçlerle nasıl başa çıkılır?

Aslında, Dubbo geliştiricileri bu sorunla da karşılaştıklarını tahmin ediyorlar, bu nedenle genelleştirilmiş hizmet kaydı sağlamak için bir giriş sağlıyorlar. Bununla birlikte, hizmet keşfinde bir dezavantaj vardır, yani bu sahte hizmeti hizmet keşfi yoluyla talep ederseniz, kayıt defterinde yalnızca bir hizmet geçerli olmalıdır, aksi takdirde tüketiciler diğer Mock dışı hizmetleri talep edeceklerdir.

Bu sorunu çözmek için Dubbo geliştiricileri, genelleştirilmiş aramalar için bir giriş noktası sağlar. Yalnızca kayıt aracılığıyla hizmetlerin keşfedilmesini değil, aynı zamanda IP + PORT aracılığıyla hizmetlerin doğrudan çağrılmasını da destekler, böylece tüketicilerin Mock'tan hizmetleri aramaları sağlanabilir.

Yukarıdaki genelleştirilmiş hizmet kaydı ve genelleştirilmiş hizmet çağrısının kombinasyonu, Dubbo hizmetinin Mock problemini çözebilecek kapalı bir döngü gibi görünmektedir. Bununla birlikte, günlük çalışma ile birleştiğinde bazı sıkıntılı sorunlar olacaktır:

  • Servis sağlayıcı halka açık bir sicil kullanır ve tüketici onu doğru bir şekilde arayamaz
  • Tüketiciler, Mock hizmetine doğrudan bağlanmak için kodu değiştiremez
  • Özel bir kayıt defteri kullanmak yukarıdaki sorunları çözebilir, ancak Mock'un minimum genişliği Yöntemdir, bir Hizmette alay edilen bir Yöntem normal şekilde işlenir ve alay edilmeyen bir Yöntem anormal olur ve bu da sunucunun Mock Service'in tüm yöntemlerine ihtiyaç duymasına neden olur.

Yukarıdaki sorunların çözülmesi öncülüğünde, gerekli bir Dubbo hizmetini hızlı bir şekilde kaydetmek ve proje işbirliği sürecinde iş verimliliğini artırmak için Mock fabrikasının tasarımı ve uygulaması gerçekleştirildi.

İşlevsel Genel Bakış

  • Mock Dubbo hizmeti
  • Tek bir sunucu, birden çok özdeş ve farklı hizmetin dağıtımını destekler
  • Dinamik çevrimiçi ve çevrimdışı hizmetler
  • Sahte olmayan yöntemler şeffaf bir şekilde temel hizmetlere aktarılır

1. Şema araştırması

1.1 Hizmet Zincirine dayalı olarak sahte hizmetin uygulama yöntemini seçin

1.1.1 Hizmet Zincirinin Kısa Tanıtımı

İş başlatma kaynağına Hizmet Zinciri logoları ekleyin. Bu logolar, sonraki uygulamalar arası uzaktan aramalarda şeffaf bir şekilde iletilecek ve bu logolara göre yönlendirilecektir. Bu şekilde, yalnızca gereksinimlerde değişiklik içeren uygulamaların örneklerini ayrı ayrı dağıtmamız ve eklememiz gerekir. Hizmet Zincirinin veri yapısının tanımında, mantıksal olarak diğer bağlantılardan tamamen izole edilmiş bir mantıksal bağlantı sanallaştırılabilir ve değiştirilmesi gerekmeyen uygulama örneklerini paylaşabilir.

Yönlendirme, şu anda adı verilen şeffaf aktarım tanımlayıcısına ve Hizmet Zincirinin temel meta verilerine göre gerçekleştirilir. Yönlendirme ilkeleri aşağıdaki gibidir:

  • Mevcut çağrı Servis Zinciri tanımlayıcısını içeriyorsa, Servis Zincirine ait herhangi bir servis düğümüne yönlendirilecektir.
  • Hizmet Zincirinin hizmet düğümü, Hizmet Zincirine ait tüm hizmet düğümleri hariç tutulduktan sonra herhangi bir hizmet düğümüne yönlendirilecektir.
  • Mevcut çağrı Servis Zinciri tanımlayıcısını içermiyorsa, Servis Zincirine ait tüm servis düğümleri hariç tutulacak ve herhangi bir servis düğümüne yönlendirilecektir.
  • Mevcut çağrı Servis Zinciri tanımlayıcısını içerdiğinde ve mevcut uygulama da belirli bir Servis Zincirine aitse, ikisi eşit değilse bir yönlendirme istisnası atılacaktır.

Dubbo çerçevesini örnek alarak, bir Servis Zinciri uygulama mimarisi diyagramı verilmiştir.

1.1.2 Sahte hizmet gerçekleştirme tasarım şeması

Çözüm 1: GenericService tabanlı Mock arabirimi gerektiren genelleştirilmiş bir uygulama oluşturun ve bunu ETCD'ye kaydedin (ana uygulama fikri aşağıdaki şekilde gösterilmektedir).

Seçenek 2: Sahte arayüz gerektiren bir Proxy uygulaması oluşturmak için Javassist'i kullanın ve bunu ETCD'ye kaydedin (ana uygulama fikirleri aşağıdaki şekilde gösterilmektedir).

1.1.3 Tasarım şemalarının karşılaştırılması

Birinci çözüm avantajı: uygulaması basittir ve sahte ihtiyaçları karşılayabilir

  • GenericService'i devral, bir $ invoke (String methodName, String parameterTypes, Object nesneleri) uyguladığınız sürece, dönüş bilgilerini belirli istek parametrelerine göre özelleştirebilirsiniz.
  • Arayüz bilgilerinin sadece arayüz adını ve protokolünü bilmesi gerekir.
  • Hizmet zaten mevcut olsa bile, genel alan tüketicilerin ilk önce sahte hizmeti kullanmasına izin verir.

Dezavantajlar: şirketin hizmet keşif mekanizmasıyla çelişki

Övgü hizmeti arka planı nedeniyle, Haunt hizmet keşfi kullanılırken, hem normal hizmetler hem de Hizmet Zinciri ile işaretlenmiş genelleştirilmiş hizmetler iade edilecektir, bu nedenle iki tür hizmet olmalıdır. Sonuç olarak, Servis Zinciri markasına sahip tüketiciler, normalde genelleştirilmiş bir hizmet talep ettiklerinde, mevcut bir çağrı olmadığını bildirirler. Örnek: 2 HelloServices kayıtlıdır:

  • Normal: genel = falseinterface = com.alia.api.HelloServicemethods = doNothing, diyelim, yaş
  • Genelleştirilmiş: genel = trueinterface = com.alia.api.HelloServicemethods = *

Hizmet keşfedildiğinde, RegistryDirectory'de tüm hizmetlerin kayıt bilgilerini kaydeden bir harita bulunur. Başka bir deyişle, method = * ve normal method = doNothing, diyelim ki yaş birlikte saklanır.

İstemci bir hizmet talep ettiğinde, genelleştirilmiş hizmeti aramak yerine önce normal hizmetin yöntemiyle eşleşir. Sonuç: erişildiğinde, genericFilter atlanacak ve herhangi bir kullanılabilir çağrı rapor edilmeyecektir.

Çözümün iki avantajı: Proxy uygulaması, otomatik olarak normal bir Dubbo arayüz uygulaması oluşturur

1. Javassist, kullanıcı koduna olan bağımlılığı büyük ölçüde basitleştiren bayt kodunu gerçekleştirmek için arayüz oluşturmak için hazır yöntemlere sahiptir. Örneğin:

  • Tek yöntemli sahte uygulama için String, Json, vb. Döndürür, kullanıcıların uygulama sınıflarını yüklemesine gerek yoktur.
  • Şeffaf iletim, platform tarafından tek tip olarak kontrol edilir Sahte konfigürasyonsuz yöntem, varsayılan olarak şeffaf iletim gerçekleştirecek ve Servis Zinciri işaretini koruyacaktır.

2. Sahte hizmet kayıt yöntemi bilgileri tamamlandı.

3. Arayüz Proxy nesnesi oluşturulurken, kesinlikle arayüz tanımına uygun olarak oluşturulur ve dönüş veri tipi garanti edilir.

Dezavantajları:

  • Öncelikli tüketim seçme işlevi yok.
  • Bayt kodu arka planda üretilir ve bu, oluşturulan proxy'deki sorunların giderilmesi için elverişli değildir.

1.1.4 Seçim sonuçları

Bir platform olarak, yalnızca sahte gereksinimleri karşılaması değil, aynı zamanda kullanıcı işlemlerini azaltması ve mevcut şirketin hizmet mimarisi sistemini desteklemesi gerekir, bu nedenle tasarım seçeneği iki seçilir.

1.2 Dinamik proxy ve ServiceConfig'e dayalı dinamik çevrimiçi ve çevrimdışı hizmetleri gerçekleştirin

1.2.1 Dubbo'nun hizmet sunum sürecine giriş

Yukarıdaki şekil (dubbo geliştirici belgelerinden) hizmet dizisi diyagramını gösterir: İlk olarak ServiceConfig sınıfı, harici hizmetler sağlayan gerçek sınıf ref (StudentInfoServiceImpl gibi) alır ve ardından ProxyFactory sınıfının getInvoker yöntemi aracılığıyla bir AbstractProxyInvoker örneği oluşturmak için ref kullanır. Bu adımda, belirli hizmetlerin Invoker'a dönüştürülmesi tamamlanır. Bir sonraki adım, Invoker'ı Exporter'a dönüştürme işlemidir ve Exporter, hizmeti URL'ye dönüştürerek ortaya çıkaracaktır. Dubbo kaynak koduna bakıldığında dubbo, Spring çerçevesi tarafından sağlanan Schema genişletilebilir mekanizma aracılığıyla konfigürasyon desteğini genişletti. dubbo-container, Spring konteynırını kapsülleyerek Spring bağlamını başlatır. Bu sırada, Spring bean konfigürasyon dosyasını (Spring'in xml konfigürasyon dosyası) ayrıştırır. dubbo: service etiketini ayrıştırırken, ayrıştırma için dubbo özel BeanDefinitionParser'ı kullanır. Dubbo'nun BeanDefinitonParser'ı DubboBeanDefinitionParser olarak uygulanmaktadır. Spring.handlers dosyası:

public class DubboNamespaceHandler, NamespaceHandlerSupport { public DubboNamespaceHandler () { } public void init () { this.registerBeanDefinitionParser ("uygulama", yeni DubboBeanDefinitionParser (ApplicationConfig.class, true)); this.registerBeanDefinitionParser ("modül", yeni DubboBeanDefinitionParser (ModuleConfig.class, true)); this.registerBeanDefinitionParser ("kayıt defteri", yeni DubboBeanDefinitionParser (RegistryConfig.class, doğru)); this.registerBeanDefinitionParser ("monitor", new DubboBeanDefinitionParser (MonitorConfig.class, true)); this.registerBeanDefinitionParser ("sağlayıcı", yeni DubboBeanDefinitionParser (ProviderConfig.class, true)); this.registerBeanDefinitionParser ("tüketici", yeni DubboBeanDefinitionParser (ConsumerConfig.class, true)); this.registerBeanDefinitionParser ("protocol", new DubboBeanDefinitionParser (ProtocolConfig.class, true)); this.registerBeanDefinitionParser ("hizmet", yeni DubboBeanDefinitionParser (ServiceBean.class, true)); this.registerBeanDefinitionParser ("referans", yeni DubboBeanDefinitionParser (ReferenceBean.class, false)); this.registerBeanDefinitionParser ("annotation", new DubboBeanDefinitionParser (AnnotationBean.class, true)); } statik { Version.checkDuplicate (DubboNamespaceHandler.class); } } DubboBeanDefinitionParser, yapılandırma etiketlerini ayrıştıracak ve karşılık gelen Javabeans'ları oluşturacak ve son olarak onları Spring Ioc kabına kaydedecektir. Bir ServiceBean kaydederken, InitializingBean arabirimini uygular.Bean kaydedildikten sonra, hizmet kaydını tamamlamak için export () yöntemini çağıran afterPropertiesSet () yöntemini çağırır. ServiceConfig'deki doExport () yönteminde, hizmetin her bir parametresi kontrol edilir. eğer (this.ref GenericService örneği) { this.interfaceClass = GenericService.class; this.generic = true; } Başka { Deneyin { this.interfaceClass = Class.forName (this.interfaceName, true, Thread.currentThread (). getContextClassLoader ()); } catch (ClassNotFoundException var5) { yeni IllegalStateException (var5.getMessage (), var5); } this.checkInterfaceAndMethods (this.interfaceClass, this.methods); this.checkRef (); this.generic = yanlış; }

Uygulama sınıfının türü, kayıt sürecinde değerlendirilecektir. Bunların arasında, GenericService arabirimi uygulanırsa, hizmet bilgileri açığa çıktığında, genel doğru olarak ayarlanır ve maruz kalan yöntem *. Değilse, servis ekleme yöntemi normal servisler gibi yapılacaktır. İşte Mock'u uygulayabileceğimiz giriş noktası.Özel Mock bilgilerine göre uygulama sınıfının bir sınıf dosyasını yazmak için Javassist'i kullanın ve ServiceConfig'e enjekte edilecek bir örnek oluşturun. Oluşturulan sınıf örneği aşağıda gösterilmektedir, bu normal bir uygulama sınıfıyla tamamen tutarlıdır ve kayıtlı hizmet normal hizmetle tamamen tutarlıdır.

paket 123.com.youzan.api; import com.youzan.api.StudentInfoService; ithal com.youzan.pojo.Pojo; import com.youzan.test.mocker.internal.common.reference.ServiceReference; public class StudentInfoServiceImpl, StudentInfoService { özel Pojo getNoValue0; özel Pojo getNoValue1; özel ServiceReference hizmeti; public void setgetNoValue0 (Pojo var1) { this.getNoValue0 = var1; } public void setgetNoValue1 (Pojo var1) { this.getNoValue1 = var1; } public Pojo getNo (int var1) { dönüş var1 == 1? this.getNoValue0: this.getNoValue1; } public void setService (ServiceReference var1) { this.service = var1; } genel çift söyle () { return (Çift) this.service.reference ("söyle", "", (Nesne) null); } public void findInfo (String var1, long var2) { this.service.reference ("findInfo", "java.lang.String, long", yeni Object {var1, new Long (var2)}); } public StudentInfoServiceImpl () {} }

Özel uygulama sınıfını enjekte etmek ve kaydı tamamlamak için ServiceConfig kullanın. Uygulama aşağıdaki gibidir:

void kayıt (Object T, String sc) { service.setFilter ("istek") service.setRef (T) service.setParameters (yeni HashMap < Dize, Dize > ()) service.getParameters (). put (Sabitler.SERVICE_CONFIG_PARAMETER_SERVICE_CHAIN_NAME, sc) service.export () if (service.isExported ()) { log.warn "Başarıyla gönderildi: $ {sc} - $ {service.interface}" } Başka { log.error "Yayınlanamadı: $ {sc} - $ {service.interface}" } }

Uygulama sınıfı, service.setRef (genericService) aracılığıyla enjekte edilir ve son olarak hizmet kaydı, service.export () aracılığıyla tamamlanır. Ref değeri, ServiceChain etiketi ile doldurulmuş ve hizmetin parametrelerine kaydedilmiştir. Spesifik hizmetlerin Invoker'e dönüştürülmesi, Invoker'ın Exporter'a dönüştürülmesi ve Exporter'ın URL'ye dönüştürülmesi ServiceChain markasıyla kayıt defterine kaydedilecektir.

1.2.2 Uygulama tasarım şeması oluşturun

Çözüm 1: Tek bir yöntemle dalga geçmek için String (veya Json) belirtmeyi destekleyin.

İşlev tanıtımı: String veya Json girdi parametresine göre proxy nesnesi oluşturun. Benzersiz yöntem tanımı, methodName ve methodParams ile elde edilir. (Tek bir yöntem taklidini desteklemeye atıfta bulunarak). Tüketici, Mock hizmetinin karşılık gelen Mock Yöntemini talep ettiğinde, Mock hizmeti kaydedilen verileri karşılık gelen dönüş türüne dönüştürür ve geri döndürür.

2.Çözüm: Birden çok yöntem için örnek oluşturmak üzere String (veya Json) belirtmeyi destekleyin.

İşlev tanıtımı: String veya Json girdi parametresine göre proxy nesnesi oluşturun. Yönteme karşılık gelen sahte veriler, methodMockMap tarafından belirtilir ve benzersiz yöntem tanımı, methodName tarafından elde edilir, bu nedenle sahte arabirim aşırı yüklenmiş yöntemlere sahip olamaz (yalnızca birden çok farklı yöntem taklidini destekler). Tüketici, Mock hizmetinin karşılık gelen sahte yöntemini talep ettiğinde, Mock hizmeti kaydedilen verileri karşılık gelen dönüş türüne dönüştürür ve geri döner.

Çözüm 3: Uygulama sınıfının (Impl) kullanılması durumunda, alay için belirli bir yöntem desteklenir.

İşlev tanıtımı: Giriş parametrelerinin uygulama sınıfına göre proxy nesneleri oluşturun. Benzersiz yöntem tanımı, methodName ve methodParams ile elde edilir. (Destek bir yöntemi taklit eder). Bir tüketici, Mock hizmetinin karşılık gelen sahte yöntemini talep ettiğinde, Mock hizmeti, uygulama sınıfının karşılık gelen yöntemini çağırır ve geri döner.

Çözüm 4: Uygulama sınıfı (Impl) kullanılması durumunda, alay için birden çok yöntem desteklenir.

İşlev tanıtımı: Giriş parametrelerinin uygulama sınıfına göre proxy nesneleri oluşturun. Tek yöntem tanımı methodName ile elde edilir, bu nedenle sahte arabirim aşırı yüklenmiş yöntemlere sahip olamaz (yalnızca bir uygulama sınıfı modelinin birden çok yöntemini destekler). Bir tüketici, Mock hizmetinin karşılık gelen sahte yöntemini talep ettiğinde, Mock hizmeti, uygulama sınıfının karşılık gelen yöntemini çağırır ve geri döner.

Çözüm 5: Birden çok yöntemle alay etmek için Özel Referans'ı kullanın.

İşlev tanıtımı: ServiceReference girdi parametresine göre proxy nesneleri oluşturun. Yönteme karşılık gelen özel ServiceReference, methodMockMap tarafından belirtilir ve benzersiz yöntem tanımı, methodName tarafından elde edilir, bu nedenle sahte arabirim aşırı yüklenmiş yöntemlere sahip olamaz (yalnızca birden çok farklı yöntem modelini destekler). Bir tüketici, Mock hizmetinin karşılık gelen sahte yöntemini talep ettiğinde, Mock hizmeti aktif olarak özel bir Dubbo hizmeti talep edecektir.

1.2.3 Tasarım şeması seçimi

Yukarıdaki beş şema, aslında tüm Mock fabrikası tarafından uygulanan yinelemeli bir süreçtir. Her planın girişimlerinde, her birinin dezavantajları keşfedildi ve bir sonraki şema ortaya çıktı. Şu anda, çeşitli kullanım senaryolarını birleştirdikten sonra Seçenek İki ve Seçenek Beş seçilmiştir.

Şema üç ve dördüncü şemanın hariç tutulmasının ana nedeni: Dubbo, yayınlanan Hizmet için uygulama sınıfının ClassLoader'ını kaydeder.Aynı className'e sahip sınıf başarıyla kaydedildikten sonra, uygulama sınıfının ClassLoader'ı, silinmesi zor olan belleğe kaydedilir. Dolayısıyla, bu iki çözümü kullanmak istiyorsanız, uygulama sınıfının className öğesini sık sık değiştirmeniz gerekir, bu da bir aracın kullanım kolaylığını büyük ölçüde azaltır. Özel uygulama sınıfı yerine özel Dubbo hizmetini (Şema 5) kullanın, ancak kullanıcıların kendileri bir Dubbo hizmeti başlatması ve IP + PORT'u bilgilendirmesi gerekir.

1.Çözüm, aslında, Mock of Service aşırı yükleme yöntemini destekleyebilen Çözüm 2'nin tamamlayıcısıdır. Kullanım sırasında belirli Yöntemin imza bilgilerinin aktarılması gerektiğinden, kullanıcı işlem maliyeti artmaktadır. Şirket dahili olarak bir Hizmetin aşırı yüklenmiş yöntemlere sahip olamayacağını garanti ettiğinden ve kullanım verimliliğini artırmak için çözüm açık değildir. Daha sonraki dönemde böyle aşırı yüklenmiş bir yöntem varsa, tekrar açın.

1.2.4 Karşılaşılan çukur

Temel veri türleri özel işlem gerektirir

Arabirim sınıfına dayalı olarak sınıfı uygulayan bir sınıf dosyası yazmak için Javassist'i kullanın.En zahmetli şey, yöntem imzası ve dönüş değeridir. Yöntemin imzası ve dönüş değeri temel veri türleriyse, parametreleri geçerken ve döndürürken özel işlem gerekir. Platformdaki en aptal sayım işleme yöntemini kullandım.Javassist kullanan bir usta varsa, iyi önerileriniz varsa lütfen beni aydınlatmaktan çekinmeyin. kod aşağıdaki gibi gösterilir:

/ ** Parametre temel bir veri türüne sahip olduğunda, temel veri türü varsayılan olarak kullanılır * Temel türler şunları içerir: * Gerçek sayı: double, float * Tamsayı: bayt, kısa, tamsayı, uzun * Karakter: karakter * Boole değeri: boole * * / private static CtClass getParamType (ClassPool classPool, String paramType) { switch (paramType) { case "char": return CtClass.charType "bayt" durumu: return CtClass.byteType durum "kısa": return CtClass.shortType case "int": return CtClass.intType durum "uzun": return CtClass.longType case "float": return CtClass.floatType durum "çift": return CtClass.doubleType case "boolean": return CtClass.booleanType varsayılan: return classPool.get (paramType) } }

1.3 Sahte olmayan yöntemlerin temel hizmetlere şeffaf aktarımı

1.3.1 Dubbo Hizmet Tüketim Sürecine Giriş

Tüketici tarafında: Spring dubbo: reference ayrıştırdığında, Dubbo ilk olarak ayrıştırıcıyı kaydetmek için com.alibaba.dubbo.config.spring.schema.NamespaceHandler'ı kullanır. Spring xml yapılandırma dosyasını ayrıştırdığında, karşılık gelen BeanDefinition'ı oluşturmak için bu ayrıştırıcıları çağırır ve Yay yönetimi. Spring, IOC kabını başlattığında, karşılık gelen ReferenceBean'in BeanDefinition örneğini elde etmek için burada kayıtlı BeanDefinitionParser'ın ayrıştırma yöntemini kullanacaktır. ReferenceBean InitializingBean arayüzünü uyguladığından, Bean'in tüm özellikleri ayarlandıktan sonra afterPropertiesSet yöntemi çağrılacaktır. AfterPropertiesSet yöntemindeki getObject, derlemeyi tamamlamak için ReferenceConfig üst sınıfının init yöntemini çağırır. ReferenceConfig sınıfının init yöntemi, hizmet tüketiminin anahtarı olan bir Invoker örneği oluşturmak için Protokolün refer yöntemini çağırır. Ardından, Invoker'ı istemcinin gerektirdiği arabirime (StudentInfoService gibi) dönüştürün. ReferenceConfig ile kesin ve Dubbo'nun API aracılığıyla genelleştirilmiş çağrısını kullanın. Kod aşağıdaki gibidir:

Nesne referansı (String s, String paramStr, Object nesneleri) { if (StringUtils.isEmpty (serviceInfoDO.interfaceName) || serviceInfoDO.interfaceName.length () < = 0) { yeni NullPointerException ("ArayüzAdı '$ {serviceInfoDO.interfaceName} olmamalıdır}, lütfen doğru" arayüzAdı "geçtiğinden emin olun") } // arayüz adını ayarla referenceConfig.setInterface (serviceInfoDO.interfaceName) referenceConfig.setApplication (serviceInfoDO.applicationConfig) // sürümü ayarla eğer (serviceInfoDO.version! = boş serviceInfoDO.version! = "" serviceInfoDO.version.length () > 0) { referenceConfig.setVersion (serviceInfoDO.version) } if (StringUtils.isEmpty (serviceInfoDO.refUrl) || serviceInfoDO.refUrl.length () < = 0) { yeni NullPointerException oluştur ("The'refUrl '$ {serviceInfoDO.refUrl} olmamalıdır, lütfen doğru'refUrl' geçirdiğinizden emin olun") } // refUrl ayarla referenceConfig.setUrl (serviceInfoDO.refUrl) reference.setGeneric (true) // Genelleştirilmiş bir arayüz olarak bildirildi // Tüm arayüz referanslarını değiştirmek için com.alibaba.dubbo.rpc.service.GenericService kullanın GenericService genericService = reference.get () Dize strs = boş eğer (paramStr! = "") { strs = paramStr.split (",") } Nesne sonucu = genericService. $ İnvoke (s, strs, nesneler) // Dönüş değeri türü belirsizdir ve özel işlem gerektirir eğer (result.getClass (). isAssignableFrom (HashMap.class)) { Sınıf dtoClass = Sınıf.forName (sonuç.get ("sınıf")) result.remove ("sınıf") String sonucuJson = JSON.toJSONString (sonuç) JSON.parseObject (resultJson, dtoClass) döndür } dönüş sonucu }

Yukarıdaki kodda gösterildiği gibi, belirli iş DTO türleri ve genelleştirilmiş arama sonuçları yalnızca sonuç verileri değil, aynı zamanda DTO sınıfı bilgileridir.Özel işlem sonuçları gereklidir ve gerekli sonuçlar alınır ve geri gönderilir.

1.3.2 Dubbo hizmet talebinin tasarım planını kaydedin

1.Çözüm: İstek bilgilerini yakalayın

Servis sağlayıcı ve servis tüketici çağrı süreci durdurma Dubbo'nun birçok işlevi bu uzantı noktasına göre uygulanır.Uzak yöntem her yürütüldüğünde, durdurma yürütülür. Sağlayıcı tarafından sağlanan çağrı zinciri, belirli çağrı zinciri kodu ProtocolFilterWrapper'ın buildInvokerChain'inde tamamlanır.Özellikle, ek açıklamada group = provider ile filtre uygulaması sırayla uygulanır ve son çağrı sırası EchoFilter- > ClassLoaderFilter- > GenericFilter- > ContextFilter- > ExceptionFilter- > TimeoutFilter- > MonitorFilter- > TraceFilter. Bunların arasında: EchoFilter'ın işlevi, bunun bir yankı testi isteği olup olmadığını belirlemek ve öyleyse içeriği doğrudan döndürmektir. Yankı testi, hizmetin mevcut olup olmadığını tespit etmek için kullanılır.Yankı testi, normal istek sürecine göre yürütülür.Tüm çağrının sorunsuz olup olmadığını ve izleme için kullanılıp kullanılamayacağını test edebilir. ClassLoaderFilter yalnızca ana işleve bir işlev ekler ve mevcut iş parçacığının ClassLoader'ını değiştirir.

ServiceConfig, AbstractInterfaceConfig'i miras alır, filtre özelliği vardır. Bunu bir giriş noktası olarak kullanarak, her bir Mock hizmetine bir filtre ekleyin ve her dubbo hizmet talebi bilgisini kaydedin (arayüz, yöntem, giriş parametresi, dönüş, yanıt süresi).

Plan iki, talep bilgilerini kaydedin

Talep bilgisi bellekte saklanır ve alay edilen bir arayüzün her yöntemi, bilgiyi kaydetmek için yaklaşık 10 kez depolanır. Kaydetmek için ikincil önbelleği kullanın, önbellek kodu aşağıdaki gibidir:

@Singleton (tembel = doğru) class CacheUtil { özel statik nihai Nesne MEVCUT = yeni Nesne () private int maxInterfaceSize = 10000 // Maksimum arayüz arabellek sayısı private int maxRequestSize = 10 // Maksimum istek önbellek numarası özel Önbellek < Dize, Önbellek < RequestDO, Object > > caches = CacheBuilder.newBuilder () .maximumSize (maxInterfaceSize) .expireAfterAccess (7, TimeUnit.DAYS) // 7 gündür talep edilmeyen arayüz, önbellek kurtarma .inşa etmek() }

Yukarıdaki kodda gösterildiği gibi, ikinci düzey önbellekteki bir Nesnenin bellek alanı boşa harcanır, ancak daha iyi bir çözüm düşünülemediği için tasarım geçici olarak korunur.

1.3.3 Karşılaşılan çukur

Genelleme çağrısı sırasında parametre nesnesi dönüşümü

Hizmeti doğrudan çağırmak için ReferenceConfig'i kullanmak, bir arabirim yönteminin imzasının doğrulanmasını atlar, bu nedenle en büyük sorun genelleştirilmiş çağrılar yaparken Object'teki parametre türüdür. Ne zaman bir veri türü sorunuyla karşılaşsam, onu numaralandırarak çözmek için yalnızca en aptalca yolu kullanırım. kod aşağıdaki gibi gösterilir:

/ ** Parametre temel bir veri türüne sahip olduğunda, temel veri türü varsayılan olarak kullanılır * Temel türler şunları içerir: * Gerçek sayı: double, float * Tamsayı: bayt, kısa, tamsayı, uzun * Karakter: karakter * Boole değeri: boole * * / private Object getInstance (String paramType, String değeri) { switch (paramType) { case "java.lang.String": geri dönüş değeri "bayt" durumu: case "java.lang.Byte": Byte.parseByte (değer) döndür durum "kısa": Short.parseShort (değer) döndür case "int": case "java.lang.Integer": return Integer.parseInt (değer) durum "uzun": case "java.lang.Long": Long.parseLong (değer) döndür case "float": case "java.lang.Float": Float.parseFloat (değer) döndür durum "çift": case "java.lang.Double": Double.parseDouble (değer) döndür case "boolean": case "java.lang.Boolean": dönüş Boolean.parseBoolean (değer) varsayılan: JSONObject jsonObject = JSON.parseObject (değer) // JSONObject'e dönüştür jsonObject döndür } }

Yukarıdaki kodda gösterildiği gibi, gelen parametreler karşılık gelen paketleme tipine dönüştürülür. Arayüz imzası int olduğunda, girdi parametresi nesnesinin Tamsayı olması da mümkündür. $ İnvoke (String methodName, String paramsTypes, Object nesneleri) olduğundan, paramsTypes yöntem imzasını kontrol eder ve ardından nesneleri çağırma için belirli hizmete iletir.

ReferenceConfig başlatma öncelik seti başlatma true olarak

Uzaktan bir Dubbo hizmeti isteği başlatmak için genel çağrıyı kullanın. Çağrıyı başlatmadan önce, bir GenericService genericService = referenceConfig.get () işlemi vardır. Dubbo hizmeti çalışmadığında, çağrı ilk kez başlatıldıktan sonra ref başlatma işlemi gerçekleştirilir. ReferenceConfig başlatma referans kodu aşağıdaki gibidir:

private void init () { eğer (başlatılmış) { dönüş; } başlatılmış = doğru; if (interfaceName == null || interfaceName.length () == 0) { yeni IllegalStateException (" < dubbo: referans arayüzü = \ "\" / > arayüz boşa izin vermiyor! "); } // Tüketici global yapılandırmasını alın checkDefault (); appendProperties (bu); eğer (getGeneric () == null getConsumer ()! = null) { setGeneric (getConsumer (). getGeneric ()); } ... }

Sonuç olarak, ilk başlatma doğru olarak ayarlandığından, ancak daha sonra geçerli bir genel hizmet alınmadığından, genelleştirme çağrısı Dubbo sunulduktan sonra bile başarısız olacaktır.

Çözüm: Genelleme çağrısı, çağırma çağrısını yürütmek için genericService'i kullanmaktır, böylece her istek yeni bir ReferenceConfig kullanır.Get () işlemi, bir istisna bildirildiğinde veya dönüş boş olduğunda başlatıldığında, get () işlemi başlatılana kadar kaydedilmez Geçerli bir genericService alındığında, genericService'i kaydedin. Uygulama kodu aşağıdaki gibidir:

senkronize edildi (hasInit) { eğer (! hasInit) { ReferenceConfig referenceConfig = new ReferenceConfig (); // arayüz adını ayarla referenceConfig.setInterface (serviceInfoDO.interfaceName) referenceConfig.setApplication (serviceInfoDO.applicationConfig) // sürümü ayarla eğer (serviceInfoDO.version! = boş serviceInfoDO.version! = "" serviceInfoDO.version.length () > 0) { referenceConfig.setVersion (serviceInfoDO.version) } if (StringUtils.isEmpty (serviceInfoDO.refUrl) || serviceInfoDO.refUrl.length () < = 0) { yeni NullPointerException oluştur ("The'refUrl '$ {serviceInfoDO.refUrl} olmamalıdır, lütfen doğru'refUrl' geçirdiğinizden emin olun") } referenceConfig.setUrl (serviceInfoDO.refUrl) referenceConfig.setGeneric (true) // Genelleştirilmiş bir arayüz olarak bildirildi genericService = referenceConfig.get () eğer (null! = genericService) { hasInit = true } } }

1.4 Tek bir sunucu, birden çok özdeş ve farklı hizmetin dağıtımını destekler

Gereksinimlere göre, iki sorunun çözülmesi gerekir: 1. Sunucu çalışması sırasında harici API'nin Jar paketini yükleme sorunu, 2. Aynı arayüzle birden fazla hizmet kaydederken aynı addaki sorun.

1.4.1 Dinamik harici Jar paketi yüklemesinin tasarım şeması

Çözüm 1: Harici Jar paketi için ayrı bir URLClassLoader oluşturun ve ardından genelleştirme kaydı sırasında kaydedilmiş ClassLoader'ı kullanın, geri arama sırasında currentThread'in ClassLoader'ını değiştirin ve aynı API arayüzünün farklı sürümlerinin alaylarını gerçekleştirin.

Kullanılamama nedeni: final Wrapper wrapper = Wrapper.getWrapper (proxy.getClass (). GetName (). İndexOf ('$') JavassistProxyFactory'de < 0? Proxy.getClass (): tür);

Wapper elde edildiğinde, makeWrapper'ın varsayılan kullanımı ClassHelper.getClassLoader (c) 'dir; bu da AppClassLoader'ın her zaman kullanılmasına neden olur. API bilgileri bir WapperMap'te depolanacak ve tüketiciler bunu talep ettiğinde, ilgili API bilgilerini bulmak için ilk olarak bu Harita alınacaktır.

Sonuca götürür:

  • 1. Genelleme kaydı nedeniyle, sınıf AppClassLoader'da değil. CurrentThread kümesine sahip ClassLoader etkili olmaz.
  • 2. dubbo, API bilgilerini depolamak için yalnızca bir Haritaya sahip olduğundan, yayınlanan hizmet için yalnızca bir API kümesi olabilir.

çözüm:

  • API bilgilerini harici Jar paketine yüklemek için özel bir ClassLoader kullanın.
  • Bir Mock terminali, bir dizi API bilgisini depolar ve API güncellenirken sunucunun yeniden başlatılması gerekir.

İkinci seçenek, program başladığında özel bir TestPlatformClassLoader kullanın. Yine de, TestPlatformClassLoader tarafından yönetilen her Jar paketi için karşılık gelen ApiClassLoader'ı oluşturun.

Kullanılamama nedeni:

Bir sahte terminalde konuşlandırılırken, ClassLoader'ı ayarlamak için -Djava.system.class.loader kullanıldığında, JVM başlangıç parametreleri kullanılamaz. Çünkü TestPlatformClassLoader, mevcut JVM'de değil, proje kodunda var. Ayrıntılı parametreler aşağıdaki gibidir:

-Djava.system.class.loader = com.youzan.test.mocker.internal.classloader.TestPlatformClassLoader

Çözüm: (mimar Wang Xing tarafından sağlanmıştır)

  • Program başlangıcı için gerekli ClassLoader, başlangıç parametreleri ve mainClass bilgilerini kaydetmek için özel Runnable () kullanın.
  • Program başladığında, yeni bir İş Parçacığı oluşturun, özel bir Çalıştırılabilir () geçirin ve ardından iş parçacığını başlatın.

3. Çözüm: Hizmeti başlatmak için özel bir kapsayıcı kullanın

Aşağıdaki şekilde gösterildiği gibi uygulama başlatma süreci (aşağıdaki şekil Youzan mimarlık ekibinden alınmıştır)

Java'nın sınıf yüklemesi, AppClassLoader ile başlayıp aşağıdan yukarıya doğru arama yaparak ve yukarıdan aşağıya yükleme yaparak ebeveyn yetkilendirmesinin tasarım modelini takip eder.Bu nedenle, özel bir ClassLoader olmadığında, uygulama, çalıştırılacak AppClassLoader aracılığıyla Ana başlangıç sınıfını yükleyerek başlar.

ClassLoader'ı özelleştirdikten sonra, ClassLoader sistemi, konteynerin özel ClassLoader'ı olarak ayarlanacak ve özel ClassLoader, sınıf işlemini başlatmak için Main'i yeniden yükleyecektir.Bu sırada, sonraki tüm sınıf yüklemeleri önce arama için özel ClassLoader'a gidecektir.

Zorluk: Uygulamanın varsayılan sistem sınıfı yükleyicisi AppClassLoader'dır ve Yeni bir nesne oluştururken özel bir ClassLoader'dan geçmeyecektir.

Akıllı: Main işlevi başladığında, AppClassLoader, Main'i ve kapsayıcıyı yükler, kapsayıcı Main sınıfını alır, Main'i özel bir ClassLoader ile yeniden yükler ve sistem sınıfı yükleyiciyi özel bir sınıf yükleyici olarak ayarlar.Bu sırada, New nesnesi özelleştirilecektir. ClassLoader.

1.4.2 Tasarım şeması seçimi

Yukarıdaki üç şema aslında uygulama sürecinde bir yinelemedir. Nihai sonuçlar:

  • 1. Çözüm: Harici Jar paketi için ayrı bir URLClassLoader oluşturmaya devam edin.
  • Çözüm 2: Özel TestPlatformClassLoader'ı koruyun ve her Jar paketi ile ClassLoader'daki API arasındaki yazışmayı kaydetmek için TestPlatformClassLoader'ı kullanın.
  • Çözüm 3: Özel bir kapsayıcıyla başlayın, yeni bir iş parçacığı başlatın, concurrentThreadClassLoader öğesini TestPlatformClassLoader olarak ayarlayın ve Main.class'ı bu iş parçacığı ile başlatın.

1.4.3 Karşılaşılan çukur

Javassist tarafından oluşturulan Sınıf adı aynıdır

Javassist tarafından oluşturulan Sınıfı kullanın, her Sınıfın Hizmet Zinciri + sınıfAdı'ndan oluşan ayrı bir SınıfAdı vardır. Aynı ada sahip bir sınıfı yeniden oluştururken, yeni ClassPool () kullanmak bile tamamen izole edilemez. Çünkü Class oluşturulduğunda Class < ? > clazz = ctClass.toClass () varsayılan olarak aynı ClassLoader'ı kullanır, bu nedenle "ad: ** için yinelenen sınıf tanımına teşebbüs edildiğini" bildirir.

Çözüm: ClassName'e dayalı olarak rastgele oluşturulmaz, bu nedenle yeni bir SecureClassLoader (ClassLoader parent) yalnızca yeni sınıfı yüklemek için önceki ClassLoader'a dayalı olarak oluşturulabilir.Eski ClassLoader Java otomatik GC'ye dayanır. kod aşağıdaki gibi gösterilir:

Sınıf < ? > clazz = ctClass.toClass (yeni SecureClassLoader (clz.classLoader))

Not: Program henüz test edilmedi ve bellek taşmasına neden olup olmayacağını bilmiyorum.

İkincisi, plan gerçekleştirildi

2.1 Sahte fabrika genel tasarım mimarisi

2.2 Mocker konteyner tasarım çizimi

2.3 İki taraflı paket yönetimi sıra diyagramı

2.4 Mocker konteyner hizmet kayıt sırası diyagramı

Üç, senaryoları destekleyin

3.1 Öğe ve isim açıklaması

Yukarıdaki şekil temel unsurların bileşimini göstermektedir ve ilgili terimler aşağıdaki şekilde açıklanmıştır:

  • Tüketici: Arayan kişi DubboRequest'i başlatır
  • Temel hizmet: Service Chain logosu olmadan normal hizmet
  • Mock hizmeti: Mock fabrikası tarafından oluşturulan dubbo hizmeti
  • ETCD: Hem Temel hizmetin hem de Mock hizmetinin kayıtlı olduğu kayıt merkezi
  • Varsayılan hizmet şeffaf iletimi: arayüzde taklit gerektirmeyen yöntemler için, genel olarak doğrudan Temel hizmeti arayın
  • Özel Hizmet (CF): Kullanıcılar genelleştirilmiş bir dubbo hizmetini kendileri başlatır (Not: kayıt defterine kaydolmaya gerek yoktur ve Hizmet Zinciri logosuna gerek yoktur)

3.2 Destek senaryolarının kısa açıklaması

Senaryo 1: Hizmet Zinciri isteği olmadan (Sahte hizmet kullanılmadığında)

Tüketiciler, Kayıt merkezinden Temel ortam hizmetinin IP + PORT'unu alır ve doğrudan Temel ortam hizmetini talep eder.

Senaryo 2: Service Chain isteği ile Mock hizmeti, JSON dönüş uygulamasını benimser

Tüketiciler kayıt defterinden iki adres alırlar: 1. Temel ortam hizmetinin IP + PORT'u, 2. Hizmet Zinciri işaretiyle (Mock hizmeti) hizmetin IP + PORT'u. Yöntemi Mock hizmetinde istemek için Hizmet Zincirine göre rotayı arayın ve Mock verilerini iade edin.

Senaryo 3: Hizmet Zinciri ile İstek, Sahte hizmetin elde etmek için bu yöntemi yok

Tüketiciler kayıt defterinden iki adres alırlar: 1. Temel ortam hizmetinin IP + PORT'u, 2. Hizmet Zinciri işaretiyle (Mock hizmeti) hizmetin IP + PORT'u. Sahte servis talep etmek için Servis Zincirine göre yönlendirme çağırın. Mock hizmetindeki bu yöntem varsayılan hizmet şeffaf iletimi olduğundan, Mock hizmeti doğrudan temel hizmeti genelleştirir ve verileri döndürür.

Senaryo 4. Hizmet Zinciri istek başlığıyla, Mock hizmeti özel hizmet (CR) tarafından uygulanır

Tüketiciler kayıt defterinden iki adres alırlar: 1. Temel ortam hizmetinin IP + PORT'u, 2. Hizmet Zinciri işaretiyle (Mock hizmeti) hizmetin IP + PORT'u. Sahte servis talep etmek için Servis Zincirine göre yönlendirme çağırın. Mock hizmetindeki bu yöntem özel bir hizmet (CF) olduğundan, Mock hizmeti kullanıcının dubbo hizmetini çağırır ve verileri döndürür.

Senaryo 5, Hizmet Zinciri istek başlığıyla, Mock hizmeti bu yöntemi uygulamıyor ve bu yöntem, Hizmet Zinciri ile InterfaceB yöntemini çağırıyor

Tüketiciler InterfaceA Metodunu aradıklarında kayıt merkezinden iki adres alırlar: 1. Temel ortam hizmetinin IP + PORT'u, 2. Hizmet Zinciri işaretli IP + PORT'u (Mock servisi). ArabirimA'nın Sahte hizmetini talep etmek için Hizmet Zincirine göre yolu çağırın. Mock hizmetindeki bu yöntem varsayılan hizmet şeffaf iletimi olduğundan, Mock hizmeti, InterfaceA'nın Temel hizmetinin 3. yöntemini doğrudan genelleştirir.

Ancak, ArayüzA Metodu 3 Arayüz B Metod 2'yi çağırdığı için, kayıt defterinden iki adres alınır: 1. Temel ortam hizmetinin IP + PORT'u, 2. Hizmet Zinciri işaretiyle (Mock servisi) hizmetin IP + PORT'u. Servis Zinciri tanımlayıcısı her zaman tüm talep zincirinde rezerve edildiğinden, yol Servis Zincirine göre çağrılır ve son olarak talep Arayüz B'nin Mock servisine gönderilir ve veriler geri döndürülür.

Senaryo 6, Hizmet Zinciri istek başlığına sahip Hizmet Zinciri hizmeti ve zaten mevcut olan Sahte

Service Chain Service Chain Mock Service Chain ETCD Mock 2345

TiDB'nin 58 Gruptaki uygulaması ve uygulaması
önceki
Çok seviyeli önbellek çözümü (TMC)
Sonraki
Prometheus-spring-boot-starter yönetimi istisna bildirimi mesajı hatırlatıcısı
Genel jar, dinamik konfigürasyon ve bileşen düzenlemesine dayalı üye görev merkezi sistemi tasarımı
api izleme sistemi - apimonitor
Bir dahaki sefere öldürüldüğümde, serialVersionUID'yi gelişigüzel değiştirmeye cesaret edemeyeceğim
Düşük kodlu hızlı geliştirme platformu JEPaaS
Tam bağlantı izleme: çözüme genel bakış ve karşılaştırma | gerçekten kuru
hanbo-push dağıtılmış mesaj push, IM servisi
Ali Great God, mikro hizmet mimarisindeki API ağ geçidi uygulamasını paylaşıyor
mallcloud-platform, springboot bulutuna dayalı bir alışveriş merkezi projesidir
MyBatis bu 9 tasarım modelini içerir, kaç tanesini biliyorsunuz?
Hurricane Sheep Knife Ashe için standart hale geldi, Polar Ranger Genting'e hükmediyor
Çift sunucu beş güçlü en iyi tek envanter: Nuoshou Jianji güçlü bir şekilde hakim
To Top