2012-10-29 18 views
18

Mamy następującą konfigurację:Redis IOException: "Istniejące połączenie przymusowo zamknięte przez zdalnego hosta" za pomocą ServiceStack C# klienta

Redis 2.6 na Ubuntu Linux 12.04LTE na przykład RackspaceCloud 8GB z następującymi ustawieniami:

daemonize yes 
pidfile /var/run/redis_6379.pid 

port 6379 

timeout 300 

loglevel notice 
logfile /var/log/redis_6379.log 

databases 16 

save 900 1 
save 300 10 
save 60 10000 

rdbcompression yes 
dbfilename dump.rdb 
dir /var/redis/6379 

requirepass PASSWORD 

maxclients 10000 

maxmemory 7gb 
maxmemory-policy allkeys-lru 
maxmemory-samples 3 

appendonly no 

slowlog-log-slower-than 10000 
slowlog-max-len 128 

activerehashing yes 

Nasze serwery aplikacji są hostowane w RackSpace Zarządzane i łączą się z Redis za pośrednictwem publicznego adresu IP (aby uniknąć konieczności konfigurowania RackSpace Connect, która jest królewską PITA) i zapewniamy pewne zabezpieczenia wymagając hasła do połączenia Redis. Ręcznie zwiększyłem limit deskryptorów plików uniksowych do 10240, maks. 10 000 połączeń powinno zapewnić wystarczającą ilość miejsca. Jak widać z powyższego pliku ustawień, ograniczam użycie pamięci do 7 GB, aby zostawić trochę miejsca na pamięci RAM.

Używamy sterownika ServiceStack C# Redis. Używamy następujących ustawień web.config:

<RedisConfig suffix=""> 
    <Primary password="PASSWORD" host="HOST" port="6379" maxReadPoolSize="50" maxWritePoolSize="50"/> 
</RedisConfig> 

Mamy singleton PooledRedisClientManager, stworzony raz na AppPool następująco:

private static PooledRedisClientManager _clientManager; 
public static PooledRedisClientManager ClientManager 
{ 
    get 
    { 
     if (_clientManager == null) 
     { 
      try 
      { 
       var poolConfig = new RedisClientManagerConfig 
       { 
        MaxReadPoolSize = RedisConfig.Config.Primary.MaxReadPoolSize, 
        MaxWritePoolSize = RedisConfig.Config.Primary.MaxWritePoolSize, 
       }; 

       _clientManager = new PooledRedisClientManager(new List<string>() { RedisConfig.Config.Primary.ToHost() }, null, poolConfig); 
      } 
      catch (Exception e) 
      { 
       log.Fatal("Could not spin up Redis", e); 
       CacheFailed = DateTime.Now; 
      } 
     } 
     return _clientManager; 
    } 
} 

A my nabyć połączenie i kładź/dostać operacji w następujący sposób:

using (var client = ClientManager.GetClient()) 
    { 
     client.Set<T>(region + key, value); 
    } 

Kod wydaje się działać głównie. Biorąc pod uwagę, że mamy ~ 20 AppPools i 50-100 read i 50-100 write clients, oczekujemy maksymalnie 2000-4000 połączeń z serwerem Redis. Jednak w naszych dziennikach błędów widzimy następujący wyjątek, zwykle kilkaset tych, które są ze sobą połączone, nic przez godzinę, i znowu, na nause.

System.IO.IOException: Unable to read data from the transport connection: 
An existing connection was forcibly closed by the remote host. 
---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host at 
System.Net.Sockets.Socket.Receive(Byte[] buffer, Int32 offset, Int32 size, SocketFlags socketFlags) at 
System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size) 
--- End of inner exception stack trace 
- at System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size) at System.IO.BufferedStream.ReadByte() at 
ServiceStack.Redis.RedisNativeClient.ReadLine() in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 85 at 
ServiceStack.Redis.RedisNativeClient.SendExpectData(Byte[][] cmdWithBinaryArgs) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 355 at 
ServiceStack.Redis.RedisNativeClient.GetBytes(String key) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisNativeClient.cs:line 404 at ServiceStack.Redis.RedisClient.GetValue(String key) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisClient.cs:line 185 at ServiceStack.Redis.RedisClient.Get[T](String key) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisClient.ICacheClient.cs:line 32 at DataPeaks.NoSQL.RedisCacheClient.Get[T](String key) in c:\dev\base\branches\currentversion\DataPeaks\DataPeaks.NoSQL\RedisCacheClient.cs:line 96 

W eksperymentowali z Redis serwera Timeout 0 (czyli nie jest limit czasu połączenia), a czas oczekiwania na 24 godzin, a pomiędzy nimi, bez powodzenia. Googling i Stackoverflowing nie przyniosły prawdziwych odpowiedzi, wszystko wskazuje na to, że postępujemy właściwie z kodem.

