redis数据库

·centos7安装redis

Download | Redis

1.1 依赖库

# Redis是基于C语言编写的,因此首先需要安装Redis所需要的gcc依赖
yum install -y gcc tcl

1.2 安装

# 1、redis安装包上传centos7
​
# 2、解压缩
tar -xzf redis-6.2.6.tar.gz 
​
# 3、 进入redis目录
cd redis-6.2.6  
​
# 4、 运行编译命令
make && make install  

如果没有出错,应该就安装成功了

默认的安装路径是在 /usr/local/bin目录下

该目录已经默认配置到环境变量,因此可以在任意目录下运行这些命令。其中:

  • redis-cli:是redis提供的命令行客户端

  • redis-server:是redis的服务端启动脚本

  • redis-sentinel:是redis的哨兵启动脚本

1.3 启动

1.3.1 默认启动

redis-server  # 这种启动属于前台启动,会阻塞整个会话窗口,窗口关闭或者按下CTRL + C则Redis停止。不推荐使用。

1.3.2 指定配置启动

如果要让Redis以后台方式启动,则必须修改Redis配置文件,就在我们之前解压的redis安装包下(/usr/local/src/redis-6.2.6),名字叫redis.conf:

# 1、先将redis.conf 这个配置文件备份一份(备份的原因是怕修改出错无法还原)
cp redis.conf redis.conf.bck
​
# 2、然后修改redis.conf文件中的一些配置:
vim redis.conf
# 允许访问的地址,默认是127.0.0.1,会导致只能在本地访问。修改为0.0.0.0则可以在任意IP访问,生产环境不要设置为0.0.0.0
bind 0.0.0.0
# 保护模式,关闭保护模式
protected-mode no
# 守护进程,修改为yes后即可后台运行
daemonize yes 
# 密码,设置后访问Redis必须输入密码
requirepass 123321
# 监听的端口
port 6379
# 工作目录,默认是当前目录,也就是运行redis-server时的命令,日志、持久化等文件会保存在这个目录
dir .
# 数据库数量,设置为1,代表只使用1个库,默认有16个库,编号0~15
databases 1
# 设置redis能够使用的最大内存
maxmemory 512mb
# 日志文件,默认为空,不记录日志,可以指定日志文件名
logfile "redis.log"
​
# 3、启动Redis
# 进入redis安装目录 
cd /usr/local/src/redis-6.2.6
# 启动
redis-server redis.conf
​
# 4、停止服务
# 利用redis-cli来执行 shutdown 命令,即可停止 Redis 服务,
# 因为之前配置了密码,因此需要通过 -u 来指定密码
redis-cli -u 123321 shutdown

1.3.3 开机自启

我们也可以通过配置来实现开机自启。

首先,新建一个系统服务文件:

vi /etc/systemd/system/redis.service

内容如下:

[Unit]
Description=redis-server
After=network.target
​
[Service]
Type=forking
ExecStart=/usr/local/bin/redis-server /usr/local/src/redis-6.2.6/redis.conf
PrivateTmp=true
​
[Install]
WantedBy=multi-user.target

然后重载系统服务:

systemctl daemon-reload

现在,我们可以用下面这组命令来操作redis了:

# 启动
systemctl start redis
# 停止
systemctl stop redis
# 重启
systemctl restart redis
# 查看状态
systemctl status redis
# 开机自启
systemctl enable redis

·Redis数据类型

1、String类型

字符串类型是redis中最简单的数据类型,它存储的值可以是字符串,其最大字符串长度支持到512M。基于此类型,可以实现博客的字数统计,将日志不断追加到指定key,实现一个分布式自增iid,实现一个博客的的点赞操作等

incr/incrby  实现数据自增自减

127.0.0.1:6379> set num 1         ---》     1    创建一个键

127.0.0.1:6379> incr num          ---》     2    键值加一

默认自加1。如果键不存在,则会自动创建该键,并且初始值为1

127.0.0.1:6379> incrby num 2      ---》     4    键值加二  

②decr/decrby   自减,同上

③append向尾部追加值

        append id "abc"   如果键不存在,则创建该键,并且值为abc

