千亿 KV 数据存储和查询方案

点击上方“小强的进阶之路”,选择“星标”公众号

优质文章,及时送达

aa5bfa7e9f625d4338ce41de6e368aa6.png

预计阅读时间: 10分钟

背景

md5是不可解密的. 通常网站http://www.cmd5.com/宣称的解密都是有一个MD5到值的映射数据库(彩虹表).

做法是提前将数据用MD5加密,然后保存成MD5到原数据的映射关系,解密时只要查询MD5对应的值就可以了.

f3bbc4e5f63984996f31eb6d3a715c35.png

业务数据将近1000亿,估算下来大概占用6T. 由于MD5的数据是32位,而且每一位都属于0-f.

如果直接查询生成的6T数据,速度估计很慢. 于是想到分区, 比如以32位MD5的前几位相同的作为一个分区,

查询时首先将MD5路由到指定的分区, 再查询这个分区的所有数据,这样每个分区的数据量就会少很多.

原始文件data.txt(最后两个字段表示MD5的前四位):

111111111111111,001e5a2b1c68d7b7dddddddddddddddc,00,1e

222222222222222,01271cc012464ae8ccccccccccccccce,01,27

Hive分区(×)

临时表和分区表:

CREATE EXTERNAL TABLE `mob_mdf_tmp`(

  `mob` string,

  `mdf` string,

  `mdf_1` string,

  `mdf_2` string

  )

ROW FORMAT delimited fields terminated by ','

LOCATION 'hdfs://tdhdfs/user/tongdun/mob_mdf_tmp';

CREATE EXTERNAL TABLE `mob_mdf`(

  `mob` string,

  `mdf` string

  )

PARTITIONED BY (

  mdf_1 string,

  mdf_2 string)

stored as parquet

LOCATION 'hdfs://tdhdfs/user/tongdun/mob_mdf';

将原始文件导入到临时表(或者用hive的load命令),然后读取临时表,加载数据到分区表

#!/bin/sh

file=$1

/usr/install/hadoop/bin/hadoop fs -put $file /user/tongdun/mod_mdf_tmp

#LOAD DATA LOCAL INPATH 'id.txt' INTO TABLE id_mdf PARTITION(mdf_1='ab',mdf_2='cd');

#LOAD DATA LOCAL INPATH 'id.txt' INTO TABLE id_mdf_tmp;

/usr/install/apache-hive/bin/hive -e "

set hive.exec.dynamic.partition=true; 

set hive.exec.dynamic.partition.mode=nonstrict; 

SET hive.exec.max.dynamic.partitions=100000;

SET hive.exec.max.dynamic.partitions.pernode=100000;

set mapreduce.map.memory.mb=5120;

set mapreduce.reduce.memory.mb=5120;

INSERT into TABLE mod_mdf PARTITION (mdf_1,mdf_2) SELECT mod,mdf,mdf_1,mdf_2 FROM mod_mdf_tmp;

msck repair table mod_mdf;

"

问题:将原始文件导入到HDFS是很快的,基本分分钟搞定.但是转换成分区的Hive表,速度起慢无比. %><%

AWK脚本处理分区

A.原始文件首先拆分成一级文件,再拆分成二级文件(×)

一级拆分: awk -F, ‘{print >> $3}’ data.txt

上面的awk命令会按照第三列即MD5的前两个字符分组生成不同的文件. 比如生成00,01文件.

然后进行二级拆分: 遍历所有的一级文件, 生成二级文件. 比如001e.txt, 0127.txt.

nums=('0' '1' '2' '3' '4' '5' '6' '7' '8' '9' 'a' 'b' 'c' 'd' 'e' 'f')

for n1 in ${nums[@]};

do

  for n2 in ${nums[@]};

  do

    var=$n1$n2

    awk -F, '{OFS=",";print $1,$2 >> $3_$4".txt"}' $var

  done

done

echo "end."

