redis系统总结

主流的应用架构

在客户端与存储器之间引入缓存器,请求先查询缓存层,若有责直接返回,若没有则穿透查询,回写到缓存器并返回,且可依赖于缓存器实现熔断,直接查询缓存器并返回
在这里插入图片描述
主流的缓存中间件Memcache与Redis的区别

Memcache:代码层面类似于hash

  • 支持简单的hash数据存储类型
  • 不支持数据持久化存储
  • 不支持主从
  • 不支持分片

reids:

  • 支持多种数据类型存储,hash、list、set、string
  • 支持数据持久化存储
  • 支持主从
  • 支持分片

为什么redis可以那么快?

redis支持100000+ QPS(QPS即query per second,每秒查询次数)

  • 完全基于内存,读写操作很快
  • 数据结构简单,数据操作简单
  • 采用单线程设计,这里的单线程设计主要指主线程处理网络请求,单线程也能处理高并发请求,想多核也可启动多个实例
  • 使用IO多路复用,非阻塞IO用来解决单线程设计规则,防止线程中断

redisIO多路复用模型

Redis采用的IO多路复用函数:epoll/kqueue/evport/select?

  • 因地制宜
  • 优先选择O(1)的IO多路复用模型
  • 以时间复杂度O(n)的select进行保底(因为底层使用的轮询,故而时间复杂度对O(n))
  • 基于react设计模式监听I/O事件

redis中常用的数据结构

  • String 相关命令为set name redis ; get name ;set name 1;incr name;

底层使用的数据结构为

/****
**保存字符串的结构
*/
struct sdshdr{
		//buff 中已占用空间的长度
		int len;
		//buff 中剩余可用空间长度
		int free;
 		//数据空间
 		char buff[];
}
  • hash结构,特别适合用与存储对象,底层是将对象转换为json字符串
hset lxf name lxf age 18 sex 1
hget lxf name
hget lxf age
  • List:列表,按照String插入顺序排序
10.212.130.5:6380> lpush mylist aaa
-> Redirected to slot [5282] located at 10.212.130.5:6379
(integer) 1
10.212.130.5:6379> lpush mylist bbb
(integer) 2
10.212.130.5:6379> lpush mylist ccc
(integer) 3
10.212.130.5:6379> 
10.212.130.5:6379> lrange mylist 0 10
1) "ccc"
2) "bbb"
3) "aaa"
10.212.130.5:6379>
  • Set : String元素组成的无序集合,通过Hash表实现,不允许重复
10.212.130.5:6379> sadd myset aaa
(integer) 1
10.212.130.5:6379> sadd myset aaa
(integer) 0
10.212.130.5:6379> sadd myset bbb
(integer) 1
10.212.130.5:6379> sadd myset ccc
(integer) 1
10.212.130.5:6379> smembers myset
1) "bbb"
2) "ccc"
3) "aaa"
  • Sorted Set: 通过分数为集合中的成员进行从大到小排序
10.212.130.5:6379> zadd myzset 3 abc
(integer) 1
10.212.130.5:6379> zadd myzset 2 adc
(integer) 1
10.212.130.5:6379> zadd myzset 3 adc
(integer) 0
10.212.130.5:6379> 
10.212.130.5:6379> 
10.212.130.5:6379> zadd myzset 1 acc
10.212.130.5:6379> zrangebyscore myzset 0 10
1) "acc"
2) "abc"
3) "adc"
  • 用于计数的HyperLogLog,多数用于在线用户统计
10.212.130.5:6379> pfadd 1 2 3 4 1 2 3 2 2 
-> Redirected to slot [9842] located at 10.212.130.5:6380
(integer) 1
10.212.130.5:6380> 
10.212.130.5:6380> 
10.212.130.5:6380> pfcount 1
(integer) 4
  • 用于支持存储地理位置信息的Geo,支持存储地理位置信息用来实现诸如附近位置、摇一摇这类依赖于地理位置信息的功能.geo的数据类型为zset.
