标签 文章 下的文章

apache配置虚拟主机(给虚拟目录添加域名)

2019年8月8日更新:
看到这篇文章感慨颇多,这个域名是我毕业第一个公司的官网域名,现在早已无法打开多年了.

httpd.conf尾部添加

NameVirtualHost *:80
<virtualHost *:80>
    ServerName www.dovao.com
    DocumentRoot E:\wwwroot\dovao\dovao
</virtualHost>
<virtualHost *:80>
    ServerName dovao.com
    DocumentRoot E:\wwwroot\dovao\dovao
</virtualHost>
<virtualHost *:80>
    ServerName www.dovao.com
    DocumentRoot E:\wwwroot\dovao\dovaobbs
</virtualHost>
<virtualHost *:80>
    ServerName www.dovao.com
    DocumentRoot E:\wwwroot\dovao\dovaobbs
</virtualHost>
<virtualHost *:80>
   ServerName tuan.dovao.com
   DocumentRoot E:\wwwroot\dovao\dovaotuan
</virtualHost>

no such file to load -- application (MissingSourceFile)

2019年8月15日更新:
今天发现这片文章,应该是本站最早的一片文章了,应该是我在上大二的时候.
那年我刚刚拥有自己的第一台笔记本电脑.

历史内容:
尝试:

mv app/controllers/application_controller.rb app/controllers/application.rb

问题解决.

Google Adsense 提示 scrape content 的解决办法

自从去年Google Adsense政策变了以后,现在谷歌申请的变得严谨很多,而且提示的信息也更加的丰富。以前如果审核不通过会直接提示违反政策等云云,现在谷歌会给出细分且易懂的人性化提示,比如:no content、 scrape content 等。

no content相对于其他的信息更容易理解,就是没有内容或者内容毫无价值的意思,在此不作为详细解读。今天主要讲一下scrape content这个类型以及相应的解决办法。

scrape content的中文意思是擦伤的内容,何为擦伤的内容?在Google Support中有明确的解析,懂英文的同学请自行前往阅读:https://support.google.com/webmasters/answer/2721312?hl=en

那么为什么会触发擦伤的内容限制呢?通过google翻译我们得到以下几点:

  • 在不添加任何原始内容或值的情况下复制和重新发布其他网站内容的网站
  • 从其他网站复制内容,稍微修改它的网站(例如,通过替换同义词或使用自动化技术),并重新发布它
  • 从其他网站复制内容供稿而不向用户提供某种类型的独特组织或利益的网站
  • 专用于嵌入来自其他网站的视频,图像或其他媒体等内容的网站,对用户没有实质性附加价值

第一和第二点不难理解,就是复制和伪原创,第三点可以理解为复制封闭空间的供稿,而第四点就是使用过多的第三方媒体资源来丰富站点。

以上的这些东西,都会触发scrape content 这个审核结果。通常,第一和第二点有时候也会被提示为no content 或者no content value等。

那么no content 和scrape content 的区别在哪呢?笔者认为可能是因为同一个站点出现不同类型风格的文章引起的,而且google 认为后者是有价值的,只不过价值不是由你创造的而已。(本站独家见解)

既然了解了触发该审核结果的根本原因,那么解决起来就容易啦!

解决办法

  • 老老实实码字,选择一个领域的素材,慢慢的创造自己的精品内容,为用户提供价值。
  • 尽量避免大量引用别站的数据和别站的论点等,可以稍微改下些。但笔者观察了下,有些文章结构也差不多的改写也会被认为scrape content。
  • 避免引用第三方的视频和图片。

千里之路始于足下,路漫漫,大家慢慢努力吧。


出现io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1的原因及解决办法

错误信息

在使用Netty构建Server服务器的时候,之前用得好好的,最近整理了一下就出现了以下错误:

WARN (AbstractChannelHandlerContext.java:294)- An exception 'java.lang.NullPointerException' [enable DEBUG level for full stacktrace] was thrown by a user handler's exceptionCaught() method while handling the following exception:
io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
    at io.netty.buffer.AbstractReferenceCountedByteBuf.toLiveRealCnt(AbstractReferenceCountedByteBuf.java:181)
    at io.netty.buffer.AbstractReferenceCountedByteBuf.release0(AbstractReferenceCountedByteBuf.java:133)

