秋招突击——第九弹——Redis缓存

文章目录

    • 引言
    • 正文
      • 缓存基础
        • 旁路缓存模式(重点)
        • 读穿透(了解)
        • 写穿透(了解)
        • 异步缓存写入模式
        • 面试重点
      • 缓存异常场景
        • 缓存穿透
        • 缓存击穿
        • 缓存雪崩
        • 面试重点
      • 缓存一致性怎么保证?
        • 缓存一致性问题是什么
        • 方案选型
        • 面试重点
      • 分布式锁
        • 分布式锁是什么?
        • 分布式锁有什么特性?
        • 分布式锁常用的方式什么?
          • 最简化版本
          • 支持过期时间
          • 增加owner字段
          • 引入Lua
        • 可靠性是如何保证的?
          • 主从容灾
          • 多机部署
        • 可靠性深究
        • 面试重点
      • 事物
        • 事物是什么?
        • Multi事物
        • Lua做事物
        • Lua主要是redis中干什么的
      • 消息队列
        • 消息队列是什么?
        • redis能做消息队列吗
        • 几种方式对比
      • 秒杀
        • 什么是秒杀
      • 场景应用面试题
    • 总结

引言

  • 感觉进度还是不够快,然后方向有点乱,又是项目,又是八股,又是redis,做项目发现很多没有学过。本来放下了redis,准备直接做项目,结果看着看着,发现redis这里有不会。

正文

缓存基础

  • 将MySQL中的热点数据存储在Redis上,利用其访问高效的特点
    • 二八原则,百分之八十的流量建立在百分之二十的信息上。

怎么做

  • 采用服务端缓存,访问MySQL数据之后的将数据放在redis中进行缓存,下次直接访问redis

缓存的几种模式

  • 旁路缓存模式(Cache-Aside Pattern):这个用的最多
  • 读穿透模式
  • 写穿透模式
  • 异步缓存写入模式
旁路缓存模式(重点)
  • 应用服务直接将缓存当作数据的旁路,直接和缓存进行交互

  • 读取数据的流程

    • 读取数据先读取缓存,缓存中有,那就加载缓存
    • 缓存中没有读取数据库,然后将读取的数据放入缓存中
    • 会预先加载一批数据到缓存中。
  • 写数据流程

    • 直接更新数据库,然后删除缓存

如果不删除缓存,会出现时序性的问题

  • 更新修改数据库和修改缓存的顺序不一样。

  • 适用场景和缺点

    • 读多血少
    • 出现缓存和数据库不一致的情况
读穿透(了解)
  • 应用服务不在和缓存直接交互,而是直接访问数据服务(代理)
    • 由数据服务访问数据库和缓存,使用者不知道数据来源,数据服务会根据情况查询缓存或数据库

特点

  • 服务对于缓存是透明,代码更加简洁
  • 命中性能不如旁路缓存模式,还多了一次服务间调用。

在这里插入图片描述

写穿透(了解)
  • 一个缓存一个数据库,更新操作比较麻烦,先更新数据库,在删除缓存
    • 应用程序访问存储服务源,然后存储服务源完成对应的缓存和数据库操作

在这里插入图片描述
具体应用

  • 写穿透配合读穿透使用
  • 对缓存及时性要求高
  • 不能忍受丢失数据和数据不一致。
异步缓存写入模式
  • 同写穿透,不过先写缓存,然后异步写入数据库,

写入方式

  • 收集写操作,在数据库负载低的时候慢慢写入
  • 一个批量一块写入

优点

  • 降低了请求延迟,减轻了数据库复旦
  • 安全性不够,系统崩溃,内存数据会丢失
面试重点

Redis缓存是如何应用的

  • 旁路缓存,先查询redis,没有的话,再查MySQL,并将数据加载到Redis

Redis设置缓存用什么命令

  • set key value,如果业务要增加一个过期时间,可以使用ex参数,set key value ex 10;

聊聊旁路缓存模式下的查询流程

  • 请求到后台服务之后,先查redis,有就返回数据,没有就查Mysql,并将mysql的数据存入redis,最后返回给用户。

缓存异常场景

