【Azure Redis 缓存】云服务Worker Role中调用StackExchange.Redis,遇见莫名异常(RedisConnectionException: UnableToConnect on xxx 或 No connection is available to service this operation: xxx)

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 【Azure Redis 缓存】云服务Worker Role中调用StackExchange.Redis,遇见莫名异常(RedisConnectionException: UnableToConnect on xxx 或 No connection is available to service this operation: xxx)

问题描述

在Visual Studio 2019中,通过Cloud Service模板创建了一个Worker Role的角色,在角色中使用StackExchange.Redis来连接Redis。遇见了一系列的异常:

  • RedisConnectionException: No connection is available to service this operation: PING; It was not possible to connect to the redis server(s); ConnectTimeout; IOCP: (Busy=0,Free=1000,Min=8,Max=1000), WORKER: (Busy=2,Free=32765,Min=8,Max=32767), Local-CPU: n/a
  • RedisConnectionException: UnableToConnect on xxxxxx.redis.cache.chinacloudapi.cn:6380/Interactive, origin: ResetNonConnected, input-buffer: 0, outstanding: 0, last-read: 5s ago, last-write: 5s ago, unanswered-write: 524763s ago, keep-alive: 60s, pending: 0, state: Connecting, last-heartbeat: never, last-mbeat: -1s ago, global: 5s ago, mgr: Inactive, err: never
  • IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.
  • SocketException: An existing connection was forcibly closed by the remote host

异常截图:

问题分析

根据异常信息 Socket Exception, 在建立连接的时候被Remote Host关闭,也就是Redis服务端强制关闭了此连接。那么就需要进一步分析,为什么Redis会强制关闭连接呢? 查看Redis的连接字符串:

xxxxxx.redis.cache.chinacloudapi.cn:6380,password=<access key>,ssl=True,abortConnect=False

使用6380端口,建立SSL连接,在连接字符串中已经启用SSL。在创建Azure Redis的资源中,会发现一段提示:TLS1.0,1.1已不被支持。需要使用TLS1.2版本。

而当前的Cloud Service使用的是.NET Framework 4.5。 而恰巧,在 .NET Framework 4.5.2 或更低版本上,Redis .NET 客户端默认使用最低的 TLS 版本;在 .NET Framework 4.6 或更高版本上,则使用最新的 TLS 版本。

所以如果使用的是较旧版本的 .NET Framework,需要手动启用 TLS 1.2: StackExchange.Redis: 在连接字符串中设置 ssl=truesslprotocols=tls12

 

问题解决

在字符串中添加 ssl=True,sslprotocols=tls12, 完整字符串为:

string cacheConnection = "xxxxxx.redis.cache.chinacloudapi.cn:6380,password=xxxxxxxxx+xxx+xxxxxxx=,ssl=True,sslprotocols=tls12, abortConnect=False";

在Visual Studio 2019代码中的效果如:

Could Service 与 Redis 使用的简单代码片段为

WorkerRole:

using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace WorkerRole1
{
    public class WorkerRole : RoleEntryPoint
    {
        private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
        private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);
        private RedisJob redisjob1 = new RedisJob();
        public override void Run()
        {
            Trace.TraceInformation("WorkerRole1 is running");
            try
            {
                this.RunAsync(this.cancellationTokenSource.Token).Wait();
            }
            finally
            {
                this.runCompleteEvent.Set();
            }
        }
        public override bool OnStart()
        {
            // Set the maximum number of concurrent connections
            ServicePointManager.DefaultConnectionLimit = 12;
            //ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
            // For information on handling configuration changes
            // see the MSDN topic at https://go.microsoft.com/fwlink/?LinkId=166357.
            bool result = base.OnStart();
            Trace.TraceInformation("WorkerRole1 has been started");
            return result;
        }
        public override void OnStop()
        {
            Trace.TraceInformation("WorkerRole1 is stopping");
            this.cancellationTokenSource.Cancel();
            this.runCompleteEvent.WaitOne();
            base.OnStop();
            Trace.TraceInformation("WorkerRole1 has stopped");
        }
        private async Task RunAsync(CancellationToken cancellationToken)
        {
            // TODO: Replace the following with your own logic.
            while (!cancellationToken.IsCancellationRequested)
            {
                Trace.TraceInformation("Working");
                redisjob1.RunReidsCommand();
                await Task.Delay(10000);
            }
        }
    }
}

