Redis基础知识

Redis

主要参考:

BiliBili狂神说Redis基础

Nosql概述

为什么需要使用Nosql?
单机Mysql:

App->DAL->MySQL

  1. 数据量太大,一个机器放不下
  2. 数据的索引(B+ Tree),一个机器内存放不下
  3. 访问量(读写混合),一个服务器承受不了
Memcached+MySQL+垂直拆分(读写分离)

网站80%的情况是在执行读操作,每次都去查询数据库便会相当的麻烦。为此希望减轻数据的压力,可以使用缓存来保证效率

分库分表+水平拆分+MySQL集群

数据库本质(读+写)

MyISAM:表锁,十分影响效率,高并发下会出现严重的锁问题

Innodb:行锁

=>使用分库分表解决写的压力

MySQL: 表分区/集群

最近年代

MySQL等关系数据库开始不够使用,现代数据量大,变化很快

为什么要使用Nosql

用户的个人信息,社交网络,地理位置。用户本身产生的数据,用户日志等爆发式增长->MySQL此时性能受限,无法使用,使用NoSQL则可以很好处理以上的情况

什么是NoSQL

NoSQL=Not Only SQL

泛指非关系性数据库。

传统关系型数据库很难对付web2.0时代,尤其是超大规模的高并发社区,暴露出许多难以克服的问题。NOSQL在当今大数据环境下变得十分迅速,Redis是当下发展最快的,且必须要掌握的数据库相关技术

关系型数据库:表格,行,列(POI)

很多数据并不需要一个固定的格式,不需要多余的操作就可实现横向扩展。类似与Map,使用键值对来进行控制

NoSQL特点

解耦

  1. 方便拓展(数据之间没有关系,很好扩展)
  2. 大数据量,高性能(Redis一秒读11万,写8万,NoSQL的缓存记录级,是一种细粒度的缓存,性能会比较高)
  3. 数据类型的多样性(不需要实现事先设计数据库,随取随用)

传统关系型数据库(RDBMS)和NoSQL的区别

传统的RDBMS
-结构化组织
-SQL
-数据和关系都存在单独的表中
-操作数据,数据定义语言
-严格的一致性
-基础的事务操作
-...
redis-benchmark性能测试
redis-benchmark -h localhost -p 6379 -c 100 -n 100000
Redis基础知识

Redis默认有16个数据库

默认使用第0个

可以使用select进行切换数据库

切换数据库
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> dbsize
(integer) 0
127.0.0.1:6379[3]> 

127.0.0.1:6379[7]> dbsize
(integer) 0
127.0.0.1:6379[7]> select 3
OK
127.0.0.1:6379[3]> get name

查看所有的key
127.0.0.1:6379[3]> keys *
1) "name"

清空当前数据库
127.0.0.1:6379[3]> flushdb
OK
127.0.0.1:6379[3]> keys *
(empty array)

清空所有数据库
127.0.0.1:6379> flushall
OK
127.0.0.1:6379> keys*

思考:为什么redis选择6379端口

Redis是单线程

redis是基于内存操作。CPU并不是redis性能瓶颈,Redis的瓶颈是根据机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程

Redis是使用C语言编写的,官方提供的数据为100000+的QPS,完全不比同样使用key-value的Memecache差

Redis单线程还那么快
  1. 误区1:高性能服务器一定是多线程的
  2. 误区2:多线程(CPU上下文切换)一定比单线程效率高

核心:

​ redis将所有的数据全部放在内存中,所以说使用单线程去操作效率是最高的。由于多线程之间会存在CPU上下文切换,会带来耗时操作。对于内存系统而言,如果没有上下文切换效率就是最好的!多次读写都是在一个CPU上的,在内存情况下,这就是最佳的方案

redis数据类型

Redis-key
127.0.0.1:6379> set name ylq
OK
127.0.0.1:6379> keys *
1) "name"
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> EXISTS name #检查key是否存在
(integer) 1
127.0.0.1:6379> EXISTS sex 
(integer) 0

127.0.0.1:6379> move name 1 #移除当前的库
(integer) 1
127.0.0.1:6379> keys *
1) "age"

127.0.0.1:6379> EXPIRE name 10 #设置key的过期时间
(integer) 1
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> get name 
(nil)
127.0.0.1:6379> 