Nasze odczucie jest takie, że regularnie występują problemy z opóźnieniami w sieci, między innymi Rackspace Hosted i Rackspace Cloud, które powodują, że blok połączeń TCP przestaje działać. Możemy rozwiązać ten problem, wprowadzając limity czasu połączenia po stronie klienta, a pytanie brzmi, czy potrzebowalibyśmy również limitu czasu po stronie serwera. Ale to tylko uczucie, a my nie jesteśmy w 100% pewni, że jesteśmy na dobrej drodze.

Pomysły?

Edit: I od czasu do czasu zobaczyć następujący komunikat o błędzie, a także:

ServiceStack.Redis.RedisException: Unable to Connect: sPort: 65025 ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host at System.Net.Sockets.Socket.Send(IList`1 buffers, SocketFlags socketFlags) at ServiceStack.Redis.RedisNativeClient.FlushSendBuffer() in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 273 at ServiceStack.Redis.RedisNativeClient.SendCommand(Byte[][] cmdWithBinaryArgs) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 203 --- End of inner exception stack trace --- at ServiceStack.Redis.RedisNativeClient.CreateConnectionError() in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 165 at ServiceStack.Redis.RedisNativeClient.SendExpectData(Byte[][] cmdWithBinaryArgs) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisNativeClient_Utils.cs:line 355 at ServiceStack.Redis.RedisNativeClient.GetBytes(String key) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisNativeClient.cs:line 404 at ServiceStack.Redis.RedisClient.GetValue(String key) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisClient.cs:line 185 at ServiceStack.Redis.RedisClient.Get[T](String key) in C:\src\ServiceStack.Redis\src\ServiceStack.Redis\RedisClient.ICacheClient.cs:line 32 at DataPeaks.NoSQL.RedisCacheClient.Get[T](String key) in c:\dev\base\branches\currentversion\DataPeaks\DataPeaks.NoSQL\RedisCacheClient.cs:line 96 

sobie wyobrazić, że jest to bezpośrednim wynikiem konieczności po stronie serwera limity czasu połączeń, które nie są obsługiwane na kliencie. Wygląda na to, że naprawdę musimy obsługiwać limity czasu połączenia po stronie klienta.

+4

Czy kiedykolwiek znalazłeś dobry sposób na poradzenie sobie z tym? Widzę ten sam problem z uruchomieniem aplikacji na Azure z Redis na oddzielnej maszynie wirtualnej. Myślę, że wyrównywacz obciążenia w chmurze zabija bezczynne połączenia powodując powyższy błąd. – Jarrod

+0

Nie można tak naprawdę korzystać z systemu równoważenia obciążenia w programie Redis, ponieważ go nie używamy. Naprawdę nie rozwiązaliśmy tego problemu - teraz widzimy mniej błędów, ponieważ zmniejszyliśmy czas oczekiwania na połączenie z serwerem do 300 sekund, ale wciąż widzimy je sporadycznie i nie mamy jeszcze rozwiązania. – Bernardo

+2

Widzę również ten błąd z wystąpieniem redis działającym w tym samym polu (przy użyciu kompilacji MSOpenTech). Używam 'BasicClientManager', ale nasz ruch jest znacznie mniejszy (pojedynczy AppPool z niskimi liczbami gości), więc nie spodziewalibyśmy się więcej niż kilka współbieżnych połączeń. Czy masz dalsze dochodzenie? – roryf

Odpowiedz

8

Uważamy znaleźliśmy przyczynę po dokładnym zapoznaniu się z dokumentacją Redis i znalezienie tego Beauty (http://redis.io/topics/persistence):

RDB needs to fork() often in order to persist on disk using a child process. 
Fork() can be time consuming if the dataset is big, and may result in Redis 
to stop serving clients for some millisecond or even for one second if the 
dataset is very big and the CPU performance not great. AOF also needs to fork() 
but you can tune how often you want to rewrite your logs without any trade-off 
on durability. 

Zwróciliśmy RDB wytrwałości off, i nie widziałem tych połączeń spada od.

+0

oznacza "save" "" i "appendonly yes" w pliku .conf? – sonjz

+0

dla mnie, widzę problem na AWS, nawet z AOF = ON i RDB = OFF. Dziwna część to tylko konkretny serwer, który będzie miał problem. jeśli mam 6 serwerów w puli, może 1 lub 2 konsekwentnie mają ten problem, reszta serwerów jest w porządku. – sonjz

+0

Opublikowany link mówi, że odradzanie RDB jest odradzane i że AOF i RDB mogą zostać połączone w jedną warstwę trwałości. Tak więc nie może to być długoterminowa poprawka, a zmiana limitu czasu serwera wydaje się być zniesmaczona. Jestem zaskoczony, że nie znalazłem więcej możliwych rozwiązań w tej kwestii. – Steven

1

Wygląda na to, że ustawienie limitu czasu serwera na 300 z 0 złagodziło problemy z połączeniem nieudolnymi. Nadal widoczne są złe połączenia, ale może to wynikać z tego, że obiekt PooledRedisClientManager nie sprawdza prawidłowo stanu połączenia dla GetInActiveWriteClient(), który jest wywoływany z GetClient().

Powiązane problemy