redis

Redis

Salvatore Sanfilippo 萨尔瓦托·桑菲利波--“Redis之父”

Salvatore在负责一个 page view 记录的系统,接收多个网站js发送来的页面访问记录数据,并存储之后展示给用户,最大负载每秒数千条页面记录,当时Salvatore在仅有硬件资源上无法用现有的数据库达到希望的性能。所以催生了redis的雏形 – 一段C程序

1. 概述

基于内存存储的,NoSql数据库 ( 非关系型数据库 ),存储结构 : key-value

Redis是一个开放源代码(BSD许可)内存中的数据结构存储,用作数据库、缓存和消息代理

对于数据量多,数据交互效率要求高的场景,可以考虑使用redis

2. 安装

**下载:**https://redis.io/download

依次执行:

yum install gcc安装依赖

yum install tcl 安装依赖

tar -zxvf redis-4.0.14.tar.gz 解压

cd redis-4.0.14 进入解压目录

make MALLOC=libc 编译,并设置用标准的libc中的内存管理函数

make install 安装

3. 启动Redis

3.1 前台模式

默认配置启动(默认端口6379)
[root@zhj ~]# redis-server

3.2 守护进程(后台)模式

在redis解压根目录中找到配置文件模板(redis.conf),复制到自定义位置:

[root@zhj ~]# cp redis.conf /usr/local/redis/7000/7000.conf

通过vi命令修改

daemonize yes  #后台模式

port 7000  #端口

pidfile /usr/local/redis/7000/7000.pid #记录进程号的文件位置
 
logfile /usr/local/redis/7000/7000.log #记录日志的文件位置

dir /usr/local/redis/7000  #redis的工作目录,用于保存自己的相关文件

bind 127.0.0.1 192.168.110.133 #允许以此ip访问redis,已达到对redis的保护
#最后启动redis: 
[root@zhj ~]# redis-server /usr/local/redis/7000/7000.conf

3.3 连接redis

#连接端口为6379 Host为127.0.0.1的redis服务器
[root@zhj ~]# redis-cli 
#连接端口为7000 Host为192.168.1.103的redis服务器
[root@zhj ~]# redis-cli -p 7000 -h 192.168.1.103

3.4 关闭

连接后,执行 shutdown 即可退出redis

3.5 性能测试

redis-benchmark -t get,spop -q -p 9001

4. 数据类型

4.1 String 【单值】

指令描述
set设置一个key/value
get根据key获得对应的value
mset一次设置多个key value
mget一次获得多个key的value
getset getset age 19获得原始key的值,同时设置新值
strlen获得对应key存储value的长度
append为对应key的value追加内容
getrange截取value的内容,对原始的值没有影响
setex setex key ex value设置一个key存活的有效期(秒)
psetex设置一个key存活的有效期(豪秒)
setnx只有当这个key不存在时等效set操作
msetnx可以同时设置多个key,在key不存在时有效
decr进行数值类型的-1操作
decrby根据提供的数据进行减法操作
incr进行数值类型的+1操作
incrby根据提供的数据进行加法操作
incrbyfloat根据提供的数据加入浮点数

keys * 查看所有key

ttl key 查看剩余存活时间

del key 删除key

select 5 切换到第6个库

flushDB 删除当前库的所有数据

4.2 List

指令描述
lpush将某个值加入到一个key列表头部
lpushx同lpush,但是必须要保证这个key存在
rpush将某个值加入到一个key列表末尾
rpushx同rpush,但是必须要保证这个key存在
lpop返回和移除列表的第一个元素
rpop返回和移除列表的最后一个元素
lrange获取某一个下标区间内的元素
llen获取列表元素个数
lset lset key index value设置某一个位置的元素(替换已有的某个值)
lindex lindex key index获取某一个位置的元素
lrem lrem key 2 xxx从列表头起,删除对应个数的指定元素
ltrim保留列表中特定区间内的元素,将其他的元素删除
; linsert key after/before old new在某一个元素之前,之后插入新元素

4.3 Set

指令描述
sadd为集合添加元素
smembers显示集合中所有元素 无序
scard返回集合中元素的个数
spop随机返回并移除一个元素
smove smove setFrom setTo xxx从一个集合中向另一个集合移动元素
srem从集合中删除一个元素
sismember sismember set77 值判断一个集合中是否含有这个元素
srandmember随机返回元素,对原始数据没有影响
sdiff sdiff seta setb减去两个集合中共有的元素
sinter求交集
sunion求并集