#查看当前key的命令
127.0.0.1:6379> type name
string
127.0.0.1:6379> type age
string
String类型
127.0.0.1:6379> append key1 hello #往某个key后面添加值,如果当前key不存在,相当于添加字符串
(integer) 7
127.0.0.1:6379> get hello
(nil)
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen keys1 #获取字符串的长度
(integer) 0
127.0.0.1:6379> strlen key1
(integer) 7
127.0.0.1:6379> append key1 10000
(integer) 12
127.0.0.1:6379> strlen key1
(integer) 12
127.0.0.1:6379> 

设置步长,指定增量和减量
127.0.0.1:6379> set viems 0
OK
127.0.0.1:6379> get views
(nil)
127.0.0.1:6379> get viems
"0"
127.0.0.1:6379> incr viems
(integer) 1
127.0.0.1:6379> get viems
"1"
127.0.0.1:6379> decr viems
(integer) 0
127.0.0.1:6379> decr viems
(integer) -1
127.0.0.1:6379> get viems
"-1"
127.0.0.1:6379> incrby viems 10
(integer) 9
127.0.0.1:6379> incrby viems 10
(integer) 19
127.0.0.1:6379> get viems
"19"
127.0.0.1:6379> decrby viems 5
(integer) 14
127.0.0.1:6379> decrby viems 5
(integer) 9
127.0.0.1:6379> get viems
"9"
127.0.0.1:6379> 

获取字符串范围
127.0.0.1:6379> clear
127.0.0.1:6379> set key2 1
OK
127.0.0.1:6379> append key2 hellonihao
(integer) 11
127.0.0.1:6379> get name key2
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> get  key2
"1hellonihao"
127.0.0.1:6379> getrange key2 0 4
"1hell"
127.0.0.1:6379> getrange key2 0 -1
"1hellonihao"

替换
127.0.0.1:6379> set key3 adcdefg
OK
127.0.0.1:6379> get key3
"adcdefg"
127.0.0.1:6379> setrange key3 1 xx
(integer) 7
127.0.0.1:6379> get key3
"axxdefg"

如果当前值存在设置/当前值不存在设置(过期时间)

setex(set with expire)

setnx(set with noexpire)–在分布式锁中常常使用

127.0.0.1:6379> setex key4 30 hello
OK
127.0.0.1:6379> ttl key4
(integer) 26
127.0.0.1:6379> get key4
"hello"
127.0.0.1:6379> setnx mykey "redis"
(integer) 1
127.0.0.1:6379> key *
(error) ERR unknown command 'key', with args beginning with: '*' 
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key3"
127.0.0.1:6379> ttl key3
(integer) -1
127.0.0.1:6379> keys *
1) "mykey"
2) "key2"
3) "key3"
127.0.0.1:6379> ttl key4
(integer) -2
127.0.0.1:6379> setnc mykey "mongodb"
(error) ERR unknown command 'setnc', with args beginning with: 'mykey' 'mongodb' 
127.0.0.1:6379> setnx mykey "mongodb"
(integer) 0
127.0.0.1:6379> get mykey
"redis"

一次性设置多个值,和多个值

msetnx是一个原子性操作,要么一块成功,要么一块失败

127.0.0.1:6379> clear
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3
OK
127.0.0.1:6379> keys *
1) "k1"
2) "key2"
3) "mykey"
4) "k3"
5) "key3"
6) "k2"
127.0.0.1:6379> mget k1 k2 k3
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 v4 k4 v5
(integer) 0
127.0.0.1:6379> get k4
(nil)
127.0.0.1:6379> 

设置对象

key:user:{id}:{filed}

127.0.0.1:6379> clear
127.0.0.1:6379> mset user:1:name zhangshan user:2:age 2
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangshan"
2) (nil)
127.0.0.1:6379> mget user:1:name user:2:age
1) "zhangshan"
2) "2"

getset命令 先get后set

如果不存在值,则返回nil,如果存在值,返回原来的值

并赋新的值

127.0.0.1:6379> getset db redis
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> get db mongodb
(error) ERR wrong number of arguments for 'get' command
127.0.0.1:6379> getset db mongodb
"redis"
127.0.0.1:6379> get db
"mongodb"
127.0.0.1:6379> 

String 类似的使用产经,Value除了我们的字符串还可以是我们的数字

  • 计数器
  • 统计多单位的数量
  • 对象缓存存储
List

列表

redis中可以是实现栈,队列,循环队列

所有的List命令都是以L开头的

列表插入元素
127.0.0.1:6379> lpush list one
(integer) 1
127.0.0.1:6379> lpush list teo
(integer) 2
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "teo"
3) "one"
127.0.0.1:6379> lrange list 0 2
1) "three"
2) "teo"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "teo"