缓存穿透
  • 缓存和数据库中都没有的数据,而用户不断发起请求
    • 缓存是被动写的,也就是查不到就去数据库查,然后把数据插入到缓存中,现在就是每次都是查缓存,在数据库也查不到,缓存就被击穿了。
    • 有人频繁利用这类DB中没有的数据不断访问,系统会崩溃

解决方案

  • 接口层校验,对于访存数据做基础校验
  • 对于取不到的数据,设置为key-null,并设置有效时间较短,防止用户在短时间内反复用同一个id暴力攻击
  • 布隆过滤器,用于快速判定某一个元素是否在数据库中。
    • 将字符串通过hash函数映射到不同的二进制位置,并将该位置设置为1
    • 空间时间都很小,但是不完全准
缓存击穿
  • 缓存中没有,但是数据库中有的数据。
    • 某一瞬间并发用户特别多,同时读缓存没有读到数据,又同时去数据库读取数据,压力瞬间增加,崩溃

解决方案

  • 热点数据续期,对于持续访问的数据不断续期,避免过期失效被击穿
  • 进程加互斥锁,重建缓存
    • 线程查询缓存发现缓存不存在,尝试对线程加锁,拿到锁的线程访问数据库,并重建缓存,其他循环重试。
缓存雪崩
  • 大量的应用请求因为异常无法在Redis缓存中进行处理,像雪崩一样,直接打到数据库上。
  • 雪崩的原因
    • 数据大批量到过期时间
    • 查询数据量巨大,引起数据库压力过大,甚至宕机

和缓存击穿的区别

  • 缓存击穿是一条热点数据在Redis没有得到及时重建,
  • 缓存雪崩是一大批数据在Redis中同时失效

解决方案

  • 缓存过期时间设置随机,防止同一时间大量数据过期现象发生
  • 加互斥锁重建缓存
    • 当线程查询缓存发现数据不存在事,加锁,多个线程抢锁,拿到锁的线程查询数据库,重建缓存,失败则循环重试。
面试重点

缓存击穿和缓存穿透有什么区别

  • 击穿是单个热点数据在缓存中不存在,但是在数据库中存在,数据库被一瞬间的流量击垮的场景
  • 穿透是某个key不在数据库也不在缓存中,被海量击打

缓存雪崩和缓存击穿有什么区别

  • 缓存适量的数量,击穿是一个key失效,雪崩是大量的key 失效。

缓存击穿怎么解决

  • 热点数据续期
  • 加锁重建缓存,防止MySQL被击穿。

缓存一致性怎么保证?

缓存一致性问题是什么
  • 缓存是数据库的冗余存储,如果数据库信息更改了,缓存的数据和数据库不一致怎么办?
  • 解决办法
    • 更新MySQL即可,不管Redis,以过期时间兜底
    • 更新MySQL之后,操作Redis
    • 异步将MySQL更新同步到Redis

仅仅更新MySQL,不管Redis,过期时间兜底

  • 仅仅更新MySQL,等redis过期了,自动从MySQL重建更新,在这段时间内数据是不一致的。
    • 时间太长,会产生很多脏数据
    • 时间太短,会出现缓存击穿

优点

  • redis原生接口,开发成本低,易于实现,直接使用ex
  • 管理成本低,出概率的问题较小

缺点

  • 完全依赖过期时间,时间太短容易造成缓存频繁失效,太长会有较长的时间不一致。

更新MySQL,立刻更新Redis

  • 除了使用过期时间进行兜底,还会将缓存数据直接删除,借此减少数据不一致的情况出现

优点

  • 想对方案一,达成最终一致性的延迟更小
  • 实现成本低,只是在方案一的基础上,增加了删除的逻辑

不足

  • 更新mysql成功,删除redis却失败,就退化为方案一
  • 在更新的时候需要额外操作redis,性能损耗

异步将MySQL更新到MySQL

  • 将消费服务作为mysql的一个salve,订阅mysql的binlog日志,解析日志的内容,在更新到redis中
    • 业务和数据访问完全解耦,redis更新对业务更加透明(阿里的canal组件)

优点

  • 和业务完全解耦,在更新mysql是,不需要额外操作
  • 无时序性问题,可靠性抢。

