redis干货

Posted 小明爱java编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis干货相关的知识,希望对你有一定的参考价值。

redis

一、NOSQL

NOSQL: no only sql(不仅仅SQL)

泛指非关系型数据库

NoSQL特点:

1.方便扩展(数据之间没有关系,很好扩展)

2.大数据量高性能(Redis一秒写8万次,读取1万,NoSQL的缓存记录级,时一种细粒度的的缓存,性能会比较高)

3.数据类型时多样型的(不需要事先设计数据库,随取随用,如果时数据量十分大的表,很多人就无法设计了)

4.传统RDBMS 和 NoSQL

RDBMS

  • 结构化组织

  • SQL

  • 数据和关系都存在单独的表中

  • 数据操作,数据定义语言

  • 严格的一致性

  • 基础的事务

NoSQL

  • 不仅仅是数据

  • 没有固定的查询语言

  • 键值对存储,列存储,文档存储,图形数据库(社交关系)

  • 最终一致性

  • CAP定理和BASE(异地多活)初级架构师

  • 高性能,高可用,高可扩

了解 3V + 3高

  • 3V

1.海量Volume

2.多样Variety

3实时Velocity

  • 3高

1.高可用

2.高可扩

3.高性能

二、阿里巴巴实践分析理解数据架构演进

1.商品的基本信息:

名称、价格、商家信息:

关系型数据库就可以解决:mysql / Oracle (淘宝早年就去IOE了--王坚 --阿里云这群疯子)

​ (IOE: IBM小型机 --Oracle数据库 --EMC存储设备)

2.商品的描述、评论(文字比较多)

文档型数据库中,MongDB

3.图片

分布式文件系统 FastDFS

  • 淘宝自己的 TFS

  • Google的 GFS

  • hadoop HDFS

  • 阿里云的 oss

4.商品的关键字 (搜索)

  • 搜素引擎 solr elasticsearch

  • ISearch:多隆

三、NoSQL四大分类

KV键值对:

  • \\新浪:Redis

  • 美团:Redis + Tair

  • 阿里、百度:Redis+memcache

文档型数据库:(bson格式和json一样)

  • MongoDB(一般必须掌握)

​ 1.MongDB是一个基于分布式文件存储的数据库,C++编写,主要用于处理大量文档

​ 2.MongDB是一个介于关系型数据库和非关系型数据库中中间的产品,MongoDB是非关系型数据库中功能最丰富,最像关系型数据库

​ 3.ConthDB

列存储数据库:

  • HBase

  • 分布式文件系统

图关系数据库:

  • 他不是存图形,放的是关系,比如:朋友圈社交网络、网络推荐

  • Neo4J, InfoGrid

四、Redis的概述

Redis的概述Remote Dictionary Server)即远程字典服务

是单线程的多路IO复用

是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、key-Value数据库,并提供多种语言的API

1.周期性把更新的数据写入磁盘或者把修改操作写入追加的记录文件

2.实现master-slaver(主从)同步

3.免费和开源,是当下最热门的NoSQL技术之一

4.结构化数据库

Redis能干嘛?

1.内存存储,持久化(rdb,aof)

2.效率高,可以用于高速缓存

3.发布订阅系统

4.地图信息分析

5.计时器、计数器(浏览量)

特征:

1.多样的数据类型

2.持久化

3.集群

4.事务

Redis推荐都是基于Linux搭建

五、Window版本Redis

版本:3.2.100

默认端口:6379

官方文档:http://redis.cn/topics/introduction

六、Linux安装Redis

Linux安装Redis

1.基本的环境配置:

(1)

yum install gcc-c++

(2)

# make

(3)

make install

2.redis默认安装路径:/usr/local/bin

3.redis配置文件:

(1)在/usr/local/bin/ 下新建文件夹kconfig

(2)将redis配置文件redis.conf,复制到/usr/local/bin/kconfig

(3)redis默认不是后台启动,需要修改配置文件,改为后台启动

4.启动redis服务:

(1)启动redis服务端

redis-server kconfig/redis.conf

(2)使用客户端连接端口号6379

redis-cli -p 6379

5.查看进程:

ps -ef |grep redis[服务名]

6.关闭redis服务:

shutdown

not connected> exit

#

七、redis性能测试

测试:100个并发连接 10 0000请求

$ redis-benchmark -h localhost -p 6379 -c 100 -n 100000

八、基础知识

1.redis默认有16个数据库,默认使用的是第0个

(1)切换数据库:

127.0.0.1:6379> select 3

(2)数据库大小:

127.0.0.1:6379> DBSIZE

(3)查看所有key:

127.0.0.1:6379> keys *  

(4)清除当前数据库

127.0.0.1:6379> flushdb

(5)清除全部数据内容

127.0.0.1:6379> flushall

(6)删除指定key数据

127.0.0.1:6379> del key

(7)根据value选择非阻塞删除

127.0.0.1:6379> unline key

(8)为给定的key设置过期时间

# 10秒钟
127.0.0.1:6379> expire key 10

2.Redis是单线程

Redis为什么单线程这么快?

1.误区1:高性能的服务器一定是多线程的?

2.误区2:多线程(CPU上下文会切换)一定比单线程效率高

核心:redis是所有的数据全部放在内存中的,所以说使用单线程去操作效率是最高的,多线程会切换CPU上下文切换,对于内存系统来说,没有上下文切换是效率最高的,多次读写都是在单个CPU上操作的

九、基本命令

设置key过期时间:

127.0.0.16379> EXPIRE name 10

查看key的剩余时间:

127.0.0.16379> ttl name[key]

查看key的类型

127.0.0.16379> type name 

Redis命令帮助文档:https://redis.io/commands

十、五大数据类型

String

String二进制安全的,意味着Redis中的String可以包含任何数据,比如jpg或序列化对象,字符串value最大可以是512M

判断存在:

127.0.0.1:6379> exist key

追加:(key值不存在,相当于set)

127.0.0.1:6379> append key1 "hello"

