项目常见问题思考
项目首页每天有大量的人访问,对数据库造成很大的访问压力,甚至是瘫痪。那如何解决呢?我们通常的做法有两种:一种是数据缓存、一种是网页静态化。我们今天讨论第一种解决方案。
Redis
redis是一款开源的Key-Value数据库,运行在内存中,由ANSI C编写。企业开发通常采用Redis来实现缓存。同类的产品还有memcache 、memcached 、MongoDB等。
(1)构建Maven工程 SpringDataRedisDemo
(2)引入Spring相关依赖、引入JUnit依赖 (内容参加其它工程)
<properties>
<junit.version>4.12junit.version>
<spring.version>4.2.4.RELEASEspring.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jdbcartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-jmsartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-context-supportartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.9version>
dependency>
dependencies>
(3)引入Jedis和SpringDataRedis依赖
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.8.1version>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>1.7.2.RELEASEversion>
dependency>
(4)在src/main/resources下创建properties文件夹,建立redis-config.properties
# 主机
redis.host=127.0.0.1
# 端口号
redis.port=6379
# 密码 一般不需要
redis.pass=
redis.database=0
redis.maxIdle=300
redis.maxWait=3000
redis.testOnBorrow=true
(5)在src/main/resources下创建spring文件夹 ,创建applicationContext-redis.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<context:property-placeholder location="classpath*:properties/*.properties" />
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
bean>
<bean id="JedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:pool-config-ref="poolConfig"/>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="JedisConnectionFactory" />
bean>
beans>
- maxIdle :最大空闲数
- maxWaitMillis:连接时的最大等待毫秒数
- testOnBorrow:在提取一个jedis实例时,是否提前进行验证操作;如果为true,则得到的jedis实例均是可用的;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestValue {
//泛型可以不指定
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
//存值
redisTemplate.boundValueOps("name").set("张三");
}
@Test
public void getValue(){
String name = redisTemplate.boundValueOps("name").get();
//如果没有 name 则会返回 null
System.out.println(name);
}
@Test
public void deleteValue(){
//移除值
redisTemplate.delete("name");
}
}
/**
* 特点: 无序,不可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestSet {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void setValue(){
redisTemplate.boundSetOps("nameset").add("曹操");
redisTemplate.boundSetOps("nameset").add("刘备");
redisTemplate.boundSetOps("nameset").add("关羽");
}
@Test
public void getValue(){
//如果没有 nameset 返回 []
Set members = redisTemplate.boundSetOps("nameset").members();
// 没有循序,和存入的循序无关
// [关羽, 刘备, 曹操]
System.out.println(members);
}
@Test
public void removeValue(){
//移除一个
redisTemplate.boundSetOps("nameset").remove("刘备");
//全部移除
redisTemplate.delete("nameset");
}
}
/**
* 特点: 有序, 可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestList {
@Autowired
private RedisTemplate redisTemplate;
/**
* 右压栈: 后添加的元素排在后面
* 先进先出
*/
@Test
public void setValueRight(){
redisTemplate.boundListOps("name").rightPush("张三");
redisTemplate.boundListOps("name").rightPush("李四");
redisTemplate.boundListOps("name").rightPush("王五");
redisTemplate.boundListOps("name").rightPush("赵六");
}
/**
* 右压栈的输入
*/
@Test
public void getValue(){
// [张三, 李四, 王五,赵六]
List name = redisTemplate.boundListOps("name").range(0, 10);
System.out.println(name);
}
/**
* 左压栈: 先进后出
*/
@Test
public void setValueLeft(){
redisTemplate.boundListOps("nameLeft").leftPush("关羽");
redisTemplate.boundListOps("nameLeft").leftPush("张飞");
redisTemplate.boundListOps("nameLeft").leftPush("诸葛亮");
redisTemplate.boundListOps("nameLeft").leftPush("刘备");
}
/**
* 左压栈的输入
*/
@Test
public void getValueLeft(){
// [刘备, 诸葛亮, 张飞, 关羽]
List name = redisTemplate.boundListOps("nameLeft").range(0, 10);
System.out.println(name);
}
/**
* 按索引位置查询元素
*/
@Test
public void searchByIndex(){
// 关羽
String index = redisTemplate.boundListOps("nameLeft").index(3);
System.out.println(index);
//集合元素个数
Long size = redisTemplate.boundListOps("nameLeft").size();
System.out.println(size);
}
/**
* 移除
*/
@Test
public void removeByIndex(){
// 移除元素的个数 移除元素的name
redisTemplate.boundListOps("nameLeft").remove(1, "诸葛亮");
redisTemplate.delete("nameLeft");//移除所有值
}
}
/**
* 存取有序,不可重复
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:spring/applicationContext-redis.xml")
public class TestHash {
@Autowired
private RedisTemplate redisTemplate;
/**
* 存值
*/
@Test
public void setValue(){
redisTemplate.boundHashOps("hash").put("a", "唐僧");
redisTemplate.boundHashOps("hash").put("b", "八戒");
redisTemplate.boundHashOps("hash").put("c", "沙和尚");
redisTemplate.boundHashOps("hash").put("d", "孙悟空");
}
/**
* 获取所有的key
*/
@Test
public void getAllKey(){
//获取所有的key : [a, b, c, d]
Set
项目中首页的广告每次都是从数据库读取, 这样当网站访问量达到高峰时段,对数据库压力很大,并且影响执行效率。我们需要将这部分广告数据缓存起来。
因为缓存对于我们整个的系统来说是通用功能。广告需要用,其它数据可能也会用到,所以我们将配置放在公共组件层中较为合理。
(1. 引入依赖
(2. 创建配置文件
思路: 在查询数据时,我们要先从缓存中查询数据,如果缓存中没有再从数据库中查询,
将从数据库中查询出来的数据存入缓存中.
@Autowired
private RedisTemplate redisTemplate;
@Override
public List findByCategoryId(Long categoryId) {
//从缓存中查询数据
List list = (List) redisTemplate.boundHashOps("content").get(categoryId);
if(list == null){
System.out.println("从数据库中查询数据并放入缓存");
TbContentExample example = new TbContentExample();
Criteria criteria = example.createCriteria();
criteria.andCategoryIdEqualTo(categoryId);//指定分类ID
criteria.andStatusEqualTo("1");//指定条件有效的
example.setOrderByClause("sort_order");//排序
list = contentMapper.selectByExample(example);
redisTemplate.boundHashOps("content").put(categoryId, list);//放入缓存
}else{
System.out.println("从缓存中查询");
}
return list;
}
问题: 当我们数据库中的数据发生改变时,从缓存中查询出的数据并没有改变.
解决: 当广告数据发生变更时,需要将缓存数据清除,这样再次查询才能获取最新的数据
我们看一下那些操作使数据库中的数据发生改变:
(1 增加
再增加之前我们要清除缓存,然后再增加,这样会重新在数据库中查询一次,那么数据库的
改变就会同步到缓存中
contentMapper.insert(content);
redisTemplate.boundHashOps("content").delete(content.getCategoryId());
(2 删除
for(Long id:ids){
//清除缓存 先清除在删除
Long categoryId = contentMapper.selectByPrimaryKey(id).getCategoryId();
redisTemplate.boundHashOps("content").delete(categoryId);
contentMapper.deleteByPrimaryKey(id);
}
(3 修改
注意`: 用户可能会修改广告的分类,这样需要把原分类的缓存和新分类的缓存都清除掉。
@Override
public void update(TbContent content){
//查询修改前的分类Id
Long categoryId = contentMapper.selectByPrimaryKey(content.getId()).getCategoryId();
redisTemplate.boundHashOps("content").delete(categoryId);
contentMapper.updateByPrimaryKey(content);
//如果分类ID发生了修改,清除修改后的分类ID的缓存
if(categoryId.longValue()!=content.getCategoryId().longValue()){
redisTemplate.boundHashOps("content").delete(content.getCategoryId());
}
}