异常描述
我们的binlog应用使用了etcd,用来协调主服务和存储数据源以及订阅相关的元数据信息。程序运行一段时间后,就会抛出mvcc: database space exceeded的异常,详细的堆栈如下:
Caused by: io.grpc.StatusRuntimeException: RESOURCE_EXHAUSTED: etcdserver: mvcc: database space exceeded at io.grpc.Status.asRuntimeException(Status.java:530) at io.grpc.stub.ClientCalls$UnaryStreamToFuture.onClose(ClientCalls.java:482) at io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39) at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23) at io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40) at io.etcd.jetcd.ClientConnectionManager$AuthTokenInterceptor$1$1.onClose(ClientConnectionManager.java:302) at io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39) at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23) at io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40) at io.grpc.internal.CensusStatsModule$StatsClientInterceptor$1$1.onClose(CensusStatsModule.java:694) at io.grpc.PartialForwardingClientCallListener.onClose(PartialForwardingClientCallListener.java:39) at io.grpc.ForwardingClientCallListener.onClose(ForwardingClientCallListener.java:23) at io.grpc.ForwardingClientCallListener$SimpleForwardingClientCallListener.onClose(ForwardingClientCallListener.java:40) at io.grpc.internal.CensusTracingModule$TracingClientInterceptor$1$1.onClose(CensusTracingModule.java:397) at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java:459) at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java:63) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.close(ClientCallImpl.java:546) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl.access$600(ClientCallImpl.java:467) at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:584) at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37) at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:123)异常原因
经查,这个异常的message是etcd服务端返回的,用来提示应用etcd服务端空间不足了。在etcd的官方文档常见问题(FAQ)版块针对这个场景有明确的说明,如:
Q、:“ mvcc:database space exceeded”是什么意思,我该如何解决?
A、:etcd中的 多版本并发控制数据模型保留了密钥空间的确切历史记录。如果不定期压缩此历史记录(例如,通过设置--auto-compaction),etcd最终将耗尽其存储空间。如果etcd的存储空间不足,则会发出空间配额警报,以保护群集免于进一步写入。只要发出警报,etcd就会以error响应写请求mvcc: database space exceeded。
要从空间不足配额警报中恢复:
附FQA地址:https://etcd.io/docs/v3.4.0/faq/
解决问题
根据FQA所述,可以通过如下命令4个步骤解决问题:
# 1、获取当前的版本 $ rev=$(ETCDCTL_API=3 etcdctl --endpoints=:2379 endpoint status --write-out="json" | egrep -o '"revision":[0-9]*' | egrep -o '[0-9].*') # 2、压缩当前版本之前的所有记录 $ ETCDCTL_API=3 etcdctl compact $rev compacted revision 1516 # 3、清理多余的碎片空间 $ ETCDCTL_API=3 etcdctl defrag Finished defragmenting etcd member[127.0.0.1:2379] # 4、解除警告 $ ETCDCTL_API=3 etcdctl alarm disarm memberID:13803658152347727308 alarm:NOSPACE
执行以上命令无误后,可以尝试写入数据,如果正常写入数据了,代表已成功释放空间了。最后一个解除警告的步骤不能漏,这就是个标记,和真正有无使用空间没有直接的逻辑关系。否则即使空间已释放了,也会提示空间不足。另除了手动压缩外,可以设置自动压缩,指令如下:
# 保留一个小时的历史记录 $ etcd --auto-compaction-retention=1
etcd不同的版本自动压缩的行为有细微差别,详情见:https://etcd.io/#history-compaction
碎片整理
压缩key空间后,会出现内部碎片,这些压缩出来的碎片空间可以被etcd使用,但是不会真正的释放物理空间,需要进行碎片整理,如:
$ etcdctl defrag Finished defragmenting etcd member[127.0.0.1:2379]以上指令只作用于当前所在的主机,不会在集群环境中复刻。可以使用--cluster标记指定所有成员以自动查找所有集群成员。如:
$ etcdctl defrag --cluster Finished defragmenting etcd member[http://127.0.0.1:2379] Finished defragmenting etcd member[http://127.0.0.1:22379] Finished defragmenting etcd member[http://127.0.0.1:32379]