由于用户量增大,请求数量也随之增大,数据压力过大。
多台服务器之间,数据不同步
多台服务器之间的锁,已经不存在互斥性了
NoSQL,泛指非关系型的数据库,NoSQL即Not-Only SQL,它可以作为关系型数据库的良好补充.
相关产品:Tokyo Cabinet/Tyrant、Redis、Voldemort、Berkeley DB 典型应用:内容缓存,主要用于处理大量数据的高访问负载; 数据模型:一系列键值对; 优势:快速查询; 劣势:存储的数据缺少结构化.
相关产品:Cassandra, HBase, Riak; 典型应用:分布式的文件系统; 数据模型:以列簇式存储,将同一列数据存在一起; 优势:查找速度快,可扩展性强,更容易进行分布式扩展; 劣势:功能相对局限.
相关产品:CouchDB、MongoDB; 典型应用:Web应用(与Key-Value类似,Value是结构化的); 数据模型:一系列键值对; 优势:数据结构要求不严格; 劣势:查询性能不高,而且缺乏统一的查询语法.
相关数据库:Neo4J、lnfoGrid、lnHnite Graph; 典型应用:社交网络; 数据模型:图结构; 优势:利用图结构相关算法; 劣势:需要对整个图做计算才能得出结果,不容易做分布式的集群方案.
1.关系型数据库(表) MySQL,Oracle,SQLServer,DB2,SQLite.... 注意:关系型数据库最大的不足,就是受限于磁盘读写能力(I/O)! 2.非关系型数据库(基于内存) Redis,MongoDB,ElasticSearch,HBase.... 非关系型数据库是为了弥补关系型数据库的不足的!
①.键值对型:redis ②.列存储型:hbase ③.图形类型:neo4j ④.文档型.mongodb
Redis是一种用C语言实现的,开源的,键值对类型的非关系型数据库! Redis:REmote Dictionary Server(远程字典服务器),它是一个完全开源免费且遵守BSD协议,用C语言开发的,高性能key-value型分布式内存数据库,它是基于内存运行并支持持久化的NoSQL数据库.可用 于缓存,事件发布或订阅,高速队列等场景.该数据库使用ANSI C语言编写,支持网络,提供字符串,哈希,列表,队列,集合结构直接存取,基于内存,可持久化.Redis是当前最热门的NoSql数据库之一,也被人们称为数据结构服务器.
①.缓存(数据查询、短连接、新闻内容、商品内容等等,最常用); ②.消息队列,任务队列(秒杀、抢购、12306 等)等队列; ③.获取最新的N个数据(取最新文档、排行榜等); ④.计数器应用,网站访问统计等; ⑤.发布/订阅消息(消息通知); ⑥.数据过期处理(可以精确到毫秒),优惠券/红包等; ⑦.商品列表,评论列表,聊天室好友列表等实时性要求较高的场景; ⑧.分布式集群架构中的 session 分离和集中存储; ⑨.各种排行榜.
最常用的一种使用Redis的情景是会话缓存(session cache).用Redis缓存会话比其他存储(如 Memcached)的优势在于:Redis提供持久化.
Reids在内存存储引擎领域的一大优点是提供list和set操作,这使得Redis能作为一个很好的消息队 列平台来使用.Redis作为队列使用的操作,就类似于本地程序语言(如Python)对list的 push/pop 操作.
Redis在内存中对数字进行递增或递减的操作实现的非常好.集合(Set)和有序集合(Sorted Set) 也使得我们在执行这些操作的时候变的非常简单,Redis正好提供了这两种数据结构.
Redis的发布/订阅的使用场景非常多,经常在社交网络连接中使用,还可作为基于发布/订阅的脚本触 发器,甚至用Redis的发布/订阅功能来建立聊天系统!
①.string字符串类型; ②.hash类型; ③.list列表类型; ④.set集合类型; ⑤.zset有序集合类型; ⑥.hyperloglog计算近似值的; ⑦.GEO地理坐标类型; ⑧.bit字节类型.
Redis本质上是一个Key-Value类型的内存数据库,很像memcached,整个数据库的加载都是在内存当中进行操作,定期通过异步操作把数据库数据flush到硬盘上进行保存. 正因为是纯内存的操作,所以Redis的性能非常出色,每秒可以处理超过10万次读写操作,是已知性能最快的Key-Value DB. Redis的出色之处不仅仅是性能,它的最大的魅力是支持保存多种数据结构,此外单个value的最大限制 是1GB,不像memcached只能保存1MB的数据,因此Redis可以用来实现很多有用的功能. 比方说可以用Redis的List数据类型来做FIFO双向链表,实现一个轻量级的高性能消息队列服务;用它的Set类型可以做高性能的tag系统等.另外Redis也可以对存入的Key-Value设置expire时间,因此也可以被当作一个功能加强版的memcached来用.
1 速度快:因为redis基于内存缓存,对数据可以进行高并发读写; 2 支持丰富数据类型:支持string,list,set,sorted set,hash; 3 支持事务:操作都是原子性,所谓的原子性就是对数据的更改要么全部执行,要么全部不执行; 4 丰富的特性:可用于缓存,消息,按key设置过期时间,过期后将会自动删除; 5 扩展性和可用性高:支持垂直扩展,提升硬件性能;通过集群支持水平扩展.
Redis的主要缺点是数据库容量受到物理内存的限制,不能用作海量数据的高性能读写,因此Redis适合的场景主要局限在较小数据量的高性能操作和运算上. 另外Redis(ACID处理非常简单)也无法做到太复杂的关系数据库模型.
• Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用. • Redis不仅仅支持简单的key-valu e类型的数据,同时还提供list,set,zset,hash等数据结构的 存储. • Redis支持数据的备份,即master-slave模式的数据备份.
version: '3.1' services: redis: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis environment: -TZ=Asia/Shanghai ports: -6379:6379
进去Redis容器的内部 docker exec -it 容器id bash 在容器内部,使用redis-cli连接
ping-pong机制
常用的5种数据结构: key-string:—个key对应一个值。 key-hash:—个key对应一个Map。 key-list:—个key对应一个列表。 key-set:—个key对应一个集合。 key-zset:-个key对应一个有序的集合。 另外三种数据结构: HyperLogLog:计算近似值的。 GEO:地理位置。 BT:一般存储的也是一个字符串,存储的是一个byte[]。
五种常用的存储数据结构图 •key-string:最常用的,一般用于存储一个值。 •key-hash:存储一个对象数据的。 •key-list :使用list结构实现栈和队列结构。 •key-set :交集,差集和并集的操作。 •key-zset:排行榜,积分存储等操作。
set key value
get key
mset key value [key value...] mget key [key...]
incr key(自增1)
decr key(自减1)
incrby key increment decrby key increment
设置值的同时,指定生存时间(每次向Redis中添加数据时,尽量都设置上生存时间) setex key second value
#8.设置值,如果当前key不存在的话(如果这个key存在,什么事都不做,如果这个key不存在,和set命令一样) setnx key value
在key对应的valu e后,追加内容 append key value
strlen key
hset key field value hget key field
hmset key field value [field value . hmget key field [field ...]
hincrby key field increment
设置值(如果key-field不存在,那么就正常添加,如果存在,什么事都不做) hsetnx key field value
hexists key field
可以删除多个 hdel key field [field ...]
获取当前hash结构中的全部field和value hgetall key
获取当前hash结构中的全部field hkeys key
获取当前hash结构中的全部value hvals key
获取当前hash结构中field的数量 hlen key
list的数据类型---->对应的java中的数据结构(栈/队列) lpush,lpop--->栈 lpush:入栈,每次是从list集合的左侧入栈; lpop:出栈,每次取list集合的栈顶的数据; rpush,rpop--->队列 rpush:入队,每次是从list集合的右侧入队; rpop:出队,每次从对尾取值.
存储数据 lpush key value [value ...] 从左侧插入数据 rpush key value [value ...] 从右侧插入数据
存储数据(如果key不存在,什么事都不做,如果key存在,但是不是list结构,什么都不做) lpushx key value rpushx key value
修改数据(在存储数据时,指定好你的索引位置,覆盖之前索引位置的数据,index超出整个列表的 长度,也会失败) lset key index value
lpop key 左侧弹出数据 rpop key 从右侧弹出数据
获取指定索引范围的数据(start从0开始,stop输入-1,代表最后一个,-2代表倒数第二个) lrange key start stop
lindex key index
llen key
删除列表中的数据(他是删除当前列表中的count个value值,count > 0从左侧向右侧删除,count < 0从右侧向左侧删除,count == 0删除列表中全部的value) lrem key count value
保留列表中的数据(保留你指定索引范围内的数据,超过整个索引范围被移除掉) ltrim key start stop
将一个列表中最后的一个数据,插入到另外一个列表的头部位置 rpoplpush list1 list2
sadd key member [member ...]
获取数据(获取全部数据) smembers key
随机获取一个数据(获取的同时,移除数据,count默认为1,代表弹出数据的数量) spop key [count]
交集(取多个set集合交集) sinter set1 set2 ...
并集(获取全部集合中的数据) sunion set1 set2 ...
差集(获取多个集合中不一样的数据) sdiff set1 set2 ...
srem key member [member ...]
#查看当前的set集合中是否包含这个值 sismember key member
添加数据(score必须是数值。member不允许重复的。) zadd key score member [score member ...]
修改member的分数(如果member是存在于key中的,正常增加分数,如果memeber不存在,这个命令就相当zadd) zincrby key increment member
zscore key member
zcard key
根据score的范围查询member数量 zcount key min max
删除zset中的成员 zrem key member [member...]
根据分数从小到大排序,获取指定范围内的数据(withscores如果添加这个参数,那么会返回member对应的分数) zrange key start stop [withscores]
根据分数从大到小排序,获取指定范围内的数据(withscores如果添加这个参数,那么会返回member对应的分数) zrevrange key start stop [withscores]
查看Redis中的全部的key (pattern: * , xxx*, *xxx) keys pattern
查看某一个key是否存在(1 - key存在,0 - key不存在) exists key
del key [key ...]
设置key的生存时间,单位为秒,单位为毫秒,设置还能活多久 expire key second pexpire key milliseconds 设置key的生存时间,单位为秒,单位为毫秒,设置能活到什么时间点 expireat key timestamp pexpireat key milliseconds
查看key的剩余生存时间,单位为秒,单位为毫秒(-2 -当前key不存在,-1 -当前key没有设置生存时间,具体剩余的生存时间) ttl key pttl key
移除key的生存时间(1 -移除成功,0 - key不存在生存时间,key不存在) persist key
select 0~15
移动key到另外一个库中 move key db
flushdb
flushall
dbsize
lastsave
monitor
如果你们项目中有 1亿 个key,如何过滤出想要的key? keys pattern通配符.---->该答案并不是面试官想要的答案!!! keys product* keys命令会造成redis线程阻塞,而scan不会! 更好的答案是回答 scan命令: scan cursor match pattern SCAN 0 match product*
<dependency> <groupId>redis.clientsgroupId> <artifactId>jedisartifactId> <version>2.9.0version> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>fastjsonartifactId> <version>1.2.46version> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> dependency>
@Test public void test1() { Jedis jedis = new Jedis("172.20.10.8", 6379); String result = jedis.ping(); System.out.println("result=" + result); jedis.close(); }
//Jedis存储一个对象到Redis以byte[]的形式 @Test public void test2() { //1.连接Redis服务 Jedis jedis = new Jedis("172.20.10.8", 6379); //2.1 准备 key(String)-value(User) String key = "user"; User user = new User(); user.setId(2); user.setUsername("特没谱"); user.setPassword("sb"); //2.2 将 key 和 valu e 转换为 byte口 byte[] key = SerializationUtils.serialize("user1"); byte[] userByte = SerializationUtils.serialize(user); jedis.set(key, userByte); jedis.close(); }
//获取对象-以byte口形式在Redis中获取 @Test public void test3() { //1.连接Redis服务 Jedis jedis = new Jedis("172.20.10.8", 6379); byte[] result = jedis.get(SerializationUtils.serialize("user1")); //反序列化 User user = (User) SerializationUtils.deserialize(result); System.out.println("user=" + user.toString()); jedis.close(); }
//存储对象-以String形式存储 @Test public void test1() { //1.连接 Redis Jedis jedis = new Jedis("172.20.10.8", 6379); User user = new User(); user.setId(1); user.setUsername("syc"); user.setPassword("123"); //转化为json字符串 String string = JSON.toJSONString(user); String json = JSON.toJSONString(user); jedis.set("user2", json); System.out.println(json); jedis.close(); }
//获取对象-以String形式获取 @Test public void test2() { Jedis jedis = new Jedis("172.20.10.8", 6379); String json = jedis.get("user2"); //将value反序列化为User User user = JSON.parseObject(json, User.class); System.out.println("user=" + user.toString()); jedis.close(); }
使用连接池操作Redis,避免频繁创建和销毁链接对象消耗资源
@Test public void test1() { //Jedis jedis = new Jedis("10.11.53.143", 6379); JedisPool pool=new JedisPool("172.20.10.8", 6379); //使用Jedis的连接池,来获取Jedis对象 Jedis jedis = pool.getResource(); User user = new User(); user.setId(1); user.setUsername("syc"); user.setPassword("123"); //转化为json字符串 String string = JSON.toJSONString(user); String json = JSON.toJSONString(user); jedis.set("user3", json); jedis.close(); }
@Test public void test2() { //Jedis jedis = new Jedis("10.11.53.143", 6379); //1.创建连接池配置信息 GenericObjectPoolConfig poolConfig=new GenericObjectPoolConfig(); //设置连接池中的最大连接数 poolConfig.setMaxTotal(100); //设置jedis对象的最大空闲时间 poolConfig.setMaxIdle(10); poolConfig.setMinIdle(5); //当连接池中没有jedis对象,在3秒钟内如果获取不到jedis对象,就会产生超时 poolConfig.setMaxWaitMillis(3000); JedisPool pool=new JedisPool(poolConfig,"172.20.10.8", 6379); //使用Jedis的连接池,来获取Jedis对象 Jedis jedis = pool.getResource(); User user = new User(); user.setId(1); user.setUsername("yqq"); user.setPassword("123"); String json = JSON.toJSONString(user); jedis.set("user4", json); jedis.close(); }
因为在操作Redis的时候,执行一个命令需要先发送请求到Redis服务器,这个过程需 要经历网络的延迟,Redis还需要给客户端一个响应。 如果我需要一次性执行很多个命令,上述的方式效率很低,可以通过Redis的管道,先 将命令放到客户端的一个Pipeline中,之后一次性的将全部命令都发送到Redis服务, Redis服务一次性的将全部的返回结果响应给客户端。
// Redis管道的操作 @Test public void pipeline(){ //1.创建连接池 JedisPool pool = new JedisPool("192.168.199.109”,6379); long l = System.currentTimeMillis。; /*//2.获取一个连接对象 Jedis jedis = pool.getResource。; //3.执行 incr - 100000次 for (int i = 0; i < 100000; i++) { jedis.incr("pp"); } //================================ //2. 获取一个连接对象 Jedis jedis = pool.getResource(); //3.创建管道 Pipeline pipelined = jedis.pipelined(); //3.执行incr - 100000次放到管道中 for (int i = 0; i < 100000; i++) { pipelined.incr("qq"); } //4.执行命令 pipelined. syncAndReturnAll。; //5. 释放资源 jedis.close(); System.out.println(System.currentTimeMillis() - l);
Redis的事务:一次事务操作,改成功的成功,该失败的失败。 先开启事务,执行一些列的命令,但是命令不会立即执行,会被放在一个队列中,如果 你执行事务,那么这个队列中的命令全部执行,如果取消了事务,一个队列中的命令全 部作废。 •开启事务:multi •输入要执行的命令:被放入到一个队列中 •执行事务:exec •取消事务:discard Redis的事务向发挥功能,需要配置watch监听机制 在开启事务之前,先通过watch命令去监听一个或多个key,在开启事务之后,如果有 其他客户端修改了我监听的key,事务会自动取消。 如果执行了事务,或者取消了事务,watch监听自动消除,一般不需要手动执行 unwatch。
@Test public void test1() { //Jedis jedis = new Jedis("10.11.53.143", 6379); JedisPool pool=new JedisPool("172.20.10.8", 6379); //使用Jedis的连接池,来获取Jedis对象 Jedis jedis = pool.getResource(); //开启事务 Transaction transaction = jedis.multi(); //redis.clients.jedis.exceptions.JedisDataException: // Cannot use Jedis when in Multi. Please use Transation or reset jedis state. //jedis.set("name","yyg"); //注意:redis中事务的使用,要利用Transaction对象来执行!不能用Jedis对象! transaction.set("name","易青青"); //提交事务 transaction.exec(); //取消事务 //transaction.discard(); jedis.close(); }
RDB是Redis默认的持久化机制 ・RDB持久化文件,速度比较快,而且存储的是一个二进制的文件,传输起来很方便。 ・RDB持久化的时机: save 900 1:在900秒内,有1个key改变了,就执行RDB持久化。 save 300 10:在300秒内,有10个key改变了,就执行RDB持久化。 save 60 10000:在60秒内,有10000个key改变了,就执行RDB持久化。 RDB无法保证数据的绝对安全
AOF持久化机制默认是关闭的,Redis官方推荐同时开启RDB和AOF持久化,更安全, 避免数据丢失。 •AOF持久化的速度,相对RDB较慢的,存储的是一个文本文件,到了后期文件会比较大,传输困难。 •AOF持久化时机。 appendfsync always:每执行一个写操作,立即持久化到AOF文件中,性能比较低。 appendfsync everysec :每秒执行一次持久化。 appendfsync no :会根据你的操作系统不同,环境的不同,在一定时间内执行一次持久化。 •AOF相对RDB更安全,推荐同时开启AOF和RDB。 注意事项: 同时开启RDB和AOF的注意事项: 如果同时开启了AOF和RDB持久化,那么在Redis宕机重启之后,需要加载一个持久化文件,优先选择AOF文件 如果先开启了RDB,再次开启AOF,如果RDB执行了持久化,那么RDB文件中的内容会被AOF覆盖掉。
加载自定义配置文件
实现跨机器访问redis服务器
保护模式,一般为yes ,在集群中建议开启
redis服务器端口:可以修改
内存设置
满足条件就自动持久化保存到磁盘》优化调优 如:save 900 1:在900秒内,有1个key改变了,就执行RDB持久化。
数据备份到dump.rdb文件
连接服务器需要密码
最多连接客户端数量
内存大小,一般根据机器性能默认分配
redis的内存淘汰策略,
当为no时,只开启RDB持久化机制, 当为yes时,就开启了AOF持久化机制,这个时候相当于两种机制同时存在
在开启AOF持久化机制后,有自己的数据库文件
AOF的3种持久化规则
save : 手动备份数据到磁盘中 恢复: 只需要把dump.rdb文件复制到data目录下,只要redis一启动,就会自动恢复.
redis的内存淘汰策略: XMEMORY POLICY: how Redis will select what to remove when maxmemory # is reached. You can select among five behaviors: # # 如下是默认自带的几种(8种)淘汰策略(重点): # volatile-lru -> Evict using approximated LRU among the keys with an expire set. # allkeys-lru -> Evict any key using approximated LRU. # volatile-lfu -> Evict using approximated LFU among the keys with an expire set. # allkeys-lfu -> Evict any key using approximated LFU. # volatile-random -> Remove a random key among the ones with an expire set. # allkeys-random -> Remove a random key, any key. # volatile-ttl -> Remove the key with the nearest expire time (minor TTL) # noeviction -> Don't evict anything, just return an error on write operations. # # LRU means Least Recently Used 最近最少使用算法 # LFU means Least Frequently Used 最近最少频繁使用算法 The default is:默认的内存淘汰策略:可以在下面修改策略 # maxmemory-policy noeviction 不驱除
①.rdb(默认)---->bgsave 设置rdb的持久化规则: save 900 1 save 300 10 save 60 10000 默认redis会使用 LZF 算法对dump.rdb文件进行压缩. Compress string objects using LZF when dump.rdb databases? 特点: 默认开启; 性能好:不会每执行一次redis增删改操作就进行一次IO操作. 生成的rdb备份文件较小,所以后来恢复速度就快; 数据完整性不够好,存在数据丢失的风险. ②.aof---->bgrewriteof aof的3种持久化规则: appendfsync always:只要做一次数据操作就做持久化 appendfsync everysec:每秒做一次持久化 appendfsync no 特点: 默认没有开启; 性能没有RDB好; 数据完整性好. aof文件会很大,备份恢复速度就较慢.
单机版Redis存在读写瓶颈的问题
version: "3.1" services: redis1: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis1 environment: - TZ=Asia/Shanghai ports: - 7001:6379 volumes: - ./conf/redis1.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis2: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis2 environment: - TZ=Asia/Shanghai ports: - 7002:6379 volumes: - ./conf/redis2.conf:/usr/local/redis/redis.conf links: - redis1:master command: ["redis-server","/usr/local/redis/redis.conf"] redis3: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis3 environment: - TZ=Asia/Shanghai ports: - 7003:6379 volumes: - ./conf/redis3.conf:/usr/local/redis/redis.conf links: - redis1:master command: ["redis-server","/usr/local/redis/redis.conf"]
redis2和redis3从节点配置 replicaof master 6379 info replication :查看主从机信息
1.主从复制:一主多从,主机可写,从机备份.类似于MySQL的读写分离,存在的问题是一但主节点down掉,整个 Redis都不可用. 2️.哨兵(2.x)机制:启用一个哨兵程序(节点),监控其余节点的状态,根据选举策略,进行主从切换. 缺点:每个节点的数据依旧是一致的,仍无法实现分布式的数据库. 3️.集群(3.x):结合上述两种模式,多主多从,实现高可用、分布数据存储.
哨兵可以帮助我们解决主从架构中的单点故障问题
version: "3.1" services: redis1: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis1 environment: - TZ=Asia/Shanghai ports: - 7001:6379 volumes: - ./conf/redis1.conf:/usr/local/redis/redis.conf - ./conf/sentinel1.conf:/data/sentinel.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis2: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis2 environment: - TZ=Asia/Shanghai ports: - 7002:6379 volumes: - ./conf/redis2.conf:/usr/local/redis/redis.conf - ./conf/sentinel2.conf:/data/sentinel.conf links: - redis1:master command: ["redis-server","/usr/local/redis/redis.conf"] redis3: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis3 environment: - TZ=Asia/Shanghai ports: - 7003:6379 volumes: - ./conf/redis3.conf:/usr/local/redis/redis.conf - ./conf/sentinel3.conf:/data/sentinel.conf links: - redis1:master command: ["redis-server","/usr/local/redis/redis.conf"]
daemonize yes #指定Master节点的ip和端口(主) sentinel monitor master 172.20.10.8 6379 2 #哨兵每隔多久监听一次redis架构 sentinel down-after-milliseconds mymaster 10000
daemonize yes #指定Master节点的ip和端口(主) sentinel monitor master 172.20.10.8 6379 2 #哨兵每隔多久监听一次redis架构 sentinel down-after-milliseconds mymaster 10000
在Redis容器内部启动sentine即可 redis-sentinel sentinel.conf
redis-cli -p 26379 info
Redis集群在保证主从加哨兵的基本功能之外,还能够提升Redis存储数据的能力。
version: "3.1" services: redis1: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis1 environment: - TZ=Asia/Shanghai ports: - 7001:7001 - 17001:17001 volumes: - ./conf/redis1.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis2: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis2 environment: - TZ=Asia/Shanghai ports: - 7002:7002 - 17002:17002 volumes: - ./conf/redis2.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis3: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis3 environment: - TZ=Asia/Shanghai ports: - 7003:7003 - 17003:17003 volumes: - ./conf/redis3.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis4: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis4 environment: - TZ=Asia/Shanghai ports: - 7004:7004 - 17004:17004 volumes: - ./conf/redis4.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis5: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis5 environment: - TZ=Asia/Shanghai ports: - 7005:7005 - 17005:17005 volumes: - ./conf/redis5.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"] redis6: image: daocloud.io/library/redis:5.0.7 restart: always container_name: redis6 environment: - TZ=Asia/Shanghai ports: - 7006:7006 - 17006:17006 volumes: - ./conf/redis6.conf:/usr/local/redis/redis.conf command: ["redis-server","/usr/local/redis/redis.conf"]
#指定redi s的端口号 port 7006 #开启Redis集群 cluster-enabled yes #集群信息的文件 cluster-config-file nodes-7006.conf #集群的对外ip地址 cluster-announce-ip 172.20.10.8 #集群的对外port cluster-announce-port 7006 #集群的总线端口 cluster-announce-bus-port 17006 注意:需要配置6个 redis.conf 端口分别为1-6
redis-cli --cluster create 172.20.10.8:7001 172.20.10.8:7002 172.20.10.8 :7003 172.20.10.8:7004 172.20.10.8:7005 172.20.10.8:7006 --cluster-rep licas 1 注意:启动前先关闭Linux防火墙 6个节点,自动划分三个主机,三个从节点
redis-cli -c -p 7001|7002 -c 表示集群模式 cluster 注意: 在集群内部任意任意主机或节点存储数据,都会根据 key 进行crc16算法,并且对16384取余,根据最终结果,将key-value存到指定redis(主机)节点上
@Test public void test1() { //服务器节点集群 Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("172.20.10.8", 7001)); nodes.add(new HostAndPort("172.20.10.8", 7002)); nodes.add(new HostAndPort("172.20.10.8", 7003)); nodes.add(new HostAndPort("172.20.10.8", 7004)); nodes.add(new HostAndPort("172.20.10.8", 7005)); nodes.add(new HostAndPort("172.20.10.8", 7006)); JedisCluster cluster = new JedisCluster(nodes); cluster.hset("cluster", "name", "testCluster"); cluster.hset("cluster", "info", "3333"); //cluster.close(); }
@Test public void test2() { //服务器节点集群 Set<HostAndPort> nodes = new HashSet<>(); nodes.add(new HostAndPort("172.20.10.8", 7001)); nodes.add(new HostAndPort("172.20.10.8", 7002)); nodes.add(new HostAndPort("172.20.10.8", 7003)); nodes.add(new HostAndPort("172.20.10.8", 7004)); nodes.add(new HostAndPort("172.20.10.8", 7005)); nodes.add(new HostAndPort("172.20.10.8", 7006)); JedisCluster cluster = new JedisCluster(nodes); String name = cluster.hget("cluster", "name"); String info = cluster.hget("cluster", "info"); System.out.println("name=" + name + "\n"+"info=" + info); //cluster.close(); }
key的生存时间到了,Redis会立即删除吗?不会立即删除。 •定期删除:Redis每隔一段时间就去会去查看Redis设置了过期时间的key,会再 100ms的间隔中默认查看3个key。 •惰性删除:如果当你去查询一个已经过了生存时间的key时,Redis会先查看当前 key的生存时间,是否已经到了,直接删除当前key,并且给用户返回一个空值。
在Redis内存已经满的时候,添加了一个新的数据,执行淘汰机制。 •volatile-lru:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近 最少使用的key。 •allkeys-lru:在内存不足时,Redis会在全部的key中干掉一个最近最少使用的 key。 •volatile-lfu:在内存不足时,Redis会在设置过了生存时间的key中干掉一个最近 最少频次使用的key。 •allkeys-lfu:在内存不足时,Redis会在全部的key中干掉一个最近最少频次使用 的key。 •volatile-random :在内存不足时,Redis会再设置过了生存时间的key中随机干掉 一个。 •allkeys-random:在内存不足时,Redis会在全部的key中随机干掉一个。 •volatile-ttl:在内存不足时,Redis会在设置过了生存时间的key中干掉一个剩余生存时间最少的key。 •noeviction :(默认)在内存不足时,直接报错。
问题出现的原因:查询的数据,Redis中没有,数据库中也没有。 1:根据id查询时,如果id是自增的,将id的最大值放到Redis中,在查询数据库之前,直接比较一下id。 2:如果id不是整形,可以将全部的id放到set中,在用户查询之前,去set中查看一下是否有一个id。 3:获取客户端的ip地址,可以将ip的访问添加限制。
问题:缓存中的热点数据,突然到期了,造成了大量的请求都去访问数据库,造成数据库宕机? 1.在访问缓存中没有的时候,直接添加一个锁,让几个请求去访问数据库,避免数据库宕机。 2.热点数据的生存时间去掉。
<servlet> <servlet-name>SpringMVCservlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class> <init-param> <param-name>contextConfigLocationparam-name> <param-value>classpath:spring/spring-web.xmlparam-value> init-param> <load-on-startup>1load-on-startup> servlet> <servlet-mapping> <servlet-name>SpringMVCservlet-name> <url-pattern>/url-pattern> servlet-mapping> <listener> <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class> listener> <context-param> <param-name>contextConfigLocationparam-name> <param-value>classpath:spring/application-*.xmlparam-value> context-param> <filter> <filter-name>Encodingfilter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class> <init-param> <param-name>encodingparam-name> <param-value>UTF-8param-value> init-param> filter> <filter-mapping> <filter-name>Encodingfilter-name> <url-pattern>/*url-pattern> filter-mapping>
<context:component-scan base-package="com.yyg.redis.web"/>
<mvc:annotation-driven/>
<mvc:resources mapping="/html/**" location="/html/"/>
<mvc:resources mapping="/js/**" location="/js/"/>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/html/"/>
<property name="suffix" value=".html"/>
bean>
<context:component-scan base-package="com.yyg.redis.service"/>
<context:property-placeholder location="classpath:conf/redis.properties"/> <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${redis.maxTotal}"/> <property name="maxIdle" value="${redis.maxIdle}"/> <property name="maxWaitMillis" value="${redis.maxWait}"/> bean> <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <property name="hostName" value="${redis.host}"/> <property name="port" value="${redis.port}"/> <property name="password" value="${redis.password}"/> <property name="poolConfig" ref="poolConfig"/> bean> <bean id="redisTemplate" class="org.springframework.data.redis.core.StringRedisTemplate"> <property name="connectionFactory" ref="connectionFactory"/> <property name="enableTransactionSupport" value="true"/> <property name="keySerializer"> <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/> property> bean> <bean id="redisUtil" class="com.yyg.redis.util.RedisUtil"> <property name="redisTemplate" ref="redisTemplate"/> bean>
<context:property-placeholder location="classpath:conf/*.properties"/> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="url" value="${jdbc.url}"/> <property name="driverClassName" value="${jdbc.driver}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> bean> <bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.yyg.redis.mapper"/> bean>
<configuration> <settings> <setting name="logImpl" value="STDOUT_LOGGING"/> settings> <typeAliases> <typeAlias type="com.yyg.redis.domain.Msg"/> typeAliases> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> plugin> plugins> configuration>
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/yqq?useUnicode=true&characterEncoding=UTF8&useSSL=false jdbc.username=root jdbc.password=root
redis.host=172.20.10.8 redis.port=6379 redis.password= redis.maxIdle=300 redis.maxTotal=600 redis.maxWait=1000
@Controller public class MsgController { @Autowired private MsgService msgService; @GetMapping("/index") public String showIndex() { return "index"; } @ResponseBody @PostMapping("/addMsg") public Map<String, Object> addMsg(Msg msg) { boolean result = msgService.addMsg(msg); Map<String, Object> map = new HashMap<>(); if (result) { map.put("code", 200); map.put("msg", "success"); } else { map.put("code", -1); map.put("msg", "error"); } return map; } @ResponseBody @GetMapping("/queryMsg") public List<Msg> queryMsg(){ return msgService.listMsg(); } }
@Service public class MsgServiceImpl implements MsgService { @Autowired private MsgMapper msgMapper; @Autowired private RedisUtil redisUtil; @Override public boolean addMsg(Msg msg) { //在进行数据库的添加,删除,修改等更新的时候,要考虑"双写一致性"问题! boolean result = msgMapper.addMsg(msg) > 0; if (result) { //第一种方案:删除某个key对应的缓存,把key-value //redisUtil.del("msg_list"); //第二种方案:使得该key-value直接过期 redisUtil.expire("msg_list", 0); //TODO:在分布式项目中,可以利用消息队列,在数据库更新成功后,发出一个消息(把缓存的key,id,内容等) //TODO:另外的一个服务中,就可以接收到该消息,从而做出redis缓存的更新! } return result; } @Override public List<Msg> listMsg() { //添加redis缓存,业务逻辑实现步骤: //1.先直接去redis缓存中查询是否有缓存 //2.如果有缓存---->直接返回缓存结果 //3.如果没有缓存--->进行数据库的查询--->把该结果存到redis缓存中 List<Msg> msgs; List<Object> list = redisUtil.lGet("msg_list", 0, -1); if (list != null && list.size() > 0) { System.out.println("执行redis缓存..."); msgs = JSON.parseArray(list.get(0).toString(), Msg.class); } else { System.out.println("执行数据库查询..."); msgs = msgMapper.findAll(); //查询mysql数据库 String json = JSON.toJSONString(msgs); Random random = new Random(); int time = random.nextInt(10000); //预防因为缓存过期时间同时失效,而造成的缓存雪崩问题 redisUtil.lSet("msg_list", json, 3000 + time); } return msgs; } }
package com.yyg.redis.util; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.CollectionUtils; public class RedisUtil { private RedisTemplate<String, Object> redisTemplate; public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = 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(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); //redisTemplate.opsForHash().put(); 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) * @return */ 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) * @return */ 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 * @return 值 */ 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 * 对应多个键值 * @return true 成功 false 失败 */ 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) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key * 键 * @param item * 项 * @param by * 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * * @param key * 键 * @return */ 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 * 键 * @return */ 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 * 键 * @return */ 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倒数第二个元素,依次类推 * @return */ 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 * 值 * @return */ 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 * 时间(秒) * @return */ 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; } } }
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-webartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-jpaartifactId> dependency> <dependency> <groupId>mysqlgroupId> <artifactId>mysql-connector-javaartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-cacheartifactId> dependency> <dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-redisartifactId> dependency> <dependency> <groupId>com.alibabagroupId> <artifactId>fastjsonartifactId> <version>1.2.39version> dependency> <dependency> <groupId>org.projectlombokgroupId> <artifactId>lombokartifactId> dependency>
cache: default-exp: 10000 #redis缓存总的过期时间,单位是秒 server: port: 8080 spring: application: name: redis-cache-demo datasource: url: jdbc:mysql://localhost:3306/db4?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8&useSSL=false&serverTimezone=UTC driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root jpa: show-sql: true database: mysql hibernate: ddl-auto: update cache: #设置项目中使用的缓存类型 type: redis redis: host: 172.20.10.8 port: 6379 database: 0 #password:
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public ResponseEntity<User> findById(@PathVariable Long id) { User user = userService.findById(id); HttpStatus status = user == null ? HttpStatus.NOT_FOUND : HttpStatus.OK; return new ResponseEntity<>(user, status); } @PostMapping public User addUser(@RequestBody User user) { return userService.addUser(user); } @PutMapping public User updateUser(@RequestBody User user) { return userService.updateUser(user); } @DeleteMapping("/{id}") public String deleteById(@PathVariable Long id) { userService.deleteById(id); return "success"; } }
@Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; /** * @Cacheable:作用在查询方法上. "value"是redis缓存的key的前缀,"key"是缓存的具体的key的值;---->redis的key=user_02 * 该注解会把当前方法的返回值缓存起来! * redis缓存的形式为:key-value * #: SpEL表达式,用来取参数的值或者取出系统中自带的对象的值! * unless = "#result eq null ":当result结果为null的时候不进行缓存! */ @Cacheable(value = "user_", key = "#id", unless = "#result eq null") @Override public User findById(Long id) { //Optional
optional = userRepository.findById(id); //User user = optional.get(); //return optional.orElse(new User()); //userRepository.findAll(Sort.by(Sort.Direction.DESC,"id","name")); return userRepository.findById(id).orElse(null); } /** * 添加方法:当往mysql数据库中添加新数据的时候,一般不需要往redis缓存中做任何操作! * 缓存预热: * 注意:value和key的值不能为空! */ @CachePut(value = "user_", key = "#result.id") @Override public User addUser(User user) { return userRepository.save(user); } /** * 双写一致性: * 缓存更新. * CacheEvict:用在删除方法上面,用来清除某个redis的缓存 */ @CacheEvict(value = "user_", key = "#id") @Override public void deleteById(Long id) { userRepository.deleteById(id); } /** * @CachePut:用在添加或者更新方法上面,用来更新redis缓存! */ @CachePut(value = "user_", key = "#user.id", unless = "#result eq null") @Override public User updateUser(User user) { return userRepository.saveAndFlush(user); } }
@Configuration @EnableCaching public class RedisCacheConfig { @Value("${cache.default-exp}") private long exps; @Value("${spring.redis.host}") private String host; @Value("${spring.redis.port}") private int port; //@Value("${spring.redis.timeout}") //private int timeout; //@Value("${spring.redis.password}") //private String password; /** * 设置redis中key的生成规则 */ @Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuffer sb = new StringBuffer(); sb.append(target.getClass().getName()); sb.append(method.getName()); for (Object obj : params) { sb.append(obj.toString()); } return sb.toString(); } }; } /** * RedisTemplate配置 */ @Bean public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer<JSON> serializer = new Jackson2JsonRedisSerializer<>(JSON.class); ObjectMapper mapper = new ObjectMapper(); mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); serializer.setObjectMapper(mapper); template.setValueSerializer(serializer); template.setHashValueSerializer(serializer); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); //使得上面的配置生效 template.afterPropertiesSet(); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { // 生成一个默认配置,通过config对象即可对缓存进行自定义配置 RedisSerializer<String> redisSerializer = new StringRedisSerializer(); // 使用Jackson2JsnRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer<JSON> serializer = new Jackson2JsonRedisSerializer<>(JSON.class); // 配置序列化 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)); config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)); // 设置缓存的默认过期时间 config.entryTtl(Duration.ofSeconds(exps)); // 不缓存空值 config.disableCachingNullValues(); return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build(); } }
@Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; /** * @Cacheable:作用在查询方法上. "value"是redis缓存的key的前缀,"key"是缓存的具体的key的值;---->redis的key=user_02 * 该注解会把当前方法的返回值缓存起来! * redis缓存的形式为:key-value * #: SpEL表达式,用来取参数的值或者取出系统中自带的对象的值! * unless = "#result eq null ":当result结果为null的时候不进行缓存! */ @Cacheable(value = "user_", key = "#id", unless = "#result eq null") @Override public User findById(Long id) { //Optional
optional = userRepository.findById(id); //User user = optional.get(); //return optional.orElse(new User()); //userRepository.findAll(Sort.by(Sort.Direction.DESC,"id","name")); return userRepository.findById(id).orElse(null); } /** * 添加方法:当往mysql数据库中添加新数据的时候,一般不需要往redis缓存中做任何操作! * 缓存预热: * 注意:value和key的值不能为空! */ @CachePut(value = "user_", key = "#result.id") @Override public User addUser(User user) { return userRepository.save(user); } /** * 双写一致性: * 缓存更新. * CacheEvict:用在删除方法上面,用来清除某个redis的缓存 */ @CacheEvict(value = "user_", key = "#id") @Override public void deleteById(Long id) { userRepository.deleteById(id); } /** * @CachePut:用在添加或者更新方法上面,用来更新redis缓存! */ @CachePut(value = "user_", key = "#user.id", unless = "#result eq null") @Override public User updateUser(User user) { return userRepository.saveAndFlush(user); } }//使得上面的配置生效 template.afterPropertiesSet(); return template; } @Bean public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { // 生成一个默认配置,通过config对象即可对缓存进行自定义配置 RedisSerializer
redisSerializer = new StringRedisSerializer(); // 使用Jackson2JsnRedisSerializer来序列化和反序列化redis的value值 Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(JSON.class); // 配置序列化 RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig(); config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)); config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer)); // 设置缓存的默认过期时间 config.entryTtl(Duration.ofSeconds(exps)); // 不缓存空值 config.disableCachingNullValues(); return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build(); } }
@Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; /** * @Cacheable:作用在查询方法上. "value"是redis缓存的key的前缀,"key"是缓存的具体的key的值;---->redis的key=user_02 * 该注解会把当前方法的返回值缓存起来! * redis缓存的形式为:key-value * #: SpEL表达式,用来取参数的值或者取出系统中自带的对象的值! * unless = "#result eq null ":当result结果为null的时候不进行缓存! */ @Cacheable(value = "user_", key = "#id", unless = "#result eq null") @Override public User findById(Long id) { //Optional
optional = userRepository.findById(id); //User user = optional.get(); //return optional.orElse(new User()); //userRepository.findAll(Sort.by(Sort.Direction.DESC,"id","name")); return userRepository.findById(id).orElse(null); } /** * 添加方法:当往mysql数据库中添加新数据的时候,一般不需要往redis缓存中做任何操作! * 缓存预热: * 注意:value和key的值不能为空! */ @CachePut(value = "user_", key = "#result.id") @Override public User addUser(User user) { return userRepository.save(user); } /** * 双写一致性: * 缓存更新. * CacheEvict:用在删除方法上面,用来清除某个redis的缓存 */ @CacheEvict(value = "user_", key = "#id") @Override public void deleteById(Long id) { userRepository.deleteById(id); } /** * @CachePut:用在添加或者更新方法上面,用来更新redis缓存! */ @CachePut(value = "user_", key = "#user.id", unless = "#result eq null") @Override public User updateUser(User user) { return userRepository.saveAndFlush(user); } }