使用Google Guava Cache进行本地缓存
很多時候,我們將不得不從數據庫或另一個Web服務獲取數據或從文件系統加載數據。 在涉及網絡呼叫的情況下,將存在固有的網絡等待時間,網絡帶寬限制。 解決此問題的方法之一是在應用程序本地擁有一個緩存。
如果您的應用程序跨越多個節點,則緩存將位于每個節點本地,從而導致固有的數據不一致。 可以權衡此數據不一致以提高吞吐量和降低延遲。 但是有時,如果數據不一致會產生重大差異,則可以減少緩存對象的ttl(生存時間),從而減少數據不一致可能發生的持續時間。
在實現本地緩存的多種方法中,我在高負載環境中使用的一種方法是Guava緩存。 我們使用了番石榴緩存來每秒處理80,000個以上的請求。 延遲的90%約為5毫秒。 這幫助我們擴展了有限的網絡帶寬需求。
在本文中,我將展示如何添加一層Guava緩存以避免頻繁的網絡呼叫。 為此,我選擇了一個非常簡單的示例,該示例使用Google Books API給出了圖書的 ISBN來獲取圖書的詳細信息。
使用ISBN13字符串獲取圖書詳細信息的示例請求為: https : //www.googleapis.com/books/v1/volumes? q = isbn:9781449370770 & key ={ API_KEY }
對我們有用的部分響應如下:
有關Guava Cache功能的非常詳細的說明,請參見此處 。 在此示例中,我將使用LoadingCache。 LoadingCache接收一個代碼塊,該代碼塊用于將數據加載到緩存中以查找丟失的密鑰。 因此,當您使用不存在的鍵進行緩存時,LoadingCache將使用CacheLoader提取數據并將其設置在緩存中,然后將其返回給調用方。
現在讓我們看一下表示書籍詳細信息所需的模型類:
- 書本類
- 作者班級
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;}現在讓我們定義一個服務,該服務將從Google Books REST API中獲取數據,并將其稱為BookService。 該服務執行以下操作:
我已經從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調用的頂部添加緩存
我們可以使用Guava庫提供的CacheBuilder API創建一個緩存對象。 它提供了設置屬性的方法,例如
- 緩存中的最大項目數
- 基于緩存對象的上次寫入時間或上次訪問時間的生存時間,
- ttl用于刷新緩存對象,
- 在緩存中記錄統計信息,例如命中,未命中,加載時間和
- 提供加載程序代碼以在高速緩存未命中或高速緩存刷新的情況下獲取數據。
因此,我們理想地希望的是,緩存未命中應調用上面編寫的API,即getBookDetailsFromGoogleBooks。 我們希望最多存儲1000個項目,并在24小時后使這些項目過期。 因此,構建緩存的代碼如下:
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);}});重要的是要注意,要存儲在緩存中的最大項目會影響應用程序使用的堆。 因此,您必須根據要緩存的每個對象的大小以及分配給應用程序的最大堆內存來仔細確定該值。
讓我們付諸實踐,并查看緩存統計信息如何報告統計信息:
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緩存的非常基本的用法,我在學習使用它時就寫了它。 在本文中,我利用了諸如Optional之類的其他Guava API,該API有助于將現有或不存在的(null)值包裝到對象中。 可以在git hub- https://github.com/sanaulla123/Guava-Cache-Demo上找到此代碼。 會有一些擔憂,例如它如何處理并發,而我沒有詳細介紹。 但是在后臺,它使用分段的并發哈希圖,因此獲取始終是非阻塞的,但是并發寫入的數量將由分段的數量決定。
一些與此相關的有用鏈接: http : //guava-libraries.googlecode.com/files/ConcurrentCachingAtGoogle.pdf
翻譯自: https://www.javacodegeeks.com/2015/01/using-google-guava-cache-for-local-caching.html
總結
以上是生活随笔為你收集整理的使用Google Guava Cache进行本地缓存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 9寸照片是多少厘米 9寸照片步骤尺寸
- 下一篇: 绿萝的样子和特点 绿萝的样子和特点是什么