整合redis缓存

整合redis缓存

分布式锁的实现和解析

(12条消息) 分布式锁之Redis实现_kuan_sun的博客-CSDN博客_redis锁的实现

整合步骤:

  1. 下载redis,解压,修改配置文件(redis.windows.conf)
  2. 导入redis启动依赖
  3. 创建redis配置类 (主要是对于kv的序列化和反序列化)
  4. 启动redis-server.exe (redis服务)(同时也可以通过指定配置文件进行启动—集群)
  5. 启动redis-cli.exe (操作客户端)

注意:

  • HashMap不能设置过期时间!!!
  • 使用device:No_001:Name 的方式来存放K
  • 在存放value(对象)时 需要导入FastJson来进行操作
  • redis操作都在redisTemplate中,有很多操作,需要熟悉

引入缓存可能导致的问题(目前能想到的)

  1. 在修改后,需要对缓存进行操作,不然缓存中的数据有误
    1. 在修改时判断是否有缓存 ?
      1. 有:改缓存,利用缓存来修改持久层的(可以慢慢操作,在缓存失效之前操作完)
      2. 没有:修改持久层的,存缓存(有修改,一定马上会用到)
  2. 删除两边都得删
  3. 新增时添加缓存(新增也一定马上会用到)
  4. 查找时:如果缓存有,是否需要更新过期时间?
  5. 分页需要做缓存吗,怎么做?

可以改进的方向:

  1. redis集群
    1. 利用redis中String来做
      1. setnx device:NO_001_Lock 1 ex time

        Untitled

      2. 0则再用,重试请求;1进行业务操作

      3. 设置过期时间,避免设置锁后宕机死锁 时间需合适

        1. 太短:还没操作完就释放了
        2. 太长:已经操作完了 还锁着
      4. 解决:线程守护

        1. 设置一定时长
        2. 例设置10s 8s时判断是否还在执行? 延长 : 不延长
1
2
3
4
5
<!-- redis依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
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
package com.jcDemo.config;

import com.alibaba.fastjson2.support.spring.data.redis.FastJsonRedisSerializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.serializer.GenericToStringSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
*@author:gao_quansui
*@user:ASUS
*@date:2022/9/28- 13:41
*@projectName:jc_demo
*/
@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
FastJsonRedisSerializer<Object> objectFastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 自定义的RedisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置key的序列化方法
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 核心的设置 1.2.36版本自动提供
redisTemplate.setValueSerializer(objectFastJsonRedisSerializer);
// 对hash的序列化操作设置
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(objectFastJsonRedisSerializer);
// 注册到工程类
redisTemplate.setConnectionFactory(factory);
return redisTemplate;
}

@Bean
public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForValue();
}

@Bean
public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForHash();
}

@Bean
public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForList();
}

@Bean
public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForSet();
}

