Spring Boot整合
使用SpringData连接Redis
创建一个我们的springboot项目,选中我们的NOSQL里面的Redis
然后我们就会发现我们的Reids依赖关系
<!-- 操作Redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
他的底层依赖是
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.4.1</version>
<scope>compile</scope>
</dependency>
在SpringBoot2.x之后,我们的jedis被替换成了lettuce
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.0.1.RELEASE</version>
<scope>compile</scope>
</dependency>
底层
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>4.1.53.Final</version>
<scope>compile</scope>
</dependency>
jedis:采用的直连,多个线程操作的话 是不安全的,如果想要避免不安全的问题,要使用我们的jedis poll连接池!类似BIO
lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数量,更新NIO模式
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
//默认是{Lettuce生效后面我们详细讲解
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)//我们可以自己定义一个redisTemplate来替换
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
//默认的RedisTemplate没有过多的设置,redis对象都是需要序列化的
//两个泛型都是Object类型,我们需要强制类型转换<String,Object>
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
//为乐方便String类型生出了这个Bean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
spring.redis.host=192.168.1.99
server.redis.port=6379
测试
@SpringBootTest
class RedisSpringbootApplicationTests {
@Autowired
private RedisTemplate redisTemplate;
@Test
void contextLoads() {
//opsForValue 操作字符串类似String
//opsForList 操作List
//opsForSet 操作Set等等等等
//除了基本的操作,我们常用的方法都可以直接用redisTemplate调API
//获取reids的链接对象
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
// connection.flushAll();
// connection.flushDb();
redisTemplate.opsForValue().set("mykey","王花花");
System.out.println(redisTemplate.opsForValue().get("mykey"));
}
}
java测试结果
王花花
但是我们的redis显示的是
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x05mykey"
我们后面可以用序列化来解决这个问题
//RedisTemplate里面的源码
//这就是我们的默认序列化配置
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer keySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer valueSerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashKeySerializer = null;
@SuppressWarnings("rawtypes") private @Nullable RedisSerializer hashValueSerializer = null;
//默认的序列化方式是JDK序列化,我们希望要用JSON来序列化
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
//我们就需要自己配置我们的类
自定义RedisTemplate
简单测试(对象不序列化)
@Test
public void test() throws JsonProcessingException {
//真是开发一般使用JSON来传递对象
User user = new User("涛涛", 3);
// String jsonUser = new ObjectMapper().writeValueAsString(user);使用这种方式就直接传递进去了
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
org.springframework.data.redis.serializer.SerializationException: Cannot serialize; nested exception is org.springframework.core.serializer.support.SerializationFailedException: Failed to serialize object using DefaultSerializer;
意思就是我们没有序列化,不能进行传输,直接传递对象会报错误
@Component
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User implements Serializable {
private String name;
private Integer age;
}
当我们的pojo类进行了序列化,在运行就会成功
User(name=涛涛, age=3)
127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x04user"
也存进来了
序列化方式
我们自己的RedisTeplate模板
package com.wang.redis.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @author 王....华
* @version 1.0
* @date 2020/11/29 17:47
* @Description TODO
* @pojectname wang-redis
*/
@Configuration
public class ReidsConfig {
//编写我们的redisTemplate
@Bean
@SuppressWarnings("all")
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
//为了方便,泛型规定为String,Object类型
RedisTemplate<String,Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//序列化 json解析任意对象
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
//使用ObjectMapper转义
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//String的序列化
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
//key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
//hash的key也采用String 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
//value采用jackson的序列化方式
template.setValueSerializer(jackson2JsonRedisSerializer);
//hash的value也采用jackson的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
注意我们的注入redisTeplate一定要跳到我们的配置中
运行我们的测试类
User(name=涛涛, age=3)
我们的redis显示
127.0.0.1:6379> keys *
- “user”
不使用原生命令,使用自定义RedisUtils
package com.wang.redis.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Component
public final class RedisUtil {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// =============================common============================
/**
* 指定缓存失效时间
* @param key 键
* @param time 时间(秒)
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据key 获取过期时间
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
* @param key 键
* @return true 存在 false不存在
*/
public boolean hasKey(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除缓存
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));
}
}
}
// ============================String=============================
/**
* 普通缓存获取
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
/**
* 普通缓存放入
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 普通缓存放入并设置时间
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 递增
* @param key 键
* @param delta 要增加几(大于0)
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
* @param key 键
* @param delta 要减少几(小于0)
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
// ================================Map=================================
/**
* HashGet
* @param key 键 不能为null
* @param item 项 不能为null
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* HashSet
* @param key 键
* @param map 对应多个键值
*/
public boolean hmset(String key, Map<String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map<String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
*
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
*
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
*
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
*
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
*/
public double hincr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
*
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
*/
public double hdecr(String key, String item, double by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
// ============================set=============================
/**
* 根据key获取Set中的所有值
* @param key 键
*/
public Set<Object> sGet(String key) {
try {
return redisTemplate.opsForSet().members(key);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 根据value从一个set中查询,是否存在
*
* @param key 键
* @param value 值
* @return true 存在 false不存在
*/
public boolean sHasKey(String key, Object value) {
try {
return redisTemplate.opsForSet().isMember(key, value);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将数据放入set缓存
*
* @param key 键
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSet(String key, Object... values) {
try {
return redisTemplate.opsForSet().add(key, values);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 将set数据放入缓存
*
* @param key 键
* @param time 时间(秒)
* @param values 值 可以是多个
* @return 成功个数
*/
public long sSetAndTime(String key, long time, Object... values) {
try {
Long count = redisTemplate.opsForSet().add(key, values);
if (time > 0)
expire(key, time);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 获取set缓存的长度
*
* @param key 键
*/
public long sGetSetSize(String key) {
try {
return redisTemplate.opsForSet().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long setRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet().remove(key, values);
return count;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
// ===============================list=================================
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 -1代表所有值
*/
public List<Object> lGet(String key, long start, long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 获取list缓存的长度
*
* @param key 键
*/
public long lGetListSize(String key) {
try {
return redisTemplate.opsForList().size(key);
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
/**
* 通过索引 获取list中的值
*
* @param key 键
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
*/
public Object lGetIndex(String key, long index) {
try {
return redisTemplate.opsForList().index(key, index);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
*/
public boolean lSet(String key, Object value) {
try {
redisTemplate.opsForList().rightPush(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
* @param key 键
* @param value 值
* @param time 时间(秒)
*/
public boolean lSet(String key, Object value, long time) {
try {
redisTemplate.opsForList().rightPush(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lSet(String key, List<Object> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lSet(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value);
if (time > 0)
expire(key, time);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList().set(key, index, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lRemove(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList().remove(key, count, value);
return remove;
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
}
测试我们疯转的工具类
@Autowired
private RedisUtil redisUtil;
@Test
public void test1(){
redisUtil.set("name","kuangshen");
System.out.println(redisUtil.get("name"));
}
kuangshen
127.0.0.1:6379> keys *
1) "user"
2) "name"
127.0.0.1:6379> get name
"\"kuangshen\""
Redis.cong详解
启动的时候就是通过配置文件来启动的
单位
配置文件对unit单位不区分大小写
包含
多个配置文件可以在这配置进来
网络
bind 127.0.0.1 #绑定ip 不注释掉代表只能在本机访问
protected-mode yes #保护模式
prot 6379 #端口号
通用配置GENERAL
daemonize yes #默认是no yes代表以守护进程的方式运行
pidfile /var/run/redis_6379.pid #如果以后台方式运行,我们就需要指定一个pid进程文件
#日志级别
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)生产环境使用
# warning (only very important / critical messages are logged)
loglevel notice
logfile "" #文件名 后面我们会改
databases 16 #数据库数量,默认是16个
always-show-logo yes #是否显示logo图标 默认开启
快照SNAPSHOTTING
持久化,在规定的时间内,执行了多少操作则会持久到文件 .rdb .aof
Reids是内存数据库,没有持久化秘书局断电即失去
# save ""
#如果900s内,如果至少有 1 个key进行修改,我们就进行持久化操作
save 900 1
#如果300s内,如果至少有 10 个key进行修改,我们就进行持久化操作
save 300 10、
#如果60s内,如果至少有 10000 个key进行修改,我们就进行持久化操作
save 60 10000
#我们之后可以设置自己的持久化规则!
#持久化出错后是否还会继续工作 默认是继续工作的
stop-writes-on-bgsave-error yes
#是否压缩我们的 rdb 文件,需要消耗我们的cpu资源
rdbcompression yes
#保存 rdb 文件的时候,是否开启进行错误的检查校验
rdbchecksum yes
# rdb 文件保存的目录
dir ./
复制REPLICATION
后面我们主从复制会详细讲到
安全SECURITY
#查看密码
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "" #默认为空的
#配置文件设置
# requirepass foobareds
requirepass 123456
# Command renaming (DEPRECATED).
#命令设置
127.0.0.1:6379> CONFIG SET requirepass "123456"#设置redis密码
OK
127.0.0.1:6379> CONFIG get requirepass #获取密码
1) "requirepass"
2) "123456"
127.0.0.1:6379> auth 123456 #使用密码登录
OK
127.0.0.1:6379> ping
PONG
客户端限制CLIENTS
扩展了解
# maxclients 10000 #默认最大客户端连接数量
# maxmemory <bytes> #设置Redis的最大内存容量
#内存满了处理的策略
# maxmemory-policy noeviction
#移除一些过期的key。。。。。
#报错
#。。。。
1、volatile-lru:只对设置了过期时间的key进行LRU(默认值)
2、allkeys-lru : 删除lru算法的key
3、volatile-random:随机删除即将过期key
4、allkeys-random:随机删除
5、volatile-ttl : 删除即将过期的
6、noeviction : 永不过期,返回错误
APPEND ONLY MODE模式 AOF配置
#默认不开启AOF模式,默认是开启RDB方式持久化的,在大部分情况下,rdb够用
appendonly no
#持久化文件的名字
appendfilename "appendonly.aof"
# appendfsync always #每次修改都会同步,速度比较慢
#每秒执行一次sync,可能会丢掉这一秒的数据
appendfsync everysec
#不同步,这个时候操作系统自己同步数据,速度最快
# appendfsync no
Redis持久化
为什么Redis要做持久化?
因为Reids是内存数据库,如果不将内存中的数据状态保存到磁盘中,一旦服务器退出,服务器的数据库状态也会消失,所以需要做持久化
RDB
(Redis DataBase)
什么是RDB
默认采用的RDB方式持久化,一般不需要修改
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里。
Redis会单独创建( fork ) 一个子进程来进行持久化,会先将数据写入到一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。整个过程中,主进程是不进行任何I0操作的。这就确保了极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是最后- -次持久化后的数据可能丢失。
rdb保存的文件是dump.rdb文件
# The filename where to dump the DB
dbfilename dump.rdb
测试
修改了配置文件
save 60 5
127.0.0.1:6379> save
OK
127.0.0.1:6379> clear
127.0.0.1:6379> set k1 v1
OK
127.0.0.1:6379> set k2 v2
OK
127.0.0.1:6379> set k3 v3
OK
127.0.0.1:6379> set k4 v4
OK
127.0.0.1:6379> set k5 v5
OK
触发机制
1.save的规则满足的情况下,会自动触发rdb规则
2.执行flushall命令,也会出发我们的rdb规则
3.退出redis,也会产生我们的rdb文件
被焚毁自动生成一个dump.rdb
如何恢复rdb文件
只需要将rdb文件放在我们的redis启动目录就可以,redis启动的时候会自动检查durm.rdb恢复其中的数据
127.0.0.1:6379> CONFIG GET dir
1) "dir"
2) "/data"
[root@bogon data]# ls
appendonly.aof dump.rdb
优缺点
优点:
适合大规模的数据恢复!!
如果对数据的完整性要求不高!
缺点:
需要一定的时间间隔进行操作!如果redis以外宕机,这个最后一次修改的数据就没有了