专栏首页程序员升级之路FastDFS不同步怎么破

FastDFS不同步怎么破

一、背景说明

FastDFS是一款开源的分布式文件系统,具体介绍就不说了,有兴趣的可以自行百度下。

以下是官方的架构图:

一次完整的写交互过程如下:

1、Client向Tracker查询可用的Storage;

2、Tracker随机返回一个Storage;

3、Client向Storage发起写请求;

一次完整的读交互:

1、Client向Tracker查询可用的Storage;

2、Tracker随机返回一个Storage;

3、Client向Storage发起读请求;

可以看到每个Storage都是对等的,即每个Storage上存储的文件都是全量的。

最近一朋友线上FastDFS服务器老是报文件不存在的错误,版本为5.11:

[2020-08-12 23:16:37] WARNING - file: storage_service.c, line: 6899, client ip: xx.xx.xxx.xxx, logic file: 06/75/xxxx.jpg not exist

架构如下:

2台Tracker,2台Storage。

每台机器上都有上述报错。

二、FastDFS同步机制分析

我们先分析FastDFS如何实现文件在不同服务器的同步的,FastDFS是以binglog的格式同步各自上传/修改的文件的,具体位置在安装目录的data/sync目录下,文件一般叫binlog.000这样,以下为我开发机的截图:

具体内容如下:

1589182799 C M00/00/00/rBVrTV65AU-ACKi2AAARqXyG2io334.jpg
1589182885 C M00/00/00/rBVrTV65AaWAAYwKAAARqXyG2io765.jpg
1589427410 C M00/00/00/rBVrTV68vNKAbceuAAARqXyG2io657.jpg

第1列是时间戳,第2列是修改内容,示例中大部分是创建文件,所以是C,其它参考文件 storage/storage_sync.h:

#define STORAGE_OP_TYPE_SOURCE_CREATE_FILE  'C'  //upload file
#define STORAGE_OP_TYPE_SOURCE_APPEND_FILE  'A'  //append file
#define STORAGE_OP_TYPE_SOURCE_DELETE_FILE  'D'  //delete file
#define STORAGE_OP_TYPE_SOURCE_UPDATE_FILE  'U'  //for whole file update such as metadata file
#define STORAGE_OP_TYPE_SOURCE_MODIFY_FILE  'M'  //for part modify
#define STORAGE_OP_TYPE_SOURCE_TRUNCATE_FILE  'T'  //truncate file
#define STORAGE_OP_TYPE_SOURCE_CREATE_LINK  'L'  //create symbol link

有了binglog只是保证不同服务器可以同步数据了,真正实现还有很多东西要考虑:

1、每次是全量还是增量同步,如果是增量,如何记录最后同步的位置,同步的位置做持久化吗;

2、binlog如何保证可靠性,即FastDFS实现的时候是binlog刷磁盘即fsync后才返回给客户端吗;

关于第1点,FastDFS是实现增量同步的,最后位置保存在安装目录的data/sync目录下,扩展名是mark的文件,具体格式是这样的:

172.21.107.236_23000.mark

即 IP_端口.mark。

如果集群中有两个Storage,172.21.104.36, 172.21.104.35,则在36上有1个mark文件:172.21.104.35_23000.mark,而在35上mark文件也只有1个:

172.21.104.36_23000.mark。

mark文件具体内容如下:

binlog_index=0
binlog_offset=3422
need_sync_old=1
sync_old_done=1
until_timestamp=1596511256
scan_row_count=118
sync_row_count=62

关键参数是binlog_offset,即binlog中最后同步成功的偏移量,每同步一个文件后,都会将偏移量更新。

那binlog是异步还是同步将binlog同步给其它的Storage呢,答案是异步,具体可以参考函数:storage_sync_thread_entrance,这个函数是线程的入口,FastDFS在启动时会启动这个线程用来同步:

int storage_sync_thread_start(const FDFSStorageBrief *pStorage)
{
  int result;
  pthread_attr_t pattr;
  pthread_t tid;

 //省略非关键代码

  /*
  //printf("start storage ip_addr: %s, g_storage_sync_thread_count=%d\n", 
      pStorage->ip_addr, g_storage_sync_thread_count);
  */

  if ((result=pthread_create(&tid, &pattr, storage_sync_thread_entrance, \
    (void *)pStorage)) != 0)
  {
    logError("file: "__FILE__", line: %d, " \
      "create thread failed, errno: %d, " \
      "error info: %s", \
      __LINE__, result, STRERROR(result));

    pthread_attr_destroy(&pattr);
    return result;
  }

在这个线程中,会周期地读取binlog,然后同步给其它的Storage:

  while (g_continue_flag && (!g_sync_part_time || \
      (current_time >= start_time && \
      current_time <= end_time)) && \
      (pStorage->status == FDFS_STORAGE_STATUS_ACTIVE || \
      pStorage->status == FDFS_STORAGE_STATUS_SYNCING))
    {
      //读取binlog
      read_result = storage_binlog_read(&reader, \
          &record, &record_len);
      //省略非关键代码
      }

      if (read_result != 0)
      {
        //省略非关键代码
      }
      else if ((sync_result=storage_sync_data(&reader, \
        &storage_server, &record)) != 0)
      {
        //上面就是就binlog同步到其它Storage
        logDebug("file: "__FILE__", line: %d, " \
          "binlog index: %d, current record " \
          "offset: %"PRId64", next " \
          "record offset: %"PRId64, \
          __LINE__, reader.binlog_index, \
          reader.binlog_offset, \
          reader.binlog_offset + record_len);
        if (rewind_to_prev_rec_end(&reader) != 0)
        {
          logCrit("file: "__FILE__", line: %d, " \
            "rewind_to_prev_rec_end fail, "\
            "program exit!", __LINE__);
          g_continue_flag = false;
        }

        break;
      }
      
    if (reader.last_scan_rows != reader.scan_row_count)
    {
      //定稿mark文件
      if (storage_write_to_mark_file(&reader) != 0)
      {
        logCrit("file: "__FILE__", line: %d, " \
          "storage_write_to_mark_file fail, " \
          "program exit!", __LINE__);
        g_continue_flag = false;
        break;
      }
    }

可以看到,这个线程周期性地调用storage_binlog_read 读取binlog,然后调用storage_sync_data同步给其它Storage,然后调用storage_write_to_mark_file 将mark文件写入到磁盘持久化。

通过上面的分析,可以判断FastDFS在异步情况下是会丢数据的,因为同步binlog给其它Storage是异步的,所以还没同步之前这台机器挂了并且起不来,数据是会丢失的;

另外binlog不是每1次都刷磁盘的,有参数设置,单位为秒:

sync_binlog_buff_interval

即保证多久将将mark文件刷新到磁盘中,果设置大于0,也是会容易丢失数据的。

三、解决方案

回到问题本身,为什么出现数据不同步呢,是因为在搭建 FastDFS的时候,运维的同学直接从其它服务器上拷过来的,包括整个data目录,也包括data下面的sync目录,这样就容易出现mark文件的偏移量不准的问题。

如何解决呢,手动修改mark文件,将binlog_offset设为0,这样FastDFS就会从头同步文件,碰到已经存在的文件,系统会略过的,这是我开发机上的日志:

[2020-08-11 20:27:36] DEBUG - file: storage_sync.c, line: 143, sync data file, logic file: M00/00/00/rBVrTV8yZl6ATOQyAAAJTMk6Vgo7337.md on dest server xx.xx.xx.xx:23000 already exists, and same as mine, ignore it

当然前提是日志级别开到DEBUG级别。

PS:

源代码中同步文件成功是没有日志的,写mark文件成功也是没有日志的,为了调试方便,我们都加上相关的调试日志了。

保存mark文件加日志可以在函数storage_write_to_mark_file中加入一条info日志。

  if ((result=storage_write_to_fd(pReader->mark_fd, \
    get_mark_filename_by_reader, pReader, buff, len)) == 0)
  {
    pReader->last_scan_rows = pReader->scan_row_count;
    pReader->last_sync_rows = pReader->sync_row_count;

    logInfo("file: "__FILE__", line: %d, " \
              "write server:%s mark file success, offset:%d", \
              __LINE__, pReader->storage_id, pReader->binlog_offset);
        
  }

本文分享自微信公众号 - 程序员升级之路(gh_1fab42db66cb),作者:刘江华

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-08-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 广告联盟设计踩坑

    现在市面上有一些广告联盟,运作方式是他们帮你引导用户,用户下单时返佣金给他们,这也是一些网站常用的推销方式。

    心平气和
  • RabbitMQ源代码分析系列三:消息存储

    今天分析RabbitMQ消息的持久化,即客户端发送一条持久化的MQ消息后,服务端做了哪些事情。

    心平气和
  • 分布式ID算法&实现

    如果数据库是跨机房部署,分布式ID是必须的,不然后续做数据分析和统计、跨机房路由会踩大坑。

    心平气和
  • DM 源码阅读系列文章(六)relay log 的实现

    本文为 DM 源码阅读系列文章的第六篇,在 上篇文章 中我们介绍了 binlog replication 处理单元的实现,对在增量复制过程中 binlog ev...

    PingCAP
  • 什么是 CN2?CN2 GT 和 CN2 GIA 有什么区别?

    傲云洛杉矶CN2推出有一段时间了,但是仍旧有很多朋友不知道CN2意味着什么,只大概清楚CN2要比其他线路优秀一些。本次就简单介绍下什么是CN2以及相关知识。

    傲云
  • Grunt-cli的执行过程以及Grunt加载原理

    通过本篇你可以了解到: 1 grunt-cli的执行原理 2 nodeJS中模块的加载过程 Grunt-cli原理 grunt-cli其实也是Node模块,它可...

    用户1154259
  • 详解Android Selinux 权限及问题

    Android 5.0以后完全引入了 SEAndroid/SELinux 安全机制,这样即使拥有 root 权限或 chmod 777 ,仍然无法再JNI以上访...

    砸漏
  • php 下 html5 XHR2 + FormData + File API 上传文件操作实例分析

    本文实例讲述了php 下 html5 XHR2 + FormData + File API 上传文件操作。分享给大家供大家参考,具体如下:

    砸漏
  • LeetCode 278. First Bad Version

    ShenduCC
  • 教你如何用OpenVZ限制虚拟机硬盘IO速度

    无论是KVM还是OpenVZ在SolusVM的主控端上都是没有限制硬盘IO这项功能的,KVM目前我还不是很清楚如何有效的去限制虚拟机硬盘IO,但OpenVZ目前...

    砸漏

扫码关注云+社区

领取腾讯云代金券