获取长度:

127.0.0.1:6379> strlen key1

自增1:

127.0.0.1:6379> incr key  自减1:
127.0.0.1:6379> dect key

指定增量:

127.0.0.1:6379> incrby key 10

指定减量:

127.0.0.1:6379> decrby key 10

获取字符串指定范围:

127.0.0.1:6379> GETRANGE key 0 3 #[0,3]

替换指定位置开始的字符串:

127.0.0.1:6379> SETRANGE key 1 xx

设置过期时间:

​ #setex(set with expire) # 设置过期时间

127.0.0.1:6379> setex key 30 "hello"

​ #setnx(set if not exist) # 不存在设置(分布式锁中常常使用)

127.0.0.1:6379> setnx key "redis"

批量set、get

#mset

127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3

#msetnx

127.0.0.1:6379> msetnx k1 v1 k4 v4 #原子性,要么全成功,要么全失败

#对象

127.0.0.1:6379> set user:1name:zhangsan,zge:3
#user:id:filer

getset #先get然后再set

127.0.0.1:6379> getset db redis 
1.#如果不存在值,则返回nil
2.#如果存在值,则返回原来的值

List

List的数据结构为快速链表quickList

首先在列表元素较少的情况请下会使用一块连续的内存储存,这个结构是ziplist,即是压缩列表

它将所有的元素紧挨着一起储存,分配的是一块连续的内存

当数据量比较多的时候才会改成quickList

因为普通的链表需要的附加指针空间太大,会比较浪费空间,比如这个列表里存的是int类型的数据,结构上还需要两个额外的指针prev和next

在redis里面,我们可以把list当成是对栈、队列、阻塞队列的操作

所有list命令都是用l开头的

# LPUSH
127.0.0.1:6379> LPUSH list one # 将一个值或多个值,插入到列表头部(左)
(integer) 1
127.0.0.1:6379> LPUSH list two
(integer) 2
127.0.0.1:6379> LPUSH list three
(integer) 3
############################################################
# LRANGE
127.0.0.1:6379> LRANGE list 0 -1 # 获取list中所有值
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0  1 # 通过区间获取具体的值
1) "three"
2) "two"
127.0.0.1:6379> LRANGE list 0  2
1) "three"
2) "two"
3) "one"
############################################################
# RPUSH、LPOP、RPOP
127.0.0.1:6379> RPUSH list right # 将一个值或多个值,插入到列表尾部(右)
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "two"
3) "one"
4) "right"
127.0.0.1:6379> LPOP list # 移除list的第一个元素
"three"
127.0.0.1:6379> RPOP list
"right"
127.0.0.1:6379> LRANGE list 0 -1 # 移除list的最后一个元素
1) "two"
2) "one"
127.0.0.1:6379> 
############################################################
# LINDEX、LPUSH、LLEN
127.0.0.1:6379> lindex list 1 # 通过下标获得list 中的某一个值
"one"
127.0.0.1:6379> lindex list 0
"two"
127.0.0.1:6379> 
127.0.0.1:6379> Lpush list one
(integer) 1
127.0.0.1:6379> Lpush list two
(integer) 2
127.0.0.1:6379> Lpush list three
(integer) 3
127.0.0.1:6379> Llen list # 获取list长度
(integer) 3
127.0.0.1:6379> Lpush list thre
(integer) 4
127.0.0.1:6379> LRANGE list 1 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> LRANGE list 0 -1
1) "thre"
2) "three"
3) "two"
4) "one"
############################################################
# LREM
127.0.0.1:6379> Lrem list 1 thre # 移除列表列表中一个元素为thre
(integer) 1
127.0.0.1:6379> Lpush list three
(integer) 4
127.0.0.1:6379> LRANGE list 0 -1
1) "three"
2) "three"
3) "two"
4) "one"
127.0.0.1:6379> Lrem list 2 three
(integer) 2
127.0.0.1:6379> LRANGE list 0 -1
1) "two"
2) "one"

trim 修剪;list截断

# LTRIM
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> Rpush mylist "hello"
(integer) 1
127.0.0.1:6379> Rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> Rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> Rpush mylist "hello3"
(integer) 4
127.0.0.1:6379> ltrim mylist 1 2 # 截取下标指定的长度,其他被截断
OK
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello1"
2) "hello2"
############################################################
RPOPLPUSH
127.0.0.1:6379> rpush mylist "hello"
(integer) 1
127.0.0.1:6379> rpush mylist "hello1"
(integer) 2
127.0.0.1:6379> rpush mylist "hello2"
(integer) 3
127.0.0.1:6379> rpoplpush mylist myotherlist # 移除最后一个元素并将它移入新的列表中
"hello2"
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "hello1"
127.0.0.1:6379> LRANGE myotherlist 0 -1
1) "hello2"
############################################################
# LSET
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lset list 0 item # 如果不存在列表我们去更新就会报错
(error) ERR no such key
127.0.0.1:6379> LPUSH list value1
(integer) 1
127.0.0.1:6379> LRANGE list 0 0
1) "value1"
127.0.0.1:6379> LRANGE list 0 1
1) "value1"
127.0.0.1:6379> LSET list 0 item # 如果存在,更新当前下标的值
OK
127.0.0.1:6379> LRANGE list 0 0
1) "item"
127.0.0.1:6379> LSET list 1 other # 如果不存在则会报错
(error) ERR index out of range
############################################################
# LINSERT
Linsert # 将某个具体的value插入到列表中某个元素的前面或者后面!
127.0.0.1:6379> RPUSH mylist "hello"
(integer) 1
127.0.0.1:6379> RPUSH mylist "world"
(integer) 2
127.0.0.1:6379> LINSERT mylist before "world" "other"
(integer) 3
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
127.0.0.1:6379> LINSERT mylist after "world" "new"
(integer) 4
127.0.0.1:6379> LRANGE mylist 0 -1
1) "hello"
2) "other"
3) "world"
4) "new"

总结:

list实际是一个链表,before Node after , left , right都可以插入值

