emm...博主在前些天学习了一下Redis,以及在网上查询了部分资料、将看到的大牛们写的相关文章整合了一下。。(如有侵权多见谅...)分享一些经验,可能写的或者了解的不是很透彻,哈哈...大牛们请无视
那么在讲解之前我们先来了解一下Redis
一、什么是Redis?
- Redis是一个缓存 ( 缓存的优势在于它的速度远远高于硬盘运行速度) 服务器 ——高性能系统必备技术之一
- Redis是一个开源的、使用ANSI C语言编写的、支持网络交互的、遵循BSD协议、可基于内存也可持久化的Key-Value数据库
二、Redis的优点:
- Redis的速度非常快,每秒可执行大约 110000 次的设置(SET)操作,每秒大约可执行 81000 次的读取/获取(GET)操作。
- Redis可以支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合) 由于开发人员常用的大多数数据类型,例如列表,集合,排序集和散列等等。这使得Redis很容易被用来解决各种问题,因为我们知道哪些问题可以更好使用地哪些数据类型来处理解决。
- 操作具有原子性所有Redis操作都是原子操作,这确保如果两个客户端并发访问,Redis服务器能接收更新的值,避免接收异常数据。
- 支持数据持久化,支持AOF和RDB两种持久化方式
- Redis是一个多实用工具,可用于多种用例,如:缓存,消息队列(Redis本地支持发布/订阅),应用程序中的任何短期数据,例如,web应用程序中的会话,网页命中计数等
缺点:
Redis比较难在线扩容,在集群的容量上限时,在线扩容会变得很复杂。
三、Redis与其他键值存储系统相比:
- Redis是键值数据库系统的不同进化路线,它的值可以包含更复杂的数据类型,可在这些数据类型上定义原子操作。
- Redis是一个内存数据库,但在磁盘数据库上是持久的,因此它代表了一个不同的权衡,在这种情况下,在不能大于存储器(内存)的数据集的限制下实现非
- 常高的写和读速度。
- 内存数据库的另一个优点是,它与磁盘上的相同数据结构相比,复杂数据结构在内存中存储表示更容易操作。 因此,Redis可以做很少的内部复杂性。
四、Redis执行流程
- 业务层所有指定的查询方法,都要先去缓存去查询,如果有,就直接返回缓存的数据(对象);如果没有,就去数据库查询,并且把数据库中查到的数据,放入缓存中;
- 删除、修改:判断Redis缓存中key是否存在,如果有,则删除缓存中的数据(对象);如果没有,则直接执行。这里修改语句的话博主认为不需要在缓存中去修改,而选择直接删除的原因是因为,在用户修改完之后可能并不需要立即使用,并且多了许多代码所产生的收益微乎其微(还可以偷懒..)。
- 增:不必要提前查出来放到缓存中或者进行key的非空验证,所以在下面的代码中博主没有做增加的环绕增强
一些实现细节大家可以看图来理解
下面来看一下相关的实现代码。
实体类以及Dao层、Service层操作省略
Bean配置
Spring-Beans.xml内代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.1.xsd">
<aop:aspectj-autoproxy/> //声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面
<context:annotation-config/>// 是用于激活那些已经在spring容器里注册过的bean(无论是通过xml的方式还是通过package sanning的方式)上面的注解。
<context:component-scan base-package="com.landun.lichenming"/>// 在xml配置了这个标签后,spring可以自动去扫描base-pack下面或者子包下面的java文件,如果扫描到有@Component @Controller@Service等这些注解的类,则把这些类注册为bean
<!-- 配置Jedis -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxActive" value="20"/>//连接池最大的连接数
<property name="maxIdle" value="10"/> //连接池中最多可空闲maxIdle个连接 ,这里取值为10,表示即使没有数据库连接时依然可以保持10空闲的连接,而不被清除,随时处于待命状态。设 0 为没有限制。minIdle 表示 连接池中最少空闲maxIdle个连接
<property name="maxWait" value="1000"/>// maxWait 连接池中连接用完时,新的请求等待时间,毫秒,取值-1,表示无限等待,直到超时为止,也可取值9000,表示9秒后超时。超过时间会出错误信息一般把maxActive设置成可能的并发量就行了
<property name="testOnBorrow" value="true"/>// 从连接池获取一个连接时,验证有效性
</bean>
<bean id="jedisPool" class="redis.clients.jedis.JedisPool">
<constructor-arg index="0" ref="jedisPoolConfig" />
<constructor-arg index="1" value="127.0.0.1" />
<constructor-arg index="2" value="6379" />
<!-- timeout -->
<constructor-arg index="3" value="2000" />
<!-- password -->
<constructor-arg index="4" value="62416038626259878353394562797" />
</bean>
<!--导入spring-mybatis.xml-->
<import resource="spring-mybatis.xml"/>
</bean>
配置mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="false"/>//延迟加载的开关,即懒加载
为什么使用懒加载:因为内存容量有限 ,为了减少并发量,减少系统资源的消耗,
</settings>
<typeAliases>
<package name="com.landun.lichenming.entity"/>//映射实体类
</typeAliases>
<mappers>
<mapper resource="com/landun/lichenming/dao/UserMapper.xml"/>//映射dao内的映射文件
</mappers>
</configuration>
配置RedisCache(进行jedis的查询,删除等操作)
package com.landun.lichenming.cache;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
@Component
public class RedisCache {
@Resource
private JedisPool jedisPool;
public Object getFromCache(String key) {
Jedis jedis = jedisPool.getResource();
byte[] result = jedis.get(key.getBytes());
// 如果查询没有为空
if (null == result) {
return null;
}
// 查询到了,反序列化
return SerializeUtil.unSerialize(result);
}
// 将数据库中查询到的数据放入redis
public void save(String redisKey, Object obj) {
// 序列化
byte[] bytes = SerializeUtil.serialize(obj);
// 存入redis
Jedis conn = jedisPool.getResource();
String result = conn.set(redisKey.getBytes(), bytes);
if ("OK".equals(result)) {
System.out.println("[RedisCache]:数据成功保存到redis cache...");
}
}
// 将数据库查询到的key删除
public void del(String key) {
Jedis jedis = jedisPool.getResource();
if (jedis.exists(key.getBytes())) {
jedis.del(key.getBytes());
if (jedis.exists(key.getBytes())) {
System.out.println(" [RedisCache] :数据删除失败!!!请联系程序员");
} else {
System.out.println(" [RedisCache] :数据删除成功");
}
} else {
System.out.println(" [RedisCache] :内存数据不存在");
}
}
}
配置SerializeUtil(对传进来的数据进行序列化与反序列化操作)
package com.landun.lichenming.cache;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class SerializeUtil {
//序列化
public static byte[] serialize(Object obj){
ObjectOutputStream obi=null;
ByteArrayOutputStream bai=null;
try {
bai=new ByteArrayOutputStream();
obi=new ObjectOutputStream(bai);
obi.writeObject(obj);
byte[] byt=bai.toByteArray();
return byt;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
//反序列化
public static Object unSerialize(byte[] byt){
ObjectInputStream oii=null;
ByteArrayInputStream bis=null;
bis=new ByteArrayInputStream(byt);
try {
oii=new ObjectInputStream(bis);
Object obj=oii.readObject();
return obj;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
定义切面
package com.landun.lichenming.cache;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;/**
* 该类表示一个切面,表示: “调用find*方法根据id查某个对象时,先从缓存查询,如果没有,再查数据库”
*
*/
@Component
// 使用Aspect注解将RedisCacheAspect定义为切面
@Aspect
public class RedisCacheAspect {private static String PROJECT_NAME = "REDIS-";
private static String MODULE_NAME = "AUTH-";private static final Logger log = Logger.getLogger(RedisCacheAspect.class);
@Resource
private RedisCache redisCache;// 定义切入点
@Pointcut("execution(* com.ketai.liudun.service..*Service.find*(..))")
public void getCachePointcut() {
}@Around("getCachePointcut()")
public Object around(ProceedingJoinPoint joinPoint) {// 先获取目标方法参数
String id = null;
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
id = String.valueOf(args[0]);
}// 得到一个具有唯一性的缓存key
String redisKey = getRedisKey(joinPoint, id);
log.debug("进入环绕增强前半部分,尝试从redis查询,生成的缓存key:" + redisKey);
// 根据key获取从redis中查询到的对象
Object data = redisCache.getFromCache(redisKey);
// 如果查询到了
if (null != data) {
log.debug("[" + redisKey + "]缓存命中:" + data);
return data;
}// 没有查到,那么查询数据库
try {
data = joinPoint.proceed();// 执行目标方法(service中的find*方法)
} catch (Throwable e) {
e.printStackTrace();
}
log.debug("没有从redis中取出数据,改为从数据库中查询的数据..." + data);// 后置:将数据库中查询的数据放到redis中
redisCache.save(redisKey, data);
log.debug("数据已存储到redis" + data);// 将查询到的数据返回
return data;}
// 定义切入点
@Pointcut("execution(* com.ketai.liudun.service..*Service.update*(..))")
public void updateCachePointcut() {
}@Pointcut("execution(* com.ketai.liudun.service..*Service.delete*(..))")
public void deleteCachePointcut() {
}@Around("updateCachePointcut() or deleteCachePointcut()")
public Object UpdateAnddelete(ProceedingJoinPoint joinPoint) {// 先获取目标方法参数
String id = null;
Object[] args = joinPoint.getArgs();
if (args != null && args.length > 0) {
id = String.valueOf(args[0]);
}// 得到一个具有唯一性的缓存key
String redisKey = getRedisKey(joinPoint, id);
// 根据key获取从redis中查询到的对象
log.debug("update进入环绕增强前半部分,尝试从redis查询,生成的缓存key:" + redisKey);
Object data = redisCache.getFromCache(redisKey);
// 如果查询到了
if (null != data) {
log.debug("update[" + redisKey + "]缓存命中:" + data);
try {
data = joinPoint.proceed();// 执行目标方法(service中的find*方法)
} catch (Throwable e) {
e.printStackTrace();
}
// 执行删除操作
redisCache.del(redisKey);
log.debug("数据已从redis删除");
return 1;
} else {
return 0;
}
}
/**
* 拼接一个独特的key
* @param joinPoint
* @param id
* @return
*/
private String getRedisKey(ProceedingJoinPoint joinPoint, String id) {
return PROJECT_NAME + MODULE_NAME + joinPoint.getTarget().getClass().getName() + "-" + id;
}
}