key:value形式的数据存储在Redis当中。
redis有16个数据库,下标为0-15,默认为0。
方式1:redis-cli -a password(默认密码为123456)
方式2:redis-cli
auth password (默认密码为123456)
exit
keys *
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...
strlen key
方式1:set key value EX seconds (例如:set a 100 EX 10)
方式2:set key value
expire key seconds
set key value
pexpire key seconds
ttl key
persist key
type key
incr key (若key存在则每次自动加1;若key不存在则自动创建,创建后的初始值为1)
incyby key increment (可通过increment指定每次递增的数值,若key存在则每次自动加increment;若key不存在则自动创建,创建后的初始值为指定的increment的数值)
decr key (若key存在则每次自动减1;若key不存在则自动创建,创建后的初始值为-1)
decrby key decrement (可通过decrement指定每次递减的数值,若key存在则每次自动减decrement;若key不存在则自动创建,创建后的初始值为指定的decrement的数值)
append key value (若key存在则追加value对应的值;若key不存在则自动创建,创建后的初始值为value)
Hash类型操作
RedisTemplate对象

--> 即如下依赖:
org.springframework.boot
spring-boot-starter-data-redis
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);

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中查看数据:

- 写一个外部类,以后写pojo类尽量都实现Serializable接口:
- 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内存数据库的现象就叫缓存穿透。
解决方案:
- 布隆过滤器(BloomFilter)
在控制层对请求先进行校验,不符合条件的请求则被丢弃,从而避免对持久层数据库造成的查询压力。
- 缓存空对象
当查询的数据不存在于redis中时,请求到了持久层数据库中去查询数据,但查询不出数据,这时会返回空对象,同时把该空对象缓存到redis里,然后设置一个过期时间,往后只要再次请求查询该条数据,该条数据都会从redis中获取(获取redis返回的空对象),从而保护了后端的数据源。
缺点:
- 因为空对象能被缓存起来,而有些请求有可能查询不出数据,所以过程中可能产生大量的返回空对象然后被redis缓存的现象,而这意味着redis需要更多的空间来存储更多的键。
- 即使对空对象设置了过期时间,但如果在redis的空对象在未过时的情况下,持久层数据库已经有了对应的数据,而redis对应的键的值仍是空对象,这时请求查询出的仍是空对象,而不是持久层里已经有的数据,而这种情况对于需要保持数据一致性的业务会造成影响。
缓存击穿:
问题描述:
redis里的一个key非常热点,导致大并发集中对这个key不断的进行访问,当在这个key过期的瞬间,持续的大并发就会跳过缓存,直接作用在持久层数据库上,请求在访问持久层数据库查询数据的同时,持久层数据库也需要回写缓存,这时候就会导致持久层数据库瞬间压力过大导致服务器宕机,这种现象就叫做缓存击穿。
解决方案:
- 设置热点key永不过期。
- 加互斥锁:使用分布式锁在redis和持久层数据库之间加锁,让每次查询都能保证只有一个线程进去,其他线程等待,这样做就能保证对于每一个key同时只能有一个线程去查询后端持久层数据库,而其他线程没有分布式锁的权限,所以只能等待,这种解决方案把高并发的压力转移到了分布式锁身上,但同时也加大了对分布式锁的考验。
缓存雪崩:
问题描述:
在某一时间段,一批key集中过期失效或者redis宕机,导致大量的请求作用在持久层数据库上,导致持久层数据库挂掉。
解决方案:
- redis高可用:redis集群搭建
- 限流降级:通过加锁或队列来控制读取持久层数据库的线程数量,例如通过对某个key加锁来保证只有一个线程对该key进行读和写,其他线程则需要等待。
- 数据预热:在正式部署前把可能被大量访问的数据先访问一遍,这些被访问的数据就会被加载到缓存中,在正式的大量访问到来之后减轻持久层数据库的压力;在发生大并发访问前手动触发加载缓存所需要的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}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${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) |
是否需要监听器 | 否(依赖阻塞命令) | 是(需持续监听频道) |
消息持久化 | 支持(消息保留至被消费) | 不支持(瞬时传递,无存储) |
适用场景 | 任务队列、顺序消费 | 实时通知、事件广播(如聊天室) |