1. 背景
站点性能优化一个很重要的方向是图片压缩,一方面能提升客户端访问性能,另外一方面因为图片体积变小,能大大减少站点的流量成本,特别是博客,相比文本内容,大量的图片占了流量成本的大头。
而图片压缩后,webp格式通常比其他常见图片格式如jpg,png等体积小(不是100%一定小),所以没有什么理由不用webp。
之前没有使用webp的原因有几点:
● 并非所有浏览器都支持webp格式,如果要兼容所有浏览器,有点麻烦。
● jpg、png图片压缩比还行,已经减少了不少,尚能接受。
不过好几次看到掘金使用了webp格式图片,想想大厂都用,也安排上吧。
2. 掘金图片使用方式
打开开发者工具,查看图片使用方式:
图片链接:
java
https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bcb9c940a37c4f0ab192094c268f0844~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=1199&h=367&s=368599&e=jpg&b=0e0e0e
从链接上大概可以猜到一些参数含义:
序号 | 类型 |
---|---|
q75 | 图片压缩质量,满分100 |
awebp | 看起来会用到webp,用不用还要根据客户端请求做判断 |
w | 原图宽度 |
h | 原图高度 |
e=jpg | 原图格式,如果客户端浏览器不支持webp,就显示原图格式,而即便是显示原图格式,也应该是压缩后的 |
另外从这个链接来看,返回给客户端的图片格式应该是动态生成的。
3. 使用webp图片
3.1 压缩比 比较
使用webp的原因是图片质量尚可,压缩比高于其他图片格式,因此比较一下做到心里有数。
以下图片压缩使用的工具是:tinify
比较结果:
图片 | 原始大小 | 原格式压缩 | webp |
---|---|---|---|
02获取连接.jpg | 50 KB | 30 KB | 35 KB |
03连接池资源.jpg | 23 KB | 12 KB | 10 KB |
06池的动态伸缩.jpg | 14 KB | 9 KB | 11 KB |
06资源不足.jpg | 38 KB | 20 KB | 15 KB |
07状态-state.jpg | 17 KB | 9 KB | 9 KB |
08工作线程.jpg | 32 KB | 18 KB | 24 KB |
evict状态.jpg | 7 KB | 14 KB | 4 KB |
jvmparameter.jpg | 68 KB | 30 KB | 10 KB |
弱引用.jpg | 236 KB | 81 KB | 34 KB |
资源相关类.jpg | 10 KB | 6 KB | 9 KB |
资源状态一.jpg | 9 KB | 6 KB | 9 KB |
累计大小 | 504 KB | 235 KB | 170 KB |
从这个结果可以看到:
● webp格式并不是总是比原格式压缩比小。
● 所有图片总的压缩体积,webp格式更小,如果总能保持这样,webp格式是划算的。
3.2 压缩工具比较
webp是谷歌推行的,并有配套的图片转换工具cwebp和gif2webp,参考:谷歌webp使用入门
比较结果:
图片 | 原始大小 | cwebp | tinify |
---|---|---|---|
02获取连接.jpg | 50 KB | 36 KB | 35 KB |
03连接池资源.jpg | 23 KB | 11 KB | 10 KB |
06池的动态伸缩.jpg | 14 KB | 11 KB | 11 KB |
06资源不足.jpg | 38 KB | 16 KB | 15 KB |
07状态-state.jpg | 17 KB | 9 KB | 9 KB |
08工作线程.jpg | 32 KB | 25 KB | 24 KB |
evict状态.jpg | 7 KB | 4 KB | 4 KB |
jvmparameter.jpg | 68 KB | 11 KB | 10 KB |
弱引用.jpg | 236 KB | 35 KB | 34 KB |
资源相关类.jpg | 10 KB | 9 KB | 9 KB |
资源状态一.jpg | 9 KB | 9 KB | 9 KB |
累计大小 | 504 KB | 176 KB | 170 KB |
注:这里cwebp的压缩质量设置和掘金一样,为75,这也是cwebp命令的默认值。
可以看到,cweb的压缩比和tinify的压缩比差不多,再对比实际图片效果,肉眼也难以分辨出来,这么来看,花307.52元买的tinify年费,并不是很划算,好在tinify除了转换webp还能压缩原格式。
3.3 兼容处理
webp图片并非所有浏览器都支持,因此要做兼容处理。
3.3.1 客户端兼容
客户端可以通过如下方式兼容:
java
<picture>
<source type="image/webp" srcset="/images/02sourcecode/01hikaricp/webp/02获取连接.webp">
<img src="/images/02sourcecode/01hikaricp/compress/02获取连接.jpg">
</picture>
同时准备两个图片,如果浏览器支持就会显示source中的图片,否则会显示img中的图片。
不过这种方式对于一个图文发布系统,操作不是很友好,因为不太可能让发布者手动去添加这样的内容。
如果是个人博客,就自己一个人去发布文章,那还能接受。
3.3.2 服务端兼容
兼容处理流程大致如下:
3.3.2.1 图片上传流程
● 存储原始图片
这一步不是必须的,如果使用原始格式压缩图片,原始图片实际可以不要。
● 压缩并存储图片
一般就是压缩为两种格式,原始格式和webp格式。
图片文件存储可以放到类似S3之类的分布式对象存储库中,这样方便分布式部署的web server访问。
● 生成图片链接
这个图片链接需要包含可以推导出最终要访问的图片的相关信息,例如图片格式,图片大小等等。
图片上传流程不是支持webp新引入的技术,具体实现不再展开描述。
3.3.2.1 图片请求流程
● 判断是否支持webp
服务端可以根据客户端请求头中的"Accept"来判断是否支持webp:
代码参考如下:
java
private boolean isSupportWebp() {
String header_accept = request.getHeader("Accept");
if (header_accept != null && header_accept.indexOf("image/webp") > -1) {
return true;
} else {
return false;
}
}
● 根据图片链接获取对应图片路径
这里仅示例用,图片链接为:
java
http://10.0.0.8:8042/api/requestImg?imageId=获取连接&sourceImageType=jpg
参数 | 描述 |
---|---|
imageId | 图片标识,用于获取正确的图片 |
sourceImageType | 原始图片类型,如果不支持webp,就使用原始图片类型的压缩图片 |
代码参考,这里仅是简单从服务器classpath获取图片:
java
private static final String IMG_HOME_PATH = "/static/images/02sourcecode/01hikaricp";
public String getImageFilePath(String imageId, String sourceImageType) {
String filePpath = "";
if (isSupportWebp()) {
filePpath = IMG_HOME_PATH + "/webp/" + imageId + ".webp";
} else {
filePpath = IMG_HOME_PATH + "/compress/" + imageId + "." + sourceImageType;
}
return filePpath;
}
● 返回图片
基本思路是根据请求参数和浏览器请求头信息,设置对应的响应头信息和返回图片,代码参考如下:
java
@RequestMapping("/api/requestImg")
public void requestImg(@RequestParam("imageId") String imageId, @RequestParam("sourceImageType") String sourceImageType) throws IOException {
// 可以设置缓存,使用这种方式时,需要注意每次重新上传相同图片,链接地址要变化,否则下次还是会使用缓存图片
response.setHeader("Cache-Control", "max-age=3153600");
if (isSupportWebp()) {
response.setContentType("image/webp");
} else {
response.setContentType("image/" + sourceImageType);
}
String filePath = getImageFilePath(imageId, sourceImageType);
ClassPathResource resource = new ClassPathResource(filePath);
try (InputStream in = resource.getInputStream(); OutputStream os = response.getOutputStream()) {
byte[] b = new byte[1024];
while (in.read(b) != -1) {
os.write(b);
}
os.flush();
}
}
● 性能验证
通过以上步骤,看起来已经支持服务端动态返回webp图片,但这种方式是先读取浏览器可支持的图片,然后再将图片以流的方式返回给客户端,虽然能返回webp图片,但是性能和直接访问webp图片链接一样么?
以下是远程网站测试结果:
序号 | 类型 | 性能 |
---|---|---|
第一行 | 原始jpg图片 | 94 ms |
第二行 | 压缩后jpg图片 | 33 ms |
第三行 | 压缩后webp图片 | 43 ms |
第四行 | 动态返回webp图片 | 37 ms |
可以看到基本是图片体积越大越耗时。
需要注意的是:如果网络不稳定会极大影响测试结果,有时候会出现动态返回的webp图片耗时大大超出静态图片耗时,因此以上结果仅供参考。
测试方法:为了避免互相干扰,以上测试结果是通过4个独立页面分别去验证一种获取图片方式,各个页面内容如下:
java
// 原始图片
<img src="/images/02sourcecode/01hikaricp/02获取连接.jpg">
// 压缩后原格式图片
<img src="/images/02sourcecode/01hikaricp/compress/02获取连接.jpg">
// webp图片
<picture>
<source type="image/webp" srcset="/images/02sourcecode/01hikaricp/webp/02获取连接.webp">
<img src="">
</picture>
// 动态获取webp图片
<img src="http://mysite.com/api/requestImg?imageId=02获取连接&sourceImageType=jpg">
4. 总结
● webp图片格式的体积小于压缩后的主流图片格式,同时图片质量可以满足非高清要求的站点,而图片体积减小对于提升性能和降低流量成本均有很大帮助,值得使用。
● 通过压缩工具对比,说明选择合适工具的重要性,收费工具性价比未必就一定高。
● 通过客户端和服务端兼容方式对比,可以决定什么场景使用哪种方式。
● 以mvp验证了动态获取方式流程能通,生产环境需要考虑图片存储在什么地方,例如使用对象存储数据库S3;另外生产环境要在网络稳定情况下做好性能测试,验证动态获取方式实际是否存在性能问题,避免做无用功。
注意:以上动态获取webp图片方法并非唯一方式,nginx或cdn也都可以实现,可以根据实际情况选择。
其他阅读:
如何编写软件设计文档
Spring Cache架构、机制及使用
布隆过滤器适配Spring Cache及问题与解决策略
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(五)函数式接口-复用,解耦之利刃