1.mongo-order.xml
<?xml version="1.0" encoding="utf-8"?>
<config>
<value>{"maxConnectionIdleTime":60000,"connectionsPerHost":20,"threadsAllowedToBlockForConnectionMultiplier":15,"maxWaitTime":5000, "connectTimeout":3000,"socketTimeout":3000,"serverSelectionTimeout":3000,"standalone":false,"readPreference":"secondaryPreferred","servers":["mongo-replic-r1-car-all-test-db.01zhuanche.com:27017","mongo-replic-r2-car-all-test-db.01zhuanche.com:27017","mongo-replic-r3-car-all-test-db.01zhuanche.com:27017"]}</value>
</config>
2.MongoEnum
public enum MongoEnum implements IMongo {
OrderMongo("mongo-order"),
DriverMongoMaster("mongo-driver-master"),
DriverMongoSlave("mongo-driver-slave"),
DispatcherOrderMongo("mongo-disp-order"),
;
MongoEnum(String configName) {
this.configName = configName;
}
@Override
public String configKey() {
return configName;
}
private String configName;
}
3.MongoConfig
@Configuration
public class MongoConfig {
@Bean(name = "driverMongoTemplate" )
public MongoTemplate getDriverMongoTemplate() {
return new MongoTemplate( new SqMongoDbFactory(MongoEnum.DriverMongoSlave.get(), "DriverDB" ));
}
@Bean(name = "driverMongoTemplateMaster" )
public MongoTemplate getDriverMasterMongoTemplate() {
return new MongoTemplate( new SqMongoDbFactory(MongoEnum.DriverMongoMaster.get(), "DriverDB" ));
}
@Bean(name = "operatingMongoTemplate" )
public MongoTemplate getMongoTemplate() {
return new MongoTemplate( new SqMongoDbFactory(MongoEnum.DriverMongoSlave.get(), "OperatingDB" ));
}
@Bean(name = "orderDispatcherMongoTemplate" )
public MongoTemplate getOrderDispatcherMongoTemplate() {
return new MongoTemplate( new SqMongoDbFactory(MongoEnum.DispatcherOrderMongo.get(), "OrderDB" ));
}
}
4.SqMongoDbFactory
public class SqMongoDbFactory implements DisposableBean, MongoDbFactory {
private final MongoClientHolder holder;
private final String databaseName;
private final PersistenceExceptionTranslator exceptionTranslator;
private WriteConcern writeConcern;
public SqMongoDbFactory(MongoClientHolder holder, String databaseName) {
Assert.hasText(databaseName, "Database name must not be empty");
Assert.isTrue(databaseName.matches("[\\w-]+"),
"Database name must only contain letters, numbers, underscores and dashes!");
this.holder = holder;
this.databaseName = databaseName;
this.exceptionTranslator = new MongoExceptionTranslator();
}
public void setWriteConcern(WriteConcern writeConcern) {
this.writeConcern = writeConcern;
}
@Override
public void destroy() throws Exception {
holder.getClient().close();
}
@Override
public DB getDb() throws DataAccessException {
return getDb(databaseName);
}
@Override
public DB getDb(String dbName) throws DataAccessException {
Assert.hasText(dbName, "Database name must not be empty.");
DB db = holder.getDB(dbName);
if (writeConcern != null) {
db.setWriteConcern(writeConcern);
}
return db;
}
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return this.exceptionTranslator;
}
}
5.IMongo
public interface IMongo {
ConcurrentHashMap<IMongo, MongoClientHolder> valueCache = new ConcurrentHashMap<>();
String configKey();
default MongoClientHolder get() {
return valueCache.computeIfAbsent(this, MongoClientHolder::new);
}
}
最重要的还是生产出构造SqMongoDbFactory所需要的MongoClientHolder对象
6.MongoClientHolder
public class MongoClientHolder implements Warmup {
public static final int MAX_WAIT_TIME = (int) SECONDS.toMillis(5);
private static final int MAX_WAIT_COMMANDS = 100;
private static final int TIME_OUT = (int) SECONDS.toMillis(3);
private static final int CONNECTION_MULTIPLIER = 15;
private static final int MAX_CONNECTION_IDLE_TIME = (int) MINUTES.toMillis(10);
private static final long STOP_PERIOD = SECONDS.toMillis(10);
private static final int MAX_MONGO_CONNECTION_PER_HOST = 10;
private static final long SECONDARY_MAX_BEHIND_SECONDS = 600L;
private static final Logger logger = LoggerFactory.getLogger(MongoClientHolder.class);
private final String configKey;
private final BaseNodeResource<MongoClient> mongoNode;
public MongoClientHolder(IMongo mongoConfig) {
this.configKey = mongoConfig.configKey();
this.mongoNode = BaseNodeResource.<MongoClient>newBuilder()
.withKey(configKey)
.withFactory(this::initMongo)
.withCleanupConsumer(this::cleanup)
.withWaitStopPeriod(3000) //鍏抽棴鑰佺殑mongo鍓嶏紝鍏堢瓑1000ms锛岀瓑寰呮棫鏁版嵁鎿嶄綔瀹屾垚銆�
.addFactoryFailedListener(this::factoryFailListener)
.build();
}
public MongoClientHolder(String configKey) {
this.configKey = configKey;
this.mongoNode = BaseNodeResource.<MongoClient>newBuilder()
.withKey(configKey)
.withFactory(this::initMongo)
.withCleanupConsumer(this::cleanup)
.withWaitStopPeriod(3000) //鍏抽棴鑰佺殑mongo鍓嶏紝鍏堢瓑1000ms锛岀瓑寰呮棫鏁版嵁鎿嶄綔瀹屾垚銆�
.addFactoryFailedListener(this::factoryFailListener)
.build();
}
private MongoClient initMongo(String content) {
MongoClusterConfig config = MongoClusterConfig.from(content);
ReadPreferenceEnum readPreferenceEnum = ReadPreferenceEnum.from(config.getReadPreference());
QuotaMonitor monitor = ConfigManager.get(QuotaMonitor.class);
String applicationName = monitor == null ? "unknown" : monitor.getAppName();
MongoClientOptions.Builder optionsBuilder = builder()
.applicationName(applicationName)
.maxConnectionIdleTime(config.getMaxConnectionIdleTime() == 0 ?
MAX_CONNECTION_IDLE_TIME : config.getMaxConnectionIdleTime())
.connectionsPerHost(config.getConnectionsPerHost() == 0 ?
MAX_MONGO_CONNECTION_PER_HOST : config.getConnectionsPerHost())
.threadsAllowedToBlockForConnectionMultiplier(config.getThreadsAllowedToBlockForConnectionMultiplier() == 0 ?
CONNECTION_MULTIPLIER : config.getThreadsAllowedToBlockForConnectionMultiplier())
// 鎻愰珮闃熷垪闀垮害
.maxWaitTime(config.getMaxWaitTime() == 0 ?
TIME_OUT : config.getMaxWaitTime())
.connectTimeout(config.getConnectTimeout() == 0 ?
TIME_OUT : config.getConnectTimeout())
.socketTimeout(config.getSocketTimeout() == 0 ?
TIME_OUT : config.getSocketTimeout())
.serverSelectionTimeout(config.getServerSelectionTimeout() == 0 ?
TIME_OUT : config.getServerSelectionTimeout())
.socketKeepAlive(true)
.addServerListener(new MongoServerListener())
.addCommandListener(new MongoClientListener());
List<MongoCredential> credentials = MongoCredentialPropertyUtils.getCredentials(config.getCredentials());
if (!config.isStandalone()) {
optionsBuilder.readPreference(readPreferenceEnum.getReadPreference());
}
List<String> servers = config.getServers();
MongoClient client;
if (!config.isStandalone()) {
List<ServerAddress> addresses = asList(
servers.stream()
.map(server -> {
if (server.contains(":")) {
String[] hosts = server.split(":");
return new ServerAddress(hosts[0], Integer.parseInt(hosts[1]));
}
return new ServerAddress(server);
}).toArray(ServerAddress[]::new));
if (CollectionUtils.isNotEmpty(credentials)) {
client = new MongoClient(addresses, credentials, optionsBuilder.build());
} else {
client = new MongoClient(addresses, optionsBuilder.build());
}
} else {
if (CollectionUtils.isNotEmpty(credentials)) {
client = new MongoClient(new ServerAddress(servers.get(0)), credentials, optionsBuilder.build());
} else {
client = new MongoClient(new ServerAddress(servers.get(0)), optionsBuilder.build());
}
}
client.listDatabaseNames();
return client;
}
private void cleanup(MongoClient client) throws IOException {
client.close();
}
private void factoryFailListener(String config, Throwable t) {
ConfigUtils.perfConfigFactoryFail(configKey, config, t);
}
/**
* 鏈柟娉曡繑鍥炵殑瀵硅薄涓嶈琚玣ield鎸佹湁,鍚﹀垯涓嶈兘鍔ㄦ�佸埛鏂颁簡銆�
*/
public MongoDatabase getDatabase(String databaseName) {
return mongoNode.get().getDatabase(databaseName);
}
@Deprecated
public DB getDB(String dbname) {
// TODO 鎹㈢敤MongoDB 3.0鐨勬帴鍙�
return mongoNode.get().getDB(dbname);
}
public MongoClient getClient() {
return mongoNode.get();
}
@Override
public void warmup() {
mongoNode.get();
}
}
7.重点关注addCommandListener(new MongoClientListener()); 这个可以监控到每个命令的执行时长等
public class MongoClientListener implements CommandListener {
private static final int COMMAND_CACHE_MAX_SIZE = 20000;
private static final Logger logger = LoggerFactory.getLogger(MongoClientListener.class);
private static final Cache<Integer, MongoCommand> COMMAND_CACHE = SqCacheBuilder.newBuilder()
.maximumSize(COMMAND_CACHE_MAX_SIZE)
.enableAutoCleanup()
.removalListener(notification -> {
if (notification.getCause() == SIZE) {
logger.warn("mongo command cache full, element evicted");
}
}).build();
private static final Set<String> QUERY_COMMANDS = ImmutableSet.of("find", "getMore");
private static final Set<String> UPDATE_COMMANDS = ImmutableSet.of("insert", "update", "delete", "findAndModify");
private static final Set<String> AGGREGATE_COMMANDS = ImmutableSet.of("aggregate", "count", "distinct", "group", "mapReduce");
@Override
public void commandStarted(CommandStartedEvent event) {
COMMAND_CACHE.put(event.getRequestId(), new MongoCommand(event));
}
@Override
public void commandSucceeded(CommandSucceededEvent event) {
this.onCommandFinish(event, true, event.getElapsedTime(MICROSECONDS));
}
@Override
public void commandFailed(CommandFailedEvent event) {
this.onCommandFinish(event, false, event.getElapsedTime(MICROSECONDS));
}
private void onCommandFinish(CommandEvent event, boolean success, long elapseTimeMicro) {
MongoCommand cmd = COMMAND_CACHE.getIfPresent(event.getRequestId());
if (cmd == null) {
logger.warn("cannot found mongo command: {}", event.getRequestId());
return;
}
COMMAND_CACHE.invalidate(event.getRequestId());
mongo(cmd.serverHostInfo, cmd.commandName, cmd.abstractCommand, elapseTimeMicro, success, getProfilerType(cmd.commandName));
cmd.stopWatch.stop(format("mongo.%s.%s%s", cmd.commandName, cmd.serverHostInfo, success ? "" : ".fail"));
}
private static ProfilerType getProfilerType(String commandName) {
if (QUERY_COMMANDS.contains(commandName)) {
return MONGO_QUERY;
}
if (UPDATE_COMMANDS.contains(commandName)) {
return MONGO_UPDATE;
}
if (AGGREGATE_COMMANDS.contains(commandName)) {
return MONGO_AGGREGATE;
}
return MONGO_OTHER;
}
private static String toAbstractString(BsonDocument document) {
return toAbstractDocument(document, 0).toJson();
}
private static BsonDocument toAbstractDocument(BsonDocument originDoc, int level) {
BsonDocument abstractDoc = new BsonDocument();
for (Map.Entry<String, BsonValue> cur : originDoc.entrySet()) {
String key = cur.getKey();
if ("$set".equalsIgnoreCase(key)) {
abstractDoc.put(key, new BsonString("{...}"));
continue;
}
BsonValue value = cur.getValue();
switch (value.getBsonType()) {
case DOCUMENT:
abstractDoc.put(key, toAbstractDocument(cur.getValue().asDocument(), level + 1));
break;
case ARRAY:
if (level == 0) {
abstractDoc.put(key, toAbstractArray(cur.getValue().asArray()));
} else {
abstractDoc.put(key, new BsonArray());
}
break;
case INT32:
case INT64:
abstractDoc.put(key, new BsonString(value.getBsonType().toString()));
break;
default:
if (level == 0) {
abstractDoc.put(key, cur.getValue());
} else {
abstractDoc.put(key, new BsonString(value.getBsonType().toString()));
}
break;
}
}
return abstractDoc;
}
private static BsonArray toAbstractArray(BsonArray originArray) {
BsonArray abstractArray = new BsonArray();
for (BsonValue val : originArray) {
BsonType valueType = val.getBsonType();
switch (valueType) {
case DOCUMENT:
abstractArray.add(toAbstractDocument(val.asDocument(), 1));
break;
case ARRAY:
abstractArray.add(new BsonArray());
break;
default:
abstractArray.add(new BsonString(valueType.toString()));
break;
}
}
return abstractArray;
}
private static class MongoCommand {
private String databaseName;
private String commandName;
private String abstractCommand;
private StopWatch stopWatch;
private MongoHostInfo serverHostInfo;
private MongoCommand(CommandStartedEvent event) {
databaseName = event.getDatabaseName();
commandName = event.getCommandName();
abstractCommand = toAbstractString(event.getCommand());
ServerAddress address = event.getConnectionDescription().getServerAddress();
serverHostInfo = fromMongoAddress(address, databaseName);
stopWatch = PerfUtils.getWatcher();
}
}
}
8.onCommandFinish的mongo方法,实际上调用了ProfilerHelper中的mongo方法打印日志
public final class ProfilerHelper {
private static Logger logger = getLogger(ProfilerHelper.class);
private static final int MAX_SQL_SIZE = 1024;
private ProfilerHelper() {
throw new UnsupportedOperationException();
}
private static String tagOf(String prefix, String type, boolean success) {
return prefix + "." + type + "." + (success ? "success" : "fail");
}
private static void mongoPerf(String type, boolean success, MongoHostInfo host,
String commandType, String query, long cost) {
String tag = tagOf("mongo", type, success);
String targetQuery = query;
if (query.length() > MAX_SQL_SIZE) {
targetQuery = query.substring(0, MAX_SQL_SIZE) + "...";
}
perf(tag, targetQuery, hostAndPort(host), commandType)
.micros(cost)
.logstash();
}
private static String hostAndPort(MongoHostInfo serverHostInfo) {
return serverHostInfo.getHost() + ":" + serverHostInfo.getPort();
}
public static void connectMongo(MongoHostInfo host) {
if (logger.isDebugEnabled()) {
logger.debug("connectMongo:{}, succ:{}", host, true);
}
mongoPerf("connect", true, host, "connect", "connect", 1);
}
public static void disconnectMongo(MongoHostInfo host) {
mongoPerf("disconnect", true, host, "connect", "disconnect", 1);
}
public static void mongo(MongoHostInfo host, String commandName, String command,
long costInMicroSecond, boolean success, ProfilerType type) {
if (logger.isDebugEnabled()) {
logger.debug("log mongo:{}->{}, cost:{}, succ:{}", host, command, costInMicroSecond,
success);
}
mongoPerf("query", success, host, commandName, command, costInMicroSecond);
}
}
9.再来看一下MongoClientOptions.addServerListener(new MongoServerListener())这个方法
MongoServerListener可以监听创建连接和释放连接的动作
public class MongoServerListener implements ServerListener {
private final AtomicLong openConnectionCount = new AtomicLong();
@Override
public void serverOpening(ServerOpeningEvent event) {
openConnectionCount.incrementAndGet();
ServerAddress address = event.getServerId().getAddress();
mark(format("mongo.connect.%s:%s", address.getHost(), address.getPort()));
connectMongo(fromMongoAddress(address, ""));
}
@Override
public void serverClosed(ServerClosedEvent event) {
openConnectionCount.decrementAndGet();
ServerAddress addr = event.getServerId().getAddress();
mark(format("mongo.disconnect.%s:%s", addr.getHost(), addr.getPort()));
disconnectMongo(fromMongoAddress(addr, ""));
}
@Override
public void serverDescriptionChanged(ServerDescriptionChangedEvent serverDescriptionChangedEvent) {
}
}
10.BaseNodeResource等基础类可以参考《redis封装和监控》,这里就不再copy啦。