NoSQL之Redis缓存技术实现
1.什么是NoSql
NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,它泛指非关系型的数据库。随着互联网2003年之后web2.0网站的兴起,传统的关系数据库在应付web2.0网站,特别是超大规模和高并发的交友类型的web2.0纯动态网站已经显得力不从心,暴露了很多难以克服的问题,而非关系型的数据库则由于其本身的特点得到了非常迅速的发展。
2,Redis是什么
Redis 是一个高性能的开源的、C语言写的Nosql(非关系型数据库),数据保存在内存中。Redis 是以key-value形式存储,和传统的关系型数据库不一样。不一定遵循传统数据库的一些基本要求,比如说,不遵循sql标准,事务,表结构等等,非关系型数据库严格上不是一种数据库,应该是一种数据结构化存储方法的集合。
特点
1.数据保存在内存,存取速度快,并发能力强
2.它支持存储的value类型相对更多,包括string(字符串)、list(链表)、set(集合)、 zset(sorted set --有序集合)和hash(哈希类型)。
3.redis的出现,很大程度补偿了memcached这类key/value存储的不足,在部分场合可以对关系数据库(如MySQL)起到很好的补充作用。
4.它提供了Java,C/C++,C#,PHP,JavaScript等客户端,使用很方便。
5.Redis支持集群(主从同步)。数据可以主服务器向任意数量从的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。
6.支持持久化,可以将数据保存在硬盘的文件中
7.支持订阅/发布(subscribe/publish)功能 QQ群
应用场景:
-
-
- 缓存
-
经常查询数据,放到读速度很快的空间(内存),以便下次访问减少时间。减轻数据库压力,减少访问时间.而redis就是存放在内存中的。
Hibernte二级缓存(ehcache)
-
-
- 计数器应用
-
网站通常需要统计注册用户数,网站总浏览次数等等
新浪微博转发数、点赞数
-
-
- 实时防攻击系统
-
暴力**:使用工具不间断尝试各种密码进行登录。防:ip--->num,到达10次以后自动锁定IP,30分钟后解锁
解决方案:
1、存数据库
登录操作的访问量非常大
2、static Map<String,int> longinFailNumMap;
Map存储空间有限,大批量就不行,并且断电以后数据丢失。
问题:Redis能解决
1、每次查询数据库,查询速度慢,多次写 内存
2、断电会丢失数据,多个节点,不能共用 redis集群,容量可以无限大,可以共享数据、并且支持过期
-
-
- 排行榜
-
总积分榜,今日积分榜,周积分,月积分,季度积分
方案:从数据库中查出来计算.
问题:
1、实时查询,查询速度慢
2、还要进行各种计算。
-
-
- 设定有效期的应用
-
设定一个数据,到一定的时间失效。 自动解锁,购物券
-
-
- 自动去重应用
-
Uniq 操作,获取某段时间所有数据排重值 这个使用 Redis 的 set 数据结构最合适了,只需要不断地将数据往 set 中扔就行了,set 意为 集合,所以会自动排重。
-
-
- 队列
-
构建队列系统 使用 list 可以构建队列系统,使用 sorted set 甚至可以构建有优先级的队列系统。
秒杀:可以把名额放到内存队列(redis),内存就能处理高并发访问。限流
-
-
- 消息订阅系统
-
Pub/Sub 构建实时消息系统 Redis 的 Pub/Sub 系统可以构建实时的消息系统,比如很多用 Pub/Sub 构建的实时聊天系统 的例子。
比如QQ群消息
3.Redis持久化配置
Redis 提供了两种不同级别的持久化方式:RDB和AOF,可以通过修改redis.conf来进行配置.
3.1 RDB模式
RDB 持久化可以在指定的时间间隔内生成数据集的时间点快照,默认开启该模式.
如何关闭 rdb 模式:
save ""
# save 900 1 //至少在900秒的时间段内至少有一次改变存储同步一次
# save xxx
# save 60 10000
3.2 AOF追加模式
AOF 持久化记录服务器执行的所有写操作命令,并在服务器启动时,通过重新执行这些命令来还原数据集,默认关闭该模式。
如何开启aof模式:
appendonly yes //yes 开启,no 关闭
# appendfsync always //每次有新命令时执行一次fsync,就将缓冲区的数据放入aof文件
#这里我们启用 everysec
appendfsync everysec //每秒 fsync 一次
# appendfsync no //从不fsync(交给操作系统来处理,可能很久才执行一次fsync)
其它的参数请大家看redis.conf配置文件详解
4.Redis Spring集成
1.导包
Spring:
redis:
2. 配置
一般项目总都是有Spring,我们使用jedis访问reids时,所有要jedis被Spring管理。集群原理就是把核心对象交给Spring管理。
Jedis核心对象:配置文件,连接池配置对象,连接池。
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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">
<!-- 扫描包 -->
<context:component-scan base-package="cn.www.redis" />
<!-- 连接池的信息 交给spring管理 -->
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="50" />
<property name="maxIdle" value="8" />
<property name="maxWaitMillis" value="1000" />
<property name="testOnBorrow" value="true"/>
<property name="testOnReturn" value="true"/>
<!-- <property name="testWhileIdle" value="true"/> -->
</bean>
<!-- 集群的配置 -->
<bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool" scope="singleton">
<constructor-arg index="0" ref="jedisPoolConfig" />
<constructor-arg index="1">
<list>
<bean class="redis.clients.jedis.JedisShardInfo">
<property name="password" value="123456" /><!--set注入 -->
<constructor-arg name="host" value="localhost" />
<constructor-arg name="port" value="6379" />
<constructor-arg name="timeout" value="2000" />
</bean>
</list>
</constructor-arg>
</bean>
</beans>
别忘记在applicationContext中加载
<!-- redis -->
<import resource="applicationContext-redis.xml"/>
5.Redis经典实用场景-缓存
在redis包中写上着三个类
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import redis.clients.jedis.ShardedJedis;
@Repository("redisClientTemplate")
public class RedisClientTemplate {
private static final Logger log = LoggerFactory.getLogger(RedisClientTemplate.class);
@Autowired
private RedisDataSource redisDataSource;
public void disconnect() {
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
shardedJedis.disconnect();
}
/**
* 设置单个值
*
* @param key
* @param value
* @return
*/
public String set(String key, String value) {
String result = null;
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.set(key, value);
} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}
/**
* 获取单个值
*
* @param key
* @return
*/
public String get(String key) {
String result = null;
ShardedJedis shardedJedis = redisDataSource.getRedisClient();
if (shardedJedis == null) {
return result;
}
boolean broken = false;
try {
result = shardedJedis.get(key);
} catch (Exception e) {
log.error(e.getMessage(), e);
broken = true;
} finally {
redisDataSource.returnResource(shardedJedis, broken);
}
return result;
}
}
import redis.clients.jedis.ShardedJedis;
public interface RedisDataSource {
public abstract ShardedJedis getRedisClient();
public void returnResource(ShardedJedis shardedJedis);
public void returnResource(ShardedJedis shardedJedis,boolean broken);
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
@Repository("redisDataSource")
public class RedisDataSourceImpl implements RedisDataSource {
private static final Logger log = LoggerFactory.getLogger(RedisDataSourceImpl.class);
@Autowired
private ShardedJedisPool shardedJedisPool;
public ShardedJedis getRedisClient() {
try {
ShardedJedis shardJedis = shardedJedisPool.getResource();
return shardJedis;
} catch (Exception e) {
log.error("getRedisClent error", e);
}
return null;
}
public void returnResource(ShardedJedis shardedJedis) {
shardedJedisPool.returnResource(shardedJedis);
}
public void returnResource(ShardedJedis shardedJedis, boolean broken) {
if (broken) {
shardedJedisPool.returnBrokenResource(shardedJedis);
} else {
shardedJedisPool.returnResource(shardedJedis);
}
}
}
然后再你想要缓存的serviceImpl中写这一串代码,这里我给一个menu菜单的一个缓存例子:
@Service
public class SystemMenuServiceImpl extends BaseServiceImpl<SystemMenu> implements ISystemMenuService {
public static final String MENUKEY = "menu";
@Autowired
RedisClientTemplate redisClientTemplate;
@Autowired
private SystemMenuMapper systemMenuMapper;
@Override
public List<SystemMenu> loadMenuTree(Long id) {
List<SystemMenu> list = null;
// 从缓存查询数据,如果缓存没有,就找数据库,再放到缓存中
String menuJson = redisClientTemplate.get(MENUKEY);
if (menuJson == null) {
// 找到数据,再放到缓存中
list = systemMenuMapper.selectByEmployeeId(id);
// 放到缓存中
redisClientTemplate.set(MENUKEY, JSON.toJSONString(list));
} else {
// 反序列化 字符串变成对象
list = JSON.parseArray(menuJson, SystemMenu.class);
}
return list;
}
}
反序列化还要导入一个包:
6.淘汰策略
淘汰一些数据,达到redis数据量都是有效的。选择合适的淘汰策略进行淘汰.怎么淘汰
volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意(随机)选择数据淘汰
allkeys-lru:从数据集(server.db[i].dict)中挑选最近最少使用的数据淘汰
allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
no-enviction(驱逐):禁止驱逐数据(不删除任意数据.默认策略,但redis还会根据引用计数器进行释放),这时如果内存不够时,会直接返回错误)
redis 确定驱逐某个键值对后,会删除这个数据并,并将这个数据变更消息发布到本地(AOF 持久化)和从机(主从连接)。