一、官网查阅和基本配置
1、redis官网
Redis。官网
2、命令大全
3、redis6的版本
需要使用大于6.0.8,redis-server -v
二、redis的单线程和多线程
1、redis为什么选择单线程
redis各个版本之间架构都不同,所以这个问题需要从不同版本去描述
- 3.x,单纯的单线程
- 数据结构简单
- 避免锁和线程切换的开销
- 基于内存
- 多路复用和非阻塞IO:redis使用IO多路复用功能来监听多个socket连接,这样就可以使用一个线程来处理多个请求,减少线程切换的开销,同时避免了IO阻塞
- 4.x,逐步开始引入多线程,负责处理客户端请求的线程是单线程,异步多线程删除
- 6.x,使用了多线程,但是默认是关闭的
由于单线程的阻塞的问题,例如出现了bigkey删除的时候,导致官方放弃了单线程的架构。
redis的单线程,主要是指redis的网络IO和键值读写是又以恶搞线程来完的,redis在处理客户端的请求时候包括如下:
以上操作都是一个顺序串行的主线程处理,这个就是所谓的单线程。redis采用Reactor模式的网络模型,对于一个客户端请求,主线程负责一个完成的处理过程。 但是其它的功能,比如持久化、异步删除、集群数据同步等都是由额外的线程执行的。
结论:redis工作线程是单线程,对于redis整体来说是多线程。对于redis系统瓶颈在于网络和内存。
2、既然单线程这么好,为什么逐渐增加了多线程
单线程时代,最头疼的问题之一,就是大key的del,当删除一个很大的key的时候,就会导致其它命令全部阻塞,只有key删除完毕,其它命令才能执行。
基于以上情景,redis4就设计了异步删除:unlink key。其它的耗时操作,也都可以使用异步的方式,从主线程剥离一个bio子线程进行操作。
3、redis6多线程和IO多路复用入门
因为redis的瓶颈在于网络IO和内存,而内存无法通过系统改变,所以redis系统优化的主要方向就是网络IO。
Unix五种网络IO模型:
- BlockingIO(阻塞IO)
- NoneBlocking IO(非阻塞IO)
- IO multiplexing IO(IO多路复用):redis使用的模型,可以得出redis安装在Unix服务器上性能才是最佳
- signal driven IO(信号驱动IO)
- asynchronous IO(异步IO)
IO多路复用,是一种IO模型,即经典的Reactor设计模式:
IO多路复用,简单的来说就是通过检测文件的读写事件再通知线程执行的相关操作,保证Redis的非阻塞I/O能够顺利执行完成的机制。多路是指多个socket连接、复用是指一个线程。
多路复用主要由以下三种技术:
- select
- poll
- epoll:redis使用的,就是让单个线程处理多个连接请求
在redis6中,增加了多线程处理IO的读写性能,主要实现思路是将主线程的IO读写任务拆分给一组独立的线程去执行,这样可以使多个socket读写并行化了,采用多路IO复用技术,可以让单个线程高效的处理多个连接请求(尽量减少网络IO的时间消耗),将最耗时的socket的读取、请求解析、单独外包出去,而执行命令还是交给主线程。
结论:IO操作多线程,核心操作单线程。
4、redis6默认是否开启了多线程
redis6默认的多线程是关闭,因为单线程大部分场景已经够用。
- io-threads-do-reads:yes表示启动多线程
- io-threads:设置线程数,官方建议4核设置为2~3,8核设置为6
三、代码整合的基础案例
TK mapper 类似于 mybatis-plus,都是在mybatis上进行二次封装,一键生成。
数据库主键,一般情况可能存在100xx、200xx、300xx,表示不同含义的数据。
出现上述情况,客户端连接的时候,使用--raw参数即可。
- 缓存击穿:是需要有才能击穿,所以是大量key到期了,导致请求进入到的DB
- 缓存穿透:穿透,就是DB和缓存都没有数据,导致大量请求进入到DB
为了防止缓存击穿,可以使用 DCL(double check lock),代码如下:
public User findUserById2(Integer id)
{
User user = null;
String key = CACHE_KEY_USER+id;
//1 先从redis里面查询,如果有直接返回结果,如果没有再去查询mysql
user = (User) redisTemplate.opsForValue().get(key);
if(user == null)
{
//2、对于高QPS的优化,进来就先加锁,保证一个请求操作,让外面的redis等待一下,避免击穿mysql
synchronized (UserService.class){
user = (User) redisTemplate.opsForValue().get(key);
//3 二次查redis还是null,可以去查mysql了(mysql默认有数据)
if (user == null) {
//4 查询mysql拿数据
user = userMapper.selectByPrimaryKey(id);//mysql有数据默认
if (user == null) {
return null;
}else{
//5 mysql里面有数据的,需要回写redis,完成数据一致性的同步工作
redisTemplate.opsForValue().setIfAbsent(key,user,7L,TimeUnit.DAYS);
}
}
}
}
return user;
}
四、redis经典五种数据类型介绍及落地运用
现阶段redis不仅仅包含5种经单的数据类型了,其实一共已经有9种:
- String(字符):60%的操作都是stirng
- incr:技术器,点赞、阅读量、喜欢文章等等
- setnx:分布式锁
- Hash(散列):理解成 Map<String, Map<Object, Object>>
- 并发了不高的时候,可以用作购物车
- 并发了不高的时候,可以用作购物车
- List(列表):双端列表
- 商品的评论列表
- 订阅文章:可以把用户订阅的某个博主的文章放入list
- Set(集合):无重复数据集合
- 集合运算,共同关注(交集)、可能认识的人(差集)
- 差集:SDIFF seta setb,属于seta不属于setb
- 交集:SINTER seta setb,seta和setb共有的
- 并集:SUNION seta setb,属于seta和属于setb的
- 抽奖:随机弹出删除元素、随机弹出不删除
- 统计某些数据,例如点赞、参加人数
- 集合运算,共同关注(交集)、可能认识的人(差集)
- SortedSet(有序集合)
- 各种排行,商品或者其它的排序
- 热搜,其实可以看作是排行的一个变种
- 各种排行,商品或者其它的排序
- BitMap(位图)
- HyperLogLog(统计)
- GEO(地图)
- Stream(官方推荐,但是使用比较少,用于MQ):发布定义功能
在redis中,命令不区分大小写,但是key区分大小写,帮助:help@类型:
五、redis新类型bitmap\hyperloglog\GEO
千万、亿级数据收集+统计。需要能够 存的进+取的块+多统计,传统的关系型数据库在部分就很乏力。
1、统计的类型有那些?
- 聚合统计:统计多个集合元素的聚合结果,交、差、并和聚合函数(max、avg等)
- 排序统计:
- 抖音VCR最新评论,需要排序+分页,list和zset都可以,但是推荐zset
- list:可以做,但是因为lpush新元素的时候,所有元素位置都会+1,用户在翻页的时候,会发现“重复”的数据
- zset:ZRANGE 、ZREVRANGE、ZRANGEBYSCORE
- 抖音VCR最新评论,需要排序+分页,list和zset都可以,但是推荐zset
- 二值统计:集合统计只有0和1两种,例如上班打开,使用bitmap
- 基数统计:去重统计,网页登陆UV,使用hyperloglog
2、bitmap
位图,是一个数组,数组上的元素就是0、1
它的出现主要是解决了数据空间的问题,可以极大的节约存储空间,支持最大位数是2^32位,使用512M就可以存放42.9亿的字节信息。
处理场景:
- 用户是否登陆过,比如登陆签到赠送
- 电影、广告是否点击播放过
- 钉钉打卡,签到统计
- 日活、月活用户数统计
- ……
应用签到:用户签到一天就是1个bit位,365天也就是365bit位,时间和空间都能够保证。
bitmap的基本命令(1亿位的bitmap,最大120M):
- setbit key offset value,其中offset从0开始,value只能是0或者1
- getbit key offset
- strlen:统计占用多少字节
- bitcount:包含多少一个1(统计登陆多少天)
- bitop:连续1的个数,联系打卡天数,使用方式挺奇怪的,需要把结果存放到另一个key中,如下图所示,在key 20200808和20200809连续出现的(bitop and deskkey 20200808 20200909),存放到deskkey中。
bitmap底层存放的就是二进制的ascII编码对应:
01100001对应的ascII为 a,如下可知:
bitmap的扩容机制,每增加8位就进行扩容。8bit=1byte。
3、hyperloglog
名次解释:
- UV:同一个IP访问的次数,根据IP去重,一个IP访问4次,算是1次UV
- PV:一共访问了多少次,一个IP访问4次,算是4次PV
- DAU:日活跃用户量,去重同一个用户
- MAU:月活跃用户量
基数:去重后的数据集
当需要统计网站UV、某个页面的UV、统计用户搜索关键词和个数,这类情境下就需要使用到去重复统计功能的基数估计算法-hyperloglog。优点和bitmap相同,统计元素非常大的时候,计算技术所需要的空间总是固定并且很小的,只需要12KB就能计算接近2^64个不同元素的基数,但是hyperloglog只能计算基数,不能想集合一样输入元素本身。
理论上使用bitmap也是可以的,但是个人感觉有两点问题,去重+数据增长。bitmap使用场景本身是二值统计并且虽然所占空间很小,但是当数据量庞大的时候,也是会逐步增加的。
概率算法:通过牺牲准确率来换取空间,对于不需要绝对精确率的场景下可以使用,因为概率算法不直接存储数据本身,通过一定的概率统计方法预估基数值,同时保定误差在一定范围内。hyperloglog就是一种概率算法。通过牺牲准确率来换取空间,误差在0.81%左右。
为什么redis集群的最大槽数是16384个?
Redis集群没有使用一执行Hash(通过hash算法确定放入到哪个节点)而是引入了哈希槽的概念。Redis集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放入到哪个槽,集群的每个节点负责一部分槽。但CRC16算法产生的hash值有16bit也就是2^16=65536,那为什么不使用65536二十16384?
- 如果使用65536个槽位,心跳信息消息头达到8KB,导致发送的数据过于庞大。16384个槽位发送的消息头只有2KB
- redis作者建议redis的集群主集节点没必要超过1000个,因为集群节点越多心跳信息的消息体越大,导致网络拥堵。1000以内的节点,16384个槽位已经够用,没必要扩展到65535
- 因为节点信息使用bitmap传输,槽位太大会影响到bitmap的压缩率
hyperloglog的命令:
- PFADD:添加数据,只不过是计算一下,并不真正存储数据
- PFCOUNT:计算某个key的基数
- PFMERGE:合并两个key的基数并放入到一个新key
4、GEO
redis中提供了地图相关的操作GEO,可以实现附近的XX,如附近的人、附近的美食、附近的车辆等等。
例如用户需要打车,就是查找记录用户(x0,y0)附近r公里范围内的车辆。
GEO命令:
- GEOADD:添加经纬度坐标
- GEOPOS:返回指定位置的坐标
- GEODIST:两个位置之间的距离(m=米、km=千米、ft=英尺、mi=英里)
- GEORADIUS:查找附近的XXX
- GEORADIUSBYMEMBER:根据集合中的元素,查找附近的XXX
- GEOHASH:返回指定位置坐标,使用GEOHASH进行存储