缺点

  • 成本高,引入了消息队列,还需要搭建同步服务。
  • 服务崩溃了,redis没法更新。
方案选型
  • 时效性要求很高,一致性要求很高,不要使用缓存
  • 通常来说,使用过期兜底是行之有效的方式,还可以增加一个删除,增加时效性。
  • 解耦层面,使用方案三。
面试重点

缓存一致性问题是什么?

  • MySQL数据更新了,redis作为缓存,数据应该怎么保存一致性的问题。

redis做旁路缓存,如果mysql更新了,该何去何从

  • 在项目中使用过期时间来兜底,并在更新DB后,增加删除操作提升一致性的问题。
  • 设计方案的时候,考虑过订阅binlog日志的方式,会成本高,并且代码复杂。

什么情况下,适合订阅binlog方式

  • 这种模式更像是同步数据,比较适合缓存很长时间过期、或者不过期的场景,如果一个视频网站,有几个视频流量很大,基本不会动,是稳定的,可以使用这种方式。

分布式锁

分布式锁是什么?

  • 针对某项资源使用权限的管理,用来控制共享资源。

分布式锁

  • 在分布式场景下的锁,多台不同机器上的进程,去竞争同一项资源。
分布式锁有什么特性?

互斥性

  • 只让一个竞争者持有锁,其他进程无法持有。

安全性

  • 避免锁因为异常永远无法释放
    • 一个进程因为异常在持有锁的时候崩溃了,所拥有的锁能够被兜底释放,保证后续其他的竞争者也能够获得锁。

对称性

  • 同一个锁,加锁和解锁的必须要是用一个进程
    • 不能把其他竞争者持有的锁释放了

可靠性

  • 有一定程度的处理能力和容灾能力
分布式锁常用的方式什么?
最简化版本
  • 使用setnx实现,因为setnx是在变量不存在的时候,设置一个新的变量,并返回1,如果变量存在,就返回0。
    • 设置一个lock变量,每一次进程访问时候,就声明这个变量,操作完成之后,在删除变量,delete lock。在访问期间,其他线程通过setnx lock就会返回0,,无法访问,妙呀!

在这里插入图片描述

支持过期时间
  • 但是上述问题,存在一个问题,就是如果获取锁的某一个线程挂掉了,那么这个锁就石沉大海了,后续的所有线程都没有办法进行访问了。所以,需要增加一个过期时间来兜底。

setnx lock 1 ex limit-times

  • 即使获取锁的进程崩溃了,但是只要超过了limits-times,仍旧能够解锁,变量就过期了,其他进程仍旧能够获取锁。

但是仍旧存在一个问题,就是会出现其他进程删对应的锁的情况

增加owner字段
  • 分布式锁需要满足谁申请谁释放的原则,不能释放别人的锁,分布式锁要有归属
  • 在编程过程中,都是使用进程id作为锁的值
set key owner(uuid) nx ex limit-seconds
  • 在获取锁的时候,会检查这个锁的值是不是自己的进程号,判定这个锁有没有变化。
引入Lua
  • 上述操作都不是原子性的,通过Lua将若干操作整合为原子操作。通过lua脚本,将若干操作合并成原子操作,保证结果的一致性 。
  • Lua是一个脚本

上述所有操作保证了对称性、安全性、互斥性

可靠性是如何保证的?
  • 上述所有方式都是针对单机模型而言的,如果redis挂了怎么办?常见方式有两种,分别是
    • 主从容灾
    • 多级部署
主从容灾
  • 为redis配置从节点,当主节点挂了,直接用从节点进行顶包。
    • 但是主从切换需要配置哨兵模式,实现主从节点自动切换,容纳灾祸

在这里插入图片描述
缺点

  • 主动节点数据同步需要时间,会丢掉部分数据
  • 分布式锁在失效的过程,会有多个机器同时获取执行权限
多机部署
  • 尝试多机部署,使用redis中的redlock
    • redlock:奇数个机器进行部署,然后半数以上的机器同意加锁才能成功,可靠性会向ETCD靠拢

具体执行流程

  • 有5个redis主节点,保证不会同时宕机,申请锁的时候,同时向5个主节点申请锁
  • 数量超过一半同意,获取锁,如果超过一半失败,向所有redis主节点发送解锁指令
  • 因为向五个节点发送锁请求,会有一个时间限制,如果在锁持有时间内,仍旧不能获取所有锁,就是失败。

