Redis技术应用

通用操作

  • 数据特征:

key:value形式的数据存储在Redis当中。

redis有16个数据库,下标为0-15,默认为0。

  • 登录:

方式1:redis-cli -a password(默认密码为123456)

方式2:redis-cli

auth password (默认密码为123456)

  • 退出:

exit

  • 查看所有数据:

keys *

  • 删除当前数据库(即0数据库)所有数据

flushdb

  • 删除所有数据库数据:

flushall

  • 选择数据库

select index (例如:select 0)

String类型操作

  • 存储数据:

set key value(例如:set k1 100)

  • 同时存储多个数据:

mset key value key value ...

  • 查看指定数据:

get key (例如:get k1)

  • 同时查看多个指定数据:

mget key key ...

  • 删除指定数据:

del k1 k2...

  • 查看key的长度:

strlen key

  • 设置数据的有效时长(s):

方式1:set key value EX seconds (例如:set a 100 EX 10)

方式2:set key value

expire key seconds

  • 设置数据的有效时长(ms):

set key value

pexpire key seconds

  • 查看剩余有效时长(s):

ttl key

  • 取消数据的有效时长:

persist key

  • 查看key的数据类型:

type key

  • key进行自增

incr key (若key存在则每次自动加1;若key不存在则自动创建,创建后的初始值为1)

incyby key increment (可通过increment指定每次递增的数值,若key存在则每次自动加increment;若key不存在则自动创建,创建后的初始值为指定的increment的数值)

  • key进行自减

decr key (若key存在则每次自动减1;若key不存在则自动创建,创建后的初始值为-1)

decrby key decrement (可通过decrement指定每次递减的数值,若key存在则每次自动减decrement;若key不存在则自动创建,创建后的初始值为指定的decrement的数值)

  • 追加数据

append key value (若key存在则追加value对应的值;若key不存在则自动创建,创建后的初始值为value)

  • 用途:记录某篇文章的点赞数等

Hash类型操作

RedisTemplate对象

  • 选择依赖:

--> 即如下依赖:

XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 示例代码1:
java 复制代码
ValueOperations vo=redisTemplate.opsForValue();
vo.set("id","1"); //key和value都会默认采用JDK序列化的方式进行数据存储
String num=vo.increment("num"); //key不存在的时候会自动创建,key会采用JDK序列化的方式进行数据存储,value采用原有的类型进行存储,不会进行序列化存储
System.out.println(vo.get("id"));
System.out.println(num);
  • 运行此段代码后,可在Linux中查看数据:
  • 示例代码2:
java 复制代码
ValueOperations vo=redisTemplate.opsForValue();
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.string());
vo.set("id","1");
vo.increment("num");
System.out.println(vo.get("id"));
System.out.println(vo.get("num"));

运行此段代码后,可在Linux中查看数据:

  • 示例代码3:
  1. 写一个外部类,以后写pojo类尽量都实现Serializable接口:
  2. IDEA自动生成序列化ID

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Data @NoArgsConstructor class Person implements Serializable { private static final long serialVersionUID = 6625951768114336548L; private int x; private int y; } |

3、写代码:

该处将value使用了json格式的序列化,应该添加如下的依赖(spring-web依赖里面有json格式序列化的方法):

|------------------------------------------------------------------------------------------------------------------------------------------|
| java <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> |

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java ValueOperations vo = redisTemplate.opsForValue(); redisTemplate.setKeySerializer(RedisSerializer.string ()); redisTemplate.setValueSerializer(RedisSerializer.json ()); //这里person是一个对象,应该使用json();如果使用string()的话,下面应改为vo.set("person","person");Linux中获取到的是person字符串,而不是赋的值。 Person person = new Person(); person.setX(1); person.setY(2); vo.set("person", person); Object obj = vo.get("person"); System.out.println(obj); |

