searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

压缩及其在Ceph上的应用

2024-06-28 10:03:05
129
0

压缩

压缩算法及工具

deflate

RFC1951。一种无损数据压缩算法,使用LZ77算法和哈夫曼编码(Huffman Coding)。
压缩级别: 0 ~ 9

zlib

RFC1950。一种压缩格式,是对deflate(默认,也可以是其它压缩算法)进行了简单的封装。
zlib = zlib头 + deflate编码的实际内容 + zlib尾

gzip

RFC1952。一种压缩格式,是对deflate(默认,目前只有这一种)进行的封装。
gzip = gzip头 + deflate编码的实际内容 + gzip尾

isa-l

isa-l是一个对存储应用相关的低级函数优化后的集合

  • Erasure codes

  • CRC

  • Raid

  • Compression And De-compression: 兼容deflate的快速数据压缩/解压缩

    压缩级别: 0 ~ 3

    • defalte
    • zlib
    • gzip
  • igzip: 一个类似gzip的命令行工具,使用ISA-L加速。

zstd

一种无损压缩算法。在实时压缩场景,相比deflate(zlib和gzip的压缩算法)更快、压缩率更好。
压缩级别: -5 ~ 22

snappy

原称zippy。Goole基于LZ77实现的压缩库。设计目标主要是超高的速度和合理的压缩率。

评价指标

  • 压缩率(compression ratio)(或压缩比)

    数据压缩后的大小/压缩前的大小 * 100%

  • 压缩速度

    MB/s

  • 解压缩速度

    MB/s

  • 资源占用
    • cpu
    • 内存

影响压缩率的因素

  • 压缩算法
  • 压缩级别
  • 压缩分块大小

ceph

压缩库封装

  1. 描述

ceph压缩库支持zlib、snappy、zstd、lz4、brotli等压缩方法。其中zlib支持isa-l。

  1. 源码
    • 目录结构
      src/compressor/
          CompressionPlugin.h
          Compressor.h
          Compressor.cc
          zlib/
              ...
          snappy/
              ...
          zstd/
              ...
          lz4/
              ...
          brotli/
              ...
      

BlueStore: inline compression

  1. 配置项
    • bluestore_compression_mode(压缩模式)
      • 描述:
      • type: str
      • default: none
      • 有效值:
        • none: 绝不压缩数据
        • passive: 当且仅当写操作有一个可压缩的标识设置的时候才压缩数据
        • aggressive: 压缩数据,除非写入操作有一个不可压缩的标识设置
        • force: 不管什么情况都尝试压缩数据
    • bluestore_compression_algorithm(压缩算法)
      • 描述: 由于压缩少量数据时CPU开销较高,因此不建议将zstd用于BlueStore。
      • type: str
      • default: snappy
      • 有效值:
        *
        • snappy
        • zlib
        • zstd
        • lz4
        • brotli
    • bluestore_compression_required_ratio(被要求的压缩率)
      • 描述: 压缩后的数据块大小相对于原始大小的比例必须至少如此小才能存储压缩版本
      • type: float
      • default: 0.85
    • bluestore_compression_min_blob_size
      • 描述: 随机访问时压缩的最大块大小。 大于此值的块在被压缩之前被分解为最多bluestore_compression_max_blob_size字节的较小blob,小于这个值的块不会被压缩
      • type: size
      • default: 0B
    • bluestore_compression_min_blob_size_hdd
      • type: size
      • default: 8Ki
    • bluestore_compression_min_blob_size_ssd
      • type: size
      • default: 64Ki
    • bluestore_compression_max_blob_size
      • 描述: 非随机访问时压缩的最大块大小。
      • type: size
      • default: 0B
    • bluestore_compression_max_blob_size_hdd
      • type: size
      • default: 64Ki
    • bluestore_compression_max_blob_size_ssd
      • type: size
      • default: 64Ki
  2. 单独设置指定存储池的属性
    ceph osd pool set <pool-name> compression_mode <mode>
    ceph osd pool set <pool-name> compression_algorithm <algorithm>
    ceph osd pool set <pool-name> compression_required_ratio <ratio>
    ceph osd pool set <pool-name> compression_min_blob_size <size>
    ceph osd pool set <pool-name> compression_max_blob_size <size>
    
  3. 相关源码
    • 压缩信息元数据bluestore_compression_header_t:
      src/os/bluestore/bluestore_types.h 
      
      struct bluestore_compression_header_t {
          uint8_t type = Compressor::COMP_ALG_NONE; // 压缩类型
          uint32_t length = 0; // 压缩后的数据的大小
          boost::optional<int32_t> compressor_message;
      
      }
      

