不停机图片升级迁移

零度    2018/08/15    总阅读量

高并发、锁   思考   面试   实践   Netty   Linux   Redis   MySQL   Nginx   Maven   Git   ElasticSearch   Spring  

在我看来,凡是真实环境需要不停机迁移,其实都是有的复杂的,需要考虑的事情有很多,也可以从重思考到很多,以及以后做类似事情可以提前考虑后期的迁移等事宜。

迁移需求

原来图片服务使用技术为A,随着图片慢慢越来越多,使用的A技术带来的弊端越来越多,后来了解到技术B,技术B目前各各方面都挺好,也满足要求,现在就有一个难题,如何使得目前图片服务由原来的A切换到B呢?由于技术A实现和技术B实现方式区别,图片存储方式都进行了变化,需要进行迁移到B实现方式上面,并且需要做到不间断,持续提供服务。由于真实比较复杂,简单抽象下:

  • 图片具体文件存储在用技术A实现的目录下面。
  • 所有的图片元信息存储在数据库里面。
  • 有N多地方会操作这个元信息表。
  • 很多图片地址可能被收录了。
  • 可能有些手机版本用户没有升级,一直用老版本情况。

需要换一个图片实现技术B,但是上面的这些问题都需要考虑,并且需要做到不间断,持续提供服务。主要使用图片技术B之后图片技术A的图片存放地方变了,导致元信息表里面的某些字段也需要由于技术B的改动而改动。

难点

  1. Java Web技术中有两种跳转,服务端跳转、客户端跳转。

    forward这种它只能服务内部资源进行跳转,超出该服务外就没办法进行跳转了(地址栏不变,但是图片资源又不在这个服务里面),而sendRedirect中则可以任意转(但是地址栏会进行变化,很多图片服务都是内部ip等资源,外网不可以自己访问的,并且地址栏不希望进行改变) 。

  2. 如何保证使用技术B的方式的图片和技术A的图片一样?如何验证呢?

    为了安全,你怎么保证那么多图片由B技术迁移过去都是正常的,如何让自己放心,如何验证呢?

  3. 原来使用的元信息表的地方不能修改,怎么办?

    由于原来使用的元信息表的地方有N个地方,换表(字段一样)不可能,修改面太大了。如果不修改如何保证一切正常,并且迁移成功呢?

  4. 收录的地址还必须要打开。

    原来的地址规则还是可以打开,该怎么处理呢?

  5. 不间断,持续提供服务。

    1.把老的历史图片迁移新图片,老的图片还在源源不断的产生,什么时候可以是个头呢?

    2.各各通道不可能一瞬间生效,话外之音就是有新的、有老的共存的时候但是如何又不能影响呢?

    3.先迁移查看图片地址呢? 还是先迁移上传图片地址呢? 2个先后如何确定才可以保证正确呢?

备注:如果是数据迁移,加一个MQ可能就很方便了,但是这里和数据迁移还是有点不同,需要考虑的稍微有点多。

如何做?

针对跳转问题

我们可以把图片资源拿到服务本地,在流写回去(性能不好,并且如果并发各各方面大的话,本次磁盘可能不够,还有一个新的问题存放在本地磁盘的文件什么时候删除呢? 都是问题)???

Nginx代理可以实现跳转并且地址栏不用变化(proxy_pass)。

双写

只有双写才会达到追上,所以必须双写(可能会考虑如果写第一个成功,第二个失败,没关系,后续还有扫描操作补数据),需要建立和原来的元信息表字段一样的表,表名后面多加一个_new即可。修改老服务(在里面添加发送请求到新的,达到双写目的),这样补齐原来老的图片即可。

SQL语句查询数据在A表而不再B表的数据:

select a.id from a left join b on a.id = b.id where b.id is null

或者

select a.id from a where not exists (select 1 from b where a.id = b.id)

通过这样可以不断扫表把数据入到新的图片服务以及新表,把历史数据补齐了。

验证新老图片一样

网上有通过基于像素的比对,但是太耗时,并且需要内存等比较大,一般比较图片大小,长宽以及人抽查看看就行了。

在好友李岩的提醒下,我去试了下md5的确也可以,而且方便很多,感谢!!!

20180815-md5

public static void main(String[] args) throws Exception {
        System.out.println(DigestUtils.md5Hex(new FileInputStream("C:\\Users\\Administrator\\Desktop\\bcd.jpg")));
        System.out.println(DigestUtils.md5Hex(new FileInputStream("C:\\Users\\Administrator\\Desktop\\tab.jpg")));
    }

老的地址还必须打得开

Nginx还是很重要的,通过nginx代理到新的即可,这里需要做一层新老映射,比如olda—->newa这样的地址,有表那么也好梳理的,借助redis里面存储(key为olda,value为newa),获取就非常方便了。

切查看再切上传

既然老的地址已经做了代理了,那么先切查看还是先切换上传无所谓了(由于老的查看已经走了新的查看了,所以本质还是需要先切查看)。

各各服务切换新的服务上传接口和查看地址前缀(如果走统一配置就很快,如果走配置文件就一个一个服务走)。

备注:由于表不能修改,或者字段不能修改,调用方太多,那么意味着新服务也是写老表。

刷老表数据

由于新的服务也向老表写数据了,那么必须把一起老表的数据刷成新表里面的新的数据才行(注意就是图片资源路径信息)。

特别注意

由于图片上传需要考虑图片大小情况,比如有些图片比较大,需要特别设置上传文件大小设置,特别是springboot的各各版本设置都是不一样的,以springboot2.x的配置如下:

参考:https://docs.spring.io/spring-boot/docs/current/reference/html/common-application-properties.html
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

最佳实践

有时候我们做的东西在测试环境都测试完成了,可能要上真实了,特别是复杂项目,涉及东西多的时候,建议大家先列一个上线清单,自己心里梳理几遍上线做的事宜,那么上线的时候不至于乱甚至出错等,这是一个好的建议,分享给大家。

结束语

本人水平有限,难免会有一些理解偏差的地方,如果发现,欢迎各位积极指出,感谢!!!


扫描关注:匠心零度

(转载本站文章请注明作者和出处 匠心零度-jiangxinlingdu

Show Disqus Comments

腾讯云:新客户代金券
腾讯云:3年时长最低265元/年
阿里云:ECS云服务器2折起

目录