RedisTemplate定制的序列化和反序列化方法

  • 通过添加全局配置类实现将key进行string类型的序列化,将value进行json类型的序列化。
  • 代码:在base->config下面新建一个RedisConfig的类,添加@Configuration注解,@Bean注解。
  • 添加上面提到的redis和spring-web依赖。

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @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; } } |

Redis缓存测试

  • 测试一:

通过上述操作可以发现仍然可以读取到k2的数值,这是因为通过redis-cli shutdown这种方式去停掉redis,其实是一种安全退出的模式,redis在退出的时候会将内存中的数据立即生成一份完整的rdb快照,通过redis日志可以直观地看出RDB缓存。

使用shutdown命令停掉redis之后可以通过redis-server /usr/local/redis/conf/redis.conf命令重启redis

  • 测试二:

窗口1命令:

窗口2命令:

窗口3日志:

通过三个窗口的操作,我们可以看出使用kill -9 端口号进行暴力杀死进程后导致后台无法保存尚未保存的数据。

Redis架构设计

主从架构

本次实战,我们设计1个master挂3个slave的主从架构,具体实现过程如下:

第一步:创建主节点(master)的配置文件,名字为redis-6379.conf

|---------------------------------------------------------------------------------------------------------------|
| Java # 在redis.conf目录下执行 cp redis.conf redis-6379.conf #假如原有的redis.conf不想要了,则可以执行mv redis.conf redis-6379.conf |

第二步:修改redis-6379.conf文件内容,具体内容如下:

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java # 将bind 127.0.0.1 -::1 修改为bind 0.0.0.0 (这里的四个0表示任意的ip地址) bind 0.0.0.0 # 将protected-mode的默认值yes修改为no protected-mode no # 默认端口6379 port 6379 # pidfile 的值不变 pidfile /usr/local/redis/logs/redis_6379.pid # 设置数据目录(这个目录需要我们手动自己创建) dir /usr/local/redis/data/6379 # 日志文件 logfile '/usr/local/redis/logs/redis-6379.log' # 设置redis的登录密码 requirepass 123456 # 主节点认证 masterauth 123456 |

第三步:创建从节点redis-6380.conf配置文件(cp redis-6379.conf redis-6380.conf),其修改的内容如下:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java # 将bind 127.0.0.1 -::1 修改为bind 0.0.0.0 (这里的四个0表示任意的ip地址) bind 0.0.0.0 # 将protected-mode的默认值yes修改为no protected-mode no # 修改端口为6380 port 6380 # pidfile 的值不变 pidfile /usr/local/redis/logs/redis_6380.pid # 设置数据目录 dir /usr/local/redis/data/6380 # 日志文件 logfile '/usr/local/redis/logs/redis-6380.log' # 设置redis的登录密码 requirepass 123456 # 主节点认证 masterauth 123456 # 设置要连接的master的ip和端口 replicaof 192.168.8.100 6379 |

第四步:创建从节点redis-6381.conf配置文件(cp redis-6380.conf redis-6381.conf),其修改的内容如下:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java # 将bind 127.0.0.1 -::1 修改为bind 0.0.0.0 (这里的四个0表示任意的ip地址) bind 0.0.0.0 # 将protected-mode的默认值yes修改为no protected-mode no # 修改端口为6381 port 6381 # pidfile 的值不变 pidfile /usr/local/redis/logs/redis_6381.pid # 设置数据目录 dir /usr/local/redis/data/6381 # 日志文件 logfile '/usr/local/redis/logs/redis-6381.log' # 设置redis的登录密码 requirepass 123456 # 主节点认证 masterauth 123456 # 设置要连接的master的ip和端口 replicaof 192.168.8.100 6379 |

第五步:创建从节点redis-6382.conf配置文件(cp redis-6381.conf redis-6382.conf),其修改的内容如下:

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java # 将bind 127.0.0.1 -::1 修改为bind 0.0.0.0 (这里的四个0表示任意的ip地址) bind 0.0.0.0 # 将protected-mode的默认值yes修改为no protected-mode no # 修改端口为6382 port 6382 # pidfile 的值不变 pidfile /usr/local/redis/logs/redis_6382.pid # 设置数据目录 dir /usr/local/redis/data/6382 # 日志文件 logfile '/usr/local/redis/logs/redis-6382.log' # 设置redis的登录密码 requirepass 123456 # 主节点认证 masterauth 123456 # 设置要连接的master的ip和端口 replicaof 192.168.8.100 6379 |

