Redis, kendi sistemine nüfuz etmek için bir numara öğrenir ve N arıza çözüm paketleriyle birlikte gelir | Kuvvet Projesi

Yazar | Mark_MMXI

Kaynak | CSDN blogu, sorumlu editör | Xi Yan

Üretildi | CSDN (ID: CSDNnews)

Önbellek, yüksek eşzamanlılık durumlarında DB baskısını azaltmak ve iş sistemi deneyimini iyileştirmek için var. İş sistemi verilere erişir ve önce önbellekte sorgular.Önbellekte veri varsa doğrudan önbelleğe alınan verileri geri döndürür, aksi takdirde veritabanını sorgular ve ardından değeri döndürür.

Redis, bir önbelleğe alma aracı ve önbelleğe alma çözümüdür, ancak Redis'in tanıtımı, önbellek penetrasyonu, önbellek bozulması ve önbellek çığ gibi sorunlara neden olabilir. Bu makale önbellek çığ sorununu derinlemesine analiz etmekte, sahne modeli üzerinden anlayışı derinleştirmekte ve sahneye dayalı ilgili çözümlerle çözmeye çalışmaktadır.

Önbelleğe alma ilkesi ve Redis çözümü

Öncelikle, önbelleğin çalışma prensibi şemasına bir göz atalım:

Redis, temelde Anahtar-Değer türü bir bellek veritabanıdır. Saf bir bellek işlemi olduğu için Redis mükemmel bir performansa sahiptir ve saniyede 100.000'den fazla okuma ve yazma işlemini gerçekleştirebilir. Redis'in bir diğer avantajı, String, List, Set, Sorted Set, hash, vb. Gibi çeşitli veri yapılarının kaydedilmesini desteklemesidir.

Önbellek çığ

2.1 Önbellek çığ açıklaması

Çığ önbellek durumu, belirli bir anda büyük ölçekli bir önbellek arızası meydana geldiğinde, örneğin, önbellek hizmetinizin çalışmadığı ve DB'nin çok sayıda istek baskısını doğrudan yükleyip askıda kalmasına neden olduğu anlamına gelir.

2.2 Önbellek çığını simüle etme

