踩坑日志之elasticSearch

极客探航者
• 阅读 2533

前言

上周六马上就下班了,正兴高采烈的想着下班吃什么呢!突然QA找到我,说我们的DB与es无法同步数据了,真是令人头皮发秃,好不容易休一天,啊啊啊,难受呀,没办法,还是赶紧找bug吧。下面我就把我这次的bug原因分享给大家,避免踩坑~。

bug原因之bulk隐藏错误信息

第一时间,我去看了一下错误日志,竟然没有错误日志,很是神奇,既然这样,那我们就DEBUG一下吧,DEBUG之前我先贴一段代码:

func (es *UserES) batchAdd(ctx context.Context, user []*model.UserEs) error {
    req := es.client.Bulk().Index(es.index)
    for _, u := range user {
        u.UpdateTime = uint64(time.Now().UnixNano()) / uint64(time.Millisecond)
        u.CreateTime = uint64(time.Now().UnixNano()) / uint64(time.Millisecond)
        doc := elastic.NewBulkIndexRequest().Id(strconv.FormatUint(u.ID, 10)).Doc(u)
        req.Add(doc)
    }
    if req.NumberOfActions() < 0 {
        return nil
    }
    if _, err := req.Do(ctx); err != nil {
        return err
    }
    return nil
}

就是上面这段代码,使用esbulk批量操作,经过DEBUG仍然没有发现任何问题,卧槽!!!没有头绪了,那就看一看es源码吧,里面是不是有什么隐藏的点没有注意到。还真被我找到了,我们先看一下req.Do(ctx)的实现:

// Do sends the batched requests to Elasticsearch. Note that, when successful,
// you can reuse the BulkService for the next batch as the list of bulk
// requests is cleared on success.
func (s *BulkService) Do(ctx context.Context) (*BulkResponse, error) {
    /**
    ...... 省略部分代码
  **/
    // Get response
    res, err := s.client.PerformRequest(ctx, PerformRequestOptions{
        Method:      "POST",
        Path:        path,
        Params:      params,
        Body:        body,
        ContentType: "application/x-ndjson",
        Retrier:     s.retrier,
        Headers:     s.headers,
    })
    if err != nil {
        return nil, err
    }

    // Return results
    ret := new(BulkResponse)
    if err := s.client.decoder.Decode(res.Body, ret); err != nil {
        return nil, err
    }

    // Reset so the request can be reused
    s.Reset()

    return ret, nil
}

我只把重要部分代码贴出来,看这一段就好了,我来解释一下:

  • 首先构建Http请求
  • 发送Http请求并分析,并解析response
  • 重置request可以重复使用

这里的重点就是ret := new(BulkResponse)new了一个BulkResponse结构,他的结构如下:

type BulkResponse struct {
    Took   int                            `json:"took,omitempty"`
    Errors bool                           `json:"errors,omitempty"`
    Items  []map[string]*BulkResponseItem `json:"items,omitempty"`
}
// BulkResponseItem is the result of a single bulk request.
type BulkResponseItem struct {
    Index         string        `json:"_index,omitempty"`
    Type          string        `json:"_type,omitempty"`
    Id            string        `json:"_id,omitempty"`
    Version       int64         `json:"_version,omitempty"`
    Result        string        `json:"result,omitempty"`
    Shards        *ShardsInfo   `json:"_shards,omitempty"`
    SeqNo         int64         `json:"_seq_no,omitempty"`
    PrimaryTerm   int64         `json:"_primary_term,omitempty"`
    Status        int           `json:"status,omitempty"`
    ForcedRefresh bool          `json:"forced_refresh,omitempty"`
    Error         *ErrorDetails `json:"error,omitempty"`
    GetResult     *GetResult    `json:"get,omitempty"`
}

先来解释一个每个字段的意思:

  • took:总共耗费了多长时间,单位是毫秒
  • Errors:如果其中任何子请求失败,该 errors 标志被设置为 true ,并且在相应的请求报告出错误明细(看下面的Items解释)
  • Items:这个里就是存储每一个子请求的response,这里的Error存储的是详细的错误信息

现在我想大家应该知道为什么我们的代码没有报err信息了,bulk的每个请求都是独立的执行,因此某个子请求的失败不会对其他子请求的成功与否造成影响,所以其中某一条出现错误我们需要从BulkResponse解出来。现在我们把代码改正确:

func (es *UserES) batchAdd(ctx context.Context, user []*model.UserEs) error {
    req := es.client.Bulk().Index(es.index)
    for _, u := range user {
        u.UpdateTime = uint64(time.Now().UnixNano()) / uint64(time.Millisecond)
        u.CreateTime = uint64(time.Now().UnixNano()) / uint64(time.Millisecond)
        doc := elastic.NewBulkIndexRequest().Id(strconv.FormatUint(u.ID, 10)).Doc(u)
        req.Add(doc)
    }
    if req.NumberOfActions() < 0 {
        return nil
    }
    res, err := req.Do(ctx)
    if err != nil {
        return err
    }
    // 任何子请求失败,该 `errors` 标志被设置为 `true` ,并且在相应的请求报告出错误明细
    // 所以如果没有出错,说明全部成功了,直接返回即可
    if !res.Errors {
        return nil
    }
    for _, it := range res.Failed() {
        if it.Error == nil {
            continue
        }
        return &elastic.Error{
            Status:  it.Status,
            Details: it.Error,
        }
    }
    return nil
}

