10 March 2015

前言

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的方案还没有比较成熟的方案。



blog comments powered by Disqus