需求
分析
- 每次调用rest服务,如果都需要和mysql交互,则数据库的压力较大,响应时延也较大。特别是首页这种经常被访问的页面,这种现象尤其明显。所以需要在服务器设置缓存,一般使用redis。
- 服务层在访问数据库之前,需要查看redis中是否存在对应的数据。如果存在则直接从redis中获取,否则从mysql中获得,并将获取的对象存储到redis中。
- 后台管理工程更新mysql中的数据,需要进行缓存同步。有两种方法。
- 服务层提供清空redis缓存的服务,后台管理工程更新mysql之后,调用服务层的服务。
- 设置缓存过期时间。超时之后自动清空。
- 前者缓存同步及时,后者操作简单。
总结
- 在业务内容的前后读取和写入redis缓存。
- 使用第一种缓存同步方案。
- 添加缓存之后的架构:
- 业务使用缓存和缓存同步的流程如下:
业务中使用缓存
配置
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<scope>provided</scope>
</dependency>
- redis.properties中配置Ip、端口号,以及服务在redis中对应的key。
redis.host=127.0.0.1
redis.prot=6379
#redis中的key的值
#redis中内容的key
CONTENT_REDIS_KEY=CONTENT_REDIS_KEY
#redis中商品目录的key
ITEM_CATAGORY_LIST_REDIS_KEY=ITEM_CATAGORY_LIST_REDIS_KEY
- spring-dao.xml中配置jedis连接池,用来操作redis,并自动扫描dao接口。
<bean id="redisClient" class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="${redis.host}"/>
<constructor-arg name="port" value="${redis.prot}"/>
<constructor-arg name="poolConfig" ref="jedisPoolConfig"/>
</bean>
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="30"/>
<property name="maxIdle" value="10"/>
<property name="numTestsPerEvictionRun" value="1024"/>
<property name="timeBetweenEvictionRunsMillis" value="30000"/>
<property name="minEvictableIdleTimeMillis" value="1800000"/>
<property name="softMinEvictableIdleTimeMillis" value="10000"/>
<property name="maxWaitMillis" value="1500"/>
<property name="testOnBorrow" value="true"/>
<property name="testWhileIdle" value="true"/>
<property name="blockWhenExhausted" value="false"/>
</bean>
<context:component-scan base-package="com.taotao.rest.dao"/>
Dao层
- com.taotao.rest.dao包下创建RedisDao接口,提供string、hash类型的增删查功能,以及过期时间和数字加一的功能。
public interface RedisDao {
String set(String key, String value);
String get(String key);
Long del(String key);
/**
* 设置hash类型的值
*/
Long hset(String key, String field, String value);
String hget(String key, String field);
Long hdel(String key, String field);
/**
* 对应的数字加1
*/
Long incr(String key);
/**
* 字段过期时间(秒为单位)
*/
Long ttl(String key);
/**
* 设置过期时间
*/
Long expire(String key, Integer second);
}
- impl包下创建RedisDao的单机版实现类。如果需要也可以使用集群版的实现类实现接口,用来替换单机版实现类。
@Repository
public class RedisDaoSingle implements RedisDao {
@Autowired
private JedisPool jedisPool;
@Override
public String set(String key, String value) {
Jedis jedis = jedisPool.getResource();
String result = jedis.set(key, value);
jedis.close();
return result;
}
@Override
public String get(String key) {
Jedis jedis = jedisPool.getResource();
String result = jedis.get(key);
jedis.close();
return result;
}
@Override
public Long del(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.del(key);
jedis.close();
return result;
}
@Override
public Long hset(String key, String field, String value) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.hset(key, field, value);
jedis.close();
return result;
}
@Override
public String hget(String key, String field) {
Jedis jedis = jedisPool.getResource();
String result = jedis.hget(key, field);
jedis.close();
return result;
}
@Override
public Long hdel(String key, String field) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.hdel(key, field);
jedis.close();
return result;
}
@Override
public Long incr(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.incr(key);
jedis.close();
return result;
}
@Override
public Long ttl(String key) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.ttl(key);
jedis.close();
return result;
}
@Override
public Long expire(String key, Integer second) {
Jedis jedis = jedisPool.getResource();
Long result = jedis.expire(key, second);
jedis.close();
return result;
}
}
Service层
- 在ContentService的getContentList方法中添加redis的读取和写入,使用hash类型。key为rest.properties中的配置,field为内容分类id。
/**
* 主页内容在redis使用的key
*/
@Value("${CONTENT_REDIS_KEY}")
private String CONTENT_REDIS_KEY;
发布缓存同步服务
Service层
@Service
public class RedisServiceImpl implements RedisService {
@Value("${CONTENT_REDIS_KEY}")
private String CONTENT_REDIS_KEY;
@Autowired
private RedisDao redisDao;
@Override
public TaotaoResult syncContent(long catagoryId) {
try {
redisDao.hdel(CONTENT_REDIS_KEY, String.valueOf(catagoryId));
} catch (Exception e) {
e.printStackTrace();
return TaotaoResult.build(500, ExceptionUtil.getStackTrace(e));
}
return TaotaoResult.ok();
}
}
Controller层
- 接收url中的内容分类Id,调用service层。
@Controller
@RequestMapping("/cache/sync")
public class RedisController {
@Autowired
private RedisService redisService;
@RequestMapping("/content/{catagoryId}")
public TaotaoResult syncContent(@PathVariable Long catagoryId) {
TaotaoResult result = redisService.syncContent(catagoryId);
return result;
}
}
调用缓存同步服务
Service层
- 在taotao-manaer-web中添加rest.properties,包含服务层url和内容缓存的url。
REST_BASE_URL=http://localhost:8081/rest
CONTENT_CACHE_URL=/cache/sync/content/
- ContentService中addContent方法中,插入数据库之后调用服务层的缓存同步服务。
缓存同步的结果
- 加载首页之后,首页有3个广告为。redis包含大广告位的信息,键值为CONTENT_REDIS_KEY。
- 后台管理工程添加新的大广告位之后,redis中的相关缓存被清空。
- 再次访问首页,大广告位内容从mysql加载,并写入redis,显示最新的5个大广告位。