分析的文章很多,这里有两篇比较靠谱的:
1、netty的异常分析 IllegalReferenceCountException refCnt: 0, decrement: 1
2、Netty中常见的IllegalReferenceCountException异常原因及解决

原因分析

从以上的分析文章中发现,SimpleChannelInboundHandler会自动释放内存(虽然这是一种软释放)即是refCnt引用数减一。

而本人在使用SimpleChannelInboundHandler作为Server端的时候,自己手动释放了一次msg的内存,导致refCnt引用数为0,这个时候框架试图去释放一次,就报如上错误。释放代码如:

msg.release();

我都解决方法是删除上面一行代码,然后就不再出现以上错误。

因此建议全局搜索下release方法,看看是不是重复释放了一次内存造成的该异常,当然前提是你使用了SimpleChannelInboundHandler作为Handler处理事务,使用AbstractChannelInboundHandler是不会主动释放内容的,这个时候需要你自己手动释放一次。


I-team 博客全文检索 Elasticsearch 实战

一直觉得博客缺点东西,最近还是发现了,当博客慢慢多起来的时候想要找一篇之前写的博客很是麻烦,于是作为后端开发的楼主觉得自己动手丰衣足食,也就有了这次博客全文检索功能Elasticsearch实战,这里还要感谢一下‘辉哥’赞助的一台服务器。

全文检索工具选型

众所周知,支持全文检索的工具有很多,像 Lucene,solr, Elasticsearch 等,相比于其他的工具,显然 Elasticsearch 社区更加活跃,遇到问题相对来说也比较好解决,另外 Elasticsearch 提供的restful接口操作起来还是比较方便的,这也是楼主选择 Elasticsearch 的重要原因,当然 Elasticsearch 占据的内存相对来说比较大一点,楼主2G的云服务器跑起来也是捉襟见肘。

数据迁移,从 MySQL 到 Elasticsearch

这个功能相对来说比较简单,就是定时从 MySQL 更新数据到 Elasticsearch 中,本来楼主打算自己写一个数据迁移的工具,但是想起之前楼主做数据迁移时用到的DataX很是不错,看了写官方文档还是支持的,但是楼主硬是没有跑起来,原因就是楼主2G内存的云服务器不够使啊,DataX光是跑起来就要1G多的内存,所以楼主只能另谋它法。对DataX感兴趣的小伙伴可以看看楼主的另一篇文章阿里离线数据同步工具 DataX 踩坑记录。

说起可以省内存的语言,小伙伴可能会想到最近比较火的golang,没错楼主也想到了。最后楼主使用的就是一个叫go-mysql-elasticsearch的工具,就是使用golang实现的从 MySQL 将数据迁移到 Elasticsearch 的工具。具体搭建过程楼主不在这里细说,感兴趣的小伙伴请移步go-mysql-elasticsearch,另外 Elasticsearch 环境的搭建,需要注意的就是安装 Elasticsearch 的机器内存应该大于或者等于2G,否则可能会出现起不起来的情况,楼主也不在这里赘述了,比较简单,请小伙伴们自行google。

另外需要注意的是,在使用 go-mysql-elasticsearch 的时候应该开启mysql的binlog功能,go-mysql-elasticsearch的实现同步数据的思想就是将自己作为MySQL的一个slave挂载在MySQL上,这样就可以很轻松的将数据实时同步到 Elasticsearch 中,在启动 go-mysql-elasticsearch 的机器上最少应该有MySQL client工具,否则会启动报错。楼主的建议是根MySQL部署在同一台机器上,因为golang耗费内存极少,并不会有太大影响。下面给出楼主同步数据时 go-mysql-elasticsearch 的配置文件:

 1# MySQL address, user and password
 2# user must have replication privilege in MySQL.
 3my_addr = "127.0.0.1:3306"
 4my_user = "root"
 5my_pass = "******"
 6my_charset = "utf8"
 7
 8# Set true when elasticsearch use https
 9#es_https = false
