JAX-RS中的缓存是如何工作的?

11
假设我有以下使用@GET方法的Web服务调用:
@GET
@Path(value = "/user/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response getUserCache(@PathParam("id") String id, @Context HttpHeaders headers) throws Exception {
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("id", id);
    SqlSession session = ConnectionFactory.getSqlSessionFactory().openSession();
    Cre8Mapper mapper = session.getMapper(Cre8Mapper.class);

    // slow it down 5 seconds
    Thread.sleep(5000);

    // get data from database
    User user = mapper.getUser(map);

    if (user == null) {
        return Response.ok().status(Status.NOT_FOUND).build();
    } else {
        CacheControl cc = new CacheControl();
        // save data for 60 seconds
        cc.setMaxAge(60);
        cc.setPrivate(true);
        return Response.ok(gson.toJson(user)).cacheControl(cc).status(Status.OK).build();
    }
}   

为了进行实验,在从数据库中获取数据之前,我会将当前线程放慢5秒。
当我使用Firefox Poster调用我的web服务时,在60秒内,第二次、第三次等等似乎要快得多,直到超过60秒。
然而,当我将URI粘贴到浏览器(Chrome)中时,它似乎每次都会减慢5秒。我真的很困惑这种技术究竟是如何进行缓存的。以下是我的问题:
  1. POSTER是否真的查看头部的max-age并决定何时获取数据?
  2. 在客户端(Web、Android等)访问我的Web服务时,我需要检查头部然后手动执行缓存,还是浏览器已经自动缓存了数据?
  3. 有没有办法避免每次从数据库获取数据?我想我必须以某种方式将我的数据存储在内存中,但它可能会耗尽内存吗?
  4. 在这个教程JAX-RS caching tutorial中:

    缓存实际上是如何工作的?第一行总是从数据库获取数据:

    Book myBook = getBookFromDB(id);

那么它如何被认为是缓存的呢?除非代码不按照自上而下的顺序执行。

    @Path("/book/{id}")
    @GET
    public Response getBook(@PathParam("id") long id, @Context Request request) {
        Book myBook = getBookFromDB(id);
        CacheControl cc = new CacheControl();
        cc.setMaxAge(86400);
        EntityTag etag = new EntityTag(Integer.toString(myBook.hashCode()));        
        ResponseBuilder builder = request.evaluatePreconditions(etag);
        // cached resource did change -> serve updated content
        if (builder == null){
            builder = Response.ok(myBook);
            builder.tag(etag);
        }
        builder.cacheControl(cc);
        return builder.build();
    } 

3
附注,不回答问题:你可以使用 TimeUnit.DAYS.toSeconds(1) 代替 86400。这样更容易阅读,而且无需进行解码努力。 ;) - fge
2
同样地,顺便提一下,你可以使用 TimeUnit.SECONDS.sleep(5) - fge
关于你的问题,你分析了浏览器接收到的头部吗?看起来是浏览器的行为不同,我不认为任何JAX-RS实现会针对不同的浏览器产生不同的头部,但是... - fge
海报依赖于 Firefox 的缓存机制。但是您需要使用 Firebug 网络选项卡来查看您的 GET 请求是否已被缓存。 - shawnzhu
2个回答

9
从你的问题中我看到你将客户端缓存(http)和服务器端缓存(数据库)混淆了。我认为这个问题的根本原因是你在Firefox和Chrome上观察到的不同行为。首先,我将尝试澄清这一点。

当我使用Firefox Poster调用我的web服务时,在60秒内,从第2次、第3次等开始,速度似乎更快,直到过去60秒。然而,当我将URI复制到浏览器(Chrome)中时,它似乎每5秒就变慢了。

示例:

 @Path("/book")
    public Response getBook() throws InterruptedException {
        String book = " Sample Text Book";
        TimeUnit.SECONDS.sleep(5); // thanks @fge
        final CacheControl cacheControl = new CacheControl();
        cacheControl.setMaxAge((int) TimeUnit.MINUTES.toSeconds(1)); 
        return Response.ok(book).cacheControl(cacheControl).build();
    }

我有一个正在运行的restful webservice,其url为

http://localhost:8780/caching-1.0/api/cache/book - GET

火狐浏览器:

第一次访问URL时,浏览器向服务器发送请求并得到带有缓存控制头的响应。

fiefox initital req

60秒内的第二个请求(使用Enter):这次Firefox没有去服务器获取响应,而是从缓存中加载数据。

enter image description here

60秒后的第三个请求(使用Enter):

这次Firefox向服务器发出了请求并得到了响应。

使用刷新(F5或Ctrl F5)的第四个请求:

如果我在上一个请求的60秒内刷新页面(而不是点击Enter),Firefox不会从缓存中加载数据,而是使用请求中的特殊头向服务器发出请求。

enter image description here

谷歌浏览器:

60秒内的第二个请求(使用Enter):这次Chrome再次向服务器发送请求,而不是从缓存中加载数据,并在请求中添加了头cache-control = "max-age=0"

聚合结果:

由于Chrome对Enter点击的响应不同,你看到了Firefox和Chrome的不同行为,这与jax-rs或你的HTTP响应无关。总之,客户端(Firefox/Chrome/Safari/Opera)将在缓存控制中缓存数据一段指定的时间,在时间到期之前或者我们进行强制刷新之前,客户端将不会向服务器发出新的请求。

希望这解答了你的问题1、2、3。

4.在这个JAX-RS缓存教程中:缓存实际上是如何工作的?第一行代码总是从数据库获取数据:

Book myBook = getBookFromDB(id);

那么它怎么算被缓存了呢?除非代码不按照自上而下的顺序执行。

你所提到的例子并不是在谈论如何最小化数据库调用,而是在讨论如何节省网络带宽。客户端已经有了数据,并且正在检查服务器是否更新了数据,如果响应中没有数据更新,你就会发送实际实体。


1
  1. 是的。

  2. 当使用像Firefox或Chrome这样的浏览器时,您不需要担心HTTP缓存,因为现代浏览器会自动处理它。例如,在使用Firefox时,它使用内存中的缓存。在使用Android时,它取决于您如何与源服务器交互。根据WebView,它实际上是一个浏览器对象,但如果使用HTTPClient,则需要自己处理HTTP缓存。

  3. 这与HTTP缓存无关,而是与您的服务器端逻辑有关。常见的答案是使用数据库缓存,这样您就不需要在每个HTTP请求中访问数据库。

  4. 实际上,JAX-RS只提供了一些处理HTTP缓存头的方法。您需要使用CacheControl和/或EntityTag来进行基于时间的缓存和条件请求。例如,使用EntityTag时,生成器将处理响应状态代码304,您永远不需要担心。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接