缺点: 每个数据文件都必须在自己的范围内生成一级文件, 然后在自己的一级文件基础上生成二级文件.

最后所有的二级文件要合并为一个文件. 比较麻烦, %><%

B.原始文件直接生成两级拆分文件

直接拆分成两级的: awk -F, ‘{OFS=”,”;print $1,$2 >> $3_$4″.txt”}’ data.txt

优点: 由于有多个原始数据文件, 执行同样的awk命令, 生成最终结果不需要任何处理.

问题: 大文件分组,速度比较慢,而且不像上面的分成两次,0000.txt文件并不会立刻有数据生成.

同样还有一个问题: 如果多个文件一起追加>>数据, 会产生冲突,即写到同一行.

C.切分原始大文件(×)

对原始大文件(20G~100G)先split: split -C 2014m $file,再进行上面的二级拆分过程.

结果: 27G切分成2G一个文件, 耗时538s. 估算6T数据需要500h~20D. %><%

paldb@linkedin(×)

linkedin开源的paldb声称对于写一次的kv存储读取性能很好. 但是一个严重的问题是不支持在已有的db文件中新增数据.

Can you open a store for writing subsequent times?

No, the final binary file is created when StoreWriter.close() is called.

所以要读取所有的原始文件后,不能一个一个文件地处理. 这期间StoreWriter要一直打开,下面是索引文件的代码:

//直接读取所有原始文件, 生成paldb

public static void indexRawFile(String[] files) throws Exception{

    List<String> prefix = generateFile();

    //提前准备好Writer

    Map<String,StoreWriter> maps = new HashMap();

    for(String pref : prefix){

        StoreWriter writer = PalDB.createWriter(new File(folder + pref + ".paldb"));

        maps.put(pref, writer);

    }

    for(String filepath : files){

        File file = new File(folder + filepath);

        BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file));

        BufferedReader reader = new BufferedReader(new InputStreamReader(fis,"utf-8"),5*1024*1024);// 用5M的缓冲读取文本文件

        String line = "";

        while((line = reader.readLine()) != null){

            String[] data = line.split(",");

            //根据前两位, 确定要使用哪个Writer. 相同2位前缀的记录写到同一个db文件里

            String prefData = data[2];

            maps.get(prefData).put(data[1], data[0]);

        }

        fis.close();

        reader.close();

    }

    for (Map.Entry<String, StoreWriter> entry : maps.entrySet()) {

        entry.getValue().close();

    }

}

查询一条记录就很简单了, 首先解析出MD5的前两位, 找到对应的paldb文件, 直接读取:

System.out.println("QUERYING>>>>>>>>>");

String file = md5.substring(0,2) + ".paldb";

StoreReader reader = PalDB.createReader(new File(folder + file));

String id = reader.get(md5);

System.out.println(id);

sparkey@spotify

sparkey也声称对于read-heavy systems with infrequent large bulk inserts对于经常读,不经常(大批量)写的性能很好.

sparkey有两种文件:索引文件(index file)和日志文件(log file).

Spark BulkLoad

HBaseRDD: 

https://github.com/unicredit/hbase-rdd

SparkOnHBase在最新的HBase版本中已经合并到了hbase代码中.

建立一个columnfamily=id. 并且在这个cf下有一个column=id存储id数据(cf必须事先建立,column则是动态的).

create 'data.md5_id','id'

put 'data.md5_id','a9fdddddddddddddddddddddddddddde','id:id','111111111111'

get 'data.md5_id','a9fdddddddddddddddddddddddddddde'

scan 'data.md5_id'

Spark的基本思路是: 读取文本文件, 构造RowKey -> Map<CF -> Map<Column -> Value>>的RDD:

val rdd = sc.textFile(folder).map({ line =>

  val data = line split ","

  val content = Map(cf -> Map(column -> data(0)))

  data(1) -> content

})

rdd.toHBaseBulk(table)

HBase BulkLoad

HBase的BulkLoad分为两个节点: 运行MapReduce生成HFile文件, 导入到HBase集群

