Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package com.ctrip.xpipe.spring;

import net.jpountz.lz4.LZ4Factory;
import net.jpountz.lz4.LZ4FastDecompressor;
import net.jpountz.lz4.LZ4SafeDecompressor;
import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.protocol.HttpContext;

import net.jpountz.lz4.LZ4FrameInputStream;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -21,6 +17,12 @@ public class LZ4DecompressionInterceptor implements HttpResponseInterceptor {

private static LZ4Factory factory = LZ4Factory.fastestInstance();

/**
* 服务端未携带 Original-Length 时的解压 buffer 上限:压缩后字节数 × 此倍数。
* 仅作 fallback,正常应走服务端下发的原始长度,避免压缩比超限时解压失败。
*/
private static final int FALLBACK_COMPRESSION_RATIO = 20;

@Override
public void process(HttpResponse response, HttpContext context) throws HttpException, IOException {
Header head = response.getFirstHeader("Content-Encoding");
Expand All @@ -40,13 +42,30 @@ public void process(HttpResponse response, HttpContext context) throws HttpExcep

byte[] compressed = outputStream.toByteArray();

// 优先用服务端下发的原始长度分配解压 buffer;缺失时退回压缩比经验上限。
int maxDecompressedLength = resolveMaxDecompressedLength(response, compressed.length);

LZ4SafeDecompressor decompressor = factory.safeDecompressor();
byte[] deCompressedData = decompressor.decompress(compressed, compressed.length * 20);
byte[] deCompressedData = decompressor.decompress(compressed, maxDecompressedLength);

// 将解压缩后的数据设置回响应实体
response.setEntity(new ByteArrayEntity(deCompressedData));

}
}

