Spring Boot รวมสปริงแคชและมีการใช้งานแคชหลายอย่างเช่น Redis, คาเฟอีน, JCache, Ehcache ฯลฯ แต่ถ้าคุณใช้แคชเพียงหนึ่งตัวไม่ว่าจะมีการบริโภคเครือข่ายขนาดใหญ่ (เช่น Redis) หรือจะมีการใช้หน่วยความจำมากเกินไป ในหลาย ๆ สถานการณ์แคชระดับแรกและครั้งที่สองสามารถรวมกันเพื่อให้เกิดการปรับปรุงขนาดใหญ่ในประสิทธิภาพการประมวลผลของแอปพลิเคชัน
คำอธิบายเนื้อหา:
เพื่อความเข้าใจที่เรียบง่ายแคชคือการอ่านข้อมูลจากสื่อการอ่านที่ช้าลงและวางไว้ในสื่อด้วยการอ่านที่เร็วขึ้นเช่นดิสก์-> หน่วยความจำ โดยปกติเราจะจัดเก็บข้อมูลบนดิสก์เช่น: ฐานข้อมูล หากคุณอ่านจากฐานข้อมูลทุกครั้งดิสก์เองจะส่งผลกระทบต่อความเร็วในการอ่านดังนั้นจะมีแคชหน่วยความจำเช่น Redis คุณสามารถอ่านข้อมูลและใส่ในหน่วยความจำเพื่อให้เมื่อคุณต้องการรับข้อมูลคุณสามารถรับข้อมูลจากหน่วยความจำโดยตรงและส่งคืนซึ่งสามารถปรับปรุงความเร็วได้อย่างมาก อย่างไรก็ตามโดยทั่วไปแล้ว Redis จะถูกนำไปใช้กับคลัสเตอร์แยกกันดังนั้นจะมีการบริโภคในเครือข่าย IO แม้ว่าจะมีเครื่องมือเชื่อมต่อการเชื่อมต่อสำหรับการเชื่อมโยงไปยังคลัสเตอร์ Redis แต่ก็ยังมีการบริโภคในการส่งข้อมูล ดังนั้นจึงมีแคชในแอปเช่น: คาเฟอีน เมื่อมีข้อมูลที่ตรงตามเกณฑ์ในแคชแอปพลิเคชันสามารถใช้งานได้โดยตรงโดยไม่ต้องได้รับผ่านเครือข่ายไปยัง Redis ดังนั้นจึงสร้างแคชสองระดับ แคชในแอปเรียกว่าแคชระดับแรกและแคชระยะไกล (เช่น Redis) เรียกว่าแคชระดับที่สอง
แคชสปริง
เมื่อใช้แคชกระบวนการต่อไปนี้โดยทั่วไปจะมีดังต่อไปนี้:
จะเห็นได้จากแผนภูมิการไหลที่เพื่อใช้แคชการดำเนินการแคชจำนวนมากได้รับการเพิ่มตามการประมวลผลธุรกิจดั้งเดิม หากสิ่งเหล่านี้เชื่อมโยงกับรหัสธุรกิจจะมีงานซ้ำ ๆ จำนวนมากเมื่อพัฒนาและไม่เอื้อต่อการทำความเข้าใจธุรกิจตามรหัส
Spring Cache เป็นส่วนประกอบแคชที่มีให้ในแพ็คเกจสปริงบริบทตามคำอธิบายประกอบ มันกำหนดอินเทอร์เฟซมาตรฐานบางอย่าง ด้วยการใช้อินเทอร์เฟซเหล่านี้แคชสามารถทำได้โดยการเพิ่มคำอธิบายประกอบลงในวิธีการ สิ่งนี้จะหลีกเลี่ยงปัญหาของรหัสแคชที่ควบคู่ไปกับการประมวลผลทางธุรกิจ การใช้งานของสปริงแคชเป็นส่วนขยายของการห่อหุ้มวิธีการ (MethodInterceptor) ในสปริง AOP แน่นอนว่า Spring AOP ยังดำเนินการตามแง่มุม
มีอินเทอร์เฟซหลักสองตัวของสปริงแคช: แคชและแคชแมนเจอร์
อินเตอร์เฟสแคช
ให้การดำเนินการแคชที่เฉพาะเจาะจงเช่นการใส่การอ่านและการทำความสะอาดแคช การใช้งานที่จัดทำโดย Framework Spring คือ:
ยกเว้น rediscache ซึ่งอยู่ในแพ็คเกจ Spring-Data-Redis ส่วนอื่น ๆ จะอยู่ในแพ็คเกจสปริงบริบทรองรับ
#cache.javapackage org.springframework.cache; นำเข้า java.util.concurrent.callable; แคชอินเตอร์เฟสสาธารณะ {// cachename ชื่อของแคช ในการใช้งานเริ่มต้น CacheManager ผ่าน Cachename เมื่อสร้าง Cache Bean สตริง getName (); // รับแคชจริงเช่น: redistemplate, com.github.benmanes.caffeine.cache.cache <Object, Object> ฉันยังไม่พบการใช้งานจริง ฉันอาจให้ถั่วที่ได้รับแคชดั้งเดิมเพื่อให้การดำเนินการแคชหรือสถิติบางอย่างจำเป็นต้องขยายออกไป วัตถุ getNativeCache (); // รับค่าแคชผ่านคีย์โปรดทราบว่าค่าที่ส่งคืนเป็น ValueWrapper เพื่อให้เข้ากันได้กับค่า NULL ค่าส่งคืนจะถูกห่อไว้ในเลเยอร์และค่าที่แท้จริงจะได้รับผ่านวิธี GET ValueWrapper รับ (คีย์วัตถุ); // รับค่าแคชผ่านคีย์ซึ่งส่งคืนค่าจริงนั่นคือประเภทค่าส่งคืนของเมธอด <t> t รับ (คีย์วัตถุ, คลาส <t> ประเภท); // รับค่าแคชผ่านคีย์คุณสามารถใช้ valueloader.call () เพื่อเรียกวิธีการโดยใช้คำอธิบายประกอบ @cacheable ใช้วิธีนี้เมื่อแอตทริบิวต์การซิงค์ของคำอธิบายประกอบ @cacheable ถูกกำหนดค่าให้เป็นจริง ดังนั้นการซิงโครไนซ์ของแหล่งที่มากลับไปยังฐานข้อมูลจะต้องมั่นใจในวิธีการ หลีกเลี่ยงการร้องขอจำนวนมากเพื่อกลับไปที่แหล่งข้อมูลไปยังฐานข้อมูลเมื่อแคชล้มเหลว <t> ไม่ได้รับ (คีย์วัตถุ, callable <t> valueloader); // ใส่ข้อมูลที่ส่งคืนโดยวิธีการอธิบายประกอบ @Cacheable ลงในโมฆะแคชใส่ (คีย์วัตถุค่าวัตถุ); // ใส่แคชเฉพาะเมื่อไม่มีคีย์ในแคช ค่าที่ส่งคืนเป็นข้อมูลดั้งเดิมเมื่อคีย์มี ValueWrapper putifabsent (คีย์วัตถุค่าวัตถุ); // ลบแคชโมฆะ Evict (คีย์วัตถุ); // ลบข้อมูลทั้งหมดในแคช ควรสังเกตว่าในการใช้งานเฉพาะข้อมูลทั้งหมดที่แคชโดยใช้คำอธิบายประกอบ @cacheable จะถูกลบและไม่ส่งผลกระทบต่อแคชอื่น ๆ ในแอปพลิเคชันโมฆะชัดเจน (); // wrappers แคช Return Value ValueWalapper {// ส่งคืนวัตถุวัตถุแคชจริงรับ (); } // เมื่อข้อยกเว้นถูกโยนโดย {@link #get (วัตถุ, callable)}, มันจะถูกห่อหุ้มเป็นข้อยกเว้นนี้ที่ถูกโยนโดย @suppresswarnings ("serial") คลาส valueretrievalexception ขยาย runtimexception {คีย์วัตถุสุดท้ายส่วนตัว; Public ValueretRievalexception (คีย์วัตถุ, callable <?> โหลดเดอร์, ex ที่โยนได้) {super (string.format ("ค่าสำหรับคีย์ '%s' ไม่สามารถโหลดได้โดยใช้ '%s'", คีย์, โหลด), ex); this.key = key; } วัตถุสาธารณะ getKey () {return this.key; -อินเทอร์เฟซ CacheManager
ส่วนใหญ่จะให้การสร้างถั่วติดตั้งแคช แต่ละแอปพลิเคชันสามารถแยกแคชผ่าน cachename และแต่ละ cachename สอดคล้องกับการใช้งานแคช การใช้งานที่จัดทำโดย Framework Spring และการใช้งานของแคชจะปรากฏเป็นคู่และโครงสร้างแพ็คเกจก็อยู่ในรูปด้านบน
#cachemanager.javapackage org.springframework.cache; นำเข้า java.util.collection; อินเตอร์เฟสสาธารณะ cachemanager {// สร้างถั่วติดตั้งแคชผ่าน cachename การใช้งานเฉพาะจำเป็นต้องจัดเก็บถั่วติดตั้งแคชที่สร้างขึ้นเพื่อหลีกเลี่ยงการสร้างซ้ำและหลีกเลี่ยงสถานการณ์ที่เนื้อหาแคชดั้งเดิมหายไปหลังจากวัตถุแคชหน่วยความจำ (เช่นคาเฟอีน) ถูกสร้างขึ้นใหม่แคช GetCache (ชื่อสตริง); // ส่งคืนคอลเลกชัน Cachename ทั้งหมด <string> getCachenames ();}คำอธิบายประกอบร่วมกัน
@Cacheable: ส่วนใหญ่นำไปใช้กับวิธีการสอบถามข้อมูล
แพ็คเกจ org.springframework.cache.annotation; นำเข้า java.lang.annotation.documented; นำเข้า java.lang.annotation.elementtype; นำเข้า java.lang.annotation.inherited; นำเข้า Java.lang.annotation. java.lang.annotation.target; นำเข้า java.util.concurrent.callable; นำเข้า org.springframework.core.annotation.aliasfor; @target ({elementtype.method, ElementType.type})@retention Cachenames, CacheManager สร้างถั่วติดตั้งที่สอดคล้องกันผ่านชื่อ @Aliasfor ("cachenames") ค่า [] ค่าเริ่มต้น () ค่าเริ่มต้น {}; @aliasfor ("value") สตริง [] cachenames () ค่าเริ่มต้น {}; // คีย์แคชรองรับการแสดงออกของ Spel ค่าเริ่มต้นคือวัตถุที่ถูกห่อหุ้มด้วยพารามิเตอร์ทั้งหมดและคีย์สตริงแฮชโค้ด (SimpleKey) ของพวกเขา () ค่าเริ่มต้น ""; // ตัวสร้างคีย์แคชการใช้งานเริ่มต้นคือ SimpleKeyGenerator String Keygenerator () ค่าเริ่มต้น ""; // ระบุว่า cachemanager ใดที่จะใช้ String cacheManager () ค่าเริ่มต้น ""; // cache parser string cacheresolver () ค่าเริ่มต้น ""; // เงื่อนไขแคชรองรับการแสดงออกของ Spel และข้อมูลแคชเฉพาะเมื่อตรงตามเงื่อนไขที่น่าพอใจ เงื่อนไขสตริง () ค่าเริ่มต้น "" จะถูกตัดสินก่อนและหลังการเรียกวิธีการ; // แคชไม่ได้รับการอัปเดตเมื่อตรงตามเงื่อนไขการแสดงออกของ Spel ได้รับการสนับสนุนและสตริงเว้นแต่ () ค่าเริ่มต้น "" จะถูกตัดสินหลังจากเรียกใช้วิธีเท่านั้น // เมื่อกลับไปยังแหล่งที่มาของวิธีการจริงเพื่อรับข้อมูลไม่ว่าจะเป็นการซิงโครไนซ์? ถ้าเป็นเท็จวิธีการ cache.get (คีย์) เรียกว่า; ถ้าเป็นจริงเมธอด cache.get (คีย์, callable) เรียกว่า boolean sync () false ค่าเริ่มต้น;} @CacheEvict: ล้างแคชส่วนใหญ่ใช้กับวิธีการลบข้อมูล มีคุณสมบัติมากกว่าสองคุณสมบัติมากกว่าที่แคช
แพ็คเกจ org.springframework.cache.annotation; นำเข้า java.lang.annotation.documented; นำเข้า java.lang.annotation.elementtype; นำเข้า java.lang.annotation.inherited; นำเข้า Java.lang.annotation. java.lang.annotation.target; นำเข้า org.springframework.core.annotation.aliasfor; @target ({elementtype.method, ElementType.type})@retention @Cacheable // ว่าจะล้างข้อมูลแคชทั้งหมดวิธี CACHE.EVICT (คีย์) เรียกว่าเมื่อเท็จ; เมื่อเป็นจริงเมธอด cache.clear () จะเรียกว่าบูลีน allentries () เท็จเริ่มต้น; // ล้างแคชก่อนหรือหลังเรียกวิธีบูลีนก่อนหน้า () เท็จเริ่มต้น;}สปริงแคชได้ถูกรวมเข้าด้วยกันใน Spring Boot และให้การกำหนดค่าแคชที่หลากหลาย เมื่อใช้งานคุณจะต้องกำหนดค่าแคช (enum cachetype) ที่จะใช้
มีการเพิ่มสิ่งขยายเพิ่มเติมในสปริงบูตซึ่งเป็นอินเทอร์เฟซ cachemanagerCustomizer คุณสามารถปรับแต่งอินเทอร์เฟซนี้จากนั้นทำการตั้งค่าบางอย่างสำหรับ CacheManager เช่น:
แพ็คเกจ com.itopener.demo.cache.redis.config; นำเข้า java.util.map; นำเข้า java.util.concurrent.concurrenthashmap; นำเข้า org.springframework.boot.autoconfigure.cachemanagercustomizer; org.springframework.data.redis.cache.ediscachemanager; คลาสสาธารณะ rediscachemanagercustomizer ใช้ cachemanagercustomizer <rediscachemanager> {@Override โมฆะสาธารณะปรับแต่ง CacheManager.SetUSEPREFIX (เท็จ); แผนที่ <string, long> expires = new ConcurrentHashMap <String, Long> (); Expires.put ("UserIdCache", 2000L); CacheManager.Setexpires (หมดอายุ); -โหลดถั่วนี้:
แพ็คเกจ com.itopener.demo.cache.redis.config; นำเข้า org.springframework.context.annotation.bean; นำเข้า org.springframework.context.annotation.configuration;/** * @author fuwei.deng * @date 22 ธันวาคม Cacheredisconfiguration {@bean สาธารณะ rediscachemanagerCustomizer rediscachemanagerCustomizer () {ส่งคืนใหม่ rediscachemanagerCustomizer ใหม่ (); -แคชที่ใช้กันทั่วไปคือ Redis Redis ใช้อินเทอร์เฟซสปริงแคชในแพ็คเกจ Spring-Data-Redis
นี่คือข้อบกพร่องบางอย่างในการใช้งาน Rediscache ที่ฉันคิดว่า:
1. ในขณะที่แคชล้มเหลวหากเธรดได้รับข้อมูลแคชมันอาจส่งคืนค่า null เหตุผลก็คือขั้นตอนต่อไปนี้อยู่ในการใช้งานของการใช้ยาเสพติด:
ดังนั้นเมื่อแคชล้มเหลวหลังจากตัดสินว่ามีคีย์อยู่แล้วได้รับแคชไม่มีข้อมูลมันจะส่งคืนค่าโมฆะ
2. แอตทริบิวต์ (CachenullValues) ที่ได้รับอนุญาตให้เก็บค่า null ใน rediscacheManager เป็นเท็จโดยค่าเริ่มต้นนั่นคือไม่ได้รับอนุญาตให้เก็บค่า NULL ซึ่งจะเสี่ยงต่อการเจาะแคช ข้อบกพร่องคือคุณสมบัตินี้เป็นประเภทสุดท้ายและสามารถสร้างวัตถุได้ก็ต่อเมื่อวัตถุถูกสร้างขึ้นผ่านวิธีตัวสร้าง ดังนั้นเพื่อหลีกเลี่ยงการเจาะแคชคุณสามารถประกาศถั่ว rediscachemanager ในแอปพลิเคชันเท่านั้น
3. คุณสมบัติใน REDISCACHEMANAGER ไม่สามารถกำหนดค่าได้โดยตรงผ่านไฟล์การกำหนดค่า พวกเขาสามารถตั้งค่าเฉพาะในส่วนต่อประสาน CacheManagerCustomizer โดยส่วนตัวแล้วฉันคิดว่ามันไม่สะดวก
คาเฟอีนเป็นแคชหน่วยความจำที่มีประสิทธิภาพสูงตามแนวคิดการออกแบบ Guava โอเพนซอร์สของ Google มันได้รับการพัฒนาโดยใช้ Java 8 หลังจากสปริงบู๊ตแนะนำคาเฟอีนการรวมกลุ่มกาวาก็ค่อยๆถูกทอดทิ้ง ซอร์สโค้ดคาเฟอีนและที่อยู่แนะนำ: คาเฟอีน
คาเฟอีนจัดเตรียมกลยุทธ์การเติมแคชและกลยุทธ์การรีไซเคิลมูลค่าที่หลากหลายและยังรวมถึงสถิติเช่นแคชฮิตซึ่งสามารถให้ความช่วยเหลือที่ดีในการเพิ่มประสิทธิภาพแคช
สำหรับการแนะนำของคาเฟอีนโปรดดูที่: http://www.vevb.com/article/134242.htm
ที่นี่เราพูดคุยสั้น ๆ เกี่ยวกับกลยุทธ์การรีไซเคิลตามเวลาคาเฟอีนต่อไปนี้:
ฉันพูดถึงตอนต้นว่าแม้ว่าจะใช้แคช Redis แต่ก็จะมีการบริโภคระดับหนึ่งในการส่งผ่านเครือข่าย ในแอปพลิเคชันจริงจะมีข้อมูลบางอย่างที่มีการเปลี่ยนแปลงที่ต่ำมากซึ่งสามารถแคชได้โดยตรงภายในแอปพลิเคชัน สำหรับข้อมูลบางอย่างที่มีข้อกำหนดแบบเรียลไทม์น้อยกว่านั้นสามารถแคชได้ภายในแอปพลิเคชันในช่วงระยะเวลาหนึ่งเพื่อลดการเข้าถึง REDIS และปรับปรุงความเร็วในการตอบสนอง
เนื่องจาก Redis มีข้อบกพร่องบางอย่างในการใช้งานของ Spring Cache ในกรอบ Spring-Data-Redis ปัญหาบางอย่างอาจเกิดขึ้นเมื่อใช้งานดังนั้นเราจะไม่ขยายตามการใช้งานดั้งเดิม เราจะอ้างถึงวิธีการใช้งานโดยตรงเพื่อใช้อินเทอร์เฟซแคชและแคชแมนเจอร์
ควรสังเกตว่าโดยทั่วไปแอปพลิเคชันจะปรับใช้หลายโหนดและแคชระดับแรกเป็นแคชภายในแอปพลิเคชันดังนั้นเมื่อข้อมูลได้รับการปรับปรุงและล้างข้อมูลทุกโหนดจะต้องได้รับการแจ้งเตือนเพื่อทำความสะอาดแคช มีหลายวิธีในการบรรลุเอฟเฟกต์นี้เช่น: Zookeeper, MQ ฯลฯ แต่เนื่องจาก Redis Cache ถูกนำมาใช้ Redis เองจึงรองรับฟังก์ชั่นการสมัครสมาชิก/การเผยแพร่ดังนั้นจึงไม่พึ่งพาส่วนประกอบอื่น ๆ มันใช้ Redis Channel โดยตรงเพื่อแจ้งโหนดอื่น ๆ เพื่อทำความสะอาดการทำงานของแคช
ต่อไปนี้เป็นขั้นตอนการห่อหุ้มสตาร์ทเตอร์และซอร์สโค้ดสำหรับสปริงบูท + สปริงแคชเพื่อใช้แคชสองระดับ (Redis + คาเฟอีน)
กำหนดคุณสมบัติคุณสมบัติการกำหนดค่า
แพ็คเกจ com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; นำเข้า java.util.hashmap; นำเข้า java.util.hashset; นำเข้า java.util.map; นำเข้า java.util.set; นำเข้า org.springframework. @author fuwei.deng * @date 29 มกราคม 2018 เวลา 11:32:15 น. * @version 1.0.0 */ @configurationProperties (คำนำหน้า = "spring.cache.multi") คลาสสาธารณะ / ** ไม่ว่าจะจัดเก็บค่า null, ค่าเริ่มต้นจริง, ป้องกันการเจาะแคช*/ บูลีนส่วนตัว cachenullValues = true; / ** ไม่ว่าจะสร้างการใช้งานแคชแบบไดนามิกตาม cachename, ค่าเริ่มต้นจริงจริง*/ บูลีนส่วนตัวไดนามิก = true; / ** คำนำหน้าของคีย์แคช*/ สตริงส่วนตัว cacheprefix; Redis Redis ส่วนตัว = ใหม่ Redis (); คาเฟอีนคาเฟอีนส่วนตัว = ใหม่คาเฟอีน (); ชั้นเรียนสาธารณะ REDIS { / ** เวลาหมดอายุทั่วโลกหน่วยมิลลิวินาทีการหมดอายุเริ่มต้น* / ส่วนตัวยาวเริ่มต้นการเพิกถอน = 0; / ** เวลาหมดอายุหน่วยมิลลิวินาทีลำดับความสำคัญสูงกว่าการเริ่มต้นการใช้งาน*/ แผนที่ส่วนตัว <String, Long> Expires = ใหม่ HashMap <> (); / ** แจ้งโหนดอื่น ๆ ของชื่อหัวข้อเมื่อแคชอัปเดต*/ สตริงส่วนตัวหัวข้อ = "แคช: Redis: คาเฟอีน: หัวข้อ"; Public Long GetDefaultExpiration () {return defaultExpiration; } โมฆะสาธารณะ setDefaultExpiration (Long DefaultExpiration) {this.defaultExpiration = defaultExpiration; } แผนที่สาธารณะ <สตริงยาว> getExpires () {return expires; } โมฆะสาธารณะ setExpires (แผนที่ <สตริงยาว> หมดอายุ) {this.expires = หมดอายุ; } สตริงสาธารณะ getTopic () {กลับหัวข้อ; } โมฆะสาธารณะ settopic (หัวข้อสตริง) {this.topic = หัวข้อ; }} คลาสสาธารณะ caffeine { / ** เวลาหมดอายุหลังจากการเข้าถึงในมิลลิวินาที* / ส่วนตัวยาว expiReaccess; / ** เวลาหมดอายุหลังจากการเขียนหน่วยมิลลิวินาที*/ การหมดอายุความยาวส่วนตัวหลังจากการเขียน / ** เวลารีเฟรชหลังการเขียนหน่วยมิลลิวินาที*/ การรีเฟรชยาวส่วนตัว / ** ขนาดการเริ่มต้น*/ private int initialcapacity; / ** จำนวนวัตถุแคชสูงสุดแคชที่วางไว้ก่อนที่จะเกินจำนวนนี้จะไม่ถูกต้อง*/ สูงสุดส่วนตัวสูงสุด /** เนื่องจากน้ำหนักต้องจัดทำโดยวัตถุแคชจึงไม่เหมาะสำหรับสถานการณ์เช่นการใช้สปริงแคชดังนั้นการกำหนดค่าจึงยังไม่รองรับ*/// สูงสุดส่วนตัว Public Long GetExpiRefterAccess () {return expiRefterAccess; } โมฆะสาธารณะ setExpiRefterAccess (Long ExpiRefterAccess) {this.expiRefterAccess = ExpiReaCcess; } สาธารณะยาว getExpiRefterWrite () {return expiRefterWrite; } โมฆะสาธารณะ setExpiRefterWrite (Long ExpiRefterWrite) {this.expiRefterWrite = ExpiRefterWrite; } สาธารณะ Long GetRefreshafterWrite () {return refreshafterwrite; } โมฆะสาธารณะ setRefreshafterWrite (refreshafterwrite ยาว) {this.refreshafterwrite = refreshafterwrite; } public int getInitialCapacity () {return initialCapacity; } โมฆะสาธารณะ setInitialCapacity (int initialCapacity) {this.initialCapacity = initialCapacity; } public Long getMaximumSize () {return maximumsize; } โมฆะสาธารณะ setMaximumsize (ยาวสูงสุด) {this.maximumsize = maximumsize; }} ชุดสาธารณะ <String> getCachenames () {return cachenames; } โมฆะสาธารณะ setCachenames (ตั้งค่า <string> cachenames) {this.cachenames = cachenames; } บูลีนสาธารณะ iscachenullValues () {return cachenullValues; } โมฆะสาธารณะ setCachenullValues (บูลีน cachenullValues) {this.cachenullValues = cachenullValues; } บูลีนสาธารณะ isdynamic () {return dynamic; } โมฆะสาธารณะ setDynamic (Boolean Dynamic) {this.dynamic = Dynamic; } สตริงสาธารณะ getCachePreFix () {return cachepreFix; } โมฆะสาธารณะ setCachePreFix (String cachepreFix) {this.cacheprefix = cacheprefix; } สาธารณะ Redis getRedis () {return redis; } โมฆะสาธารณะ setredis (Redis redis) {this.redis = redis; } คาเฟอีนสาธารณะ getCaffeine () {return caffeine; } โมฆะสาธารณะ setcaffeine (คาเฟอีนคาเฟอีน) {this.caffeine = คาเฟอีน; - มีคลาสนามธรรม AbstractValueAdaptingCache ที่ใช้อินเทอร์เฟซแคชในสปริงแคชซึ่งมีบรรจุภัณฑ์ของค่าว่างและบรรจุภัณฑ์ของค่าแคชดังนั้นจึงไม่จำเป็นต้องใช้อินเทอร์เฟซแคช
แพ็คเกจ com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; นำเข้า java.lang.reflect.constructor; นำเข้า java.util.map; นำเข้า java.util.set; java.util.concurrent.locks.reentrantlock; นำเข้า org.slf4j.logger; นำเข้า org.slf4j.loggerfactory; นำเข้า org.springframework.cache.support.abstractValueAdaptingCache; org.springframework.util.stringutils; นำเข้า com.github.benmanes.caffeine.cache.cache; นำเข้า com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscaffeineperties; 5:24:11 PM * @Version 1.0.0 */คลาสสาธารณะ rediscaffeinecache ขยาย AbstractValueAdaptingCache {Logger สุดท้าย Logger สุดท้าย = loggerFactory.getLogger (rediscaffeinecache.class); ชื่อสตริงส่วนตัว; Private Redistemplate <Object, Object> Redistemplate; แคชส่วนตัว <วัตถุวัตถุ> caffeinecache; สตริงส่วนตัว cacheprefix; ความยาวส่วนตัวเริ่มต้นการเพิกถอน = 0; แผนที่ส่วนตัว <สตริงยาว> หมดอายุ หัวข้อสตริงส่วนตัว = "แคช: redis: คาเฟอีน: หัวข้อ"; ได้รับการป้องกัน rediscaffeinecache (บูลีน allownullvalues) {super (allownullvalues); } public rediscaffeinecache (ชื่อสตริง, redistemplate <วัตถุ, วัตถุ> redistemplate, แคช <วัตถุ, วัตถุ> caffeinecache, cacherediscaffeineproperties cacherediscaffeineperties) {super (cacherediscaffeineproperties.iscachenullvalues () this.name = ชื่อ; this.edistemplate = redistemplate; this.caffeinecache = caffeinecache; this.cacheprefix = cacherediscaffeineproperties.getCachePreFix (); this.defaultExpiration = cacherediscaffeineproperties.getRedis (). getDefaultExpiration (); this.expires = cacherediscaffeineproperties.getRedis (). getExpires (); this.topic = cacherediscaffeineproperties.getRedis (). getTopic (); } @Override สตริงสาธารณะ getName () {return this.name; } @Override วัตถุสาธารณะ getNativeCache () {ส่งคืนสิ่งนี้; } @suppresswarnings ("ไม่ได้ตรวจสอบ") @Override สาธารณะ <t> t รับ (คีย์วัตถุ, callable <t> valuelaoader) {ค่าวัตถุ = การค้นหา (คีย์); if (value! = null) {return (t) ค่า; } reentrantlock lock = ใหม่ reentrantlock (); ลอง {lock.lock (); ค่า = การค้นหา (คีย์); if (value! = null) {return (t) ค่า; } value = valueloader.call (); Object storeValue = tostorevalue (valueloader.call ()); ใส่ (คีย์, storeValue); ค่าคืน (t) ค่า; } catch (exception e) {ลอง {class <?> c = class.forName ("org.springframework.cache.cache $ valueretrievalexception"); ตัวสร้าง <?> constructor = c.getConstructor (Object.class, callable.class, throwable.class); RuntimeException Exception = (RuntimeException) Constructor.newInstance (คีย์, valueloader, e.getCause ()); โยนข้อยกเว้น; } catch (Exception e1) {โยนใหม่ legenlstateException (E1); }} ในที่สุด {lock.unlock (); }} @Override โมฆะสาธารณะใส่ (คีย์วัตถุค่าวัตถุ) {ถ้า (! super.isallownullValues () && value == null) {this.evict (คีย์); กลับ; } Long Expire = getExpire (); if (Expire> 0) {redistemplate.opSforValue (). set (getKey (key), tostoreValue (ค่า), หมดอายุ, timeUnit.milliseconds); } else {redistemplate.opSforValue (). set (getKey (key), tostoreValue (ค่า)); } push (cachemessage ใหม่ (this.name, key)); caffeinecache.put (คีย์, ค่า); } @Override Public ValueWrapper Putifabsent (คีย์วัตถุค่าวัตถุ) {Object Cachekey = getKey (คีย์); วัตถุ prevvalue = null; // พิจารณาใช้การล็อคแบบกระจายหรือเปลี่ยน setifabsent ของ redis เป็นการทำงานของอะตอมซิงโครไนซ์ (คีย์) {prevValue = redistemplate.opSforValue () รับ (cachekey); if (prevValue == null) {long expire = getExpire (); if (Expire> 0) {redistemplate.opSforValue (). set (getKey (key), tostoreValue (ค่า), หมดอายุ, timeUnit.milliseconds); } else {redistemplate.opSforValue (). set (getKey (key), tostoreValue (ค่า)); } push (cachemessage ใหม่ (this.name, key)); caffeinecache.put (คีย์, tostorevalue (ค่า)); }} return tovaluewrapper (prevValue); } @Override โมฆะสาธารณะ Evict (คีย์วัตถุ) {// ก่อนล้างข้อมูลแคชใน Redis จากนั้นล้างแคชในคาเฟอีนเพื่อหลีกเลี่ยงแคชในช่วงเวลาสั้น ๆ หากแคชแคชถูกล้างก่อนและคำขออื่น ๆ จะถูกโหลดจาก Redis push (cachemessage ใหม่ (this.name, key)); caffeinecache.invalidate (คีย์); } @Override โมฆะสาธารณะ Clear () {// ล้างข้อมูลแคชใน Redis จากนั้นล้างแคชในคาเฟอีนเพื่อหลีกเลี่ยงแคชในช่วงเวลาสั้น ๆ หากแคชแคชถูกล้างก่อนแล้วคำขออื่น ๆ จะถูกโหลดจาก Redis ไปยังคาเฟอีน สำหรับ (คีย์วัตถุ: คีย์) {redistemplate.delete (คีย์); } push (cachemessage ใหม่ (this.name, null)); Caffeinecache.invalidateAll (); } @Override การค้นหาวัตถุที่ได้รับการป้องกัน (คีย์วัตถุ) {Object cachekey = getKey (คีย์); ค่าวัตถุ = caffeinecache.getifpresent (คีย์); if (value! = null) {logger.debug ("รับแคชจาก caffeine, คีย์คือ: {}", cachekey); ค่าส่งคืน; } value = redistemplate.opSforValue (). รับ (cachekey); if (value! = null) {logger.debug ("รับแคชจาก redis และใส่ในคาเฟอีนคีย์คือ: {}", cachekey); cacheinecache.put (คีย์, ค่า); } ค่าส่งคืน; } วัตถุส่วนตัว getKey (คีย์วัตถุ) {return this.name.concat (":"). concat (stringutils.isempty (cacheprefix)? key.toString (): cacheprefix.concat (":"). concat (key.toString ()); } Private Long GetExpire () {Long Expire = defaultExpiration; Long CachenameExpire = Expires.get (this.name); ส่งคืน cachenameexpire == null? หมดอายุ: CachenameExpire.longValue (); } / ** * @description แจ้งโหนดอื่นให้ทำความสะอาดแคชท้องถิ่นเมื่อแคชเปลี่ยนไป * @author fuwei.deng * @date 31 มกราคม 2018 เวลา 3:20:28 PM * @version 1.0.0 * @param ข้อความ * / ข้อความโมฆะส่วนตัว } / ** * @description ทำความสะอาดแคชท้องถิ่น * @author fuwei.deng * @date 31 มกราคม 2018 เวลา 3:15:39 น. * @version 1.0.0 * @param key * / โมฆะสาธารณะ Clearlocal (คีย์วัตถุ) {logger.debug if (key == null) {caffeinecache.invalidateAll (); } else {caffeinecache.invalidate (คีย์); - ใช้อินเทอร์เฟซ cacheManager
แพ็คเกจ com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; นำเข้า java.util.collection; นำเข้า java.util.set นำเข้า java.util.concurrent.concurrenthashmap; java.util.concurrent.timeUnit; นำเข้า org.slf4j.logger; นำเข้า org.slf4j.loggerfactory; นำเข้า org.springframework.cache.cache; นำเข้า org.springframework.cache.cachemanager; com.github.benmanes.caffeine.cache.caffeine; นำเข้า com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscaffeineproperties;/** * @author fuwei.deng * @date มกราคม 26, 2018 REDISCAFFEINECACHEMANAGER ใช้ CACHEMANAGER {ส่วนตัว Logger สุดท้าย Logger = LoggerFactory.getLogger Private ConcurrentMap <string, cache> cachemap = new ConcurrentHashMap <String, Cache> (); Cacherediscaffeineproperties ส่วนตัว Private Redistemplate <Object, Object> Redistemplate; บูลีนส่วนตัวไดนามิก = true; ชุดส่วนตัว <String> Cachenames; สาธารณะ rediscaffeinecaffeinemanager (cacherediscaffeineproperties cacherediscaffeineproperties, redistemplate <วัตถุ, วัตถุ> redistemplate) {super (); สิ่งนี้ cacherediscaffeineproperties = cacherediscaffeineproperties; this.edistemplate = redistemplate; this.dynamic = cacherediscaffeineproperties.isdynamic (); this.cachenames = cacherediscaffeineproperties.getCachenames (); } @Override แคชสาธารณะ getCache (ชื่อสตริง) {แคชแคช = cachemap.get (ชื่อ); if (cache! = null) {return cache; } if (! dynamic &&! cachenames.contains (ชื่อ)) {return cache; } cache = ใหม่ rediscaffeinecache (ชื่อ, redistemplate, cache (), cacherediscaffeineproperties); แคช oldCache = cachemap.putifabsent (ชื่อ, แคช); logger.debug ("สร้างอินสแตนซ์แคชชื่อแคชคือ: {}", ชื่อ); return oldcache == null? แคช: OldCache; } สาธารณะ com.github.benmanes.caffeine.cache.cache <วัตถุ, วัตถุ> caffeinecache () {caffeine <วัตถุ, วัตถุ> cachebuilder = caffeine.newbuilder (); if (cacherediscaffeineproperties.getCaffeine (). getExpiRefterAccess ()> 0) {cachebuilder.expipiRefteraccess (Cacherediscaffeineproperties.getCaffeine (). } if (cacherediscaffeineproperties.getCaffeine (). getExpiRefterwrite ()> 0) {cachebuilder.expipiRefterwrite (cacherediscaffeineproperties.getCaffeine (). } if (cacherediscaffeineproperties.getCaffeine (). getInitialCapacity ()> 0) {cachebuilder.initialCapacity (cacherediscaffeineproperties.getCaffeine () getInitialCapacity (); } if (cacherediscaffeineproperties.getCaffeine (). getMaximumSize ()> 0) {cachebuilder.maximumsize (cacherediscaffeineproperties.getCaffeine (). getMaximumSize ()); } if (cacherediscaffeineproperties.getCaffeine (). getRefreshafterWrite ()> 0) {cachebuilder.refreshafterwrite (cacherediscaffeineproperties.getCaffeine (). } return cachebuilder.build (); } @Override Public Collection <String> getCachenames () {return this.cachenames; } โมฆะสาธารณะ ClearLocal (String Cachename, Key Object) {แคชแคช = cachemap.get (cachename); if (cache == null) {return; } rediscaffeinecache rediscaffeinecache = (rediscaffeinecache) แคช; rediscaffeinecache.clearlocal (คีย์); - Redis Message เผยแพร่/สมัครสมาชิกคลาสข้อความสำหรับการส่ง
แพ็คเกจ com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; นำเข้า java.io.serializable;/** * @author fuwei.deng * @date 29 มกราคม 2018 1:31:17 PM * @version 1.0.0 serialversionuid สุดท้ายยาว = 5987219310442078193L; cachename สตริงส่วนตัว; คีย์วัตถุส่วนตัว cachemessage สาธารณะ (สตริง cachename, ปุ่มวัตถุ) {super (); this.cachename = cachename; this.key = key; } สตริงสาธารณะ getCachename () {return cachename; } โมฆะสาธารณะ setCachename (สตริง cachename) {this.cachename = cachename; } วัตถุสาธารณะ getKey () {คีย์ return; } โมฆะสาธารณะ setKey (คีย์วัตถุ) {this.key = key; - การฟังข้อความ Redis ต้องใช้อินเทอร์เฟซ MessageListener
แพ็คเกจ com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; นำเข้า org.slf4j.logger; นำเข้า org.slf4j.loggerfactory; นำเข้า org.springframework.data.edis. org.springframework.data.redis.connection.messagelistener; นำเข้า org.springframework.data.redis.core.redistemplate;/** * @author fuwei.deng * @date 30 มกราคม 2018 ที่ 5:22 MessageListener {ส่วนตัว logger สุดท้าย logger = loggerFactory.getLogger (cachemessageListener.class); Private Redistemplate <Object, Object> Redistemplate; rediscaffeinecachemanager ส่วนตัว rediscaffeinecachemanager; Public CachemessageListener (Redistemplate <Object, Object> Redistemplate, Rediscaffeinecachemanager rediscaffeinecachemanager) {super (); this.edistemplate = redistemplate; this.rediscaffeinecachemanager = rediscaffeinecachemanager; } @Override โมฆะสาธารณะ onMessage (ข้อความข้อความ, ไบต์ [] รูปแบบ) {cachemessage cachemessage = (cachemessage) redistemplate.getValueserializer (). deserialize (message.getbody ()); logger.debug ("Recevice a redis topic message, ล้างแคชท้องถิ่น, cachename คือ {}, คีย์คือ {}", cachemessage.getCachename (), cachemessage.getKey ()); rediscaffeinecachemanager.clearlocal (cachemessage.getcachename (), cachemessage.getKey ()); - เพิ่มคลาสการกำหนดค่าสปริงบูต
แพ็คเกจ com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; นำเข้า org.springframework.beans.factory.annotation.autowired; นำเข้า org.springframework.boot.autoconfigure.autoconfiguration; org.springframework.boot.autoconfigure.condition.conditionalonbean; นำเข้า org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration; org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.listener.ChannelTopic;import org.springframework.data.redis.listener.RedisMessageListenerContainer;import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support.CacheMessageListener;import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support.RedisCaffeineCacheManager;/** * @author fuwei.deng * @date January 26, 2018 at 5:23:03 pm * @version 1.0.0 */@Configuration@AutoConfigureAfter(RedisAutoConfiguration.class)@EnableConfigurationProperties(CacheRedisCaffeineProperties.class)public class CacheRedisCaffeineAutoConfiguration { @Autowired private CacheRedisCaffeineProperties cacheRedisCaffeineProperties; @Bean @ConditionalOnBean(RedisTemplate.class) public RedisCaffeineCaffeine cacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { return new RedisCaffeineCacheManager(cacheRedisCaffeineProperties, redisTemplate); } @Bean public RedisMessageListenerContainer redisMessageListenerContainer(RedisTemplate<Object, Object> redisTemplate, RedisCaffeineCacheManager redisCaffeineCacheManager) { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory()); CacheMessageListener cacheMessageListener = new CacheMessageListener(redisTemplate, redisCaffeineCaffeineCacheManager); redisMessageListenerContainer.addMessageListener(cacheMessageListener, new ChannelTopic(cacheRedisCaffeineProperties.getRedis().getTopic())); return redisMessageListenerContainer; -在resources/META-INF/spring.factories文件中增加spring boot配置扫描
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=/com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.CacheRedisCaffeineAutoConfiguration
接下来就可以使用maven引入使用了
<dependency> <groupId>com.itopener</groupId> <artifactId>cache-redis-caffeine-spring-boot-starter</artifactId> <version>1.0.0-SNAPSHOT</version> <type>pom</type></dependency>
在启动类上增加@EnableCaching注解,在需要缓存的方法上增加@Cacheable注解
package com.itopener.demo.cache.redis.caffeine.service;import java.util.Random;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;import com.itopener.demo.cache.redis.caffeine.vo.UserVO;import com.itopener.utils.TimestampUtil;@Servicepublic class CacheRedisCaffeineService { private final Logger logger = LoggerFactory.getLogger(CacheRedisCaffeineService.class); @Cacheable(key = "'cache_user_id_' + #id", value = "userIdCache", cacheManager = "cacheManager") public UserVO get(long id) { logger.info("get by id from db"); UserVO user = new UserVO(); user.setId(id); user.setName("name" + id); user.setCreateTime(TimestampUtil.current()); return user; } @Cacheable(key = "'cache_user_name_' + #name", value = "userNameCache", cacheManager = "cacheManager") public UserVO get(String name) { logger.info("get by name from db"); UserVO user = new UserVO(); user.setId(new Random().nextLong()); user.setName (ชื่อ); user.setCreateTime(TimestampUtil.current()); return user; } @CachePut(key = "'cache_user_id_' + #userVO.id", value = "userIdCache", cacheManager = "cacheManager") public UserVO update(UserVO userVO) { logger.info("update to db"); userVO.setCreateTime(TimestampUtil.current()); return userVO; } @CacheEvict(key = "'cache_user_id_' + #id", value = "userIdCache", cacheManager = "cacheManager") public void delete(long id) { logger.info("delete from db"); - properties文件中redis的配置跟使用redis是一样的,可以增加两级缓存的配置
#两级缓存的配置spring.cache.multi.caffeine.expireAfterAccess=5000spring.cache.multi.redis.defaultExpiration=60000#spring cache配置spring.cache.cache-names=userIdCache,userNameCache#redis配置#spring.redis.timeout=10000#spring.redis.password=redispwd#redis pool#spring.redis.pool.maxIdle=10#spring.redis.pool.minIdle=2#spring.redis.pool.maxActive=10#spring.redis.pool.maxWait=3000#redis clusterspring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005,127.0.0.1:7006spring.redis.cluster.maxRedirects=3
扩展
个人认为redisson的封装更方便一些
后续可以增加对于缓存命中率的统计endpoint,这样就可以更好的监控各个缓存的命中情况,以便对缓存配置进行优化
源码下载
starter目录:springboot / itopener-parent / spring-boot-starters-parent / cache-redis-caffeine-spring-boot-starter-parent
示例代码目录: springboot / itopener-parent / demo-parent / demo-cache-redis-caffeine
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น