Redis集群的搭建和Redis的使用

一.Redis的简介和用途

Redis五种基本数据类型

简介
Redis是一个高性能的key-value数据库。
Redis 与其他 key - value 缓存产品有以下三个特点:

  • Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
  • Redis不仅仅支持简单的key-value类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
  • Redis支持数据的备份,即master-slave模式的数据备份。

五种用途

1.全页面缓存

整页缓存。如果你正在使用服务器端呈现的内容,则不需要为每个单独的请求重新渲染每个页面。使用如Redis这样的缓存,你可以缓存经常请求的内容,从而大大减少请求最多的页面的延迟,并且大多数框架针对Redis缓存页面都有hooks。

// 设置全页面缓存1分钟
 SET key “…” EX 60
 // 获取全页面
 GET key
2.顺序排列

由于Redis在内存中Set数据结构可以非常快速和高效进行递增和递减,性能远远高于SQL查询。比较这与Redis的排序集相结合意味着你可以以毫秒为单位抓取列表中评分最高的项目,而且实现起来非常容易。

// 向排序集合中添加值
 ZADD sortedSet 1 “one”
 // 从排序集合中获取所有值
 ZRANGE sortedSet 0 -1
 // 从排序集合中获取所有值与权重
 ZRANGE sortedSet 0 -1 WITHSCORES
3.会话Session存储

我所见的Redis最常见的用途是会话存储。与其他会话存储(如Memcache)不同,Redis可以保留数据,以便在缓存停止的情况下,在重新启动时,所有数据仍然存在。即便不是需要严格持续的任务,此功能仍可以为你的用户省去大量的麻烦。没有人会乐于见到他们的会话被无缘无故随机删掉。

// 设置一分钟过期的session
 SET randomHash “{userId}” EX 60
 // 获取用户ID
 GET randomHash
4.队列

使用Redis可以做的一个不太常见,但非常有用的事情是排队。无论是电子邮件队列还是其他应用程序使用的数据,你都可以在Redis中创建一个高效的队列。任何熟悉堆栈以及会push和pop项目的开发人员都可以轻松自然地使用此功能。

// 添加消息
 HSET messages 
 ZADD due <due_timestamp> 
 // 接收消息
 ZRANGEBYSCORE due -inf <current_timestamp> LIMIT 0 1
 HGET messages <message_id>
 // 删除消息
 ZREM due <message_id>
 HDEL messages <message_id>
5.pub/sub

Redis在真实世界的最终用法即我将在这篇文章中提出的pub / sub。这是Redis内置的最强大的功能之一;得到的可能是无限的。你可以创建一个实时聊天系统,在社交网络上触发好友请求的通知等等。这个功能是Redis提供的最被低估的功能之一,但功能非常强大,而且使用简单。

// 向通道中推送消息
 PUBLISH channel message
 // 从通道中获取消息
 SUBSCRIBE channel

二.搭建Redis集群

注意:配置集群前需要先安装ruby,并且要求ruby语言版本大于2.0.0,集群至少需要6个节点

1.获取redis源码包

Redis可以存集合 redis集群怎么存数据_redis


解压进入目录并安装make && make install

Redis可以存集合 redis集群怎么存数据_Redis_02

cd /usr/local/
mkdir redis_cluster
cd redis_cluster
mkdir 7000 7001 7002 7003 7004 7005 //分别代表三个节点

其对应端口 7000 7001 7002

//创建7000节点为例,拷贝到7000目录

cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7000/   

cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7001/   

cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7002/

cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7003/   

cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7004/   

cp /usr/local/redis-3.2.1/redis.conf  ./redis_cluster/7005/

Redis可以存集合 redis集群怎么存数据_redis_03


修改配置文件

daemonize    yes                          //redis后台运行

pidfile  /var/run/redis_7000.pid          //pidfile文件对应7000,7002,7003

port  7000                                //端口7000,7002,7003

cluster-enabled  yes                      //开启集群  把注释#去掉

cluster-config-file  nodes_7000.conf      //集群的配置  配置文件首次启动自动生成 7000,7001,7002

cluster-node-timeout  5000                //请求超时  设置5秒够了

appendonly  yes                           //aof日志开启  有需要就开启,它会每次写操作都记录一条日志

