一次Redis内存排查记录

1. 起因 萌叔手上有一个测试用的Redis集群,集群是Master-slave模式, 平常也不怎么使用。 于是打算清理释放一下内存,打算降低一下配置。 使用FLUSHALL FLUSHALL 可以看到所有的key都已经被清空 > info Keyspace # Keyspace 查看内存开销情况 > info memory # Memory used_memory:537921192 used_memory_human:513.00M used_memory_rss:544067584 used_memory_rss_human:518.86M used_memory_peak:650406864 实例的内存开销超过500MB,内存让狗吃了? 2. 排查 # Memory used_memory:537921192 used_memory_human:513.00M used_memory_rss:544067584 used_memory_rss_human:518.86M used_memory_peak:650406864 used_memory_peak_human:620.28M used_memory_peak_perc:82.71% used_memory_overhead:537863648 used_memory_startup:791600 used_memory_dataset:57544 used_memory_dataset_perc:0.01% allocator_allocated:537984728 allocator_active:538705920 allocator_resident:548462592 total_system_memory:201219264512 total_system_memory_human:187.40G used_memory_lua:40960 used_memory_lua_human:40.00K used_memory_scripts:216 used_memory_scripts_human:216B number_of_cached_scripts:1 maxmemory:1073741824 maxmemory_human:1.00G maxmemory_policy:allkeys-lru allocator_frag_ratio:1.00 allocator_frag_bytes:721192 allocator_rss_ratio:1.02 allocator_rss_bytes:9756672 rss_overhead_ratio:0.99 rss_overhead_bytes:-4395008 mem_fragmentation_ratio:1.01 mem_fragmentation_bytes:6229608 mem_not_counted_for_evict:0 mem_replication_backlog:536870912 // 约为512MB mem_clients_slaves:16922 mem_clients_normal:183998 mem_aof_buffer:0 mem_allocator:jemalloc-5.1.0 active_defrag_running:0 lazyfree_pending_objects:0 # Replication role:master connected_slaves:1 slave0:ip=192.168.88.29,port=9981,state=online,offset=23482674189,lag=0 master_replid:b58677283938996fdae00f7692d24d11d88ff488 master_replid2:effdd3cd4ab2030a2d49273e0cf2ed74df4bccb9 master_repl_offset:23482674347 second_repl_offset:17046600529 repl_backlog_active:1 repl_backlog_size:536870912 // 约为512MB repl_backlog_first_byte_offset:22945803436 repl_backlog_histlen:536870912 # Cluster cluster_enabled:0 # Keyspace 将INFO命令返回的所有内容都交给chatGPT分析,笔者找到了答案,主要原因是复制积压缓冲区设置的过大。 ...

September 11, 2024 · 1 min

闲聊Kvrocks中List结构

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. List 结构 List中的数据会被存储在2个列族中 1) list metadata +----------+------------+-----------+-----------+-----------+-----------+ key => | flags | expire | version | size | head | tail | | (1byte) | (Ebyte) | (8byte) | (Sbyte) | (8byte) | (8byte) | +----------+------------+-----------+-----------+-----------+-----------+ 2) list sub keys-values +---------------+ key|version|index => | value | +---------------+ head和tail的初始值是UINT64_MAX / 2 redis_metadata.cc ListMetadata::ListMetadata(bool generate_version) : Metadata(kRedisList, generate_version) { head = UINT64_MAX / 2; tail = head; } 2. 使用 2.1 使用 LPUSH LPUSH mylist "A" subKey的序列如下 ...

July 13, 2023 · 1 min