@Bean
public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
return redisTemplate.opsForZSet();
}
}

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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.jcDemo.controller;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.jcDemo.commom.CommonException;
import com.jcDemo.entity.entities.Device;
import com.jcDemo.entity.res.PageResult;
import com.jcDemo.entity.res.Result;
import com.jcDemo.entity.res.ResultCode;
import com.jcDemo.service.device.DeviceService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.text.ParseException;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
*@author:gao_quansui
*@user:ASUS
*@date:2022/9/23- 10:51
*@projectName:jc_demo
*/
@Slf4j
@Api(tags = "设备管理")
@RestController
@RequestMapping("/api/device")
public class DeviceController {

@Autowired
DeviceService deviceService;

@Resource
RedisTemplate redisTemplate;

@ApiOperation("查找所有设备")
@GetMapping("/getDevices")
public Result getDevices(@RequestParam(defaultValue = "1") int pageNum,
@RequestParam(defaultValue = "10") int pageSize) {
PageResult pr = null;
try {
Page page = PageHelper.startPage(pageNum, pageSize);
List<Device> devices = deviceService.getDevices();

pr = new PageResult(page.getTotal(), devices);
log.info("pageNum:{},pageSize:{}", pageNum, pageSize);
} catch (CommonException e) {
e.printStackTrace();
return new Result(ResultCode.EMPTY);
}
return new Result(ResultCode.SUCCESS, pr);
}

//设备编号查询
@ApiOperation("查找设备ByNo")
@GetMapping("/getDeviceByNo/{no}")
public Result getDeviceByNo(@PathVariable("no") String no) throws CommonException {
Device device = null;
try {
if (redisTemplate.hasKey("devices:" + no)) {
log.info("重置时间->devices:{}", no);
redisTemplate.expire("devices:" + no, 300, TimeUnit.SECONDS);
log.info("从redis取出来的devices:{}", no);
device = JSON.parseObject(String.valueOf(redisTemplate.opsForValue().get("devices:" + no)), Device.class);
} else {
log.info("从mysql取出来的{}", no);
//mysql没有抛异常 下面捕获返回空
device = deviceService.getDeviceByNo(no);
//取出来存入缓存
redisTemplate.opsForValue().set("devices:" + no, JSON.toJSONString(device), 300, TimeUnit.SECONDS);
}
return new Result(ResultCode.SUCCESS, device);
} catch (CommonException e) {
e.printStackTrace();
return new Result(ResultCode.EMPTY);
}
}

//查询
@ApiOperation("头部查找")
@GetMapping("/searchDevice")
public Result searchDevice(@RequestBody Device device) {
List<Device> devices;
try {
devices = deviceService.searchDevice(device);
return new Result(ResultCode.SUCCESS, devices);
} catch (CommonException e) {
e.printStackTrace();
return new Result(ResultCode.EMPTY);
}
}

@ApiOperation("更新设备")
@PostMapping("/updateDevice")
public Result updateDevice(@RequestBody Device device) throws ParseException {
if (deviceService.updateDevice(device) == 1) {
//更新成功后判断是否有缓存 有就换
if (redisTemplate.hasKey("devices:" + device.getDeviceNo())) {
Boolean delete = redisTemplate.delete("devices:" + device.getDeviceNo());
if (delete) {
redisTemplate.opsForValue().set("devices:" + device.getDeviceNo(), JSON.toJSONString(device), 300, TimeUnit.SECONDS);
} else {
log.warn("缓存更新失败,请检查!");
}
} else {
redisTemplate.opsForValue().set("devices:" + device.getDeviceNo(), JSON.toJSONString(device), 300, TimeUnit.SECONDS);
}
return new Result(ResultCode.SUCCESS);
} else {
return new Result(ResultCode.ERROR);
}
}

@ApiOperation("删除设备")
@DeleteMapping("/delete/{no}")
public Result deleteDeviceById(@PathVariable("no") String no) {
if (deviceService.deleteDeviceByNo(no) == 1) {
//删除成功后处理redis
if (redisTemplate.delete("devices:" + no)) {
log.warn("缓存删除成功");
} else {
//缓存删除失败 or 从select里面取的数据,没有进redis 直接删除也会打印log
log.warn("缓存删除失败,请检查!");
}
return new Result(ResultCode.SUCCESS);
} else {
return new Result(ResultCode.ERROR);
}
}

@ApiOperation("新增设备")
@PostMapping("/insert")
public Result insertDevice(@RequestBody Device device) throws ParseException {
if (deviceService.insertDevice(device) == 1) {
redisTemplate.opsForValue().set("devices:" + device.getDeviceNo(), JSON.toJSONString(device), 300, TimeUnit.SECONDS);
return new Result(ResultCode.SUCCESS);
} else {
return new Result(ResultCode.ERROR);
}
}
}

shiro结合ehcache实现缓存

Description

为解决每次调用接口都执行shiro授权操作,影响效率,因此引入缓存概念

