MySQL 8.0+WEB项目读写分离

1.集群和数据库主从复制

1.1集群

通过多个物理机服务器共同处理和完成同一个业务功能,能很好的提高网站性能和可靠性。不同的环境下需要采用不同的集群策略,集群主要分为以下几种:

(1)高可用集群:节点间以主备形式实现数据库容灾,在宕机情况下能快速恢复,节点间共享资源。

(2)负载均衡集群:节点间相互独立不共享资源,通过一定的算法和模式将业务操作分到不同的节点,充分利用每个节点的资源,能够提高扩展性,加强了网络数据处理能力。

(3)科学计算集群:相对串行计算而言的,多台计算机执行多个指令达到单台计算机无法达到了计算速度。

1.2主从复制

对上面高可用集群的理解,实现高可用集群可以配置服务器的主从关系,通过了解准备使用Mycat来对数据库进行管理,在此之前需要对数据库进行主从配置。

(1)主从配置:为了将mysql的数据库分不到多个系统中,最简单的实现方式就是复制了。而mysql内建有一种复制方式,即指定一台或多台服务器为主机master,另外一台或多台服务器作为从机slave。当master存在更新的时候,master将更新写入二进制文件,并维护了一个索引文件跟踪日志。当slave连接到主服务器之后,slave会通知master进行同步,master通过索引找日志文件上一次同步的位置,然后将这段时间内的更新数据发送给slave进行同步。

在这里插入图片描述
2)mysql主从复制支持类型:

a. 基于sql的支持:通过将matser日志文件中更新的sql语句复制到slave上,slave执行。

b. 基于行的支持:当sql不精确或者记录丢失时,mysql会将改变的行直接复制到slave上

c. 混合支持:默认使用a方式

(2)主从复制优缺点

优点:高可用,提高容错率,数据分布,负载均衡。

缺点:单向同步,无法解决主机宕机问题

2.主从配置

为了方便演示,采用单主单从的主从配置,单主多从的配置原理一样。双主配置能很好地避免主服务器的宕机情况,配置比较复杂,下次有空演示。

2.1 测试环境

mysql 8.0,为了避免因为版本不一致带来的麻烦,主从数据库用同一版本数据库库

主机ip:192.168.3.234( windows 10)  从机ip:192.168.3.244(centos7)

1)准备条件
(1)关闭防火墙

(2)保证两台主机的mysql远程连接开启(必须)

(3)3306端口(数据库端口号,不固定,按自己的配置去确定)是否公开

(4)在主机和从机上创建datago_innovate数据库

2.2 主机配置

(1)在mysql的安装文件夹中找到mysql.ini文件,在该文件的mysqld标签下添加以下属性(还有其他很多属性可以自行了解):
在这里插入图片描述

  • 参数说明

    server-id=1 #服务器id不固定,整数即可
    log-bin=C:/ProgramData/MySQL/MySQL Server 8.0/Data/mysql-bin #同步日志的文件存放路径
    binlog-do-db=datago_innovate #备份哪些些数据库的二进制日志
    #binlog-ignore-db=… #也可以直接设置哪些数据库不同步

(2)登陆mysql,给从机配置登录名,登陆,密码和权限

```
grant all privileges  on *.* to 'root'@'%'  with grant option;
flush privileges;
```

(3)查看主机状态

show master status;

在这里插入图片描述

查出主机的file和postin在从机配置中需要使用,记住即可。致此,主机配置完成。

2.3 从机配置

(1)在从机mysql安装目录下找到my.cnf(一般在/etc/my.cnf)文件,在该文件的mysqld标签下添加如下(和主机一样,不多做解释,server-id不能和主机重复):
在这里插入图片描述
配置好之后重启数据库

(2)修改(第一次应该叫指定)主机信息

 stop slave;      关闭从机同步连接
 change master to master_host='192.168.3.38',master_user='root',master_password='root',master_port=3306,master_log_file='YR-20200102LXDN-bin.000001',master_log_pos=73134;

参数说明:
master_host 主机ip
master_user 主机数据库名
master_password 主机数据库密码
master_port 主机数据库端口
master_log_file 主机二进制文件(和主机查询状态2.2(3)保持一直)
master_log_pos 和主机查询状态2.2(3)保持一直

将主机地址,主机名,主机密码,二进制文件地址,刚刚截图的主机file和position指定到主机信息中(上面的change语句)