Redis可以存集合 redis集群怎么存数据_redis_04


cd /usr/local (启动Redis集群)

redis-server redis_cluster/7000/redis.conf

redis-server redis_cluster/7001/redis.conf 

redis-server redis_cluster/7002/redis.conf

redis-server redis_cluster/7003/redis.conf

redis-server redis_cluster/7004/redis.conf 

redis-server redis_cluster/7005/redis.conf

Redis可以存集合 redis集群怎么存数据_redis_05


安装yum仓库

yum install centos-release-scl-rh

Redis可以存集合 redis集群怎么存数据_redis_06

安装高版本的ruby

yum install rh-ruby23  -y

Redis可以存集合 redis集群怎么存数据_java_07

scl  enable  rh-ruby23 bash

Redis可以存集合 redis集群怎么存数据_redis_08


安装gem

gem install redis

Redis可以存集合 redis集群怎么存数据_spring_09

启动集群(刚开始搭建集群时,需要执行,下次再启动集群则只需要 redis-server redis_cluster/7000/redis.conf)

/usr/local/redis-3.2.1/src/redis-trib.rb create --replicas 1 192.168.122.204:7000 192.168.122.204:7001 192.168.122.204:7002 192.168.122.204:7003 192.168.122.204:7004 192.168.122.204:7005

目的是为了建立内部各个节点的对应关系,比如主从关系,这些关系仅且只能在一个集群中初始化时对应一次;

Redis可以存集合 redis集群怎么存数据_spring_10


测试,在7000上set,在7002上get

Redis可以存集合 redis集群怎么存数据_redis_11

退出:exit

关闭Redis集群:

集群关闭直接将各个节点的进程kill掉即可;

[root@master bin]# ps -ef | grep redis
root      11516      1  0 16:15 ?        00:00:10 ./redis-server 192.168.xxx.21:7002 [cluster]
root      11521      1  0 16:15 ?        00:00:09 ./redis-server 192.168.xxx.21:7003 [cluster]
root      11526      1  0 16:15 ?        00:00:10 ./redis-server 192.168.xxx.21:8001 [cluster]
root      11531      1  0 16:15 ?        00:00:10 ./redis-server 192.168.xxx.21:8002 [cluster]
root      11536      1  0 16:15 ?        00:00:10 ./redis-server 192.168.xxx.21:8003 [cluster]
root      11869      1  0 16:33 ?        00:00:07 ./redis-server 192.168.xxx.21:7001 [cluster]
root      12528   9737  0 17:39 pts/7    00:00:00 grep --color=auto redis
[root@master bin]# kill -9 11516
[root@master bin]# kill -9 11521
[root@master bin]# kill -9 11526
[root@master bin]# kill -9 11531
[root@master bin]# kill -9 11536
[root@master bin]# kill -9 11869

三.使用Java实现对Redis集群的数据操作

springboot整合Redis实现对Redis数据库的操作

1.springboot中application.yml配置文件,节点信息(或者是properties.yml)

spring:
  redis:
    cluster:
      #设置key的生存时间,当key过期时,它会被自动删除;
      expire-seconds: 120
      #设置命令的执行时间,如果超过这个时间,则报错;
      command-timeout: 101000
      #设置redis集群的节点信息,其中namenode为域名解析,通过解析域名来获取相应的地址;
      nodes: 
      192.168.58.130:7000,
      192.168.58.130:7001,
      192.168.58.130:7002,
      192.168.58.130:7003,
      192.168.58.130:7004,
      192.168.58.130:7005

2.controller中的具体实现(测试)

@RestController
public class RedisController {
    @Autowired
    private RedisProperties redisProperties;

    @Autowired
    private RedisConfig redisConfig;

    @Autowired
    private JedisClientCluster jedisClientCluster;

