Phase01 Day2_缓存,读写分离

User_Model Day2

依赖中添加

<!--Spring Redis 二级缓存-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> 
<dependency>
    <groupId>redis.clients</groupId>
	<artifactId>jedis</artifactId>
    <version>3.2.0</version>
</dependency>

application.properties中添加

#配置Redis连接
spring.redis.host=CentOS
spring.redis.port=6379
spring.redis.timeout=5s
spring.redis.lettuce.pool.max-active=10
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-wait=5ms
spring.redis.lettuce.pool.min-idle=1

UserModelApplication中添加

//redis缓存
    @Bean
    public RedisTemplate<Object,Object>redisTemplate(RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException
    {
        RedisTemplate<Object,Object> redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }

logback.xml中添加

<logger name="com.baizhi.cache" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT" />
</logger>

二级缓存-在UserDao.xml添加

<cache type="com.baizhi.cache.UserDefineCache">
	<property name="timeout" value="60"/>
</cache>

小窍门:开个工厂的后门拿到springBean工厂内的redisTempl

package com.baizhi;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
 * @Author:Caijq
 * 创建日期:2020/11/6
 * 类的作用:小技巧:拿到springbean工厂内的对象
 */
@Component
public class ApplicationContextHold implements ApplicationContextAware {

    private static ApplicationContext applicationContext;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
    }
    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }
}

com.baizhi.cache

package com.baizhi.cache;

import com.baizhi.ApplicationContextHold;
import org.apache.ibatis.cache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @Author:Caijq
 * 创建日期:2020/11/6
 * 类的作用:Cache
 */
public class UserDefineRedisCache implements Cache {
    private Logger logger = LoggerFactory.getLogger(UserDefineRedisCache.class);

    private String namespace;//存储namespace
    private RedisTemplate redisTemplate= (RedisTemplate)ApplicationContextHold.getBean("redisTemplate");
    private long timeout = 300;
    private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();

    public UserDefineRedisCache(String namespace){
        this.namespace=namespace;
    }
    @Override
    public String getId() {
        return namespace;
    }

    @Override
    public void putObject(Object key, Object value) {
        logger.debug("将查询结果存储到cache.key:"+key+".value"+value);
        ValueOperations opsForValue = redisTemplate.opsForValue();
        opsForValue.set(key,value,timeout, TimeUnit.SECONDS);
    }

    @Override
    public Object getObject(Object key) {
        logger.debug("从缓存中读取结果.key"+key);
        ValueOperations opsForValue = redisTemplate.opsForValue();
        return opsForValue.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        logger.debug("从缓存中删除.key"+key);
        ValueOperations opsForValue = redisTemplate.opsForValue();
        Object value = opsForValue.get(key);
        redisTemplate.delete(key);
        return value;
    }

    @Override
    public void clear() {
         logger.debug("清空缓存区");
         redisTemplate .execute((RedisConnection connection)-> {
             connection.flushAll();
            return null;
         });
    }

    @Override
    public int getSize() {
        return 0;
    }

    @Override
    public ReadWriteLock getReadWriteLock() {
        return readWriteLock;
    }
}

设置读写分离

数据库主从配置:https://blog.csdn.net/weixin_38231448/article/details/104932764 
安装数据库:https://blog.csdn.net/weixin_38231448/article/details/103858887
注意:可能会报错需要:修改相关路径和文件
主:master
修改/etc/my.cnf
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

server-id=1
log-bin=mysql-bin
binlog-do-db=zpark
binlog-do-db=baizhi
binlog-ignore-db=mysql
binlog-ignore-db=test
expire_logs_days=10
auto_increment_increment=2
auto_increment_offset=1

[mysqld_safe]
log-error=/var/log/mariadb/mariadb.log
pid-file=/var/run/mariadb/mariadb.pid

