redis学习笔记,常用方法

Redis-day01-note

Redis介绍

  • 特点及优点
1、开源的,使用C编写,基于内存且支持持久化
2、高性能的Key-Value的NoSQL数据库
3、支持数据类型丰富,字符串strings,散列hashes,列表lists,集合sets,有序集合sorted sets 等
4、支持多种编程语言(C C++ Python Java PHP ...
  • 与其他数据库对比
1、MySQL : 关系型数据库,表格,基于磁盘,慢
2、MongoDB:键值对文档型数据库,值为JSON文档,基于磁盘,慢,存储数据类型单一
3、Redis的诞生是为了解决什么问题??
   # 解决硬盘IO带来的性能瓶颈
  • 应用场景
1、使用Redis来缓存一些经常被用到、或者需要耗费大量资源的内容,通过这些内容放到redis里面,程序可以快速读取这些内容
2、一个网站,如果某个页面经常会被访问到,或者创建页面时消耗的资源比较多,比如需要多次访问数据库、生成时间比较长等,我们可以使用redis将这个页面缓存起来,减轻网站负担,降低网站的延迟,比如说网站首页等
3、比如新浪微博
	# 新浪微博,基于TB级的内存数据库
  	# 内容 :存储在MySQL数据库
  	# 关系 :存储在redis数据库
  	# 数字 :粉丝数量,关注数量,存储在redis数据库
	# 消息队列
  • 数据库排名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lnfujJcG-1573565054411)(E:\工作相关\讲课文件\Redis\redis_day01_note\db_rank.png)]

  • redis版本
1、最新版本:5.0
2、常用版本:2.42.62.8
	3.0(里程碑)、3.23.44.05.0
3、图形界面管理工具(写的一般)
	RedisDesktopManager
# 为了解决负载问题,所以发明了redis
  • 诞生历程
# 1、历史
LLOOGG.com 帮助别的网站统计用户信息,各个网站发送的浏览记录都会存储到存储队列,5-10000条记录,多余5条需要收费

# 2、原理
FIFO机制,先进先出,满了进一条就出一条,网站越多,队列越多,推入和弹出操作越多

# 3、技术及问题
开始使用MySQL进行硬盘读写,速度很慢,导致无法实时显示,所以自己写了一个列表结构的内存数据库,程序性能不会受到硬盘IO的限制,加了持久化的功能

# 4、redis数据库戛然而生
  • Redis附加功能
1、持久化
  将内存中数据保存到磁盘中,保证数据安全,方便进行数据备份和恢复
2、发布与订阅功能
   将消息同时分发给多个客户端,用于构建广播系统
3、过期键功能
   为键设置一个过期时间,让它在指定时间内自动删除
   <节省内存空间>
   # 音乐播放器,日播放排名,过期自动删除
4、事务功能
   原子的执行多个操作
5、主从复制
6、Sentinel哨兵

安装

  • Ubuntu
# 安装
sudo apt-get install redis-server
# 确认是否启动
ps -aux | grep redis
# 服务端启动
sudo /etc/init.d/redis-server status
sudo /etc/init.d/redis-server start
sudo /etc/init.d/redis-server stop
sudo /etc/init.d/redis-server restart
# 客户端连接
redis-cli -h IP地址 -p 端口
redis-cli # 默认连接本机的6379端口
127.0.0.1:6379>ping
PONG
  • Windows
1、下载安装包
	Github下载地址:https://github.com/MicrosoftArchive/redis/releases
	百度网盘下载地址 https://pan.baidu.com/s/1z1_OdNVbtgyEjiktqgB83g 密码:kdfq
2、解压
3、启动服务端
   双击解压后的 redis-server.exe 
4、客户端连接
   双击解压后的 redis-cli.exe
    
# 问题:关闭终端后服务终止
# 解决:将Redis服务安装到本地服务
1、重命名 redis.windows.conf 为 redis.conf,作为redis服务的配置文件
2、cmd命令行,进入到redis-server.exe所在目录
3、执行:redis-server --service-install redis.conf --loglevel verbose
4、计算机-管理-服务-Redis-启动

# 卸载
到 redis-server.exe 所在路径执行:
1、redis-server --service-uninstall
2、sc delete Redis

配置文件详解

  • 配置文件所在路径
1、Ubuntu
	/etc/redis/redis.conf

2、windows 下载解压后的redis文件夹中
	redis.windows.conf 
	redis.conf
  • 设置连接密码
1、requirepass 密码
2、重启服务
   sudo /etc/init.d/redis-server restart
3、客户端连接
   redis-cli -h 127.0.0.1 -p 6379 -a 123456
   127.0.0.1:6379>ping
  • 允许远程连接
1# 注释掉IP地址绑定
   bind 127.0.0.1
2# 关闭保护模式(默认开始,不允许外部网络访问)
   protected-mode no
3# 重启redis服务
   sudo /etc/init.d/redis-server restart
  • 远程连接测试

    Windows连接Ubuntu的Redis服务

# cmd命令行
1、d:
2、cd Redis3.0
3、redis-cli -h x.x.x.x -a 123456
4、x.x.x.x:6379>ping

数据类型

字符串类型(string)

特点

1、字符串、数字,都会转为字符串来存储
2、以二进制的方式存储在内存中

必须掌握命令

1set key value
2、setnx key value
3set key value ex seconds
4、get key
5、mset key1 value1 key2 value2 
6、mget key1 key2 key3 
7、stren key 
# 数字操作
8、incr key
9、decr key

扩展命令

1、append key value
2、setrange key index value
3、getrange key start stop
4、incrby key step
5、decrby key step

常用命令

  • set | get命令

    作用: 设置键值,获取键对应的值

    命令格式: set key value

    ​ get key

tarena@tedu:~$ redis-cli -h 127.0.0.1 -p 6379 -a 123456
127.0.0.1:6379> set name 'Lucy'
OK
127.0.0.1:6379> get name
"Lucy"
127.0.0.1:6379> set number 10
OK
127.0.0.1:6379> get number
"10"
127.0.0.1:6379> set number 6.66
OK
127.0.0.1:6379> get number
"6.66"
  • set命令之 - setnx

    setnx key value : 键不存在时才能进行设置**(必须掌握)**

# 键不存在,进行设置(此处name键已经存在)
127.0.0.1:6379> setnx name 'Tom'
(nil)
127.0.0.1:6379>
  • set命令之 - ex

    作用: 设置过期时间

    命令格式: set key value ex seconds

127.0.0.1:6379> set name 'Tiechui' ex 3
OK
127.0.0.1:6379> get name
"Tiechui"
# 3秒后再次获取,得到 nil
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> 
  • mset | mget

    作用: 同时设置多个值,获取多个值

127.0.0.1:6379> mset name1 'lucy' name2 'tom' name3 'jim'
OK
127.0.0.1:6379> mget name1 name2 name3
1) "lucy"
2) "tom"
3) "jim"
127.0.0.1:6379> 

键的命名规范

​ mset wang::email wangweichao@tedu.cn

127.0.0.1:6379> mset wang::email wangweichao@tedu.cn guo::email guods@tedu.cn
OK
127.0.0.1:6379> mget wang::email guo::email
1) "wangweichao@tedu.cn"
2) "guods@tedu.cn"
127.0.0.1:6379> 
  • strlen

    作用: 获取值的长度

    命令格式: strlen key

127.0.0.1:6379> strlen name
(integer) 11
127.0.0.1:6379> 
  • 字符串索引操作

    setrange key 索引值 value

    作用: 从索引值开始,value替换原内容

127.0.0.1:6379> get message
"hello world"
127.0.0.1:6379> setrange message 6 'tarena'
(integer) 12
127.0.0.1:6379> get message
"hello tarena"
127.0.0.1:6379> 

getrange key 起始值 终止值

作用: 获取指定范围切片内容

127.0.0.1:6379> get message
"hello tarena"
127.0.0.1:6379> getrange message 0 4
"hello"
127.0.0.1:6379> getrange message 0 -1
"hello tarena"
127.0.0.1:6379> 
  • append key value

    作用: 追加拼接value的值

127.0.0.1:6379> set message 'hello '
OK
127.0.0.1:6379> append message 'world'
(integer) 11
127.0.0.1:6379> get message
"hello world"
127.0.0.1:6379> 

整数操作

​ INCRBY key 步长

​ DECRBY key 步长

127.0.0.1:6379> set number 10
OK
127.0.0.1:6379> get number
"10"
127.0.0.1:6379> INCRBY number 5
(integer) 15
127.0.0.1:6379> get number
"15"
127.0.0.1:6379> DECRBY number 5
(integer) 5
127.0.0.1:6379> get number
"5"

INCR key : +1操作

DECR key : -1操作

127.0.0.1:6379> incr number
(integer) 7
127.0.0.1:6379> decr number
(integer) 6
127.0.0.1:6379> get number
"6"
127.0.0.1:6379> 

应用场景

抖音上有人关注你了,是不是可以用INCR呢,如果取消关注了是不是可以用DECR

浮点数操作

​ incrbyfloat key step

127.0.0.1:6379> get number
"10"
127.0.0.1:6379> INCRBYFLOAT number 6.66
"12.66"
127.0.0.1:6379> INCRBYFLOAT number -6.66
"6"
127.0.0.1:6379> 
# 先转为数字类型,然后再进行相加减,不能使用append

string命令汇总

