zoukankan      html  css  js  c++  java
  • SSM框架(4):实现主从数据库读写分离

    一、主从读写分离的概念

    1、什么是主从读写分离?

      一般而言,应用APP对数据库都是“读多写少”,意味着数据库的读取压力比较大。为了解决这个问题,有一个方案就是说采用数据库集群,其中一台是主库,专门负责写入数据,我们称之为“写库”;其它都是从库,负责读取查询数据,我们称之为“读库”;

    2、主从读写分离,对数据库的要求,有以下几点:

    • 读库和写库的数据一致;
    • 写数据必须写到写库;
    • 读数据必须到读库

    二、实现方案

    1、在应用层增加逻辑,实现主从读写分离

      在编写业务逻辑时,根据查询、修改的业务类型,主动地设置去访问不同的数据源。

      (1)优点:

    • 多数据源切换方便,由程序自动完成;
    • 不需要引入中间件;
    • 理论上支持任何数据库;

      (2)缺点:

    • 由程序员完成,运维参与不到;
    • 不能做到动态增加数据源;

    2、使用第三方中间件,由中间件来控制读写分离

      使用第三方中间件控制的方式,对应用程序而言,相当于访问一个数据源。

      (1)优点:

    • 源程序不需要做任何改动就可以实现读写分离;
    • 动态添加数据源不需要重启程序;

      (2)缺点:

    • 程序依赖于中间件,会导致切换数据库变得困难;
    • 由中间件做了中转代理,性能有所下降;

    注:以上内容转载自网络
    原文标题:使用java Spring实现读写分离( MySQL实现主从复制)
    原文地址:https://blog.csdn.net/liu976180578/article/details/77684583

    三、项目实战

             推荐阅读:主从读写实现:方法名前缀方式、数据库策略方式、一主多从方式

    实现原理:

      在开始编码前,统一service层方法名的命名规则,比如,查询类型的方法统一前缀为"query、search、find、get",而其他前缀的方法统一认定为是修改操作。每次接收到请求时,在执行service层的方法前,通过切面的方式获取要执行方法的名称,判读方法名称的前缀是否为"query、search、find、get",如果是,则设置数据源为从库;如果不是,则设置数据源为主库。从而实现主从读写分离。  

    步骤一:首先,在项目中实现多数据源访问的功能。

        实现方式,可参考以下资源:

             【原创】SSM框架(2):多数据源支持,动态配置依赖的数据源

    步骤二:定义切面DataSourceAspect,植入到service层方法之中

    package com.newbie.util.dbReadWriteSeparation;
    
    import com.newbie.util.dbMultipleRouting.DynamicDataSourceHolder;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    /**
     *  定义数据源的AOP切面,通过该Service的方法名判断是应该走读库还是写库
     */
    @Component
    @Aspect
    public class DataSourceAspect {
        /**
         * 定义切面植入的增强功能:在service对象的方法执行前,进行切入
         * @param joinpoint : 切面对象
         */
        @Before("execution(* *..service.*.*(..))")
        public void before(JoinPoint joinpoint){
            String methodName = joinpoint.getSignature().getName();
            //根据方法名称的前缀,判断是否是查询方法
            //如果是查询方法,则使用从库;否则,使用主库
            if(isSlaveMethod(methodName)){
                //标记为从库
                DynamicDataSourceHolder.setDataSourceKey("slave1");
            }else {
                //标记为主库
                DynamicDataSourceHolder.setDataSourceKey("master");
            }
        }
    
        /**
         * 根据方法名称的前缀,判断是否是查询方法
         * @param methodName : 方法名称
         * @return true :是查询方法<br/>
         *          false : 不是查询方法
         */
        public boolean isSlaveMethod(String methodName){
            if(methodName == null){
                return false;
            }
            //遍历方式,判断方法的前缀
            List<String> methodPrefix = new ArrayList<String>();
            methodPrefix.add("query");
            methodPrefix.add("search");
            methodPrefix.add("find");
            methodPrefix.add("get");
            Iterator<String> iterator = methodPrefix.iterator();
            while (iterator.hasNext()){
                String prefixName = iterator.next();
                if(methodName.startsWith(prefixName)){
                    return true;
                }
            }
            return false;
        }
    }

    步骤三、编写service层的业务逻辑

    package com.newbie.service.impl;
    
    import com.newbie.dao.UserMapper;
    import com.newbie.domain.User;
    import com.newbie.service.IReadWriteSeparationService;
    import com.newbie.util.dbMultipleRouting.DynamicDataSourceHolder;
    import org.springframework.stereotype.Service;
    import javax.annotation.Resource;
    import java.util.Iterator;
    import java.util.List;
    /**
     * 主从读写分离的实现
     */
    @Service
    public class ReadWriteSeparationService implements IReadWriteSeparationService {
        @Resource
        private UserMapper userMapper;
    
        /**
         * 向主库增加一个用户
         */
        public String insertOneUser() {
            String message = "插入完成";
            message += "<p>当前线程数据源:key = "+ DynamicDataSourceHolder.getDataSourceKey()+"</p>";
            User user = new User();
            String id = String.valueOf(System.currentTimeMillis());
            user.setId(id);
            user.setUsername("我是新插入的用户");
            userMapper.insertSelective(user);
            return message;
        }
        /**
         * 使用非query方法,会被认为是修改类操作,查询主库数据
         * @return
         */
        public String notQueryAllUser() {
            String message = "<p>查询成功<p>";
            message += "<p>当前线程数据源:key = "+ DynamicDataSourceHolder.getDataSourceKey()+"</p>";
            //查询数据库中的所有用户信息
            List<User> users = userMapper.selectByExample(null);
            Iterator<User> iterator = users.iterator();
            while (iterator.hasNext()){
                User user = iterator.next();
                message += "<p>用户ID : "+user.getId()+" , 用户名 : "+user.getUsername()+" , 数据来源:"+user.getRemark()+"</p>";
            }
            System.out.println("================== Service.notQueryAllUser() : 当前线程数据源 = "+DynamicDataSourceHolder.getDataSourceKey());
            System.out.println("================== Service.notQueryAllUser() : 返回信息 = "+message);
            return message;
        }
        /**
         * 使用query方法,查询从库数据
         * @return
         */
        public String queryAllUser() {
            String message = "<p>查询成功<p>";
            message += "<p>当前线程数据源:key = "+ DynamicDataSourceHolder.getDataSourceKey()+"</p>";
            //查询数据库中的所有用户信息
            List<User> users = userMapper.selectByExample(null);
            Iterator<User> iterator = users.iterator();
            while (iterator.hasNext()){
                User user = iterator.next();
                message += "<p>用户ID : "+user.getId()+" , 用户名 : "+user.getUsername()+" , 数据来源:"+user.getRemark()+"</p>";
            }
            System.out.println("================== Service.notQueryAllUser() : 当前线程数据源 = "+DynamicDataSourceHolder.getDataSourceKey());
            System.out.println("================== Service.queryAllUser() : 返回信息 = "+message);
            return message;
        }
    }

    步骤五、编写控制层Controller 和 测试视图

    package com.newbie.controller;
    
    import com.newbie.service.IReadWriteSeparationService;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import javax.annotation.Resource;
    
    /**
     * 测试:主从读写分离的实现
     */
    @Controller
    public class ReadWriteSeparationController {
        @Resource
        IReadWriteSeparationService readWriteSeparationService;
        /**
         * 向主库增加一个用户
         */
        @RequestMapping("/insertOneUser")
        public String insertOneUser(Model model) {
            String message = readWriteSeparationService.insertOneUser();
            model.addAttribute("message", message);
            return "showInfo";
        }
        /**
         * 使用非query方法,查询主库数据
         */
        @RequestMapping("/notQueryAllUser")
        public String notQueryAllUser(Model model) {
            String message = readWriteSeparationService.notQueryAllUser();
            model.addAttribute("message", message);
            return "showInfo";
        }
        /**
         * 使用query方法,查询从库数据
         */
        @RequestMapping("/queryAllUser")
        public String queryAllUser(Model model) {
            String message = readWriteSeparationService.queryAllUser();
            model.addAttribute("message", message);
            return "showInfo";
        }
    
    }
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title></title>
    </head>
    <body>
    <h2>练习三:实现主从读写分离</h2>
    <a href="insertOneUser">插入主库:向主库增加一个用户</a><br/><br/>
    <a href="queryAllUser">查询从库:使用query方法,查询从库数据</a><br/><br/>
    <a href="notQueryAllUser">查询主库(非正常操作):使用非query方法,查询主库数据</a><br/><br/>
    </body>
    </html>

    步骤六、执行程序,观察结果

  • 相关阅读:
    python 爬取豆瓣电影短评并wordcloud生成词云图
    基于javaweb人脸识别注册登录系统
    html/jsp导出pdf格式的几种方法(jsPDF,iText,wkhtmltopdf)
    微信小程序wx.getLocation()获取经纬度及JavaScript SDK调用腾讯地图API获取某一类地址
    微信小程序登录流程及解析用户openid session_key,获取用户信息
    Windows Server 2012 R2服务器部署Tomcat JDK、安装Mysql以及将Java项目部署到CVM
    Tomcat出现端口占用错误
    linux cmake error undefined reference to symbol 'pthread_create@@GLIBC_2.2.5
    CMake Warning This command specifies the relative path as a link directory.
    linux vim查看文件编码格式
  • 原文地址:https://www.cnblogs.com/newbie27/p/10843823.html
Copyright © 2011-2022 走看看