如果key不存在,创建新的链表

如果key存在,新增内容

如果移除了所有值,空链表,也代表不存在

在两边插入或者改动值,效率最高,中间元素,相对来说效率会低一点

消息队列 | LpushRpop , 栈 | LpushLpop

Set

Set数据结构是dict字典,字典使用哈希表实现的

Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象

Redis的set结构也是一样,它的内部也使用了hash结构,所有的value都指向同一个内部值

# SADD、SMEMBERS、SISMEMBER、SCARD
127.0.0.1:6379> sadd myset "hello" # set集合添加元素
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> SMEMBERS myset # 查看指定set所有值
1) "world"
2) "kuangshen"
3) "hello"
127.0.0.1:6379> SISMEMBER myset "hello" # 判断某一个值是不是在集合set中
(integer) 1
127.0.0.1:6379> SISMEMBER myset "happy"
(integer) 0
127.0.0.1:6379> scard myset # 获取集合set中元素的个数
(integer) 3
127.0.0.1:6379> 
############################################################
# SRANDMEMBER
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "kuangshen"
3) "hello"
127.0.0.1:6379> SRANDMEMBER myset # 随机抽选出一个元素
"hello"
127.0.0.1:6379> SRANDMEMBER myset
"hello"
127.0.0.1:6379> SRANDMEMBER myset
"world"
127.0.0.1:6379> SRANDMEMBER myset
"kuangshen"
127.0.0.1:6379> SRANDMEMBER myset 2 随机抽选出指定个数的元素
1) "kuangshen"
2) "world"
127.0.0.1:6379> SRANDMEMBER myset 2
1) "kuangshen"
2) "hello"
############################################################
# SPOP
127.0.0.1:6379> SMEMBERS myset
1) "world"
2) "kuangshen"
3) "hello"
127.0.0.1:6379> spop myset # 随机删除一些set集合中的元素
"world"
127.0.0.1:6379> spop myset
"kuangshen"
127.0.0.1:6379> SMEMBERS myset
1) "hello"
############################################################
# SMOVE
将一个指定的值,移动到另一个set集合
127.0.0.1:6379> clear
127.0.0.1:6379> sadd myset "hello"
(integer) 1
127.0.0.1:6379> sadd myset "world"
(integer) 1
127.0.0.1:6379> sadd myset "kuangshen"
(integer) 1
127.0.0.1:6379> sadd myset2 "kuangshen"
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
3) "hello"
127.0.0.1:6379> SMEMBERS myset2
1) "kuangshen"
127.0.0.1:6379> SMOVE myset myset2 "hello" # 将一个指定的值,移动到另一个set集合
(integer) 1
127.0.0.1:6379> SMEMBERS myset
1) "kuangshen"
2) "world"
127.0.0.1:6379> SMEMBERS myset2
1) "kuangshen"
2) "hello"
############################################################
# SDIFF、SINTER、SUNION
B站、微博共同关注(并集)
127.0.0.1:6379> sadd key1 a
(integer) 1
127.0.0.1:6379> sadd key1 b
(integer) 1
127.0.0.1:6379> sadd key1 c
(integer) 1
127.0.0.1:6379> sadd key2 a
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sadd key2 e
(integer) 1
127.0.0.1:6379> SDIFF key1 key2 # 差集 左 - 右
1) "c"
2) "b"
127.0.0.1:6379> SDIFF key2 key1
1) "e"
2) "d"
127.0.0.1:6379> SINTER key1 key2 # 交集
1) "a"
127.0.0.1:6379> SUNION key1 key2 # 并集
1) "a"
2) "c"
3) "b"
4) "e"
5) "d"

Hash

Map集合,key-map 这个值是一个Map,本质和Sring没有太大区别,还是一个简单的key-value

Hash类型对应的数据结构有两种:ziplist(压缩列表)、hashtable(哈希表),当field-value长度交短且个数较少时,使用ziplist,否则使用hashtable

set myhash field kuangshen

# HSET、HGET、HMSET、HMGET、HGETALL
127.0.0.1:6379> hset myhash field1 kuangshen # set 一个具体key-value
(integer) 1
127.0.0.1:6379> hget myhash field1 # 获取一个字段值
"kuangshen"
127.0.0.1:6379> hmset myhash field1 hello field2 world # set 多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field2 # 获取多个字段值
1) "hello"
2) "world"
127.0.0.1:6379> hgetall myhash # 获取全部的数据
1) "field1"
2) "hello"
3) "field2"
4) "world"
############################################################
# HDEL
127.0.0.1:6379> hset myhash field1 hello
(integer) 1
127.0.0.1:6379> hset myhash field2 world
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
3) "field2"
4) "world"
127.0.0.1:6379> hdel myhash field1 # 删除hash指定ley字段,对应的value也就消失了
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
############################################################
# hlen
127.0.0.1:6379> hlen myhash # 获取hash表的字段数量
(integer) 1
############################################################
# HEXISTS
127.0.0.1:6379> HEXISTS myhash field1 #判断hash 中指定的字段是否存在
(integer) 0
127.0.0.1:6379> HEXISTS myhash field2
(integer) 1
############################################################
# HKEYS 、HVALS
只获得所有field,只获得所有value
127.0.0.1:6379> hkeys myhash # 只获得所有field
1) "field2"
127.0.0.1:6379> hvals myhash # 只获得所有value
1) "world"
############################################################
# HINCRBY、HSETNX
127.0.0.1:6379> hset myhash field3 5
(integer) 1
127.0.0.1:6379> HINCRBY myhash field3 1 # 指定增量
(integer) 6
127.0.0.1:6379> HINCRBY myhash field3 -1
(integer) 5
127.0.0.1:6379> HSETNX myhash field4 hello  # 如果value不存在则可以设置
(integer) 1
127.0.0.1:6379> HSETNX myhash field4 world # 如果value存在则不允许设置
(integer) 0

hash变更的数据user name age,尤其是用户信息之类的,经常变动的信息,hash更适合于对象的存储,String更适合字符串存储