STEP

  1. 导入依赖
  2. 配置ehcache-shieo.xml文件
  3. ShiroConfig类中添加相关Bean
    1. EhCacheManager
    2. DefaultWebSecurityManager(设置缓存管理器,即a)
    3. UserRealm中启用验证,名字为xml文件中的
  4. 启动类开启缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 开启 cache 缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!-- ehcache 缓存 -->
<!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-ehcache -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.10.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache-core -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache-core</artifactId>
<version>2.6.11</version>
</dependency>
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
<?xml version="1.0" encoding="UTF-8"?>
<ehcache name="es">

<!--
缓存对象存放路径
java.io.tmpdir:默认的临时文件存放路径。
user.home:用户的主目录。
user.dir:用户的当前工作目录,即当前程序所对应的工作路径。
其它通过命令行指定的系统属性,如“java –DdiskStore.path=D:\\abc ……”。
-->
<diskStore path="java.io.tmpdir"/>

<!--
name:缓存名称。
maxElementsOnDisk:硬盘最大缓存个数。0表示不限制
maxEntriesLocalHeap:指定允许在内存中存放元素的最大数量,0表示不限制。
maxBytesLocalDisk:指定当前缓存能够使用的硬盘的最大字节数,其值可以是数字加单位,单位可以是K、M或者G,不区分大小写,
如:30G。当在CacheManager级别指定了该属性后,Cache级别也可以用百分比来表示,
如:60%,表示最多使用CacheManager级别指定硬盘容量的60%。该属性也可以在运行期指定。当指定了该属性后会隐式的使当前Cache的overflowToDisk为true。
maxEntriesInCache:指定缓存中允许存放元素的最大数量。这个属性也可以在运行期动态修改。但是这个属性只对Terracotta分布式缓存有用。
maxBytesLocalHeap:指定当前缓存能够使用的堆内存的最大字节数,其值的设置规则跟maxBytesLocalDisk是一样的。
maxBytesLocalOffHeap:指定当前Cache允许使用的非堆内存的最大字节数。当指定了该属性后,会使当前Cache的overflowToOffHeap的值变为true,
如果我们需要关闭overflowToOffHeap,那么我们需要显示的指定overflowToOffHeap的值为false。
overflowToDisk:boolean类型,默认为false。当内存里面的缓存已经达到预设的上限时是否允许将按驱除策略驱除的元素保存在硬盘上,默认是LRU(最近最少使用)。
当指定为false的时候表示缓存信息不会保存到磁盘上,只会保存在内存中。
该属性现在已经废弃,推荐使用cache元素的子元素persistence来代替,如:<persistence strategy=”localTempSwap”/>。
diskSpoolBufferSizeMB:当往磁盘上写入缓存信息时缓冲区的大小,单位是MB,默认是30。
overflowToOffHeap:boolean类型,默认为false。表示是否允许Cache使用非堆内存进行存储,非堆内存是不受Java GC影响的。该属性只对企业版Ehcache有用。
copyOnRead:当指定该属性为true时,我们在从Cache中读数据时取到的是Cache中对应元素的一个copy副本,而不是对应的一个引用。默认为false。
copyOnWrite:当指定该属性为true时,我们在往Cache中写入数据时用的是原对象的一个copy副本,而不是对应的一个引用。默认为false。
timeToIdleSeconds:单位是秒,表示一个元素所允许闲置的最大时间,也就是说一个元素在不被请求的情况下允许在缓存中待的最大时间。默认是0,表示不限制。
timeToLiveSeconds:单位是秒,表示无论一个元素闲置与否,其允许在Cache中存在的最大时间。默认是0,表示不限制。
eternal:boolean类型,表示是否永恒,默认为false。如果设为true,将忽略timeToIdleSeconds和timeToLiveSeconds,Cache内的元素永远都不会过期,也就不会因为元素的过期而被清除了。
diskExpiryThreadIntervalSeconds :单位是秒,表示多久检查元素是否过期的线程多久运行一次,默认是120秒。
clearOnFlush:boolean类型。表示在调用Cache的flush方法时是否要清空MemoryStore。默认为true。
diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
maxElementsInMemory:缓存最大数目
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
memoryStoreEvictionPolicy:
Ehcache的三种清空策略;
FIFO,first in first out,这个是大家最熟的,先进先出。
LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>