变换添入list的方向
127.0.0.1:6379> rpush list five
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "teo"
3) "one"
4) "five"

redis不区分大小写

移除list元素
127.0.0.1:6379> lpop list
"three"
127.0.0.1:6379> rpop lis
(nil)
127.0.0.1:6379> rpop list
"five"
127.0.0.1:6379> lrange list 0 -1
1) "teo"
2) "one"
127.0.0.1:6379> 

获得list的下标
127.0.0.1:6379> lindex list 1
"one"
127.0.0.1:6379> lindex list 2
(nil)
127.0.0.1:6379> lindex list 0
"teo"

获取list的长度
127.0.0.1:6379> llen list
(integer) 2
127.0.0.1:6379> 

移除指定的值
127.0.0.1:6379> lrem list 1 soft
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "arc"
2) "arc"
3) "teo"
127.0.0.1:6379> lrem list 2 arc
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "teo"
127.0.0.1:6379> 

截取list中的一部分
127.0.0.1:6379> rpush list hello
(integer) 1
127.0.0.1:6379> rpush list hello2
(integer) 2
127.0.0.1:6379> rpush list hello3
(integer) 3
127.0.0.1:6379> rpush list hello4
(integer) 4
127.0.0.1:6379> lrange list 0 -1
1) "hello"
2) "hello2"
3) "hello3"
4) "hello4"
127.0.0.1:6379> ltrim list 1 2
OK
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello3"

rpoplpush 移除列表的最后一个元素最后添加到一个新的列表中
127.0.0.1:6379> lrange mylist 0 -1
1) "yes"
2) "yes1"
3) "yes2"
127.0.0.1:6379> lrange list 0 -1
1) "hello2"
2) "hello3"
127.0.0.1:6379> rpoplpush mylist list
"yes2"
127.0.0.1:6379> lrange list 0 -1
1) "yes2"
2) "hello2"
3) "hello3"
127.0.0.1:6379> lrange mylist 0 -1
1) "yes"
2) "yes1"

替换列表中的值
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> lset list 0 item
OK
127.0.0.1:6379> lrange list 0 0
1) "item"
127.0.0.1:6379> lset list 1 value2
(error) ERR index out of range

插入命令

将某个具体的值插入列表中某个值的元素前面/后面

127.0.0.1:6379> lrange list 0 -1
1) "item"
127.0.0.1:6379> linsert list before item yes
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "yes"
2) "item"
127.0.0.1:6379> linsert list after yes no
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "yes"
2) "no"
3) "item"

list实际上是一个链表

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

如果key存在,新增内容

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

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

Set

set中的值不能重复

添加/查看/判断某个元素是否在集合中
127.0.0.1:6379> sadd myset hellp
(integer) 1
127.0.0.1:6379> sadd myset hellp2
(integer) 1
127.0.0.1:6379> sadd myset hellp3
(integer) 1
127.0.0.1:6379> sadd myset hellp4
(integer) 1
127.0.0.1:6379> smembers myset
1) "hellp4"
2) "hellp2"
3) "hellp"
4) "hellp3"
127.0.0.1:6379> sismember hello
(error) ERR wrong number of arguments for 'sismember' command
127.0.0.1:6379> sismember myset hellp
(integer) 1
127.0.0.1:6379> sismember myset hell0
(integer) 0
127.0.0.1:6379> 

获取set集合中的个数
127.0.0.1:6379> scard myset
(integer) 4
127.0.0.1:6379> 
移除set集合中的指定元素
127.0.0.1:6379> srem myset hellp
(integer) 1
127.0.0.1:6379> smembers myset
1) "hellp4"
2) "hellp2"
3) "hellp3"

set无需不重复集合,抽随机
127.0.0.1:6379> srandmember myset
"hellp3"
127.0.0.1:6379> srandmember myset
"hellp3"
127.0.0.1:6379> srandmember myset
"hellp4"
127.0.0.1:6379> srandmember myset
"hellp3"
127.0.0.1:6379> 
127.0.0.1:6379> srandmember myset
"hellp4"
127.0.0.1:6379> 
127.0.0.1:6379> srandmember myset
"hellp3"
127.0.0.1:6379> srandmember myset
"hellp2"

127.0.0.1:6379> srandmember myset 2
1) "hellp4"
2) "hellp3"
127.0.0.1:6379> srandmember myset 2
1) "hellp2"
2) "hellp3"


