博客

  • php 字符串中是否包含指定字符串的多种方法

    程序代码如下:

    1. strstr

    strstr() 函数搜索一个字符串在另一个字符串中的第一次出现。
    该函数返回字符串的其余部分(从匹配点)。如果未找到所搜索的字符串,则返回 false。

    代码如下:

    1

    2

    3

    4

    5

    6

    7

    <?php

     /*如手册上的举例*/

     $email = 'user@example.com';

     $domain = strstr($email, '@');

     echo $domain;

     // prints @example.com

    ?>

    2. stristr

    stristr() 函数查找字符串在另一个字符串中第一次出现的位置。
    如果成功,则返回字符串的其余部分(从匹配点)。如果没有找到该字符串,则返回 false。

    它和strstr的使用方法完全一样.唯一的区别是stristr不区分大小写.

    3. strpos

    strpos函数返回boolean值.FALSE和TRUE不用多说.用 “===”进行判断.strpos在执行速度上都比以上两个函数快,另外strpos有一个参数指定判断的位置,但是默认为空.意思是判断整个字符串.缺点是对中文的支持不好.

    实例1

    1

    2

    3

    4

    5

    if(strpos('www.jb51.net','jb51') !== false){

     echo '包含jb51';

    }else{

     echo '不包含jb51';

    }

    实例2

    1

    2

    3

    $str= 'abc';

    $needle= 'a';

    $pos = strpos($str, $needle); // 返回第一次找到改字符串的位置,这里返回为1,若查不到则返回False

    4. explode

    用explode进行判断PHP判断字符串的包含代码如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    function checkstr($str){

     $needle ='a';//判断是否包含a这个字符

     $tmparray = explode($needle,$str);

     if(count($tmparray)>1){

     return true;

     } else{

     return false;

     }

    }

    5、substr例如我们需要判断最后一个字符是不是制定字符

    1

    2

    3

    4

    5

    6

    <?php

    /*

    $str1="<p>这是个winrar专用的dll然后下哦啊不错的dll文件,QlogWin32.dll</p>";

    if(substr($str1,-8)==".dll</p>"){

    echo substr($str1,0,-4);

    }

    6、substr_count统计“子字符串”在“原始字符串中出现的次数”

    substr_count()函数本是一个小字符串在一个大字符串中出现的次数:
    $number = substr_count(big_string, small_string);
    正好今天需要一个查找字符串的函数,要实现判断字符串big_string是否包含字符串small_string,返回true或fasle;

    查了半天手册没有找到现成的函数,于是想到可以用substr_count函数来实现代码如下:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    function check_str($str, $substr)

    {

     $nums=substr_count($str,$substr);

     if ($nums>=1)

     {

      return true;

     }

     else

     {

      return false;

     }

    }

  • php 正则匹配出a标签级a标签中的内容

    程序代码如下:

    <?php
    header("Content-type: text/html; charset=utf-8");

    $str=file_get_contents("https://www.xxxxxx.com/");

    //拿出网页中所有《a》标签放到数组
    $reg1="/<a .*?>.*?<\/a>/";
    $aarray;//这个存放的就是正则匹配出来的所有《a》标签数组
    preg_match_all($reg1,$str,$aarray);
    //拿出《a》标签中的链接和标签内容
    $hrefarray;//这个存放的是匹配出来的href的链接地址
    $acontent;//存放匹配出来的a标签的内容
    $reg2="/href=\"([^\”]+)/";
    for($i=0;$i<count($aarray[0]);$i++){
    preg_match_all($reg2,$aarray[0][$i],$hrefarray);
    echo $hrefarray[1][0]."\r\n";//这里输出的就是遍历出来的所有a标签的链接
    //拿出《a》标签的内容
    $reg3="/>(.*)<\/a>/";
    preg_match_all($reg3,$aarray[0][$i],$acontent);
    echo $acontent[1][0]."\r\n";//这里输出的就是a标签的文字了
    }
    ?>

  • 微信客服消息:点击文字直接回复公众号的实现方式(点击文字模拟用户发送关键字)

    功能描述:
    1. 这里要讲的核心内容是基于微信浏览器的URL Schemes来实现微信内部消息的控制,就是weixin://,这个是不是和http://有一样的格式,表达的意思类似,就是告知浏览器,采取什么协议解析URL内容,只是这里的weixin://只能是微信浏览器才能识别。
    2. 微信内部的客服消息,其功能,就是将推荐的客户可能问到的问题,以一个问题列表的方式展现给客户,方便客户直接点击问题,就相当于提问了,表现形式,就是客户点击了的问题,直接在提问框中显示出来了,和客户自己输入了这个问题一样的效果

     这里,我们先看一个效果图:

    下面来说说,具体的技术逻辑:

    实现方法
    在返回的文字中,使用如下方法即可:
    <a href="weixin://bizmsgmenu?msgmenucontent=xxx&msgmenuid=1">xxx</a>
    示例:
    <a  href="weixin://bizmsgmenu?msgmenucontent=饿了么红包&msgmenuid=0">【点我】领饿了么红包</a>

    其中:
    msgmenucontent:是你点击后,展示位微信对话框的内容
    msgmenuid:微信会按照text方式将数据给开发者服务器,开发者可以拿到该参数进行后续的逻辑判断处理(MsgType为event,Event为text)

    注意:
    其实如果是返回固定的数据,那么msgmenuid其实并不需要考虑,但是如果需要使用到开发者服务器记录的数据,那么就需要进行借助到该参数了

    转载于:
    https://blog.csdn.net/gymaisyl/article/details/111151077 
    https://www.cnblogs.com/houss/p/11304389.html

  • 《pi network》密码找回教程

    有的小伙伴长时间不登录pi network,不小心忘记了自己账户的密码。那么pi币密码忘了怎么办?该如何找回呢?下面小编为大家带来了pi network密码找回教程,相信会对你有所帮助。

    《pi network》密码找回教程

    pi币密码忘了怎么办?

    1、打开Pi币,点击“Continue with phone number”(使用手机号码)

    《pi network》密码找回教程

    2、点击“Password forgotten?”(忘记密码)

    《pi network》密码找回教程

    3、点击“RECOVER ACCOUNT”(找回账号)

    《pi network》密码找回教程

    4、填写注册时的手机号码,点击“SUBMIT

    《pi network》密码找回教程

    5、可以选择美国通道或英国通道,点击“OPEN SMS”(打开短信)

    《pi network》密码找回教程

    6、点击“OPEN SMS”(打开短信,收件号码和发送内容自动弹出,点击发送即可)

    《pi network》密码找回教程

    7、发送成功后,返回Pi

    《pi network》密码找回教程

    8、点击“SET UP PASSWORD”(重新设置密码)

    《pi network》密码找回教程

    9、填写密码并确认密码(密码长度大于8位的大小写+数字组成),点击“SUBMIT”即可成功修改密码。

    《pi network》密码找回教程

    以上就是小编带来的pi币密码忘了怎么办?pi network密码找回教程了,更多相关资讯教程,请关注3DM手游网。

     

    来源:https://shouyou.3dmgame.com/gl/194856.html

     

  • Linux系统下载(超全镜像下载)

    1、Linux官方镜像

    Deepin镜像
    https://www.deepin.org/mirrors/releases/

    Mint 镜像
    https://www.linuxmint.com/mirrors.php

    kernel 镜像站:
    http://mirrors.kernel.org/

    Fedora 官方镜像站:
    http://mirrors.fedoraproject.org/publiclist
    https://torrents.fedoraproject.org/
    https://admin.fedoraproject.org/mirrormanager/mirrors

    Debian 全球镜像站:
    http://www.debian.org/mirror/
    https://cdimage.debian.org/cdimage/archive/

    Ubuntu 官方镜像站:
    http://releases.ubuntu.com/releases/
    http://cdimage.ubuntu.com/
    https://launchpad.net/ubuntu/+cdmirrors
    http://old-releases.ubuntu.com/releases/

    SUSE官方镜像站:
    http://download.opensuse.org/
    https://mirrors.opensuse.org/

    CentOS:
    http://mirror-status.centos.org/#cn

    Archlinux:
    https://www.archlinux.org/mirrors/status/

    Apache:
    http://www.apache.org/mirrors/#cn

    Cygwin:
    https://www.cygwin.com/mirrors.html

    kali linux:
    https://www.kali.org/downloads/

    2、企业机构开源镜像站

    网易开源镜像站:
    http://mirrors.163.com/

    搜狐开源镜像站:
    http://mirrors.sohu.com/

    首都在线科技股份有限公司:
    http://mirrors.yun-idc.com/

    中国互联网信息中心:
    http://mirrors.cnnic.cn (Apache镜像)

    阿里云开源镜像:
    http://mirrors.aliyun.com/

    常州贝特康姆软件技术有限公司(原cn99):
    http://centos.bitcomm.cn/

    开源世界:
    http://mirror.lupaworld.com/

    3、国内高校的开源镜像站

    电子科技大学:
    http://ubuntu.uestc.edu.cn/

    上海交通大学:
    http://ftp.sjtu.edu.cn/ (IPv4 only)
    http://ftp6.sjtu.edu.cn(IPv6 only)

    中国科学技术大学:
    http://mirrors.ustc.edu.cn/ (IPv4+IPv6)
    http://mirrors4.ustc.edu.cn/
    http://mirrors6.ustc.edu.cn/

    东北大学:
    http://mirror.neu.edu.cn/ (IPv4 only)
    http://mirror.neu6.edu.cn/ (IPv6 only)

    北京交通大学:
    http://mirror.bjtu.edu.cn (IPv4 only)
    http://mirror6.bjtu.edu.cn
    http://debian.bjtu.edu.cn

    北京化工大学:
    http://ubuntu.buct.edu.cn/

    天津大学 :
    http://mirror.tju.edu.cn/

    厦门大学:
    http://mirrors.xmu.edu.cn/

    浙江大学:
    http://mirrors.zju.edu.cn/

    中山大学镜像:
    http://mirror.sysu.edu.cn/

    华中科技大学:
    http://mirrors.hustunique.com/

    上海交通大学:
    http://ftp.sjtu.edu.cn/html/resources.xml

    华中科技大学:
    http://mirror.hust.edu.cn/

    清华大学:
    http://mirrors.tuna.tsinghua.edu.cn/

    北京理工大学:
    http://mirror.bit.edu.cn/web/

    兰州大学:
    http://mirror.lzu.edu.cn/

    中国科技大学:
    http://mirrors.ustc.edu.cn/

    大连东软信息学院:
    http://mirrors.neusoft.edu.cn/

    东北大学:
    http://mirror.neu.edu.cn/

    大连理工大学:
    http://mirror.dlut.edu.cn/

    哈尔滨工业大学:
    http://run.hit.edu.cn/html/

    北京交通大学:
    http://mirror.bjtu.edu.cn/cn/

    天津大学:
    http://mirror.tju.edu.cn

    中国地质大学:
    http://mirrors.cug.edu.cn/

    浙江大学:
    http://mirrors.zju.edu.cn/

    厦门大学:
    http://mirrors.xmu.edu.cn/

    中山大学:
    http://mirror.sysu.edu.cn/

    重庆大学:
    http://mirrors.cqu.edu.cn/

    北京化工大学:
    http://ubuntu.buct.edu.cn/

    南阳理工学院:
    http://mirror.nyist.edu.cn/

    中国科学院:
    http://www.opencas.org/mirrors/

    电子科技大学:
    http://ubuntu.uestc.edu.cn/

    电子科技大学星辰工作室:
    http://mirrors.stuhome.net/

    西北农林科技大学:
    http://mirrors.nwsuaf.edu.cn/

    香港中文大学 :
    http://ftp.cuhk.edu.hk/pub/Linux/

    香港浸会大学 :
    http://ftp.comp.hkbu.edu.hk/pub/

    Rackspace HK:
    http://hkg.mirror.rackspace.com

    4、分类镜像服务器

    RedHat Enterprise Linux 红帽Linux镜像站点(主要是RHEL资源):
    http://eduunix.ccut.edu.cn/index2/unixsystem/RedHat/
    http://www.mmnt.net/db/0/0/volt.iem.pw.edu.pl/pub/Linux/RedHat
    http://ftp.corbina.net/pub/Linux/redhat/

    首都在线科技股份有限公司(英文名Capital Online Data Service):
    http://mirrors.yun-idc.com/

    中国电信天翼云:
    http://mirrors.ctyun.cn/

    noc.im:http://mirrors.noc.im/

    常州贝特康姆软件技术有限公司:
    http://centos.bitcomm.cn/

    公云PubYun(母公司为贝特康姆):
    http://mirrors.pubyun.com/

    Linux运维派:
    http://mirrors.skyshe.cn/

    中国互联网络信息中心:
    http://mirrors.cnnic.cn/

    Fayea工作室:
    http://apache.fayea.com/

    中国科学院:
    http://mirrors.opencas.ac.cn/android/repository/

    南洋理工学院:
    http://mirror.nyist.edu.cn/android/repository/

    中国科学院:
    http://mirrors.opencas.cn/android/repository/

    5、国外各大学的镜像站

    北陆先端科学技术大学院大学JAIST:
    http://ftp.jaist.ac.jp/pub/

    卡内基梅隆大学CMU:
    http://www.club.cc.cmu.edu/pub

    麻省理工学院MIT:
    http://mirrors.mit.edu/

    哥伦比亚大学:
    http://mirror.cc.columbia.edu/

    俄勒冈州立大学:
    http://ftp.osuosl.org/pub

    伊利诺伊大学厄巴纳-香槟分校:
    http://cosmos.cites.illinois.edu/

    杜克大学:
    http://archive.linux.duke.edu/

    约翰·霍普金斯大学:
    http://mirrors.acm.jhu.edu/

    俄罗斯镜像服务器:
    http://ftp.kddilabs.jp/
    http://ftp.jaist.ac.jp/pub/
    http://ftp.kaist.ac.kr/
    http://mirror.karneval.cz/pub/
    http://ftp.gwdg.de/pub/
    http://ftp.estpak.ee/pub/

    持续更新ing

    以上部分需要翻墙,还有部分链接可能失效,如遇到还请告知。
    emmm都是从各个地方收集来的,我也不知道谁的2333,如有侵权,请告知

     
    ————————————————
    版权声明:本文为CSDN博主「xfx98」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/qq_40827780/article/details/83385464

  • PHP爬取微信公众号文章(可做为扩展类直接使用)

    开门见山的说,我是在GitHub上拿的代码,但由于那个项目年代比较久远了,公众号的一些规则变了,不能用了,所以我就改了部分代码,达到直接用的地步。

    功能:根据微信公众号链接,爬取文章的文字和图片(下载到本地),以html形式保存。

    直接贴代码,就一个文件,可以直接用

    程序代码如下:
    <?php
    namespace WxCrawler;
    /**
     * 微信公众号文章爬取类
     */
    class WxCrawler
    {
     
        //微信内容div正则
        private $wxContentDiv = '/<div class="rich_media_content " id="js_content" style="visibility: hidden;">(.*?)<\/div>/s';
        //微信图片样式
        private $imageStyle = 'style="max-width: 677px !important;height: auto !important;visibility: visible !important;"';
     
        /**
         * 爬取内容
         * @param  $url
         * @return false|string
         * @author bignerd
         * @since  2016-08-16T10:13:58+0800
         */
        private function _get($url)
        {
            return file_get_contents($url);
        }
     
        public function crawByUrl($url)
        {
            $content = $this->_get($url);
     
            $basicInfo = $this->articleBasicInfo($content);
            list($content_html, $content_text) = $this->contentHandle($content);
            return array_merge($basicInfo,['content_html' => $content_html,'content_text' => $content_text]);
        }
        /**
         * 处理微信文章源码,提取文章主体,处理图片链接
         * @author bignerd
         * @since  2016-08-16T15:59:27+0800
         * @param  $content 抓取的微信文章源码
         * @return [带图html文本,无图html文本]
         */
        private function contentHandle($content)
        {
            $content_html_pattern = $this->wxContentDiv;
            preg_match_all($content_html_pattern, $content, $html_matchs);
            if(empty(array_filter($html_matchs))) {
                echo '文章不存在';
                exit();
            }
            $content_html = $html_matchs[0][0];
            //去除掉hidden隐藏
            $content_html = str_replace('style="visibility: hidden;"','',$content_html);
            //过滤掉iframe
            $content_html = preg_replace('/<iframe(.*?)<\/iframe>/','',$content_html);
            $path = 'article/';
            /** @var  带图片html文本 */
            $content_html = preg_replace_callback('/data-src="(.*?)"/', function($matches) use ($path){
                return 'src="' . $path . $this->getImg($matches[1]).'" '.$this->imageStyle;
            }, $content_html);
     
            //添加微信样式
            $content_html = '<div style="max-width: 677px;margin-left: auto;margin-right: auto;">'.$content_html. '</div>';
            /** @var  无图html文本 */
            $content_text = preg_replace('/<img.*?>/s','',$content_html);
     
            return [$content_html,$content_text];
        }
        /**
         * 获取文章的基本信息
         * @author bignerd
         * @since  2016-08-16T17:16:32+0800
         * @param  $content 文章详情源码
         * @return $basicInfo
         */
        private function articleBasicInfo($content)
        {
            //待获取item
            $item = [
                'ct' => 'date',//发布时间
                'msg_title' => 'title',//标题
                'msg_desc' => 'digest',//描述
                'msg_link' => 'content_url',//文章链接
                'msg_cdn_url' => 'cover',//封面图片链接
                'nickname' => 'wechatname',//公众号名称
            ];
            $basicInfo = [
                'author' => '',
                'copyright_stat' => '',
            ];
            foreach ($item as $k => $v) {
                if($k == 'msg_title')
                    $pattern = '/ var '.$k.' = (.*?)\.html\(false\);/s';
                else
                    $pattern = '/ var '.$k.' = "(.*?)";/s';
     
                preg_match_all($pattern,$content,$matches);
                if(array_key_exists(1, $matches) && !empty($matches[1][0])){
                    $basicInfo[$v] = $this->htmlTransform($matches[1][0]);
                }else{
                    $basicInfo[$v] = '';
                }
            }
            //2020/4/3获取作者已失效
    //        /** 获取作者 */
    //        preg_match('/<em class="rich_media_meta rich_media_meta_text">(.*?)<\/em>/s', $content, $matchAuthor);
    //        if(!empty($matchAuthor[1])) $basicInfo['author'] = $matchAuthor[1];
    //        /** 文章类型 */
    //        preg_match('/<span id="copyright_logo" class="rich_media_meta meta_original_tag">(.*?)<\/span>/s', $content, $matchType);
    //        if(!empty($matchType[1])) $basicInfo['copyright_stat'] = $matchType[1];
     
            return $basicInfo;
        }
        /**
         * 特殊字符转换
         * @author bignerd
         * @since  2016-08-16T17:30:52+0800
         * @param  $string
         * @return $string
         */
        private function htmlTransform($string)
        {
            $string = str_replace('&quot;','"',$string);
            $string = str_replace('&amp;','&',$string);
            $string = str_replace('amp;','',$string);
            $string = str_replace('&lt;','<',$string);
            $string = str_replace('&gt;','>',$string);
            $string = str_replace('&nbsp;',' ',$string);
            $string = str_replace("\\", '',$string);
            return $string;
        }
     
        /**
         * @param $url
         * @return string
         */
        private function getImg($url){
            $refer = "http://www.qq.com/";
            $opt = [
                'http'=>[
                    'header'=>"Referer: " . $refer
                ]
            ];
            $context = stream_context_create($opt);
            //接受数据流
            $file_contents = file_get_contents($url,false, $context);
            $imageSteam =  Imagecreatefromstring($file_contents);
            $path = 'article/';
            if(!file_exists($path))
                mkdir($path,0777,true);
            $fileName = time().rand(0,99999) . '.jpg';
            //生成新图片
            imagejpeg($imageSteam, $path . $fileName);
            return $fileName;
        }
    }
     
    $url = 'https://mp.weixin.qq.com/s/4gwonJ3m0wd-kwTA3SmU-g';
    $crawler = new WxCrawler();
    $content = $crawler->crawByUrl($url);
    echo $content['content_html'];
    

     


    ————————————————
    版权声明:本文为CSDN博主「Me佳佳丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/q6627666/article/details/105432090

  • 远程图片转化为base64

    程序代码如下:
    <?php
            /* *
            * 第一种方法
            * 远程图片转化为base64,只支持http(推荐使用)
            * */
            public static function imgUrl_to_base64($url,$auto_http = false){
                /*  $header = array(
                    'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:45.0) Gecko/20100101 Firefox/45.0',
                    'Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
                    'Accept-Encoding: gzip, deflate',);*/
                if(strpos($url,'https://') !== false && $auto_http == true){
                    //是否自动转为http
                    $url = str_replace('https://','http://',$url);
                }
                $curl = curl_init();
                curl_setopt($curl, CURLOPT_URL, $url);
                curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
                curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
                curl_setopt($curl, CURLOPT_ENCODING, 'gzip');
               // curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
                $data = curl_exec($curl);
                $code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
                curl_close($curl);
                $imgBase64Code = '';
                if($code == 200){
                    $imgBase64Code = "data:image/jpeg;base64," . base64_encode($data);
                }
    
                return $imgBase64Code;
            }
    
            /* *
            * 第二种方法
            * 远程图片转化为base64,支持https,http(比较消耗服务器资源)
            * */
            function imgUrl_base64($url){
                $url='https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTJm4xPKAf4u6TAQ6AGmXEmBh4yfyFcfZTOQoRzumzBEvIPaaRJXAB8Rlia1iaugJWRsdqzBhu3BTtkw/0';
                $img_file = file_get_contents($url);  $img_content=  "data:image/jpeg;base64," .base64_encode($img_file);
                return $img_content;
            }
          
        ?>
    

  • go是强类型语言么

    go语言是静态强类型语言,同时go语言也是编译型语言,语法与C语言相近。go语言支持垃圾回收功能,同时它内嵌了关联数组,就像字符串类型一样。

     Go(又称 Golang)是 Google 的 Robert Griesemer,Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型语言。Go 语言语法与 C 相近,但功能上有:内存安全,GC(垃圾回收),结构形态及 CSP-style 并发计算。

    Go的语法接近C语言,但对于变量的声明有所不同。Go支持垃圾回收功能。Go的并行模型是以东尼·霍尔的通信顺序进程(CSP)为基础,采取类似模型的其他语言包括Occam和Limbo,但它也具有Pi运算的特征,比如通道传输。在1.8版本中开放插件(Plugin)的支持,这意味着现在能从Go中动态加载部分函数。

    与C++相比,Go并不包括如枚举、异常处理、继承、泛型、断言、虚函数等功能,但增加了 切片(Slice) 型、并发、管道、垃圾回收、接口(Interface)等特性的语言级支持。Go 2.0版本将支持泛型,对于断言的存在,则持负面态度,同时也为自己不提供类型继承来辩护。

    不同于Java,Go内嵌了关联数组(也称为哈希表(Hashes)或字典(Dictionaries)),就像字符串类型一样。

    本文原创发布php中文网 ,转载请注明出处,感谢您的尊重!
  • php 判断扫码的客户端是支付宝还是微信

    描述:判断扫码的客户端是支付宝还是微信
    程序代码如下:
    <?php 
    if (strstr($_SERVER['HTTP_USER_AGENT'], 'AlipayClient')) { 
       echo '扫码的设备为支付宝app'; 
    } else if(strstr($_SERVER['HTTP_USER_AGENT'], 'MicroMessenger')){
       echo '扫码的设备为微信app'; 
    }else {
       echo '非微信或支付宝APP'; 
    }
    

  • Redis 缓存穿透、缓存雪崩、缓存击穿解决方案

    描述:Redis 缓存穿透、缓存雪崩、缓存击穿解决方案
    内容如下:

    1、什么样的数据适合缓存?

    2、What?

    缓存穿透:

    缓存穿透是指查询一个一定不存在的数据,由于缓存是不命中时需要从数据库查询,查不到数据则不写入缓存,这将导致这个不存在的数据每次请求都要到数据库去查询,造成缓存穿透。在流量大时,可能DB就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞。

    比如说,一个用户的基本信息(缓存key为uid)或订单的信息(缓存key为order_id),缓存或数据库里都没有这个uid或order_id的信息,但是如果有请求要获取这个信息,那么逻辑处理时就会跨过缓存这一层去查数据库,如果这样的请求短时间内非常多可能会压垮数据库。

    缓存击穿:

    对于一些设置了过期时间的key,如果这些key可能会在某些时间点被超高并发地访问,是一种非常“热点”的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。 
    缓存在某个时间点过期的时候,恰好在这个时间点对这个Key有大量的并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

    比如说,订单的信息(缓存key为order_id)在缓存中有过期时间,如果在特定的时间这个订单信息在缓存中已经过期但是尚未从数据库查出最新的信息set到缓存上,恰好这个时候大并发请求过来了,那么这些请求的逻辑处理也会跨过缓存直接查询数据库,这个大并发的查询可能会压垮数据库。

    缓存雪崩:

    缓存雪崩是指在设置缓存时采用了相同的过期时间,导致缓存在某一时刻同时失效,导致所有的查询都落在数据库上,造成了缓存雪崩。

    上面说到缓存击穿是一个key在特定时间过期,那么如果缓存系统中大量的缓存在同一时间或时间段内过期,这个时候的请求也会跨过缓存直达数据库,数据库压力陡增也可能会压垮数据库。

    3How ?

    缓存穿透:

    1)有很多种方法可以有效地解决缓存穿透问题,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被这个bitmap拦截掉,从而避免了对底层数据库的查询压力。

    2)另外也有一个更为简单粗暴的方法,如果一个查询返回的数据为空(不管是数据不存在,还是系统故障),仍然把这个空结果进行缓存,附代码如下。

        public function getCache($key) {
            $redis =  new Redis();               //redis对象
            $expireTime= 120;                    //key过期时间依据实际情况而定
            $result = $redis->get($key);
            if ($result) {
                return $result;
            }else {
                //新手入学版
                //redis没查到,去查db
                $dbData = querySelectDb($key);   //模拟查询db,拿到db数据
                if ($dbData) {
                    //正常情况下数据库查到的数据,更新缓存key数据,返回数据
                    $redis->set($key, $dbData, $expireTime);
                    return $dbData;
                }else {
                    //数据库查不到该$key对应的数据,设置一个默认值,更新缓存key数据,返回数据
                    $dbData= 'Empty Data';
                    $redis->set($key, $dbData, $expireTime);
                    return $dbData;
                }
              
    
                //精简版
                /**
                *   $dbData= $dbData?: 'Empty Data';
                *   $redis->set($key, $dbData, $expireTime);
                *   return $dbData;
                */          
            }
        }
    		

    缓存击穿:

    1)后台刷新

    后台定义一个crontab job专门主动更新缓存数据.比如,一个缓存中的数据过期时间是30分钟,那么job每隔29分钟定时刷新数据(将从数据库中查到的数据更新到缓存中).

    注:这种方案比较容易理解,但会增加系统复杂度。比较适合那些 key 相对固定,cache 粒度较大的业务,key 比较分散的则不太适合,实现起来较为复杂。

    2)检查更新

    将缓存key的过期时间(绝对时间)一起保存到缓存中(可以拼接,可以添加新字段,可以采用单独的key保存..不管用什么方式,只要两者建立好关联关系就行).在每次执行get操作后,都将get出来的缓存过期时间与当前系统时间做一个对比,如果缓存过期时间-当前系统时间<=1分钟(自定义的一个值),则主动更新缓存.这样就能保证缓存中的数据始终是最新的(和方案一一样,让数据不过期.)

    注:这种方案在特殊情况下也会有问题。假设缓存过期时间是12:00,而 11:59 到 12:00这 1 分钟时间里恰好没有 get 请求过来,又恰好请求都在 11:30 分的时 候高并发过来,那就悲剧了。这种情况比较极端,但并不是没有可能。因为“高 并发”也可能是阶段性在某个时间点爆发。

    3)分级缓存

    采用 L1 (一级缓存)和 L2(二级缓存) 缓存方式,L1 缓存失效时间短,L2 缓存失效时间长。 请求优先从 L1 缓存获取数据,如果 L1缓存未命中则加锁,只有 1 个线程获取到锁,这个线程再从数据库中读取数据并将数据再更新到到 L1 缓存和 L2 缓存中,而其他线程依旧从 L2 缓存获取数据并返回。

    注:这种方式,主要是通过避免缓存同时失效并结合锁机制实现。所以,当数据更 新时,只能淘汰 L1 缓存,不能同时将 L1 和 L2 中的缓存同时淘汰。L2 缓存中 可能会存在脏数据,需要业务能够容忍这种短时间的不一致。而且,这种方案 可能会造成额外的缓存空间浪费。

    4)加锁

    	public function getCache($key) {
            $redis = new Redis(); //redis对象
            $expireTime= 300;
            $result = $redis->get($key);
            if ($result) {
                return $result;
            }else {
                //redis里没查到,去查db
                $mutexKey = $key . '_mutex';
                //这里假设同时有10个查询的线程,1个线程抢到了这个锁,其他的9个线程就会阻塞或需要等待
                if ($redis->setnx($mutexKey, 1, 60)) {
                    //抢到锁的线程,去执行数据查询,更新缓存这些操作
                    $dbData= querySelectDb($key);//模拟查询db,拿到的数据
                    $result = $dbData?: 'empty data';
                    $redis->set($key, $result, $expireTime);
                    $redis->del($mutexKey);
                }else {
                    //没抢到锁的线程,就稍微等一会啦,然后再获取最新的缓存数据
                    sleep(0.1); //视情况而定
                    $result = getCache($key);
                }                    
                return $result;          
            }
        }
    		

     缓存雪崩:

       短时间大量数据读写操作极大可能导致数据库垮掉。为了避免出现这种情况,可以在常规的缓存set操作的基础上,在预设的过期时间基础上再额外增加一些时间;也可以单独起一个进程去监控redis中快过期的key,如果有快过期的key,就去重新查询更新。

    1)在缓存失效后,通过加锁或者队列来控制读数据库写缓存的线程数量。比如对某个key只允许一个线程查询数据和写缓存,其他线程等待。

    2)可以通过缓存reload机制,预先去更新缓存,在即将发生大并发访问前手动触发加载缓存。

    3)不同的key,设置不同的过期时间,让缓存失效的时间点尽量均匀。

    4)做二级缓存,或者双缓存策略。A1为原始缓存,A2为拷贝缓存,A1失效时,可以访问A2,A1缓存失效时间设置为短期,A2设置为长期。

     

     本文作者:杨宇飞
    本文链接:https://www.cnblogs.com/afeige/p/13590450.html
    关于博主:评论和私信会在第一时间回复。或者直接私信我。
    版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
    声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!