第六步:启动主从节点服务(可以打开多个窗口,在不同窗口启动不同服务)

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java redis-server /usr/local/redis/conf/redis-6379.conf #主节点服务 redis-server /usr/local/redis/conf/redis-6380.conf redis-server /usr/local/redis/conf/redis-6381.conf redis-server /usr/local/redis/conf/redis-6382.conf |

第七步:登录主节点,并检查主从架构状态

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java [root@JSD-Services ~]# redis-cli -p 6379 127.0.0.1:6379> auth 123456 OK 127.0.0.1:6379> info replication # Replication role:master connected_slaves:3 slave0:ip=192.168.8.100,port=6380,state=online,offset=84,lag=1 slave1:ip=192.168.8.100,port=6381,state=online,offset=84,lag=1 slave2:ip=192.168.8.100,port=6382,state=online,offset=84,lag=1 master_failover_state:no-failover master_replid:a1a3412199ada0e96d2097fc2d67dc2b323ee439 master_replid2:0000000000000000000000000000000000000000 master_repl_offset:84 second_repl_offset:-1 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:1 repl_backlog_histlen:84 |

第八步:在主节点写入数据,检查从节点是否可以读取到数据;在任意一个从节点写入数据,检查主节点和其它从节点是否可以读取到数据

经检验都是可以实现的。

哨兵模式

Redis缓存穿透,缓存击穿,缓存雪崩

缓存穿透:

问题描述:

用户查询某一个数据,但该数据不存在于redis内存数据库中(缓存没有命中),这时候就会向持久层数据库查询,但持久层数据库也没有该数据,于是本次查询失败,若用户很多时,他们查询的数据不存在于redis内存数据库中(缓存没有命中),于是都去请求了持久层数据库,这样就会给持久层数据库带来很大的压力,这种大量不走redis内存数据库的现象就叫缓存穿透。

解决方案:

  1. 布隆过滤器(BloomFilter)

在控制层对请求先进行校验,不符合条件的请求则被丢弃,从而避免对持久层数据库造成的查询压力。

  1. 缓存空对象

当查询的数据不存在于redis中时,请求到了持久层数据库中去查询数据,但查询不出数据,这时会返回空对象,同时把该空对象缓存到redis里,然后设置一个过期时间,往后只要再次请求查询该条数据,该条数据都会从redis中获取(获取redis返回的空对象),从而保护了后端的数据源。

缺点:

  • 因为空对象能被缓存起来,而有些请求有可能查询不出数据,所以过程中可能产生大量的返回空对象然后被redis缓存的现象,而这意味着redis需要更多的空间来存储更多的键。
  • 即使对空对象设置了过期时间,但如果在redis的空对象在未过时的情况下,持久层数据库已经有了对应的数据,而redis对应的键的值仍是空对象,这时请求查询出的仍是空对象,而不是持久层里已经有的数据,而这种情况对于需要保持数据一致性的业务会造成影响。

缓存击穿:

问题描述:

redis里的一个key非常热点,导致大并发集中对这个key不断的进行访问,当在这个key过期的瞬间,持续的大并发就会跳过缓存,直接作用在持久层数据库上,请求在访问持久层数据库查询数据的同时,持久层数据库也需要回写缓存,这时候就会导致持久层数据库瞬间压力过大导致服务器宕机,这种现象就叫做缓存击穿。

解决方案:

  1. 设置热点key永不过期。
  1. 加互斥锁:使用分布式锁在redis和持久层数据库之间加锁,让每次查询都能保证只有一个线程进去,其他线程等待,这样做就能保证对于每一个key同时只能有一个线程去查询后端持久层数据库,而其他线程没有分布式锁的权限,所以只能等待,这种解决方案把高并发的压力转移到了分布式锁身上,但同时也加大了对分布式锁的考验。