10# Elasticsearch address
11es_addr = "127.0.0.1:9200"
12# Elasticsearch user and password, maybe set by shield, nginx, or x-pack
13es_user = ""
14es_pass = ""
15
16# Path to store data, like master.info, if not set or empty,
17# we must use this to support breakpoint resume syncing.
18# TODO: support other storage, like etcd.
19data_dir = "./var"
20
21# Inner Http status address
22stat_addr = "127.0.0.1:12800"
23
24# pseudo server id like a slave
25server_id = 1001
26
27# mysql or mariadb
28flavor = "mysql"
29
30# mysqldump execution path
31# if not set or empty, ignore mysqldump.
32mysqldump = "mysqldump"
33
34# if we have no privilege to use mysqldump with --master-data,
35# we must skip it.
36#skip_master_data = false
37
38# minimal items to be inserted in one bulk
39bulk_size = 128
40
41# force flush the pending requests if we don't have enough items >= bulk_size
42flush_bulk_time = "200ms"
43
44# Ignore table without primary key
45skip_no_pk_table = false
46
47# MySQL data source
48[[source]]
49schema = "billboard-blog"
50
51# Only below tables will be synced into Elasticsearch.
52tables = ["content"]
53# Below is for special rule mapping
54[[rule]]
55schema = "billboard-blog"
56table = "content"
57index = "contentindex"
58type = "content"
59
60[rule.field]
61title="title"
62blog_desc="blog_desc"
63content="content"
64
65
66# Filter rule
67[[rule]]
68 schema = "billboard-blog"
69 table = "content"
70 index = "contentindex"
71 type = "content"
72
73# Only sync following columns
74filter = ["title", "blog_desc", "content"]
75
76# id rule
77[[rule]]
78 schema = "billboard-blog"
79 table = "content"
80 index = "contentindex"
81 type = "content"
82 id = ["id"]

实现全文检索功能的服务

要想实现全文检索的功能并对外提供服务,web服务必不可少,楼主使用Spring Boot搭建web服务,对Spring Boot感兴趣的小伙伴也可以看一下楼主的另一篇文章,使用Spring Boot实现博客统计服务。好了废话不多说了,请看代码

接口实现代码,代码比较简单就是接收参数,调用service代码

1    @ApiOperation(value="全文检索接口", notes="")
 2    @ApiImplicitParam(name = "searchParam", value = "博客搜索条件(作者,描述,内容,标题)", required = true, dataType = "String")
 3    @RequestMapping(value = "/get_content_list_from_es", method = RequestMethod.GET)
 4    public ResultCode<List<ContentsWithBLOBs>> getContentListFromEs(String searchParam) {
 5        ResultCode<List<ContentsWithBLOBs>> resultCode = new ResultCode();
 6        try {
 7            LOGGER.info(">>>>>> method getContentListFromEs request params : {},{},{}",searchParam);
 8            resultCode = contentService.getContentListFromEs(searchParam);
 9            LOGGER.info(">>>>>> method getContentListFromEs return value : {}",JSON.toJSONString(resultCode));
10        } catch (Exception e) {
11            e.printStackTrace();
12            resultCode.setCode(Messages.API_ERROR_CODE);
13            resultCode.setMsg(Messages.API_ERROR_MSG);
14        }
15        return resultCode;
16    }

service代码实现,这里代码主要功能就是调用es的工具类,对博客描述,作者,博客标题,博客内容进行全文检索。

