mongodb的封装和监控

本文介绍了MongoDB的封装过程,重点在于构造SqMongoDbFactory所需的MongoClientHolder对象,并通过添加CommandListener来监控命令执行时长。同时提到了MongoServerListener用于监听连接的创建和释放,以实现更全面的监控。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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啦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值