Önbellek çığının açıklamasına göre, aslında şu noktaları simüle etmemiz gerekiyor:

  • Büyük ölçekli önbellek aynı anda başarısız olur.

  • Hata anında çok sayıda sorgu isteği DB'ye ulaşır

  • @Test public void testQuery {ExecutorService es = Executors.newFixedThreadPool (10); int loop = 1000; int init = 2000; // 1k anahtarları sorgulayın ve (int i = init; i için önbelleğe koyun) < loop + init; i ++) {userService.queryById (i);} // Önbellek sona erme süresi 1s, aynı anda 1'lerin dolmasını bekleyin {Thread.sleep (1000);} catch (Exception e) {e.printStackTrace;} // Çok iş parçacıklı çılgın sorgu kullanılarak başlatıldı (int i = 0; i < 100; i ++) {es.execute (- > {for (int k = init; k < döngü + init; k ++) {userService.queryById (k);}});}}

    Çökmeyi hızlandırmak için veritabanına maksimum bağlantı sayısı 5'e ayarlandı ve veritabanı tablosunun veri hacmi bir milyon seviyeye çıkarıldı.

    Ardından test programını çalıştırın ve kısa süre sonra program bir hata bildirir ve durur. Ayrıntılı hata aşağıdaki gibidir:

    "Pool-1-thread-12" iş parçacığında özel durum org.springframework.data.redis.RedisSystemException: Redis istisnası; yuvalanmış istisna io.lettuce.core.RedisException: org.springframework.data.redis.connection.lettuce adresinde bağlantı kapatıldı .LettuceExceptionConverter.convert (LettuceExceptionConverter.java:74), org.springframework.data.redis.connection.lettuce.LettuceExceptionConverter.convert (LettuceExceptionConverter.java:41), org.springframework.data.redis.PassThroughExceptionTranshrception (PassThroughExceptionTranshrception. : 44) org.springframework.data.redis.FallbackExceptionTranslationStrategy.translate (FallbackExceptionTranslationStrategy.java:42) at org.springframework.data.redis.connection.lettuce.LettuceConnection.convertLettuceAccessException (LettuceConnection.java:270). org.springframework.data.redis.connection.lettuce.Le'de data.redis.connection.lettuce.LettuceStringCommands.convertLettuceAccessException (LettuceStringCommands.java:799) ttuceStringCommands.get (LettuceStringCommands.java:68), org.springframework.data.redis.connection.DefaultedRedisConnection.get'de (DefaultedRedisConnection.java:260) org.springframework.data.redis.cache.DefaultRedisCache $RedisCacheWriter. $ .java: 109) org.springframework.data.redis.cache.DefaultRedisCacheWriter.execute (DefaultRedisCacheWriter.java:242), org.springframework.data.redis.cache.DefaultRedisCacheWriter.get (DefaultRedisCacheWriter.java:109). org.springframework.cache.support.AbstractValueAdaptingCache.get (AbstractValueAdaptingCache.java:58) at org.springframework.cache.interceptor.AbstractCache'de springframework.data.redis.cache.RedisCache.lookup (RedisCache.java:88) AbstractCacheInvoker.java:73) org.springframework.cache.interceptor.CacheAspectSupport.findInCaches (CacheAspectSupport.java:554), org.springframework.cache.interceptor.CacheAspectSupport.findCachedItem (CacheAspectSupport.java:519) at .interceptor. CacheAspectSupport.execute (CacheAspectSupport.java:401), org.springframework.cache.interceptor.CacheAspectSupport.execute'de (CacheAspectSupport.java:345) org.springframework.cache.interceptor.CacheInterceptor.invoke'da (CacheInterceptor.invoke61) önbellek .springframework.aop.framework.ReflectiveMethodInvocation.proceed (ReflectiveMethodInvocation.java:186), org.springframework.aop.framework.CglibAopProxy $ CglibMethodInvocation.proceed (CglibAopProxy $.java:747) org.springframework. .intercept (CglibAopProxy.java:689), com.example.demo.user.service.impl.UserServiceImpl $$ EnhancerBySpringCGLIB $$ ba6638d2.queryById ( < oluşturulmuş > ) com.example.demo.DemoApplicationTests adresinde $ 1.run (DemoApplicationTests.java:55) java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1149), java.util.concurrent.ThreadPoolExecutor $ Worker.run (ThreadPoolExecutor) .java: 624) java.lang.Thread.run'da (Thread.java:748) Neden: io.lettuce.core.RedisException: Bağlantı io.lettuce.core.protocol.DefaultEndpoint.validateWrite (DefaultEndpoint.java: 195) io.lettuce.core.protocol.DefaultEndpoint.write adresinde (DefaultEndpoint.java:137) io.lettuce.core.protocol.CommandExpiryWriter.write (CommandExpiryWriter.java:112) 2020-03-0822: 31: 14.432 HATA 37892 --- com.alibaba.druid.pool.DruidDataSource: bağlantı oluştur SQLException, url: jdbc: mysql: // localhost: 3306 / redis_demo? UseUnicode = truecharacterEncoding = UTF-8allowMultiQueries = trueuseJDBCCompliodeTezone 1040, eyalet 08004 java.sql.SQLNonTransientConnectionException: Veri kaynağı bağlantı kurulmasını reddetti, sunucudan mesaj: "Çok fazla bağlantı"

    Temel sorun veritabanı bağlantısının dolu olması ve sorgu için veritabanı bağlantısının elde edilememesidir.Bu olay önbellek çığının etkisidir.

    2.3 Önbellek çığını çözün

    2.3.1 Çığ sahnelerini analiz edin

    Resmi kullanarak, aslında, üst akış basıncını taşıyan bir redis yoktur.

    Aslında, bu resimden yola çıkarak, genel uygulamalarımız için müşteri, uygulamadan veritabanına tüm bağlantı sürecine erişir. Aslında, yoğun trafikle karşılaştığımızda, trafik tamponlaması için genellikle "ters üçgen" modelini kullanırız. "Ters üçgen" modeli

    "Ters üçgen" modeli sayesinde, sistem eşzamanlılık ihtiyaçlarına göre optimize edilir. Çığ olması durumunda, "ters üçgen" modeline göre optimizasyon yapabilirsiniz. Çığın teoride tamamen çözülmesinin bir yolu olmadığını ve sonunda donanım konfigürasyonunun iyileştirilmesi gerekebileceğini unutmayın.

    2.3.1 Çığ optimizasyon planı

    Analizden sonra çığın çözümü:

    1. Çığları bir dereceye kadar hafifletebilen rastgele önbellek sona erme süresi 2. Önbelleği güncellemek için kilitler veya kuyruklar kullanın ve sona erme bayrakları ayarlayın 3. Çok seviyeli önbellek elde etmek için yerel önbellek ekleyin 4. Devre kesici ekleyin ve tampon basıncına akım sınırını düşürün.

    2.3.1.1 Rastgele önbellek süresi

    Rastgele önbellek süresi, çok sayıda kısayol tuşunun aynı anda geçersiz kılınmasını önlemek için tasarlanmıştır.

    Sonra, biz Redis + SpringBoot + SpringCache temel projesi Bu projeyi oluşturun ve uygulamaya devam edin.

    SpringCache kullanıldığından, en uygun çözümümüz, ifadeler gibi @ Önbelleğe alınabilir gibi ek açıklamalara doğrudan parametreler eklemektir, böylece veriler önbelleğe alındığında sona erme süresi ifade / parametre değerine göre tanımlanır.

    Öyleyse ilk olarak orijinal RedisCache'nin koyma mantığını kontrol edelim

    RedisCacheManager, Önbellek oluşturur

    korumalı RedisCache createRedisCache (Dize adı, @able RedisCacheConfiguration cacheConfig) {yeni RedisCache (ad, this.cacheWriter, cacheConfig! =? cacheConfig: this.defaultCacheConfig);}

    RedisCache.class'ı açın ve aşağıdaki gibi put yöntemini kontrol edin:

    public void put (Object key, @able Object value) {Object cacheValue = this.preProcessCacheValue (value); if (! this.isAllowValues cacheValue ==) {throw new IllegalArgumentException (String.format ("Cache '% s' değil '' değerlerine izin verin. '@ Önbelleğe alınabilir (= \ "# sonuç == \")' aracılığıyla depolamaktan kaçının veya RedisCache'yi RedisCacheConfiguration aracılığıyla '' izin verecek şekilde yapılandırın. ", this.name));} else {this.cacheWriter.put (this.name, this.createAndConvertCacheKey (anahtar), this.serializeCacheValue (cacheValue), this.cacheConfig.getTtl);}}

    Burada this.cacheConfig.getTtl, önbelleğin sona erme süresidir.Verilerin önbellek sona erme süresinin, genel önbellek yapılandırmasından elde edilen sona erme süresinden yapılandırıldığını ve elde etmem gereken şey, belirli bir önbellek altındaki her anahtarın rastgele zamanlarda süresinin dolmasına izin vermek olduğunu görebilirsiniz. Yani burada this.cacheConfig.getTtl'yi değiştirmemiz gerekiyor, sadece RedisCache oluşturduğumuzda bu değeri değiştirmemiz gerekiyor.

    1. Dize kodunu java'ya dayalı olarak dinamik olarak çalıştırın ve sona erme süresini döndürün.

    Spring.expression'a dayalı ExpressService'i uygulayın

    / ** * @title: ExpressUtil * @projectName redisdemo * @description: dize kodunu dinamik olarak yürütün * @author lps * @date 2020/3/912: 01 * / @ Slf4jpublic class ExpressService { özel ExpressionParser spelExpressionParser; private ParserContext parserContext; // ifade analizi bağlamı özel StandardEvaluationContext EvaluationContext; public static enum ExpressType {/ ** * $ {} İfade formatı * / TYPE_FIRST, / ** * # {} İfade formatı * / TYPE_SECOND} private static final String PRE_TYPE_1 = "$ {"; private static final String PRE_TYPE_2 = "# {"; özel statik nihai Dize SUF_STR = "}"; private ExpressService (String pre, String suf) {spelExpressionParser = new SpelExpressionParser; log.debug ("Expression prefix = {}, expression sonix = {}", pre, suf); EvaluationContext = new StandardEvaluationContext; // Harita analizi ekle Scheme EvaluationContext.addPropertyAccessor (new MapAccessor); parserContext = new TemplateParserContext (pre, suf);} / ** * * < p > * İfade işleme hizmeti nesnesi oluşturun Varsayılan, # {} biçim ifadesi oluşturmaktır. İfade biçimini ExpressType aracılığıyla belirtin. İki tür $ {} ve # {} vardır * < / p > * * * @param type * İfade biçimi türü * @return İfade analiz nesnesi * / public static ExpressService createExpressService (ExpressType türü) {if (type == ExpressType.TYPE_FIRST) {log.debug ("İfade, ifade oluştur Önek = {} ", PRE_TYPE_1); yeni ExpressService döndür (PRE_TYPE_1, SUF_STR);} else {yeni ExpressService (PRE_TYPE_2, SUF_STR);} } public Object expressParse (String express, Object data) Exception {log.debug ("parse expression information = {}", express); Expression expression = spelExpressionParser.parseExpression (express, this.parserContext); return expression.getValue (EvaluationContext , veri);} }

    Test araması:

    @Ölçek public void testExpress {ExpressService express = ExpressService.createExpressService; deneyin {// Fixed timeout System.out.println ("ttl =" + express.expressParse ("# {60}",)); // Rastgele son kullanım oluşturmak için çağrı yöntemi Zaman System.out.println ("ttl =" + express.expressParse ("# {T (org.apache.commons.lang3.RandomUtils) .nextInt (60,200)}",));} catch (İstisna e) {e .Yığın İzi yazdır;}}

    2. Tasarım adı ekleme ttl kuralları

    CreateRedisCache yalnızca iki parametre adına ve cacheConfig'e sahip olduğundan ve yalnızca ad tek bir önbellek için olduğundan ve cacheConfig genel bir önbellek için olduğundan, ad parametresinde önbelleğin adını ve sona erme zamanını belirtmek için kurallar tasarlamamız gerekir.

    Ad atama kuralı: ad | ttlFun

    örneğin: @Cacheable (cacheName = "test | # {T (org.apache.commons.lang3.RandomUtils) .nextInt (60,200)}") @Cacheable (cacheName = "test | # {60}")

    3. Adı ayrıştırmak için kod yazın

    / ** * Ayırıcı | * / private static final String SEPERATE_LINE = "|"; public MyRedisCacheManager (RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {super (cacheWriter, defaultCacheConfiguration);} korumalı RedisCache createRedisCache (Dize adı, @able RedisCacheConfiguration cacheConfig) {// `` ad atama kuralı: ad | ttlFun `if (name.contains (SEPERATE_LINE)) {String cacheName = name.substring (0, name.indexOf (SEPERATE_LINE) )); Dize ifadesi = name.substring (name.indexOf (SEPERATE_LINE) +1); {ExpressService express = ExpressService.createExpressService; long ttl = Long.parseLong (express.expressParse (ifade,) .toString); cacheConfig = cacheConfig .entryTtl (Duration.ofSeconds (ttl)); super.createRedisCache (cacheName, cacheConfig) döndür; } catch (Exception e) {e.printStackTrace; return super.createRedisCache (name, cacheConfig);} } return super.createRedisCache (ad, cacheConfig); }

    4. CacheConfig'i değiştirin

    Orijinal RedisManager'ı # 3 ile yazılmış MyRedisManager ile değiştirin

    / ** * Önbellek yöneticisini yapılandırın * / @Bean public CacheManager cacheManager (RedisConnectionFactory fabrikası) {// Temel nokta, yay önbellek ek açıklamalarının kullandığı serileştirme buradan gelir, bu yapılandırma olmadan jdk'nin kendi serileştirmesi kullanılır, aslında bu değildir Kullanımı etkiler, ancak insan gözünün RedisCacheConfiguration'ı tanıması uygun değildir cacheConfig = RedisCacheConfiguration.defaultCacheConfig // Anahtarı bir dizeye seri hale getirin. SerializeKeysWith (RedisSerializationContext.SerializationPair.fromSerializer (yeni StringRediserialSerializationCtextWsonize değeri) //. SerializationPair. cacheConfig) .build; * / // RedisCacheManager'ı MyRedisCacheManager olarak değiştirin MyRedisCacheManager redisCacheManager = new MyRedisCacheManager (RedisCacheWriter.nonLockingRedisCacheWriter (fabrika), cacheConfig); geri dönüş} redisCache

    5. Test etme

    Birim testleri yazın

    @Ölçek public void testQueryIdWithExpress {Assert.assertNot (userService.queryById (3333));}

    Sorgunun önbellek tanımını yeniden tanımlayın

    @Override @Cacheable (value = "ca1 | # {60}", key = "#id", until = "# result ==") // @Cacheable (value = "ca1 | # {T (org.apache.commons. lang3.RandomUtils) .nextInt (100,200)} ", key =" #id ", without =" # result == ") public User queryById (int id) {return this.user Dao.queryById (id);}

    Değer = ca1 | # {60} olduğunda, kalan 58'ler Redis'in TTL'sini kontrol ederek

    Değer = ca1 | # {T (org.apache.commons.lang3.RandomUtils) .nextInt (100,200)} olduğunda, 100-220 aralığında rastgele saniye sayısı, Redis TTL'yi kontrol ederek, kalan sayı 107s

    Şu anda, rastgele sona erme süresi rastgele yöntem kullanılarak gerçekleştirilebilir.Gauss (normal) dağılımına uyan rastgele sayıyı seçmek daha iyidir.

    yeni Random.nextGaussian

    2.3.1.2 Mutex kuyruğu

    Endüstride fiyatları karşılaştırmak için yaygın bir uygulama, değer boşken anahtara dayalı bir değer elde etmek, onu kilitlemek ve verileri veritabanından yükledikten sonra kilidi serbest bırakmaktır. Diğer iş parçacıkları kilidi alamazsa, bir süre bekleyin ve tekrar deneyin. Burada, dağıtılmış kilitlerin dağıtılmış bir ortamda kullanılması gerektiği ve tek bir makine için normal kilitlerin (senkronize, Kilit) yeterli olduğu belirtilmelidir.

    Bu düşünce tarzı açıktır ve aynı zamanda veri tabanı üzerindeki baskıyı bir dereceye kadar azaltır, ancak kilit mekanizması mantığın karmaşıklığını arttırır ve verimi düşürür ki bu kalıcı bir tedavi olmaktan çok geçici bir çözümdür.

    1. muteksi ayarlamak için setnx kullanın

    public User queryById (int id) {try {if (redisTemplate.hasKey (id + "")) {return (User) redisTemplate.opsForValue.get (id + "");} else {// Kilidi al if (lock (id + " ")) {// Veritabanı sorgusu Kullanıcı user = user Dao.queryById (id); redisTemplate.opsForValue.set (id +" ", user, Duration.ofSeconds (3000)); // redisTemplate.delete kilidini (LOCK_PREFIX +" id) ");}}} catch (İstisna e) {e.printStackTrace;} return (Kullanıcı) redisTemplate.opsForValue.get (" "+ id);} private static String LOCK_PREFIX =" önek "; özel statik uzun LOCK_EXPIRE = 3000; / ** * Karşılıklı dışlama kilidi uygulaması * / public boolean lock (String key) {String lock = LOCK_PREFIX + key; return (Boolean) redisTemplate.execute ((RedisCallback) bağlantısı- > {long expireAt = System.currentTimeMillis + LOCK_EXPIRE + 1; // SETNX Boolean alım = connection.setNX (lock.getBytes, String.valueOf (expireAt) .getBytes); if (acquire) {return true;} else {bayt değeri = connection.get (lock.getBytes); if (Objects.non (value) value.length > 0) {long expireTime = Long.parseLong (new String (value)); // Kilidin süresinin dolup dolmadığını belirle if (expireTime < System.currentTimeMillis) {bayt oldValue = connection.getSet (lock.getBytes, String.valueOf (System.currentTimeMillis + LOCK_EXPIRE + 1) .getBytes); döndür Long.parseLong (yeni String (eskiDeğer)) < System.currentTimeMillis;}}} dönüş yanlış;});}

    2.3.1.3 Önbelleği güncellemek için son kullanma bayrağını ayarlayın

    Arabelleğe alma etkisi elde etmek için bazı istekleri engellemek için önbelleği düzenli olarak güncelleyin ve ayrıca anahtarı hiçbir zaman sona ermeyecek şekilde ayarlayabilirsiniz.

    2.3.1.4 Çok düzeyli önbellek

    Bu çözüm esas olarak redis çalışmadığında veya anahtar önbelleğin ortasında güncellendiğinde ortaya çıkar, bu da iş uygulamalarına yanıt verebilir ve baskıyı azaltabilir

    2.3.1.5 Sigorta azaltılmış akım sınırı

    Bu çözüm, daha düşük basıncı azaltmak için talep akışını doğrudan iş uygulamasının üzerinde kontrol etmektir.

    Orijinal bağlantı: https://blog.csdn.net/qq_28540443/article/details/104746655

    Yapay zeka devinin Google DeepMind'ını derinden ortaya çıkarın
    önceki
    Apple'ın resmi web sitesi tüm iPhone 8 serilerini kaldırıyor; Alibaba "Alibaba Bulut Konferansı" nı başlattı; deepin 20 BETA yayınlandı | Geek Manşet
    Sonraki
    İnternetin karşı karşıya olduğu en büyük tehlikelerden biri olarak, mevcut DDoS trendi nedir?
    Yeni bir işlemci oluşturmak neden zordur?
    Platform günlük 700 milyon ziyarete direniyor ve Ar-Ge kalite kontrol süreci tam olarak açıklanıyor
    Derinlemesine kuru ürünler! Derin öğrenme eğitim performansı birkaç kez nasıl geliştirilir?
    Eski Baidu baş mimarı kendi işini kurdu ve iki yılda on milyonlarca dolar topladı. Yeni yapay zeka ilaçlarının geliştirilmesinin altın bir on yılı başlatacağını söyledi.
    Nginx'ten Pandownload'e, programcılar hapishane programlamasından nasıl kaçınabilir?
    "Makine öğrenimini kullanmak hala zor!"
    ABD borsa kargaşasının ve Ruixing'in sahtekarlığının gölgesi altında, ABD'de halka açıldı, Kingsoft Cloud neden bu kadar endişeli?
    Ningbo Demiryolunun Chuanshan Liman İstasyonu resmen açıldı
    Bir başka tanınmış emlak şirketi iflas etti ve binlerce malikane yarı fiyatına başladı! Hefei 17 lüks evleri borcunu ödemek için açık artırmaya çıkarıldı
    iPhone 12 tasarım çizimleri ortaya çıktı; Jack Ma, dünyanın en büyük anti-salgın lideri seçildi
    Google'ın kuantum bilgi işlem donanım lideri istifa etti! Kazmak mı? Trump uyarısına kulak verin
    To Top