Java EE并发和锁定

4
我有一个MDB(消息驱动bean),它接收表示单词的字符串消息。我还有一个数据库中的表。MDB应该在表中存储单词和每个单词接收到的次数(计数器)。
问题是为了获得更好的性能,MDB启动了许多实例,当不同实例接收到相同的新单词时,它们都会创建具有计数器1的相同行。
为解决此问题,我应该使单词字段唯一,然后第二个实例将在提交时失败,重新传输消息,这将起作用,但可能会有问题。这是一个好的做法吗?
另一种解决方案是以后合并这些行并总结计数器。但如果另一个实例在更新中增加计数器怎么办?
如果两个实例尝试增加计数器怎么办? @Version足够了吗?
我不确定这里的正确解决方案是什么。您如何处理这样的情况?
此外,您能否建议一些关于并发实践的书籍(不涉及使用synchronized,因为我需要支持Java EE并可能运行应用程序服务器集群)?
更新:阅读更多关于EJB和JPA的资料后,我想要的是类似锁定实体的东西。例如,我可以创建一个只有id和key列以及以下数据的新表:
ID | KEY
1  | WORDS_CREATE_LOCK

这样当我需要处理一个新单词时,我会做类似于这样的事情(不是准确的代码,不确定它是否能编译):

// MAIN FUNCTION
public void handleWord(String wordStr) {
  Word w = getWord(wordStr);

  if (w == null)
    w = getNewOrSychronizedWord(wordStr);

  em.lock(w);
  w.setCounter(w.getCounter() + 1);
  em.unlock(w);
}

// Returns Word instance or null if not found
private Word getWord(String wordStr) {
  Word w = null;

  Query query = em.createQuery("select w from words as w where w.string = :wordStr order by w.id asc");
  query.setParameter("wordStr", wordStr);
  List<Word> words = query.getResultList();

  if (words.getSize() > 0)
    w = words.get(0);

  return w;
}

// Handles locking to prevent duplicate word creation
private Word getNewOrSynchronizedWord(String wordStr) {
  Word w = null;
  Locks l = em.find(WORDS_CREATE_LOCK_ID, Locks.class);
  em.lock(l);

  Word w = getWord(wordStr);

  if (w == null) {
    w = new Word(wordStr);
    em.persist(w);
  }

  em.unlock(l);
  return w;
}

所以问题是这种方法可行吗?我能否在不维护带有锁定行的DB表的情况下做同样的事情?也许有一些Java EE容器锁定机制?
如果有帮助,我正在使用JBoss 4.2。
我有一个新的想法。我可以创建两个MDB:
第一个MDB允许多个实例,将处理所有消息,如果找不到单词,将发送单词给第二个MDB
第二个MDB只允许一个实例,将按顺序处理消息并允许创建新单词
最好的部分:没有整个表/方法/进程锁定,只有计数器更新时的行锁定
那太棒了!
谢谢。
3个回答

2

如果您想要提高性能、避免锁定等问题,我建议您再创建一张表:(word, timestamp)。您的MDBs只需插入单词和时间戳。另一个进程将统计并更新总表。


1

这似乎需要在数据库中通过选择正确的事务隔离级别来解决 - 可重复读应该足够。

你需要一本关于数据库的书,重点是事务。


REPEATABLE READ的隔离级别不是解决方案,因为当两个实例都对同一个不存在的单词运行选择查询时,它们都会返回零行,并且没有锁定任何内容。之后,每个实例都将使用相同的单词进行插入。SERIALIZABLE隔离级别更好,因为它会锁定新单词的不存在行(不确定),但会对性能产生很大影响,并且可能在Oracle数据库上出现问题。 - m_vitaly

1

您的意思是多个实例正在处理相同的消息,还是不同的消息中使用了相同的单词?如果是相同的消息,则应该使用队列而不是主题。当然,这并不能解决多个消息中相同单词的问题。对于这种情况,您可以遵循@Michael Borgwardt和@Vitaly Polonetsky的建议。

除了数据库之外,另一个选择是让不同的MDB实例处理以一组字母开头的单词。这可以通过选择器轻松完成。然后只有一个MDB处理任何特定的单词,但处理仍然分散在多个实例之间以提高性能。我并不认为这是更好的选择,但它支持基于队列的相当简单的处理。


我在不同的消息中使用了相同的词,它是一个队列MDB。"另一种观点"很好,但是否有一种J2EE的方式来在MDB上进行选择器操作?我知道我可以使用一个选择器MDB来转发到更具体的MDB。但是我必须确保特定的MDB只有一个实例(如何实现?在集群中工作吗?)。 - m_vitaly
@ Vitaly Polonetsky - 选择器是MDB部署的配置方面,就像实例数量(线程的并发会话)一样。您可以多次部署相同的MDB(不同的EAR),但每个MDB都有不同的选择器,并配置为仅允许一个会话。 - Robin
@Robin 谢谢,但是我能否通过选择器在同一个 mdb 上拥有多个选择器的一个实例?或者我必须像选择器一样拥有许多类? - m_vitaly
@Vitaly Polonetsky - 不确定您的意思。每个部署的MDB只能有一个选择器,因为它是部署描述符配置项,但您可以多次部署相同的MDB,每个MDB都有自己的选择器和JNDI名称。这消除了编写多个MDB的需要(因为它们都执行完全相同的操作),但允许您使每个MDB处理不同的消息。 - Robin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接