这里再解释一下res.Failed方法,这里会把itemsbulk response带错误的返回,所以在这里面找错误信息就可以了。

至此,这个bug原因终于被我找到了,接下来可以看下一个bug了,我们先简单总结一下:

bulk API 允许在单个步骤中进行多次 createindexupdatedelete 请求,每个子请求都是独立执行,因此某个子请求的失败不会对其他子请求的成功与否造成影响。bulk的response结构中Erros字段,如果其中任何子请求失败,该 errors 标志被设置为 true ,并且在相应的请求报告出错误明细,items字段是一个数组,,这个数组的内容是以请求的顺序列出来的每个请求的结果。所以在使用bulk时一定要从response中判断是否有err

bug原因之数值范围越界

这里完全是自己使用不当造成,但还是想说一说es的映射数字类型范围的问题:

数字类型有如下分类:

类型说明
byte有符号的8位整数, 范围: [-128 ~ 127]
short有符号的16位整数, 范围: [-32768 ~ 32767]
integer有符号的32位整数, 范围: [−231−231 ~ 231231-1]
long有符号的64位整数, 范围: [−263−263 ~ 263263-1]
float32位单精度浮点数
double64位双精度浮点数
half_float16位半精度IEEE 754浮点类型
scaled_float缩放类型的的浮点数, 比如price字段只需精确到分, 57.34缩放因子为100, 存储结果为5734

这里都是有符号类型的,无符号在es7.10.1版本才开始支持,有兴趣的同学戳这里

这里把这些数字类型及范围列出来就是方便说我的bug原因,这里直接解释一下:

我在DB设置字段的类型是tinyint unsignedtinyint是一个字节存储,无符号的话范围是0-255,而我在es中映射类型选择的是byte,范围是-128~127,当DB中数值超过这个范围是,在进行同步时就会出现这个问题,这里需要大家注意一下数值范围的问题,不要像我一样,因为这个还排查了好久的bug,有些空间没必要省,反正也占不了多少空间。

总结

这篇文章就是简单总结一下我在工作中遇到的问题,发表出来就是给大家提个醒,有人踩过的坑,就不要在踩了,浪费时间!!!!

好啦,这篇文章就到这里啦,素质三连(分享、点赞、在看)都是笔者持续创作更多优质内容的动力!

结尾给大家发一个小福利吧,最近我在看[微服务架构设计模式]这一本书,讲的很好,自己也收集了一本PDF,有需要的小伙可以到自行下载。获取方式:关注公众号:[Golang梦工厂],后台回复:[微服务],即可获取。

我翻译了一份GIN中文文档,会定期进行维护,有需要的小伙伴后台回复[gin]即可下载。

翻译了一份Machinery中文文档,会定期进行维护,有需要的小伙伴们后台回复[machinery]即可获取。

我是asong,一名普普通通的程序猿,让gi我一起慢慢变强吧。欢迎各位的关注,我们下期见~~~

踩坑日志之elasticSearch

推荐往期文章:

点赞
收藏
评论区
推荐文章
blmius blmius
4年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
linbojue linbojue
1年前
超完整的Electron打包签名更新指南,这真是太酷啦!
大家好,我是多喝热水。在踩了数不清的坑之后,终于从0到1完成了一个桌面端应用,但万万没想到,最最痛苦的还不是开发过程,而是开发完成后的打包签名阶段,这真是踩坑踩麻了!!!超完整的Electron打包签名更新指南,这真是太酷啦!ok,踩坑归踩坑,收获也是不小
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
7个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Stella981 Stella981
3年前
Node.js 中使用 ECDSA 签名遇到的坑
文/Fenying最近有个朋友问我关于Node.js下使用ECDSA的问题,主要是使用Node.js的Crypto模块无法校验网络传输过来的签名结果。在踩坑无数后,终于搞清楚了原因。坑0x00:签名输出格式在排除了证书、消息不一致的可能之后,我开始对比使用Node.js签名的结果与网络传输过来的签
Stella981 Stella981
3年前
Docker 部署SpringBoot项目不香吗?
  公众号改版后文章乱序推荐,希望你可以点击上方“Java进阶架构师”,点击右上角,将我们设为★“星标”!这样才不会错过每日进阶架构文章呀。  !(http://dingyue.ws.126.net/2020/0920/b00fbfc7j00qgy5xy002kd200qo00hsg00it00cj.jpg)  2
Stella981 Stella981
3年前
Linux日志安全分析技巧
0x00前言我正在整理一个项目,收集和汇总了一些应急响应案例(不断更新中)。GitHub地址:https://github.com/Bypass007/EmergencyResponseNotes本文主要介绍Linux日志分析的技巧,更多详细信息请访问Github地址,欢迎Star。0x01日志简介Lin
Stella981 Stella981
3年前
ELK学习笔记之ElasticSearch的索引详解
0x00ElasticSearch的索引和MySQL的索引方式对比Elasticsearch是通过Lucene的倒排索引技术实现比关系型数据库更快的过滤。特别是它对多条件的过滤支持非常好,比如年龄在18和30之间,性别为女性这样的组合查询。倒排索引很多地方都有介绍,但是其比关系型
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这