Object Gateway(RadosGW): compression

  1. 相关源码
    • compression_block
      src/rgw/rgw_compression_types.h
      
      struct compression_block {
          uint64_t old_ofs;   // 压缩后的块在原始对象的偏移位置
          uint64_t new_ofs;   // 压缩后的块在压缩后的对象的偏移位置
          uint64_t len;       // 压缩后的块的大小
      }
      
    • RGWCompressionInfo
      src/rgw/rgw_compression_types.h
      
      struct RGWCompressionInfo {
        std::string compression_type; // 压缩类型
        uint64_t orig_size;   // 原始对象的大小
        boost::optional<int32_t> compressor_message;
        std::vector<compression_block> blocks;    // 压缩后的块的描述信息
      
      }
      
  2. 示例
    1. 生成一个30M的全零文件: dd if=/dev/zero of=30M count=30 bs=1M
    2. 上传后对象信息
      radosgw-admin object stat --bucket bucket01 --object 30M
      
      {
          "name": "30M",
          "size": 1144,
          "policy": {
              "acl": {
                  "acl_user_map": [
                      {
                          "user": "slh01",
                          "acl": 15
                      }
                  ],
                  "acl_group_map": [],
                  "grant_map": [
                      {
                          "id": "slh01",
                          "grant": {
                              "type": {
                                  "type": 0
                              },
                              "id": "slh01",
                              "email": "",
                              "permission": {
                                  "flags": 15
                              },
                              "name": "SLH01",
                              "group": 0,
                              "url_spec": ""
                          }
                      }
                  ]
              },
              "owner": {
                  "id": "slh01",
                  "display_name": "SLH01"
              }
          },
          "compression": {
              "compression_type": "zstd",
              "orig_size": 31457280,
              "blocks": [
                  {
                      "old_ofs": 0,
                      "new_ofs": 0,
                      "len": 151
                  },
                  {
                      "old_ofs": 4194304,
                      "new_ofs": 151,
                      "len": 151
                  },
                  {
                      "old_ofs": 8388608,
                      "new_ofs": 302,
                      "len": 151
                  },
                  {
                      "old_ofs": 12582912,
                      "new_ofs": 453,
                      "len": 151
                  },
                  {
                      "old_ofs": 16777216,
                      "new_ofs": 604,
                      "len": 151
                  },
                  {
                      "old_ofs": 20971520,
                      "new_ofs": 755,
                      "len": 151
                  },
                  {
                      "old_ofs": 25165824,
                      "new_ofs": 906,
                      "len": 151
                  },
                  {
                      "old_ofs": 29360128,
                      "new_ofs": 1057,
                      "len": 87
                  }
              ]
          },
          "etag": "56313328e6aa42b57cd4d0bdeaac85f4-2",
          "tag": "f59e7e9f-d376-42f1-8239-250db02a393b.164415.5",
          "manifest": {
              "objs": [],
              "obj_size": 1144,
              "explicit_objs": "false",
              "head_size": 0,
              "max_head_size": 0,
              "prefix": "30M.2~ywARpG73u1BwG4F5rkAUBi0vHrwmiu-",
              "rules": [
                  {
                      "key": 0,
                      "val": {
                          "start_part_num": 1,
                          "start_ofs": 0,
                          "part_size": 604,
                          "stripe_max_size": 4194304,
                          "override_prefix": ""
                      }
                  },
                  {
                      "key": 604,
                      "val": {
                          "start_part_num": 2,
                          "start_ofs": 604,
                          "part_size": 540,
                          "stripe_max_size": 4194304,
                          "override_prefix": ""
                      }
                  }
              ],
              "tail_instance": "",
              "tail_placement": {
                  "bucket": {
                      "name": "bucket01",
                      "marker": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                      "bucket_id": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                      "tenant": "",
                      "explicit_placement": {
                          "data_pool": "",
                          "data_extra_pool": "",
                          "index_pool": ""
                      }
                  },
                  "placement_rule": "default-placement"
              },
              "begin_iter": {
                  "part_ofs": 0,
                  "stripe_ofs": 0,
                  "ofs": 0,
                  "stripe_size": 604,
                  "cur_part_id": 1,
                  "cur_stripe": 0,
                  "cur_override_prefix": "",
                  "location": {
                      "placement_rule": "default-placement",
                      "obj": {
                          "bucket": {
                              "name": "bucket01",
                              "marker": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                              "bucket_id": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                              "tenant": "",
                              "explicit_placement": {
                                  "data_pool": "",
                                  "data_extra_pool": "",
                                  "index_pool": ""
                              }
                          },
                          "key": {
                              "name": "30M.2~ywARpG73u1BwG4F5rkAUBi0vHrwmiu-.1",
                              "instance": "",
                              "ns": "multipart"
                          }
                      },
                      "raw_obj": {
                          "pool": "",
                          "oid": "",
                          "loc": ""
                      },
                      "is_raw": false
                  }
              },
              "end_iter": {
                  "part_ofs": 1144,
                  "stripe_ofs": 1144,
                  "ofs": 1144,
                  "stripe_size": 540,
                  "cur_part_id": 3,
                  "cur_stripe": 0,
                  "cur_override_prefix": "",
                  "location": {
                      "placement_rule": "default-placement",
                      "obj": {
                          "bucket": {
                              "name": "bucket01",
                              "marker": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                              "bucket_id": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                              "tenant": "",
                              "explicit_placement": {
                                  "data_pool": "",
                                  "data_extra_pool": "",
                                  "index_pool": ""
                              }
                          },
                          "key": {
                              "name": "30M.2~ywARpG73u1BwG4F5rkAUBi0vHrwmiu-.3",
                              "instance": "",
                              "ns": "multipart"
                          }
                      },
                      "raw_obj": {
                          "pool": "",
                          "oid": "",
                          "loc": ""
                      },
                      "is_raw": false
                  }
              }
          },
          "attrs": {
              "user.rgw.content_type": "application/octet-stream",
              "user.rgw.pg_ver": "\u0005",
              "user.rgw.source_zone": "",
              "user.rgw.tail_tag": "f59e7e9f-d376-42f1-8239-250db02a393b.164415.5",
              "user.rgw.x-amz-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
              "user.rgw.x-amz-date": "20220424T033328Z",
              "user.rgw.x-amz-meta-s3cmd-attrs": "atime:1650770954/ctime:1650770914/gid:0/gname:root/md5:281ed1d5ae50e8419f9b978aab16de83/mode:33188/mtime:1650770914/uid:0/uname:root"
          }
      }
      

