合理选择Redis部署模式
查看推荐配置
冷热数据分离,不要将所有数据全部都放到Redis中
建议根据业务只将高频热数据存储到Redis中【QPS大于5000】,对于低频冷数据可以使用Mysql/ElasticSearch等基于磁盘的存储方式,不仅节省内存成本,而且数据量小在操作时速度更快、效率更高!
不同的业务数据要分开存储
Redis默认是提供了32个DataBase来使用,以自然数来做区分,如果不选择DataBase,都会默认使用0这个库
推荐一个部门一个DataBase,然后再做NameSpace的隔离,该操作需要有SDK的支持,一个DataBase可以设置多个NameSpace,这样可以便于管理且方便清理
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
规范Key的格式
合适的key,便于查看,统计,排错。
比如:
平台名
平台缩写
网管
GW
“平台缩写“+“-”+“项目名”+“-”+“业务含义”
例如:GW-TRADE-USERID
GW是新网关,TRADE是交易项目,USERID为业务ID。
存储的Key一定要设置超时时间
如果应用将Redis定位为缓存Cache使用,对于存放的Key一定要设置超时时间!因为若不设置,这些Key会一直占用内存不释放,造成极大的浪费,而且随着时间的推移会导致内存占用越来越大,直到达到服务器内存上限!另外Key的超时长短要根据业务综合评估,而不是越长越好!(某些业务要求key长期有效。可以在每次写入时,都设置超时时间,让超时时间顺延。)
public Boolean set(final byte[] key, final byte[] value, final long liveTime) {
return redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
connection.set(key, value);
if (liveTime > 0) {
connection.expire(key, liveTime);
}
return Boolean.TRUE;
}
});
}
对于必须要存储的大文本数据一定要压缩后存储
对于大文本【超过500字节】写入到Redis时,一定要压缩后存储!大文本数据存入Redis,除了带来极大的内存占用外,在访问量高时,很容易就会将网卡流量占满,进而造成整个服务器上的所有服务不可用,并引发雪崩效应,造成各个系统瘫痪!
public Boolean setBigValue(final byte[] key, final byte[] value, final long liveTime){
return redisTemplate.execute(new RedisCallback() {
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
byte[] compressedBytes = CompressUtil.compress(value);
connection.set(key, compressedBytes);
if (liveTime > 0) {
connection.expire(key, liveTime);
}
return Boolean.TRUE;
}
});
}
压缩方式可参考
public class CompressUtil {
private static final Inflater infl = new Inflater();
private static final Deflater defl = new Deflater();
private CompressUtil(){
}
public static byte[] uncompress(byte[] inputByte) throws IOException {
int len = 0;
infl.setInput(inputByte);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] outByte = new byte[1024];
try {
while (!infl.finished()) {
len = infl.inflate(outByte);
if (len == 0) {
break;
}
bos.write(outByte, 0, len);
}
infl.end();
} catch (Exception e) {
} finally {
bos.close();
}
return bos.toByteArray();
}
public static byte[] compress(byte[] inputByte) throws IOException {
int len = 0;
defl.setInput(inputByte);
defl.finish();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] outputByte = new byte[1024];
try {
while (!defl.finished()) {
len = defl.deflate(outputByte);
bos.write(outputByte, 0, len);
}
defl.end();
} finally {
bos.close();
}
return bos.toByteArray();
}
}
线上Redis禁止使用Keys正则匹配操作
Redis是单线程处理,在线上KEY数量较多时,操作效率极低【时间复杂度为O(N)】,该命令一旦执行会严重阻塞线上其它命令的正常请求,而且在高QPS情况下会直接造成Redis服务崩溃!如果有类似需求,请使用scan命令代替!
//此操作禁止
public Set get(final byte[] pattern){
return redisTemplate.execute(new RedisCallback>() {
@Override
public Set doInRedis(RedisConnection connection) throws DataAccessException {
return connection.keys(pattern);
}
});
}
谨慎全量操作Hash、Set等集合结构
在使用HASH结构存储对象属性时,开始只有有限的十几个field,往往使用HGETALL获取所有成员,效率也很高,但是随着业务发展,会将field扩张到上百个甚至几百个,此时还使用HGETALL会出现效率急剧下降、网卡频繁打满等问题【时间复杂度O(N)】,此时建议根据业务拆分为多个Hash结构;或者如果大部分都是获取所有属性的操作,可以将所有属性序列化为一个STRING类型存储!同样在使用SMEMBERS操作SET结构类型时也是相同的情况!
根据业务场景合理使用不同的数据结构类型
目前Redis支持的数据库结构类型较多:字符串(String),哈希(Hash),列表(List),集合(Set),有序集合(Sorted Set), Bitmap, HyperLogLog和地理空间索引(geospatial)等,需要根据业务场景选择合适的类型,常见的如:String可以用作普通的K-V、计数类;Hash可以用作对象等,包含较多属性的信息;List可以用作消息队列、粉丝/关注列表等;Set可以用于推荐;Sorted Set可以用于排行等!
考虑Redis的sharding机制
目前spring-data-redis的JedisConnectionFactory没有实现sharding的功能,但是其依赖Jedis实现了,所以如果要sharding,需要自己实现逻辑,需要自己去实现封装
对重要的数据使用try/catch
如果必须确保关键性的数据可以被放入到 Redis 的实例中,我强烈建议将其放入 try/except 块中。几乎所有的Redis客户端采用的都是“发送即忘”策略,因此经常需要考虑一个 key 是否真正被放到 Redis 数据库中了。至于将 try/expect 放到 Redis 命令中的复杂性并不是本文要讲的,你只需要知道这样做可以确保重要的数据放到该放的地方就可以了。
合理使用Hash
1. 127.0.0.1:6379> HSET foo first_name "Joe"
3. 127.0.0.1:6379> HSET foo last_name "Engel"
5. 127.0.0.1:6379> HSET foo address "1 Fanatical Pl"
7. 127.0.0.1:6379> HGETALL foo
"first_name"
"Joe"
"last_name"
"Engel"
"address"
"1 Fanatical Pl"
14. 127.0.0.1:6379> HGET foo first_name
"Joe"
推荐配置
properties配置
####单机模式#######
##redis的服务器地址
redis.host=192.168.1.20
##redis的服务端口
redis.port=6400
####哨兵模式
##redis的服务器地址
redis.sentinel.master.host=192.168.1.20
##redis的服务端口
redis.sentinel.master.port=26379
redis.sentinel.slave1.host=192.168.1.21
redis.sentinel.slave1.port=26380
redis.sentinel.slave2=192.168.1.22
redis.sentinel.slave2.port=26381
####集群模式
##redis的服务器地址
redis.cluster.host1=192.168.1.20
##redis的服务端口
redis.cluster.port1=6400
redis.cluster.host2=192.168.1.20
redis.cluster.port2=6400
redis.cluster.host3=192.168.1.20
redis.cluster.port3=6400
##redis密码
redis.pass=1234xxxxx
##redis连接数据库
redis.default.db=0
##客户端超时时间单位是毫秒
redis.timeout=100000
##最大连接数
redis.maxActive=300
##最大空闲数
redis.maxIdle=100
##最大建立连接等待时间
redis.maxWait=1000
##指明是否在从池中取出连接前进行检验,如果检验失败,则从池中去除连接并尝试取出另一个
redis.testOnBorrow=true
单机模式
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
classpath:redis.properties
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
p:connectionFactory-ref="jedisConnFactory" p:enableTransactionSupport="true">
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
哨兵模式
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
classpath:redis.properties
class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
p:connectionFactory-ref="jedisConnFactory" p:enableTransactionSupport="true">
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
集群模式
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
classpath:redis.properties
class="org.springframework.data.redis.connection.RedisClusterConfiguration">
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
p:connectionFactory-ref="jedisConnFactory" p:enableTransactionSupport="true">
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />
class="org.springframework.data.redis.serializer.StringRedisSerializer" />