redis常用的几大类数据结构 1、String ,token存储、分布式锁、计数 、json存储 2、Hash,购物车。。。 3、List 4、Set 集合,不重复,抽奖、朋友圈... ,交并集 5、Zset score ,做排行榜... key \*condition scan count 1000 set key value nx px 30000 # 本节要点 - SpringBoot项目集成redis - 熟悉五大数据类型API - 实操练习 # 一、创建一个项目 !\[image-20251012005937327\](https://woniumd.oss-cn-hangzhou.aliyuncs.com/java/daiwei/20251012005937429.png) # 二、加依赖 \`\`\` org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis org.apache.commons commons-pool2 \`\`\` # 三、加配置 \`\`\` server: port: 9090 spring: application: name: springboot-redis data: redis: host: 182.92.108.193 #redis的地址 port: 16379 #redis的端口 password: 7e40796#Tb9bbf861 #密码 database: 13 lettuce: pool: max-active: 8 #最大连接 max-idle: 8 #最大空闲连接 min-idle: 0 #最小空闲连接 max-wait: 100 #最大等待时间 \`\`\` - \*\*Jedis:同步阻塞,非线程安全\*\*:Jedis 采用同步阻塞的 I/O 模型。它的实例\*\*不是线程安全\*\*的,不能在多线程环境下直接共享。通常需要使用\*\*连接池\*\*来管理,每个线程从池中获取一个独立的 Jedis 实例进行操作。 - \*\*Lettuce:异步非阻塞,线程安全\*\*:Lettuce 基于 Netty 框架,采用异步非阻塞的 I/O 模型。它的实例是\*\*线程安全\*\*的,多个线程可以共享同一个连接,无需额外的同步措施。 # 四、配置类 核心用户就是:\*\*序列化\*\* 序列化和不序列化的区别如下 \`\`\` // 存储的数据在 Redis 中是这样的: // key: \\xac\\xed\\x00\\x05t\\x00\\x04user // value: \\xac\\xed\\x00\\x05sr\\x00\\x11com.example.User... (二进制乱码) // 配置后,数据在 Redis 中清晰可读: // key: "user:123" // value: {"id":123,"name":"张三","age":25} \`\`\` \`\`\` package config; 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.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; /\*\* \* @author daiwei \* @Description: \*/ @Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate\<\>(); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setKeySerializer(RedisSerializer.string()); //序列化内容,保证是可见 redisTemplate.setValueSerializer(RedisSerializer.json()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setHashValueSerializer(RedisSerializer.json()); return redisTemplate; } } \`\`\` stringRedisTemplate的问题 \`\`\` stringRedisTemplate.opsForValue().set("user:1", new User("X", 18)); \`\`\` # 五、controller测试 总体说明 https://docs.spring.io/spring-data/redis/reference/3.5/redis/getting-started.html \| API \| 返回类型 \| 说明 \| \| --------------------------- \| -------- \| --------------------------- \| \| redisTemplate.opsForValue() \| String \| 字符串 token、json \| \| redisTemplate.opsForHash() \| Hash \| k-\>kv对,购物车 \| \| redisTemplate.opsForList() \| List \| 队列先进先出,栈先进后 \| \| redisTemplate.opsForSet() \| Set \| 无顺序集合,好友 \| \| redisTemplate.opsForZSet() \| Zset \| 比set多了一个排序分 ,排行榜 \| \`\`\` 注入 @Autowired private RedisTemplate redisTemplate; //默认对String进行了序列化 //@Autowired //private StringRedisTemplate stringRedisTemplate; \`\`\` ## 1、String 1、获取操作String的对象 \`\`\` ValueOperations opt = redisTemplate.opsForValue(); \`\`\` 2、写一个String \`\`\` opt.set("com:woniu:token:user:1001","ddbae104173c08bfa061c1a24019423"); \`\`\` 3、json对象存放 - 自己加了序列化的RedisTemplate \`\`\` @Autowired private RedisTemplate redisTemplate; ... ValueOperations ops =redisTemplate.opsForValue(); UserInfoVo userInfoVo1 = UserInfoVo.builder() .userId("10001") .username("admin1") .password("11111") .build(); ops.set("userinfo1", userInfoVo1); \`\`\` - 用自带的StringRedisTemplate进行操作 \`\`\` @Autowired private StringRedisTemplate stringRedisTemplate; ... ObjectMapper mapper = new ObjectMapper(); UserInfoVo userInfoVo = UserInfoVo.builder() .userId("1000") .username("admin") .password("") .build(); stringRedisTemplate.opsForValue().set("userinfo", mapper.writeValueAsString(userInfoVo)); \`\`\` 4、如果想要去掉class,就需要修改配置类(\*\*可选\*\*) \`\`\` package com.example.redis131.config; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; 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.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate redisTemplate = new RedisTemplate\<\>(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer\<\>(Object.class); ObjectMapper objectMapper = JsonMapper.builder() .addModule(new JavaTimeModule()) // 支持 LocalDate/LocalDateTime .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 时间转字符串 .build(); objectMapper.deactivateDefaultTyping(); //核心代码 serializer.setObjectMapper(objectMapper); redisTemplate.setKeySerializer(RedisSerializer.string()); redisTemplate.setHashKeySerializer(RedisSerializer.string()); redisTemplate.setValueSerializer(serializer); // 使用上面配好的 redisTemplate.setHashValueSerializer(serializer); // Hash 也保持一致 redisTemplate.afterPropertiesSet(); //生效配置 return redisTemplate; } } \`\`\` 写入json对象 \`\`\` ValueOperations opt = redisTemplate.opsForValue(); UserInfoVo userInfoVo = UserInfoVo.builder().username("user1").password("PASSWORD").userId("1").build(); opt.set("userinfo",userInfoVo); \`\`\` 获取json对象 \`\`\` @Autowired private ObjectMapper objectMapper; //注入一个objectMapper用于转换 ... // 1. 先取出来,用 Object 接收 Object result = opt.get("userinfo"); // 2. 用之前配置好的 ObjectMapper 转成目标类 // 注意:你需要把 RedisConfig 里那个 objectMapper 注入到当前类中 UserInfoVo avo = objectMapper.convertValue(result, UserInfoVo.class); \`\`\` 3、自增加计数器 \`\`\` opt.increment("num"); \`\`\` 4、自减计数器 \`\`\` opt.decrement("num"); \`\`\` 5、分布式锁(无过期时间) \`\`\` opt.setIfAbsent("lock","lock")); \`\`\` 6、有过期时间(安全的) \`\`\` opt.setIfAbsent("wc1","有人", 15, TimeUnit.SECONDS) // 与命令行的 SET wc1 "有人" NX EX 15000 \`\`\` 7、释放锁(不安全) \`\`\` rediesTemplate.delete("key") //直接删除是非原子性,并非安全 \`\`\` \`\`\` String value = redisTemplate.opsForValue().get("redisLock"); //判断这个锁是否属于自己 if (value.equals("threadId-100")) { //如果是,删除锁 redisTemplate.delete("redisLock"); } \`\`\` 8、\*\*正确的释放锁(重点)\*\* - lua脚本的释放锁,更加的安全,一条命令是原子性的 - KEYS\[1\]代表的是加锁的key,ARGV\[1\]代表的是加锁的value - ARGV\[1\]一般用当前线程id;因为 lua脚本有判断当前的key是否和value匹配, - 用线程id就可以保障,加锁和解锁都是同一个线程;避免A线程加锁,被B线程释放掉,增加了是否是当前线程判断 KEYS\[1\]) == ARGV\[1\]。 - 完整写法如下 \`\`\` public String redisUnLock(){ String luaScript = "if redis.call('get', KEYS\[1\]) == ARGV\[1\] then " + " return redis.call('del', KEYS\[1\]) " + "else " + " return 0 " + "end"; DefaultRedisScript redisScript = new DefaultRedisScript\<\>(); redisScript.setScriptText(luaScript); redisScript.setResultType(Long.class); // 原子性地执行脚本 Long result = (Long) redisTemplate.execute(redisScript, Arrays.asList("redisLock"), "threadId-100"); } \`\`\` 加锁的时候同样需要传递加锁线程的threadId-100 \`\`\` public String redisLock(){ System.out.println("有过期时间的分布式锁测试:"+redisTemplate.opsForValue().setIfAbsent("redisLock","threadId-100", 15, TimeUnit.SECONDS)); return "redisLock"; } \`\`\` 放token的时候注意一下key的取名字: com:woniu:user:manage:token {"id",uname,.....} get("com:woniu:user:manage:"+com:woniu:user:manage:token); TTL \`\`\` //单个kv ValueOperations opt = redisTemplate.opsForValue(); opt.set("token","userinfo"); System.out.println("set了一个test,获取试试:"+opt.get("token")); System.out.println("判断某一个key是否存在:"+redisTemplate.hasKey("test")); //+1 redisTemplate.opsForValue().increment("num"); //-1 redisTemplate.opsForValue().decrement("num"); //分布式锁 System.out.println("分布式锁测试:"+redisTemplate.opsForValue().setIfAbsent("lock","lock")); //有过期时间的分布式锁 //System.out.println("有过期时间的分布式锁测试:"+redisTemplate.opsForValue().setIfAbsent("wc1","有人", Duration.ofSeconds(30))); System.out.println("有过期时间的分布式锁测试:"+redisTemplate.opsForValue().setIfAbsent("wc1","有人", 15, TimeUnit.SECONDS));//等同于 SET key wc1 NX EX 3000 //json 方式存储 UserInfo userInfo = new UserInfo(); userInfo.setId(1); userInfo.setName("test"); userInfo.setAge(18); redisTemplate.opsForValue().set("userInfo",userInfo); \`\`\` 补充: \`\`\` // RedisTemplate 方法 对应 Redis 命令 opsForValue().set("key", "value") → SET key value opsForValue().get("key") → GET key opsForValue().increment("key") → INCR key opsForValue().append("key", "val") → APPEND key val opsForValue().setIfAbsent("k", "v")→ SETNX k v opsForValue().multiSet(map) → MSET key1 val1 key2 val2 opsForValue().getAndSet("k", "v") → GETSET k v \`\`\` ## 2、Hash 购物车 key (key value) 1、生成一个操作hash的对象 \`\`\` //单个操作 HashOperations value = redisTemplate.opsForHash(); \`\`\` 2、用这个对象写入redis 格式: value.put(key,key,value) \`\`\` value.put("user1","id","1"); \`\`\` 3、批量写入用java的map \`\`\` //组装好map Map map = new HashMap\<\>(); map.put("id","2"); map.put("name","daiwei2"); map.put("age","19"); //一次性写入 value.putAll("user2",map); \`\`\` 4、模拟购物车也一样 value.put(key,key,value) \`\`\` value.put("shopcart:user:1","苹果手机",1); value.put("shopcart:user:1","PS5",1); value.put("shopcart:user:1","小米su7",1); \`\`\` 5、查询hash内容 value.entries(key); \`\`\` //返回所有 value.entries("shopcart:user:1"); \`\`\` 6、增加购物车 value.increment(key,key,num),num代表增加的数量 \`\`\` value.increment("shopcart:user:1","小米su7",1); //+1 \`\`\` 7、查看购物车数量 value.size(key) \`\`\` //查看购车的商品数量 value.size("shopcart:user:1"); \`\`\` 8、删除购物车里的商品 value.delete(key,key) \`\`\` //删除购车商品 value.delete("shopcart:user:1","苹果手机"); \`\`\` \`\`\` /\*\* \* hash数据类型 适合购物车等场景 \* @return \*/ @RequestMapping("/testHash") public String testHash() { //单个操作 HashOperations value = redisTemplate.opsForHash(); value.put("user1","id","1"); value.put("user1","name","daiwei"); value.put("user1","age","18"); //keymap Map map = new HashMap\<\>(); map.put("id","2"); map.put("name","daiwei2"); map.put("age","19"); value.putAll("user2",map); //购物车 value.put("shopcart:user:1","苹果手机",1); value.put("shopcart:user:1","PS5",1); value.put("shopcart:user:1","小米su7",1); //返回所有 value.entries("shopcart:user:1"); //+1 1称为步长可以自己定义 value.increment("shopcart:user:1","小米su7",1); //查看购车的商品数量 value.size("shopcart:user:1"); //删除购车商品 value.delete("shopcart:user:1","苹果手机"); return "RedisHash Test..."; } \`\`\` \`\`\` //补充 // RedisTemplate 方法 对应 Redis 命令 opsForHash().put("hkey", "field", "val") → HSET hkey field val opsForHash().get("hkey", "field") → HGET hkey field opsForHash().entries("hkey") → HGETALL hkey opsForHash().keys("hkey") → HKEYS hkey opsForHash().values("hkey") → HVALS hkey opsForHash().delete("hkey", "field") → HDEL hkey field opsForHash().hasKey("hkey", "field") → HEXISTS hkey field opsForHash().increment("hkey", "f", 1) → HINCRBY hkey f 1 \`\`\` ## 3、List链表 双向连接 队列 先进先出 栈 先进后出 \`\`\` /\*\* \* list 双向链表 ,适合聊天记录 \* @return \*/ @RequestMapping("/testList") public String testList() { ListOperations value = redisTemplate.opsForList(); //从left开始放数据 value.leftPush("list1",1); value.leftPush("list1",2); value.leftPush("list1",3); value.leftPush("list1",4); //从左出栈 System.out.println("出栈 leftpop:"+value.leftPop("list1")); //从右出栈 System.out.println("出栈 rightpop:"+value.rightPop("list1")); //查看list System.out.println("查看list:"+value.size("list1")); return "RedisList Test..."; } \`\`\` 补充 \`\`\` // RedisTemplate 方法 对应 Redis 命令 opsForList().leftPush("list", "val") → LPUSH list val opsForList().rightPush("list", "val") → RPUSH list val opsForList().leftPop("list") → LPOP list opsForList().rightPop("list") → RPOP list opsForList().range("list", 0, -1) → LRANGE list 0 -1 opsForList().size("list") → LLEN list opsForList().index("list", 0) → LINDEX list 0 opsForList().set("list", 0, "newVal") → LSET list 0 newVal opsForList().trim("list", 0, 5) → LTRIM list 0 5 \`\`\` ## 4、Set集合 \*\*不重复的数据,没有顺序\*\*;\*\*使用场景有哪些朋友关注过,或者点过赞\*\* 1、获取Set操作对象 \`\`\` SetOperations value =redisTemplate.opsForSet(); \`\`\` 2、向集合添加内容 add value.add(key,value); \`\`\` value.add("set1","苹果"); \`\`\` 3、删除集合里的内容remove \`\`\` value.remove("set1","香蕉"); \`\`\` 4、查看集合有哪些数据members \`\`\` value.members("set1") \`\`\` 5、判断是否存在某一个成员isMember \`\`\` value.isMember("set1","香蕉") \`\`\` 6、集合的数量size \`\`\` System.out.println(value.size("set1")); \`\`\` 完整demo如下: \`\`\` /\*\* \* 集合无序不重复 \* \* @return \*/ @RequestMapping("/testSet") public String testSet() { SetOperations value =redisTemplate.opsForSet(); //向集合添加内容 value.add("set1","苹果"); value.add("set1","香蕉"); value.add("set1","橘子"); value.add("set1","香蕉"); //查看集合 System.out.println(value.members("set1")); //删除 value.remove("set1","香蕉"); //查看集合 System.out.println(value.members("set1")); //判断是否是成员 System.out.println(value.isMember("set1","香蕉")); //查看集合大小 System.out.println(value.size("set1")); return "RedisSet Test..."; } \`\`\` 补充 \`\`\` // RedisTemplate 方法 对应 Redis 命令 opsForSet().add("set", "val1", "val2") → SADD set val1 val2 opsForSet().members("set") → SMEMBERS set opsForSet().isMember("set", "val") → SISMEMBER set val opsForSet().remove("set", "val") → SREM set val opsForSet().size("set") → SCARD set opsForSet().pop("set") → SPOP set opsForSet().move("set1", "val", "set2") → SMOVE set1 set2 val opsForSet().intersect("set1", "set2") → SINTER set1 set2 opsForSet().union("set1", "set2") → SUNION set1 set2 opsForSet().difference("set1", "set2") → SDIFF set1 set2 \`\`\` ## 5、ZSet集合 \*\*排行榜,有一个scorse,有排序功能\*\* 1、获取Zset操作对象 \`\`\` ZSetOperations value = redisTemplate.opsForZSet(); \`\`\` 2、写入操作add value.add(key,value,score) \`\`\` value.add("zset1","苹果",1); \`\`\` 3、查看集合数量size \`\`\` value.size("zset1") \`\`\` 4、增加数量或分值 incrementScore \`\`\` value.incrementScore("zset1","苹果",1); //加1 \`\`\` 5、正排数据range \`\`\` value.range("zset1",0,-1) \`\`\` 6、倒排数据reverseRange \`\`\` value.reverseRange("zset1",0,-1) \`\`\` 7、交集 \`\`\` value.intersectAndStore("zset1","zset2","zset3") 第一个参数代表的是,原有的set1 第二个参数代表的是,原有的set2 第三个参数代表的是,合并以后的交集的set3 \`\`\` 8、并集 \`\`\` value.unionAndStore("zset1","zset2","zset4") \`\`\` \`\`\` @RequestMapping("/testZSet") public String testZSet() { ZSetOperations value = redisTemplate.opsForZSet(); value.add("zset1","苹果",1); value.add("zset1","香蕉",3); value.add("zset1","橘子",4); value.add("zset1","西瓜",2); value.add("zset1","芒果",6); value.add("zset1","葡萄",5); value.add("zset1","柠檬",6); value.add("zset2","葡萄",1); value.add("zset2","柠檬",2); value.add("zset2","大冬瓜",9); //查看集合 System.out.println(value.size("zset1")); //增加数量 +1 value.incrementScore("zset1","苹果",1); //正序排列 range 从 0开始 System.out.println(value.range("zset1",0,-1)); //倒序排列 reverseRange System.out.println(value.reverseRange("zset1",0,-1)); //求交集 System.out.println(value.intersectAndStore("zset1","zset2","zset3")); //求并集 System.out.println(value.unionAndStore("zset1","zset2","zset4")); return "RedisZSet Test..."; } \`\`\` 补充 \`\`\` // RedisTemplate 方法 对应 Redis 命令 opsForZSet().add("zset", "val", 1.0) → ZADD zset 1.0 val opsForZSet().range("zset", 0, -1) → ZRANGE zset 0 -1 opsForZSet().reverseRange("zset", 0, -1)→ ZREVRANGE zset 0 -1 opsForZSet().score("zset", "val") → ZSCORE zset val opsForZSet().rank("zset", "val") → ZRANK zset val opsForZSet().reverseRank("zset", "val") → ZREVRANK zset val opsForZSet().count("zset", 1, 5) → ZCOUNT zset 1 5 opsForZSet().remove("zset", "val") → ZREM zset val opsForZSet().incrementScore("zset","v",1)→ ZINCRBY zset 1 v \`\`\`