1    @Override
 2    public ResultCode<List<ContentsWithBLOBs>> getContentListFromEs(String searchParam) {
 3        ResultCode resultCode = new ResultCode();
 4
 5        // 校验参数,参数不能为空
 6        if (StringUtils.isBlank(searchParam)) {
 7            LOGGER.info(">>>>>> params not be null");
 8            resultCode.setMsg(Messages.INPUT_ERROR_MSG);
 9            resultCode.setCode(Messages.INPUT_ERROR_CODE);
10            return resultCode;
11        }
12
13        String matchStr = "blog_desc=" + searchParam;
14        List<Map<String, Object>> result = ElasticsearchUtils.searchListData(BillboardContants.ES_CONTENT_INDEX,BillboardContants.ES_CONTENT_TYPE,BillboardContants.ES_CONTENT_FIELD,true,matchStr);
15
16        matchStr = "author=" + searchParam;
17        result.addAll(ElasticsearchUtils.searchListData(BillboardContants.ES_CONTENT_INDEX,BillboardContants.ES_CONTENT_TYPE,BillboardContants.ES_CONTENT_FIELD,true,matchStr));
18
19        matchStr = "title=" + searchParam;
20        result.addAll(ElasticsearchUtils.searchListData(BillboardContants.ES_CONTENT_INDEX,BillboardContants.ES_CONTENT_TYPE,BillboardContants.ES_CONTENT_FIELD,true,matchStr));
21
22        matchStr = "content=" + searchParam;
23        result.addAll(ElasticsearchUtils.searchListData(BillboardContants.ES_CONTENT_INDEX,BillboardContants.ES_CONTENT_TYPE,BillboardContants.ES_CONTENT_FIELD,true,matchStr));
24
25        List<ContentsWithBLOBs> data = JSON.parseArray(JSON.toJSONString(result),ContentsWithBLOBs.class);
26        LOGGER.info("es return data : {}",JSON.toJSONString(result));
27        resultCode.setData(data);
28        return resultCode;
29    }

楼主用到的es的工具类代码实现,就是使用es的java客户端对es进行检索。

 1    /**
  2     * 使用分词查询
  3     *
  4     * @param index       索引名称
  5     * @param type        类型名称,可传入多个type逗号分隔
  6     * @param fields      需要显示的字段,逗号分隔(缺省为全部字段)
  7     * @param matchPhrase true 使用,短语精准匹配
  8     * @param matchStr    过滤条件(xxx=111,aaa=222)
  9     * @return
 10     */
 11    public static List<Map<String, Object>> searchListData(String index, String type, String fields, boolean matchPhrase, String matchStr) {
 12        return searchListData(index, type, 0, 0, null, fields, null, matchPhrase, null, matchStr);
 13    }
 14
 15    /**
 16     * 使用分词查询
 17     *
 18     * @param index          索引名称
 19     * @param type           类型名称,可传入多个type逗号分隔
 20     * @param startTime      开始时间
 21     * @param endTime        结束时间
 22     * @param size           文档大小限制
 23     * @param fields         需要显示的字段,逗号分隔(缺省为全部字段)
 24     * @param sortField      排序字段
 25     * @param matchPhrase    true 使用,短语精准匹配
 26     * @param highlightField 高亮字段
 27     * @param matchStr       过滤条件(xxx=111,aaa=222)
 28     * @return
 29     */
 30    public static List<Map<String, Object>> searchListData(String index, String type, long startTime, long endTime, Integer size, String fields, String sortField, boolean matchPhrase, String highlightField, String matchStr) {
 31
 32        SearchRequestBuilder searchRequestBuilder = client.prepareSearch(index);
 33        if (StringUtils.isNotEmpty(type)) {
 34            searchRequestBuilder.setTypes(type.split(","));
 35        }
 36        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
 37
 38        if (startTime > 0 && endTime > 0) {
 39            boolQuery.must(QueryBuilders.rangeQuery("processTime")
 40                    .format("epoch_millis")
 41                    .from(startTime)
 42                    .to(endTime)
 43                    .includeLower(true)
 44                    .includeUpper(true));
 45        }
 46
 47        //搜索的的字段
 48        if (StringUtils.isNotEmpty(matchStr)) {
 49            for (String s : matchStr.split(",")) {
 50                String[] ss = s.split("=");
 51                if (ss.length > 1) {
 52                    if (matchPhrase == Boolean.TRUE) {
 53                        boolQuery.must(QueryBuilders.matchPhraseQuery(s.split("=")[0], s.split("=")[1]));
 54                    } else {
 55                        boolQuery.must(QueryBuilders.matchQuery(s.split("=")[0], s.split("=")[1]));
 56                    }
 57                }
 58
 59            }
 60        }
 61
 62        // 高亮(xxx=111,aaa=222)
 63        if (StringUtils.isNotEmpty(highlightField)) {
 64            HighlightBuilder highlightBuilder = new HighlightBuilder();
 65
 66            //highlightBuilder.preTags("<span style='color:red' >");//设置前缀
 67            //highlightBuilder.postTags("</span>");//设置后缀
 68
 69            // 设置高亮字段
 70            highlightBuilder.field(highlightField);
 71            searchRequestBuilder.highlighter(highlightBuilder);
 72        }
 73
 74
 75        searchRequestBuilder.setQuery(boolQuery);
 76
 77        if (StringUtils.isNotEmpty(fields)) {
 78            searchRequestBuilder.setFetchSource(fields.split(","), null);
 79        }
 80        searchRequestBuilder.setFetchSource(true);
 81
 82        if (StringUtils.isNotEmpty(sortField)) {
 83            searchRequestBuilder.addSort(sortField, SortOrder.DESC);
 84        }
 85
 86        if (size != null && size > 0) {
 87            searchRequestBuilder.setSize(size);
 88        }
 89
 90        //打印的内容 可以在 Elasticsearch head 和 Kibana  上执行查询
 91        LOGGER.info("\n{}", searchRequestBuilder);
 92
 93        SearchResponse searchResponse = searchRequestBuilder.execute().actionGet();
 94
 95        long totalHits = searchResponse.getHits().totalHits;
 96        long length = searchResponse.getHits().getHits().length;
 97
 98        LOGGER.info("共查询到[{}]条数据,处理数据条数[{}]", totalHits, length);
 99
100        if (searchResponse.status().getStatus() == 200) {
101            // 解析对象
102            return setSearchResponse(searchResponse, highlightField);
103        }
104
105        return null;
106
107    }