<!-- 授权缓存 -->
<cache name="authorizationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>

<!-- 认证缓存 -->
<cache name="authenticationCache"
maxEntriesLocalHeap="2000"
eternal="false"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
statistics="true">
</cache>

</ehcache>
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
package com.jc_demo.shiro;

import com.jc_demo.entity.entities.User;
import com.jc_demo.interceptor.MyFormAuthenticationFilter;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* @author: gao_quansui
* @user:ASUS
* @date:2022/9/21 - 10:57
* @projectName:jc_demo
*/

@Configuration
public class ShiroConfig {

@Bean
public UserRealm userRealm() {
UserRealm ur= new UserRealm();
// 告诉realm,使用credentialsMatcher加密算法类来验证密文
// ur.setCredentialsMatcher(hashedCredentialsMatcher());
/* 开启支持缓存,需要配置如下几个参数 */
ur.setCachingEnabled(true);
// 启用身份验证缓存,即缓存AuthenticationInfo信息,默认false
ur.setAuthenticationCachingEnabled(true);
// 缓存AuthenticationInfo信息的缓存名称 在 ehcache-shiro.xml 中有对应缓存的配置
ur.setAuthenticationCacheName("authenticationCache");
// 启用授权缓存,即缓存AuthorizationInfo信息,默认false
ur.setAuthorizationCachingEnabled(true);
// 缓存AuthorizationInfo 信息的缓存名称 在 ehcache-shiro.xml 中有对应缓存的配置
ur.setAuthorizationCacheName("authorizationCache");

return ur;
}

@Bean(name = "filterShiroFilterRegistrationBean")
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("SecurityManager") DefaultWebSecurityManager defaultWebSecurityManager) {

// 1.创建过滤工厂
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();

Map<String, Filter> filters = new LinkedHashMap<>();
filters.put("MyFormAuthenticationFilter", new MyFormAuthenticationFilter());
bean.setFilters(filters);
// 2.设置安全管理器
bean.setSecurityManager(defaultWebSecurityManager);

// 3.配置未授权跳转页面
// bean.setLoginUrl("/test");

// 4.设置filter
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/api/user/login", "anon");

// filterMap.put("/api/user/role/**", "authc");

filterMap.put("/api/**", "MyFormAuthenticationFilter");

bean.setFilterChainDefinitionMap(filterMap);

return bean;
}

@Bean(name = "SecurityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

// 将 CookieRememberMeManager 注入到 SecurityManager 中,否则不会生效
// securityManager.setRememberMeManager(rememberMeManager());
// 将 sessionManager 注入到 SecurityManager 中,否则不会生效
// securityManager.setSessionManager(sessionManager());
// 将 EhCacheManager 注入到 SecurityManager 中,否则不会生效
securityManager.setCacheManager(ehCacheManager());
securityManager.setRealm(userRealm);
return securityManager;
}

// 开启shiro注解
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}

// 开启aop注解支持
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
defaultAAP.setProxyTargetClass(true);
return defaultAAP;
}

// shiro 缓存
@Bean
public EhCacheManager ehCacheManager(){
EhCacheManager cacheManager = new EhCacheManager();
cacheManager.setCacheManagerConfigFile("classpath:\\shiro\\ehcache-shiro.xml");
return cacheManager;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.jc_demo;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import springfox.documentation.oas.annotations.EnableOpenApi;

@EnableCaching
// 开启缓存 shiro权限认证
@EnableOpenApi
@SpringBootApplication
@MapperScan("com.jc_demo.mapper")
public class JcDemoApplication {

public static void main(String[] args) {
SpringApplication.run(JcDemoApplication.class, args);
}

}