Redis

一、Redis安装配置

优点:
丰富的数据结构
高速读写(性能极高)
代码量很少
数据在内存中、支持数据持久化
原子性

非关系型数据库(NOSQL):
数据与数据之间是没有任何关联的。
命名来解决

新购云服务器初始配置

groupadd manager	#创建一个组manager
useradd -m -g manager kisen	#新增用户kisen
passwd kisen	#设置用户密码

#为用户设置root权限
vim /etc/sudoers	#以root身份登录,若是只读权限,需修改文件权限:chmod u+w /etc/sudoers
#方式一:为某一用户增加sudo功能
在root ALL=(ALL) ALL下面一行增加:
userName ALL=(ALL)ALL或userName ALL=(ALL)NOPASSWD:ALL	#注:增加NOPASSWD是为了省去使用sudo功能时需要你输入当前账号的密码
#方式二:为某一用户组增加sudo功能(自荐)
在%whell ALL=(ALL)ALL下面一行增加:
%groupName ALL=(ALL)ALL或%groupName ALL=(ALL)NOPASSWD:ALL

chmod 777 -R kisen #切换至home目录,root用户给kisen文件夹授权
grep bash /etc/passwd	#查看整个系统用户的命令
usermod -d / kisen	#切换到root用户,修改用户kisen默认登入目录为/

#修改命令提示符及颜色
vim /etc/profile
# add PS1
export PS1='\[\e[35m\][\u@\h \w \t]\$\[\e[0m\]'
# 使生效
source /etc/profile

#华为云修改主机名
https://support.huaweicloud.com/ecs_faq/zh-cn_topic_0050735736.html

CentOS7以上使用root用户配置防火墙

firewall-cmd --state	#查看防火墙状态
systemctl stop firewalld	#关闭防火墙
systemctl start firewalld	#启动防火墙
firewall-cmd --zone=public --list-ports	#查看指定区域(默认public)已开放的端口
firewall-cmd --zone=public(作用域) --add-port=6379/tcp(端口和访问类型) --permanent(永久生效)	#开启6379端口
firewall-cmd --reload	#重启防火墙
systemctl enable firewalld	#开机启动
systemctl disable firewalld	#停止并禁用开机启动
firewall-cmd --zone=public --remove-port=6379/tcp --permanent	#删除
# redis是面向机器语言(C语言)编写的,所以要先安装gcc
wget http://download.redis.io/releases/redis-5.0.5.tar.gz
tar -zxvf redis-5.0.5.tar.gz
mv redis-5.0.5 redis5.0
cd redis5.0
# 编译(C语言)
make	#或make MALLOC=libc
# (安装编译后的文件)安装到指定目录
make PREFIX=/usr/local/redis install
# 注:PREFIX必须大写,同时会自动为我们创建redis目录,并将结果安装到此目录

# 启动Redis服务
进入对应的安装目录/usr/local/redis
执行命令:./bin/redis-server		#redis默认端口:6379
# 启动Redis客户端
./bin/redis-cli

#测试服务端是否启动
在客户端输入ping回车,响应PONG说明服务端已启动

配置Redis

#把/usr/local/redis整个文件夹授予给kisen用户
sudo chown -R kisen /usr/local/redis

cd /var/local/redis5.0
#将配置文件复制到安装文件的目录下
cp redis.conf /usr/local/redis
#配置文件很大,使用如下命令查看更好些
less -mN redis.conf
vim redis.conf
将daemonize no改为daemonize yes	#启用守护进程
注释掉bind 127.0.0.1	#取消只有本机能访问,后期需要远程访问redis
requirepass kisen	#设置密码

# 要使配置文件生效,启动时要带上配置文件
./bin/redis-server ./redis.conf
#生效后redis就以守护进程启动了,使用以下命令查看redis进程
ps -ef|grep redis

#本地客户端登录
./bin/redis-cli -a kisen	#用密码登录
#远程服务登录
redis-cli -h host -p port -a password

# 断电、非正常关闭(容易数据丢失)
ps -ef|grep redis	#查询PID
kill -9 PID
# 正常方式关闭服务端(数据保存)
./bin/redis-cli shutdown

Redis设置开机自启动:
vim /etc/init.d/redis #编写脚本

#!/bin/sh
# chkconfig: 2345 10 90  
# description: Start and Stop redis   

REDISPORT=6379
EXEC=/usr/local/redis/bin/redis-server
CLIEXEC=/usr/local/redis/bin/redis-cli

PIDFILE=/var/run/redis_${REDISPORT}.pid
CONF="/usr/local/redis/redis.conf"

case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $CLIEXEC -p $REDISPORT shutdown
                while [ -x /proc/${PID} ]
                do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
    restart)
        "$0" stop
        "$0" start
        ;;
    *)
        echo "Please use start or stop or restart as first argument"
        ;;
esac

修改文件权限:

chmod +x /etc/init.d/redis

启动redis:

service redis start

设置开机启动:

chkconfig redis on

二、Redis常用命令

1.Redis命令描述

Redis命令用于redis服务上执行操作。
要在redis服务上执行命令需要一个redis客户端
Redis客户端在我们之前下载的redis的安装包中。

Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)等

1.1.Redis键key

Redis键命令用于管理redis的键。命令不区分大小写

DEL key		#该命令用于在key存在时删除key
DUMP key	#序列化给定key,并返回被序列化的值
EXISTS key	#检查给定key是否存在
EXPIRE key seconds	#为给定key设置过期时间(以秒计)
PEXPIRE key milliseconds	#设置key的过期时间以毫秒计
TTL key		#以秒为单位,返回给定key的剩余生存时间(TTL,time to live),返回-1代表永久有效,-2代表无效
PTTL key	#以毫秒为单位返回key的剩余的过期时间
PERSIST key	#移除key的过期时间,key将持久保持

#查找所有符合给定模式(pattern)的key
#keys 通配符	获取所有与pattern匹配的key,返回所有与该匹配
#通配符:
#		*	代表所有
#		?	代表一个字符
KEYS pattern
RENAME key newkey	#修改key的名称
MOVE key db	#将当前数据库的key移动到给定的数据库db当中
TYPE key	#返回key所存储的值的类型

1.2应用场景

EXPIRE key seconds

1、限时的优惠活动信息
2、网站数据缓存(对于一些需要定时更新的数据,例如:积分排行榜)
3、手机验证码
4、限制网站访客频率(例如:1分钟最多访问10次)

1.3Key的命名建议

redis单个key存入512M大小

1.key不要太长,尽量不要超过1024字节,这不仅消耗内存,而且会降低查找的效率;
2.key也不要太短,太短的话,key的可读性会降低;
3.在一个项目中,key最好使用统一的命名模式,例如user:123:password;

2.Redis数据类型