Q & A

  1. RGW对于上传的对象为什么要按照4M拆分数据,这和压缩有什么关联

数据条带化(Data Striping)是一种数据存储技术,主要应用于磁盘阵列(如 RAID)和分布式存储系统中。其主要目的是将数据分割成多个块,并在多个存储设备上并行存储,以提高系统的性能、可靠性和可用性。

在这里,RGW的拆分数据的过程就是数据条带化,事实上,基于RADOS对象存储的RBD、CephFS都需要进行数据条带化,如果用户需要在RADOS对象存储上实现自己的存储应用,也需要进行数据条带化,Ceph在librados基础上提供了libradosstriper接口封装了数据条带化的过程。

从压缩的角度考虑,如果分块很大但只需要读取很小的一段数据(RadosGW支持Range读取),仍然需要读取整个分块的数据(IO放大); 如果分块很小,需要记录的压缩信息元数据就会比较大。因此分块的大小需要选取一个合适的值。

0条评论
0 / 1000
c****x
1文章数
0粉丝数
c****x
1 文章 | 0 粉丝
c****x
1文章数
0粉丝数
c****x
1 文章 | 0 粉丝
原创

压缩及其在Ceph上的应用

2024-06-28 10:03:05
129
0

压缩

压缩算法及工具

deflate

