Databases 17 min read

How Robustdb Enables Client‑Side Read/Write Splitting Beyond Atlas

This article explains how a company built Robustdb, a lightweight client‑side read/write splitting solution to replace Atlas, detailing its background, routing core, method‑level control, dynamic data source management, performance gains, and providing code examples for implementation.

Java Backend Technology
Java Backend Technology
Java Backend Technology
How Robustdb Enables Client‑Side Read/Write Splitting Beyond Atlas

Company DBA complained about the difficulty of using Atlas for read‑write splitting, so the team built Robustdb, a lightweight client‑side solution using only about ten classes and ~2k lines of code.

Background

As traffic grows, many companies use vertical sharding and partitioned tables, combined with read‑write splitting to alleviate load. The typical architecture places a VIP layer and a read‑write proxy (Atlas) that routes DML to the master and DQL to slaves based on configured ratios.

Atlas has several drawbacks: it is no longer maintained, lacks mapping between application IP and database IP, only routes at the SQL level, does not refresh closed connections, and is hard to extend.

Robustdb Design

To address these issues and centralize DB account and connection management, Robustdb implements client‑side routing.

1. Routing Core

Each SQL is classified as DML or DQL; DML forces the thread‑local variable to master, DQL to slave. An AspectJ interceptor reads a @DataSourceType annotation on service methods to force all SQL in the method to use the master, ensuring transactional consistency.

Thread‑local variables are stored using Alibaba’s TransmittableThreadLocal to preserve context across thread pools.

Key Classes

@Aspect
@Component
public class DataSourceAspect {
    @Around("execution(* *(..)) && @annotation(dataSourceType)")
    public Object aroundMethod(ProceedingJoinPoint pjd, DataSourceType dataSourceType) throws Throwable {
        DataSourceContextHolder.setMultiSqlDataSourceType(dataSourceType.name());
        Object result = pjd.proceed();
        DataSourceContextHolder.clearMultiSqlDataSourceType();
        return result;
    }
}
public final class BackendConnection extends AbstractConnectionAdapter {
    private AbstractRoutingDataSource abstractRoutingDataSource;
    private final Map<String, Connection> connectionMap = new HashMap<>();

    public BackendConnection(AbstractRoutingDataSource ds) {
        this.abstractRoutingDataSource = ds;
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return getConnectionInternal(sql).prepareStatement(sql);
    }

    private Connection getConnectionInternal(final String sql) throws SQLException {
        if (ExecutionEventUtil.isDML(sql)) {
            DataSourceContextHolder.setSingleSqlDataSourceType(DataSourceType.MASTER);
        } else if (ExecutionEventUtil.isDQL(sql)) {
            DataSourceContextHolder.setSingleSqlDataSourceType(DataSourceType.SLAVE);
        }
        Object key = abstractRoutingDataSource.determineCurrentLookupKey();
        Optional<Connection> cached = fetchCachedConnection(key.toString());
        if (cached.isPresent()) {
            return cached.get();
        }
        Connection conn = abstractRoutingDataSource.getTargetDataSource(key).getConnection();
        conn.setAutoCommit(super.getAutoCommit());
        conn.setTransactionIsolation(super.getTransactionIsolation());
        connectionMap.put(key.toString(), conn);
        return conn;
    }
    // other overridden methods omitted for brevity
}

DynamicDataSource extends AbstractRoutingDataSource and implements a weighted round‑robin algorithm to select a slave.

public class DynamicDataSource extends AbstractRoutingDataSource implements InitializingBean {
    private List<Object> slaveDataSources = new ArrayList<>();
    private AtomicInteger counter = new AtomicInteger(-1);
    private Map<Object, Integer> slaveDataSourcesWeight;

    @Override
    public Object determineCurrentLookupKey() {
        if (DataSourceContextHolder.isSlave()) {
            return getSlaveKey();
        }
        return "master";
    }

    public Object getSlaveKey() {
        if (slaveDataSources.isEmpty()) return null;
        int index = counter.incrementAndGet() % slaveDataSources.size();
        if (counter.get() > 9999) counter.set(-1);
        return slaveDataSources.get(index);
    }
}

Read‑Write Traffic Allocation

All connections are centrally managed; the traffic distribution to read replicas can be configured dynamically (random or sequential) via an internal configuration center (gconfig) or alternatives like Diamond.

When a new configuration arrives, the bean factory is refreshed, rebuilding the datasource map.

public void refreshDataSource(String properties) {
    YamlDynamicDataSource ds = new YamlDynamicDataSource(properties);
    // validation omitted
    DynamicDataSource dynamic = (DynamicDataSource) beanFactory.getBean(dataSourceName);
    dynamic.setResolvedDefaultDataSource(ds.getResolvedDefaultDataSource());
    dynamic.setResolvedDataSources(new HashMap<>());
    ds.getResolvedDataSources().forEach(dynamic::putNewDataSource);
    dynamic.setSlaveDataSourcesWeight(ds.getSlaveDataSourcesWeight());
    dynamic.afterPropertiesSet();
}

Performance

Load testing shows Robustdb outperforms Atlas under the same workload.

References

https://tech.meituan.com/mtddl.html

https://tech.meituan.com/数据库高可用架构的演进与设计.html

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Spring BootTTLread/write splittingdatabase routingAtlasdynamic-datasourceRobustdb
Java Backend Technology
Written by

Java Backend Technology

Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.