Redis缓存中间件使用

Redis简介

Redis是一个开源的使用C语言编写、支持网络、可基于内存可持久化的日志型,Key-Value数据库,并提供多种语言的API。

Redis 优势

1.性能极高-Redis能读的速度是110000次/s,写的速度是81000次/s
2.丰富的数据类型 -支持Strings, Lists, Hash, Sets, Ordered Sets ,GEO和Stream,目前有7种数据类型。
3.原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
4.丰富的特性 – Redis还支持 publish/subscribe, 通知, key 过期等等特性。

Redis的官网 https://redis.io/
Redis中文网 http://www.redis.cn/

官网.png

中文网.png

Redis Server For Windows 下载地址 https://github.com/tporadowski/redis/releases

image.png

Redis 数据类型

1.String 字符串

string是redis最基本的类型,一个key对应一个value。
string类型是二进制安全的。意思是redis的string可以包含任何数据。比如jpg图片或者序列化的对象 。
string类型是Redis最基本的数据类型,一个键最大能存储512MB。

2.Hash 哈希

Redis hash 是一个键值对集合。
Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

3.List 列表

Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边)。

4.Set 集合

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

5.zset(sorted set:有序集合)

Redis zset 和 set 一样也是string类型元素的集合,且不允许重复的成员。
不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。
zset的成员是唯一的,但分数(score)却可以重复。

6.GEO 地理位置

Redis 3.2版本开始对GEO地理位置的支持
使用场景:LBS应用开放

7.Stream

Redis 5.0版本开始支持新的结构 “流”
使用场景:生成者 消费者 场景 类似MQ
官网文档介绍 https://redis.io/topics/streams-intro#streams-basics

常用命令

//官网命令大全
https://redis.io/commands

//切换到redis目录下运行 
//开启服务 
redis-server --service-start 
或者开启单个条件的服务
redis-server conf/redis-6379.conf
//关闭服务 
redis-server --service-stop 
//查看版本
redis-server --version
redis-server -v
// 查看 执行的命令
monitor

//连接redis数据库:
redis-cli.exe -h 127.0.0.1 -p 6379
//查看是否redis开启情况
ps -ef |grep redis

//杀死进程
kill -9 进程id
//清空db
flushdb

//设置键值对 myKey 值abc
set myKey abc
//取出键值对 
get myKey
//删除key
del mykey
//key类型
type mykey
//是否存在key
exists mykey
//遍历所有的key
keys *




stream使用
sadd stream_1 * phone 10086 content hello
// 帮助
xinfo help
//stream消息
xinfo stream stream_1
//遍历 stream_1 从小到大的方式
xrange stream_1 - +
//读取2条数据,按时间由远到近
xrange stream_1 - + count 2
//遍历 stream_1 按时间由近到远
xrevrange stream_1 + - count 2

SpringBoot集成Redis

1.引入依赖包



    org.springframework.boot
    spring-boot-starter-data-redis



    org.apache.commons
    commons-pool2

spring-boot-starter-data-redis依赖于spring-data-redislettuce.
Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接。

2.添加配置文件

# Redis数据库索引(默认为0,一般默认有16个数据库)
spring.redis.database=0  
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379  
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=8
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=0

3.添加 cache 的配置类

@Configuration
//配置Spring Cache注解功能
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport{
    
    //配置redisTemplate 对象转换
    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        RedisTemplate redisTemplate = new RedisTemplate();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //配置对象转换规则,比如使用json格式对object进行存储
        //Object -> 序列化 -> 二进制 -> redis-server存储
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
        return redisTemplate;
    }
    
    // 配置Spring Cache注解功能,把redis的连接管理交给springcache管理
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, redisCacheConfiguration);
        return cacheManager;
    }
}
  1. 代码业务使用
/**
* 手动使用的方式
**/
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestRedis {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void test() throws Exception {
        stringRedisTemplate.opsForValue().set("aaa", "111");
        Assert.assertEquals("111", stringRedisTemplate.opsForValue().get("aaa"));
    }
    
    @Test
    public void testObj() throws Exception {
        User user=new User("[email protected]", "aa", "aa123456", "aa","123");
        ValueOperations operations=redisTemplate.opsForValue();
        operations.set("test1", user);
        operations.set("test2", user,1, TimeUnit.SECONDS);
        Thread.sleep(1000);
        //redisTemplate.delete("test2");
        boolean exists=redisTemplate.hasKey("test2");
        if(exists){
            System.out.println("exists is true");
        }else{
            System.out.println("exists is false");
        }        
       // Assert.assertEquals("aa", operations.get("test2").getUserName());
    }
}
//自动根据方法生成缓存
@RestController
public class UserController {
   //其中 value 的值就是缓存到 Redis 中的 key
   @RequestMapping("/getUser")
   @Cacheable(value="user-key")
   public User getUser() {
       User user=new User("[email protected]", "aa", "aa123456", "aa","123");
      //从数据库读数据等业务操作
       return user;
   }
   
