前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Redis连接数为何会偏高

Redis连接数为何会偏高

作者头像
needrunning
发布2019-07-04 11:03:24
4.8K0
发布2019-07-04 11:03:24
举报
文章被收录于专栏:图南科技图南科技

本文介绍了ThinkPHP和YII2两个框架中对于redis的典型使用场景,通过连接数偏高的现象引出了长连接与短连接的概念,并且简单描述了几种网络连接状态,包括TIME_WAIT,ESTABLISHED,同时介绍了应用开发中Socket与TCP UDP的关联关系。

本文的大纲

问题描述 初步排查 TCP连接状态 ESTABLISHED TIME_WAIT 三次握手 Socket连接 长连接还是短链接 代码示例 结论

问题描述

运维收到线上服务器报警,反映Redis连接数过高,大量ESTABLISHED状态的连接,需要处理。 基础环境 PHP5.6 Think5 +Redis

初步排查

首先查看Redis配置,发现缓存业务连接参数采用的是短连接,第一反应是代码是否没有做close。一查代码层面果然没有close,立马加上看效果,依然如此。

再次查看,Redis托管Session部分的Redis采用的默认连接,默认连接是长连接。后边示例中有代码说明。

TCP网络连接状态

检查TIME_WAIT 连接个数 128个,状态显示 ESTABLISHED

代码语言:javascript
复制
netstat -na | grep 6379 | grep TIME_WAIT | wc -l

128
ESTABLISHED

大量ESTABLISHED状态代表什么,那我们往下看

ESTABLISHED的意思是建立连接。表示两台机器正在通信。

TIME_WAIT

这是 TCP 连接完全关闭前的最后一个状态,一个连接被关闭时,主动关闭的一端最后会进入 TIME_WAIT 状态,等待足够的时间以确保远程 TCP 接收到连接中断请求的确认,这个时间最大为四分钟,可调整。

如下图,图片来源于知乎

四次挥手断开连接

如上图,TCP中主动断开的一方确实会保持TIME_WAIT一段时间

两个状态都是TCP连接中的概念,说到TCP连接,我们不得不提三次握手和四次挥手以及Socket。三次握手用于建立连接,四次挥手用于断开连接。

三次握手

三次握手

三次握手

Socket连接

Socket连接到底是个什么概念? 1.完整的套接字格式{protocol,src_addr,src_port,dest_addr,dest_port}。 这常被称为套接字的五元组。其中protocol指定了是TCP还是UDP连接,其余的分别指定了源地址、源端口、目标地址、目标端口。

还有这么一个概念

TCP的连接端点称为 套接字(socket),根据TCP协议的规定,端口号拼接到IP地址即构成了套接字。

下面我们整理下TCP连接与Socket之间的关系。

TCP的连接端点实际上是一对儿客户端和服务器端,或者说是源地址和目标地址。

TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口。

借助网络的一张图,我们看看Socket在整个网络协议上的位置

socket是在应用层和传输层之间的一个抽象层

由于socket是全双工的工作模式,一个socket的关闭,是需要四次挥手来完成的。

主动关闭连接的一方,调用close();协议层发送FIN包 被动关闭的一方收到FIN包后,协议层回复ACK;然后被动关闭的一方,进入CLOSE_WAIT状态, 主动关闭的一方等待对方关闭,则进入FIN_WAIT_2状态;此时,主动关闭的一方 等待 被动关闭一方的应用程序,调用close操作。

被动关闭的一方在完成所有数据发送后,调用close()操作;此时,协议层发送FIN包给主动关闭的一方,等待对方的ACK,被动关闭的一方进入LAST_ACK状态;

主动关闭的一方收到FIN包,协议层回复ACK;此时,主动关闭连接的一方,进入TIME_WAIT状态;而被动关闭的一方,进入CLOSED状态。 等待2MSL时间,主动关闭的一方,结束TIME_WAIT,进入CLOSED状态

下面我们了解下长连接和短连接的相关概念

长连接还是短链接

知乎找到两张图来解释长连接和短连接

长连接

长连接

长连接,也叫持久连接,在TCP层握手成功后,不立即断开连接,并在此连接的基础上进行多次消息(包括心跳)交互,直至连接的任意一方(客户端OR服务端)主动断开连接,此过程称为一次完整的长连接。HTTP 1.1相对于1.0最重要的新特性就是引入了长连接。

