起源

之前爬取过一百万的歌曲,包括歌手名,歌词等,最近了解到倒排索引,像es,solr这种太大,配置要求太高,对于一百万的数据量有些小题大做,所以想到了redis做一个倒排索引。

我的配置

这里说一下我的配置,后面用的到:

cpu:i7 8750HQ (六核十二线程)
内存:8G ddr4
硬盘:ssd(.m2接口)

思路

简单来说就是把MySQL中的数据取出来,分词(包括去除停用词),将分词后得到的一个个词语存入redis。在redis当中,一个词语就是一个set,set里存放的是歌词中包含这个词语的歌的主键。

当我们生成这么一个倒排索引后,就可以实现“搜索一句话,很快得到有这些话的歌曲集合”。

因为一百万的数据还是挺大的,所以考虑多线程执行,按过程来说分为两部分:

1、从数据库中取出来,放到Redis的list结构里去,使用list的lpush和rpop达到一种消息队列的效果。

2、从Redis中rpop出一首歌,分词,然后将分词结果存入Redis,形成倒排索引。

下面就根据这两部分讲一下具体的实现。

实现

MySQL->Redis部分的实现

这一部分思路就是从MySQL中取出数据,使用FastJson进行序列化,存入key为“dbWorkersKey”的list里,这里使用的是lpush命令。

我们把上面的思路封装到一个Thread里,多线程的去搬运就很快了。

多线程下有以下几个问题和回答:

Q:我们使用的数据访问工具是Spring的JdbcTemplate,他是线程安全的吗

A:是线程安全的,Spring把session,connection这些非线程安全的使用ThreadLocal做了线程私有化,避免了这些问题。

Q:每个线程负责一块数据,数据划分怎么做

A:使用了一个AtomicInteger,多个线程同时持有一个该对象,每次都incrementAndGet,在SQL语句中结合limit使用,做到数据的划分。

Q:考虑到多线程,那肯定要用线程池了,线程池有什么需要注意的吗

A:有,因为一个任务的很大的两块时间——从MySQL获取数据和向Redis添加数据——都是网络IO,为了更好地利用处理器,我们可以把线程池大小设置为2*核心数,同时别忘记把数据库连接池的最大连接数设置为大于线程数,比如我用的dbcp2默认的maxTotal是8。

Q:如何搬运完毕后自动停止

A:这里因为我知道搬运条目的总数量为1106599,而且我每次获取1000条,所以当AtomicInteger >1107时,就是结束的时候了

worker代码如下:

static class DbWorker extends Thread {private JdbcTemplate jdbcTemplate;private RedisCacheManager redisCacheManager;private String name;private AtomicInteger atomicInteger;public DbWorker(JdbcTemplate jdbcTemplate, RedisCacheManager redisCacheManager, String name, AtomicInteger atomicInteger) {this.jdbcTemplate = jdbcTemplate;this.redisCacheManager = redisCacheManager;this.name = name;setName(name);this.atomicInteger = atomicInteger;}@Overridepublic void run() {super.run();long lastSongId = 0;while (true) {int index = atomicInteger.incrementAndGet();if (index > 1107) {System.out.println(TimeUtils.dateToString() + " dbWorkers-" + getName() + "-db中应该是没有数据了,结束线程运行...-get index = " + index + " ... lastSongId = " + lastSongId);return;}int start = (index - 1) * 1000;List<Song> result = jdbcTemplate.query("select id,lyric from song limit " + start + ",1000", new Object[] {},new BeanPropertyRowMapper<Song>(Song.class));for (Song temp :result) {redisCacheManager.lpush(REDIS_DB_WORKERS_KEY, JSON.toJSONString(temp));}lastSongId = result.get(result.size()-1).getId();System.out.println("dbWorkers-" + getName() + "-获得" + result.size() + "条数据后已经将这些数据运往redis保存了,继续下一次db获取... -get index = " + index + " ... lastSongId = " + lastSongId);}}}

消耗时间

当时设置的是16条线程,忘记修改最大连接数,导致最大连接数为8,而且打印的内容有点多,所以,1106599条数据,从MySQL搬运到Redis用了7min16s的时间。

Redis->分词->Redis中

这一部分主要是从Redis中使用rpop出一首歌,使用FastJson反序列化后,对歌词进行分词,这里分词使用的是结巴分词的Java版本,将分词结果去除停用词后,存入key为“song:词语”的set结构中。

当然也要用到多线程了,要不得到啥时候去。

Q&A

Q:在多线程池中,注意的问题?