缓存雪崩:

问题描述:

在某一时间段,一批key集中过期失效或者redis宕机,导致大量的请求作用在持久层数据库上,导致持久层数据库挂掉。

解决方案:

  1. redis高可用:redis集群搭建
  1. 限流降级:通过加锁或队列来控制读取持久层数据库的线程数量,例如通过对某个key加锁来保证只有一个线程对该key进行读和写,其他线程则需要等待。
  1. 数据预热:在正式部署前把可能被大量访问的数据先访问一遍,这些被访问的数据就会被加载到缓存中,在正式的大量访问到来之后减轻持久层数据库的压力;在发生大并发访问前手动触发加载缓存所需要的key,并给这些key设置不同的过期时间,让key失效的时间点尽量均匀开来,避免缓存雪崩。

综合案例

案例描述:

基于数据库中字典项表的设计,实现CRUD,基于本地缓存、Redis缓存提高查询的效率,并保证数据的一致性。

案例实现:

步骤一:建立数据库/数据表

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| sql create database db_system if not exits; drop table if exists sys_dict_type; create table sys_dict_type ( dict_id bigint(20) not null auto_increment comment '字典主键', dict_name varchar(100) default '' comment '字典名称', dict_type varchar(100) default '' comment '字典类型', status char(1) default '0' comment '状态(0正常 1停用)', create_by varchar(64) default '' comment '创建者', create_time datetime comment '创建时间', update_by varchar(64) default '' comment '更新者', update_time datetime comment '更新时间', remark varchar(500) default null comment '备注', primary key (dict_id), unique (dict_type) ) engine=innodb auto_increment=100 comment = '字典类型表'; insert into sys_dict_type values(1, '用户性别', 'sys_user_sex', '0', 'admin', sysdate (), '', null, '用户性别列表'); insert into sys_dict_type values(2, '菜单状态', 'sys_show_hide', '0', 'admin', sysdate (), '', null, '菜单状态列表'); insert into sys_dict_type values(3, '系统开关', 'sys_normal_disable', '0', 'admin', sysdate (), '', null, '系统开关列表'); insert into sys_dict_type values(4, '任务状态', 'sys_job_status', '0', 'admin', sysdate (), '', null, '任务状态列表'); insert into sys_dict_type values(5, '任务分组', 'sys_job_group', '0', 'admin', sysdate (), '', null, '任务分组列表'); insert into sys_dict_type values(6, '系统是否', 'sys_yes_no', '0', 'admin', sysdate (), '', null, '系统是否列表'); insert into sys_dict_type values(7, '通知类型', 'sys_notice_type', '0', 'admin', sysdate (), '', null, '通知类型列表'); insert into sys_dict_type values(8, '通知状态', 'sys_notice_status', '0', 'admin', sysdate (), '', null, '通知状态列表'); insert into sys_dict_type values(9, '操作类型', 'sys_oper_type', '0', 'admin', sysdate (), '', null, '操作类型列表'); insert into sys_dict_type values(10, '系统状态', 'sys_common_status', '0', 'admin', sysdate(), '', null, '登录状态列表'); |

步骤二:引入项目依赖

pom.xml

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.example</groupId> <artifactId>redis_dictionary</artifactId> <version>0.0.1-SNAPSHOT</version> <name>redis_dictionary</name> <description>redis_dictionary</description> <properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.7.6</spring-boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.0</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--本地缓存--> <dependency> <groupId>com.github.ben-manes.caffeine</groupId> <artifactId>caffeine</artifactId> </dependency> <!--redis缓存--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>{spring-boot.version}\ \pom\ \import\ \ \ \ \ \ \ \org.apache.maven.plugins\ \maven-compiler-plugin\ \3.8.1\ \ \1.8\ \1.8\ \UTF-8\ \ \ \ \org.springframework.boot\ \spring-boot-maven-plugin\ \{spring-boot.version}</version> <configuration> <mainClass>com.example.redis_dictionary.RedisDictionaryApplication</mainClass> <skip>true</skip> </configuration> <executions> <execution> <id>repackage</id> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project> |

