MySQL —— 主从复制 读写分离


    MySQL — — — Replication**

    主从复制的好处:

    1. * 做数据的热备
    2. * 如果主数据库宕机,可以快速将业务系统切换到从数据库上,可避免数据丢失。
    3. * 业务量越来越大,I/O访问频率过高,单机无法满足,此时做多库的存储,降低磁盘I/O访问的频率,提高单个机器的I/O性能。如果对数据库的读和写都在同一个数据库服务器中操作,业务系统性能会降低。

    主从复制(也称 AB 复制)允许将来自一个MySQL数据库服务器(主服务器)的数据复制到一个或多个MySQL数据库服务器(从服务器)。

    1. 复制是异步的 从站不需要永久连接以接收来自主站的更新。

    image.png

    1. * 主服务器上面的任何修改都会通过自己的 I/O tread(I/O 线程)保存在二进制日志 Binary log 里面。
    2. * 从服务器上面也启动一个 I/O thread,通过配置好的用户名和密码, 连接到主服务器上面请求读取二进制日志,然后把读取到的二进制日志写到本地的一个Realy log(中继日志)里面。
    3. * 从服务器上面同时开启一个 SQL thread 定时检查 Realy log(这个文件也是二进制的),如果发现有更新立即把更新的内容在本机的数据库上面执行一遍。

    前提是作为主服务器角色的数据库服务器必须开启二进制日志。
    每个从服务器都会收到主服务器二进制日志的全部内容的副本。
    从服务器设备负责决定应该执行二进制日志中的哪些语句。
    除非另行指定,否则主从二进制日志中的所有事件都在从站上执行。


    MySQL — — — 读写分离
    **
    一般应用对数据库而言都是“读多写少”,也就说对数据库读取数据的压力比较大,有一个思路就是说采用数据库集群的方案,其中一个是主库,负责写入数据,我们称之为:写库;其它都是从库,负责读取数据,我们称之为:读库。
    那么,对我们的要求是:

    1. 1、读库和写库的数据一致;(这个是很重要的一个问题,处理业务逻辑要放在service层去处理,不要在dao或者mapper层面去处理)
    2. 2、写数据必须写到写库;读数据必须到读库。
    3. 解决读写分离的方案有两种:应用层解决和中间件解决。

    应用层解决读写分离
    在进入Service之前,使用AOP来做出判断,是使用写库还是读库,判断依据可以根据方法名判断,比如说以query、find、get等开头的就走读库,其他的走写库。

    1. /**
    2. * @author
    3. * 定义动态数据源,实现通过集成Spring提供的AbstractRoutingDataSource,只需要实现determineCurrentLookupKey方法即可
    4. * 由于DynamicDataSource是单例的,线程不安全的,所以采用ThreadLocal保证线程安全,由DynamicDataSourceHolder完成。
    5. */
    6. public class DynamicDataSource extends AbstractRoutingDataSource {
    7. @Override
    8. protected Object determineCurrentLookupKey() {
    9. // 使用DynamicDataSourceHolder保证线程安全,并且得到当前线程中的数据源key
    10. return DynamicDataSourceHolder.getDataSourceKey();
    11. }
    12. }

    记录线程的数据源

    1. /**
    2. * @author
    3. * 使用ThreadLocal技术来记录当前线程中的数据源的key
    4. */
    5. public class DynamicDataSourceHolder {
    6. //写库对应的数据源key
    7. private static final String MASTER = "master";
    8. //读库对应的数据源key
    9. private static final String SLAVE = "slave";
    10. //使用ThreadLocal记录当前线程的数据源key
    11. private static final ThreadLocal<String> holder = new ThreadLocal<String>();
    12. /**
    13. * 设置数据源key
    14. * @param key
    15. */
    16. public static void putDataSourceKey(String key) {
    17. holder.set(key);
    18. }
    19. /**
    20. * 获取数据源key
    21. * @return
    22. */
    23. public static String getDataSourceKey() {
    24. return holder.get();
    25. }
    26. /**
    27. * 标记写库
    28. */
    29. public static void markMaster(){
    30. putDataSourceKey(MASTER);
    31. }
    32. /**
    33. * 标记读库
    34. */
    35. public static void markSlave(){
    36. putDataSourceKey(SLAVE);
    37. }
    38. }

    切面

    1. /**
    2. * @author
    3. * 定义数据源的AOP切面,通过该Service的方法名判断是应该走主库还是从库
    4. */
    5. @AspectJ
    6. public class DataSourceAspect {
    7. /**
    8. * 在进入Service方法之前执行
    9. *
    10. * @param point 切面对象
    11. */
    12. public void before(JoinPoint point) {
    13. // 获取到当前执行的方法名
    14. String methodName = point.getSignature().getName();
    15. if (isSlave(methodName)) {
    16. // 标记为主库
    17. DynamicDataSourceHolder.markSlave();
    18. } else {
    19. // 标记为次库
    20. DynamicDataSourceHolder.markMaster();
    21. }
    22. }
    23. /**
    24. * 判断是否为次库
    25. *
    26. * @param methodName
    27. * @return
    28. */
    29. private Boolean isSlave(String methodName) {
    30. // 方法名以query、find、get开头的方法名走从库
    31. return StringUtils.startsWithAny(methodName, "query","find","get");
    32. }
    33. }