短连接

短连接,顾名思义,与长连接的区别就是,客户端收到服务端的响应后,立刻发送FIN消息,主动释放连接。也有服务端主动断连的情况,凡是在一次消息交互(发请求-收响应)之后立刻断开连接的情况都称为短连接。缺点是每个连接都需要经过三次握手和四次握手的过程,耗时大大增加。

注:短连接是建立在TCP协议上的,有完整的握手挥手流程,区别于UDP协议。

作者:南阳居士 链接:https://www.zhihu.com/question/22677800/answer/382380580 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

短连接

Redis本身提供了两种对外连接访问接口pconnect和connect,也就是说应用程序有两种连接Redis方式,长连接(pconnect)和短连接(connect)。

这里说的长连接是指多次请求之间可以对redis连接进行复用,即只在第一次执行请求是建立连接,以后每次请求只是从连接池中将连接取出,不再重新建立连接;而短连接表示连接在多次请求之间不可复用,每次请求都需要重新建立连接。与上文描述的长连接和短连接含义一致。

对于PHP使用长连接,业界有一个观点

长连接只会在PHP-FPM进程结束之后结束,连接的生命周期就是PHP-FPM进程的生命周期。

如果代码中使用pconnect, close的作用仅是使当前php不能再进行redis请求,但无法真正关闭redis长连接,连接在后续请求中仍然会被重用,直至fpm进程生命周期结束。而这个连接数量由php-fpm的最大连接数决定 如: ps.maxChild=128,那么最大连接数就是128

疑问

使用connect需要显式调用close方法,会不会自动断开连接,是否需要显式设置连接超时时间?

我们在生产环境下遇到使用redis长连接方式连接数过高问题,改为短连接后,针对连接数偏高的想象,连接数立刻恢复正常。

既然pconnect可以 重用连接,什么场景下应该使用pconnect建立连接?

PHP生态下,没有真正意义的基于连接池的连接

代码示例

项目使用的ThinkPHP5,有两处使用Redis,一处是Redis托管登录Session信息,一处是其它业务Redis缓存。

参数配置中 persistent很关键,用于设置采用长连接还是短连接,生产环境的问题就是因为托管登录Session信息的配置中没有显式指定persistent=>false 造成的

Redis托管Session信息

我们先看Session相关的设置和执行

Session相关的Redis配置参数

代码语言:javascript
复制
     // +----------------------------------------------------------------------
    // | 会话设置
    // +----------------------------------------------------------------------
    'session' => [
        'id' => '',
        // SESSION_ID的提交变量,解决flash上传跨域
        'var_session_id' => '',
        // SESSION 前缀
        'prefix' => 'etcp',
        // 驱动方式 支持redis memcache memcached
        'type' => 'redis',
        // redis主机
        'host' => 'host',
        // redis端口
        'port' => 6379,
        // 密码
        'password' => '',
        'select' => 14,
        // 是否自动开启 SESSION
        'auto_start' => true,
        'time'
    ],

驱动文件路径 thinkphp/library/think/session/driver/Redis.php

代码语言:javascript
复制
    /**
     * 打开Session
     * @access public
     * @param string $savePath
     * @param mixed  $sessName
     * @return bool
     * @throws Exception
     */
    public function open($savePath, $sessName)
    {
        // 检测php环境
        if (!extension_loaded('redis')) {
            throw new Exception('not support:redis');
        }
        $this->handler = new \Redis;

        // 建立连接
        $func = $this->config['persistent'] ? 'pconnect' : 'connect';
        $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']);

        if ('' != $this->config['password']) {
            $this->handler->auth($this->config['password']);
        }

        if (0 != $this->config['select']) {
            $this->handler->select($this->config['select']);
        }

        return true;
    }

依据配置,通过Open函数可以看到默认通过persistent建立了长连接。

Redis缓存业务信息

配置参数

代码语言:javascript
复制
 'redis_server' => [
        'host' => 'REDIS_RW',
        'port' => 6379,
        'persistent' => false,
        'database' => 0,
        'profiler' => true,
        'password' => 'password',
    ]

设置与读取示例 借助github上一个开源redis操作包 "shen2/easy-redis": "dev-master"

代码语言:javascript
复制
class RedisClient
{
    private static $config;

