分类目录归档:其它

GIT本地仓库与远程仓库

很多初学GIT的同学对GIT的本地仓库和远程仓库无法做到很好的区分,我就画了一张图,来阐明他们之间的关系:

Advertisements

ThreadLocal 原理探究

ThreadLocal 的作用:

This class provides thread-local variables. 提供了线程级别的本地变量。

ThreadLocal的原理:

  1. 每一个线程Thread内部维护了两个package private级别的变量:
    • ThreadLocal.ThreadLocalMap threadLocals
    • ThreadLocal.ThreadLocalMap inheritableThreadLocals
  2. 每一个 ThreadLocal 对象有一个创建时生成唯一的 Id称为:threadLocalHashCode,访问一个 ThreadLocal 变量的值,就是用threadLocalHashCode去 本线程 的 ThreadLocalMap 中查找对应的值。
    private Entry getEntry(ThreadLocal<?> key) {
     int i = key.threadLocalHashCode 
     & (table.length - 1);
     Entry e = table[i];
     if (e != null && e.get() == key)
         return e;
     else
         return getEntryAfterMiss(key, i, e);
    }
  3. ThreadLocalMap 为一个静态内部类,包含了一个Entity数组(Entry[] table)来保持ThreadLocal的线程变量;

继续阅读

MapStruct 代替BeanUtil 和ModelMapper

一、Object mapping 的技术分类:

  1. 运行期 反射调用set/get 或者是直接对成员变量赋值 。 该方式通过invoke执行赋值,实现时一般会采用beanutil, Javassist等开源库。这类的代表:Dozer,ModelMaper
  2. 编译期 动态生成set/get代码的class文件 ,在运行时直接调用该class文件。该方式实际上扔会存在set/get代码,只是不需要自己写了。 这类的代表:MapStruct,Selma,Orika ### 主要框架性能对比: 每秒钟执行的object mapping越多越好。 

明显可以看出通过在运行期进行反射的方式执行,性能远不如编译器生成class的方式;

MapStruct 与 Selma的对比:https://java.libhunt.com/project/mapstruct/vs/selma

MapStruct 与 ModelMapper的对比:https://java.libhunt.com/project/mapstruct/vs/modelmapper

综合比较性能、问题排查、文档、成熟度、扩展性等因素来考虑,MapStruct 是一个不错的选择;

继续阅读

Task混用ThreadPool导致无限等待

现象:

生产环境商品打标异步任务提交任务后,任务没有被执行;查看日志,没有异常日志抛出。

初步猜测:

可能是队列出现了饱和或者死锁,但是如果出现了饱和,我们设置的线程池设置的饱和策略是通过主线程去执行,为什么主线程也没有执行呢?

具体分析:

我定义了一个线程池Pool-Z,core_size=5,max_size=20,queue_size=1000,第一个任务A提交后,占用一个线程,那么这个任务A又会被分解成多个异步任务去执行,比如分解为A1、A2、A3(这些子任务为耗时任务),这些异步任务使用了同一个线程池Pool-Z提供线程,但是任务A内部做了一个事情,通过CountDownLatch.await实现了当所有A的子任务都执行完毕后,才执行后续的一个扫尾工作,也就是说线程A会等待同一个线程池中的A1、A2、A3执行完毕后才能释放出线程A的资源;如果没有其它的主任务B或者C加进来,这些任务总会被执行完毕;但是如果主任务提交非常频繁,比如连续提交了几个包含非常多子任务的B、C、D进来,此时这样的不敢具体子任务只是等待子任务完成的主任务就占用了4个主要线程,只有一个子任务在执行工作,由于不会分配新的线程来处理子任务,所以B、C、D的子任务都会被扔到队列里面,知道队列满掉,当队列慢了后,会开辟新的线程来处理任务,但是我们知道由于很多主任务提交进来都处于等待子任务释放而处于等待状态,如果当某一刻20个核心线程都成为了主任务且都在等子任务执行完毕的时候,子任务就得不到线程,锁等待就可能发生了,因为当时配置的任务队列比较大,为3000,所以当时加入的任务都被加入到了队列中,由于总数没有达到3000个,所以没有触发线程池的饱和策略。

解决方案:

  • 方案一是去掉锁的使用,且需要保证相同业务含义;实现较困难;
  • 方案二把两个线程池拆开,不共用队列,避免了相互等待而产生死循环;

总结:

  • 尽量避免线程之间的依赖;处理“当子任务都完成后再执行主任务”的场景最容易产生这样的线程依赖问题;
  • 尽量避免使用wait、lock等操作;如果必须使用也切记加入超时时间;
  • 对线程池的使用时最好自定义线程池的名称,方便拍错和定位问题;
  • 如果需要使用内部类且内部类不需要应用外部类的属性,尽可能把内部类设置为static形态;

继续阅读

商品打标签设计踩过的坑

问题一:同步操作,导致批量处理过慢,锁超时

现象:在批量打标过程中,由于之前设计的是同步操作,一次打标的商品数过多时,导致执行时间较长,运营以为操作失败,多次重新导入和点击,导致系统出现不可预知情况。
原因:之前为了数据的一致性,对打标过程使用了事务处理,所以当批量打标的时候,相当于对这批数据都进行了行锁,其它操作这些数据的请求都会等待事务的提交,当等待超时,就报错了,“Lock wait timeout exceeded; try restarting transaction”(排查过程)。
代码:5F6F0B52-DC6D-4208-810B-596151DDB267
解决方案:分析业务非强一致性,允许出错,故将事务去掉,将任务修改为异步任务,引入了自动重试机制,并将出错的任务加入到异常队列,用户可以看到那些是失败的任务,并主动进行重试(一般失败原因为商品主图太大,比如20~30M,导致打标失败)。
总结:
  • 系统设计前缺乏整体性思考,明知道同步操作会导致业务处理缓慢,但未进行主动改进。
  • 对项目编写完整的技术方案还是有必要的,有助于梳理现有业务的特性,并为之提供特定的解决方案。

继续阅读