A:因为分词是一个计算型的任务,所以我们需要压榨处理器,设置线程数为核数+1,减少线程切换次数

Q:如果全部数据处理完毕,如何停止任务呢?

A:每次rpop出的value,如果为空,则rpopIsNull计数器+1,并线程沉睡rpopIsNull*500毫秒,rpopIsNull大于5之后,退出线程。如果又一次rpop出的value不为空,则将rpopIsNull重置为0,这样还可以避免生产者消费者的处理能力不均的问题。

其他:

A:注意多线程异常

A:停用词使用的是结巴提供的词语库

A:使用SpringRedis的时候,他默认的序列化器是Java默认的序列化器,这个序列化器会在序列化后的内容最前头加上类信息,每个key、value都有,看着不舒服的同时还浪费内存空间,我就换成了StringRedisSerializer,参考的这一篇文章,文章末还推荐了一片【Redis 内存优化】节约内存:Instagram的Redis实践也很棒

A:使用VisualVM进行监控,特别是VisualVM中各个状态的意义,还有如何分析出死锁

A:Redis在生产环境中,使用keys,一般肯定把服务器打挂,一般使用scan和dbsize,具体文章点击Redis查询当前库有多少个 key和2.1.1 列出key——极客学院课程

代码:

static class FenCiWorker extends Thread {private RedisCacheManager redisCacheManager;private String name;private int cantPop = 0;private JiebaSegmenter segmenter;public FenCiWorker(RedisCacheManager redisCacheManager,String name) {this.redisCacheManager = redisCacheManager;this.name = name;setName(name);segmenter = new JiebaSegmenter();}@Overridepublic void run() {super.run();long lastSongId = 0;while (true) {Object value = redisCacheManager.rpop(REDIS_DB_WORKERS_KEY);if (value != null) {cantPop = 0;Song song = JSON.parseObject((String) value, Song.class);lastSongId = song.getId();String lyric = song.getLyric();if (StringUtils.isEmpty(lyric)) {//                        多线程的异常,这里如果不检测lyric是否为null,线程会报异常后不提示而结束...continue;}
//                    System.out.println(TimeUtils.dateToString() + " fenciWorker-" + getName() + "-开始处理一首歌 id = " + lastSongId);List<SegToken> result = segmenter.process(lyric, JiebaSegmenter.SegMode.INDEX);for (SegToken temp :result) {String word = temp.word;if (!stopWordSet.contains(word)) {redisCacheManager.sSet(REDIS_SONG_INDEX_PRE + word,song.getId().toString());}}
//                    System.out.println(TimeUtils.dateToString() + " fenciWorker-" + getName() + "-处理了完一首歌 id = " + lastSongId);} else {cantPop++;if (cantPop >= 5) {System.out.println(TimeUtils.dateToString() + " fenciWorker-" + getName() + "-超过5次没有pop到数据,线程退出了... lastSongId = " + lastSongId);return;} else {long sleep = cantPop * 500;System.out.println(TimeUtils.dateToString() + " fenciWorker-" + getName() + "-已经+ " + cantPop + "次没有pop到数据... 线程将沉睡" + sleep + " lastSongId = " + lastSongId);try {Thread.sleep(sleep);} catch (InterruptedException e) {e.printStackTrace();}}}}}}

消耗时间

开了8个线程,花了16min35s,共1106559条数据,速度1112.12首/s。

到这里,倒排索引就建好了,备份一下dump.rdb文件。

使用

简单的实现思路,用户输入一句话,对这句话分词,根据分词结果去redis查询,将查询结果放到idSet里,最后对idSet进行遍历,使用主键去数据库查询。

不足

  1. 当直接查询歌名时,但也做了分词,查到很多没用的记录
  2. 查询结果没有根据与目标符合程度的排序
  3. 有的比如“我”,“爱”,“你”这种词太多歌里都有了,所以用这种词查询意义不大

优化

  1. 索引应该加入歌名,直接搜歌名

  2. 加入优先级属性,比如搜歌名得到的结果应该放到最前面

  3. 其他的可以去查阅一些关于搜索的文章

redis+结巴分词做倒排索引相关推荐

  1. python分词统计词频_基于结巴分词做的全文分词统计词频小脚本

    受朋友之托,写一个小脚本,断断续续做了两天,写一下两天的收获. 起因 有个朋友说专业文档很枯燥难懂,需要一个能把全文的关键词找出来并排序的东西,找不到现成的,问我能不能做一个.我前些天也听车神说有关分 ...

  2. 【python】结巴分词案例(英文词组识别)

