Salvatore Sanfilippo 萨尔瓦托·桑菲利波--“Redis之父”
Salvatore在负责一个
page view
记录的系统,接收多个网站js发送来的页面访问记录数据,并存储之后展示给用户,最大负载每秒数千条页面记录,当时Salvatore在仅有硬件资源上无法用现有的数据库达到希望的性能。所以催生了redis的雏形 –一段C程序
基于内存存储的,NoSql数据库 ( 非关系型数据库 ),存储结构 : key-value
Redis是一个开放源代码(BSD许可)内存中的数据结构存储,用作数据库、缓存和消息代理。
对于数据量多,数据交互效率要求高的场景,可以考虑使用redis
**下载:**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 安装
默认配置启动(默认端口6379)
[root@zhj ~]# redis-server
在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
#连接端口为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
连接后,执行
shutdown
即可退出redis
redis-benchmark -t get,spop -q -p 9001
指令 | 描述 |
---|---|
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
删除当前库的所有数据
指令 | 描述 |
---|---|
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 | 在某一个元素之前,之后插入新元素 |
指令 | 描述 |
---|---|
sadd | 为集合添加元素 |
smembers | 显示集合中所有元素 无序 |
scard | 返回集合中元素的个数 |
spop | 随机返回并移除一个元素 |
smove smove setFrom setTo xxx | 从一个集合中向另一个集合移动元素 |
srem | 从集合中删除一个元素 |
sismember sismember set77 值 | 判断一个集合中是否含有这个元素 |
srandmember | 随机返回元素,对原始数据没有影响 |
sdiff sdiff seta setb | 减去两个集合中共有的元素 |
sinter | 求交集 |
sunion | 求并集 |
指令 | 描述 |
---|---|
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 | 给某个特定元素加分 |
指令 | 描述 |
---|---|
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加入浮点值 |
redis是内存型的nosql数据库,所以数据安全必须考虑,redis支持将数据持久化到磁盘。
Snapshotting(RDB)机制的运行原理
1> 在某些时刻,Redis通过fork产生子进程,一个父进程的快照(副本),
其中有和父进程当前时刻相同的数据
2> 父进程继续处理client请求,子进程负责将快照(数据副本)写入临时文件
3> 子进程写完后,用临时文件替换原来的快照文件,然后子进程退出。
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 #是否压缩快照文件
1> 如果发生系统崩溃,则会丢失最近一次rdb之后的数据,所以如果项目不能接受这样的数据损失,还需要其他安全手段
2> 不适于实时性持久化,但其数据体量小,执行速度快,适合做数据版本控制
3> 如果数据量巨大,则创建子进程的时间长,导致redis卡顿,要谨慎设置save参数时间间隔大一些;
或如果软件允许,可以每天在闲时手动同步(凌晨后…)
4> 将生成的快照文件,留在原地,则可以在重启redis后,保持数据状态
将生产的快照文件,复制到其他redis服务中,可以方便的将数据移植过去
1> 某一个
save
参数被满足2> 执行
bgsave
3> 执行
save
4> redis服务关闭时
1> Redis将每一个写操作(执行成功),写入一个aof文件,
2> Redis重启时只要从头到尾执行一次aof文件,即可恢复据;
也可以将aof文件复制到别的服务器,做数据移植
注意:在重启时,要恢复数据,如果rdb文件和aof文件同时存在,以AOF为准
appendonly yes # 启动AOF机制
appendfsync always # 每次收到写命令就立即强制 写入磁盘,保证完全的持久化,但产生极大的IO开销(不推荐使用)
appendfsync everysec # 每秒钟强制写入磁盘一次,在性能和持久化方面做了很好的折中(推荐使用)
appendfsync no # 由操作系统决定何时同步,如果系统宕机则导致redis丢失不定数量数据
appendfilename “appendonly.aof” #设置aof文件名
1> AOF文件会不断增长(可能比快照文件大几倍),在极端情况下,可能会对硬盘空间造成压力
2> Redis重启时,需要重新执行一个可能非常大的AOF,时间会很长
3> AOF同步时间间隔小,数据更安全,理论上至多丢失1秒的数据,比rdb更擅长做更实时的持久化
为了减小aof文件的体量,可以手动发送
bgrewriteaof
命令,则会创建子进程,生成更小体量的aof,然后替换掉旧的、大体量的aof文件。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
在体量超过64mb,且比上次重写后的体量增加了100%时自动触发重写
1> 如果当前数据量巨大,则子进程创建过程会很耗时
2> 在替换aof文件时,如果旧aof很大,则删除它也是一个耗时的过程
在处理大量复杂的数据时,基于主从的
复制( replication )
,将有效保证redis的高性能。一个Redis主服务器,并为其关联多个从服务器,主服务器会将自己的数据状态不断的同步给从服务器。
所有读取操作负载到多个从服务器中,主服务器负责写操作
##6.2 配置
slaveof 192.168.1.103 7000
#成为 103的从机
主服务器的所有数据会在初始接收到从服务器的连接时全部发送到从服务器。之后每次主服务器执行完一个写操作,都会发送到从服务器
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.9.0version>
dependency>
@Test
public void shouldAnswerWithTrue(){
Jedis jedis = new Jedis("192.168.110.133", 6379);
jedis.set("name","臧红久");
System.out.println(jedis.get("name"));
}
提供了一套API,实现了一整套Redis操作的方案,使得Java和Redis通信变得极其简单
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
<version>2.9.0version>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-redisartifactId>
<version>1.8.6.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>4.3.10.RELEASEversion>
dependency>
<dependency>
<groupId>com.fasterxml.jackson.coregroupId>
<artifactId>jackson-databindartifactId>
<version>2.9.8version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.54version>
dependency>
将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>
<property name="blockWhenExhausted" value="true" />
<property name="maxWaitMillis" value="30000" />
<property name="testOnBorrow" value="false" />
<property name="testWhileIdle" value="false">property>
<property name="minEvictableIdleTimeMillis" value="60000">property>
<property name="timeBetweenEvictionRunsMillis" value="30000">property>
<property name="numTestsPerEvictionRun" value="3">property>
<property name="lifo" value="true">property>
bean>
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="192.168.110.133">property>
<property name="port" value="9001">property>
<property name="poolConfig" ref="jedisPoolConfig">property>
bean>
<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>
<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>
//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);
}
Mybatsi默认缓存对象 PerpetualCache,是本地缓存
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;
}
}
<mapper namespace="com.zhj.dao.UserDAO">
<cache type="com.zhj.cache.RedisCache9"/>
Cache:负责完成缓存数据的功能
CacheManager:负责管理Cache核心流程,重点是会创建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;
}
}
// 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;
}
}
<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">
<bean class="com.zhj.cache.ShiroCacheManager9" id="cacheManager9">
<property name="rt" ref="redisTemplate"/>
bean>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="realm1"/>
<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"/>
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="SHA-256"/>
<property name="storedCredentialsHexEncoded" value="false"/>
<property name="hashIterations" value="1024"/>
bean>
property>
bean>
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
bean>
beans>
SimpleSession : Session类型
SessionFactory : 直属SessionManager,默认为SimpleSessionFactory ,负责创建Session对象,默认为SimpleSession
SessionDAO : 直属SessionManager,负责将每个Session对象存储起来,需要定制这个组件,以集成redis
SessionManager : 直属SecurityManager,负责管理Session处理的核心调度过程。
// 定义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,存入时定义了"前缀"
return sessionId;
}
//从redis获取session对象,供项目使用
@Override
protected Session doReadSession(Serializable sessionId) {
// 从redis获取session,获取时,也要定义 “前缀”; [前缀+sessionID]
Session session = (Session)template.opsForValue().get("session"+sessionId);
return session;
}
//session中数据变动时,要将session对象同步到redis
@Override
public void update(Session session) throws UnknownSessionException {
System.out.println("update session");
Serializable sessionId = session.getId();
// 将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;
}
}
将SessionDAO注册在SessionManager中;
SessionManager注册在SecurityManager中;
注意RedisTemplate的序列化方式。
<bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
<property name="sessionDAO">
<bean class="com.zhj.dao.MySessionDAO">
<property name="template" ref="redisTemplate2"/>
bean>
property>
bean>
<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"/>
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="myRealm"/>
<property name="cacheManager" ref="cacheManager03"/>
<property name="sessionManager" ref="sessionManager"/>
bean>