10.212.130.5:6380> geoadd cities 179 -70 tianjintianjin
(integer) 1
10.212.130.5:6380> geopos cities tianjintianjin
1) 1) "179.00000005960464478"
  2) "-69.99999889346560167"

在Redis中查询符合一定前缀的key

10.212.130.5:6380> keys l*
1) "lxf"

数据量较大的情况该指令会造成生产环境卡顿
解决方式使用SCAN,其中0代表游标,match参数为匹配的规则,count为每次返回的结果个数

10.212.130.5:6380> scan 0 match l* count 10
1) "0"
2) 1) "lxf"

使用redis实现分布式锁

分布式锁要解决的问题

  • 互斥性,同一时刻只有一个客户端获取到该锁
  • 安全性,锁只能由持有的客户端删除,不能由其他客户端删除
  • 死锁,获取锁的客户端由于其他原因宕机,无法释放锁,其他客户端也获取不到该锁
  • 容错,提供redis的节点宕机后,客户端也可以获取到锁

第一种解决方案

1.使用setnx key value ,该值若key存在,则设置失败,返回0,成功返回0
2.使用expire key value,设置某个key的过期时间,默认为s
程序实例如下:

long status = redisService.setnx(key,"1");
if(status ==1){
	redisService.expire(key,expire);
	//执行独占资源逻辑
	doSomeThing();
}
/*此方法的缺点是违反原子性,
setnx与expire应该是原子操作才可实现分布式锁,但该程序这样写如果在setnx之后程序出现异常,则锁就被长期占有,形成死锁
*/

针对上述问题的解决方案使用新set的指令同时设置key与设置过期时间,指令如下

set locktarget request_id/session_id ex 10 nx
//ex 设置过期时间为10秒
//nx 设置存储key为类似setnx模式,若设置为xx 则key存在时才可以设置成功

代码实例如下

String result = redisSevice.set(lockKey,requestID, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME,expiresTime);
if("ok".equals(result)){
   //独占资源需要执行的逻辑代码
   doSomeThing();
}

衍生思考,若key大批量集中过期,如何解决

解决思路为在设置过期时间时使用随机数

第二种解决方案使用redisson

//1.引入maven依赖
<dependency>
       <groupId>org.redisson</groupId>
       <artifactId>redisson</artifactId>
        <version>2.7.0</version>
</dependency>

//2.配置Redisson
public class RedissonManager {
   private static Config config = new Config();
   //声明redisso对象
   private static Redisson redisson = null;
  //实例化redisson
static{

    config.useSingleServer().setAddress("127.0.0.1:6379");
         //得到redisson对象
       redisson = (Redisson) Redisson.create(config);

}

//获取redisson对象的方法
   public static Redisson getRedisson(){
       return redisson;
   }
}

//3.锁的获取与释放
public class DistributedRedisLock {
  //从配置类中获取redisson对象
   private static Redisson redisson = RedissonManager.getRedisson();
   private static final String LOCK_TITLE = "redisLock_";
  //加锁
   public static boolean acquire(String lockName){
      //声明key对象
       String key = LOCK_TITLE + lockName;
      //获取锁对象
       RLock mylock = redisson.getLock(key);
      //加锁,并且设置锁过期时间,防止死锁的产生
       mylock.lock(2, TimeUnit.MINUTES); 
       System.err.println("======lock======"+Thread.currentThread().getName());
      //加锁成功
       return  true;
   }
 //锁的释放
   public static void release(String lockName){
      //必须是和加锁时的同一个key
       String key = LOCK_TITLE + lockName;
      //获取所对象
       RLock mylock = redisson.getLock(key);
     //释放锁(解锁)
       mylock.unlock();
       System.err.println("======unlock======"+Thread.currentThread().getName());
   }
}