删除指定的key/随机删除key
127.0.0.1:6379> smembers myset
1) "hellp2"
2) "hello"
3) "hellp3"
4) "hellp4"
5) "hello6"
6) "hello5"
127.0.0.1:6379> spop myset 3
1) "hellp3"
2) "hellp2"
3) "hello5"
127.0.0.1:6379> smembers myset
1) "hellp4"
2) "hello6"
3) "hello"

将一个指定的值,移动到另外一个set中
127.0.0.1:6379> smembers myset
1) "hellp4"
2) "hello6"
3) "hello"
127.0.0.1:6379> sadd myset2 world
(integer) 1
127.0.0.1:6379> sadd myset2 world3
(integer) 1
127.0.0.1:6379> smove myset myset2 hello
(integer) 1
127.0.0.1:6379> smember myset2 
(error) ERR unknown command 'smember', with args beginning with: 'myset2' 
127.0.0.1:6379> smembers myset2 
1) "hello"
2) "world3"
3) "world"

差集/交集/并集
127.0.0.1:6379> sadd set1 a
(integer) 1
127.0.0.1:6379> sadd set1 b
(integer) 1
127.0.0.1:6379> sadd set1 c
(integer) 1
127.0.0.1:6379> smembers set1
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> sadd set2 a
(integer) 1
127.0.0.1:6379> sadd set2 e
(integer) 1
127.0.0.1:6379> sadd set2 g
(integer) 1
127.0.0.1:6379> smembers set2
1) "a"
2) "g"
3) "e"
127.0.0.1:6379> sdiff set1 set2
1) "b"
2) "c"
127.0.0.1:6379> sinter set1 set2
1) "a"
127.0.0.1:6379> sunion set1 set2
1) "a"
2) "b"
3) "g"
4) "c"
5) "e"

Hash

Map集合,key-Map集合,这时候这个值是一个Map集合

本质和String类型没有什么太大区别,本质还是一个简单的key-value

读写操作
127.0.0.1:6379> clear
127.0.0.1:6379> hset myhash field1 hello
(integer) 1
127.0.0.1:6379> hget field1 hello
(nil)
127.0.0.1:6379> hget myhash field1
"hello"
127.0.0.1:6379> hmset myhsh field1 world field2 nihao
OK
127.0.0.1:6379> hmget myhsh field1 field2
1) "world"
2) "nihao"
127.0.0.1:6379> hgetall myhash
1) "field1"
2) "hello"
127.0.0.1:6379> hgetall myhsh
1) "field1"
2) "world"
3) "field2"
4) "nihao"
删除操作
127.0.0.1:6379> hdel myhsh field1
(integer) 1
127.0.0.1:6379> hgetall myhsh
1) "field2"
2) "nihao"
127.0.0.1:6379> 

获取哈希的长度
127.0.0.1:6379> hlen myhash
(integer) 2

判断是否存在
127.0.0.1:6379> hexists myhash field1
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0

只获得所有的key/value
127.0.0.1:6379> hkeys myhash
1) "field1"
2) "field2"
127.0.0.1:6379> hvals myhash
1) "hello"
2) "normal"

incr/decr
127.0.0.1:6379> hincrby myhash f2 10
(integer) 16
127.0.0.1:6379> hincrby myhash f2 -5
(integer) 11
127.0.0.1:6379> hsetnx myhash f6 hello
(integer) 1
127.0.0.1:6379> hsetnx myhash f6 world
(integer) 0
127.0.0.1:6379> hgetall myhash
1) "f"
2) "4"
3) "f1"
4) "5"
5) "f2"
6) "11"
7) "f6"
8) "hello"

hash变更的数据user name age ,尤其是用户信息之类的,经常变动的信息

hash更适合对象的存储,String更适合字符串存储

Zset(有序集合)

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

添加和查看对象
127.0.0.1:6379> zadd  myset 1 one
(integer) 1
127.0.0.1:6379> zadd  myset 2 two
(integer) 1
127.0.0.1:6379> zadd  myset 2 two 3 three 
(integer) 1
127.0.0.1:6379> zrange myset 0 -1
1) "one"
2) "two"
3) "three"

排序

降序排序和升序排序

