ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

Redis分布式缓存(五)

2022-04-04 13:00:44  阅读:119  来源: 互联网

标签:缓存 spring Redis public key com id 分布式


1 SpringBoot整合Redis

1 导入依赖

创建SpringBoot项目时不要乱选默认开发工具,有一个坑

<!--若创建时已选择,则不用导入-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2 编写配置文件

# 配置redis
# Redis服务器地址
spring.redis.host=192.168.0.108
# Redis服务器连接端口
spring.redis.port=6379
# 使用数据库的索引编号,一个示例有16个数据库 0 到 15
spring.redis.database=0 
# Redis服务器连接密码(默认为空)
spring.redis.password=

3 定制RedisTemplate的模板

com/yu/config/RedisConfig

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
        // 将template 泛型设置为 <String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate();
        // 连接工厂,不必修改
        template.setConnectionFactory(redisConnectionFactory);
        /*
         * 序列化设置
         */
        // key、hash的key 采用 String序列化方式
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        ///////////////////
        // value、hash的value 采用 Jackson 序列化方式
        template.setValueSerializer(RedisSerializer.json());
        template.setHashValueSerializer(RedisSerializer.json());
        template.afterPropertiesSet();

        return template;
    }
}

4 使用测试

只要实体类进行了序列化,我们存什么都不会有乱码的担忧了。

//开发中都是序列化
public class User implements Serializable {
    private String name;
    private int age;
}
@Autowired
@Qualifier("redisTemplate")
//确保使用的是自己定义的RedisTemplate
private RedisTemplate redisTemplate; 

@Test
void contextLoads() {
    User user = new User("小明", 12); //pojo

    redisTemplate.opsForValue().set("key1",user);
    System.out.println(redisTemplate.opsForValue().get("key1"));
}

2 分布式缓存

缓存

缓存:读多写少,多用于查询
本地缓存:存在应用服务器内存中的数据
分布式缓存:存在在当前应用服务器内存之外的数据
集群:将同一种服务的多个节点放在一起,共同对系统提供服务的过程
分布式系统:有多个不同的服务集群,共同对系统提供服务

利用mybatis自身本地缓存结合redis实现分布式缓存

mybatis中的应用级缓存(二级缓存):SQLSessionFactory级别缓存
开启方式:mapper.xml中

mybatis是里面有个便签,可以实现分布式缓存,只要把里面的实现替换成redis的实现,就可以实现分布式缓存。注意,增删改都会清除缓存。

3 基本框架(mybatis二级缓存)

1 依赖pom

<!--引入依赖 spring data redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>
<!--mysql-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>
<!--druid-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.19</version>
</dependency>

2 配置文件application.properties

server.port=8080

#redis 单节点(这个是单节点的配置,即主从复制或者没有主从配置)
spring.redis.host=192.168.0.108
spring.redis.port=6377
spring.redis.database=0

spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/toutiao?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=12345