优点

  • 增加灾难容错率,给运维进一步增加修复时间
  • 单点reids 的所有可靠性保持手段都可以使用,所以增加了可靠性
可靠性深究

没有完全可靠的分布式锁,NPC困境

N网络延迟

  • 当分布式锁获得返回包的时间过长,锁会很快就过期。
    • 锁剩余时间,减去请求时间,解决锁的网络延迟问题

P进程暂停

  • 进程在进行垃圾回收时,锁过期了,其他进程仍旧能够获得锁,两个进程就获得了同一个分布式锁。
  • GC的时间无法掌控,看门狗会随着GC而阻塞,也无法解决

C时钟漂移

  • 不同机器发生时间漂移,会出现同时获得一个锁的情况,无解。

具体来说,需要配合业务开展,不是完全可靠的

面试重点

分布式锁实现要点是什么?

  • 加锁的时候,要设置owner和过期时间
    • owner便于解锁的时候,进行拥有者的判断
    • 过期时间,对异常情况进行兜底
  • 解锁的时候,要先判断owner是否是自己,是自己在释放
    • 使用Lua脚本,保证操作的原子性

为什么要引入owner概念

  • 分布式锁需要保证对称性,谁加锁,就谁解锁
    • 具体情况说明,某一个服务执行时间太久,然后锁过期了,其他服务又获取了锁。

你提到Lua,Lua一定能够保证原子性吗?

  • Lua本身不具有原子性,通过Lua保证原子性是因为Redis是单线程执行的,一个线程放进Lua来执行,相当于打包再一次,redis执行的过程中,不会被其他请求打断

分布式锁是完全可靠的吗?

  • 没有完全可靠的分布式锁,关键业务需要靠幂等来兜底
  • 当然也可以使用redlock集群化的分布式锁,这种模式出问题的概率很低。

事物

事物是什么?
  • Mysql中的事物具有ACID原子特定,Redis不具有原子性和持久性。
  • 多个操作被看作是一个整体,也就是事物。事物通常具有原子性。
Multi事物
  • 通过multi关键字,进入多任务队列,执行对应的操作,后续所有输入的命令都会进入queue中
    • 使用exec指令,执行queue中的所有命令
    • 使用discard指令,丢弃最终的事物
    • 使用watch保证事物中的变量不会丢弃和改变。

multi事物具有原子性吗

  • 不具备,因为只是通过单线程的特性,其他操作切不进来
  • 但是因为中途自己崩溃,或者自己的问题只做一半,就没有意义了

multi事物的缺点

  • 弱原子性,事物开启之后每一个命令都是一次调用,浪费资源。
  • watch难用
  • 失败了还会继续执行,没意义
Lua做事物

Lua是什么

  • 标准C语言编写的轻量脚本语言
    • 目的是为应用程序提供扩展

Redis和Lua

  • redis通过内嵌支持lua环境,执行脚本的常用命令是eval
  • redis是单线程,处理过程中,不会被打断并切换到其他处理
    • redis执行lua脚本具有原子性

具体执行指令

eval “return {redis.call('set' ,'ka','a1'),redis.call('icnr','ka')}

优势

  • 可以编写if-else选择逻辑
  • 事物中间执行事变,会中断后续执行
  • 使用方便,开启lua就可以执行事物
Lua主要是redis中干什么的
  • 分布式锁和秒杀场景

消息队列

消息队列是什么?
  • 传递消息的队列
  • 用于
    • 异步流程、消息分发、流量削峰
  • 优点
    • 是实现高性能、高可用和高扩展的架构
      常见的消息队列中间件
  • Kafka,RabbitQ,MetaMQ等
redis能做消息队列吗
  • 可以,适合做一个轻量级的消息队列,常见的有三种方案

List做消息队列

  • 将数据放入list,消息作为商品,这就是一个典型的生产者和消费者模式。
    • 通过rpush和lpop实现消息的传递,但是这里不知道消息队列什么时候有消息,只能轮询

