手把手教你如何从源码分析问题——mongo多数据源切换

手把手教你如何从源码分析问题——mongo多数据源切换还有人说 可以提升分析解决问题的能力 今天我们就从一个小的问题解决的过程 讲一下面对一个未知的问题 如何从源码来分析解决

大家好,欢迎来到IT知识分享网。

引言

看源码到底有什么用?有人说看一个成熟的源码,可以学习大牛的编码风格,编程思想;有人说可以了解框架工具的原理,更容易理解使用框架的api;还有人说,可以提升分析解决问题的能力。今天我们就从一个小的问题解决的过程,讲一下面对一个未知的问题,如何从源码来分析解决。

需求背景

最近要进行生产压测,但是这些压测数据如果进入到线上,就会对真实用户数据造成污染。所以我们需要对数据进行隔离。这个解决办法就是弄一个影子库,真实数据进入到生产库,压测数据根据传入的标识进入到影子库中,实现隔离。市面上有很多mysql的成熟解决方案,比如ShardingSphere里有影子库的功能,dynamic-datasource多数据源切换也能实现。具体可以去了解一下。今天主要说的是mongo,我们的项目中需要用到,但是市面上没有什么工具,而且我们尽量不要侵入原有的代码。所以需要在框架上二次开发下。

解决过程

首先,肯定是大家最先想到的方法——百度。

哈哈,所以我们也不例外,百度上确实也有,是不是很符合我们的需求?感觉成功近在眼前了。

附:该篇文章地址 mongodb 多数据源动态切换 – 简书

手把手教你如何从源码分析问题——mongo多数据源切换

mongo多数据源切换

可是理想很美好,现实很骨感。我们的项目使用的mongo api版本有点高,这篇文章所用到的class已经被废弃删除了。

手把手教你如何从源码分析问题——mongo多数据源切换

老的api使用的类在新版本已经删除了

这下完犊子了。只能用其他的方法了。当然原作者写的文章有借鉴之处,原理是一样的,但这个不是本篇文章的重点,所以我们假设百度上没有搜到解决方案时怎么办——在源码中找答案。


分析源码

我们先考虑一下,既然是动态切换,那只能在请求的时候才能根据参数来切换数据源。所以我们从请求开始看。

 @Autowired private MongoTemplate mongoTemplate; @Override public List<ItemEntity> findByIds(List<String> ids) { List<ObjectId> itemIds = ids.stream().map(i -> new ObjectId(i)).collect(Collectors.toList()); Query query = new Query(Criteria.where("_id").in(itemIds)); return mongoTemplate.find(query, ItemEntity.class, ItemEntity.TABLE_NAME); }

我们发现我们要想实现功能,就必须在mongoTemplate的find方法入手。我们看一下find的方法,一路往下跟,我们找到了核心代码。

private <T> List<T> executeFindMultiInternal(CollectionCallback<FindIterable<Document>> collectionCallback, CursorPreparer preparer, DocumentCallback<T> objectCallback, String collectionName) { try { MongoCursor<Document> cursor = null; try { cursor = preparer .initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection) .iterator(); List<T> result = new ArrayList<>(); while (cursor.hasNext()) { Document object = cursor.next(); result.add(objectCallback.doWith(object)); } return result; } finally { if (cursor != null) { cursor.close(); } } } catch (RuntimeException e) { throw potentiallyConvertRuntimeException(e, exceptionTranslator); } }

重点就是这句话:

cursor = preparer.initiateFind(getAndPrepareCollection(doGetDatabase(), collectionName), collectionCallback::doInCollection).iterator();

这里有一个doGetDatabase(),从名称上看很好理解,获取数据源。所以我们继续往下跟,一路跟到最后,我们发现它调用的MongoDatabaseFactory的getMongoDatabase方法。这玩意是个interface啊,好像跟到头了,初步想法就是实现这个interface就行了。

 private static MongoDatabase doGetMongoDatabase(@Nullable String dbName, MongoDatabaseFactory factory, SessionSynchronization sessionSynchronization) { Assert.notNull(factory, "Factory must not be null!"); if (!TransactionSynchronizationManager.isSynchronizationActive()) { return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase(); } ClientSession session = doGetSession(factory, sessionSynchronization); if (session == null) { return StringUtils.hasText(dbName) ? factory.getMongoDatabase(dbName) : factory.getMongoDatabase(); } MongoDatabaseFactory factoryToUse = factory.withSession(session); return StringUtils.hasText(dbName) ? factoryToUse.getMongoDatabase(dbName) : factoryToUse.getMongoDatabase(); }

这里就得凭点经验了(如果是小白,还有另外一个方法,后面文章会讲),熟悉spring的都知道,spring bean利用了大量工厂模式,MongoDatabaseFactory这个是不是在注册mongotemplete时装配进来的呢?带着这个猜想,我们看一下mongotemplete构造函数。

 / * Constructor used for a basic template configuration. * * @param mongoClient must not be {@literal null}. * @param databaseName must not be {@literal null} or empty. * @since 2.1 */ public MongoTemplate(MongoClient mongoClient, String databaseName) { this(new SimpleMongoClientDatabaseFactory(mongoClient, databaseName), (MongoConverter) null); }

诶,看到了一个似曾相识的东西,一路点进去看一下,发现了一个虚类实现了MongoDatabaseFactory。太棒了,这个猜想没问题:

public abstract class MongoDatabaseFactorySupport<C> implements MongoDatabaseFactory { private final C mongoClient; private final String databaseName; private final boolean mongoInstanceCreated; private final PersistenceExceptionTranslator exceptionTranslator; private @Nullable WriteConcern writeConcern; / * Create a new {@link MongoDatabaseFactorySupport} object given {@code mongoClient}, {@code databaseName}, * {@code mongoInstanceCreated} and {@link PersistenceExceptionTranslator}. * * @param mongoClient must not be {@literal null}. * @param databaseName must not be {@literal null} or empty. * @param mongoInstanceCreated {@literal true} if the client instance was created by a subclass of * {@link MongoDatabaseFactorySupport} to close the client on {@link #destroy()}. * @param exceptionTranslator must not be {@literal null}. */ protected MongoDatabaseFactorySupport(C mongoClient, String databaseName, boolean mongoInstanceCreated, PersistenceExceptionTranslator exceptionTranslator) { Assert.notNull(mongoClient, "MongoClient must not be null!"); Assert.hasText(databaseName, "Database name must not be empty!"); Assert.isTrue(databaseName.matches("[^/\\\\.$\"\\s]+"), "Database name must not contain slashes, dots, spaces, quotes, or dollar signs!"); this.mongoClient = mongoClient; this.databaseName = databaseName; this.mongoInstanceCreated = mongoInstanceCreated; this.exceptionTranslator = exceptionTranslator; },太棒了

我们在看一下getMongoDatabase是如何实现的。

 /* * (non-Javadoc) * @see org.springframework.data.mongodb.MongoDbFactory#getMongoDatabase(java.lang.String) */ @Override public MongoDatabase getMongoDatabase(String dbName) throws DataAccessException { Assert.hasText(dbName, "Database name must not be empty!"); MongoDatabase db = doGetMongoDatabase(dbName); if (writeConcern == null) { return db; } return db.withWriteConcern(writeConcern); } / * Get the actual {@link MongoDatabase} from the client. * * @param dbName must not be {@literal null} or empty. * @return */ protected abstract MongoDatabase doGetMongoDatabase(String dbName);

ok,到这里我们发现调用了一个abstract方法,源码的作者想让我们重写这个方法来做我们的逻辑。好,我们看一下子类SimpleMongoClientDatabaseFactory是怎么重写的。它调用了MongoClient的getDatabase方法。

 /* * (non-Javadoc) * @see org.springframework.data.mongodb.core.MongoDbFactoryBase#doGetMongoDatabase(java.lang.String) */ @Override protected MongoDatabase doGetMongoDatabase(String dbName) { return getMongoClient().getDatabase(dbName); }

好,现在基本上完成一大半了,我们试着写一个咱们自己的factory,让mongotemplete创建时装配进去就行了。

代码如下

/ * Factory to create {@link MongoDatabase} instances from a {@link MongoClient} instance. * add shodow MongoDatabases * @author gehongbin */ public class DynamicMongoDbFactory extends SimpleMongoClientDatabaseFactory { / * 影子库的客户端与数据库 */ private MongoClient mongoClientShodow; private String databaseNameShodow; public DynamicMongoDbFactory(MongoClient mongoClientPro, MongoClient mongoClientShodow, String databaseNamePro, String databaseNameShodow) { super(mongoClientPro, databaseNamePro); this.mongoClientShodow = mongoClientShodow; this.databaseNameShodow = databaseNameShodow; } @Override protected MongoDatabase doGetMongoDatabase(String dbName) { // Env是一个自定义的ThreadLocal,用来存储请求中的切换信息 String env = Env.get(HeaderKeyEnum.ENV); //实现数据源动态切换的逻辑,判断是否是压测请求 if (EnvEnum.preTest.name().equals(env)) { return mongoClientShodow.getDatabase(databaseNameShodow); } return super.doGetMongoDatabase(dbName); } }

重写一个mongotemplete bean。

/ * mongo切换 * * @author gehongbin */ @Configuration public class GabrielMongoConfig { @Value("${spring.data.mongodb.shadow.uri}") private String shadowUri; @Value("${spring.data.mongodb.uri}") private String uri; / * 获取mongoTemplate */ @Bean public MongoTemplate getMongoTemplate() { return new MongoTemplate(new DynamicMongoDbFactory(MongoClients.create(uri),MongoClients.create(shadowUri),"gabriel","gabriel_test")); } }

ok,代码完成了,问题解决了。当压测的情况下,切换到自定义的shadow数据源。正常生产环境下,使用默认数据源。


好像还有个问题没讲,就是这个,怎么能遗漏掉呢。

手把手教你如何从源码分析问题——mongo多数据源切换

还记得吗?找这个接口MongoDatabaseFactory的实现类。好,对小白同学的干货到了,开始。

这里我们就需要借助debug

首先我们找到刚才咱们看的代码,下个断点,开始debug:

手把手教你如何从源码分析问题——mongo多数据源切换

然后单步跟踪,进入到factory.getMongoDatabase中去。ok,这已经就是接口的实现类了。可是这个类是abstract的,应该还有个子类继承了它。

手把手教你如何从源码分析问题——mongo多数据源切换

我们接着往下debug,发现了SimpleMongoClientDatabaseFactory类。ok,终于找到了赞

手把手教你如何从源码分析问题——mongo多数据源切换


以上就是所有分析过程。代码其实很简单,关键在于如何掌握分析问题的方法。希望大家遇到一个未知的问题时候能够有一个好的思路去解决。从解决实际问题中看源码,很有成就感,印象也更深刻。时间长了你会发现,看源码并不是枯燥乏味的。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/93559.html

(0)
上一篇 2024-10-26 07:26
下一篇 2024-10-26 09:26

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信