Redis(一)核心数据结构与高性能

核心数据结构,高性能
先来说说redis的核心数据结构,总得来说,他是一个key.value类型,他的key是统一类型,但redis可以有很多不同的value,总的来说有string类型,hash类型,list类型,set类型和zset类型。他的这些类型都提供了操作的api。特殊的还有bitmap类型,

String

先来说string类型吧,他是我们最常用的类型,他的value就是普通的字符串类型,他提供的基本api有普通的set,get方法,还有批量操作的mset,mget方法,还有判空的setnx,还有删除和设置过期时间,del和expire。除了基本的增删查操作外,string还提供了原子加减命令incr和decr。字符串的应用场景有单值缓存,对象缓存,存session和最简单的分布式锁等等。单值缓存就不说了,平常用得最多的。来说一下对象缓存,他需要和hash结构进行对比,string的对象缓存有两种,一是把对象序列化成json来存入value,用set来存整体,二是把对象拆分成单个属性来存,那么他的key就是对象名和属性一起做为key,用mset来进行批量存储,显然第二种比第一种灵活,但都没有hash结构的对象存储灵活后面说。string类型还能做分布式锁,利用他提供的setnx和del来做,原因就在于redis是单线程的,天然就能解决并发问题,请求锁时用setnx,释放锁时用del。还有一些其他的应用场景,比如string提供的自增操作可以用来做阅读量的统计,还可以用他的自增操作来做分布式id分配,要注意的是,他的自增每次不要只自增一,因为这样太慢了,命令太多,而redis又是单线程的,太影响性能,最好是每次incr自增1000,那么相当于一次性产生了一千个id,然后在java代码中就可以进行具体的分配。他还提供keys指令可以扫出指定模式的key列表。

虽然keys指令很好,但是redis是单线程,keys指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用scan指令,scan指令可以无阻塞的提取出指定模式的key列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用keys指令长。

Hash

接下来说一下hash结构,他的value是map形式,类似于map嵌套map的结构,他的常用api就hset和hget,还有获取map的容量的hlen命令,自增命令hincreby,再来说他的应用场景吧,他可以很灵活的做对象缓存,key就是class名,value中的key就是object名加属性名,这样就定位好了属性,在value的value中进行存储,他和string的对象存储更加灵活,想改数据也很容易,因为检索属性很快,而string需要把json反序列化成对象,改完再序列化为json,不够灵活,而且比string操作的性能更高,消耗cpu少,更省内存,所以hash很适合用来做对象缓存,但他也有很多缺点,最大的缺点是没办法设置过期时间,不是不能,而是过期时间的粒度太大,作用的是整个key,而不能具体要value的每个map。在应用场景上,这个map嵌套map的结构就能很容易得来完成需求,就比如电商的购物车,他有很明显的层次结构,而如果用mysql这种关系性数据库的话,就很复杂,而用hash就可以用一条命令来实现各种操作。用户id作为key,用商品id和商品数据就能当作内层map,此时想要加商品数据,减商品数据就能用他的自增操作和hdel,商品总数也能用hlen,获取所有商品可以用hgetall。如果用mysql的话,他是关系性数据库,没有层次结构,设计的表就比较复杂。

List

再来说说list结构,他的特性就是,他的value是一个列表,他的命令命令也是push和pop之类的,但是需要注意的是,他可以双向增删数据,如果命令前面是l开头,那就是操作表头,如果命令前面是r开头,那就是操作表尾,相当于双向列表。他还提供了索引,就是返回指定范围索引的数据,又像是数组。他还提供了阻塞的性质,也就是以b开头的命令,所以可以用list来做阻塞队列。因为他的这些灵活的命令,我们就能用他来模拟各种数据结构,比如栈,队列,阻塞队列。这是他的一个应用场景,他还有一个应用场景是消息流列表,就类似于微博和微信公众号发的消息那种,用list来装数据,每个消息进行压入list,用range命令传入索引,来获取最新的指定条数的消息。还可以根据lrange命令读取某个闭区间内的元素,可以做一个高性能分页

如果用list来做消息队列中间件的话,这种消息队列没有ack,无法保证消息的可靠性,做小系统时可以用,避免了RabbitMQ的交换机和队列等操作。问题是如果队列为空,那么陷入pop的死循环,解决方法一是让线程sleep睡一秒了再去pop,二是list提供了一个blpop(block)用来当list为空时阻塞住,有值再返回客户端,这样就不需要客户端轮询了。如果要一条消息被多次消费,redis还提供发布订阅模式,总共只有6个命令。如果要实现延时队列的话,使用sortedset,拿时间戳作为score,消息内容作为key调用zadd来生产消息,消费者用zrangebyscore指令获取N秒之前的数据轮询进行处理。

Set

