Al pisar el foso, encontré un BUG de ShardingJdbc separación de lectura y escritura

¡Trabajar juntos para crear y crecer juntos! Este es el día 33 de mi participación en el "Nuggets Daily New Plan·Desafío de actualización de agosto", haga clic para ver los detalles del evento

prefacio

Recientemente, la empresa se está preparando para acceder a ShardingJdbc para la separación de lectura y escritura. El jefe nos pidió que averigüáramos si existe un escenario en el que los datos se leen inmediatamente después de la escritura, porque hay un retraso en la sincronización maestro-esclavo. La biblioteca sucedió. se retrasó y los datos no se leyeron. ¿No podría haber causado un accidente de producción?

Hoy, echemos un vistazo a cómo ShardingJdbc, como un marco maduro, maneja el escenario de leer datos inmediatamente después de escribirlos.

Introducción a la base de datos

Usé dos bibliotecas localmente para experimentos, la biblioteca de escritura (ds_0_master) y la biblioteca de lectura (ds_0_salve).Las dos bibliotecas no están configuradas con maestro y esclavo, pero no afectan la operación experimental.

La biblioteca tiene una mesa de la ciudad. La tabla de ciudades de la biblioteca maestra no tiene datos, mientras que la tabla de ciudades de la biblioteca esclava tiene una pieza de datos. El contenido de los datos es el siguiente:

Discutimos 4 escenarios de negocios:

  1. Escritura y lectura regulares
  2. Llame a otro servicio2 en un servicio para leer
  3. Crear un nuevo hilo en un servicio para llamar a service2
  4. Llame a service2 en un servicio, pero service2 es una transacción recién abierta

Primero vaya directamente a los resultados experimentales:

1. Escritura y lectura regulares

@Service
public class CityService {

    @Autowired
    private CityRepository cityRepository;

    @Autowired
    private CityService2 cityService2;

    @Transactional(rollbackFor = Exception.class)
    public void test(){
        City city=new City();
        city.setName("眉山");
        city.setProvince("四川");
        cityRepository.save(city);

        List<City> all = cityRepository.findAll();
        all.forEach(x->{
            System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
        });
    }
}
复制代码

imprimir resultado:

Análisis experimental: después de insertar la tabla de ciudades, consultamos la tabla de ciudades y el contenido encontrado es el contenido que acabamos de insertar. Significa que la operación de consulta no va a la biblioteca ambulante, sino a la biblioteca principal.

2. Llamar a otro servicio en un servicio

el código se muestra a continuación:

 @Transactional(rollbackFor = Exception.class)
    public void test(){
        City city=new City();
        city.setName("眉山");
        city.setProvince("四川");
        cityRepository.save(city);

        //调用其他service
        cityService2.test();


        List<City> all = cityRepository.findAll();
        all.forEach(x->{
            System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
        });
    }
}
复制代码

Código para servicio2:

public void test(){
    List<City> all = cityRepository.findAll();
    all.forEach(x->{
        System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
    });
}
复制代码

imprimir resultado:

Análisis experimental: cuando se llama a otros servicios en el método de servicio, otros servicios también se verán afectados. service2 es también la biblioteca principal para ir.

3. Abra un nuevo hilo para llamar a service2

el código se muestra a continuación:

@Service
public class CityService {

    @Autowired
    private CityRepository cityRepository;

    @Autowired
    private CityService2 cityService2;

    @Transactional(rollbackFor = Exception.class)
    public void test(){
        City city=new City();
        city.setName("眉山");
        city.setProvince("四川");
        cityRepository.save(city);

        new Thread(()->{cityService2.test();}).start();

        List<City> all = cityRepository.findAll();
        all.forEach(x->{
            System.out.println("cityService:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
        });
    }
}
复制代码
@Service
public class CityService2 {

    @Autowired
    private CityRepository cityRepository;

    public void test(){

        List<City> all = cityRepository.findAll();
        all.forEach(x->{
            System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
        });
    }
}
复制代码

imprimir resultado:

实验分析: 我们新开了线程对 city 表进行查询,此次查询读的是从库。新开的线程会走从库,我猜想是新开的线程它认为是没有写入/修改操作,所以走了从库。

我又改动了 service2,加了一段写入操作。代码如下:

    public void test(){
        City city=new City();
        city.setName("成都");
        city.setProvince("四川");
        cityRepository.save(city);

        List<City> all = cityRepository.findAll();
        all.forEach(x->{
            System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
        });
    }
复制代码

再次执行,结果如下:

和预想的不一样,依旧是走的从库。

4. service2 新开一个事务执行

我们调整 service2 的事务传播行为级别。代码如下:

@Transactional(propagation = Propagation.REQUIRES_NEW)
    public void test(){

        List<City> all = cityRepository.findAll();
        all.forEach(x->{
            System.err.println("cityService2:"+((x.getProvince().equals("四川"))?"主库":"从库")+":"+x);
        });
    }
复制代码

REQUIRES_NEW 的含义是:

强制自己开启一个新的事务,如果一个事务已经存在,那么将这个事务挂起.如 ServiceA.methodA()调用 ServiceB.methodB(),methodB()上的传播级别是 PROPAGATION_REQUIRES_NEW 的话,那么如果 methodA 报错,不影响 methodB 的事务,如果 methodB 报错,那么 methodA 是可以选择是回滚或者提交的,就看你是否将 methodB 报的错误抛出还是 try catch 了.

打印结果:

实验分析: 这个结果确实是没想到,service2 新开了个事务走的是主库,而 service 里面的同一个事务里的写后读,反而走了从库。

实验总结:

场景 service service2
同一个 service 里写完读 主库 主库
service 里写完调用另一个 servcie 进行读操作 主库 主库
service 里写完新开线程调用另一个 servcie 进行读操作 主库 从库
service 里写完新开一个事务调用另一个 servcie 进行读操作 从库 主库

常规的写完读操作和写完在另一个 service 里进行读操作,都能够走到主库,保证了常规业务的正确性,也满足了我们一般的使用场景了。而新开线程进行读操作的情况其实比较少,如果非要使用,我们可以用强制指定主库的方式进行处理。

En el último caso, se llama a otro servicio2 (transacción recién abierta) en el servicio, y las operaciones de escritura y lectura de la misma transacción en el servicio original van a la biblioteca esclava. Si no tiene cuidado, fácilmente provocará negocios reales. errores, y los usuarios deben usarlo con precaución. ¿Crees que esto es un error de ShardingJdbc?

Supongo que te gusta

Origin juejin.im/post/7136828037173084196
Recomendado
Clasificación