BRPOP和BLPOP

  • 没有消息在队列中,会陷入阻塞,阻塞时间内有消息,再继续执行。
  • 缺点
    • 没有ACK机制,消费失败了,还要放回去
    • 不支持多人消费
      在这里插入图片描述

Pub/Sub生产订阅模式
在这里插入图片描述

  • 使用subscribe channel订阅消息频道
  • 使用publish channel msg往特定频道发送消息

缺点

  • 没有办法实现ACK功能
  • 不支持持久化,redis重启消息会全部丢失,若以pub和sub适合处理不重要的消息

Stream做消息队列

  • redis实现,具有持久化,消费组的功能,但是不考
几种方式对比

List:不需要ACK,不需要消费组,可以使用

PUB\SUB:不需要ACK,不需要持久化,可以用

STREAM:需要ACK,需要消费组,需要持久化,可用

秒杀

什么是秒杀
  • 一瞬间产生巨大流量的场景

需要注意以下几个问题

  • 海量请求,服务要抗住
  • 不能超卖
  • 避免少买
  • 保证触达是用户不是黄牛

没有相关的要求,直接跳过

场景应用面试题

实际使用redis做什么应用

  • 缓存和消息队列

redis缓存一般是如何使用的

  • 旁路缓存

redis做旁路缓存了,如果Mysql做了更新,该何去何从

  • 使用过期时间都低,更新DB之后,删除缓存

redis做秒杀场景可以吗

  • redis因为高性能,经常被用于做秒杀产经,预扣库存,结合Lua脚本,可以在一定程度上保证预扣库存的原子性

redis可以做消息队列吗

  • 可以,但是针对那种轻量级,不需要持久化,不需要ack的简单消息队列可以。

总结

  • 就剩限流器和秒杀没看了,感觉比较偏,我想去的并不是这一类服务,所以还是算了把,不看了,省时间。
  • 下面就是结合项目看一下项目的数据库怎么做的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/34303.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[职场] 策略运营求职简历范文精选 #知识分享#微信#微信

策略运营求职简历范文精选 策略运营是用户运营的一种模式,主要针对于用户量级在千人到百万人规模的运营。下面是策略运营求职简历范文精选,供大家参考。 个人信息 姓名:蓝山 年龄:33岁 地址:北京 工作经验&#x…

C++STL梳理

CSTL标准手册: https://cplusplus.com/reference/stl/ https://cplusplus.com/reference/vector/vector/at/ 1、STL基础 1.1、STL基本组成(6大组件13个头文件) 通常认为,STL 是由容器、算法、迭代器、函数对象、适配器、内存分配器这 6 部分构成&…

icloud 邮箱登入失败

APP NAME mail2HOSTING APP NAME cloudos2CLIENT TIME Tue Jun 11 2024 09:00:47 GMT0800 (中国标准时间) (1718067647802)USER AGENT Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36HOSTNAME www.icloud.…

使用阿里开源的Spring Cloud Alibaba AI开发第一个大模型应用

背景 前段时间看到Spring推出了SpringAI,可以方便快速的接入ChatGPT等国外的大模型,现在阿里巴巴也紧追脚步推出了Spring Cloud Alibaba AI,Spring Cloud Alibaba AI 目前基于 Spring AI 0.8.1 版本 API 完成通义系列大模型的接入。通义接入…

【Spine学习15】变换约束

变换约束:能让一个骨骼受另一个骨骼的变化影响。 1、选择m创建一个变换约束: 2、点击这个约束, 将移动数值拉的越满,m越接近s骨骼 当约束为0也就是默认的时候,m骨骼将不会受影响,变换约束可有可无。 tips…

Mysql简述

Java - sql语句学习 sql分类 sql语句 sql数据类型

《数字图像处理》实验报告一

一、实验任务与要求 1、用 matlab 编写空间域点处理操作处理给定的几幅图像,要求: 使用 imread 读取当前工作目录下的图像设计点处理操作并用代码实现处理用 imnshow 显示处理后的图像用 imwrite 保存处理后的图像 2、提交内容:m文件 实验…

ARM单片机使用CAN总线部署BootLoader

1.引言 1.1.单片机开发BootLoader意义 单片机开发BootLoader的原因主要与其在嵌入式系统中的关键作用有关。BootLoader是硬件启动的引导程序,它在操作系统内核或用户应用程序运行之前执行。以下是单片机开发BootLoader的主要原因: 初始化硬件设备&…

算法设计与分析:并查集法求图论桥问题

目录 一、实验目的 二、问题描述 三、实验要求 四、算法思想 1. 基准算法 1.1 算法思想 1.2 代码 1.3 时间复杂度 2. 使用并查集的高效算法 2.1 算法思想 2.2 代码: 2.3 时间复杂度: 五、实验结果 一、实验目的 1. 掌握图的连通性。 2. 掌…

卷积的通俗解释

以时间和空间两个维度分别理解卷积,先用文字来描述: 时间上,任何当前信号状态都是迄至当前所有信号状态的叠加;时间上,任何当前记忆状态都是迄至当前所有记忆状态的叠加;空间上,任何位置状态都…

python怎样自动提示

第一步、打开pycharm,如下图所示: 第二步、File→Power Save Mode,把下面如图所示的勾去掉: 第三步、去掉勾后,不再使用省电模式,新建一个python文件。输入单词前两个字母,就会自动提示了&#…

为什么说大模型训练很难?

前言 在人工智能的浪潮中,大模型训练无疑是一股不可忽视的力量。然而,这背后的过程却充满了挑战与困难。今天,让我们一同揭开大模型训练的神秘面纱,探讨为何它值得您的关注与投入。 大模型训练的挑战 大模型训练之所以难&…

选择门店收银系统要考虑哪些方面?美业系统Java源码分享私

开店前的一个重要事件就是选择门店收银软件/系统,尤其是针对美容、医美等美业门店,一个优秀专业的系统十分重要,它必须贴合门店的经营需求,提供更全面、便捷、高效的管理功能,帮助提升门店的服务质量和经营效益。 以下…

Python笔记 文件的读取操作

1.open()打开函数 再Python,使用open函数,可以打开一个已经存在的文件,或者创建一个新文件,语法如下 open(name,mode,encoding) name:是要打开的文件名的字符串(可以包含文件所在的具体路径) mode&…

【几何】多少正方形?

题目枚举边长为1边长为 2 \sqrt{2} 2 ​边长为 5 \sqrt{5} 5 ​边长为 8 \sqrt{8} 8 ​边长为 13 \sqrt{13} 13 ​ 扩展-使用代码来数1、定义点对象2、定义正方形对象3、初始化所有点4、调用完整代码 题目 多少正方形? 枚举 设每个横纵相邻点得间距为1&#xff0…

线程池概念、线程池的不同创建方式、线程池的拒绝策略

文章目录 💐线程池概念以及什么是工厂模式💐标准库中的线程池💐什么是工厂模式?💐ThreadPoolExecutor💐模拟实现线程池 💐线程池概念以及什么是工厂模式 线程的诞生是因为,频繁的创…

3D Web轻量化引擎HOOPS Commuicator是如何创建AEC查看器的?

在当今数字化时代,建筑、工程和施工(AEC)行业正经历着一场技术革命。HOOPS Communicator,一款基于HOOPS Web平台的3D Web轻量化引擎,正是这场革命的先锋之一。本文将探讨HOOPS Communicator是如何创建AEC查看器的&…

【CentOS 7】深入指南:使用LVM和扩展文件系统增加root分区存储容量

【CentOS 7】深入指南:使用LVM和扩展文件系统增加root分区存储容量 大家好 我是寸铁👊 【CentOS 7】深入指南:使用LVM和扩展文件系统增加root分区存储容量 ✨ 喜欢的小伙伴可以点点关注 💝 前言 在运行CentOS 7服务器或虚拟机时&a…

【扫雷游戏】C语言详解

Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~ 💥💥个人主页:奋斗的小羊 💥💥所属专栏:C语言 🚀本系列文章为个人学习…

lvs集群 Keepalived

Keepalived高可用集群 Keepalived概述 功能 LVS规则管理LVS集群真实服务器状态监测管理VIP Keepalived实现web高可用 安装keepalived软件 在webservers上配置 启动服务 webservers systemctl start keepalived.service ip a s | grep 192.168 #web1主机绑定vip 测试…