对自己写的代码的反思 编程
程序员文章站
2024-02-02 08:40:16
...
代码review
写这篇文章的原因是昨天在工作期间完成业务要求的时候,根据组长的要求写了自己的代码,结果在之后组长review的时候,发现了很多不足的地方,所以写这篇博客进行记录。
话不多说,先上原始代码。
这里先大概解释一下这段代码的业务需求。
由于在业务方进行一个查询工作的时候,发现有一段查询语句的时间抖动非常大,最长耗时可能到6000ms,快的时候在400ms左右。所以组长分析是该查询语句涉及到排序,而solr周期性的softCommit之后,会需要对整个docValue进行重新merge,所以会造成周期性的抖动。所以需要在softComiit之后先进行对docValue的预热。
确定逻辑后,让我上手完成这个逻辑。整个代码逻辑也不复杂,就是写一个eventListener,在init()方法中初始化solrconfig.xml的配置,在newSearcher()方法中对docValue进行预热。
所以写了以上这一版本,在组长review的时候,发现了很多不足,现在记录一下,也是以后尽量避免。
1: 在多线程并发情况下,没有设置主线程的堵塞,及在多线程同时对docValue进行预热的情况下,主线程并不等待预热完成而直接继续执行。针对这个问题,在修改的第一版本中,在多线程开启后,加入thread.join(),完成主线程的等待,在组长的建议下修改成了countDownLatch。这里顺便写一下countDownLatch和join用法的区别。
本质上countDownLatch和join都实现同一个目的,即多线程并发编程时,当各别线程需要等待特定的其他线程任务执行完成后再执行时,用到以上两个方法。具体使用方法这里先不赘述了,主要讲讲不同的使用场景。
在使用join时,阻塞线程必须等待其他线程任务完全结束后才能继续执行,而使用countDownLatch时,可以任意指定其他线程的执行程度。
举个例子,此时有A,B,C三个线程进行任务执行,假设三个线程任务均为执行100的计数且A线程必须等到B,C计数完成再执行,此时用join或者countDownLatch都能完成任务要求。但是如果任务改成,A线程必须等到B,C都计数到50时,再开始执行,此时join不能完成这个要求,但是用countDownLatch时可以实现,即先
以上代码即可实现要求,以上仅为伪代码,实际编程时注意要把cld.countDown()写在finally里面,避免有一线程崩溃而造成死锁。
第二个不足在于对代码的可配置化程度上,第一版的代码将代码参数写的过于生硬,很难被以后的代码复用,经过组长提示,对AbstractSolrEventListener源码的阅读发现有一个init方法,可以实现对schema.xml文件的配置参数的读取,果断覆写这个方法,将所需要预热的field配置在xml文件中。
本文主要记录一下这次的经验。
写这篇文章的原因是昨天在工作期间完成业务要求的时候,根据组长的要求写了自己的代码,结果在之后组长review的时候,发现了很多不足的地方,所以写这篇博客进行记录。
话不多说,先上原始代码。
public class DocValuesWarmUpListener extends AbstractSolrEventListener{ private static final Logger logger = LoggerFactory.getLogger(DocValuesWarmUpListener.class); private List<String> fields; public DocValuesWarmUpListener(SolrCore core) { super(core); } @Override public void init(NamedList args) { super.init(args); fields = new ArrayList<>(); NamedList o; if(args != null) { o = (NamedList) args.get("fields"); if(o!=null && o instanceof NamedList) { for (int i = 0; i < o.size(); i++) { fields.add((String) o.getVal(i)); } } } } @Override public void newSearcher(SolrIndexSearcher newSearcher, SolrIndexSearcher currentSearcher) { long start = System.currentTimeMillis(); ExecutorService executorService = Executors.newFixedThreadPool(fields.size()); try { //排序所需要用到的字段,用一个线程池多线程预热 for(int i=0;i<fields.size();i++) { executorService.execute(new WarmUpDocValueThread(fields.get(i),newSearcher)); } } catch (Exception e) { throw new IllegalStateException(e); }finally { logger.info("warming up consumes ["+(System.currentTimeMillis()-start)+"] Millis"); } } class WarmUpDocValueThread implements Runnable{ private String field; private SolrIndexSearcher newSearcher; public WarmUpDocValueThread(String field,SolrIndexSearcher newSearcher) { this.field = field; this.newSearcher = newSearcher; } @Override public void run() { try { DocValues.getSorted(this.newSearcher.getLeafReader(), this.field); } catch (IOException e) { e.printStackTrace(); } } } }
这里先大概解释一下这段代码的业务需求。
由于在业务方进行一个查询工作的时候,发现有一段查询语句的时间抖动非常大,最长耗时可能到6000ms,快的时候在400ms左右。所以组长分析是该查询语句涉及到排序,而solr周期性的softCommit之后,会需要对整个docValue进行重新merge,所以会造成周期性的抖动。所以需要在softComiit之后先进行对docValue的预热。
确定逻辑后,让我上手完成这个逻辑。整个代码逻辑也不复杂,就是写一个eventListener,在init()方法中初始化solrconfig.xml的配置,在newSearcher()方法中对docValue进行预热。
所以写了以上这一版本,在组长review的时候,发现了很多不足,现在记录一下,也是以后尽量避免。
1: 在多线程并发情况下,没有设置主线程的堵塞,及在多线程同时对docValue进行预热的情况下,主线程并不等待预热完成而直接继续执行。针对这个问题,在修改的第一版本中,在多线程开启后,加入thread.join(),完成主线程的等待,在组长的建议下修改成了countDownLatch。这里顺便写一下countDownLatch和join用法的区别。
本质上countDownLatch和join都实现同一个目的,即多线程并发编程时,当各别线程需要等待特定的其他线程任务执行完成后再执行时,用到以上两个方法。具体使用方法这里先不赘述了,主要讲讲不同的使用场景。
在使用join时,阻塞线程必须等待其他线程任务完全结束后才能继续执行,而使用countDownLatch时,可以任意指定其他线程的执行程度。
举个例子,此时有A,B,C三个线程进行任务执行,假设三个线程任务均为执行100的计数且A线程必须等到B,C计数完成再执行,此时用join或者countDownLatch都能完成任务要求。但是如果任务改成,A线程必须等到B,C都计数到50时,再开始执行,此时join不能完成这个要求,但是用countDownLatch时可以实现,即先
CountDownLatch cdl = new CountDownLatch(2) //A线程 cld.await() //B线程完成50次计数时 cld.countDown() //C线程完成50次计数时 cld.countDown()
以上代码即可实现要求,以上仅为伪代码,实际编程时注意要把cld.countDown()写在finally里面,避免有一线程崩溃而造成死锁。
第二个不足在于对代码的可配置化程度上,第一版的代码将代码参数写的过于生硬,很难被以后的代码复用,经过组长提示,对AbstractSolrEventListener源码的阅读发现有一个init方法,可以实现对schema.xml文件的配置参数的读取,果断覆写这个方法,将所需要预热的field配置在xml文件中。
本文主要记录一下这次的经验。