目录
五、Redis的Java客户端-lettuce(Spring Data Redis包含该客户端)
3、方式一:使用RedisTemplate工具类,需要自定义RedisTemplate处理序列化
4、方式二:StringRedisTemplate类,存储java对象时,需手动完成对象的序列化和反序列化
一、认识NoSQL及Redis
1、SQL与NoSQL对比
SQL:数据结构化并且是关联的,使用SQL查询,事务特性ACID,磁盘存储,垂直扩展
使用场景:数据结构固定,相关业务对数据安全性、一致性要求较高
NoSQL:数据非结构化并且无关联,非SQL查询,事务特性BASE,内存存储,水平扩展
使用场景:数据结构不固定,对一致性、安全性要求不高,对性能要求
2、认识Redis
Redis诞生于2009年,全称是Remote Dictionary Server,远程词典服务器,是一个基于内存的键值型NoSQL数据库。
特征:
键值(key-value)型,value支持多种不同数据结构,功能丰富
单线程,每个命令具备原子性
低延迟,速度快(基于内存、IO多路复用、良好的编码)
支持数据持久化
支持主从集群、分片集群
支持多语言客户端
3、key结构
Redis的key允许有多个单词形成层级结构,多个单词之间用':'隔开
格式示例:
项目名:业务名:类型:id
二、通用命令
# 输入ip端口密码连接redis
redis-cli -h 192.168.1.104 -p 6379 -a 123456
# 输入ip端口连接redis
redis-cli -h 192.168.1.104 -p 6379
# 输入密码进入redis
AUTH 123456
# 切换到数据库1
select 1
# 查询所有key,不建议在生产环境设备上使用
keys *
# 查询所有带a的key,不建议在生产环境设备上使用
key a*
# 设置一个key为name,值为张三的数据 返回OK代表成功
set name '张三'
# 同时设置多个key,返回OK代表成功
mset k1 v1 k2 v2
# 删除一个key为name的数据 返回删除成功的个数 支持多个key,空格隔开
del name
del k1 k2 # 删除多个
# 判断key为k1,k2的数据是否存在,不存在返回0,存在则返回存在个数
exists k1 k2
# 设置k1在500秒后过期 返回"1"代表成功
expire k1 500
# 查看k1的有效时间 返回秒数 -1为永久有效 -2为已过期
ttl k1
三、数据结构-基本类型
1、String类型-字符串类型
Value是字符串,根据格式不同分为3类:
string:普通字符串
int:整数类型,可以做自增、自减操作
float:浮点类型,可以做自增、自减操作
不管是哪种格式,底层都是字节数组形式存储,只不过是编码方式不同。字符串类型的最大空间不能超过512m.
示例:
# 添加/修改一个String类型键值对 返回OK
set msg "hello world"
# 设置msg在500秒后过期 返回"1"代表成功
expire msg 500
# 批量添加多个String类型键值对 返回OK
mset num 11 score 93.5
# 根据key获取String类型的value,返回"hello world"
get msg
# 根据多个key获取多个String类型的值 返回 1) "11" 2) "93.5"
mget num score
# 让一个整型的key自增1 返回"12"
incr num
# 让一个整型的key自增并指定步长 返回 "14"
incrby num 2
# 让一个浮点类型的数字自增并指定步长 返回 "95.599999999999994"
incrbyfloat score 2.1
# 添加一个String类型的键值对,前提是这个key不存在,否则不执行
setnx msg "test" # 存在msg,未设置成功,返回"0"
setnx test "test" # 不存在test,设置成功,返回"1"
# 添加一个String类型的键值对,并且指定有效期
setex test 100 "test1" # 重置test键的值并设置100秒后过期
ttl test # 查看test过期秒数
2、Hash类型
Hash类型,也叫散列,其value是一个无序字典,类似于Java中的HashMap结构。
String结构是将对象序列化为JSON字符串后存储,当需要修改对象某个字段时很不方便;
Hash结构可以将对象中的每个字段独立存储,可以针对单个字段做CRUD
示例(key为sample:hs:score):
# 添加/修改hash类型key的field值 返回"1"
hset sample:hs:score "张三" 59
# 设置sample:hs:score在500秒后过期 返回"1"代表成功
expire sample:hs:score 500
# 批量添加多个hash类型key的field的值 返回 "OK"
hmset sample:hs:score "李四" 61 "王五" 60
# 获取一个hash类型key的field的值 返回 "59"
hget sample:hs:score "张三"
# 批量获取多个hash类型key的field的值 返回 1) "61" 2) "60"
hmget sample:hs:score "李四" "王五"
# 获取一个hash类型的key中的所有的field和value
# 返回 1) "张三" 2) "59" 3) "李四" 4) "61" 5) "王五" 6) "60"
hgetAll sample:hs:score
# 获取一个hash类型的key中的所有的field 返回 1) "张三" 2) "李四" 3) "王五"
hkeys sample:hs:score
# 获取一个hash类型的key中的所有的value 返回 1) "59" 2) "61" 3) "60"
hvals sample:hs:score
# 让一个hash类型key的字段值自增并指定步长 返回 "69"
hincrby sample:hs:score "张三" 10
# 添加一个hash类型的key的field值,前提是这个field不存在,否则不执行
hsetnx sample:hs:score "张三" 50 # 存在,未设置成功,返回 "0"
hsetnx sample:hs:score "赵六" 50 # 不存在,设置成功,返回 "1"
3、List类型
Redis中的List类型与Java中的LinkedList类似,可以看作是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。
特征:有序、元素可以重复、插入和删除快、查询速度一般
常用来存储一个有序数据,例如:朋友圈点赞列表,评论列表等。
示例:
# 向列表左侧插入一个或多个元素,row=1 value=b 最后插入的元素在最前面
lpush sample:comment "a" "b"
# 设置sample:comment在500秒后过期 返回"1"代表成功
expire sample:comment 500
# 移除并返回列表左侧的第一个元素,没有则返回null
lpop sample:comment # 返回"b"
# 向列表右侧插入一个或多个元素,row=1 value=a 最后插入的元素在最后面
rpush sample:comment "a" "b"
# 移除并返回列表右侧的第一个元素,返回"b"
rpop sample:comment
# 返回一段角标范围内的所有元素,不包含起始下标值 返回 1) "b"
lrange sample:comment 1 2
# 与lpop和rpop类似,只不过在没有元素时等待指定时间,而不是直接返回null
# 有值时返回 1) "sample:comment" 2) "b",无值等待10秒返回空白
blpop sample:comment 10
brpop sample:comment 10
4、Set类型
Reids的Set结构与Java中的HashSet类似,可以看作是一个value为null的HashMap。因为也是一个hash表,因此具备与HashSet类似的特征:无序、元素不可重复、查找快、支持交集/并集/差集等功能。
示例:
# 向set中添加一个或多个元素
sadd sample:rank 6 7 8
# 设置sample:rank在500秒后过期 返回"1"代表成功
expire sample:rank 500
# 移除set中的指定元素 返回"1"代表成功
srem sample:rank 7
# 返回set中元素的个数 返回"2"
scard sample:rank
# 判断一个元素是否存在于set中
sismember sample:rank 8 # 存在,返回"1"
sismember sample:rank 7 # 不存在,返回"0"
# 获取set中的所有元素 返回 1) "6" 2) "8"
smembers sample:rank
5、SortedSet类型
Redis的SortedSet是一个可排序的set集合,与Java中的TreeSet有些类似,但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性,可以基于score属性对元素排序,底层的实现是一个跳表(SkipList)加hash表。
特性:可排序、元素不重复、查询速度快
因为SortedSet的可排序特性,经常被用来实现排行榜这样的功能。
示例(排名默认升序,降序在命令z后面加rev即可):
# 添加一个或多个元素到sorted set,如果已经存在则更新其score值
# 新增行返回"1" 修改行返回"0"
zadd sample:goldRank 101 "张三" 200 "李四" 150 "王五"
# 设置sample:rank在500秒后过期 返回"1"代表成功
expire sample:goldRank 500
# 删除sorted set中的一个指定元素 返回"1"代表成功 返回"0"代表不存在
zrem sample:goldRank "王五"
# 获取sorted set中的指定元素的score值 返回"200"
zscore sample:goldRank "李四"
# 获取sorted set中指定元素的排名 张三 201 李四 200
zrank sample:goldRank "张三" # 返回"1"
zrevrank sample:goldRank "张三" # 返回"0"
# 获取sorted set中的元素个数 返回"2"
zcard sample:goldRank
# 统计score值在给定范围内的所有元素的个数 返回"1"
zcount sample:goldRank 200 500
# 让sorted set中的指定元素自增,步长为100 返回"201"
zincrby sample:goldRank 100 "张三"
# 按照score排序后,获取指定排名范围内的元素,排名从0开始,0为最小值 返回 1) "张三"
zrange sample:goldRank 1 10
# 获取指定排名范围内的元素及score 返回 1) "张三" 2) "201"
zrange sample:goldRank 1 10 withscores
# 按照score排序后,获取指定score范围内的元素
zrangebyscore sample:goldRank (200 (500 # 200<score<500 返回 1) "张三"
zrangebyscore sample:goldRank (200 500 # 200<score<=500 返回 1) "张三"
zrangebyscore sample:goldRank 200 500 # 200<=score<=500 返回 1) "李四" 2) "张三"
zrangebyscore sample:goldRank -inf +inf # 显示所有数据 返回 1) "李四" 2) "张三"
# score<=200,并返回score 返回 1) "李四"2) "200"
zrangebyscore sample:goldRank -inf 200 withscores
# 求差集、交集、并集
zdiff zinter zunion
四、Redis的Java客户端-Jedis
以Reids命令作为方法名称,学习成本低,简单实用,
但是Jedis实例是线程不安全的,多线程环境下需要基于连接池来使用。
Spring Data Redis包含该客户端
1、推荐使用Jedis连接池代替Jedis的直连方式
public class JedisConnectionFactory {
private static final JedisPool jedisPool;
static {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
//最大连接
jedisPoolConfig.setMaxTotal(8);
//最大空闲连接
jedisPoolConfig.setMaxIdle(8);
//最小空闲连接
jedisPoolConfig.setMinIdle(0);
//设置最长等待时间 ms
jedisPoolConfig.setMaxWaitMillis(200);
//注意没密码不要设置为"",会auth报错
jedisPool = new JedisPool(jedisPoolConfig, "192.168.10.104", 6379, 1000);
}
/**
* 获取Jedis对象
*
* @return
*/
public static Jedis getJedis() {
return jedisPool.getResource();
}
}
2、gradle引入依赖
implementation 'redis.clients:jedis:5.1.2'
implementation 'org.apache.commons:commons-pool2:2.10.0'
3、示例junit测试代码
@SpringBootTest
public class JedisApplicationTests {
private Jedis jedis;
@BeforeEach
void setUp() {
//jedis直连
//jedis = new Jedis("192.168.10.104", 6379);
//有密码需设置
//jedis.auth("");
//使用jedis连接池
jedis = JedisConnectionFactory.getJedis();
jedis.select(1);
}
@Test
void testString() {
//设置STRING类型数据
String result = jedis.set("name1", "张三");
System.out.println("result=" + result);
//设置500秒过期
jedis.expire("name1", 500);
//根据key获取value
String name = jedis.get("name1");
System.out.println("name=" + name);
}
@Test
void testHash() {
//创建hash并添加键值对
jedis.hset("sample:hs:score", "张三", "100");
//设置500秒过期
jedis.expire("sample:hs:score", 500);
//追加键值对到hash
HashMap<String, String> map = new HashMap<>();
map.put("李四", "200");
map.put("王五", "150");
jedis.hmset("sample:hs:score", map);
//获取hash所有键值对
Map<String, String> m = jedis.hgetAll("sample:hs:score");
System.out.println(m.toString());
}
@Test
void testList() {
//创建list并插入数据
jedis.lpush("sample:comment", "a", "b");
jedis.rpush("sample:comment", "c", "d");
List<String> list = jedis.lrange("sample:comment", 0, 100);
list.forEach(System.out::println);
//设置500秒过期
jedis.expire("sample:comment", 500);
//从左侧移除数据
jedis.lpop("sample:comment");
}
@Test
void testSet() {
//创建set并插入数据
jedis.sadd("sample:rank", "6", "7", "8");
//设置500秒过期
jedis.expire("sample:rank", 500);
//移除某个元素
jedis.srem("sample:rank", "6");
//获取所有元素
Set<String> set = jedis.smembers("sample:rank");
System.out.println("数据:" + set.toString());
}
@Test
void testSortedSet() {
jedis.zadd("sample:goldRank", 100, "张三");
HashMap<String, Double> map = new HashMap<>();
map.put("李四", 200D);
map.put("王五", 150D);
//设置500秒过期
jedis.expire("sample:goldRank", 500);
jedis.zrem("sample:goldRank", "王五");
//获取指定排名范围内的元素及score
List<Tuple> list = jedis.zrangeWithScores("sample:goldRank", 1, 10);
list.forEach(System.out::println);
}
@AfterEach
void tearDown() {
//释放资源
if (jedis != null) {
jedis.close();
}
}
}
五、Redis的Java客户端-lettuce(Spring Data Redis包含该客户端)
lettuce是基于netty实现的,支持同步、异步和响应式编程方式,并且是线程安全的。
支持Redis的哨兵模式、集群模式和管道模式。
六、Redis的Java客户端-Redisson
Redisson是一个基于Redis实现的分布式、可伸缩的Java数据结构集合。
包含了诸如Map、Queue、Lock、semaphore、AtomicLong等强大功能。
七、SpringDataRedis
SpringData是Spring中数据操作的模块,包含对各种数据库的集成,
其中对Redis的集成模块就叫做SpringDataRedis,
官网地址:https://spring.io/projects/spring-data-redis
提供了对不同Redis客户端的整合(Lettuce和Jedis)
提供了RedisTemplate统一API来操作Redis
支持Redis的发布订阅模型
支持Redis哨兵和Redis集群
支持基于Lettuce的响应式编程
支持基于JDK、JSON、字符串、Spring对象的数据序列化及反序列化
支持基于Redis的JDK Collection实现
1、gradle引入依赖
//spring-boot-starter-data-redis依赖
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
//连接池依赖,jedis和spring-boot-starter-data-redis都需要
implementation 'org.apache.commons:commons-pool2:2.10.0'
2、配置application.yml
server:
port: 8050
spring:
redis:
host: localhost
port: 6379
password:
lettuce: # jedis:
pool:
max-active: 8 # 最大连接
max-idle: 8 # 最大空闲连接
min-idle: 0 # 最小空闲连接
max-wait: 100 # 连接等待时间
database: 1
3、方式一:使用RedisTemplate工具类,需要自定义RedisTemplate处理序列化
/**
* 自定义RedisTemplate,修改RedisTemplate的序列化器,避免key-value乱码,
* 但java对象json序列化会将类的class类型写入json结果,存入Redis,会带来额外的内存开销。
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
//创建RedisTemplate对象
RedisTemplate<String,Object> redisTemplate=new RedisTemplate<>();
//设置连接工厂
redisTemplate.setConnectionFactory(connectionFactory);
//创建Json序列化工具
GenericJackson2JsonRedisSerializer jsonRedisSerializer=new GenericJackson2JsonRedisSerializer();
//设置key的序列化
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setHashKeySerializer(RedisSerializer.string());
//设置value的序列化
redisTemplate.setValueSerializer(jsonRedisSerializer);
redisTemplate.setHashValueSerializer(jsonRedisSerializer);
//返回
return redisTemplate;
}
}
// String类型存实体数据格式
{
"@class": "com.fafa.jedis.User",
"userId": 10001,
"name": "张三三"
}
/**
* RedisTemplate测试示例
*/
@SpringBootTest
public class RedisTemplateTests {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Test
void testString() {
//插入一条String类型数据
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("name", "李四");
//存入java对象,会将类的class类型写入json
User user = new User(10001, "张三三");
valueOperations.set("user", user);
//设置500秒过期
valueOperations.getOperations().expire("name", 500, TimeUnit.SECONDS);
//读取一条String类型数据
Object name = valueOperations.get("name");
System.out.println("数据name=" + name);
}
@Test
void testHash() {
//插入一条Hash类型数据
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.put("sample:hs:score", "张三", "100");
//追加键值对到hash
HashMap<String, String> map = new HashMap<>();
map.put("李四", "200");
map.put("王五", "150");
hashOperations.putAll("sample:hs:score", map);
//设置500秒过期
hashOperations.getOperations().expire("sample:hs:score", 500, TimeUnit.SECONDS);
//获取hash所有键值对
Map<String, String> m = hashOperations.entries("sample:hs:score");
System.out.println("数据:" + m.toString());
}
@Test
void testList() {
//创建list并插入数据
ListOperations listOperations = redisTemplate.opsForList();
listOperations.leftPushAll("sample:comment", "a", "b");
listOperations.rightPushAll("sample:comment", "c", "d");
List<String> list = listOperations.range("sample:comment", 0, 100);
list.forEach(System.out::println);
//设置500秒过期
listOperations.getOperations().expire("sample:comment", 500, TimeUnit.SECONDS);
//从左侧移除数据
listOperations.leftPop("sample:comment");
}
@Test
void testSet() {
//创建set并插入数据
SetOperations setOperations = redisTemplate.opsForSet();
setOperations.add("sample:rank", "6", "7", "8");
//设置500秒过期
setOperations.getOperations().expire("sample:rank", 500, TimeUnit.SECONDS);
//移除某个元素
setOperations.remove("sample:rank", "6");
//获取所有元素
Set<String> set = setOperations.members("sample:rank");
System.out.println("数据:" + set.toString());
}
@Test
void testSortedSet() {
ZSetOperations sortedSetOption = redisTemplate.opsForZSet();
sortedSetOption.add("sample:goldRank", "张三", 100);
HashMap<String, Double> map = new HashMap<>();
map.put("李四", 200D);
map.put("王五", 150D);
//设置500秒过期
sortedSetOption.getOperations().expire("sample:goldRank", 500, TimeUnit.SECONDS);
sortedSetOption.remove("sample:goldRank", "王五");
//获取指定排名范围内的元素
Set list = sortedSetOption.range("sample:goldRank", 0, 10);
list.forEach(System.out::println);
}
}
4、方式二:StringRedisTemplate类,存储java对象时,需手动完成对象的序列化和反序列化
@SpringBootTest
public class StringRedisTemplateTests {
@Autowired
private StringRedisTemplate stringRedisTemplate;
//JSON工具
private static final ObjectMapper mapper = new ObjectMapper();
@Test
void testString() throws JsonProcessingException {
//插入一条String类型数据
ValueOperations valueOperations = stringRedisTemplate.opsForValue();
valueOperations.set("name", "李四");
//读取一条String类型数据
Object name = valueOperations.get("name");
System.out.println("数据name=" + name);
//创建对象
User user = new User(10001, "张三三");
//jackson手动序列化
String json = mapper.writeValueAsString(user);
//写入数据到redis
valueOperations.set("user", json);
//读取数据
String val = (String) valueOperations.get("user");
//jackson反序列化
User user1 = mapper.readValue(val, User.class);
System.out.println("user1=" + user1);
//设置500秒过期
valueOperations.getOperations().expire("name", 500, TimeUnit.SECONDS);
}
@Test
void testHash() {
//插入一条Hash类型数据
HashOperations hashOperations = stringRedisTemplate.opsForHash();
hashOperations.put("sample:hs:score", "张三", "100");
//追加键值对到hash
HashMap<String, String> map = new HashMap<>();
map.put("李四", "200");
map.put("王五", "150");
hashOperations.putAll("sample:hs:score", map);
//设置500秒过期
hashOperations.getOperations().expire("sample:hs:score", 500, TimeUnit.SECONDS);
//获取hash所有键值对
Map<String, String> m = hashOperations.entries("sample:hs:score");
System.out.println("数据:" + m.toString());
}
@Test
void testList() {
//创建list并插入数据
ListOperations listOperations = stringRedisTemplate.opsForList();
listOperations.leftPushAll("sample:comment", "a", "b");
listOperations.rightPushAll("sample:comment", "c", "d");
List<String> list = listOperations.range("sample:comment", 0, 100);
list.forEach(System.out::println);
//设置500秒过期
listOperations.getOperations().expire("sample:comment", 500, TimeUnit.SECONDS);
//从左侧移除数据
listOperations.leftPop("sample:comment");
}
@Test
void testSet() {
//创建set并插入数据
SetOperations setOperations = stringRedisTemplate.opsForSet();
setOperations.add("sample:rank", "6", "7", "8");
//设置500秒过期
setOperations.getOperations().expire("sample:rank", 500, TimeUnit.SECONDS);
//移除某个元素
setOperations.remove("sample:rank", "6");
//获取所有元素
Set<String> set = setOperations.members("sample:rank");
System.out.println("数据:" + set.toString());
}
@Test
void testSortedSet() {
ZSetOperations sortedSetOption = stringRedisTemplate.opsForZSet();
sortedSetOption.add("sample:goldRank", "张三", 100);
HashMap<String, Double> map = new HashMap<>();
map.put("李四", 200D);
map.put("王五", 150D);
//设置500秒过期
sortedSetOption.getOperations().expire("sample:goldRank", 500, TimeUnit.SECONDS);
sortedSetOption.remove("sample:goldRank", "王五");
//获取指定排名范围内的元素
Set list = sortedSetOption.range("sample:goldRank", 0, 10);
list.forEach(System.out::println);
}
}