注意:这个步骤如果出现警告,请查看日志文件进行排查,不然后续合同无法成功。修改完成后

start slave;     开启同步连接
show slave status\G;   查看主从机连接信息

查看同步状态会有:
在这里插入图片描述
图中的IO状态表示主从数据库是否连接成功,sql表示两个同步文件和slave从机是够连接成功,参考第一张图的IO和sql

如果两个都是yes即表示同步配置成功。

3.Springboot+Web项目中的配置

3.1 yml文件的配置

# 数据源配置
spring:
    datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driverClassName: com.mysql.cj.jdbc.Driver
        druid:
            # 主库数据源
            master:
                url: jdbc:mysql://127.0.0.1:3306/datago_innovate?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
                username: root
                password: root
            # 从库数据源
            slave:
                # 从数据源开关/默认关闭
                enabled: true
                url: jdbc:mysql://192.168.3.77:3306/datago_innovate?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&nullCatalogMeansCurrent=true
                username: root
                password: root
            # 初始连接数
            initialSize: 5
            # 最小连接池数量
            minIdle: 10
            # 最大连接池数量
            maxActive: 20
            # 配置获取连接等待超时的时间
            maxWait: 60000
            # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
            timeBetweenEvictionRunsMillis: 60000
            # 配置一个连接在池中最小生存的时间,单位是毫秒
            minEvictableIdleTimeMillis: 300000
            # 配置一个连接在池中最大生存的时间,单位是毫秒
            maxEvictableIdleTimeMillis: 900000
            # 配置检测连接是否有效
            validationQuery: SELECT 1 FROM DUAL
            testWhileIdle: true
            testOnBorrow: false
            testOnReturn: false
            webStatFilter: 
                enabled: true
            statViewServlet:
                enabled: true
                # 设置白名单,不填则允许所有访问
                allow:
                url-pattern: /druid/*
                # 控制台管理用户名和密码
                login-username: datago
                login-password: 123456
            filter:
                stat:
                    enabled: true
                    # 慢SQL记录
                    log-slow-sql: true
                    slow-sql-millis: 1000
                    merge-sql: true
                wall:
                    config:
                       multi-statement-allow: true

3.2 其他代码

/**
 * 数据源
 * 
 * @author HBO
 */
public enum DataSourceType
{
    /**
     * 主库
     */
    MASTER,

    /**
     * 从库
     */
    SLAVE
}
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.datago.common.enums.DataSourceType;

/**
 * 自定义多数据源切换注解
 *
 * 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
 *
 * @author HBO
 */
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
    /**
     * 切换数据源名称
     */
    public DataSourceType value() default DataSourceType.MASTER;
}
package com.datago.framework.config;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.sql.DataSource;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.alibaba.druid.spring.boot.autoconfigure.properties.DruidStatProperties;
import com.alibaba.druid.util.Utils;
import com.datago.common.enums.DataSourceType;
import com.datago.common.utils.spring.SpringUtils;
import com.datago.framework.config.properties.DruidProperties;
import com.datago.framework.datasource.DynamicDataSource;

/**
 * druid 配置多数据源
 * 
 * @author HBO
 */
@Configuration
public class DruidConfig
{
    @Bean
    @ConfigurationProperties("spring.datasource.druid.master")
    public DataSource masterDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean
    @ConfigurationProperties("spring.datasource.druid.slave")
    @ConditionalOnProperty(prefix = "spring.datasource.druid.slave", name = "enabled", havingValue = "true")
    public DataSource slaveDataSource(DruidProperties druidProperties)
    {
        DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
        return druidProperties.dataSource(dataSource);
    }

