Redis实战之实现特价商品展示
【摘要】 Redis实战之实现特价商品展示
前言🔍
上一篇:Redis入门
本篇涉及相关命令:DEL、Lrange、Rpush
示例基于SpringBoot 2.7.14
DEL
DEL命令用于删除已存在的键。不存在的 key 会被忽略。
Lrange
Lrange 返回列表中指定区间内的元素,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
返回值:一个列表,包含指定区间内的元素。
Rpush
Rpush 命令用于将一个或多个值插入到列表的尾部(最右边)。如果列表不存在,一个空列表会被创建并执行 Rpush 操作。 当列表存在但不是列表类型时,返回一个错误。
返回值:执行 Rpush 操作后,列表的长度。
示例💡
通常特价商品页有数据量少、高并发等特点。实现思路是先把数据库中的数据抽取到Redis里面。采用定时任务来刷新缓存。
模仿如下图的特价商品的展示,我们可以使用Redis 列表(List)实现将这部分数据定时刷到Redis中。
以下示例基于SpringBoot 2.7.14,定时任务用Quartz实现。
引入相关场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.18</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- spring-boot-starter-quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>3.0.6</version>
</dependency>
application.properties
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=1
spring.redis.client-type=lettuce
spring.redis.connect-timeout=5000
spring.redis.lettuce.pool.enabled=true
spring.redis.lettuce.pool.min-idle=5
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-active=10
##最大阻塞等待时间,-1 表示不限制
spring.redis.lettuce.pool.max-wait=3000
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
spring.quartz.properties.org.quartz.threadPool.threadCount=10
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
RedisConguration
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
/**
* @author lonewalker
*/
@Configuration
public class RedisConfiguration {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database}")
private int database;
@Bean
@Primary
@ConfigurationProperties(prefix = "spring.redis.lettuce.pool")
public GenericObjectPoolConfig<LettucePoolingClientConfiguration> genericObjectPoolConfig() {
return new GenericObjectPoolConfig<>();
}
@Bean
public RedisStandaloneConfiguration redisStandaloneConfiguration() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setDatabase(database);
redisStandaloneConfiguration.setHostName(host);
redisStandaloneConfiguration.setPassword(password);
redisStandaloneConfiguration.setPort(port);
return redisStandaloneConfiguration;
}
@Bean
public LettuceClientConfiguration lettuceClientConfiguration(GenericObjectPoolConfig<LettucePoolingClientConfiguration> genericObjectPoolConfig) {
return LettucePoolingClientConfiguration.builder().poolConfig(genericObjectPoolConfig).build();
}
@Bean
public LettuceConnectionFactory lettuceConnectionFactory(RedisStandaloneConfiguration redisSentinelConfiguration, LettuceClientConfiguration lettuceClientConfiguration) {
return new LettuceConnectionFactory(redisSentinelConfiguration, lettuceClientConfiguration);
}
/**
* 创建 RedisTemplate 设置序列化方式
*/
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory factory) {
// 创建 RedisTemplate 对象
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 设置 RedisConnection 工厂
template.setConnectionFactory(factory);
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
// 不将日期写为时间戳
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
// 忽略未知属性
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
// 对象属性为空时可以序列化
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
// 记录被序列化的类型信息
.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.WRAPPER_ARRAY)
// null 值不序列化
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
// 日期处理
.registerModule(new JavaTimeModule());
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer(objectMapper);
// 默认使用了jdk的序列化方式,可读性差,我们使用 String 序列化方式,序列化 KEY 。
template.setKeySerializer(RedisSerializer.string());
template.setHashKeySerializer(RedisSerializer.string());
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
template.setValueSerializer(serializer);
template.setHashValueSerializer(serializer);
return template;
}
}
RedisUtil
@Component
public class RedisUtil {
private RedisTemplate<String, Object> redisTemplate;
@Autowired
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(Arrays.asList(key));
}
}
}
/**
* 获取list缓存的内容
*
* @param key 键
* @param start 开始
* @param end 结束 0 到 以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
* @return List
*/
public List<Object> lRange(String key, Long start, Long end) {
try {
return redisTemplate.opsForList().range(key, start, end);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return Boolean
*/
public Boolean rightPush(String key, List<?> value) {
try {
redisTemplate.opsForList().rightPushAll(key, value.toArray());
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return Boolean
*/
public Boolean rightPush(String key, List<?> value, Long time) {
try {
redisTemplate.opsForList().rightPushAll(key, value.toArray());
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
将数据刷入到Redis的任务。
/**
* @author lonewalker
*/
@Slf4j
public class RefreshJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) {
//模拟从数据库读取100件特价商品,用于加载到特价商品的页面中
List<Product> list= products();
RedisUtil.delete(RedisKey.BARGAIN_KEY);
//采用redis list数据结构的rPush来实现存储
RedisUtil.rightPush(RedisKey.BARGAIN_KEY, list);
log.info("特价商品已刷新");
}
public static List<Product> products() {
List<Product> list=new ArrayList<>();
for (int i = 0; i < 100; i++) {
Random rand = new Random();
int id= rand.nextInt(10000);
Product obj=new Product((long) id,"product"+i,i,"detail");
list.add(obj);
}
return list;
}
}
常量
public class RedisKey {
public static final String BARGAIN_KEY = "bargain";
}
定时任务
import com.example.redis.job.RefreshJob;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
import static org.quartz.CronScheduleBuilder.cronSchedule;
@Slf4j
@Configuration
public class SchedulerConfiguration {
@Resource
private Scheduler scheduler;
@Bean
public void startJob() throws SchedulerException {
refreshData(scheduler);
}
private void refreshData(Scheduler scheduler) throws SchedulerException {
//定义一个触发器
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("triggerOne", "bargain")
.startNow()
.withSchedule(cronSchedule("0 0/1 * * * ?"))
.build();
//定义一个JobDetail
JobDetail jobDetail = JobBuilder.newJob(RefreshJob.class)
.withIdentity("jobOne", "bargain")
.build();
//调度加入这个job
scheduler.scheduleJob(jobDetail, trigger);
}
}
entity
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Product {
private Long id;
private String name;
private Integer amount;
private String description;
}
controller
import com.example.redis.entity.Product;
import com.example.redis.service.RedisService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
/**
* @author lonewalker
*/
@RequestMapping("/redis")
@RestController
public class RedisController {
@Resource
private RedisService redisService;
@GetMapping("/queryBargain")
public List<Product> queryBargain(int pageNum,int pageSize){
return redisService.queryBargain(pageNum, pageSize);
}
}
service
@Slf4j
@Service
public class RedisService {
@Resource
private RedisUtil redisUtil;
public List<Product> queryBargain(int pageNum,int pageSize){
List<Product> products = new ArrayList<>();
long start = (long) (pageNum - 1) * pageSize;
long end = start + pageSize - 1;
try {
//采用redis list数据结构的lrange命令实现分页查询
List<Object> list = redisUtil.lRange(RedisKey.BARGAIN_KEY, start, end);
if (CollUtil.isEmpty(list)) {
//TODO 走DB查询
}
products = (List<Product>) (Object) list;
} catch (Exception ex) {
//这里的异常,一般是redis瘫痪 ,或 redis网络timeout
log.error("exception:", ex);
//TODO 走DB查询
}
return products;
}
}
演示📖
启动项目看一下效果:
数据刷入Redis之后可以请求接口了
再看一下Redis里是存入了100条
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)