redis是一种基于键值对(key-value)数据库,其中value可以为string、hash、list、set、zset等多种数据结构,可以满足很多应用场景。还提供了键过期、发布订阅、事务、流水线等附加功能,流水线:Redis 的流水线功能允许客户端一次将多个命令请求发送给服务器, 并将被执行的多个命令请求的结果在一个命令回复中全部返回给客户端,使用这个功能可以有效地减少客户端在执行多个命令时需要与服务器进行通信的次数。
1)速度快,数据放在内存中,官方给出的读写性能10万/S,与机器性能也有关。
A)数据放内存中是速度快的主要原因。
B)C语言实现,与操作系统距离近。
C)使用了单线程架构,预防多线程可能产生的竞争问题。
2)键值对的数据结构服务器。
3)丰富的功能:见上功能。
4)简单稳定:单线程。
5)持久化:发生断电或机器故障,数据可能会丢失,持久化到硬盘。
6)主从复制:实现多个相同数据的 redis 副本。
8)高可用和分布式:哨兵机制实现高可用,保证redis节点故障发现和自动转移。
9)客户端语言多:java、php、python、c、c++、nodejs等。
1)缓存:合理使用缓存加快数据访问速度,降低后端数据源压力。
2)排行榜:按照热度排名,按照发布时间排行,主要用到列表和有序集合。
3)计数器应用:视频网站播放数,网站浏览数,使用redis计数。
4)社交网络:赞、踩、粉丝、下拉刷新。
5)消息队列:发布和订阅。
linux上安装,windows也能装,但我们以linux环境为主。
4.1.1、windows也能装使用说明:
Windows版本Redis下载是在github上面下载的:https://github.com/MicrosoftArchive/redis/tags
版本选择:
Redis-x64-3.2.100.msi
Redis-x64-3.2.100.zip
这里使用压缩包方式,直接解压到相应目录如:D:\tools\Redis-x64-3.2.100
启动Redis:双击运营redis-server.exe即可启动
启动redis-cli.exe 然后可以简单使用命令测试
具体命令如下:
127.0.0.1:6379> set name redis
OK
127.0.0.1:6379> get name
"redis"
4.1.2、linux上安装
前置条件:请先安装好VmWare虚拟机软件(Windwos平台),再根据Centos7系统镜像安装Centos7操作系统,最后请打开vm设置主机模式上网文档,设置上网。当完成Centos7操作系统安装后,先安装依赖包(确保虚拟机能上外网,不然不能安装)
1)依赖包检查
yum install cpp -y
yum install binutils -y
yum install glibc-kernheaders -y
yum install glibc-common -y
yum install glibc-devel -y
yum install gcc -y
yum install make -y
2)下载Redis源码,解压缩后编译源码。
cd /usr/local
mkdir soft
cd soft
wget http://download.redis.io/releases/redis-4.0.6.tar.gz
tar xzf redis-4.0.6.tar.gz
cd redis-4.0.6
make
3)复制配置信息到/usr/local/redis目录下面
mkdir /usr/local/redis
cp redis-server /usr/local/redis
cp redis-benchmark /usr/local/redis
cp redis-check-rdb /usr/local/redis
cp redis-sentinel /usr/local/redis
cp redis-cli /usr/local/redis
cp redis.conf /usr/local/redis
4)远程访问6379节点(后期做为主节点使用):
redis.conf修改requirepass 12345678,注释掉bind 127.0.0.1
./redis-server redis.conf // redis启动
./redis-cli -h 192.168.30.156 -p 6379 -a 12345678 // redis.conf
备注:配置说明
1)在redis.conf第89行,将protected-mode no
2)在redis.conf第71行修改下bind 192.168.42.111 (ip为你linux的ip);
3)在redis.conf第502行,加上 requirepass "12345678", 登录密码
测试:
1)启动redis: ./redis-server redis.conf &
2)连接redis: ./redis-cli -h 192.168.30.156 -p 6379 -a 12345678
1)默认配置:redis-server,日志输出版本信息,端口6379。
2)运行启动:redis-server --port 6380不建议。
3)配置文件启动:redis-server /opt/redis/redis.conf灵活,生产环境使用这种。
1)交互式:redis-cli -h {host} -p {prot}连接到redis服务,没有h默认连127.0.0.1;
例如:redis-cli -h 127.0.0.1 -p 6379 //没有 p 默认连 6379。
2)命令式:redis-cli -h 127.0.0.1 -p 6379 get hello //取key=hello的value。
3)停止redis服务:
redis-cli shutdown
注意事项:
4)重大版本:
1)版本号第二位为奇数,为非稳定版本(2.7、2.9、3.1)。
2)第二为偶数,为稳定版本(2.6、2.8、3.0)。
3)当前奇数版本是下一个稳定版本的开发版本,如2.9是3.0的开发版本。
1)查看所有键:keys *(先设值:set school hankin set hello world)。
2)键总数dbsize 示例:2个键,如果存在大量键,线上禁止使用此指令。
3)检查键是否存在:exists key,例如:exists school存在返回1,不存在返回0
4)删除键:del key
例如:del hello school,返回删除键个数,删除不存在键返回0
5)键过期:expire key seconds
示例:set age 23 ex 10,示例:10秒后过期,px 10000毫秒过期,ttl age查看剩余的过期时间
6)键的数据结构类型:type key,示例:type hello //返回string,键不存在返回none
列举例子:三个客户端同时执行命令
客户端 1:set name test
客户端 2:incr num
客户端 3:incr num
执行过程:发送指令-〉执行命令-〉返回结果。
执行命令:单线程执行,所有命令进入队列,按顺序执行,使用I/O多路复用解决I/O问题,后面有介绍(通过select/poll/epoll/kqueue这些I/O多路复用函数库,我们解决了一个线程处理多个连接的问题)。
单线程快原因:纯内存访问,非阻塞I/O(使用多路复用),单线程避免线程切换和竞争产生资源消耗。
问题:如果某个命令执行,会造成其它命令的阻塞。
实际上可以是字符串(包括XML JSON),还有数字(整形、浮点数),二进制(图片、音频、视频),最大不能超过512MB。
set age 23 ex 10 //10秒后过期px 10000毫秒过期;
setnx name test //不存在键name时,返回1设置成功,存在的话失败0;
set age 25 xx //存在键age时,返回1成功。
场景:如果有多客户同时执行setnx,只有一个能设置成功,可做分布式锁。
获值命令:get age //存在则返回 value,不存在返回nil
批量设值:mset country china city beijing
批量获取:mget country city address //返回china beigjin,address为nil
执行结果如下:
192.168.30.156:6379> mset country china city beijing
OK
192.168.30.156:6379> mget country city address
1) "china"
2) "beijing"
3) (nil)
若没mget命令,则要执行n次get命令:
使用mget=1次网络请求+redis内部n次查询:
incr age //必须为整数自加1,非整数返回错误,无age键从0自增返回1。
decr age //整数age减1
incrby age 2 //整数 age+2,每次加2
decrby age 2 //整数 age -2
incrbyfloat score 1.1 //浮点型score+1.1
set name hello
append name world //追加后成helloworld
set hello “世界”
strlen hello //结果6,每个中文占3个字节
set name helloworld;
getrange name 2 4 //返回llo
set age 100
object encoding age //返回int
Embstr返回小于等于39字节串:
set name bejing
object encodeing name //返回embstr
raw:大于39字节的字符串
set a fsdfwerwerweffffffffffdfs //返回raw
1)键值设计:业务名:对象名:id:[属性]
数据库为order,用户表user,对应的键可为order:user:1或order:user:1:name
注意:redis目前处于受保护模式,不允许非本地客户端链接,可以通过给redis设置密码,然后客户端链接的时候,写上密码就可以了。
例如:
127.0.0.1:6379> config set requirepass 123456 临时生效,或者修改redis.conf requirepass 123456,启动时./redis-server redis.conf指定conf:./redis-cli -p 6379 -a 12345678 //需要加入密码才能访问
切换数据库:select 2
是一个string类型的field和value的映射表,hash特适合用于存储对象。
设值:hset user:1 name hankin //成功返回1,失败返回0
取值:hget user:1 name //返回 hankin
删值:hdel user:1 age //返回删除的个数
计算个数:
hset user:1 name hankin;
hset user:1 age 23;
hlen user:1 //返回 2,user:1有两个属性值
批量设值:hmset user:2 name hankin age 23 sex boy //返回 OK
批量取值:hmget user:2 name age sex //返回三行:hankin 23 boy
判断field是否存在:hexists user:2 name //若存在返回1,不存在返回0
获取所有field: hkeys user:2 //返回name age sex三个field
获取user:2所有value:hvals user:2 //返回hankin 23 boy
获取user:2所有field与value:hgetall user:2 //name age sex hankin 23 boy值
增加1:
hincrby user:2 age 1 //age+1
hincrbyfloat user:2 age 2 //浮点型加2
ziplist<压缩列表>和hashtable<哈希表>当field个数少且没有大的value时,内部编码为ziplist。
例如:
hmset user:3 name hankin age 24
object encoding user:3 //返回ziplist
当value大于64字节,内部编码由ziplist变成hashtable
例如:
hset user:4 address “fsfsdfsdgsgsegdsgsgagaggsgsggssdssdsdsdsgerrgdhfshdfhhtrhfhg走开多少64字节"
object encoding user:4 //返回 hashtable
比如将关系型数据表转成redis存储:
使用 hash 后的存储方式为:
如果有值为NULL,那么如下:
HASH类型是稀疏,每个键可以有不同的filed,若用redis模拟做关系复杂查询开发因难,维护成本高。
1)原生:
set user:1:name hankin;
set user:1:age 23;
set user:1:sex boy;
优点:简单直观,每个键对应一个值。
缺点:键数过多,占用内存多,用户信息过于分散,不用于生产环境。
2)将对象序列化存入redis
set user:1 serialize(userInfo);
优点:编程简单,若使用序列化合理内存使用率高。
缺点:序列化与反序列化有一定开销,更新属性时需要把userInfo全取出来进行反序列化,更新后再序列化到redis。
3)使用hash类型:
hmset user:1 name hankin age 23 sex boy
优点:简单直观,使用合理可减少内存空间消耗。
缺点:要控制ziplist与hashtable两种编码转换,且hashtable会消耗更多内存总结:对于更新不多的情况下,可以使用序列化,对于VALUE值不大于64字节可以使用hash类型。
一个列表最多可存2的32次方减1个元素因为有序,可以通过索引下标获取元素或某个范围内元素列表,列表元素可以重复。
1)添加命令:
rpush hankin c b a //从右向左插入cba返回值3
lrange hankin 0 -1 //从左到右获取列表所有元素返回cba
lpush key c b a //从左向右插入cba
linsert hankin before b teacher //在b之前插入teacher,after为之后,使用lrange hankin 0 -1查看返回结果:c teacher b a
2)查找命令:lrange key start end 索引下标特点,从左到右为0到N-1。
lindex hankin -1 //返回最右末尾a,-2返回b
llen hankin //返回当前列表长度
lpop hankin //把最左边的第一个元素c删除
rpop hankin //把最右边的元素a删除
lrange hankin 0 -1
3)删除指定元素:lrem key count value
如:lpush test b b b b b j x z //键test放入z x j b b b b b
lrange test 0 -1 //查询结果为z x j b b b b b
lrem test 4 b //从左右开始删除b的元素,删除4个
若lrem test 8 b, 删除8个b,但只有5个全部删除
lrange test 0 -1 //删除后的结果为:z x j b
lrem test 0 b //检索所有b全部删除j x z
lpush user b b b b b j x z //键user从左到右放入z x j b b b b b
ltrim user 1 3 //只保留从第2到第4的元素,其它全删
lrange user 0 -1 //查询结果为x j b,其它已全被删掉
lpush user01 z y x //键user01从左到右放入x y z
lset user01 2 java //把第3个元素z替换成java
lrange user01 0 -1 //查询结果为x y java
4)应用场景设计:cacheListHashApplicationTest用例
每个用户有多个订单key为order:1 order:2 order:3,结合hmset
hmset order:1 orderId 1 money 36.6 time 2018-01-01
hmset order:2 orderId 2 money 38.6 time 2018-01-01
hmset order:3 orderId 3 money 39.6 time 2018-01-01
把订单信息的key放到队列:
lpush user:1:order order:1 order:2 order:3
每新产生一个订单:
hmset order:4 orderId 4 money 40.6 time 2018-01-01
追加一个order:4 放入队列第一个位置:
lpush user:1:order order:4
当需要查询用户订单记录时:
List orderKeys = lrange user:1:order 0 -1 //查询user:1的所有订单key值
for(Order order: orderKeys){
hmget order:1
}
从redis的官网查阅了相关资料,在3.2版本以后,redis提供了quicklist内部编码,它结合了ziplist和linkedlist两者的优势,之前的ziplist是存在BUG的,使用quicklist内部编码效率更高,所以我们现在3.2以后看不到这两个编码,只看到quicklist,可以看一下https://matt.sh/redis-quicklist国外的这篇博客有重点提到,如果想了解可以下一下3.1之前的版本。
用户标签,社交,查询有共同兴趣爱好的人,智能推荐
保存多元素,与列表不一样的是不允许有重复元素,且集合是无序,一个集合最多可存2的32次方减1个元素,除了支持增删改查,还支持集合交集、并集、差集。
exists user //检查user键值是否存在
sadd user a b c //向user插入3个元素,返回3
sadd user a b //若再加入相同的元素,则重复无效,返回0
smembers user //获取user的所有元素,返回结果无序
srem user a //返回1,删除a元素
scard user //返回2,计算元素个数
sismember user a //判断元素是否在集合存在,存在返回1,不存在0
srandmember user 2 //随机返回2个元素,2为元素个数
spop user 2 //随机返回2个元素a b,并将a b从集合中删除
smembers user //此时已没有a b,只有c
1)集合的交集:
sadd user:1 zhangsan 24 girl
sadd user:2 hankin24 boy //初始化两个集合
sinter user:1 user:2 //求两集合交集,此时返回24
sadd user:3 wang 24 girl //新增第三个元素
sinter user:1 user:2 user:3 //求三个集合的交集,此时返回24
2)集合的并集(集合合并去重):
sunion user:1 user:2 user:3 //三集合合并(并集),去重24
sdiff user:1 user:2 //1和2差集,(zhangsan 24 girl)-(hankin 24 boy)=zhangsan girl
3)将交集(jj)、并集(bj)、差集(cj)的结果保存:
sinterstore user_jj user:1 user:2 //将user:1 user:2的交集保存到 user_jj
sunionstore user_bj user:1 user:2 //将user:1 user:2的(并)合集保存 user_bj
sdiffstore user_cj user:1 user:2 //将user:1-user:2的差集保存 user_cj
smemebers user_cj //返回zhangsan girl
sadd user 1 2 3 4 //当元素个数少(小于512个)且都为整数,redis使用intset减少内存的使用。
sadd user 1 2...513 //当超过512个或不为整数(比如a b)时,编码为:hashtable object encoding user //返回结果:hashtables
标签,社交,查询有共同兴趣爱好的人,智能推荐
使用方式:
1)给用户添加标签:
sadd set1 a b c
sadd set2 a b d e
或给标签添加用户:
sadd aset set1 set2
sadd bset set1 set2 set3
2)计算出共同感兴趣的人:
sinter set1 set2
3)规则(后续详细介绍):
sadd (常用于标签) spop/srandmember(随机,比如抽奖)
sadd+sinter (用于社交,查询共同爱好的人,匹配)
常用于排行榜,如视频网站需要对用户上传视频做排行榜,或点赞数,与集合有联系,不能有重复的成员
与LIST SET对比
zadd key score member [score member......]
zadd user:zan 200 hankin //hankin的点赞数1,返回操作成功的条数1
zadd user:zan 200 hankin 120 mike 100 lee //返回3
zadd test:1 nx 100 hankin //键test:1必须不存在,主用于添加
zadd test:1 xx incr 200 hankin //键test:1必须存在,主用于修改,此时为300
zadd test:1 xx ch incr -299 hankin //返回操作结果1,300-299=1
zrange test:1 0 -1 withscores //查看点赞(分数)与成员名
zcard test:1 //计算成员个数,返回1
1)查点赞数
zadd test:2 nx 100 hankin //新增一个集合
zscore test:2 hankin //查看hankin的点赞数(分数),返回100
2)排名:
zadd user:3 200 hankin 120 mike 100 lee //先插入数据
zrange user:3 0 -1 withscores //查看分数与成员
zrank user:3 hankin //返回名次:第3名返回2,从0开始到2,共3名
zrevrank user:3 hankin //返回0,反排序,点赞数越高,排名越前
3)删除成员:
zrem user:3 jame mike //返回成功删除2个成员,还剩lee
4)增加分数:
zincrby user:3 10 lee //成员lee的分数加10
zadd user:3 xx incr 10 lee //和上面效果一样
返回指定排名范围的分数与成员
zadd user:4 200 hankin 120 mike 100 lee //先插入数据
zrange user:4 0 -1 withscores //返回结果如下图
zrevrange user:4 0 -1 withscores //倒序,结果如下图
返回结果为:lee mike hankin 100 120 200
5)返回指定分数范围的成员
zrangebyscore user:4 110 300 withscores //返回120 lee,200 hankin由低到高。
zrevrangebyscore user:4 300 110 withscores //返回200 hankin 120 lee由高到低。
zrangebyscore user:4 (110 +inf withscores //110到无限大,120mike 200 hankin。
zrevrangebyscore user:4 (110 -inf withscores //无限小到110,返回100 lee。
6)返回指定分数范围的成员个数
zcount user:4 110 300 //返回2,由mike120和hankin200两条数据。
7)删除指定排名内的升序元素
zremrangebyrank user:4 0 1 //分数升序排列,删除第0个与第1个,只hankin。
8)删除指定分数范围的成员
zadd user:5 200 hankin 120 mike 100 lee //先插入测试数据。
zremrangebyscore user:5 100 300 //删除分数在100与300范围的成员。
zremrangebyscore user:5 (100 +inf //删除分数大于100(不包括100),还剩lee。
9)有序集合交集:
格式:zinterstore destination numkeys key ... [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
Destination:交集产生新的元素存储键名称。
Numkeys:要做交集计算的键个数。
Key:元素键值
Weights:每个被选中的键对应值乘weight,默认为1。
初始化数据:
zadd user:7 1 hankin 2 mike 4 jack 5 kate //初始化user:7数据。
zadd user:8 3 hankin 4 mike 4 lucy 2 lee 6 jim //初始化user:8数据。
10)交集例子:
zinterstore user_jj 2 user:7 user:8 aggregate sum //2代表键合并个数,aggregate sum可加也不可加上,因为默认是sum,结果user_jj:4hankin(1+3), 6mike(2+4)。
zinterstore user_jj max 2 user:7 user:8 aggregate max或min //取交集最大的分数,返回结果3hankin 4mike,min取最小。
11)weights
zinterstore user_jjweight 2 user:7 user:8 weights 8 4 aggregate max
①取两个成员相同的交集,user:7->1 hankin 2 mike; user:8->3 hankin 4 mike
②将user:7->hankin 1*8=8,user:7->mike 2*8 =16,最后user:7结果:8 hankin 16 mike
③将user:8-> hankin 3*4=12,user:8->mike 4*4=16,最后user:8结果:12 hankin 16 mike
④最终相乘后的结果,取最大值为12 hankin 16mike //5
zrange user_jjweight 0 -1 withscores,查询结果为:12 hankin 16mike
总结:
将user:7成员值乘8,将user:8成员值乘4,取交集,取最大有序集合并集(合并去重):
格式:zunionstore destination numkeys key ... [WEIGHTS weight] [AGGREGATE SUM|MIN|MAX]
Destination:交集产生新的元素存储键名称
Numkeys:要做交集计算的键个数
key:元素键值
Weights:每个被选中的键对应值乘weight,默认为1
zunionstore user_jjweight2 2 user:7 user:8 weights 8 4 aggregate max //与以上zinterstore一样,只是取并集,指令一样。
1)ziplist
zadd user:9 20 hankin 30 mike 40 lee
object encoding user:init //当元素个数少(小于128个),元素值小于64字节时,使用ziplist编码,可有效减少内存的使用。
2)skiplist
zadd user:10 20 hankin //大于128个元素或元素值大于64字节时为skiplist编码。
排行榜系统,如视频网站需要对用户上传的视频做排行榜
点赞数:zadd user:1:20191110 3 mike //mike获得3个赞
再获一赞:zincrby user:1:20191110 1 mike //在3的基础上加1
用户作弊,将用户从排行榜删掉:zrem user:1:20191110 mike
展示赞数最多的5个用户:
zadd user:4:20191111 9 jack 10 jj 11 dd 3 hankin 4 lee 6 mark 7 kate
zrevrangebylex user:4:20191111 + - limit 0 5
查看用户赞数与排名:
zscore user:1:20191110 mike
zrank user:1:20191110 mike
前提:为节约内存,Redis一般会做定期清除操作
1)当查询key=hankin的值,此时Redis没有数据
2)如果有5000个用户并发来查询key=hankin,全到Mysql里去查,Mysql会挂掉导致雪崩:
解决方案如下:
A、设置热点数据永远不过期。
B、加互斥锁,互斥锁参考代码如下:
前提:黑客模拟一个不存在的订单号xxxx
1)Redis中无此值
2)Mysql中也无此值,但一直被查询
解决方案:
1)对订单表所有数据查询出来放到布隆过滤器,经过布隆过滤器处理的数据很小(只存0或1)
2)每次查订单表前,先到过滤器里查询当前订单号状态是0还是1,0的话代表数据库没有数据,直接拒绝查询。
redis支持RDB和AOF两种持久化机制,持久化可以避免因进程退出而造成数据丢失;
RDB持久化把当前进程数据生成快照(.rdb)文件保存到硬盘的过程,有手动触发和自动触发。
手动触发有save和bgsave两命令:
save命令:阻塞当前Redis,直到RDB持久化过程完成为止,若内存实例比较大会造成长时间阻塞,线上环境不建议用它。
bgsave命令:redis进程执行fork操作创建子线程,由子线程完成持久化,阻塞时间很短(微秒级),是save的优化,在执行redis-cli shutdown关闭redis服务时,如果没有开启AOF持久化,自动执行bgsave,显然bgsave是对save的优化。
bgsave 运行流程:
命令:config set dir //usr/local //设置rdb文件保存路径。
备份:bgsave //将dump.rdb保存到usr/local下。
恢复:将dump.rdb放到redis安装目录与redis.conf同级目录,重启redis即可。
优点:
1)压缩后的二进制文,适用于备份、全量复制,用于灾难恢复。
2)加载RDB恢复数据远快于AOF方式。
缺点:
1)无法做到实时持久化,每次都要创建子进程,频繁操作成本过高。
2)保存后的二进制文件,存在老版本不兼容新版本rdb文件的问题。
针对RDB不适合实时持久化,redis提供了AOF持久化方式来解决。
开启:redis.conf设置:appendonly yes (默认不开启,为no)。
默认文件名:appendfilename "appendonly.aof"。
1)所有的写入命令(set hset)会append追加到aof_buf缓冲区中。
2)AOF缓冲区向硬盘做sync同步。
3)随着AOF文件越来越大,需定期对AOF文件rewrite重写,达到压缩。
4)当redis服务重启,可load加载AOF文件进行恢复。
AOF持久化流程:命令写入(append),文件同步(sync),文件重写(rewrite),重启加载(load)
redis的AOF配置详解:
appendonly yes //启用aof持久化方式
# appendfsync always //每收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用。
appendfsync everysec //每秒强制写入磁盘一次,性能和持久化方面做了折中,推荐# appendfsync no //完全依赖os,性能最好,持久化没保证(操作系统自身的同步)。
no-appendfsync-on-rewrite yes //正在导出rdb快照的过程中,要不要停止同步aof。
auto-aof-rewrite-percentage 100 //aof文件大小比起上次重写时的大小,增长率100%时重写。
auto-aof-rewrite-min-size 64mb //aof文件,至少超过64M时重写。
1)设置appendonly yes;
2)将appendonly.aof 放到dir参数指定的目录;
3)启动Redis,Redis会自动加载appendonly.aof文件。
1)当AOF和RDB文件同时存在时,优先加载
2)若关闭了AOF,加载RDB文件
3)加载AOF/RDB成功,redis重启成功
4)AOF/RDB存在错误,redis启动失败并打印错误信息