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

Golang协程调度

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 本文基于go1.17.6 1.什么是协程? 协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』, 具有对内核来说不可见的特性。 1.1 协程的特点 线程的切换由操作系统负责调度,协程由用户自己进行调度 线程的默认Stack大小是MB级别,而协程更轻量,接近KB级别。 在同一个线程上的多个协程,访问某些资源可以不需要锁 适用于被阻塞的,且需要大量并发的场景。 1.2 Golang的GMP模型 CPU驱动线程上的任务执行 线程由操作系统内核进行调度,Goroutine由Golang运行时(runtime)进行调度 P的 local runnable queue是无锁的,global runnable queue是有锁的 P的 local runnable queue长度限制为256 注意: M和P是绑定关系 M和G是绑定关系 P只是暂存G,他们之间不是绑定关系 E-R图 简化后的E-R图 注意: 后面为了书写简单直接将 local runnable queue表示为本地队列 global runnable queue表示为全局队列 延伸 timer的四叉堆和内存分配器使用的mcache也是每个P一个 Q: 为什么默认情况下P的数量与CPU数量一致? A: 这样可以避免把CPU时间浪费在上线文切换上 1.3 协程和线程的资源消耗对比 类别 栈内存 上下文切换 备注 Thread 1MB 1us 内存占用使用的是Java线程的默认栈大小 Goroutine 4KB 0.2us 内存占用使用的是Linux+x86下的栈大小 2. 常见的Goroutine 让出/调度/抢占场景 ...

March 25, 2022 · 5 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

聊聊Golang中形形色色的同步原语

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 注意: 本文基于Golang 1.17.1 1.引言 阅读Golang的源码的话,会发现里面有形形色色的同步原语,发挥着重要的作用。这篇文章,萌叔打算把它们之间的依赖关系,以及与Linux的同步原语的对应关系整理一下。 2. 与Linux系统的同步原语之间的对应关系 锁类型 Golang Linux 互斥锁 runtime.mutex (低阶) sync.Mutex (高阶) #include <pthread.h>int pthread_mutex_lock(pthread_mutex_t *mutex);int pthread_mutex_trylock(pthread_mutex_t *mutex);int pthread_mutex_unlock(pthread_mutex_t *mutex); 信号量 runtime.semaphore #include <sys/sem.h>int semget(key_t key, int nsems, int semflg);int semctl(int semid, int semnum, int cmd, …);int semop(int semid, struct sembuf *sops, size_t nsops); 条件变量 sync.Cond #include <pthread.h>int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);int pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond); 3.依赖关系 3.1 runtime.mutex runtime/lock_futex.go ...

January 5, 2022 · 1 min

为什么Golang的Timer实现使用四叉堆而不是二叉堆

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 Golang在Timer(计时器)的实现中使用了四叉堆,而不是常见的二叉堆,这个问题引起了我的兴趣。 对于这个问题,网上的解释是这样的 但是我仔细查阅了资料以后,有了点有趣的发现。 2.说明 假设: 堆为d叉堆,堆中共有n个节点 备注: 时间复杂度的证明见d-ary heap Timer最常见的2个操作 1)Insert() 添加一个计时器。涉及 sift-up(上推)操作,时间复杂度为O(log n / log d) Python代码 import math result = math.log(n, d) 时间消耗(需要交换或者比较的次数) n(数量) 二叉堆 四叉堆 八叉堆 1w 13.3 6.64 4.43 10w 16.61 8.30 5.54 100w 19.93 9.97 6.64 1000w 23.25 11.63 7.75 2) Delete() 指定时间到达后,清除计时器。涉及 sift-down(下推)操作,时间复杂度为O(d * log n / log d) Python代码 import math result = d * math.log(n, d) 时间消耗(需要交换或者比较的次数) n(数量) 二叉堆 四叉堆 八叉堆 1w 26.58 26.58 35.43 10w 33.33 33.33 44.29 100w 39.86 39.86 53.15 1000w 46.51 46.51 62.01 3. 结论 正常情况下,插入操作:多叉堆执行性能优于二叉堆; 删除操作:多叉堆执行性能弱于二叉堆。 但是细心的朋友应该已经发现,四叉堆插入操作的耗时只有二叉堆的一半,而删除操作的耗时与二叉堆完全相同。 这么一对比,优势确实显著。 ...