④strlen  返回数据的长度

        如果键不存在则返回0。注意,如果键值为空串,返回也是0

        strlen id

⑤mset/mget  同时储存/获取多个键值

        mset id1 1 id2 2 id3 3

        mget id1 id2 id3

⑥get/set   储存/获取单个键值

⑦  del  name     删除键(通用)

2、Hash类型

数据结构:id name sun age 21  字段 键 值 键 值

hset/hget设置/获取对象和的属性

HSET命令不区分插入和更新操作,当执行插入操作时HSET 命令返回1,当执行更新操作时返回0。

        hset同时储存多个键对: id name sun age 21

        hget获取一个键的值   hget name sun

②hgetall id 获取该字段下的所有键值对 

hincrby  实现值自增,自减

        自增hincrby id age 1

        自减hincrby id age -2

hmset/hmget设置/获取对象和的属性

        Hmset id name tony age 18    为属性设置值

        hmget id age name      获取id中age和name对应的值

hexists  判断属性是否存在

        hexists id name  返回1表示存在

        hexists abb age  返回0表示不存在

hdel  删除属性

        Hdel id name  返回1删除成功

hkeys/hvals  获取字段的全部键或者全部值

        hkeys id  获取该字段全部键

        Hvals id 获取该字段全部值

3、list类型

Redis的list类型相当于java中的LinkedList,其原理就就是一个双向链表。支持正向、反向查找和遍历等操作,插入删除速度比较快。经常用于实现热销榜,最新评论等的设计。

lpush在key对应list的头部添加元素

        lpush list1 "sun"

rpush在key对应list的尾部添加元素

lrange  查询该list集合指定长度的数据

        lrange list1 0 -1    -1表示该list的末尾下标

del 同时删除多个list集合

linsert 在集合中指定值之前或则之后添加元素

        linsert list1 before "world" "there"  在之前

        linsert list1 before "world" "there"  在之后

lrem从key对应list中删除count个和value相同的元素

        lrem list1 2 "hello"  当count>0按从头到尾的顺序删除

        lrem list1 -2 "hello"  当count<0按从头的顺序删除

        lrem list1 0 "hello"  当count=0 全部删除

ltrim保留指定key 的值范围内的数据

        ltrim list1 1 -1   保留list1集合中索引1到末尾  

lpop从list的头部删除元素,并返回删除元素

        lpop list1  删除id 集合中的头部元素,并返回

llen返回key对应list的长度

        Llen list1 

lindex返回名称为key的list中index位置的元素

        lindex list1 0   返回list1下标为0的数据

11.rpoplpush

从第一个list的尾部移除元素并添加到第二个list的头部,最后返回被移除的元素值,整个操作是原子的.如果第一个list是空或者不存在返回nil: rpoplpush lst1 lst1

4、set类型

特点:是string类型的无序集合。集合成员是唯一的

  sadd添加元素,重复元素添加失败,返回0

        sadd set1 one

smembers获取指定键对应set中的内容

        smembers set1

spop移除并返回集合中的一个随机元素

        spop set1

Srem 命令命令用于移除集合中的一个或多个成员元素 

        注意:不存在的成员元素会被忽略

        SREM set1 "hello"

scard获取set集合成员个数       

        scard name

⑥smove把位置1的集合的指定元素移动到位置2的集合中

⑦sunion将多个set集合合并

sismember 命令  判断成员元素是否是集合的成员  

·Java客户端

1、jedis

Jedis是Java中操作redis的一个客户端,类似通过jdbc访问mysql数据库。C/S架构,版本6.0之前都是单线程,之后网络io操作引入了多线程

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.7.1</version>
</dependency>

连接池JedisPool

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

/**
 * @ClassName JedisTest
 * @Description jedis测试
 * @Author syh
 * @Date 2022/12/16 13:46
 * @Version 1.0
 */