步骤三:添加全局配置

application.properties

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Properties server.port=8080 spring.datasource.url=jdbc:mysql://localhost:3306/db_system?allowPublicKeyRetrieval=true&useSSL=true&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 mybatis.mapper-locations=classpath:mappers/*.xml mybatis.configuration.map-underscore-to-camel-case=true logging.level.com.example=INFO logging.level.org.springframework.cache=TRACE logging.level.com.github.benmanes.caffeine=DEBUG spring.redis.host=192.168.8.100 spring.redis.port=6379 spring.redis.password=123456 |

步骤四:添加配置类

Cache.config

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java package com.example.redis_dictionary.base.config; import com.github.benmanes.caffeine.cache.Caffeine; import org.springframework.cache.CacheManager; import org.springframework.cache.caffeine.CaffeineCacheManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.concurrent.TimeUnit; @Configuration public class CacheConfig { //设置本地缓存的最大容量为5000,过期时间为10分钟 @Bean public CacheManager cacheManager() { CaffeineCacheManager cacheManager = new CaffeineCacheManager(); cacheManager.setCaffeine(Caffeine.newBuilder () .maximumSize(5000) .expireAfterWrite(10, TimeUnit.MINUTES )); return cacheManager; } } |

Redis.config

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java package com.example.redis_dictionary.base.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; @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; } } |

JsonResult.java

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java package com.example.redis_dictionary.base; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class JsonResult { private Integer code = 0; private String message = "ok"; private Object data; public JsonResult(Integer code, String message) { this.code = code; this.message = message; } public JsonResult(Object data) { this.data = data; } } |

步骤五:创建实体类

Dict.java

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java package com.example.redis_dictionary.pojo.entity; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; import java.time.LocalDateTime; @Data @AllArgsConstructor @NoArgsConstructor public class Dict implements Serializable { private static final long serialVersionUID = 5385654745413557337L; private Long dictId; private String dictName; private String dictType; private String status; private String createBy; private String updateBy; private String remark; } |

步骤六:创建Mapper

DictMapper.java

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java package com.example.redis_dictionary.mapper; import com.example.redis_dictionary.pojo.entity.Dict; import org.apache.ibatis.annotations.Mapper; @Mapper public interface DictMapper { int insert(Dict dict); Dict selectById(Long id); } |

步骤七:创建xml

DictMapper.xml

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| xml <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.redis_dictionary.mapper.DictMapper"> <select id="selectById" resultType="com.example.redis_dictionary.pojo.entity.Dict"> select * from sys_dict_type where dict_id=#{dictId} </select> <insert id="insert" keyProperty="dictId" useGeneratedKeys="true" parameterType="com.example.redis_dictionary.pojo.entity.Dict"> insert into sys_dict_type(dict_name,dict_type,status,create_by,update_by,remark) values(#{dictName},#{dictType},#{status},#{createBy},#{updateBy},#{remark}) </insert> </mapper> |

步骤八:创建Service

DictService.java

|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java package com.example.redis_dictionary.service; import com.example.redis_dictionary.pojo.entity.Dict; public interface DictService { Dict selectById(Long dictId); int saveDict(Dict dictType); } |

步骤九:创建ServiceImpl

