主流的应用架构
在客户端与存储器之间引入缓存器,请求先查询缓存层,若有责直接返回,若没有则穿透查询,回写到缓存器并返回,且可依赖于缓存器实现熔断,直接查询缓存器并返回
主流的缓存中间件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多路复用