掘金都使用webp图片提速降本,必须安排!

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基础(五)函数式接口-复用,解耦之利刃

相关推荐
七折困2 分钟前
列表、数组排序总结:Collections.sort()、list.sort()、list.stream().sorted()、Arrays.sort()
java·集合·数组·排序
苹果酱056721 分钟前
一文读懂SpringCLoud
java·开发语言·spring boot·后端·中间件
掐指一算乀缺钱42 分钟前
SpringBoot 数据库表结构文档生成
java·数据库·spring boot·后端·spring
晚睡早起₍˄·͈༝·͈˄*₎◞ ̑̑1 小时前
苍穹外卖学习笔记(七)
java·windows·笔记·学习·mybatis
就这个java爽!1 小时前
JAVA网络编程【基于TCP和UDP协议】超详细!!!
java·开发语言·网络·tcp/ip·udp·eclipse·idea
一叶飘零_sweeeet1 小时前
为什么 Feign 要用 HTTP 而不是 RPC?
java·网络协议·http·spring cloud·rpc·feign
懒洋洋大魔王1 小时前
7.Java高级编程 多线程
java·开发语言·jvm
茶馆大橘1 小时前
【黑马点评】已解决java.lang.NullPointerException异常
java·开发语言
星辰@Sea1 小时前
服务注册中心对比及使用场景分析
java·云原生
马剑威(威哥爱编程)1 小时前
除了递归算法,要如何优化实现文件搜索功能
java·开发语言·算法·递归算法·威哥爱编程·memoization