数据存储: http://zqhxuyuan.github.io/2015/12/19/2015-12-19-HBase-BulkLoad/

9248dc8d2b407400a433980f35582f4c.png

查询(多线程): http://zqhxuyuan.github.io/2015/12/21/2015-12-21-HBase-Query/

b70f68e614c0137bfe2f52a535a643aa.png

存在的问题: 在生成HFile时,是对每个原始文件做MR任务的,即每个原始文件都启动一个MR作业生成HFile.

这样只保证了Reduce生成的HFile在这个原始文件是有序的.不能保证所有原始文件生成的HFile是全局有序的.

这样当只导入第一个文件夹时,BulkLoad是直接移动文件.但是导入接下来生成的文件夹时,就会发生Split操作!

虽然每个MapReduce生成的HFile在这个文件夹内是有序的. 但是不能保证所有MR作业的HFile是全局有序的!

      MapReduce/importtsv                 completebulkload(mv)           

txt1  ------------------->  HFile(00-03)  -------------------->   Region 

                            HFile(03-10)  -------------------->   Region 

                            HFile(10-30) ️ -------------------->   Region

      MapReduce/importtsv                 bulkload(split and copy!)

txt2  ------------------->  HFile(01-04)  

                            HFile(04-06)

                            HFile(06-15)

数据验证:

hbase(main):002:0> get 'data.md5_mob2','2774f8075a3a7707ddf6b3429c78c041'

COLUMN                                             CELL

0 row(s) in 0.2790 seconds

hbase(main):003:0> get 'data.md5_mob2','695c52195b25cd74fef1a02f4947d2b5'

COLUMN                                             CELL

 mob:c1                                            timestamp=1450535656819, value=69

 mob:c2                                            timestamp=1450535656819, value=5c

 mob:mob                                           timestamp=1450535656819, value=13829274666

3 row(s) in 0.0640 seconds

Cassandra

Cassandra和HBase都是列式数据库.HBase因为使用MapReduce,所以读取HDFS上的大文件时,会分成多个Map任务.

Cassandra导入数据不可避免的是需要读取原始的大文件,一种直接生成SSTable,一种是读取后直接写入到集群中.

SSTable Writer

//构造Cassandra的Writer对象

CQLSSTableWriter.Builder builder = CQLSSTableWriter.builder();

builder.inDirectory(outputDir).forTable(SCHEMA).using(INSERT_STMT).withPartitioner(new Murmur3Partitioner());

CQLSSTableWriter writer = builder.build();

//读取大文件,写入到Writer对象,最终会生成SSTable文件

while ((line = reader.readLine()) != null) {

    writer.addRow(line.split(",")[1],line.split(",")[0]);

}

单独地遍历文件,不做任何事情,耗时100s=2min. 则读取6T的文件,耗时2000min=33hour.

Driver API

  List<Statement> statementList = new ArrayList();

  while ((line = reader.readLine()) != null) {

      BoundStatement bound = insert.bind(line.split(",")[1],line.split(",")[0]);

      statementList.add(bound);

      if(statementList.size() >= 65535){

          flush(statementList);

          statementList.clear();

      }

  }

// 批量写入

public static void flush(List<Statement> buffer) {

    BatchStatement batch = new BatchStatement(BatchStatement.Type.UNLOGGED);

    for (Statement bound : buffer) {

        batch.add(bound);

    }

    client.execute(batch);

}

KV DataBase

其实我们的业务中只是KeyValue,最适合的不是列式数据库,而是KV数据库.常见的KV数据库有:MemCache,Redis,LevelDB/RocksDB,Riak.

LevelDB

