2019独角兽企业重金招聘Python工程师标准>>>
首先交代一下笔者想要完成的功能:
从http://news.qq.com上收集相应的新闻事件,存入数据库event表,并且将新闻的标题做分词处理,存入tag表。tag和event之间建立关系,event_tag。长话短说,就是要建立一个多对多的关系。
实现方式:
爬虫:webmagic
中间件:spring
持久层:mybatis
其中遇到的一个问题是:因为webmagic一次性开启多个线程,在分词完成以后先将标签存入数据库,数据库标签应该是唯一的(unique_constraint保证)。最初的实现是这样的:
@Transactional(propagation=Propagation.REQUIRED)
private void createTagIndex(Event event){
String title = event.getTitle();
//分词,并且去除重复
Set tags = CharactorSegmentor.seperate(title);
for(String t:tags){
Tag tag = tagDAO.getByLabel(t);
/**
*创建tag的时候应该去加锁,保证不会重复插入tag
*因为在某一时刻,可能n个线程检测到tag数据库都是null,所以同时插入。
*数据库因为unique_constraint 就会报错
* */
if (tag == null)
{
lock.lock();
try{
tag = tagDAO.getByLabel(t);
/**
* 双重检查,很有可能是自己被同步锁阻塞了,回过头来,其实别人已经插入了。
* */
if(tag == null){
tag = new Tag();
tag.setLabel(t);
tagDAO.insertTag(tag);
}
}
finally {
lock.unlock();
}
}
connectTagWithEvent(event, tag);
}
}
这样想的是没错,在tag保存进入数据库时,很有可能线程A处理的tag当中有"中国",线程B处理的tag当中有"中国",但是数据库当中没有“中国”这个标签,那么AB线程就会竞争。如果不想出现unqiue constraint exception,那么同步就是必须的了。
但是有个问题:当前的方法被声明在一个spring事务当中,结果上面的代码仍然不对。原因是什么呢?
由于事务范围大于锁代码块范围,在锁代码块执行完成后,此时事务还未提交,导致此时进入锁代码块的其他线程,读到的仍是原有的库存数据。
意识到了这一点之后,后面就知道怎么做了,事务应该直接在DAO层声明。就能够解决问题了。从上面代码的执行情况来看,最初是挺慢的,因为数据库当中没有标签,每次都要加锁竞争。到后面,标签很多了,几乎每次遇到的标签都已经保存过了,自然就不会加锁了,速度也就提升了。
还有一个小问题需要注意:采用Spring的自动装配,由于我的DAO层不是实现统一接口的,因此在声明事务当中配置应该要这样配置
这样采用cglib生成动态代理,而不是JDK动态代理。JDK生成动态代理会有类型上的错误(如果不强制转型)
更多更详细的代码:https://github.com/qiulimao/collector/