实战_实战项目 - CSDN
精华内容
参与话题
  • 实战2、pycharm+python+MS SQLSERVER实现爬虫程序。 我累了,稍后更新....

    实战2、pycharm+python+MS SQLSERVER实现爬虫程序。

     

    实现 python 爬老毛桃网站 https://www.laomaotao.net/   u盘启动制作工具 所有版本号,然后写入MS SQLSERVER数据库中

    稍后更新...

    首先在pycharm的点左下角的  Terminal ,输入pip install requests    和  pip install BeautifulSoup4 安装包

    一、先热下身,写第一个 爬百度网站。

    import requests
    from bs4 import BeautifulSoup

    resp=requests.get('https://www.baidu.com') #请求百度首页
    print(resp) #打印请求结果的状态码
    print(resp.content) #打印请求到的网页源码

    bsobj=BeautifulSoup(resp.content,'lxml') #将网页源码构造成BeautifulSoup对象,方便操作
    a_list=bsobj.find_all('a') #获取网页中的所有a标签对象
    text='' # 创建一个空字符串
    for a in a_list:
        href=a.get('href') #获取a标签对象的href属性,即这个对象指向的链接地址
        text+=href+'\n' #加入到字符串中,并换行
    with open('url.txt','w') as f: #在当前路径下,以写的方式打开一个名为'url.txt',如果不存在则创建
        f.write(text) #将text里的数据写入到文本中

    二、爬 老毛桃网站

    老毛桃网站有防爬,所以我改为另一个网友的例子并加入写入数据库部分。

    我们要爬的网站 http://www.weather.com.cn/weather/101190401.shtml

    1、首先在MS SQLSERVER 中 建立新表t_tq

    2、pycharm IDE 中选new project->pure python , 在项目上新建一个python文件testtq.py 然后拷贝入如下代码

    import requests
    import csv
    import random
    import time
    import socket
    import http.client
    # import urllib.request
    from bs4 import BeautifulSoup
    
    import pymssql
    
    def insert(results):
        # Create your views here.
        # 打开数据库连接
        db = pymssql.connect(host='127.0.0.1', user='sa', password='3201319', database='bhjs', port=1433)
    
        # 使用cursor()方法获取操作游标
        cur = db.cursor()
        sql = "INSERT INTO t_tq  (rq,tq,zgwd,zdwd)  VALUES (%s, %s,  %s, %s)"
    
        try:
            cur.execute(sql,(results['rq'],results['tq'],results['zgwd'],results['zdwd'])) # 执行sql语句
            #results = cur.fetchall()  # 所有记录
            db.commit()
        except Exception as e:
            raise e
    
        return results
    
    def get_content(url , data = None):
        header={
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
            'Accept-Encoding': 'gzip, deflate, sdch',
            'Accept-Language': 'zh-CN,zh;q=0.8',
            'Connection': 'keep-alive',
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.235'
        }
        timeout = random.choice(range(80, 180))
        while True:
            try:
                rep = requests.get(url,headers = header,timeout = timeout)
                rep.encoding = 'utf-8'
                # req = urllib.request.Request(url, data, header)
                # response = urllib.request.urlopen(req, timeout=timeout)
                # html1 = response.read().decode('UTF-8', errors='ignore')
                # response.close()
                break
            # except urllib.request.HTTPError as e:
            #         print( '1:', e)
            #         time.sleep(random.choice(range(5, 10)))
            #
            # except urllib.request.URLError as e:
            #     print( '2:', e)
            #     time.sleep(random.choice(range(5, 10)))
            except socket.timeout as e:
                print( '3:', e)
                time.sleep(random.choice(range(8,15)))
    
            except socket.error as e:
                print( '4:', e)
                time.sleep(random.choice(range(20, 60)))
    
            except http.client.BadStatusLine as e:
                print( '5:', e)
                time.sleep(random.choice(range(30, 80)))
    
            except http.client.IncompleteRead as e:
                print( '6:', e)
                time.sleep(random.choice(range(5, 15)))
    
        return rep.text
    
    
    def get_data(html_text):
        item_p={'rq':50,'tq':50,'zgwd':50,'zdwd':50}
        final = []
        bs = BeautifulSoup(html_text, "html.parser")  # 创建BeautifulSoup对象
        body = bs.body # 获取body部分
        data = body.find('div', {'id': '7d'})  # 找到id为7d的div
        ul = data.find('ul')  # 获取ul部分
        li = ul.find_all('li')  # 获取所有的li
    
        for day in li: # 对每个li标签中的内容进行遍历
            temp = []
            date = day.find('h1').string  # 找到日期
            temp.append(date)  # 添加到temp中
            item_p['rq']=date
    
            inf = day.find_all('p')  # 找到li中的所有p标签
            temp.append(inf[0].string)  # 第一个p标签中的内容(天气状况)加到temp中
    
            item_p['tq']=inf[0].string
    
            if inf[1].find('span') is None:
                temperature_highest = None # 天气预报可能没有当天的最高气温(到了傍晚,就是这样),需要加个判断语句,来输出最低气温
            else:
                temperature_highest = inf[1].find('span').string  # 找到最高温
                temperature_highest = temperature_highest.replace('℃', '')  # 到了晚上网站会变,最高温度后面也有个℃
            temperature_lowest = inf[1].find('i').string  # 找到最低温
            temperature_lowest = temperature_lowest.replace('℃', '')  # 最低温度后面有个℃,去掉这个符号
            temp.append(temperature_highest)   # 将最高温添加到temp中
            temp.append(temperature_lowest)   #将最低温添加到temp中
    
            item_p['zgwd']=temperature_highest
            item_p['zdwd']=temperature_lowest
            final.append(temp)   #将temp加到final中
    
            insert(item_p)
    
        return final
    
    
    def write_data(data, name):
        file_name = name
        with open(file_name, 'a', errors='ignore', newline='') as f:
                f_csv = csv.writer(f)
                f_csv.writerows(data)
    
    
    if __name__ == '__main__':
        url ='http://www.weather.com.cn/weather/101190401.shtml'
        html = get_content(url)
        result = get_data(html)
       # write_data(result, 'weather.csv')
    

     

    3、运行,在SQLSERVER中可得到如下结果

     

     

                 打呆仗,结硬寨,用最好的工具。——曾国藩

                 https://www.jetbrains.com/zh-cn/

                

     

    展开全文
  • 实战

    2019-07-02 10:45:28
    <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>实例<...link rel="stylesheet" type="text/css" href="css/index.css" />.../head>...
    <!DOCTYPE html>
    <html>
    
    	<head>
    		<meta charset="utf-8" />
    		<title>实例</title>
    		<link rel="stylesheet" type="text/css" href="css/index.css" />
    	</head>
    
    	<body>
    		<!--头部-->
    		<div class="header">
    			<div class="logo">
    				<img src="img/logo.png" alt="logo" />
    			</div>
    			<div class="nav">
    				<ul>
    					<li>首页</li>
    					<li>图片</li>
    					<li>视频</li>
    					<li>手记</li>
    				</ul>
    			</div>
    		</div>
    		<!--主体-->
    		<div class="main">
    			<!--中间头部-->
    			<div class="top">
    				<img src="img/1.jpeg">
    			</div>
    			<!--遮罩层-->
    			<div class="tooplayer">
    				<div class="word">My BEAUTIFUL LIFE</div>
    			</div>
    			<div class="tooplayer-top">
    				<button>LOOK MORE &gt</button>
    			</div>
    			<!--中间的中间-->
    			<div class="middle">
    				<div class="m-top">
    					<div class="m-top-weibo">
    						<img src="img/weibo.png" alt="weibo"/>
    						<div class="comm">MICROBLOG</div>
    					</div>
    					<div class="m-top-weixin">
    						<img src="img/weixin.png" alt="weixin"/>
    						<div class="comm">WECHAT</div>
    					</div>
    					<div class="m-top-qq">
    						<img src="img/QQ.png" alt="qq"/>
    						<div class="comm">QQ</div>
    					</div>
    					<div class="clear"></div>
    				</div>
    				<div class="m-middle">
    					"I want to give good things to record down,
    					after the recall will be vary beautiful."
    				</div>
    				<div class="m-bottom"></div>
    			</div>
    			<!--中间的底部-->
    			<div class="bottom"></div>
    			
    		</div>
    		<!--底部-->
    		<div class="footer">
    
    		</div>
    	</body>
    
    </html>

     

    html.css

    *{
    	padding: 0;
    	margin: 0;
    }
    .header{
    	width: 100%;
    	height: 100px;
    }
    .header .logo img{
    	width: 300px;
    	height: 85px;
    	padding-left:60px ;
    	padding-top: 8px;
    }
    
    .header .logo{
    	float: left;
    }
    .header .nav{
    	float: right;
    }
    .header .nav ul{
    	padding-right: 30px;
    }
    .header .nav ul li{
    	float: left;
    	list-style: none;
    	width: 60px;
    	height: 100px;
    	line-height: 90px;
    	font-size: 15px;
    	font-weight: bolder;
        color: #7D7D7D;
    }
    .main .top{
    	width: 100%;
    	height:600px ;
    }
    .main .top  img{
    	width: 100%;
    	height:600px ;
    }
    .main .tooplayer{
    	position: absolute;
    	top:100px;
    	left: 0;
    	background-color: #000;
    	width: 100%;
    	height: 600px;
    	opacity: 0.5;
    }
    
    .main .tooplayer-top{
    	position: absolute;
        width: 500px;
        height: 250px;
        top: 400px;
        margin-top: -30px;
        z-index: 2;
        right: 500px;
    }
    .main  .tooplayer .word{
    	padding-top: 200px;
    	color: white;
    	font-size: 45px;
    	font-weight: bolder;
        text-align: center;
        font-family: "微软雅黑";
    }
    .main .tooplayer-top button{
    	width: 200px;
    	height: 60px;
    	margin-top: 50px;
    	color: white;
    	background-color: #F5704F;
        font-family: "微软雅黑";	
    	text-align: center;
    	font-weight: bolder;
        font-size: 14px;
        <!--把方框设置成圆角-->
        border-radius:8px ;
        margin-left: 150px; 
    }
    .main .middle{
    	width: 1000px;
    	margin: 0 auto;
    }
    .main .middle .m-top .m-top-weibo{
    	float: left;
    	width: 33.3%;
    	padding-top: 50px;
    	text-align: center;
    }
    .main .middle .m-top .m-top-weixin{
    	float: left;
    	width: 33.3%;
    	padding-top: 50px;
    	text-align: center;
    }
    .main .middle .m-top .m-top-qq{
    	float: left;
    	width: 33.3%;
    	padding-top: 50px;
    	text-align: center;
    }
    .main .middle .m-top .comm{
    	font-size: 15px;
    	color: #7D7C7F;
    	font-weight:bold ;
    	padding-top: 20px;
    }
    .main .middle .m-middle{
    	font-size: 22px;
    	color: #E19796;
    	font-weight: bold;
    	padding-top: 50px;
    	font-style: italic;
    }
    .main .middle .m-top .clear{
    	clear:both;
    }
    

     

    展开全文
  • 《Qt 实战一二三》

    万次阅读 多人点赞 2020-03-28 18:53:52
    Qt 基础与实战,主要包含:环境搭建、信号槽、事件机制、数据类型、常用部件/布局、对话框、QPainter 绘图等内容。

    “我们来自 Qt 技术交流,我们来自 QML 技术交流”,不管你是笑了,还是笑了,反正我们是认真的。我们就是要找寻一种 Hold 不住的状态,来开始每一天的点滴分享,我们是一个有激情,有态度的部队。

    但是我们还是我们,我们只是多了一份责任。古语有云:“不积跬步无以至千里,不积小流无以成江海”,所以每一个伟大事务的产生都不是一蹴而就的。现在我们要立足眼下,把第一站放在地球,“《Qt 实战一二三》”应运而生。

    这里,我们不扯淡,只谈技术、只交流、只分享。大胆的把你的问题、建议与意见说出来!不说,憋坏了怎么办?

    | 版权声明:一去、二三里,未经博主允许不得转载。

    关于命名

    关于 Qt 系列命名,之前想了很久,想给它一个属于它自己的名字,但妄图用几个字来概括一个系列的难度,几乎不亚于重写几篇文章。

    早上,把我的想法说出来的时候,foruok 大神的一句“《Qt 实战一二三》”让我灵光一闪,那一刹那,说是醍醐灌顶也不为过,我和小伙伴们几乎同时兴奋的说,不如就用这个。

    多美妙的一句话,和我的名字一样 - 一去丶二三里,这几乎可以用来概括所有对于 Qt 实战方面的大大小小的困惑和不解。

    《Qt 实战一二三》

    以下是《Qt 实战一二三》系列的目录结构,所有的代码都经过严格自测,并通过。我会尽可能的把所有的源码都放出来,供大家方便学习、交流。。。

    项目实战部分 - 主要是讲解平时在项目中遇到的大大小小的技术点。

    你关心的,才是我要分享的!!!

    资料大全

    1. Qt 资料大全

    环境与工具

    1. 关于 Qt
    2. Qt 环境搭建(Visual Studio)
    3. Qt 环境搭建(Qt Creator)
    4. Qt5.7 + VS2015 环境搭建
    5. Linux 下搭建 Qt 环境
    6. Qt Creator 快捷键
    7. Qt 之命令行编译(nmake)
    8. Qt Creator 介绍
    9. Qt Assistant 介绍
    10. Qt Linguist 介绍
    11. Qt 之 pro 配置多个子工程/子模块
    12. Qt 之输出控制
    13. 查看和调试 Qt 源码
    14. Qt Creator 添加自定义注释

    qmake

    1. Qt 之 pro 配置详解
    2. Qt 之资源系统
    3. Qt 之添加 Windows 资源文件(.rc文件)
    4. Qt 之生成 Windows 资源文件(.rc 文件)

    对象 & 属性 & 事件

    1. Qt 之 Meta-Object 系统
    2. Qt 之属性系统
    3. Qt 之事件系统
    4. Qt 之 Timers
    5. Qt 之对象树与所有权
    6. Qt 之 findChild

    基本部件

    1. QWidget、QDialog、QMainWindow 的异同点
    2. Qt 之模式、非模式、半模式对话框
    3. Qt 之 QLabel
    4. Qt 之 QLCDNumber
    5. Qt 之 QAbstractButton
    6. Qt 之 QPushButton
    7. Qt 之 QToolButton
    8. Qt 之 QCheckBox
    9. Qt 之 QRadioButton
    10. Qt 之 QLineEdit
    11. Qt 之 QSpinBox 和 QDoubleSpinBox
    12. Qt 之 QSlider
    13. Qt 之 QProgressBar
    14. Qt 之 QDateTimeEdit
    15. Qt 之 QDateEdit和QTimeEdit
    16. Qt 之 QScrollArea
    17. Qt 之 QToolBox
    18. Qt 之 QSystemTrayIcon

    布局管理器

    1. Qt 之布局管理器
    2. Qt 之自定义布局管理器(QCardLayout)
    3. Qt 之自定义布局管理器(QFlowLayout)
    4. Qt 之自定义布局管理器(QBorderLayout)
    5. Qt 之手动布局
    6. Qt 之水平/垂直布局(QBoxLayout、QHBoxLayout、QVBoxLayout)
    7. Qt 之格栅布局(QGridLayout)
    8. Qt 之表单布局(QFormLayout)
    9. Qt 之 QStackedLayout
    10. Qt 之 QStackedWidget
    11. Qt 之 QSpacerItem
    12. Qt 之 QSizePolicy

    高级控件

    对话框

    国际化和翻译

    1. Qt 之国际化
    2. Qt 之国际化(系统文本 - QMessageBox 按钮、QLineEdit 右键菜单等)
    3. Qt 翻译原生 widgets(QTextEdit 右键菜单等)

    数据类型与数据操作类

    1. Qt 之 QFileSystemWatcher
    2. Qt 之 QDesktopServices
    3. Qt 之 QTimer
    4. Qt 之 QFileIconProvider
    5. Qt 之 QTemporaryFile
    6. Qt 之 QCryptographicHash
    7. Qt 之 qInstallMessageHandler(输出详细日志)
    8. Qt 之 qInstallMessageHandler(重定向至文件)
    9. Qt 之 qSetMessagePattern

    数据存储与获取

    1. Qt 之界面数据存储与获取

    事件

    1. Qt 之 QEvent

    QPainter 2D 图形

    1. Qt 之坐标系统
    2. Qt 之图形(QPainter 的基本绘图)
    3. Qt 之图形(渐变填充)
    4. Qt 之图形(转换)
    5. Qt 之图形(绘制文本)
    6. Qt 之图形(QPainterPath)
    7. Qt 之描绘轮廓
    8. Qt 之图形(组合)
    9. Qt 之图形(绘制漂亮的圆弧)
    10. Qt 之图形(简笔画-绘制漂亮的西瓜)
    11. Qt 之图形(简笔画-绘制卡通蚂蚁)
    12. Qt 之绘制时钟
    13. Qt 之绘制闪烁文本

    Images

    1. Qt 之 QImageWriter
    2. Qt 之 QImageReader

    进程间通信

    1. Qt 之进程间通信(IPC)
    2. Qt 之进程间通信(Windows消息)
    3. Qt 之进程间通信(共享内存)
    4. Qt 之进程间通信(QProcess)
    5. Qt 之进程间通信(TCP/IP)

    自定义界面

    1. Qt 之自定义界面(实现无边框、可移动)
    2. Qt 之自定义界面(添加自定义标题栏)
    3. Qt 之自定义界面(窗体缩放)
    4. Qt 之自定义界面(窗体缩放-跨平台终极版)
    5. Qt 之窗体拖拽、自适应分辨率、自适应大小
    6. Qt 之自定义界面(QMessageBox)
    7. Qt 之自定义界面(右下角冒泡)
    8. Qt 之自定义控件(开关按钮)
    9. Qt 之透明提示框
    10. Qt 之自定义搜索框

    Third-Party

    1. Qt 使用第三方库
    2. 基于 Qt 的图表库
    3. Qt 之 QuaZIP(zip压缩/解压缩)
    4. Qt 之 OpenSSL
    5. Qt 之 QtSoap(访问WebService)
    6. Qt 之二维码扫描
    7. Qt 之 QCustomPlot(图形库)
    8. Qt 之 QRoundProgressBar(圆形进度条)
    9. Qt 之 QProgressIndicator(等待提示框)
    10. Qt 之 QScintilla(源代码编辑器)

    项目实战

    1. Qt 之 Tab 键切换焦点顺序
    2. Qt 之密码框不可选中、复制、粘贴、无右键菜单等
    3. QDialog 之屏蔽 Esc 键
    4. Qt 之命令行参数
    5. Qt 之重启应用程序
    6. Qt 之 QFileIconProvider(根据扩展名获取文件图标、类型)
    7. Qt 之根据扩展名获取文件图标、类型
    8. Qt 之启动外部程序
    9. Qt 之提取 exe/dll/icon 文件图标
    10. Qt 之运行一个实例进程
    11. Qt 之 QTableView添加复选框(QAbstractTableModel)
    12. Qt 之 QTableView 添加复选框(QAbstractItemDelegate)
    13. Qt 之 QHeaderView 添加复选框
    14. Qt 之 QHeaderView 排序
    15. Qt 之 QHeaderView自定义排序(QSortFilterProxyModel)
    16. Qt 之 QHeaderView 自定义排序(终极版)
    17. Qt 之 QHeaderView 自定义排序(获取正确的 QModelIndex)
    18. Qt 之 QTableView 显示富文本
    19. Qt 之模型/视图(自定义进度条)
    20. Qt 之模型/视图(自定义按钮)
    21. Qt 之显示网络图片
    22. Qt 之滚动字幕
    23. Qt 之保持 GUI 响应
    24. Qt 之设置应用程序图标
    25. Qt 之保存/恢复窗口的几何形状
    26. Qt 之等待提示框(QPropertyAnimation)
    27. Qt 之等待提示框(QTimer)
    28. Qt 之等待提示框(QMovie)
    29. Qt 之字典划词
    30. Qt 之镜像旋转
    31. Qt 之窗体透明

    Qt 新版本

    1. Qt5.7 新特性

    共同进步

    进步始于交流,收获源于分享。希望一起走过的日子里,我们能够更多地交流心得,共同进步 - You are not alone。

    亲们,记住呦,后期分享的所有内容都可以随时交流。欢迎大家留言,不要吝啬你们的建议与意见,收到后我会第一时间进行回复。

    青春不老,奋斗不止!纯正开源之美,有趣、好玩、靠谱。。。

    持续更新中…

    展开全文
  • Redis内存数据库

    Redis内存数据库

    更新说明:此文第一版是2019年4月份写得,当时也对Redis没有太多的认识,写了较为基础的一些东西,如今已到年末,对Redis的认识也多了很多,特此更新2.0版本。
    新增的内容有:
    1、排版优化
    2、新增基础数据类型的底层实现
    3、新增高级数据类型
    4、新增了Redis扩容
    5、新增Redis混合备份的方式
    6、优化Redis集群主从通信的细节
    7、对一些bug进行了优化
    (目前更新进度80%,2019/12/11)

    第二篇原创技术博客就贡献给Redis,做缓存这块,Redis内存数据库极其擅长,记得在Redis之前还有个Memcached,但支持的数据类型较少只有一种,而Redis支持多达五种数据类型。可谓长江后浪推前浪,前浪死在沙滩上了。

    本文旨在让新手小白也能快速上手,所以从最基本的讲起,如果您是中手或者高手,可以跳到响应章节查漏补缺。

    1.什么是Redis

    Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如
    ①字符串(strings),
    ②散列(hashes),
    ③列表(lists),
    ④ 集合(sets),
    ⑤有序集合(sorted sets),

    与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。

    2.为什么要用Redis

    请看上一段,Redis是内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件

    • 数据库
      Redis本质是内存数据库,所以自然可以当做数据库来使用,但要注意的是内存空间是极其有限的,可不像硬盘那样浩瀚无垠,所以大多数情况下我们还是用关系型数据库+Redis缓存的方式运用Redis
    • 缓存
      比如Mysql,可承担的并发访问量有多大呢?答案是几百左右就会扛不住了,所以我们为了支持更高的并发,会使用缓存,为数据库筑起一道护盾,让大多数请求都发生在缓存这一层。Redis是把数据存储在内存上的,访问数据速度相当快,很适合做缓存。
    • 消息中间件
      Redis支持发布/订阅消息,当然真正的MQ我们一般在Rabbit,Rocket,卡夫卡之间选一个,这并不是Redis的强项

    3.Redis的使用方式

    这一块分为两个部分,第一部分是把Redis部署在linux上,我们在linux使用Redis。
    第二部分是通过SpringBoot来操作使用Redis。

    3.1 Linux上使用

    3.1.1 redis启动

    1、备份redis.conf(此文件为Redis配置文件,非常重要):拷贝一份redis.conf到其他目录

    2、修改redis.conf文件将里面的daemonize no 改成 yes,让服务在后台启动

    3、启动命令:执行 redis-server /myredis/redis.conf(后面那个是配置文件的位置)

    4、用客户端访问: Redis-cli

    多个端口可以 Redis-cli –p 6379

    5、测试验证: ping 若成功启动会返回 pong!

    3.1.2 redis关闭

    单实例关闭:Redis-cli shutdown

    也可以进入终端后再关闭 shutdown

    多实例关闭,指定端口关闭:Redis-cli -p 6379 shutdown

    3.1.3 Redis–key/value

    Redis作为Nosql数据库,数据都以键值对的形式存储,Value内置了5大数据类型,
    在看Value操作之前,先看一看Key的操作

    keys * 查询当前库的所有键

    exists 判断某个键是否存在

    type 查看键的类型

    del 删除某个键

    expire 为键值设置过期时间,单位秒。

    ttl 查看还有多少秒过期,-1表示永不过期 (-2表示已过期)

    dbsize 查看当前数据库的key的数量

    Flushdb 清空当前库(慎用!)

    Flushall 通杀全部库(删库跑路!!!忘了这个命令吧)

    4 Redis五大数据类型

    4.1 Redis五大数据类型–String

    Redis 的字符串是动态字符串,是可以修改的字符串,内部结构实现上类似于 Java 的 ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配,,内部为当前字符串实际分配的空间 capacity 一般要高于实际字符串长度 len。当字符串长度小于 1M 时,扩容都是加倍现有的空间,如果超过 1M,扩容时一次只会多扩 1M 的空间。需要注意的是字符串最大长度为 512M。

    4.1.2 关于String底层实现

    Redis 的字符串叫着「SDS」,也就是Simple Dynamic String。它的结构是一个带长度信息的字节数组。所以以后有人在面试的时候问你:请聊一聊SDS。就不要傻乎乎的问人家“Redis有名为SDS的数据结构么?”
    在这里插入图片描述

    上图为容量与长度的关系,是不是简单明了。

    SDS的结构:
    struct SDS {
    T capacity; // 数组容量
    T len; // 数组长度
    byte flags; // 特殊标识位,不理睬它
    byte[] content; // 数组内容
    }

    SDS 结构使用了范型 T,为什么不直接用 int 呢,这是因为当字符串比较短时,len 和 capacity 可以使用 byte 和 short 来表示,Redis 为了对内存做极致的优化,不同长度的字符串使用不同的结构体来表示。所以别人才那么快嘛,太精细了!
    注意:创建字符串时 len 和 capacity 一样长,不会多分配冗余空间,这是因为绝大多数场景下我们不会使用 append 操作来修改字符串。

    4.1.3 String常用命令

    get 查询对应键值

    set 添加键值对

    append 将给定的 追加到原值的末尾

    strlen 获得值的长度

    setnx 只有在 key 不存在时设置 key 的值

    incr

    将 key 中储存的数字值增1

    只能对数字值操作,如果为空,新增值为1

    decr

    将 key 中储存的数字值减1

    只能对数字值操作,如果为空,新增值为-1

    incrby / decrby <步长>

    将 key 中储存的数字值增减。自定义步长。

    mset …

    同时设置一个或多个 key-value对

    mget …

    同时获取一个或多个 value

    msetnx …

    同时设置一个或多个 key-value 对,当且仅当所有给定 key 都不存在。

    getrange <起始位置> <结束位置>

    获得值的范围,类似java中的substring

    setrange <起始位置>

    用 覆写 所储存的字符串值, 从<起始位置>开始。

    setex <过期时间>

    设置键值的同时,设置过期时间,单位秒。

    getset

    以新换旧,设置了新值同时获得旧值。

    4.2 Redis五大数据类型–list

    单键多值

    Redis 列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素导列表的头部(左边)或者尾部(右边)。

    它的底层实际是个双向链表,对两端的操作性能很高,通过索引下标的操作中间的节点性能会较差。操作的时间复杂度为 O(1),但是索引定位很慢,时间复杂度为 O(n)

    4.2.1 底层实现原理

    上面也讲了底层是个双向链表,但还是太模糊了,具体一点,其实底层用的是ZipList(压缩链表)和QuickList(快速链表)。

    首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是 ziplist,也即是压缩列表。它将所有的元素紧挨着一起存储,分配的是一块连续的内存。(感觉好像数组啊,连续的内存空间)

    当数据量比较多的时候才会改成 quickList。那么什么是quickList呢?Redis 将链表和 ziplist 结合起来组成了 quickList。如下图所示:在这里插入图片描述

    4.2.1.1 压缩链表
    • 压缩链表结构:
      struct ziplist {
      int32 zlbytes; // 整个压缩列表占用字节数
      int32 zltail_offset; // 最后一个元素距离压缩列表起始位置的偏移量,用于快速定位到最后一个节点
      int16 zllength; // 元素个数
      T[] entries; // 元素内容列表,挨个挨个紧凑存储
      int8 zlend; // 标志压缩列表的结束,值恒为 0xFF
      }
      在这里插入图片描述

      压缩列表为了支持双向遍历,所以才会有 ztail_offset 这个字段,用来快速定位到最后一个元素,然后倒着遍历。

    • Entry的数据结构:
      entry 块随着容纳的元素类型不同,也会有不一样的结构。
      struct entry {
      int prevlen; // 前一个 entry 的字节长度
      int encoding; // 元素类型编码
      optional byte[] content; // 元素内容
      }

      encoding字段是Redis自己定义的N种编码类型,罗列出来也无意义。只需要知道可以根据编码字段来确定元素内容的类型即可。

    • 增加元素:
      因为 ziplist 都是紧凑存储,没有冗余空间 (对比一下 Redis 的字符串结构)。意味着插入一个新的元素就需要调用 realloc 扩展内存。取决于内存分配器算法和当前的 ziplist 内存大小,realloc 可能会重新分配新的内存空间,并将之前的内容一次性拷贝到新的地址,也可能在原有的地址上进行扩展,这时就不需要进行旧内容的内存拷贝。

    如果 ziplist 占据内存太大,重新分配内存和拷贝内存就会有很大的消耗。所以 ziplist 不适合存储大型字符串,存储的元素也不宜过多。

    4.2.1.2 快速链表

    先看下最基础的双向链表结构:
    // 链表的节点
    struct listNode {
    listNode* prev;
    listNode* next;
    T value;
    }
    // 链表
    struct list {
    listNode *head;
    listNode *tail;
    long length;
    }

    链表的附加空间相对太高,prev 和 next 指针就要占去 16 个字节 (64bit 系统的指针是 8 个字节),另外每个节点的内存都是单独分配,会加剧内存的碎片化,影响内存管理效率

    为了解决上述问题,就引出了quicklist:
    quicklist 是 ziplist 和 linkedlist 的混合体,它将 linkedlist 按段切分,每一段使用 ziplist 来紧凑存储,多个 ziplist 之间使用双向指针串接起来。
    在这里插入图片描述zipList因为是连续的内存空间,所以不需要prev 和 next 指针,空间占用大幅减少。同样,比起原来每个节点的一盘散沙,现在zipList是一坨一坨的沙砖,碎片化的问题也解决了。

    • 快速链表数据结构:
      struct quicklistNode {
      quicklistNode* prev;
      quicklistNode* next;
      ziplist* zl; // 指向压缩列表
      int32 size; // ziplist 的字节总数
      int16 count; // ziplist 中的元素数量
      int2 encoding; // 存储形式 2bit,原生字节数组还是 LZF 压缩存储

      }
      struct quicklist {
      quicklistNode* head;
      quicklistNode* tail;
      long count; // 元素总数
      int nodes; // ziplist 节点的个数
      int compressDepth; // LZF 算法压缩深度

      }

    quicklist 内部默认单个 ziplist 长度为 8k 字节,超出了这个字节数,就会新起一个 ziplist。ziplist 的长度由配置参数list-max-ziplist-size决定,是可以配置的。
    为了更进一步的压缩,在原来zipList的基础上,还有压缩的zipList使用 LZF 算法压缩,可以选择压缩深度
    struct ziplist_compressed {
    int32 size;
    byte[] compressed_data;
    }
    在这里插入图片描述quicklist 默认的压缩深度是 0,也就是不压缩。压缩的实际深度由配置参数list-compress-depth决定。为了支持快速的 push/pop 操作,quicklist 的首尾两个 ziplist 不压缩,此时深度就是 1。如果深度为 2,就表示 quicklist 的首尾第一个 ziplist 以及首尾第二个 ziplist 都不压缩。

    4.2.2 常用命令

    lpush/rpush …

    从左边/右边插入一个或多个值。

    lpop/rpop

    从左边/右边吐出一个值。

    值在键在,值光键亡。

    rpoplpush

    从列表右边吐出一个值,插到列表左边。

    lrange

    按照索引下标获得元素(从左到右)

    lindex

    按照索引下标获得元素(从左到右)

    llen

    获得列表长度

    linsert before

    在的后面插入 插入值

    lrem

    从左边删除n个value(从左到右)

    4.3 Redis五大数据类型–set

    Redis set对外提供的功能与list类似是一个列表的功能,特殊之处在于set是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,set是一个很好的选择,并且set提供了判断某个成员是否在一个set集合内的重要接口,这个也是list所不能提供的。

    Redis的Set是string类型的无序集合。它底层其实是一个value为null的hash表,所以添加,删除,查找的复杂度都是O(1)。

    4.3.1 底层实现原理

    Set的实现参考哈希的底层实现,Set就是Value都为空的Hash。

    4.3.2 常用命令

    sadd …

    将一个或多个 member 元素加入到集合 key 当中,已经存在于集合的 member 元素将被忽略。

    smembers

    取出该集合的所有值。

    sismember

    判断集合是否为含有该值,有返回1,没有返回0

    scard

    返回该集合的元素个数。

    srem …

    删除集合中的某个元素。

    spop

    随机从该集合中吐出一个值。

    srandmember

    随机从该集合中取出n个值。

    不会从集合中删除

    sinter

    返回两个集合的交集元素。

    sunion

    返回两个集合的并集元素。

    sdiff

    返回两个集合的差集元素。

    4.4 Redis五大数据类型–hash

    Redis hash 是一个键值对集合。

    Redis hash是一个string类型的field和value的映射表,hash特别适合用于存储对象。

    类似Java里面的Map

    4.4.1 底层实现原理

    使用字典作为存储结构:

    dict 结构内部包含两个 hashtable,通常情况下只有一个 hashtable 是有值的。但是在 dict 扩容缩容时,需要分配新的 hashtable,然后进行渐进式搬迁,这时候两个 hashtable 存储的分别是旧的 hashtable 和新的 hashtable。待搬迁结束后,旧的 hashtable 被删除,新的 hashtable 取而代之。
    在这里插入图片描述所以,字典数据结构的精华就落在了 hashtable 结构上了。hashtable 的结构和 Java 的 HashMap 几乎是一样的,都是通过分桶的方式解决 hash 冲突。第一维是数组,第二维是链表。数组中存储的是第二维链表的第一个元素的指针。

    struct dictEntry {
    void
    key;
    void
    val;
    dictEntry* next; // 链接下一个 entry
    }
    struct dictht {
    dictEntry** table; // 二维
    long size; // 第一维数组的长度
    long used; // hash 表中的元素个数

    }**

    4.4.1.1 Redis扩容 渐进式Rehash

    大字典的扩容是比较耗时间的,需要重新申请新的数组,然后将旧字典所有链表中的元素重新挂接到新的数组下面,这是一个O(n)级别的操作,作为单线程的Redis表示很难承受这样耗时的过程。步子迈大了会扯着蛋,所以Redis使用渐进式rehash小步搬迁。虽然慢一点,但是肯定可以搬完。
    在这里插入图片描述搬迁操作埋伏在当前字典的后续指令中(来自客户端的hset/hdel指令等),实际上在redis中每一个增删改查命令中都会判断数据库字典中的哈希表是否正在进行渐进式rehash,如果是则帮助执行一次。但是有可能客户端闲下来了,没有了后续指令来触发这个搬迁,那么Redis就置之不理了么?当然不会,优雅的Redis怎么可能设计的这样潦草。Redis还会在定时任务中对字典进行主动搬迁。

    • redis使用的Hash函数:
      hashtable 的性能好不好完全取决于 hash 函数的质量。hash 函数如果可以将 key 打散的比较均匀,那么这个 hash 函数就是个好函数。Redis 的字典默认的 hash 函数是 siphash。siphash 算法即使在输入 key 很小的情况下,也可以产生随机性特别好的输出,而且它的性能也非常突出。对于 Redis 这样的单线程来说,字典数据结构如此普遍,字典操作也会非常频繁,hash 函数自然也是越快越好。

    • 扩容条件:
      正常情况下,当 hash 表中元素的个数等于第一维数组的长度时,就会开始扩容,扩容的新数组是原数组大小的 2 倍。不过如果 Redis 正在做 bgsave,为了减少内存页的过多分离 (Copy On Write),Redis 尽量不去扩容 (dict_can_resize),但是如果 hash 表已经非常满了,元素的个数已经达到了第一维数组长度的 5 倍 (dict_force_resize_ratio),说明 hash 表已经过于拥挤了,这个时候就会强制扩容。

    • 缩容条件:
      当 hash 表因为元素的逐渐删除变得越来越稀疏时,Redis 会对 hash 表进行缩容来减少 hash 表的第一维数组空间占用。缩容的条件是元素个数低于数组长度的 10%。缩容不会考虑 Redis 是否正在做 bgsave。

    4.4.2 常用命令

    hset

    给集合中的 键赋值

    hget

    从集合 取出 value

    hmset …

    批量设置hash的值

    hexists key

    查看哈希表 key 中,给定域 field 是否存在。

    hkeys

    列出该hash集合的所有field

    hvals

    列出该hash集合的所有value

    hincrby

    为哈希表 key 中的域 field 的值加上增量 increment

    hsetnx

    将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在 .

    4.5 Redis五大数据类型–zset (sorted set)

    Redis有序集合zset与普通集合set非常相似,是一个没有重复元素的字符串集合。不同之处是有序集合的每个成员都关联了一个评分(score) ,这个评分(score)被用来按照从最低分到最高分的方式排序集合中的成员。集合的成员是唯一的,但是评分可以是重复了 。

    因为元素是有序的, 所以你也可以很快的根据评分(score)或者次序(position)来获取一个范围的元素。访问有序集合的中间元素也是非常快的,因此你能够使用有序集合作为一个没有重复成员的智能列表。

    4.5.1 底层原理

    它的内部实现用的是一种叫着「跳跃链表」的数据结构。
    因为 zset 要支持随机的插入和删除,所以它不好使用数组来表示(数组的随机插入删除效率太低)。所以肯定要用链表。但zset的特点就是有一个Score值,Zset又是有序的,每次插入新元素要插入到适当的位置而不是无脑追加到末尾,也就是插入前肯定要定位。二分查找法对象只能是数组(因为有索引下标),这时候跳跃链表(skipList)就闪亮登场了。
    关于跳跃链表,《Redis深度历险》这本书举得例子挺好,我就直接粘过来了:

    想想一个创业公司,刚开始只有几个人,团队成员之间人人平等,都是联合创始人。随着公司的成长,人数渐渐变多,团队沟通成本随之增加。这时候就会引入组长制,对团队进行划分。每个团队会有一个组长。开会的时候分团队进行,多个组长之间还会有自己的会议安排。公司规模进一步扩展,需要再增加一个层级 —— 部门,每个部门会从组长列表中推选出一个代表来作为部长。部长们之间还会有自己的高层会议安排。
    跳跃列表就是类似于这种层级制,最下面一层所有的元素都会串起来。然后每隔几个元素挑选出一个代表来,再将这几个代表使用另外一级指针串起来。然后在这些代表里再挑出二级代表,再串起来。最终就形成了金字塔结构。 想想你老家在世界地图中的位置:亚洲–>中国->安徽省->安庆市->枞阳县->汤沟镇->田间村->xxxx号,也是这样一个类似的结构。

    在这里插入图片描述

    「跳跃列表」之所以「跳跃」,是因为内部的元素可能「身兼数职」,比如上图中间的这个元素,同时处于 L0、L1 和 L2 层,可以快速在不同层次之间进行「跳跃」。
    定位插入点时,先在顶层进行定位,然后下潜到下一级定位,一直下潜到最底层找到合适的位置,将新元素插进去。你也许会问,那新插入的元素如何才有机会「身兼数职」呢?
    跳跃列表采取一个随机策略来决定新元素可以兼职到第几层。
    首先 L0 层肯定是 100% 了,L1 层只有 50% 的概率,L2 层只有 25% 的概率,L3 层只有 12.5% 的概率,一直随机到最顶层 L31 层。绝大多数元素都过不了几层,只有极少数元素可以深入到顶层。列表中的元素越多,能够深入的层次就越深,能进入到顶层的概率就会越大。

    查找过程:
    在这里插入图片描述

    • 随机层数:
      对于每一个新插入的节点,都需要调用一个随机算法给它分配一个合理的层数。直观上期望的目标是 50% 的 Level1,25% 的 Level2,12.5% 的 Level3,一直到最顶层2^-63,因为这里每一层的晋升概率是 50%。
      不过 Redis 标准源码中的晋升概率只有 25%,也就是代码中的 ZSKIPLIST_P 的值。所以官方的跳跃列表更加的扁平化,层高相对较低,在单个层上需要遍历的节点数量会稍多一点。
      也正是因为层数一般不高,所以遍历的时候从顶层开始往下遍历会非常浪费。跳跃列表会记录一下当前的最高层数maxLevel,遍历时从这个 maxLevel 开始遍历性能就会提高很多。

    • 插入过程:
      首先我们在搜索合适插入点的过程中将「搜索路径」摸出来了,然后就可以开始创建新节点了,创建的时候需要给这个节点随机分配一个层数,再将搜索路径上的节点和这个新节点通过前向后向指针串起来。如果分配的新节点的高度高于当前跳跃列表的最大高度,就需要更新一下跳跃列表的最大高度。

    • 删除过程:
      删除过程和插入过程类似,都需先把这个「搜索路径」找出来。然后对于每个层的相关节点都重排一下前向后向指针就可以了。同时还要注意更新一下最高层数maxLevel。

    • 更新过程:
      当我们调用 zadd 方法时,如果对应的 value 不存在,那就是插入过程。如果这个 value 已经存在了,只是调整一下 score 的值,那就需要走一个更新的流程。假设这个新的 score 值不会带来排序位置上的改变,那么就不需要调整位置,直接修改元素的 score 值就可以了。但是如果排序位置改变了,那就要调整位置。
      一个简单的策略就是先删除这个元素,再插入这个元素,需要经过两次路径搜索。Redis 就是这么干的。 不过 Redis 遇到 score 值改变了就直接删除再插入,不会去判断位置是否。

    • 特殊情况:如果Score都一样
      在一个极端的情况下,zset 中所有的 score 值都是一样的,zset 的查找性能会退化为 O(n) 么?Redis 作者自然考虑到了这一点,所以 zset 的排序元素不只看 score 值,如果 score 值相同还需要再比较 value 值 (字符串比较)。

    4.5.2 常用命令

    zadd …

    将一个或多个 member 元素及其 score 值加入到有序集 key 当中。

    zrange [WITHSCORES]

    返回有序集 key 中,下标在 之间的元素

    带WITHSCORES,可以让分数一起和值返回到结果集。

    zrangebyscore key min max [withscores] [limit offset count]

    返回有序集 key 中,所有 score 值介于 min 和 max 之间(包括等于 min 或 max )的成员。有序集成员按 score 值递增(从小到大)次序排列。

    zrevrangebyscore key max min [withscores] [limit offset count]

    同上,改为从大到小排列。

    zincrby

    为元素的score加上增量

    zrem

    删除该集合下,指定值的元素

    zcount

    统计该集合,分数区间内的元素个数

    zrank

    返回该值在集合中的排名,从0开始。




    5 Redis的事务

    5.1 事务基础

    MULTI 、 EXEC 、 DISCARD 和 WATCH 是 Redis 事务相关的命令。事务可以一次执行多个命令, 并且带有以下两个重要的保证:

    事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。

    事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。

    EXEC 命令负责触发并执行事务中的所有命令:

    如果客户端在使用 MULTI 开启了一个事务之后,却因为断线而没有成功执行 EXEC ,那么事务中的所有命令都不会被执行。
    另一方面,如果客户端成功在开启事务之后执行 EXEC ,那么事务中的所有命令都会被执行。

    当使用 AOF 方式做持久化的时候, Redis 会使用单个 write(2) 命令将事务写入到磁盘中。

    然而,如果 Redis 服务器因为某些原因被管理员杀死,或者遇上某种硬件故障,那么可能只有部分事务命令会被成功写入到磁盘中。

    如果 Redis 在重新启动时发现 AOF 文件出了这样的问题,那么它会退出,并汇报一个错误。

    使用redis-check-aof程序可以修复这一问题:它会移除 AOF 文件中不完整事务的信息,确保服务器可以顺利启动。

    从 2.2 版本开始,Redis 还可以通过乐观锁(optimistic lock)实现 CAS (check-and-set)操作。

    MULTI 命令用于开启一个事务,它总是返回 OK 。 MULTI 执行之后, 客户端可以继续向服务器发送任意多条命令, 这些命令不会立即被执行, 而是被放到一个队列中, 当 EXEC命令被调用时, 所有队列中的命令才会被执行。

    另一方面, 通过调用 DISCARD , 客户端可以清空事务队列, 并放弃执行事务。

    以下是一个事务例子, 它原子地增加了 foo 和 bar 两个键的值:

    > MULTI
    OK
    > INCR foo
    QUEUED
    > INCR bar
    QUEUED
    > EXEC
    1) (integer) 1
    2) (integer) 1
    

    EXEC 命令的回复是一个数组, 数组中的每个元素都是执行事务中的命令所产生的回复。 其中, 回复元素的先后顺序和命令发送的先后顺序一致。

    当客户端处于事务状态时, 所有传入的命令都会返回一个内容为 QUEUED 的状态回复(status reply), 这些被入队的命令将在 EXEC 命令被调用时执行。

    5.2 事务中的错误

    使用事务时可能会遇上以下两种错误:

    事务在执行 EXEC 之前,入队的命令可能会出错。比如说,命令可能会产生语法错误(参数数量错误,参数名错误,等等),或者其他更严重的错误,比如内存不足(如果服务器使用 maxmemory 设置了最大内存限制的话)。
    命令可能在 EXEC 调用之后失败。举个例子,事务中的命令可能处理了错误类型的键,比如将列表命令用在了字符串键上面,诸如此类。

    对于发生在 EXEC 执行之前的错误,客户端以前的做法是检查命令入队所得的返回值:如果命令入队时返回 QUEUED ,那么入队成功;否则,就是入队失败。如果有命令在入队时失败,那么大部分客户端都会停止并取消这个事务。

    不过,从 Redis 2.6.5 开始,服务器会对命令入队失败的情况进行记录,并在客户端调用 EXEC 命令时,拒绝执行并自动放弃这个事务。

    在 Redis 2.6.5 以前, Redis 只执行事务中那些入队成功的命令,而忽略那些入队失败的命令。 而新的处理方式则使得在流水线(pipeline)中包含事务变得简单,因为发送事务和读取事务的回复都只需要和服务器进行一次通讯。

    至于那些在 EXEC 命令执行之后所产生的错误, 并没有对它们进行特别处理: 即使事务中有某个/某些命令在执行时产生了错误, 事务中的其他命令仍然会继续执行。

    从协议的角度来看这个问题,会更容易理解一些。 以下例子中, LPOP 命令的执行将出错, 尽管调用它的语法是正确的:

    > Trying 127.0.0.1...
    Connected to localhost.
    Escape character is '^]'.
    MULTI
    +OK
    SET a 3
    abc
    +QUEUED
    LPOP a
    +QUEUED
    EXEC
    *2
    +OK
    > -ERR Operation against a key holding the wrong kind of value
    

    EXEC 返回两条bulk-string-reply: 第一条是 OK ,而第二条是 -ERR 。 至于怎样用合适的方法来表示事务中的错误, 则是由客户端自己决定的。

    最重要的是记住这样一条, 即使事务中有某条/某些命令执行失败了, 事务队列中的其他命令仍然会继续执行 —— Redis 不会停止执行事务中的命令

    以下例子展示的是另一种情况, 当命令在入队时产生错误, 错误会立即被返回给客户端:

    MULTI
    +OK
    INCR a b c
    -ERR wrong number of arguments for ‘incr’ command

    因为调用 INCR 命令的参数格式不正确, 所以这个 INCR 命令入队失败。

    5.5 乐观锁(watch)

    watch 会在事务开始之前盯住 1 个或多个关键变量,当事务执行时,也就是服务器收到了 exec 指令要顺序执行缓存的事务队列时,Redis 会检查关键变量自 watch 之后,是否被修改了 (包括当前事务所在的客户端)。如果关键变量被人动过了,exec 指令就会返回 null 回复告知客户端事务执行失败,这个时候客户端一般会选择重试。

    > watch books
    OK
    > incr books # 被修改了
    (integer) 1
    > multi
    OK
    > incr books
    QUEUED
    > exec # 事务执行失败
    (nil)
    

    5.4 事务总结

    综上所述,笔者认为Redis是部分支持事务的,当语法没有错误时,若遇到异常,会执行余下事务,当语法有错误时,会整批报错,不执行。

    6 Redis的配置

    在linux中 vim redis.conf可以看见一个900余行的配置文件,下面介绍常用的30余个配置:

    1、redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程:

    daemonize no

    2、当redis以守护进程方式运行时,redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定:

    pidfile /var/run/redis.pid

    3、指定redis监听端口,默认端口号为6379,作者在自己的一篇博文中解析了为什么选用6379作为默认端口,因为6379在手机按键上MERZ对应的号码,而MERZ取自意大利女歌手Alessia Merz的名字:

    port 6379

    4、设置tcp的backlog,backlog是一个连接队列,backlog队列总和=未完成三次握手队列+已完成三次握手队列。在高并发环境下你需要一个高backlog值来避免慢客户端连接问题。注意Linux内核会将这个值减小到/proc/sys/net/core/somaxconn 的值,所以需要确认增大somaxconn和tcp_max_syn_backlog两个值来达到想要的

    效果:

    tcp-backlog 511

    5、绑定的主机地址:

    bind 127.0.0.1

    6、当客户端闲置多长时间后关闭连接,如果指定为0,表示永不关闭:

    timeout 300

    7、设置检测客户端网络中断时间间隔,单位为秒,如果设置为0,则不检测,建议设置为60:

    tcp-keepalive 0

    8、指定日志记录级别,redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose:

    loglevel verbose

    9、日志记录方式,默认为标准输出,如果配置redis为守护进程方式运行,而这里又配置为日志记录方式为标准输出,则日志将会发送给/dev/null:

    logfile stdout

    10、设置数据库数量,默认值为16,默认当前数据库为0,可以使用select命令在连接上指定数据库id:

    databases 16

    11、指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合:

    save
    save 300 10:表示300秒内有10个更改就将数据同步到数据文件

    12、指定存储至本地数据库时是否压缩数据,默认为yes,redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变得巨大:

    rdbcompssion yes

    13、指定本地数据库文件名,默认值为dump.rdb:

    dbfilename dump.rdb

    14、指定本地数据库存放目录:

    dir ./

    15、设置当本机为slave服务时,设置master服务的IP地址及端口,在redis启动时,它会自动从master进行数据同步:

    slaveof

    16、当master服务设置了密码保护时,slave服务连接master的密码:

    masterauth

    17、设置redis连接密码,如果配置了连接密码,客户端在连接redis时需要通过auth 命令提供密码,默认关闭:

    requirepass foobared

    18、设置同一时间最大客户端连接数,默认无限制,redis可以同时打开的客户端连接数为redis进程可以打开的最大文件描述符数,如果设置maxclients 0,表示不作限制。当客 户端连接数到达限制时,redis会关闭新的连接并向客户端返回 max number of clients reached错误消息:

    maxclients 128

    19、指定redis最大内存限制,redis在启动时会把数据加载到内存中,达到最大内存后,redis会先尝试清除已到期或即将到期的key,当次方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。Redis新的vm机制, 会把key存放内存,value会存放在swap区:

    maxmemory

    20、设置缓存过期策略,有6种选择:(LRU算法最近最少使用)

    volatile-lru:使用LRU算法移除key,只对设置了过期时间的key;

    allkeys-lru:使用LRU算法移除key,作用对象所有key;

    volatile-random:在过期集合key中随机移除key,只对设置了过期时间的key;

    allkeys-random:随机移除key,作用对象为所有key;

    volarile-ttl:移除哪些ttl值最小即最近要过期的key;

    noeviction:永不过期,针对写操作,会返回错误信息。

    maxmemory-policy noeviction

    21、指定是否在每次更新操作后进行日志记录,redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内数据丢失。因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内置存在于内存中。默认为no:

    appendonly no

    22、指定更新日志文件名,默认为appendonly.aof:

    appendfilename appendonly.aof

    23、指定更新日志条件,共有3个可选值:

    no:表示等操作系统进行数据缓存同步到磁盘(快);

    always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全);

    everysec:表示每秒同步一次(折中,默认值)

    appendfsync everysec

    24、指定是否启用虚拟内存机制,默认值为no,简单介绍一下,VM机制将数据分页存放,由redis将访问量较小的页即冷数据 swap到磁盘上,访问多的页面由磁盘自动换出到内存中:

    vm-enabled no

    25、虚拟内存文件路径,默认值为/tmp/redis.swap,不可多个redis实例共享:

    vm-swap-file /tmp/redis.swap

    26、将所有大于vm-max-memory的数据存入虚拟内存,无论vm-max-memory设置多小,所有索引数据都是内存存储的(redis的索引数据就是keys),也就是说,当vm-max-memory设置为0的时候,其实是所有value都存在于磁盘。默认值为 0:

    vm-max-memory 0

    27、redis swap文件分成了很多的page,一个对象可以保存在多个page上面,但一个page上不能被多个对象共享,vm-page-size是根据存储的数据大小来设定的,作者建议如果储存很多小对象,page大小最好设置为32或者64bytes;如果存储很多大对象,则可以使用更大的page,如果不确定,就使用默认值:

    vm-page-size 32

    28、设置swap文件中page数量,由于页表(一种表示页面空闲或使用的bitmap)是放在内存中的,在磁盘上每8个pages将消耗1byte的内存:

    vm-pages 134217728

    29、设置访问swap文件的线程数,最好不要超过机器的核数,如果设置为0,那么所有对swap文件的操作都是串行的,可能会造成长时间的延迟。默认值为4:

    vm-max-threads 4

    30、设置在客户端应答时,是否把较小的包含并为一个包发送,默认为开启:

    glueoutputbuf yes

    31、指定在超过一定数量或者最大的元素超过某一临界值时,采用一种特殊的哈希算法:

    hash-max-zipmap-entries 64

    hash-max-zipmap-value 512

    32、指定是否激活重置hash,默认开启:

    activerehashing yes

    33、指定包含其他配置文件,可以在同一主机上多个redis实例之间使用同一份配置文件,而同时各个实例又拥有自己的特定配置文件:

    include /path/to/local.conf

    7 Redis 两大持久化策略:RDB 与AOF

    Redis 提供了不同级别的持久化方式:

    RDB(RedisDataBase)持久化方式能够在指定的时间间隔能对你的数据进行快照存储.

    AOF(AppendOnlyFile)持久化方式记录每次对服务器写的操作,当服务器重启的时候会重新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操作到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大.

    如果你只希望你的数据在服务器运行的时候存在,你也可以不使用任何持久化方式.
    你也可以同时开启两种持久化方式, 在这种情况下, 当redis重启的时候会优先载入AOF文件来恢复原始的数据,因为在通常情况下AOF文件保存的数据集要比RDB文件保存的数据集要完整.
    最重要的事情是了解RDB和AOF持久化方式的不同,让我们以RDB持久化方式开始:

    7.1 RDB(RedisDataBase)

    7.1.1 RDB优点

    • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
    • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
    • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
      与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

    7.1.2 RDB缺点

    • 如果你希望在redis意外停止工作(例如电源中断)的情况下丢失的数据最少的话,那么RDB不适合你.虽然你可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),是Redis要完整的保存整个数据集是一个比较繁重的工作,你通常会每隔5分钟或者更久做一次完整的保存,万一在Redis意外宕机,你可能会丢失几分钟的数据.
    • RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来提高数据集的耐久度.

    7.2 AOF(AppendOnlyFile)

    7.2.1 AOF优点

    • 使用AOF 会让你的Redis更加耐久: 你可以使用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.使用默认的每秒fsync策略,Redis的性能依然很好(fsync是由后台线程进行处理的,主线程会尽力处理客户端请求),一旦出现故障,你最多丢失1秒的数据.
    • AOF文件是一个只进行追加的日志文件,所以不需要写入seek,即使由于某些原因(磁盘空间已满,写的过程中宕机等等)未执行完整的写入命令,你也也可使用redis-check-aof工具修复这些问题.
      Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新 AOF 文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为 Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
    • AOF 文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以 Redis 协议的格式保存, 因此 AOF 文件的内容非常容易被人读懂, 对文件进行分析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只要 AOF 文件未被重写, 那么只要停止服务器, 移除 AOF 文件末尾的 FLUSHALL 命令, 并重启 Redis , 就可以将数据集恢复到 FLUSHALL 执行之前的状态。

    7.2.2 AOF缺点

    • 对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
    • 根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在一般情况下, 每秒 fsync 的性能依然非常高, 而关闭 fsync 可以让 AOF 的速度和 RDB 一样快, 即使在高负荷之下也是如此。 不过在处理巨大的写入载入时,RDB 可以提供更有保证的最大延迟时间(latency)。

    7.3 关于新的混合式备份

    7.3.1 开启混合持久化

    4.0版本的混合持久化默认关闭的,通过aof-use-rdb-preamble配置参数控制,yes则表示开启,no表示禁用,默认是禁用的,可通过config set修改。
    在这里插入图片描述

    7.3.2 混合持久化过程

    了解了AOF持久化过程和RDB持久化过程以后,混合持久化过程就相对简单了。

    混合持久化同样也是通过bgrewriteaof完成的,不同的是当开启混合持久化时,fork出的子进程先将共享的内存副本全量的以RDB方式写入aof文件,然后在将重写缓冲区的增量命令以AOF方式写入到文件,写入完成后通知主进程更新统计信息,并将新的含有RDB格式和AOF格式的AOF文件替换旧的的AOF文件。简单的说:新的AOF文件前半段是RDB格式的全量数据后半段是AOF格式的增量数据

    7.3.3 数据恢复过程

    当我们开启了混合持久化时,启动redis依然优先加载aof文件,aof文件加载可能有两种情况如下:

    aof文件开头是rdb的格式, 先加载 rdb内容再加载剩余的 aof。
    aof文件开头不是rdb的格式,直接以aof格式加载整个文件。
    

    8 Redis高可用主从复制集群

    8.1 同步方式

    8.1.1 增量同步

    Redis 同步的是指令流,主节点会将那些对自己的状态产生修改性影响的指令记录在本地的内存 buffer 中,然后异步将 buffer 中的指令同步到从节点,从节点一边执行同步的指令流来达到和主节点一样的状态,一遍向主节点反馈自己同步到哪里了 (偏移量)。
    因为内存的 buffer 是有限的,所以 Redis 主库不能将所有的指令都记录在内存 buffer 中。Redis 的复制内存 buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。
    在这里插入图片描述如果因为网络状况不好,从节点在短时间内无法和主节点进行同步,那么当网络状况恢复时,Redis 的主节点中那些没有同步的指令在 buffer 中有可能已经被后续的指令覆盖掉了,从节点将无法直接通过指令流来进行同步,这个时候就需要用到更加复杂的同步机制 —— 快照同步。

    8.1.2 快照同步

    快照同步是一个非常耗费资源的操作,它首先需要在主库上进行一次 bgsave 将当前内存的数据全部快照到磁盘文件中,然后再将快照文件的内容全部传送到从节点。从节点将快照文件接受完毕后,立即执行一次全量加载,加载之前先要将当前内存的数据清空。加载完毕后通知主节点继续进行增量同步。
    在整个快照同步进行的过程中,主节点的复制 buffer 还在不停的往前移动,如果快照同步的时间过长或者复制 buffer 太小,都会导致同步期间的增量指令在复制 buffer 中被覆盖,这样就会导致快照同步完成后无法进行增量复制,然后会再次发起快照同步,如此极有可能会陷入快照同步的死循环。在这里插入图片描述当从节点刚刚加入到集群时,它必须先要进行一次快照同步,同步完成后再继续进行增量同步。

    常用3招

    • 一主二仆
      在这里插入图片描述

      一个Master,两个Slave,Slave只能读不能写;当Slave与Master断开后需要重新slave of连接才可建立之前的主从关系;Master挂掉后,Master关系依然存在,Master重启即可恢复。

    • 薪火相传
      在这里插入图片描述
      上一个Slave可以是下一个Slave的Master,Slave同样可以接收其他slaves的连接和同步请求,那么该slave作为了

    链条中下一个slave的Master,如此可以有效减轻Master的写压力。如果slave中途变更转向,会清除之前的数据,重新

    建立最新的。

    • 反客为主

    当Master挂掉后,Slave可键入命令 slaveof no one使当前redis停止与其他Master redis数据同步,转成

    Master redis。

    四、复制原理
    • 1、Slave启动成功连接到master后会发送一个sync命令;

    • 2、Master接到命令启动后的存盘进程,同时收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master 将传送整个数据文件到slave,以完成一次完全同步;

    • 3、全量复制:而slave服务在数据库文件数据后,将其存盘并加载到内存中;

    • 4、增量复制:Master继续将新的所有收集到的修改命令依次传给slave,完成同步;

    • 5、但是只要是重新连接master,一次完全同步(全量复制)将被自动执行。

    五、哨兵模式(sentinel)
       反客为主的自动版,能够后台监控Master库是否故障,如果故障了根据投票数自动将slave库转换为主库。一组sentinel能
    
       同时监控多个Master。
    
       使用步骤:
    
       1、在Master对应redis.conf同目录下新建sentinel.conf文件,名字绝对不能错;
    
       2、配置哨兵,在sentinel.conf文件中填入内容:
    
             sentinel monitor 被监控数据库名字(自己起名字) ip port 1
    
             说明:上面最后一个数字1,表示主机挂掉后slave投票看让谁接替成为主机,得票数多少后成为主机。
    
      3、启动哨兵模式:
    
            命令键入:redis-sentinel  /myredis/sentinel.conf
    
           注:上述sentinel.conf路径按各自实际情况配置
    
    六、复制的缺点

    延时,由于所有的写操作都是在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使得这个问题更加严重。

    9 Redis高级数据结构

    9.1 位图

    其实不该把位图放在“高级数据结构”子项的,因为位图不是特殊的数据结构,它的内容其实就是普通的字符串,也就是 byte 数组。我们可以使用普通的 get/set 直接获取和设置整个位图的内容,也可以使用位图操作 getbit/setbit 等将 byte 数组看成「位数组」来处理。

    位图可以很好的节约空间,有的数据只有2种情况,比如记录一个用户是否登录。可以用String可以记录,记1为登录,0为未登录,可以01100111。。。这样记录,但很耗空间。若我们用位数组来存,一个顶八个,因为即使是首个0,它也是八位的,已经够我们记录8天的登录情况了!

    • 统计和查找
      Redis 提供了位图统计指令 bitcount 和位图查找指令 bitpos,bitcount 用来统计指定位置范围内 1 的个数,bitpos 用来查找指定范围内出现的第一个 0 或 1。
      比如我们可以通过 bitcount 统计用户一共签到了多少天,通过 bitpos 指令查找用户从哪一天开始第一次签到。如果指定了范围参数[start, end],就可以统计在某个时间范围内用户签到了多少天,用户自某天以后的哪天开始签到。
      遗憾的是, start 和 end 参数是字节索引,也就是说指定的位范围必须是 8 的倍数,而不能任意指定。这很奇怪。因为这个设计,我们无法直接计算某个月内用户签到了多少天,而必须要将这个月所覆盖的字节内容全部取出来 (getrange 可以取出字符串的子串) 然后在内存里进行统计,这个非常繁琐。

    9.2 HyperLogLog

    HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,标准误差是 0.81%,这样的精确度已经可以满足上面的 UV 统计需求了。

    9.2.1 使用场景

    记录网站的不同客户的访问量(同一个用户多次登录也只算一次)

    9.2.2 使用方法

    HyperLogLog 提供了两个指令 pfadd 和 pfcount,根据字面意义很好理解,一个是增加计数,一个是获取计数。pfadd 用法和 set 集合的 sadd 是一样的,来一个用户 ID,就将用户 ID 塞进去就是。pfcount 和 scard 用法是一样的,直接获取计数值。

    HyperLogLog 除了上面的 pfadd 和 pfcount 之外,还提供了第三个指令 pfmerge,用于将多个 pf 计数值累加在一起形成一个新的 pf 值。
    比如在网站中我们有两个内容差不多的页面,运营说需要这两个页面的数据进行合并。其中页面的 UV 访问量也需要合并,那这个时候 pfmerge 就可以派上用场了。

    9.2.3 注意事项

    它需要占据一定 12k 的存储空间,所以它不适合统计单个用户相关的数据。如果你的用户上亿,可以算算,这个空间成本是非常惊人的。但是相比 set 存储方案,HyperLogLog 所使用的空间那真是可以使用千斤对比四两来形容了。

    9.3 布隆过滤器

    上一节我们学会了使用 HyperLogLog 数据结构来进行估数,它非常有价值,可以解决很多精确度不高的统计需求。
    但是如果我们想知道某一个值是不是已经在 HyperLogLog 结构里面了,它就无能为力了,它只提供了 pfadd 和 pfcount 方法,没有提供 pfcontains 这种方法。

    布隆过滤器可以理解为一个不怎么精确的 set 结构,当你使用它的 contains 方法判断某个对象是否存在时,它可能会误判。但是布隆过滤器也不是特别不精确,只要参数设置的合理,它的精确度可以控制的相对足够精确,只会有小小的误判概率。

    当布隆过滤器说某个值存在时,这个值可能不存在;当它说不存在时,那就肯定不存在。打个比方,当它说不认识你时,肯定就不认识;当它说见过你时,可能根本就没见过面,不过因为你的脸跟它认识的人中某脸比较相似 (某些熟脸的系数组合),所以误判以前见过你。

    9.3.1 使用场景

    我们在使用新闻客户端看新闻时,它会给我们不停地推荐新的内容,它每次推荐时要去重,去掉那些已经看过的内容。新闻客户端推荐系统如何实现推送去重?

    9.3.2 使用方法

    Redis 官方提供的布隆过滤器到了 Redis 4.0 提供了插件功能之后才正式登场。布隆过滤器作为一个插件加载到 Redis Server 中,给 Redis 提供了强大的布隆去重功能。

    布隆过滤器有二个基本指令,bf.add 添加元素,bf.exists 查询元素是否存在,它的用法和 set 集合的 sadd 和 sismember 差不多。注意 bf.add 只能一次添加一个元素,如果想要一次添加多个,就需要用到 bf.madd 指令。同样如果需要一次查询多个元素是否存在,就需要用到 bf.mexists 指令。

    虽然在Redis中使用挺方便的,但作为一个JAVA开发,在JAVA代码中并没有太好的办法使用布隆过滤器,笔者在RedisTemplate并没有找到布隆过滤器的操作方法。
    只能引入jar包

    <dependency>
    		<groupId>com.redislabs</groupId>
    		<artifactId>jrebloom</artifactId>
    		<version>1.0.1</version>
    </dependency>
    

    在这里插入图片描述在这里插入图片描述

    9.3.3 布隆过滤器的原理

    每个布隆过滤器对应到 Redis 的数据结构里面就是一个大型的位数组和几个不一样的无偏 hash 函数。所谓无偏就是能够把元素的 hash 值算得比较均匀。

    向布隆过滤器中添加 key 时,会使用多个 hash 函数对 key 进行 hash 算得一个整数索引值然后对位数组长度进行取模运算得到一个位置,每个 hash 函数都会算得一个不同的位置。再把位数组的这几个位置都置为 1 就完成了 add 操作。
    向布隆过滤器询问 key 是否存在时,跟 add 一样,也会把 hash 的几个位置都算出来,看看位数组中这几个位置是否都位 1,只要有一个位为 0,那么说明布隆过滤器中这个 key 不存在。如果都是 1,这并不能说明这个 key 就一定存在,只是极有可能存在,因为这些位被置为 1 可能是因为其它的 key 存在所致。如果这个位数组比较稀疏,这个概率就会很大,如果这个位数组比较拥挤,这个概率就会降低。具体的概率计算公式比较复杂。

    使用时不要让实际元素远大于初始化大小,当实际元素开始超出初始化大小时,应该对布隆过滤器进行重建,重新分配一个 size 更大的过滤器,再将所有的历史元素批量 add 进去 (这就要求我们在其它的存储器中记录所有的历史元素)。因为 error_rate 不会因为数量超出就急剧增加,这就给我们重建过滤器提供了较为宽松的时间。
    在这里插入图片描述

    9.3.4 其他应用

    在爬虫系统中,我们需要对 URL 进行去重,已经爬过的网页就可以不用爬了。但是 URL 太多了,几千万几个亿,如果用一个集合装下这些 URL 地址那是非常浪费空间的。这时候就可以考虑使用布隆过滤器。它可以大幅降低去重存储消耗,只不过也会使得爬虫系统错过少量的页面。
    布隆过滤器在 NoSQL 数据库领域使用非常广泛,我们平时用到的 HBase、Cassandra 还有 LevelDB、RocksDB 内部都有布隆过滤器结构,布隆过滤器可以显著降低数据库的 IO 请求数量。当用户来查询某个 row 时,可以先通过内存中的布隆过滤器过滤掉大量不存在的 row 请求,然后再去磁盘进行查询。
    邮箱系统的垃圾邮件过滤功能也普遍用到了布隆过滤器,因为用了这个过滤器,所以平时也会遇到某些正常的邮件被放进了垃圾邮件目录中,这个就是误判所致,概率很低。

    10 SpringBoot上使用

    11 Redis实现分布式锁

    RedisTemplate
    待填坑 2019.4.28

    彩蛋:关于Redis为什么是单线程?

    在这里插入图片描述

    在这里插入图片描述

    展开全文
  • Spring实战(第四版)

    千次阅读 多人点赞 2019-03-03 20:17:19
    Spring实战(第四版) 链接:https://pan.baidu.com/s/1PhnJqOsQPz5hqe-zxkqPOg 提取码:eu15 复制这段内容后打开百度网盘手机App,操作更方便哦
  • Vue + Spring Boot 项目实战(一):项目简介

    万次阅读 多人点赞 2020-09-03 22:26:42
    白卷是一款使用 Vue+Spring Boot 开发的前后端分离项目。除用于入门练手之外,亦可作为搭建小型 Web 项目的脚手架。
  • Python数据分析与机器学习实战教程,该课程精心挑选真实的数据集为案例,通过python数据科学库numpy,pandas,matplot结合机器学习库scikit-learn完成一些列的机器学习案例。课程以实战为基础,所有课时都结合代码演示...
  • windows运维实战视频从零基础讲起,做到全方位技术提高,内容包括Windows Server 2012 R2概述、安装与基本环境设置,本地用户与组账户的管理,建立Active Directory域,NTFS磁盘的安全性与管理,访问网络文件,...
  • 微信小程序开发实战

    万人学习 2019-04-01 14:05:00
    本套课程使用了元认知教学法,直接实战式教学,摆脱学院派的理论式讲解,对于0基础的学员可以入门编写微信小程序,过程中指导如何学习使用文档查阅接口等,通过两个完整的实战小项目的实例,入手小程序开发。
  • 实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL&...
  • JFinal极速开发企业实战视频教程

    万人学习 2020-05-08 16:45:56
    手把手从零开始带大家开发一个整站,通过本课程的学习可以深入理解WEB开发核心流程,深入理解JFinal核心架构设计原理,熟练使用JFinal开发项目,掌握企业实战技巧
  • 机器学习&深度学习系统实战

    万人学习 2020-07-06 11:37:45
    数学原理推导与案例实战紧密结合,由机器学习经典算法过度到深度学习的世界,结合深度学习两大主流框架Caffe与Tensorflow,选择经典项目实战人脸检测与验证码识别。原理推导,形象解读,案例实战缺一不可!具体课程...
  • 大数据Spark实战视频教程

    万人学习 2019-12-19 12:46:49
    大数据Spark实战视频培训教程:本课程内容涉及,Spark虚拟机安装、Spark表配置、平台搭建、快学Scala入门、Spark集群通信、任务调度、持久化等实战内容。Spark是UC Berkeley AMP lab (加州大学伯克利分校的AMP实验室...
  • 本课程是一门具有很强实践性质的“项目实战”课程,即“企业中台系统实战”,其中主要包含三大块核心内容,如下图所示(右键可以在新标签页中打开图片放大查看): 即主要包含以下三大块内容...
  • Android入门实战教程

    万人学习 2018-10-22 21:38:02
    本课程由CSDN讲师博主,IT_xiao小巫倾情相授,本课程针对Android初学者的入门实战课程,详尽的介绍Android基础的方方面面,从开发环境搭建到基础控件的使用、Android四大组件的应用、数据存储、AndroidUI设计、网络...
  • web级mysql实战

    万人学习 2018-10-22 21:38:04
    本课程是基于web开发领域下的实战mysql课程。本课程会模拟一个项目需求,从一万数据到百万数据逐步讲解如何建立、优化和第三方库结合的过程。 同时本课程的高潮在: 1、mysql+memcached的结合实战 2、mysql+redis...
  • 11个web前端开发实战项目案例+源码!拿走就是了

    万次阅读 多人点赞 2019-07-25 22:11:00
    小白为大家收集了11个web前端开发,大企业实战项目案例+5W行源码!拿走玩去吧! 老规矩:转发+关注并私信小编:“资料”全部打包带走! 下面给大家简单介绍几个: 小米官网: 项目描述 首先选择小米官网为第...
  • Go语言实战笔记

    万次阅读 多人点赞 2018-08-03 10:45:26
    Go语言实战笔记(一)| Go包管理 Go语言实战笔记(二)| Go开发工具 Go语言实战笔记(三)| Go Doc 文档 Go语言实战笔记(四)| Go 数组 Go语言实战笔记(五)| Go 切片 Go语言实战笔记(六)| Go Map Go语言...
  • 基于OpenLayers实战地理信息系统视频

    万次阅读 多人点赞 2017-08-04 15:49:59
    看到大家都在找寻关于基于Openlayers实战地理信息系统的视频,小编在此共享,但是由于可能会涉及版权的问题,我将视频上传到了360云盘上,需要的朋友请留言...  第一讲:概述  第二讲:庞杂的GIS体系概览  第三讲:...
  • Python3网络爬虫快速入门实战解析

    万次阅读 多人点赞 2020-04-23 09:58:13
    本文以实战为主,阅读过程如稍有不适,还望多加练习。 本文的实战内容有:网络小说下载(静态网站)、优美壁纸下载(动态网站)、爱奇艺VIP视频下载 PS:本文为Gitchat线上分享文章,该文章发布时间为2017年09月19日。
1 2 3 4 5 ... 20
收藏数 542,921
精华内容 217,168
关键字:

实战