本文主要讲 Redis 的使用,如何与 SpringBoot 项目整合,如何使用注解方式和 RedisTemplate 方式实现缓存。最后会给一个用 Redis 实现分布式锁,用在秒杀系统中的案例。
更多 Redis 的实际运用场景请关注开源项目 coderiver
项目地址:https://github.com/cachecats/coderiver
一、NoSQL 概述
什么是 NoSQL ?
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,泛指非关系型的数据库。
为什么需要 NoSQL ?
随着互联网web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的SNS类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。NoSQL数据库的产生就是为了解决大规模数据集合多重数据种类带来的挑战,尤其是大数据应用难题。 – 百度百科
NoSQL 数据库的四大分类
- 键值(key-value)存储
- 列存储
- 文档数据库
- 图形数据库
分类 | 相关产品 | 典型应用 | 数据模型 | 优点 | 缺点 |
---|---|---|---|---|---|
键值(key-value) | Tokyo、 Cabinet/Tyrant、Redis、Voldemort、Berkeley DB | 内容缓存,主要用于处理大量数据的高访问负载 | 一系列键值对 | 快速查询 | 存储的数据缺少结构化 |
列存储数据库 | Cassandra, HBase, Riak | 分布式的文件系统 | 以列簇式存储,将同一列数据存在一起 | 查找速度快,可扩展性强,更容易进行分布式扩展 | 功能相对局限 |
文档数据库 | CouchDB, MongoDB | Web应用(与Key-Value类似,value是结构化的) | 一系列键值对 | 数据结构要求不严格 | 查询性能不高,而且缺乏统一的查询语法 |
图形(Graph)数据库 | Neo4J, InfoGrid, Infinite Graph | 社交网络,推荐系统等。专注于构建关系图谱 | 图结构 | 利用图结构相关算法 | 需要对整个图做计算才能得出结果,不容易做分布式集群方案 |
NoSQL 的特点
- 易扩展
- 灵活的数据模型
- 大数据量,高性能
- 高可用
二、Redis 概述
Redis的应用场景
- 缓存
- 任务队列
- 网站访问统计
- 应用排行榜
- 数据过期处理
- 分布式集群架构中的 session 分离
Redis 安装
网上有很多 Redis 的安装教程,这里就不多说了,只说下 Docker 的安装方法:
Docker 安装运行 Redis
docker run -d -p 6379:6379 redis:4.0.8
如果以后想启动 Redis 服务,打开命令行,输入以下命令即可。
redis-server
使用前先引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
三、注解方式使用 Redis 缓存
使用缓存有两个前置步骤
-
在
pom.xml
引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
-
在启动类上加注解
@EnableCaching
@SpringBootApplication @EnableCaching public class SellApplication { public static void main(String[] args) { SpringApplication.run(SellApplication.class, args); } }
常用的注解有以下几个
-
@Cacheable
属性如下图
用于查询和添加缓存,第一次查询的时候返回该方法返回值,并向 Redis 服务器保存数据。
以后调用该方法先从 Redis 中查是否有数据,如果有直接返回 Redis 缓存的数据,而不执行方法里的代码。如果没有则正常执行方法体中的代码。
value 或 cacheNames 属性做键,key 属性则可以看作为 value 的子键, 一个 value 可以有多个 key 组成不同值存在 Redis 服务器。
验证了下,value 和 cacheNames 的作用是一样的,都是标识主键。两个属性不能同时定义,只能定义一个,否则会报错。
condition 和 unless 是条件,后面会讲用法。其他的几个属性不常用,其实我也不知道怎么用…
-
@CachePut
更新 Redis 中对应键的值。属性和
@Cacheable
相同 -
@CacheEvict
删除 Redis 中对应键的值。
3.1 添加缓存
在需要加缓存的方法上添加注解 @Cacheable(cacheNames = "product", key = "123")
,
cacheNames
和 key
都必须填,如果不填 key
,默认的 key
是当前的方法名,更新缓存时会因为方法名不同而更新失败。
如在订单列表上加缓存
@RequestMapping(value = "/list", method = RequestMethod.GET)
@Cacheable(cacheNames = "product", key = "123")
public ResultVO list() {
// 1.查询所有上架商品
List<ProductInfo> productInfoList = productInfoService.findUpAll();
// 2.查询类目(一次性查询)
//用 java8 的特性获取到上架商品的所有类型
List<Integer> categoryTypes = productInfoList.stream().map(e -> e.getCategoryType()).collect(Collectors.toList());
List<ProductCategory> productCategoryList = categoryService.findByCategoryTypeIn(categoryTypes);
List<ProductVO> productVOList = new ArrayList<>();
//数据拼装
for (ProductCategory category : productCategoryList) {
ProductVO productVO = new ProductVO();
//属性拷贝
BeanUtils.copyProperties(category, productVO);
//把类型匹配的商品添加进去
List<ProductInfoVO> productInfoVOList = new ArrayList<>();
for (ProductInfo productInfo : productInfoList) {
if (productInfo.getCategoryType().equals(category.getCategoryType())) {
ProductInfoVO productInfoVO = new ProductInfoVO();
BeanUtils.copyProperties(productInfo, productInfoVO);
productInfoVOList.add(productInfoVO);
}
}
productVO.setProductInfoVOList(productInfoVOList);
productVOList.add(productVO);
}
return ResultVOUtils.success(productVOList);
}
可能会报如下错误
对象未序列化。让对象实现 Serializable
方法即可
@Data
public class ProductVO implements Serializable {
private static final long serialVersionUID = 961235512220891746L;
@JsonProperty("name")
private String categoryName;
@JsonProperty("type")
private Integer categoryType;
@JsonProperty("foods")
private List<ProductInfoVO> productInfoVOList ;
}
生成唯一的 id 在 IDEA 里有一个插件:GenerateSerialVersionUID
比较方便。
重启项目访问订单列表,在 rdm 里查看 Redis 缓存,有 product::123
说明缓存成功。
3.2 更新缓存
在需要更新缓存的方法上加注解: @CachePut(cacheNames = "prodcut", key = "123")
注意
cacheNames
和key
要跟@Cacheable()
里的一致,才会正确更新。