底层原理
通过代码与redis服务器进行交互的原理是,
TCP网络传输协议,
和mysql一样,我们可以通过代码编写redis指令,
然后通过网络请求传给redis服务器程序执行,
并且通过网络接收响应回来的结果
部署redis服务端
redis程序目前只支持在linux系统上运行
我们可以在centos或ubuntu虚拟机上,使用docker进行部署
docker run -p 6379:6379 -it redis:latest
Jedis
jedis,即java redis,是redis服务器的客户端,是用户通过代码实现与redis服务器进行交互的途径
方法 | 参数 | 返回结果 | 功能 |
---|---|---|---|
connect() | 无 | void | 连接到 Redis 服务器。 |
disconnect() | 无 | void | 断开与 Redis 服务器的连接。 |
ping() | 无 | 字符串 “PONG” 表示连接正常 | 检测与 Redis 服务器的连接状态。 |
set(String, String) | String key ,String value | 字符串 “OK” 表示设置成功 | 设置指定 key 的值为给定的字符串。 |
get(String) | String key | 指定 key 的值,如果 key 不存在,则返回 null | 获取指定 key 的值。 |
del(String...) | 可变参数 String... keys | 删除的 key 的数量 | 删除一个或多个 key。 |
exists(String) | String key | 如果 key 存在,则返回 true,否则返回 false | 检查给定 key 是否存在。 |
expire(String, int) | String key ,int seconds | 如果设置成功,则返回 1;如果 key 不存在或设置失败,则返回 0 | 设置指定 key 的过期时间,单位为秒。 |
ttl(String) | String key | 指定 key 的剩余过期时间(单位:秒),-1 表示永不过期,-2 表示 key 不存在 | 获取指定 key 的剩余过期时间。 |
hset(String, String, String) | String key ,String field ,String value | 如果 field 是一个新的字段,并成功设置了值,则返回 true;如果 field 已经存在,旧值将被新值覆盖,返回 false | 为哈希表中的字段赋值。 |
hget(String, String) | String key ,String field | 指定字段的值,如果 key 或者 field 不存在,则返回 null | 获取哈希表中指定字段的值。 |
hdel(String, String...) | String key ,可变参数 String... fields | 删除的字段的数量 | 删除哈希表中的一个或多个字段。 |
hexists(String, String) | String key ,String field | 如果哈希表中存在给定字段,则返回 true,否则返回 false | 检查哈希表中是否存在给定字段。 |
hgetAll(String) | String key | 哈希表中所有字段-值对的 Map 集合,如果 key 不存在,则返回空的 Map | 获取哈希表中所有字段的值。 |
zadd(String, double, String) | String key ,double score ,String member | 如果 member 成员是一个新的成员,并成功添加,则返回 true;如果 member 已经是集合中的成员,只是更新对应的 score 值,则返回 false | 将一个 member 元素及其 score 值加入到有序集合中。 |
zrange(String, long, long) | String key ,long start ,long end | 有序集合指定区间内的成员列表 | 返回有序集合中指定区间内的成员,按 score 值递增排序。 |
zrem(String, String...) | String key ,可变参数 String... members | 移除的成员的数量 | 移除有序集合中的一个或多个成员。 |
zcard(String) | String key | 有序集合的基数(集合中的成员数量) | 获取有序集合的成员数量。 |
首先,引入依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>5.0.0</version>
</dependency>
然后,就是建立连接
因为,jedis本身并不是线程安全的,所以需要为每一个线程单独建立一个连接,这就需要用到连接池了
Jedis连接池
用到的api有:
- JedisPoolConfig:用于封装连接池的配置项参数
- JedisPool:连接池对象
public class JedisConnectionFactory {
private static final JedisPool pool;
static {
JedisPoolConfig config = new JedisPoolConfig();
//最多创建连接数
config.setMaxTotal(8);
//空闲池最大连接数
config.setMaxIdle(8);
//空闲池最少拥有连接
config.setMinIdle(0);
//等待时间
config.setMaxWaitMillis(200L);
//根据配置参数创建连接池
pool = new JedisPool(config, "192.168.59.136", 6379,
1000, "1234");
}
/**
* 获取连接
* */
public static Jedis getConnection(){
return pool.getResource();
}
}
连接池做了什么事情呢
我们可以从连接池通过getResource,获得一个jedis对象,这与我们直接创建的jedis对象不同的是,从连接池获得的jedis对象多了一个不为null的DataSource对象,其实就是连接池对象,因此当我们调用jedis.close时不会销毁连接,而是将连接归还到连接池中
源码部分如下
当我们调用pool.getResource时,就自动为jedis对象设置了DataSource属性
public Jedis getResource() {
Jedis jedis = super.getResource();
jedis.setDataSource(this);
return jedis;
}
protected void setDataSource(Pool<Jedis> jedisPool) {
this.dataSource = jedisPool;
}
@Override
public void close() {
if (dataSource != null) {
Pool<Jedis> pool = this.dataSource;
this.dataSource = null;
if (isBroken()) {
pool.returnBrokenResource(this);
} else {
pool.returnResource(this);
}
} else {
connection.close();
}
}
SpringDataRedis
核心api是RedisTemplate ,是spring的一个模板类,底层仍是基于jedis
RedisTemplate
调api很简单,直接通过自动注入的redisTemplate对象去调就行了
需要注意,这里将操作根据不同的redis数据类型进行了分类,我们要先通过redisTemplate的方法获得对应数据类型的对象,才能执行相关的操作
方法 | 参数 | 返回结果 | 功能 |
---|---|---|---|
opsForValue() | 无 | ValueOperations<K, V> 对象 | 获取用于操作字符串类型数据的 ValueOperations 对象 |
opsForList() | 无 | ListOperations<K, V> 对象 | 获取用于操作列表类型数据的 ListOperations 对象 |
opsForSet() | 无 | SetOperations<K, V> 对象 | 获取用于操作集合类型数据的 SetOperations 对象 |
opsForZSet() | 无 | ZSetOperations<K, V> 对象 | 获取用于操作有序集合类型数据的 ZSetOperations 对象 |
opsForHash() | 无 | HashOperations<K, HK, HV> 对象 | 获取用于操作哈希类型数据的 HashOperations 对象 |
boundValueOps() | K key | BoundValueOperations<K, V> 对象 | 获取指定 key 的绑定操作对象,用于对该 key 进行操作 |
boundListOps() | K key | BoundListOperations<K, V> 对象 | 获取指定 key 的列表绑定操作对象,用于对该列表进行操作 |
boundSetOps() | K key | BoundSetOperations<K, V> 对象 | 获取指定 key 的集合绑定操作对象,用于对该集合进行操作 |
boundZSetOps() | K key | BoundZSetOperations<K, V> 对象 | 获取指定 key 的有序集合绑定操作对象,用于对该有序集合进行操作 |
boundHashOps() | K key | BoundHashOperations<K, HK, HV> 对象 | 获取指定 key 的哈希绑定操作对象,用于对该哈希进行操作 |
execute(RedisCallback<T> action) | RedisCallback<T> action | 根据 RedisCallback 的泛型参数 T 的返回值类型 | 执行 Redis 命令,并通过 RedisCallback 回调函数处理返回结果 |
execute(RedisScript<T> script, List<K> keys, Object... args) | RedisScript<T> script , List<K> keys , Object... args | 根据 RedisScript 的泛型参数 T 的返回值类型 | 执行 Lua 脚本,并传入参数进行处理 |
相关依赖
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>3.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-pool2 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.12.0</version>
</dependency>
其中,spring-boot-starter-data-redis是一个依赖组,包括三个子依赖
配置文件
springData将配置信息从代码中分离,单独写在application.yml文件中,项目启动后,程序会自动读取配置信息
spring:
data:
redis:
host: 192.168.59.136
port: 6379
password: 1234
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: 100
序列化器
众所周知,计算机只能识别0101这样的二进制数据,无法识别人类语言中的字符,所以数据在保存之前,是先要依据字符集以及编码格式,编码成二进制数据,这就是所谓的序列化
redisTemplate可以接收复杂的java对象作为value值存到redis中,前提是需要先将java对象编码成二进制数据
接下来,将详细分析集中序列化方案
序列化方案
redisTemplate中支持的序列化器都实现了RedisSerializer接口
推荐使用第二个方式,占用内存最小,速度也最快,也就是先把对象转json字符串,再转字节数组
反序列化时,就先将字节数组转json字符串,再转java对象
序列化器 | 接收参数 | 实现细节 | 功能 | 优点 | 缺点 |
---|---|---|---|---|---|
JdkSerializationRedisSerializer | 待序列化/反序列化的对象 | 使用 JDK 自带的序列化方式将对象序列化为字节流 | 将 Java 对象序列化为字节数组,将字节数组反序列化为对象 | 原生自带,无需要引入第三方,默认使用 | k,v都被序列成字节数组,查询出来是16进制,看不懂 |
StringRedisSerializer | 待序列化/反序列化的对象 | 将对象序列化为字符串形式存储在 Redis 中 | 将字符串对象转换为字节数组,将字节数组转换为字符串对象 | 占用空间小,性能高 | 专门处理string类型 |
GenericJackson2JsonRedisSerializer | 待序列化/反序列化的对象 | 使用 Jackson 库将对象序列化为 JSON 格式 | 将对象序列化为 JSON 字符串,将 JSON 字符串反序列化为对象 | 适用于所有类型的对象 | 性能差,速度慢,序列化后的数据较大 ,自动加上了全类名字段 |
可以创建一个配置类来设置redisTemplate对象的序列化方式
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String,Object> redisTemplateConfig(RedisConnectionFactory factory)throws Exception{
//创建redisTemplate对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
//设置连接
template.setConnectionFactory(factory);
//设置序列化器
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
template.setKeySerializer(serializer);
template.setHashKeySerializer(serializer);
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
}
StringRedisTemplate
默认采用string序列化,需要手动将java对象序列化
其实很简单,相信大家也都用过FastJson序列化和反序列化,和以前一样的用法
@SpringBootTest(classes = Application.class)
public class TestRedisTemplate {
@Autowired
StringRedisTemplate template;
@Test
public void testStringSerial(){
//手动序列化对象
User user = new User(1,"zs", "123");
String json = JSONObject.toJSONString(user);
//存入redis
ValueOperations<String, String> opsForValue = template.opsForValue();
opsForValue.set("user"+":"+user.getUserId(),json);
}
}
效果如图
对于value是对象的,我们还可以使用Hash作为数据类型,将字段和值进行分离,方便进行对数据进行精细化操作
@SpringBootTest(classes = Application.class)
public class TestRedisTemplate {
@Autowired
StringRedisTemplate template;
@Test
public void testStringSerial()throws Exception{
//手动序列化对象
User user = new User(1,"zs", "123");
String json = JSONObject.toJSONString(user);
//存入redis
Class<? extends User> clazz = user.getClass();
HashOperations<String, Object, Object> hashOperations = template.opsForHash();
String key = "user" + ":" + user.getUserId();
for (Field field : clazz.getDeclaredFields()) {
field.setAccessible(true);
hashOperations.put("user",field.getName(),field.get(user).toString());
}
}
}
效果