一个数据库一次只能被一个进程打开。leveldb的实现要求使用来自操作系统的锁来阻止对数据库的滥用。在单进程中,同一个leveldb::DB对象可以被多个并发线程安全地共享。即,针对同一个数据库,在没有任何外部同步措施的前提下(leveldb实现本身将会自动去做所需要的同步过程),不同的线程可以写入迭代器或者获取迭代器或者调用Get方法。但是,其它的对象(比如Iterator和WriteBatch)可能需要外部的同步过程。如果两个线程共享一个这样的对象,这俩线程必须通过它们各自的加锁协议(locking protocol)来保护对这个对象的访问。

-rw-r--r--. 1 qihuang.zheng users     0 12月 24 11:44 000003.log

-rw-r--r--. 1 qihuang.zheng users    16 12月 24 11:44 CURRENT

-rw-r--r--. 1 qihuang.zheng users     0 12月 24 11:44 LOCK

-rw-r--r--. 1 qihuang.zheng users    57 12月 24 11:44 LOG

-rw-r--r--. 1 qihuang.zheng users 65536 12月 24 11:44 MANIFEST-000002

⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇⬇

-rw-r--r--. 1 qihuang.zheng users 2116214 12月 24 11:49 000408.sst

...

-rw-r--r--. 1 qihuang.zheng users 3080192 12月 24 11:55 001210.sst

-rw-r--r--. 1 qihuang.zheng users      16 12月 24 11:44 CURRENT

-rw-r--r--. 1 qihuang.zheng users       0 12月 24 11:44 LOCK

-rw-r--r--. 1 qihuang.zheng users  215845 12月 24 11:55 LOG

-rw-r--r--. 1 qihuang.zheng users  196608 12月 24 11:55 MANIFEST-000002

可以看到旧的sst(SSTable)不断被删除,并用新的sst文件代替. 但是速度在处理大文件时依旧很慢.

结论: 涉及到要读取原始文件,遍历每一行,然后调用存储的写入方式即使采用批量,也会很慢.

而HBase的BulkLoad会开启多个Map任务读取大文件,因此速度会比遍历读取大文件要快.

happybase

既然读取大文件很慢,能不能在生成md5数据的时候不写文件, 直接写到目标数据库.

import happybase

connection = happybase.Connection('192.168.47.213')

table = connection.table('data.md5_id2')

def write_data(li):

    batch = table.batch(wal=False)

    for ele in li:

        #wf.write(','.join(ele) + '\n')

        #wf.flush()

        batch.put(ele[0], {'id:id': ele[1]})

    batch.send()

运行一个省份(35,记录数34亿)耗时:

2015-12-29 09:53:38 350100 19550229 999 60000

2015-12-31 02:35:38 359002 20011119 999 3457560000

其他

删除文件名长度=4的所有文件(不包括文件名后缀)

find . -type f | grep -P '/.{8}$' | xargs rm

a=($(ls | grep -E '[0-9a-f]{4}.txt')) && for i in "${a[@]}";do rm -rf "$i";done

查看进程的文件句柄数量(开了两个进程在跑,每个进程用了16^4=65535)

[qihuang.zheng@192-168-47-248 version2]$ lsof -n|awk '{print $2}'|sort|uniq -c |sort -nr|head -2

  65562 6516

  65562 10230

[qihuang.zheng@192-168-47-248 version2]$ jps

6516 GenIdCardRawFile

10230 GenIdCardRawFile

Final:Cassandra

数据存储

建表,列名统一为md5和id

CREATE KEYSPACE data WITH replication = {

  'class': 'NetworkTopologyStrategy',

  'DC2': '1',

  'DC1': '1'

};

use data;

CREATE TABLE md5_id (

  md5 text,

  id text,

  PRIMARY KEY (md5)

);

CREATE TABLE md5_mob (

  md5 text,

  id text,

  PRIMARY KEY (md5)

);

存储时,指定tbl比如md5_id或者md5_mob

nohup java -cp /home/qihuang.zheng/rainbow-table-1.0-SNAPSHOT-jar-with-dependencies.jar \

com.td.bigdata.rainbowtable.store.Rainbow2Cassandra \

-size 5000 -host 192.168.48.47 -tbl md5_mob > rainbow-table.log 2>&1 &

