使用Google Guava Cache进行本地缓存
很多時候,我們將不得不從數(shù)據(jù)庫或另一個Web服務(wù)獲取數(shù)據(jù)或從文件系統(tǒng)加載數(shù)據(jù)。 在涉及網(wǎng)絡(luò)呼叫的情況下,將存在固有的網(wǎng)絡(luò)等待時間,網(wǎng)絡(luò)帶寬限制。 解決此問題的方法之一是在應(yīng)用程序本地?fù)碛幸粋€緩存。
如果您的應(yīng)用程序跨越多個節(jié)點,則緩存將位于每個節(jié)點本地,從而導(dǎo)致固有的數(shù)據(jù)不一致。 可以權(quán)衡此數(shù)據(jù)不一致以提高吞吐量和降低延遲。 但是有時,如果數(shù)據(jù)不一致會產(chǎn)生重大差異,則可以減少緩存對象的ttl(生存時間),從而減少數(shù)據(jù)不一致可能發(fā)生的持續(xù)時間。
在實現(xiàn)本地緩存的多種方法中,我在高負(fù)載環(huán)境中使用的一種方法是Guava緩存。 我們使用了番石榴緩存來每秒處理80,000個以上的請求。 延遲的90%約為5毫秒。 這幫助我們擴(kuò)展了有限的網(wǎng)絡(luò)帶寬需求。
在本文中,我將展示如何添加一層Guava緩存以避免頻繁的網(wǎng)絡(luò)呼叫。 為此,我選擇了一個非常簡單的示例,該示例使用Google Books API給出了圖書的 ISBN來獲取圖書的詳細(xì)信息。
使用ISBN13字符串獲取圖書詳細(xì)信息的示例請求為: https : //www.googleapis.com/books/v1/volumes? q = isbn:9781449370770 & key ={ API_KEY }
對我們有用的部分響應(yīng)如下:
有關(guān)Guava Cache功能的非常詳細(xì)的說明,請參見此處 。 在此示例中,我將使用LoadingCache。 LoadingCache接收一個代碼塊,該代碼塊用于將數(shù)據(jù)加載到緩存中以查找丟失的密鑰。 因此,當(dāng)您使用不存在的鍵進(jìn)行緩存時,LoadingCache將使用CacheLoader提取數(shù)據(jù)并將其設(shè)置在緩存中,然后將其返回給調(diào)用方。
現(xiàn)在讓我們看一下表示書籍詳細(xì)信息所需的模型類:
- 書本類
- 作者班級
Book類定義為:
//Book.java package info.sanaulla.model;import java.util.ArrayList; import java.util.Date; import java.util.List;public class Book {private String isbn13;private List<Author> authors;private String publisher;private String title;private String summary;private Integer pageCount;private String publishedDate;public String getIsbn13() {return isbn13;}public void setIsbn13(String isbn13) {this.isbn13 = isbn13;}public List<Author> getAuthors() {return authors;}public void setAuthors(List<Author> authors) {this.authors = authors;}public String getPublisher() {return publisher;}public void setPublisher(String publisher) {this.publisher = publisher;}public String getTitle() {return title;}public void setTitle(String title) {this.title = title;}public String getSummary() {return summary;}public void setSummary(String summary) {this.summary = summary;}public void addAuthor(Author author){if ( authors == null ){authors = new ArrayList<Author>();}authors.add(author);}public Integer getPageCount() {return pageCount;}public void setPageCount(Integer pageCount) {this.pageCount = pageCount;}public String getPublishedDate() {return publishedDate;}public void setPublishedDate(String publishedDate) {this.publishedDate = publishedDate;} }而Author類的定義為:
//Author.java package info.sanaulla.model;public class Author {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}現(xiàn)在讓我們定義一個服務(wù),該服務(wù)將從Google Books REST API中獲取數(shù)據(jù),并將其稱為BookService。 該服務(wù)執(zhí)行以下操作:
我已經(jīng)從BookService中提取了一些操作到Util類中,即:
以下是Util類的定義方式:
//Util.javapackage info.sanaulla;import com.fasterxml.jackson.databind.ObjectMapper;import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.ProtocolException; import java.net.URL; import java.util.ArrayList; import java.util.List; import java.util.Properties;public class Util {private static ObjectMapper objectMapper = new ObjectMapper();private static Properties properties = null;public static ObjectMapper getObjectMapper(){return objectMapper;}public static Properties getProperties() throws IOException {if ( properties != null){return properties;}properties = new Properties();InputStream inputStream = Util.class.getClassLoader().getResourceAsStream("application.properties");properties.load(inputStream);return properties;}public static String getHttpResponse(String urlStr) throws IOException {URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setRequestProperty("Accept", "application/json");conn.setConnectTimeout(5000);//conn.setReadTimeout(20000);if (conn.getResponseCode() != 200) {throw new RuntimeException("Failed : HTTP error code : "+ conn.getResponseCode());}BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));StringBuilder outputBuilder = new StringBuilder();String output;while ((output = br.readLine()) != null) {outputBuilder.append(output);}conn.disconnect();return outputBuilder.toString();} }因此,我們的Service類如下所示:
//BookService.java package info.sanaulla.service;import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Optional; import com.google.common.base.Strings;import info.sanaulla.Constants; import info.sanaulla.Util; import info.sanaulla.model.Author; import info.sanaulla.model.Book;import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties;public class BookService {public static Optional<Book> getBookDetailsFromGoogleBooks(String isbn13) throws IOException{Properties properties = Util.getProperties();String key = properties.getProperty(Constants.GOOGLE_API_KEY);String url = "https://www.googleapis.com/books/v1/volumes?q=isbn:"+isbn13;String response = Util.getHttpResponse(url);Map bookMap = Util.getObjectMapper().readValue(response,Map.class);Object bookDataListObj = bookMap.get("items");Book book = null;if ( bookDataListObj == null || !(bookDataListObj instanceof List)){return Optional.fromNullable(book);}List bookDataList = (List)bookDataListObj;if ( bookDataList.size() < 1){return Optional.fromNullable(null);}Map bookData = (Map) bookDataList.get(0);Map volumeInfo = (Map)bookData.get("volumeInfo");book = new Book();book.setTitle(getFromJsonResponse(volumeInfo,"title",""));book.setPublisher(getFromJsonResponse(volumeInfo,"publisher",""));List authorDataList = (List)volumeInfo.get("authors");for(Object authorDataObj : authorDataList){Author author = new Author();author.setName(authorDataObj.toString());book.addAuthor(author);}book.setIsbn13(isbn13);book.setSummary(getFromJsonResponse(volumeInfo,"description",""));book.setPageCount(Integer.parseInt(getFromJsonResponse(volumeInfo, "pageCount", "0")));book.setPublishedDate(getFromJsonResponse(volumeInfo,"publishedDate",""));return Optional.fromNullable(book);}private static String getFromJsonResponse(Map jsonData, String key, String defaultValue){return Optional.fromNullable(jsonData.get(key)).or(defaultValue).toString();} }在Google Books API調(diào)用的頂部添加緩存
我們可以使用Guava庫提供的CacheBuilder API創(chuàng)建一個緩存對象。 它提供了設(shè)置屬性的方法,例如
- 緩存中的最大項目數(shù)
- 基于緩存對象的上次寫入時間或上次訪問時間的生存時間,
- ttl用于刷新緩存對象,
- 在緩存中記錄統(tǒng)計信息,例如命中,未命中,加載時間和
- 提供加載程序代碼以在高速緩存未命中或高速緩存刷新的情況下獲取數(shù)據(jù)。
因此,我們理想地希望的是,緩存未命中應(yīng)調(diào)用上面編寫的API,即getBookDetailsFromGoogleBooks。 我們希望最多存儲1000個項目,并在24小時后使這些項目過期。 因此,構(gòu)建緩存的代碼如下:
private static LoadingCache<String, Optional<Book>> cache = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(24, TimeUnit.HOURS).recordStats().build(new CacheLoader<String, Optional<Book>>() {@Overridepublic Optional<Book> load(String s) throws IOException {return getBookDetailsFromGoogleBooks(s);}});重要的是要注意,要存儲在緩存中的最大項目會影響應(yīng)用程序使用的堆。 因此,您必須根據(jù)要緩存的每個對象的大小以及分配給應(yīng)用程序的最大堆內(nèi)存來仔細(xì)確定該值。
讓我們付諸實踐,并查看緩存統(tǒng)計信息如何報告統(tǒng)計信息:
package info.sanaulla;import com.google.common.cache.CacheStats; import info.sanaulla.model.Book; import info.sanaulla.service.BookService;import java.io.IOException; import java.util.Properties; import java.util.concurrent.ExecutionException;public class App {public static void main( String[] args ) throws IOException, ExecutionException {Book book = BookService.getBookDetails("9780596009205").get();System.out.println(Util.getObjectMapper().writeValueAsString(book));book = BookService.getBookDetails("9780596009205").get();book = BookService.getBookDetails("9780596009205").get();book = BookService.getBookDetails("9780596009205").get();book = BookService.getBookDetails("9780596009205").get();CacheStats cacheStats = BookService.getCacheStats();System.out.println(cacheStats.toString());} }我們將得到的輸出是:
{"isbn13":"9780596009205","authors":[{"name":"Kathy Sierra"},{"name":"Bert Bates"}],"publisher":"\"O'Reilly Media, Inc.\"","title":"Head First Java","summary":"An interactive guide to the fundamentals of the Java programming language utilizes icons, cartoons, and numerous other visual aids to introduce the features and functions of Java and to teach the principles of designing and writing Java programs.","pageCount":688,"publishedDate":"2005-02-09"} CacheStats{hitCount=4, missCount=1, loadSuccessCount=1, loadExceptionCount=0, totalLoadTime=3744128770, evictionCount=0}這是Guava緩存的非?;镜挠梅?#xff0c;我在學(xué)習(xí)使用它時就寫了它。 在本文中,我利用了諸如Optional之類的其他Guava API,該API有助于將現(xiàn)有或不存在的(null)值包裝到對象中。 可以在git hub- https://github.com/sanaulla123/Guava-Cache-Demo上找到此代碼。 會有一些擔(dān)憂,例如它如何處理并發(fā),而我沒有詳細(xì)介紹。 但是在后臺,它使用分段的并發(fā)哈希圖,因此獲取始終是非阻塞的,但是并發(fā)寫入的數(shù)量將由分段的數(shù)量決定。
一些與此相關(guān)的有用鏈接: http : //guava-libraries.googlecode.com/files/ConcurrentCachingAtGoogle.pdf
翻譯自: https://www.javacodegeeks.com/2015/01/using-google-guava-cache-for-local-caching.html
總結(jié)
以上是生活随笔為你收集整理的使用Google Guava Cache进行本地缓存的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 9寸照片是多少厘米 9寸照片步骤尺寸
- 下一篇: 用装饰器改变收藏