误操作引起的Logstash重复收集

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了误操作引起的Logstash重复收集相关的知识,希望对你有一定的参考价值。

参考技术A 当需要做一个日志系统时,可能第一时间会想到ELK全家桶。
ELK分别表示:Elasticsearch , Logstash, Kibana 。他们组成了一套完整的日志系统的解决方案,方便我们使用。

在使用Logstash进行收集的时候,仅仅需要简单的一个配置文件就可以启动一个logstash-agent来进行日志收集。

上述示例简单的从某个文件目录下收集日志打印到控制台。

然后我们启动这个logstash-agent: bin/logstash -f conf/datafall/test.config

往日志文件push数据:
echo "hello world" >> 1.txt

就可以看到控制输出:

到此时是正确的一个流程。

误操作开始了:

但是我们以 vim 命令进入日志文件的时候,在文件末尾追加一个数据 happy ,此时控制台会输出:

会发现logstash会从文件开始从新读取数据(此时就会造成数据的重复收集)。

为什么会出现这种情况呢?

Logstash有一个有趣的组件或功能叫做sincedb。该文件存储了当前logstash-agent收集的文件日志的offset。在前面 test.config 配置了sincedb的位置。
如果不配置,它会默认在当前用户的根目录下创建一个 .sincedb开头的文件 。

sincedb的具体内容:

看到上述内容,发现我们明明只收集了一个日志文件,为什么会又多出来一条记录呢。

这就要追述到使用 vim 命令编辑文件并保存时。相当于会创建拥有全新inode的文件。

此时logstash会发现一个文件名一样但是inode却不一样的文件。logstash还是会进行文件的收集工作。
通过debug模式可以看到更详细的细节:

从上述日志可以看出logstash会产生监控到一个新的inode文件,并且在原有 sincedb 文件中并没有这个inode记录,因此logstash会从头开始收集这个日志文件中的日志。

当我们在使用logstash收集日志文件时,尽量不要用 Vim、vi 命令去打开日志文件,尽量使用 cat、more 这之类的。

注:
inode: 操作系统中的文件数据都储存在"块"中,当然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"
inode包含文件的元信息,具体来说有以下内容:

一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。

vim、vi:为什么在编辑的时候会产生一个新的inode: 在使用vim打开文件是,会把当前打开的文件放入buffer中(内存),然后进行操作。当我们保存时,相当于替换了原来的文件。所以会有个新的inode的文件产生。

如何优雅地处理用户的误操作引起的多次请求

在互联网应用中,我们经常用到的场景,比如用户点击某个按钮,触发的操作会和后台api进行数据交互,生成一些记录,比如下单购买。如果后台api请求比较慢,而客户端体验又做得不到位,导致用户以为没点击到或者是页面假死,在上次请求还没处理完,就再次点击按钮。这样会导致某个操作生成多次记录,导致一些异常的bug。

很显然,后台的api在这方面是需要做好处理。然而,面对用户,我们需要更好的体验,可以在客户端去避免这些问题,前置地解决问题。

最近听产品经理常说,用户点击某个按钮多次,后台还没处理完导致多笔记录生成,我们需要在用户点击后跳转到一个新的页面,其实这根本不是跳页问题,是程序问题。如果程序员真这么干,是不是要下岗了。

以前偷懒的时候,在前端我们可能会这么处理:

 

var getUserDataFlag = false;
function getUserData() {
  if (getDataFlag) {
    return;
  }
  getDataFlag = true;
  $.ajax({
    url: ‘/xxx/getUser‘,
    success: function () {
      getUserData = false;
      //todo
    },
    error: function () {
      getUserData = false;
    }
  })
}
//当接口很多的时候,我们的代码就变成这样
var getUserAssetFlag = true;
function getUserAsset() {
  if (getDataFlag) {
    return;
  }
  getDataFlag = true;
  $.ajax({
    url: ‘/xxx/getUserAsset‘,
    success: function () {
      getUserAssetFlag = false;
      //todo
    },
    error: function () {
      getUserAssetFlag = false;
    }
  })
}

  

 

上面的例子你会发现,当接口越来越多,维护请求状态的变量将会越来越多,并且当存在依赖时,维护成本更高,也更容易出错。

