66.4-Redis服务和字符串类型

人生最大的幸福就是健康地生活着!

总结:

  1. redis最关注的问题: 是你要用python来解决数据存储(如何选择数据存储的类型)的问题;内存如何合理使用的问题;
  2. redis的数据安全指的是:将所有的二进制数据转成10进制后,在转成字符串;
  3. redis的本质是key-value对, 只不过value 可以为:str/Dict(hash 字典V 套字典)/set(集合),所以他的变化指的是value的类型变化; 他就是内存结构的典型的嵌套结构的一个复杂的K-V对;
  4. 任何 flashdb都是危险操作;
  5. 介绍 Redis 的对象系统中的字符串对象(String)、列表对象(List)、哈希对象(Hash)、集合对象(Set)和有序集合对象(ZSet)
  6. Redis 的list 可以当成缓冲队列 来使用的;业务场景:

参考:

  1. Redis数据结构详解,五种数据结构分分钟掌握
  2. 十二张图详解Redis的数据结构和对象系统
  3. 万字长文的Redis五种数据结构详解(理论+实战),建议收藏

Redis

官方网站:http://www.radis.io
中文网站:http://www.redis.cn
开源的(BSD协议),使用ANSI C 编写,基于内存的且支持持久化,高性能的Key-Value的NoSQL数据库。

支持数据结构类型丰富,有如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。
丰富的支持主流语言的客户端,C、C++、Python、Erlang、R、C#、Java、PHP、Objective-C、Perl、Ruby、Scala、Go、JavaScript。
用途:缓存(StackOverFlow)、数据库(微博)、消息中间件(微博)

Redis版本
目前主要版本为3.2
redis在2017年也发布了4.x版本,目前是4.0.x
部署环境是Linux,本次部署在CentOS 6.x上。

Windows版本由微软提供 https://github.com/MicrosoftArchive/redis
可视化工具RedisDesktopManager。windows目前使用redis-desktop-manager-0.8.x,0.9有点问题。

1. Redis安装

Linux单节点安装
# yum -y install gcc tcl
# tar xf redis-3.2.12.tar.gz
# mv redis-3.2.12/ redis
# cd redis
# make
------------------------------------编译安装成功;
   LINK redis-server            # 最常用的3个文件 ; 
    INSTALL redis-sentinel
    CC redis-cli.o
Hint: It's a good idea to run 'make test' ;)
make[1]: Leaving directory `/root/redis-3.2.12/src'

如果出错,需要redis目录下
# cd deps
# make jemalloc
# make lua
# make linenoise
# make hiredis
# cd ..

缺省安装
# make install
默认安装到/usr/local/bin

自定义安装
# mkdir -p /magedu/redis
# make PREFIX=/magedu/redis install

可执行文件

cd src
redis-benchmark
redis-check-aof
redis-check-dump
redis-cli # 客户端
redis-server

[root@Centos7 src]# mkdir /magedu/redis -p   # 创建目录

[root@Centos7 redis-3.2.12]# make PREFIX=/magedu/redis/ install   # 拷贝文件;
cd src && make install
make[1]: Entering directory `/root/redis-3.2.12/src'

Hint: It's a good idea to run 'make test' ;)

    INSTALL install
    INSTALL install
    INSTALL install
    INSTALL install
    INSTALL install
