针对系统中访问频率很高的页面,常见的优化方案如下:

  1. 将页面做页面静态化处理
  2. 如果静态化之后依旧无法满足需求,可以将静态资源转移到CDN服务器
  3. 页面上变动的内容可以异步请求后端服务器获取数据,后端可以添加缓存来减轻数据库的压力
  4. 数据库方面可以通过合理的存储引擎选择、表字段设计、索引设计等进一步提高查询效率

页面静态化

什么是页面静态化?

页面静态化就是使用模板引擎技术(freemarker、Thymeleaf)将动态数据生成html静态网页

并将其与之使用的图片、css、js等静态资源存储到类似于Nginx之类支持高并发的存储中

因为纯静态网页通过类似Nginx这种服务器去加载要比去Tomcat加载快很多

CDN

内容分发网络(Content Delivery Network,CDN),是一种分布式的内容分发网,它需要在多个城市建立节点

然后将内容分发到各个城市的节点上,用户访问的时候就可以请求就近的服务器了

58cbeb3e-e951-42ec-8f3b-b15dfe615001

SpringCache

介绍

目前业界最主流的缓存技术就是Redis,而Java操作Redis目前有两种主流方案:

  • RedisTemplate:Spring提供的一个对象,内置了大量操作redis的方法,使用起来比较灵活
  • SpringCache:Spring提供的一套注解,可以基于方法级别对redis进行操作,使用起来非常简单

两种方式各有自己的优缺点,推荐以SpringCache为主,特殊情况下再采用RedisTemplate

SpringCache常用的注解有下面几个:

  • @EnableCaching:标注在启动类上,用来开启缓存注解功能
  • @Cacheable:主要标注在查询方法上,表示先从缓存中查询数据,如果有直接返回,如果没有再从数据库查询,返回的同时保存到缓存中一份
  • @CacheEvict:主要标注在新增、修改、删除方法上,用于将一条或多条数据从缓存中删除
  • @CachePut:用于更新缓存,它可以将方法的返回值放到缓存中

SpringCache支持多种缓存提供者,如Redis、EhCache、Caffeine等,可以自由切换他们

甚至可以不提供任何的缓存服务器,SpringCache依旧可以使用,他会使用内存做为缓存提供者

准备环境

单独提供一个环境来测试SpringCache

  1. 解压资料中提供的代码demo-springcache.zip,使用idea打开

7384f861-6a2c-4552-908c-52c076b32638

  1. 使用下面的语句创建用于测试的库表
1
2
3
4
5
6
7
8
CREATE DATABASE spring_cache_demo;
USE spring_cache_demo;

CREATE TABLE `user` (
`id` bigint(20) PRIMARY KEY AUTO_INCREMENT,
`name` varchar(50) DEFAULT NULL,
`age` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

缓存使用

使用SpringCache,主要就是学习如何来使用它提供的几个注解

@EnableCaching

@EnableCaching注解标注在启动类或者配置类上,用于开启基于注解的缓存功能

62023e5f-f037-4eeb-9fe1-12000de050ed

@CachePut

@CachePut:用于将方法的返回值放到缓存中,支持两个属性:

  • value: 缓存模块名称,每个模块下可以有很多key
  • key: 缓存的唯一标识,要注意的是它仅仅在一个value下保持唯一 也就是说value+key才能保证全局唯一
1
2
3
4
5
6
7
8
9
10
11
//@CachePut:将当前方法的返回值放入缓存中
//value:用于指定缓存内存区域的名称(划片)
//key:用于缓存数据的key,支持SPEL表达式
// #user.id 表示获取当前方法参数user对象中的id属性值
// #result.id 表示获取当前方法返回值对象中的id属性值
@CachePut(value = "userCache", key = "#result.id")
@PostMapping("/user")
public User save(@RequestBody User user) {
userMapper.insert(user);
return user;
}

当key用一个固定字符串时需要在双引号中用单引号括起来

@Cacheable

@Cacheable:主要标注在查询方法上,表示先从缓存中查询数据,如果有直接返回,如果没有再从数据库查询,返回的同时保存到缓存中一份

1
2
3
4
5
6
//@Cacheable:先查询缓存,没有再查数据库同步到缓存
@Cacheable(value = "userCache",key = "#id")
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id) {
return userMapper.getById(id);
}

在Cacheable注解中有两个属性可以指定条件进行缓存

  • condition:对方法参数进行判断,只缓存符合条件的
  • unless:对方法返回结果进行判断,只缓存不符合条件的

@CacheEvict

@CacheEvict:用于删除缓存,将一条或多条数据从缓存中删除

1
2
3
4
5
6
7
8
9
10
11
12
13
//@CacheEvict:清理指定key的缓存
@CacheEvict(value = "userCache", key = "#id")
@DeleteMapping("/user/{id}")
public void deleteById(@PathVariable Long id) {
userMapper.deleteById(id);
}

//@CacheEvict:清理整个value区域所有缓存
@CacheEvict(value = "userCache", allEntries = true)
@DeleteMapping("/user")
public void deleteAll() {
userMapper.deleteAll();
}

@Caching

注解**@Caching**可以将多个@Cacheable、@CacheEvict注解组合使用

1
2
3
4
5
6
7
8
9
10
11
@Caching(
cacheable = {
//返回数据为空,则缓存空值30分钟,这样可以避免缓存穿透
@Cacheable(value = RedisConstants.CacheName.SERVE_ICON, key ="#regionId" ,
unless ="#result.size() > 0",cacheManager = RedisConstants.CacheManager.THIRTY_MINUTES),

//返回值不为空,则永久缓存数据
@Cacheable(value = RedisConstants.CacheName.SERVE_ICON, key ="#regionId" ,
unless ="#result.size() == 0",cacheManager = RedisConstants.CacheManager.FOREVER)
}
)

设置缓存时间

默认情况下,使用@Cacheable或者@CachePut放入缓存中的数据都是永久生效的,如何设置缓存的过期时间呢?

在@Cacheable、@CachePut注解中有一个属性为cacheManager,表示缓存管理器,通过它可以设置缓存过期时间

  1. 创建一个配置类,然后向容器中放入一个RedisCacheManager
1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class SpringCacheConfig {
@Bean
public RedisCacheManager cacheManager30Minutes(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(30 * 60L));

return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
}
  1. 在@Cacheable注解中使用cacheManager引用刚刚放入容器中的缓存管理器
1
2
3
4
5
@Cacheable(value = "userCache",key = "#id",cacheManager = "cacheManager30Minutes")
@GetMapping("/user/{id}")
public User findById(@PathVariable Long id){
return userMapper.getById(id);
}