※快速掌握key-value数据库

一.Redis概述

(一)什么是Redis

Redis是一个开源的使用ANSI C语言编写、支持网络、 可基于内存亦可持久化的日志型、Key-Value型 NoSQL数据库,并提供多种语言的API。从2010年3 月15日起,Redis的开发工作由VMware主持。从 2013年5月开始,Redis的开发由Pivotal赞助。

(二)Redis的特点

速度快 广泛的语言支持 持久化 多种数据结构 主从复制 高可用与分布式

(三)Redis数据结构

1.String

1.1使用情景:

缓存 秒杀 分布式锁 配置中心 对象序列化 计数器

1.2用法

laravel redis 获取key剩余时间 获取redis key的值_spring


2.List(字符串列表)

2.1 可用于有顺序要求的场景,如时间轴

2.2 用法

laravel redis 获取key剩余时间 获取redis key的值_spring_02


3.Set(字符串集合,通过哈希表实现的)

3.1应用场景

集合的交集,并集那些,还有随机抽出抽奖的人

3.2用法

4.ZSet(有序字符串集合)

4.1用用场景:排行榜

4.2用法

laravel redis 获取key剩余时间 获取redis key的值_redis_03


zcount player:rank 700 900

zrangebyscore player:rank 700 900

zrangebyscore player:rank 700 900 withscores

5.Hash(键值对)

5.1应用场景:存储对象

5.2用法

laravel redis 获取key剩余时间 获取redis key的值_spring_04


(四)Redis原理

laravel redis 获取key剩余时间 获取redis key的值_redis_05


laravel redis 获取key剩余时间 获取redis key的值_redis_06


laravel redis 获取key剩余时间 获取redis key的值_Redis_07


(五)Redis持久化-RDB数据文件保存

laravel redis 获取key剩余时间 获取redis key的值_spring_08


laravel redis 获取key剩余时间 获取redis key的值_spring_09


(六)Redis持久化-AOF日志保存

  • AOF的追加(fsync)策略 ualways - 随时写入(不推荐) ueverysec - 每秒写入(推荐) uno -
    依赖os规则写入(不推荐)



    rewrite说明 u aof 文件小于64mb,不重写 u aof第一次大于64mb时自动重写,压缩至33mb u aof再次增长至66mb时,相较上次重写后33mb增长率 =100%且大于64mb最小值,自动重写,压缩至50mb u 依次类推,下次再次达到100%增长率,即100mb时自动 重写,压缩.
    RDB与AOF的选择 u 当RDB与AOF都开启后,Redis官方默认采用AOF恢复 数据 u 在日常开发中,我们也推荐AOF来持久化数据 u Redis主从数据同步底层采用RDB实现
    (七)Redis内存管理与回收策略



二.单点搭建Redis

(一)安装步骤

laravel redis 获取key剩余时间 获取redis key的值_redis_10


(二)自定义配置文件

laravel redis 获取key剩余时间 获取redis key的值_redis_11

laravel redis 获取key剩余时间 获取redis key的值_redis_12


laravel redis 获取key剩余时间 获取redis key的值_redis_13


laravel redis 获取key剩余时间 获取redis key的值_Redis_14


save,bgsave手动保存到硬盘

(三)(命令行客户端通用命令)

laravel redis 获取key剩余时间 获取redis key的值_Redis_15


flushdb(删除本数据库) flushall(删除所有数据库)

三.集群搭建Redis