# 字符串操作
1set key value
2、setnx key value
3、get key
3、mset
4、mget
5set key value ex seconds
6、strlen key 
# 数字操作
7、incrby key 步长
8、decrby key 步长
9、incr key
10、decr key
11、incrbyfloat key number
# 设置过期时间的两种方式
# 方式一
1set key value ex 3
# 方式二
1set key value
2、expire key 5 # 秒
3、pexpire key 5 # 毫秒
# 查看存活时间
ttl key
# 删除过期
persist key

  • 通用命令
# 切换库
select number
# 查看键
keys * 
# 键类型
TYPE key
# 键是否存在
exists key
# 删除键
del key
# 键重命名
rename key newkey
# 返回旧值并设置新值(如果键不存在,就创建并赋值)
getset key value
# 清除当前库中所有数据(慎用)
flushdb
# 清除所有库中所有数据(慎用)
flushall

string数据类型注意

# key值取值原则
1、key值不宜过长,消耗内存,且在数据中查找这类键值的计算成本高
2、不宜过短,可读性较差
# 值
1、一个字符串类型的值最多能存储512M内容

练习

1、查看 db0 库中所有的键
   select 0
   keys *
2、设置键 trill:username 对应的值为 user001,并查看
   set trill:username 'user001'
3、获取 trill:username 值的长度
   strlen trill:username
4、一次性设置 trill:password 、trill:gender、trill:fansnumber 并查看(值自定义)
   mset trill:password '123456' trill:gender 'm' trill:fansnumber 10
   mget trill:password trill:gender trill:fansnumber
5、查看键 trill:score 是否存在
   exists trill:score
6、增加10个粉丝
   incrby trill:fansnumber 10
7、增加2个粉丝(一个一个加)
   incr trill:fansnumber 
   incr trill:fansnumber
8、有3个粉丝取消关注你了
   decrby trill:fansnumber 10
9、又有1个粉丝取消关注你了
   decrby trill:fansnumber 1
10、思考、思考、思考...,清除当前库
   flushdb
11、一万个思考之后,清除所有库
   flushall

列表数据类型(List)

  • 特点
1、元素是字符串类型
2、列表头尾增删快,中间增删慢,增删元素是常态
3、元素可重复
4、最多可包含2^32 -1个元素
5、索引同python列表

  • 头尾压入元素(LPUSH | RPUSH)

​ 1、LPUSH key value

​ 2、RPUSH key value

127.0.0.1:6379> LPUSH mylist1 0 1 2 3 4
(integer) 5
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "0"
127.0.0.1:6379> RPUSH mylist2 0 1 2 3 4
(integer) 5
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "0"
2) "1"
3) "2"
4) "3"
5) "4"
127.0.0.1:6379> 

  • 查看|设置 列表元素

    查看(LRANGE)

    LRANGE key start stop
    # 查看列表中所有元素
    LRANGE key 0 -1
    
    

    获取指定位置元素(LINDEX)

    LINDEX key index
    
    

    设置指定位置元素的值(LSET)

    LSET key index value
    
    

    获取列表长度(LLEN)

    LLEN key
    
    
  • 头尾弹出元素(LPOP | RPOP)

    LPOP key : 从列表头部弹出一个元素

    RPOP key : 从列表尾部弹出一个元素

    RPOPLPUSH source destination : 从一个列表尾部弹出元素压入到另一个列表头部

127.0.0.1:6379> LRANGE mylist1 0 -1
1) "4"
2) "3"
3) "2"
4) "1"
5) "8"
127.0.0.1:6379> LPOP mylist1
"4"
127.0.0.1:6379> RPOP mylist1
"8"
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "3"
2) "2"
3) "1"
127.0.0.1:6379> RPOPLPUSH mylist1 mylist2
"1"
127.0.0.1:6379> LRANGE mylist1 0 -1
1) "3"
2) "2"
127.0.0.1:6379> LRANGE mylist2 0 -1
1) "1"
2) "0"
3) "1"
4) "2"

  • 移除指定元素(LREM)

    LREM key count value

    count>0:表示从头部开始向表尾搜索,移除与value相等的元素,数量为count
    count<0:表示从尾部开始向表头搜索,移除与value相等的元素,数量为count
    count=0:移除表中所有与value相等的值
    
    
    

    示例

    127.0.0.1:6379> LRANGE mylist1 0 -1
    1) "3"
    2) "2"
    127.0.0.1:6379> LPUSH mylist1 3 2
    (integer) 4
    127.0.0.1:6379> LRANGE mylist1 0 -1
    1) "2"
    2) "3"
    3) "3"
    4) "2"
    127.0.0.1:6379> LREM mylist1 1 2
    (integer) 1
    127.0.0.1:6379> LRANGE mylist1 0 -1
    1) "3"
    2) "3"
    3) "2"
    127.0.0.1:6379> LREM mylist1 1 3
    (integer) 1
    127.0.0.1:6379> LRANGE mylist1 0 -1
    1) "3"
    2) "2"
    127.0.0.1:6379> 
    
    
    
  • **去除指定范围外元素(LTRIM) **

    LTRIM key start stop

    127.0.0.1:6379> LRANGE mylist2 0 -1
    1) "1"
    2) "0"
    3) "1"
    4) "2"
    5) "3"
    6) "4"
    127.0.0.1:6379> LTRIM mylist2 0 -2
    OK
    127.0.0.1:6379> LRANGE mylist2 0 -1
    1) "1"
    2) "0"
    3) "1"
    4) "2"
    5) "3"
    127.0.0.1:6379> 
    
    
    

    应用场景: 保存微博评论最后500条

    LTRIM user001::comments 0 499
    
    
    
  • 列表中插入值(LINSERT)

    LINSERT key BEFORE|AFTER pivot value

    key和pivot不存在,不进行任何操作

    示例代码

    127.0.0.1:6379> LRANGE mylist2 0 -1
    1) "0"
    2) "1"
    3) "2"
    4) "3"
    5) "4"
    127.0.0.1:6379> LINSERT mylist2 after 2 666
    (integer) 6
    127.0.0.1:6379> LINSERT mylist2 before 4 888
    (integer) 7
    127.0.0.1:6379> LRANGE mylist2 0 -1
    1) "0"
    2) "1"
    3) "2"
    4) "666"
    5) "3"
    6) "888"
    7) "4"
    127.0.0.1:6379> 
    
    
    
  • 阻塞弹出(BLPOP | BRPOP)

    BLPOP key timeout

    BRPOP key timeout

    1、如果弹出的列表不存在或者为空,就会阻塞
    2、超时时间设置为0,就是永久阻塞,直到有数据可以弹出
    3、如果多个客户端阻塞再同一个列表上,使用First In First Service原则,先到先服务
    
    
    

    示例

    127.0.0.1:6379> BLPOP mylist2 0
    1) "mylist2"
    2) "3"
    127.0.0.1:6379> BLPOP mylist2 0
    1) "mylist2"
    2) "2"
    127.0.0.1:6379> BLPOP mylist2 0
    1) "mylist2"
    2) "1"
    127.0.0.1:6379> BLPOP mylist2 0
    # 阻塞了
    
    
    

列表常用命令总结

# 增
1、LPUSH key value1 value2 
2、RPUSH key value1 value2
3、RPOPLPUSH source destination
4、LINSERT key after|before value newvalue
# 查
5、LRANGE key start stop
6、LLEN key
# 删
7、LPOP key
8、RPOP key
9、BLPOP key timeout
10、BRPOP key timeout
11、LREM key count value
12、LTRIM key start stop
# 改
13、LSET key index newvalue


练习

1、查看所有的键
   keys *
2、向列表 spider::urls 中以RPUSH放入如下几个元素:01_baidu.com、02_taobao.com、03_sina.com、04_jd.com、05_xxx.com
   RPUSH spider::urls 01_baidu.com 02_taobao.com 03_sina.com 04_jd.com 05_xxx.com
3、查看列表中所有元素
   LRANGE spider::urls 0 -1
4、查看列表长度
   LLEN spider::urls
5、将列表中01_baidu.com 改为 01_tmall.com
   LSET spider::urls 0 01_tmall.com
6、在列表中04_jd.com之后再加1个元素 02_taobao.com
   LINSERT spider::urls after 04_jd.com 02_taobao.com
7、弹出列表中的最后一个元素
   RPOP spider::urls
8、删除列表中所有的 02_taobao.com
   LREM spider::urls 0 02_taobao.com
9、剔除列表中的其他元素,只剩前3条
   LTRIM spider::urls 0 2


与python交互

  • 模块

Ubuntu

sudo pip3 install redis


Windows

python -m pip install redis


  • 使用流程
import redis
# 创建数据库连接对象
r = redis.Redis(host='127.0.0.1',port=6379,db=0,password='123456')


  • 通用命令代码示例
import redis

# 创建数据库连接对象
r = redis.Redis(host='192.168.43.49',port=6379,db=0,password='123456')
# [b'key1',b'key2']
print(r.keys('*'))
# 键类型:string
print(type('spider::urls'))
# 是否存在:1 或者 0
print(r.exists('spider::urls'))
# 删除key:spider::urls
r.delete('spider::urls')


字符串命令代码示例

import redis

r = redis.Redis(host='192.168.43.49',port=6379,db=0)