单机SSD,设置批处理大小为5000,不能设置太大,写入记录数36亿,耗时52小时(身份证表)。

total cost[normal]:75705 s

total cost[error]:0 s

结果手工验证

根据md5查询一条记录,大概在6ms之内,看起来能满足线上的要求了。

cqlsh:data> select * from md5_mob where md5='00905121bedd2bb93247f4bd55ff6a73'

 activity                                                                                  | timestamp    | source        | source_elapsed

-------------------------------------------------------------------------------------------+--------------+---------------+----------------

                                                                        execute_cql3_query | 11:57:08,100 | 192.168.48.47 |              0

 Parsing select * from md5_mob where md5='00905121bedd2bb93247f4bd55ff6a73'\n LIMIT 10000; | 11:57:08,102 | 192.168.48.47 |           1340

                                                                       Preparing statement | 11:57:08,103 | 192.168.48.47 |           2529

                                               Executing single-partition query on md5_mob | 11:57:08,104 | 192.168.48.47 |           3576

                                                              Acquiring sstable references | 11:57:08,104 | 192.168.48.47 |           3711

                                                               Merging memtable tombstones | 11:57:08,104 | 192.168.48.47 |           3822

                                     Partition index with 0 entries found for sstable 2790 | 11:57:08,105 | 192.168.48.47 |           4726

                                               Seeking to partition beginning in data file | 11:57:08,105 | 192.168.48.47 |           4765

                 Skipped 0/1 non-slice-intersecting sstables, included 0 due to tombstones | 11:57:08,106 | 192.168.48.47 |           5570

                                                Merging data from memtables and 1 sstables | 11:57:08,106 | 192.168.48.47 |           5597

                                                         Read 1 live and 0 tombstone cells | 11:57:08,106 | 192.168.48.47 |           5728

                                                                          Request complete | 11:57:08,106 | 192.168.48.47 |           6243

发生一次查询后查看系统的状态

[qihuang.zheng@192-168-48-47 ~]$ nodetool cfstats data.md5_mob

Keyspace: data

  Read Count: 1

  Read Latency: 2.361 ms.

  Write Count: 3600002520

  Write Latency: 0.008993030521545303 ms.

  Pending Tasks: 0

    Table: md5_mob

    SSTable count: 11

    Space used (live), bytes: 372167591162

    Space used (total), bytes: 372167591162

    Off heap memory used (total), bytes: 5780134424

    SSTable Compression Ratio: 0.57171179318478

    Number of keys (estimate): 3599990528

    Memtable cell count: 20292

    Memtable data size, bytes: 9344184

    Memtable switch count: 9599

    Local read count: 1

    Local read latency: 2.361 ms

    Local write count: 3600002520

    Local write latency: 0.000 ms

    Pending tasks: 0

    Bloom filter false positives: 0

    Bloom filter false ratio: 0.00000

    Bloom filter space used, bytes: 4500010896

    Bloom filter off heap memory used, bytes: 4,500,010,808

    Index summary off heap memory used, bytes: 1237496744

    Compression metadata off heap memory used, bytes: 42626872

    Compacted partition minimum bytes: 87

    Compacted partition maximum bytes: 103

    Compacted partition mean bytes: 103

    Average live cells per slice (last five minutes): 1.0

    Average tombstones per slice (last five minutes): 0.0

查看直方统计图:

[qihuang.zheng@192-168-48-47 ~]$ nodetool cfhistograms data md5_mob

data/md5_mob histograms

SSTables per Read

1 sstables: 1