DictServiceImpl.java

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java package com.example.redis_dictionary.service.impl; import com.example.redis_dictionary.mapper.DictMapper; import com.example.redis_dictionary.pojo.entity.Dict; import com.example.redis_dictionary.service.DictService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Isolation; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; //事务注解 @Transactional(readOnly = false, rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED , propagation = Propagation.REQUIRED ) @Service public class DictServiceImpl implements DictService { @Autowired private DictMapper dictMapper; //redis缓存 @Autowired private RedisTemplate redisTemplate; //本地缓存 @Autowired private CacheManager cacheManager; @Transactional(readOnly = true) @Override public Dict selectById(Long dictId) { String key = "dict:" + dictId; //从本地缓存中获取数据 Cache cache = cacheManager.getCache("dictCache"); assert cache != null; Dict dict = cache.get(key, Dict.class); if (dict!= null) { return dict; } //从redis缓存中获取数据 ValueOperations vo = redisTemplate.opsForValue(); dict = (Dict) vo.get(key); if (dict != null) { cache.put(key, dict); return dict; } dict=dictMapper.selectById(dictId); cache.put(key, dict); vo.set(key, dict); return dict; } @Override public int saveDict(Dict dictType) { return 0; } } |

步骤十:创建Controller

DictController.java

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java package com.example.redis_dictionary.controller; import com.example.redis_dictionary.base.JsonResult; import com.example.redis_dictionary.service.DictService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; @RestController public class DictController { @Autowired private DictService dictService; @GetMapping("/dict/{id}") public JsonResult selectById(@PathVariable("id") Long dictId) { return new JsonResult(dictService.selectById(dictId)); } } |

步骤十一:创建HttpClient进行测试

dict-api-rest.http

|-------------------------------------------|
| HTTP ### GET http://localhost:8080/dict/1 |

步骤十二:断点测试

在DictServiceImpl.java中添加断点测试缓存是否生效

第一次:本地缓存为NULL,Redis缓存为NULL,执行数据库查询;

第二次:本地缓存存在数据,直接执行本地缓存查询,返回数据。

业务加强:

问题描述:

用户查询某一个数据,但该数据不存在于redis内存数据库中(缓存没有命中),这时候就会向持久层数据库查询,但持久层数据库也没有该数据,于是本次查询失败,若用户很多时,他们查询的数据不存在于redis内存数据库中(缓存没有命中),于是都去请求了持久层数据库,这样就会给持久层数据库带来很大的压力,这种大量不走redis内存数据库的现象就叫缓存穿透,为了解决缓存穿透的问题,我们可以通过设置布隆过滤器来解决。

问题解决:

步骤一:添加布隆过滤器对应的pom依赖

|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| xml <!--hutool 工具包--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.31</version> </dependency> |