r.set('mystring','python')
# b'python'
print(r.get('mystring'))
# False
print(r.setnx('mystring','socket'))
# mset:参数为字典
r.mset({'mystring2':'mysql','mystring3':'mongodb'})
# mget:结果为一个列表
print(r.mget('mystring','mystring2','mystring3'))
# mystring长度:6
print(r.strlen('mystring'))
# 数字类型操作
r.set('number',10)
r.incrby('number',5)
r.decrby('number',5)
r.incr('number')
r.decr('number')
r.incrbyfloat('number',6.66)
r.incrbyfloat('number',-6.66)
# b'10'
print(r.get('number'))


python操作list

import redis

r = redis.Redis(host='192.168.43.49',port=6379,db=0)
# ['mysql','redis']
r.lpush('pylist','redis','mysql')
# ['mysql','redis','django','spider']
r.rpush('pylist','django','spider')
# ['mysql','redis','django','spider','AI']
r.linsert('pylist','after','spider','AI')
# 5
print(r.llen('pylist'))
# ['redis','django','spider']
r.lpop('pylist')
r.rpop('pylist')
# ['redis','django','spider']
print(r.lrange('pylist',0,-1))
# ['redis','spider']
r.lrem('pylist',0,'django')
# 返回True,['redis']
r.ltrim('pylist',0,0)
# 返回True,['spiderman']
r.lset('pylist',0,'spiderman')

r.delete('pylist')


位图操作bitmap(重要)

位图不是真正的数据类型,它是定义在字符串类型中
一个字符串类型的值最多能存储512M字节的内容,位上限:2^32

强势点

可以实时的进行统计,极其节省空间。官方在模拟1亿28百万用户的模拟环境下,在一台MacBookPro上,典型的统计如“日用户数”的时间消耗小于50ms, 占用16MB内存

设置某一位上的值

setbit key offset value
# offset是偏移量,从0开始


示例

# 默认扩展位以0填充
127.0.0.1:6379> set mykey ab
OK
127.0.0.1:6379> get mykey
"ab"
127.0.0.1:6379> SETBIT mykey 0 1
(integer) 0
127.0.0.1:6379> get mykey
"\xe1b"
127.0.0.1:6379> 


获取某一位上的值

GETBIT key offset

127.0.0.1:6379> GETBIT mykey 3
(integer) 0
127.0.0.1:6379> GETBIT mykey 0
(integer) 1
127.0.0.1:6379> 


bitcount

统计键所对应的值中有多少个 1

127.0.0.1:6379> SETBIT user001 1 1
(integer) 0
127.0.0.1:6379> SETBIT user001 30 1
(integer) 0
127.0.0.1:6379> bitcount user001
(integer) 2
127.0.0.1:6379> 


应用场景案例

网站用户的上线次数统计(寻找活跃用户)

用户名为key,上线的天作为offset,上线设置为1

示例: 用户名为 user001 的用户,今年第1天上线,第30天上线

SETBIT user001 1 1

SETBIT user001 30 1

BITCOUNT user001

代码实现

import redis

r = redis.Redis(host='192.168.43.49',port=6379,db=2,password='123456')

# user1,一年之中第1天和第5天登录
r.setbit('user001',1,1)
r.setbit('user001',5,1)
# user2,一年之中第100天和第200天登录
r.setbit('user002',100,1)
r.setbit('user002',200,1)
# user3,一年之中好多天登录
for i in range(0,365,2):
    r.setbit('user003',i,1)
# user4,一年之中好多天登录
for i in range(0,365,3):
    r.setbit('user004',i,1)

user_list = r.keys('user*')
print(user_list)

# 活跃用户
active_users = []
# 不活跃用户
noactive_user = []

for user in user_list:
    # 统计位图中有多少个 1
    login_count = r.bitcount(user)
    if login_count >= 100:
       active_users.append((user,login_count))
    else:
      noactive_user.append((user,login_count))

# 打印活跃用户
for active in active_users:
    print('活跃用户:',active)


list案例: 一个进程负责生产url,一个进程负责消费url

进程1: 生产者

import redis
import random
import time

urls_list = [
    '01_baidu.com',
    '02_sina.com',
    '03_taobao.com',
    '04_tmall.com',
    '05_jd.com'
]
r = redis.Redis(host='192.168.43.49',db=0,password='123456')
while True:
    url = random.choice(urls_list)
    r.lpush('spider::urls',url)
    time.sleep(random.randint(1,5))


进程2: 消费者

import redis

r = redis.Redis(host='192.168.43.49',db=0,password='123456')

while True:
    # 结果为元组
    try:
        url = r.blpop('spider::urls',3)
        print(url[1])
        r.lrem('spider::urls',count=0,value=url[1])
    except:
        print('爬取结束')
        break


redis_day01回顾

Redis的特点

1、基于key-value的非关系型数据库
2、基于内存存储,速度很快
3、基于内存存储,经常当作缓存型数据库使用,常用信息存储在热地是数据库中

五大数据类型