   /**
    * springcache注解版本(官方大部分资料开始往springboot方向引导,实际上不用springboot,也是差不多的方式)
    */
   // value~单独的缓存前缀
   // key缓存key 可以用springEL表达式
   // value 在redis中 {value::key} 如:cache-1::userid
   @GetMapping("/getCache/{key}")
   @Cacheable(cacheManager = "cacheManager",key = "#userId",value = "cache-1")
   public void findById(@PathVariable("key") String userId){

       System.out.println("");
   }
   //删除
   @CacheEvict(cacheManager = "cacheManager", value = "cache-1", key = "#userId")
   public void deleteUserById(String userId) throws Exception {
       System.out.println("用户从数据库删除成功,请检查缓存是否清除~~" + userId);
   }

   // 如果数据库更新成功,更新redis缓存
   @CachePut(cacheManager = "cacheManager", value = "cache-1", key = "#user.userId", condition = "#result ne null")
   public Student updateUser(Student user) throws Exception {
       // 读取数据库
       System.out.println("数据库进行了更新,检查缓存是否一致");
       return user; // 返回最新内容,代表更新成功
   }

}

发布 订阅

@Autowired
RedisTemplate redisTemplate;

/** 用于通道名称 */
public final static String TEST_CHANNEL_NAME = "sms_send";

redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {//发布消息
               Long received= connection.publish(TEST_CHANNEL_NAME.getBytes(),
                        "发布一条新消息,请注意查收".getBytes());
                return received;
            }
        });


redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {//订阅 消息
                connection.subscribe(new MessageListener() {
                    @Override
                    public void onMessage(Message message, byte[] pattern) {
                        System.out.println("收到消息,使用redisTemplate收到的:" + message);
                    }
                },TEST_CHANNEL_NAME.getBytes());//消费消息

                return null;
            }
        });


redisTemplate.execute(new RedisCallback() {
            @Override
            public Long doInRedis(RedisConnection connection) throws DataAccessException {
                connection.subscribe((message, pattern) -> {
                    System.out.println("收到消息,使用redisTemplate收到的:" + message);
                }, "__keyevent@0__:del".getBytes());//当有key删除的时候,会发出消息
                return null;
            }
        });

持久化

Redis的数据都存放在内存中,如果没有配置持久化,redis重启后数据就全丢失了,于是需要开启redis的持久化功能,将数据保存在磁盘上,当redis重启后,可以从磁盘中恢复数据。
redis的持久化有2种:RDB持久化 和 AOF (append only file)持久化
RDB持久化

RDB持久化方式能够在指定的时间间隔数据进行快照存储

在Redis的目录下有个配置文件,redis.conf文件,

#900秒之内至少一次写操作
#save 900 1
#300秒之内至少发生10次写操作
#save 300 10
#60秒之内发生至少10000次写操作
#save 60 10000
image.png

REB的优缺点
优点:对性能影响最小;RDB文件进行数据恢复比使用AOF要快很多
缺点:同步是会丢失数据;如果数据集非常大且cpu不够强,Redis在fork子进程时
可能会消耗相对长的时间,影响Redis对外提供服务的能力。

AOF (append only file)持久化

AOF持久化方式记录每次对服务器写的操作,当服务器重启的时候重新执行这些命令来恢复原始的数据

优点:同步不会丢失数据
缺点:对性能会有影响

开启AOF持久化

appendonly yes
image.png

AOF策略调整

//每次有数据修改发生时都会写入AOF文件
appendfsync always
//每秒钟同步一次,该策略为AOF的缺省策略
appendfsync everysec
//不同步,高效但是数据不会被持久化
appendfsync no

内存管理

不同的数据类型的大小限制:
String类型:一个String类型的value最大可以存储512M。
List类型:list元素个数最多为2^32-1个,即4294967295个。
Set类型:元素个数最多为2^32-1个,即4294967295个。
Hash类型:键值对个数最多为2^32-1,即4294967295个。