2.1.String

  • 简介

    string是redis最基本的类型,一个key对应一个value。

    string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象。

    string类型是Redis最基本的数据类型,一个键最大能存储512MB。

    二进制安全是指,在传输数据时,保证二进制数据的信息安全,也就是不被篡改、破译等,如果被攻击,能够及时监测出来

    二进制安全特点:

    1.编码、解码发生在客户端完成,执行效率高
    2.不需要频繁的编解码,不会出现乱码

  • String命令

    赋值命令:

    SET KEY_NAME VALUE

    Redis SET 命令用于设置给定key的值。如果key已经存储值,SET就覆写旧值,且无视类型

    SETNX key value //分布式锁 方案之一

    只有在key不存在时设置key的值。Setnx(SET if Not eXists)命令在指定的key不存在时,为key设置指定的值

    MSET key value [key value ...]

    同时设置一个或多个 key-value对

    取值语法:

    GET KEY_NAME

    Redis GET命令用于获取指定key的值。如果key不存在,返回nil。如果key储存的值不是字符串类型,返回一个错误。

    GETRANGE key start end

    用于获取存储在指定key中字符串的子字符串。字符串的截取范围由start和end两个偏移量决定(包括start和end在内)

    GETBIT key offset

    对key所储存的字符串值,获取指定偏移量上的位(bit)

    MGET key [key ...]

    获取所有(一个或多个)给定的key的值

    GETSET语法:GETSET KEY_NAME VALUE

    Getset命令用于设置指定key的值,并返回key的旧值,当key不存在时,返回nil

    STRLEN key

    返回key所储存的字符串的长度

    删除语法:

    DEL KEY_Name

    删除指定的key,如果存在,返回值数字类型

    自增/自减:

    INCR KEY_Name

    incr命令将key中储存的数字值增1。如果key不存在,那么key的值会先被初始化为0,然后再执行incr操作

    自增:INCRBY KEY_Name 增量值

    incrby命令将key中储存的数字加上指定的增量值

    自减:DECR KEY_NAME 或 DECRBY KEY_NAME 减值

    decr命令将key中储存的数字减1

    字符串拼接:APPEND KEY_NAME VALUE

    append命令为指定的key追加至末尾,如果不存在,为其赋值

  • 应用场景:

    1.String通常用于保存单个字符串或JSON字符串数据
    2.因String是二进制安全的,所以完全可以把一个图片的内容作为字符串来存储
    3.计数器(常规key-value缓存应用。常规计数:微博数,粉丝数)

    INCR等指令本身就具有原子操作的特性,所以我们完全可以利用redis的INCR、INCRBY、DECR、DECRBY等指令来实现原子计数的效果。假如,在某种场景下有3个客户端同时读取了mynum的值(值为2),然后对其同时进行了加1的操作,那么,最后mynum的值一定是5.

    不少网站都利用redis的这个特性来实现业务上的统计计数需求。

2.2 哈希(Hash)

String:字符串类型数据(JSON串)

Hash:一个JavaBean

  • 简介

    Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象.Redis中每个hash可以存储232-1键值对(40多亿)可以看成具有KEY和VALUE的MAP容器,该类型非常适合于存储值对象的信息,如:uname,upass,age等,该类型的数据仅占用很少的磁盘空间(相比于JSON)

  • Hash命令

    赋值语法

    HSET KEY FIELD VALUE //为指定的KEY,设定FIELD/VALUE

    HMSET KEY FIELD VALUE [FIELD1,VALUE1] ··· 同时将多个field-value(域-值)对设置到哈希表key中。

    ​ hmset users uname guo age 20 address "北京市"

    取值语法

    HGET KEY FIELD //获取存储在HASH中的值,根据FIELD得到VALUE

    HMGET key field [field1] //获取key所有给定字段的值

    HGETALL key //返回HASH表中所有的字段和值

    HKEYS key //获取所有哈希表中的字段

    HLEN key //获取哈希表中字段的数量

    删除语法

    HDEL KEY field1[field2] //删除一个或多个HASH表字段(在Redis中是不允许没有值的key存在的)

    其他语法

    HSETNX key field value

    只有在字段field不存在时,设置哈希表字段的值

    HINCRBY key field increment

    为哈希表key中的指定字段的整数值加上增量increment

    HINCRBYFLOAT key field increment

    为哈希表key中的指定字段的浮点数值加上增量increment

    HEXISTS key field //查看哈希表key中,指定的字段是否存在

  • 应用场景

    Hash的应用场景:(存储一个用户信息对象数据)

    1、常用于存储一个对象

    2、为什么不用string存储一个对象?

    hash是最接近关系数据库结构的数据类型,可以将数据库一条记录或程序中一个对象转换成hashmap存放在redis中。

    用户ID为查找的key,存储的value用户对象包含姓名,年龄,生日等信息,如果用普通的key/value结构来存储,主要有以下2中存储方式:

    ​ 第一种方式将用户ID作为查找key,把其他信息封装成一个对象以序列化的方式存储,这种方式的缺点是,增加了序列化/反序列化的开销,并且在需要修改其中一项信息时,需要把整个对象取回,并且修改操作需要对并发进行保护,引入CAS等复杂问题。转换对象问题修改值的问题

    ​ 第二种方式是这个用户信息对象有多少成员就存入多少个key-value对,用用户ID+对应属性的名称作为唯一标识来取得对应属性的值,虽然省去了序列化开销和并发问题,但是用户ID为重复存储,如果存在大量这样的数据,内存浪费还是非常可观的。

    总结:

    Redis提供的Hash很好的解决了这个问题,Redis的Hash实际是内部存储的Value为一个HashMap,并提供了直接存储这个Map成员的接口.

Hash:适合存储一个对象

Users(id,name,age,remark)

宋凯的个人信息存到Redis中

第一种方式

String类型: key:id value:JSON串

第二种方式

String类型:

key:users:id 1

​ users🆔name songkai

​ users🆔age 22

3.Java连接Redis

在官方网站列一些Java客户端访问,有Jedis/Redisson/Jredis/JDBC-Redis等,其中官方推荐使用Jedis和Redission。常用Jedis

开始在Java中使用Redis前,我们需要确保已经安装了redis服务及Java redis驱动,且你的机器上能正常使用Java。Java的安装配置可以参考我们的Java开发环境配置;接下来安装Java redis驱动

安装相应的JAR:

<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

连接测试:

    /**
     * Java端通过Jedis操作Redis服务器
     * @param args
     */
    public static void main(String[] args) {
        String host="47.112.149.156";
        int port=6379;
        Jedis jedis= new Jedis(host,port);
        //权限认证
        jedis.auth("carry");
        System.out.println(jedis.ping());
    }

3.1开启端口防火墙

配置防火墙:vim /etc/sysconfig/iptables

开放端口:

#redis-A INPUT -p tcp -m tcp --dport 6379 -j ACCEPT

重启防火墙:systemctl restart iptables

Redis中有哪些命令,Jedis就有哪些方法

    /**
     * Redis作用:为了减轻数据库(MySql)的访问压力
     * 需求:判断某KEY是否存在,如果存在,就从Redis中查询
     *      如果不存在,就查询数据库,且要将查询出的数据存入Redis
     */
    @Test
    public void t2(){
        //获取Redis连接
        Jedis jedis=new Jedis("47.112.149.156",6379);
        jedis.auth("carrry");

        String key="applicationName";   //key的名称
        if (jedis.exists(key)){
            String result=jedis.get(key);
            System.out.println("Redis数据库中查询得到:"+result);
        }else {
            //在数据库中查询
            String result="微信开发会议达人";
            jedis.set(key,result);
            System.out.println("MySql数据库查询得到:"+result);
        }

        jedis.close();
    }