Write Latency (microseconds)

      1 us: 57588

      2 us: 10773767

      3 us: 87425134

      4 us: 309487598

      5 us: 632214057

      6 us: 802464460

      7 us: 704315044

      8 us: 477557852

     10 us: 419183030

     12 us: 108322995

     14 us: 28197472

     17 us: 10274579

     20 us: 2620990

     24 us: 1673315

     29 us: 1436756

     35 us: 833132

     42 us: 328493

     50 us: 154832

     60 us: 119731

     72 us: 109200

     86 us: 111004

    103 us: 87783

    124 us: 95593

    149 us: 94378

    179 us: 93731

    215 us: 102252

    258 us: 107963

    310 us: 109766

    372 us: 112553

    446 us: 110686

    535 us: 108196

    642 us: 101888

    770 us: 96206

    924 us: 90912

   1109 us: 88118

   1331 us: 83811

   1597 us: 80263

   1916 us: 75550

   2299 us: 73414

   2759 us: 65003

   3311 us: 57738

   3973 us: 46244

   4768 us: 42409

   5722 us: 72641

   6866 us: 106743

   8239 us: 84552

   9887 us: 47690

  11864 us: 36826

  14237 us: 26347

  17084 us: 13423

  20501 us: 7169

  24601 us: 3241

  29521 us: 1327

  35425 us: 547

  42510 us: 242

  51012 us: 82

  61214 us: 31

  73457 us: 31

  88148 us: 255

 105778 us: 244

 126934 us: 322

 152321 us: 1882

 182785 us: 4259

 219342 us: 5060

 263210 us: 3006

 315852 us: 629

 379022 us: 340

 454826 us: 95

 545791 us: 13

 654949 us: 5

 785939 us: 10

 943127 us: 0

1131752 us: 19

1358102 us: 0

1629722 us: 0

1955666 us: 0

2346799 us: 2

2816159 us: 1

Read Latency (microseconds)

2759 us: 1

Partition Size (bytes)

103 bytes: 3599989854

Cell Count per Partition

2 cells: 3599989854

随机查询RT是否满足。

End

推荐阅读:

工作中一些原则体会

程序员因接外包坐牢 456 天!两万字长文揭露心酸真实经历

清华大学两名博士生被开除:你不吃学习的苦,就要吃生活的苦

15b26a857ebb829b7a05d43e280dbfed.png