Zset

SortedSet(zset)是Redis提供的一个非常特别的数据结构,一方面它等价于Java数据结构Map<String, Double>,可以给每一个元素赋予权重score,另一方面它有类似于TreeSet,内部的元素会安装score权重进行排序,可以得到每个元素的名次,还可以通过score的范围来获取元素的列表。

查找使用的是跳跃链表

在set的基础上,增加了一个值,增加了一个值,set k1 v1 , zset k1 score1 v1

# ZADD、ZRANGE
127.0.0.1:6379> zadd myset 1 one # 添加一个值
(integer) 1
127.0.0.1:6379> zadd myset 2 two 3 three # 添加多个值
(integer) 2
127.0.0.1:6379> ZRANGE myset 0 -1
1) "one"
2) "two"
3) "three"
############################################################
# ZRANGEBYSCORE
127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhagsan
(integer) 1
127.0.0.1:6379> zadd salary 500 kuangshen
(integer) 1
127.0.0.1:6379> ZRANGE salary 0 -1  # 显示全部用户,从小到大
1) "zhangsan"
2) "xiaohong"
3) "xiaoming"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf # 显示全部用户 从小到大
1) "kuangshen"
2) "xiaohong"
3) "zhagsan"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf +inf withscores # 从小到大显示全部用户并且附带成绩
1) "kuangshen"
2) "500"
3) "xiaohong"
4) "2500"
5) "zhagsan"
6) "5000"
127.0.0.1:6379> ZRANGEBYSCORE salary -inf 2500 withscores
1) "kuangshen"
2) "500"
3) "xiaohong"
4) "2500"
############################################################
# ZREVRANGE、ZREVRANGEBYSCORE
127.0.0.1:6379> zadd  salary 2500 xiaoming 
(integer) 1
127.0.0.1:6379> zadd  salary 1800 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 1500 zhangsan
(integer) 1
127.0.0.1:6379> ZREVRANGE salary 0 -1 # 显示全部用户,从大到小
1) "xiaoming"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf # 从大到小显示
1) "xiaoming"
2) "xiaohong"
3) "zhangsan"
127.0.0.1:6379> ZREVRANGEBYSCORE salary +inf -inf withscores # 从大到小显示全部用户并且附带成绩
1) "xiaoming"
2) "2500"
3) "xiaohong"
4) "1800"
5) "zhangsan"
6) "1500"
############################################################
# ZCOUNT
127.0.0.1:6379> zcount salary 1500 3000 # 读取指定区间的用户数量
(integer) 3

总结:

  • ZRANGE 后面只能跟 0 -1
  • ZRANGEBYSCORE后面只能跟 范围,例如: -inf +inf

十一、三大特殊数据类型

Geospatial地理位置

八命令:

# geoadd 添加地理位置
#规则 两级无法直接添加,我们一般会下载城市数据,直接通过java程序一次性导入
#参数 key 值(经度 纬度 名称) 
127.0.0.1:6379> geoadd china:city 116.40 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai
(integer) 1
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shengzhen
(integer) 2
127.0.0.1:6379> geoadd china:city 120.16 30.24 hangzhou
(integer) 1
127.0.0.1:6379>
############################################################
#geopos
127.0.0.1:6379> GEOPOS china:city beijing # 获取地理位置坐标
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> GEOPOS china:city shanghai
1) 1) "121.47000163793563843"
   2) "31.22999903975783553"
# GEODIST
两人之间的距离

单位:

  • m表示单位为米
  • km表示单位为千米
  • mi表示单位为英里
  • ft表示单位为英尺
# GEODIST
127.0.0.1:6379> GEODIST china:city beijing shanghai # 北京到上海的直径距离
"1067378.7564"
127.0.0.1:6379> GEODIST china:city beijing shanghai km
"1067.3788"
127.0.0.1:6379> GEODIST china:city beijing chongqing km
"1464.0708"
# GEORADIUS [WITHCOORD] [WITHDIST]
127.0.0.1:6379> GEORADIUS china:city 110 30 1000 km # 获取处在某个具体坐标指定半径内的地理位置
1) "chongqing"
2) "xian"
3) "shengzhen"
4) "hangzhou"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km
1) "chongqing"
2) "xian"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist
1) 1) "chongqing"
   2) "341.9374"
2) 1) "xian"
   2) "483.8340"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withcoord
1) 1) "chongqing"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) 1) "108.96000176668167114"
      2) "34.25999964418929977"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 1
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> GEORADIUS china:city 110 30 500 km withdist withcoord count 2
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "xian"
   2) "483.8340"
   3) 1) "108.96000176668167114"
      2) "34.25999964418929977"
# GEOHASH
该命令返回11个字符的Geohash字符串
# 将二维的经纬度转换为一维的字符串,如果两个字符串越接近,name则距离越近
127.0.0.1:6379> GEOHASH china:city beijing chongqing
1) "wx4fbxxfke0"
2) "wm5xzrybty0"
GEO 底层的实现原理其实就是Zset,我们可以使用Zset命令来操作geo
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> ZREM china:city beijing
(integer) 1
127.0.0.1:6379> ZRANGE china:city 0 -1
1) "chongqing"
2) "xian"
3) "shengzhen"
4) "hangzhou"
5) "shanghai"

Hyperloglog基数统计

Redis2.8.9版本就更新了Hyperloglog数据结构

Redis Hyperloglog基数统计的算法

优点:占用的内存是固定,计算2^64不同的元素的技术,只需要费12KB内存,如果要从内存角度来比较,Hyperloglog首选

网页的UV

传统方式:set保存用户的id,然后统计set中的元素数量作为标准判断

0.81%错误率,统计UV任务,可以忽略不计

