最近在使用springboot整合shardingsphere和druid實現mysql數據庫讀寫分離時遇到了一些問題,特此記錄一下。
依賴版本
- Springboot 2.1.6.RElEASE
- shardingsphere 4.1.1
- druid 1.1.23
需要的依賴如下:
<dependency>
<groupId>org.apache.shardingsphere</groupId>
<artifactId>sharding-jdbc-spring-boot-starter</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
yml文件配置
datasource配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: root
druid:
async-init: true
keep-alive: true
filters: stat,wall,logback # 必須配置項,否則sql監控頁面沒有內容
initial-size: 5
max-active: 50
min-idle: 5
max-wait: 6000
validation-query: SELECT 'x'
test-on-borrow: false
test-on-return: false
test-while-idle: true
time-between-eviction-runs-millis: 60000
min-evictable-idle-time-millis: 300000
remove-abandoned: false
log-abandoned: true
filter:
stat:
enabled: true
log-slow-sql: true
web-stat-filter:
enabled: true
url-pattern: /*
exclusions: '*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*'
stat-view-servlet:
enabled: true # 控制是否開啟監控頁面
url-pattern: /druid/*
reset-enable: false
# ip白名單,默認是127.0.0.1,為空時表示所有的ip都可以訪問,如果這里允許所有ip訪問必須配置為空,否則只能127.0.0.1訪問
allow:
# ip黑名單,同理白名單
deny:
login-username: druid # 監控頁面登陸用戶名
login-password: druid # 監控頁面登陸密碼
讀寫分離的sharding配置
spring:
shardingsphere:
enabled: true # 是否啟用sharding,不啟用時使用datasource配置的數據源
datasource:
names: master,slave0 # 節點名稱,多個時使用逗號隔開
master:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.18.121.222:3306/spider?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 以下為druid配置,可以共用datasource中的druid配置,需要覆蓋時再重新配置
filters: stat,wall,logback
initial-size: 2
max-active: 45
min-idle: 6
slave0:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://10.18.121.222:3307/spider?characterEncoding=utf-8&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: 123456
# 以下為druid配置,可以共用datasource中的druid配置,需要覆蓋時再重新配置
filters: stat,wall,logback
initial-size: 3
max-active: 20
min-idle: 8
masterslave:
name: ms
master-data-source-name: master
slave-data-source-names: slave0
props:
sql:
show: true
注意:
sharding數據源中的druid配置項基本可以使用spring.datasource.druid中的配置項,但是在實際使用過程中發現,filters: stat,wall,logback這項配置必須重新給各個數據源配置,不然sql監控頁面沒有任何內容展示。
遇到的問題
通過以上兩個步驟的配置,啟動沒有任何業務類的springboot項目,成功啟動,並且訪問druid監控頁面也正常,在滿懷激動的往正式項目中遷移后,發現項目啟動失敗,出現了下圖所示的異常:
這是什么鬼?以為配置出錯了,趕緊一頓檢查檢查配置,發現沒有任何問題,在多次嘗試無果后將配置還原發現又可以正常啟動從而基本確定是sharding和druid的配置導致了項目啟動失敗。
從sharding官網的FAQ中發現如下解釋:
根據官網的解釋以及從網上查的一些資料來看,解決方案有兩個:
- 去掉druid-spring-boot-starter,直接使用druid-xxx.jar來替代,這就不會出現兩個數據源沖突的問題
- 仍然使用druid-spring-boot-starter,但是在springboot的啟動類上exclude掉DruidDataSourceAutoConfigure這個類,忽略druid連接池的默認數據源配置(
@SpringBootApplication(exclude = {DruidDataSourceAutoConfigure.class})
)
經過一番嘗試,以上兩種方案都可以解決啟動報錯的問題,但是使用上述兩種方案,即使配置了打開druid監控頁面的配置,訪問監控頁面時仍然是404,我的需求是要能監控數據庫的,因此上述兩種方案都不可行。
又要有監控頁面,又要項目正常啟動,一時陷入了僵局,后來在查找資料的過程中,發現可以通過手動創建數據源配置,並且將其指定為默認數據源就可以解決該問題,經過查到的資料以及公司大佬的支持,添加如下所示的配置類:
@Configuration
@EnableConfigurationProperties(JpaProperties.class)
public class DataSourceConfiguration {
private final JpaProperties jpaProperties;
private final Environment environment;
public DataSourceConfiguration(JpaProperties jpaProperties, Environment environment) {
this.jpaProperties = jpaProperties;
this.environment = environment;
}
@Primary
@Bean
public DataSource dataSource() {
String prefix = "spring.shardingsphere.datasource.";
String each = this.getDataSourceNames(prefix).get(0);
try {
return this.getDataSource(prefix, each);
} catch (final ReflectiveOperationException ex) {
throw new ShardingSphereException("Can't find datasource type!", ex);
}
}
@Primary
@Bean
public EntityManagerFactory entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setDatabase(Database.MYSQL);
vendorAdapter.setGenerateDdl(true);
vendorAdapter.setShowSql(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPersistenceUnitName("default");
factory.setPackagesToScan("com.lzm.*");
factory.setDataSource(this.dataSource());
factory.setJpaPropertyMap(this.jpaProperties.getProperties());
factory.afterPropertiesSet();
return factory.getObject();
}
@Bean
@Primary
public EntityManager entityManager(EntityManagerFactory entityManagerFactory) {
return SharedEntityManagerCreator.createSharedEntityManager(entityManagerFactory);
}
@Primary
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
private List<String> getDataSourceNames(final String prefix) {
StandardEnvironment standardEnv = (StandardEnvironment) this.environment;
standardEnv.setIgnoreUnresolvableNestedPlaceholders(true);
return null == standardEnv.getProperty(prefix + "name")
? new InlineExpressionParser(standardEnv.getProperty(prefix + "names")).splitAndEvaluate()
: Collections.singletonList(standardEnv.getProperty(prefix + "name"));
}
@SuppressWarnings("unchecked")
private DataSource getDataSource(final String prefix, final String dataSourceName) throws ReflectiveOperationException {
Map dataSourceProps = PropertyUtil.handle(this.environment, prefix + dataSourceName.trim(), Map.class);
Preconditions.checkState(!dataSourceProps.isEmpty(), "Wrong datasource properties!");
DataSource result = DataSourceUtil.getDataSource(dataSourceProps.get("type").toString(), dataSourceProps);
DataSourcePropertiesSetterHolder.getDataSourcePropertiesSetterByType(dataSourceProps.get("type").toString())
.ifPresent(dataSourcePropertiesSetter -> dataSourcePropertiesSetter.propertiesSet(this.environment, prefix, dataSourceName, result));
return result;
}
}
以上打碼中根據sharding的配置手動創建數據源DataSource以及EntityManagerFactory等Bean,並且設置為默認加載的bean類型(@Primary
),添加以上配置類后,重新啟動項目,項目正常啟動而且druid的監控頁面也可以正常訪問,此問題得到完美解決。具體源碼可以參考碼雲