3.2 Java Jedis连接池优化

    //jedis连接池
    public static void main(String[] args) {
        //1.连接池Redis Pool基本配置信息
        JedisPoolConfig poolConfig=new JedisPoolConfig();
        // 设置最大连接数
        poolConfig.setMaxTotal(5);
        // 设置空闲时的连接数
        poolConfig.setMaxIdle(1);
        // ......
        
        //2.设置连接池
        JedisPool pool=new JedisPool(poolConfig,"47.112.149.156",6379);
        // 在池中得到redis
        Jedis jedis=pool.getResource();
        jedis.auth("carry");

        System.out.println(jedis.ping());

        //3.关闭连接,返回连接池
        jedis.close();
    }
//Jedis工具类
public class RedisPoolUtil {

    private static final String HOST= PropertiesUtil.getProperty("ftp.server.ip");
    private static final int PORT=6379;
    private static final String PASSWORD="carry";

    private static JedisPool pool;

    static {
        //1.连接池Redis Pool基本配置信息
        JedisPoolConfig poolConfig=new JedisPoolConfig();
        poolConfig.setMaxTotal(5);// 最大连接数
        poolConfig.setMaxIdle(1);// 最大空闲数
        // ......
        //2.连接池
        pool=new JedisPool(poolConfig,HOST,PORT);
    }

    public static Jedis getJedis(){
        Jedis jedis=pool.getResource();
        jedis.auth(PASSWORD);
        return jedis;
    }
    //关闭连接功能
    public static void close(Jedis jedis){
        jedis.close();
    }
}

3.3 案例:Jedis操作Redis String和Hash

    /**
     * Redis作用:为了减轻数据库(MySql)的访问压力
     * 需求:判断某KEY是否存在,如果存在,就从Redis中查询
     *      如果不存在,就查询数据库,且要将查询出的数据存入Redis
     */
    @Test
    public void t2(){
        //获取Redis连接
        //Jedis jedis=JedisFactory.connectJedis();
        Jedis jedis= RedisPoolUtil.getJedis();

        String key="applicationName";   //key的名称
        if (jedis.exists(key)){
            String result=jedis.get(key);
            System.out.println("Redis数据库中查询得到:"+result);
        }else {
            //在数据库中查询
            String result="微信开发会议达人";
            jedis.set(key,result);
            System.out.println("MySql数据库查询得到:"+result);
        }

        jedis.close();
    }
    /**
     * Jedis完成对Hash类型操作
     * 需求:hash存储一个对象
     *  判断Redis中是否存在该key,如果存在,直接返回对应值
     *  如果不存在,查询数据库,(将查询的结果存入redis)并返回给用户
     */
    @Test
    public void t4(){
        //Users selectById(String id);
        Jedis jedis=RedisPoolUtil.getJedis();

        Integer id=20;
        String key= User.getKeyName()+id;   // user:20
        if (jedis.exists(key)){
            //redis中取出该对象
            Map<String,String> map=jedis.hgetAll(key);
            User user=new User();
            user.setId(Integer.parseInt(map.get("id")));
            user.setName(map.get("name"));
            user.setAge(Integer.parseInt(map.get("age")));
            user.setPassword(map.get("password"));
            user.setUsername(map.get("username"));
            System.out.println("Redis数据库中查询的对象:"+user);
        }else{
            // mysql数据库查询
            User user=new User();
            user.setId(id);
            user.setName("徐萌萌");
            user.setAge(23);
            user.setPassword("这是一个?号");
            user.setUsername("徐同学");

            Map<String,String> map= Maps.newHashMap();
            map.put("id",user.getId().toString());
            map.put("name",user.getName());
            map.put("age",user.getAge().toString());
            map.put("password",user.getPassword());
            map.put("username",user.getUsername());
            System.out.println("MYSQL查询的User对象是:"+user);

            jedis.hmset(key,map);
        }

        RedisPoolUtil.close(jedis);
    }

4.RedisTemplate

4.1 简介

Spring data提供了RedisTemplate模板

它封装了redis连接池管理的逻辑,业务代码无须关心获取,释放连接逻辑;spring redis同时支持了Jedis,Jredis,rjc客户端操作;

在RedisTemplate中提供了几个常用的接口方法的使用,分别是

// cache singleton objects (where possible)
private ValueOperation<K,V> valueOps;
private ListOperation<K,V> listOps;
private SetOperation<K,V> setOps;
private ZSetOpreation<K,V> zSetOps;

@Resource(name = "redisTemplate")
ListOperation<String,String> list;

4.2 Spring data使用RedisTemplate模板

1、pom.xml导入jar包

<!-- redis和spring整合 -->
<dependency>
      <groupId>redis.clients</groupId>
      <artifactId>jedis</artifactId>
      <version>2.9.0</version>
    </dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.6.RELEASE</version>
</dependency>

2、对应实体Bean进行序列化操作

public class User implements Serializable{
    /**
     *	序列化ID
     */
    private static final long serialVersionUID = 3531318246010599390L;
}

3、编写相应配置文件

利用RedisTemplate操作Redis

redis.properties:

redis.host=47.112.149.156
redis.port=6379
redis.password=carry
redis.maxIdle=5
redis.maxTotal=50
redis.maxWaitMillis=1000
redis.testOnBorrow=true

springRedis.xml:

  <!-- 加载配置文件 -->
  <context:property-placeholder location="classpath:redis.properties"/>
  <!-- 1.配置连接池信息 -->
  <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
    <!-- 最大连接数 -->
    <property name="maxTotal" value="${redis.maxTotal}"/>
    <!-- 最大空闲数maxIdle -->
    <property name="maxIdle" value="${redis.maxIdle}"/>
  </bean>
  <!-- 2.Spring整合Jedis(Redis) -->
  <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <!-- 指定服务器地址 -->
    <property name="hostName" value="${redis.host}"/>
    <!-- 指定服务器端口号 -->
    <property name="port" value="${redis.port}"/>
    <!-- 指定的密码 -->
    <property name="password" value="carry"/>

    <!-- 自定义连接池配置 -->
    <property name="poolConfig" ref="jedisPoolConfig"/>
  </bean>
  <!-- 3.RedisTemplate -->
  <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory"/>
  </bean>

4.3 使用RedisTemplate测试

通过某个key得到值

    /**
     * Redis String类型测试
     * 通过某个key得到值
     * 如果key在redis中不存在,到数据库中查询
     * 如果存在,就到redis中查询
     * @param key
     * @return
     */
    @Override
    public String getString(String key){
        ValueOperations<String,String> string=redisTemplate.opsForValue();

//        redisTemplate.expire("java1802",2, TimeUnit.HOURS);

        redisTemplate.opsForValue().set("java1803","这是一个测试数据",2, TimeUnit.HOURS);

        if (redisTemplate.hasKey(key)){
            // 在Redis中取出并返回
            System.out.println("在Redis中取出并返回");
            return string.get(key);
        }else {
            // 查询数据库
            String result="RedisTemplate模板练习";
            string.set(key,result);
            System.out.println("在MySQL数据库中取出并返回");
            return result;
        }
    }

启动Junit测试:

public class RedisTemplateTest {

