Sentinel
Sentinel copied to clipboard
memory leak caused by many origin
Issue Description
我的用法是这样的,服务A调用服务B(服务C、D、E、F....都可能会调用B),在服务B中用了 SphU.entry(resourceName,ResourceTypeConstants.COMMON_RPC, EntryType.IN)
,这本身没有问题。但近期给B服务的其他组件也加上了Sentinel,比如调用DB、redis、甚至是调用其他服务的时候(都在B里面)。上线之后内存增长非常快,基本上高峰期10分钟就会一次full gc,young gc的频次也变得频繁了。
Type: bug report or feature request
Describe what happened (or what feature you want)
我看了一下,具体的原因,应该是有多个上游的时候,origin数量过多,导致MetricNode数量过多。但感觉里面有一些设计不太合理的地方,比如以下链路,A->B->redis、DB、....等其他资源。按照我的理解,B有多个上游,通过设置origin进行区分,问题不大。但是B去调用其他资源的时候,由于属于A->B的子链路,会导致B->redis、B->DB也会带上origin,但事实上,这些子链路(EntryType.OUT的时候)的origin应该是自己,这个时候去区分origin没有任何意义,反而会增加统计的成本。
Describe what you expected to happen
- 是否可以调整为,EntryType为IN的时候才统计origin,避免origin的上下文信息传递到OUT的调用模块。比如以下的代码(不完全是这里,只是粗略的看了 一下,应该还有其他地方要调整)
改为
- 当resource规则没有配置时,是否可以不记录MetricNode
Tell us your environment
1.8.0
服务A调用服务B(服务C、D、E、F....都可能会调用B
是Dubbo服务用的sentinel-dubbo-adapter吗
2.当resource规则没有配置时,是否可以不记录MetricNode
这个社区有同学提到过,可参考 #2169
@cdfive
是Dubbo服务用的sentinel-dubbo-adapter吗
是公司内部的RPC框架,不过是参考sentinel-dubbo-adapter写的,resourcename和dubbo差不多,也是分了 interfaceResourceName 和 methodResourceName
这个社区有同学提到过,可参考 #2169
- 这个应该还没有实现吧。如果过滤resourceName,确实会看不到资源的调用情况。
- 但按照我描述的方案,如果OUT类型不创建origin统计,应该也可以有效降低内存。目前看ContextUtil.enter设置的origin是一个全局变量,会影响所有调用ContextUtil.enter(resourceName, origin) 以后的entry,直到ContextUtil.exit()。如果只是区分来源的话,对于OUT的场景,是否真的有必要也读取这个origin?
具体的原因,应该是有多个上游的时候,origin数量过多,导致MetricNode数量过多。但感觉里面有一些设计不太合理的地方,比如以下链路,A->B->redis、DB、....等其他资源。按照我的理解,B有多个上游,通过设置origin进行区分,问题不大。但是B去调用其他资源的时候,由于属于A->B的子链路,会导致B->redis、B->DB也会带上origin,但事实上,这些子链路(EntryType.OUT的时候)的origin应该是自己,这个时候去区分origin没有任何意义,反而会增加统计的成本。
origin是通过ContextUtil.enter(resourceName, origin);
设置的,在ClusterBuilderSlot
里创建了新的origin的Node。
可参考sentinel-dubbo-adapter中的SentinelDubboProviderFilter
和SentinelDubboConsumerFilter
,
注意只在Provider端调用了ContextUtil.enter
,Consumer端没有;
如:A->B->redis,这个链路,B作为provider端,调用了ContextUtil.enter(resourceName, origin);
,其中origin是通过A传过来的,即origin是A;接着B作为consumer端调redis的时候,也就是OUT的场景,在SentinelDubboConsumerFilter
里并没有调ContextUtil.enter
产生新的origin,即复用当前的context,origin还是A。
你是不是在B->redis的时候也调用了ContextUtil.enter(resourceName, origin);
?所以产生了新的origin。
目前公司项目我们部门所有微服务都接入了sentinel,也存在一个服务被多个服务调用的情况,没有出现内存增长特别快或者gc频繁的情况。
你是不是在B->redis的时候也调用了ContextUtil.enter(resourceName, origin);?所以产生了新的origin。
这个目前没有,只有在provider的时候设置了enter。不过我查了一下,唯一的不同是,设置 ContextUtil.enter(resourceName, origin);
的resourceName,是一个固定值,而dubbo是根据methodResourceName变化的,不知道是否和这个有关系。
接着B作为consumer端调redis的时候,也就是OUT的场景,在SentinelDubboConsumerFilter里并没有调ContextUtil.enter产生新的origin,即复用当前的context,origin还是A
origin确实是A,但是B作为consumer调用其他资源,应该不会经过SentinelDubboConsumerFilter吧?我是这样做的,这里没有额外设置ContextUtil.enter
// 这里通过rpc调用bizMethod,rpc的provider的filter中加入了 ContextUtil.enter
void bizMethod() {
redisClient.get("test:123", value);
}
redisClient中
Entry entry1 = null;
Entry entry2 = null;
try {
entry1 = HahasSphU.entry(String.format("redis:%s",redisName), ResourceTypeConstants.COMMON, EntryType.OUT);
entry2 = HahasSphU.entry(String.format("redis:%s:%s", redisName, "test:%s"), // 刚刚的key,提取的规则
ResourceTypeConstants.COMMON, EntryType.OUT,
"test:123"
);
return callRedis();
} catch (BlockException e) {
throw e;
} catch (Throwable e) {
Tracer.traceEntry(e, entry1);
Tracer.traceEntry(e, entry2);
throw e;
} finally {
if (entry2!=null){
entry2.exit(1, args)
}
if (entry1!=null){
entry1.exit()
}
}
如果我一个服务的redis规则有10种,就会产生20个redis的entry统计,如果还配合origin,比如有10个origin,按照我的理解就会有20*(10+1) = 220种entry统计(看源码好像还包含一个不区分origin的统计),如果用了热点参数限流是不是会更多?
比如有10个origin,按照我的理解就会有20*(10+1) = 220
我理解是1个origin仅会创建1个StatisticNode
,entry个数只跟资源名有关,这个理解对吗?请@sczyh30 帮忙看看
比如有10个origin,按照我的理解就会有20*(10+1) = 220
我理解是1个origin仅会创建1个
StatisticNode
,entry个数只跟资源名有关,这个理解对吗?请@sczyh30 帮忙看看
可以参考 https://github.com/alibaba/Sentinel/wiki/Sentinel-核心类解析#node
- ClusterBuilderSlot:首先根据 resourceName 创建 ClusterNode,并且 set clusterNode to defaultNode;然后再根据 origin 创建来源节点(类型为 StatisticNode),并且 set originNode to curEntry。
- 来源节点(类型为 StatisticNode)的维度是 resource * origin,存在每个 ClusterNode 的 originCountMap 里面
所以StatisticNode数量确实会随着origin增加而增加,而且没有区分resource类型IN和OUT,如果是OUT类型,也会区分origin。导致统计的指标过多
@cdfive 这个是否可以考虑优化一下,resource类型为OUT的就不创建来源节点了
感觉是String.format("redis:%s",redisName), 这个代码 , 不同的redis命令都会生成一个统计节点, 导致resouce过多了
@lazyfighter redisName 和 key的数量排查过,所有resource合计没有超过100种。但是dump的数据里,metricNode的数量比其他数据高出好几十倍。唯一的变化就是redis接入了Sentinel,接入前的gc频率明显低于接入后的情况。
如果EntryType.OUT对外调用很多的话,确实会产生很多StatisticNode的origin统计,这个主要是看你需要每个Node的out的orign的统计嘛?毕竟FetchOriginCommandHandler拉取的是每个节点下的origin的统计数据。
给一个小小的建议:在 ContextUtil.enter(resourceName, origin) 之前检查一下origin是否在规则中,如果在规则中则 使 origin=""