    private static $_instance;

    /**
     * __construct
     *
     * @return mixed
     */
    public function __construct()
    {
        if (self::$config == null) {
            self::$config = Config::get('redis_server');
        }
    }

    /**
     * GetInstance
     *
     * @return mixed
     */
    public static function getInstance()
    {
        if (!self::$_instance instanceof self) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }

    /**
     * redis
     *
     * @return mixed
     */
    public static function getMainInstance()
    {
        if (!self::$_instance instanceof self) {
            self::$_instance = new self();
        }
        return self::$_instance;
    }


    /**
     * 设置key值
     *
     * @param mixed $key    键
     * @param mixed $value  值
     * @param mixed $expire 过期时间
     *
     * @return mixed
     */
    public function setCommond($key, $value, $expire)
    {
        $redisManager = new EasyRedis\Manager(self::$config);
        return $redisManager->call('set', $key, $value, $expire);
    }
    /**
     * 获取value
     *
     * @param mixed $name
     *
     * @return mixed
     */
    public function getCommond($name)
    {
        $redisManager = new EasyRedis\Manager(self::$config);
        return $redisManager->call('get', $name);
    }
    /*

YII2+Redis

驱动包github地址 https://github.com/yiisoft/yii2-redis

官方文档 Redis Cache, Session and ActiveRecord for Yii 2

从题目中就可以看出这个扩展的三块使用场景,缓存数据,托管Session,操作ActiveRecord。

可以在这里查看到更多使用信息 https://github.com/yiisoft/yii2-redis/blob/master/docs/guide/README.md

配置

代码语言:javascript
复制
[
    'class' => 'yii\redis\Connection',
    'hostname' => 'REDIS_PHP',
    'port' => 6379,
    'password' => 'password',
    'database' => 0,
];

使用

代码语言:javascript
复制
/**
     * CreateLoginToken
     *
     * @param mixed $userId
     * @param mixed $timeStamp
     * @param mixed $token_ttl 3600
     * @param mixed $data      缓存数据
     *
     * @return mixed
     */
    public function createLoginToken($userId, $timeStamp, $token_ttl=3600, $data=[])
    {
        $token = md5($userId . self::SALT . $timeStamp);
        $value=json_encode(array("uid"=>$userId,"data"=>$data));
        $redis = \Yii::$app->redis;
        $this->_ttl = $token_ttl;
        $result = $redis->setex($token, $this->_ttl, $value);
        return $token;
    }

    /**
     * Remove LoginToken
     *
     * @param mixed $token Token
     *
     * @return mixed
     */
    public function removeLoginToken($token)
    {
        $redis = \Yii::$app->redis;
        $result = $redis->del($token);
        return $result;
    }

连接池

连接池的作用是复用连接,省去了每次都要建立连接的通信成本。使用连接池,就得研究长连接。

PHP是否使用连接池

php作为脚本语言,上文提到的观点

长连接只会在PHP-FPM进程结束之后结束,连接的生命周期就是PHP-FPM进程的生命周期

或者换一种说法,真正基于连接池的长连接,并且能实现重用连接,达到降低系统消耗的目的,PHP环境下做不到。

如果TIME_WAIT数太多,并不是将连接改为长连接即可,PHP环境下,可以改为短连接验证一下,是否能够满足业务场景需要。一切以能满足业务场景为最终目的。

结论

都说长连接可以降低系统消耗,公用连接,这里的公用连接是有代价的,并不是只要长连接都可以降低系统资源。PHP环境下,连一次断一次,一个请求一次处理,挺好。

本篇文章涉及的网络协议比较多,由实际问题入手,层层深入,部分观点整理于网络,由于作者水平有限,文中的观点有不确切之处,欢迎评论讨论。请阅读原文获取更好的阅读体验。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-06-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 图南科技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题描述
  • 初步排查
  • TCP网络连接状态
    • ESTABLISHED
      • TIME_WAIT
      • 三次握手
      • Socket连接
      • 长连接还是短链接
        • 长连接
          • 短连接
          • 疑问
          • 代码示例
          • Redis托管Session信息
          • Redis缓存业务信息
          • YII2+Redis
          • 配置
          • 连接池
          • PHP是否使用连接池
          • 结论
          相关产品与服务
          云数据库 Redis
          腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档