4.4 ZSet

指令描述
zadd zadd key 10 a 5 b 30 c添加一个有序集合元素,根据元素的score排序
zcard返回集合的元素个数
zrange返回一个范围内的元素
zrangebyscore按照分数查找一个范围内的元素
zrank zrank key xx返回对应元素的排名
zrevrank返回对应元素倒序排名
zscore zscore key xxx显示某一个元素的分数
zrem移除某一个元素
zincrby zincrby key 10 lining给某个特定元素加分

4.5 Hash

指令描述
hset设置一个key/value对
hget获得一个key对应的value
hgetall获得所有的key/value对
hdel删除某一个key/value对
hexists判断一个key是否存在
hkeys获得所有的key
hvals获得所有的value
hmset设置多个key/value
hmget获得多个key的value
hsetnx设置一个不存在的key的值
hincrby hincrby key k 2为value进行加法运算
hincrbyfloat为value加入浮点值

5. 持久化

redis是内存型的nosql数据库,所以数据安全必须考虑,redis支持将数据持久化到磁盘。

5.1 RDB

5.1.1 原理

Snapshotting(RDB)机制的运行原理

1> 在某些时刻,Redis通过fork产生子进程,一个父进程的快照(副本),

​ 其中有和父进程当前时刻相同的数据

2> 父进程继续处理client请求,子进程负责将快照(数据副本)写入临时文件

3> 子进程写完后,用临时文件替换原来的快照文件,然后子进程退出。

5.1.2 配置

save 900 1 #900秒超过1个key被修改

save 300 10 #300秒超过10个key被修改 (删除所有save项,则会关闭rdb)

dbfilename dump.rdb #快照文件名

stop-writes-on-bgsave-error yes #快照失败后是否继续写操作

rdbcompression yes #是否压缩快照文件

5.1.3 细节

1> 如果发生系统崩溃,则会丢失最近一次rdb之后的数据,所以如果项目不能接受这样的数据损失,还需要其他安全手段

2> 不适于实时性持久化,但其数据体量小,执行速度快,适合做数据版本控制

3> 如果数据量巨大,则创建子进程的时间长,导致redis卡顿,要谨慎设置save参数时间间隔大一些;

或如果软件允许,可以每天在闲时手动同步(凌晨后…)

4> 将生成的快照文件,留在原地,则可以在重启redis后,保持数据状态

将生产的快照文件,复制到其他redis服务中,可以方便的将数据移植过去

5.1.4 触发方式

1> 某一个save参数被满足

2> 执行 bgsave

3> 执行 save

4> redis服务关闭时

5.2 AOF

5.2.1 原理

1> Redis将每一个写操作(执行成功),写入一个aof文件,

2> Redis重启时只要从头到尾执行一次aof文件,即可恢复据;

也可以将aof文件复制到别的服务器,做数据移植

注意:在重启时,要恢复数据,如果rdb文件和aof文件同时存在,以AOF为准

5.2.2 配置

appendonly yes # 启动AOF机制

appendfsync always # 每次收到写命令就立即强制 写入磁盘,保证完全的持久化,但产生极大的IO开销(不推荐使用)

appendfsync everysec # 每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中(推荐使用)

appendfsync no # 由操作系统决定何时同步,如果系统宕机则导致redis丢失不定数量数据

appendfilename “appendonly.aof” #设置aof文件名

5.2.3 细节

1> AOF文件会不断增长(可能比快照文件大几倍),在极端情况下,可能会对硬盘空间造成压力

2> Redis重启时,需要重新执行一个可能非常大的AOF,时间会很长

3> AOF同步时间间隔小,数据更安全,理论上至多丢失1秒的数据,比rdb更擅长做更实时的持久化

5.3 AOF重写

5.3.1 原理

为了减小aof文件的体量,可以手动发送 bgrewriteaof 命令,则会创建子进程,生成更小体量的aof,然后替换掉旧的、大体量的aof文件。

5.3.2 配置

auto-aof-rewrite-percentage 100

auto-aof-rewrite-min-size 64mb

在体量超过64mb,且比上次重写后的体量增加了100%时自动触发重写

5.3.3 细节

1> 如果当前数据量巨大,则子进程创建过程会很耗时

2> 在替换aof文件时,如果旧aof很大,则删除它也是一个耗时的过程

6. 集群

