有這樣一種場景鸣哀,公司的各個鏈路服務要進行壓測鹰溜,為了盡量得出準確的壓測結(jié)果虽填,直接在生產(chǎn)的服務上進行壓測,但是有幾個條件
- 壓測的請求必須要標記識別曹动,數(shù)據(jù)庫斋日,中間件這些數(shù)據(jù)的流轉(zhuǎn)存儲也必須作區(qū)分
- mysql 有影子庫,判斷是壓測的請求就直接操作影子庫墓陈,除了 mysql, 可能還有 mongodb, es, hbase, redis這些數(shù)據(jù)都要落到影子庫恶守,壓測完影子庫刪掉,不對生產(chǎn)庫造成影響
- 重點是壓測請求的識別與數(shù)據(jù)源的動態(tài)切換
- 請求的識別還包括在一個鏈路調(diào)用中贡必,請求標記的識別跟透傳
- 為了減少原有服務壓測時候的改動兔港,可以把這些影子庫配置,動態(tài)切換的邏輯做成一個SDK仔拟,壓測項目引入 SDK依賴就可以了
根據(jù)上面場景衫樊,現(xiàn)在來說明mongodb的多數(shù)據(jù)源的影子庫,生產(chǎn)庫 動態(tài)切換怎么完成利花,mysql 已經(jīng)有很多成熟的方案了橡伞,redis盒揉,mq這些有機會再研究.
1 壓測請求的標記識別
這個應該說是相當簡單了,在壓測的請求加個header 標記, 過濾去里面判斷到 header 標記是否存在兑徘,然后把標記放到 ThreadLocal 里面刚盈,后面的動態(tài)切換需要依賴這個 副本變量
public class RequestTagFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String header = httpServletRequest.getHeader("test");
HeaderThreadLocal.setIsTestRequest(!StringUtils.isEmpty(header));
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
2 mongodb 多數(shù)據(jù)源配置
生產(chǎn)和影子庫的配置文件是分開的
3 多數(shù)據(jù)源的切換邏輯的bean注入,只要加入這2個bean 就可以了
public class DynamicMongoDbFactory extends SimpleMongoDbFactory {
//生產(chǎn)庫的客戶端與數(shù)據(jù)庫
private MongoClient mongoClientPro;
private String databaseNamePro;
//影子庫的客戶端與數(shù)據(jù)庫
private MongoClient mongoClientShodow;
private String databaseNameShodow;
private final boolean mongoInstanceCreated = false;
private final PersistenceExceptionTranslator exceptionTranslator = null;
private @Nullable WriteConcern writeConcern;
public DynamicMongoDbFactory(MongoClient mongoClientPro, MongoClient mongoClientShodow,
String databaseNamePro, String databaseNameShodow) {
super(mongoClientPro, databaseNamePro);
this.mongoClientPro = mongoClientPro;
this.mongoClientShodow = mongoClientShodow;
this.databaseNamePro = databaseNamePro;
this.databaseNameShodow = databaseNameShodow;
}
@Override
public void destroy() throws Exception {
super.destroy();
}
@Override
public MongoDatabase getDb() throws DataAccessException {
//實現(xiàn)數(shù)據(jù)源動態(tài)切換的邏輯挂脑,判斷是否是壓測請求
if (HeaderThreadLocal.isTestRequest()){
return mongoClientShodow.getDatabase(databaseNameShodow);
}
return mongoClientPro.getDatabase(databaseNamePro);
}
@Override
public MongoDatabase getDb(String dbName) throws DataAccessException {
MongoDatabase mongoDatabase = mongoClientPro.getDatabase(dbName);
if (mongoDatabase != null){
return mongoDatabase;
}
return mongoClientPro.getDatabase(dbName);
}
@Override
public PersistenceExceptionTranslator getExceptionTranslator() {
return new MongoExceptionTranslator();
}
@SuppressWarnings("deprecation")
@Override
public DB getLegacyDb() {
return mongoClientPro.getDB(databaseNamePro);
}
}
@Configuration
@EnableAutoConfiguration
public class DynamicMongoRegister implements EnvironmentAware{
private Environment environment;
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
@Bean
public DynamicMongoDbFactory simpleMongoDbFactory(){
String mongoUrlPro = environment.getProperty("spring.data.mongodb.uri");
String mongoUrlShadow = environment.getProperty("spring.data.mongodb.uri.shadow");
//這里獲取2個數(shù)據(jù)源的配置藕漱,生成2個數(shù)據(jù)源的客戶端實例
MongoClient mongoClientPro = new MongoClient(new MongoClientURI(mongoUrlPro, MongoClientOptions.builder()));
MongoClient mongoClientShadow = new MongoClient(new MongoClientURI(mongoUrlShadow, MongoClientOptions.builder()));
return new DynamicMongoDbFactory(mongoClientPro, mongoClientShadow, "produce", "shadow");
}
@Bean
public MongoTemplate mongoTemplate(){
return new MongoTemplate(simpleMongoDbFactory());
}
}