    @Bean(name = "dynamicDataSource")
    @Primary
    public DynamicDataSource dataSource(DataSource masterDataSource)
    {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DataSourceType.MASTER.name(), masterDataSource);
        setDataSource(targetDataSources, DataSourceType.SLAVE.name(), "slaveDataSource");
        return new DynamicDataSource(masterDataSource, targetDataSources);
    }
    
    /**
     * 设置数据源
     * 
     * @param targetDataSources 备选数据源集合
     * @param sourceName 数据源名称
     * @param beanName bean名称
     */
    public void setDataSource(Map<Object, Object> targetDataSources, String sourceName, String beanName)
    {
        try
        {
            DataSource dataSource = SpringUtils.getBean(beanName);
            targetDataSources.put(sourceName, dataSource);
        }
        catch (Exception e)
        {
        }
    }

    /**
     * 去除监控页面底部的广告
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    @Bean
    @ConditionalOnProperty(name = "spring.datasource.druid.statViewServlet.enabled", havingValue = "true")
    public FilterRegistrationBean removeDruidFilterRegistrationBean(DruidStatProperties properties)
    {
        // 获取web监控页面的参数
        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();
        // 提取common.js的配置路径
        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : "/druid/*";
        String commonJsPattern = pattern.replaceAll("\\*", "js/common.js");
        final String filePath = "support/http/resources/js/common.js";
        // 创建filter进行过滤
        Filter filter = new Filter()
        {
            @Override
            public void init(javax.servlet.FilterConfig filterConfig) throws ServletException
            {
            }
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                    throws IOException, ServletException
            {
                chain.doFilter(request, response);
                // 重置缓冲区,响应头不会被重置
                response.resetBuffer();
                // 获取common.js
                String text = Utils.readFromResource(filePath);
                // 正则替换banner, 除去底部的广告信息
                text = text.replaceAll("<a.*?banner\"></a><br/>", "");
                text = text.replaceAll("powered.*?shrek.wang</a>", "");
                response.getWriter().write(text);
            }
            @Override
            public void destroy()
            {
            }
        };
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(filter);
        registrationBean.addUrlPatterns(commonJsPattern);
        return registrationBean;
    }
}

package com.datago.framework.config.properties;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import com.alibaba.druid.pool.DruidDataSource;

/**
 * druid 配置属性
 * 
 * @author HBO
 */
@Configuration
public class DruidProperties
{
    @Value("${spring.datasource.druid.initialSize}")
    private int initialSize;

    @Value("${spring.datasource.druid.minIdle}")
    private int minIdle;

    @Value("${spring.datasource.druid.maxActive}")
    private int maxActive;

    @Value("${spring.datasource.druid.maxWait}")
    private int maxWait;

    @Value("${spring.datasource.druid.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;

    @Value("${spring.datasource.druid.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.maxEvictableIdleTimeMillis}")
    private int maxEvictableIdleTimeMillis;

    @Value("${spring.datasource.druid.validationQuery}")
    private String validationQuery;

    @Value("${spring.datasource.druid.testWhileIdle}")
    private boolean testWhileIdle;

    @Value("${spring.datasource.druid.testOnBorrow}")
    private boolean testOnBorrow;

    @Value("${spring.datasource.druid.testOnReturn}")
    private boolean testOnReturn;

    public DruidDataSource dataSource(DruidDataSource datasource)
    {
        /** 配置初始化大小、最小、最大 */
        datasource.setInitialSize(initialSize);
        datasource.setMaxActive(maxActive);
        datasource.setMinIdle(minIdle);

        /** 配置获取连接等待超时的时间 */
        datasource.setMaxWait(maxWait);

        /** 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 */
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);

        /** 配置一个连接在池中最小、最大生存的时间,单位是毫秒 */
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setMaxEvictableIdleTimeMillis(maxEvictableIdleTimeMillis);

        /**
         * 用来检测连接是否有效的sql,要求是一个查询语句,常用select 'x'。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用。
         */
        datasource.setValidationQuery(validationQuery);
        /** 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。 */
        datasource.setTestWhileIdle(testWhileIdle);
        /** 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
        datasource.setTestOnBorrow(testOnBorrow);
        /** 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。 */
        datasource.setTestOnReturn(testOnReturn);
        return datasource;
    }
}

package com.datago.common.utils.spring;

import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import com.datago.common.utils.StringUtils;

/**
 * spring工具类 方便在非spring管理环境中获取bean
 * 
 * @author HBO
 */
@Component
public final class SpringUtils implements BeanFactoryPostProcessor, ApplicationContextAware 
{
    /** Spring应用上下文环境 */
    private static ConfigurableListableBeanFactory beanFactory;