1、字符串类型(string)
2、列表类型(list3、哈希类型(hash4、集合类型(set5、有序集合类型(sorted set

字符串类型

# 设置key相关操作
1set key value
2、setnx key value
3、mset k1 v1 k2 v2 k3 v3
4set key value ex seconds
5set key value
5、expire key 5
5、pexpire key 5
5、ttl key
5、persist key
# 获取key相关操作
6、get key
7、mget k1 k2 k3
8、strlen key 
# 数字相关操作
7、incrby key 步长
8、decrby key 步长
9、incr key
10、decr key
11、incrbyfloat key number

列表类型

# 插入元素相关操作
1、LPUSH key value1 value2 
2、RPUSH key value1 value2
3、RPOPLPUSH source destination
4、LINSERT key after|before value newvalue
# 查询相关操作
5、LRANGE key start stop
6、LLEN key
# 删除相关操作
7、LPOP key
8、RPOP key
9、BLPOP key timeout
10、BRPOP key timeout
11、LREM key count value
12、LTRIM key start stop
# 修改指定元素相关操作
13、LSET key index newvalue

思考:

Redis列表如何当做共享队列来使用???

# 同学你好,你还记得小米应用商店爬取URL地址的案例吗?
1、生产者消费者模型
2、生产者进程在列表中 LPUSH | RPUSH 数据,消费者进程在列表中 RPOP | LPOP 数据

redis_day02笔记

Python操作字符串类型

import redis

r = redis.Redis(host='192.168.43.49',port=6379,password='123456',db=0)

r.set('mystring','python')
# b'python'
print(r.get('mystring'))
# False
print(r.setnx('mystring','socket'))
# mset:参数为字典
r.mset({'mystring2':'mysql','mystring3':'mongodb'})
# mget:结果为一个列表
print(r.mget('mystring','mystring2','mystring3'))
# mystring长度:6
print(r.strlen('mystring'))
# 数字类型操作
r.set('number',10)
r.incrby('number',5)
r.decrby('number',5)
r.incr('number')
r.decr('number')
r.incrbyfloat('number',6.66)
r.incrbyfloat('number',-6.66)
# b'10'
print(r.get('number'))

位图操作bitmap

定义

1、位图不是真正的数据类型,它是定义在字符串类型中
2、一个字符串类型的值最多能存储512M字节的内容,位上限:2^32
# 1MB = 1024KB
# 1KB = 1024Byte(字节)
# 1Byte = 8bit(位)

强势点

可以实时的进行统计,极其节省空间。官方在模拟1亿2千8百万用户的模拟环境下,在一台MacBookPro上,典型的统计如“日用户数”的时间消耗小于50ms, 占用16MB内存

设置某一位上的值(setbit)

# 设置某一位上的值(offset是偏移量,从0开始)
setbit key offset value
# 获取某一位上的值
GETBIT key offset
# 统计键所对应的值中有多少个 1 
BITCOUNT key

示例

# 默认扩展位以0填充
127.0.0.1:6379> set mykey ab
OK
127.0.0.1:6379> get mykey
"ab"
127.0.0.1:6379> SETBIT mykey 0 1
(integer) 0
127.0.0.1:6379> get mykey
"\xe1b"
127.0.0.1:6379> 

获取某一位上的值

GETBIT key offset

127.0.0.1:6379> GETBIT mykey 3
(integer) 0
127.0.0.1:6379> GETBIT mykey 0
(integer) 1
127.0.0.1:6379> 

bitcount

统计键所对应的值中有多少个 1

127.0.0.1:6379> SETBIT user001 1 1
(integer) 0
127.0.0.1:6379> SETBIT user001 30 1
(integer) 0
127.0.0.1:6379> bitcount user001
(integer) 2
127.0.0.1:6379> 

应用场景案例

网站用户的上线次数统计(寻找活跃用户)

用户名为key,上线的天作为offset,上线设置为1

示例: 用户名为 user001 的用户,今年第1天上线,第30天上线

SETBIT user001 1 1

SETBIT user001 30 1

BITCOUNT user001

代码实现

import redis

r = redis.Redis(host='192.168.43.49',port=6379,db=0)

# user1,一年之中第1天和第5天登录
r.setbit('user001',1,1)
r.setbit('user001',5,1)
# user2,一年之中第100天和第200天登录
r.setbit('user002',100,1)
r.setbit('user002',200,1)
# user3,一年之中好多天登录
for i in range(0,365,2):
    r.setbit('user003',i,1)
# user4,一年之中好多天登录
for i in range(0,365,3):
    r.setbit('user004',i,1)

user_list = r.keys('user*')
print(user_list)

# 活跃用户
active_users = []
# 不活跃用户
noactive_user = []

for user in user_list:
    # 统计位图中有多少个 1
    login_count = r.bitcount(user)
    if login_count >= 100:
       active_users.append((user,login_count))
    else:
      noactive_user.append((user,login_count))

# 打印活跃用户
for active in active_users:
    print('活跃用户:',active)

Hash散列数据类型

  • 定义

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVs8X0e3-1573565054414)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1562834840173.png)]

1、由field和关联的value组成的键值对
2、field和value是字符串类型
3、一个hash中最多包含2^32-1个键值对

  • 优点
1、节约内存空间
2、每创建一个键,它都会为这个键储存一些附加的管理信息(比如这个键的类型,这个键最后一次被访问的时间等)
3、键越多,redis数据库在储存附件管理信息方面耗费内存越多,花在管理数据库键上的CPU也会越多

  • 缺点(不适合hash情况)
1、使用二进制位操作命令:SETBIT、GETBIT、BITCOUNT等,如果想使用这些操作,只能用字符串键
2、使用过期键功能:键过期功能只能对键进行过期操作,而不能对散列的字段进行过期操作

基本命令操作

# 1、设置单个字段
HSET key field value
HSETNX key field value
# 2、设置多个字段
HMSET key field value field value
# 3、返回字段个数
HLEN key
# 4、判断字段是否存在(不存在返回0)
HEXISTS key field
# 5、返回字段值
HGET key field
# 6、返回多个字段值
HMGET key field filed
# 7、返回所有的键值对
HGETALL key
# 8、返回所有字段名
HKEYS key
# 9、返回所有值
HVALS key
# 10、删除指定字段
HDEL key field 
# 11、在字段对应值上进行整数增量运算
HINCRBY key filed increment
# 12、在字段对应值上进行浮点数增量运算
HINCRBYFLOAT key field increment

python基本方法

# 1、更新一条数据的属性,没有则新建
hset(name, key, value) 
# 2、读取这条数据的指定属性, 返回字符串类型
hget(name, key)
# 3、批量更新数据(没有则新建)属性
hmset(name, mapping)
# 4、批量读取数据(没有则新建)属性
hmget(name, keys, *args)
# 5、获取这条数据的所有属性和对应的值,返回字典类型
hgetall(name)
# 6、获取这条数据的所有属性名,返回列表类型
hkeys(name)
# 7、删除这条数据的指定属性
hdel(name, *keys)

Python代码hash散列

import redis

r = redis.Redis(host="192.168.43.49", port=6379, db=0,password='123456')
# 新建一条键名为"userinfo"的数据, 包含属性username
r.hset("userinfo", "username", 'zhanshen001')
# 更改键名为"userinfo"的数据, 更改属性username的值
r.hset("userinfo", "username", 'zhanshen002')

# 取出属性username的值
username = r.hget("userinfo", "username")

# 输出看一下(发现属性值已经为str)
print('username',username)

# 属性集合
user_dict = {
    "password": "123456",
    "name": "Wang Success",
    "sex": "male",
    "height": '178',
    "Tel": '13838383888',
}
# 批量添加属性
r.hmset("userinfo", user_dict)
# 取出所有数据(返回值为字典)
h_data = r.hgetall("userinfo")
print('all:', h_data)
# 删除属性(可以批量删除)
r.hdel("userinfo", "Tel")
# 取出所有属性名
h_keys = r.hkeys("userinfo")
print('all_key_name:',h_keys)
# 取出所有属性值
h_values = r.hvals('userinfo')
print('all_values:',h_values)

应用场景:微博好友关注

1、用户ID为key,Field为好友ID,Value为关注时间
	user:10000 user:606 20190520
	user:10000 user:605 20190521
2、用户维度统计
   统计数包括:关注数、粉丝数、喜欢商品数、发帖数
   用户为key,不同维度为field,value为统计数
   比如关注了5人
	HSET user:10000 fans 5
	HINCRBY user:10000 fans 1

应用场景: redis+mysql+hash组合使用

  • 原理

    用户想要查询个人信息
    1、到redis缓存中查询个人信息
    2、redis中查询不到,到mysql查询,并缓存到redis
    3、再次查询个人信息
    
    
  • 代码实现

    import redis
    import pymysql
    
    # 1、到redis中查询个人信息
    # 2、redis中查询不到,到mysql查询,并缓存到redis
    # 3、再次查询个人信息
    
    username = input('请输入用户名:')
    
    # 到redis缓存中查询
    r = redis.Redis(host='192.168.153.128',port=6379,password='123456',db=0)
    
    # 如果redis中没有缓存,则返回空字典{}
    result = r.hgetall(username)
    print('redis中找到:',result)
    
    if not result:
        db = pymysql.connect('192.168.153.128','tiger','123456','spider',charset='utf8')
        cursor = db.cursor()
        cursor.execute('select gender,age from user where username=%s',[username])
        # (('zhanshen001','m',30),)
        userinfo = cursor.fetchall()
        if not userinfo:
            print('MySQL中用户信息不存在')
        else:
            dict = {
                'gender':userinfo[0][0],
                'age':userinfo[0][1]
            }
            # hmset第二个参数为字典
            r.hmset(username,dict)
            # 设置过期时间为5分钟
            r.expire(username,60*5)
            print('redis缓存成功')
    
    

mysql数据库中数据更新信息后同步到redis缓存

用户修改个人信息时,要将数据同步到redis缓存

import redis
import pymysql

# 当用户修改个人信息时,要同步更新到redis缓存中

username = input('请输入用户名:')
new_age = input('请输入新年龄:')

#  连接redis准备更新
r = redis.Redis(host='192.168.153.128',port=6379,password='123456',db=0)
#  连接MySQL
db = pymysql.connect('192.168.153.128','tiger','123456','spider',charset='utf8')
cursor = db.cursor()
cursor.execute('update user set age=%s where username=%s',[new_age,username])
db.commit()

# 同步更新redis缓存
r.hset(username,'age',new_age)
print('已同步到redis缓存')
# 设置过期时间为5分钟
r.expire(username,60*5)

集合数据类型(set)

  • 特点
1、无序、去重
2、元素是字符串类型
3、最多包含2^32-1个元素

  • 基本命令
# 1、增加一个或者多个元素,自动去重
SADD key member1 member2
# 2、查看集合中所有元素
SMEMBERS key
# 3、删除一个或者多个元素,元素不存在自动忽略
SREM key member1 member2
# 4、元素是否存在
SISMEMBER key member
# 5、随机返回集合中指定个数的元素,默认为1个
SRANDOMMEMBER key count
# 6、返回集合中元素的个数,不会遍历整个集合,只是存储在键当中了
SCARD key
# 7、把元素从源集合移动到目标集合
SMOVE source destination member
# 8、差集(number1 1 2 3 number2 1 2 4)
SDIFF key1 key2 
# 9、差集保存到另一个集合中
SDIFFSTORE destination key1 key2
# 10、交集
SINTER key1 key2
SINTERSTORE destination key1 key2
# 11、并集
SUNION key1 key2
SUNIONSTORE destination key1 key2

案例: 新浪微博的共同关注

需求: 当用户访问另一个用户的时候,会显示出两个用户共同关注过哪些相同的用户

设计: 将每个用户关注的用户放在集合中,求交集即可

实现:

user001 = {‘peiqi’,‘qiaozhi’,‘danni’}

user002 = {‘peiqi’,‘qiaozhi’,‘lingyang’}

user001和user002的共同关注为:

SINTER user001 user002

结果为: {‘peiqi’,‘qiaozhi’}

python操作set

# 1、给name对应的集合中添加元素
sadd(name,values)
r.sadd("set_name","tom")
r.sadd("set_name","tom","jim")

# 2、获取name对应的集合的所有成员
smembers(name)

# 3、获取name对应的集合中的元素个数
scard(name)
r.scard("set_name")

# 4、检查value是否是name对应的集合内的元素
sismember(name, value)

# 5、随机删除并返回指定集合的一个元素
spop(name)

# 6、删除集合中的某个元素
srem(name, value) 
r.srem("set_name", "tom")

# 7、获取多个name对应集合的交集
sinter(keys, *args)

r.sadd("set_name","a","b")
r.sadd("set_name1","b","c")
r.sadd("set_name2","b","c","d")

print(r.sinter("set_name","set_name1","set_name2"))
#输出:{b'b'}

# 8、获取多个name对应的集合的并集
sunion(keys, *args)
r.sunion("set_name","set_name1","set_name2")

python代码实现微博关注

import redis

r = redis.Redis(host='192.168.153.128',port=6379,password='123456')

# 用户1关注的人
r.sadd('user_one','peiqi','qiaozhi','danni')
# 用户2关注的人
r.sadd('user_two','peiqi','qiaozhi','lingyang')

# user001和user002的共同关注的人为??求差集
result = r.sinter('user_one','user_two')
# 把集合中的每个元素转为string数据类型
focus_on_set = set()
for r in result:
    focus_on_set.add(r.decode())

print(focus_on_set)

有序集合sortedset

  • 特点
1、有序、去重
2、元素是字符串类型
3、每个元素都关联着一个浮点数分值(score),并按照分支从小到大的顺序排列集合中的元素(分值可以相同)
4、最多包含2^32-1元素

  • 示例

    一个保存了水果价格的有序集合

分值2.04.06.08.010.0
元素西瓜葡萄芒果香蕉苹果

一个保存了员工薪水的有序集合

分值600080001000012000
元素lucytomjimjack

一个保存了正在阅读某些技术书的人数

分值300400555666777
元素核心编程阿凡提本拉登阿姆斯特朗比尔盖茨
  • 增加

zadd key score member

# 在有序集合中添加一个成员
zadd key score member
# 查看指定区间元素(升序)
zrange key start stop [withscores]
# 查看指定区间元素(降序)
ZREVRANGE key start stop [withscores]
# 查看指定元素的分值
ZSCORE key member
# 返回指定区间元素
# offset : 跳过多少个元素
# count : 返回几个
# 小括号 : 开区间  zrangebyscore fruits (2.0 8.0
zrangebyscore key min max [withscores] [limit offset count]
# 删除成员
zrem key member
# 增加或者减少分值
zincrby key increment member
# 返回元素排名
zrank key member
# 返回元素逆序排名
zrevrank key member
# 删除指定区间内的元素
zremrangebyscore key min max
# 返回集合中元素个数
zcard key
# 返回指定范围中元素的个数
zcount key min max
zcount fruits 4 7 
zcount fruits (4 7
# 并集
zunionstore destination numkeys key [weights 权重值] [AGGREGATE SUM|MIN|MAX]
# 交集:和并集类似,只取相同的元素
ZINTERSTORE destination numkeys key1 key2 WEIGHTS weight AGGREGATE SUM|MIN|MAX

  • 查看: 指定索引区间元素(升序)

zrange key start stop [withscores]

127.0.0.1:6379> ZRANGE salary 0 -1
1) "lucy"
2) "tom"
3) "jim"
4) "jack"
127.0.0.1:6379> ZRANGE salary 0 -1 withscores
1) "lucy"
2) "6000"
3) "tom"
4) "8000"
5) "jim"
6) "10000"
7) "jack"
8) "12000"
127.0.0.1:6379> 

  • 查看: 指定索引区间元素(降序)

    ZREVRANGE key start stop [withscores]

  • 显示指定元素的分值

    ZSCORE key member