#最大内存控制
maxmemory 最大内存阈值
maxmemory-policy 到达阈值的执行策略
image.png

内存压缩

#配置字段最多512个
hash-max-zipmap-entries 512
#配置value最大64字节
hash-max-zipmap-value 64
#配置元素个数最多512个
list-max-ziplist-entries 512
#配置value最大64字节
list-max-ziplist-value 64
#配置元素个数最多512个
set-max-intset-entries 512
#配置元素个数最多128个
zset-max-ziplist-entries 128
#配置value最大为64字节
zset-max-ziplist-value 64

过期数据的处理策略
1.主动处理,redis主动触发检测key是否过期,每秒执行10次

a.从具有相关过期的秘钥机制测试20个随机秘钥
b.删除找到的所有秘钥已过期
c.如果超过25%的秘钥已过期,步骤a重新开始

被动处理:

每次访问key的时候,发现超时后被动过期,清理掉

数据恢复阶段过期数据
RDB方式

过期的key不会被持久化到文件中。
载入时过期的key,会通过redis的主动和被动方式清理掉

AOF方式

每次遇到过期的key,redis会追加一条DEL命令到AOF文件。

Redis 内存回收策略
在配置文件中设置:

#
maxmemory-policy noeviction

或者动态设置:config set maxmemory-policy noevication


image.png

maxmemory-policy [回收策略]


image.png

LRU算法
(Least recently used,最近最少使用):根据数据的历史访问记录来进行淘汰数据。

LRU算法并非完整的实现,完整的实现LRU实现是因为需要太多的内存
方法:通过对少量keys进行取样50%,然后回收其中一个最好的key
配置方式 maxmemory-samples 5

LRU算法
(Least Frequently Used)根据数据的历史访问频率来淘汰数据

每次对key访问时,基于概率的对数计数器来记录访问次数,同时这计数器会随着时间推移而减少

主从复制

主从复制:一台服务器做主redis-server,另一台服务器做从redis-server,主redis-server的数据会同步到从redis-server。

为什么使用主从复制?
redis-server单点故障;单节点QPS有限;

主从复制使用场景
读写分离场景,规避redis单机瓶颈;故障切换,master出问题后还有slave节点可以使用

搭建主从复制

主Redis-server 普通模式启动,主要是启动从服务器的方式:
第一种方式:命令行

//连接需要实现 从节点 的redis,执行命令
slaveof [ip] [port]
//加入集群
slaveof 127.0.0.1 6380
//退出集群
slaveof no one 
//防火墙关闭
systemctl stop firewalld.service
// 是否连接上
telnet 127.0.0.1 6380
image.png

image.png

第二种方式:redis.conf配置文件

#配置文件中增加
slaveof [ip] [port]
#从 服务器 是否只读(默认yes)
slave-read-only yes
#加入集群
slaveof 127.0.0.1 6379

退出主从集群方式:

//退出主从集群方式
slaveof no one
//新版本把slaveof 改成replication
replication 
image.png

redis server 新版本 把slaveof 改为了 replacaof


检查主从复制

info replication
image.png

主从复制流程

image.png

image.png

主从复制应用场景

image.png

主从复制主要事项:
读写分离场景:

数据复制延时导致读到过期数据或者读不到数据(网络原因或者slave阻塞);
单节点故障(多个client如何迁移)

全量复制情况下:

每一次建立主从关系或者runid不匹配会导致全量复制;
故障转移的时候会出现全量复制

复制风暴

master故障重启,如果slave节点较多,所有salve都要复制,对服务器的性能,网络压力有很多大的影响;
如果一个机器部署了多个master

哨兵高可用机制

一般在3台服务器以上就可以考虑使用哨兵。
比如:一台是主redis-server,2台式从redis-server,用3个哨兵,一个监控主,2个监控从。

配置文件 (可以在redis的目录下建conf目录,在里面建配置文件)
redis-6379.conf,redis-6380.conf,redis-6381.conf配置文件

#后台启动
daemonize yes
#端口
port 6380
#0.0.0.0.0 或者127.0.0.1 
bind 0.0.0.0
#这个文件会自动生成(如果同一台服务器上启动,注意要修改不同的端口)
pidfile /www/server/redis/redis_6380.pid 

3个服务