    /**
     * 测试string RedisTemplate
     */
    @Test
    public void getString() {
        ClassPathXmlApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserService iuserService=ctx.getBean(IUserService.class);
        String key="applicationName";
        String result=iuserService.getString(key);
        System.out.println(result);
    }
}

4.4 Redis客户端工具

https://redisdesktop.com/download

4.4.1 查看Redis客户端

结果:key:java1803变成了\xac\xed\x00\x05t\x00\bjava1803

原因:把任何数据保存到redis中时,都需要进行序列化,默认使用JdkSerializationRedisSerializer进行数据序列化。

所有的key和value还有hashkey和hashvalue的原始字符前,都加了一串字符。

在springRedis.xml中进行序列化设置:

  <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
    <property name="connectionFactory" ref="jedisConnectionFactory"/>
    <!-- key进行序列化设置 默认JDK改为String -->
    <property name="keySerializer">
      <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    </property>
    <!-- value 默认JDK改为String -->
    <property name="valueSerializer">
      <bean class="org.springframework.data.redis.serializer.StringRedisSerializer"/>
    </property>
  </bean>

5.案例作业

5.1 限制登录功能

用户在2分钟内,仅允许输入错误密码5次。如果超过次数,限制其登录1小时。(要求每登录失败时,都要给相应提示)

登录错误次数key:	user:loginCount:fail:用户名
锁定限制登录key:	user:loginTime:lock:用户名
执行登录功能时:
	1.判断当前登录的用户是否被限制登录
	1.1如果没有被限制
		(执行登录功能)
		2.判断是否登录成功
		2.1 登录成功-->(清除输入密码错误次数信息)
		2.2 登录失败
			3 记录登录错误次数
			(判断Redis中的登录次数KEY是否存在)	user:loginCount:fail:用户名
			3.1如果不存在
				是第一次登录失败次数为1	user:loginCount:fail:用户名进行赋值,同时设置失效期
			3.2如果存在
				查询登录失败次数的key结果
				if(结果<4)
					user:loginCount:fail:+1
				else{//4
					限制登录KEY存在,同时设置限制登录时间锁定1小时。
				}
	1.2如果被限制
		做相应限制

注意:redisTemplate中在赋值时,不能直接赋值并设置失效期(会失效失败)

#清空redis数据库命令
flushdb

Redis支持五种数据类型:string(字符串)、hash(哈希)、list(列表)、set(集合)及zset(sorted set:有序集合)等

6.Redis数据类型

string: Java中的字符串

hash: Java中的一个bean对象

list: Java中的LinkedList类型

6.1 List类型

  • 简介

    Redis列表是简单的字符串列表,按照插入顺序排序,你可以添加一个元素到列表的头部(左边)或者尾部(右边),一个列表最多可以包括232-1个元素(4294967295,每个列表超过40亿个元素)

    →类似Java中的LinkedList

  • 命令

    赋值命令

    LPUSH key value1 [value2] // 将一个或多个值插入到列表头部(从左侧添加)

    RPUSH key value1 [value2] // 在列表中添加一个或多个值(从右侧添加)

    LPUSHX key value // 将一个值插入到已存在的列表头部。如果列表不存在,操作无效。

    RPUSHX key value // 一个值插入已存在的列表尾部(最右边)。如果列表不存在,操作无效。

    取值命令

    LLEN key // 获取列表长度

    LINDEX key index // 通过索引获取列表中的元素

    LRANGE key start stop // 获取列表指定范围的元素

    描述:返回列表中指定区间内的元素,区间以偏移量START和END指定。其中0表示列表的第一个元素,1表示列表的第二个元素,意思类推。也可以使用负数下标,以-1表示列表的最后一个元素,-2表示列表的倒数第二个元素,以此类推。

    删除语法

    LPOP key // 移除并获取列表的第一个元素(从左侧删除)

    RPOP key // 移除列表的最后一个元素,返回值为移除的元素(从右侧删除)

    BLPOP key1 [key2] timeout // 移除并获取列表的第一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

    实例

    127.0.0.1:6379>BLPOP list1 100
    

    在以上实例中,操作会被阻塞,如果指定的列表 key list1 存在数据则会返回第一个元素,否则在等待 100秒后会返回nil。

    BRPOP key1 [key2] timeout // 移除并获取列表的最后一个元素,如果列表没有元素会阻塞列表直到等待超时或发现可弹出元素为止。

    LTRIM key start stop // 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都被删除。

    修改语法

    LSET key index value // 通过索引设置列表元素的值

    LINSERT key BEFORE|AFTER world value // 在列表的元素前或者后插入元素

    描述:将值value插入到列表key当中,位于值world之前或之后

    高级语法

    RPOPLPUSH source destination // 移除列表的最后一个元素,并将该元素添加到另一个列表并返回

    示例描述

    RPOPLPUSH a1 a2 // a1的最后元素移到a2的左侧

    RPOPLPUSH a1 a1 // 循环列表,将最后元素移到最左侧

  • 应用场景

    项目应用于:1、对数据量大的集合数据删减 2、任务队列

1、对数据量大的集合数据删减

列表数据显示、关注列表、粉丝列表、留言评价等...分页、热点新闻(Top5)等
利用LRANGE还可以很方便的实现分页的功能,在博客系统中,每篇博文的评论也可以存入一个单独的list中

2、任务队列

(list通常用来实现一个消息队列,而且可以确保先后顺序,不必像MySQL那样还需要通过ORDER BY来进行排序)

任务队列介绍(生产者和消费者模式):

在处理Web客户端发送的命令请求时,某些操作的执行时间可能会比我们预期的更长一些,通过将待执行任务的相关信息放入队列里面,并在之后对队列进行处理,用户可以推迟执行那些需要一段时间才能完成的操作,这种将工作交给任务处理器来执行的做法称为任务队列(task)。

RPOPLPUSH source destination

移除列表的最后一个元素,并将该元素添加到另一个列表并返回

示例描述:

常用案例:订单系统的下单流程、用户系统登录注册短信等

代码演示:

订单系统的下单流程:
完成付款后,淘宝后台帮我们生成一个队列 订单ID

1.已揽件
2.湖州安吉塘浦镇营业点-->嘉兴快运中转场
3.嘉兴快运中转场-->长沙快运中转场
4.长沙快运中转场-->娄底万宝集散点
5.娄底万宝集散点-->娄底冷水江市外环路营业点
6.派送中
7.已签收
hash:	类似于JavaBean
	应用场景:用于存储一个Java对象
		access_token(expire_time access_token)
		用于存储:session
list:	类似于LinkedList
	应用场景:用于存储一组数据
		热点数据(数据量较大的数据做删除/修改功能)
		任务队列、RPOPLPUSH 集合A 集合B
set:	类似于Java中的Hashtable集合
	Redis的Set是String类型的无序集合。
	集合成员是唯一的,这就意味着集合中不能出现重复的数据。

zset