127.0.0.1:6379> zscore salary jack
"14000"
127.0.0.1:6379> 

  • 返回指定区间元素

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

    offset : 跳过多少个元素

    count : 返回几个

    小括号 : 开区间 zrangebyscore fruits (2.0 8.0

127.0.0.1:6379> ZRANGEBYSCORE salary (8000 12000
1) "jim"
2) "jack"
127.0.0.1:6379> ZRANGE salary 0 -1 withscores
1) "lucy"
2) "6000"
3) "tom"
4) "8000"
5) "jim"
6) "10000"
7) "jack"
8) "12000"

  • 删除

zrem key member

127.0.0.1:6379> ZREM salary jim
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 withscores
1) "lucy"
2) "6000"
3) "tom"
4) "8000"
5) "jack"
6) "12000"
127.0.0.1:6379> 

  • 增加或者减少分值

    zincrby key increment member

127.0.0.1:6379> ZINCRBY salary 2000 jack
"14000"
127.0.0.1:6379> ZRANGE salary 0 -1 withscores
1) "lucy"
2) "6000"
3) "tom"
4) "8000"
5) "jack"
6) "14000"
127.0.0.1:6379> 

  • 返回元素的排名(索引)

zrank key member

127.0.0.1:6379> zrank salary jack
(integer) 2
127.0.0.1:6379> 

  • 返回元素逆序排名

zrevrank key member

127.0.0.1:6379> ZREVRANK salary jack
(integer) 0
127.0.0.1:6379> ZREVRANK salary lucy
(integer) 2
127.0.0.1:6379> 

  • 删除指定区间内的元素

zremrangebyscore key min max

127.0.0.1:6379> ZREMRANGEBYSCORE salary 4000 6000
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1 withscores
1) "tom"
2) "8000"
3) "jack"
4) "14000"
127.0.0.1:6379> 

  • 返回集合中元素个数

    zcard key

127.0.0.1:6379> ZCARD salary
(integer) 2
127.0.0.1:6379>

  • 返回指定范围中元素的个数

    zcount key min max

    zcount fruits 4 7

    zcount fruits (4 7

    127.0.0.1:6379> ZRANGE salary 0 -1 withscores
    1) "tom"
    2) "8000"
    3) "jack"
    4) "14000"
    127.0.0.1:6379> zcount salary 8000 14000
    (integer) 2
    # 不包含8000,包含14000
    127.0.0.1:6379> zcount salary (8000 14000
    (integer) 1
    127.0.0.1:6379> 
    
    
    
  • 并集

    zunionstore destination numkeys key [weights ] [AGGREGATE SUM|MIN|MAX]

    127.0.0.1:6379> zadd stu_score1 60 tom 70 jim
    (integer) 2
    127.0.0.1:6379> zadd stu_score2 80 tom 90 lucy
    (integer) 2
    # 默认为SUM
    127.0.0.1:6379> ZUNIONSTORE stu_score3 2 stu_score1 stu_score2
    (integer) 3
    127.0.0.1:6379> ZRANGE stu_score3 0 -1 withscores
    1) "jim"
    2) "70"
    3) "lucy"
    4) "90"
    5) "tom"
    6) "140"
    127.0.0.1:6379> 
    # WEIGHTS 和 AGGREGATE 
    127.0.0.1:6379> ZRANGE stu_score1 0 -1 withscores
    1) "tom"
    2) "60"
    3) "jim"
    4) "70"
    127.0.0.1:6379> ZRANGE stu_score2 0 -1 withscores
    1) "tom"
    2) "80"
    3) "lucy"
    4) "90"
    # 权重1给stu_score1,权重0.5给stu_score2,算完权重之后求和SUM
    127.0.0.1:6379> ZUNIONSTORE stu_score8 2 stu_score1 stu_score2 weights 1 0.5 AGGREGATE SUM
    (integer) 3
    127.0.0.1:6379> ZRANGE stu_score8 0 -1 withscores
    1) "lucy"
    2) "45"
    3) "jim"
    4) "70"
    5) "tom"
    6) "100"
    127.0.0.1:6379> 
    
    
    
  • 交集

    ZINTERSTORE destination numkeys key1 key2 WEIGHTS weight AGGREGATE SUM|MIN|MAX

    和并集类似,只取相同的元素

python操作sorted set

import redis

r = redis.Redis(host='192.168.43.49',port=6379,password='123456',db=0)
# 注意第二个参数为字典
r.zadd('salary',{'tom':6000,'jim':8000,'jack':12000})
# 结果为列表中存放元组[(),(),()]
print(r.zrange('salary',0,-1,withscores=True))
print(r.zrevrange('salary',0,-1,withscores=True))
# start:起始值,num:显示条数
print(r.zrangebyscore('salary',6000,12000,start=1,num=2,withscores=True))
# 删除
r.zrem('salary','tom')
print(r.zrange('salary',0,-1,withscores=True))
# 增加分值
r.zincrby('salary',5000,'jack')
print(r.zrange('salary',0,-1,withscores=True))
# 返回元素排名
print(r.zrank('salary','jack'))
print(r.zrevrank('salary','jack'))
# 删除指定区间内的元素
r.zremrangebyscore('salary',6000,8000)
print(r.zrange('salary',0,-1,withscores=True))
# 统计元素个数
print(r.zcard('salary'))
# 返回指定范围内元素个数
print(r.zcount('salary',6000,20000))
# 并集
r.zadd('salary2',{'jack':17000,'lucy':8000})
r.zunionstore('salary3',('salary','salary2'),aggregate='max')
print(r.zrange('salary3',0,-1,withscores=True))
# 交集
r.zinterstore('salary4',('salary','salary2'),aggregate='max')
print(r.zrange('salary4',0,-1,withscores=True))


案例1:网易音乐排行榜

1、每首歌的歌名作为元素(先不考虑重复)
2、每首歌的播放次数作为分值
3、使用ZREVRANGE来获取播放次数最多的歌曲


代码实现

import redis

r = redis.Redis(host='192.168.43.49',port=6379,password='123456',db=0)

r.zadd('ranking',{'song1':1,'song2':1,'song3':1,'song4':1})
r.zadd('ranking',{'song5':1,'song6':1,'song7':1})
r.zadd('ranking',{'song8':1,'song9':1})

r.zincrby('ranking',50,'song3')
r.zincrby('ranking',60,'song5')
r.zincrby('ranking',80,'song7')
# 获取前10名
rlist = r.zrevrange('ranking',0,2,withscores=True)

i = 1
for r in rlist:
    print('第%d名:%s' % (i,r[0].decode()))
    i += 1


案例2: 京东商品畅销榜

# 第1天
ZADD mobile-001 5000 'huawei' 4000 'oppo' 3000 'iphone'
# 第2天
ZADD mobile-002 5200 'huawei' 4300 'oppo' 3230 'iphone'
# 第3天
ZADD mobile-003 5500 'huawei' 4660 'oppo' 3580 'iphone'
问题:如何获取三款收集的销量排名?
ZUNIONSTORE mobile-001:003 mobile-001 mobile-002 mobile-003 # 可否?
# 正确
1、ZADD mobile-003 5500 'huawei' 4660 'oppo' 3580 'iphone'
2、ZUNIONSTORE mobile-001:003 mobile-001 mobile-002 mobile-003 AGGREGATE MAX


python代码实现

import redis

r = redis.Redis(host='192.168.43.49',port=6379,password='123456',db=0)