如何优雅地解决这样的问题,其实封装一下请求就能简单又能自动地处理这个问题。

最近在重构angular的项目以及在写微信小程序demo,有一些小实践和总结,例子请参照原文链接https://github.com/navyxie/avoid-multi-request-from-client-。下面我们以微信小程序请求后台数据为例解说:

import {isObject} from ‘./util‘

let Promise = require(‘../libs/bluebird.min‘)
let requestList = {} //api请求记录

// 将当前请求的api记录起来
export function addRequestKey (key) {
    requestList[key] = true
}

// 将请求完成的api从记录中移除
export function removeRequestKey (key) {
    delete requestList[key]
}

//当前请求的api是否已有记录
export function hitRequestKey (key) {
    return requestList[key]
}

// 获取串行请求的key,方便记录
export function getLockRequestKey (data) {
    if (!isObject(data)) {
        return data
    }
    let ajaxKey = ‘lockRequestKey:‘
    try {
        ajaxKey += JSON.stringify(data)
    } catch (e) {
        ajaxKey += data
    }
    return ajaxKey
}

//根据请求的地址,请求参数组装成api请求的key,方便记录
export function getRequestKey (data) {
    if (!isObject(data)) {
        return data
    }
    let ajaxKey = ‘Method: ‘ + data.method + ‘,Url: ‘ + data.url + ‘,Data: ‘
    try {
        ajaxKey += JSON.stringify(data.data)
    } catch (e) {
        ajaxKey += data.data
    }
    return ajaxKey
}
//所有与服务器进行http请求的出口
export function http (data) {
    if (!isObject(data)) {
        throw Error(‘ajax请求参数必须是json对象: ‘ + data)
    }
    data.method = (data.method || ‘GET‘).toUpperCase()
    //下面5行是对所有http请求做防重复请求处理,后面单独分享原理
    let ajaxKey = getRequestKey(data)
    if (hitRequestKey(ajaxKey)) {
        throw Error(‘重复提交请求:‘ + ajaxKey)
    }
    addRequestKey(ajaxKey)
    //bluebird.js包装成promisepromise api
    return new Promise(function (resolve, reject) {
        //通过wx.request api 向服务器端发出http请求
        wx.request({
            url: data.url,
            data: data.data,
            method: data.method,
            header: data.header || {‘Content-Type‘: ‘application/json‘},
            complete: function (res) {
                // 请求完成,释放记录的key,可以发起下次请求了
                removeRequestKey(ajaxKey)
                let statusCode = res.statusCode
                if (statusCode === 200 || statusCode === 304) {
                    return resolve(res.data)
                }
                return reject(res)
            }
        })
    }) 
}

//通用get请求方法
export function httpGet (data) {
    return http(data)
}

//通用post请求方法
export function httpPost (data) {
    data.method = ‘POST‘
    return http(data)
}

// 该方法适用于串行请求的api
export function lockRequest (data, fn) {
    let ajaxKey = getLockRequestKey(data)
    if (hitRequestKey(ajaxKey)) {
        throw Error(‘重复提交请求:‘ + ajaxKey)
    }
    addRequestKey(ajaxKey)returnnewPromise(function(resolve, reject){
        fn(data).then(function(data){
                removeRequestKey(ajaxKey)return resolve(data)}).catch(function(error){
                removeRequestKey(ajaxKey)return reject(error)})})}

  

整体思路就是统一所有请求的入口,然后以API请求的地址,参数,请求类型(get,post)等组装为唯一key缓存起来。这样就能知道某个请求的完成状态,当第二个相同的请求过来时,我们可以根据上一次的状态来判断下一步的操作。

angular和微信小程序的例子看这里,欢迎大家交流更多好的解决方案。

以上是关于误操作引起的Logstash重复收集的主要内容,如果未能解决你的问题,请参考以下文章

如何优雅地处理用户的误操作引起的多次请求

Flashback for MySQL 5.7

win10系统误删系统文件开机蓝屏怎么办?

当数据被误删除/误操作后造成数据丢失。你尝试过用什么手段来挽救数据/损失?

拿走不谢,Flashback for MySQL 5.7

MySQL 误操作后如何快速恢复数据~!~!~