--------------------------------------
mysql> show master status\G
--------------------------------------
从:slave
[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
user=mysql
# Disabling symbolic-links is recommended to prevent assorted security risks
symbolic-links=0

server-id=2
log-bin=mysql-bin
replicate-do-db=zpark
replicate-do-db=baizhi
replicate-ignore-db=mysql
replicate-ignore-db=test
expire_logs_days=10
auto_increment_increment=2
auto_increment_offset=2

[mysqld_safe]
log-error=/var/log/mariadb/mariadb.log
pid-file=/var/run/mariadb/mariadb.pid
--------------------------------------
mysql> stop slave;
mysql> change master to master_host='主机IP',master_user='root',master_password='root',master_log_file='mysql-bin.00000x',master_log_pos=xxxxx;
mysql> start slave;
mysql> show slave status\G

可以重置slave:
reset slave;

配置datasouce

在application.properties中修改配置:
#数据源
#spring.datasource.username=root
#spring.datasource.password=root
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#spring.datasource.url=jdbc:mysql://CentOSM:3306/test?useUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false

spring.datasource.master.username=root
spring.datasource.master.password=root
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.master.jdbcurl=jdbc:mysql://CentOSM:3306/baizhiuseUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false

spring.datasource.slave1.username=root
spring.datasource.slave1.password=root
spring.datasource.slave1.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave1.jdbcurl=jdbc:mysql://CentOSA:3306/baizhiuseUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false

spring.datasource.slave2.username=root
spring.datasource.slave2.password=root
spring.datasource.slave2.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave2.jdbcurl=jdbc:mysql://CentOSB:3306/baizhiuseUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false

spring.datasource.slave3.username=root
spring.datasource.slave3.password=root
spring.datasource.slave3.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave3.jdbcurl=jdbc:mysql://CentOSC:3306/baizhiuseUnicode=true&characterEncoding=UTF8&serverTimezone=UTC&useSSL=false

## MyBatis 配置信息
#mybatis.type-aliases-package=com.baizhi.entities
#mybatis.mapper-locations=classpath*:mappers/*.xml
#mybatis.executor-type=batch
#创建dataSource类:
package com.baizhi.datasource;

import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Author:Caijq
 * 创建日期:2020/11/8
 * 类的作用:该类是自定义数据源,由于必须将系统的数据源给替换掉。
 */
@Configuration
public class UserDefineDatasourceConfig {
    @Bean
    @ConfigurationProperties("spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave1")
    public DataSource slave1DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave2")
    public DataSource slave2DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.slave3")
    public DataSource slave3DataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean
    public DataSource proxyDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                      @Qualifier("slave1DataSource") DataSource slave1DataSource,
                                      @Qualifier("slave2DataSource") DataSource slave2DataSource,
                                      @Qualifier("slave3DataSource") DataSource slave3DataSource){
        DataSourceProxy proxy = new DataSourceProxy();
        proxy.setDefaultTargetDataSource(masterDataSource);//设置默认数据源
        Map<Object,Object> mappedDataSource=new HashMap<>();
        mappedDataSource.put("master",masterDataSource);
        mappedDataSource.put("slave1",slave1DataSource);
        mappedDataSource.put("slave2",slave2DataSource);
        mappedDataSource.put("slave3",slave3DataSource);
        proxy.setTargetDataSources(mappedDataSource); //注册所有数据源
        return proxy;
    }

    /**
     * 当自定义数据源,用户必须覆盖SqlSessionFactory创建
     * @param dataSource
     * @return
     * @throws Exception
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("proxyDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();

        sqlSessionFactoryBean.setDataSource(dataSource);
        sqlSessionFactoryBean.setTypeAliasesPackage("com.baizhi.entities");
        sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mappers/*.xml"));
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();

        return  sqlSessionFactory;
    }

    /**
     * 当自定义数据源,用户必须覆盖SqlSessionTemplate,开启BATCH处理模式
     * @param sqlSessionFactory
     * @return
     */
    @Bean
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
        return new SqlSessionTemplate(sqlSessionFactory, ExecutorType.BATCH);

    }

    /***
     * 当自定义数据源,用户必须注入,否则事务控制不生效
     * @param dataSource
     * @return
     */
    @Bean
    public PlatformTransactionManager platformTransactionManager(@Qualifier("proxyDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

}

创建代理数据源:
package com.baizhi.datasource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author:Caijq
 * 创建日期:2020/11/8
 * 类的作用:该类属于代理数据源,负责负载均衡
 */
public class DataSourceProxy extends AbstractRoutingDataSource {

    private static final Logger logger= LoggerFactory.getLogger(DataSourceProxy.class);

    private  String masterDBKey="master";
    private  List<String> slaveDBKeys= Arrays.asList("slave1","slave2","slave3");

    private static final AtomicInteger round=new AtomicInteger(0);
    /**
     * 需要在该方法中,判断当前用户的操作是读操作还是写操作
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        String dbKey=null;
        OperType operType = OperTypeContextHolder.getOperType();
        if(operType.equals(OperType.WRITE)){
            dbKey=masterDBKey;
        }else{
            //轮询返回  0 1 2 3 4 5 6
            int value = round.getAndIncrement();
            if(value < 0){
                round.set(0);
            }
            Integer index=round.get()%slaveDBKeys.size();

            dbKey=slaveDBKeys.get(index);
        }
        logger.debug("当前的DBkey:"+dbKey);
        return dbKey;
    }
}
创建读写操作的类型,由于后续进行判断:
    /**
 * @Author:Caijq
 * 创建日期:2020/11/8
 * 类的作用:写、读类型
 */
public enum OperType {
    WRITE,READ;
}
创建中间件,记录操作类型:
/**
 * @Author:Caijq
 * 创建日期:2020/11/8
 * 类的作用:该类主要是用于存储,当前用户的操作类型,将当前的操作存储在当前线程的上下文中
 *
 */
public class OperTypeContextHolder  {
    private static final ThreadLocal<OperType> OPER_TYPE_THREAD_LOCAL=new ThreadLocal<>();

    public static void setOperType(OperType operType){
        OPER_TYPE_THREAD_LOCAL.set(operType);
    }
    public static OperType getOperType(){
        return OPER_TYPE_THREAD_LOCAL.get();
    }
    public static void clear(){
        OPER_TYPE_THREAD_LOCAL.remove();
    }
}
创建切面编程:
    package com.baizhi.datasource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;

/**
 * @Author:Caijq
 * 创建日期:2020/11/8
 * 用户自定义切面,负责读取SlaveDB注解,并且在DBTypeContextHolder中设置读写类型
 */
@Aspect
@Order(0) //必须设置0,表示Spring会将UserDefineDataSourceAOP放置在所有切面的前面
@Component
public class ServiceMethodAOP {
    private static final Logger logger= LoggerFactory.getLogger(ServiceMethodAOP.class);

    @Around("execution(* com.baizhi.service..*.*(..))")
    public Object methodInterceptor(ProceedingJoinPoint pjp){
        Object result = null;
        try {
            //获取当前的方法信息
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Method method = methodSignature.getMethod();
            //判断方法上是否存在注解@SlaveDB
            boolean present = method.isAnnotationPresent(SlaveDB.class);

            OperType operType=null;
            if(!present){
                operType=OperType.WRITE;
            }else{
                operType=OperType.READ;
            }
            OperTypeContextHolder.setOperType(operType);
            logger.debug("当前操作:"+operType);
            result = pjp.proceed();
            //清除线程变量
            OperTypeContextHolder.clear();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        return result;
    }
}
创建一个从服务DB接口,用于标记多读操作:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
 /**
 * 标记的业务方法是否是读操作,在service层的查询方法上添加注解@SlaveDB
 */
 @Retention(value = RetentionPolicy.RUNTIME)
 @Target(value = {ElementType.METHOD})
 public @interface SlaveDB {
 }
修改logback.xml:
    <?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" >
        <encoder>
            <pattern>%p %c#%M %d{yyyy-MM-dd HH:mm:ss} %m%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
              <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                 <fileNamePattern>logs/userLoginFile-%d{yyyyMMdd}.log</fileNamePattern>
                 <maxHistory>30</maxHistory>
              </rollingPolicy>
              <encoder>
                 <pattern>%p %c#%M %d{yyyy-MM-dd HH:mm:ss} %m%n</pattern>
                        <charset>UTF-8</charset>
              </encoder>
    </appender>
    <!-- 控制台输出⽇志级别 -->
    <root level="ERROR">
        <appender-ref ref="STDOUT" />
    </root>

    <logger name="org.springframework.jdbc" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>

    <logger name="com.baizhi.dao" level="TRACE" additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>

    <logger name="com.baizhi.controller" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>

    <logger name="com.baizhi.cache" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>

    <logger name="com.baizhi.datasource" level="DEBUG" additivity="false">
        <appender-ref ref="STDOUT" />
    </logger>

</configuration>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值