从MySQL client使用引出的bug聊起

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引子 前几天一个同事在MySQL实例上执行一些数据处理,程序大致如下: modify1.go package main import ( "context" "database/sql" "fmt" _ "github.com/go-sql-driver/mysql" "log" "time" ) func main() { db, err := sql.Open("mysql", "testdb_user:12345678@tcp(192.168.2.100:25037)/testdb?charset=utf8&loc=Asia%2FShanghai&parseTime=true") if err != nil { fmt.Println("===0===", err) } defer db.Close() tx, err := db.Begin() if err != nil { fmt.Println("===0===", err) } // 第1个SQL rows, err := tx.QueryContext(context.Background(), "select mtime, money from test where money > ?", 1) if err != nil { log.Fatal(err) } defer rows.Close() // 迭代查询结果 for rows.Next() { var mtime time.Time var money int if err := rows.Scan(&mtime, &money); err != nil { // Check for a scan error. // Query rows will be closed with defer. log.Fatal(err) } log.Println(mtime, money) // 第2个SQL result, err := tx.ExecContext(context.Background(), "insert into test2(`mtime`, `money`) values (?, ?)", mtime, money) if err != nil { log.Fatal(err) } rowsAffected, _ := result.RowsAffected() lastInsertIdresult, _ := result.LastInsertId() log.Println(rowsAffected, lastInsertIdresult) } tx.Commit() } 报错信息如下: ...

June 18, 2023 · 3 min

LSM-Tree分享

参考资料 1.The Log-Structured Merge-Tree 2.一文带你看透基于LSM-tree的NoSQL系统优化方向 3.数据库存储与索引技术:分布式数据库基石——LSM树 4.WiscKey: Separating Keys from Values in SSD-conscious 5.dgraph-io/badger 6.badger源码分析 7.B树与LSM树读写和空间放大分析 8.大白话彻底搞懂 HBase Rowkey 设计和实现方式 9.Kvrocks: 一款开源的企业级磁盘KV存储服务 10.Kvrocks data structures design 11.布隆过滤器的原理及完整公式推导 1. LSM-Tree简介 1.1 LSM-Tree的概念 LSM-Tree 全称是Log Structured Merge Tree,是一种分层、有序、面向磁盘的数据结构,其核心思想是充分利用磁盘的顺序写性能要远高于随机写性能这一特性,将批量的随机写转化为一次性的顺序写。 1.2 应用场景 以NoSQL为代表的分布式数据库多采用LSM树用于构建底层的存储系统。 Apache Cassandra:Cassandra 是一个高可用性、高可扩展性的分布式 NoSQL 数据库,它使用了 LSM 树来存储数据。 LevelDB:由 Google 开发的高性能键值对数据库,使用 LSM 树来存储数据。 RocksDB:由 Facebook 开发的高性能嵌入式键值对数据库,它是 LevelDB 的改进版本,同样使用 LSM 树来存储数据。 HBase:HBase 是一个分布式列存储数据库,它是基于 Hadoop 的一个开源项目。HBase 的数据存储是基于 LSM 树实现的。 ScyllaDB:ScyllaDB 是一个高性能的分布式 NoSQL 数据库,使用 LSM 树来存储数据,并且兼容 Cassandra API。 Apache Lucene:一个开源的信息检索库,广泛用于各种搜索引擎和全文检索系统。虽然Lucene主要关注于索引结构,但其底层的数据存储也使用了LSM Tree的设计思想。 另外TiDB的本地存储使用的是RocksDB TiDB本地存储 ...

May 6, 2023 · 3 min

REDIS-CLUSTER集群slot迁移过程分析

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.前言 在前面的文章中, 《REDIS-CLUSTER集群创建内部细节详解》 萌叔创建一个Redis集群。 这篇文章,我会为集群添加2个节点,并介绍slot的迁移过程。 2.集群扩容 添加主节点127.0.0.1:7006 redis-cli --cluster add-node 127.0.0.1:7006 127.0.0.1:7000 添加从节点127.0.0.1:7007(并指定其Master节点) redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7000 --cluster-slave --cluster-master-id 86f3cb72813a2d07711b56b3143ff727911f4e1e 新添加的节点上并没有slot分布,需要通过命令让slot重新分布 root@BJ-02:~/cluster-test/7007# redis-cli --cluster reshard 127.0.0.1:7000 >>> Performing Cluster Check (using node 127.0.0.1:7000) S: 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000 slots: (0 slots) slave replicates be905740b96469fc6f20339fc9898e153c06d497 M: 86f3cb72813a2d07711b56b3143ff727911f4e1e 127.0.0.1:7006 slots: (0 slots) master 1 additional replica(s) M: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 slots:[0-5460] (5461 slots) master 1 additional replica(s) S: 9e29dd4b2a7318e0e29a48ae4b37a7bd5ea0a828 127.0.0.1:7007 slots: (0 slots) slave replicates 86f3cb72813a2d07711b56b3143ff727911f4e1e S: 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003 slots: (0 slots) slave replicates 784fa4b720213b0e2b51a4542469f5e318e8658b M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) S: 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004 slots: (0 slots) slave replicates 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. How many slots do you want to move (from 1 to 16384)? 0 How many slots do you want to move (from 1 to 16384)? 1 What is the receiving node ID? 86f3cb72813a2d07711b56b3143ff727911f4e1e Please enter all the source node IDs. Type 'all' to use all the nodes as source nodes for the hash slots. Type 'done' once you entered all the source nodes IDs. Source node #1: all Ready to move 1 slots. Source nodes: M: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) Destination node: M: 86f3cb72813a2d07711b56b3143ff727911f4e1e 127.0.0.1:7006 slots: (0 slots) master 1 additional replica(s) Resharding plan: Moving slot 5461 from 784fa4b720213b0e2b51a4542469f5e318e8658b Do you want to proceed with the proposed reshard plan (yes/no)? yes Moving slot 5461 from 127.0.0.1:7001 to 127.0.0.1:7006: 3. Slot迁移过程 迁移过程在clusterManagerMoveSlot() 中 ...

June 4, 2022 · 3 min

Redis-Cluster集群创建内部细节详解

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 本文基于redis 6.2.7 1.引言 Redis-Cluster集群的搭建非常简单。搭建过程见参考资料1。 执行 redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \ 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ --cluster-replicas 1 root@BJ-02:~/cluster-test# redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 \ > 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 \ > --cluster-replicas 1 >>> Performing hash slots allocation on 6 nodes... Master[0] -> Slots 0 - 5460 Master[1] -> Slots 5461 - 10922 Master[2] -> Slots 10923 - 16383 Adding replica 127.0.0.1:7004 to 127.0.0.1:7000 Adding replica 127.0.0.1:7005 to 127.0.0.1:7001 Adding replica 127.0.0.1:7003 to 127.0.0.1:7002 >>> Trying to optimize slaves allocation for anti-affinity [WARNING] Some slaves are in the same host as their master M: 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000 slots:[0-5460] (5461 slots) master M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master S: 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003 replicates 784fa4b720213b0e2b51a4542469f5e318e8658b S: 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004 replicates 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 S: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 replicates 7eb7ceb4d886580c6d122e7fd92e436594cc105e Can I set the above configuration? (type 'yes' to accept): yes >>> Nodes configuration updated >>> Assign a different config epoch to each node >>> Sending CLUSTER MEET messages to join the cluster Waiting for the cluster to join .. >>> Performing Cluster Check (using node 127.0.0.1:7000) M: 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000 slots:[0-5460] (5461 slots) master 1 additional replica(s) M: 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001 slots:[5461-10922] (5462 slots) master 1 additional replica(s) M: 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002 slots:[10923-16383] (5461 slots) master 1 additional replica(s) S: 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004 slots: (0 slots) slave replicates 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 S: be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005 slots: (0 slots) slave replicates 7eb7ceb4d886580c6d122e7fd92e436594cc105e S: 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003 slots: (0 slots) slave replicates 784fa4b720213b0e2b51a4542469f5e318e8658b [OK] All nodes agree about slots configuration. >>> Check for open slots... >>> Check slots coverage... [OK] All 16384 slots covered. 验证 root@BJ-02:~# redis-cli -h 127.0.0.1 -p 7000 127.0.0.1:7000> cluster nodes 784fa4b720213b0e2b51a4542469f5e318e8658b 127.0.0.1:7001@17001 master - 0 1652672999293 2 connected 5461-10922 7eb7ceb4d886580c6d122e7fd92e436594cc105e 127.0.0.1:7000@17000 myself,master - 0 1652672998000 1 connected 0-5460 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 127.0.0.1:7002@17002 master - 0 1652673000333 3 connected 10923-16383 585c7df69fb267941a40611bbd8ed90349b49175 127.0.0.1:7004@17004 slave 4e0e4be1b4afd2cd1d10166a6788449dd812a4c0 0 1652673000126 3 connected be905740b96469fc6f20339fc9898e153c06d497 127.0.0.1:7005@17005 slave 7eb7ceb4d886580c6d122e7fd92e436594cc105e 0 1652673001373 1 connected 603a8a403536f625f53467881f5f78def9bd46e5 127.0.0.1:7003@17003 slave 784fa4b720213b0e2b51a4542469f5e318e8658b 0 1652673000000 2 connected 一行命令就完成了集群的部署,redis-cli --cluster create到底做了什么。这篇文章萌叔将带你探寻其中的秘密。 ...

May 16, 2022 · 3 min

Redis-Cluster集群模式下Redis客户端如何获得slot的路由信息

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 我们都知道在Redis-Cluster集群模式下,集群中有18634个slot,slot分布在集群多个实例上,当执行一个Command时,Redis客户端会提取Command中的key 根据下面的算法得出key所属的slot slot=CRC16(key)&16383 在根据客户端中的路由表,找到slot所在的Redis实例 这里的路由表存储的就是 slot -> redis-instance 那么问题来了redis客户端是如何得到这个路由表的呢? 2. 分析 下面以go-redis/redis的代码为例,谈谈Redis客户端如何获取和维护slot路由信息。 2.1 存储结构 type clusterClient struct { opt *ClusterOptions nodes *clusterNodes state *clusterStateHolder // 在这里 cmdsInfoCache *cmdsInfoCache //nolint:structcheck } type clusterState struct { nodes *clusterNodes Masters []*clusterNode Slaves []*clusterNode slots []*clusterSlot // 路由信息存储在这里 generation uint32 createdAt time.Time } type clusterSlot struct { start, end int nodes []*clusterNode } 2.2 命令执行过程 1)通过key计算出对应slot 2)通过路由表查找到对应的node信息 3)向node发送CMD 其实第2步根据slot从clusterState中查询对应clusterNode ...

May 14, 2022 · 2 min

BoltDB-MVCC的一种极简实践

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.前言 BoltDB是一个纯Golang实现的极简的key/value数据库。 传送门:boltdb/bolt 主库从2018年起就不再更新了。但它有2个衍生版本 hashicorp/raft-boltdb和etcd-io/bbolt分别被使用在Consul和Etcd。另外其它使用BoltDB的项目 BoltDB之所以能够得到广泛的应用,最大原因是它足够轻量,核心代码只有4000多行,另外大多数关系数据库(SQLite 除外)都需要在应用程序之外独立运行服务。比如MySQL你需要启动MySQL Server,这样就增加了额外的维护成本。而BoltDB可以嵌入在应用程序之中,同生同死。 本文萌叔会把重点放在BoltDB的事务机制的实现上 2. 事务并发控制关系 在BoltDB中官方的描述是有2中类型的事务,读写事务和读事务 2.1 读写事务包含 Bucket.Put()和Bucket.Delete() 等操作 func (b *Bucket) Put(key []byte, value []byte) error func (b *Bucket) Delete(key []byte) error 2.2 读事务包含 Bucket.Get() 等操作 func (b *Bucket) Get(key []byte) []byte 注意: 读写事务在后面的文章中简写为写事务 2.3 写事务并发控制 2.3.1 同一个进程内部的多个协程 通过读写锁来保证某一时刻只能有一个写事务运行。 模式 可以并发 备注 读-读 是 可以并发 读-写 是 1个写事务和多个读事务可以并发 写-写 否 互斥 type DB struct { ... rwlock sync.Mutex // Allows only one writer at a time. ... } 2.3.2 同一台设备上的多个进程 使用操作系统的文件读写锁控制并发 ...

March 2, 2022 · 3 min

elasticsearch如何存储关联关系?

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 之所以写这篇文章是因为我已经在不止一个群里,看到有人问如何在ES中存储关联关系。 2. 答案 你可能会在网上看到有说Join datatype和Nested data type的,但是其实这都不是ES该有的玩法。 Join datatype和Nested data type都会涉及多次查询的开销 Join datatype本身的数据就是在不同的表中,对于分布式数据库,还涉及数据从不同的节点上拉取和组装的开销。 那么应该怎么做?答案就是用冗余的宽表来存储关联关系 举例说明 假如我需要在ES中存储的实体有书籍、书籍有作者信息、书名等等信息,显然实体之间有如下关系 如果在传统的关系型数据库中,就需要创建2张表,一张表表示作者,一张表代表书。但是对于nosql数据库,只需要一张表(书)即可,doc结构形如: { "name":"zhangsan", "publisher_identifier": "xxx-xxxx-xxx" "author":{ "name": "jobs", "phone": "111111111" } } 作者信息作为书的属性存储在一起,放一个doc中即可。 这样的做法必然是会带来数据冗余,但是以空间换时间,查询速度就有了保障。 现代的nosql数据库大多应对的海量数据的存储查询的问题,因此大都是分布式结构。在这种情况下,整体的设计方案必须足够简单,才能够易于维护和扩展。同样的做法,也完全适用于HBase。 3. 说几句某些人可能不爱听的话 ES集群的使用成本其实是很贵的,用了就别怕贵,觉得烧钱就别用 ES自身的性能优化工作做得还是很好的,对大多数人而言,不需要考虑优化,性能不够,就老老实实的加硬件就行。高版本相比低版本性能和稳定性都有很大的提升,优先考虑高版本 SSD对ES的性能提升非常明显(便宜不一定不是好货,但好货一定不便宜) 4. 参考资料 1.Join datatype 2.Nested data type 3.宽表和窄表的区别 打赏我

July 2, 2020 · 1 min

玩转CONSUL(2)–分布式锁

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 前言 分布式锁的场景,大家应该都有遇到过。比如对可靠性有较高要求的系统中,我们需要做主备切换。这时我们可以利用分布式锁,来做选主动作,抢到锁作为主,执行对应的任务,剩余的实例作为备份 redis和zookeeper都可以用来做分布式锁,典型的如redis,可以使用SETNX命令来实现分布式锁。本文将介绍基于consul的分布式锁实现 2. 例子 测试例子 test_lock.go package main import ( "github.com/hashicorp/consul/api" "log" "strconv" "sync" "time" ) func main() { wg := &sync.WaitGroup{} for i := 0; i < 3; i++ { wg.Add(1) go tryLock("mylock", "session"+strconv.Itoa(i), wg) } wg.Wait() } func tryLock(key string, sessionName string, wg *sync.WaitGroup) { defer wg.Done() // Get a new client config := &api.Config{ Address: "dev1:8500", Scheme: "http", } client, err := api.NewClient(config) if err != nil { panic(err) } opts := &api.LockOptions{ Key: key, SessionName: sessionName, } lock, err := client.LockOpts(opts) log.Println(sessionName, "try to get lock obj") for i := 0; i < 3; i++ { leaderCh, err := lock.Lock(nil) if err != nil { log.Println("err", err, sessionName) } if leaderCh == nil{ log.Println("err", err, sessionName) continue } log.Println(sessionName, "lock and sleep") time.Sleep(5 * time.Second) err = lock.Unlock() if err != nil { log.Fatal("err", err) } log.Println(sessionName, "unlock") time.Sleep(5 * time.Second) } } 3. 原理 consul中锁的主要是依赖KV Store和Session相关API ...

May 1, 2019 · 2 min