# 第1天
day01_dict = {
    'huawei' : 5000,
    'oppo'   : 4000,
    'iphone' : 3000
}
# 第2天
day02_dict = {
    'huawei' : 5200,
    'oppo'   : 4300,
    'iphone' : 3230
}
# 第3天
day03_dict = {
    'huawei' : 5500,
    'oppo'   : 4660,
    'iphone' : 3580
}
r.zadd('mobile-day01',day01_dict)
r.zadd('mobile-day02',day02_dict)
r.zadd('mobile-day03',day03_dict)

r.zunionstore('mobile-day01:03',('mobile-day01','mobile-day02','mobile-day03'),aggregate='max')
rlist = r.zrevrange('mobile-day01:03',0,-1,withscores=True)

i = 1
for r in rlist:
    print('第{}名:{}'.format(i,r[0].decode()) )


redis_day02回顾

五大数据类型

1、字符串类型(string)
2、列表类型(list3、哈希类型(hash4、集合类型(set5、有序集合类型(sorted set

位图操作(bitmap)

# 应用场景
1、可以实时的进行数据统计(网站用户的上线次数统计)
# 常用命令
1、setbit key offset value
2、BITCOUNT key

哈希(散列)类型

# 应用场景
1、很适合存储对象类型,比如说用户ID作为key,用户的所有属性及值作为key对应的value
(用户维度统计-各种数据统计-发帖数、粉丝数等)
# 常用命令
HSET key field value
HSETNX key field value
HMSET key field value field value

HGET key field
HMGET key field filed
HGETALL key
HKEYS key
HVALS key

HLEN key
HEXISTS key field

HINCRBY key filed increment
HINCRBYFLOAT key field increment

HDEL key field 

集合类型

# 应用场景
1、共同关注、共同好友
# 常用命令

SADD key member1 member2

SMEMBERS key
SCARD key

SREM key member1 member2
SRANDOMMEMBER key [count]

SISMEMBER key member

SDIFF key1 key2 
SDIFFSTORE destination key1 key2

SINTER key1 key2
SINTERSTORE destination key1 key2

SUNION key1 key2
SUNIONSTORE destination key1 key2

有序集合

# 应用场景
1、各种排行榜
   1、游戏:列出前100名高分选手
   2、列出某用户当前的全球排名
   3、各种日排行榜、周排行榜、月排行榜
# 常用命令
zadd key score member

ZRANGE key start stop [withscores]
ZREVRANGE key start stop [withscores]
ZRANGEBYSCORE key min max [withscores] [limit offset count]
ZSCORE key member
ZCOUNT key min max
ZCARD key

ZRANK key member
ZREVRANK key member

ZINCRBY key increment member

ZREM key member
ZREMRANGEBYSCORE key min max

zunionstore destination numkeys key [weights 权重值] [AGGREGATE SUM|MIN|MAX]
ZINTERSTORE destination numkeys key1 key2 WEIGHTS weight AGGREGATE SUM|MIN|MAX

redis_day03笔记

有序集合sortedset

有序集合的交集与并集

# 交集(weights代表权重值,aggregate代表聚合方式 - 先计算权重值,然后再聚合)
ZINTERSTORE destination numkeys key1 key2 WEIGHTS weight AGGREGATE SUM|MIN|MAX
# 并集(weights代表权重值,aggregate代表聚合方式 - 先计算权重值,然后再聚合)
ZUNIONSTORE destination numkeys key [weights 权重值] [AGGREGATE SUM|MIN|MAX]

案例1:网易音乐排行榜

1、每首歌的歌名作为元素(先不考虑重复)
2、每首歌的播放次数作为分值
3、使用ZREVRANGE来获取播放次数最多的歌曲

代码实现

import redis

r = redis.Redis(host='192.168.43.49',port=6379,password='123456',db=0)

r.zadd('ranking',{'song1':1,'song2':1,'song3':1,'song4':1})
r.zadd('ranking',{'song5':1,'song6':1,'song7':1})
r.zadd('ranking',{'song8':1,'song9':1})

r.zincrby('ranking',50,'song3')
r.zincrby('ranking',60,'song5')
r.zincrby('ranking',80,'song7')
# 获取前10名
rlist = r.zrevrange('ranking',0,2,withscores=True)

i = 1
for r in rlist:
    print('第%d名:%s' % (i,r[0].decode()))
    i += 1

案例2: 京东商品畅销榜

# 第1天
ZADD mobile-001 5000 'huawei' 4000 'oppo' 3000 'iphone'
# 第2天
ZADD mobile-002 5200 'huawei' 4300 'oppo' 3230 'iphone'
# 第3天
ZADD mobile-003 5500 'huawei' 4660 'oppo' 3580 'iphone'
问题:如何获取三款手机的销量排名?
ZUNIONSTORE mobile-001:003 mobile-001 mobile-002 mobile-003 # 可否?
# 正确
1、ZADD mobile-003 5500 'huawei' 4660 'oppo' 3580 'iphone'
2、ZUNIONSTORE mobile-001:003 mobile-001 mobile-002 mobile-003 AGGREGATE MAX


python代码实现

import redis

r = redis.Redis(host='192.168.43.49',port=6379,password='123456',db=0)

# 第1天
day01_dict = {
    'huawei' : 5000,
    'oppo'   : 4000,
    'iphone' : 3000
}
# 第2天
day02_dict = {
    'huawei' : 5200,
    'oppo'   : 4300,
    'iphone' : 3230
}
# 第3天
day03_dict = {
    'huawei' : 5500,
    'oppo'   : 4660,
    'iphone' : 3580
}
r.zadd('mobile-day01',day01_dict)
r.zadd('mobile-day02',day02_dict)
r.zadd('mobile-day03',day03_dict)

r.zunionstore('mobile-day01:03',('mobile-day01','mobile-day02','mobile-day03'),aggregate='max')
rlist = r.zrevrange('mobile-day01:03',0,-1,withscores=True)

i = 1
for r in rlist:
    print('第{}名:{}'.format(i,r[0].decode()) )


数据持久化

持久化定义

将数据从掉电易失的内存放到永久存储的设备上


为什么需要持久化

因为所有的数据都在内存上,所以必须得持久化


  • 数据持久化分类之 - RDB模式(默认开启)

默认模式

1、保存真实的数据
2、将服务器包含的所有数据库数据以二进制文件的形式保存到硬盘里面
3、默认文件名 :/var/lib/redis/dump.rdb


创建rdb文件的两种方式

**方式一:**服务器执行客户端发送的SAVE或者BGSAVE命令

127.0.0.1:6379> SAVE
OK
# 特点
1、执行SAVE命令过程中,redis服务器将被阻塞,无法处理客户端发送的命令请求,在SAVE命令执行完毕后,服务器才会重新开始处理客户端发送的命令请求
2、如果RDB文件已经存在,那么服务器将自动使用新的RDB文件代替旧的RDB文件
# 工作中定时持久化保存一个文件

127.0.0.1:6379> BGSAVE
Background saving started
# 执行过程如下
1、客户端 发送 BGSAVE 给服务器
2、服务器马上返回 Background saving started 给客户端
3、服务器 fork() 子进程做这件事情
4、服务器继续提供服务
5、子进程创建完RDB文件后再告知Redis服务器

# 配置文件相关操作
/etc/redis/redis.conf
dir /var/lib/redis   # 表示rdb文件存放路径
dbfilename dump.rdb  # 文件名

# 两个命令比较
SAVE比BGSAVE快,因为需要创建子进程,消耗额外的内存

# 补充:可以通过查看日志文件来查看redis都做了哪些操作
# 日志文件:配置文件中搜索 logfile
logfile /var/log/redis/redis-server.log


方式二:设置配置文件条件满足时自动保存(使用最多)

# 命令行示例
redis>save 300 10
  表示如果距离上一次创建RDB文件已经过去了300秒,并且服务器的所有数据库总共已经发生了不少于10次修改,那么执行BGSAVE命令
redis>save 60 10000
  表示'如果距离上一次创建rdb文件已经过去60秒,并且服务器所有数据库总共已经发生了不少于10000次修改,那么执行bgsave命令'

# redis配置文件默认
save 900 1
save 300 10
save 60 10000
  1、只要三个条件中的任意一个被满足时,服务器就会自动执行BGSAVE
  2、每次创建RDB文件之后,服务器为实现自动持久化而设置的时间计数器和次数计数器就会被清零,并重新开始计数,所以多个保存条件的效果不会叠加


  • 数据持久化分类之 - AOF(AppendOnlyFile,默认未开启)

特点

1、存储的是命令,而不是真实数据
2、默认不开启
# 开启方式(修改配置文件)
1/etc/redis/redis.conf
  appendonly yes # 把 no 改为 yes
  appendfilename "appendonly.aof"
2、重启服务
  sudo /etc/init.d/redis-server restart


RDB缺点

1、创建RDB文件需要将服务器所有的数据库的数据都保存起来,这是一个非常消耗资源和时间的操作,所以服务器需要隔一段时间才创建一个新的RDB文件,也就是说,创建RDB文件不能执行的过于频繁,否则会严重影响服务器的性能
2、可能丢失数据


AOF持久化原理及优点

# 原理
   1、每当有修改数据库的命令被执行时,服务器就会将执行的命令写入到AOF文件的末尾
   2、因为AOF文件里面存储了服务器执行过的所有数据库修改的命令,所以给定一个AOF文件,服务器只要重新执行一遍AOF文件里面包含的所有命令,就可以达到还原数据库的目的

# 优点
  用户可以根据自己的需要对AOF持久化进行调整,让Redis在遭遇意外停机时不丢失任何数据,或者只丢失一秒钟的数据,这比RDB持久化丢失的数据要少的多


安全性问题考虑

# 因为
  虽然服务器执行一个修改数据库的命令,就会把执行的命令写入到AOF文件,但这并不意味着AOF文件持久化不会丢失任何数据,在目前常见的操作系统中,执行系统调用write函数,将一些内容写入到某个文件里面时,为了提高效率,系统通常不会直接将内容写入硬盘里面,而是将内容放入一个内存缓存区(buffer)里面,等到缓冲区被填满时才将存储在缓冲区里面的内容真正写入到硬盘里

# 所以
  1、AOF持久化:当一条命令真正的被写入到硬盘里面时,这条命令才不会因为停机而意外丢失
  2、AOF持久化在遭遇停机时丢失命令的数量,取决于命令被写入到硬盘的时间
  3、越早将命令写入到硬盘,发生意外停机时丢失的数据就越少,反之亦然


策略 - 配置文件

# 打开配置文件:/etc/redis/redis.conf,找到相关策略如下
1、alwarys
   服务器每写入一条命令,就将缓冲区里面的命令写入到硬盘里面,服务器就算意外停机,也不会丢失任何已经成功执行的命令数据
2、everysec(# 默认)
   服务器每一秒将缓冲区里面的命令写入到硬盘里面,这种模式下,服务器即使遭遇意外停机,最多只丢失1秒的数据
3、no
   服务器不主动将命令写入硬盘,由操作系统决定何时将缓冲区里面的命令写入到硬盘里面,丢失命令数量不确定

# 运行速度比较
always:速度慢
everysec和no都很快,默认值为everysec


AOF文件中是否会产生很多的冗余命令?

为了让AOF文件的大小控制在合理范围,避免胡乱总长,redis提供了AOF重写功能,通过这个功能,服务器可以产生一个新的AOF文件
  -- 新的AOF文件记录的数据库数据和原由的AOF文件记录的数据库数据完全一样
  -- 新的AOF文件会使用尽可能少的命令来记录数据库数据,因此新的AOF文件的提及通常会小很多
  -- AOF重写期间,服务器不会被阻塞,可以正常处理客户端发送的命令请求


示例

原有AOF文件重写后的AOF文件
select 0SELECT 0
sadd myset peiqiSADD myset peiqi qiaozhi danni
sadd myset qiaozhiSET msg ‘hello tarena’
sadd myset danniRPUSH mylist 2 3 5
sadd myset lingyang
INCR number
INCR number
DEL number
SET message ‘hello world’
SET message ‘hello tarena’
RPUSH mylist 1 2 3
RPUSH mylist 5
LPOP mylist

AOF文件重写方法触发

1、客户端向服务器发送BGREWRITEAOF命令
   127.0.0.1:6379> BGREWRITEAOF
   Background append only file rewriting started

2、修改配置文件让服务器自动执行BGREWRITEAOF命令
  auto-aof-rewrite-percentage 100
  auto-aof-rewrite-min-size 64mb
  # 解释
    1、只有当AOF文件的增量大于100%时才进行重写,也就是大一倍的时候才触发
        # 第一次重写新增:64M
        # 第二次重写新增:128M
        # 第三次重写新增:256M(新增128M)


RDB和AOF持久化对比

RDB持久化AOF持久化
全量备份,一次保存整个数据库增量备份,一次保存一个修改数据库的命令
保存的间隔较长保存的间隔默认为一秒钟
数据还原速度快数据还原速度一般,冗余命令多,还原速度慢
执行SAVE命令时会阻塞服务器,但手动或者自动触发的BGSAVE不会阻塞服务器无论是平时还是进行AOF重写时,都不会阻塞服务器
更适合数据备份更适合用来保存数据,通常意义上的数据持久化,在appendfsync always模式下运行时
# 用redis用来存储真正数据,每一条都不能丢失,都要用always,有的做缓存,有的保存真数据,我可以开多个redis服务,不同业务使用不同的持久化,新浪每个服务器上有4个redis服务,整个业务中有上千个redis服务,分不同的业务,每个持久化的级别都是不一样的。


Redis主从复制

  • 定义
1、一个Redis服务可以有多个该服务的复制品,这个Redis服务成为master,其他复制品成为slaves
2、网络正常,master会一直将自己的数据更新同步给slaves,保持主从同步
3、只有master可以执行写命令,slave只能执行读命令


  • 作用
分担了读的压力(高并发)


  • 原理
从服务器执行客户端发送的读命令,比如GET、LRANGE、SMEMMBERS、HGET、ZRANGE等等,客户端可以连接slaves执行读请求,来降低master的读压力


  • 两种实现方式

    方式一(命令行实现1)

    redis-server --slaveof

# 从服务端
redis-server --port 6300 --slaveof 127.0.0.1 6379
# 从客户端
redis-cli -p 6300
127.0.0.1:6300> keys * 
# 发现是复制了原6379端口的redis中数据
127.0.0.1:6380> set mykey 123
(error) READONLY You can't write against a read only slave.
127.0.0.1:6380> 
# 从服务器只能读数据,不能写数据


方式一(命令行实现2)

# 服务端启动
redis-server --port 6380
# 客户端连接
tarena@tedu:~$ redis-cli -p 6300
127.0.0.1:6300> keys *
1) "myset"
2) "mylist"
127.0.0.1:6300> set mykey 123
OK
# 切换为从
127.0.0.1:6300> slaveof 127.0.0.1 6379
OK
127.0.0.1:6300> set newkey 456
(error) READONLY You can't write against a read only slave.
127.0.0.1:6300> keys *
1) "myset"
2) "mylist"
# 再切换为主
127.0.0.1:6300> slaveof no one
OK
127.0.0.1:6300> set name hello
OK


方式二(修改配置文件)

# 修改配置文件
vi redis_6300.conf
slaveof 127.0.0.1 6379
port 6300
# 启动redis服务
redis-server redis_6300.conf
# 客户端连接测试
redis-cli -p 6300
127.0.0.1:6300> hset user_001 username guods
(error) READONLY You can't write against a read only slave.


问题总结

1、一个Master可以有多个Slaves
2、Slave下线,只是读请求的处理性能下降
3、Master下线,写请求无法执行
4、其中一台Slave使用SLAVEOF no one命令成为Master,其他Slaves执行SLAVEOF命令指向这个新的Master,从它这里同步数据
# 以上过程是手动的,能够实现自动,这就需要Sentine哨兵,实现故障转移Failover操作


演示

1、启动端口6400redis,设置为6379的slave
   redis-server --port 6400
   redis-cli -p 6400
   redis>slaveof 127.0.0.1 6379
2、启动端口6401redis,设置为6379的slave
   redis-server --port 6401
   redis-cli -p 6401
   redis>slaveof 127.0.0.1 6379
3、关闭6379redis
   sudo /etc/init.d/redis-server stop
4、把6400redis设置为master
   redis-cli -p 6401
   redis>slaveof no one
5、把6401的redis设置为6400redis的salve
   redis-cli -p 6401
   redis>slaveof 127.0.0.1 6400
# 这是手动操作,效率低,而且需要时间,有没有自动的???


官方高可用方案Sentinel

Redis之哨兵 - sentinel

1、Sentinel会不断检查Master和Slaves是否正常
2、每一个Sentinel可以监控任意多个Master和该Master下的Slaves


案例演示

​ **1、**环境搭建

# 共3台redis的服务器,如果是不同机器端口号可以是一样的
1、启动6379的redis服务器
   	sudo /etc/init.d/redis-server start
2、启动6380的redis服务器,设置为6379的从
    redis-server --port 6380
    tarena@tedu:~$ redis-cli -p 6380
    127.0.0.1:6380> slaveof 127.0.0.1 6379
    OK
3、启动6381的redis服务器,设置为6379的从
   	redis-server --port 6381
   	tarena@tedu:~$ redis-cli -p 6381
   	127.0.0.1:6381> slaveof 127.0.0.1 6379


​ **2、**安装并搭建sentinel哨兵

# 1、安装redis-sentinel
sudo apt install redis-sentinel

# 2、新建配置文件sentinel.conf
port 26379
Sentinel monitor tedu 127.0.0.1 6379 1

# 3、启动sentinel
方式一: redis-sentinel sentinel.conf
方式二: redis-server sentinel.conf --sentinel

# 将master的redis服务终止,查看从是否会提升为主
sudo /etc/init.d/redis-server stop
# 发现提升6381为master,其他两个为从
# 在6381上设置新值,6380查看
127.0.0.1:6381> set name tedu
OK

# 启动6379,观察日志,发现变为了6381的从
主从+哨兵基本就够用了


sentinel.conf解释

# sentinel监听端口,默认是26379,可以修改
port 26379
# 告诉sentinel去监听地址为ip:port的一个master,这里的master-name可以自定义,quorum是一个数字,指明当有多少个sentinel认为一个master失效时,master才算真正失效
sentinel monitor <master-name> <ip> <redis-port> <quorum>


博客项目解决高并发问题

1、在数据库中创建库 blog_server,指定字符编码utf8

mysql -uroot -p123456
mysql>create database blog_server charset utf8;


2、同步数据库,并在user_profile中插入表记录

1、python3 manage.py makemigrations
2、python3 manage.py migrate
3、insert into user_profile values ('guoxiaonao','guoxiaonao','guoxiaonao@tedu.cn','123456','aaaaaaaa','bbbbbbbb','cccccccc');


3、启动django项目,并找到django路由测试 test_api函数

1、python3 manage.py runserver
2、查看项目的 urls.py 路由,打开firefox浏览器输入地址:http://127.0.0.1:8000/test_api
# 返回结果:	code	200


4、在数据库表中创建测试字段score

1、user/models.py添加:
   score = models.IntegerField(verbose_name=u'分数',null=True,default=0)
2、同步到数据库
   python3 manage.py makemigrations user
   python3 manage.py migrate user
3、到数据库中确认查看


3、在blog_server/views.py中补充 test_api 函数,对数据库中score字段进行 +1 操作

from user.models import UserProfile
def test_api(request):
    #JsonResponse 1,将返回内容序列化成json
    #2,response中添加 content-type: application/json
    # return JsonResponse({'code':200})

    u = UserProfile.objects.get(username='guoxiaonao')
    u.score += 1
    u.save()

    return JsonResponse({'msg': 'test is ok'})


4、启多个服务端,模拟30个并发请求

(1)在tools中新建py文件 test_api.py,模拟30个并发请求

import threading
import requests
import random


def getRequest():
    url='http://127.0.0.1:8000/test_api'
    url2='http://127.0.0.1:8001/test_api'
    get_url = random.choice([url, url2])
    requests.get(get_url)

ts = []
for i in range(30):

    t=threading.Thread(target=getRequest,args=())
    ts.append(t)

if __name__ == '__main__':

    for t in ts:
        t.start()

    for t in ts:
        t.join()


(2) python3 test_api.py

(3) 在数据库中查看 score 字段的值

并没有+30,而且没有规律,每次加的次数都不同,如何解决???


解决方案:redis分布式锁

def test_api(request):
	# 解决方法二:redis分布式锁
    import redis
    pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
    r = redis.Redis(connection_pool=pool)
    try:
        with r.lock('guoxiaonao', blocking_timeout=3) as lock:
            u = UserProfile.objects.get(username='guoxiaonao')
            u.score += 1
            u.save()
    except Exception as e:
        print('lock is failed')


day04笔记

Redis事务

特点

1. 单独的隔离操作:事务中的所有命令会被序列化、按顺序执行,在执行的过程中不会被其他客户端发送来的命令打断
2. 不保证原子性:redis中的一个事务中如果存在命令执行失败,那么其他命令依然会被执行,没有回滚机制

事务命令

1、MULTI  # 开启事务
2、命令1  # 执行命令
3、命令2 ... ...
4、EXEC  # 提交到数据库执行
4、DISCARD # 取消事务

使用步骤

# 开启事务
127.0.0.1:6379> MULTI
OK
# 命令1入队列
127.0.0.1:6379> INCR n1
QUEUED
# 命令2入队列
127.0.0.1:6379> INCR n2
QUEUED
# 提交到数据库执行
127.0.0.1:6379> EXEC
1) (integer) 1
2) (integer) 1