    @RequestMapping(value = "getRedisValue")
    public String getRedisValue(){

        List<Person> list = new ArrayList<>();
        for (int i=0;i<10;i++){
            Person person = new Person();
            person.setId(UUID.randomUUID().toString());
            person.setAge(30+i);
            person.setName("张三");
            person.setPassword("123456789");

            System.out.println("哈哈哈:" + redisProperties.toString());
            System.out.println("哈哈哈:" + redisConfig.getJedisCluster().getClusterNodes());
            System.out.println(jedisClientCluster.set("yp", "123456"));
            System.out.println(jedisClientCluster.get("yp"));



            System.out.println(jedisClientCluster.get("12"));
            list.add(person);
        }
        String string = JSONObject.toJSONString(list);
        jedisClientCluster.set("person集合",string);
        jedisClientCluster.set("嘿嘿嘿", "<html lang=\"en\">\n" +
                "<head>\n" +
                "    <meta charset=\"UTF-8\">\n" +
                "    <title>Title</title>\n" +
                "</head>\n" +
                "<body style=\"color: #666; font-size: 14px; font-family: 'Open Sans',Helvetica,Arial,sans-serif;\">\n" +
                "<div class=\"box-content\" style=\"width: 80%; margin: 20px auto; max-width: 1500px; min-width: 600px;\">\n" +
                "    <div class=\"header-tip\" style=\"font-size: 12px;\n" +
                "                                   color: #aaa;\n" +
                "                                   text-align: right;\n" +
                "                                   padding-right: 25px;\n" +
                "                                   padding-bottom: 10px;\">\n" +
                "    </div>\n" +
                "    <div class=\"info-top\" style=\"padding: 15px 25px;\n" +
                "                                 border-top-left-radius: 10px;\n" +
                "                                 border-top-right-radius: 10px;\n" +
                "                                 background: {0};\n" +
                "                                 color: #fff;\n" +
                "                                 overflow: hidden;\n" +
                "                                 line-height: 32px;\">\n" +
                "        <div style=\"color:#010e07\"><strong>分销平台产品列表通知</strong></div>\n" +
                "    </div>\n" +
                "    <div class=\"info-wrap\" style=\"border-bottom-left-radius: 10px;\n" +
                "                                  border-bottom-right-radius: 10px;\n" +
                "                                  border:1px solid #ddd;\n" +
                "                                  overflow: hidden;\n" +
                "                                  padding: 15px 15px 20px;\">\n" +
                "        <div class=\"tips\" style=\"padding:15px;\">\n" +
                "            <p style=\" list-style: 160%; margin: 10px 0;\">Hi,</p>\n" +
                "            <p style=\" list-style: 160%; margin: 10px 0;\">{1}</p>\n" +
                "        </div>\n" +
                "        <div class=\"time\" style=\"text-align: right; color: #999; padding: 0 15px 15px;\">{2}</div>\n" +
                "        <br>\n" +
                "        <table class=\"list\" style=\"width: 100%; border-collapse: collapse; border-top:1px solid #eee; font-size:12px; table-layout: fixed;\">\n" +
                "            <thead>\n" +
                "            <tr style=\" background: #fafafa; color: #333; border-bottom: 1px solid #eee;\">\n" +
                "                {3}\n" +
                "            </tr>\n" +
                "            </thead>\n" +
                "            <tbody>\n" +
                "            {4}\n" +
                "            </tbody>\n" +
                "        </table>\n" +
                "    </div>\n" +
                "</div>\n" +
                "</body>\n" +
                "</html>");
            return jedisClientCluster.get("嘿嘿嘿");
        }
	}

2.获取节点配置信息配置类 RedisProperties

//读取application.yml配置文件中的属性信息到bean中,并注入到spring容器;
/**
 * @Description: 使用ConfigurationProperties注解读取yml文件中的字段值,并使用Component注入到spring容器中;
 */
//依赖注入
@Component
//该注解用于读取配置文件中的属性,其中prefix表示前缀;  获取配置文件的配置信息
@ConfigurationProperties(prefix = "spring.redis.cluster")

public class RedisProperties {
    private int expireSeconds;
    private String nodes;
    private int commandTimeout;

    public int getExpireSeconds() {
        return expireSeconds;
    }

    public void setExpireSeconds(int expireSeconds) {
        this.expireSeconds = expireSeconds;
    }

    public String getNodes() {
        return nodes;
    }

    public void setNodes(String nodes) {
        this.nodes = nodes;
    }

    public int getCommandTimeout() {
        return commandTimeout;
    }

    public void setCommandTimeout(int commandTimeout) {
        this.commandTimeout = commandTimeout;
    }

