面试四:高并发 + redis

1.秒杀并发控制设计

1.页面设置成静态html

2.限流: 鉴于只有少部分用户能够秒杀成功,所以要限制大部分流量,只允许少部分流量进入服务后端。

3.削峰:对于秒杀系统瞬时会有大量用户涌入,把瞬间的高流量变成一段时间平稳的流量。
实现削峰的常用的方法有利用缓存和消息中间件等技术。

4.异步处理:秒杀系统是一个高并发系统,采用异步处理模式可以极大地提高系统并发量,
其实异步处理就是削峰的一种实现方式。nio

5.内存缓存:秒杀系统最大的瓶颈一般都是数据库读写,如果能够把部分数据或业务逻辑转移到内存缓存,
效率会有极大地提升。redis

id:选择标识符
select_type:表示查询的类型。
table:输出结果集的表
partitions:匹配的分区
type:表示表的连接类型
possible_keys:表示查询时,可能使用的索引
key:表示实际使用的索引

key_len:索引字段的长度
ref:列与索引的比较
rows:扫描出的行数(估算的行数)
filtered:按表条件过滤的行百分比
Extra:执行情况的描述和说明

3.redis是单线程吗?为什么这么快?

单线程

1.绝大部分请求是纯粹的内存操作(非常快速)
2.采用单线程,避免了不必要的上下文切换和竞争条件
3.非阻塞IO 内部实现采用epoll,采用了epoll+自己实现的简单的事件框架。

4.redis数据类型

String
Hash
List
Set
Sorted set(zset)

list类型的应用场景 :
 1. 消息队列
  list类型的lpop和rpush(或者反过来,lpush和rpop)能实现队列的功能,故而可以用Redis的list类型实现简单的点对点的消息队列。
  2. 排行榜
  list类型的lrange命令可以分页查看队列中的数据。
  
5.redis和Memcached区别

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

3.分布式缓存

redis、Memcached

redis和Memcached区别

1 、Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
2 、Redis支持数据的备份,即master-slave模式的数据备份。
3 、Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
4 、redis是单线程,memcached是多线程

4.分布式redis缓存击穿

1、使用互斥锁排队:根据key获取value值为空时,锁上,从数据库中load数据后再释放锁。
若其它线程获取锁失败,则等待一段时间后重试。

2、接口限流与熔断、降级:重要的接口一定要做好限流策略,防止用户恶意刷接口,同时要降级准备,
当接口中的某些服务不可用时候,进行熔断,失败快速返回机制(Hystrix:服务熔断,服务降级)

3、布隆过滤器:bloomfilter就类似于一个hash set,用于快速判某个元素是否存在于集合中,
其典型的应用场景就是快速判断一个key是否存在于某容器,不存在就直接返回。

5.分布式缓存雪崩

缓存在同一时间内大量键过期(失效),接着来的一大波请求瞬间都落在了数据库中导致连接异常。

解决方案:

方案1、也是像解决缓存穿透一样加锁排队,实现同上;

方案2、建立备份缓存,缓存A和缓存B,A设置超时时间,B不设值超时时间,先从A读缓存,A没有读B,
并且更新A缓存和B缓存;

方案3、设置缓存超时时间的时候加上一个随机的时间长度,比如这个缓存key的超时时间是固定的5分钟
加上随机的2分钟,酱紫可从一定程度上避免雪崩问题;

6.redis哨兵模式

哨兵模式是一种特殊的模式,首先Redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立运行。其原理是哨兵通过发送命令,等待Redis服务器响应,从而监控运行的多个Redis实例。

这里的哨兵有两个作用

通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。

当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

故障切换:假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行故障切换过程,仅仅是哨兵1主观的认为主服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且数量达到一定值时,那么哨兵之间就会进行一次投票,投票的结果由一个哨兵发起,进行故障切换操作。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称为客观下线。这样对于客户端而言,一切都是透明的。

哨兵的工作方式:

1):每个哨兵以每秒钟一次的频率向它所知的Master,Slave以及其他 哨兵 实例发送一个 PING 命令
2):如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过 down-after-milliseconds 选项所指定的值, 则这个实例会被 哨兵标记为主观下线。
3):如果一个Master被标记为主观下线,则正在监视这个Master的所有 哨兵要以每秒一次的频率确认Master的确进入了主观下线状态。
4):当有足够数量的 哨兵(大于等于配置文件指定的值)在指定的时间范围内确认Master的确进入了主观下线状态, 则Master会被标记为客观下线
5):在一般情况下, 每个 哨兵 会以每 10 秒一次的频率向它已知的所有Master,Slave发送 INFO 命令
6):当Master被 哨兵标记为客观下线时,哨兵向下线的 Master 的所有 Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次
7):若没有足够数量的 哨兵同意 Master 已经下线, Master 的客观下线状态就会被移除。
若 Master 重新向 哨兵的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。

7.redis 持久化的两种方式

RDB:RDB 持久化机制,是对 redis 中的数据执行周期性的持久化。
AOF:AOF 机制对每条写入命令作为日志,以 append-only 的模式写入一个日志文件中,在 redis 重启的时候,可以通过回放 AOF 日志中的写入指令来重新构建整个数据集。
redis 支持同时开启开启两种持久化方式,我们可以综合使用 AOF 和 RDB 两种持久化机制

8.如何保证缓存与数据库的双写一致性?
1.读的时候,先读缓存,缓存没有的话,就读数据库,然后取出数据后放入缓存,同时返回响应。
2.更新的时候,先更新数据库,然后再删除缓存。

9.redis分布式锁

public class RedisLock {
private StringRedisTemplate redisTemplate;

/**
 * 加锁
 * @param key 被秒杀商品的id
 * @param value 当前线程操作时的 System.currentTimeMillis() + 2000,2000是超时时间,这个地方不需要去设置redis的expire,
 *              也不需要超时后手动去删除该key,因为会存在并发的线程都会去删除,造成上一个锁失效,结果都获得锁去执行,并发操作失败了就。
 * @return
 */
public boolean lock(String key, String value) {
    //如果key值不存在,则返回 true,且设置 value
    if (redisTemplate.opsForValue().setIfAbsent(key, value)) {
        return true;
    }

    //获取key的值,判断是是否超时
    String curVal = redisTemplate.opsForValue().get(key);
    if (StringUtils.isNotEmpty(curVal) && Long.parseLong(curVal) < System.currentTimeMillis()) {
        //获得之前的key值,同时设置当前的传入的value。这个地方可能几个线程同时过来,但是redis本身天然是单线程的,所以getAndSet方法还是会安全执行,
        //首先执行的线程,此时curVal当然和oldVal值相等,因为就是同一个值,之后该线程set了自己的value,后面的线程就取不到锁了
        String oldVal = redisTemplate.opsForValue().getAndSet(key, value);
        if(StringUtils.isNotEmpty(oldVal) && oldVal.equals(curVal)) {
            return true;
        }
    }
    return false;
}

/**
 * 解锁
 * @param key
 * @param value
 */
public void unlock(String key, String value) {
    try {
        String curVal = redisTemplate.opsForValue().get(key);
        if (StringUtils.isNotEmpty(curVal) && curVal.equals(value)) {
            redisTemplate.opsForValue().getOperations().delete(key);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值