public class JedisTest {
    public static void main(String[] args) {
        //定义连接池的配置,有默认配置,需要就更改
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(18);//最大连接数
        config.setMaxIdle(6);//最大空闲时间(连接不用了要释放)

        //创建连接池
        JedisPool jedisPool = new JedisPool(config, "43.138.127.27", 6379);

        //从池中获取一个连接
        Jedis resource = jedisPool.getResource();
        resource.auth("root");  //设置了密码登录就需要使用此配置

        //通过jedis连接存取数据
        resource.set("class", "cgb2004");
        String clazz = resource.get("class");
        System.out.println(clazz);

        //将链接返回池中
        resource.close();
        //关闭连接池
        jedisPool.close();
    }
}

2、RedisTemplate

@Autowired
private RedisTemplate RedisTemplate;

void testString01(){

    //1.获取字符串操作对象
    ValueOperations<String,String> vo = redisTemplate.opsForValue();

    //2.读写redis数据
    vo.set("name","redis");
    vo.set("author","tony",10);//10表示有效时长
    String author = vo.get("author");
    System.out.println(author);
}

定制RedisTemplate序列化和反序列化方式

​ 1、序列化方式
(1)JdkSerializationRedisSerializer
这是RestTemplate类默认的序列化方式
优点:
反序列化时不需要提供类型信息(class), 
缺点:
需要实现Serializable接口 
存储的为二进制数据 
序列化后的结果非常庞大,是JSON格式的5倍左右,这样就会消耗redis服务器的大量内存 
(2)StringRedisSerializer
是StringRedisTemplate默认的序列化方式,key和value都会采用此方式进行序列化,是被推荐使用的,对开发者友好,轻量级,效率也比较高。
(3)GenericToStringSerializer
需要调用者给传一个对象到字符串互转的Converter
(4)Jackson2JsonRedisSerializer
点:
速度快,序列化后的字符串短小精悍,不需要实现Serializable接口。 
缺点:
此类的构造函数中有一个类型参数,必须提供要序列化对象的类型信息(.class对象),其在反序列化过程中用到了类型信息 
(5)GenericJackson2JsonRedisSerializer
与Jackson2JsonRedisSerializer大致相同,会额外存储序列化对象的包命和类名

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory){
        // 创建RedisTemplate对象
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 设置连接工厂
        template.setConnectionFactory(connectionFactory);
        // 创建JSON序列化工具
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = 
            							new GenericJackson2JsonRedisSerializer();
        // 设置Key的序列化
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // 设置Value的序列化
        template.setValueSerializer(jsonRedisSerializer);
        template.setHashValueSerializer(jsonRedisSerializer);
        // 返回
        return template;
    }
}

   

3、StringRedisTemplate

这种用法比较普遍,因此SpringDataRedis就提供了RedisTemplate的子类:StringRedisTemplate,它的key和value的序列化方式默认就是String方式。省去了我们自定义RedisTemplate的序列化方式的步骤,而是直接使用。

@Autowired
private StringRedisTemplate stringRedisTemplate;
// JSON序列化工具
private static final ObjectMapper mapper = new ObjectMapper();

@Test
void testSaveUser() throws JsonProcessingException {
    // 创建对象
    User user = new User("虎哥", 21);
    // 手动序列化
    String json = mapper.writeValueAsString(user);
    // 写入数据
    stringRedisTemplate.opsForValue().set("user:200", json);

    // 获取数据
    String jsonUser = stringRedisTemplate.opsForValue().get("user:200");
    // 手动反序列化
    User user1 = mapper.readValue(jsonUser, User.class);
    System.out.println("user1 = " + user1);
}

·Redis持久化操作

持久化的作用:Redis是一种内存数据库,在断电时数据可能会丢失。数据持久化,就可以保证 一 些数据不丢失,保证数据的可靠性。

 持久化方式:Redis中为了保证在系统宕机(类似进程被杀死)情况下,能更快的进行故障恢复,设计了两种数据持久化方案,分别为rdb和aof方式。 

1、Rdb方式持久化

此方式是默认开启

修改redis.conf文件:

# 这里表示每隔60s,如果有超过1000个key发生了变更,那么就生成一个新的dump.rdb文件,就是当前redis内存中完整的数据快照,这个操作也被称之为snapshotting(快照)。

save 60 1000