    private static ApplicationContext applicationContext;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException 
    {
        SpringUtils.beanFactory = beanFactory;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException 
    {
        SpringUtils.applicationContext = applicationContext;
    }

    /**
     * 获取对象
     *
     * @param name
     * @return Object 一个以所给名字注册的bean的实例
     * @throws org.springframework.beans.BeansException
     *
     */
    @SuppressWarnings("unchecked")
    public static <T> T getBean(String name) throws BeansException
    {
        return (T) beanFactory.getBean(name);
    }

    /**
     * 获取类型为requiredType的对象
     *
     * @param clz
     * @return
     * @throws org.springframework.beans.BeansException
     *
     */
    public static <T> T getBean(Class<T> clz) throws BeansException
    {
        T result = (T) beanFactory.getBean(clz);
        return result;
    }

    /**
     * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
     *
     * @param name
     * @return boolean
     */
    public static boolean containsBean(String name)
    {
        return beanFactory.containsBean(name);
    }

    /**
     * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
     *
     * @param name
     * @return boolean
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.isSingleton(name);
    }

    /**
     * @param name
     * @return Class 注册对象的类型
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static Class<?> getType(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getType(name);
    }

    /**
     * 如果给定的bean名字在bean定义中有别名,则返回这些别名
     *
     * @param name
     * @return
     * @throws org.springframework.beans.factory.NoSuchBeanDefinitionException
     *
     */
    public static String[] getAliases(String name) throws NoSuchBeanDefinitionException
    {
        return beanFactory.getAliases(name);
    }

    /**
     * 获取aop代理对象
     * 
     * @param invoker
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T getAopProxy(T invoker)
    {
        return (T) AopContext.currentProxy();
    }

    /**
     * 获取当前的环境配置,无配置返回null
     *
     * @return 当前的环境配置
     */
    public static String[] getActiveProfiles()
    {
        return applicationContext.getEnvironment().getActiveProfiles();
    }

    /**
     * 获取当前的环境配置,当有多个环境配置时,只获取第一个
     *
     * @return 当前的环境配置
     */
    public static String getActiveProfile()
    {
        final String[] activeProfiles = getActiveProfiles();
        return StringUtils.isNotEmpty(activeProfiles) ? activeProfiles[0] : null;
    }
}

3.3应用

 /**
     * 查询栏目管理列表(使用从库查询)
     *
     * @param datagoColumn 栏目管理
     * @return 栏目管理
     */
    @Override
    @DataSource(value = DataSourceType.SLAVE)
    public List<DatagoColumn> selectDatagoColumnList(DatagoColumn datagoColumn) {
        return datagoColumnMapper.selectDatagoColumnList(datagoColumn);
    }

博主很懒,代码有点凌乱,不喜勿喷。欢迎一起学习探讨

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL 8.0及以上版本并没有内置的序列函数,但可以通过自定义函数实现类似的功能。以下是一个简单的自定义函数实现序列的示例: 1. 创建自定义函数 ``` DELIMITER $$ CREATE FUNCTION seq_nextval(seq_name VARCHAR(50)) RETURNS BIGINT BEGIN DECLARE seq_val BIGINT DEFAULT 0; SET seq_val = (SELECT seq_val FROM sequence WHERE seq_name = seq_name FOR UPDATE); IF seq_val IS NULL THEN SET seq_val = 1; INSERT INTO sequence (seq_name, seq_val) VALUES (seq_name, seq_val); ELSE SET seq_val = seq_val + 1; UPDATE sequence SET seq_val = seq_val WHERE seq_name = seq_name; END IF; RETURN seq_val; END$$ DELIMITER ; ``` 上述代码中,我们创建了一个名为 seq_nextval 的自定义函数,它接受一个参数 seq_name,表示序列的名称。函数的作用是获取指定名称的序列的下一个值。 函数首先从名为 sequence 的表中获取指定名称的序列的当前值,如果该序列不存在,则将其初始化为1,并返回1。如果序列存在,则将其值加1,并更新 sequence 表中的值,最后返回更新后的值。 2. 创建序列表 在自定义函数中,我们用到了一个名为 sequence 的表,它用于存储所有序列的当前值。我们需要先创建该表: ``` CREATE TABLE sequence ( seq_name VARCHAR(50) PRIMARY KEY, seq_val BIGINT ); ``` 3. 使用自定义函数获取序列的下一个值 现在我们可以使用 seq_nextval 函数获取指定名称的序列的下一个值了: ``` SELECT seq_nextval('my_seq'); ``` 上述代码中,我们调用 seq_nextval 函数,并传入名称为 my_seq 的序列。函数会返回该序列的下一个值。 需要注意的是,由于自定义函数中用到了事务和行级锁,因此在高并发场景下可能会出现性能问题。如果需要生成大量的序列,建议使用自增主键或其他更高效的方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值