6.2 Set类型

  • 简介

    Redis的Set是String类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。

    Redis中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。

    集合中最大的成员数为232-1(4294967295,每个集合可存储40多亿个成员)。

    类似于Java中的Hashtable集合

    redis的集合对象set的底层存储结构特别神奇,底层使用了intset和hashtable两种数据结构存储的,intset我们可以理解为数组,hashtable就是普通的哈希表(key为set的值,value为null)。

    intset内部其实是一个数组(int8_t coentents[]数组),而且存储数据的时候是有序的,因为在查找数据的时候是通过二分查找来实现的。

  • 命令

    赋值语法:

    SADD key member1 [member2] //向集合添加一个或多个成员

    取值语法:

    SCARD key //获取集合的成员数

    SMEMBERS key //返回集合中的所有成员

    SISMEMBER key member //判断member元素是否是集合key的成员(开发中:验证是否存在判断)

    SRANDMEMBER key [count] //返回集合中一个或多个随机数(抽奖功能)

    删除语法:

    SREM key member1 [member2] //移除集合中一个或多个成员

    SPOP key [count] //移除并返回集合中的一个随机元素

    SMOVE source destination memeber //将member元素从source集合移动到destination集合

    差集语法:

    SDIFF key1 [key2] //返回给定所有集合的差集(左侧)

    SDIFFSTORE destination key1 [key2] //返回给定所有集合的差集并存储在destination中

    交集语法:

    SINTER key1 [key2] //返回给定所有集合的交集(共有数据)

    SINTERSTORE destination key1 [key2] //返回给定所有集合的交集并存储在destination中

    并集语法:

    SUNION key1 [key2] //返回所有给定集合的并集

    SUNIONSTORE destination key1 [key2] //返回给定所有集合的并集并存储在destination中

  • 应用场景

    常应用于:对两个集合间的数据[计算]进行交集、并集、差集运算

    1、以非常方便得实现如共同关注、共同喜好、二度好友等功能。对上面的所有集合操作,你还可以使用不同的命令选择将结果返回给客户端还是存储到一个新的集合中。

    2、利用唯一性,可以统计访问网站的所有独立IP

6.3 有序集合ZSet(sorted set)

  • 简介

    1、Redis有序集合和集合一样也是String类型元素的集合,且不允许重复的成员。

    2、不同的是每个元素都会关联一个double类型的分数。Redis正是通过分数来为集合中的成员进行从小到大的排序。

    3、有序集合的成员是唯一的,但分数(score)却可以重复

    4、集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是O(1)。集合中最大的成员数为232-1(4294967295,每个集合可存储40多亿个成员)。

    Redis的ZSet是有序、且不重复

    (很多时候,我们都将Redis中的有序集合叫做zsets,这是因为在Redis中,有序集合相关的操作指令都是以z开头的)

  • 命令

    赋值命令:

    ZADD key score1 member1 [score member2] //向有序集合添加一个或多个成员,或者更新已存在成员的分数

    取值语法:

    ZCARD key //获取有序集合的成员数

    ZCOUNT key min max //计算有序集合中指定区间分数的成员数

    ZRANK key member //返回有序集合中指定成员的索引

    ZRANGE key start top [WITHSCORES] //通过索引区间返回有序集合指定区间内的成员(低到高)

    ZREVRANGE key start top [WITHSCORES] //返回有序集合中指定区间内的成员,通过索引,分数从高到低

    删除语法:

    del key //移除集合

    ZREM key member [member...] //移除有序集合中的一个或多个成员

    ZREMRANGEBYRANK key start top //移除有序集合中给定的排名区间的所有成员(第一名是0)

    ZREMRANGEBYSCORE key min max //移除有序集合中给定的分数区间的所有成员

  • 应用场景

    常应用于:排行榜

    1.比如twitter的public timeline可以以发表时间作为score来存储,这样获取时就是自动按时间排好序的。
    2.比如一个存储全班同学成绩的Scored Set,其集合value可以是同学的学号,而score就可以是其考试得分,这样在数据插入集合的时候,就已经进行了天然的排序。
    3.还可以用Scored Set来做带权重的队列,比如普通消息的score为1,重要消息的score为2,然后工作线程可以选择按score的倒序来获取工作任务。让重要的任务优先执行。

7.Redis发布订阅

  • 简介

    Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收消息。

    Redis客户端可以订阅任意数量的频道。

  • 示例

    下图展示了频道channel1

    以及订阅这个频道的三个客户端——client2、client5和client1之间的关系:

    当有新消息通过PUBLISH命令发送给频道channel1时,这个消息就会被发送给订阅它的三个客户端:

  • 命令

    订阅频道:

    SUBSCRIBE channel [channel...] //订阅给定的一个或多个频道的信息

    PSUBSCRIBE channel [channel...] //订阅一个或多个符合给定模式的频道

    发布频道:

    PUBLISH channel message //将消息发送到指定的频道

    退订频道:

    UNSUBSCRIBE [channel [channel...]] //只退订给定的频道

    PUNSUBSCRIBE [pattern [pattern...]] //退订所有给定模式的频道

  • 应用场景

    这一功能最明显的用法就是构建实时消息系统,比如普通的即时聊天群聊等功能

    1.在一个博客网站中,有100个粉丝订阅了你,当你发布新文章,就可以推送消息给粉丝们。
    2.微信公众号模式

8.Redis多数据库

Redis下,数据库是由一个整数索引标识,而不是由一个数据库名称。默认情况下,一个客户端连接到数据0。

Redis配置文件中下面的参数来控制数据库总数:

database 16 //(从0开始1 2 3 ... 15)

select 数据库 //数据库的切换

移动数据(将当前key移动到另个库)

move key 数据库

数据库清空:

flushdb //清除当前数据库的所有key

flushall //清除整个Redis的数据库所有key

9.Redis事务

Redis事务可以一次执行多个命令,(按顺序地串行化执行,执行中不会被其它命令插入,不许加塞)

  • 简介

    Redis事务可以一次执行多个命令(允许在一次单独的步骤中执行一组命令),并且带有以下两个重要的保证:

    批量操作在发送EXEC命令前被放入队列缓存。

    收到EXEC命令后进入事务执行,事务中任意命令执行失败,其余的命令依然被执行。在事务执行过程,其他客户端提交的命令请求不会插入到事务执行命令序列中。

    1、Redis会将一个事务中的所有命令序列化,然后按顺序执行

    2、执行中不会被其它命令插入,不许出现加塞行为

    一个事务从开始到执行会经历一下三个阶段:

    开始事务。

    命令入列。

    执行事务。

    事务的错误处理:

    队列中的某个命令出现了报告错误,执行时整个的所有队列都会被取消。

  • 命令

    Redis事务命令

    下表列出了redis事务的相关命令:

  • 示例1MULTIEXEC

    转账功能,A向B账号转账50元

    一个事务的例子,它先以MULTI开始一个事务,然后将多个命令入列到事务中,最后由EXEC命令触发事务

    1.输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行
    2.直到输入Exec后,Redis会将之前的命令队列中的命令依次执行

  • 示例2DISCARD放弃队列运行

    1.输入Multi命令开始,输入的命令都会依次进入命令队列中,但不会执行

    2.直到输入Exec后,Redis会将之前的命令队列中的命令依次执行

    3.命令队列的过程中可以通过discard来放弃队列运行

  • 示例3事务的错误处理

    事务的错误处理:

    如果执行的某个命令报出了错误,则只有报错的命令不会被执行,而其它的命令都会执行,不会回滚。

  • 示例4事务的错误处理

    事务的错误处理:

    队列中的某个命令出现了报告错误,执行时整个的所有队列都会被取消。

  • 示例5事务的WATCH

    WATCH key [key ...]

    监视一个(或多个)key,如果在事务执行之前这个(或这些)key被其他命令所改动,那么事务将被打断。

    需求:某一个账户在一事务内进行操作,在提交事务前,另一个进程对该账户进行操作。

  • 示例6 UNWATCH

    Redis Unwatch命令用户取消WATCH命令对所有key的监视。

    如果在执行WATCH命令之后,EXEC命令或DISCARD命令先被执行的话,那就不需要再执行UNWATCH了

  • 应用场景

    一组命令必须同时都执行,或者都不执行。

    我们想要保证一组命令在执行的过程中不被其它命令插入。

    商品秒杀(活动)。

    转账活动