    本人菜鸡一只,今天来写写结巴分词! 哇,距离上一次写文章已经20天过去了,最近这些天还真是挺忙的,主要是上上周到了跑月数据的节点,然后上周原始数据出了问题,我调了一周多才把这个错误解决了,还修复了一个 ...

  3. 结巴分词python安装_“结巴”分词:做最好的Python分词组件

    python 结巴分词学习 https://www.toutiao.com/a6643201326710784520/ 2019-01-06 10:14:00 结巴分词(自然语言处理之中文分词器) j ...

  4. 十分钟快速上手结巴分词

    一.特点 1.支持三种分词模式 精确模式,试图将句子最精确的切开: 全模式,把句子中所有的可以成词的词语都扫描出来,速度非常快,但是不能解决歧义: 搜索引擎模式,在精确模式的基础上,对长词再次切分,提 ...

  5. python结巴分词代码_python结巴分词SEO的应用详解

    本帖最后由 为人生而奋斗 于 2019-10-15 16:19 编辑 结巴分词在SEO中可以应用于分析/提取文章关键词.关键词归类.标题重写.文章伪原创等等方面,用处非常多. 具体结巴分词项目:htt ...

  6. Python 结巴分词(1)分词

    利用结巴分词来进行词频的统计,并输出到文件中. 结巴分词github地址:结巴分词 结巴分词的特点: 支持三种分词模式: 精确模式,试图将句子最精确地切开,适合文本分析: 全模式,把句子中所有的可以成 ...

  7. 结巴分词优点_中文分词概述及结巴分词原理

    词是中文表达语义的最小单位,中文分词是中文文本处理的一个基础步骤,分词的结果对中文信息处理至为关键. 本文先对中文分词方法进行概述,然后简单介绍结巴分词背后的原理. 1. 中文分词概述 中文分词根据实 ...

  8. 结巴分词关键词相似度_jieba+gensim 实现相似度

    相似度 自然语言处理(NLP) : 大概意思就是 让计算机明白一句话要表达的意思,NLP就相当于计算机在思考你说的话,让计算机知道"你是谁","你叫啥",&qu ...

  9. 结巴分词关键词相似度_辨别标题党--提取关键词与比较相似度

    最近好几天都没有更新博客,因为网络设置崩了,然后各种扎心,最后还重装电脑,而且还有一些软件需要重新安装或者配置,所以烦了好久,搞好电脑之后,老师又布置了一个任务,个人觉得很有趣--判别学校新闻是否是标 ...

最新文章

  1. Java开发常用Linux命令
  2. 初探团队基于session的探索性测试
  3. pycharm如何标记代码?创建代码标签?创建数字标签?收藏代码标签
  4. web项目开发人员配比_我如何找到Web开发人员的第一份工作
  5. [程序员必备工具]分享一款不错的个人代码个人知识管理软件wiz
  6. Java中使用foreach带来的一些问题
  7. 现网问题排查实战:Jstat,Jstack,Jmap
  8. 将linux系统分区变成逻辑卷,linux运维基础知识-系统分区及LVM逻辑卷的创建
  9. javaJNI(javah用法)
  10. WPS中word转pdf文件时给pdf文件增加目录
  11. 网络协议之NAT穿透原理
  12. NOIP模拟题——复制粘贴2
  13. HDU 4826Labyrinth(dp)
  14. 永远闪亮,网的眼睛 (转)
  15. 【深度学习】实验5答案:滴滴出行-交通场景目标检测
  16. Unity3D 放大缩小图片
  17. php课设报告致谢_科学网—博士论文致谢 - 曹墨源的博文
  18. 国外技术论坛和顶级公司技术博客汇总
  19. C#:实现字数统计函数算法(附完整源码)
  20. ALSA (高级Linux声音架构)、ASOC基础知识

热门文章

  1. 杰里之AC69 系列内置混响 K 歌宝硬件设计注意事项篇
  2. PNAS:睡眠时间预测大学生的第一年的成绩
  3. 每個Linux用戶都應該瞭解的命令行省時技巧
  4. 支付功能测试用例(参考微信平台)
  5. AT91SAM9G45开发板ARM9工业控制板
  6. 12123查询违章显示服务器异常,我的交管12123以前可以处理违章 现在处理不了是什么情况 。提示服务异常...
  7. Comparison of Big Data OLAP DB : ClickHouse, Druid, and Pinot
  8. 使用Vue3自定义指令,让你的应用更具交互性
  9. Realme的Login接入过程记录
  10. 抖音小游戏推广爆火,背后有什么特点?想进圈应注意哪些方面?