# Redis **Repository Path**: TFboy630/redis ## Basic Information - **Project Name**: Redis - **Description**: Redis的入门学习,SpringBoot-Redis 集成学习 Redis的概念,优点,缺点,定位,与mysql 的区别 Redis的事务,Redis的可持久化机制策略,Redis的内存淘汰策略和过期key的处理 以及Redis 的key , value 的设计思想 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2022-04-13 - **Last Updated**: 2023-10-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README Redis 1.Redis的概念 No only SQL: 也称为非关系型数据库 Redis安装完成后,默认会有16张表在数据库里面 redis 是以key-value键值对的形式存储的, 是非关系型的数据库,和传统的关系型数据库不一样 优点: 1>对数据高并发读写(直接在内存中进行读写,效率高) 读的速度:11W/s 写的速度:8w/s 2>对海量数据的高效率存储和访问 3>对数据的可拓展性和高可用性 4>单线程操作,每个操作都是原子操作,没有并发相关的问题,在redis6版本前,redis都是单线程操作的,没有并发问题,在版本6后,redis是出现了多线程操作。 缺点: redis(ACID处理非常简单)事务处理简单 无法做太复杂的关系数据库模型 定位: redis的定位的是缓存,提高数据的读写能力,减轻数据库的保存和访问压力 项目中涉及到缓存,我们优先考虑方案是:Redis 缓存:临时存储(数据可能会丢失),重要的数据不建议放在缓存中 与mysql的区别: 1>redis是非关系型数据库,是以 key-value键值对的形式存储,不同于传统的关系型数据库 2>redis的读写效率非常高,属于单线程操作,不会出现并发问题(redis6) 3>对于海量数据的处理,redis的能力更加出色 2.Redis的常用数据类型命令 使用Jedis进行操作 1>String 类型 set 添加 get 查询 setex 设置存活时间 ttl 查询剩余时间 setnx 判断key是否存在,存在不做任何操作,不存在则直接添加 public class JedisStringTest { public static void main(String[] args) { // 1:创建Jedis连接池 JedisPool pool = new JedisPool("localhost", 6379); // 2:从连接池中获取Jedis对象 Jedis jedis = pool.getResource(); /* 设置密码 jedis.auth(密码); */ //String类型 //存入键值对 jedis.set("name","dafei"); //根据键取出值 System.out.println(jedis.get("name")); //dafei jedis.set("age","18"); System.out.println(jedis.get("age")); //18 System.out.println("============="); //把值自增1 incr jedis.incr("age"); System.out.println(jedis.get("age")); //19 //把值自减1 decr jedis.decr("age"); System.out.println(jedis.get("age")); //18 System.out.println("====================="); jedis.set("sex","man"); System.out.println(jedis.get("sex")); //man //根据键值删除对应的键值对 jedis.del("sex"); System.out.println(jedis.get("sex")); //null System.out.println("============="); //setex key timeout value 存入键值对,并设置存活时间,单位为s jedis.setex("gen",5,"girl"); //根据key 查询还剩余存活的时间 ttl key System.out.println(jedis.ttl("gen")); //5s System.out.println("============="); //setnx key value 如果当前key已经存在,不做任何操作,如果不存在,直接添加 //key存在,不做任何操作 jedis.setnx("gen","boy"); System.out.println(jedis.get("gen")); //girl //key不存在,直接添加 jedis.setnx("sex","boy"); System.out.println(jedis.get("sex")); //boy /**setnx和set 有什么区别? * 1>setnx 会判断当前的key是否存在,存在不做任何操作,不存在就直接添加 * 2>set 是不会判断当前key是否存在的,如果已经存在,那么就会覆盖相同key的值,不存在就创建 */ // 4:关闭资源 jedis.close(); pool.destroy(); } } 2>hash类型 hset 添加 hget 查询 hdel 删除 hexists 是否包含某个对象 public class JedisHashTest { public static void main(String[] args) { // 1:创建Jedis连接池 JedisPool pool = new JedisPool("localhost", 6379); // 2:从连接池中获取Jedis对象 Jedis jedis = pool.getResource(); /* 设置密码 jedis.auth(密码); */ //hash类型 Map> map Map map = new HashMap<>(); //map.put("age","18"); map.put("sex","boy"); //存入一个hash对象 hset //注意:hset方法可以传入map,也可以直接传入参数,但是传入map的话,会出现异常 jedis.hset("user", "name","dafei"); jedis.hset("user", map); //根据hash对象键取值 hget System.out.println(jedis.hget("user", "name")); //dafei System.out.println(jedis.hget("user", "sex")); //boy //System.out.println(jedis.hget("user", "age")); System.out.println("========================"); //判断hash对象是否含义某个key键 hexists (对象key 对象的某个key) System.out.println(jedis.hexists("user", "name")); //true System.out.println(jedis.hexists("user", "age")); //true System.out.println(jedis.hexists("user", "max")); //false //根据hashkey 删除hash对象键值对 hdel key hashkey System.out.println(jedis.hdel("user", "sex")); //1 System.out.println(jedis.hget("user", "sex")); //null //判断对象是否存在某个key,存在不创建,不存在就直接添加 //hstenx jedis.hsetnx("user","gen","xiaofang"); System.out.println(jedis.hget("user", "gen")); /**小结:对于hash数据类型的使用 Map> map * 由于hash 特别适合存储对象,所以一般用于存储对象 * key 表示对象的key hashKey 表示 键值key * 1>存入一个hash对象,hset(key,hashKey,value) * 2>根据hask对象的键值,取值 hget(key,hashKey) * 3>判断hash对象,是否包含某个键 hexists(key,hashKey) * 4>根据hashKey删除hash对象键值对 hdel(key,hashkey) * 对于hash 数据类型的使用,需要注意,在hset方法中,可以传入map集合进去,也可以直接传递值进去 * 但是如果传入的是map,那么map中只能存储一个值 * 还有时刻注意,key 在每个方法中都是需要用上的 * hash同样有hsetnx方法,作用和用法是一样的 */ // 4:关闭资源 jedis.close(); pool.destroy(); } } 3>list类型 Push 添加 left 左 right 右 pop 弹出 llen 长度 public class JedisListTest { public static void main(String[] args) { // 1:创建Jedis连接池 JedisPool pool = new JedisPool("localhost", 6379); // 2:从连接池中获取Jedis对象 Jedis jedis = pool.getResource(); /* 设置密码 jedis.auth(密码); */ //list的设计非常精巧,即可以作为栈也可以作为队列 //list数据类型: Map,一般用于存储多个数据,并且数据允许重复有序 //注意:在IDEA中使用jedis操作,list类型,方法名是补全的,不同于命令行的缺失 //rpush--往列表右边添加数据 jedis.rpush("List", "1","2","3","4","5"); //lrange key 0 -1 --范围显示列表数据,全显示则设置 0 -1 System.out.println(jedis.lrange("List", 0, -1)); //[1,2,3,4,5] //lpush key value --往列表左边添加数据 jedis.lpush("List","a"); System.out.println(jedis.lrange("List", 0, -1));//[a,1,2,3,4,5] //lpop key 弹出列表最左边的数据 System.out.println(jedis.lpop("List")); //a //弹出等于删除 //rpop key 弹出列表最右边的数据 System.out.println(jedis.rpop("List")); //5 //llen key 获取列表长度 System.out.println(jedis.llen("List")); //4 /**小结:对于list类型的使用 * 1>rpush key-- 往列表右边添加数据 * 2>lpush key -- 往列表左边添加数据 * 3>lpop key-- 弹出列表最左边的数据 * 4>rpop key-- 弹出列表最右边的数据 * 5>llen key -- 获取列表长度 * 注意事项:往列表中添加数据的时候,可以单个添加 * 也可以多个添加,但是多个添加需要使用, 分割 * 例:"a","b","c","d",否则挤在一起就是一个字符串 * 还有:弹出等于删除操作 * 注意:key一定要唯一 */ // 4:关闭资源 jedis.close(); pool.destroy(); } } 出现的异常信息: 造成的原因:是因为存放key 的Map存在了相同的key,造成了异常错误信息 解决方法:key必须保持唯一 Exception in thread "main" redis.clients.jedis.exceptions.JedisDataException: WRONGTYPE Operation against a key holding the wrong kind of value 4>set类型 方法规律:一般以 S 开头 sadd 添加 smembers 查询 srem 删除 sdiff 差集 sinter 交集 sunion 并集 public class JedisSetTest { public static void main(String[] args) { // 1:创建Jedis连接池 JedisPool pool = new JedisPool("localhost", 6379); // 2:从连接池中获取Jedis对象 Jedis jedis = pool.getResource(); /* 设置密码 jedis.auth(密码); */ //Set 数据类型: Map 一般用于多个数据的存储,要求数据不能重复且无序 //set 集合显示的结果是无序的 //往set集合中添加元素 sadd key value jedis.sadd("set","a","b","c","d","e"); //列出set集合中的元素 smembers key System.out.println(jedis.smembers("set")); //删除set集合中的元素 srem key value //jedis.srem("set","e"); //System.out.println(jedis.smembers("set")); //随机弹出集合中的元素 spop key count(个数) jedis.spop("set",1); System.out.println(jedis.smembers("set")); //返回两个集合中的差集(特有元素) jedis.sadd("set1","a","b","c","d"); jedis.sadd("set2","c","d","e","f"); //sdiff key1 key2 --返回两个集合中的差集(特有元素) //注意:是以第一个key 作为参考 //例如:set1 中和set2 比较,显示ste1中特有的元素 System.out.println(jedis.sdiff("set1", "set2")); System.out.println(jedis.sdiff("set2", "set1")); //sinter key1 key2 --返回2个集合中特有的交集 System.out.println(jedis.sinter("set1", "set2")); //sunion key1 key2 --返回两个集合中的并集 System.out.println(jedis.sunion("set1", "set2")); //scard key-- 返回集合汇总的元素个数 System.out.println(jedis.scard("set1")); System.out.println(jedis.scard("set2")); /**小结:Set类型的使用 * 1> sadd key 往set集合中添加元素 * 2> smembers key 列出set集合中的元素 * 3> srem key value 删除set集合中某个元素 * 4> spop key count 随机弹出set集合中元素和个数 * 5> sdiff key1 key2 返回key1集合中特有的元素(差集) * 6> sinter key1 key2 返回两个set集合中的相同的元素(并集) * 7> sunion key1 key2 返回两个集合中所有的元素(并集) * 8> scard key 返回集合中元素的 个数 * 注意:因为是Set集合存储,所以存储的元素是唯一的,且查询是无序的 */ // 4:关闭资源 jedis.close(); pool.destroy(); } } 5>sorted_set类型(一般应用于,实时数据排行,抽奖等)去重 zadd key score column 添加分数和名称 zincrby key score column 偏移名称对应的分数 zrange key start end 按照分数升序输出名称 0 -1 表示输出全部 zrevange key start end 按照分数降序输出名称 名称range zrank key name 升序返回排名 索引从0开始 排名rank zrevrank key name 降序返回排名 降序 rev zcard key 返回元素个数 zscore key 返回指定元素的score值 public class JedisSorted_setTest { public static void main(String[] args) { // 1:创建Jedis连接池 JedisPool pool = new JedisPool("localhost", 6379); // 2:从连接池中获取Jedis对象 Jedis jedis = pool.getResource(); /* 设置密码 jedis.auth(密码); */ //sorted_set类型:一般用于实时排行的应用场景,例如微博热搜等 //存入分数和名称 zadd key score(分数) column(对象) jedis.zadd("players",3000,"A"); jedis.zadd("players",3000,"B"); jedis.zadd("players",3000,"C"); jedis.zadd("players",3000,"D"); jedis.zadd("players",3000,"E"); //zincrby key score column 偏移名称对应的分数(给对应的对象添加分数) jedis.zincrby("players",2000,"C"); jedis.zincrby("players",2000,"D"); // zrange key start end 0 -1 按照分数升序输出名称 System.out.println(jedis.zrange("players", 0, -1)); //[A, B, E, C, D] // zrevrage key start end 0 -1 显示全部 按照分数降序输出名称 System.out.println(jedis.zrevrange("players", 0, -1)); //[D, C, E, B, A] // zrank key name -- 升序返回排名 从0 开始 System.out.println(jedis.zrank("players", "C")); //3 //zrevrank key name 降序返回排名 System.out.println(jedis.zrevrank("players", "A")); //4 //zcard key 返回集合元素个数 System.out.println(jedis.zcard("players")); //5 //zrangebyscore key min max [winthscores] 按照分数范围升序输出名称 System.out.println(jedis.zrangeByScore("players", 4000, 5000)); //[C, D] //zscore 获取sorted_set中指定元素的 score值 System.out.println(jedis.zscore("players", "C")); //5000.0 System.out.println(jedis.zscore("players", "D")); //5000.0 // 4:关闭资源 jedis.close(); pool.destroy(); } } 3.Redis 高级命令 keys("*") 返回满足的所有键keys * (可以模糊匹配) exisit 是否存在某个指定key expire 设置某个key的过期时间,使用ttl查看剩余时间 persist 取消过期时间 flushdb 清空当前数据库 flushall 清空所有数据库 dbsize 查询数据库的key 数量 info 获取数据库信息 public class JedisExpertTest { public static void main(String[] args) { // 1:创建Jedis连接池 JedisPool pool = new JedisPool("localhost", 6379); // 2:从连接池中获取Jedis对象 Jedis jedis = pool.getResource(); /* 设置密码 jedis.auth(密码); */ //redis的高级命令 //1.返回满足的所有键 keys * (可以模糊匹配查询) System.out.println(jedis.keys("*")); //[plyers, a, zai, set, set2, players, sex, name, set1, List, leftList, list, user, myset, age, hobby] //exists 是否存在某个指定key // 如果传入的是多个key,返回的是数量,如果传入一个,返回boolean System.out.println(jedis.exists("name")); //true System.out.println(jedis.exists("name","age","xxx")); //expire 设置某个key 的过期时间,使用ttl查看剩余时间 单位为s jedis.expire("age",30); System.out.println(jedis.ttl("age")); //30s //persist 取消过期时间 -1 表示未失效,-2表示已失效 jedis.persist("age"); System.out.println(jedis.ttl("age")); //-1 //dbsize 查看数据库的key数量 System.out.println(jedis.dbSize()); //16 //info 查询数据库信息 System.out.println(jedis.info()); // 4:关闭资源 jedis.close(); pool.destroy(); } } 4.Redis的事务操作(ACID) redis的事务:既可以说支持,也可以说不支持 面试题: redis的单个命令是原子性的,但是redis的事务操作,实际上是把多个命令 合成一个大的脚本去执行,不会因为其中一个命令出错,去进行回退,和影响到后面其他命令的执行 大白话: 事务可以理解为一个打包的批量执行脚本,但批量指令并非原子化的操作,也就是说,如果中途发生异常,不会进行回滚,也不会影响到下面的指令执行 5.Redis持久化机制策略(默认为 RDB策略) 面试题:在你们公司,Redis的持久化机制策略一般选择什么? 回答:如果知道的情况下,将优点和缺点一一列出,如果不知道,那么就说默认 Redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到硬盘中,来保证持久化(也就是保证数据的完整性) Redis持久化的方式有两种 RDB方式 优点: 可以有三种保存的记录选择,第一个为在900秒内,监测到超过一个key 的变化,就会将数据写入到数据库中,第二个以此类推,这样可以很快的将数据从内存中写入到数据库保存 缺点: 1.需要从主线程分出一个子线程去进行写的操作,如果是大量的数据,会消耗性能 2.如果是在写的过程中发生了异常,那么就会导致数据丢失,例如是断电2分钟,那么就会丢失2分钟的数据,数据的完整性受到了很大的挑战 1.snapshotting(快照)默认方式.将内存中以快照的方式写入到二进制文件中.默认为dump.rdb.可以配置设置自动做快照持久化方式.我们可以配置redis在n秒内如果超过m个key就修改自动做快照. Snapshotting设置: save 900 1 #900秒内如果超过1个Key被修改则发起快照保存 save 300 10 #300秒内如果超过10个key被修改,则发起快照保存 save 60 10000 AOF方式: 一般选用 每秒写入磁盘一次 优点: 可以最大限度的保证数据的完整性 缺点: 因为是将写操作命令存在临时文件中,在重启redis的过程中,会需要去临时文件中,将命令操作读取到Redis中,长期下来,临时文件臃肿,导致加载临时文件数据的时候,效率很慢 2.append-only file (缩写aof)的方式,由于快照方式是在一定时间间隔做一次,所以可能发生reids意外down的情况就会丢失最后一次快照后的所有修改的数据.aof比快照方式有更好的持久化性,是由于在使用aof时,redis会将每一个收到的写命令都通过write函数追加到命令中,当redis重新启动时会重新执行文件中保存的写命令来在内存中重建这个数据库的内容.这个文件在bin目录下:appendonly.aof aof不是立即写到硬盘中,可以通过配置文件修改强制写到硬盘中. aof设置: appendonly yes //启动aof持久化方式有三种修改方式 #appendfsync always//收到命令就立即写到磁盘,效率最慢.但是能保证完全的持久化 #appendfsync everysec//每秒写入磁盘一次,在性能和持久化方面做了很好的折中 #appendfsync no //完全以依赖os 性能最好,持久化没保证 6.Redis 过期 key 淘汰机制及过期key处理 Redis内存淘汰机制   Redis内存淘汰机制是指当内存使用达到上限(可通过maxmemory配置,0为不限制,即服务器内存上限),根据一定的算法来决定淘汰掉哪些数据,以保证新数据的存入。   常见的内存淘汰机制分为四大类:   1. LRU:LRU是Least recently used,最近最少使用的意思,简单的理解就是从数据库中删除最近最少访问的数据,该算法认为,你长期不用的数据,那么被再次访问的概率也就很小了,淘汰的数据为最长时间没有被使用,仅与时间相关。   2. LFU:LFU是Least Frequently Used,最不经常使用的意思,简单的理解就是淘汰一段时间内,使用次数最少的数据,这个与频次和时间相关。   3. TTL:Redis中,有的数据是设置了过期时间的,而设置了过期时间的这部分数据,就是该算法要解决的对象。如果你快过期了,不好意思,我内存现在不够了,反正你也要退休了,提前送你一程,把你干掉吧。   4. 随机淘汰:生死有命,富贵在天,是否被干掉,全凭天意了。   通过maxmemroy-policy可以配置具体的淘汰机制,看了网上很多文章说只有6种,其实有8种,可以看Redis5.0的配置文件,上面有说明:   1. volatile-lru -> 找出已经设置过期时间的数据集,将最近最少使用(被访问到)的数据干掉。   2. volatile-ttl -> 找出已经设置过期时间的数据集,将即将过期的数据干掉。   3. volatile-random -> 找出已经设置过期时间的数据集,进行无差别攻击,随机干掉数据。   4. volatile-lfu -> 找出已经设置过期时间的数据集,将一段时间内,使用次数最少的数据干掉。   5. allkeys-lru ->与第1个差不多,数据集从设置过期时间数据变为全体数据。   6. allkeys-lfu -> 与第4个差不多,数据集从设置过期时间数据变为全体数据。   7. allkeys-random -> 与第3个差不多,数据集从设置过期时间数据变为全体数据。   8. no-enviction -> 什么都不干,报错,告诉你内存不足,这样的好处是可以保证数据不丢失,这也是系统默认的淘汰策略。 过期key的处理: Redis过期Key清除策略   Redis中大家会对存入的数据设置过期时间,那么这些数据如果过期了,Redis是怎么样把他们消灭掉的呢?我们一起来探讨一下。下面介绍三种清除策略:   惰性删除:当访问Key时,才去判断它是否过期,如果过期,直接干掉。这种方式对CPU很友好,但是一个key如果长期不用,一直存在内存里,会造成内存浪费。   定时删除:设置键的过期时间的同时,创建一个定时器,当到达过期时间点,立即执行对Key的删除操作,这种方式最不友好。   定期删除:隔一段时间,对数据进行一次检查,删除里面的过期Key,至于要删除多少过期Key,检查多少数据,则由算法决定。举个例子方便大家理解:Redis每秒随机取100个数据进行过期检查,删除检查数据中所有已经过期的Key,如果过期的Key占比大于总数的25%,也就是超过25个,再重复上述检查操作。   Redis服务器实际使用的是惰性删除和定期删除两种策略:通过配合使用这两种删除策略,可以很好地在合理使用CPU和避免浪费内存之间取得平衡。 负载均衡服务器(Nginx): 共享session:把相关的登录信息,存入到缓存层中,当需要进入登录的时候,直接去缓存层中获取即可 缓存层(redis): 经典运用场景:session共享 7.java操作Redis jedis springBoot-redis 使用Spring-Boot-redis进行操作 准备工作: 1>导入相关的依赖 org.springframework.boot spring-boot-starter-data-redis 2>因为是使用 StringRedisTemplate 继承 RedisTemplate 在RedisTemplate中,需要明确指定泛型,所以在使用的过程中StringRedisTemplate的类型为StringRedisTemplate 3>所以数据类型的命令都需要通过StringRedisTemplate 去调用方法 opsForValue //String类型 opsForHahs //hash类型 opsForList //List类型 opsForSet //Set类型 opsForZSet //sorted_set类型 1>String类型: @SpringBootTest(classes = App.class) //因为当前测视类和启动类编译后的路径不在同一包下,所以需要告诉Spring容器,将当前对象一起加载到容器中进行管理 public class StringRedisTest { @Autowired private StringRedisTemplate template; @Test public void test1() throws InterruptedException { //字符串数据类型: opsForValue.xx() //set key value 存入键值对 template.opsForValue().set("name","dafei"); // get key 根据键,取值 System.out.println(template.opsForValue().get("name")); System.out.println("========================="); template.opsForValue().set("age","18"); System.out.println(template.opsForValue().get("age")); // increment key 把值自增1 template.opsForValue().increment("age"); System.out.println(template.opsForValue().get("age")); // decrement key 把值递减1 template.opsForValue().decrement("age"); System.out.println(template.opsForValue().get("age")); System.out.println("========================="); // delete 根据键值删除 键值对 //直接通过 template 进行删除 //template.delete("age"); //template.delete("name"); System.out.println(template.opsForValue().get("age")); System.out.println(template.opsForValue().get("name")); System.out.println("========================="); //setex key value 存入键值对,tiemout表示失效时间 ,单位为s //set TimeUnit 表示 指定时间单位 60 * 2 =120s template.opsForValue().set("sex","girl", 60 * 2,TimeUnit.SECONDS); System.out.println(template.opsForValue().get("sex")); System.out.println("========================="); //获取失效时间 getExpire(tll) Thread thread = new Thread(); //线程睡眠 毫秒为单位 1000 表示1秒 //睡眠5秒 ,在进行操作 //thread.sleep(5 * 1000); //打印的是毫秒单位 //-1 表示当前key 未失效 -2 表示已失效 System.out.println(template.getExpire("sex")); //120s System.out.println(template.boundValueOps("sex").getExpire()); System.out.println("========================="); //setnx key value 判断key是否存在,存在不做操作,不存在就直接添加 // setIfAbsent 判断key是否存在,存在不做操作,不存在就直接添加 template.opsForValue().setIfAbsent("age","27"); System.out.println(template.opsForValue().get("age")); //setIfPresent 替换值,判断当前key 的value和输入的是否一致,一致不操作,不一致替换 template.opsForValue().setIfPresent("age","27"); System.out.println(template.opsForValue().get("age")); /**小结:在springboot中,操作redis的话,需要通过StringRedisTemplate去操作 * 1.获取StringRedisTemplate 对象 * 2.因为操作的是 String 类型,所以是 opsForValue.xx * 3.添加 opsForValue.set * 4.查询opsForValue.get * 5.自增 opsForValue.increment * 6.自减 opsForValue.decrement * 7.删除 StringRedisTemplate 对象.delete * 8.设置key 的存活时间 opsForValue().set(key,value,time) * 9.查看存活时间,opsForValue().getExpire() * 10.判断当前key是否存在,opsForValue().setIfAbsent(key,value) */ } } 2>hash类型: public class HashRedisTest { @Autowired private StringRedisTemplate template; @Test public void test1() { //hash数据类型: opsForHash.xx() //put key value 存入一个hash对象 template.opsForHash().put("rng","xiaohu","2200"); //putAll 也可以存入一个Map集合 Map map = new HashMap<>(); map.put("uzi","2800"); map.put("ming","3600"); map.put("leteme","5500"); template.opsForHash().putAll("rng",map); // get 根据hash 对象键取值 System.out.println(template.opsForHash().get("rng", "xiaohu")); //2200 System.out.println(template.opsForHash().get("rng", "uzi")); //2800 System.out.println(template.opsForHash().get("rng", "ming")); //3600 System.out.println(template.opsForHash().get("rng", "leteme")); //5500 //hasKey 判断hash对象是否包含某个 System.out.println(template.opsForHash().hasKey("rng", "mlxg")); System.out.println(template.opsForHash().hasKey("rng", "kasra")); System.out.println(template.opsForHash().hasKey("rng", "zzt")); System.out.println(template.opsForHash().hasKey("rng", "xiaohu"));//true //delete key hashKey 根据hashKey 删除 hash对象中的键值对 template.opsForHash().delete("rng","leteme"); System.out.println(template.opsForHash().get("rng", "leteme")); /**小结:对于hash数据类型的使用,一般用于存储对象使用 * 1.添加操作: 单个 put ,也可以存在一个集合 map putAll * 2.查询操作: get * 3.查看集合中是否包含某个key: hasKey * 4.删除: delete(key haskkey) */ } } 3>List类型: public class ListRedisTest { @Autowired private StringRedisTemplate template; @Test public void test1() { //Lsit数据类型: opsForList.xx() //往列表右边插入数据 //单个插入 rightPush template.opsForList().rightPush("fpx","gong"); //多个插入 rightpushAll template.opsForList().rightPushAll("fpx","tian","doinb","lwx"); //范围显示列表数据,0 -1 全显示 range System.out.println(template.opsForList().range("fpx", 0, -1)); System.out.println("======================================"); //往列表左边插入数据 leftpush template.opsForList().leftPush("fpx","lqs"); System.out.println(template.opsForList().range("fpx", 0, -1)); //弹出列表最左边的数据 leftpop template.opsForList().leftPop("fpx"); //弹出列表最右边的数据 rightPop template.opsForList().rightPop("fpx"); System.out.println(template.opsForList().range("fpx", 0, -1)); //获取列表长度 size System.out.println(template.opsForList().size("fpx")); /**Lsit类型小结:一般用于存储多个数据,并且数据允许重复 * 在List类型中,springboot -List类型的方法就是redis命令的全称 * 1>列表右边添加数据:rightPush * 2>列表左边添加数据:leftPush * 3>添加多个数据: left/rightPushAll * 4>弹出列表左边的数据:leftpop * 5>弹出列表右边的数据:rightPop * 6>获取列表长度: size * 7>显示列表数据元素:range 0 -1 表示显示全部 * 注意:弹出等于删除 */ } } 4>Set类型: public class SetRedisTest { @Autowired private StringRedisTemplate template; @Test public void test1() { //Set数据类型: opsForSet.xx() //往set集合中添加单个元素 sadd template.opsForSet().add("edg","ray"); template.opsForSet().add("edg","clearLove7"); template.opsForSet().add("edg","scout"); template.opsForSet().add("edg","iboy"); template.opsForSet().add("edg","meiko"); //列出set集合中的元素 members,显示的无序的 System.out.println(template.opsForSet().members("edg")); //随机弹出set集合中的元素 randomMember System.out.println(template.opsForSet().randomMember("edg")); //随机弹出set集合中 count 个数的元素 randomMember count System.out.println(template.opsForSet().randomMembers("edg",3)); //删除set集合中的元素 remove(一个/多个元素) template.opsForSet().remove("edg","iboy","ray"); System.out.println(template.opsForSet().members("edg")); //两个集合中的差集 template.opsForSet().add("ra","iboy"); template.opsForSet().add("ra","leyan"); template.opsForSet().add("ra","fofo"); template.opsForSet().add("ra","on"); template.opsForSet().add("ra","ray"); //两个集合中的差集 diff 返回key(edg) 中 特有的元素 System.out.println(template.opsForSet().difference("edg", "ra")); //两个集合中的差集 diff 返回key(ra) 中 特有的元素 System.out.println(template.opsForSet().difference("ra", "edg")); System.out.println("============================="); //两个集合中的相同元素 inter 交集 System.out.println(template.opsForSet().intersect("edg", "ra")); //两个集合的并集 union ,两个集合的元素集合 ,相同的去掉 System.out.println(template.opsForSet().union("edg", "ra")); //返回set集合的元素个数 size System.out.println(template.opsForSet().size("ra")); /**小结:Set类型,一般用于存入多个数据并且数据不能重复,且无序 * 1>往set集合中添加元素:add key value * 2>列出set中的元素:members key * 3>删除set集合中的元素:remove key value * 4>随机弹出集合中的元素:pop key [count] 个数 注意:这里的弹出不等于删除 * 5>差集:diff set1 set2 返回set1中特有的元素 * 6>交集:inter 返回两个集合中相同的元素 * 7>并集:union 返回两个集合中全部的元素 * 8>返回set集合中的元素个数 size * 注意;set数据类型查询的结果是无序的 */ } } 5>sorted_set类型: public class Sorted_SetRedisTest { @Autowired private StringRedisTemplate template; @Test public void test1() { //Sorted_Set数据类型: opsForZSet.xx() //存入分数和名称 add template.opsForZSet().add("lng","alx",50); template.opsForZSet().add("lng","tranz",200); template.opsForZSet().add("lng","doinb",1500); template.opsForZSet().add("lng","right",300); template.opsForZSet().add("lng","lumao",100); //偏移名称对应的分数 incrementScore template.opsForZSet().incrementScore("lng","alx",200); //按照分数升序排序输出名称 range //名称range System.out.println(template.opsForZSet().range("lng", 0, -1)); //按照分数降序返回名称 reverseRange //降序 reverse System.out.println(template.opsForZSet().reverseRange("lng", 0, -1)); //升序返回某个元素的排名 //排名 rank 从0 开始 System.out.println(template.opsForZSet().rank("lng", "alx")); //2 //降序返回某个元素的排名 //降序:reversRank 从0 开始 System.out.println(template.opsForZSet().reverseRank("lng", "lumao"));//4 //返回元素个数 size System.out.println(template.opsForZSet().zCard("lng")); //5 System.out.println(template.opsForZSet().size("lng")); //5 /**小结:对于Sorted_set类型,opsForZSet 一般用于实时排行,抽奖等的运用 * 1>存入分数额名称 : add score column * 2>偏移名称对应的分数:incrementScore key value score * 3>按照分数升序输出名称:range * 4>按照分数降序输出名称:reversRange * 5>按照升序返回某个元素的排名,从0开始:rank * 6>按照降序返回某个元素的排名,从0开始:reverseRank * 7>返回元素个数:card key / size key */ } } 6>Redis的高级命令: public class ExpertRedisTest { @Autowired private StringRedisTemplate template; @Test public void test1() { //高级命令: //返回满足的所有键 keys * (模糊匹配) key System.out.println(template.keys("*")); //是否存在指定的key hasKey System.out.println(template.hasKey("lng")); System.out.println(template.hasKey("v5")); System.out.println(template.hasKey("rng")); //设置某个key 的存活时间,查询剩余存活时间 expire template.expire("rng", 10 * 2,TimeUnit.SECONDS); //查看剩余时间 getExpire System.out.println(template.getExpire("rng")); //20s //取消过期时间 persist template.persist("rng"); System.out.println(template.getExpire("rng")); //查看数据库的key数量 System.out.println(template.keys("*").size()); System.out.println(template.opsForList().size("fpx")); //清空所有的数据库 //template.delete(template.keys("*")); //清空当前数据库 //template.delete(template.opsForValue().get("name")); /**小结:对于高级命令的使用 * 1>返回满足所有键的 keys * (可以进行模糊匹配) * 2>hasKey 是否存在指定key * 3>expire 设置key 的失效时间 * 4>getExpire 查看某个key的失效时间 * 5>persist 取消某个key的失效时间 * 6>清空当前数据库:delete * 7>查看数据库的key数量:keys.size * 8>查看某个数据库的key数量:对应的类型的长度方法 */ } } 小案例:文章阅读数统计 前端: Title 阅读数0 【乘客#为少付1元车费致两公交相撞#:辱骂并拉拽驾驶员使公交车失控】5月7日16时18分
浙江宁波一男性乘客因不愿按规定缴纳车费,辱骂并强行拉拽驾驶员胳膊,
致使正常行使中的公交车失控,穿过中间绿化带与对向行驶的公交车相撞。
事故造成2名驾驶员和该肇事乘客受伤,无人员死亡。
后端: controller控制层 @RestController @RequestMapping("articles") public class ArticleController { @Autowired private IArticlesService articlesService; //接收请求 @PostMapping("/ince") public Integer ince(Long id){ return articlesService.incr(id); } } service接口实现类 @Service public class ArticlesServiceImpl implements IArticlesService { @Autowired private StringRedisTemplate template; @Override public Integer incr(Long id) { //数据自增1,使用到redis 中的对象 //这里的key 设计:主要是为了增加唯一标识性和可读性 String key = "article_viwnum:"+ id.toString(); //这里不需要去判断,是否存在key,如果不存在,那么就会创建 Long increment = template.opsForValue().increment(key); return increment.intValue(); } } 总结:redis本质上是一个Map 总体思考流程: 1>项目中是否使用到缓存技术,如果有优先考虑redis 2>如果项目选用redis技术,那么就需要考虑数据类型 3>考虑value的数据类型 4>考虑key的,唯一性,可读性,灵活性,时效性 redis的数据类型选用思考? 如果项目确定选用redis,那么接下来就是选择数据类型的选用 value的设计:根据value的数据特点去选用 1>如果是要排序,选用zset 2>如果数据是多个且允许重复,选用list 3>如果数据是多个且不允许重复,选用set 4>是否需要存对象--hash--json格式的字符串对象 hash:对象, map 或者其他 json格式的字符串对象:map.put("name", Json.toJsonString(user)); 5>最后选择String类型 key的设计: 1.唯一性 实际开发怎么保证唯一:一般数据表主键--比如用户id 2.可读性 加上可读性的前缀 --> 项目名+ 业务模块+可识别的前缀+唯一性标识: key value 例如:user_login:1 11 user_login:3 33 3.灵活性 4.时效性 需要考虑key是否是临时的,还是永久的 例如:共享的session,不是永久有效的