# 持久化 rdb文件遇到问题时,主进程是否接受写入,yes 表示停止写入,如果是no 表示redis继续提供服务。

stop-writes-on-bgsave-error yes

# 在进行快照镜像时,是否进行压缩。yes:压缩,但是需要一些cpu的消耗。no:不压缩,需要更多的磁盘空间。

rdbcompression yes

# 一个CRC64的校验就被放在了文件末尾,当存储或者加载rbd文件的时候会有一个10%左右的性能下降,为了达到性能的最大化,你可以关掉这个配置项。

rdbchecksum yes

# 快照的文件名

dbfilename dump.rdb

# 存放快照的目录

dir /var/lib/redis

缺点:假如redis故障时,要尽可能少的丢失数据,那么RDB方式不太好,它都是每隔5分钟或更长时间做一次快照,这个时候一旦redis进程宕机,那么会丢失最近几分钟的数据。

优点:RDB会生成多个数据文件,每个数据文件都代表了某一个时刻中redis的数据,这种多个数据文件的方式,非常适合做冷备相对于AOF持久化机制来说,直接基于RDB数据文件来重启和恢复redis进程,更加快速。

2、Aof方式数据持久化

修改redis.conf文件实现Aof:

# 是否开启AOF,默认关闭

appendonly yes

# 指定 AOF 文件名

appendfilename appendonly.aof

# Redis支持三种刷写模式:

# appendfsync always #每次收到写命令就立即强制写入磁盘,类似MySQL的sync_binlog=1,是最安全的。但该模式下速度也是最慢的,一般不推荐使用。

appendfsync everysec #每秒钟强制写入磁盘一次,在性能和持久化方面做平衡,推荐该方式。

# appendfsync no     #完全依赖OS的写入,一般为30秒左右一次,性能最好但是持久化最没有保证,不推荐。

    

#在日志重写时,不进行命令追加操作,而只是将其放在缓冲区里,避免与命令的追加造成DISK IO上的冲突。

#设置为yes表示rewrite期间对新写操作不fsync,暂时存在内存中,等rewrite完成后再写入,默认为no,建议yes

no-appendfsync-on-rewrite yes

#当前AOF文件大小是上次日志重写得到AOF文件大小的二倍时,自动启动新的日志重写过程。

auto-aof-rewrite-percentage 100

#当前AOF文件启动新的日志重写过程的最小值,避免刚刚启动Reids时由于文件尺寸较小导致频繁的重写。

auto-aof-rewrite-min-size 64mb

优点:

第一:AOF可以更好的保护数据不丢失,一般AOF会每隔1秒,通过一 个后台线程执行一次fsync操作,最多丢失1秒钟的数据.

第二:AOF日志文件通常以append-only模式写入,所以没有任何磁盘 寻址的开销,写入性能非常高,并且文件不容易破损,即使文件 尾部破损,也很容易修复。

第三:AOF日志文件过大的时候,出现后台重写操作,也不会影响客 户端的读写。因为在rewrite log的时候,会对其中的日志进行压 缩,创建出一份需要恢复数据的最小日志出来。再创建新日志文 件的时候,老的日志文件还是照常写入。当新的merge后的日志文 件ready的时候,再交换新老日志文件即可。

第四:AOF日志文件的命令通过易读的方式进行记录,这个特性非常 适合做灾难性的误删除的紧急恢复。比如某人不小心用flushall 命令清空了所有数据,只要这个时候后台rewrite还没有发生,那 么就可以立即拷贝AOF文件,将最后一条flushall命令给删了,然 后再将该AOF文件放回去,就可以通过恢复机制,自动恢复所有数 据.

缺点:

第一:对于同一份数据来说,AOF日志文件通常比RDB数据快照文 件更大。

第二:AOF开启后,支持的写QPS会比RDB支持的写QPS低,因为A OF一般会配置成每秒fsync一次日志文件,当然,每秒一次fsync, 性能也还是很高的。

第三:AOF这种基于命令日志方式,比基于RDB每次持久化一份完 整的数据快照文件的方式,更加脆弱一些,容易有bug。

