异常描述
我们的binlog应用使用了etcd,用来协调主服务和存储数据源以及订阅相关的元数据信息。程序运行一段时间后,就会抛出mvcc: database space exceeded的异常,详细的堆栈如下:
代码语言:javascript复制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。
要从空间不足配额警报中恢复:
- Compact etcd的历史。
- 对每个etcd端点进行碎片整理。
- 解除警报。
附FQA地址:https://etcd.io/docs/v3.4.0/faq/
解决问题
根据FQA所述,可以通过如下命令4个步骤解决问题:
代码语言:javascript复制# 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
执行以上命令无误后,可以尝试写入数据,如果正常写入数据了,代表已成功释放空间了。最后一个解除警告的步骤不能漏,这就是个标记,和真正有无使用空间没有直接的逻辑关系。否则即使空间已释放了,也会提示空间不足。另除了手动压缩外,可以设置自动压缩,指令如下:
代码语言:javascript复制# 保留一个小时的历史记录
$ etcd --auto-compaction-retention=1
etcd不同的版本自动压缩的行为有细微差别,详情见:https://etcd.io/#history-compaction
碎片整理
压缩key空间后,会出现内部碎片,这些压缩出来的碎片空间可以被etcd使用,但是不会真正的释放物理空间,需要进行碎片整理,如:
代码语言:javascript复制$ etcdctl defrag
Finished defragmenting etcd member[127.0.0.1:2379]
以上指令只作用于当前所在的主机,不会在集群环境中复刻。可以使用--cluster标记指定所有成员以自动查找所有集群成员。如:
代码语言:javascript复制$ 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]