├── .coveralls.yml ├── .gitignore ├── .rspec ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── lib ├── redis-ssdb-proxy.rb ├── redis_ssdb_proxy.rb └── redis_ssdb_proxy │ ├── client.rb │ ├── delegator.rb │ └── version.rb ├── redis-ssdb-proxy.gemspec └── spec ├── client_spec.rb └── spec_helper.rb /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Gemfile.lock 2 | /coverage/ 3 | /spec/reports/ 4 | 5 | *.gem 6 | *.rbc 7 | /.config 8 | /coverage/ 9 | /InstalledFiles 10 | /pkg/ 11 | /spec/reports/ 12 | /spec/examples.txt 13 | /test/tmp/ 14 | /test/version_tmp/ 15 | /tmp/ 16 | 17 | # Used by dotenv library to load environment variables. 18 | # .env 19 | 20 | ## Specific to RubyMotion: 21 | .dat* 22 | .repl_history 23 | build/ 24 | *.bridgesupport 25 | build-iPhoneOS/ 26 | build-iPhoneSimulator/ 27 | 28 | ## Specific to RubyMotion (use of CocoaPods): 29 | # 30 | # We recommend against adding the Pods directory to your .gitignore. However 31 | # you should judge for yourself, the pros and cons are mentioned at: 32 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 33 | # 34 | # vendor/Pods/ 35 | 36 | ## Documentation cache and generated files: 37 | /.yardoc/ 38 | /_yardoc/ 39 | /doc/ 40 | /rdoc/ 41 | 42 | ## Environment normalization: 43 | /.bundle/ 44 | /vendor/bundle 45 | /lib/bundler/man/ 46 | .idea/ 47 | 48 | # for a library or gem, you might want to ignore these files since the code is 49 | # intended to run in multiple environments; otherwise, check them in: 50 | # Gemfile.lock 51 | # .ruby-version 52 | # .ruby-gemset 53 | 54 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 55 | .rvmrc 56 | .env 57 | *.un~ 58 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # services: 2 | # - redis-server 3 | 4 | 5 | language: ruby 6 | rvm: 7 | - 2.0 8 | - 2.1.5 9 | - 2.2 10 | - 2.3.0 11 | script: xvfb-run rspec 12 | before_install: gem install bundler -v 1.11.2 13 | services: 14 | - redis 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in redis-ssdb-proxy.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jason Hou 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redis SSDB Proxy [![Build Status](https://travis-ci.org/falm/redis-ssdb-proxy.svg?branch=master)](https://travis-ci.org/falm/redis-ssdb-proxy) [![Coverage Status](https://coveralls.io/repos/github/falm/redis-ssdb-proxy/badge.svg?branch=master)](https://coveralls.io/github/falm/redis-ssdb-proxy?branch=master) [![Code Climate](https://codeclimate.com/github/falm/redis-ssdb-proxy/badges/gpa.svg)](https://codeclimate.com/github/falm/redis-ssdb-proxy) [![Dependency Status](https://gemnasium.com/badges/github.com/falm/redis-ssdb-proxy.svg)](https://gemnasium.com/github.com/falm/redis-ssdb-proxy) [![Gem Version](https://badge.fury.io/rb/redis-ssdb-proxy.svg)](https://badge.fury.io/rb/redis-ssdb-proxy) 2 | 3 | The Redis SSDB Proxy that read from redis(or ssdb) write to both use for redis <=> ssdb migration on production 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ```ruby 10 | gem 'redis-ssdb-proxy' 11 | ``` 12 | 13 | And then execute: 14 | 15 | $ bundle 16 | 17 | Or install it yourself as: 18 | 19 | $ gem install redis-ssdb-proxy 20 | 21 | ## Usage 22 | 23 | If you want migrate redis data to SSDB the below code will write data to both of redis and ssdb and read from redis only 24 | 25 | The options *ssdb: :slave* tells Proxy which redis-client is connected to the ssdb server which means Proxy will be delegating the unsupport data-structure (set) of SSDB to supported(zset) 26 | ```ruby 27 | ssdb = Redis.new(host: 'localhost', port: '8888') 28 | redis = Redis.new(host: 'localhost', port: '6379') 29 | 30 | $redis = RedisSsdbProxy.new(master: redis, slave: ssdb, ssdb: :slave) 31 | 32 | $redis.set(:quotes, 'May the force be with you') # set to both 33 | $redis.get(:quotes) # read from master (redis) 34 | # => May the force be with you 35 | ``` 36 | 37 | When the data migrated the below line code will being prepare for the rollback 38 | 39 | ```ruby 40 | $redis = RedisSsdbProxy.new(master: ssdb, slave: redis, ssdb: :master) 41 | ``` 42 | 43 | ## Contributing 44 | 45 | Bug reports and pull requests are welcome on GitHub at https://github.com/falm/redis-ssdb-proxy. 46 | 47 | ## License 48 | MIT © [Falm](https://github.com/falm) 49 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | task :default => :spec 3 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "redis_ssdb_proxy" 5 | 6 | # You can add fixtures and/or initialization code here to make experimenting 7 | # with your gem easier. You can also use a different console, if you like. 8 | 9 | # (If you use this, don't forget to add pry to your Gemfile!) 10 | # require "pry" 11 | # Pry.start 12 | 13 | require "irb" 14 | IRB.start 15 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | 8 | # Do any other automated setup that you need to do here 9 | -------------------------------------------------------------------------------- /lib/redis-ssdb-proxy.rb: -------------------------------------------------------------------------------- 1 | require 'redis_ssdb_proxy' 2 | -------------------------------------------------------------------------------- /lib/redis_ssdb_proxy.rb: -------------------------------------------------------------------------------- 1 | 2 | Gem.find_files('redis_ssdb_proxy/**/*.rb').each { |file| require file } 3 | 4 | module RedisSsdbProxy 5 | 6 | def self.new(*args) 7 | Client.new(*args) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/redis_ssdb_proxy/client.rb: -------------------------------------------------------------------------------- 1 | 2 | module RedisSsdbProxy 3 | 4 | class Client 5 | 6 | attr_accessor :master, :slave 7 | 8 | def initialize(args) 9 | self.master, self.slave = args.fetch(:master), args.fetch(:slave) 10 | Delegator.call(self, args[:ssdb]) 11 | end 12 | 13 | class << self 14 | private 15 | def send_to_slave(command) 16 | class_eval <<-EOS 17 | def #{command}(*args, &block) 18 | slave.#{command}(*args, &block) 19 | end 20 | EOS 21 | end 22 | 23 | def send_to_master(command) 24 | class_eval <<-EOS 25 | def #{command}(*args, &block) 26 | master.#{command}(*args, &block) 27 | end 28 | EOS 29 | end 30 | 31 | def send_to_both(command) 32 | class_eval <<-EOS 33 | def #{command}(*args, &block) 34 | slave.#{command}(*args, &block) 35 | master.#{command}(*args, &block) 36 | end 37 | EOS 38 | end 39 | end 40 | 41 | %i(dbsize exists get getbit getrange hexists hget hgetall hkeys hlen hmget hvals keys lindex llen lrange mget 42 | randomkey scard sdiff sinter sismember smembers sort srandmember strlen sunion ttl type zcard zcount zrange 43 | zrangebyscore zrank zrevrange zscore).each do |command| 44 | send_to_master command 45 | end 46 | 47 | # all write opreate send to master slave both 48 | def method_missing(name, *args, &block) 49 | if master.respond_to?(name) 50 | self.class.send(:send_to_both, name) 51 | slave.send(name, *args, &block) 52 | master.send(name, *args, &block) 53 | else 54 | super 55 | end 56 | end 57 | 58 | 59 | end # Client 60 | 61 | end # RedisSsdbProxy 62 | -------------------------------------------------------------------------------- /lib/redis_ssdb_proxy/delegator.rb: -------------------------------------------------------------------------------- 1 | module RedisSsdbProxy 2 | 3 | class Delegator 4 | 5 | attr_accessor :client 6 | 7 | def self.call(client, role) 8 | if [:master, :slave].include? role 9 | self.new(client.send(role)).delegate 10 | end 11 | end 12 | 13 | def initialize(client) 14 | self.client = client 15 | end 16 | 17 | def delegate 18 | delegate_nonsupport_method 19 | end 20 | 21 | def delegate_nonsupport_method 22 | client.instance_eval do 23 | def sadd(key, *args) 24 | zadd(key, args.map{ |arg| 25 | [Time.now.to_i, arg] 26 | }) 27 | end 28 | 29 | def srem(*args) 30 | zrem(*args) 31 | end 32 | end 33 | end 34 | 35 | end 36 | 37 | end 38 | -------------------------------------------------------------------------------- /lib/redis_ssdb_proxy/version.rb: -------------------------------------------------------------------------------- 1 | module RedisSsdbProxy 2 | VERSION = "0.1.1" 3 | end 4 | -------------------------------------------------------------------------------- /redis-ssdb-proxy.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'redis_ssdb_proxy/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "redis-ssdb-proxy" 8 | spec.version = RedisSsdbProxy::VERSION 9 | spec.authors = ["falm"] 10 | spec.email = ["hjj1992@gmail.com"] 11 | 12 | spec.summary = %q{proxy that read from redis(or ssdb) write to both use for redis <=> ssdb migration on production} 13 | spec.description = %q{proxy that read from redis(or ssdb) write to both use for redis <=> ssdb migration on production} 14 | spec.homepage = "https://github.com/falm/redis-ssdb-proxy" 15 | 16 | spec.licenses = ["MIT"] 17 | 18 | spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } 19 | spec.bindir = "exe" 20 | spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } 21 | spec.require_paths = ["lib"] 22 | 23 | spec.add_development_dependency "bundler", "~> 1.11" 24 | spec.add_development_dependency "rake", "~> 10.0" 25 | spec.add_development_dependency 'rspec' 26 | spec.add_development_dependency 'redis' 27 | spec.add_development_dependency 'redis-namespace' 28 | spec.add_development_dependency 'dotenv' 29 | spec.add_development_dependency 'coveralls' 30 | 31 | end 32 | -------------------------------------------------------------------------------- /spec/client_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | 4 | describe RedisSsdbProxy do 5 | 6 | let(:redis_to_ssdb_proxy) { RedisSsdbProxy.new(master: @redis, slave: @ssdb, ssdb: :slave) } 7 | 8 | let(:ssdb_to_redis_proxy) { RedisSsdbProxy.new(master: @ssdb, slave: @master, ssdb: :master) } 9 | 10 | before(:each) do 11 | @redis.set(:lines, "I'll be back") 12 | end 13 | 14 | it 'should works' do 15 | redis_to_ssdb_proxy.set(:name, 'Tom') 16 | expect(redis_to_ssdb_proxy.get(:name)).to eq(@redis.get(:name)) 17 | end 18 | 19 | it 'should read from master' do 20 | expect(redis_to_ssdb_proxy.master).to receive(:get) 21 | redis_to_ssdb_proxy.get(:lines) 22 | end 23 | 24 | it 'should write to redis and ssdb' do 25 | args = [:quotes, 'star_wars'] 26 | redis_to_ssdb_proxy.hset( *(args + ['May the force be with you']) ) 27 | expect(@redis.hget(*args)).to eq(@ssdb.hget(*args)) 28 | end 29 | 30 | it 'delegate set to zset' do 31 | quotes = ["I'm your father", "That's no moon", 'Use the force, Luke'] 32 | key = "#{rand(20)}_quotes_of_star_wars" 33 | quotes.each { |q| redis_to_ssdb_proxy.sadd(key, q) } 34 | result = @ssdb.zrange(key, 0, -1) 35 | expect(result).to eq(quotes) 36 | end 37 | 38 | 39 | end 40 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'redis' 3 | require 'redis-namespace' 4 | require 'redis-ssdb-proxy' 5 | require 'dotenv' 6 | require 'coveralls' 7 | 8 | Coveralls.wear! 9 | 10 | Dotenv.load 11 | 12 | redis_host, redis_port = (ENV['redis_host'] || 'localhost'), (ENV['redis_port'] || 6379) 13 | ssdb_host, ssdb_port = (ENV['ssdb_host'] || 'localhost'), (ENV['ssdb_port'] || 8888) 14 | $redis = Redis::Namespace.new :redis_test, redis: Redis.new(host: redis_host, port: redis_port) 15 | $ssdb = Redis::Namespace.new :ssdb_test, redis: Redis.new(host: ssdb_host, port: ssdb_port) 16 | 17 | RSpec.configure do |config| 18 | 19 | config.before(:example) do 20 | @redis = $redis 21 | @ssdb = $ssdb 22 | end 23 | 24 | config.expect_with :rspec do |expectations| 25 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 26 | end 27 | 28 | config.mock_with :rspec do |mocks| 29 | mocks.verify_partial_doubles = true 30 | end 31 | 32 | end --------------------------------------------------------------------------------