项目中选择redis的持久化方式

第一:不要仅仅使用RDB,因为那样会导致你丢失很多数据。

第二:也不要仅仅使用AOF,因为AOF做冷备没有RDB做冷备进行 数据恢复的速度快,并且RDB简单粗暴的数据快照方式更加健壮。

第三:综合使用AOF和RDB两种持久化机制,用AOF来保证数据不 丢失,作为数据恢复的第一选择; 用RDB来做不同程度的冷备。

·Redis事务

基本指令

redis进行事务控制时,通常是基于如下指令进行实现,例如:

  • multi 开启事务
  • exec 提交事务
  • discard 取消事务
  • watch 监控,如果监控的值发生变化,则提交事务时会失败
  • unwatch 去掉监控

watch监控应用

基于一个秒杀,抢购案例,演示redis乐观锁方式开启一个事务,改变某个键值,并且使用监控,在开启另一个事务,改变键值,提交事务,则第一个事务中监控发现该键发生改变,该事务提交失败。

开客户端1,执行如下操作

127.0.0.1:6379> set ticket 1  

OK

127.0.0.1:6379> set money 0

OK

127.0.0.1:6379> watch ticket #乐观锁,对值进行观察,改变则事务失败

OK

127.0.0.1:6379> multi #开启事务

OK

127.0.0.1:6379> decr ticket   #ticket键减一操作

QUEUED

127.0.0.1:6379> incrby money 100    #键money加100操作

QUEUED

打开客户端2,执行如下操作,演示还没等客户端1提交事务,此时客户端2把票买到了。

127.0.0.1:6379> get ticket

"1"

127.0.0.1:6379> decr ticket

(integer) 0

回到客户端1:提交事务,检查ticket的值

127.0.0.1:6379> exec

(nil) #执行事务,失败

127.0.0.1:6379> get ticket

“0”

127.0.0.1:6379> unwatch #取消监控

·Redis架构设计

作用:单个Redis支持的读写能力还是有限的,此时我们可以使用多个redis来提高redis的并发处理能力

1、redis主从架构设计

Redis全量同步:

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份

Redis增量同步

Redis增量复制是指Slave初始化后,开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

配置流程:

1、进入你的宿主机docker目录,然后将redis01拷贝多份

c​​​​​​​p -r redis01/ redis02


2、启动三个新的redis容器

docker run -p 6379:6379 --name redis6379 \

-v /usr/local/docker/redis01/data:/data \

-v /usr/local/docker/redis01/conf/redis.conf:/etc/redis/redis.conf \

-d redis redis-server /etc/redis/redis.conf \

--appendonly yes



docker run -p 6380:6379 --name redis6380 \

-v /usr/local/docker/redis02/data:/data \

-v /usr/local/docker/redis02/conf/redis.conf:/etc/redis/redis.conf \

-d redis redis-server /etc/redis/redis.conf \

--appendonly yes



docker run -p 6381:6379 --name redis6381 \

-v /usr/local/docker/redis03/data:/data \

-v /usr/local/docker/redis03/conf/redis.conf:/etc/redis/redis.conf \

-d redis redis-server /etc/redis/redis.conf \

--appendonly yes

3、检测主机ip

docker inspect redis6379

4、将从机设置为角色设置为Slave

从机执行此命令  ip 和端口号为主机

slaveof 172.17.0.2 6379

5、查看角色

info replication

2、Redis哨兵模式

是Redis的主从架构模式下,实现高可用性(high availability)的一种机制。由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

配置流程

1、分别进入redis容器内部进行配置,在容器指定目录/etc/redis创建sentinel.conf文件,文件内容为:

sentinel monitor redis6379 172.17.0.2 6379 1

如上指令表示要的监控的master, redis6379为服务名, 172.17.0.2和6379为master的ip和端口,1表示多少个sentinel认为一个master失效时,master才算真正失效.

        通过指令创建该文件,分别执行两条下载命令

apt-get update   apt-get install vim

        假如网络不好,也可以在宿主机对应的挂载目录去创建文件,或者在容器中执行:

        cat <<EOF > /etc/redis/sentinel.conf

        sentinel monitor redis6379 172.17.0.2 6379 1

        EOF