10.Redis数据淘汰策略redis.conf

Redis官方给的警告,当内存不足是,Redis会根据配置的缓存策略淘汰部分keys,以保证写入成功,当无淘汰策略时或没有找到合适淘汰的key时,Redis直接返回out of memory错误。

最大缓存配置

在redis中,允许用户设置最大使用内存大小

maxmemory 512G

redis提供6中数据淘汰策略:

  • volatile-lru:从已设置过期时间的数据集中挑选最近最少使用的数据淘汰
  • volatile-lfu:从已设置过期的keys中,删除一段时间内使用次数最少使用的
  • volatile-ttl:从已设置过期时间的数据集中挑选最近将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集中随机选择数据淘汰
  • allkeys-lru:从数据集中挑选最近最少使用的数据淘汰
  • allkeys-lfu:从所有keys中,删除一段时间内使用次数最少使用的
  • allkeys-random:从数据集中随机选择数据淘汰
  • no-enviction(驱逐):禁止驱逐数据(不采用任何淘汰策略。默认即为此配置),针对写操作,返回错误信息

建议:了解了Redis的淘汰策略之后,在平时使用时应尽量主动设置/更新key的expire时间,主动剔除不活跃的旧数据,有助于提升查询性能

11.Redis持久化

  • 简介

    数据存放于:

    内存:高效、断电(关机)内存数据会丢失

    硬盘:读写速度慢于内存,断电数据不会丢失

  • RDB

    RDB:是redis的默认持久化机制。 RDB相当于照快照,保存的是一种状态。

    几十G数据—>几KB快照

    快照是默认的持久化方式。这种方式就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。

    优点:

    快照保存数据极快、还原数据极快

    适用于灾难备份

    缺点:小内存机器不适合使用,RDB机制符合要求就会照快照

    快照条件:

    1、服务器正常关闭时 ./bin/redis-cli shutdown

    2、key满足一定条件,会进行快照

    vim redis.conf	#搜索save
    :/save
    
    save 900 1	//每900秒(15分钟)至少1个key发生变化,产生快照
    save 300 10	//每300秒(5分钟)至少10个key发生变化,产生快照
    save 60 10000	//每60秒(1分钟)至少10000个key发生变化,产生快照
    
  • AOF

    由于快照方式是在一定间隔时间做一次的,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改。如果应用要求不能丢失任何修改的话,可以采用AOF持久化方式。

    Append-only file:aof 比快照方式有更好的持久化性,是由于在使用AOF持久化时,redis会将每一个收到的写命令都通过write函数追加到文件中(默认是appendonly:aof)。当redis重启时会通过执行文件中保存的写命令来在内存中重建整个数据库的内容。

    有三种方式如下(默认是:每秒fsync一次)

    • appendonly yes //启用aof持久化方式
    • #appendfsync always //收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
    • appendfsync everysec //每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
    • #appendfsync no //完全依赖OS,性能最好,持久化没保证

    产生的问题:

    aof的方式也同时带来了另一个问题。持久化文件会变得越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条是多余的。

12.redis缓存与数据库一致性

12.1 解决方案

  • 1.实时同步

    对强一致性要求比较高的,应采用实时同步方案,即查询缓存查询不到再从DB查询,保存到缓存;更新缓存时,先更新数据库,再将缓存的设置过期(建议不要去更新缓存内容,直接设置缓存过期)。

    @Cacheable:查询时使用,注意Long类型需转换为String类型,否则会抛异常

    @CachePut:更新时使用,使用此注解,一定会从DB上查询数据

    @CacheEvict:删除时使用

    @Caching:组合用法

  • 2.异步队列

    对于并发程度较高的,可采用异步队列的方式同步,可采用kafka等消息中间件处理生产和消费。

  • 3.使用阿里的同步工具canal

    canal实现方式是模拟mysql slave和master的同步机制,监控DB bitlog的日志更新来触发缓存的更新,此种方法可以解放程序员双手,减少工作量,但在使用时有些局限性。

    1.master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events;可以通过show binlog events进行查看);
    2.slave将master的binary log events拷贝到它的中继日志(relay log);
    3.slave重做中继日志中的事件,将改变反映它自己的数据。

    canal的工作原理:

    1.canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议
    2.mysql master收到dump请求,开始推送binary log给slave(也就是canal)
    3.canal解析binary log对象(原始为byte流)

  • 4.采用UDF自定义函数的方式

    面对mysql的API进行编程,利用触发器进行缓存同步,但UDF主要是C/C++语言实现,学习成本高。

    数据库中触发器:

    insert into user()

    create tigger{
    }

  • 5.Lua脚本

    Redis脚本使用Lua解释器来执行脚本。Redis2.6版本通过内嵌支持Lua环境。执行脚本的常用命令为EVAL。

13.总结

1.穿透

缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库中去查询,造成缓存穿透。

解决办法:持久层查询不到就缓存空结果,查询时先判断缓存中是否exists(key),如果有直接返回空,没有则查询后返回,

注意insert时需要清理查询的key,否则即便DB中有值也查询不到(当然也可以设置空缓存的过期时间)

2.雪崩

雪崩:缓存大量失效的时候,引发大量查询数据库。

解决办法:
①用锁/分布式锁或者队列串行访问
②缓存失效时间均匀分布

3.热点key

热点key:某个key访问非常频繁,当key失效的时候有大量线程来构建缓存,导致负载增加,系统崩溃。

解决办法:

①使用锁,单机用synchronized,lock等,分布式用分布式锁

②缓存过期时间不设置,而是设置在key对应的value里。如果检测到存的时间超过过期时间则异步更新缓存。

③在value设置一个比过期时间t0小的过期时间t1,当t1过期的时候,延长t1并做更新缓存操作。

④设置标签缓存,标签缓存设置过期时间,标签缓存过期后,需异步地更新实际缓存

三、Redis高级

1.可能的问题

一般来说,要将Redis运用于工程项目中,只使用一台Redis是万万不能的,原因如下:

1.从结构上,单个Redis服务器会发生单点故障,并且一台服务器需要处理所有的请求负载,压力较大;(容错性)
2.从容量上,单个Redis服务器内存容量有限;就算一台Redis服务器内存容量为256G,也不能将所有内容用作Redis存储内存,一般来说,单台Redis最大使用内存不应该超过20G。

2.基本概述

2.1 高可用

