0%

redis缓存总结

Redis在Springboot项目实现简单的初步缓存

新建了一个demo项目,来实现一下缓存,了解了一下mybatis自带的二级缓存和使用redis作为缓存的差异,感觉很不完善,但是我也是新手入门,慢慢学习成长中……

1.application.yml中配置mysql和mybatis

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
spring:
#Mysql配置
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/demo?serverTimezone=UTC&useUnicode=true
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Redis配置
redis:
host: 192.168.139.188 #ip
port: 6379 #端口号
database: 0 #具体哪一个数据库,0-15

#Mybatis配置
mybatis:
mapper-locations: classpath:cn/zl/springbootredis/mapper/*.xml
type-aliases-package: cn.zl.springbootredis.dao

#开启dao层的日志级别,便于查看sql语句
logging:
level:
cn:
zl:
springbootredis:
dao: debug

2.User实体类以及数据库表的设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package cn.zl.springbootredis.entity;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
* @Description:
* @Author: zhanglang
* @Date: 2020/9/7 12:31
*/
@Data
@Accessors(chain = true)
public class User implements Serializable {
private Long id;
private String name;
private String password;
private Integer age;
private String email;
}

img

3.编写sql语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?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="cn.zl.springbootredis.dao.UserDao">


<!--开启mybatis的二级缓存,也是本地缓存,存在问题就是无法分布式实现缓存,并且每次结束Application都会清空缓存-->
<cache/>

<!--findAll-->
<select id="findAll" resultType="cn.zl.springbootredis.entity.User">
select * from user
</select>
<!--findById-->
<select id="findById" resultType="cn.zl.springbootredis.entity.User">
select * from user where id = #{id}
</select>
<!--delete-->
<delete id="delete" parameterType="Long">
delete from user where id = #{id}
</delete>
<!--save-->
<insert id="save" parameterType="cn.zl.springbootredis.entity.User">
insert into user values(#{id},#{name},#{password},#{age},#{email})
</insert>
<!--update-->
<update id="update" parameterType="cn.zl.springbootredis.entity.User">
update user set name = #{name},password = #{password},age = #{age},email = #{email} where id = #{id}
</update>
</mapper>

4.接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package cn.zl.springbootredis.dao;

import cn.zl.springbootredis.entity.User;

import java.util.List;

/**
* @Description:
* @Author: zhanglang
* @Date: 2020/9/7 14:47
*/
public interface UserDao {
List<User> findAll();

User findById(Long id);

void delete(Long id);

void save(User user);

void update(User user);
}

5.业务层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
package cn.zl.springbootredis.service.UserService;

import cn.zl.springbootredis.entity.User;

import java.util.List;

/**
* @Description:
* @Author: zhanglang
* @Date: 2020/9/7 14:52
*/
public interface UserService {
List<User> findAll();

User findById(Long id);

void delete(Long id);

void save(User user);

void update(User user);

}


package cn.zl.springbootredis.service.UserServiceImpl;

import cn.zl.springbootredis.entity.User;
import cn.zl.springbootredis.dao.UserDao;
import cn.zl.springbootredis.service.UserService.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

/**
* @Description:
* @Author: zhanglang
* @Date: 2020/9/7 14:54
*/
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public List<User> findAll() {
return userDao.findAll();
}

@Override
public User findById(Long id) {
return userDao.findById(id);
}

@Override
public void delete(Long id) {
userDao.delete(id);
}

@Override
public void save(User user) {
userDao.save(user);
}

@Override
public void update(User user) {
userDao.update(user);
}
}

6.测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package cn.zl.springbootredis.redis;

import cn.zl.springbootredis.SpringbootRedisApplication;
import cn.zl.springbootredis.entity.User;
import cn.zl.springbootredis.service.UserService.UserService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
* @Description:
* @Author: zhanglang
* @Date: 2020/9/7 14:56
*/
//启动Springboot应用
@SpringBootTest(classes = SpringbootRedisApplication.class)
@RunWith(SpringRunner.class)
public class TestUserService {
@Autowired
private UserService userService;

@Test
public void testFindAll(){//测试缓存
userService.findAll().forEach(user -> System.out.println("user =" + user));
System.out.println("====================================================");
userService.findAll().forEach(user -> System.out.println("user =" + user));//第二次会从mybatis的二级缓存获取
}

@Test
public void testFindById(){
User byId = userService.findById(55L);
System.out.println("====================================================");
User byId1 = userService.findById(55L);
}

@Test
public void testDelete(){
userService.delete(2L);
System.out.println("====================================================");
}

@Test
public void testSave(){
User user = new User();
user.setId(2L).setName("Hali").setAge(21).setPassword("333").setEmail("wwww@163.com");
userService.save(user);
System.out.println("====================================================");
}

@Test
public void testUpdate(){
User user = new User();
user.setId(1L).setName("Honey").setAge(22).setPassword("334").setEmail("nnn@163.com");
userService.update(user);
System.out.println("====================================================");
}
}