还有就是set结构,是无序集合,会自动去重的那种,他的value是一个set集合,也是一系列值的集合,他提供了很多有意思的,很实用的命令,他因为是s开头,所以他的命令的名字有了比较大的改变,增删查长度分别是,sadd,srem,smember,scard。除了传统的增删查获长度的操作外,还提供了集合操作,比如交集,差集,还有随机抽取的命令,可以用这个随机抽取的命令来做抽奖,而且非常适合,甚至可以轻松实现普通抽奖和一等奖二等奖的命令,对于普通抽奖,就是srandmember count来抽取指定人数,用这一条命令搞定,他的特点是抽取完后不删除元素,而一等奖二等奖的需求中,显然,获得了二等奖的人不能获得一等奖,那么就用spop一条命令来实现。除此之外,还能用set来实现点赞,收藏。这些需求的特点就是有一个状态,点赞状态和未点赞状态,还要知道自己是否已经点赞。用set来实现就很适合,前两个操作用sadd和srem来实现,他们的key是事件名称+消息id,相当于一个消息id对应多个用户。重点在于查看自己是否已经点赞也能只用一条命令sismember,用他传入自己的用户id就能很快知道自己是否已经点赞。获取点赞数量和具体用户也可以用命令scard和smember命令。set应用最广泛的是他的集合操作,提供了交集并集差集,分别是sinter,sunoin和sdiff最经典的是用户关注模型,可以实现两个用户之间的共同关注sinter,我关注的人也关注他sismember,我可能关注的人sdiffer。再一个经典场景是电商商品筛选,每个维度的分类都是一个set,我们选商品时多个维度就能用交集来实现了,新增商品时维护好各个维度,如果用mysql的多表查询的话,数据量太大时很慢,而用redis时,支持高并发。

用set还能统计网站的UV,因为他天然去重,但是如果流量太大,set结构就太大,而且UV没有必要太准确,此时就能用HyperLogLog 提供的两个指令 pfadd 和 pfcount,根据字面意义很好理解,一个是增加计数,一个是获取计数,他做到了用很小的结构,存大量数据,而且去重。如果是PV,不用去重,用redis的计数器的incrby就可以了。

ZSet

最后一个数据结构是zset,他类似于set,但是那个value对应有一个score,根据这个score进行排序,是一个有序集合,用他可以来实现热度排行榜,阅读量每增加一次,相应的score就加一。可以显示热度前十用zrevrange+key+0+9+可选参数withscores。不止如此,还可以进行集合运算zunionstore。除此之外,redis还有其他的应用场景,比如,附近的人,摇一摇,搜索自动补全

Bitmap

bitmap是一个特殊的数据结构,他的value只能是0和1,很方便用来存boolean类型,比如是否签到,就很适合用bitmap来做,他的结构是key offset value,查询时,通过key offset就能够拿到value,他解决的痛点是,签到如果用key value来存一年的签到情况,那么一个用户一年就表示365个数据,太大了。而bitmap的用月份做key时,offset可以让一个value存一个月的签到情况,且value是bit类型,很节约空间,十分合适。对于连续签到,还可以用他提供的bitcount,传入参数start,offset来算特定偏移量的1的个数。还可以用来存用户的在线状态,用用户id作为offset,不光查找很快,而是3亿用户的在线状态只用36M。同理用来统计一天的活跃用户。

HyperLogLog

之前提到的HyperLogLog已经能够实现精度不高的估数了,但是他不支持contains,所以不能用来去重。所以,在大数据的去重上就要使用布隆过滤器了。用set的话,需要的存储太大,而布隆过滤器就可以大大降低去重的存储消耗。

高性能

再来说一下redis 的单线程模型和高性能。redis的模型是由三个部分组成的,类似于netty的线程模型,其实并不相同。首先是用来连接了socket客户端io多路复用程序,他是用epoll实现的,实现了高并发的客户端链接信息,将连接信息和事件放到队列中,依次放到第二部分文件事件分派器,然后再分派到事件处理器,他里面有个分类,把事件分类处理,包括链接应答处理器,命令请求处理器,命令回复处理器。

至于redis是单线程为什么这么快,他的最主要的原因是他的所有数据存在内存中,所有命令的运算都是内存级别的运算,相当快。其次的原因是单线程避免了多线程切换的性能开销,缺点是我们要避免使用那些很耗时的命令,比如keys列出所有的key,完成redis卡顿。除非scan无阻塞。且多核CPU时完全可以多开几个redis实例。数据结构结构简单,操作高效。

上下文切换

高级用法:Bitmap做签到,HyperLogLog做UV,Geospatial附近的人,半径位置,最优地图路径,pub/sbu订阅发布功能做简单的消息队列,Pipeline批量操作一次性返回全部结果,Lua脚本

高级命令info显示运行状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值