文章目录
01 Redis简介
NoSQL简介
NoSQL(NoSQL = Not Only SQL ),意为“不仅仅是SQL”,泛指非关系型的数据库。NoSQL数据库的产生就是为了解决大规模数据集合和多重数据种类带来的挑战,尤其是大数据应用难题,包括超大规模数据的存储。
Redis简介
Redis(Remote Dictionary Server)远程字典数据服务的缩写,由意大利
人开发的是一款内存高速缓存数据库。使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API的非关系型数据库。
Redis数据库为什么叫做内存告诉缓存数据库?
缓存有两种类型:
• 页面缓存经常会用在CMS(content management system)里面。
• 数据缓存经常会用在页面的具体数据里面。
京东商城页面适合做数据缓存:如果数据在短时间内不会发生变化,且频繁被访问, 为了提高用户的请求速度和降低网站的负载, 将数据放到一个读取速度更快的介质上,称为数据缓存。该介质可以是文件、数据库、内存。内存经常用于数据缓存。
Redis特性
1、Redis支持数据的持久化,可以将内存中的数据保存在磁盘中,重启的时候可以再次加载进行使用。
2、Redis不仅仅支持简单的key-value类型的数据,同时还提供String,list,set,zset,hash等数据结构的存储。
3、Redis支持数据的备份,即master-slave(主从)模式的数据备份。
4、性能极高 – Redis能读的速度是110000次/s,写的速度是81000次/s 。
5、原子 – Redis的所有操作都是原子性的,同时Redis还支持对几个操作全并后的原子性执行。
6、丰富的特性 – Redis还支持 publish/subscribe, 通知, 设置key有效期等等特性。
Redis和Memcache对比?
Redis参考资料
Github 源码:https://github.com/antirez/redis
Redis 官网:https://redis.io/
Redis中的内置数据结构
• string 类型 是二进制安全的。可以包含任何数据(eg: jpg 图片或者序列化的对象)。 从内部实现来看其实 string 可以看作 byte 数组,最大上限是 1G 字节。
• hash类型 是一个 string 类型的 field 和 value 的映射表.它的添加、删除操作都是 O(1)(平均)。
• list类型 是一个 string 类型的双向链表。最大长度是(2的32次方 )。
• set 类型 是 string 类型的通过 hash table 实现的无序集合。添加、删除和查找的复 杂度都是 O(1)。最大长度是(2的32次方 )。
Redis的内置指令:http://doc.redisfans.com/
KEY值操作
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> get name
“fentiao”
127.0.0.1:6379> GETRANGE name 0 3 # 取name中从0-3
“fent”
127.0.0.1:6379> SETRANGE name 0 westos # 从第0位开始后面六位设置为westos
(integer) 7
127.0.0.1:6379> get name
“westoso”
127.0.0.1:6379> SETRANGE 2 123
(error) ERR wrong number of arguments for ‘setrange’ command
127.0.0.1:6379> SETRANGE name 2 123
(integer) 7
127.0.0.1:6379> get name
“we123so”
127.0.0.1:6379> SETRANGE name 2 4763291567492714892759071
(integer) 27
127.0.0.1:6379> get name
“we4763291567492714892759071”
String字符串操作
[root@localhost 6379]# redis-cli
127.0.0.1:6379> mget name age
“we4763291567492714892759071”
“18”
127.0.0.1:6379> mget name age a
“we4763291567492714892759071”
“18”
“123”
127.0.0.1:6379> STRLEN NAEM
(integer) 0
127.0.0.1:6379> STRLEN NAME
(integer) 0
127.0.0.1:6379> STRLEN name
(integer) 27
127.0.0.1:6379> INCR name
(error) ERR value is not an integer or out of range
127.0.0.1:6379> INCR age
(integer) 19
127.0.0.1:6379> get age
“19”
127.0.0.1:6379> INCR a
(integer) 124
127.0.0.1:6379> INCR a 5
(error) ERR wrong number of arguments for ‘incr’ command
127.0.0.1:6379> INCRBY a 10
(integer) 134
127.0.0.1:6379> get age
“19”
127.0.0.1:6379> get a
“134”
127.0.0.1:6379> DESC age
(error) ERR unknown commandDESC
, with args beginning with:age
,
127.0.0.1:6379> DECR age
(integer) 18
127.0.0.1:6379> DECRBY age 10
(integer) 8
127.0.0.1:6379> set name yw
OK
127.0.0.1:6379> get name
“yw”
127.0.0.1:6379> APPEND name -westos
(integer) 9
127.0.0.1:6379> get name
“yw-westos”
127.0.0.1:6379> GETSET name westos
“yw-westos”
127.0.0.1:6379> get name
“westos”
List列表操作
127.0.0.1:6379> LPUSH names name1 name2 name3
(integer) 3
127.0.0.1:6379> LRANGE names
(error) ERR wrong number of arguments for ‘lrange’ command
127.0.0.1:6379> LRANGE names 0 3
“name3”
“name2”
“name1”
127.0.0.1:6379> RPUSH names yw
(integer) 4
127.0.0.1:6379> LRANGE names 0 4
“name3”
“name2”
“name1”
“yw”
127.0.0.1:6379> RPUSH names yw1 yw2
(integer) 6
127.0.0.1:6379> LRANGE names 0 5
“name3”
“name2”
“name1”
“yw”
“yw1”
“yw2”
Redis的应用场景
Redis持久化
(1)Redis 是一个内存数据库,与传统的MySQL,Oracle等关系型数据库直接将内容保存到硬盘 中相比,内存数据库的读写效率比传统数据库要快的多(内存的读写效率远远大于硬盘的读写 效率)。但是保存在内存中也随之带来了一个缺点,一旦断电或者宕机,那么内存数据库中的 数据将会全部丢失。
(2)目标: 持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。
(3)Redis 提供了两种持久化式:RDB(默认) 和AOF。
Redis持久化: RDB
RDB是Redis用来进行持久化的一种方式,是把当前内存中的数据集快照写入磁盘,也就是 Snapshot 快照(数据库中所有键值对数据)。恢复时是将快
照文件直接读到内存里
我们可以配置 redis在 n 秒内如果超过 m 个 key 被修改就自动做快照,下面是默认的快照保存配置,我们也可以在配置文件中修改配置:
dbfilename dump.rdb
save 900 1
save 300 10
#持久化存储文件名为 dump.rdb
#900 秒内如果超过 1 个 key 被修改,则发起快照保存
#300 秒内容如超过 10 个 key 被修改,则发起快照保存
save 60 10000 # 600 秒内容如超过 10000 个 key 被修改,则发起快照保存
• RDB 持久化存是一定时间内做一次备份,如果
redis意外down掉的话,就会丢失最后一次快照后
的所有修改(数据有丢失)。
• 工作原理: 当 redis 重启时会通过重新执行文件中
保存的写命令来在内存中重建整个数据库的内容。
Redis持久化:AOF
通过配置文件告诉 redis 我们想要通过 fsync 函数强制 os 写入到磁盘的时机。有三种方式如下(默认是:每秒 fsync 一次), 通过BGREWRITEAOF指令压缩/优化命令
//启用 aof 持久化方式
appendonly yes
//收到写命令就立即写入磁盘,最慢,但是保证完全的持久化
#appendfsync always
//每秒钟写入磁盘一次,在性能和持久化方面做了很好的折中
appendfsync everysec
//完全依赖 os,性能最好,持久化没保证
#appendfsync no
02 Redis的安装与部署
- 首先上官网下载Redis 压缩包,地址 http://redis.io/download
- 压缩包执行解压操作并进行编译
tar xzf redis-x.x.x.tar.gz #解压
cd redis-x.x.x/
make # 编译
make install # 安装
sh utils/install_server.sh # 执行脚本
安装中的一些配置信息可以选择默认,也可以自己选择
redis安装成功
3.redis的部署
3. 执行Redis-server 命令,启动Redis 服务
redis-server
4. 客户端redisClient
redis-cli
#登录redis
set ‘a’ ‘123’
5. 当添加键值后,关闭服务端就会发现在配置的Data dir目录下,创建了一个文件:dump.rdb,这个文件用于将数据持久化存储
03 Redis架构模式
主从复制
Redis 的复制(replication)功能允许用户根据一个 Redis 服务器来创建任意多个该 服务器的复制品,其中被复制的服务器为主服务器(master),而通过复制创建出来 的服务器复制品则为从服务器(slave)。 只要主从服务器之间的网络连接正常,主从 服务器两者会具有相同的数据,主服务器就会一直将发生在自己身上的数据更新同步 给 从服务器,从而一直保证主从服务器的数据相同。
特点: 1、master/slave 角色 2、master/slave 数据相同 3、降低 master 读压力在转交从库
问题: 1. 无法保证高可用 2. 没有解决 master 写的压力
04 Redis与Python
Python实现Redis数据库的连接
"""
常用的字符串操作有:
get key 获取对应key值的value值
mget key1 key2 ... 获取多个key值对应的value值
set key value 设置key值
mset key value key1 value1 设置多个key值
incr key 使数值型key自增1
incrby key increment 让key增加指定值
decr key 使数值型key自减1
decrby 让key增加指定值
strlen key 计算key的长度
"""
import redis
# 建立数据库连接
redis_client = redis.StrictRedis(host='172.25.254.15',port=6379)
print('连接成功')
redis_client.mset({'name':'westos','age':10})
name = redis_client.get('name').decode('utf-8')# 默认传过来的是bytes类型,需要解码为字符串
name_len = redis_client.strlen('name')
print('用户名称:',name,'用户名称长度:',name_len)
age_add = redis_client.incr('age')
print('当前年龄为:',redis_client.get('age'))
age_add1 = redis_client.incrby('age',10)
print('时年后为',redis_client.get('age'))
案例一:生成给用户发送的验证码,验证码限制3s内生成一次,3s内第二次则无法获取
项目需求:
限制某段时间内的访问次数,就比如我们登录的功能可以用手机获取验证码登录,但是我们发送验证码使用的第三方,是多少钱多少条的,肯定不能让他一直点,一直发短信,就算前端js做了校验,若有些人用fiddler拦截绕过前台就麻烦了,这时候可以用redis的incr命令和expire结合起来做一个解决方案
- 用户请求了我们的发送短信验证码服务, 用户对应的key(电话号码)
- 如果缓存中存在了,就是已经请求过验证码了,比如我系统参数设置的至少60秒发送一次短信, 那就代表60之内再次请求了,返回False阻止
- 否则就是第一次请求短信验证码或者等了60再次请求的验证码, 发送验证码. 放入redis缓存并设置失效时间60秒
import time
import redis
def send_msg(phone):
"""模拟手机发送验证码"""
import string
import random
nums_list = random.sample(string.digits,4)
nums_code = "".join(nums_list)
print('%s验证码:%s[西部开元公司]' %(phone,nums_code))
return nums_code
def phone_code(phone):
"""
电话验证码模拟,3秒之后可以再次发送
:param phone:
:return:
"""
client = redis.StrictRedis()
if client.get(phone):
print('验证码发送频繁,请稍后再试')
return False
else:
code = send_msg(phone)
client.set(phone,code,ex=3) # 设置过期时间为3秒
if __name__ == '__main__':
print('第一次发送')
phone_code('110')
print('第二次发送')
phone_code('110')
time.sleep(3)
print('第三次发送')
phone_code('110')
String set(String key, String value, String nxxx, String expx, long time);
该方法是: 存储数据到缓存中,并制定过期时间和当Key存在时是否覆盖。
nxxx: 只能取NX或者XX,如果取NX,则只有当key不存在是才进行set,如果取XX,则只有当key已经存在时才进行set
expx: 只能取EX或者PX,代表数据过期时间的单位,EX代表秒,PX代表毫秒。
time: 过期时间,单位是expx所代表的单位。
案例二:基于IP限制Http访问频率
项目需求:
IP限制频繁访问:
如果你运行 HTTP 服务,并且希望限制 HTTP 的访问频率,那么你可以借助一些比较稳定的工具,
例如: github.com/didip/tollbooth。不过如果你构建的应用比较简单,也可以自己来实现。
IP限制的规则:
每分钟访问次数不能超过60次。
def ip_limit(IP):
"""每分钟访问次数不能超过60次"""
import redis
client = redis.StrictRedis()
if client.exists(IP): # exists方法判断key值是否存在
count = client.get(IP).decode('utf-8')
if int(count) >= 60:
print('%s访问频繁' %(IP))
else:
client.incr(IP)
print("访问+1")
else:
client.set(IP,1,ex=60)
print('%s第一次访问' %(IP))
if __name__ == '__main__':
ip_limit('127.0.0.1')
for i in range(100):
ip_limit('172.25.254.25')
案例三:通过Redis中的列表操作实现消息队列
LPUSH names name1,name2,name3 # :左边插入名为names元素为 name1,name2,name3的列表
LRANGE names 0 3 #查看列表索引0-3的值
RPUSH #右边插入
LPOP #删除左边的元素并弹出删除的元素
RPOP #删除右边的元素并弹出删除的元素
BLPOP names 10 #弹出左边的元素,当前列表为空会一直等待10s,如果等待10s还有没有元素就会退出
LPUSH westos #另一个terminal中插入westos,此时10s内 BLPOP names 10 会弹出左边的元素westos
import queue
class RedisQueue(object):
"""左边为队头,右边为队尾的消息队列"""
def __init__(self,name,**conf):
import redis
self.__client = redis.Redis(**conf)
self.key = name
def qsize(self):
return self.__client.llen(self.key)
def put(self,item):
"""入队操作"""
self.__client.rpush(self.key,item)
def get(self,timeout=5):
"""获取队头,如果没有到,等待时间为timeout"""
item = self.__client.blpop(self.key,timeout=timeout)
return item
def get_nowait(self):
item = self.__client.blpop(self.key)
return item
if __name__ == '__main__':
q = RedisQueue('scores',host='172.25.254.15',port=6379)
for i in range(10):
q.put(i)
while True:
result = q.get(timeout=100)
print(result)
if not result:
break
实现本地连接另一台主机redis数据库
注:实验成功的前提是两台实验机的redis都安装成功并且是开启状态,另外被连接实验机的防火墙也必须是关闭状态,才能保证实验顺利进行。
服务端配置:
设置步骤如下:
第一步:服务端:172.25.254.15设置redis配置信息:
vim /etc/redis/6379.conf #进入redis的配置文件中修改信息
bind 0.0.0.0 #任何IP都可以链接12主机的redis库
protected-mode no
wq保存退出
第二步:客户端连接
redis-cli -h 172.25.254.15 #登陆服务端的redis
pipline
缓冲多条命令,然后一次值型减少服务端和客户端TCP数据库包,从而提高效率
import redis
redis_client = redis.StrictRedis(host='172.25.254.18', port=6380)
pipe = redis_client.pipeline()
pipe.set('name', 'westos')
pipe.set('name', 'westos')
pipe.set('name', 'westos')
pipe.set('name', 'westos')
pipe.set('name', 'westos')
pipe.execute()
封装
• 连接redis服务器部分是一致的
• 将string类型的读写进行封装
邮件信息传递工作原理
SMTP协议: Simple Mail Transfer Protocol, 是一种提供可靠且有效的电子邮件传输的协议。SMTP建立 在FTP文件传输服务上的一种邮件服务,主要用于系统之间的邮件信息传递,并提供有关来信的通知。 POP3协议: Post Office Protocol - Version 3, 主要用于支持使用客户端远程管理在服务器上的电子邮 件。
用户读取邮件使用的协议:POP3
SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。
Python对SMTP支持有smtplib和email两个模块,email负责构造邮件,smtplib负责发送邮件。
# 设置服务器,用户名、口令以及邮箱的后缀
import smtplib
from email.mime.text import MIMEText
from email.utils import formataddr
smtp_server = "smtp.163.com"
from_username = '西部开源技术中心'
mail_user = "yw17392517656@163.com"
# 是开启smtp的授权吗不是真实的密码。
mail_password = "yw17392517656"
# 邮件主题的前缀
mail_prefix = "[运维开发部]-"
def send_email(to_addrs, subject, msg):
try:
# 将要发送的文本信息做MIME封装
msg = MIMEText(msg)
# 格式化发件人名称
msg['From'] = formataddr([from_username, mail_user])# 发送者的信息
msg['To'] = to_addrs # 接收者的信息
msg['Subject'] = mail_prefix + subject
# 1. 实例化smtp对象
server = smtplib.SMTP()
# 2. 连接邮件服务器
server.connect(smtp_server)
# 3. 登录
server.login(mail_user, mail_password)
# 4. 发送邮件内容
server.sendmail(mail_user, to_addrs, msg.as_string())
# 5. 关闭连接
server.quit()
except Exception as e:
print(str(e))
return False
else:
return True
if __name__ == '__main__':
send_email('741047561@qq.com','寒假作业', '请与2月2日之前提交')
print("发送成功.....")