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


<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"));
}

8. Spring-Data-Redis

提供了一套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>

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>
        
        <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>

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 使用自定义的缓存组件

<mapper namespace="com.zhj.dao.UserDAO">
    
    <cache type="com.zhj.cache.RedisCache9"/>