    @Override
    public String toString() {
        return "RedisProperties{" +
                "expireSeconds=" + expireSeconds +
                ", nodes='" + nodes + '\'' +
                ", commandTimeout=" + commandTimeout +
                '}';
    }

	}

3.获取redis集群的ip及端口号等相关信息类 RedisConfig从RedisProperties中获取节点并分割得到想要的数据节点信息

@Component
	//@Configuration
	public class RedisConfig {
	    @Autowired
	    private RedisProperties redisProperties;

    @Bean
    public JedisCluster getJedisCluster(){
        //获取redis集群的ip及端口号等相关信息;
        String[] serverArray = redisProperties.getNodes().split(",");
        Set<HostAndPort> nodes = new HashSet<>();

        //遍历add到HostAndPort中;
        for (String ipPort : serverArray) {
            String[] ipPortPair = ipPort.split(":");
            System.out.println("ipPort = " + ipPortPair[0].trim()+Integer.valueOf(ipPortPair[1].trim()));


            nodes.add(new HostAndPort(ipPortPair[0].trim(), Integer.valueOf(ipPortPair[1].trim())));
        }
        //构建对象并返回;
        System.out.println("nodes.toString() = " + nodes.toString());
        return new JedisCluster(nodes, redisProperties.getCommandTimeout());
    }

}

4.Redis操作方法接口JedisClient(含有对Redis数据库操作的一系列方法)

public interface JedisClient {
    String set(String key, String value);

    String get(String key);

    Boolean exists(String key);

    Long expire(String key, int seconds);

    Long ttl(String key);

    Long incr(String key);

    Long hset(String key, String field, String value);

    String hget(String key, String field);

    Long hdel(String key, String... field);

}

5.接口JedisClient的实现类

@Component
public class JedisClientCluster  implements JedisClient {
    @Autowired
    private JedisCluster jedisCluster;

    @Override
    public String set(String key, String value) {
        return jedisCluster.set(key, value);
    }

    @Override
    public String get(String key) {
        return jedisCluster.get(key);
    }

    @Override
    public Boolean exists(String key) {
        return jedisCluster.exists(key);
    }

    @Override
    public Long expire(String key, int seconds) {
        return jedisCluster.expire(key, seconds);
    }

    @Override
    public Long ttl(String key) {
        return jedisCluster.ttl(key);
    }

    @Override
    public Long incr(String key) {
        return jedisCluster.incr(key);
    }

    @Override
    public Long hset(String key, String field, String value) {
        return jedisCluster.hset(key, field, value);
    }

    @Override
    public String hget(String key, String field) {
        return jedisCluster.hget(key, field);
    }

    @Override
    public Long hdel(String key, String... field) {
        return jedisCluster.hdel(key, field);
    }
}

6.主类如下

/*
*spring Data redis整合操作数据
**/
@SpringBootApplication
public class RedistApplication {

    public static void main(String[] args) {
        SpringApplication.run(RedistApplication.class, args);
    }

    @Bean
    public RedisTemplate<Object, Person> personRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate<Object, Person> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Person> serializer = new Jackson2JsonRedisSerializer<>(Person.class);
        template.setDefaultSerializer(serializer);
        return template;


    }
}

7.实现效果图大致如下

Redis可以存集合 redis集群怎么存数据_redis_12

四.Redis持久化及其配置

1、什么是Redis持久化
 简单来说,就是Redis通过将数据存储于内存或者虚拟内存(也是Redis常用的技术),通过某种技术手段将数据保存于可永久保存的存储设备或媒介中,以此来保证数据完整不丢失、高速访问数据、快速恢复。

2、Redis持久化的两种方式
Redis一般通过两种方式实现持久化:快照方式(RDB模式,默认方式),日志追加方式(AOF模式)

  1. 快照方式(RDB方式)
    这种方式有一下几个特点,显而易见RDB方式总结起来就是一种将数据以快照方式写入二进制文件中,在间隔时间内全量写入磁盘的一个过程。它的优缺点也在一下四个特点中体现,

缺点:第一是多少间隔时间的重要性,第二是数据量大的情况下,全量写入会影响性能

优点:对于恢复操作相对比较简单,因为全量写入只需要保证一个二进制文件的恢复即可;