1.Redis部署采用主从复制集群部署,由于环境设备限制,故采用伪集群部署,将多个Redis实例安装在阿里云东上119.23.60.34。通过Redis提供的主从复制解决方案,采用一主机两从机,主机进行数据更新,从机复制数据并只能查询;设立三个哨兵(Sentinel)监控三台Redis主机的情况和互相监督哨兵的情况。
2.搭建步骤如下:
(1)从中文官网http://www.redis.cn/下载最新版Redis5.0.4.tar.gz,上传到服务器
/usr/local
(2)在windows电脑创建3个启动Redis配置文件,三个Sentinel启动日志
(A)redis-7000.conf
port 7000
daemonize yes
logfile “7000.log”
dir “./”
bind 0.0.0.0
(B)redis-7001.conf
port 7001
daemonize yes
logfile “7001.log”
dir “./”
slaveof 119.23.60.34 7000
bind 0.0.0.0
(C)redis-7002.conf
port 7002
daemonize yes
logfile “7002.log”
dir “./”
slaveof 119.23.60.34 7000
bind 0.0.0.0
(D)sentinel-26379.conf
port 26379
daemonize yes
logfile “26379.log”
dir “./”
sentinel monitor mymaster 119.23.60.34 7000 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 15000
bind 0.0.0.0
(E)sentinel-26380.conf
port 26380
daemonize yes
logfile “26380.log”
dir “./”
sentinel monitor mymaster 119.23.60.34 7000 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 15000
bind 0.0.0.0
(F)sentinel-26381.conf
port 26381
daemonize yes
logfile “26381.log”
dir “./”
sentinel monitor mymaster 119.23.60.34 7000 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 15000
bind 0.0.0.0
(3)解压缩redis5.0.4文件,将上面配置文件复制到Redis5.0.4中,登陆阿里云服务器,操作以下命令,并在操作命令前开启7000,7001,7002,26379,26380,26381端口
[root@izwz98b396i3cl6vnnpqc1z ~]# yum install gcc
[root@izwz98b396i3cl6vnnpqc1z local]# cd /usr/local/redis-5.0.4
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-server redis-7000.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-server redis-7001.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-server redis-7002.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-sentinel sentinel-26379.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-sentinel sentinel-26380.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# ./src/redis-sentinel sentinel-26381.conf
[root@izwz98b396i3cl6vnnpqc1z redis-5.0.4]# netstat -tulpn

最后修改项目的配置文件

#配置访问Redis集群 Sentinel
spring.redis.database=0
#spring.redis.host=119.23.60.34
#spring.redis.port=26379

spring.redis.jedis.pool.max-active=100
spring.redis.jedis.pool.max-idle=100
spring.redis.jedis.pool.min-idle=10
spring.redis.jedis.pool.max-wait=1000ms
spring.redis.sentinel.master=mymaster
spring.redis.sentinel.nodes=119.23.60.34:26379,119.23.60.34:26380,119.23.60.34:26381

四.Redis Desktop Manager使用

1.Redis默认不允许外网访问,需要在自定义配置文件中增加 bind 0.0.0.0
2.开发RedisLinux端口

五.Jedis使用

(一)创建Maven工程,导入jar包

<dependency>
        <groupId>com.yugabyte</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0-yb-11</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
        </dependency>

(二)jedis直连用法

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.List;
import java.util.Map;

public class JedisTestor {
    @Test
    public void testJedis() throws Exception{
        //创建一个Redis通道
        Jedis jedis = new Jedis("192.168.132.128", 6666, 1000);
        try {
            jedis.auth("123456");//设置登录密码
            jedis.select(3);//选择第四个数据库,数据库下标从0开始
            jedis.flushDB();//清空第四个数据库
            jedis.set("hello", "world"); //jedis.xxx方法名字就是命令
            System.out.println( jedis.get("hello"));
            jedis.mset(new String[]{"a", "1", "b", "2", "c" ,"3"});
            List<String> strs =  jedis.mget(new String[]{"a", "c"});
            System.out.println(strs);
            System.out.println(jedis.incr("c"));
            System.out.println(jedis.del("b"));;
        } catch (Exception e) {
            throw e;
        }finally {
            jedis.close();//释放链接
        }
    }

    @Test
    public void testHash() throws Exception {
        Jedis jedis = new Jedis("192.168.132.128", 6666, 1000);
        try {
            jedis.auth("123456");//设置登录密码
            jedis.select(3);//选择第四个数据库,数据库下标从0开始
            jedis.flushDB();//清空第四个数据库
            jedis.hset("user:1:info", "name", "齐毅");
            jedis.hset("user:1:info", "age", "35");
            jedis.hset("user:1:info", "height", "180");
            String name = jedis.hget("user:1:info", "name");
            System.out.println(name);
            Map user1 = jedis.hgetAll("user:1:info");
            System.out.println(user1);
        } catch (Exception e) {
            throw e;
        }finally {
            jedis.close();//释放链接
        }
    }

}

(三)Jedis线程池用法

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * 连接池测试类
 */