最后,楼主使用postman测试web服务,如下图所示:

640 (1).webp640 (1).webp

过程中遇到的坑

IK分词器的设置
1$ ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.3.0/elasticsearch-analysis-ik-6.3.0.zip

接着,重新启动 Elastic,就会自动加载这个新安装的插件。

然后,新建一个 Index,指定需要分词的字段。这一步根据数据结构而异,下面的命令只针对本文。基本上,凡是需要搜索的中文字段,都要单独设置一下。

这里需要注意的是,Elasticsearch的版本一定要与ik分词器的版本对应,不对应的话 Elasticsearch 会报错的。

 1$ curl -X PUT 'localhost:9200/contentindex'  -H 'Content-Type: application/json' -d '
 2{
 3  "mappings": {
 4    "content": {
 5      "properties": {
 6        "content": {
 7          "type": "text",
 8          "analyzer": "ik_max_word",
 9          "search_analyzer": "ik_max_word"
10        },
11        "title": {
12          "type": "text",
13          "analyzer": "ik_max_word",
14          "search_analyzer": "ik_max_word"
15        },
16        "blog_desc": {
17          "type": "text",
18          "analyzer": "ik_max_word",
19          "search_analyzer": "ik_max_word"
20        },
21        "author": {
22          "type": "text",
23          "analyzer": "ik_max_word",
24          "search_analyzer": "ik_max_word"
25        }
26      }
27    }
28  }
29}'

上面代码中,首先新建一个名称为contentindex的 Index,里面有一个名称为content的 Type。content有好多个字段,这里只为其中四个字段指定分词,content, title, blog_desc,author 。

这四个字段都是中文,而且类型都是文本(text),所以需要指定中文分词器,不能使用默认的英文分词器。

MySQL binlog的设置

因为楼主运行 go-mysql-elasticsearch 的时候使用的MySQL的客户端跟要导出数据的MySQL server端的版本不一致导致报错,最终在 go-mysql-elasticsearch 原作者的帮助下解决,所以一定要使用同版本的MySQL server 与client,因为不同版本的MySQL特性不一样,也就导致了 go-mysql-elasticsearch 导出数据有略微的不同。

小结

整个过程相对来说比较简单,当然楼主通过这个功能的实现,也对es有了一个相对的认识,学习了一项新的技能,可能有的小伙伴对楼主的整个工程的代码比较感兴趣,暂时先不能透露,等楼主完善好了一并贡献出来。