├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example.cr ├── example2.cr ├── shard.yml ├── spec ├── redisoid_spec.cr └── spec_helper.cr └── src └── redisoid.cr /.gitignore: -------------------------------------------------------------------------------- 1 | /doc/ 2 | /lib/ 3 | /.crystal/ 4 | /.shards/ 5 | 6 | 7 | # Libraries don't need dependency lock 8 | # Dependencies will be locked in application that uses them 9 | /shard.lock 10 | 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 'Konstantin Makarchev' 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # THIS PROJECT DEPRECATED, RECONNECTION LOGIC AND POOLING NOW IMPLEMENTED IN stefanwille/crystal-redis 2 | 3 | # redisoid 4 | 5 | Redis client for Crystal with auto-reconnection and pool (wrapper for stefanwille/crystal-redis, kostya/redis-reconnect, ysbaddaden/pool). Ready to use in production. 6 | 7 | ## Installation 8 | 9 | 10 | Add this to your application's `shard.yml`: 11 | 12 | ```yaml 13 | dependencies: 14 | redisoid: 15 | github: kostya/redisoid 16 | ``` 17 | 18 | 19 | ## Usage 20 | 21 | 22 | ```crystal 23 | require "redisoid" 24 | 25 | client = Redisoid.new(host: "localhost", port: 6379, pool: 50) 26 | client.set("bla", "abc") 27 | p client.get("bla") 28 | ``` 29 | 30 | ## Use it in high concurency code 31 | 32 | ```crystal 33 | require "redisoid" 34 | 35 | client = Redisoid.new(host: "localhost", port: 6379, pool: 250) 36 | 37 | client.del("queue") 38 | 39 | count = 0 40 | 41 | 100.times do 42 | spawn do 43 | loop do 44 | client.lpush("queue", "abc") 45 | sleep 0.01 46 | end 47 | end 48 | 49 | spawn do 50 | loop do 51 | if res = client.lpop("queue") 52 | count += 1 if res.size == 3 53 | else 54 | sleep 0.01 55 | end 56 | end 57 | end 58 | end 59 | 60 | sleep 5.0 61 | 62 | p count 63 | p client.pool_size 64 | p client.pool_pending 65 | 66 | client.del("queue") 67 | 68 | ``` 69 | 70 | ``` 71 | 42259 72 | 200 73 | 204 74 | ``` 75 | -------------------------------------------------------------------------------- /example.cr: -------------------------------------------------------------------------------- 1 | require "./src/redisoid" 2 | 3 | client = Redisoid.new(host: "localhost", port: 6379, pool: 50) 4 | client.set("bla", "abc") 5 | p client.get("bla") 6 | -------------------------------------------------------------------------------- /example2.cr: -------------------------------------------------------------------------------- 1 | require "./src/redisoid" 2 | 3 | client = Redisoid.new(host: "localhost", port: 6379, pool: 250) 4 | 5 | client.del("queue") 6 | 7 | count = 0 8 | 9 | 100.times do 10 | spawn do 11 | loop do 12 | client.lpush("queue", "abc") 13 | sleep 0.01 14 | end 15 | end 16 | 17 | spawn do 18 | loop do 19 | if res = client.lpop("queue") 20 | count += 1 if res.size == 3 21 | else 22 | sleep 0.01 23 | end 24 | end 25 | end 26 | end 27 | 28 | sleep 5.0 29 | 30 | p count 31 | p client.pool_size 32 | p client.pool_pending 33 | 34 | client.del("queue") 35 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: redisoid 2 | version: 0.3 3 | 4 | authors: 5 | - Konstantin Makarchev kostya27@gmail.com 6 | 7 | dependencies: 8 | pool: 9 | github: ysbaddaden/pool 10 | redis-reconnect: 11 | github: kostya/redis-reconnect 12 | version: ">= 0.3" 13 | 14 | license: MIT 15 | -------------------------------------------------------------------------------- /spec/redisoid_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | require "redis" 3 | 4 | # manualy run: redis-server --port 7777 --timeout 2 5 | CONFIG = {host: "localhost", port: 7777} 6 | TIMEOUT = 2 7 | 8 | describe Redisoid do 9 | it "standard client" do 10 | client = Redis.new(**CONFIG) 11 | client.set("bla1", "a") 12 | client.get("bla1").should eq "a" 13 | 14 | sleep(TIMEOUT + 1.0) 15 | 16 | expect_raises(Redis::DisconnectedError) do 17 | client.get("bla1") 18 | end 19 | end 20 | 21 | it "reconnect client" do 22 | client = Redisoid.new(**CONFIG) 23 | client.set("bla2", "a") 24 | client.get("bla2").should eq "a" 25 | 26 | sleep(TIMEOUT + 1.0) 27 | 28 | client.get("bla2").should eq "a" 29 | end 30 | 31 | it "connect with url also" do 32 | client = Redisoid.new(url: "localhost:7777", pool: 5) 33 | client.set("bla3", "a") 34 | client.get("bla3").should eq "a" 35 | end 36 | 37 | it "reconnect method with block" do 38 | client1 = Redisoid.new(**CONFIG) 39 | client2 = Redisoid.new(**CONFIG) 40 | ch = Channel(String).new 41 | spawn do 42 | client2.subscribe("sub_test") do |on| 43 | on.message do |_, msg| 44 | ch.send(msg) 45 | end 46 | end 47 | end 48 | sleep(TIMEOUT + 1.0) 49 | client1.publish("sub_test", "bla") 50 | 51 | ch.receive.should eq "bla" 52 | end 53 | 54 | it "work with pipelined" do 55 | client = Redisoid.new(**CONFIG) 56 | client.pipelined do |pipeline| 57 | pipeline.del("foo") 58 | pipeline.del("foo1") 59 | pipeline.del("foo2") 60 | pipeline.del("foo3") 61 | pipeline.set("foo1", "first") 62 | pipeline.set("foo2", "second") 63 | pipeline.set("foo3", "third") 64 | end 65 | 66 | client.get("foo2").should eq "second" 67 | end 68 | 69 | it "work with transaction" do 70 | client = Redisoid.new(**CONFIG) 71 | client.multi do |multi| 72 | multi.del("foo") 73 | multi.del("foo1") 74 | multi.del("foo2") 75 | multi.del("foo3") 76 | multi.set("foo1", "first") 77 | multi.set("foo2", "second") 78 | multi.set("foo3", "third") 79 | end 80 | 81 | client.get("foo2").should eq "second" 82 | end 83 | 84 | it "work with transaction with futures" do 85 | client = Redisoid.new(**CONFIG) 86 | future_1 = Redis::Future.new 87 | future_2 = Redis::Future.new 88 | client.multi do |multi| 89 | multi.set("foo1", "A") 90 | multi.set("foo2", "B") 91 | future_1 = multi.get("foo1") 92 | future_2 = multi.get("foo2") 93 | end 94 | 95 | future_1.value.should eq "A" 96 | future_2.value.should eq "B" 97 | end 98 | 99 | it "stats" do 100 | client = Redisoid.new(**CONFIG) 101 | client.stats.should eq({capacity: 10, available: 10, used: 0}) 102 | end 103 | 104 | it "quite multiconcurrent execution" do 105 | client = Redisoid.new(url: "localhost:7777", pool: 200) 106 | client.del("test-queue") 107 | res = [] of String 108 | checks = 0 109 | 110 | n1 = 50 111 | n2 = 200 112 | 113 | n1.times do |i| 114 | spawn do 115 | n2.times do |j| 116 | client.set("key-#{i}-#{j}", "#{i}-#{j}") 117 | client.rpush("test-queue", "#{i}-#{j}") 118 | sleep 0.0001 119 | end 120 | end 121 | end 122 | 123 | ch = Channel(Bool).new 124 | 125 | n1.times do 126 | spawn do 127 | loop do 128 | if v = client.lpop("test-queue") 129 | res << v 130 | if client.get("key-#{v}") == v 131 | checks += 1 132 | client.del("key-#{v}") 133 | end 134 | else 135 | sleep 0.0001 136 | end 137 | 138 | break if res.size >= n1 * n2 139 | end 140 | ch.send(true) 141 | end 142 | end 143 | 144 | n1.times { ch.receive } 145 | 146 | res.size.should eq n1 * n2 147 | res.uniq.size.should eq n1 * n2 148 | 149 | checks.should eq n1 * n2 150 | 151 | uniqs = [] of Int64 152 | 153 | res.each do |v| 154 | a, b = v.split('-') 155 | uniqs << (a.to_i64 * n2 + b.to_i64).to_i64 156 | end 157 | 158 | uniqs.sum.should eq ((n1 * n2 - 1).to_i64 * n1.to_i64 * n2.to_i64).to_i64 / 2 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | require "../src/redisoid" 3 | -------------------------------------------------------------------------------- /src/redisoid.cr: -------------------------------------------------------------------------------- 1 | require "redis-reconnect" 2 | require "pool/connection" 3 | 4 | class Redisoid 5 | VERSION = "0.3" 6 | 7 | @cp : ConnectionPool(Redis::Reconnect) 8 | 9 | def initialize(@host : String = "localhost", 10 | @port : Int32 = 6379, 11 | @unixsocket : String? = nil, 12 | @password : String? = nil, 13 | @database : Int32? = nil, 14 | @pool : Int32 = 10, 15 | @url : String? = nil) 16 | @cp = ConnectionPool(Redis::Reconnect).new(capacity: @pool) do 17 | Redis::Reconnect.new(host: @host, port: @port, unixsocket: @unixsocket, password: @password, database: @database, url: @url) 18 | end 19 | end 20 | 21 | macro method_missing(call) 22 | @cp.connection do |cn| 23 | return cn.{{call}} 24 | end 25 | end 26 | 27 | def pool_size 28 | @cp.size 29 | end 30 | 31 | def pool_pending 32 | @cp.pending 33 | end 34 | 35 | def stats 36 | {capacity: @pool, available: pool_pending, used: pool_size} 37 | end 38 | 39 | def subscribe(*channels, &callback_setup_block : Redis::Subscription ->) 40 | @cp.connection &.subscribe(*channels) { |s| callback_setup_block.call(s) } 41 | end 42 | 43 | def psubscribe(*channel_patterns, &callback_setup_block : Redis::Subscription ->) 44 | @cp.connection &.subscribe(*channel_patterns) { |s| callback_setup_block.call(s) } 45 | end 46 | end 47 | --------------------------------------------------------------------------------