“高可用性”(High Availability)通常来描述一个系统经过专门的设计,从而减少停工时间,而保持其服务的高度可用性。

2.2 高并发

高并发(High Concurrency)是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时并行处理很多请求

高并发相关常用的一些指标有响应时间(Response Time),吞吐量(Troughput),每秒查询率QPS(Query Per Second),并发用户数等。

响应时间:系统对请求做出响应的时间。例如系统处理一个HTTP请求需要200ms,这个200ms就是系统的响应时间。

吞吐量:单位时间内处理的请求数量

QPS:每秒响应请求数。在互联网领域,这个指标和吞吐量区分的没有这么明显。

并发用户数:同时承载正常使用系统功能的用户数量。例如一个即时通讯系统,同时在线量一定程度上代表了系统的并发用户数。

提升系统的并发能力

提高系统并发能力的方式,方法论上主要有两种:垂直扩展(Scale Up)与水平扩展(Scale Out)

  • 1.垂直扩展

    垂直扩展:提升单机处理能力。垂直扩展的方式又有两种

    (1)增强单机硬件性能,例如:增加CPU核数如32核,升级更好的网卡如万兆,升级更好的硬盘如SSD,扩展硬盘容量如2T,扩充系统内存如128G;

    (2)提升单机架构性能,例如:使用Cache来减少IO次数,使用异步来增加单服务吞吐量,使用无锁数据结构来减少响应时间;

    在互联网业务发展非常迅猛的早期,如果预算不是问题,强烈建议使用“增强单机硬件性能”的方式提升系统并发能力,因为这个阶段,公司的战略往往是发展业务抢时间,而“增强单机硬件性能”往往是最快的方法。

    总结:不管是提升单机硬件性能,还是提升单机架构性能,都有一个致命的不足:单机性能总是有极限的。所以互联网分布式架构设计高并发终极解决方案还是水平扩展。

  • 2.水平扩展

    水平扩展:只要增加服务器数量,就能线性扩展系统性能。水平扩展对系统架构设计是有要求的,难点在于:如何在架构各层进行可水平扩展的设计。

3.Redis主从复制

  • 简介

    应用场景:电子商务网站上的商品,一般都是一次上传,无数次浏览的,说专业点也就是“多读少写”。

    主从复制

    一个Redis服务可以有多个该服务的复制品,这个Redis服务称为Master,其他复制称为Slaves

    如图中所示,我们将一台Redis服务器作主库(Master),其他三台作为从库(Slave),主库只负责写数据,每次有数据更新都将更新的数据同步到它所有的从库,而从库只负责读数据,这样一来,就有了两个好处:

    1.读写分离,不仅可以提高服务器的负载能力,并且可以根据读请求的规模自由增加或者减少从库的数量。
    2.数据被复制成了好几份,就算有一台机器出现故障,也可以使用其他机器的数据快速恢复。

    需要注意的是:在Redis主从模式中,一台主库可以拥有多个从库,但是一个从库只能隶属于一个主库。

  • Redis主从复制配置

    在Redis中,要实现主从复制架构非常简单,只需要在从数据库的配置文件中加上如下命令即可:

    1、主数据库不要任务配置,创建一个从数据库:

    -- port 6380	//从服务的端口号
    --slaveof 127.0.0.1 6379	//指定主服务器
    

    ./bin/redis-server ./redis.conf --port 6380 --salveof 127.0.0.1 6379

    加上slaveof参数启动另一个Redis实例作为从库,并且监听6380端口

    2、登录到从服务器客户端:

    ./bin/redis-cli -p 6380 -a chester

    变回主: slaveof on one //不是任何从

    变回从: slaveof ip地址 端口号

3.1.哨兵模式

http://c.biancheng.net/redis/sentinel-model.html

4.Redis Cluster集群

  • 简介

    • 为什么使用redis-cluster?
    • 为了在大流量访问下提供稳定的业务,集群化是存储的必然形式
    • 未来的发展趋势肯定是云计算和大数据的紧密结合
    • 只有分布式架构能满足要求

    Redis集群搭建的方式有多种,但从redis3.0之后版本支持redis-cluster集群,至少需要3(Master)+3(Slave)才能建立集群。Redis-Cluster采用无中心结构,每个节点保存数据和整个集群状态,每个节点和其他所有节点连接。其redis-cluster架构图如右上侧:

  • Redis Cluster集群特点

    1、所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。

    2、节点的fail是通过集群中超过半数的节点检测失效时才生效。

    3、客户端与redis节点直连,不需要中间proxy层,客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可。

    4、redis-cluster把所有的物理节点映射到[0-16383]slot上(不一定是平均分配),cluster复制维护。

    5、Redis集群预分好16384个哈希槽,当需要在Redis集群中放置一个key-value时,redis先对key使用crc16算法算出一个结果,然后把结果对16384求余数,这样每个key都会对应一个编号在0-16384之间的哈希值,redis会根据节点数量大致均等的将哈希槽映射到不同的节点

  • Redis Cluster容错

    容错性,是指软件检测应用程序所运行的软件或硬件中发生的错误并从错误中恢复的能力,通常可以从系统的可靠性、可用性、可测性等几个方面来衡量。

    redis-cluster投票:容错

    1.投票过程是集群中所有master参与,如果半数以上master节点与master节点通信超时(cluster-node-timeout),认为当前mater节点挂掉。

    2.什么时候整个集群不可用(cluster_state:fail)?

    如果集群任意master挂掉,且当前master没有slave,集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完整时进入fail状态。如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。

  • redis-cluster节点分配

    官方推荐)三个主节点分别是:A,B,C三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽(hash slot)的方式来分配16384个slot的话,它们三个节点分别承担的slot区间是:

    节点A覆盖0-5460;

    节点B覆盖5461-10922;

    节点C覆盖1093-16383;

    新增一个节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上:

    节点A覆盖1365-5460;

    节点B覆盖6827-10922;

    节点C覆盖12288-16383;

    节点D覆盖0-1364,5461-6826,10923-12287。