private int resolveMaxDecompressedLength(HttpResponse response, int compressedLength) {
Header originalLength = response.getFirstHeader("Original-Length");
if (originalLength != null) {
try {
int len = Integer.parseInt(originalLength.getValue().trim());
if (len > 0) {
return len;
}
} catch (NumberFormatException ignored) {
}
}
return compressedLength * FALLBACK_COMPRESSION_RATIO;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@
import com.ctrip.xpipe.redis.checker.RedisInfoManager;
import com.ctrip.xpipe.redis.checker.controller.result.ActionContextRetMessage;
import com.ctrip.xpipe.redis.checker.healthcheck.*;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.interaction.*;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisinfo.RedisMsgCollector;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.interaction.DefaultDelayPingActionCollector;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.interaction.DefaultPsubPingActionCollector;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.interaction.HEALTH_STATE;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.interaction.HealthStatusDesc;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisconf.AbstractRedisConfigRuleAction;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisinfo.InfoActionContext;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisinfo.RedisMsgCollector;
import com.ctrip.xpipe.redis.checker.healthcheck.stability.StabilityHolder;
import com.ctrip.xpipe.redis.checker.model.RedisMsg;
import com.ctrip.xpipe.redis.core.meta.MetaCache;
Expand Down Expand Up @@ -104,13 +108,16 @@ public String getHealthCheckRedisInstanceForPingAction(@PathVariable String ip,
}

@RequestMapping(value = "/health/redis/info/{ip}/{port}", method = RequestMethod.GET)
public ActionContextRetMessage<Map<String, String>> getRedisInfo(@PathVariable String ip, @PathVariable int port) {
return ActionContextRetMessage.from(redisInfoManager.getInfoByHostPort(new HostPort(ip, port)));
public ActionContextRetMessage<Map<String, String>> getRedisInfo(
@PathVariable String ip, @PathVariable int port,
@RequestParam(value = "section", required = false) String section) {
return InfoActionContext.toRetMessage(redisInfoManager.getInfoByHostPort(new HostPort(ip, port)), section);
}

@RequestMapping(value = "/health/redis/info/all", method = RequestMethod.GET)
public Map<HostPort, ActionContextRetMessage<Map<String, String>>> getAllRedisInfo() {
return ActionContextRetMessage.map(redisInfoManager.getAllInfos());
public Map<HostPort, ActionContextRetMessage<Map<String, String>>> getAllRedisInfo(
@RequestParam(value = "section", required = false) String section) {
return InfoActionContext.toRetMessage(redisInfoManager.getAllInfos(), section);
}

@GetMapping("/health/check/status/all")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,60 @@ class Result extends ActionContextRetMessage<Map<String, String>> {}

class ResultMap extends HashMap<HostPort, ActionContextRetMessage<Map<String, String>>> {}

@Override
default Map<String, String> parse(String info) {
/**
* Section 感知解析:section 为 null/空时全量返回(等同原行为);非空时只返回
* 落在匹配 section(`# SectionName` 头标识,忽略大小写)内的 key:value。
* 使用 split(":", 2) 避免值含冒号被截断。
*/
static Map<String, String> parse(String info, String section) {
Map<String, String> result = new HashMap<>();
boolean sectionFilter = section != null && !section.isEmpty();
String wanted = sectionFilter ? section.trim() : null;
String currentSection = null;
String[] lines = info.split("\r\n");
for (String line : lines) {
String[] keyValues = line.split(":");
if (line.startsWith("# ")) {
currentSection = line.substring(2).trim();
continue;
}
String[] keyValues = line.split(":", 2);
if (keyValues.length == 2) {
result.put(keyValues[0], keyValues[1]);
if (!sectionFilter || (currentSection != null && currentSection.equalsIgnoreCase(wanted))) {
result.put(keyValues[0], keyValues[1]);
}
}
}
return result;
}

@Override
default Map<String, String> parse(String info) {
return parse(info, null);
}

/**
* Section 感知版的 ActionContextRetMessage.from(ctx):success 时 payload 为
* parse(inner().getResult(), section) 过滤后的 Map;fail 时 state=FAIL + cause message。
*/
static Result toRetMessage(InfoActionContext ctx, String section) {
Result ret = new Result();
if (ctx.isSuccess()) {
ret.setState(ActionContextRetMessage.SUCCESS_STATE);
ret.setPayload(parse(ctx.inner().getResult(), section));
} else {
ret.setState(ActionContextRetMessage.FAIL_STATE);
Throwable c = ctx.getCause();
ret.setMessage(c == null ? "unknown" : c.getMessage());
}
return ret;
}

/**
* 对多个 InfoActionContext 聚合为 ResultMap,逐个套 toRetMessage(ctx, section)。
*/
static ResultMap toRetMessage(Map<HostPort, InfoActionContext> contexts, String section) {
ResultMap result = new ResultMap();
contexts.forEach((hp, ctx) -> result.put(hp, toRetMessage(ctx, section)));
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisconf.diskless.DiskLessReplCheckActionTest;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisconf.version.VersionCheckActionFactoryTest;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisconf.version.VersionCheckActionTest;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisinfo.InfoActionContextTest;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisinfo.RedisMsgCollectorTest;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redismaster.*;
import com.ctrip.xpipe.redis.checker.healthcheck.actions.redisstats.crdtinforeplication.CrdtInfoReplicationActionFactoryTest;
Expand Down Expand Up @@ -77,8 +78,8 @@
import com.ctrip.xpipe.redis.checker.healthcheck.actions.sentinel.controller.OneWaySentinelHelloCheckControllerTest;
import com.ctrip.xpipe.redis.checker.healthcheck.allleader.DefaultSentinelMonitorsCheckTest;
import com.ctrip.xpipe.redis.checker.healthcheck.clusteractions.beacon.BeaconConsistencyCheckActionTest;
import com.ctrip.xpipe.redis.checker.healthcheck.clusteractions.beacon.SentinelBeaconConsistencyCheckActionTest;
import com.ctrip.xpipe.redis.checker.healthcheck.clusteractions.beacon.DefaultBeaconMetaControllerTest;
import com.ctrip.xpipe.redis.checker.healthcheck.clusteractions.beacon.SentinelBeaconConsistencyCheckActionTest;
import com.ctrip.xpipe.redis.checker.healthcheck.clusteractions.beacon.SentinelBeaconMigrationControllerTest;
import com.ctrip.xpipe.redis.checker.healthcheck.config.DefaultHealthCheckConfigTest;
import com.ctrip.xpipe.redis.checker.healthcheck.factory.DefaultHealthCheckEndpointFactoryTest;
Expand Down Expand Up @@ -215,7 +216,9 @@
DefaultDelayPingActionCollectorTest.class,
DefaultAggregatorPullServiceTest.class,
RedisMsgCollectorTest.class,
RedisMsgReporterTest.class
RedisMsgReporterTest.class,

InfoActionContextTest.class

})
public class AllTests {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.ctrip.xpipe.redis.checker.healthcheck.actions.redisinfo;

import com.ctrip.xpipe.endpoint.HostPort;
import com.ctrip.xpipe.redis.checker.controller.result.ActionContextRetMessage;
import org.junit.Assert;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

public class InfoActionContextTest {

private static final String RAW =
"# Server\r\n" +
"redis_version:7.0.0\r\n" +
"uptime_in_seconds:100\r\n" +
"# Replication\r\n" +
"role:master\r\n" +
"connected_slaves:2\r\n" +
"# Memory\r\n" +
"used_memory:1024\r\n";

@Test
public void parseNullSection_returnsAll() {
Map<String, String> m = InfoActionContext.parse(RAW, null);
Assert.assertEquals(5, m.size());
Assert.assertEquals("master", m.get("role"));
}

@Test
public void parseEmptySection_returnsAll() {
Map<String, String> m = InfoActionContext.parse(RAW, "");
Assert.assertEquals(5, m.size());
}

@Test
public void parseReplicationSection_returnsOnlyReplication() {
Map<String, String> m = InfoActionContext.parse(RAW, "replication");
Assert.assertEquals(2, m.size());
Assert.assertEquals("master", m.get("role"));
Assert.assertEquals("2", m.get("connected_slaves"));
Assert.assertNull(m.get("redis_version"));
}

@Test
public void parseSectionCaseInsensitive() {
Map<String, String> m = InfoActionContext.parse(RAW, "REPLICATION");
Assert.assertEquals(2, m.size());
}

@Test
public void parseUnknownSection_returnsEmpty() {
Map<String, String> m = InfoActionContext.parse(RAW, "foo");
Assert.assertTrue(m.isEmpty());
}

@Test
public void toRetMessage_successWithSection_returnsFilteredPayload() {
RawInfoActionContext raw = new RawInfoActionContext(null, RAW);
InfoActionContext ctx = () -> raw;
InfoActionContext.Result ret = InfoActionContext.toRetMessage(ctx, "replication");
Assert.assertEquals(ActionContextRetMessage.SUCCESS_STATE, ret.getState());
@SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) ret.getPayload();
Assert.assertEquals(2, payload.size());
Assert.assertEquals("master", payload.get("role"));
Assert.assertEquals("2", payload.get("connected_slaves"));
Assert.assertNull(payload.get("redis_version"));
}

@Test
public void toRetMessage_successNullSection_returnsAllPayload() {
RawInfoActionContext raw = new RawInfoActionContext(null, RAW);
InfoActionContext ctx = () -> raw;
InfoActionContext.Result ret = InfoActionContext.toRetMessage(ctx, null);
Assert.assertEquals(ActionContextRetMessage.SUCCESS_STATE, ret.getState());
@SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) ret.getPayload();
Assert.assertEquals(5, payload.size());
}

@Test
public void toRetMessage_fail_setsFailStateAndMessage() {
RuntimeException cause = new RuntimeException("boom");
RawInfoActionContext rawFail = new RawInfoActionContext(null, cause);
InfoActionContext failCtx = () -> rawFail;
InfoActionContext.Result ret = InfoActionContext.toRetMessage(failCtx, "replication");
Assert.assertEquals(ActionContextRetMessage.FAIL_STATE, ret.getState());
Assert.assertEquals("boom", ret.getMessage());
Assert.assertNull(ret.getPayload());
}

@Test
public void toRetMessage_mapAggregatesAll() {
RawInfoActionContext raw = new RawInfoActionContext(null, RAW);
InfoActionContext ctx = () -> raw;
Map<HostPort, InfoActionContext> contexts = new HashMap<>();
contexts.put(new HostPort("1.1.1.1", 6379), ctx);
InfoActionContext.ResultMap result = InfoActionContext.toRetMessage(contexts, "replication");
Assert.assertEquals(1, result.size());
InfoActionContext.Result ret = (InfoActionContext.Result) result.get(new HostPort("1.1.1.1", 6379));
Assert.assertEquals(ActionContextRetMessage.SUCCESS_STATE, ret.getState());
@SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) ret.getPayload();
Assert.assertEquals(2, payload.size());
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
*/
public interface ConsoleCheckerService {

Map<HostPort, ActionContextRetMessage<Map<String, String>>> getAllLocalRedisInfos();
Map<HostPort, ActionContextRetMessage<Map<String, String>>> getAllLocalRedisInfos(String section);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,13 @@ public DefaultConsoleCheckerService(HostPort hostPort) {
allRedisInfosUrl = String.format("%s/api/health/redis/info/all", this.address);
}

public Map<HostPort, ActionContextRetMessage<Map<String, String>>> getAllLocalRedisInfos() {
@Override
public Map<HostPort, ActionContextRetMessage<Map<String, String>>> getAllLocalRedisInfos(String section) {
try {
return restTemplate.getForObject(allRedisInfosUrl, InfoActionContext.ResultMap.class);
String url = (section == null || section.isEmpty())
? allRedisInfosUrl
: allRedisInfosUrl + "?section=" + java.net.URLEncoder.encode(section, "UTF-8");
return restTemplate.getForObject(url, InfoActionContext.ResultMap.class);
} catch (Throwable t) {
return Collections.emptyMap();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.ctrip.xpipe.redis.console.console;

import com.ctrip.xpipe.endpoint.HostPort;
import com.ctrip.xpipe.redis.checker.controller.result.RetMessage;
import com.ctrip.xpipe.redis.checker.CheckerService;
import com.ctrip.xpipe.redis.checker.controller.result.ActionContextRetMessage;
import com.ctrip.xpipe.redis.checker.controller.result.RetMessage;
import com.ctrip.xpipe.redis.console.controller.api.vo.SentinelBeaconUsageItem;
import com.ctrip.xpipe.redis.console.controller.api.vo.SentinelClusterBeaconRouteItem;
import com.ctrip.xpipe.redis.console.healthcheck.fulllink.model.ShardCheckerHealthCheckModel;
Expand Down Expand Up @@ -43,7 +43,7 @@ public interface ConsoleService extends CheckerService {

Map<String, Pair<HostPort, Long>> getCrossMasterDelayFromParallelService(String sourceDcId, String clusterId, String shardId);

Map<HostPort, ActionContextRetMessage<Map<String, String>>> getAllLocalRedisInfos();
Map<HostPort, ActionContextRetMessage<Map<String, String>>> getAllLocalRedisInfos(String section);

List<ShardCheckerHealthCheckModel> getShardAllCheckerGroupHealthCheck(String dcId, String clusterId, String shardId);

Expand Down
Loading
Loading