关于ArrayBlockingQueue的put和offer方法

最近线上出现了一个故障,故障的表现就是服务请响应很慢,依赖方获取不到执行结果,查看调用堆栈,发现所有的操作都阻塞在写日志的地方,这个写日志是先写日志到内存,然后再刷新到其他地方。采用了ArrayBlockingQueue,但是调用了ArrayBlockQueue的put方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * Inserts the specified element at the tail of this queue, waiting
 * for space to become available if the queue is full.
 *
 * @throws InterruptedException {@inheritDoc}
 * @throws NullPointerException {@inheritDoc}
 */
public void put(E e) throws InterruptedException {
    checkNotNull(e);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length)
            notFull.await();
        insert(e);
    } finally {
        lock.unlock();
    }
}

这里的put方法会等待一个空的位置出来,然后再执行insert,但是系统的请求量非常大,此时一个请求过来后,前面的请求可能还处于等待空位置这一步,此时当前请求获取lock就等待,这样这个业务操作就一直处于获取锁获取不到的场景中了。这是一个真实出现的case,血一般的教训,当时只能不断重启机器来缓解问题。如何彻底解决这个问题,换个API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/**
 * Inserts the specified element at the tail of this queue, waiting
 * up to the specified wait time for space to become available if
 * the queue is full.
 *
 * @throws InterruptedException {@inheritDoc}
 * @throws NullPointerException {@inheritDoc}
 */
public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    checkNotNull(e);
    long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == items.length) {
            if (nanos <= 0)
                return false;
            nanos = notFull.awaitNanos(nanos);
        }
        insert(e);
        return true;
    } finally {
        lock.unlock();
    }
}

换成offer判断返回值为false的情况,不然把业务操作阻塞住。以后在高并发场景下面就不要在使用put这个API了。

关于系统架构设计的心得(一)

一.概述

关于系统的架构设计,其实有很多内容,要想设计出足够灵活的软件系统,确实不是一件容易的事情,特别是面对我们日新月异的业务,业务的变动完全可以推翻你之前所做的所有设计,如何在复杂的业务中剥离出一些固定的规律,这大概就是系统架构的主要目的了。要想做好系统架构,你需要对业务有一个宏观层面的认识和理解,需要把你所面对的业务模块化,结构化,然后再针对每个模块每个结构进行详细的设计。当然这些模块的划分以及抽象,也需要你有一定的经验。

关于YGC时间变长的记录

最近看见很多同事都在讨论一个JVM YGC时间变长的问题,在平时业务开发的过程中,我们经常使用

1
XStream xs = new XStream();

来实现XML和JavaBean之间的相互转换,看看实现就知道在上面的构造函数中不断创建新的classloader出来

1
2
3
4
public XStream(
            ReflectionProvider reflectionProvider, Mapper mapper, HierarchicalStreamDriver driver) {
        this(reflectionProvider, driver, new ClassLoaderReference(new CompositeClassLoader()), mapper, new DefaultConverterLookup(), null);
    }

不断创建新的classloader会导致YGC的时间变长。JVM的类加载机制都是双亲委派机制。
假如:AClassLoader->BClassLoader->CClassLoader,现在需要加载X这个类,AClassLoader首先会交给BClassLoader去加载,BClassLoader会交给CClassLoader去加载,如果CClassLoader能加载到,那么X这个类就被加载了。此时在SystemDictionary这个HashTable数据结构中会存储3条记录。

1
2
3
4
5
X-AClassLoader-X.cls
X-BClassLoader-X.cls
X-CClassLoader-X.cls

此时SystemDictionary中有三条关于X的加载记录,如果发现任何一条,就认为X已经加载过了。

其中AClassLoader和BClassLoader叫做X的出始类加载器。CClassLoader叫做X的定义类加载器。如果不断自定义ClassLoader的话,SystemDictionary中会不断增加K-V记录,这样YGC扫描的范围就越大,YGC耗时就越多。

最后,在使用XStream时,最好别每次都创建一个新的ClassLoader来,减少YGC的时间,提升性能。

使用xstream解析utf-8格式的字符串

最近在使用xstream解析一个xml字符串时,出现了解析失败的问题,出错原因很简单,xml字符串中指定了utf-8的编码,此时我们直接构造一个

1
XStream xstream = new XStream();

这样构造的话,一定会解析失败。
需要按照下面的方式来构造才能正确解析utf-8格式的xml字符串

1
XStream xstream = new XStream(new DomDriver("UTF-8"));

xstream是一个比较方便的工具,能够实现xml和object之间的相互转换,注意在平时开发过程中的灵活使用。

mac遇到的一个svn的问题

今天在mac使用svn pe svn:ignore 来设置忽略文件夹时出现了下面的错误

1
svn: E205007: None of the environment variables SVN_EDITOR, VISUAL or EDITOR are set, and no 'editor-cmd' run-time configuration option was found

解决办法,在.bash_profile中增加下面一行,然后source生效即可

1
export SVN_EDITOR=vim

注意使用

1
svn pe svn:global-ignores .

进行全局设置,避免给每个目录执行

1
svn pe svn:ignore .

来设置

拓宽技术视野一

利用空闲的时间,随便看看一些新的技术,拓宽自己的技术视野。

  • Betamax 利用这个开源二方库能够mock一个http请求。
  • cucumber 基于DSL来做单元测试,具体例子
  • jOOR 一个简化发射调用的JAVA框架,把反射调用变成一行链式调用,这种框架貌似挺多的,源码地址

velocity工具函数以及InputStream到String的转换方法

一.背景

最近在做需求的时候,需要在程序中把一个vm文件渲染成一个字符串,同时也需要把一个InputStream流转换成一个字符串,虽然说很简单,但是自己还是翻了翻以前的代码,也在网上找了相关的例子,为了下次使用的是能能够快速找到,专门记录一下。

awk使用总结

一.概述

在平时工作中经常会遇到一些文本的处理,awk就是一种文本处理语言,很方便,很轻量,可以说awk也是一种编程语言。

awk的工作流程是这样的:读入有’\n’换行符分割的一条记录,然后将记录按指定的域分隔符划分域,填充域,\$0则表示所有域,\$1表示第一个域,\$n表示第n个域。默认域的分隔符是”空白键”或”tab键”。

Java并发编程艺术读书笔记一

  • volatile
    轻量级的同步机制,有volatile修饰的变量在进行写操作的时候会多出一条lock前缀的指令,比如lock addl $0 x 0,(%esp)。lock前缀的指令在多核处理器下面会引发下面两件事情:①将当前处理器缓存行的数据写回到系统内存。②这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。③volatile写代表锁的释放,volatile读代表锁的获取。