现在的位置: 首页 > 综合 > 正文

ruby redis-rb 使用方法介绍

2018年02月11日 ⁄ 综合 ⁄ 共 2611字 ⁄ 字号 评论关闭

今天研究了 redis-rb 的源代码( gem 'redis' ), 分享一下 :

redis 服务本身设计为单线程执行,所以不需要锁机制,每个命令的执行都是原子操作,在前一个命令执行完毕后,才执行下一个命令,由于内存操作所以都很高效。网上的测试结果,读写次数可以达到10万次每秒。

redis 在默认的6379端口接收socket连接和请求,redis-rb 就是一个ruby编写的连接redis服务端的client sdk。 在用户的进程中如何使用redis的连接句柄?是每次使用时创建句柄?还是全局共享一个长连接的句柄(如何保证长连接句柄不超时)?如果是共享句柄的方式,这个句柄就是一个临界资源,多线程的情况下是否安全? 把更多的长连接句柄放入Pool中是否更高效?

少量请求

如果用户进程很少请求redis(很少是指5分钟或更久请求一次),因为redis默认的timeout 是300秒,所以这种情况,每次使用前创建句柄,使用后关闭。如果维持一个长连接的句柄,每次使用前还需要检查连接是否有效,如果无效,还是要reconnect一次。所以少量请求的场景下建议如下使用:

redis = Redis.new(host: "127.0.0.1", port: 6379)
redis.***   #命令  get  set  等
redis.quit  # 使用后关闭
如果你使用3.0.5及其更早的版本,你更需要使用上面的代码来及时释放不用的句柄,否则句柄数会一直增长(超时默认回收),有可能会导致句柄超过最大数量限制。3.0.7版本中修复了这个缺陷,即使不关闭,同一个pid,共享句柄。  参考 https://github.com/redis/redis-rb/issues/382  
tower(https://tower.im/s/91i)在LU机制优化时就因为没有quit,导致句柄数超限,当时临时把timeout由300降低到30秒。

中等规模请求

考虑到网络延时等因素,假设一次redis请求耗时5ms,那么一个socket句柄,1秒钟可以处理200次请求。因此在用户进程中如果请求少于200次每秒,多余每5分钟1次,那么就数据中等规模请求。在这个场景下,建议在用户进程中保持一个全局共享的长连接句柄,

全局共享的变量可以保存在  config/environments/development.rb

  redis = Redis.new(host: "127.0.0.1", port: 6379)
  Redis.current = redis

如果使用 unicorn, 可以保存在  config/unicorn.rb

after_fork do |server, worker|
  .....
  redis = Redis.new(host: "127.0.0.1", port: 6379)
  Redis.current = redis
end
使用时

redis  = Redis.current
redis.get***   #  命令等
注意: Redis.current  本身是非线程安全的,看它的代码,如果@current未初始化,那么多线程执行会有问题,但如果在单线程中预先对这个做了设置就没有问题了,所以在进程初始化的时候做 current = 操作。

  def self.current
    @current ||= Redis.new
  end
用户进程内,有可能时多线程的,如何保证多线程访问共享的句柄安全呢? 这就要求redis-rb 线程安全,简单的说就是,多个线程用一个句柄,写入时要排好队,一个个执行。

redis-rb 是这样来保证的,

  def synchronize
    mon_synchronize { yield(@client) }
  end

  def ping
    synchronize do |client|
      client.call([:ping])
    end
  end

所有的call调用,都封装在同步方法中。

还有一种情况,在用户的进程中使用redis,有可能是需要事务的。比如,select 和get命令需要放在一起执行不受其他线程干扰,这种情况可以使用 multi 方法。 

    redis.multi do |multi|
      multi.select 1
      multi.set("key", "value")
    end 

大规模请求

如果用户进程中,对redis调用的次数每秒上千次以上, 那么单个连接是无法满足要求的。redis的资料中介绍, 50个并发能达到10万次的读写, 一个连接理想情况下2000次访问也是到最高值了,如果是跨网络访问,性能还有下降一个数量级。

针对这种规模的请求,最好的办法就是同时创建多个句柄连接,然后放到共享池中。redis-rb 的文档中有提到,在以后会支持如下这种调用方式。

Redis.current = Redis::Pool.connect  # 以后的版本会支持
可以配置多个连接,每次获取一个空闲的句柄使用。

也可以参考 Connection Pool Gem 地址 https://github.com/mperham/connection_pool

关于底层的连接方式(drivers)

redis-rb 目前支持3种连接方式, 如下, 第一种是默认的。 

Redis::Connection.drivers << Redis::Connection::Ruby  
Redis::Connection.drivers << Redis::Connection::Hiredis
Redis::Connection.drivers << Redis::Connection::Synchrony

建议使用第2种,

测试代码, 执行1万次调用,对比了 第一种和第二种方法的效率,1万次调用 hiredis能节省100ms左右。

%w[ruby hiredis].each do |d|

  redis = Redis.new(host: "127.0.0.1", port: 6379, driver: "#{d}")
  Redis.current  = redis
  redis.set("var1", 0)

  t0 = Time.now
  for i in (1..10000) do
    Redis.current.incr("var1")
  end
  t1 = Time.now
  p (t1-t0)

end

$ ruby test.rb
0.761553
0.68214
$ ruby test.rb
0.764066
0.659123
$ ruby test.rb
0.761662
0.637084

抱歉!评论已关闭.