记一次项目经历遇到各种难题

记一次项目经历遇到各种难题

第一回 接到任务

小帅刚到公司不久便接到一个任务,需要将甲方公司的人员、部门等信息同步到我方产品中。
小帅很兴奋,想要展现一下自己的实力,于是什么jdk1.8新特性什么的花里胡哨的东西都给用上,为了避免过多的麻烦,因此设计的时候是将两方数据都拿出来,在内存中比较那些是新增数据,哪些是重复数据(重复数据不论是否更新除了工号等不变的东西都会置为最新的),哪些是需要删除的数据(甲方未对数据做分页处理)。然后一一对这些数据进行处理,小帅对自己的设计很满意。

第二回 遇到难题

测试途中遇到很多问题,包括甲方数据变更,不符合规则数据未进行验证,我的天,鬼知道他们给的数据不是全数据呀,不是全数据就算了,为啥数据结构,字段都会变,当然小帅狠狠的紧张了一把,因为第一个项目都这么多bug,而且因为环境原因,测试提供版本也很复杂,大大的延后了进度。当然,这些都是外部原因,也是比较简单的bug,小帅在这里就不介绍了。

当然,这都不是重点,重点是由于全数据太多,全量同步居然需要20多分钟(首次同步全量),增量居然也需要20多分钟(第一回有过伏笔:每次更新如果重复数据都会置为最新的,这也就意味着需要对人员进行很多次IO请求,比如照片呀等等其他信息)。

由于小帅比较菜,采用的是串行方式进行同步,当然,导师建议过用并发的方式,但是甲方不太在意时间,导师也没让我改。

后来在导师的建议下,设计了一个标记字段,只有首次同步才会去请求照片啊,等等其他信息。但是,这也是治标不治本。首次同步依然需要20多分钟,增量也需要10多分钟

第三回 解决难题

上回说到,由于数据量太大,每条数据都会请求IO,而且还是对两方系统发起至少2次以上IO,你们说,耗时能不长么。虽然甲方不在意,但是我调bug痛苦呀。。但是怎么解决,让甲方改接口?明显不现实。于是我尝试启用线程池来执行任务。由于线程上下文切换也是需要时间的,线程过多很有可能会导致时间更长,于是首先是选择一个合适的大小。由于数据也不是太多,选择了每个线程分1000个数据,根据总数除以1000看是否有余数啊设置了线程池的大小。然后开了3个线程池,一个线程池负责比较两方数据,向重复数据集合以及应增加数据集合中写入数据。一个线程池负责将重复数据集合中的数据更新到己方产品中。一个线程池负责将增加数据集合增加到己方产品中。

第四回 再遇难题

小帅对自己的修改很满意,进行了测试,首次同步需要2分钟到3分钟,增量同步也仅仅需要1分钟左右。小帅很兴奋,不停的点击这同步,突然,界面跳出了空指针字样。小帅大吃一惊,不过还好,控制着而已,应该是哪里没有判空,加上就是了。于是根据控制台信息找到了报错地方。what???只见

public void updateRepeatPerson(List<PersonBO> personBORepeatList){
        if (!CollectionUtils.isEmpty(personBORepeatList)) {
            List<Long> idList = personBORepeatList.stream().map(PersonBO::getId).collect(Collectors.toList());

怎么可能,集合判空了,由于数据是从本方系统查出来,ID也不可能为空,但是处于谨慎对集合进行了过滤,拿出ID不为空的数据,但是没用,依然偶尔报空指针。这可烧脑了,为啥这一句会报空指针,为啥还是偶尔报呢,改成foreach也是一样。

第五回 再解决难题

小帅突然想起来之前这段代码可没报过空指针呀,为啥这次报了呢,难道是因为多线程,可是不应该是线程池插入集合的时候就应该报空指针么,为啥在这个位置,而且在我的印象中,集合操作不是只有删除操作因为改变了size大小影响遍历才会报空指针么,插入又没遍历,而且插入的数据都不相关为啥会报空指针呢?沉思了一会儿,决定先去看看ArrayList源码

 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

总算是找出原因了,如此说来就能说明为啥遍历集合的时候会报空指针了。插入会有线程安全问题呀。

于是小帅又尝试着解决这个问题,嘿嘿,虽然并发编程经验几乎没有,但是当初因为面试可是了解了不少。什么线程安全的Vectory,什么集合工具类–Collections。都花里胡哨来一遍,可是明显不行,因为写也锁住了,那么多线程还有什么意义呢?于是就得读写分开加锁,于是又想到读写锁ReentrantReadWriteLock。仔细一想貌似也不太行,对如果对集合的写入操作在一个代码块里还好,但是由于处理逻辑比较复杂,多个代码块都有对集合的写入操作,这样来使用ReentrantReadWriteLock貌似并不是最好的选择,而且也不太好实现。

小帅一直以来都想着各种花里胡哨的操作,默默无闻的synchronized一直被小帅遗忘在某个角落,直到小帅感觉无法驾驭这些花里胡哨的东西时终于想起了synchronized。于是开始考虑synchronized加在哪个地方呢?粒度太大可是会影响效率的,所以肯定不能再数据比较的方法上加synchronized,那么哪个位置最合适呢?既然线程安全问题是因为写入导致的,那么在写入的时候加不就行了。于是小帅将两个集合的写入都抽到一个方法里面,并将每一个出现在代码块中的添加操作都替换成方法

public synchronized List<PersonBO> addRepeatPerson(List<PersonBO> personBORepeatList,PersonBO personBO){
        personBORepeatList.add(personBO);
        return personBORepeatList;
    }
    public synchronized List<PersonBO> addAddPerson(List<PersonBO> personBOAddList,PersonBO personBO){
        personBOAddList.add(personBO);
        return personBOAddList;
    }

虽然这种操作看着很low,但是很好的解决了小帅的问题,至此小帅的项目,暂时高一段落了,再次进入测试。

第六回 感想

刚接到项目,很兴奋,急于展现自己,各种花里胡哨的都给用上,解决问题也是,最开始总想着有没有什么花里胡哨的东西来解决这个问题。由于这种惯性思维,或许不是很困难的问题,让小帅解决起来却不是那么容易。最合适的方法不是花里胡哨的技术,而是刚好能解决当前问题的技术。

博主公众号
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值