作为一个处理高并发访问的项目,就如《大型网站技术架构 核心原理与案例分析》所说,加缓存是首选。而使用NoSQL数据库的中间件,有(MongoDB,Memcache,Redis。)在本项目中使用便是Redis
介绍下Redis
Redis是款开源的内存高速缓存数据库,由C语言编写(部署的时候需要gcc环境),本质上是一个K-V类型的内存数据库,具有非常出色的性能,每秒可以处理10w次读写操作。
相比于其他nosql:
优点: 具有支持多种数据结构(string,list,set,hash等),单value可以支持高达1GB的存储,支持持久化操作,性能优异。
缺点: Redis很容易受到物理内存的限制,不能用作海量数据的高性能读写。
本项目在选课这一高并发条件下,读写频率高,但数据量并不大,正是Redis最适合使用的一种情况。
有关Redis的其他详细信息网上有许多介绍,有的甚至深入底层,有兴趣的朋友可以搜搜看看,这里主要结合本项目的应用进行介绍。
结合项目,还是先上架构图。
在非选课这一具体操作下,对MySQL数据库压力最大的便是读操作,每次访问,浏览都需要对数据库进行读操作,从数据库获取数据。
但是创建数据表时放弃使用MyISAM这种对读操作更好的引擎。使用的InnoDB引擎,具有"行锁",所以须有对的写操作表现较好,其支持事务,对数据的质量保证较好。
所以首要就是对需要减轻数据库的读压力
思路: 通过Redis缓存需要的数据,每次数据读取向Redis获取,减少对数据库获取的频率,从而减少对数据库的读操作。即如架构图所示,所有服务层如需要获取数据都先对Redis请求。
实现: 需要连接Redis服务,本项目使用的Jedis,采用Jedis工具类,即通过创建Jedis对象(如果是集群版,请使用JedisPool,开发阶段本着方便,便宜,便宜,便宜的原则就用的单机版)对Redis进行操作。
连接Rides步骤如下:
(1)将安装redis服务的服务器的ip地址和redis的端口号作为构造参数传递给Jedis,用来创建一个Jedis对象
Jedis jedis = new Jedis(ip,port);
(2)通过第一步创建的jedis对象,操作redis的5大数据类型(hash类型,string类型,list类型,set类型,zset类型,有序)
jedis.set(string key,string value);
jedis.get(string key);
(3)操作完成后关闭jedis连接
jedis.close();
每用一次都得创建一次对象…这可不太好,而对对象的管理自然想到Spring,没错这东西能和Spring做整合,根据IOC控制反转,创建对象这种事情交给Spring就好了。
<!-- 连接redis单机版 -->
<bean id="jedisClientPool" class="cn.eas.common.jedis.JedisClientPool">
<property name="jedisPool" ref="jedisPool"></property>
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg name="host" value="Redis地址"/>
<constructor-arg name="port" value="6379"/>
</bean>
OK,准备就绪看看我们的业务,首先查询缓存,缓存查询不到再查数据库。
可以表示成
1.查询缓存
2.缓存中没有,查询数据库-->2.1设置查询条件-->2.2执行查询获取查询结果
3.把结果添加到缓存-->3.1设置过期时间
4.返回查询结果
代码实现,课程查询为例
/**
* 课程查询
*/
@Override
public TbCourse getCourseById(long courseId) {
//查询缓存
try {
String json = jedisClient.get(REDIS_ITEM_PRE + ":" + courseId + ":BASE");
if(StringUtils.isNotBlank(json)) {
TbCourse tbCourse = JsonUtils.jsonToPojo(json, TbCourse.class);
return tbCourse;
}
} catch (Exception e) {
e.printStackTrace();
}
//缓存中没有,查询数据库
TbCourseExample example = new TbCourseExample();
cn.eas.pojo.TbCourseExample.Criteria criteria = example.createCriteria();
//设置查询条件
criteria.andIdEqualTo(courseId);
//执行查询
List<TbCourse> list = courseMapper.selectByExample(example);
if (list != null && list.size() > 0) {
//把结果添加到缓存
try {
jedisClient.set(REDIS_ITEM_PRE + ":" + courseId + ":BASE", JsonUtils.objectToJson(list.get(0)));
//设置过期时间
jedisClient.expire(REDIS_ITEM_PRE + ":" + courseId + ":BASE", ITEM_CACHE_EXPIRE);
} catch (Exception e) {
e.printStackTrace();
}
return list.get(0);
}
if(list != null && list.size() > 0){
return list.get(0);
}
return null;
}
这里将REDIS_ITEM_PRE这种比较常修改的参数,采用了软编码,通过加载resource.properties配置文件获取。
对于删除,修改这种"写"操作,就变现为先更新数据库,再将缓存的设置过期。后读取缓存时执行如上步骤。
这种使用方式我认为有如下
优点:
1.学习成本低…看看就会;
2.较为符合面向对象思想,且和Spring易于整合,连接Redis就去找Jedis对象…创建Jedis对象交给Spring来做。
缺点
1.代码量比较大…我这都是小功能好一些
2.需要导入Jar包,Maven构建的项目,依赖好使比较好管理的。
读操作解决了但是为了保证数据的质量,咋不能对缓存做写啊…所以如果是写操作,直接对MySQL数据库进行,缓存直接过期,重新获取,虽然表采用了InnoDB,但是在高频率写操作下依旧容易造成数据库压力过大 ,在本项目中后期部署时为解决该问题,采取了读写分离,进一步降低了数据库压力,在预期并发量下足够应对,如果并发量过大,可以采用kafka等中间的方式解决。如有兴趣网上资料也不少。当然选课这一高并发方式的解决办法不是这样,会在之后的博客进行介绍。