# PFADD、PFCOUNT、PFMERGE
测试
127.0.0.1:6379> pfadd mykey a b c d e f g h i # 创建第一组元素 key
(integer) 1
127.0.0.1:6379> pfadd mykey2 i j z x c v b n m 
(integer) 1
127.0.0.1:6379> pfcount mykey2 # 统计mykey2元素的基数数量
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并两组 mykey mykey2 => mykey3 并集
OK
127.0.0.1:6379> pfcount mykey3 # 统计并集中的元素数量
(integer) 15

要使用Hyperloglog,前提是允许容错!

Bitmaps位图场景

位存储
  • 统计用户信息,活跃,未活跃,登录,未登录,打卡,365打卡,两个状态的,都可以使用Bitmapes!

Bitmaps位图,数据结构!都是操作二进制位来进行记录,就只有0和1两个状态

  • 合理地使用操作位能够有效地提高内存使用率和开发效率

(1)Bitmaps本身不是一种数据类型,实际上它就是字符串(key-value),但是它可以对字符串的位进行操作

(2)Bitmaps单独提供了一套命令,所以在Redis中使用Bitmaps和使用字符串的方法不太相同。可以把Bitmaps想象成一个以位为单位的数组,数组的每个单元只能存储1和0,数组的下标在Bitmaps中叫做偏移量

365天=365bit 1字节=8bit 46个字节左右

测试
# SETBIT、GETBIT
127.0.0.1:6379> setbit sign 0 1 # 设置第一天打卡
(integer) 0
127.0.0.1:6379> setbit sign 1 0
(integer) 0
127.0.0.1:6379> setbit sign 2 1
(integer) 0
127.0.0.1:6379> setbit sign 3 0
(integer) 0
127.0.0.1:6379> setbit sign 3 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> getbit sign 3 # 查看第三天是否有打卡
(integer) 1
# bitop

(1)格式

bitop and(or/not/xor) <destkey> [key....]

bitop是一个复合操作,它可以做多个Bitmaps的and(交集)、or(并集)、not(非)。

xor(异或)操作并将结果保存在destkey中

  • 注:

很多用户的id以一个指定的id开头,直接将用户的id和Bitmaps的偏移量对应势必会造成一定的浪费,通常的做法是每次做setbit操作时将用户id减去这个指定数字。

在第一次初始化Bitmaps时,假如偏移量非常大,那么整个初始化过程执行会比较慢,可能会造成Redis阻塞。

十二、Redis实现乐观锁

监控 Watch(面试常问!)

悲观锁:

  • 很悲观,认为什么时候都会出问题,无论做什么都会加锁
  • 传统的关系数据库里边就用到这种锁机制,比如行锁、表锁等,读锁、写锁等,都是在做操作之前先上锁
  • 实现方法:在sql后面加上 for update或者for update nowait

乐观锁:

  • 很乐观,认为什么时候都不会出现问题,所以不会上锁,更新数据的时候去判断一下,在此期间是否有人修改过这个数据,
  • 获取version
  • 更新的时候比较数据库的version
  • 乐观锁于多读的应用类型,这样可以提高吞吐量,Redis就是利用这种check-and-set机制实现事务的。
Redis监视测试

两种之间的差别就是,在并发执行的时候,悲观锁会阻塞,而乐观锁会不执行

执行成功:

127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 0
OK
127.0.0.1:6379> watch money # 监视 money 对象
OK
127.0.0.1:6379> multi # 事务正常结束,数据期间没有发生变动,这个时候就正常执行成功
OK
127.0.0.1:6379(TX)> DECRBY money 20
QUEUED
127.0.0.1:6379(TX)> INCRBY out 20
QUEUED
127.0.0.1:6379(TX)> exec
1) (integer) 80
2) (integer) 20

执行失败:

客户端1:

127.0.0.1:6379> watch money # 监视money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> DECRBY money 10
QUEUED
127.0.0.1:6379(TX)> INCRBY out 10
QUEUED
127.0.0.1:6379(TX)> exec # 执行之前,另外一个线程,修改了我们的值,这个时候,就会导致事务执行失败
(nil)
127.0.0.1:6379> get money
"1000"
127.0.0.1:6379> 

客户端2:

[fangyupeng@hadoop102 ~]$ redis-cli -p 6379
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK

原因:watch到脏数据,需要unwatch后再watch

注意:实现乐观锁开启监视必须在开启事务之前!

十三、Redis基本事务操作

Redis事务本质:一组命令的集合,一个事务中的所有命令都会被序列化,在事务执行过程中,会按照顺序执行

一次性、顺序性、排他性,执行一些列的命令

Redis事务的主要作用串联多个命令,防止别的命令插队

  • 单独的隔离操作,事务执行过程中不会被客户端发送来的命令请求所打断
  • Redis事务没有隔离级别的概念

所有的命令在事务中,并没有直接被执行,只有发起执行命令的时候才会被执行,exec

  • Redis单挑命令是保证原子性的,但是事务不保证原子性

redis事务:

  1. 开启事务(Multi)
  2. 命令入队(....)
  3. 执行事务(exec)
正常执行事务
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
放弃事务
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> DISCARD
OK
127.0.0.1:6379> get k4
(nil)
编译型异常(代码有问题,命令有错),事务中所有的命令都不会执行
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v1
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> getset k3 # 错误的命令
(error) ERR wrong number of arguments for getset command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> set k5 v5
QUEUED
127.0.0.1:6379(TX)> exec # 所有的命令都不会执行
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k5
(nil)
运行时异常(1/0),如果事务队列中存在语法性,那么执行命令的时候,其他命令可以正常执行,错误命令抛出异常
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi # 开启事务
OK
127.0.0.1:6379(TX)> incr k1 # 失败
QUEUED
127.0.0.1:6379(TX)> set k2 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v3
QUEUED
127.0.0.1:6379(TX)> get k3
QUEUED
127.0.0.1:6379(TX)> exec # 事务可以执行
1) (error) ERR value is not an integer or out of range
2) OK
3) OK
4) "v3"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"
127.0.0.1:6379> get k1
"v1"

实现秒杀

  • 使用乐观锁(库存遗留问题)
  • 使用 lua(解决库存遗留问题)

