问题描述

在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=true 和 sslprotocols=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=true 和 sslprotocols=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。

【完】

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

  1. Windows Azure -Azure 网站、云服务和虚拟机的对比

    Azure 网站.云服务和虚拟机对比 概述 Azure提供了几种方法来承载网站: Azure网站.云服务和虚拟机.本文帮助您了解选项和为您的Web应用程序做出正确选择. Azure网站是大多数web应 ...

  2. Azure 网站、云服务和虚拟机比较

    最后更新时间(英文版):09/24/2014 最后更新时间(中文版):04/11/2015 Azure 提供几种方式托管 web 应用程序,如 Azure 网站.云服务和虚拟机.查看这些不同的选项后, ...

  3. Windows Azure移动终端云服务管理(公测版)

    概览 云在远方,管理在您手中.在这个移动为先 云为先的世界,服务不再是基于请求才提供,而是主动来到身边方便您的模式了.我们最近将会陆续推出几大移动端利器帮助您随时随地管理您的云服务. 首批利器之中排名 ...

  4. nopCommerce 3.9 大波浪系列 之 使用部署在Docker中的Redis缓存主从服务

    一.概述 nop支持Redis作为缓存,Redis出众的性能在企业中得到了广泛的应用.Redis支持主从复制,HA,集群. 一般来说,只有一台Redis是不可行的,原因如下: 单台Redis服务器会发 ...

  5. 【Azure Redis 缓存】Windows和Linux系统本地安装Redis, 加载dump.rdb中数据以及通过AOF日志文件追加数据

    任务描述 本次集中介绍使用Windows和Linux()搭建本地Redis服务器的步骤,从备份的RDB文件中加载数据,以及如何生成AOF文件和通过AOF文件想已经运行的Redis追加数据. 操作步骤 ...

  6. Windows Azure虚拟机和云服务实例计费方式更新

    在之前的Windows Azure计费账单中,A0,A1,A2,A3,A4系列的虚拟机(云服务实例)都是以A1为基准计费单位的,即: 虚拟机大小 计费单位(小时) A0 A1*0.25 A1 A1*1 ...

  7. Redis总结(二)C#中如何使用redis

    上一篇讲述了安装redis<Redis总结(一)Redis安装>,同时也大致介绍了redis的优势和应用场景.本篇着重讲解.NET中如何使用redis和C#. Redis官网提供了很多开源 ...

  8. Redis总结(二)C#中如何使用redis(转载)

    上一篇讲述了安装redis<Redis总结(一)Redis安装>,同时也大致介绍了redis的优势和应用场景.本篇着重讲解.NET中如何使用redis和C#. Redis官网提供了很多开源 ...

  9. 在.net Core中使用StackExchange.Redis 2.0

    StackExchange.Redis 2.0做了大量的改进包括使用了高性能的IO库System.IO.Pipelines来提升性能以及解决Timeouts问题, 但是在.net Core2.2之前为 ...

随机推荐

  1. 虹软人脸识别SDK接入Milvus实现海量人脸快速检索

    一.背景 人脸识别是近年来最热门的计算机视觉领域的应用之一,而且现在已经出现了非常多的人脸识别算法,如:DeepID.FaceNet.DeepFace等等.人脸识别被广泛应用于景区.客运.酒店.办公室 ...

  2. 【Azure 环境】由为存储账号(Storage Account)拒绝分配权限而引出的Azure 蓝图(Blueprint)使用问题

    问题描述 当打开Azure存储账号(Storage Account)门户页面时,从 "访问控制(标识和访问管理)" 页面中发现有"拒绝分配"的功能,所以就思考, ...

  3. 四、配置及使用Zabbix监控系统

    要求: 沿用练习- - -,使用Zabbix监控平台监控Linux服务器,实现以下目标:1.监控CPU2.监控内存3.监控进程4.监控网络流量5.监控硬盘 方案:通过Zabbix监控平台,添加被监控z ...

  4. Pipeline模式与Factory+Provider模式的应用

    前言 我正在写FastGithub这个小麻雀项目,里面主要涉及了Pipeline模式和Factory+Provider模式,这两种设计模式,让这个项目在"ip扫描"和"i ...

  5. 【dp】状压dp

    二进制的力量 状态压缩DP 愤怒的小鸟 第一次接触状态压缩DP是在NOIP2016的愤怒的小鸟,当时菜得连题目都没看懂,不过现在回过头来看还是挺简单的,那么我们再来看看这道题吧. 题意&数据范 ...

  6. RAC+DG修改sys密码

    一.版本: 操作系统版本:SUSE 11 数据库版本:11.2.0.4 二.需求 因安全要求,需要修改SYS密码 三.步骤 1节点执行命令: alter user sys identified by ...

  7. 测试开发之网络篇-IP地址

    IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异.这里介绍一下目前广泛使用的IPv4版本. IP地址使用一种统一的格式,为互联 ...

  8. Java知识复习(三)

    Set里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是equals()? 它们有何区别?用contains来区分是否有重复的对象.还是都不用. 在比较时先调用hashCode方法, ...

  9. 可扩展的 Web 架构与分布式系统

    作者:Kate Matsudaira 译者:尹星 本文介绍了分布式架构是如何解决系统扩展性问题的粗略方法,适合刚刚入门分布式系统的同学,我把整篇文章翻译如下,希望给你一些启发. 备注:[idea]标注 ...

  10. git 认证问题之一的解决 : http ssh 互换

    场景 使用git 我们经常会遇到 认证失败的情况,有时候确实是搞错了用户名或者密码,还有的时候及时用户名密码用对了也还是认证失败. 此时, 就有可能是下面这个情况. 没有配置 ssh 秘钥, 而用了 ...