public class JedisPoolTestor {
    @Test
    public void testJedisPool(){
        GenericObjectPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100); //设置连接池中最多允许放100个Jedis对象
        //我的建议maxidle与maxTotal一样,相当于储备干部
        config.setMaxIdle(100);//设置连接池中最大允许的空闲连接
        config.setMinIdle(10);//设置连接池中最小允许的连接数
        config.setTestOnBorrow(false); //借出连接的时候是否测试有效性,推荐false
        config.setTestOnReturn(false); //归还时是否测试,推荐false
        //远程Redis配置默认有protected-mode yes,所以这里设置false
        config.setTestOnCreate(false); //创建时是否测试有效
        config.setBlockWhenExhausted(true);//当连接池内jedis无可用资源时,是否等待资源 ,true
        config.setMaxWaitMillis(1000); //没有获取资源时最长等待1秒,1秒后还没有的话就报错
        //创建jedis,这句话运行后就自动根据上面的配置来初始化jedis资源了
        JedisPool pool = new JedisPool(config , "192.168.132.128" , 6666);
        Jedis jedis = null;
        try {
            jedis = pool.getResource(); //从连接池中"借出(borrow)"一个jedis对象
            jedis.auth("123456");
            jedis.set("abc" , "bbb");
            System.out.println(jedis.get("abc"));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(jedis != null){
                jedis.close();//在使用连接池的时候,close()方法不再是关闭,而是归还(return)
            }
        }


    }
}

六.SSM下使用Jedis

(一)创建Maven工程,导入依赖

<dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
            <version>2.0.8.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.yugabyte</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0-yb-11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
      <!--  json序列化工具-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.6</version>
        </dependency>
        <!--对象转化Map-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>1.9.3</version>
        </dependency>

(二)增加配置文件applicationContext.xml

<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--连接池配置对象-->
    <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <property name="maxTotal" value="100"></property>
        <property name="maxIdle" value="100"></property>
        <property name="minIdle" value="10"></property>
        <property name="maxWaitMillis" value="1000"></property>
        <property name="blockWhenExhausted" value="true"></property>
        <property name="testOnBorrow" value="false"></property>
        <property name="testOnReturn" value="false"></property>
        <property name="testOnCreate" value="false"></property>
    </bean>
    <!-- usePool = false Jedis直连, true 连接池 -->
    <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        p:usePool="true" p:poolConfig-ref="poolConfig" p:hostName="192.168.132.128" p:port="6666" p:password="123456" p:database="10">
    </bean>
    <!--redisTemplate本质就是Jedis的封装,在Jedis基础上进行了大幅度的简化,并且对连接池友好,允许自动回收连接.
    PS:前面课程自己写的JedisPool如果没有调用close方法则会出现连接泄露的问题,但在Spring-Data-Redis中则不会-->
    <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connectionFactory-ref="jedisConnFactory">
        <!-- keySerializer key序列化采用String原文保存 -->
        <property name="keySerializer">
            <!-- StringRedisSerializer 字符串原文序列化保存 -->
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
        </property>
        <property name="valueSerializer">
            <!--//利用Jackson进行对象序列化-->
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"></bean>
        </property>
        <property name="hashKeySerializer">
            <!-- StringRedisSerializer 字符串原文序列化保存 -->
            <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"></bean>
        </property>
        <property name="hashValueSerializer">
            <!--//利用Jackson进行对象序列化-->
            <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer"></bean>
        </property>
    </bean>
</beans>

(三)测试使用

import org.apache.commons.beanutils.BeanUtils;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;

//启动时初始化Spring IOC容器
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:applicationContext.xml"})
public class SpringJedisTestor {
    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    @Test
    public void testSetGet(){
        //opsForValue()是对String字符串操作的类
        //默认情况下,Spring-Data-Redis 会采用JDK 序列化的方式将所有key value进行二进制序列化
        //这会导致redis数据库的值可读性极差,通常我们需要用字符串或JSON的形式来保存对象
        redisTemplate.opsForValue().set("a" , "b1111");
        String b = (String)redisTemplate.opsForValue().get("a");
        System.out.println(b);
    }

    @Test
    public void testObjectSerializer(){
        User u1 = new User("u1" , "p1");
        User u2 = new User("u2" , "p2");
        redisTemplate.opsForValue().set("user:u1" , u1);
        redisTemplate.opsForValue().set("user:u2" , u2);
    }
    @Test
    public void testObjectDeserializer(){
        User u1 = (User)redisTemplate.opsForValue().get("user:u1");
        System.out.println(u1.getUsername());
    }

    @Test
    public void testHashSerializer() throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        User u = new User("u3" , "p3");
        //关键是如何将JavaBean转为Map
        Map m1 = BeanUtils.describe(u);
        redisTemplate.opsForHash().putAll("user:m1" , m1);
    }

    @Test
    public void testHashDeserializer() throws InvocationTargetException, IllegalAccessException {
        Map m1 =  (Map)redisTemplate.opsForHash().entries("user:m1");
        User u1 = new User();
        BeanUtils.populate(u1 , m1);
        System.out.println(u1.getUsername());
    }

    @Test
    public void testList(){
        for(int i = 0 ; i < 10 ; i++){
            User u = new User("l" + i , "p" + i);
            redisTemplate.opsForList().rightPush("VipUserRank" , u);
        }

        List users = redisTemplate.opsForList().range("VipUserRank" ,1 , 5);
        System.out.println(users);
    }

    @Test
    //数据库层面的命令要是用execute接口实现
    public void testFlushDB(){
        redisTemplate.execute(new RedisCallback() {
            public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
                redisConnection.flushDb();
                return null;
            }
        });
    }
}