库存遗留问题:第一个用户成功秒杀时,并发时的其他用户都秒杀失败,就算存在库存,lua作为嵌入式语言,可以使得整个事务具有原子性,不会被其他命令插队,不存在版本号不同问题

十四、通过Jedis操作Redis

依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>3.6.0</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.62</version>
</dependency>

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-nop</artifactId>
    <version>1.7.2</version>
</dependency>
  • testList
package com.zhkucst;

import redis.clients.jedis.Jedis;

public class TestList   
    public static void main(String[] args) 
        Jedis jedis = new Jedis("192.168.10.102",6379);

        jedis.flushDB();
        System.out.println("======添加一个List======");
        jedis.lpush("collections","ArrayList", "Vector","Stack", "HashMap","WeakHashMap","LinkedHashMap");
        jedis.lpush("collections","HashSet");
        jedis.lpush("collections","TreeSet");
        jedis.lpush("collections","TressMap");
        System.out.println("collection的内容:" + jedis.lrange("collections",0, -1));
        System.out.println("============================");
        System.out.println("删除指定元素个数:" + jedis.lrem("collections",2, "HashMao"));
        System.out.println("删除下表0-3区间之外的元素:" + jedis.ltrim("collections",0,3));
        System.out.println("collections的内容:" + jedis.lrange("collections",0, -1));
        System.out.println("collections列表出栈(左端):" + jedis.lpop("collections"));
        System.out.println("collections的内容:" + jedis.lrange("collections",0, -1));
        System.out.println("collections列表出栈(右端 ):" + jedis.rpop("collections"));
        System.out.println("collections的内容:" + jedis.lrange("collections",0, -1));
        System.out.println("修改collections列表指定下标1的内容:" + jedis.lset("collections",1,"UpdateHashSet"));
        System.out.println("collections的内容:" + jedis.lrange("collections",0, -1));
        System.out.println("============================");
        System.out.println("collections的长度:" + jedis.llen("collections"));
        System.out.println("获取collections下标为2的元素:" + jedis.lindex("collections",2));
        System.out.println("============================");
        jedis.lpush("sortedList", "3","6","2","8","7","4");
        System.out.println("sortedList排序前:" + jedis.lrange("sortedList", 0, -1));
        System.out.println(jedis.sort("sortedList"));
        System.out.println("sortedList排序后:" + jedis.lrange("sortedList", 0, -1));

    
  • testSet
package com.zhkucst;

import redis.clients.jedis.Jedis;