RFC1951。一种无损数据压缩算法,使用LZ77算法和哈夫曼编码(Huffman Coding)。
压缩级别: 0 ~ 9

zlib

RFC1950。一种压缩格式,是对deflate(默认,也可以是其它压缩算法)进行了简单的封装。
zlib = zlib头 + deflate编码的实际内容 + zlib尾

gzip

RFC1952。一种压缩格式,是对deflate(默认,目前只有这一种)进行的封装。
gzip = gzip头 + deflate编码的实际内容 + gzip尾

isa-l

isa-l是一个对存储应用相关的低级函数优化后的集合

  • Erasure codes

  • CRC

  • Raid

  • Compression And De-compression: 兼容deflate的快速数据压缩/解压缩

    压缩级别: 0 ~ 3

    • defalte
    • zlib
    • gzip
  • igzip: 一个类似gzip的命令行工具,使用ISA-L加速。

zstd

一种无损压缩算法。在实时压缩场景,相比deflate(zlib和gzip的压缩算法)更快、压缩率更好。
压缩级别: -5 ~ 22

snappy

原称zippy。Goole基于LZ77实现的压缩库。设计目标主要是超高的速度和合理的压缩率。

评价指标

  • 压缩率(compression ratio)(或压缩比)

    数据压缩后的大小/压缩前的大小 * 100%

  • 压缩速度

    MB/s

  • 解压缩速度

    MB/s

  • 资源占用
    • cpu
    • 内存

影响压缩率的因素

  • 压缩算法
  • 压缩级别
  • 压缩分块大小

ceph

压缩库封装

  1. 描述

ceph压缩库支持zlib、snappy、zstd、lz4、brotli等压缩方法。其中zlib支持isa-l。

  1. 源码
    • 目录结构
      src/compressor/
          CompressionPlugin.h
          Compressor.h
          Compressor.cc
          zlib/
              ...
          snappy/
              ...
          zstd/
              ...
          lz4/
              ...
          brotli/
              ...
      

BlueStore: inline compression

  1. 配置项
    • bluestore_compression_mode(压缩模式)
      • 描述:
      • type: str
      • default: none
      • 有效值:
        • none: 绝不压缩数据
        • passive: 当且仅当写操作有一个可压缩的标识设置的时候才压缩数据
        • aggressive: 压缩数据,除非写入操作有一个不可压缩的标识设置
        • force: 不管什么情况都尝试压缩数据
    • bluestore_compression_algorithm(压缩算法)
      • 描述: 由于压缩少量数据时CPU开销较高,因此不建议将zstd用于BlueStore。
      • type: str
      • default: snappy
      • 有效值:
        *
        • snappy
        • zlib
        • zstd
        • lz4
        • brotli
    • bluestore_compression_required_ratio(被要求的压缩率)
      • 描述: 压缩后的数据块大小相对于原始大小的比例必须至少如此小才能存储压缩版本
      • type: float
      • default: 0.85
    • bluestore_compression_min_blob_size
      • 描述: 随机访问时压缩的最大块大小。 大于此值的块在被压缩之前被分解为最多bluestore_compression_max_blob_size字节的较小blob,小于这个值的块不会被压缩
      • type: size
      • default: 0B
    • bluestore_compression_min_blob_size_hdd
      • type: size
      • default: 8Ki
    • bluestore_compression_min_blob_size_ssd
      • type: size
      • default: 64Ki
    • bluestore_compression_max_blob_size
      • 描述: 非随机访问时压缩的最大块大小。
      • type: size
      • default: 0B
    • bluestore_compression_max_blob_size_hdd
      • type: size
      • default: 64Ki
    • bluestore_compression_max_blob_size_ssd
      • type: size
      • default: 64Ki
  2. 单独设置指定存储池的属性
    ceph osd pool set <pool-name> compression_mode <mode>
    ceph osd pool set <pool-name> compression_algorithm <algorithm>
    ceph osd pool set <pool-name> compression_required_ratio <ratio>
    ceph osd pool set <pool-name> compression_min_blob_size <size>
    ceph osd pool set <pool-name> compression_max_blob_size <size>
    
  3. 相关源码
    • 压缩信息元数据bluestore_compression_header_t:
      src/os/bluestore/bluestore_types.h 
      
      struct bluestore_compression_header_t {
          uint8_t type = Compressor::COMP_ALG_NONE; // 压缩类型
          uint32_t length = 0; // 压缩后的数据的大小
          boost::optional<int32_t> compressor_message;
      
      }
      