6.1 原理

在处理大量复杂的数据时,基于主从的复制( replication ),将有效保证redis的高性能。

一个Redis主服务器,并为其关联多个从服务器,主服务器会将自己的数据状态不断的同步给从服务器。

所有读取操作负载到多个从服务器中,主服务器负责写操作

##6.2 配置

slaveof 192.168.1.103 7000 #成为 103的从机

主服务器的所有数据会在初始接收到从服务器的连接时全部发送到从服务器。之后每次主服务器执行完一个写操作,都会发送到从服务器

7. Java-Redis

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
@Test
public void shouldAnswerWithTrue(){
    Jedis jedis = new Jedis("192.168.110.133", 6379);
    jedis.set("name","臧红久");
    System.out.println(jedis.get("name"));
}

8. Spring-Data-Redis

提供了一套API,实现了一整套Redis操作的方案,使得Java和Redis通信变得极其简单
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis 
     需要spring-4.3.10版本
-->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.6.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.3.10.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.54</version>
</dependency>

8.1 配置

将Spring-Data-Reids的核心组件纳入工厂
<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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                         http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <!-- 连接池配置 -->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!-- 最多空闲连接数 -->
        <property name="maxIdle" value="1" />
        <!-- 最多有多少连接 -->
        <property name="maxTotal" value="5" />
        <property name="minIdle" value="1"></property>
        <!-- 连接数用完时,是否阻塞,阻塞超过maxWaitMillis会抛出异常 -->
        <property name="blockWhenExhausted" value="true" />
        <!-- 检出连接时,最大等待时长 -->
        <property name="maxWaitMillis" value="30000" />
        <!-- 在检出时是否检测 -->
        <property name="testOnBorrow" value="false" />
        <!-- 空闲时是否检测连接是否可用 -->
        <property name="testWhileIdle" value="false"></property>
        <!-- Evict=驱逐  连接至少要空闲多少时间才会成为可以被驱逐线程扫描并移除 -->
        <property name="minEvictableIdleTimeMillis" value="60000"></property>
        <!-- 驱逐线程 两次驱逐之间要sleep的时间 如果小于0,则不会有驱逐线程,则minEvictableIdleTimeMillis无效-->
        <property name="timeBetweenEvictionRunsMillis" value="30000"></property>
        <!-- 驱逐线程每次最多扫描几个连接 -->
        <property name="numTestsPerEvictionRun" value="3"></property>
        <!-- last in first out 检出策略 后入先出  或 先入先出 -->
        <property name="lifo" value="true"></property>
    </bean>

    <!-- 连接Factory -->
    <bean id="jedisConnectionFactory"
          class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!-- Redis主机 -->
        <property name="hostName" value="192.168.110.133"></property>
        <property name="port" value="9001"></property>
        <!-- 连接池配置信息 -->
        <property name="poolConfig" ref="jedisPoolConfig"></property>
    </bean>

    <!-- 如果没有设置序列化,则默认使用DefaultSerializer。
		 声明序列化组件
    -->
    <bean id="ss" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
    <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"      			id="jacks" />
    <bean class="com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer" id="fast"></bean>
    <!-- RedisTemplate:核心组件 -->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
          p:connectionFactory-ref="jedisConnectionFactory"
          p:keySerializer-ref="ss"
          p:hashKeySerializer-ref="ss"
          p:hashValueSerializer-ref="fast"
          p:stringSerializer-ref="ss"
          p:valueSerializer-ref="fast"/>
</beans>

8.2 测试

//spring测试集成
//注入RedisTemplate,自动转换为对应Operation
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AppTest
{
    @Resource(name = "redisTemplate") //spring-data-redis 会自动 将RedisTemplate转换为 XXOperations
    private ValueOperations<String,Object> vo;

    @Resource(name = "redisTemplate")
    private ListOperations<String,Object> lo;

    @Resource(name = "redisTemplate")
    private SetOperations<String,Object> so;

    @Resource(name = "redisTemplate")
    private ZSetOperations<String,Object> zo;

    @Resource(name = "redisTemplate")
    private HashOperations<String,String,Object> ho;

    @Test
    public void shouldAnswerWithTrue2(){
        vo.set("java",new User("臧红久",new Date()));
        User user = (User)vo.get("java");
        System.out.println(user);
        lo.leftPush("list",new User("臧红久",new Date()));
        so.add("set",new User("臧红久",new Date()));
        zo.add("zset",new User("臧红久",new Date()),1);
        ho.put("hash","user",new User("臧红久",new Date()));
    }
}

RedisTempalte 可以直接注入给 5中数据类型的 XXOperations引用

	@Resource(name = "redisTemplate")   //PropertyEditor
    private ValueOperations<String,Object> valueOps;

    @Resource(name = "redisTemplate")   //PropertyEditor
    private ListOperations<String,Object> listOps;

    @Resource(name = "redisTemplate")   //PropertyEditor
    private SetOperations<String,Object> setOps;

    @Resource(name = "redisTemplate")   //PropertyEditor
    private ZSetOperations<String,Object> zsetOps;

    @Resource(name = "redisTemplate")   //PropertyEditor
    private HashOperations<String,String,Object> hashOps;

RedisTemplate 有一个子类 StringRedisTemplate

// 如果所有数据都是字符串,则可以选用此子类
public StringRedisTemplate() {
		RedisSerializer<String> stringSerializer = new StringRedisSerializer();
		setKeySerializer(stringSerializer);
		setValueSerializer(stringSerializer);
		setHashKeySerializer(stringSerializer);
		setHashValueSerializer(stringSerializer);
}

9. MyBatis-Redis-Cache

Mybatsi默认缓存对象 PerpetualCache,是本地缓存

9.1 定义缓存组件

org.apache.ibatis.cache.Cache
import org.apache.ibatis.cache.Cache;
...
// 自定义 Mybatis 缓存组件
public class RedisCache9 implements Cache{
    //存储 namespace
    private String id;
    private ReentrantReadWriteLock lock;//锁,保证线程安全
    public RedisCache9(){}

    // mybatis为一个mapper创建缓存对象时,会调用该构造,传入mapper的namespace
    public RedisCache9(String id) {//id=namespace
        this.lock = new ReentrantReadWriteLock();
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }

    /*
    	将数据缓存入Redis,key = “查询的sql”  value = "查询结果"	
    */
    public void putObject(Object key, Object value) {
        // 获取spring工厂
        WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
        // 获取RedisTemplate
        RedisTemplate<String,Object> rt = (RedisTemplate<String, Object>) context.getBean("redisTemplate");
        // 存入缓存到Redis
        ValueOperations<String, Object> vo = rt.opsForValue();
        vo.set(key.toString(),value);
    }

    /*
    	从redis中获取缓存数据, key = "查询时的sql"
    */
    public Object getObject(Object key) {
        // 获取spring工厂
        WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
        // 获取RedisTemplate
        RedisTemplate<String,Object> rt = (RedisTemplate<String, Object>) context.getBean("redisTemplate");
        // 从redis中查询缓存
        ValueOperations<String, Object> vo = rt.opsForValue();
        return vo.get(key.toString());
    }

    /*
    	删除某个缓存 key
    */
    public Object removeObject(Object key) {
        WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
        RedisTemplate<String,Object> rt = (RedisTemplate<String, Object>) context.getBean("redisTemplate");
        rt.delete(key.toString());
        return null;
    }

    /*
    	清空缓存
    */
    public void clear() {
        WebApplicationContext context = ContextLoader.getCurrentWebApplicationContext();
        RedisTemplate<String,Object> rt = (RedisTemplate<String, Object>) context.getBean("redisTemplate");
        ValueOperations<String, Object> vo = rt.opsForValue();
        Set<String> keys = rt.keys("*" + id + "*");
        rt.delete(keys);
    }

    // 忽略
    public int getSize() {
        return 0;
    }

    // 返回一个锁对象
    public ReadWriteLock getReadWriteLock() {
        return this.lock;
    }
}

9.2 使用自定义的缓存组件

<cache type="com.zhj.cache.RedisCache9"/>

<mapper namespace="com.zhj.dao.UserDAO">
    <!-- 为Mapper 指定自定义缓存,查询会存入缓存,更新会清空缓存 -->
    <cache type="com.zhj.cache.RedisCache9"/>
    <select ...
	<insert ...
    ....
</mapper>

10. Shiro-Redis-Cache

Cache:负责完成缓存数据的功能

CacheManager:负责管理Cache核心流程,重点是会创建Cache对象

10.1 定义Cache组件

org.apache.shiro.cache.Cache
//定义Cache类
import org.apache.shiro.cache.Cache;
....
/**
 * 负责将Shiro的 Realm查询到的 身份信息 和 权限信息 缓存如redis
 *
 * 此类需要RedisTemplate 对象
 */
public class ShiroCache9 implements Cache{
    //Cache对象由CacheManager 创建,并会传入缓存前缀
    private String name;//自定义属性,存储缓存前缀:“com.zhj.realm.MyRealm.authorizationCache”
    private RedisTemplate rt;//自定义属性,便于通信Redis
    public ShiroCache9(){}
    public ShiroCache9(String name){// 构造,为缓存前缀赋值
        System.out.println("ShiroCache's Name:"+name);
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public RedisTemplate getRt() {
        return rt;
    }
    public void setRt(RedisTemplate rt) {
        this.rt = rt;
    }

    ///以上自定义属性///

    /*
    	从缓存中取值
    	o = 用户名
    */
    public Object get(Object o) throws CacheException {
        System.out.println("Realm get Cache :"+o);
        return rt.opsForValue().get(name+":"+o.toString()); // key做了拼接 【前缀+“:”+用户名】
    }

    /*
    	将数据(权限信息)存入Redis
    	o = 用户名
    	o2 = 权限信息
    */
    public Object put(Object o, Object o2) throws CacheException {
        System.out.println("Realm save Cache :"+o.toString()+" -- "+o2);
        rt.opsForValue().set(name+":"+o.toString(),o2);// key做了拼接 【前缀+“:”+用户名】
        return null;
    }

    /*
    	删除缓存数据
    	o = 用户名
    */
    public Object remove(Object o) throws CacheException {
        System.out.println("Realm remove Cache :"+o.toString());
        rt.delete(name+":"+o.toString());// key做了拼接 【前缀+“:”+用户名】
        return null;
    }

    // 忽略
    public void clear() throws CacheException {
        System.out.println("clear~~");
    }

    // 忽略
    public int size() {
        System.out.println("size~~~~~~~~");
        return 0;
    }

    // 忽略
    public Set keys() {
        System.out.println("keys~~~~`");
        return null;
    }

    // 忽略
    public Collection values() {
        System.out.println("values~~~~~~");
        return null;
    }
}

10.2 定义CacheManager

// CacheManager,负责创建Cache对象
public class ShiroCacheManager9 extends AbstractCacheManager {

    private RedisTemplate rt;// 需要注入RedisTemplate

    public RedisTemplate getRt() {
        return rt;
    }

    public void setRt(RedisTemplate rt) {
        this.rt = rt;
    }

    @Override
    // 创建Cache对象,并传入前缀,供Cache中使用
    // 权限缓存prefix : com.zhj.realm.MyRealm.authorizationCache
    protected Cache createCache(String prefix) throws CacheException {
        ShiroCache9 cache = new ShiroCache9(prefix);//新建 自定义的Cache对象
        cache.setRt(rt);// 为缓存对象存入RedisTemplate,供缓存对象中通信Redis
        return cache;
    }
}

10.3 配置Cache和CacheManager

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd 				
                           http://www.springframework.org/schema/context 
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 声明CacheManager,注意Cache不用声明,会由CacheManager创建 -->
    <bean class="com.zhj.cache.ShiroCacheManager9" id="cacheManager9">
        <property name="rt" ref="redisTemplate"/>
    </bean>
    
    <!-- 声明SecurityManager -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="realm1"/>
        <!-- 注册自定义的CacheManager -->
        <property name="cacheManager" ref="cacheManager9"/>
    </bean>
    
    <!-- 以下内容 不变 -->
    <bean id="realm1" class="com.zhj.realm.MyRealm">
        <property name="userService" ref="userServiceImpl"/>
        <property name="roleService" ref="roleServiceImpl"/>
        <property name="permService" ref="permServiceImpl"/>

        <!-- 此属性如果通过注解注入,则需要将注解加载set方法上,不能用在属性上。
              此属性是父类属性,所以只有在set方法上注入,才能覆盖父类属性值。
        -->
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <property name="hashAlgorithmName" value="SHA-256"/>
                <!-- true means hex encoded, false means base64 encoded -->
                <property name="storedCredentialsHexEncoded" value="false"/>
                <property name="hashIterations" value="1024"/>
            </bean>
        </property>
        <!-- 身份信息缓存不需要开启 默认为false;身份信息会自动存入session中-->
        <!--<property name="authenticationCachingEnabled" value="false"/>-->
    </bean>

