正确返回Http 304 状态信息
对于每一次Web静态资源的请求,Web 服务应用的SampleServlet 通过文件最后一次被改动的时间戳和Http请求中相关信息来确认此资源文件是否被修改过。如果没有被修改过的话,返回HttpServletResponse.SC_NOT_MODIFIED,也就是http code 304状态告诉浏览器请使用本地缓存信息。如果此资源文件已经更新了,SampleServlet 会从硬盘上读取该资源文件,并更新缓存信息。清单 3 示例代码如下:
清单 3. 正确返回Http 304 状态信息1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| if (checkcache(request, resource.getLastModified())){
// 如果在浏览器缓存中,返回 HTTP status code 304
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
private boolean checkcache(HttpServletRequest req, long FileModified){
if (isdebug) //开发环境需要重新加载静态资源文件
return false;
long ModifiedSince = -1L;
try{
ModifiedSince = req.getDateHeader("If-Modified-Since");
}
catch (IllegalArgumentException iae){
ModifiedSince = -1L;
}
long systemTime = System.currentTimeMillis();
if (ModifiedSince != -1L){
if (ModifiedSince / 1000L == FileModified / 1000L){
return true; // 返回304
}
if ((systemTime >= ModifiedSince) && (ModifiedSince > FileModified)){
return true; // 返回304
}
if (systemTime < ModifiedSince){
LOG.log(Level.FINEST, "The IfModifiedSince date is later than the server's current
time so it is invalid, ignore.");
}
}
return false;
}
|
用NFS 服务器存储多版本的静态资源文件这个Web 应用服务器知道部署在本地的静态资源文件在哪里,通常是一个war 包或ear 包。如果用户的请求URL中的版本时间戳和本地部署的版本时间戳一致,那么SampleSevlet 很容易读取本地文件内容,缓存文件内容,并返回给客户端。
在SampleServlet 的doGet 方法中,我们发现资源文件的版本时间戳不是本地Web服务器部署的版本时间戳,就会向NFS 文件服务器发出请求。这需要Web 服务器事先能够访问NFS 文件服务器,否则时间开销会很大,用户请求的响应会很慢。通常在部署Web服务应用的脚本里面或在应用服务启动的过程中挂载到NFS 服务器。清单4给出了示例代码。
清单 4. 请求NFS资源文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| File file = new File(nfsfilepath);
if (file.isFile())
{
long lastModified = file.lastModified();//获取文件的被修改的时间戳
resource.setLastModified(lastModified);
LOG.log(Level.INFO, "The web resource from NFS server is " + nfsfilepath + " and the last
modified time is " + lastModified);
if (isdebug || !cache.containsKey(resourcePath))//查看缓存信息
{
try
{
byte[] bytes = NFSFileUtil.nfs_getFileBytes(file, NFSFileUtil.NFS_RETRY_SECONDS);
resource.setData(bytes);//从NFS 服务器上读取到资源文件
LOG.log(Level.INFO, "Obtained web resource from NFS server for - " + nfsfilepath);
}
catch (IOException e)
{
LOG.log(Level.SEVERE, "Failed to obtain web resource from NFS server for - " +
nfsfilepath, e);
return null;
}
}
}
else
{
LOG.log(Level.SEVERE, "Failed to obtain web resource's lastModified and data from NFS
server for - " + nfsfilepath);
return null;
}
|
zooKeeper 管理静态资源文件读到这里,您一定会考虑这些问题:什么时机把新版本的静态资源文件拷贝到NFS 服务器上?为什么不把NFS 服务器上的资源文件下载到Web 服务器上呢? 什么时机去下载?如果NFS 服务器上的版本越来越多该怎么办?如何管理?这里面涉及到时间复杂度和空间复杂度的算法问题。一个最根本的指导原则,用户请求的响应要及时,妥善处理好程序的性能问题。一个最根本的价值观就是客户为要。
在云平台Web 服务器集群的复杂环境中,我们通过zooKeeper 服务器来管理NFS服务器上哪些静态资源版本在线,哪些版本已经下线,进而及时清理硬盘空间。清单5 给出了python脚本的示例代码:
清单 5. zooKeeper管理资源文件代码示例1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| def updateZookeeperNode():
print "The online version is %s" % ONLINE_VERSION #打印当前准备上线的版本号
if (ONLINE_VERSION in [None,'']):
logging.error("Severe error - the version to be onlined is empty.")
return
offline = None
try:
baseTN = registryParser.getBaseTopologyName() #获取云平台的名称
side = registryParser.getTopologyName() #获取云平台环境即将上线的面,值是A 或 B
versionPath = '/%s/data/SampleApp/version/%s' %(baseTN, side) #版本路径信息
sideValue = zooKeeperClient.getData(versionPath)#获得该环境上已经上线的版本号
if (sideValue == "null" or sideValue in [None,'']): #节点上还没有上线的版本号,第一次上线
print "Create node %s on zookeeper and its value is %s" % (versionPath, ONLINE_VERSION)
logging.info("Create node %s on zookeeper and its value is %s" % (versionPath, ONLINE_VERSION))
zooKeeperClient.createNode(versionPath, ONLINE_VERSION) #节点上存储上线的版本号
return
else:
logging.info("Old %s SampleApp version is %s" % (side, sideValue))#输出旧的版本号
if(sideValue != ONLINE_VERSION):
zooKeeperClient.setData(versionPath, ONLINE_VERSION) #新版本上线
logging.info("New %s SampleApp version is %s" % (side, ONLINE_VERSION))
children = zooKeeperClient.getChildNodes('/%s/data/SampleApp/version' %(baseTN))
offlineFlag = True
for node in children: # 循环查看即将下线的版本在其他云平台环境中有没有被用到
#Filter itself and empty side name
if(node == side or node == ''):
continue
else:
data = zooKeeperClient.getData('/%s/data/SampleApp/version/%s' %(baseTN, node))
if(data == sideValue):
offlineFlag = False #其他环境正在使用该版本号,不删除其在NFS上的资源文件
break
if(offlineFlag):
offline = sideValue # 没有其他环境用到,旧版本准备下线
else:
logging.info("The obsolete version %s is still using by anther side." % sideValue)
else:
print "Flip failover case - no new version is online."
logging.info("Flip failover case - no new version is online.")
if (offline is not None):
logging.info("The offline version is %s" % offline)
global OFFILE_VERSION
OFFILE_VERSION = offline
except Exception,e:
logging.error(e)
raise
|
在部署Web应用程序的时候,调用清单5的示例脚本把资源文件信息拷贝到NFS服务器上。在集群环境中,不需要在部署每一个服务节点的过程中都这么做。示例中,我们通过zookeeper选举出一个节点来完成静态资源到NFS 服务器的拷贝任务。 |