2、在每个redis容器内部的/etc/redis目录下执行如下指令(最好是在多个客户端窗口执行),启动哨兵服务

redis-sentinel sentinel.conf

检测是否配置成功

1、关闭master服务

docker stop redis6379

其它客户端窗口,检测日志输出,例如

410:X 11 Jul 2021 09:54:27.383 # +switch-master redis6379 172.17.0.2 6379 172.17.0.4 6379   //表示master172.17.0.2由转移到172.17.0.4

410:X 11 Jul 2021 09:54:27.383 * +slave slave 172.17.0.3:6379 172.17.0.3 6379 @ redis6379 172.17.0.4 6379

410:X 11 Jul 2021 09:54:27.383 * +slave slave 172.17.0.2:6379 172.17.0.2 6379 @ redis6379 172.17.0.4 6379

2、登陆ip为172.17.0.4对应的服务进行info检测,例如:

127.0.0.1:6379> info replication

\# Replication

role:master

connected_slaves:1

slave0:ip=172.17.0.3,port=6379,state=online,offset=222807,lag=0

master_failover_state:no-failover

master_replid:3d63e8474dd7bcb282ff38027d4a78c413cede53

master_replid2:5baf174fd40e97663998abf5d8e89a51f7458488

master_repl_offset:222807

second_repl_offset:110197

repl_backlog_active:1

repl_backlog_size:1048576

repl_backlog_first_byte_offset:29

repl_backlog_histlen:222779

127.0.0.1:6379>

Sentinel 配置进阶

对于sentinel.conf文件中的内容,我们还可以基于实际需求,进行增强配置,例如:

cat <<EOF > /etc/redis/sentinel.conf

sentinel monitor redis6379 172.17.0.2 6379 1

daemonize yes

logfile "/var/log/sentinel_log.log"

sentinel down-after-milliseconds redis6379 30000

EOF

其中:
1)daemonize yes表示后台运行(默认为no)
2)logfile 用于指定日志文件位置以及名字
3)sentinel down-after-milliseconds 表示master失效了多长时间才认为失效

哨兵工作原理分析


1):每个Sentinel以每秒钟一次的频率向它所知的Master,Slave以及其他 Sentinel 实例发送一个 PING 命令。

2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值(这个配置项指定了需要多少失效时间,一个master才会被这个sentinel主观地认为是不可用的。 单位是毫秒,默认为30秒), 则这个实例会被 Sentinel 标记为主观下线。

3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 Sentinel 要以每秒一次的频率确认Master的确进入了主观下线状态。

4):当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线 。

5):在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令 。

6):当Master被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次 。

7):若没有足够数量的 Sentinel 同意 Master 已经下线, Master 的客观下线状态就会被移除。
8): 若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。
 

3、Redis集群高可用

简述


Redis单机模式可靠性保证不是很好,容易出现单点故障,同时其性能也受限于CPU的处理能力,实际开发中Redis必然是高可用的,所以单机模式并不是我们的终点,我们需要对目前redis的架构模式进行升级。
Sentinel模式做到了高可用,但是实质还是只有一个master在提供服务(读写分离的情况本质也是master在提供服务),当master节点所在的机器内存不足以支撑系统的数据时,就需要考虑集群了。
Redis集群架构实现了对redis的水平扩容,即启动N个redis节点,将整个数据分布存储在这N个redis节点中,每个节点存储总数据的1/N。redis集群通过分区提供一定程度的可用性,即使集群中有一部分节点失效或无法进行通讯,集群也可以继续处理命令请求。

基本架构


对于redis集群(Cluster),一般最少设置为6个节点,3个master,3个slave,其简易架构如下:

创建集群


第一步:准备网络环境
创建虚拟网卡,主要是用于redis-cluster能于外界进行网络通信,一般常用桥接模式。

docker network create redis-net


查看docker的网卡信息,可使用如下指令

docker network ls


查看docker网络详细信息,可使用命令

docker network inspect redis-net


第二步:准备redis配置模板

