分享一个全新的轻量级JDBC读写分离(postgre, gaussdb主备数据库访问)方案(不用Sharding-jdbc)

11 篇文章 0 订阅
2 篇文章 0 订阅

由于很多项目使用云数据库,为了可靠性,一般会采用一主多备的方案,这时JDBC访问数据库,就需要数据源支持读写分离,把写操作落到主库,读操作落到备库上去。提到读写分离大家肯定会想到sharding-sphere, 虽然sharding-jdbc确实也能达到目的,但sharding-jdbc毕竟是为分库分表等复杂场景而设计,杀鸡何须宰牛刀?何况sharding-jdbc要底层解析SQL语句,性能消耗也不小,所以这里我自己做了个轻量级的读写分离数据源,现分享给大家。

1、动态数据源类

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Data
@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {
    private DataSource master;
    private List<DataSource> slaves = new ArrayList<>();
    private AtomicInteger pointer = new AtomicInteger(-1);
    @Override
    protected DataSource determineTargetDataSource() {
        boolean readonly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
        log.debug("isCurrentTransactionReadOnly: {}", readonly);
        if (readonly) {
            if (slaves.size() > 1) {
                int i = pointer.incrementAndGet();
                if (i == Integer.MAX_VALUE) {
                    pointer.set(0);
                    i = 0;
                }
                return slaves.get(i % slaves.size());
            }
            return slaves.get(0);
        }
        return master;

    }

    @Override
    protected Object determineCurrentLookupKey() {
        return null;
    }

}

本类有一个master和一组slaves数据源,master连到主库,能读写,这里只用作写数据,slaves则是一组只读数据源,连接到从库。
这里有一个关键点,就是事务必须清晰的指明,是只读还是读写,这一般在service类中声明,如:

@Service
@Transactional(readOnly = true) //本类的方法缺省情况下是只读事务,将被分配从数据源
public class BatchServiceImpl implements IBatchService {
    @Override //本方法是只读
	public PBatchEntity getBatchInfo(Long id) {
        Optional<PBatchEntity> batchEntity = batchRepository.findById(id);
        return batchEntity.get();
    }

    @Override
	@Transactional(readOnly = false) //本方法是读写事务,只能分配主数据源
    public boolean deleteBatch(Long batchId) {
    }

...
}

2、修复Spring-jdbc的bug

(注:由于我已经报告这个问题给spring,原问题链接
他们已经在如下版本中修复了:
v4.3.29.RELEASE,v5.0.19.RELEASE,v5.2.9.RELEASE,v5.1.18.RELEASE)

spring-jdbc有个bug,就是在选择数据源的时候, boolean readonly = TransactionSynchronizationManager.isCurrentTransactionReadOnly() 读取不到真正的值,所以用创建如下代码

import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.sql.DataSource;

/**
 * 共享数据库连接。只读事务下,不提交事务
 *
 * @author WeiZhou
 *
 */
@SuppressWarnings("serial")
public class DynaDataSourceTransactionManager extends DataSourceTransactionManager {

    public DynaDataSourceTransactionManager() {
        super();
    }

    public DynaDataSourceTransactionManager(DataSource dataSource) {
        super(dataSource);
    }

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        TransactionSynchronizationManager.setCurrentTransactionReadOnly(definition.isReadOnly());
        super.doBegin(transaction, definition);
    }

}

3、配置类

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.transaction.TransactionManagerCustomizers;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DataSourceConfig {
    @Bean
    DataSourceTransactionManager transactionManager(DataSource dataSource,
            ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
        DataSourceTransactionManager transactionManager = new DynaDataSourceTransactionManager(dataSource);
        transactionManagerCustomizers.ifAvailable((customizers) -> customizers.customize(transactionManager));
        return transactionManager;
    }

    @ConfigurationProperties(prefix = "spring.datasources")
    @Bean(name = "dsConfig")
    public Map<String, Map<String, String>> hdfsConfig() {
        return new HashMap<>();
    }
    @Bean
    public DataSource createDataSource(@Qualifier("dsConfig") Map<String, Map<String, String>> confMap) {
        DynamicDataSource dds = new DynamicDataSource();

        for (Map.Entry<String, Map<String, String>> ent : confMap.entrySet()) {
            Map<String, String> confs = ent.getValue();
            DataSource ds = DataSourceBuilder.create()
                    .driverClassName(confs.get("driver-class-name"))
                    .password(confs.get("password"))
                    .url(confs.get("url"))
                    .username(confs.get("username"))
                    .type(HikariDataSource.class).build();
            if ("master".equals(ent.getKey())) {
                dds.setMaster(ds);
            } else {
                dds.getSlaves().add(ds);
            }
        }
        return dds;
    }
}

本类实例化我们自己的数据源,取代Spring的默认数据源,创建DynaDataSourceTransactionManager实例,取代Spring默认的DataSourceTransactionManager。

4、配置

application.properties中配置多数据源:

spring.datasources.master.driver-class-name=org.opengauss.Driver
spring.datasources.master.url=jdbc:opengauss://localhost:25432/postgres
spring.datasources.master.username=gaussdb
spring.datasources.master.password=Enmo@123
spring.datasources.node.url=jdbc:opengauss://localhost:25432/postgres
spring.datasources.node.username=readonly
spring.datasources.node.password=read@123
spring.datasources.node.driver-class-name=org.opengauss.Driver

好了,这么简单就能实现读写分离,你还会为了读写分离使用sharding-jdbc吗?

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值