1. บทนำ
ในบทความนี้มาดูคาเฟอีน - ไลบรารีแคช Java ที่มีประสิทธิภาพสูง
ความแตกต่างพื้นฐานอย่างหนึ่งระหว่างแคชและแผนที่คือแคชสามารถรีไซเคิลรายการที่เก็บไว้ได้
นโยบายการรีไซเคิลคือการลบวัตถุในเวลาที่กำหนด กลยุทธ์นี้มีผลโดยตรงต่ออัตราการเข้าแคช - คุณลักษณะที่สำคัญของไลบรารีแคช
คาเฟอีนให้อัตราการเข้าชมที่ดีที่สุดเนื่องจากการใช้กลยุทธ์การรีไซเคิล Window Tinylfu
2. การพึ่งพาอาศัยกัน
เราจำเป็นต้องเพิ่มการพึ่งพาคาเฟอีนใน pom.xml:
<Ederency> <roupId> com.github.ben-manes.caffeine </groupid> <ratifactid> คาเฟอีน </artifactid> <version> 2.5.5 </version>
คุณสามารถค้นหาคาเฟอีนเวอร์ชันล่าสุดบน Maven Central
3. เติมแคช
ลองมาดูกลยุทธ์การเติมแคชสามของคาเฟอีน: คู่มือการโหลดแบบซิงโครนัสและการโหลดแบบอะซิงโครนัส
ก่อนอื่นเราเขียนคลาสสำหรับประเภทค่าที่จะเก็บไว้ในแคช:
คลาส DataObject {ข้อมูลสตริงสุดท้ายส่วนตัว; int private int objectCounter = 0; // standard constructors/getters public Static dataObject รับ (ข้อมูลสตริง) {ObjectCounter ++; ส่งคืน DataObject ใหม่ (ข้อมูล); -3.1. ไส้ด้วยตนเอง
ในกลยุทธ์นี้เราใส่ค่าลงในแคชด้วยตนเองก่อนที่จะดึงมัน
มาเริ่มต้นแคช:
แคช <String, dataObject> cache = caffeine.newbuilder () .ExpiPiRefterwrite (1, timeUnit.minutes) .MaximumSize (100) .build ();
ตอนนี้เราสามารถใช้วิธี getIfpresent เพื่อรับค่าบางอย่างจากแคช หากค่านี้ไม่มีอยู่ในแคชวิธีนี้จะส่งคืนค่า null:
String key = "a"; dataObject dataObject = cache.getifpresent (คีย์); assertNull (dataObject);
เราสามารถใช้วิธีการใส่ในการเติมแคชด้วยตนเอง:
cache.put (คีย์, dataObject); dataObject = cache.getifpresent (คีย์); assertNotNull (dataObject);
นอกจากนี้เรายังสามารถใช้วิธี GET เพื่อรับค่าซึ่งผ่านฟังก์ชั่นกับคีย์พารามิเตอร์เป็นพารามิเตอร์ หากคีย์ไม่มีอยู่ในแคชฟังก์ชั่นจะถูกใช้เพื่อให้ค่าทางเลือกซึ่งจะถูกแทรกลงในแคชหลังจากการคำนวณ:
dataObject = cache .get (คีย์, k -> dataObject.get ("ข้อมูลสำหรับ")); assertNotNull (dataObject); assertequals ("ข้อมูลสำหรับ", dataObject.getData ());วิธี GET สามารถทำการคำนวณได้อย่างเป็นอะตอม ซึ่งหมายความว่าคุณทำการคำนวณเพียงครั้งเดียว - แม้ว่าหลายเธรดจะขอค่าในเวลาเดียวกัน นี่คือเหตุผลที่การใช้ Get ดีกว่า getifpresent
บางครั้งเราจำเป็นต้องทำให้ค่าแคชบางอย่างเป็นโมฆะด้วยตนเอง:
cache.invalidate (คีย์); dataObject = cache.getifpresent (คีย์); assertNull (dataObject);
3.2. การโหลดแบบซิงโครนัส
วิธีการโหลดแคชนี้ใช้วิธีการ GET ด้วยกลยุทธ์แบบแมนนวลคล้ายกับฟังก์ชั่นที่ใช้ในการเริ่มต้นค่า มาดูกันว่าจะใช้อย่างไร
ก่อนอื่นเราต้องเริ่มต้นแคช:
loadingCache <string, dataObject> cache = caffeine.newbuilder () .maximumsize (100) .ExpiPiRefterwrite (1, timeUnit.minutes). สร้าง (k -> dataObject.get ("ข้อมูลสำหรับ" + k));ตอนนี้เราสามารถใช้วิธีการรับเพื่อดึงค่า:
dataObject dataObject = cache.get (คีย์); assertNotNull (dataObject); asserTequals ("ข้อมูลสำหรับ" + คีย์, dataObject.getData ());นอกจากนี้เรายังสามารถใช้วิธี Getall เพื่อรับชุดของค่า:
แผนที่ <สตริง, dataObject> dataObjectMap = cache.getAll (array.aslist ("a", "b", "c")); assertequals (3, dataObjectmap.size ());ดึงค่าจากฟังก์ชั่นการเริ่มต้นแบ็กเอนด์พื้นฐานที่ส่งผ่านไปยังวิธีการสร้าง สิ่งนี้ช่วยให้การใช้แคชเป็นส่วนหน้าหลักของการเข้าถึงค่า
3.3. การโหลดแบบอะซิงโครนัส
นโยบายนี้ทำสิ่งเดียวกันกับก่อนหน้านี้ แต่ดำเนินการแบบอะซิงโครนัสและส่งคืนค่าที่สมบูรณ์แบบที่มีค่า:
asyncLoadingCache <string, dataObject> cache = caffeine.newbuilder () .maximumsize (100) .ExpiPiRefterwrite (1, timeUnit.minutes) .buildasync (k -> dataObject.get.get ("ข้อมูลสำหรับ" + k));เราสามารถใช้วิธีการรับและ getall ในลักษณะเดียวกันโดยคำนึงถึงว่าพวกเขากำลังส่งคืน future future:
คีย์สตริง = "A"; cache.get (คีย์) .Thenaccept (dataObject -> {assertNotNull (dataObject); asserTequals ("ข้อมูลสำหรับ" + คีย์, dataObject.getData ());}); cache.getall (array.aslist ("a", "b", "c"))). thenaccept (dataObjectmap -> assertequals (3, dataObjectmap.size ());PomptextFuture มี API ที่มีประโยชน์มากมายและคุณสามารถรับมากขึ้นในบทความนี้
4. การกู้คืนค่า
คาเฟอีนมีกลยุทธ์การกู้คืนค่าสามประการ ได้แก่ ขนาดตามขนาดเวลาและการอ้างอิงตาม
4.1. การรีไซเคิลตามขนาด
วิธีการรีไซเคิลนี้ถือว่าการรีไซเคิลเกิดขึ้นเมื่อเกินขีด จำกัด ขนาดแคชที่กำหนดค่าไว้ มีสองวิธีในการรับขนาด: นับวัตถุในแคชหรือรับน้ำหนัก
มาดูวิธีการคำนวณวัตถุในแคช เมื่อแคชเริ่มต้นขนาดของมันจะเท่ากับศูนย์:
loadingCache <string, dataObject> cache = caffeine.newbuilder () .MaximumSize (1) .build (k -> dataObject.get ("ข้อมูลสำหรับ" + k)); assertequals (0, cache.atimatedSize ());เมื่อเราเพิ่มค่าขนาดจะเพิ่มขึ้นอย่างมีนัยสำคัญ:
cache.get ("a"); assertequals (1, cache.atimatedSize ());เราสามารถเพิ่มค่าที่สองลงในแคชซึ่งทำให้ค่าแรกถูกลบ:
cache.get ("B"); cache.cleanup (); assertequals (1, cache.atimatedSize ());เป็นมูลค่าการกล่าวขวัญว่าก่อนที่จะได้รับขนาดแคชเราเรียกวิธีการล้างข้อมูล นี่เป็นเพราะการรีไซเคิลแคชดำเนินการแบบอะซิงโครนัสและวิธีการนี้ช่วยให้รอการรีไซเคิลให้เสร็จสมบูรณ์
นอกจากนี้เรายังสามารถผ่านฟังก์ชั่นการชั่งน้ำหนักเพื่อรับขนาดแคช:
loadingCache <string, dataObject> cache = caffeine.newbuilder () .Maximumweight (10) .weighter ((k, v) -> 5) .build (k -> dataObject.get ("ข้อมูลสำหรับ" + k)); assertequals (0, cache.atimatedSize ()); cache.get ("a"); assertequals (1, cache.atimatedSize ()); cache.get ("B"); assertequals (2, cache.atimatedSize ());เมื่อน้ำหนักเกิน 10 ค่าจะถูกลบออกจากแคช:
cache.get ("C"); cache.cleanup (); assertequals (2, cache.atimatedSize ());4.2. ขึ้นอยู่กับการกู้คืนเวลา
กลยุทธ์การรีไซเคิลนี้ขึ้นอยู่กับเวลาหมดอายุของรายการและมีสามประเภท:
มากำหนดค่านโยบายการหมดอายุหลังการเข้าถึงโดยใช้วิธีการ ExpiReAccess:
loadingCache <string, dataObject> cache = caffeine.newBuilder () .ExpiPiRefterAccess (5, timeUnit.minutes) .build (k -> dataObject.get ("ข้อมูลสำหรับ" + k));ในการกำหนดค่านโยบายการหมดอายุหลังเขียนเราใช้วิธีการ ExpiReTterWrite:
cache = caffeine.newbuilder () .ExpiPiRefterWrite (10, timeUnit.Seconds) .weakEys () .weakValues () .build (k -> dataObject.get ("ข้อมูลสำหรับ" + k));ในการเริ่มต้นนโยบายที่กำหนดเองเราจำเป็นต้องใช้อินเทอร์เฟซหมดอายุ:
cache = caffeine.newbuilder (). ExpiRefter (Expiry ใหม่ <String, DataObject> () {@Override สาธารณะยาว ExpiRefterCreate (คีย์สตริง, ค่า dataObject, ช่วงเวลาที่ยาวนาน) CurrentDuration;} @Override Public Long ExpiReDread (คีย์สตริง, ค่า DataObject, Long CurrentTime, Long CurrentDuration) {ส่งคืน CurrentDuration;4.3. การรีไซเคิลขึ้นอยู่กับการอ้างอิง
เราสามารถกำหนดค่าแคชเพื่อเปิดใช้งานการรวบรวมขยะของค่าคีย์แคช ในการทำเช่นนี้เรากำหนดค่าคีย์และค่าเป็นการอ้างอิงที่อ่อนแอและเราสามารถกำหนดค่าการอ้างอิงที่อ่อนนุ่มสำหรับการรวบรวมขยะเท่านั้น
เมื่อไม่มีการอ้างอิงที่แข็งแกร่งไปยังวัตถุการใช้ความอ่อนแอสามารถเปิดใช้งานการเก็บขยะของวัตถุ Softreference ช่วยให้วัตถุสามารถรวบรวมขยะได้ตามนโยบายที่ใช้น้อยที่สุดทั่วโลกของ JVM สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการอ้างอิง Java ดูที่นี่
เราควรเปิดใช้งานแต่ละตัวเลือกโดยใช้ caffeine.weakkeys (), caffeine.weakvalues () และ caffeine.softValues ():
loadingCache <string, dataObject> cache = caffeine.newBuilder () .ExpiPiRefterWrite (10, TimeUnit.seconds) .weakkeys () .weakValues (). build (k -> dataObject.get ("ข้อมูลสำหรับ" + k)); cache = caffeine.newbuilder () .ExpiPiRefterWrite (10, timeUnit.Seconds) .SoftValues () .build (k -> dataObject.get ("ข้อมูลสำหรับ" + k));5. รีเฟรช
แคชสามารถกำหนดค่าให้รีเฟรชรายการโดยอัตโนมัติหลังจากระยะเวลาที่กำหนด มาดูวิธีใช้วิธีการ refreshafterwrite:
caffeine.newbuilder () .RefreshafterWrite (1, timeUnit.minutes) .build (k -> dataObject.get ("ข้อมูลสำหรับ" + k));ที่นี่เราควรเข้าใจความแตกต่างระหว่าง ExpiRefter และ Refreshfter เมื่อมีการร้องขอรายการหมดอายุการดำเนินการจะบล็อกจนกว่าฟังก์ชั่นการสร้างจะคำนวณค่าใหม่
อย่างไรก็ตามหากรายการสามารถรีเฟรชแคชจะส่งคืนค่าเก่าและโหลดค่าแบบอะซิงโครนัสใหม่
6. สถิติ
คาเฟอีนมีวิธีบันทึกการใช้แคช:
loadingCache <string, dataObject> cache = caffeine.newbuilder () .maximumsize (100) .recordstats () .build (k -> dataObject.get ("ข้อมูลสำหรับ" + k)); cache.get ("a"); cache.get ("a"); assertequals (1, cache.stats (). hitcount ()); assertequals (1, cache.stats (). misscount ());เราอาจส่งผ่านซัพพลายเออร์ RecordStats เพื่อสร้างการใช้งาน StatScounter วัตถุนี้ถูกผลักดันการเปลี่ยนแปลงที่เกี่ยวข้องกับสถิติทุกครั้ง
7. บทสรุป
ในบทความนี้เราคุ้นเคยกับห้องสมุดแคชคาเฟอีนของ Java เราเห็นวิธีการกำหนดค่าและเติมแคชและวิธีการเลือกนโยบายการหมดอายุที่เหมาะสมหรือการรีเฟรชตามความต้องการของเรา
ซอร์สโค้ดสำหรับตัวอย่างในบทความนี้สามารถพบได้ใน GitHub
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น