简介
背景
在传统的 Java Web 项目中,使用数据库进行存储数据,但是有一些致命的弊端,这些弊端主要来自于性能方面。由于数据库持久化数据主要是面向磁盘,而磁盘的读/写比较慢,很少存在高并发。
特点
-
汇编语言,基于内存,单线程
属于NoSQL,每秒十几万次的读/写操作,并提供一定的持久化
-
支持高并发
支持集群、分布式、主从同步,还能支持一定的事务能力
-
数据结构简单
6 种数据类型
作用:
-
缓存
读>写(9:1),内存空间有限
在使用Redis 存储的时候,需要从 3 个方面进行考虑:
业务数据常用吗?命中率如何?如果命中率很低,就没有必要写入缓存。
该业务数据是读操作多,还是写操作多 ,如果写操作多 ,频繁需要写入数据库 ,也没有必要使用缓存。
业务数据大小如何?如果要存储几百兆字节的文件,会给缓存带来很大的压力,有没有必要?
-
高速读/写场合
商品秒杀
如果使用的是数据库, 一个瞬间数据库就需要执行成千上万的 SQL,很容易造成数据库的瓶颈,严重的会导致数据库瘫痪,造成 Java Web 系统服务崩溃。
使用 Redis 去应对, 把这些需要高速读/写的数据 , 缓存到 Redis 中,在满足一定的条件下,触发这些缓存的数据异步写入数据库中。
时效性控制(超时设置)
安装
Windows
常见问题:
-
windows下redis启动闪退:
cmd到redis目录下,按以下方式启动:redis-server.exe redis.windows.conf
redis修改密码:redis 127.0.0.1:6379> config set requirepass dnc.2009
redis查看密码:config get requirepass
若查看密码错误,需授权:auth dnc.2009
-
局域网内不能访问的问题:
1在配置文件redis.windows.conf中注释掉bind 127.0.0.1
2.关闭防火墙,或者允许redis可以通过防火墙,重启redis
3.完成以上步骤,若还不能访问,请通过强制启用配置文件的方式>redis-server.exe redis.windows.conf
Linux
下载 wget http://download.redis.io/releases/redis-3.2.8.tar.gz
解压 tar -xvf redis-3.2.4.tar.gz
安装 make (MALLOC=libc)
准备 允许redis可以通过防火墙
systemctl status firewalld
systemctl stop firewalld
systemctl start firewalld
修改配置
1.注释掉bind 127.0.0.1(指定本机网卡对应的IP地址,即只有本机能访问;0.0.0.0,表示所有主机都可以连接到redis)
2.protected mode yes(此项开启时需配置bind或设置密码)改成no(关闭保护模式外网可以访问)
3.设置上redis的密码requirepass(推荐)requirepass dnc.2009
4.2和3任选一
daemonize:yes 在该模式下,redis会在后台运行
启动 $redis/src/redis-server ../redis.conf --port 6379
$redis/src/redis-cli -p 6379
设为开机启动
1.编写 service 启动文件
vi /etc/init.d/redis,将下面代码写入文件,修改文件中相应的路径和配置
#!/bin/sh
# chkconfig: 2345 10 90
# description: Start and Stop redis
#端口根据自己 redis 需求配置
REDISPORT=6379
#server 路径根据自己 redis 实际路径配置
EXEC=/root/redis-4.0.9/src/redis-server
#cli 路径根据自己 redis 实际路径配置
REDIS_CLI=/root/redis-4.0.9/src/redis-cli
PIDFILE=/var/run/redis_${REDISPORT}.pid
#redis.conf 路径根据自己 redis 实际路径配置
CONF="/root/redis-4.0.9/redis.conf"
#如果redis配置了requirepass,auth 为 redis.conf 中配置的 requirepass
AUTH="dnc.2009"
case "$1" in
start)
if [ -f $PIDFILE ]
then
echo "$PIDFILE exists, process is already running or crashed."
else
echo "Starting Redis server..."
$EXEC $CONF
fi
if [ "$?"="0" ]
then
echo "Redis is running..."
fi
;;
stop)
if [ ! -f $PIDFILE ]
then
echo "$PIDFILE exists, process is not running."
else
PID=$(cat $PIDFILE)
echo "Stopping..."
$REDIS_CLI -p $REDISPORT -a $AUTH SHUTDOWN
sleep 2
while [ -x $PIDFILE ]
do
echo "Waiting for Redis to shutdown..."
sleep 1
done
echo "Redis stopped"
fi
;;
restart|force-reload)
${0} stop
${0} start
;;
*)
echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
exit 1
esac
上面的注释的意思是,redis服务必须在运行级2,3,4,5下被启动或关闭,启动的优先级是90,关闭的优先级是10。
给启动文件设置权限和用户组
chmod +x /etc/init.d/redis
chown root:root /etc/init.d/redis
添加redis服务:
chkconfig --add redis
设为开机启动 :
chkconfig redis on
打开/关闭redis命令:
service redis start/stop
6 种数据类型
常用命令
DEL key
EXISTS key
KEYS pattern
RANDOMKEY
TYPE key
RENAME key newkey
RENAMENX key newkey 不存在才改名
SORT 对list或set排序,仅排序不改变原数据
select db
move key db
FLUSHALL
字符串(String)
-
特点
string 类型是 Redis 最基本的数据类型,一个 key 对应一个 value
string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据。比如jpg图片或者序列化的对象
string 类型的值最大能存储 512MB
-
常用命令
(m)set key value (multiple)
setnx key value (not exist)
(m)get key
strlen key
getset key value
append key value
-
简单运算命令(redis所有操作都是原子操作,采用单线程处理所有业务,因此无需考虑并发影响==》解决分布式id问题,最大范围java中的long范围)
incr key
incrby key increment
decr key
decrby key decrement
incrbyfloat key increment
哈希(Hash)
hash 是一个 String 类型的 field 和 value(只能是字符串,不存在嵌套对象) 的映射表,如同 Java 的 map 一样 , 一个对象里面有许多键值对,它特别适合存储对象。
-
常用命令
hmset key field1 value1[field2 value2...] 覆盖
hset key filed value
hsetnx key field value 不存在才设置值,返回数量
hmget key field1[field2.....] 当key 不存在时,返回 nil
hdel key field1 [field2 ......]
hexists key field
hgetall key
hkeys key
hvals key
hlen key
-
简单运算命令
hincrby key field increment
hincrbyfloat key field increment
-
String存储对象(Json) VS Hash存储对象
String存在对象讲究整体性,以读为主
Hash存储对象讲究分散性,以写为主
列表(List)
-
特点
它可以存储多个字符串,而且它是有序的,双向的。有利于增删,不利于查找。
-
常用命令
lpush key value1 [value2].. 注意顺序
lpushx key value 列表存在则插入,不然失败
lpop key
......................分左右......................
lrem key count value 删除列表key中count个(为0则是全部)值为value的节点
lset key index value
lrange key start stop 获取列表从start到stop的节点值,负数代表倒数第x个,-1即为最后一个
lindex key index 读取下标为index的节点
llen key
linsert key before|after pivot value 将value插入到列表key中的pivot之前/后
...............................................是否进程安全...............................................
blpop key timeout <-->lpop 可以设置等待时间,在等待时间内可以获取到客户端插入的值
集合(Set)
-
特点
随机,无序的==》抽奖
不重复的(重复数据新加不了)==》黑名单
每个元素是String
底层是哈希表结构,仅存储键不存储值(nil),添加、删除、查找的复杂度都是 O(1)
集合操作
-
常用命令
sadd key member1 [member2...]
scard key
smembers key 返回所有元素
sismember key member 包含
smove src des member
srem key member1 [member2...]
spop key [count] 随机弹出集合的count个元素,3.2以上的版本可指定count
srandmember key [count] 随机获取count个元素
sdiff key1 [key2] 差集,rkey2为空,返回key1所有元素
sdiffstore des key1 [key2] 求出差集并存储到指定集合
sinter key1 [key2] 交集,rkey2为空,返回key1所有元素
sintestore des key1 [key2]
sunion key1 [key2] 并集,rkey2为空,返回key1所有元素
sunionstore key1 [key2]
有序集合(sorted set)
-
特点
有序集合和集合类似,只不过它会关联一个double类型的分数,依赖 key 标示它是属于哪个集合,依赖分数进行排序,所以值和分数是必须的。而实际上不仅可以对分数进行排序,在满足一定的条件下,也可以对值进行排序 。如果添加重复数据,value进不去,但分数会覆盖。定时任务执行顺序。
-
常用方法
zadd key score1 value1 [score2 value2...]
zrange key start stop [withscores] 按分值大小从小到大获取全部数据【及相应score(奇数位是值,偶数位数score)】
zrevrange key start stop [withscores] 反向获取
zrangebyscore key min max [withscores] [limit] 按分数(bylex:按值)获取指定范围内数据
zrevrangebyscore key max min [withscores] [limit] (bylex:按值,byrank按索引)
zrem key member [member]
zremrange key start stop
zcard key
zcount key min max 根据分数返回对应的成员列表,包含min和max
zlexcount key min max
zcount key min max
zincrby key increment member
zinterstore desKey numkeys key1 [key2 key3 ....] [aggregate max\min\sum]其中numkeys 是一个整数,表示多少个有序集合,求交集后还可进行元素的max或sum操作,默认sum
z(rev)ank key member 按从小到大展示集合排行(第一为0,第二为1)
zscore key member 获取member的分数
基数(HyperLogLog)
-
特点
用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。基数的作用是评估大约需要准备多少个存储单元去存储数据(并不进行存储),但是基数的算法一般会存在一定的误差(一般是可控的)。
-
常用命令
pfadd key element
pfcount key
pfmerge desKey key1 [key2 key3 ......]
超时与回收
https://www.jianshu.com/p/6a5eb0ddf57b
-
常用命令
EXPIRE key seconds 单位秒
EXPIREAT key timstamp 时间戳
TTL key 以秒为单位,返回给定 key 的剩余生存时间(TTL, time to live)
PERSIST key 移除 key 的过期时间,key 将持久保持
del key
SETEX key seconds value 将键 key 的值设置为 value , 并将键 key 的生存时间设置为 seconds 秒钟。
PSETEX key milliseconds value
-
超时机制
Redis 的 key 超时不会被其自动回收,它只会标识哪些键值对超时了。
原因:如果一个很大的键值对超时,回收需要很长的时间 。 如果采用超时回收,则可能产生停顿。
缺点:浪费空间。
-
回收方式
定时回收是指在确定的某个时间触发一段代码,回收超时的键值对。redis中有个时间事件,它会清理数据库中已经过期的键。
惰性回收则是当一个超时的键,被再次用 get 命令访问时,将触发 Redis 将其从内存中清空。它的优势是可以指定回收超时的键值对;它的缺点是要执行一个莫名其妙的 get 操作,或者在某些时候,我们也难以判断哪些键值对已经超时。
redis采用的是两种方式的结合,轮询数据库随机抽查,重点抽查
-
内存控制
maxmemory占用物理内存的比例,默认为0 即不限制
每次选取待删除数据的个数
-
maxmemory-policy 内存溢出控制策略
检查易失数据(可能会过期的数据集) volatile
检查全库数据 allkeys
放弃数据驱逐(no-enviction),会引发OOM
备份与恢复
分类
-
快照(RDB)
save(单线程,会影响后面命令执行效率)
bgsave(调用fork函数生成子线程,日志中可以看到成功通知)
配置文件内save second changes 自动执行,满足限定时间范围second内key的变化数量达到指定数量changes即进行持久化,只对增删改有影响
-
特殊启动方式;
全量复制(主从复制中)
服务器运行过程中重启:debug reload
关闭服务器时保存:shutdown save
-
AOF只追加文件(记录操作日志)
-
写数据三种策略:
always
everysec(每秒)
no(系统控制)
-
配置文件配置:
appendonly yes|no 是否开启AOF,默认no
appendfsync always|everysec|no
appendfilename
-
AOF重写
-
规则
同一数据若干条命令合并
已超时数据
只保留最后的有效数据
-
方式
手动重写 bgrewriteaof
-
自动重写
自动重写触发条件1:aof_current_size>auto_aof_rewrite_min_size
自动重写触发条件2:(aof_current_size - aof_base_size)/aof_base_size > auto_aof_rewrite-percentage
其中,auto_aof_rewrite_min_size和auto_aof_rewrite-percentage可在配置文件中设置,aof_current_size和aof_base_size可运行info Persitence获取具体信息
-
-
配置文件
定位:
SNAPSHOTTING
APPEND ONLY MODE
命令
SAVE(不能写)或BGSAVE(异步)命令在 redis 安装目录中创建dump.rdb文件
将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可
bgrewriteaof
事务及锁
redis是单线程的,但是不通客户端会争抢运行线程
-
事务命令
开启事务:multi
执行事务:exec
取消事务:discard
-
事务中的错误
语法错误(命令书写错误)--》事务中所有命令都不执行,包括正确的
语法正确但不能执行--》运行正确的指令,跳过错误的,不会回滚,需要在代码中控制
-
锁
在开启事务前watch,监控key的值有没有改变,如果有其他客户端对事务内同一个对象进行操作则返回nil
添加监控锁:watch xxx
取消监控缩:unwatch
-
分布式锁
setnx lock-key value (最后一件商品被多人抢购,不能用watch,value随意)
del lock-key
expire lock-key second
-->set key value [EX seconds] [PX milliseconds] nx 分布式锁改进,通过set进行原子性操作
主从同步
读写分离:master负责写,slave负责读
负载均衡
故障恢复
数据冗余(数据热备份)
高可用(集群)
从:
slaveof 192.168.1.107 6379
masterauth dnc.2009
主:
注释掉bind 127.0.0.1
从cli查看:
info replication
其它
哨兵
对主从结构中的每台服务器进行监控,当master主机宕机时通过投票机制选择新的master并通知。哨兵也是一台redis服务器,只是不提供数据服务;通常配置为单数
-
配置
参考sentinel.conf
-
启动
redis-sentinel sentinel-端口号
集群
-
数据存储设计
存储槽 可扩展性
-
内部通讯设计
各个redis保存其它redis存储槽顺序信息
-
命令
redis-cli --cluster create xxx
redis-cli -c -p 6379
缓存预热
提前将相关的缓存数据放到redis
缓存雪崩
短时间内大量key集中过期
解决:缓存组件设计高可用(如集群,防止服务器故障)
请求限流与服务熔断降级机制
设置缓存过期时间一定的随机分布
缓存击穿
单个高热的key数据过期
解决:设置二级缓存,或者设置热点缓存永不过期
使用互斥锁,在执行过程中,如果缓存过期,那么先获取分布式锁,在执行从数据库中加载数据,如果找到数据就存入缓存,没有就继续该有的动作,在这个过程中能保证只有一个线程操作数据库,避免了对数据库的大量请求。
缓存穿透
大量非正常url请求redis,出现大量未命中(查询一个一定不存在的数据)
解决:缓存空值,需和真正缓存的数据区分开,另外将其过期时间设为较短时间。
使用布隆过滤器(能判断一个 key 一定不存在),在缓存的基础上,构建布隆过滤器数据结构,在布隆过滤器中存储对应的 key,如果存在,则说明 key 对应的值为空。
性能监控
redis-benchmark -c 100 -n 5000 压测,100个连接,5000次请求对应的性能
monitor 调试信息
slowlog 慢日志
SpringBoot2通过 RedisTemplate使用Redis
引入依赖
compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-redis'
compile group: 'org.apache.commons', name: 'commons-pool2'
说明:
redis依赖commons-pool 这个依赖,用作 redis 连接池。
修改配置文件
spring:
redis:
# 数据库索引
database: 0
host: 39.106.99.124
port: 6379
password: dnc.2009
# 连接超时时间(毫秒)
timeout: 5000
lettuce:
# 关闭超时时间
shutdown-timeout: 100
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 8
# 最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 连接池中的最大空闲连接
max-idle: 8
# 连接池中的最小空闲连接
min-idle: 0
说明:
- 操作redis的客户端有jedis跟Lettuce。在springboot1.x系列中,其中使用的是jedis,但是到了springboot2.x其中使用的是Lettuce。 因为我们的版本是springboot2.x系列,所以今天使用的是Lettuce。
关于jedis跟lettuce的区别:
Lettuce 和 Jedis 的定位都是Redis的client,所以他们当然可以直接连接redis server。
Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。
- redis的自动配置可以参照RedisAutoConfiguration.java,它提供两个 Bean 来操作 redis RedisTemplate ,StringRedisTemplate , StringRedisTemplate 是 RedisTemplate 的子类。
-
StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。
RedisTemplate存入数据会将数据先序列化成字节数组然后在存入Redis数据库,这个时候打开Redis查看的时候,你会看到你的数据不是以可读的形式展现的,而是以字节数组显示,这样就会导致一个问题,当需要获取的数据不是以字节数组存在redis当中而是正常的可读的字符串的时候,RedisTemplate就无法获取导数据,这个时候获取到的值就是NULL,这个时候StringRedisTempate就派上了用场;StringRedisTemplate 默认做了 string 的 序列化,这样可以在redis客户端看到这些数据,StringRedisTemplate只能处理String-String的键值对数据。
-
(1)你的Redis数据库里面本来存的是字符串数据或者是你要存取的数据就是字符串类型数据的时候,那么你就使用StringRedisTemplate即可;
(2)但是如果你的数据是复杂的对象类型,而取出的时候又不想做任何数据转换,直接从Redis里面取出一个对象,那么使用RedisTemplate是更好的选择;
(3)RedisTemplate中存取数据都是字节数组。当Redis职工存入的数据是可读形式而非字节数组时,使用RedisTemplate取值的时候会无法获取导出数据,获得的值为null。可以使用StringRedisTemplate试试;
RedisTemplate默认是使用的JdkSerializationRedisSerializer序列化,序列化后的值包含了对象信息,版本号,类信息等,是一串字符串,所以无法进行数值自增操作。而Redis自身的机制是 如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作。所以当key不存在时,调用increment方法后,通过ValueOperations.get(key)或BoundValueOperations.get()获取value时会由于无法序列化而报错。而且JdkSerializationRedisSerializer处理对象时必须实现Serializable接口。
编写配置文件
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
/**
* @description 缓存key前缀
*/
private static final String KEY_PREFIX = "";
/**
* @description 默认超时时间,单位:小时
*/
private static final long DEFAULT_OVERTIME = -1L;
/*@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
return RedisCacheManager.create(connectionFactory);
}*/
@Bean
@Primary
public CacheManager cacheManager(RedisConnectionFactory factory){
//缓存配置对象
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
//设置缓存的默认超时时间
.entryTtl(Duration.ofHours(DEFAULT_OVERTIME))
//如果是空值,不缓存
.disableCachingNullValues()
//设置key序列化器
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
//设置value序列化器
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer((new GenericJackson2JsonRedisSerializer())));
//可以抽取的公共配置
Map map = new HashMap<>(RedisEnum.values().length);
for(RedisEnum redisEnum:RedisEnum.values()){
map.put(redisEnum.getDesc(), redisCacheConfiguration(cacheConfig,redisEnum.getValue()));
}
return RedisCacheManager
.builder(factory)
.transactionAware()
.cacheDefaults(cacheConfig)
.withInitialCacheConfigurations(map)
.build();
}
public RedisCacheConfiguration redisCacheConfiguration(RedisCacheConfiguration configuration, Duration duration){
configuration = configuration.entryTtl(duration);
return configuration;
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setEnableTransactionSupport(true);
// 配置redis 的 jackson 序列化方案
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
/*Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
//解决查询缓存转换异常的问题
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);*/
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key序列化
redisTemplate.setKeySerializer(stringRedisSerializer);
// value序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 缓存生成key的策略
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
//StringJoiner是 java8对String字符串拼接提供的工具类, 构造器第一个参数表示拼接以冒号分隔,参数二是前缀,参数三是后缀
StringJoiner joiner = new StringJoiner(":",KEY_PREFIX,"");
joiner.add(target.getClass().getSimpleName());
joiner.add(method.getName());
ObjectMapper objectMapper = new ObjectMapper();
for (Object param : params) {
try {
joiner.add(objectMapper.writeValueAsString(param));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
return joiner.toString();
};
}
}
redis工具类
@Component
public class RedisUtil {
@Autowired
RedisTemplate redisTemplate;
/**
* 默认超时单位:分钟
*/
public void setString(String key, Object value, int expireTime) {
ValueOperations ops = redisTemplate.opsForValue();
if (expireTime != 0) {
ops.set(key, value, expireTime, TimeUnit.MINUTES);
} else {
ops.set(key, value);
}
}
public void setString(String key, Object value, int expireTime, TimeUnit timeUnit) {
ValueOperations ops = redisTemplate.opsForValue();
if (expireTime != 0) {
ops.set(key, value, expireTime, timeUnit);
} else {
ops.set(key, value);
}
}
public Object getString(String key) {
ValueOperations ops = this.redisTemplate.opsForValue();
return ops.get(key);
}
/**
* 删除多个key
*
* @param keys
*/
public void deleteKey(String... keys) {
Set kSet = Stream.of(keys).collect(Collectors.toSet());
redisTemplate.delete(kSet);
}
/**
* 删除Key的集合
*
* @param keys
*/
public void deleteKey(Collection keys) {
Set kSet = keys.stream().collect(Collectors.toSet());
redisTemplate.delete(kSet);
}
/**
* 指定key在指定的日期过期
*
* @param key
* @param date
*/
public void expireKeyAt(String key, Date date) {
redisTemplate.expireAt(key, date);
}
/**
* 若未改变默认jdk序列化的方式, 也通过下面方法获取自增值
*
* @param key
*/
public Long getIncrValue(final String key) {
return redisTemplate.execute((RedisCallback) connection -> {
RedisSerializer serializer=redisTemplate.getStringSerializer();
byte[] rowKey=serializer.serialize(key);
byte[] rowVal=connection.get(rowKey);
try {
String val=serializer.deserialize(rowVal);
return Long.parseLong(val);
} catch (Exception e) {
return 0L;
}
});
}
测试类
@SpringBootTest
class SpringbootGradleApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private RedisUtil redisUtil;
@Test
public void redisTest() {
/*opsForValue: 对应 String(字符串)
opsForZSet: 对应 ZSet(有序集合)
opsForHash: 对应 Hash(哈希)
opsForList: 对应 List(列表)
opsForSet: 对应 Set(集合)
boundValueOps() Redis字符串(或值)键绑定操作*/
/*opsForValue() VS boundValueOps(K key)
ops开头的操作可以操作多个不同的key
bound开头的操作只可以操作一个传入的key*/
ValueOperations ops = redisTemplate.opsForValue();
ops.set("k1", "v1");
ops.increment("k2");
UserInfo userInfo = new UserInfo();
userInfo.setUserName("name1");
userInfo.setId("id1");
ops.set("k3", userInfo);
System.out.println(ops.get("k1"));
System.out.println(redisUtil.getIncrValue("k2"));
System.out.println(ops.get("k2"));
System.out.println(ops.get("k3"));
}
}
SpringBoot通过Spring Cache 集成 Redis
application.yml
spring:
cache:
# 使用了Spring Cache后,能指定spring.cache.type就手动指定一下,虽然它会自动去适配已有Cache的依赖,但先后顺序会对Redis使用有影响(JCache -> EhCache -> Redis -> Guava)
type: redis
使用举例
@Service
@CacheConfig(cacheNames = RedisEnum.DescConstant.USER_CACHE_10_MINUTES,keyGenerator = "keyGenerator")
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private RedisUtil redisUtil;
@Transactional(rollbackFor = Exception.class)
@CacheEvict(allEntries = true)
public AppResponse insertUser(UserInfo userInfo) {
//写业务逻辑
String userName = userInfo.getUserName();
if(null != userName && !"".equals(userName)){
userName = userName + "aaa";
userInfo.setUserName(userName);
}
//判断用户账号不能重复
if(userDao.countUserAcct(userInfo)>0){
return AppResponse.error("不能账号重复");
}
//上数据库查询姓名的count
int num = userDao.saveUser(userInfo);
if(num > 0){
return AppResponse.success("新增成功");
}
return AppResponse.error("新增成功");
}
/**
* 对无条件查询进行缓存
*/
@Cacheable(condition = "T(com.neusoft.springboot_gradle.common.util.EntityUtil).checkObjAllFieldsIsNull(#userInfo) == true")
public AppResponse listUsers(String flag, UserInfo userInfo) {
List userInfoList = userDao.listUsers(flag, userInfo);
if(userInfoList != null && userInfoList.size() <= 0){
return AppResponse.error("数据为空");
}
return AppResponse.success("查询成功", userInfoList);
}
/**
* 修改时更新相应的用户查询信息,列表查询时效性不大暂不更新
*/
@Transactional(rollbackFor = Exception.class)
public AppResponse updateUser(UserInfo userInfo) {
//判断用户账号不能重复
if(userDao.countUserAcct(userInfo)>0){
return AppResponse.error("不能账号重复");
}
int num = userDao.updateUser(userInfo);
if(num > 0){
String key = RedisEnum.DescConstant.USER_CACHE_10_MINUTES+"::"+this.getClass().getSimpleName()+":"+userInfo.getId();
redisUtil.setString(key,AppResponse.success("查询成功", userDao.getUser(userInfo.getId())),10);
return AppResponse.success("修改成功");
}
return AppResponse.error("修改失败");
}
@Cacheable(key = "#root.targetClass.simpleName + ':' + #id")
public AppResponse getUser(String id) {
UserInfo userInfo = userDao.getUser(id);
return AppResponse.success("查询成功", userInfo);
}
@CacheEvict(allEntries = true)
@Transactional(rollbackFor = Exception.class)
public AppResponse deleteUserBatch(UserInfo userInfo) {
int num = userDao.deleteUserBatch(userInfo);
if(num > 0){
return AppResponse.success("删除成功");
}
return AppResponse.error("删除失败");
}
/**
* 删除时删除同组(value或cachenames)下的缓存
*/
@CacheEvict(allEntries = true)
@Transactional(rollbackFor = Exception.class)
public AppResponse deleteUserBatch2(String lastModifiedBy,List list) {
Map map = new HashMap(2);
map.put("lastModifiedBy", lastModifiedBy);
map.put("myObjectList", list);
int num = userDao.deleteUserBatchByAcctAndName(map);
if(num > 0){
return AppResponse.success("删除成功");
}
return AppResponse.error("删除失败");
}
说明:
-
@CacheEvict:缓存清除
key:指定要清除的数据
allEntries = true : 指定清除同组(value或CacheNames)缓存中的所有数据
beforeInvocation=false: 缓存的清除是否在方法之前执行
默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
beforeInvocation=true 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
-
@CachePut: 既调用方法,又更新缓存数据;同步更新缓存
修改了数据库的某个数据,同时更新缓存
运行:
1.先调用目标方法
2.将目标方法的结果缓存起来
我们更新id=1的员工 信息,返回了更新后的值,这个时候我再查询id=1的员工,可以看到只有之前更新时打印的数据,并没有从数据库查数据
@CachePut 注释,这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中。
本文首发于我的个人博客的技术模块,请戳==》 www.moomal.top ,转载请标明出处!
版权所有:papayadog