127.0.0.1:6379> zadd salary 2500 xiaohong
(integer) 1
127.0.0.1:6379> zadd salary 5000 zhangshan
(integer) 1
127.0.0.1:6379> zadd salary 38000 xiaodong
(integer) 1
127.0.0.1:6379> zadd salary 3800 xiaojie
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf
1) "xiaohong"
2) "xiaojie"
3) "zhangshan"
4) "xiaodong"
127.0.0.1:6379> zrangebyscore salary 0 -1
(empty array)
127.0.0.1:6379> zrangebyscore salary +inf -inf
(empty array)
127.0.0.1:6379> zrangebyscore salary -inf +inf
1) "xiaohong"
2) "xiaojie"
3) "zhangshan"
4) "xiaodong"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores
1) "xiaohong"
2) "2500"
3) "xiaojie"
4) "3800"
5) "zhangshan"
6) "5000"
7) "xiaodong"
8) "38000"
127.0.0.1:6379> zrangebyscore salary -inf 3800 withscores
1) "xiaohong"
2) "2500"
3) "xiaojie"
4) "3800"
127.0.0.1:6379> zrevrangebyscore alary +inf -inf 
(empty array)
127.0.0.1:6379> zrevrangebyscore salary +inf -inf 
1) "xiaodong"
2) "zhangshan"
3) "xiaojie"
4) "xiaohong"

移除元素
127.0.0.1:6379> zrange salary 0 -1
1) "xiaohong"
2) "xiaojie"
3) "zhangshan"
4) "xiaodong"
127.0.0.1:6379> zrem salary xiaohong
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "xiaojie"
2) "zhangshan"
3) "xiaodong"

获取有序集合中的个数
127.0.0.1:6379> zcard salary
(integer) 3

获取指定区间的成员数量
127.0.0.1:6379> zadd myset 1 hello
(integer) 1
127.0.0.1:6379> zadd myset 2 world
(integer) 1
127.0.0.1:6379> zadd myset 4 nihao
(integer) 1
127.0.0.1:6379> zcount myset 1 4
(integer) 3
127.0.0.1:6379> zcount myset 1 3
(integer) 2
127.0.0.1:6379> zcount myset 2 3
(integer) 1

特殊的数据类型

geospatial(地理位置)

Redis 的Geo,可以推算地理位置的信息

添加信息
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 chongqi 114.05 22.52 shengzhen
(integer) 2

获取指定城市的经纬度
127.0.0.1:6379> geopos china:city beijing
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing chongqi
1) 1) "116.39999896287918091"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"

返回两个位置之间的距离
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"

搜索以某一点为xx千米为半径出现的城市
127.0.0.1:6379> georadius china:city 110 30 1000 km
1) "chongqi"
2) "shengzhen"
127.0.0.1:6379> georadius china:city 110 30 500 km
1) "chongqi"


127.0.0.1:6379> georadius china:city 110 30 1000 km withdist
1) 1) "chongqi"
   2) "341.9374"
2) 1) "shengzhen"
   2) "924.6408"
127.0.0.1:6379> georadius china:city 110 30 1000 km withcoord
1) 1) "chongqi"
   2) 1) "106.49999767541885376"
      2) "29.52999957900659211"
2) 1) "shengzhen"
   2) 1) "114.04999762773513794"
      2) "22.5200000879503861"
127.0.0.1:6379> georadius china:city 100 30 1000 km withdist withcoord count 1
1) 1) "chongqi"
   2) "629.6756"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadius china:city 100 30 1000 km withdist withcoord count 2
1) 1) "chongqi"
   2) "629.6756"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
以一个元素进行查询
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km
1) "beijing"
127.0.0.1:6379> georadiusbymember china:city beijing 2000 km
1) "chongqi"
2) "shengzhen"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> 

将二维的经纬度转换为一维的字符串

如果两个字符串越像则代表越接近,则距离越近

127.0.0.1:6379> geohash china:city beijing chongqi
1) "wx4fbxxfke0"
2) "wm5xzrybty0"

Geo底层的实现原理其实就是Zset

查看地图中或则删除指定元素
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqi"
2) "shengzhen"
3) "shanghai"
4) "beijing"
127.0.0.1:6379> zrem china:city beijing
(integer) 1
127.0.0.1:6379> zrange china:city 0 -1
1) "chongqi"
2) "shengzhen"
3) "shanghai"

Hyperloglog

什么是基数?

基数:不重复的元素

优点:

占用的内存是固定的,2^64不同的元素的技术,只需要12KB的内存

example

127.0.0.1:6379> clear
127.0.0.1:6379> pfadd mykey a b c d e f g h j
(integer) 1
127.0.0.1:6379> pfcount mykey
(integer) 9
127.0.0.1:6379> pfadd mykey2 a b c d e f g h j j j
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2
OK
127.0.0.1:6379> pfcount mykey3
(integer) 9