RedisJob:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using StackExchange.Redis;
namespace WorkerRole1
{
    class RedisJob
    {
        private static Lazy<ConnectionMultiplexer> lazyConnection = CreateConnection();
        public static ConnectionMultiplexer Connection
        {
            get
            {
                return lazyConnection.Value;
            }
        }
        private static Lazy<ConnectionMultiplexer> CreateConnection()
        {
            return new Lazy<ConnectionMultiplexer>(() =>
            {
                string cacheConnection = "xxxxxx.redis.cache.chinacloudapi.cn:6380,password=xxxxxx+xxx+xxxx=,ssl=True,sslprotocols=tls12, abortConnect=False";
                return ConnectionMultiplexer.Connect(cacheConnection);
            });
        }
        public void RunReidsCommand() {
            IDatabase cache = Connection.GetDatabase();
            // Perform cache operations using the cache object...
            // Simple PING command
            string cacheCommand = "PING";
            Console.WriteLine("\nCache command  : " + cacheCommand);
            Console.WriteLine("Cache response : " + cache.Execute(cacheCommand).ToString());
            // Simple get and put of integral data types into the cache
            cacheCommand = "GET Message";
            Console.WriteLine("\nCache command  : " + cacheCommand + " or StringGet()");
            Console.WriteLine("Cache response : " + cache.StringGet("Message").ToString());
            cacheCommand = "SET Message \"Hello! The cache is working from a .NET console app!\"";
            Console.WriteLine("\nCache command  : " + cacheCommand + " or StringSet()");
            Console.WriteLine("Cache response : " + cache.StringSet("Message", "Hello! The cache is working from a .NET console app!").ToString());
            // Demonstrate "SET Message" executed as expected...
            cacheCommand = "GET Message";
            Console.WriteLine("\nCache command  : " + cacheCommand + " or StringGet()");
            Console.WriteLine("Cache response : " + cache.StringGet("Message").ToString());
        }
    }
}

 

参考资料

删除与 Azure Cache for Redis 配合使用的 TLS 1.0 和 1.1:  https://docs.microsoft.com/zh-cn/azure/azure-cache-for-redis/cache-remove-tls-10-11

将应用程序配置为使用 TLS 1.2

大多数应用程序使用 Redis 客户端库来处理与缓存的通信。 这里说明了如何将以各种编程语言和框架编写的某些流行客户端库配置为使用 TLS 1.2。

.NET Framework

在 .NET Framework 4.5.2 或更低版本上,Redis .NET 客户端默认使用最低的 TLS 版本;在 .NET Framework 4.6 或更高版本上,则使用最新的 TLS 版本。 如果使用的是较旧版本的 .NET Framework,则可以手动启用 TLS 1.2:

  • StackExchange.Redis: 在连接字符串中设置 ssl=truesslprotocols=tls12
  • ServiceStack.Redis: 请按照 ServiceStack.Redis 说明操作,并至少需要 ServiceStack.Redis v5.6。

.NET Core

Redis .NET Core 客户端默认为操作系统默认 TLS 版本,此版本明显取决于操作系统本身。

根据操作系统版本和已应用的任何修补程序,有效的默认 TLS 版本可能会有所不同。 有一个关于此内容的信息源,也可以访问此处,阅读适用于 Windows 的相应文章。

但是,如果你使用的是旧操作系统,或者只是想要确保我们建议通过客户端手动配置首选 TLS 版本。

Java

Redis Java 客户端基于 Java 版本 6 或更早版本使用 TLS 1.0。 如果在缓存中禁用了 TLS 1.0,则 Jedis、Lettuce 和 Redisson 无法连接到 Azure Cache for Redis。 升级 Java 框架以使用新的 TLS 版本。

对于 Java 7,Redis 客户端默认不使用 TLS 1.2,但可以配置为使用此版本。 Jedis 允许你使用以下代码片段指定基础 TLS 设置:

SSLSocketFactory sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLParameters sslParameters = new SSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
sslParameters.setProtocols(new String[]{"TLSv1.2"});
 
URI uri = URI.create("rediss://host:port");
JedisShardInfo shardInfo = new JedisShardInfo(uri, sslSocketFactory, sslParameters, null);
 
shardInfo.setPassword("cachePassword");
 
Jedis jedis = new Jedis(shardInfo);

Lettuce 和 Redisson 客户端尚不支持指定 TLS 版本,因此,如果缓存仅接受 TLS 1.2 连接,这些客户端将无法工作。 我们正在审查这些客户端的修补程序,因此请检查那些包是否有包含此支持的更新版本。

在 Java 8 中,默认情况下会使用 TLS 1.2,并且在大多数情况下都不需要更新客户端配置。 为了安全起见,请测试你的应用程序。

Node.js

Node Redis 和 IORedis 默认使用 TLS 1.2。

PHP

Predis

  • 低于 PHP 7 的版本:Predis 仅支持 TLS 1.0。 这些版本不支持 TLS 1.2;必须升级才能使用 TLS 1.2。
  • PHP 7.0 到 PHP 7.2.1:默认情况下,Predis 仅使用 TLS 1.0 或 TLS 1.1。 可以通过以下变通办法来使用 TLS 1.2。 在创建客户端实例时指定 TLS 1.2:
$redis=newPredis\Client([
    'scheme'=>'tls',
    'host'=>'host',
    'port'=>6380,
    'password'=>'password',
    'ssl'=>[
        'crypto_type'=>STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
    ],
]);
  • PHP 7.3 及更高版本:Predis 使用最新的 TLS 版本。

PhpRedis

PhpRedis 在任何 PHP 版本上均不支持 TLS。

Python

Redis-py 默认使用 TLS 1.2。

GO

Redigo 默认使用 TLS 1.2。

 

【完】

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
18天前
|
缓存 NoSQL Java
Redis深度解析:解锁高性能缓存的终极武器,让你的应用飞起来
【8月更文挑战第29天】本文从基本概念入手,通过实战示例、原理解析和高级使用技巧,全面讲解Redis这一高性能键值对数据库。Redis基于内存存储,支持多种数据结构,如字符串、列表和哈希表等,常用于数据库、缓存及消息队列。文中详细介绍了如何在Spring Boot项目中集成Redis,并展示了其工作原理、缓存实现方法及高级特性,如事务、发布/订阅、Lua脚本和集群等,帮助读者从入门到精通Redis,大幅提升应用性能与可扩展性。
40 0
|
1天前
|
canal 缓存 NoSQL
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
根据对一致性的要求程度,提出多种解决方案:同步删除、同步删除+可靠消息、延时双删、异步监听+可靠消息、多重保障方案
Redis缓存与数据库如何保证一致性?同步删除+延时双删+异步监听+多重保障方案
|
2天前
|
存储 NoSQL Redis
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
Redis持久化、RDB和AOF方案、Redis主从集群、哨兵、分片集群、散列插槽、自动手动故障转移
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
|
12天前
|
缓存 NoSQL 关系型数据库
MySQL与Redis缓存一致性的实现与挑战
在现代软件开发中,MySQL作为关系型数据库管理系统,广泛应用于数据存储;而Redis则以其高性能的内存数据结构存储特性,常被用作缓存层来提升数据访问速度。然而,当MySQL与Redis结合使用时,确保两者之间的数据一致性成为了一个重要且复杂的挑战。本文将从技术角度分享MySQL与Redis缓存一致性的实现方法及其面临的挑战。
36 2
|
15天前
|
Java UED Maven
紧跟技术潮流:手把手教你构建响应式Vaadin应用,让用户体验无缝接轨!
【8月更文挑战第31天】本文从零开始,详细介绍如何使用强大的Java框架Vaadin构建流畅且响应式的Web应用程序。首先,确保安装JDK 1.8+、Maven 3.3.9+及IDE。接着,创建Maven项目并添加Vaadin依赖。然后,通过继承`UI`类创建主界面,并定义自定义主题与样式。利用Vaadin的响应式布局组件,如`HorizontalLayout`和`VerticalLayout`,实现多设备兼容性。
26 0
|
15天前
|
缓存 NoSQL Redis
Entity Framework Core 与 Redis 强强联手!实现高速缓存,提升应用性能超厉害
【8月更文挑战第31天】在现代应用开发中,结合 Entity Framework Core 与 Redis 可显著提升数据访问速度。Entity Framework Core 是一个强大的 ORM 框架,但处理频繁访问的数据时可能遇到性能瓶颈。Redis 作为高性能内存数据库,具备快速读写能力。两者结合利用 Redis 高速缓存,减少直接数据库访问,提高应用响应速度及性能。
23 0
|
18天前
|
缓存 NoSQL Java
惊!Spring Boot遇上Redis,竟开启了一场缓存实战的革命!
【8月更文挑战第29天】在互联网时代,数据的高速读写至关重要。Spring Boot凭借简洁高效的特点广受开发者喜爱,而Redis作为高性能内存数据库,在缓存和消息队列领域表现出色。本文通过电商平台商品推荐系统的实战案例,详细介绍如何在Spring Boot项目中整合Redis,提升系统响应速度和用户体验。
41 0
|
21天前
|
NoSQL 网络协议 Linux
【Azure Redis】Redis客户端出现15分钟的超时异常
【Azure Redis】Redis客户端出现15分钟的超时异常
|
21天前
|
缓存 NoSQL Linux
【Azure Redis 缓存】应用中出现连接Redis服务错误(production.ERROR: Connection refused)的排查步骤
【Azure Redis 缓存】应用中出现连接Redis服务错误(production.ERROR: Connection refused)的排查步骤
|
21天前
|
缓存 NoSQL 网络安全
【Azure Redis 缓存】使用开源工具redis-copy时遇见6379端口无法连接到Redis服务器的问题
【Azure Redis 缓存】使用开源工具redis-copy时遇见6379端口无法连接到Redis服务器的问题