//调用过程
@RequestMapping("/redder")
   @ResponseBody
   public String redder() throws IOException{
        String key = "test123";
       //加锁 
       DistributedRedisLock.acquire(key);
       //执行具体业务逻辑
       dosoming
       //释放锁
       DistributedRedisLock.release(key);
      //返回结果
       return soming; 
   }

如何使用redis构建一个异步队列

利用redis指令rpush 与lpop指令可以简单实现,但是若消息队列为空则直接返回,俩种方案解决,休眠几秒重试或者使用blpop指令解决

redis中的订阅/消费模式

//订阅指令,myTopic为订阅的主题名
subscribe myTopic
//发布指令
publish myTopic "hello"

Redis数据持久化解决方案

第一种RDB快照方式,对应的redis.conf的文件配置如下

save 900 1  //代表如果900秒内有1次写入操作,则产生一次快照
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes // 代表如果redis主进程出错后,则不再写入数据,为了保证数据备份的一致性
rdbcompression yes  //备份时将RDB文件进行压缩后在保存
//redis 保存rdb快照的指令为
save //会阻塞主线程
bgsave //非阻塞主线程,后台启用一个子线程执行

第二种使用AOF实现redis记录操作持久化,其对应的redis.conf如下

appendonly yes
appendfilename "appendonly.aof"
appendfsync no/everysec/always //分别对应缓存填满才会进行备份,每秒进行备份一次,数据区一旦变动则进行备份

第三种使用RDB-AOF混合持久化模式

BGSAVE做镜像全量持久化,AOF做增量持久化

什么是redis的二八定律?

redis的二八定律是指80%的业务需求命中在20%的数据中,所以产生二八定律,并引入缓存的应用

什么是缓存雪崩?

redis缓存雪崩指的redis中的缓存数据大面积过期,导致很多数据请求到底层数据库,从而导致数据库压力过大导致宕机事故

解决缓存雪崩的关键是?
1.对存储的数据设置随机的过期时间
2.设置缓存过期标记,若过期则自动执行数据查询并缓存

什么是缓存穿透?

redis缓存穿透指的是在缓存中为获得请求数据后,又查询底层数据库,依旧未获取到数据,故而重复请求俩次无用操作。

解决缓存穿透的关键是?
1.当第一次数据库查询数据为空时,缓存空结果,设置较短的过期时间,防止二次请求有重新进行2次无效查询

什么是缓存预热?

redis缓存预热指的是在系统上线前对热点数据进行预存储,从而避免首次查询时,进行数据缓存

什么是缓存更新

redis缓存更新指的是对redis中的数据进行过期操作,redis内部机制支持6种数据过期操作

在redis.conf中配置

maxmemory-policy volatile-lru //该参数值有6种选择

  • volatile-lru -> 根据LRU算法生成的过期时间来删除
  • allkeys-lru -> 根据LRU算法删除任何key。
  • volatile-random -> 根据过期设置来随机删除key。
  • allkeys->random -> 无差别随机删。
  • volatile-ttl -> 根据最近过期时间来删除(辅以TTL)
  • noeviction -> 谁也不删,直接在写操作时返回错误。

为什么redis是单线程&redis这么快

多快?

Redis是采用基于内存的单进程单线程模型的KV数据库,由C语言编写,官方数据是100000+QPS(每秒查询的数据量)

为什么快?

1.完全基于内存操作,内存IO速率很高,且redis数据库类似于HashMap的数据操作,时间复杂度为O(1),查找存储效率很高
2.数据结构简单,对数据操作也简单
3.采用单线程,避免了不必要的进程上下文切换和竞争条件,也不会存在多线程之间切换的CPU时间消耗,也不存在加锁、释放锁的问题
4.采用IO多路复用模型,且实用NIO,非阻塞IO

什么是IO多路复用模型?
多路指的是多个网络连接,复用指的是使用用一个线程进行处理,其原理是使用NIO编程,利用select、channel、buffer进行数据传输,利用select进行对多个客户端连接的监听,从而完成IO多路复用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值