需要允许容错

Bitmap

位图-》都是操作二进制位进行记录,只有0和1两种状态

事务

redis单条命令保证原子性,但是事务不保证原子性。

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

一次性,顺序性,排他性!

Redis事务没有隔离级别的概念

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

redis事务执行流程
  • 开启事务(multi)

  • 命令入队(…)

  • 执行事务(exec)

127.0.0.1:6379> clear
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)> 
127.0.0.1:6379(TX)> get k2
QUEUED
127.0.0.1:6379(TX)> get k1
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
4) "v2"
5) "v1"

放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379(TX)> set k1 v2
QUEUED
127.0.0.1:6379(TX)> set k3 v5
QUEUED
127.0.0.1:6379(TX)> set k5 v6
QUEUED
127.0.0.1:6379(TX)> discard
OK

编译型异常(代码有问题!命令有错!)

事务中所有的命令都不会被执行

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 v1
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> getset k4
(error) ERR wrong number of arguments for 'getset' command
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.

运行时异常

(1/0)如果事务队列中存在语法性,那么执行命令的时候,其他命令是可以正常执行,错误命令抛出异常

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)> incr k1
QUEUED
127.0.0.1:6379(TX)> getset k1 v1
QUEUED
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> exec
1) OK
2) OK
3) OK
4) (error) ERR value is not an integer or out of range
5) "v1"
6) OK

监控(面试常问)

悲观锁
  • 认为什么时候都会出问题,无论做什么都会加锁
乐观锁
  • 认为是什么时候都不会出问题,所以不会上锁!更新数据的时候去判断一下,在此期间是否有人修改过这个数据
Redis监控测试
正常执行
127.0.0.1:6379> clear
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
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

测试多线程修改值,使用watch当作redis的乐观锁操作
线程1:
127.0.0.1:6379> watch 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

线程2:

127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK

线程1:
127.0.0.1:6379(TX)> 
127.0.0.1:6379(TX)> exec
(nil)

  • 如果事务执行失败,先解锁
  • 获取最新的值,再次监控
  • 比对监视的值是否发生变化,如果发现变化,那么可以执行成功。如果执行失败,则重复执行。

Jedis

什么是Jedis

是redis官方推荐的java链接开发工具。使用java操作redis中间件。

导入对应的依赖
<!--    导入jedis的包-->
    <dependencies>
        <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>4.3.1</version>
        </dependency>

<!--        fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.62</version>
        </dependency>
    </dependencies>
编码测试
连接数据库
public class Main {
    public static void main(String[] args) {

        //new jedis对象
        Jedis jedis=new Jedis("47.96.10.50",6379);

        String ping=jedis.ping();

        System.out.println(ping);
    }
}
操作命令
断开连接

Springboot 继承redis

所有与数据相关的都被封装在SpringData模块中

SpringData是与SpringBoot齐名的项目

在SpringBoot2.x之后,原先使用的jedis被替换成lettuce?

  • jedis:采用的直连,多个线程操作是并不安全的,如果想要避免不安全,使用jedis pool 连接池,BIO模式
  • lettuce: 采用netty,实例可以在多个线程中进行共享,不存在线程不安全的情况!可以减少线程数量,NIO模式

Redis.conf详解

单位
  • 对大小写不敏感
包含
  • 多个配置文件可以一块使用
网络
  • 绑定ip
  • 保护模式
  • 端口
通用
  • 以守护进程方式运行,默认是no,需手动开启yes
  • pidfile 若以后台方式运行,需指定pid文件
  • log 日志
  • logfile 日志保存地址
  • databases 数据库的数量、
  • always-show-logo yes 是否显示logo
快照

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

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

复制
安全
限制
AOF配置
  • 默认不开启AOF模式。默认使用rdb持久化

Redis持久化

Redis是内存数据库,如果不将内存中的数据库保存到磁盘,则一旦服务器进程退出,服务器中的数据库状态也会消失,因此redis提供了持久化功能

RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘,即Snapshot快照,恢复时是将快照文件直接读到内存中。

Redis会单独创建(fork)一个子进程用以进行持久化,会先将数据写入到一个临时文件中,待持久化过程结束,再用这个临时文件替换上次持久化好的文件。真个过程中,主进程不进行任何IO操作,确保Redis具有极高的性能。如果需要进行大规模数据的恢复,且对于数据恢复的完整性并不是非常敏感,使用RDB方式会比AOF方式更加高效。RDB的缺点是最后一次持久化后的数据有可能丢失。Redis默认使用RDB模式