December 28, 2021 · 1 min

打印struct中Field内存对齐的小工具

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1. 引言 因为使用golangci-lint时长有关于内存对齐的告警提示,所以做了一个小工具,可以查看struct中Field在内存中的对齐情况 传送门: vearne/mem-align 2. 使用 package main import ( &quot;github.com/vearne/mem-align&quot; ) type Car struct { flag bool age int32 F1 int8 age2 int32 age3 int16 F2 int64 F3 *int32 InnerStruct struct{ InnerByte byte //InnerStr string } F4 []byte Name string F5 error } func main(){ memalign.PrintStructAlignment(Car{}) } 输出: 相同字符,相同颜色的的字符,表示同一个Field。 后记 其实golang官方在go vet中提供了一个子工具fieldalignment golangci-lint也是使用这个分析器进行内存对齐的检查 安装 fieldalignment go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest 使用-fix参数可以直接修改代码文件 fieldalignment -fix {packagePath} 参考资料 opennota/check mdempsky/maligned Structure size optimization in Golang (alignment/padding) size and alignment guarantees fieldalignment

December 13, 2021 · 1 min

RocketMQ架构设计中的"暴力美学"(1)-NameServer高可用

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 1.引言 人们在潜意识里,总会觉得复杂且精巧的东西是好东西。但是这个复杂这个词在软件架构设计中,却不一定是好事情。因为过于精巧和复杂的系统往往意味着系统更难以维护,出现问题后,故障更难排查。萌叔在阅读和分享RocketMQ的过程中,发现它有很多设计非常的简单粗暴,堪称"暴力美学"的典范,同时又给人眼前一亮的感觉(还能这么玩)。 2. NameServer高可用 在RocketMQ的架构体系中,NameServer的作用类似于注册中心,Broker会周期性的向NameServer发送心跳,注册Topic信息。Producer和Consumer会向NameServer查询某个Topic的路由信息(Topic位于哪个Broker) Topic路由信息 { &quot;OrderTopicConf&quot;: &quot;&quot;, &quot;queueDatas&quot;: [{ &quot;brokerName&quot;: &quot;broker-3&quot;, &quot;readQueueNums&quot;: 4, &quot;writeQueueNums&quot;: 4, &quot;perm&quot;: 6, &quot;topicSynFlag&quot;: 0 }, { &quot;brokerName&quot;: &quot;broker-4&quot;, &quot;readQueueNums&quot;: 4, &quot;writeQueueNums&quot;: 4, &quot;perm&quot;: 6, &quot;topicSynFlag&quot;: 0 }], ... } 如上面所示,某个Topic位于broker-3和broker-4,每个Broker上有4个MessageQueue 那么如何保证部分NameServer实例宕机后,注册中心的功能仍然能够正常运转呢? 按照正常点的想法,肯定是NameServer分成Master和Slave,然后Master和Slave之间在加上数据同步,如果Master宕机了,只要进行主从切换即可。这种做法肯定没问题,毕竟Hadoop中的NameNode也就是这么做的。 RocketMQ中的实现要简单的多,每个NameServer相互独立,并且它们之间没有通信。Broker会向每一个NameServer、NameServer1、NameServer2等等发送心跳,心跳中包含有它所维护的每个Topic的信息),这样每个NameServer就都含有路由信息。 等到Producer和Consumer向NameServer查询路由信息时,它会尝试向每一个NameServer进行请求。 下面的代码萌叔使用的是rocketmq-client-go的代码,因为rocketmq-client-go比Java的代码更加明显。 internal/route.go#queryTopicRouteInfoFromServer func (s *namesrvs) queryTopicRouteInfoFromServer(topic string) (*TopicRouteData, error) { request := &amp;GetRouteInfoRequestHeader{ Topic: topic, } var ( response *remote.RemotingCommand err error ) ... // 遍历每一个NameServer for i := 0; i &lt; s.Size(); i++ { rc := remote.NewRemotingCommand(ReqGetRouteInfoByTopic, request, nil) ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) response, err = s.nameSrvClient.InvokeSync(ctx, s.getNameServerAddress(), rc) if err == nil { cancel() break } cancel() } if err != nil { rlog.Error(&quot;connect to namesrv failed.&quot;, map[string]interface{}{ &quot;namesrv&quot;: s, &quot;topic&quot;: topic, }) return nil, primitive.NewRemotingErr(err.Error()) } switch response.Code { case ResSuccess: if response.Body == nil { return nil, primitive.NewMQClientErr(response.Code, response.Remark) } routeData := &amp;TopicRouteData{} err = routeData.decode(string(response.Body)) if err != nil { rlog.Warning(&quot;decode TopicRouteData error: %s&quot;, map[string]interface{}{ rlog.LogKeyUnderlayError: err, &quot;topic&quot;: topic, }) return nil, err } return routeData, nil case ResTopicNotExist: return nil, ErrTopicNotExist default: return nil, primitive.NewMQClientErr(response.Code, response.Remark) } }

December 7, 2021 · 1 min

服务调优经验总结

版权声明 本站原创文章 由 萌叔 发表 转载请注明 萌叔 | http://vearne.cc 前言 萌叔工作十多年,有不少服务调优的经验,在这里整理一下思路。希望对自己和同行有所帮助。 1. 发现问题 1.1 发现问题比解决问题难度更大 找出系统瓶颈和问题难度很大,一般只要能够找出且能够稳定复现,问题都能够被解决。 1.2 压测能够帮我们发现系统瓶颈,找出问题 压测不仅可以帮助我们发现系统瓶颈,还能用于发现死锁问题 1.3 尽可能细粒度的监控指标对发现问题帮助很大 讲几个容易忽略的细节 使用Cache的场景,要留意Cache的命中率 使用Channel的细节,要监控channel的长度,注意Channel是否写满,阻塞其它协程 对于比较复杂的接口,如果延迟高,需要有tracing记录,关注到底是那个环节耗时过多 对于MQ和数据库的场景,甚至还需要监控PageCache的命中率和使用情况、磁盘IO等等 GC 的耗时(特别是Java中Full GC) 2. 常见的思路 2.1 绝大多数服务的系统瓶颈是IO 我们接触到的绝大多数服务,都是IO型服务,影响延迟的主要因素是IO,磁盘IO、网络IO,数据库、微服务的响应延迟等等 2.2 如果CPU是瓶颈,需要有火焰图,关注是哪个环节消耗的CPU过多?每个部分占比是否合理?(注意压测) 比如是否大量的CPU时间被消耗在了锁竞争、上下文切换、垃圾回收、调度等。 在Golang中,由于锁中含有自旋锁,如果对锁的竞争激烈,会有大量的CPU时间被消耗在锁上。 在Golang中,P设置不合理也会导致大量的上下文切换 见文章 GOMAXPROCS你设置对了吗? 2.3 某一类型的服务,往往会有一组需要额外注意的核心指标 某些特定的服务容易受到特定因素的影响,比如 基于模型的分类器,压测时要特别关注它的CPU使用率是否能够跑满 见文章 AI预测模型工程化性能调优 数据库服务要留意磁盘IO 2.4 GC 有GC的场景,还要特别留意GC可能带来的负面影响 2.5 日志对服务性能影响不小 写文件和写控制台的性能差不多, 每秒钟差不多30w条。假定一个请求产生30条日志,也就是说即使处理这个请求没有任何逻辑,光是打日志,QPS也就差不多1w左右。(这也是很多服务压测的QPS无法超过1w的原因)。 见文章 玩转高性能日志库ZAP(5)-异步写日志 当你不知道该怎么调优的时候,可以试试关闭所有日志看看。? 3. 常见的优化手段 3.1 串行变并发 在IO型服务非常常见 3.2 同步变异步 常见的场景是回写数据库 3.3 单条变批量 比如数据需要回写数据库,可以在内存中缓存一拨,批量回写数据库,可以减少网络开销,降低数据库的开销 另外像写日志文件,批量写可以减少系统调用,提高写入效率。 笔者之前曾做过测试,将日志从单条同步写改为异步批量写,吞吐能力提升了1倍多。 见文章 玩转高性能日志库ZAP(5)-异步写日志 3.4 善用Cache 在多读少写的服务中,Cache对提升服务的能力,降低延迟,效果非常显著。(别忘了监控Cache) ...

December 4, 2021 · 1 min