Object Gateway(RadosGW): compression

  1. 相关源码
    • compression_block
      src/rgw/rgw_compression_types.h
      
      struct compression_block {
          uint64_t old_ofs;   // 压缩后的块在原始对象的偏移位置
          uint64_t new_ofs;   // 压缩后的块在压缩后的对象的偏移位置
          uint64_t len;       // 压缩后的块的大小
      }
      
    • RGWCompressionInfo
      src/rgw/rgw_compression_types.h
      
      struct RGWCompressionInfo {
        std::string compression_type; // 压缩类型
        uint64_t orig_size;   // 原始对象的大小
        boost::optional<int32_t> compressor_message;
        std::vector<compression_block> blocks;    // 压缩后的块的描述信息
      
      }
      
  2. 示例
    1. 生成一个30M的全零文件: dd if=/dev/zero of=30M count=30 bs=1M
    2. 上传后对象信息
      radosgw-admin object stat --bucket bucket01 --object 30M
      
      {
          "name": "30M",
          "size": 1144,
          "policy": {
              "acl": {
                  "acl_user_map": [
                      {
                          "user": "slh01",
                          "acl": 15
                      }
                  ],
                  "acl_group_map": [],
                  "grant_map": [
                      {
                          "id": "slh01",
                          "grant": {
                              "type": {
                                  "type": 0
                              },
                              "id": "slh01",
                              "email": "",
                              "permission": {
                                  "flags": 15
                              },
                              "name": "SLH01",
                              "group": 0,
                              "url_spec": ""
                          }
                      }
                  ]
              },
              "owner": {
                  "id": "slh01",
                  "display_name": "SLH01"
              }
          },
          "compression": {
              "compression_type": "zstd",
              "orig_size": 31457280,
              "blocks": [
                  {
                      "old_ofs": 0,
                      "new_ofs": 0,
                      "len": 151
                  },
                  {
                      "old_ofs": 4194304,
                      "new_ofs": 151,
                      "len": 151
                  },
                  {
                      "old_ofs": 8388608,
                      "new_ofs": 302,
                      "len": 151
                  },
                  {
                      "old_ofs": 12582912,
                      "new_ofs": 453,
                      "len": 151
                  },
                  {
                      "old_ofs": 16777216,
                      "new_ofs": 604,
                      "len": 151
                  },
                  {
                      "old_ofs": 20971520,
                      "new_ofs": 755,
                      "len": 151
                  },
                  {
                      "old_ofs": 25165824,
                      "new_ofs": 906,
                      "len": 151
                  },
                  {
                      "old_ofs": 29360128,
                      "new_ofs": 1057,
                      "len": 87
                  }
              ]
          },
          "etag": "56313328e6aa42b57cd4d0bdeaac85f4-2",
          "tag": "f59e7e9f-d376-42f1-8239-250db02a393b.164415.5",
          "manifest": {
              "objs": [],
              "obj_size": 1144,
              "explicit_objs": "false",
              "head_size": 0,
              "max_head_size": 0,
              "prefix": "30M.2~ywARpG73u1BwG4F5rkAUBi0vHrwmiu-",
              "rules": [
                  {
                      "key": 0,
                      "val": {
                          "start_part_num": 1,
                          "start_ofs": 0,
                          "part_size": 604,
                          "stripe_max_size": 4194304,
                          "override_prefix": ""
                      }
                  },
                  {
                      "key": 604,
                      "val": {
                          "start_part_num": 2,
                          "start_ofs": 604,
                          "part_size": 540,
                          "stripe_max_size": 4194304,
                          "override_prefix": ""
                      }
                  }
              ],
              "tail_instance": "",
              "tail_placement": {
                  "bucket": {
                      "name": "bucket01",
                      "marker": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                      "bucket_id": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                      "tenant": "",
                      "explicit_placement": {
                          "data_pool": "",
                          "data_extra_pool": "",
                          "index_pool": ""
                      }
                  },
                  "placement_rule": "default-placement"
              },
              "begin_iter": {
                  "part_ofs": 0,
                  "stripe_ofs": 0,
                  "ofs": 0,
                  "stripe_size": 604,
                  "cur_part_id": 1,
                  "cur_stripe": 0,
                  "cur_override_prefix": "",
                  "location": {
                      "placement_rule": "default-placement",
                      "obj": {
                          "bucket": {
                              "name": "bucket01",
                              "marker": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                              "bucket_id": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                              "tenant": "",
                              "explicit_placement": {
                                  "data_pool": "",
                                  "data_extra_pool": "",
                                  "index_pool": ""
                              }
                          },
                          "key": {
                              "name": "30M.2~ywARpG73u1BwG4F5rkAUBi0vHrwmiu-.1",
                              "instance": "",
                              "ns": "multipart"
                          }
                      },
                      "raw_obj": {
                          "pool": "",
                          "oid": "",
                          "loc": ""
                      },
                      "is_raw": false
                  }
              },
              "end_iter": {
                  "part_ofs": 1144,
                  "stripe_ofs": 1144,
                  "ofs": 1144,
                  "stripe_size": 540,
                  "cur_part_id": 3,
                  "cur_stripe": 0,
                  "cur_override_prefix": "",
                  "location": {
                      "placement_rule": "default-placement",
                      "obj": {
                          "bucket": {
                              "name": "bucket01",
                              "marker": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                              "bucket_id": "f59e7e9f-d376-42f1-8239-250db02a393b.25157.1",
                              "tenant": "",
                              "explicit_placement": {
                                  "data_pool": "",
                                  "data_extra_pool": "",
                                  "index_pool": ""
                              }
                          },
                          "key": {
                              "name": "30M.2~ywARpG73u1BwG4F5rkAUBi0vHrwmiu-.3",
                              "instance": "",
                              "ns": "multipart"
                          }
                      },
                      "raw_obj": {
                          "pool": "",
                          "oid": "",
                          "loc": ""
                      },
                      "is_raw": false
                  }
              }
          },
          "attrs": {
              "user.rgw.content_type": "application/octet-stream",
              "user.rgw.pg_ver": "\u0005",
              "user.rgw.source_zone": "",
              "user.rgw.tail_tag": "f59e7e9f-d376-42f1-8239-250db02a393b.164415.5",
              "user.rgw.x-amz-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
              "user.rgw.x-amz-date": "20220424T033328Z",
              "user.rgw.x-amz-meta-s3cmd-attrs": "atime:1650770954/ctime:1650770914/gid:0/gname:root/md5:281ed1d5ae50e8419f9b978aab16de83/mode:33188/mtime:1650770914/uid:0/uname:root"
          }
      }
      

Q & A

  1. RGW对于上传的对象为什么要按照4M拆分数据,这和压缩有什么关联

数据条带化(Data Striping)是一种数据存储技术,主要应用于磁盘阵列(如 RAID)和分布式存储系统中。其主要目的是将数据分割成多个块,并在多个存储设备上并行存储,以提高系统的性能、可靠性和可用性。

在这里,RGW的拆分数据的过程就是数据条带化,事实上,基于RADOS对象存储的RBD、CephFS都需要进行数据条带化,如果用户需要在RADOS对象存储上实现自己的存储应用,也需要进行数据条带化,Ceph在librados基础上提供了libradosstriper接口封装了数据条带化的过程。

从压缩的角度考虑,如果分块很大但只需要读取很小的一段数据(RadosGW支持Range读取),仍然需要读取整个分块的数据(IO放大); 如果分块很小,需要记录的压缩信息元数据就会比较大。因此分块的大小需要选取一个合适的值。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0