有时候在生产环境会对rdb文件进行备份

rdb保存的文件是dump.rdb

触发机制:
  1. save的规则满足的情况下,会自动触发rdb规则
  2. 执行flushall命令,也会自动触发rdb规则
  3. 退出redis,也会产生rdb文件

备份就会自动生成一个dump.rdb文件

如何恢复rdb文件
  1. 只需要将rdb文件放在redis启动目录。redis启动的时候会自动检查dump.rdb恢复其中的数据
  2. 查看需要存放的位置
优缺点

优点:

  1. 适合大规模的数据恢复
  2. 若对数据完整性要求不高

缺点:

  1. 需要一定的时间间隔,若redis意外宕机,最后一次修改的数据将会消失
  2. fork进程的时候,会占用一定的内存空间
AOF(Appened Only File)

将所有命令都记录下来,类似history,恢复的时候相当于将这个文件里的所有命令重新执行一遍

以日志的形式来记录每个写操作。将Redis执行过的所有指令记录下来(读操作不记录),只需追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作

Aof文件中保存的是appendonly.aof文件

  • 默认不开启,需要手动进行配置
  • 重启redis即可生效
  • 如果aof文件有错误。此时redis是启动不起来的,此时可以利用redis-check-aof --fix 可进行修复。
  • 如果文件正常,则代表可以修复
优缺点

优点:

  1. 每一次修改都同步,使得文件完整性会比更好
  2. 每秒同步数据,可能会丢失一秒的而数据
  3. 从不同步,效率是最高的

缺点:

  1. 相对于数据文件来说,aof远远大于rdb,修复的速度也比rdb慢
  2. aof运行效率要比rof慢

Redis发布订阅

Redis发布订阅(pub/sub)是一种消息通信模式:发送则(pub)发送信息,订阅者(sub)接收信息。

Redis客户端可以订阅任意数量的频道

  • 消息发送者
  • 频道
  • 消息订阅者
发送端
127.0.0.1:6379> publish ylq hello
(integer) 1
127.0.0.1:6379> publish ylq "what are you doing"
(integer) 1
127.0.0.1:6379> 

订阅端
127.0.0.1:6379> subscribe ylq 
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "ylq"
3) (integer) 1
1) "message"
2) "ylq"
3) "hello"
1) "message"
2) "ylq"
3) "what are you doing"

Redis主从复制

主从复制,将一台redis服务器的数据,复制到其他的Redis服务器,前者称为主节点,后者称为从节点;数据的复制是单向的,只能从主节点到从节点。主节点以写为主,从节点以读为主

主从复制,读写分离!80%的情况下都在进行读操作!减缓服务器的压力!架构中经常使用!一主二从

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

主从复制的主要作用
  1. 数据冗余
  2. 故障恢复
  3. 负载均衡
  4. 高可用基石(集群)

Redis运用于工程中,只使用一台Redis是万万不能的(会出现宕机)

环境配置

只配置从库,不用配置主库

查看信息
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
master_replid:3b14864269b20d205fcf594f69d0dffd0bbba509
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:0
second_repl_offset:-1
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
127.0.0.1:6379> 

配置一主二冲

默认情况下,每台redis服务器都是主节点,我们一般情况下只需要配置从机就可以了

在从机中进行配置

127.0.0.1:6380> clear
127.0.0.1:6380>  slaveof 127.0.0.1 6379
OK
127.0.0.1:6380> info replication
# Replication
role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
master_last_io_seconds_ago:3
master_sync_in_progress:0
slave_read_repl_offset:14
slave_repl_offset:14
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:70227a4c150f77a7fb12b2c0cbb161efa0f3d0a5
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:14
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:14
127.0.0.1:6380> 

真实的主从配置是在配置文件中进行配置,这样是永久的,采用命令的配置,其实是暂时的

细节

主机可以进行写操作,从机不能进行写操作,只能读

主机中的所有信息和数据,都会自动被从机获取和保存

127.0.0.1:6381> get k1
"v1"
127.0.0.1:6381> set k2 v2
(error) READONLY You can't write against a read only replica.
127.0.0.1:6381> 

测试:主机断开连接,从机仍然连接到主机,但是没有写操作

​ 这个时候主机如果回来了,从机依旧可以直接获取从机写的信息

​ 如果使用命令行配置的主从,这个时候如果重启了,就会变回主机