5.Redis Cluster集群搭建

  • 简介

    集群中至少应该有奇数个节点,所以搭建集群最少需要3台主机。同时每个节点至少有一个备份节点,所以下面最少需要创建使用6台机器,才能完成Redis Cluster集群(主节点、备份节点由redis-cluster集群确定)

    真集群:准备6台服务器

    192.168.1.110:6379

    192.168.1.111:6379

    192.168.1.112:6379

    假集群:一台服务器存在6个redis服务

    192.168.1.110: 6379 6380 6381 6382 ...

  • 搭建流程

    1、创建Redis节点安装目录

    mkdir /usr/local/redis_cluster //指定目录下创建redis_cluster

    2、在redis_cluster目录下,创建7001-7006个文件夹

    mkdir 7001 7002 7003 7004 7005 7006

    3、并将redis-conf分别拷贝只7001-7006文件夹下

    cp /usr/local/redis/redis.conf ./7001

    4、分别修改如下配置文件,修改如下内容

    同时protected-mode是为了禁止公网访问redis cache,加强redis安全的。

    它启动的条件,有两个:

    1)没有bind IP

    2)没有设置访问密码

    由于Linux上的redis处于安全保护模式,这就让你无法从虚拟机外部去轻松建立连接。

    如果外部访问:redis.conf中设置保护模式为 protected-mode:no

    #bind 127.0.0.1	//指定服务器IP地址
    port 7000	//指定端口号,必须修改,以此来区分Redis实例
    daemonize yes	//后台运行
    pidfile /var/run/redis-7000.pid	//修改pid进程文件名,以端口命名
    #设置redis集群密码masterauth chester和requirepass chester,所有节点的密码都必须一致
    masterauth chester
    requirepass chester
    #logfile /root/application/program/redis-cluster/7000/redis.log	//修改日志文件名称,以端口号为目录来区分
    #dir /root/application/program/redis-cluster/7000/	//修改数据文件存放地址,以端口号为目录来区分
    cluster-enabled yes	//启动集群
    cluster-config-file nodes-7000.conf	//配置每个节点的配置文件,同样以端口号为名称
    cluster-node-timeout 15000	//配置集群节点的超时时间,可改可不改
    appendonly yes	//启动AOF增量持久化策略
    #appendfsync always	//发生改变就记录日志
    
    sed -i "s/7001/7002/g" 文件
    

    5、启动各个redis节点:

    #将/home/chester/download/redis5.0/下src文件拷贝</到各个redis 7001-7006目录下。
    cp -r /home/chester/download/redis5.0/src /usr/local/redis_cluster/7001	//进行拷贝,依次复制7001-7006
    
    #启动各个Redis节点:
    cd /usr/local/redis_cluster/	//进入redis集群配置文件目录下
    ./7001/src/redis-server ./7001/redis.conf	//依次启动7001-7006各节点服务
    

    6、检查Redis启动情况

    ps -ef|grep -i redis

  • 创建集群

    Redis官方提供了redis-trib.rb这个工具,就在解压目录的src目录中。(为了方便操作)将其文件复制到/usr/local/bin目录下,可直接访问此命令

    cp /home/chester/download/redis5.0/src/redis-trib.rb /usr/local/bin/

    可以直接在命令行中执行: ip:port格式

    若是Redis5.0以上,则直接使用如下命令,后面步骤直接省略:

    redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1 -a chester
    

    若是Redis5.0以下,按如下步骤操作:

    redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006

    如果出现此错误:

    这个工具是用ruby实现的,所以需要安装ruby。

  • 安装ruby

    yum -y install ruby ruby-level rubygems rpm-build
    
    #执行rvm install 2.6.3前修改ruby镜像(改为国内的镜像,国外的实在是太慢了)
    gem update --system	#更新rubygems到2.6.x以上
    gem -v 	#查看rubygems版本
    gem sources -l   #查看当前使用的源地址
    gem sources --add https://gems.ruby-china.com/ --remove https://rubygems.org/	#添加国内rubygems镜像
    gem sources -u    #更新源的缓存
    
    gem install redis
    

    如上错误原因:CentOS默认支持ruby到2.0.0,可gem安装redis需要最低版本是2.3.0

    解决办法是:先安装rvm,再把ruby版本升级

  • 安装RVM

    gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3 7D2BAF1CF37B13E2069D6956105BD0E739499BDB	--获取密钥
    curl -sSL https://get.rvm.io | bash -s stable  --安装rvm
    find / -name rvm -print	--打印安装信息
    source /etc/profile.d/rvm.sh	--更新配置文件
    rvm list known	--查看rvm库中所有ruby可安装版本
    

  • 安装一个ruby版本

    #修改RVM的Ruby安装源到Ruby China的Ruby镜像服务器,提高ruby安装速度
    echo "ruby_url=https://cache.ruby-china.com/pub/ruby" > /usr/local/rvm/user/db
    
    rvm install 2.6.3	--安装ruby2.6.3
    #使用一个ruby版本
    rvm use 2.6.3
    #设置默认ruby版本
    rvm use 2.6.3 --default
    #查看ruby版本
    ruby -v
    #安装redis
    gem install redis
    
  • Redis Cluster集群搭建

    ruby安装完成后,再次执行此命令:

    可以直接在命令行中执行: ip:port格式

    redis-trib.rb create --replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006

    Redis5.0会有如下警告:

    Redis5.0以上使用redis-cli作为创建集群的命令,使用c语言实现,不再使用ruby语言,可以直接使用如下命令:

    redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 --cluster-replicas 1 -a chester
    

6.Redis Cluster集群验证

Redis集群测试:

为方便测试和开发访问,可将redis-cli客户端命令放到/usr/local/bin目录下

cp /home/chester/download/redis5.0/src/redis-cli /usr/local/bin

在某台机器上(或)连接集群的7001端口的节点:

redis-cli -h 127.0.0.1 -c -p 7001 -a chester

加参数-c 可连接到集群,因为上面redis.conf注释掉了bind ip地址,所以-h 参数可以忽略。

(在该节点下添加对应key数据)

启动另一个集群中的客户节点:例如:7005

进行读取命令。

redis cluster在设计的时候,就考虑到了去中心化,去中间件,也就是说,集群中的每个节点都是平等的关系,都是对等的,每个节点都保存各自的数据和整个集群的状态。每个节点都和其他所有节点连接,而且这些连接保持活跃,这样就保证了我们只需要连接集群中的任意一个节点,就可以获取到其他节点的数据

  • 基本命令

    info replication通过Cluster Nodes命令和Cluster Info命令来看看集群效果

  • 测试集群数据

    输入命令cluster nodes,查看集群所有信息:

    每个Redis的节点都有一个ID值,此ID将被此特定redis实例永久使用,以便实例在集群上下文中具有唯一的名称。每个节点都会记住使用此ID的每个其他节点,而不是通过IP或端口。IP地址和端口可能发生变化,但唯一的节点标识符在节点的整个生命周期内都不会改变。我们简单地这个标识符为节点ID

    测试数据:

7.代码示例

  • 开启端口权限

    依次开放端口7001、7002...7006(如下命令只针对CentOS7以上)

    查看已经开发的端口:firewall-cmd --list-ports

    开启端口:

    firewall-cmd --zone=public --add-port=7001/tcp --permanent

    重启防火墙:

    firewall-cmd --reload #重启

      public void testRedisCluster(){
          // 第一步,使用JedisCluster对象,需要一个Set<HostAndPost>参数,Redis节点的列表
          Set<HostAndPort> nodes=new HashSet<HostAndPort>();
          nodes.add(new HostAndPort("121.37.15.219",7001));
          nodes.add(new HostAndPort("121.37.15.219",7002));
          nodes.add(new HostAndPort("121.37.15.219",7003));
          nodes.add(new HostAndPort("121.37.15.219",7004));
          nodes.add(new HostAndPort("121.37.15.219",7005));
          nodes.add(new HostAndPort("121.37.15.219",7006));
          // 第二步,直接使用JedisCluster对象操作redis,在系统中单例存在
          JedisCluster jedisCluster=new JedisCluster(nodes);
          jedisCluster.set("nawxs","你爱我像谁");
          String result=jedisCluster.get("nawxs");
          // 第三步,打印结果
          System.out.println(result);
      }
    
posted @ 2021-10-05 14:13  冰枫丶  阅读(60)  评论(0编辑  收藏  举报