make[1]: Leaving directory `/root/redis-3.2.12/src'

[root@Centos7 src]# cd /magedu/redis/
[root@Centos7 redis]# ls
bin
[root@Centos7 redis]# cd bin/
[root@Centos7 bin]# ll
total 15080
-rwxr-xr-x. 1 root root 2433432 Aug 13 17:10 redis-benchmark
-rwxr-xr-x. 1 root root   24992 Aug 13 17:10 redis-check-aof
-rwxr-xr-x. 1 root root 5191752 Aug 13 17:10 redis-check-rdb
-rwxr-xr-x. 1 root root 2586480 Aug 13 17:10 redis-cli
lrwxrwxrwx. 1 root root      12 Aug 13 17:10 redis-sentinel -> redis-server
-rwxr-xr-x. 1 root root 5191752 Aug 13 17:10 redis-server


# 把redis变成一个服务;
[root@Centos7 utils]# ./install_server.sh
Welcome to the redis service installer
This script will help you easily set up a running redis server

Please select the redis port for this instance: [6379]
Selecting default: 6379
Please select the redis config file name [/etc/redis/6379.conf]
Selected default - /etc/redis/6379.conf
Please select the redis log file name [/var/log/redis_6379.log]
Selected default - /var/log/redis_6379.log
Please select the data directory for this instance [/var/lib/redis/6379]
Selected default - /var/lib/redis/6379
Please select the redis executable path []

环境变量
可以将下面的变量追加入到~/.bash_profile文件末尾
export REDIS_HOME=/magedu/redis
export PATH=PATH:REDIS_HOME/bin

 配置PATH路径
[root@Centos7 bin]# nano ~/.bash_profile

# .bash_profile

# Get the aliases and functions
if [ -f ~/.bashrc ]; then
        . ~/.bashrc
fi

# User specific environment and startup programs

PATH=$PATH:$HOME/bin

export PATH
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"

export REDIS_HOME=/magedu/redis
export PATH=$PATH:$REDIS_HOME/bin

[root@Centos7 bin]# source ~/.bash_profile         # 刷新
[root@Centos7 bin]# ls
redis-benchmark  redis-check-aof  redis-check-rdb  redis-cli  redis-sentinel  redis-server
[root@Centos7 bin]# echo $REDIS_HOME            # 配置路径成功;
/magedu/redis

# 服务
[root@Centos7 ~]# redis-3.2.12/utils/install_server.sh
Welcome to the redis service installer
This script will help you easily set up a running redis server

Please select the redis port for this instance: [6379]
Selecting default: 6379
Please select the redis config file name [/etc/redis/6379.conf]
Selected default - /etc/redis/6379.conf
Please select the redis log file name [/var/log/redis_6379.log]
Selected default - /var/log/redis_6379.log
Please select the data directory for this instance [/var/lib/redis/6379]
Selected default - /var/lib/redis/6379
Please select the redis executable path [] /magedu/redis/bin/redis-server    # 路径要正确
Selected config:
Port           : 6379
Config file    : /etc/redis/6379.conf
Log file       : /var/log/redis_6379.log
Data dir       : /var/lib/redis/6379
Executable     : /magedu/redis/bin/redis-server
Cli Executable : /magedu/redis/bin/redis-cli
Is this ok? Then press ENTER to go on or Ctrl-C to abort.
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service...
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server...
Installation successful!

[root@Centos7 bin]# cd /etc/init.d
[root@Centos7 init.d]# ll
total 48
-rw-r--r--. 1 root root 18281 Aug 19  2019 functions
-rwxr-xr-x. 1 root root  4569 Aug 19  2019 netconsole
-rwxr-xr-x. 1 root root  7928 Aug 19  2019 network
-rwxr-xr-x. 1 root root  4014 Nov 30  2017 rabbitmq-server
-rw-r--r--. 1 root root  1160 Jun 30 23:11 README
-rwxr-xr-x. 1 root root  1708 Aug 13 17:14 redis_6379
[root@Centos7 init.d]# mv redis_6379 redisd           # 改一下服务名称;

redis服务

[root@Centos7 init.d]# chkconfig

Note: This output shows SysV services only and does not include native
      systemd services. SysV configuration data might be overridden by native
      systemd configuration.

      If you want to list systemd services use 'systemctl list-unit-files'.
      To see services enabled on particular target use
      'systemctl list-dependencies [target]'.

netconsole      0:off   1:off   2:off   3:off   4:off   5:off   6:off
network         0:off   1:off   2:on    3:on    4:on    5:on    6:off
rabbitmq-server 0:off   1:off   2:on    3:on    4:on    5:on    6:off
[root@Centos7 init.d]# chkconfig redisd on           # 开机启动 redis 2345
[root@Centos7 init.d]# chkconfig

Note: This output shows SysV services only and does not include native
      systemd services. SysV configuration data might be overridden by native
      systemd configuration.

      If you want to list systemd services use 'systemctl list-unit-files'.
      To see services enabled on particular target use
      'systemctl list-dependencies [target]'.

netconsole      0:off   1:off   2:off   3:off   4:off   5:off   6:off
network         0:off   1:off   2:on    3:on    4:on    5:on    6:off
rabbitmq-server 0:off   1:off   2:on    3:on    4:on    5:on    6:off
redisd          0:off   1:off   2:on    3:on    4:on    5:on    6:off



# redis 配置文件
[root@Centos7 src]# nano /etc/redis/6379.conf

bind 192.168.0.100 127.0.0.1   
protected-mode yes
port 6379

tcp-keepalive 300          # TCP服务保存多长时间;
daemonize yes            # 以后台服务跑起来
databases 16            # 总共有16 个库(0-15)


# 修改配置文件后  重新 启动 redisd 
[root@Centos7 ~]# systemctl restart redisd.service
[root@Centos7 ~]# ss -tanl
LISTEN     0      128                                                                                      127.0.0.1:6379                                                                                                         *:*
LISTEN     0      128                                                                                  192.168.0.100:6379
Redis Windows安装

项目地址 https://github.com/MicrosoftArchive/redis
下载地址 https://github.com/MicrosoftArchive/redis/releases/download/win-3.2.100/Redis-x64-3.2.100.zip
解压缩,就可以用了。

2. Redis数据模型

redis支持数据模型非常丰富

2.1 键Key

Redis key 值是二进制安全的,这意味着可以用任何二进制序列作为key值,从形如”foo”的简单字符串到一个JPEG文件的内容都可以。空字符串也是有效key值
Key取值原则
键值不需要太长,消耗内存,而且查找这类键值的计算成本较高
键值不宜过短,可读性较差
习惯上key采用'user:123:password'形式,表示用户id为123的用户的密码

2.2字符串

字符串是一种最基本简单的Redis值类型Redis字符串是二进制安全的,这意味着一个Redis字符串能包含任意类型的数据,例如: 一张JPEG格式的图片或者一个序列化的Ruby对象。
一个字符串类型的值最多能存储512M字节的内容。

1. python中redis编程
安装redis库
$ pip install redis
import redis

db = redis.Redis('192.168.0.100') # 默认本地6379的0号库
print(db.keys('*')) # 查看所有匹配keys

db.set(0b01100001, 'a') # 0x61(16进制61) bin二进制=>十进制97=>str :a
print(db.keys('*'))
#------------------------------
[]
[b'97']

print(db.get(0b01100001))
print(db.get('97'))        # 用字符串 拿
#----------------------
b'a'
b'a'

# key的三种形式
db.set(0b11, 'abc')
print(db.get(0b11))
print(db.get(3))
print(db.get('3'))
#----------------------
b'abc'
b'abc'
b'abc'

注意:上例中0x62实际上发生了类型变化,因为返回的bytes类型98,实际上对应ASCII的98,已经是2字节了。

数值会先转换成10进制64位有符号数后,再转成字符串,存入redis中。

2. 查看帮助
> Help 查看帮助
> Help <tab> 使用tab键切换帮助
> Help set 查看set命令帮助
> Help @string 查看命令组帮助
3. 字符串设置
SET key value [EX seconds][PX milliseconds] [NX|XX]
设置字符串值
EX 设置过期时间,秒,等同于  SETEX key seconds value
PX 设置过期时间,毫秒,等同于  PSETEX key milliseconds value
NX 键不存在,才能设置,等同于  SETNX key value
XX 键存在时,才能设置

MSET key value [key value ...]
设置多个键的字符串值,key存在则覆盖,key不存在则增加
原子操作

MSETNX key value [key value ...]
key不存在则设置,key存在则失败。nx指不存在。
这个命令是原子操作(一个失败,操作全部失败)
4. 过期操作和生存时间

Redis中可以给每个Key设置一个生存时间(秒或毫秒),当达到这个时长后,这些键值将会被自动删除

EXPIRE key seconds
PEXPIRE key milliseconds
设置多少秒或者毫秒后过期

EXPIREAT key timestamp
PEXPIREAT key milliseconds-timestamp
设置在指定Unix时间戳过期

PERSIST key
持久key,即取消过期

Time To Live,Key的剩余生存时间
TTL key
PTTL key
key存在但没有设置TTL,返回-1 
key存在,但还在生存期内,返回剩余的秒或者毫秒
key曾经存在,但已经消亡,返回-2(2.8版本之前返回-1)
图片.png

适用场景
一、多少秒过期,例如一个缓存数据失效
二、PEXPIREAT key milliseconds-timestamp,比如现在开始缓存数据,到0点失效

set s5 abc ex 20
ttl s5

setnx s6 6
expire s6 60

pttl s6
persist s6
ttl s6

EXPIREAT cache 1355292000
PEXPIREAT mykey 1555555555005
5. key操作

keys pattern
pattern可以取如下值:
1. * 任意长度字符
2. ? 任意一个字符
3. [] 字符集合,表示一个字符

keys *
keys s?
keys s[13]
keys s*
keys ??
keys s[1-3]
TYPE key key类型
EXISTS key key是否存在
RENAME key newkey 、 RENAMENX key newkey 键重命名
DEL key [key ...] 键删除
6. 字符串获取
GET key 获取值
MGET key [key ...] 获取多个给定的键的值
GETSET key value 返回旧值并设置新值,如果键不存在,就创建并赋值
STRLEN key 字符串长度
get s4
mget s1 s3 s5 s7
strlen s3
mgetset s5 100
7. 字符串操作
APPEND key value 追加字符串。如果键存在就追加;如果不存在就等同于SET key value
获取子字符串
GETRANGE key start end 索引值从0开始,支持负索引,-1表示最后一个字符。范围是[start, end],start必须在end的左边,否则返回空串(前后都包括)
SETRANGE key offset value 从指定索引处开始覆盖字符串,返回覆盖后字符串长度。key不存在会创建新的
127.0.0.1:6379[1]> get s4
(nil)
127.0.0.1:6379[1]> APPEND s4 0123
(integer) 4
127.0.0.1:6379[1]> get s4
"0123"
127.0.0.1:6379[1]> setrange s4 2 a
(integer) 4
127.0.0.1:6379[1]> get s4
"01a3"
127.0.0.1:6379[1]> setrange s4 2 abcde
(integer) 7
127.0.0.1:6379[1]> get s4
"01abcde"
8. 自增、自减
INCR key 和 DECR key 步长1的增减
INCRBY key decrement 和 DECRBY key decrement 步长增减

字符串值会被解释成64位有符号的十进制整数来操作,结果依然转成字符串

127.0.0.1:6379[1]> set s3 10
OK
127.0.0.1:6379[1]> incr s3
(integer) 11
127.0.0.1:6379[1]> incrby s3 100
(integer) 111
127.0.0.1:6379[1]> DECRBY s3 200
(integer) -89
127.0.0.1:6379[1]>
2.3 库操作

任何 flashdb都是危险操作,一定有损失;

登录不同的库
redis-cli --help
redis-cli -n 2

清除当前库数据
FLUSHDB

清除所有库中的数据
FLUSHALL

2.4 位图bitmap

位图不是真正的数据类型,它是定义在字符串类型上,只不过把字符串按位操作
一个字符串类型的值最多能存储512M字节的内容,可以表示2^32位位上限

512=29
1M=1024*1024=210+10
1Byte=8bit=23bit
2(9+10+10+3) = 232 b = 4294967296 b ,接近43亿个位

SETBIT key offset value 设置某一位上的值
offset偏移量,从0开始;
value不写,默认是0
GETBIT key offset 获取某一位上的值
BITPOS key bit [start][end] 返回指定值0或者1在指定区间上第一次出现的位置
BITCOUNT key [start][end] 统计指定位区间上值为1的个数,从左向右从0开始,从右向左从-1开始,注意官方start、end是位,测试后是字节
BITCOUNT testkey 0 0 表示从索引为0个字节到索引为0个字节,就是第一个字节的统计
BITCOUNT testkey 0 -1 等同于 BITCOUNT testkey;最常用的就是 BITCOUNT testkey
# 记住 \x3  7 
127.0.0.1:6379[1]> set s4 7
OK
127.0.0.1:6379[1]> getbit s4 0
(integer) 0
127.0.0.1:6379[1]> getbit s4 1
(integer) 0
127.0.0.1:6379[1]> getbit s4 2
(integer) 1
127.0.0.1:6379[1]> getbit s4 3
(integer) 1
127.0.0.1:6379[1]> getbit s4 4
(integer) 0
127.0.0.1:6379[1]> getbit s4 5
(integer) 1
127.0.0.1:6379[1]> getbit s4 6
(integer) 1
127.0.0.1:6379[1]> getbit s4 7
(integer) 1

#------------------------------------
127.0.0.1:6379[1]> set str1 abc
OK
127.0.0.1:6379[1]> setbit str1 6 1
(integer) 0
127.0.0.1:6379[1]> setbit str1 7 0
(integer) 1
127.0.0.1:6379[1]> get str1
"bbc"
#------------------------------------
# 按照位统计的; 
127.0.0.1:6379[1]> set s4 77
OK
127.0.0.1:6379[1]> bitcount s4 0 0
(integer) 5
127.0.0.1:6379[1]> bitcount s4 1 1
(integer) 5
127.0.0.1:6379[1]> bitcount s4
(integer) 10
1. 位操作

BITOP命令用于对多个值(除NOT操作外)执行位运算操作,并将结果保存至指定的键值对中。BITOP命令将返回结果字符串的长度,其值等于输入中最长字符串的长度。

BITOP operation destkey key [key ...]

BITOP命令支持与(AND)、或(OR)、亦或(XOR)以及非(NOT)四个位运算操作,其使用方式为:

AND 与操作,使用方式为BITOP AND destkey srckey1 srckey2 ...
OR 或操作,使用方式为BITOP OR destkey srckey1 srckey2 ...
XOR 亦或操作,使用方式为BITOP XOR destkey srckey1 srckey2 ...
NOT 非操作,使用方式为BITOP NOT destkey srckey

当输入的字符串长度不同时,将使用0填充至与最长长度相同。若输入的键不存在则认定为一个空白字符串,并以0填充至与最长长度相同。

# 10101010
redis> SET key1 "\xaa"
OK
# 01010101
redis> SET key2 "\x55"
OK
# 11110000
redis> SET key3 "\xf0"
OK
# 01010101 01010101
redis> SET key4 "\x5555"
OK
与(AND)操作:

# 10101010 & 01010101 = 00000000
redis> BITOP AND result key1 key2
(integer) 1
redis> GET result
"\x00"
或(OR)操作:

# 10101010 | 01010101 = 11111111
redis> BITOP OR result key1 key2
(integer) 1
redis> GET result
"\xff"
亦或(XOR)操作:

# 10101010 ^ 11110000 = 01011010
redis> BITOP XOR result key1 key3
(integer) 1
# 字符Z二进制值为 01011010
redis> GET result
"Z"
或(OR)操作:

# !10101010 = 01010101
redis> BITOP NOT result key1
(integer) 1
# 字符U二进制值为 01010101
redis> GET result
"U"
不同长度的字符串进行位运算:

# key1的值将以0填充为 10101010 00000000
# 10101010 00000000 | 01010101 01010101 = 11111111 00000000
redis> BITOP OR result key1 key4
(integer) 2
redis> GET result
"\xffU"

思考:'a'位或'b'是什么?

set s1 ab
bitcount s1
bitcount s1 0 0
bitcount s1 1 1

set s2 a
set s3 b
bitop or s8 s2 s3 # 等于什么?

set cn 中
get cn
bitcount cn
2. 习题

1、网站用户的上线次数统计(活跃用户)
2、按天统计网站活跃用户

参考
1、网站用户的上线次数统计(活跃用户)
为每一个用户做上线记录,某天登录就标记一次。
用户ID为key,天作为offset,上线置为1
ID为500的用户,今年的第1天上线、第30天上线
SETBIT u:500 1 1
SETBIT u:500 30 1
BITCOUNT u:500
KYES u*

from redis import Redis

redis = Redis('192.168.0.100', db=2)

# user id 1
redis.setbit('u:1', 1, 1)
redis.setbit('u:1', 30, 1)

# user id 101
redis.setbit('u:101', 3, 1)
redis.setbit('u:1', 30, 1)

# user id 501
for i in range(3, 365, 3):
    redis.setbit('u:501', i, 1)

for i in range(2, 365, 2):
    redis.setbit('u:1000', i, 1)

active = []
inactive = []

users = redis.keys('u*')
print(users)

for user in users:
    count = redis.bitcount(user)
    if count > 100:
        active.append(user)
    else:
        inactive.append(user)

print('活跃用户为{},{}'.format(active, len(active)))
print('不活跃用户为{},{}'.format(inactive, len(inactive)))

2、按天统计网站活跃用户
天作为key,用户ID为offset,上线置为1
求一段时间内活跃用户数
SETBIT 20160602 15 1
SETBIT 20160601 123 1
SETBIT 20160606 123 1

求6月1日到6月10日的活跃用户
BITOP OR 20160601-10 20160601 20160602 20160603 20160610
BITCOUNT 20160601-10
结果为2

2.5 List列表

列表类型存储了一个有序的字符串列表。常用的操作是向两端插入新的元素。时间复杂度为O(1)。结构为一个链表。记录头和尾的地址。看到这里,Redis数据类型的列表类型一个重大的作用呼之欲出,那就是队列。新来的请求插入到尾部,新处理过的从头部删除。另外,比如微博的新鲜事。比如日志。列表类型就是一个下标从0开始的数组。由于是链表存储,那么越靠近头和尾的元素操作越快,越靠近中间则越慢。

  • 其列表是基于双向链表实现,列表头尾增删快,中间增删慢
  • 元素是字符串类型
  • 元素可以重复出现
  • 索引支持正索引和负索引,从左至右从0开始,从右至左从-1开始
    命令说明
字母 说明
B Block阻塞
L Left左起
R Right 右起
X exist 存在
LPUSH key value [value ...] 从左边向队列中压入元素
LPUSHX key value 从左边向队列加入元素,要求key必须存在
RPUSH key value [value ...] 从右边向队列中压入数据
RPUSHX key value 要求key存在
- -
LPOP key 从左边弹出列表中一个元素
RPOP key 从右边弹出列表中一个元素
- -
RPOPLPUSH source destination 从源列表中右边pop一个元素,从左边加入到目标列表
LRANGE key start stop 返回列表中指定访问的元素,例如LRANGE user 0 -1
- -
LINDEX key index 返回列表中指定索引的元素
LSET key index value 设置列表中指定索引位置的元素值,index不能超界
LREM key count value 从左边删除列表中与value相等的元素
count > 0 从左至右搜索,移除与 value 相等的元素,数量至多为 count 次
count < 0 从右至左搜索,移除与 value 相等的元素,数量至多为 -count次
count = 0 移除列表中所有value值
- -
LTRIM key start stop 去除指定范围外的元素
RPUSH listkey c abc c ab 123 ab bj ab redis list
LTRIM listkey 0 -1 # 什么都没有去除
LTRIM listkey 1 -1 # 去掉左边头
LTRIM listkey 1 10000
- -
LINSERT key BEFORE AFTER pivot value 在列表中某个存在的值(pivot)前或后插入元素一次,key或pivot不存在,不进行任何操作
RPUSH lst 1 2 3 4 2 8 
LINSERT lst AFTER  2 Python 
LINSERT lst BEFORE 2 Ruby

阻塞(可以作为队列使用)

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

BLPOP key [key ...] timeout 列表左边阻塞弹出元素。timeout是超时秒数,为0为永久阻塞。
BRPOP key [key ...] timeout 列表左边阻塞弹出元素
BRPOPLPUSH source destination timeout 从一个列表尾部阻塞弹出元素压入到另一个列表的头部
# 阻塞式消息队列
BLPOP MyQueue 0
RPUSH MyQueue hello
习题

微博某贴最后评论的50条

LPUSH u1234:forumid:comments "这是第1条评论"
LPUSH u1234:forumid:comments "这是第2条评论"
LPUSH u1234:forumid:comments "这是第3条评论"
LTRIM u1234:forumid:comments 0 49
2.6 hash散列

值是由field和value组成的map键值对
field和value都是字符串类型


HSET key field value 设置单个字段。field不存在创建,存在覆盖value
HSETNX key field value 设置单个字段,要求field不存在。如果key不存在,相当于field也不存在
HMSET key field value [field value ...] 设置多个字段
HLEN key 返回字段个数
HEXISTS key field 判断字段是否存在。key或者field不存在,返回0
HGET key field 返回字段值
HMGET key field [field ...] 返回多个字段值
HGETALL key 返回所有的键值对
HKEYS key 返回所有字段名
HVALS key 返回所有值
HINCRBY key field increment 在字段对应的值上进行整数的增量计算
HINCRBYFLOAT key field increment 在字段对应的值上进行浮点数的增量计算
HDEL key field [field ...] 删除指定的字段
hash用途

节约内存空间
每创建一个键,它都会为这个键储存一些附加的管理信息(比如这个键的类型,这个键最后一次被访问的时间等等)

所以数据库里面的键越多,redis数据库服务器在储存附加管理信息方面耗费的内存就越多,花在管理数据库键上的CPU时间也会越多

不适合hash的情况

使用二进制位操作命令:因为Redis目前支持对字符串键进行SETBIT、GETBIT、BITOP等操作,如果你想使用这些操作,那么只能使用字符串键,虽然散列也能保存二进制数据

使用过期键功能:Redis的键过期功能目前只能对键进行过期操作,而不能对散列的字段进行过期操作,因此如果你要对键值对数据使用过期功能的话,那么只能把键值对储存在字符串里面

习题

用户维度统计

统计数包括:关注数、粉丝数、喜欢商品数、发帖数
用户为Key,不同维度为Field,Value为统计数
比如关注了5人
HSET user:100000 follow 5
HINCRBY user:100000 follow 1

商品维度统计
统计值包括喜欢数,评论数,购买数,浏览数等
HSET item:58000 fav 500
HINCRBY item:58000 fav 1

缓存用户信息
登录后,反复需要读取用户的常用信息,最好的方式就是缓存起来

set user:001 "bob,18,20010101" 
mset user:001:name "bob" user:001:age 18 user:001:birthday "20010101" 
hmset user:001 name "bob" age 18 birthday "20010101    # 推荐使用 
2.7 Set集合

集合的元素是无序的、去重的,元素是字符串类型。

SADD key member [member ...] 增加一个或多个元素,元素已存在将忽略
SREM key member [member ...] 移除一个或多个元素,元素不存在自动忽略
SCARD key 返回集合中元素的个数。不需要遍历。
SMEMBERS key 返回集合中的所有元素(hash顺序)。注意,如果集合中元素过多,应当避免使用该方法
SISMEMBER key member 元素是否是在集合中
# 拿数据的顺序 不一定一致 ;
127.0.0.1:6379> sadd f1 perter
(integer) 1
127.0.0.1:6379> sadd f1 john may tom
(integer) 3
127.0.0.1:6379> SMEMBERS f1
1) "tom"
2) "may"
3) "perter"
4) "john"
127.0.0.1:6379> sadd f1 ben
(integer) 1
127.0.0.1:6379> sadd f2 john may tom perter ben
(integer) 5
127.0.0.1:6379> SMEMBERS f2
1) "tom"
2) "may"
3) "perter"
4) "john"
5) "ben"
127.0.0.1:6379> SMEMBERS f1
1) "tom"
2) "may"
3) "perter"
4) "john"
5) "ben"

SRANDMEMBER key [count] 随机返回集合中指定个数的元素如果 count 为正数,且小于集合基数,那么命令返回一个包含 count 个元素的数组,数组中的元素各不相同。如果 count 大于等于集合基数,那么返回整个集合
如果 count 为负数,那么命令返回一个数组,数组中的元素可能会重复出现多次,而数组的长度为 count 的绝对值

如果 count 为 0,返回空
如果 count 不指定,随机返回一个元素

集合运算
差集
SDIFF key [key ...] 从第一个key的集合中去除其他集合和自己的交集部分
SDIFFSTORE destination key [key ...] 将差集结果存储在目标key中
SADD number1 123 456 789
SADD number2 123 456 999
SDIFF number1 number2
交集
SINTER key [key ...] 取所有集合交集部分
SINTERSTORE destination key [key ...] 将交集结果存储在目标key中
SADD number1 123 456 789
SADD number2 123 456 999
SINTER number1 number2
并集
SUNION key [key ...] 取所有集合并集
SUNIONSTORE destination key [key ...] 将并集结果存储在目标key中
SADD number1 123 456 789
SADD number2 123 456 999
SUNION number1 number2

习题

微博的共同关注
需求:当用户访问另一个用户的时候,会显示出两个用户共同关注哪些相同的用户
设计:将每个用户关注的用户放在集合中,求交集即可

2.8 SortedSet有序集合

类似Set集合,有序的集合。
每一个元素都关联着一个浮点数分值(Score),并按照分值从小到大的顺序排列集合中的元素。分值可以相同


ZADD key score member [score member ...] 增加一个或多个元素。如果元素已经存在,则使用新的score
ZCARD key 返回集合的元素个数
ZCOUNT key min max 返回指定score范围元素的个数
ZSCORE key member 显示分值
ZINCRBY key increment member 增加或减少分值。increment为负数就是减少
ZRANGE key start stop [WITHSCORES] 返回指定索引区间元素
如果score相同,则按照字典序lexicographical order 排列
默认按照score从小到大,如果需要score从大到小排列,使用ZREVRANGE
ZREVRANGE key start stop [WITHSCORES] 返回指定索引区间元素
如果score相同,则按照字典序lexicographical order 的 逆序 排列
默认按照score从大到小,如果需要score从小到大排列,使用ZRANGE
ZRANK key member 返回元素的排名(索引)
ZREVRANK key member 返回元素的逆序排名(索引)
ZADD employees 3500 jack 4000 peter 4000 john 4500 tom 2500 david
ZCOUNT employees 3000 4000 # 空的;索引
ZCOUNT employees 0 1   # david jack

ZADD employees 3.2 david
ZSCORE employees david

ZINCRBY employees 1.5 jack
ZINCRBY employees -500 tom

ZRANGE employees 0 -1 WITHSCORES
ZRANK employees peter

ZREVRANGE employees 0 -1 WITHSCORES # 逆序后的索引0到-1,即返回所有
ZREVRANK employees peter

ZRANGEBYSCORE key min max [WITHSCORES][LIMIT offset count] 返回指定分数区间的元素,返回score默认属于[min,max]之间,元素按照score升序排列,score相同字典序
LIMIT中offset代表跳过多少个元素,count是返回几个。类似于Mysql
使用小括号,修改区间为开区间,例如 (5 或者 (10、5)
-inf和+inf表示负无穷和正无穷

ZREVRANGEBYSCORE key max min [WITHSCORES][LIMIT offset count] 降序返回指定分数区间的元素 返回score默认属于[min,max]之间,元素按照score降序排列,score相同字典降序

ZRANGEBYSCORE employees 3500 4000
ZRANGEBYSCORE employees (4000 5000
ZRANGEBYSCORE employees 4000 5000 LIMIT 1 5 # 跳过一个,返回至多5个

ZREVRANGEBYSCORE employees +inf -inf
ZREM key member [member ...] 移除一个或多个元素。元素不存在,自动忽略
ZREMRANGEBYRANK key start stop 移除指定排名范围的元素
ZREMRANGEBYSCORE key min max 移除指定分值范围的元素
ZREMRANGEBYRANK employees 0 1
ZREMRANGEBYSCORE employees 4000 5000

集合运算
并集

ZUNIONSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] numkeys指定key的数量,必须
WEIGHTS选项,与前面设定的key对应,对应key中每一个score都要乘以这个权重
AGGREGATE选项,指定并集结果的聚合方式
SUM: 将所有集合中某一个元素的score值之和作为结果集中该成员的score值,默认
MIN: 将所有集合中某一个元素的score值中最小值作为结果集中该成员的score值
MAX: 将所有集合中某一个元素的score值中最大值作为结果集中该成员的score值
ZADD scores1 70 tom 80 peter 60 john
ZADD scores2 90 peter 60 ben

ZUNIONSTORE scores-all 2 scores1 scores2
ZUNIONSTORE scores-all1 2 scores1 scores2 AGGREGATE SUM

ZUNIONSTORE scores-all2 2 scores1 scores2 WEIGHTS 1 0.5 AGGREGATE SUM

交集

ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX] numkeys指定key的数量,必须
WEIGHTS选项,与前面设定的key对应,对应key中每一个score都要乘以这个权重
AGGREGATE选项,指定并集结果的聚合方式
SUM: 将所有集合中某一个元素的score值之和作为结果集中该成员的score值
MIN: 将所有集合中某一个元素的score值中最小值作为结果集中该成员的score值
MAX: 将所有集合中某一个元素的score值中最大值作为结果集中该成员的score值
习题

音乐排行榜怎样实现


每首歌的歌名作为元素(先不考虑重复)
每首歌的播放次数作为分值
ZREVRANGE来获取播放次数最多的歌曲(就是最多播放榜了,云音乐热歌榜,没有竞价,没有权重)

redis3 版本与 2X版本变化较大;

# redis库3.x版本

import redis
r = redis.Redis('127.0.0.1', 6379, db=3)
r.zadd('mboard', {'yellow':1, 'rolling in the deep':1, 'happy':1, 'just the way you are':1})
r.zadd('mboard', {'eye of the tiger':1, 'billie jean':1, 'say you say me':1, 'payphone':1})
r.zadd('mboard', {'my heart will go on':1, 'when you believe':1, 'hero':1})
r.zincrby('mboard', 50, 'yellow')
r.zincrby('mboard', 60, 'rolling in the deep')
r.zincrby('mboard', 68.8, 'my heart will go on')
r.zincrby('mboard', 70, 'when you believe')
allmusic = r.zrange('mboard', 0, -1, withscores=True)
print(type(allmusic))
for m in allmusic:
    print(m)
print('-' * 30)
# 排行榜
musicboard = r.zrevrange('mboard', 0, 9, True)
print('欧美热歌榜')
for i, m in enumerate(musicboard):
    print(i, *m)


# redis库 2.x版本

import redis

r = redis.Redis(host='192.168.142.135', port=6379, db=3)

r.zadd('mboard','yellow',1,'rolling in the deep',1,'happy',1,'just the way you are',1)
r.zadd('mboard','eye of the tiger',1,'billie jean',1,'say you say me',1,'payphone',1)
r.zadd('mboard','my heart will go on',1,'when you believe',1,'hero',1)

r.zincrby('mboard','yellow',50)
r.zincrby('mboard','rolling in the deep',60)
r.zincrby('mboard','my heart will go on',68.8)
r.zincrby('mboard','when you believe',70)

# 所有元素
allmusic = r.zrange('mboard', 0, -1, withscores=True)
print(type(allmusic))
for m in allmusic:
    print(m)
print('-'*30)

# 排行榜
musicboard = r.zrevrange('mboard', 0, 9, True)
print('欧美热曲榜')
for i, m in enumerate(musicboard):
    print(i, *m)

新浪微博翻页
新闻网站、博客、论坛、搜索引擎,页面列表条目多,都需要分页

blog这个key中使用时间戳作为score
ZADD blog 1407000000 '今天天气不错'
ZADD blog 1450000000 '今天我们学习Redis'
ZADD blog 1560000000 '几个Redis使用示例'
ZREVRANGE blog 10 20
显示所有博客中的最后的指定的条目

京东图书畅销榜
统计单日榜,计算出周榜单、月榜单、年榜单 , 怎么做?

每天统计一次排行榜
ZADD bk:it:01 1000 'java' 1500 'Redis' 2000 'haoop' 100 'scala'  80 'python'
ZADD bk:it:02 1020 'java' 1500 'Redis' 2100 'haoop' 120 'python' 110 'scala' 
ZADD bk:it:03 1620 'java' 1510 'Redis' 3000 'haoop' 150 'storm' 120 'python'

求销售前10名
ZUNIONSTORE bk:it:01-03 3 bk:it:01 bk:it:02 bk:it:03
行吗?

因为上面的单日榜单是累计值,所以不能直接使用并集,要指定聚合运算为MAX

ZUNIONSTORE bk:it:01-03 3 bk:it:01 bk:it:02 bk:it:03 AGGREGATE MAX 
ZREVRANGE bk:it:01-03 0 9 WITHSCORES 

注意:如果参与并集元素的元素太多,会耗费大量内存和计算时间,可能会导致Redis服务阻塞,如果非要计算,选在空闲时间或备用服务器上计算。
另一种统计

# 常见:
ZADD bk:it:01 50 'java' 20 'Redis' 40 'haoop' 
ZADD bk:it:02 70 'java' 30 'Redis' 20 'haoop' 
ZADD bk:it:03 20 'java' 30 'Redis' 5 'haoop' 
每天统计当天销售量,统计IT类图书一段时间的最新销售榜单
ZUNIONSTORE bk:it:01-03 3 bk:it:01 bk:it:02 bk:it:03 AGGREGATE SUM 
ZREVRANGE bk:it:01-03 0 9 WITHSCORES 
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335