步骤二:在CacheConfig中添加布隆过滤器配置

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java @Bean public BloomFilter bloomFilter(){ BitMapBloomFilter filter= new BitMapBloomFilter(500);//设置过滤器大小为500 filter.add("dict:1");//这里可以存储一个key,用于测试,实际做了添加就不用了 return filter; } |

步骤三:在DictServiceImpl中添加布隆过滤器过滤数据逻辑

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Java @Autowired private BloomFilter bloomFilter; @Override public Dict selectById(Long dictId) { String key = "dict:" + dictId; //使用布隆过滤器判断key是否存在,防止缓存击穿 if (!bloomFilter.contains(key)) { return null; } //从本地缓存中获取数据 Cache cache = cacheManager.getCache("dictCache"); assert cache != null; Dict dict = cache.get(key, Dict.class); if (dict!= null) { return dict; } //从redis缓存中获取数据 ValueOperations vo = redisTemplate.opsForValue(); dict = (Dict) vo.get(key); if (dict != null) { cache.put(key, dict); return dict; } dict=dictMapper.selectById(dictId); cache.put(key, dict); vo.set(key, dict); return dict; } |

消息队列

List实现

步骤一:创建ListQueueService

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| typescript package com.example.redis_dictionary.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @Service public class ListQueueService { @Autowired private RedisTemplate redisTemplate; //左侧入队 public void enqueue(String key, Object value) { redisTemplate.opsForList().leftPush(key, value); } //右侧出队 public Object dequeue(String key) { return redisTemplate.opsForList().rightPop(key); } } |

步骤二:测试分析

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| typescript package com.example.redis_dictionary; import com.example.redis_dictionary.service.ListQueueService; import com.fasterxml.jackson.annotation.JsonSubTypes; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class ListQueueTest { @Autowired private ListQueueService listQueueService; @Test //测试入队 public void testEnqueue() { listQueueService.enqueue("test", "value1"); listQueueService.enqueue("test", "value2"); listQueueService.enqueue("test", "value3"); } @Test //测试出队 public void testDequeue() { System.out .println(listQueueService.dequeue("test")); System.out .println(listQueueService.dequeue("test")); System.out .println(listQueueService.dequeue("test")); } } |

发布订阅

步骤一:创建Service对象

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| typescript package com.example.redis_dictionary.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @Service public class PubSubService { @Autowired private RedisTemplate redisTemplate; //向指定频道发布消息 public void publish(String channel, String message) { redisTemplate.convertAndSend(channel, message); } } |

步骤二:消息监听对象

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java package cn.tedu.dictionary.service.listener; import org.springframework.data.redis.connection.Message; import org.springframework.data.redis.connection.MessageListener; import org.springframework.stereotype.Component; @Component public class PubSubMessageListener implements MessageListener { public PubSubMessageListener() { System.out .println("PubSubMessageListener()"); } @Override public void onMessage(Message message, byte[] pattern) { System.out .println("channel:"+new String(message.getChannel())); System.out .println("message:"+new String(message.getBody())); } } |

步骤三:RedisConfig对象

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| java @Bean public RedisMessageListenerContainer container(RedisConnectionFactory factory, MessageListener messageListener) { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(factory); //只能监听一个通道 // container.addMessageListener(messageListener, new ChannelTopic("channel")); //可以监听多个通道 container.addMessageListener(messageListener,new PatternTopic("channel.*")); return container; } |

步骤四:Controller对象

|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| typescript package com.example.redis_dictionary.controller; import com.example.redis_dictionary.base.JsonResult; import com.example.redis_dictionary.service.PubSubService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class MessageController { @Autowired private PubSubService pubSubService; @PostMapping("/publish/{channel}/{message}") public JsonResult publish(@PathVariable String channel, @PathVariable Object message) { System.out .println("channel="+channel); System.out .println("message="+message); pubSubService.publish(channel,message); return new JsonResult(); } } |

步骤五:创建HttpClient进行测试

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| HTTP ### POST http://localhost:8080/publish/channel1/hello wu laoshi Content-Type : application/json { } ### POST http://localhost:8080/publish/channel2/hello world Content-Type : application/json { } |

对比分析

|---------|--------------------|-----------------|
| 特性 | List(点对点对列) | Pub/Sub(发布订阅) |
| 消息模型 | 点对点(P2P),消息被消费后即删除 | 广播式,消息发送给所有订阅者 |
| 消费者行为 | 主动拉取(Pull) | 被动接收推送(Push) |
| 是否需要监听器 | 否(依赖阻塞命令) | 是(需持续监听频道) |
| 消息持久化 | 支持(消息保留至被消费) | 不支持(瞬时传递,无存储) |
| 适用场景 | 任务队列、顺序消费 | 实时通知、事件广播(如聊天室) |

相关推荐
isaki1373 小时前
qt day1
开发语言·数据库·qt
流星白龙4 小时前
【Qt】4.项目文件解析
开发语言·数据库·qt
小钻风33664 小时前
HTTPS是如何确保安全的
网络·数据库
低音钢琴4 小时前
【SpringBoot从初学者到专家的成长11】Spring Boot中的application.properties与application.yml详解
java·spring boot·后端
越千年4 小时前
用Go实现类似WinGet风格彩色进度条
后端
蓝色汪洋4 小时前
string字符集
java·开发语言
卿言卿语4 小时前
CC1-二叉树的最小深度
java·数据结构·算法·leetcode·职场和发展
淳朴小学生4 小时前
静态代理和动态代理
后端
不太会写4 小时前
又开始了 小程序定制
vue.js·spring boot·python·小程序