前言
Redis在很多公司的产品中都在使用,其特点是 快,简单,强大的多类型的数据结构的支持。本文总结了几个在我司中的产品的应用。
1. 应用session缓存
这个就不说了,最基本的使用了Json序列化的数据存在Redis里面。不过Json序列化的效率不是很高,当时间严格要求的场景下还在用一些其他序列化的方法,有司有人调查过kryo得效率要搞一倍多。
2. 大容量的集群Timer定时器的构建
项目需求有大量的分布式Timer场景,精度在秒级别。 实现原理:采用1个sort set和1个list。 sleeping list是一个sort set,里面存储了超时时间和timer的标志,有一个Jdk的timer每0.5秒去扫描这个sleeping list,如果到期了就放到另外一个ready list,里面存储了所有到期的timer。所有的分布式的timer都去这个ready list里面去拿到期的timer干活。 要点:redis 的操作都是以lua脚本实现。
附上dispatcher.lua ~~~
-- KEYS: [1]job:sleeping, [2]job:ready, [3]job:ack, [4]counter:distribute
-- ARGV: [1]currentTime
-- Comments: result is a array, one element is job id, next element is score, next element is job id....
local jobWithScores=redis.call('zrangebyscore', KEYS[1], '-inf', ARGV[1], 'withscores')
local arraySize = table.maxn(jobWithScores)
if arraySize>0 then
-- Comments: add to the Ready job
for i=1,arraySize,2 do
redis.call('lpush', KEYS[2], jobWithScores[i])
end
if ARGV[2]=="true" then
-- Comments: add to the Ack job
for i=1,arraySize,2 do
redis.call('zadd', KEYS[3], jobWithScores[i+1], jobWithScores[i])
end
end
-- Comments: remove from Sleeping job
redis.call('zremrangebyscore', KEYS[1], '-inf', ARGV[1])
-- Comments: update the statistics counter
redis.call('incrby', KEYS[4], arraySize/2)
end
当然这种每次放一个效率不是很高,改进版就是批量的操作。
-- KEYS: [1]job:sleeping, [2]job:ready, [3]counter:distribute
-- ARGV: [1]currentTime
-- Comments: result is a array, one element is job id, next element is score, next element is job id....
local jobIds=redis.call('zrangebyscore', KEYS[1], '-inf', ARGV[1])
local arraySize = table.maxn(jobIds)
local batchSize = 128
-- This function is to get the ready job and packed them in batch.
-- Then add them to the redis list in batch.
-- batchSize could be changed, based on the test, set to 128 has good results.
local function addToReadyList ( arraySize )
local i=1
while i<=arraySize do
local j=0
local tempTable = {}
while j<batchSize and i<=arraySize do
j=j+1
tempTable[j] = jobIds[i]
i=i+1
end
if j>0 then
redis.call('lpush', KEYS[2], unpack(tempTable))
end
--print(unpack(tempTable))
end
end
-- Main entry from here.
if arraySize>0 then
addToReadyList(arraySize)
-- Comments: remove from Sleeping job
redis.call('zremrangebyscore', KEYS[1], '-inf', ARGV[1])
-- Comments: update the statistics counter
redis.call('incrby', KEYS[3], arraySize)
end
####4. 分布式系统中的MasterElector
在分布式系统中经常时候到master选举,Redis的Set with Nx,Ex极大的方便了很多功能,基于它也很容易构建出Master 选举的功能,简单的场景下就不需要用到zookeeper啦。
实现原理:。
要点:。
附上周期性的timer处理代码。
@Override
public void run() {
jedisTemplate.execute(new JedisActionNoResult() {
@Override
public void action(Jedis jedis) {
String masterFromRedis = jedis.get(masterKey);
logger.debug("master is {}", masterFromRedis);
//if master is null, the cluster just start or the master had crashed, try to register myself as master
if (masterFromRedis == null) {
//use setnx to make sure only one client can register as master.
if (jedis.set(masterKey, hostId, "NX", "EX", expiredPeriodSeconds) != null) {
master.set(true);
logger.info("master is changed to {}.", hostId);
return;
} else {
master.set(false);
return;
}
}
//if master is myself, update the expire time.
if (hostId.equals(masterFromRedis)) {
jedis.expire(masterKey, expiredPeriodSeconds);
master.set(true);
return;
}
master.set(false);
}
});
}
~~~
后记
Redis已经比较成熟了,但是现在对于GeoRed的方案还没有比较成熟的方案。