wang-jiahua opened a new issue, #10512:
URL: https://github.com/apache/rocketmq/issues/10512
### Before Creating the Enhancement Request
- [x] I have confirmed that this should be classified as an enhancement
rather than a bug/feature.
### Summary
Three independent per-RPC allocations in the remoting framework:
1. **Guava `Stopwatch`**: `RemotingCommand` uses `Stopwatch.createStarted()`
which allocates a new object per RPC. Replace with `System.nanoTime()`
(primitive long).
2. **`Class.getDeclaredConstructor()`**: Copies the `Constructor` object on
every call. Cache in a `ConcurrentHashMap<Class<?>, Constructor<?>>` to pay the
reflective lookup once per class.
3. **`NettyRemotingServer.channelWritabilityChanged`**: Logs at INFO/WARN on
every writability change, triggering `RemotingHelper.parseChannelRemoteAddr()`
+ String concatenation per event. Downgrade to DEBUG with `isDebugEnabled()`
guard.
Additionally: `TopicQueueMappingContext.EMPTY` static singleton to avoid
creating empty context objects for non-static-topic messages (>99% of traffic),
and `NettyDecoder` adapted to use `setProcessTimerNanos(long)` instead of
`setProcessTimer(Stopwatch)`.
### Motivation
JFR `settings=profile` on a broker under steady-state load shows:
- `Class.getDeclaredConstructor()` copies `Constructor` objects — ~237
allocation events per 60s on `RemotingCommand.createResponseCommand` and
`decodeCommandCustomHeaderDirectly`
- Guava `Stopwatch.createStarted()` allocates one object per RPC
request/response pair
- `NettyRemotingServer.channelWritabilityChanged` logged **81,434 lines** in
90 seconds (~900 lines/sec), each triggering `parseChannelRemoteAddr()` +
String concat + AsyncAppender enqueue
### Describe the Solution You'd Like
**1. RemotingCommand — Stopwatch → nanoTime**
```java
// Before
private transient Stopwatch processTimer;
public void markProcessTimer() { processTimer = Stopwatch.createStarted(); }
// After
private transient long processTimerNanos;
public void setProcessTimerNanos(long nanos) { this.processTimerNanos =
nanos; }
public long processTimerElapsedMs() { return (System.nanoTime() -
processTimerNanos) / 1_000_000; }
```
**2. RemotingCommand — Constructor cache**
```java
private static final Map<Class<?>, Constructor<?>> HEADER_CTOR_CACHE = new
ConcurrentHashMap<>();
private static <T extends CommandCustomHeader> T newHeaderInstance(Class<T>
clazz) {
Constructor<?> ctor = HEADER_CTOR_CACHE.computeIfAbsent(clazz, c -> {
try { Constructor<?> ct = c.getDeclaredConstructor();
ct.setAccessible(true); return ct; }
catch (NoSuchMethodException e) { throw new RuntimeException(e); }
});
return (T) ctor.newInstance();
}
```
**3. NettyRemotingServer — log downgrade**
```java
// Before
log.info("Channel[{}] turns writable...",
RemotingHelper.parseChannelRemoteAddr(channel), ...);
// After
if (log.isDebugEnabled()) {
log.debug("Channel[{}] turns writable...",
RemotingHelper.parseChannelRemoteAddr(channel), ...);
}
```
**4. TopicQueueMappingContext — EMPTY singleton**
```java
public static final TopicQueueMappingContext EMPTY =
new TopicQueueMappingContext(null, null, null, null, null);
```
### Describe Alternatives You've Considered
- **Keeping Stopwatch**: Guava Stopwatch is convenient but allocates on
every `createStarted()`. `System.nanoTime()` is JDK built-in and
zero-allocation.
- **Reflection caching via MethodHandles**: More complex setup for the same
end result. `ConcurrentHashMap` with `computeIfAbsent` is simpler and
well-understood.
### Additional Context
Files changed:
- `RemotingCommand.java` — Stopwatch removal + Constructor cache
- `TopicQueueMappingContext.java` — EMPTY singleton
- `NettyRemotingServer.java` — log downgrade
- `NettyDecoder.java` — adapted to `setProcessTimerNanos(long)`
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]