记得应该是4年前,开始接触redis,那时候刚好需要一个高性能的kv存储,经过对比和分析选择了redis作为当时的存储应用。
具体的一些知识,大家可以参阅我博客的历史文章,直接搜索redis应该可以获取很多结果,当然导航部分也有redis的系列专题。
其实这几年redis改变很多,优化了很多,所以本文和大家分享一些redis相对更进阶一些的部分内容和用法。
1、redis的数据结构
一般都会被首先问到redis都有哪些数据结构,多数真的有看过的人,都可以答出来redis有5种常用数据结构,分别是字符串、哈希、list、set、sortedset(zset)。这是基础的回答,如果你要展现你了解的更多的话,你还应该说出,redis还支持geo、HyperLogLog以及pub/sub。
这时候你应该知道这些数据结构的用处都有哪些,基础的字符串、哈希、set就不说了。
这里介绍一下list、zset、geo、HyperLogLog、pub/sub。
1.1、list
list就是列表存储结构,除了可以直接作为列表的存储来使用,其实list还可以作为一个队列使用,另外,list还支持阻塞的方式从一端取值,来达到阻塞队列的使用。
1.2、zset
zset具有根据score来排序存储数据的作用,所以用处就很多了。
比如可以每次更新数据,都不删除旧数据,而是把新数据存储下来,使用当前时间戳作为score的值,在取值的时候直接取最新的值作为当前值返回。这样可以在需要的时候找到这个值的历史记录。(当然可以根据实际情况,只保存zset中最新的n个值,可以通过异步线程来触发这个操作)
另外,zset还有个很有用的用处,就是作为排行榜使用。比如投票,以投票id作为key,被投票人id作为value,票数作为score。再每次某个被投票人获得票之后,增长某个被投票人id对应的score即可,再展现排行榜的时候,按照score大小排序获取需要展示的名词区间就可以了。
1.3、geo
这个其实没有什么特别要说的,做地理位置的计算很方便。
1.4、HyperLogLog
这是个神奇的数据结构。其实这个数据结构我在之前的文章也介绍过,大家有兴趣可以点击这里去详细了解一下。这个数据结构是基于基数统计的方法来实现的,可以比较准确的估算出数据的数量,但是没有办法知道里面具体的内容。它有一个很大的优点就是占用的内存量非常低,多低呢,不管里面存的什么,一个键的值都只会占用12k的内存。
1.5、pub/sub
这实际上不算数据结构了,这只是redis提供的一个功能而已。我们上面提到了列表,可以用作队列来做生产消费。那如果想要生产一份数据,多个客户端消费呢,这时候就到pub/sub上场的时候了。但是它也会有一个问题,如果生产者发送了一个消息,消费者的客户端没有在线,那么就算这个客户端再上线了也不会收到以前的消息了。
2、失效时间问题
由于redis存储使用的是内存储存,内存肯定是要比硬盘存储的成本高很多的了,所以一般我们会把redis作为缓存使用。这样就需要对数据做失效处理。有时候,我们会把一部分kv存储并做失效处理,而很有可能一些key的失效时间比较集中(一般我们都会设置一个固定的缓存失效时间),这样在失效的时候很有可能会产生瞬时的redis卡顿。
怎么避免产生这种情况呢,最好的办法就是对失效时间做个随机数增加的处理。这样就可以避免一些数据再同一时刻失效了。
其实这种解决问题的思路同样适用于其他应用场景上。比如我们之前有一个前端页面,需要再指定的时刻去获取一个结果页面自动展示给用户看,这个结果页面比如1点可以获取到。那么如果前端就根据服务器返回时间计算之后就在1点准时获取的话,那么服务器就会在1点的瞬时获取大量请求,就会产生风险。其实对于这种实时性没有那么强的需求,可以做一个随机数的增加,让所有用户平均分配在一个允许的时间范围内请求就好了。
3、分布式锁
第一部分说了那么多数据结构的问题,其实最常用的主要还是字符串类型的结构。而这其中很常用的一个场景就是分布式锁。
先说说为什么需要分布式锁,首先锁的使用肯定是为了锁定资源,这里就不多说了。由于现在我们都是使用的分布式应用,如果只是在某一个应用中对某个资源进行加锁(本地锁,比如jdk的锁),那么只是在这个服务器上加锁生效,但是其他服务器上仍然是未加锁状态。所以我们需要使用分布式锁来处理这个问题。
使用分布式锁有一个点一定需要注意,就是一定要设置失效时间,这样假如由于某些原因锁没有释放,不至于导致这个资源被彻底锁定无法使用。
一般我们会使用setnx来做加锁,这个命令在key已经存在的时候,设置失败,只有在key不存在的时候才能设置成功,从而达到加锁的目的。另外失效时间通过expire命令来实现。那么问题来了,如果再setnx设置成功了之后,expire设置失败了怎么办,这样在出现没有解锁的时候这个锁就无法被释放了。
在以前的版本的确会出现这个问题,但是在2.6.12版本之后,redis拓展了set函数的参数。可以通过set函数来同时达到nx和expire的功能。
参数是这样的:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
EX表示过期时间单位是秒,PX表示毫秒,NX表示不存在设置,XX表示存在才设置。
下面举几个例子。
1 2 3 4 5 6 |
#不存在才设置,有效期600s set key value EX 600 NX set key value PX 600000 NX #存在才设置(覆盖),有效期600s set key value EX 600 XX set key value PX 600000 XX |
有了现在的set参数的拓展,我们就不用担心两个命令第一个成功第二个失败导致问题了。
4、其他
另外我们还需要了解pipeline、主从同步机制、集群的相关信息。
我们在使用redis的时候,实际上有很大一部分是网络IO的性能损耗(其实公司内网的网络IO损耗也不会很大),pipeline的作用就是把多个命令通过一次网络IO发过去执行。由于是多个命令一次发送,所以这些命令有一个要求就是不能有依赖关系。
新上节点的同步机制其实是两段同步,第一次同步会先进行全量同步但是再同步的过程中肯定还是会有数据的变化在产生的,所以在全量同步结束之后,还需要做一次这段时间数据变化的增量同步来达到数据同步。
集群其实有两种方式,一种是以twemproxy为代表的Sentinal模式,一种是redis3.0提供的Cluster模式,两种模式各有优劣。Sentinal在于高可用,Cluster模式在于拓展。详细的对比大家有兴趣可以详细研究一下。
到此简单介绍了下redis的一些进阶使用,希望可以帮到大家更好的学习redis。
©原创文章,转载请注明来源: 赵伊凡's Blog
©本文链接地址: Redis高级应用