读写分离

优质
小牛编辑
140浏览
2023-12-01

在ReplicaSet副本集环境中,可以通过bugu-mongo设置读写分离。

默认情况下,写操作、读操作,全部都是在Primary上进行,如下图,这可能会导致Primary的负载比较高。

为了降低Primary的负载,可以设置成允许从Secondary读取数据,如下图:

慎重考虑

读写分离,看上去很美,但实际有如下问题需要考虑:

  1. 一般情况下,并不建议对ReplicaSet进行读写分离。因为,对于写操作,Secondary和Primary的负载其实是一样的。Secondary只能分担读操作带来的负载。
  2. 写到Primary上的数据,同步到Secondary,是需要一定时间的。因此,从Secondary读取到的数据,很可能不是最新的。

因此,建议的最佳做法是:

  1. 如果你的系统是由于大量的Write操作导致的负载高,配置ReplicaSet读写分离并不能解决问题,建议考虑Sharding分片。
  2. 如果系统是由于大量的Read操作导致的负载高,那么,可以考虑将部分Read操作分流到Secondary,但只能分流那些允许延迟读取的数据。

ReadPreference

读写分离,可以通过BuguDao的setReadPreference()方法进行设置:

protected void setReadPreference(ReadPreference readPreference)

参数是个ReadPreference对象。ReadPreference类本身提供了如下几个静态方法,用于创建对象:

  • primary()——强制从Primary读取;这是默认值。

  • secondary()——强制从Secondary读取;

  • primaryPreferred()——优先从Primary读取;如果Primary不可用,则从Secondary读取;

  • secondaryPreferred()——优先从Secondary读取;如果Secondary不可用,则从Primary读取;

应用举例

以一个Blog系统为例。用户在网站后台发布了一篇博客文章,他自己想要立刻看到文章信息,则需要从Primary读取,如果从Secondary读取,则可能会读取不到。

DAO定义如下:

public class BlogDao extends BuguDao<Blog> {

    public BlogDao() {
        super(Blog.class);
        //不对该DAO设置ReadPreference,默认是primary
    }

}

保存文章,并且立刻读取,均操作在Primary上:

BlogDao dao = ......

Blog blog = new Blog();
blog.setTitle("About iPhone");
blog.setContent("Bla Bla Bla");

dao.save(blog);

//保存后立刻读取,没有问题,因为操作的是Primary
Blog x = dao.findOne("title", "About iPhone");

而对于其他情况下的读取,比如其他用户从网站前台浏览博客文章,则可以分流到Secondary。定义一个DAO如下:

public class SecondaryBlogDao extends BuguDao<Blog> {

    public SecondaryBlogDao() {
        super(Blog.class);
        //设置该DAO优先从Secondary读取
        super.setReadPreference(ReadPreference.secondaryPreferred());
    }

}

在该DAO的构造函数中,设置了ReadPreference值为secondaryPreferred,优先从Secondary读取数据。

SecondaryBlogDao dao = ......

Blog y = dao.findOne("title", "About iPhone");

为什么要针对Blog定义两个不同的DAO呢?

这是因为,在应用中,DAO一般都设置成单例,都是同一个DAO对象,如果随时去改变该对象的ReadPreference属性,则会随时改变该DAO对象操作数据库的行为,这在多线程、高并发的环境中,会导致混乱,产生与预期不一致的结果。