Google Guava本地缓存的实战

简介: Google Guava本地缓存的实战

前言

很多时候,我们将不得不从数据库或另一个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}

响应

响应信息如下图所示:

image.png

可以在此处找到有关Guava Cache功能的非常详细的说明。在此示例中,我将使用LoadingCache。LoadingCache接收一个代码块,该代码块用于将数据加载到缓存中以查找丢失的密钥。因此,当您使用不存在的键进行缓存时,LoadingCache将使用CacheLoader提取数据并将其设置在缓存中,然后将其返回给调用方。

实体类

现在让我们看一下表示书籍详细信息所需的模型类:

  • Book class
  • Author class
//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.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。该服务执行以下操作:

  1. 从REST API获取HTTP响应。
  2. 使用Jackson的ObjectMapper将JSON解析为Map。
  3. 从步骤2中获得的Map对象中获取相关信息。

从BookService中提取了一些操作到Util类中,即:

  1. 读取包含Google图书API密钥的application.yml文件
  2. 向REST API发出HTTP请求并返回JSON响应。
//Util.java
package 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();
}
}
//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>>() {
     @Override
     public 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"}

以上是对Google Guava作为本地缓存的简单实用!

相关实践学习
在云上部署ChatGLM2-6B大模型(GPU版)
ChatGLM2-6B是由智谱AI及清华KEG实验室于2023年6月发布的中英双语对话开源大模型。通过本实验,可以学习如何配置AIGC开发环境,如何部署ChatGLM2-6B大模型。
目录
相关文章
|
3月前
|
缓存 并行计算 监控
vLLM 性能优化实战:批处理、量化与缓存配置方案
本文深入解析vLLM高性能部署实践,揭秘如何通过continuous batching、PagedAttention与前缀缓存提升吞吐;详解批处理、量化、并发参数调优,助力实现高TPS与低延迟平衡,真正发挥vLLM生产级潜力。
916 0
vLLM 性能优化实战:批处理、量化与缓存配置方案
|
4月前
|
存储 缓存 NoSQL
Redis专题-实战篇二-商户查询缓存
本文介绍了缓存的基本概念、应用场景及实现方式,涵盖Redis缓存设计、缓存更新策略、缓存穿透问题及其解决方案。重点讲解了缓存空对象与布隆过滤器的使用,并通过代码示例演示了商铺查询的缓存优化实践。
253 1
Redis专题-实战篇二-商户查询缓存
|
6月前
|
存储 缓存 安全
Go语言实战案例-LRU缓存机制模拟
本文介绍了使用Go语言实现LRU缓存机制的方法。LRU(最近最少使用)是一种常见缓存淘汰策略,当缓存满时,优先删除最近最少使用的数据。实现中使用哈希表和双向链表结合的方式,确保Get和Put操作均在O(1)时间内完成。适用于Web缓存、数据库查询优化等场景。
|
7月前
|
存储 人工智能 前端开发
Google揭秘Agent架构三大核心:工具、模型与编排层实战指南
本文为Google发布的Agent白皮书全文翻译。本文揭示了智能体如何突破传统AI边界,通过模型、工具与编排层的三位一体架构,实现自主推理与现实交互。它不仅详解了ReAct、思维树等认知框架的运作逻辑,更通过航班预订、旅行规划等案例,展示了智能体如何调用Extensions、Functions和Data Stores,将抽象指令转化为真实世界操作。文中提出的“智能体链式组合”概念,预示了未来多智能体协作解决复杂问题的革命性潜力——这不仅是技术升级,更是AI赋能产业的范式颠覆。
2443 1
|
缓存 安全 Android开发
Android经典实战之用Kotlin泛型实现键值对缓存
本文介绍了Kotlin中泛型的基础知识与实际应用。泛型能提升代码的重用性、类型安全及可读性。文中详细解释了泛型的基本语法、泛型函数、泛型约束以及协变和逆变的概念,并通过一个数据缓存系统的实例展示了泛型的强大功能。
178 2
|
存储 缓存 Java
Java中的分布式缓存与Memcached集成实战
通过在Java项目中集成Memcached,可以显著提升系统的性能和响应速度。合理的缓存策略、分布式架构设计和异常处理机制是实现高效缓存的关键。希望本文提供的实战示例和优化建议能够帮助开发者更好地应用Memcached,实现高性能的分布式缓存解决方案。
253 9
|
安全 中间件 PHP
Google Hacking高级实战-搜索特定口子-敏感信息
Google Hacking高级实战-搜索特定口子-敏感信息
|
缓存 NoSQL 数据库
go-zero微服务实战系列(五、缓存代码怎么写)
go-zero微服务实战系列(五、缓存代码怎么写)
|
Java 数据库连接
提升编程效率的利器: 解析Google Guava库之IO工具类(九)
提升编程效率的利器: 解析Google Guava库之IO工具类(九)
|
缓存 NoSQL Java
惊!Spring Boot遇上Redis,竟开启了一场缓存实战的革命!
【8月更文挑战第29天】在互联网时代,数据的高速读写至关重要。Spring Boot凭借简洁高效的特点广受开发者喜爱,而Redis作为高性能内存数据库,在缓存和消息队列领域表现出色。本文通过电商平台商品推荐系统的实战案例,详细介绍如何在Spring Boot项目中整合Redis,提升系统响应速度和用户体验。
249 0

热门文章

最新文章

推荐镜像

更多