6.在没有使用redis作为缓存类型之前,一般是使用mybatis自带的二级缓存机制

1
2
3
在mapper.xml中开启二级缓存
<!--开启mybatis的二级缓存,也是本地缓存,存在问题就是无法分布式实现缓存,并且每次结束Application都会清空缓存-->
<cache/>

.原理:首次查询会从数据库查询,然后将数据存储在本地缓存中,其实也就是调用了Cache,再次查询的时候就实现缓存击中,但是每次重启项目或者说重启进程,都将会清空本地的缓存,又会重新从数据库查询,再加入本地缓存,这样效率很低

7.引入redis作为缓存类型,添加type属性,指向自己配置的redis缓存类

1
2
<!--开启mybatis的二级缓存,也是本地缓存,存在问题就是无法分布式实现缓存,并且每次结束Application都会清空缓存,指定type-->
<cache type="cn.zl.springbootredis.cache.RedisCache"/>

8.通过自定义RedisCache,实现Cache中的接口,来实现自定义的Redis缓存功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
package cn.zl.springbootredis.cache;

import cn.zl.springbootredis.util.ApplicationContextUtils;
import org.apache.ibatis.cache.Cache;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import sun.awt.SunHints;

import java.util.concurrent.locks.ReadWriteLock;

/**
* @Description:
* @Author: zhanglang
* @Date: 2020/9/7 16:21
*/
public class RedisCache implements Cache {
private final String id;

//必须存在构造方法
public RedisCache(String id){
this.id = id;
}
//返回cache唯一标识
@Override
public String getId() {
return this.id;
}

//在缓存中放入值 redis RedisTemplate StringRedisTemplate
@Override
public void putObject(Object key, Object value) {
System.out.println("key: " + key.toString());
System.out.println("value: " + value);
//通过Application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());

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

//从缓存中获取数据
@Override
public Object getObject(Object key) {
System.out.println("key: " + key.toString());
//通过Application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());

//根据key 从redis的hash类型中获取数据
return getRedisTemplate().opsForHash().get(id.toString(), key.toString());
}

//注意:此方法为mybatis保留方法,默认没有实现,目前暂未实现
@Override
public Object removeObject(Object o) {
System.out.println("根据指定key删除缓存");
return null;
}

//清空redis中的缓存
@Override
public void clear() {
System.out.println("清空缓存");
//通过Application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//清空namespace
getRedisTemplate().delete(id.toString());//清空缓存
}

@Override
public int getSize() {
//通过Application工具类获取redisTemplate
// RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
// redisTemplate.setKeySerializer(new StringRedisSerializer());
// redisTemplate.setHashKeySerializer(new StringRedisSerializer());
//获取hash中key value数量
return getRedisTemplate().opsForHash().size(id.toString()).intValue();
}

//封装redisTemplate
private RedisTemplate getRedisTemplate(){
//通过Application工具类获取redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
}

总结:普通的二级缓存通过namespace来实现,具有很大的局限性,而通过redis来实现缓存,数据都存放在redis中,一次添加缓存能使用很久,但是这是单表情况,多表的时候会出问题,

比如一个Student表,一个Class表,通过一个classId关联,在Student的实体类中有一个Class class对象,当我对class表进行修改的时候,清空缓存只会清空class这个对应id的缓存,而student中因为缓存没有清空重新生成,所以class信息还是以前的class信息。

优化方法:使用cache-ref,将缓存放在一个id里面

1
2
<!--关联多张表的关系缓存处理,这样就把缓存放入到了同一个id里面,清空缓存的时候会全部清空-->
<cache-ref namespace="cn.zl.springbootredis.entity.Student"/>

还有就是设计缓存的优化:

缓存优化策略——-对key的设计简洁化

算法:MD5

特点:a.一切文件字符串等经过md5处理之后,都会生成32位16进制字符串;

b.不同内容文件经过md5进行加密,加密结果一定不一致;

c.相同内容文件多次经过md5生成结果始终一致

直接使用Spring自带的Md5转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cn.zl.springbootredis.util;

import org.springframework.util.DigestUtils;

/**
* @Description:
* @Author: zl
* @Date: 2020/9/8 9:34
*/
public class Md5Utils {
public static void main(String[] args) {

}
public static String getKeyMd5(String key){
//利用Spring框架提供的md5工具类
return DigestUtils.md5DigestAsHex(key.getBytes());
}
}

然后在RedisCache中将key进行修整:
Md5Utils.getKeyMd5(key.toString())

注意:如果使用了哨兵机制,在yml配置文件中,其它配置不变,但是redis的配置需要进行修整,不再是连接具体的redis主机,而是设置监听的配置

1
2
3
4
5
6
7
#Redis配置
redis:
sentinel:
# master是指的在sentinel.conf中书写的监听名称
master: mymaster
# 连接的不再是一个具体的redis主机,写的是多个哨兵节点
nodes: 192.168.139.188:26379
----------本文结束感谢您的阅读----------