mkdir -p /usr/local/docker/redis-cluster
cd /usr/local/docker/redis-cluster
vim redis-cluster.tmpl



在redis-cluster.tmpl中输入以下内容

port ${PORT}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
cluster-announce-ip 192.168.126.129
cluster-announce-port ${PORT}
cluster-announce-bus-port 1${PORT}
appendonly yes


其中:

各节点解释如下所示:

port:节点端口,即对外提供通信的端口
cluster-enabled:是否启用集群
cluster-config-file:集群配置文件
cluster-node-timeout:连接超时时间
cluster-announce-ip:宿主机ip
cluster-announce-port:集群节点映射端口
cluster-announce-bus-port:集群总线端口
appendonly:持久化模式


第三步:创建节点配置文件

在redis-cluser中执行以下命令

for port in $(seq 8010 8015); \
do \
  mkdir -p ./${port}/conf  \
  && PORT=${port} envsubst < ./redis-cluster.tmpl > ./${port}/conf/redis.conf \
  && mkdir -p ./${port}/data; \
done


其中:

for 变量 in $(seq var1 var2);do …; done为linux中的一种shell 循环脚本, 例如:


[root@centos7964 ~]# for i in $(seq 1 5);
> do echo $i;
> done;

指令envsubst <源文件>目标文件,用于将源文件内容更新到目标文件中.


通过cat指令查看配置文件内容

cat /usr/local/docker/redis-cluster/801{0..5}/conf/redis.conf


第四步:创建集群中的redis节点容器

for port in $(seq 8010 8015); \
do \
   docker run -it -d -p ${port}:${port} -p 1${port}:1${port} \
  --privileged=true -v /usr/local/docker/redis-cluster/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf \
  --privileged=true -v /usr/local/docker/redis-cluster/${port}/data:/data \
  --restart always --name redis-${port} --net redis-net \
  --sysctl net.core.somaxconn=1024 redis redis-server /usr/local/etc/redis/redis.conf; \
done


其中, --privileged=true表示让启动的容器用户具备真正root权限, --sysctl net.core.somaxconn=1024 这是一个linux的内核参数,用于设置请求队列大小,默认为128,后续启动redis的启动指令需要先放到这个请求队列中,然后依次启动.
创建成功以后,通过docker ps指令查看节点内容。

第五步:创建redis-cluster集群配置

docker exec -it redis-8010 bash
redis-cli --cluster create 192.168.126.129:8010 192.168.126.129:8011 192.168.126.129:8012 192.168.126.129:8013 192.168.126.129:8014 192.168.126.129:8015 --cluster-replicas 1


如上指令要尽量放在一行执行,其中最后的1表示主从比例,当出现选择提示信息时,输入yes即可。

当集群创建好以后,可以通过一些相关指令查看集群信息,例如

cluster nodes   #查看集群节点数
cluster info #查看集群基本信息




第六步:连接redis-cluster,并添加数据到redis

redis-cli -c -h 192.168.126.129 -p 8010

其中,这里-c表示集群(cluster),-h表示host(一般写ip地址),-p为端口(port)

其它:
在搭建过程,可能在出现问题后,需要停止或直接删除docker容器,可以使用以下参考命令:

批量停止docker 容器,例如:

                docker ps -a | grep -i "redis-801*" | awk '{print $1}' | xargs docker stop

批量删除docker 容器,例如

                docker ps -a | grep -i "redis-801*" | awk '{print $1}' | xargs docker rm -f

批量删除文件,目录等,例如:

                rm -rf 801{0..5}/conf/redis.conf
                rm -rf 801{0..5}

以上就是基于docker搭建redis-cluster的简单步骤,实际应用中可能还要更复杂一些,该文仅用于参考。

Jedis读写数据测试