​ 只要变为从机,立马就会从主机中获取值

  • 全量复制
  • 增量复制
复制原理

Slave启动成功连接到master后会发送一个sync同步命令

Mater 接到命令,启动后台的存盘进程,同步收集所有接收到的用于修改数据集命令,在后台进程执行完毕之后,master将传送整个数据文件到slave,并完成一次完全同步

全量复制:slave服务在接收到数据库文件数据后,将其存盘并加载到内存中

增量复制:Maste继续将新的所有手机到的修改命令一次传给slave,完成同步

只要是重新连接Master,一次完全同步(全量复制)将自动执行,我们的数据一定可以在从机中获取到

层层链路模型
如果主机宕机,能否自动选择一个从机当作主机

只能手动完成

如果主机断开连接,可以使用slaveof no one ,使自己变成主机,其他的节点就可以手动连接到最新的这个主节点(手动)

如果主机恢复,要重新手动设置

哨兵模式(自动选取主机的模式)

能够后台监控主机是否故障,如果故障了则会根据投票数自动将从库转换为主库

哨兵模式使一种特殊的模式,首先redis提供了哨兵的命令,哨兵是一个独立的进程,作为进程,它会独立进行。其原理是哨兵通过发送命令,等待redis服务器相应,从而监控运行的多个redis实例

然而一个哨兵进程对redis服务器进行监控,可能会出现问题,为此,可以使用多个哨兵进行监控,各个哨兵之间还会互相进行监控,从而形成多哨兵模式。

哨兵原理

假设主服务器宕机,哨兵1先检测到这个结果,系统并不会马上进行failover过程,仅仅哨兵1主观的认为服务器不可用,这个现象成为主观下线。当后面的哨兵也检测到主服务器不可用,并且当数量达到一定值时,则哨兵之间会进行一次投票,投票的结果由一个哨兵发起,进行failover(故障转移)操作一。切换成功后,就会通过发布订阅模式,让各个哨兵把自己监控的从服务器实现切换主机,这个过程称之为客观下线

配置哨兵配置文件sentinel.conj
sentinel monitor myredis 127.0.0.1 6379 1

后面的数字1,代表主机挂了,哨兵投票看让谁接替成为主机,票数最多的,就会成为主机

启动哨兵
redis-sentinel redisconfig/sentinel.conf

如果主机节点断开,就会从从机中随机选取一个服务器

如果主机此时回来了,只能归并到新的主机下,当作从机,这就是哨兵模式的规则

优缺点:

优点

  1. 哨兵模式,基于主从复制模式,所有主从配置优点,它全由
  2. 主从可以切换,故障可以转移,系统的可用性就会更好
  3. 哨兵模式就是主从模式的升级,手动到自动,更加健壮

缺点:

  1. Redis不好在线扩容,集群容量一旦到达上限,在线扩容就会变得十分麻烦
  2. 实现哨兵模式的配置比较麻烦,路面有很多选择

Redis缓存穿透和雪崩

缓存穿透(查不到)

用于想要查询一个数据,发现redis内存数据库中没有,也就是缓存没有命中,于是向持久层数据库查询。发现也没有,于是本次查询失败。当用户很多的时候,缓存都没有命中,于是都去请求持久层数据库。这会给持久层数据库造成很大的压力,相当于出现了缓存穿透

解决方法
布隆过滤器

对所有可能查询的参数以hash形式存储,在控制层先进行校验,不符合就丢弃,从而避免对底层存储系统的查询压力

缓存空对象

当存储层不命中后,即使返回的空对象也将其缓存起来,同时设置一个过期时间,之后再访问这个数据就会从缓存中获取,从而保护后端数据

存在两个问题:

  1. 如果空值能够被缓存起来,便意味着缓存需要更多的空间存储更多的键,因为这当中可能会有很多的空值的键
  2. 即使对空值设置了过期时间,还是会存在缓存层和存储层的数据会有一段时间窗口的不一致,这对于需要保持一致性的业务会有影响
缓存击穿(量太多,缓存过期)

缓存击穿,是指一个key非常热点,在不停的扛着大并发,把并发集中对这一个点进行访问,当这个key在失效的瞬间,持续的大并发就会穿破缓存,直接请求数据库,就像在一个屏障上凿开一个洞

  • 设置热点数据永不过期
  • 加互斥锁

缓存雪崩

在某一个时间段,缓存集中过期失效。Redis宕机

  • redis高可用(异地多活)
  • 限流降级
  • 数据预热
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值