七.Spring Boot下整合spring cache和Redis

(一)创建工程,使用Initize方法,选择cache,Redis

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.9.6</version>
		</dependency>

(二)在启动中加入

@EnableCaching //启用SpringCache缓存 , SpringCache会根据底层引用的Jar包决定缓存的类型
public class Ch14SpringcacheApplication {
@Bean
	public RedisCacheConfiguration redisCacheConfiguration(){
		//加载redis缓存的默认配置
		RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
		configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
		return configuration;
	}

(三)在配置文件中

spring:
  redis:
    database: 9
    host: 192.168.132.128
    port: 6666
    password: 123456
    jedis:
      pool:
        max-active: 100
        max-idle: 100
        min-idle: 10
        max-wait: 1000ms

(四)创建实体类

package com.itlaoqi.redis.ch14springcache.entity;

import java.io.Serializable;
import java.util.Date;

public class Emp implements Serializable{
    private Integer empno;
    private String name;
    private Date birthday;
    private Float salary;
    private String department;
    public Emp(){ //必须要有默认构造函数

    }
    public Emp(Integer empno, String name, Date birthday, Float salary, String department) {
        this.empno = empno;
        this.name = name;
        this.birthday = birthday;
        this.salary = salary;
        this.department = department;
    }

    public Integer getEmpno() {
        return empno;
    }

    public void setEmpno(Integer empno) {
        this.empno = empno;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public Float getSalary() {
        return salary;
    }

    public void setSalary(Float salary) {
        this.salary = salary;
    }

    public String getDepartment() {
        return department;
    }

    public void setDepartment(String department) {
        this.department = department;
    }
}

(五)写service方法,加上声明式缓存注解

package com.itlaoqi.redis.ch14springcache.service;

import com.itlaoqi.redis.ch14springcache.entity.Emp;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Service
public class EmpService {
    //对于默认情况下, Redis对象的序列化使用的是JDK序列化.必须要求实体类实现Serili..接口
    //Cacheable会将方法的返回值序列化后存储到redis中,key就是参数执行的字符串
    //Cacheable的用途就是在执行方法前检查对应的key是否存在,存在则直接从redis中取出不执行方法中的代码
    //没有对应的key则执行方法代码,并将返回值序列化保存到redis中
    //condition代表条件成立的时候才执行缓存的数据 , 反之有一个unless ,代表条件不成立的时候才获取缓存
    @Cacheable(value = "emp" , key = "#empId" , condition = "#empId != 1000")
    public Emp findById(Integer empId) {
        System.out.println("执行了FindById方法:EmpId:" + empId);
        return new Emp(empId , "itlaoqi"  , new Date()  , 1000f ,"RESEARCH");
    }
    //冒号分割
    @Cacheable(value = "emp:rank:salary")
    public List<Emp> getEmpRank() {
        List list = new ArrayList();
        for(int i = 0 ; i < 10 ; i++) {
            list.add(new Emp(i , "emp" +  i , new Date() , 5000 + i * 100f , "RESEARCH"));
        }
        return list;
    }

    //@CachePut 作用是不管redis是否存在key, 都对数据进行更新
    @CachePut(value="emp" , key = "#emp.empno")
    public Emp create(Emp emp){
        System.out.println("正在创建" + emp.getEmpno() + "员工信息");
        return emp;
    }

    //@CachePut 作用是不管redis是否存在key, 都对数据进行更新
    //Update也是用CachePut
    @CachePut(value="emp" , key = "#emp.empno")
    public Emp update(Emp emp){
        System.out.println("正在更新" + emp.getEmpno() + "员工信息");
        return emp;
    }

    @CacheEvict(value="emp" , key = "#empno")
    public void delete(Integer empno){
        System.out.println("删除" + empno + "员工信息");
    }

}

八.Redis开发规范

laravel redis 获取key剩余时间 获取redis key的值_spring_16


laravel redis 获取key剩余时间 获取redis key的值_Redis_17

laravel redis 获取key剩余时间 获取redis key的值_Redis_18

laravel redis 获取key剩余时间 获取redis key的值_Redis_19