public class TestSet 
    public static void main(String[] args) 
        Jedis jedis = new Jedis("192.168.10.102", 6379);
        jedis.flushDB();
        System.out.println("======向集合中添加元素(不重复)======");
        System.out.println(jedis.sadd("eleSet","e1","e2","e3","e4","e0","e8","e7","e5"));
        System.out.println(jedis.sadd("eleSet","e6"));
        System.out.println(jedis.sadd("eleSet","e6"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("删除一个元素e0:" + jedis.srem("eleSet","e0"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("删除两个元素e7,e6:" + jedis.srem("eleSet","e7", "e6"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("随机地移除集合中的一个元素:" + jedis.spop("eleSet"));
        System.out.println("随机地移除集合中的一个元素:" + jedis.spop("eleSet"));
        System.out.println("eleSet的所有元素为:" + jedis.smembers("eleSet"));
        System.out.println("eleSet集合中包含的元素个数:" + jedis.scard("eleSet"));
        System.out.println("e3是否在eleSet集合中:" + jedis.sismember("eleSet","e3"));
        System.out.println("e1是否在eleSet集合中:" + jedis.sismember("eleSet","e1"));
        System.out.println("e5是否在eleSet集合中:" + jedis.sismember("eleSet","e5"));
        System.out.println("============================");
        System.out.println("eleSet1:" + jedis.sadd("eleSet1","e1","e2","e4","e3","e0","e8","e7","e5"));
        System.out.println("eleSet2:" + jedis.sadd("eleSet2","e1","e2","e4","e3","e0","e8"));
        System.out.println("eleSet1的元素:" + jedis.smembers("eleSet1"));
        System.out.println("eleSet2的元素:" + jedis.smembers("eleSet2"));
        System.out.println("将eleSet1中删除e1并存入eleSet3:" + jedis.smove("eleSet1","eleSet3","e1"));
        System.out.println("将eleSet1中删除e2并存入eleSet3:" + jedis.smove("eleSet1","eleSet3","e2"));
        System.out.println("eleSet1的元素:" + jedis.smembers("eleSet1"));
        System.out.println("eleSet3的元素:" + jedis.smembers("eleSet3"));
        System.out.println("======集合运算======");
        System.out.println("eleSet1的元素:" + jedis.smembers("eleSet1"));
        System.out.println("eleSet2的元素:" + jedis.smembers("eleSet2"));
        System.out.println("eleSet1与eleSet2的交集:" + jedis.sinter("eleSet1", "eleSet2"));
        System.out.println("eleSet1与eleSet2的并集:" + jedis.sunion("eleSet1","eleSet2"));
        System.out.println("eleSet1与eleSet2的差集:" + jedis.sdiff("eleSet1","eleSet2"));

    
  • testHash
package com.zhkucst;

import redis.clients.jedis.Jedis;

import java.util.HashMap;
import java.util.Map;

public class TestHash 
    public static void main(String[] args) 
        Jedis jedis = new Jedis("192.168.10.102", 6379);
        jedis.flushDB();
        Map<String,String> map = new HashMap<>();
        map.put("k1","v1");
        map.put("k2","v2");
        map.put("k3","v3");
        map.put("k4","v4");

        jedis.hmset("hash",map);
        jedis.hset("hash","key5","value5");
        System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
        System.out.println("散列hash的所有键:"  + jedis.keys("hash"));
        System.out.println("散列hash的所有值:" + jedis.hvals("hash"));
        System.out.println("将k6保存的值加上一个整数,如果k6不存在则添加k6:" + jedis.hincrBy("hash","key6",6));
        System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
        System.out.println("将k6保存的值加上一个整数,如果k6不存在则添加k6:" + jedis.hincrBy("hash","key6",3));
        System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
        System.out.println("删除一个或多个键值对:" + jedis.hdel("hash","k2"));
        System.out.println("散列hash的所有键值对为:" + jedis.hgetAll("hash"));
        System.out.println("散列表hash中键值对个数:" + jedis.hlen("hash"));
        System.out.println("判断hash中是否存在k2:" + jedis.hexists("hash","k2"));
        System.out.println("判断hash中是否存在k3:" + jedis.hexists("hash","k3"));
        System.out.println("获取hash中k3的值:" + jedis.hmget("hash","k2"));
        System.out.println("获取hash中k2、k3的值:" + jedis.hmget("hash","k2","k3"));

    

十五、通过Jedis操作事务

TestTX.jar

package com.zhkucst;

import com.alibaba.fastjson.JSONObject;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TextTX 
    public static void main(String[] args) 
        Jedis jedis = new Jedis("192.168.10.102",6379);

        JSONObject jsonObject = new JSONObject();
        jsonObject.put("hello","world");
        jsonObject.put("name","zhangsan");

        jedis.flushDB();
        System.out.println("开启事务...");
        Transaction multi = jedis.multi();

        //jedis.watch();//开启监视
        // .....
        //jedis.unwatch();关闭监视
        try 
            String result = jsonObject.toJSONString();

            multi.set("user1",result);
            multi.set("user2",result);
            int i = 1/0;
            multi.exec();
         catch (Exception exception) 
            multi.discard();
            exception.printStackTrace();//放弃事务
         finally 
            System.out.println(jedis.get("user1"));
            System.out.println(jedis.get("user2"));
            jedis.close();//关闭连接
        

    

十六、自定义RedisTemplate

通过自动配置类源码:

/*
 * Copyright 2012-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.boot.autoconfigure.data.redis;

import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

/**
 * @link EnableAutoConfiguration Auto-configuration for Spring Datas Redis support.
 *
 * @author Dave Syer
 * @author Andy Wilkinson
 * @author Christian Dupuis
 * @author Christoph Strobl
 * @author Phillip Webb
 * @author Eddú Meléndez
 * @author Stephane Nicoll
 * @author Marco Aust
 * @author Mark Paluch
 * @since 1.0.0
 */
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import( LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class )
public class RedisAutoConfiguration 

   @Bean
   @ConditionalOnMissingBean(name = "redisTemplate")
   @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
   public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) 
      RedisTemplate<Object, Object> template = new RedisTemplate<>();
      template.setConnectionFactory(redisConnectionFactory);
      return template;
   

   @Bean
   @ConditionalOnMissingBean
   @ConditionalOnSingleCandidate(RedisConnectionFactory.class)
   public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) 
      StringRedisTemplate template = new StringRedisTemplate();
      template.setConnectionFactory(redisConnectionFactory);
      return template;
   

可知,Redis可以自定义配置类,来实现序列化

  • 通过接口 public interface RedisSerializer<T> 的实现类分析,有几种序列化方式
  • pojo类实现接口Serializable以实现能够序列化对象
  • 自定义RedisTemplate
package com.zhkucst.conf;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig 

    @Bean
    //抑制所有警告
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) 
        //一般使用<String, Object>
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory);

        //json序列化设置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);

        //String序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        //key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        //hash的key采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        //value采用json的序列化方式
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //hash的value采用json序列化方式
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        //其他未自定义的属性通过该方法配置默认的PropertiesSet
        template.afterPropertiesSet();

        return template;
    

  • 测试
    @Test
    public void test() throws JsonProcessingException 
        User user = new User("张三",23);
        //String jsonUser = new ObjectMapper().writeValueAsString(user);

        redisTemplate.opsForValue().set("user", user);

        System.out.println(redisTemplate.opsForValue().get("user"));
    

十七、Redis配置文件详解

单位

1.配置文件unit单位对大小写不敏感,只支持byte,不支持bit

包含

好比我们学习Spring、Import、include

网络

bind 127.0.0.1 # 绑定的id
protected-mode yes # 保护模式
port 6379 # 端口设置

通用GENERAL

daemonize yes # 以守护进程的方式进行,默认是no,我们需要自己开启为yes
pidfile /var/run/redis_6379.pid #如果以后台的方式运行,我们需要指定一个pid文件
# 日志
# Specify the server verbosity level.
# This can be one of:
# debug (a lot of information, useful for development/testing)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably) 生产模式
# warning (only very important / critical messages are logged)
loglevel notice # 日志文件的通知等级
logfile "/usr/local/bin/klog" # 日志的文件位置名
databases 16 # 数据库的数量, 默认是16个数据库
always-show-logo no # 是否总显示logo

快照

持久化,在规定时间内,执行了多少次操作,则会持久化到文件,rdb.aof

redis是内存数据库,如果没有持久化,那么数据数据断电及失

# 如果900s内,如果至少有1 key进行了修改,我们及进行持久化操作
save 900 1
# 如果300s内,如果至少有10 key进行了修改,我们及进行持久化操作
save 300 10
# 如果60s内,如果至少有10000 key进行了修改,我们及进行持久化操作
save 60 10000
stop-writes-on-bgsave-error yes # 持久化如果出错,是否需要继续工作
rdbcompression yes # 是否压缩rdb文件,需要消耗一些cpu
rdbchecksum yes # 保存rdb文件的时候,进行错误的检查校验
#dir ./ # rdb 文件保存的目录,默认为启动服务时的当前路径,最好改成绝对路径
 dir /usr/local/bin/krdb

REPLICATION复制,主从复制

详见十九篇:Redis主从复制

SECURITY 安全

可以在这里设置redis的密码,默认是没有密码

# root
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> config get requirepass # 获取redis的密码
1) "requirepass"
2) ""
127.0.0.1:6379> config set requirepass "123456" # 设置redis的密码
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"
# localhost
127.0.0.1:6379> config get requirepass
(error) NOAUTH Authentication required.
127.0.0.1:6379> ping
(error) NOAUTH Authentication required.
127.0.0.1:6379> auth 123456 # 使用密码进行登录
OK
127.0.0.1:6379> config get requirepass
1) "requirepass"
2) "123456"

