一、目的
实验目的:
理解缓存中间件Redis基本原理
掌握SpringBoot整合Redis,了解Redis的优缺点
实验要求:
独立完成实验操作,并撰写实验报告
实验内容:
Redis安装与使用
(1)安装Redis,启动redis_server.exe;
(2)运行redis_cli.exe,连接server,并测试添加、查询命令行。附注:建议测试四种常用的数据结构(字符串、集合、列表、Hash),具体测试内容自定。
SpringBoot中整合Redis
在实验1搭建的工程基础上,完成如下操作:
(1)在pom.xml中添加redis支持;
(2)在控制器中添加一个RequestMapping,用于测试Redis集合元素的添加(类型不限,可以考虑使用Jackson序列化),并把所有集合元素显示到页面中。
秒杀程序
为工程添加一个秒杀模块,完成用户抢单的并发可行性操作。
(1)合理设计数据库,相应地在工程中添加驱动、MyBatis配置;
(2)设计基于Redis的秒杀程序,包括发布秒杀信息、秒杀抢单操作(注意封装);
(3)测试时(使用jmeter或其他),模拟多个用户并发抢单,并查看秒杀结果;
(4)修改不同参数(商品数量、并发用户数),测试程序是否能够实现如下基本功能要求:抢到的总数不超过商品总数,且每个用户最多抢到一次。
说明:本题可以是模块(可参照教材的例子),也可以完整的网页,但要预留可并发测试的接口。
二、实验内容与设计思想
2.1 设计思路
Redis安装与使用
(1)安装Redis,启动redis_server.exe;
(2)运行redis_cli.exe,连接server,并测试添加、查询命令行。附注:建议测试四种常用的数据结构(字符串、集合、列表、Hash),具体测试内容自定。
SpringBoot中整合Redis
在实验1搭建的工程基础上,完成如下操作:
(1)在pom.xml中添加redis支持;
(2)在控制器中添加一个RequestMapping,用于测试Redis集合元素的添加(类型不限,可以考虑使用Jackson序列化),并把所有集合元素显示到页面中。
秒杀程序
为工程添加一个秒杀模块,完成用户抢单的并发可行性操作。
(1)合理设计数据库,相应地在工程中添加驱动、MyBatis配置;
(2)设计基于Redis的秒杀程序,包括发布秒杀信息、秒杀抢单操作(注意封装);
(3)测试时(使用jmeter或其他),模拟多个用户并发抢单,并查看秒杀结果;
(4)修改不同参数(商品数量、并发用户数),测试程序是否能够实现如下基本功能要求:抢到的总数不超过商品总数,且每个用户最多抢到一次。
说明:本题可以是模块(可参照教材的例子),也可以完整的网页,但要预留可并发测试的接口。
2.2 主要数据结构
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Custom implements Serializable {
private static final long serialVersionUID = 320539504981592510L;
private Integer customId;
private String name;
private Integer age;
private String sex;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Goods implements Serializable {
private static final long serialVersionUID = -42557906261804648L;
private Integer goodsId;
/**库存*/ private Integer inventory;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Record implements Serializable
{
private static final long serialVersionUID = -85697347674864643L;
private Integer goodsId;
private Date createTime;
private Integer customId;
}
2.3 主要代码结构
└── service/
└── controller/
├── CustomController.java
├── GoodsController.java
├── RecordController.java
├── RedisController.java
├── RedisSetController.java
├── SeckillController.java
└── domain/
├── Custom.java
├── Goods.java
├── Record.java
└── mapper/
├── CustomMapper.java
├── GoodsMapper.java
├── RecordMapper.java
└── service/
├── CustomService.java
├── GoodsService.java
└── impl/
├── CustomServiceImpl.java
├── GoodsServiceImpl.java
├── RecordServiceImpl.java
├── RedisServiceImpl.java
├── SeckillServiceImpl.java
├── RecordService.java
├── RedisService.java
├── SeckillService.java
├── ServiceApplication.java
└── util/
├── Config.java
├── CORSFilter.java
├── R.java
├── RedisConfig.java
三、实验使用环境
平台:win10
软件:idea
实验步骤和调试过程
4.1 实验步骤
4.1.1 Redis安装与使用
(1)安装Redis,启动redis_server.exe;
(2) 运行redis_cli.exe,连接server,并测试添加、查询命令行。附注:建议测试四种常用的数据结构(字符串、集合、列表、Hash),具体测试内容自定。
字符串操作:
设置键值对:SET key value
获取键的值:GET key
追加值到键的值末尾:APPEND key value
获取指定范围内的子字符串:GETRANGE key start end
集合操作:
添加一个或多个元素到集合中:sadd key2 value1 value2
获取集合中的所有元素:SMEMBERS key2
获取集合中元素的数量:SCARD key2
判断元素是否在集合中:SISMEMBER key2 value1,SISMEMBER key2 value3
列表操作:
在列表的头部添加一个或多个元素:LPUSH key3 value1 value2
在列表的尾部添加一个或多个元素:RPUSH key3 value1 value2
获取列表指定范围的元素:LRANGE key3 start end
获取列表所有元素:LRANGE key3 0 -1
获取列表的长度:LLEN key3
删除列表:DEL key3
哈希操作:
设置哈希中的字段和值:HSET key4 field1 value1 field2 value2
获取哈希中指定字段的值:HGET key4 field
获取哈希中所有字段和值:HGETALL key4
获取哈希中字段的数量:HLEN key4
4.1.2. SpringBoot中整合Redis
(1)在pom.xml添加redis支持
(2)在控制器中添加一个RequestMapping,用于测试Redis集合元素的添加(类型不限,可以考虑使用Jackson序列化),并把所有集合元素显示到页面中。
RedisConfig.java
@Configuration
public class RedisConfig {
@Autowired(required = false)
private RedisConnectionFactory redisConnectionFactory; //Redis连接工厂(redis-cli窗口)
@Bean
public RedisTemplate
RedisTemplate
redisTemplate.setConnectionFactory(redisConnectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer()); //指定大key序列化策略为为String序列化
// redisTemplate.setKeySerializer(new JdkSerializationRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); //JSON泛型
// redisTemplate.setHashKeySerializer(new StringRedisSerializer()); //指定hashKey序列化策略为String序列化
return redisTemplate;
}
/**
* 缓存redis-stringRedisTemplate
*/
@Bean
public StringRedisTemplate stringRedisTemplate(){
//采用默认配置即可-后续有自定义配置时则在此处添加即可
StringRedisTemplate stringRedisTemplate=new StringRedisTemplate();
stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
return stringRedisTemplate;
}
}
RedisSetController.java
@RestController
@RequestMapping("/redis")
public class RedisSetController
{
@Autowired
private RedisTemplate
@GetMapping
public R getAllValue()
{
Set
keys.remove("name");
System.out.println("keys" + keys);
Map
if (keys != null)
{
for (String key : keys)
{
System.out.println(key);
Object value = redisTemplate.opsForValue().get(key);
data.put(key, value);
}
}
return R.ok().setData(data);
}
@DeleteMapping
public R deleteValue()
{
try
{
Set
keys.remove("name");
for (String key : keys)
{
redisTemplate.delete(key);
}
return R.ok().setData(true);
} catch (Exception e)
{
return R.exp().setData(e.getMessage());
}
}
@GetMapping("/{key}")
public R getValue(@PathVariable String key)
{
return R.ok().setData(redisTemplate.opsForSet().members(key));
}
@PostMapping("/{key}")
public R addValue(@PathVariable String key, @RequestParam String[] values)
{
try
{
for (String value : values)
redisTemplate.opsForSet().add(key, value);
return R.ok().setData(redisTemplate.opsForSet().members(key));
} catch (Exception e)
{
e.printStackTrace();
return R.exp().setData(e.getMessage());
}
}
@DeleteMapping("/{key}")
public R deleteValue(@PathVariable String key,@RequestParam String[] values)
{
try
{
for(String value:values)
redisTemplate.opsForSet().remove(key, value);
return R.ok().setData(true);
} catch (Exception e)
{
return R.exp().setData(e.getMessage());
}
}
@DeleteMapping("/all/{key}")
public R deleteValue(@PathVariable String key)
{
try{
redisTemplate.delete(key);
return R.ok().setData(true);
}catch (Exception e)
{
return R.exp().setData(e.getMessage());
}
}
@PutMapping("/{key}")
@ResponseBody
public R updateValue(@PathVariable String key, @RequestParam(required = false) String[] values)
{
try
{
System.out.println("==="+values);
redisTemplate.delete(key);
for (String value : values)
redisTemplate.opsForSet().add(key, value);
return R.ok().setData("更新成功");
} catch (Exception e)
{
e.printStackTrace();
return R.exp().setData(e.getMessage());
}
}
}
4.1.3 秒杀程序
(1)合理设计数据库,相应地在工程中添加驱动、MyBatis配置;
记录顾客的信息:custom表:
custom_id: int 类型,自增字段,自定义表的唯一标识符。 name: varchar(255) 类型,字符型字段,表示客户的姓名。 age: int 类型,整型字段,表示客户的年龄。 sex: varchar(255) 类型,字符型字段,表示客户的性别。
记录商品的信息:goods 表:
goods_id: int 类型,自增字段,商品表的唯一标识符。 inventory: int 类型,整型字段,表示商品的库存数量。
记录顾客购买商品的信息:record 表:
goods_id: int 类型,表示商品的唯一标识符,同时也是 record 表的外键,关联 goods 表中的 goods_id 字段。 create_time: datetime 类型,表示记录创建时间的日期和时间。 custom_id: int 类型,表示客户的唯一标识符,同时也是 record 表的外键,关联 custom 表中的 custom_id 字段。
(2)设计基于Redis的秒杀程序,包括发布秒杀信息、秒杀抢单操作(注意封装);
下面展示SeckillController,SeckillService,SeckillServiceImpl,RedisService,RedisServiceImpl等主要程序的代码。(关于数据库三个表代码太多了,所以仅展示custom表的Controller,Service,Impl,domain作为典型案例)
@RestController
@RequestMapping("/seckill")
public class SeckillController
{
@Resource
private SeckillService seckillService;
@GetMapping
public int getValue(int goodsId, int customId)
{
System.out.println("货物id:"+goodsId+",顾客id:"+customId);
return seckillService.seckill(goodsId, customId);
}
@GetMapping("/init")
public R init()
{
return seckillService.init();
}
}
public interface SeckillService
{
R init();
int seckill(int goodsId, int customId);
}
@Service("seckillService")
public class SeckillServiceImpl implements SeckillService
{
@Resource
private RedisService redisService;
@Resource
private RecordService recordService;
@Resource
private GoodsService goodsService;
@Override
public R init()
{
//清空redis
redisService.deleteValue();
// 加载已有记录到redis
List
for (Record record : records)
redisService.addValue(record.getGoodsId() + ":" + record.getCustomId(), 1);
// 加载货物剩余数量到redis
List
for (Goods goods : goodss)
redisService.addValue(goods.getGoodsId() + ":total", goods.getInventory());
return R.ok().setData(redisService.getAllValue());
}
@Override
public int seckill(int goodsId, int customId)
{
String robId = goodsId + ":" + customId; //记录是否已经抢到货物
String goodsTotal = goodsId + ":total";
//1. 查询是否已经抢到货物,若已经抢到,则返回-1
Object obj = redisService.getValue(robId).getData();
if (obj != null && !"".equals("" + obj))
return -1;
//2. 加锁
Boolean lock = (Boolean) redisService.reidsLock(robId + "-lock", "lock").getData();
if (lock)
{
while(!(Boolean) redisService.reidsLock(goodsId + "-lock", "lock").getData());
//2. 若剩余货物列表为空,则返回-1。
int total = (int) redisService.getValue(goodsTotal).getData();
if (total <= 0)
return -1;
//3. 否则,取一个货物,并返回1。
System.out.println("======> " + customId + "抢到" + goodsId);
//update total
redisService.decrement(goodsTotal);
//取消锁
redisService.reidsUnlock(goodsId+"-lock");
//update robId
redisService.addValue(robId, 1);
//update mysql
recordService.insert(new Record(goodsId, new Date(System.currentTimeMillis()), customId));
goodsService.updateLow(goodsId);
return 1;
} else
return -1;
}
}
public interface RedisService
{
R getValue(String key);
R getAllValue();
R addValue(String key, Object value);
R setWithExpiration(String key, int expirationInSeconds, Object value);
R deleteValue(String key);
R updateValue(String key, Object value);
R reidsLock(String key, String value);
R reidsUnlock(String key);
R decrement(String key);
R deleteValue();
}
@Service("redisService")
public class RedisServiceImpl implements com.jihuiting.service.service.RedisService
{
@Autowired
private RedisTemplate
@Override
public R getValue(String key)
{
return R.ok().setData(redisTemplate.opsForValue().get(key));
}
@Override
public R getAllValue()
{
Set
keys.remove("name");
System.out.println("keys" + keys);
Map
if (keys != null)
{
for (String key : keys)
{
System.out.println(key);
Object value = redisTemplate.opsForValue().get(key);
data.put(key, value);
}
}
return R.ok().setData(data);
}
@Override
public R addValue(String key, Object value)
{
try
{
System.out.println("post:key:" + key);
redisTemplate.opsForValue().set(key, value);
return R.ok().setData(true);
} catch (Exception e)
{
e.printStackTrace();
return R.exp().setData(e.getMessage());
}
}
@Override
public R setWithExpiration(String key, int expirationInSeconds, Object value)
{
try
{
System.out.println("post:key:" + key);
ValueOperations
valueOperations.set(key, value);
redisTemplate.expire(key, expirationInSeconds, TimeUnit.SECONDS);
return R.ok().setData(true);
} catch (Exception e)
{
e.printStackTrace();
return R.exp().setData(e.getMessage());
}
}
@Override
public R deleteValue(String key)
{
try{
System.out.println("delete:key:" + key);
redisTemplate.delete(key);
return R.ok().setData(true);
}catch (Exception e)
{
return R.exp().setData(e.getMessage());
}
}
@Override
public R updateValue(String key, Object value)
{
try{
redisTemplate.opsForValue().set(key, value);
return R.ok().setData(true);
}catch (Exception e)
{
return R.exp().setData(e.getMessage());
}
}
@Override
public R reidsLock(String key, String value)
{
return R.ok().setData(redisTemplate.opsForValue().setIfAbsent(key,value));
}
@Override
public R reidsUnlock(String key)
{
return R.ok().setData(redisTemplate.delete(key));
}
@Override
public R decrement(String key)
{
return R.ok().setData(redisTemplate.opsForValue().decrement(key));
}
@Override
public R deleteValue()
{
try
{
Set
keys.remove("name");
for (String key : keys)
{
redisTemplate.delete(key);
}
return R.ok().setData(true);
} catch (Exception e)
{
return R.exp().setData(e.getMessage());
}
}
}
/**
* (Custom)表控制层
*
* @since 2023-10-18 11:10:10
*/
@RestController
@RequestMapping("/custom")
public class CustomController {
/**
* 服务对象
*/
@Resource
private CustomService customService;
/**
* 全查询
*
* @param custom 筛选条件
* @return 查询结果
*/
@GetMapping
public R queryAll(Custom custom) {
return this.customService.queryAll(custom);
}
/**
* 分页查询
*
* @param custom 筛选条件
* @param pageSize 每页多少航
* @param page 第几页
* @return 查询结果
*/
@GetMapping("/page")
public R queryAllPage(Custom custom, Integer pageSize, Integer page) {
return this.customService.queryAllPage(custom,pageSize,page);
}
/**
* 模糊查找
*
* @param searchText 查找关键字
* @return 查询结果
*/
@GetMapping("/search/{searchText}")
public R queryBySearchText(@PathVariable("searchText") String searchText) {
return this.customService.queryBySearchText(searchText);
}
/**
* 通过主键查询单条数据
*
* @param id 主键
* @return 单条数据
*/
@GetMapping("{id}")
public R queryById(@PathVariable("id") Integer id) {
return this.customService.queryById(id);
}
/**
* 统计总行数
*
* @param custom 筛选条件
* @return 查询结果
*/
@GetMapping("/count")
public R getCount(Custom custom) {
return this.customService.getCount(custom);
}
/**
* 新增数据
*
* @param custom 实体
* @return 新增结果
*/
@PostMapping
public R add(@RequestBody Custom custom) {
return this.customService.insert(custom);
}
/**
* 更新数据
*
* @param custom 实体
* @return 更新结果
*/
@PutMapping
public R update(@RequestBody Custom custom) {
return this.customService.update(custom);
}
/**
* 删除数据
*
* @param id 主键
* @return 删除是否成功
*/
@DeleteMapping
public R deleteById(Integer id) {
return this.customService.deleteById(id);
}
}
/**
* 实体类
*
* @since 2023-10-18 11:10:10
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Custom implements Serializable {
private static final long serialVersionUID = 320539504981592510L;
private Integer customId;
private String name;
private Integer age;
private String sex;
}
}
/**
* (Custom)表服务接口
*
* @since 2023-10-18 11:10:10
*/
public interface CustomService {
/**
* 通过ID查询单条数据
*
* @param customId 主键
* @return 实例对象
*/
R queryById(Integer customId);
/**
* 全查询
*
* @param custom 筛选条件
* @return 查询结果
*/
R queryAll(Custom custom);
/**
* 分页查询
*
* @param custom 筛选条件
* @param pageSize 每页多少航
* @param page 第几页
* @return 查询结果
*/
R queryAllPage(Custom custom, Integer pageSize ,Integer page);
/**
* 模糊查找
*
* @param searchText 查找关键字
* @return 查询结果
*/
R queryBySearchText(String searchText);
/**
* 统计总行数
*
* @param custom 筛选条件
* @return 查询结果
*/
R getCount(Custom custom);
/**
* 新增数据
*
* @param custom 实例对象
* @return 实例对象
*/
R insert(Custom custom);
/**
* 修改数据
*
* @param custom 实例对象
* @return 实例对象
*/
R update(Custom custom);
/**
* 通过主键删除数据
*
* @param customId 主键
* @return 是否成功
*/
R deleteById(Integer customId);
}
/**
* (Custom)表服务实现类
*
* @since 2023-10-18 11:10:10
*/
@Service("customService")
public class CustomServiceImpl implements CustomService {
@Resource
private CustomMapper customMapper;
/**
* 通过ID查询单条数据
*
* @param customId 主键
* @return 实例对象
*/
@Override
public R queryById(Integer customId) {
return R.ok().setData(this.customMapper.queryById(customId));
}
/**
* 分页查询
*
* @param custom 筛选条件
* @param pageSize 每页多少航
* @param page 第几页
* @return 查询结果
*/
@Override
public R queryAllPage(Custom custom,Integer pageSize, Integer page) {
return R.ok().setData(this.customMapper.queryAllPage(custom,pageSize,page));
}
/**
* 全查询
*
* @param custom 筛选条件
* @return 查询结果
*/
@Override
public R queryAll(Custom custom) {
return R.ok().setData(this.customMapper.queryAll(custom));
}
/**
* 模糊查找
*
* @param searchText 查找关键字
* @return 查询结果
*/
@Override
public R queryBySearchText(String searchText){
return R.ok().setData(this.customMapper.queryBySearchText(searchText));
}
/**
* 统计总行数
*
* @param custom 筛选条件
* @return 查询结果
*/
@Override
public R getCount(Custom custom) {
return R.ok().setData(this.customMapper.count(custom));
}
/**
* 新增数据
*
* @param custom 实例对象
* @return 实例对象
*/
@Override
public R insert(Custom custom) {
if(custom.getCustomId()!=null && this.customMapper.queryById( custom.getCustomId() ) != null )
return R.error("已经存在");
this.customMapper.insert(custom);
return R.ok().setData(custom);
}
/**
* 修改数据
*
* @param custom 实例对象
* @return 实例对象
*/
@Override
public R update(Custom custom) {
this.customMapper.update(custom);
return R.ok().setData(this.queryById(custom.getCustomId()));
}
/**
* 通过主键删除数据
*
* @param customId 主键
* @return 是否成功
*/
@Override
public R deleteById(Integer customId) {
try{
this.customMapper.deleteById(customId);
return R.ok();
} catch (Exception e)
{
e.printStackTrace();
return R.exp().setData(e.getMessage());
}
}
}
/**
* (Custom)表数据库访问层
*
* @since 2023-10-18 11:10:10
*/
@Mapper
public interface CustomMapper {
/**
* 通过ID查询单条数据
*
* @param customId 主键
* @return 实例对象
*/
Custom queryById(Integer customId);
/**
* 全查询
*
* @param custom 查询条件
* @return 对象列表
*/
List
/**
* 分页查询
*
* @param custom 查询条件
* @param pageSize 每页多少航
* @param page 第几页
* @return 对象列表
*/
List
@Param("pageSize") Integer pageSize ,@Param("page") Integer page);
/**
* 模糊查找
*
* @param searchText 查找关键字
* @return 查询结果
*/
List
/**
* 统计总行数
*
* @param custom 查询条件
* @return 总行数
*/
long count(Custom custom);
/**
* 新增数据
*
* @param custom 实例对象
* @return 影响行数
*/
int insert(Custom custom);
/**
* 批量新增数据(MyBatis原生foreach方法)
*
* @param entities List
* @return 影响行数
*/
int insertBatch(@Param("entities") List
/**
* 批量新增或按主键更新数据(MyBatis原生foreach方法)
*
* @param entities List
* @return 影响行数
* @throws org.springframework.jdbc.BadSqlGrammarException 入参是空List的时候会抛SQL语句错误的异常,请自行校验入参
*/
int insertOrUpdateBatch(@Param("entities") List
/**
* 修改数据
*
* @param custom 实例对象
* @return 影响行数
*/
int update(Custom custom);
/**
* 通过主键删除数据
*
* @param customId 主键
* @return 影响行数
*/
int deleteById(Integer customId);
}
4.2 测试数据设计
4.2.1 Redis安装与使用
数据设计:
分别测试Redis字符串,集合,列表,哈希操作。
结果在下一章节4.3.1中详细描述。
字符串测试数据设计:
设置键值对:SET key value
获取键的值:GET key
追加值到键的值末尾:APPEND key value
获取指定范围内的子字符串:GETRANGE key start end
集合测试数据设计:
添加一个或多个元素到集合中:sadd key2 value1 value2
获取集合中的所有元素:SMEMBERS key2
获取集合中元素的数量:SCARD key2
判断元素是否在集合中:SISMEMBER key2 value1,SISMEMBER key2 value3
列表测试数据设计:
在列表的头部添加一个或多个元素:LPUSH key3 value1 value2
在列表的尾部添加一个或多个元素:RPUSH key3 value1 value2
获取列表指定范围的元素:LRANGE key3 start end
获取列表所有元素:LRANGE key3 0 -1
获取列表的长度:LLEN key3
删除列表:DEL key3
哈希测试数据设计:
设置哈希中的字段和值:HSET key4 field1 value1 field2 value2
获取哈希中指定字段的值:HGET key4 field
获取哈希中所有字段和值:HGETALL key4
获取哈希中字段的数量:HLEN key4
4.2.2 SpringBoot中整合Redis
数据设计:
Redis集合元素的添加,删除,查询,更新。
结果在下一章节4.3.2中详细展示。
4.2.3 秒杀程序
测试数据设计:
测试单用户对单件商品的多次抢购 测试多用户对单件商品的超数量抢购 测试多用户对多商品的超数量抢购
结果在下一章节4.3.3中详细展示。
4.3 测试结果分析
4.3.1 Redis安装与使用
字符串操作:
集合操作:
列表操作:
哈希操作:
4.3.2 SpringBoot中整合Redis
测试添加:
key:key1 value:[aaa,bbb] (测试向key1添加两个元素aaa和bbb)
key:key1 value:[ccc] (测试向key1添加一个元素ccc)
key:key1 value:[ddd] (测试向key1添加一个元素ddd)
key:key2 value:[CCC] (测试向key2添加元素CCC)
测试查询:
分别查询key1,查询key2,查询key3
测试更新:
测试删除:
4.3.3 秒杀程序
首先进行初始化,将redis中键值清空,将用户秒杀记录和货物剩余库存信息载入到redis中:
结果如上:总共有1~10的10件货物,每件货物余量为5。且暂无用户抢购记录。
测试1:测试单用户对单件商品的多次抢购
模拟单用户并发抢购单商品,测试"单用户不能多次抢购同一件商品"规则。
模拟过程:用户1对商品1发出10次请求。
模拟结果:
redis中1号商品数量-1,变成了4;并出现了"1:1",代表1号用户抢购1号商品的抢购记录。
数据库seckill的record表中出现了抢购记录
数据库seckill的goods表中1号货物数量-1,变成了4。
测试2:测试多用户对单件商品的超数量抢购
模拟1~10号顾客对1号商品发起的1000个请求
测试结果:
redis结果如下,1号商品的总量更新为0;分别是1,3,4,9,10号顾客抢中了1号商品。
抢购记录:1,3,4,9,10号顾客抢中了1号商品
测试3:测试多用户对多商品的超数量抢购
添加商品csv文件
将商品和顾客的值改成
g
o
o
d
s
I
d
和
{goodsId}和
goodsId和{customId}
测试结果:
redis结果(redis结果比较杂乱,所以下面从货物数量和抢购记录进行验证):
数据库中货物数量,可以看到所有库存都清空了,变成0:
数据库抢购记录,可以看到商品抢购记录正好50条,说明程序正确:
五、实验小结
实验中遇到的问题及解决过程 无实验中产生的错误及原因分析 无实验体会和收获。 无
推荐阅读
大家都在找:
缓存:缓存清理命令
redis:redis哨兵
发表评论