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群

应用场景:

      1. 缓存

经常查询数据,放到读速度很快的空间(内存),以便下次访问减少时间。减轻数据库压力,减少访问时间.而redis就是存放在内存中的。

Hibernte二级缓存(ehcache)

      1. 计数器应用

网站通常需要统计注册用户数网站总浏览次数等

新浪微博转发数、点赞数

 

      1. 实时防攻击系统

暴力**:使用工具不间断尝试各种密码进行登录。防:ip--->num,到达10次以后自动锁定IP,30分钟后解锁

解决方案:

     1、存数据库

 登录操作的访问量非常大

 2、static Map<String,int> longinFailNumMap;

          Map存储空间有限,大批量就不行,并且断电以后数据丢失。

问题:Redis能解决

    1、每次查询数据库,查询速度慢,多次写 内存

    2、断电会丢失数据,多个节点,不能共用   redis集群,容量可以无限大,可以共享数据、并且支持过期 

      1. 排行榜

总积分榜,今日积分榜,周积分,月积分,季度积分

方案:从数据库中查出来计算.

问题:

1、实时查询,查询速度慢

2、还要进行各种计算。

      1. 设定有效期的应用

设定一个数据,到一定的时间失效。 自动解锁,购物券

      1. 自动去重应用

Uniq 操作,获取某段时间所有数据排重值 这个使用 Redis 的 set 数据结构最合适了,只需要不断地将数据往 set 中扔就行了,set 意为 集合,所以会自动排重。

      1. 队列

构建队列系统 使用 list 可以构建队列系统,使用 sorted set 甚至可以构建有优先级的队列系统。

秒杀可以把名额放到内存队列(redis,内存就能处理高并发访问。限流

      1. 消息订阅系统

Pub/Sub 构建实时消息系统 Redis 的 Pub/Sub 系统可以构建实时的消息系统,比如很多用 Pub/Sub 构建的实时聊天系统 的例子。

比如QQ群消息

 

3.Redis持久化配置

Redis 提供了两种不同级别的持久化方式:RDB和AOF,可以通过修改redis.conf来进行配置.

NoSQL之Redis缓存技术实现

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:

     NoSQL之Redis缓存技术实现

redis:

 NoSQL之Redis缓存技术实现

2.    配置

   一般项目总都是有Spring,我们使用jedis访问reids时,所有要jedisSpring管理。集群原理就是把心对象交给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;
	}

}

反序列化还要导入一个包:

  NoSQL之Redis缓存技术实现

 

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 持久化)和从机(主从连接)。