您的位置:首页 > 数据库 > Redis

后端(Java)开发人员使用Redis时的注意事项

2018-01-29 20:53 253 查看
后端开发人员在使用Redis时的注意事项,我们分为设计阶段和使用阶段来讲,先讲设计阶段。

设计阶段

1.缓存数据筛选

我们知道Redis是一个缓存数据库,他的数据都是存放在内存中的,所以能够实现高效的存取和写入,但内存单位的高昂代价注定了其难以取代磁盘,作为数据的最终存储介质。使用缓存最重要的作用就是降低存储层的承受压力,提高请求的响应速度,所以如何选择数据很关键。注定了不能缓存所有数据,那么站在存储层的角度,自然优先缓存那些访问最频繁的数据,也就是所谓的热点数据,如何判断是否为热点数据需要根据实际的业务场景作相应的择取。站在应用的角度,自然是将那些响应时间长的数据做缓存,能够有效的提高用户的使用体验。站在缓存的角度上,自然是希望缓存那些更新不是很频繁的数据,否则频繁的缓存重建就失去了缓存的意义了。站在Redis的角度,自然是希望能够将自身优势发挥出来的,缓存那些数据量不是很大,但是很关键的数据,比如用户登录信息等,同时能够发挥自身特点,比如高速存储和写入,可以执行简单的算术操作,可以设置被动过期时间等。从多个方面考虑缓存数据的筛选问题,是设计阶段应该优先考虑的事情。

2.缓存粒度控制

粒度,就是缓存是数据的相对多少问题。粒度越大,操作时越简单,但占用空间越多,且缓存重建时需要的资源就越多;粒度越小,控制越复杂,但占用空间想小,且缓存重建时需要的资源就越少,这就是一个缓存性能,空间和操作的平衡问题。假设用户的信息由A,B,C三部分组成,每次获取的时候A和B用的较多,C用的不多,此时缓存的策略有4中情况:

1. A,B,C合并后缓存

2. A,B合并缓冲,C不作缓存

3. A,B,C各自分开缓存

4. A,B缓存,C不作缓存

每种缓存策略均有各自的优势及局限性,第一种情况下,从缓存提取简单,但占据空间大,且若A,B,C中的一个数据发生改变均需要重建整个缓存;第二种情况能降低占据空间,但是提高提取缓存的操作复杂性;第三种策略提取操作最复杂,占据空间大,但是重建缓存的性能最好;第四种能降低占据空间,但是提高了缓存重建的复杂性。

如何权衡缓存的粒度控制,需要根据实际业务提前设计好。

3.缓存更新策略

根据不同的业务场景指定不同的缓存更新策略。

一致性:缓存数据和真实数据源的数据一致。

对于低一致性要求的业务场景,可以配置Redis的最大内存配合淘汰策略作用。缓存淘汰策略可以使用LRU(Least Recently Used),LFU(Least Frequently Used)和FIFO(First In First Out)等。

对于高一致性要求的业务场景,可以使用Redis的超时剔除和主动更新策略。

4.缓存穿透优化

缓存穿透是指查询一个根本不存在的数据,缓存层和存储层都不会命中。通常处于容错的考虑,如果从存储层查询不到数据则不会写入缓存层。缓存穿透将导致请求不存在的数据每次都要到存储层去在找,就失去了缓存保护后端存储的意义。

造成缓存穿透的原因主要有两个:

1. 自身业务代码或者数据出现问题

2. 一些恶意攻击或爬虫造成大量空命中

对于缓存穿透,可以给不存在的数据缓存一个空对象,同时设置超时时间。如果在此期间缓存层和存储层的数据不一致,可使用消息系统或者其他操作剔除缓存中的空对象。

5.热点key重建

对于并发量较大的应用,当一个热点key重建时,可能会触发多个线程同时执行重建工作。多个线程同时重建,耗费额外性能生成资源,同时可能会有多次的缓存替换操作,对整体性能可能有一定影响。此时可以使用互斥锁机制,保证同一时间对于同一key只有一个线程能够执行重建工作。但是要注意,如果重建工作耗时较长,可能存在死锁和线程阻塞的风险。

6.缓存雪崩应对

缓存的层级位于客户端和存储层之间,能够有效的降低存储层的压力,但缓存可能存在不可用的情况,如何应对这种情形?

首先自然是降低缓存层的宕机几率,有条件可以使用Redis Sentinel和Redis Cluster。

其次隔离缓存和存储层的数据获取接口,防止缓存的宕机影响存储层的数据获取。

最后在项目上线前演练缓存宕机的情形,在此基础上做一预案设定。

好的架构和代码都需要有一个好的设计,如果设计阶段就出了偏差,那么在编程阶段无论怎么调整都难以弥补。

使用阶段

我们从数据存储和数据获取两个方面来说明开发时的注意事项。

1.数据存储

因为内存空间的局限性,注定了能存储的数据量有限,如何在有限的空间内存储更多的数据信息是我们应该关注的。Redis内存储的都是键值对,那么如何减小键值对所占据的内存空间就是空间优化的本质。

在能清晰表达业务含义的基础上尽可能缩减Key的字符长度,比如一个键是user:{id}:logintime ,可以使用业务属性的简写来u:{id}:lgt,只要能清晰表达业务意义,使用简写形式是有其必要性的。

在不影响使用的情况下,缩减Value的数据大小。如果Value是较大的数据信息,比如图片,大文本等,可以使用压缩工具压缩过后再存入Redis;如果Value是对象序列化或者gson信息,可以考虑去除非必要的业务属性。

减少键值对的数量,对于大量的String类型的小对象,可以尝试使用Hash的形式组合他们,在Hash对象内Field数量少于1000,且Value的字符长度小于40时,内部使用ziplist的编码形式,能够极大的降低小对象占据的内存空间。

Redis内维护了一个[0-9999]的整数对象池,类似Java内的运行时常量池,只创建一个常量,使用时都去引用这个常量,所以当存储的value是这个范围内的数字时均是引用向都一个内存地址,所以能够降低一些内存空间耗费。但是共享对象池和maxmemory+LRU的内存回收策略冲突,因为共享Value对象的lru值也共享,难以通过lru知道哪个Key的最后引用时间,所以永远也不能回收内存。

如果多次数据操作要求原子性,可使用Multi来实现Redis的事务。

2.数据查询

Redis是一种数据库,和其他数据库一样,操作时也需要有连接对象,连接对象的创建和销毁也需要耗费资源,复用连接对象很有必要,所以推荐使用连接池来管理连接。

Redis数据存储在内存中,查询很快,但不代表连接也很快。一次Redis查询可能IO部分占据了请求时间的绝大部分比例,缩短IO时间是开发过程中很需要注意的一点。

对于一个业务内的多次查询,考虑使用Pipeline,将多次查询合并为一次查询,命令会被执行多次,但是只有一个IO传输,能够有效的提高响应速度。

对于多次String类型的查询,使用mget,将多次请求合并为一次,同时命令和会被合并为一次,能有效提高响应速度,对于Hash内多个Field查询,使用hmget,起到和mget同样的效果。

Redis是单线程执行的,也就是说同一时间只能执行一条命令,如果一条命令执行的时间较长,其他线程在此期间均会被阻塞,所以在操作Redis时要注意操作指令的涉及的数据量,尽量降低单次操作的执行时间。

4000
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  redis