事务中命令错误处理

# 1、命令语法错误,命令入队失败,直接自动discard退出这个事务
  这个命令在执行调用之前会发生错误。例如,这个命令可能有语法错误(错误的参数数量,错误的命令名),或者其他某些紧急的状态,如内存溢出
  处理方案:客户端发生了第一个错误情况,在exec执行之前发生的。通过检查队列命令返回值:如果这个命令回答这个队列的命令是正确的,否者redis会返回一个错误。如果那里发生了一个队列命令错误,大部分客户端将会退出并丢弃这个事务

# 2、命令语法没错,但类型操作有误,则事务执行调用之后失败,无法进行事务回滚
   从我们施行了一个由于错误的value的key操作(例如对着String类型的value施行了List命令操作)
   处理方案:发生在EXEC之后的是没有特殊方式去处理的:即使某些命令在事务中失败,所有的其他命令都将会被执行。
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set num 10
QUEUED
127.0.0.1:6379> LPOP num
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get num
"10"
127.0.0.1:6379> 

为什么redis不支持事务回滚

  • 观点
1、Redis的内部极其简单和快速,来源于它不需要回滚功能
2、在生产环境中,通常回滚并不能解决来自编程的错误。举个例子,你本来想+1,却+2了,又或者+在错误的类型上。回滚并不能解决。由于无法提供一个避免程序员自己的错误,而这种错误在产品中并不会出现,所以选择一个简单和快速的方法去支持回滚这个事务