    <!-- 生产SpringShiroFilter
         ( 持有shiro的过滤相关规则,可进行请求的过滤校验,校验请求是否合法 )
    -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
    </bean>
</beans>

11. Shiro-Redis-Session

SimpleSession : Session类型

SessionFactory : 直属SessionManager,默认为SimpleSessionFactory ,负责创建Session对象,默认为SimpleSession

SessionDAO : 直属SessionManager,负责将每个Session对象存储起来,需要定制这个组件,以集成redis

SessionManager : 直属SecurityManager,负责管理Session处理的核心调度过程。

11.1 定义SessionDAO

// 定义SessionDAO,负责存储Session对象
public class MySessionDAO extends AbstractSessionDAO {
    private RedisTemplate template;//需要注入RedisTemplate,以支持后续的Redis通信
    public RedisTemplate getTemplate() {
        return template;
    }
    public void setTemplate(RedisTemplate template) {
        this.template = template;
    }

    //将Session对象存入redis;(由SimpleSessionFactory创建的Session对象)
    protected Serializable doCreate(Session session) {
		// 生成sessionID
        Serializable sessionId = generateSessionId(session);
        //存入redis
        System.out.println("save session to redis");
        assignSessionId(session,sessionId);// 将sessionID赋值给Session对象
        // key = 前缀+sessionID  ;  value = session对象
        //template.opsForValue().set("session"+sessionId,session);//Session对象存入Redis,存入时定义了"前缀"
        // Session对象存入Redis,存入时定义了"前缀",且定义了超时时间
        template.opsForValue().set("session04:"+JSESSIONID04,session,10, TimeUnit.SECONDS);
        return sessionId;
    }

    //从redis获取session对象,供项目使用
    @Override
    protected Session doReadSession(Serializable sessionId) {
        // 从redis获取session,获取时,也要定义 “前缀”; [前缀+sessionID]
        Session session = (Session)template.opsForValue().get("session"+sessionId);
        // 设置session的key的有效时间
        template.expire("session04:"+sessionId,10,TimeUnit.SECONDS);
        return session;
    }

    //session中数据变动时,要将session对象同步到redis
    @Override
    public void update(Session session) throws UnknownSessionException {
        System.out.println("update session");
        Serializable sessionId = session.getId();
        // 设置session的key的有效时间
        template.expire("session04:"+sessionId,10,TimeUnit.SECONDS);
        // 将session,覆盖存储到Redis
        template.opsForValue().set("session"+sessionId,session);
    }

    //session过期时,从redis中删除session
    @Override
    public void delete(Session session) {
        Serializable sessionId = session.getId();//获取sessionID
        // 从redis删除session
        template.delete("session"+sessionId);
    }

    //session检测时,需要获取所有session
    @Override
    public Collection<Session> getActiveSessions() {
        //从redis获取全部或部分session
        Set keys = template.keys("session*");//通过前缀获取Session
        List<Session> list = template.opsForValue().multiGet(keys);
        return list;
    }
}

11.2 配置SessionDAO

将SessionDAO注册在SessionManager中

SessionManager注册在SecurityManager中

注意RedisTemplate的序列化方式

<!-- SessionManager,使用Shiro的DefaultWebSessionManager即可,其中注入自定义的SessionDAO -->
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!-- 注册SessionDAO,负责将Session对象存入redis -->
    <property name="sessionDAO">
        <bean class="com.zhj.dao.MySessionDAO">
            <property name="template" ref="redisTemplate2"/>
        </bean>
    </property>
</bean>

<!-- 注意:此RedisTemplate key序列化采用String序列化, 
          value没有使用Jackson或FastJson序列化,而是采用的默认的Jdk的序列化 
		  因为:SimpleSession中的属性都是transient,只能通过jdk序列化去回调 writeObject 和 readObject完成序列化和
               反序列化
-->
<bean id="ss" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<bean id="redisTemplate2" class="org.springframework.data.redis.core.RedisTemplate"
      p:connectionFactory-ref="jedisConnectionFactory"
      p:keySerializer-ref="ss"/>

<!-- Securitymanager -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <property name="realm" ref="myRealm"/>
    <!-- 注册cacheManager -->
    <property name="cacheManager" ref="cacheManager03"/>
    <!-- 注册sessionManager,其中会调度SessionDAO,将session存入Redis -->
    <property name="sessionManager" ref="sessionManager"/>
</bean>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值