mybatis.mapper-locations=classpath:com/yu/mapper/*.xml
mybatis.type-aliases-package=com.yu.entity

logging.level.com.yu.dao=debug

3 启动类@MapperScan

@SpringBootApplication
//指定要变成实现类的接口所在的包,然后包下面的所有接口在编译之后都会生成相应的实现类
//添加@MapperScan(“com.yu.dao”)注解以后,
//com.yu.dao包下面的接口类,在编译之后都会生成相应的实现类
@MapperScan("com.yu.dao")
public class DistributedRedisMybatis01Application {...}

4 cache、dao、entity、service、utilc层的编写

///////////////entity层com.yu.entity////////////
@Data
@Accessors(chain = true)
//链式访问,该注解设置chain=true,
//生成setter方法返回this(也就是返回的是对象),代替了默认的返回void。
public class User implements Serializable {
    private String id;
    private String name;
    private Integer age;
    private Date bit;
}
///////////////dao层com.yu.dao////////////
public interface UserDao {
    List<User> findAll();
}
<!--Mapper层resources.com.yu.mapper.UserDaoMapper.xml-->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.yu.dao.UserDao">
    <select id="findAll" resultType="User">
       select id,name,age,bir from t_user;
    </select>
</mapper>
///////////////service层com.yu.service////////////
public interface UserService {
    List<User> findAll();
}

///////////////service层实现类////////////
@Service
@Transactional
//@Transactional注解在代码执行出错的时候能够进行事务的回滚。
public class UserServiceImpl implements UserService{
    @Autowired
    private UserDao userDao;
    @Override
    public List<User> findAll() {
        return userDao.findAll();
    }
}

///////////////测试////////////
import org.junit.Test; //不要导错包

@SpringBootTest(classes = DistributedRedisMybatis01Application.class)
@RunWith(SpringRunner.class)
//@RunWith是Junit4提供的注解,将Spring和Junit链接了起来。
public class TestUserService {
    @Autowired
    private UserService userService;
    @Test
    //测试方法用public修饰
    public void contextLoads() {
        userService.findAll().forEach(u-> System.out.println("u="+u));
    }
}

4 开启mybatis二级缓存, 修改mapper.xml

<mapper namespace="com.yu.dao.UserDao">
    <!-- 开启mybatis二级缓存-->
    <cache/>
</mapper>

测试

@Test
public void contextLoads() {
    userService.findAll().forEach(u-> System.out.println("u="+u));
    System.out.println("====================");
    userService.findAll().forEach(u-> System.out.println("u="+u));
}
输出:1 Preparing: select id,name,age,bir from t_user;
     2 Cache Hit Ratio [com.yu.dao.UserDao]: 0.5 击中缓存

4 自定义RedisCache实现缓存

1 查看Mybatis的cache标签的缓存实现

mybatis底层默认使用的是org.apache.ibatis.cache.impl.PerpetualCache实现。
通过mybatis默认cache源码可知,能使用自定义Cache类(implement Cache)对里面的方法进行实现。

2 自定义RedisCache缓存实现

使用:<cache type="xxx.RedisCache"/>

修改为: <cache type="com.yu.cache.RedisCache"/>

编写com.yu.cache.RedisCache:

public class RedisCache implements Cache {
  
    //id:当前放入缓存的mapper的namespace
    //id==com.yu.dao.UserDao
    private String id;
    //必须存在构造方法
    public RedisCache(String id){
        this.id = id;
    }

    //返回cache唯一标识
    @Override
    public String getId() {
        return this.id;
    }
    //缓存放入值
    @Override
    public void putObject(Object o, Object o1) {

    }
    //获取缓存中的数据
    @Override
    public Object getObject(Object o) {
        return null;
    }
    //...
}

但是还需要获取RedisTemplate工厂,编写获取工厂工具类

//用来获取springboot创建好的工厂
//首先要交给spring管理,@Configuration或:
@Component
//要拿到工厂。要实现ApplicationContextAware接口
public class ApplicationContextUtils implements ApplicationContextAware {

    //将工厂保留下来
    private static ApplicationContext applicationContext;

    //将创建好的工厂以参数形式传递给这个类
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    //提供在工厂中获取对象的方法 //如RedisTemplate  redisTemplate
    public static Object getBean(String beanName){
        return applicationContext.getBean(beanName);
    }

}

编写RedisCache类

public class RedisCache implements Cache {

    //id:当前放入缓存的mapper的namespace
    //id==com.yu.dao.UserDao
    private String id;
    //必须存在构造方法
    public RedisCache(String id){
        this.id = id;
    }

    //返回cache唯一标识
    @Override
    public String getId() {
        return this.id;
    }

    //=========通过application工具类获取redisTemplat========
    //并封装此方法
    private RedisTemplate getRedisTemplate(){
        RedisTemplate  redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
        //放入key时的序列化方式
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }

    //缓存放入值
    @Override
    public void putObject(Object key, Object value) {
        System.out.println("key:"+key.toString());
        System.out.println("value:"+value);

        //使用redishash类型作为缓存存储模型  key   hashkey  value
        getRedisTemplate().opsForHash().put(id.toString(),key.toString(),value);
    }

    //获取缓存中的数据
    @Override
    public Object getObject(Object key) {
        //根据key 从redis的hash类型中获取数据
        return getRedisTemplate().opsForHash().get(id.toString(),key.toString());
    }

    ////注意:这个方法为mybatis保留方法 默认没有实现 后续版本可能会实现
    @Override
    public Object removeObject(Object key) {
        return null;//根据指定key删除缓存
    }

    //清空缓存
    @Override
    public void clear() {
        System.out.println("清空缓存!");
        //清空namespace
        getRedisTemplate().delete(id.toString());//清空缓存
    }

    @Override
    public int getSize() {
        //获取hash中key value数量
        return getRedisTemplate().opsForHash().size(id.toString()).intValue();
    }
}

5 缓存在项目中应用存在的问题

1 如果项目中表的查询之间没有任何的关联,查询使用现在的这种缓存方式是没有任何问题的。

2 现有的缓存方式在多表连接查询过程中一定存在问题!!!

3 在mybatis的缓存中解决关联关系时更新缓存信息的问题。

<chche-ref namespace=xxx />:用来将多个具有关联关系查询缓存放在一起处理

修改:EmpDaoMapper.xml

<mapper namespace="com.yu.dao.EmpDao">
    <!-- 关联关系缓存的处理-->
    <cache-ref namespace="com.yu.dao.UserDao"/>

    <select id="findAll" resultType="Emp">
       select id,name from t_emp;
    </select>
</mapper>

6 缓存优化策略

对放入redis中的key进行优化,key的长度不能太长。

原来长度:1004332409:4292646850:com.yu.dao.EmpDao.findAll:0:2147483647:select id,name from t_emp;:SqlSessionFactoryBean

MD5加密后长度:``

使用MD5加密算法,缩短key长度

1 处理后,会生成32位16进制字符串
2 不同内容文件经过md5进行加密,结果一定不一样

在redis整合mybatis过程中将key进行md5优化处理

利用spring框架提供的md5工具类:

String key = "hello";
string str = DigestUtils.md5DigestAsHex(key.getBytes());

RedisCache类中封装一个优化方法:

//封装一个对key进行md5处理方法
private String getKeyToMD5(String key){
    return DigestUtils.md5DigestAsHex(key.getBytes());
}

使用:

getRedisTemplate().opsForHash().put(id.toString(),getKeyToMD5(key.toString()),value);
getRedisTemplate().opsForHash().get(id.toString(), getKeyToMD5(key.toString()));

缓存穿透与缓存雪崩解决办法

设定缓存失效时间

//缓存放入值
@Override
public void putObject(Object key, Object value) {
    System.out.println("key:" + key.toString());
    System.out.println("value:" + value);

    //使用redishash类型作为缓存存储模型  key   hashkey  value
  getRedisTemplate().opsForHash().put(id.toString(),getKeyToMD5(key.toString()),value);

    if(id.equals("com.yu.dao.UserDAO")){
        //缓存超时  client  用户   client  员工
        getRedisTemplate().expire(id.toString(),1, TimeUnit.HOURS);
    }

    if(id.equals("com.yu.dao.CityDAO")){
        //缓存超时  client  用户   client  员工
        getRedisTemplate().expire(id.toString(),30, TimeUnit.MINUTES);
    }

    //.....指定不同业务模块设置不同缓存超时时间
}

标签:缓存,spring,Redis,public,key,com,id,分布式
来源: https://www.cnblogs.com/52yu/p/16098885.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有