启动3个服务
redis-server conf/redis-6379.conf
redis-server conf/redis-6380.conf
redis-server conf/redis-6381.conf

配置1主2从
redis-cli -p 6380 slaveof 172.17.0.12 6379
redis-cli -p 6381 slaveof 172.17.0.12 6379

检查主从集群
redis-cli -p 6380 info Replication
redis-cli -p 6381 info Replication

哨兵配置文件
可以在redis的目录下建conf目录,在里面建配置文件
sentinel-26379.conf,sentinel-26380.conf,sentinel-26381.conf,把相应的端口号改掉。

#配置文件,sentinel.conf,在sentinel运行期间会被动态修改
#sentinel如果启动时,根据这个配置来恢复之前所监控的redis集群状态
bind 0.0.0.0
#后台运行
daemonize yes
#默认yes,没指定密码或者指定ip的情况下,外网无法访问
protected-mode no
#哨兵 端口
port 26380
#哨兵自己的ip,手动设定也可自动发现,用于与其他哨兵通信
#sentinel announce-ip 临时文件
dir /temp
#日志
logfile "/www/server/redis/logs/sentinel-26380.log"
#sentinel监控的master的名字叫做mymaster,
#初始地址172.17.0.12 6380 ,2代表2个以上哨兵认定死亡,才认为是真的死亡
sentinel monitor mymaster 172.17.0.12 6380 2
#发送心跳ping来确认master是否存活
#如果master在 一定时间范围 内不回应ping,或者是回复一个错误消息
#那么这个sentinel会主观(单方面)认为这master已经不可用了
sentinel down-after-milliseconds mymaster 1000
#如果在该时间内ms 内未完成failover操作,则认为该failover失败
sentinel failover-timeout mymaster 3000
#指定了在执行故障转移时,最多可以有多少个从Redis实例在同步新的主实例,
#在从Redis实例较多的情况下这个数字越小,同步时间越长,完成故障转移所需的时间就长
sentinel parallel-syncs mymaster 1

启动哨兵集群

redis-server /conf/sentinel-26379.conf --sentinel
redis-server /conf/sentinel-26380.conf --sentinel
redis-server /conf/sentinel-26381.conf --sentinel
image.png

测试

#停掉 master 主从切换过程
启动哨兵(客户端通过哨兵发现Redis实例信息)
哨兵通过连接master发现主从集群内的所有实例信息
哨兵监控redis实例的监控状态
哨兵一旦发现master不能正常提供服务,,则通知给其它哨兵
当一定数量的哨兵都认为master挂了
选举一个哨兵作为故障转移的执行者

哨兵是如何指定redis主从信息

在哨兵的配置文件中,保存着 主从 集群中master的信息,可以通过 info 命令,进行主从信息自动发现。

什么是主观下线sdown

主观下线:单个哨兵自身认为redis实例已经不能提供服务
检测机制:哨兵向redis发送ping请求,+pong,-loading,-masterdown这三种情况视为正常,其它回复均为无效
对应的配置文件配置项:

sentinel down-after-milliseconds mymaster 100

什么是客观下线

客观下线:一定数量值的哨兵认为master已经下线
检测机制:当哨兵主观认为master下线后,则会通过 sentinel is-master-down-by-addr 命令 询问其它哨兵是否认为master已经下线,如果达成共识(达到quorum个数),则会认为master节点客观下线,开始故障转移流程。

哨兵之间通信

哨兵之间自动发现:哨兵之间会发布自己的信息和订阅其它哨兵的信息

//查看通道里的订阅消息
pubsub channels
//发布一个 _sentinel_:hello 的信息
subscribe _sentinel_:hello
//
image.png

哨兵领导选举机制

image.png
//文档
https://raft.github.io/
http://thesecretlivesofdata.com/

slave 选举方案
服务器实例按顺序筛选:
slave节点状态,非s_down,o_down,discounnected

判断规则:(down-after-milliseconds*10)+millisecondes_since_master_is_in_sdown_state
sentinel slaves mymaster

优先级:

redis.conf中的一个配置项:slave-priority值越小,优先级越高

数据同步情况

Replication offset processed

最小的run id

run id 比较方案:字典顺序,ASCII码

最终主从切换的过程
针对即将成为master的slave节点,将其撤出主从集群,自动执行:
slaveof no one

针对其他的slave节点,使它们成为新master的从属,自动执行:
slaveof new_master_host new_master_port

你可能感兴趣的:(Redis缓存中间件使用)