(1) 将内存中数据以快照的方式写入二进制文件中,默认文件名为dump.rdb

(2) 客户端使用save/bgsave命令做一次快照持久化(save操作在主线程中保存快照,Redis是用一个主线程处理所有的客户端请求,所以可能会阻塞所有的客户端请求,不推荐使用);

(4) 快照方式并不是增量数据,而是全量重新写入,数据量大的情况下会严重影响性能(主要是由于大量IO操作以及主进程不断fork()子进程去处理持久化工作)。

(5) 快照方式是间隔时间做一次,所以如果redis意外宕机的话,就会丢失最后一次快照的后的所有数据。

  1. 日志追加方式(AOF持久化)
    Redis会将每一个收到的写命令都通过write函数追加到文件中(默认为appendonly.aof),当redis重启时会通过重新执行文件中保存的写命令来在内存中重建整个数据库的内容。

缺点:相比较RDB而已AOF的文件相对较大(redis当AOF文件比较大时,可以重写),AOF的速度比RDB相对较慢;

优点:采用everysec配置,那么顶多损失前一秒的数据;不会像RDB那样损失很多(当然只是相对而言);

AOF的文件没有被重写的话,比如当我们不小心FULASHALL,现在需要恢复。只需要AOF文件末尾中去掉该命令,重启Redis载入即可(即AOF文件比较容易读懂,恢复上一个状态简单)

(1) 由于OS会在内存中缓存write的修改,所以并不会立即写到磁盘上,这样可能会导致丢失部分修改,可以通过配置来实现强制OS写到磁盘时机。

a) appendonly yes //启用日志追加持久化方式

b) appendfsync always //每次收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用

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

d) #appendfsync no //完全依赖操作系统,性能最好,持久化没保证

(2)持久化的文件会越来越大,比如执行incr test命令100次,最后恢复使用set test 100就够了,但是这种方式保存了100条命令,其中99次是多余的;

(3) 为了(2)中提到的缺点,redis提供了bgrewriteaof命令:redis将内存中的数据以命令的方式保存到临时文件中,最后替换原来的持久化日志文件。

RDB与AOF两种方式各有优缺点

1.RDB:数据快照

优点:与AOF相比,RDB文件相对较小,恢复数据比较快(原因见数据恢复部分)
缺点:服务器宕机,RBD方式会丢失掉上一次RDB持久化后的数据;使用bgsave fork子进程时会耗费内存。

2.AOF:命令存储
优点: AOF只是追加文件,对服务器性能影响较小,速度比RDB快,消耗内存也少,同时可读性高。
缺点:生成的文件相对较大,即使通过AOF重写,仍然会比较大;恢复数据的速度比RDB慢。

1. RDB默认方式配置

# 时间策略:当满足每900s/300s/60s内至少1/10/10000次写操作,则会触发bgsave命令进行持久化,三个策略中只需要满足其中任何一条即可持久化
save 900 1
save 300 10
save 60 10000
 
# 文件名称
dbfilename dump.rdb
 
# 文件保存路径
dir /home/redis/data/
 
# 如果持久化出错,主进程是否停止写入:是为了保证数据的一致性,工作进程(子进程)持久化出错后,主进程停止写入请求
stop-writes-on-bgsave-error yes
 
# 是否压缩
rdbcompression yes
 
# 导入时是否检查
rdbchecksum yes

2. AOF日志追加方式配置:

# 是否开启aof
appendonly yes
 
# 文件名称
appendfilename "appendonly.aof"
 
# 同步方式,上文已提到
appendfsync everysec
 
# aof重写操作是否同步,yes则不进行同步,no则同步
no-appendfsync-on-rewrite no
 
# 重写触发配置
auto-aof-rewrite-percentage 100 # 当前AOF文件大小是上次日志重写时的AOF文件大小两倍时,发生BGREWRITEAOF操作。
auto-aof-rewrite-min-size 64mb # 当前AOF文件执行BGREWRITEAOF命令的最小值,避免刚开始启动Reids时由于文件尺寸较小导致频繁的BGREWRITEAOF。
 
# 加载aof时如果有错如何处理,忽略最后一条可能存在问题的指令
aof-load-truncated yes
# Redis4.0新增RDB-AOF混合持久化格式。
aof-use-rdb-preamble no