明天见(。・ω・。)ノ♡

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
SD2405AL实时时钟模块介绍: SD2400系列是一种具有内置晶振、支持IIC串行接口的高精度实时时钟芯片,CPU可使用该接口通过5位地址寻址来读写片内32字节寄存器的数据(包括时间寄存器、报警寄存器、控制寄存器、通用SRAM寄存器)。 SD2400系列内置晶振,该芯片可保证时钟精度为±5ppm(在25℃下),即年误差小于2.5 分钟;该芯片内置时钟精度调整功能,可以在很宽的范围内校正时钟的偏差(分辨力3ppm),通过外置或内置的数字温度传感器可设定适应温度变化的调整值,实现在宽温范围内高精度的计时功能。 SD2400系列内置的一次性工业级电池或充电电池可保证在外部掉电情况下时钟使用寿命为5~8年时间;内部具备电源切换电路,当芯片检测到主电源VDD掉到电池电压以下,芯片会自动转为由备电电池供电。 SD2400系列内置单路定时/报警中断输出,报警中断时间最长可设至100年;内置频率中断输出和倒计时中断输出。 SD2400系列采用了多种提高芯片可靠性的技术,可满足对实时时钟芯片的各种需要,是在选用高精度实时时钟时的理想选择。 该模块采用Gadgeteer接口,同时很好的兼容Arduino(UNO、MegaDue等)和Maple系列控制板,也可与其他微控制器协同使用。 SD2405AL实时时钟模块特性: 低功耗: 1.0μA 典型值(时钟电路部分,Ta=25℃)。 工作电压:3.3V~5.5V,工作温度:民用级0℃~70℃,工业级-40℃~85℃。 标准IIC总线接口方式, 时钟电路最高速度400KHZ(4.5V~5.5V)。 年、月、日、星期、时、分、秒的BCD码输入/输出,并可通过独立的地址访问各时间寄存器 闰年自动调整功能(从2000年~2099年)。 可选择12/24小时制式. 内置年、月、日、星期、时、分、秒共7字节的报警数据寄存器及1字节的报警允许寄存器。 内置12字节通用SRAM寄存器可用于存储用户的一般数据。 三种中断均可选择从INT脚输出,并具有两个中断标志位. 可设定并自动重置的单路报警中断功能(时间范围最长设至100年),年、月、日、星期、时、分、秒报警共有96种组合方式,并有单事件报警和周期性报警两种中断输出模式. 周期性频率中断输出:从32768Hz~1/16Hz……1秒共十五种方波脉冲. 自动重置的8位倒计时定时器,可选的4种时钟源(4096HZ、64HZ、1HZ、1/60HZ)。 内置晶振,出厂前已对时钟进行校准,时钟精度为±5ppm(在25℃±1℃下),即年误差小于2.5 分钟。 内置时钟精度数字调整功能,可通过程序来调整走时的快慢。用户采用外置或内置的温度传感器,设定适应温度变化的调整值,可实现在宽温范围内高精度的计时功能(在-10℃~50℃小于5 ppm, 在-40℃~85℃小于10ppm)。 内置备电自动切换功能 ,芯片依据不同的电压自动从VDD切换到VBAT或从VBAT切换到VDD。 在VBAT模式下,芯片具有中断输出允许或禁止的功能,可满足在备用电池供电时输出中断的需要。 内置的充电电池及充电电路,累计电池电量超过550mAh,电池使用寿命为5~8年时间;内置的一次性民用级电池使用寿命为3~5年,一次性工业级电池使用寿命为5~8年时间。 内置的16kbit~256kbit非易失性SRAM(C/D/E型),其读写次数为100亿次,且内部写延时小于300ns。 内置的2kbit~256kbitE2PROM(F/B/C/D/E型),其擦写次数100万次 内置IIC总线0.5秒自动复位功能(从Start命令开始计时),保证时钟数据的有效性及可靠性,避免总线挂死问题。 内置三个时钟数据写保护位, 避免对数据的误写操作,可更好地保护时钟数据。 内置VBAT模式IIC总线通信禁止功能,从而避免在电池供电时CPU对时钟操作所消耗的电池电量,也可避免在主电源上、下电的过程中因CPU的I/O端口所输出的不受控的杂波信号对时钟芯片的误写操作,进一步提高时钟芯片的可靠性。 内置上电复位电路及指示位;内置电源稳压,内部计时电压可低至1.5V。 芯片管脚抗静电(ESD)>4KV。 外形尺寸:36x31x14mm 实物购买链接:https://item.taobao.com/item.htm?spm=2013.1.20141001.2.LgLOhp&id=17280765860&scm=1007.10115.36023.100200300000000&pvid=5ade1258-3a58-432a-90dd-c2c12ae31961&idnum=0
在Go语言中,可以使用map数据结构来实现键值对的存储。map是Go语言中的内置类型,可以用于存储任意类型的键和值。下面是一个示例代码,演示了如何插入数据到map中: ```go package main import "fmt" func main() { // 创建一个空的map kvMap := make(map\[string\]string) // 插入键值对 kvMap\["key1"\] = "value1" kvMap\["key2"\] = "value2" // 打印map中的数据 fmt.Println(kvMap) } ``` 在上面的代码中,我们首先使用`make`函数创建了一个空的map,然后使用`键`作为索引,将`值`插入到map中。最后,我们打印了整个map的内容。 需要注意的是,map是无序的,每次遍历map的顺序可能不同。如果需要按照特定的顺序遍历map,可以先将map的键存储到一个切片中,然后对切片进行排序。 希望以上信息对您有所帮助!\[2\] #### 引用[.reference_title] - *1* [Nebula Graph 的 KV 存储分离原理和性能测评](https://blog.csdn.net/weixin_44324814/article/details/123199607)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* *3* [Rust学习:11.3_集合类型之KV 存储 HashMap](https://blog.csdn.net/cacique111/article/details/126334213)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值