限制CLIENTS

maxclients 10000MEMORY MANAGEMENT 内存处理策略
maxmemory <bytes> # redis配置最大内存容量
maxmemory-policy noeviction # 内存到达上限后的处理策略
1、volatile-lru -> Evict using approximated LRU(Least Recently Used), only keys with an expire set.
  # 只对设置了过期时间的key 进行LRU(默认值)
2、allkeys-lru -> Evict any key using approximated LRU.
  # 删除LRU算法的key
3、volatile-lfu -> Evict using approximated LFU(Least Frequently Used), only keys with an expire set.
  # 只对设置了过期时间的key 进行LFU
4、allkeys-lfu -> Evict any key using approximated LFU.
  # 删除LFU算法的key
5、volatile-random -> Remove a random key having an expire set.
  # 随机删除即将过期的key
6、allkeys-random -> Remove a random key, any key.
  # 随机删除
7、volatile-ttl -> Remove the key with the nearest expire time (minor TTL)
  # 删除即将过期的
8、noeviction -> Dont evict anything, just return an error on write operations.
  # 永不过期,返回错误

# LRU means Least Recently Used
# LFU means Least Frequently Used

APPEND ONLY MODE aof模式

appendonly no # 默认是不开启aof模式,默认是是rdb方式持久化,在大部分所有的情况下,rdb够用
appendfilename "appendonly.aof" # 持久化的文件的名字

# appendfsync always # 每次修改都会sync,消耗性能
appendfsync everysec # 每秒执行 sync, 可能会丢失1s的数据
# appendfsync no # 不执行 sync, 这个时候操作系统自己同步数据,速度更快 

十八、持久化-RDB操作

什么是RDB?

查找文件名:root@hadoop102 bin# find / -name dump.rdb

查找配置文件中的配置,命令:config get dir

save 和 bgsave

save:save时只管保存,其他不管,全部阻塞,手动保存,不建议

bgsave:Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求

生成RDB的方式:

  • 满足save规则,会自动触发rdb规则
  • FLUSHALL命令,触发rdb规则
  • shutdown,退出redis,产生rdb文件

恢复RDB文件

  • 步骤
  1. 只需将rdb文件放在redis启动目录,即配置文件中指定的dir路径:
127.0.0.1:6379> config get dir
1) "dir"
2) "/usr/local/bin" # 如果在该目录下存在dump.rdb,启动redis会自动恢复其中的数据,放到内存中
  1. redis启动后会自动检查demp.rdb恢复其中的数据
  • 优点
  1. 适合大规模的数据恢复
  2. 对数据的完整性要求不高
  3. 节省磁盘空间
  4. 恢复速度快
  • 缺点
  1. 需要一定的时间间隔进程操作,如果redis意外死机了,这个最后一次修改的数据就没有了。(因为rdb持久化操作是先保存在了临时文件中,这是宕机那临时文件也就不能替换原来的rdb文件,导致最后一次持久化操作数据丢失)
  2. fork进程的时候,会占用一定的内容空间,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  3. 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能

其他配置

  • stop-writes-on-bgsave-error

当Redis无法写入磁盘的话,直接关掉Redis的写操作,推荐yes

  • rdbcompression压缩文件

对于存储到磁盘中的快照,可以设置是否进行压缩存储,如果是的话,redis会采用LZF算法进行压缩,默认是yes

  • rdbchecksum检查完整性

在存储快照后,还可以让redis使用CRC64算法来进行数据校验

十九、持久化-AOF操作

什么是AOF?(Append Only File)

aof保存的是 appendonly.aof文件

默认是不开启的,我们需要手动配置,我们只需将 appendonly 改为 yes 就开启了 aof

如果这个aof文件有错误,这时候redis是启动不起来的,我们需要修复这个aof文件

redis给我们提供了一个工具 redis-check-aof

appendonly no # 默认是不开启aof模式,默认使用rdb方式持久化,在大部分情况下,有rdb就够了
appendfilename "appendonly.aof" # 持久化的文件名

appendfsync always # 每次修改都会sync ,消耗性能
appendfsync everysec # 每秒执行一次 sync,可能会丢失1s的数据
appendfsync no # 不执行sync,这个时候操作系统自己同步数据,速度更快

AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)

AOF和RDB所在的目录在同一个位置

开启AOF进行备份时要先关闭redis或开启后重启redis,配置才会生效

重启redis便会加载aof文件进行恢复,文件损坏会启动失败

修复AOF文件

redis-check-aof --fix krdb/appendonly.aof

重写规则说明

aof默认就是文件的无限追加,文件会越来越大

no-appendfsync-on-rewrite no

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb # 如果aof文件大于64m,太大了,fork一个新的进程来将我们的文件进行重写

优点和缺点

  • 优点
  1. 每一次修改都同步,文件的完整性会更加好
  2. 每秒同步一次,可能会丢失一秒的数据
  3. 从不同步,效率最高
  • 缺点
  1. 相对于数据文件来说,aof远远大于rdb,恢复的速度比rdb慢
  2. aof运行效率要比rdb慢,所以我们redis默认的配置就是rdb持久化

二十、Redis主从复制

概念

主从复制,是指将一台redis服务器的数据,复制到其他Redis服务器,前者称为主节点(master/leader),后者称为从节点(slaver/follower),数据的复制是单向的,只能从主节点到从节点,Master以写为主,Slaver以读为主。

默认情况下,每套Redis服务器都是主节点,且一个主节点可以有多个从节点(或没有从节点),但一个从节点只能有一个主节点。

三种实现方式:

  • 一主二仆
  • 薪火相传
  • 反客为主

主从复制的作用主要包括: