我有多个流(N),应该更新相同的缓存。因此,假设至少有N个线程。每个线程可以使用相似的键来处理值。问题是,如果我确实更新如下:
1. Read old value from cache (multiple threads get the same old value)
2. Merge new value with old value (each thread update old value)
3. Save updated value back to the cache (only the last update was saved, another one is lost)
如果多个线程同时尝试更新同一记录,我可能会丢失一些更新。乍看之下,有一种解决方案可以使所有更新原子化:例如,在hbase中使用Increment
突变,或在Aerospike中使用add
(目前,我正在为我的情况考虑这些缓存)。如果value只包含数字原始类型,则可以,因为两种缓存实现都支持原子inc / dec。
1. Inc/dec each value (cache will resolve sequence of this ops by it's self)
但是,如果值不仅包含基本元素,该怎么办?然后,我必须读取值并在我的代码中对其进行更新。在这种情况下,我仍然会丢失一些更新。
正如我所写,目前我正在考虑使用hbase和aerospike,但两者都不完全适合我的情况。据我所知,在hbase中,无法从客户端锁定行(>〜0.98),因此我必须对每种复杂类型使用checkAndPut
操作。在Aerospike中,我可以使用lua udfs实现类似基于行的锁定,但是我想避免使用它们。 Redis允许进行watch
记录,并且如果另一个线程进行了更新,则事务将失败,并且我可以捕获此错误,然后重试。
所以,我的问题是如何实现诸如基于行的锁定之类的更新,并且基于行的锁定将是正确的方法吗?也许还有另一种方法?
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[2]").setAppName("sample")
val sc = new SparkContext(sparkConf)
val ssc = new StreamingContext(sc, Duration(500))
val source = Source()
val stream = source.stream(ssc)
stream.foreachRDD(rdd => {
if (!rdd.isEmpty()) {
rdd.foreachPartition(partition => {
if (partition.nonEmpty) {
val cache = Cache()
partition.foreach(entity=> {
// in this block if 2 distributed workers (in case of apache spark, for example)
//will process entities with the same keys i can lose one of this update
// worker1 and worker2 will get the same value
val value = cache.get(entity.key)
// both workers will update this value but may get different results
val updatedValue = ??? // some non-trivial update depends on entity
// for example, worker1 put new value, then worker2 put new value. In this case only updates from worker2 are visible and updates from worker1 are lost
cache.put(entity.key, updatedValue)
})
}
})
}
})
ssc.start()
ssc.awaitTermination()
}
因此,如果我使用kafka作为源,则可以通过消息对消息进行分区来解决此问题。在这种情况下,我可以依靠这样的事实,即在任何时间点只有一名工作人员会处理特定的记录。但是,当邮件随机分区(密钥在邮件正文中)时,如何处理相同的情况?