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) |
| 是否需要监听器 | 否(依赖阻塞命令) | 是(需持续监听频道) |
| 消息持久化 | 支持(消息保留至被消费) | 不支持(瞬时传递,无存储) |
| 适用场景 | 任务队列、顺序消费 | 实时通知、事件广播(如聊天室) |

相关推荐
IvorySQL7 小时前
你真的知道你正在运行哪个 PostgreSQL吗?
数据库·postgresql
申阳8 小时前
Day 6:04. 基于Nuxt开发博客项目-LOGO生成以及ICON图标引入
前端·后端·程序员
硅胶人8 小时前
[prowlarr][radarr][sonarr][qBitorrent]套件打造家庭影音中心
后端
l1t8 小时前
利用DeepSeek采用hugeint转字符串函数完善luadbi-duckdb的decimal处理
数据库·lua·c·duckdb·deepseek
无敌最俊朗@8 小时前
Qt 开发终极坑点手册图表版本
数据库
JavaGuide8 小时前
OPPO 后端校招面试,过于简单了!
java·后端
码割机8 小时前
Linux服务器安装jdk和maven详解
java·linux·maven
调试人生的显微镜8 小时前
如何查看手机使用记录?四种实用方法详解
后端
侯爵8 小时前
rabbitmq 如何保证消息顺序消费
后端
sualpha8 小时前
再见,StringManipulation!AI一键搞定字符串转换、JSON格式化与翻译
后端