pipeline补充

python使用pipeline()与execute()批量进行批量操作

作用:减少数据库的IO来提升数据库的性能

pipe = r.pipeline()
pipe.REDIS命令
pipe.REDIS命令
pipe.execute()

示例

import redis

# 创建连接池并连接到redis
pool = redis.ConnectionPool(host = '192.168.153.130',db=0,port=6379)
r = redis.Redis(connection_pool=pool)

# 第一组
pipe = r.pipeline()
pipe.set('fans',50)
pipe.incr('fans')
pipe.incrby('fans',100)
pipe.execute()

# 第二组
pipe.get('fans')
pipe.get('pwd')
# [b'151', b'123']
result = pipe.execute()
print(result)

Redis常见问题汇总

  • Redis优点
1、读写速度快. 数据存放在内存中
2、支持数据类型丰富,string,hash,list,set,sorted
3、支持事务,watch
4、可以用于缓存,消息队列,按key设置过期时间,到期后自动删除
5、支持数据持久化(将内存数据持久化到磁盘),支持AOF和RDB两种持久化方式,从而进行数据恢复操作,可以有效地防止数据丢失
5、支持主从(master-slave)复制来实现数据备份,主机会自动将数据同步到从机

  • 来介绍一下redis中的数据类型

    类型特点使用场景
    string简单key-value类型,value可为字符串和数字常规计数(微博数, 粉丝数等功能)
    hash是一个string类型的field和value的映射表,hash特别适合用于存储对象存储部分可能需要变更的数据(比如用户信息)
    list有序可重复列表关注列表,粉丝列表,消息队列等
    set无序不可重复列表存储并计算关系(如微博,关注人或粉丝存放在集合,可通过交集、并集、差集等操作实现如共同关注、共同喜好等功能)
    sorted set每个元素带有分值的集合各种排行榜
  • redis中的持久化方案

# RDB
快照形式,定期把内存中的数据保存到磁盘。Redis默认支持的持久化方案。速度快但是服务器断电的时候会丢失部分数据

# AOF
把所有对redis数据库增删改操作的命令保存到文件中。数据库恢复时把所有的命令执行一遍即可。
# 两种持久化方案同时开启使用AOF文件来恢复数据库.能保证数据的完整性,但是速度慢。默认使用AOF恢复数据


  • 使用过Redis分布式锁么,它是什么回事?

    从redis2.8开始,set命令集成了两个参数,nx和ex,先拿nx来争抢锁,抢到之后,再用ex参数给锁加一个过期时间防止锁忘记了释放,造成死锁
    应用场景:秒杀活动
        进程1:set miaosha aaa nx ex 3 获取锁
            秒杀记录 -1
            del miaosha
        进程2:set miaosha 111 nx ex 3 无法获取锁
    
    # 2.8以前是两条命令,这样容易造成死锁
    # setnx key value # 获取锁
    # expier key 5 # 释放锁
    
    # 解决方法:redis分布式锁
        import redis
    
        pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
        r = redis.Redis(connection_pool=pool)
        try:
            with r.lock('guoxiaonao', blocking_timeout=3) as lock:
                u = UserProfile.objects.get(username='yanxu')
                u.score += 1
                u.save()
        except Exception as e:
            print('lock is failed')
        return JsonResponse({"msg": 'test is ok'})
    
    
    
  • 缓存穿透

# 原理
缓存和数据库都没有的数据,而用户反复发起请求, 如 假的用户ID

# 场景
比如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大

# 解决方案:
   1、请求校验,接口层增加校验,如对id做基础校验,id<=0的直接拦截
   2、都无法取到数据时也可以将key-value对写为key-null,缓存有效时间比如30秒左右,这样可以防止攻击用户反复用同一个id暴力攻击


  • 缓存击穿

    # 原理
    缓存没有,数据库有,一般是缓存时间到期, 顺势并发太大
    
    #解决方案
    1、热点数据不过期  
    2、上锁: 重新设计缓存的使用方式,当我们通过key去查询数据时,首先查询缓存,如果没有,就通过分布式锁进行加锁,取得锁的进程查DB并设置缓存,然后解锁;其他进程如果发现有锁就等待,然后等解锁后返回缓存数据或者再次查询DB
    
    
    
  • 缓存雪崩

    # 原理
    缓存中大批量数据过期,导致瞬时大批量不同请求注入DB
    
    # 解决方案
    解决方案
    1、缓存设置随机时间(避免缓存设置相近的有效期;为有效期增加随机值)
    2、热点数据不过期
    
    
    
  • 哈希碰撞

    # 在位置处发生哈希碰撞
    name --> 哈希处理 --> 哈希值 --> 哈希函数算值找位置(比如是30)
    age --> 哈希处理 --> 哈希值 --> 哈希函数算值如果算出来的也是30  则发生了哈希碰撞
    
    
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值