2

我正在使用 cassandra-orm 插件 ( cassandra-orm:0.4.5 ) 将点击从 Postgres DB 迁移到 Cassandra。(我知道我可以使用原始数据导入,但我想使用由插件维护的 groupBy 和显式索引)。

迁移过程很简单:我从 Postgres(通过 GORM)中选择一堆点击,然后将它们刷新到 Cassandra。每次点击都是一条新记录,并且在 Grails 中创建一个新对象并保存在 Cassandra 中。使用 20 个线程,我能够达到 2000 次点击/秒的吞吐量。在导入 500 万次点击后,性能开始急剧下降到 50 次点击/秒。

我做了一些分析,发现有 19 个线程正在等待(停放),一个线程正在对 Groovy 的 AbstractConcurrentMapBase 执行重新哈希处理。

等待线程的堆栈跟踪:

Name: pool-4-thread-2
State: WAITING on org.codehaus.groovy.util.ManagedConcurrentMap$Segment@5387f7af
Total blocked: 45,027  Total waited: 55,891

Stack trace: 
 sun.misc.Unsafe.park(Native Method)
java.util.concurrent.locks.LockSupport.park(LockSupport.java:156)
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:811)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:842)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1178)
org.codehaus.groovy.util.LockableObject.lock(LockableObject.java:34)
org.codehaus.groovy.util.AbstractConcurrentMap$Segment.put(AbstractConcurrentMap.java:101)
org.codehaus.groovy.util.AbstractConcurrentMap$Segment.getOrPut(AbstractConcurrentMap.java:97)
org.codehaus.groovy.util.AbstractConcurrentMap.getOrPut(AbstractConcurrentMap.java:35)
org.codehaus.groovy.runtime.metaclass.ThreadManagedMetaBeanProperty$ThreadBoundGetter.invoke(ThreadManagedMetaBeanProperty.java:180)
groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:1604)
groovy.lang.ExpandoMetaClass.getProperty(ExpandoMetaClass.java:1140)
groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:3332)
groovy.lang.ExpandoMetaClass.getProperty(ExpandoMetaClass.java:1152)
com.nosql.Click.getProperty(Click.groovy)

重新哈希线程的堆栈跟踪:

Name: pool-4-thread-11
State: RUNNABLE
Total blocked: 46,544  Total waited: 57,433

Stack trace: 
 org.codehaus.groovy.util.AbstractConcurrentMapBase$Segment.rehash(AbstractConcurrentMapBase.java:217)
org.codehaus.groovy.util.AbstractConcurrentMap$Segment.put(AbstractConcurrentMap.java:105)
org.codehaus.groovy.util.AbstractConcurrentMap$Segment.getOrPut(AbstractConcurrentMap.java:97)
org.codehaus.groovy.util.AbstractConcurrentMap.getOrPut(AbstractConcurrentMap.java:35)
org.codehaus.groovy.runtime.metaclass.ThreadManagedMetaBeanProperty$ThreadBoundGetter.invoke(ThreadManagedMetaBeanProperty.java:180)
groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:233)
groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:1604)
groovy.lang.ExpandoMetaClass.getProperty(ExpandoMetaClass.java:1140)
groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:3332)
groovy.lang.ExpandoMetaClass.getProperty(ExpandoMetaClass.java:1152)
com.fma.nosql.Click.getProperty(Click.groovy)

经过数小时的调试,我发现问题出在添加到所有插件管理对象的动态属性“_cassandra_cluster_”中:

// cluster property (_cassandra_cluster_)
clazz.metaClass."${CLUSTER_PROP}" = null

然后,此属性在内部保存在ThreadManagedMetaBeanProperty instance2Prop 映射中。当访问动态属性时def cluster = click._cassandra_cluster_,单击实例将保存到带有软引用的 instance2Prop 映射中。到目前为止一切顺利,软引用可以被垃圾收集,对吧。然而,在 ManagedConcurrentMap 实现中似乎存在一个错误,它忽略了垃圾收集的元素并继续重新散列和扩展地图(描述herehere)。

解决方法

由于地图内部保存在类级别,唯一可行的解​​决方案是重新启动服务器。最终我开发了一个肮脏的解决方案,从僵尸元素中清除内部地图。以下代码在单独的线程中运行:

public void rehashClickSegmentsIfNecessary() {
    ManagedConcurrentMap instanceMap = lookupInstanceMap(Click.class, "_cassandra_cluster_")
    if(instanceMap.fullSize() - instanceMap.size() > 50000) {
        //we have more than 50 000 zombie references in map
        rehashSegments(instanceMap)
    }
}

private void rehashSegments(ManagedConcurrentMap instanceMap) {
    org.codehaus.groovy.util.ManagedConcurrentMap.Segment[] segments = instanceMap.segments
    for(int i=0;i<segments.length;i++) {
        segments[i].lock()
        try {
            segments[i].rehash()
        } finally {
            segments[i].unlock()
        }
    }
}

private ManagedConcurrentMap lookupInstanceMap(Class clazz, String prop) {
    MetaClassRegistry registry = GroovySystem.metaClassRegistry
    MetaClassImpl metaClass = registry.getMetaClass(clazz)
    return metaClass.getMetaProperty(prop, false).instance2Prop
}

你有使用 cassandra-orm 或任何其他连接到 cassandra 的 grails 插件的生产经验吗?

4

0 回答 0