@Test
void testJedisCluster()throws Exception{
      Set<HostAndPort> nodes = new HashSet<>();
      nodes.add(new HostAndPort("192.168.126.129",8010));
      nodes.add(new HostAndPort("192.168.126.129",8011));
      nodes.add(new HostAndPort("192.168.126.129",8012));
      nodes.add(new HostAndPort("192.168.126.129",8013));
      nodes.add(new HostAndPort("192.168.126.129",8014));
      nodes.add(new HostAndPort("192.168.126.129",8015));
      JedisCluster jedisCluster = new JedisCluster(nodes);
      //使用jedisCluster操作redis
      jedisCluster.set("test", "cluster");
      String str = jedisCluster.get("test");
      System.out.println(str);
      //关闭连接池
      jedisCluster.close();
 }


RedisTemplate读写数据测试


第一步:配置application.yml,例如:

spring:
  redis:
    cluster: #redis 集群配置
        nodes:192.168.126.129:8010,192.168.126.129:8011,192.168.126.129:8012,

                   192.168.126.129:8013,192.168.126.129:8014,192.168.126.129:8015
        max-redirects: 3 #最大跳转次数
    timeout: 5000 #超时时间
    database: 0
    jedis: #连接池
      pool:
        max-idle: 8
        max-wait: 0




第二步:编写单元测试类,代码如下:

package com.cy.redis;


@SpringBootTest
public class RedisClusterTests {
    @Autowired
    private RedisTemplate redisTemplate;
    @Test
    void testMasterReadWrite(){
        //1.获取数据操作对象
        ValueOperations valueOperations = redisTemplate.opsForValue();
        //2.读写数据
        valueOperations.set("city","beijing");
        Object city=valueOperations.get("city");
        System.out.println(city);
    }
}

·关于Redis雪崩,穿透,击穿的理解

1、缓存雪崩 

对于系统 A,假设每天高峰期每秒 5000 个请求,本来缓存在高峰期可以扛住每秒 4000 个请求,但是缓存机器意外发生了全盘宕机。缓存挂了,此时 1 秒 5000 个请求全部落数据库,数据库必然扛不住,它会报一下警,然后就挂了。此时,如果没有采用什么特别的方案来处理这个故障,DBA 很着急,重启数据库,但是数据库立马又被新的流量给打死了。

缓存雪崩解决方案:

部署redis时:redis 高可用,主从+哨兵,redis cluster,避免全盘崩溃。实现redis 持久化,万一崩溃,可以快速恢复缓存数据。

程序设计:本地 ehcache 缓存 +限流&降级,避免 MySQL 被打死。用户发送一个请求,系统 A收到请求后,先查本地 ehcache 缓存,如果没查到再查 redis。如果 ehcache 和 redis 都没有,再查数据库,将数据库中的结果,写入 ehcache 和 redis 中。

2、缓存穿透

对于系统A,假设一秒 5000 个请求,结果其中 4000 个请求是黑客发出的恶意攻击。

黑客发出的那 4000 个攻击,缓存中查不到,每次你去数据库里查,也查不到。

举个栗子。数据库 id 是从 1 开始的,结果黑客发过来的请求 id 全部都是负数。这样的话,缓存中不会有,请求每次都“视缓存于无物”,直接查询数据库。这种恶意攻击场景的缓存穿透就会直接把数据库给打死。

解决方式

每次系统从数据库中只要没查到,就写一个空值到缓存里去,比如 set -999 UNKNOWN。然后设置一个过期时间,这样的话,下次有相同的 key 来访问的时候,在缓存失效之前,都可以直接从缓存中取数据。 

3、缓存击穿

缓存击穿,就是说某个 key 非常热点,访问非常频繁,处于集中式高并发访问的情况,当这个 key 在失效的瞬间,大量的请求就击穿了缓存,直接请求数据库,就像是在一道屏障上凿开了一个洞。

解决办法:

  1. 将缓存失效时间分散开,比如每个key的过期时间是随机,防止同一时间大量数据过期现象发生,这样不会出现同一时间全部请求都落在数据库层,如果缓存数据库是分布式部署,将热点数据均匀分布在不同Redis和数据库中,有效分担压力,别一个人扛。
  2. 简单粗暴,让Redis数据永不过期(如果业务准许,比如不用更新的名单类)。当然,如果业务数据准许的情况下可以,比如中奖名单用户,每期用户开奖后,名单不可能会变了,无需更新。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

S Y H

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值