Redis入门

目录

NoSQL概述

什么是NoSQL?

为什么要用NoSQL?

NoSQL 的特点

NoSQL的四大分类

Redis入门

概述

基础知识

五大数据类型

Redis——Key

String(字符串)

List(列表)

Set(集合)

Hash(哈希)

Zset(有序集合)

三种特殊数据类型

Geospatial 地理位置

Hyperloglog

Bitmap

事务

C++连接 Redis

Redis 配置

Redis 持久化

快照(Snapshot)

AOF 只追加日志文件

AOF文件的重写

Redis主从复制

主从复制

主从复制架构图

主从复制原理

搭建主从复制

无法解决的问题

Redis哨兵机制

哨兵Sentinel机制

哨兵架构原理

搭建哨兵架构

无法解决的问题

Redis集群

集群

集群架构图

集群细节


NoSQL概述

什么是NoSQL?

NoSQL = Not Only SQL(不仅仅是SQL),是非关系型数据库。

为什么要用NoSQL?

如今最近的年代,数据量大,数据变化快,MySQL 等关系型数据库就不够用了

NoSQL 的特点

解耦!

1、方便扩展:数据之间没有关系,很好扩展

2、大数据量高性能:Redis 一秒写8万次,读取11万次,NoSQL的缓存是记录级的,是一种细粒度的缓存,性能会比较高

3、数据类型是多样的:不需要事先设计数据库,随取随用

4、传统 RDBMS 和 NoSQL

传统的 RDBMS

● 结构化组织

● SQL

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

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

● 严格的一致性

● 基础的事务

......

NoSQL

● 不仅仅是数据

● 没有固定的查询语言

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

● 最终一致性

● 高性能,高可用,高可扩

......

NoSQL的四大分类

KV键值对

● Redis

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

● MongoDB

MongoDB 是一个基于分布式分拣存储的数据库,C++编写,主要用来处理大量的文档;

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

列存储数据库

● HBase

● 分布式文件系统

图型数据库

● 它不是存图形,放的是关系,比如:朋友圈社交网络,广告推荐;

● Neo4J,InfoGrid;

四者对比

Redis入门

概述

Redis 是什么?

Redis(Remote Dictionary Server),即远程字典服务;

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

redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。

Redis 能干嘛?

1、内存存储、持久化,内存中是断电即失,所以持久化很重要(rdb、aof);

2、效率高,可用于高速缓存

3、发布订阅系统

4、地图信息分析

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

......

特性

1、多样的数据类型

2、持久化

3、集群

4、事务

......

基础知识

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

切换数据库:

127.0.0.1:6379> select 3
OK

查看数据库大小:

127.0.0.1:6379> DBSIZE
(integer) 1

查看数据库所有的key:

127.0.0.1:6379> keys *
1) "name"

清除当前数据库:

127.0.0.1:6379> flushdb
OK

清除全部数据库的内容:

127.0.0.1:6379> flushall
OK

Redis 是单线程的!

Redis是很快的,官方表示,Redis是基于内存操作的,CPU不是Redis性能瓶颈,Redis的瓶颈是机器的内存和网络带宽,既然可以使用单线程来实现,就使用单线程了。

Redis 为什么单线程还这么快?

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

五大数据类型

Redis——Key

127.0.0.1:6379> keys * #查看所有key
(empty array)
127.0.0.1:6379> set name ljh #设置 key-value
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) "age"
2) "name"
127.0.0.1:6379> EXISTS name #判断当前key是否存在
(integer) 1
127.0.0.1:6379> EXISTS id
(integer) 0
127.0.0.1:6379> move name 3 #移除key到第3个数据库
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name jiahao
OK
127.0.0.1:6379> keys *
1) "age"
2) "name"
127.0.0.1:6379> get name #获取key所对应的value
"jiahao"
127.0.0.1:6379> EXPIRE name 10 #设置key的过期时间
(integer) 1
127.0.0.1:6379> ttl name #查看当前key的剩余时间
(integer) 7
127.0.0.1:6379> ttl name
(integer) 6
127.0.0.1:6379> ttl name
(integer) 4
127.0.0.1:6379> ttl name
(integer) 2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> ttl name
(integer) -2
127.0.0.1:6379> get name
(nil)
127.0.0.1:6379> type name
none
127.0.0.1:6379> type age #查看key的类型
string

String(字符串)

######################################################
127.0.0.1:6379> set key1 v1 #设置值
OK
127.0.0.1:6379> get key1    #获取值
"v1"
127.0.0.1:6379> keys *      #获取所有的key
1) "key1"
127.0.0.1:6379> exists key1 #判断某一个key是否存在
(integer) 1
127.0.0.1:6379> APPEND key1 hello #追加字符串,如果当前key不存在,就相当于set key
(integer) 7
127.0.0.1:6379> keys *
1) "key1"
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> STRLEN key1  #获取字符串的长度
(integer) 7
127.0.0.1:6379> append key1 ", ljh"
(integer) 12
127.0.0.1:6379> get key1
"v1hello, ljh"
127.0.0.1:6379> strlen key1
(integer) 12
######################################################
#i++
#步长 i+=

127.0.0.1:6379> set views 0     #设置key
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views      #使key自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> get views
"2"
127.0.0.1:6379> decr views      #使key自减1
(integer) 1
127.0.0.1:6379> decr views
(integer) 0
127.0.0.1:6379> decr views
(integer) -1
127.0.0.1:6379> decr views
(integer) -2
127.0.0.1:6379> get views
"-2"
127.0.0.1:6379> incrby views 50 #可以设置步长,指定增量
(integer) 48
127.0.0.1:6379> decrby views 34 #可以设置步长,指定减量
(integer) 14
127.0.0.1:6379> get views
"14"
######################################################
# 字符串范围 range

127.0.0.1:6379> set key1 "hello, ljh"
OK
127.0.0.1:6379> get key1
"hello, ljh"
127.0.0.1:6379> GETRANGE key1 0 3   #截取字符串 [0,3]
"hell"
127.0.0.1:6379> GETRANGE key1 0 -1  #从第0个字符开始截取全部的字符串,和get key一样
"hello, ljh"
127.0.0.1:6379> GETRANGE key1 2 -1  #从第2个字符开始截取后面全部的字符串
"llo, ljh"
​
# 替换 setrange
127.0.0.1:6379> set key2 "abcdefg"
OK
127.0.0.1:6379> get key2
"abcdefg"
127.0.0.1:6379> SETRANGE key2 1 xx  #替换指定位置开始的字符串
(integer) 7
127.0.0.1:6379> get key2
"axxdefg"
######################################################
# setex (set with expire)     # 设置key时带上过期时间
# setnx (set if not exist)    # key不存在时则设置

127.0.0.1:6379> setex key3 30 "hello" # 设置key3 30秒后过期,值为hello
OK
127.0.0.1:6379> ttl key3
(integer) 25
127.0.0.1:6379> get key3
"hello"
127.0.0.1:6379> ttl key3
(integer) 11
127.0.0.1:6379> setnx key4 "redis"    #如果key4不存在,则创建key4
(integer) 1
127.0.0.1:6379> get key4
"redis"
127.0.0.1:6379> keys *
1) "key2"
2) "key4"
3) "key1"
127.0.0.1:6379> ttl key3
(integer) -2
127.0.0.1:6379> setnx key4 "mongodb"  #如果key4存在,则创建失败
(integer) 0
127.0.0.1:6379> get key4
"redis"
######################################################
mget
mset
​
127.0.0.1:6379> mset k1 v1 k2 v2 k3 v3  # 同时设置多个值
OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"
127.0.0.1:6379> mget k1 k2 k3           # 同时获取多个值
1) "v1"
2) "v2"
3) "v3"
127.0.0.1:6379> msetnx k1 vv k4 v4      # msetnx是一个原子性的操作,要么一起成功,要么一起失败 
(integer) 0
127.0.0.1:6379> get k4
(nil)
​
# 对象
127.0.0.1:6379> set user:1 {name:zhangsan,age:18} # 设置一个user:1对象,值用json字符串来保存
OK
127.0.0.1:6379> keys *
1) "user:1"
127.0.0.1:6379> get user:1
"{name:zhangsan,age:18}"
​
# 这里的key是一个巧妙的设计:user:{id}:{filed}
​
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 18
OK
127.0.0.1:6379> keys *
1) "user:1:age"
2) "user:1:name"
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "18"
​
######################################################
getset # 先get然后再set
​
127.0.0.1:6379> getset db redis    # 如果不存在值,则返回 nil
(nil)
127.0.0.1:6379> get db
"redis"
127.0.0.1:6379> getset db mongodb  # 如果存在值,获取原来的值,并设置新的值
"redis"
127.0.0.1:6379> get db
"mongodb"

List(列表)

基本的数据类型,列表,实际上是一个链表。

######################################################
lpush/rpush    #将一个或多个值 ,插入到列表的头部或尾部
lpop/rpop      #移除列表头部或尾部的一个或多个值
​
127.0.0.1:6379> rpush mylist one     #在列表尾部插入
(integer) 1
127.0.0.1:6379> rpush mylist two
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1   #获取列表的值
1) "one"
2) "two"
127.0.0.1:6379> lpush mylist three   #在列表头部插入
(integer) 3
127.0.0.1:6379> lpush mylist three
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
2) "three"
3) "one"
4) "two"
127.0.0.1:6379> lpop mylist           #移除列表头部的一个值
"three"
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
2) "one"
3) "two"
127.0.0.1:6379> rpop mylist 2         #移除列表尾部的两个值
1) "two"
2) "one"
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
127.0.0.1:6379> lpush mylist one two  #在列表头部依次插入值
(integer) 3
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "one"
3) "three"
​
######################################################
lindex   #获取列表中指定下标的值
​
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "one"
3) "three"
127.0.0.1:6379> lindex mylist 0     #获取列表下标为0的值
"two"
127.0.0.1:6379> lindex mylist 2
"three"
​
######################################################
llen  #获取列表的长度
​
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "one"
3) "three"
127.0.0.1:6379> llen mylist
(integer) 3
​
######################################################
lrem   #移除列表中指定个数的value
​
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "one"
3) "three"
127.0.0.1:6379> lpush mylist three
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
2) "two"
3) "one"
4) "three"
127.0.0.1:6379> lrem mylist 1 one    #移除mylist中一个one
(integer) 1
127.0.0.1:6379> lrange mylist 0 -1
1) "three"
2) "two"
3) "three"
127.0.0.1:6379> lrem mylist 2 three  #移除mylist中两个three
(integer) 2
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
​
######################################################
ltrim   #修剪列表,截断列表中指定长度中的值
​
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> ltrim mylist 1 2     #截断列表中下标1到2的值,此时列表已经改变
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "two"
2) "three"
​
######################################################
rpoplpush  #将源列表尾部的一个元素移至目的列表的头部
​
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> rpoplpush mylist otherlist  #将mylist的尾部元素移到otherlist的头部
"three"
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
2) "two"
127.0.0.1:6379> lrange otherlist 0 -1
1) "three"
127.0.0.1:6379> rpoplpush mylist otherlist
"two"
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
127.0.0.1:6379> lrange otherlist 0 -1
1) "two"
2) "three"
​
######################################################
lset   #更新指定下标的元素
​
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> lset mylist 0 four   #将mylist中下标为0的更新成four
OK
127.0.0.1:6379> lrange mylist 0 -1
1) "four"
2) "two"
3) "three"
​
######################################################
linsert    #在列表具体某个值的前/后插入另一个值
​
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
2) "two"
3) "three"
127.0.0.1:6379> linsert mylist before two four  #在mylist的two前插入four
(integer) 4
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
2) "four"
3) "two"
4) "three"
127.0.0.1:6379> linsert mylist after two five   #在mylist的two后插入five
(integer) 5
127.0.0.1:6379> lrange mylist 0 -1
1) "one"
2) "four"
3) "two"
4) "five"
5) "three"

Set(集合)

######################################################
sadd
smembers
sismember
​
127.0.0.1:6379> sadd myset a       #向集合中添加元素
(integer) 1
127.0.0.1:6379> keys *
1) "myset"
127.0.0.1:6379> sadd myset b
(integer) 1
127.0.0.1:6379> smembers myset     #查看集合中的元素
1) "b"
2) "a"
127.0.0.1:6379> sadd myset c
(integer) 1
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "c" 
127.0.0.1:6379> sismember myset b   #判断一个元素是否在集合中
(integer) 1
127.0.0.1:6379> sismember myset e
(integer) 0
​
######################################################
scard
​
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> scard myset   #获取集合中元素个数
(integer) 3
​
######################################################
srem
​
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "d"
4) "c"
127.0.0.1:6379> srem myset c  #移除集合中指定的元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "d"
127.0.0.1:6379> srem myset a b
(integer) 2
127.0.0.1:6379> smembers myset
1) "d"
​
######################################################
set 无序不重复
srandmember  #随机抽取集合中的元素
​
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "c"
4) "d"
127.0.0.1:6379> srandmember myset
"b"
127.0.0.1:6379> srandmember myset
"b"
127.0.0.1:6379> srandmember myset
"c"
127.0.0.1:6379> srandmember myset
"b"
127.0.0.1:6379> srandmember myset
"a"
127.0.0.1:6379> srandmember myset
"d"
127.0.0.1:6379> srandmember myset 1
1) "c"
127.0.0.1:6379> srandmember myset 2
1) "b"
2) "a"
127.0.0.1:6379> srandmember myset 3
1) "b"
2) "d"
3) "c"
​
######################################################
spop  #随机删除集合中的元素
​
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "c"
4) "d"
127.0.0.1:6379> spop myset      #随机删除一个
"c"
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "d"
127.0.0.1:6379> spop myset 2     #随机删除两个
1) "d"
2) "b"
127.0.0.1:6379> smembers myset
1) "a"
​
######################################################
smove   #将源集合中的指定元素移到另一个集合中
​
127.0.0.1:6379> smembers myset
1) "b"
2) "a"
3) "d"
4) "c"
127.0.0.1:6379> smove myset otherset b
(integer) 1
127.0.0.1:6379> smembers myset
1) "a"
2) "d"
3) "c"
127.0.0.1:6379> smembers otherset
1) "b"
​
######################################################
差集、交集、并集
sdiff
sinter
sunion
​
127.0.0.1:6379> smembers set1
1) "b"
2) "a"
3) "c"
127.0.0.1:6379> smembers set2
1) "e"
2) "d"
3) "c"
127.0.0.1:6379> sdiff set1 set2    #差集
1) "b"
2) "a"
127.0.0.1:6379> sinter set1 set2   #交集
1) "c"
127.0.0.1:6379> sunion set1 set2   #并集
1) "a"
2) "c"
3) "b"
4) "d"
5) "e"
​

Hash(哈希)

key-map,此时的值是一个map集合。

######################################################
hset
hget
hmset
hmget
hgetall
hdel
​
127.0.0.1:6379> hset myhash field1 hello   #向myhash中设置一个具体的key-value
(integer) 1
127.0.0.1:6379> keys *
1) "myhash"
127.0.0.1:6379> hget myhash field1   #获取一个字段值
"hello"
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> hmset myhash field1 hello field2 redis #设置多个key-value
OK
127.0.0.1:6379> hmget myhash field1 field2   #获取多个字段值 
1) "hello"
2) "redis"
127.0.0.1:6379> hdel myhash field1   #删除指定key字段
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "redis"
​
######################################################
hlen
​
127.0.0.1:6379> hmset myhash field1 hello field2 world
OK
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hlen myhash   #获取哈希表的字段个数
(integer) 2
​
######################################################
hexists
​
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
3) "field1"
4) "hello"
127.0.0.1:6379> hexists myhash field1  #判断hash中指定字段是否存在
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
​
######################################################
hkeys   #只获取所有field
hvals   #只获取所有value
​
127.0.0.1:6379> hkeys myhash
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash
1) "world"
2) "hello"
​
######################################################
hincrby  
hsetnx
​
127.0.0.1:6379> hset myhash field3 6
(integer) 1
127.0.0.1:6379> hincrby myhash field3 10    #指定增量
(integer) 16
127.0.0.1:6379> hincrby myhash field3 -10
(integer) 6
127.0.0.1:6379> hsetnx myhash field4 redis  #如果不存在则设置
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 mongodb    #如果存在则不能设置
(integer) 0

Zset(有序集合)

######################################################
zadd     #添加元素
zrange   #获取下标之间的值,升序显示
​
127.0.0.1:6379> zadd salary 100 zhangsan #添加值的同时设置权重
(integer) 1
127.0.0.1:6379> zadd salary 500 lisi
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "lisi"
​
######################################################
zrangebyscore     #获取score之间的值,按升序显示,范围可以是-inf +inf,表示正无穷到负无穷
zrevrangebyscore  #获取score之间的值,按降序显示
​
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "lisi
127.0.0.1:6379> zrangebyscore salary 0 200   #获取[0,200]之间的值,升序显示
1) "zhangsan"
127.0.0.1:6379> zrangebyscore salary 0 500   #获取[0,500]之间的值
1) "zhangsan"
2) "lisi"
127.0.0.1:6379> zrangebyscore salary 0 (500  #获取[0,500)之间的值
1) "zhangsan"
127.0.0.1:6379> zrangebyscore salary 0 1000 withscores #获取[0,1000]之间的值,带分数
1) "zhangsan"
2) "100"
3) "lisi"
4) "500"
127.0.0.1:6379> zadd salary 700 wangwu
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "zhangsan"
2) "100"
3) "lisi"
4) "500"
5) "wangwu"
6) "700"
127.0.0.1:6379> zrevrangebyscore salary 500 0  #获取[0,500]之间的值,降序显示
1) "lisi"
2) "zhangsan"
127.0.0.1:6379> zrevrangebyscore salary 500 0 withscores
1) "lisi"
2) "500"
3) "zhangsan"
4) "100"
127.0.0.1:6379> zrevrangebyscore salary 800 0 withscores
1) "wangwu"
2) "700"
3) "lisi"
4) "500"
5) "zhangsan"
6) "100"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores #获取负无穷到正无穷之间的值
1) "zhangsan"
2) "100"
3) "lisi"
4) "500"
5) "wangwu"
6) "700"
​
######################################################
zrem  #移除集合中的元素
​
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "lisi"
3) "wangwu"
127.0.0.1:6379> zrem salary lisi
(integer) 1
127.0.0.1:6379> zrange salary 0 -1
1) "zhangsan"
2) "wangwu"
​
######################################################
zcount    #获取指定区间中元素的个数 
​
127.0.0.1:6379> zrange salary 0 -1 withscores
1) "zhangsan"
2) "100"
3) "zhaoliu"
4) "300"
5) "wangwu"
6) "700"
127.0.0.1:6379> zcount salary 1 2     #获取score在[1,2]中的元素个数
(integer) 0
127.0.0.1:6379> zcount salary 0 200   #获取score在[0,200]中的元素个数
(integer) 1
127.0.0.1:6379> zcount salary 200 800 #获取score在[200,800]中的元素个数
(integer) 2

三种特殊数据类型

Geospatial 地理位置

geoadd

将指定的地理空间位置(纬度、经度、名称)添加到指定的key

geopos

key里返回所有给定位置元素的位置(经度和纬度)。

geodist

返回两个给定位置之间的距离

georadius

以给定的经纬度为中心, 返回键包含的位置元素当中, 与中心的距离不超过给定最大距离的所有位置元素。

georadiusbymember

这个命令和 GEORADIUS 命令一样, 都可以找出位于指定范围内的元素, 但是 GEORADIUSBYMEMBER 的中心点是由给定的位置元素决定的, 而不是像 GEORADIUS 那样, 使用输入的经度和纬度来决定中心点

geohash

该命令将返回11个字符的Geohash字符串


geo底层的实现原理就是 Zset !我们可以使用 Zset 命令来操作 geo

Hyperloglog

用来基数统计,什么是基数?基数指一份数据中,元素种类的个数,相同元素只记一次数。

如 A = {2,4,5,7,2,4,8,6},则A的基数为6。

如果需要统计一个网页的用户访问次数,一个用户访问网页多次,但只算一次;

传统的方式,用set保存用户的id,然后统计set中元素的个数;

但我们的目的是为了计数,并不是保存用户的id;

这样浪费了大量的内存来保存用户id;

##############################################################
pfadd    #向key中添加元素
pfcount  #统计key的基数
pfmerge  #将多个key合并到一个key中
​
127.0.0.1:6379> pfadd mykey1 a b c d e f g
(integer) 1
127.0.0.1:6379> pfcount mykey1
(integer) 7
127.0.0.1:6379> pfadd mykey2 d e f g h i j
(integer) 1
127.0.0.1:6379> pfcount mykey2
(integer) 7
127.0.0.1:6379> pfmerge mykey3 mykey1 mykey2
OK
127.0.0.1:6379> pfcount mykey3
(integer) 10
127.0.0.1:6379> keys *
1) "mykey1"
2) "mykey3"
3) "mykey2"

Hyperloglog有 0.81%错误率,如果允许容错,则可以使用。

Bitmap

位存储,可以用来统计用户活跃,不活跃;登录,未登录;打卡,未打卡;

两个状态的,都可以用Bitmap;

Bitmap是一种数据结构,是操作二进制位来进行记录,只有0,1两个状态;

##########################################################
setbit     #设置一个比特位
getbit     #获取一个比特位
bitcount   #统计位图中比特位为1的个数
​
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 1
(integer) 0
127.0.0.1:6379> setbit sign 4 1
(integer) 0
127.0.0.1:6379> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 1
(integer) 0
127.0.0.1:6379> getbit sign 2
(integer) 1
127.0.0.1:6379> getbit sign 5
(integer) 0
127.0.0.1:6379> bitcount sign
(integer) 5

事务

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

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

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

Redis的事务:

● 开启事务(multi)

● 命令入队(......)

● 执行事务(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)> get k3
QUEUED
127.0.0.1:6379(TX)> exec  #执行事务
1) OK
2) OK
3) "v2"
4) OK
5) "v3"

放弃事务

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)> discard  #取消事务
OK
127.0.0.1:6379> get k1   #事务中的命令都不会执行
(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   #错误的命令
(error) ERR wrong number of arguments for 'set' command
127.0.0.1:6379(TX)> set k4 v4
QUEUED
127.0.0.1:6379(TX)> get k4
QUEUED
127.0.0.1:6379(TX)> exec   #执行事务报错,
(error) EXECABORT Transaction discarded because of previous errors.
127.0.0.1:6379> get k1    #所有的命令都不会执行
(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    #对一个字符串自增1,运行时会报错!
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 k1
"v1"
127.0.0.1:6379> get k2
"v2"
127.0.0.1:6379> get k3
"v3"

监控!watch

悲观锁:很悲观,认为每次都会出问题,无论什么时候,都会加锁;

乐观锁:很乐观,认为每次都不会出问题,所以不会上锁!只在更新数据时判断一下,在此期间是否有人修改数据;先获取version,更新时比较version;

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 10
QUEUED
127.0.0.1:6379(TX)> incrby out 10
QUEUED
127.0.0.1:6379(TX)> exec   #期间数据没有发生改动,事务正常执行!
1) (integer) 90
2) (integer) 10
127.0.0.1:6379> get money
"90"
127.0.0.1:6379> get out
"10"

多线程,监视对象后,事务还未执行时,数据被修改

会导致exec失败, 事务执行失败!

如果修改失败,解锁后,重新获取最新值即可!

 

C++连接 Redis

使用C++来连接redis,需要用到官方的C++连接工具hiredis。

1.下载hiredis.h

GitHub - redis/hiredis: Minimalistic C client for Redis >= 1.2

2.安装所需要的库文件

unzip hiredis-master
cd hiredis-master
make
sudo make install

3.自己编写一个方便操作redis的类

redis.h

#ifndef _REDIS_H_
#define _REDIS_H_
 
#include <iostream>
#include <string.h>
#include <string>
#include <stdio.h>
 
#include <hiredis/hiredis.h>
 
class Redis
{
public:
 
    Redis(){}
 
    ~Redis()
  {
  this->_connect = NULL;
    this->_reply = NULL;                
  }
 
  bool connect(std::string host, int port)
  {
      this->_connect = redisConnect(host.c_str(), port);
    if(this->_connect != NULL && this->_connect->err)
    {
        printf("connect error: %s\n", this->_connect->errstr);
      return 0;
    }
    return 1;
  }
 
    std::string get(std::string key)
  {
    this->_reply = (redisReply*)redisCommand(this->_connect, "GET %s", key.c_str());
    std::string str = this->_reply->str;
    freeReplyObject(this->_reply);
    return str;
  }
 
  void set(std::string key, std::string value)
  {
  redisCommand(this->_connect, "SET %s %s", key.c_str(), value.c_str());
  }
 
private:
 
    redisContext* _connect;
  redisReply* _reply;
        
};
 
#endif  //_REDIS_H_

4.测试redis

test.cpp

#include "redis.h"
​
int main()
{
    Redis *r = new Redis();
    if(!r->connect("127.0.0.1", 6379))
    {
        printf("connect error!\n");
        return 0;
    }
    r->set("key", "Hello,Redis!");
    std::cout << "Get the key is " << r->get("key").c_str() << std::endl;
    delete r;
    return 0;
}

5.编译test.cpp

g++ -o $@ $^ -std=c++11 -L/usr/local/lib/ -lhiredis

在我们安装好hiredis的时候,我们的头文件hiredis.h会被自动放到/usr/local/include下,使用时,直接#include <hiredis/hiredis.h>

我们的库文件libhiredis.so被自动放到/usr/local/lib下,在编译时,需要指明库文件的搜索路径-L/usr/local/lib/,同时还要指明链接的动态库-lhiredis

此时我们可以成功编译我们的test.cpp了。

6.运行test

如果运行报错,提示找不到要链接的库;

是因为,我们之前所做的操作,只是告诉了g++这个编译器库的路径和要链接的库,但运行时,需要系统帮我们去寻找链接的库,我们还需要给系统指明库的搜索路径。

此时,我们需要在/etc/ld.so.conf.d/目录下,新建一个文件usr-libs.conf

并在里面写上/usr/local/lib就可以了;

然后使用ldconfig命令,重新加载一下我们的配置文件;

现在就可以运行我们的代码了;

主要操作如下:

$ su
$ cd /etc/ld.so.conf.d/
$ touch usr-libs.conf
$ vim usr-libs.conf
/usr/local/lib
$ ldconfig

 

 

Redis 配置

单位

1.配置文件 unit 单位对大小写不敏感!(比如 1GB、1gB、1gb都是一样的)

包含 INCLUDE

 

1.此配置文件中可以包含其它配置文件!

网络 NETWORK

bind 127.0.0.1  # 绑定的ip
​
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 "" # 日志文件名,若为空字符串,日志被输出到标准输出上
​
databases 16 # 数据库的数量,默认是16个数据库
​
always-show-logo no # 是否总是显示logo

快照 SNAPSHOTTING

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

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

save 3600 1     # 如果3600秒内,至少有1 key进行了修改,则持久化
save 300 100    # 如果300秒内,至少有100 key进行了修改,则持久化
save 60 10000   # 如果60秒内,至少有10000 key进行了修改,则持久化
​
stop-writes-on-bgsave-error yes  # 持久化如果出错,是否还继续工作
​
rdbcompression yes  # 是否压缩 rdb 文件,需要消耗一些 cpu 资源
​
rdbchecksum yes  # 保存 rdb 文件的时候,进行错误的检查校验
​
dir ./  # rdb 文件保存的目录

复制 REPLICATION

replicaof <masterip> <masterport> # 配置从节点

安全 SECURITY

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

# 在 redis 中通过命令设置密码并登录
config get requirepass   # 查看redis密码
config set requirepass "xxxxx"  # 设置 redis 密码
​
auth xxxxx    #使用密码进行登录

客户端 CLIENTS

maxclients 10000  # 设置能连接上 redis 的最大客户端的数量

内存管理 MEMORY MANAGEMENT

maxmemory <bytes>  # redis 配置最大的内存容量
​
maxmemory-policy noeviction  # 内存达到上限之后的处理策略
    1.volatile-lru:只对设置了过期时间的key进行LRU
    2.allkeys-lru:删除LRU算法的key
    3.volatile-random:随机删除即将过期的key
    4.allkeys-random:随机删除
    5.volatile-ttl:删除即将过期的
    6.noeviction:永不删除,返回错误

aof 配置 APPEND ONLY MODE

appendonly no  # 默认不开启 aof 模式,默认使用 rdb 方式持久化的,在大部分情况下,rdb 完全够用
​
appendfilename "appendonly.aof"  # 持久化文件的名字
​
# appendfsync always   # 每次修改都会 sync,消耗性能
appendfsync everysec   # 每秒执行一次 sync(同步),可能会丢失这 1s 的数据
# appendfsync no       # 不执行 sync,这个时候操作系统自己同步数据,速度最快

Redis 持久化

client redis 将内存数据持久化到硬盘中。

Redis 官方提供了两种不同的持久化方式来将数据存储到硬盘:

● 快照(Snapshot)

● AOF(Append Only File) 只追加日志文件

快照(Snapshot)

这种方式可以将某一时刻的所有数据都写入硬盘中,这也是 redis 的默认开启持久化方式。保存的文件以 .rdb 形式结尾的文件,这种方式也被称为 RDB 方式。

快照生成方式

● 客户端方式:BGSAVE 和 SAVE 指令

● 服务器配置自动触发

1.客户端方式之 BGSAVE

BGSAVE 是 Background save ,客户端可以使用它来创建一个快照,当接收到客户端的 BGSAVE 命令时,redis 会调用 fork 来创建一个子进程,然后子进程负责将快照写入磁盘中,而父进程则继续处理命令请求。

好处:使用 fork 创建的子进程会与父进程共享进程地址空间,也就共享了内存资源,可以方便的将内存数据读到磁盘上,完成快照的创建。就算父进程此时对 redis 有写入操作,父进程也会通过写时拷贝,将原有数据拷贝到另一块内存空间中,而子进程的内存数据不会被影响,因此快照同样可以成功创建。

2.客户端方式之 SAVE

客户端还可以使用 SAVE 命令来创建一个快照,接收到 SAVE命令的 redis 服务器在快照创建完毕之前将不再响应任何其他命令,也就是 redis 主进程负责快照的创建。

SAVE 命令并不常用,使用 SAVE 命令在快照创建完毕之前,redis 处于阻塞状态,无法对外服务!

3.服务器配置方式之满足配置自动触发

如果用户在 redis.conf 中设置了 save 配置选项,redis 会在 save 选项条件满足之后自动触发一次 BGSAVE 命令,如果设置多个 save 配置选项,当任意一个 save 配置选项条件满足,redis 也会触发一个 BGSAVE 命令。

4.服务器接收客户端 shutdown 指令

当 redis 通过 shutdown 指令接收到关闭服务器的请求时,会执行一个 save 命令,阻塞所有客户端,不再执行客户端发送的任何命令,并且在 save 命令执行完毕之后关闭服务器。

AOF 只追加日志文件

这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF持久化会将被执行的写命令写到AOF的文件末尾,以此来记录数据发生的变化,因此只要redis从头到尾执行一次AOF文件所包含的所有写命令,就可以恢复AOF文件的记录的数据集。

开启AOF持久化

在 redis 的默认配置中AOF持久化机制是没有开启的,需要在配置中开启

# 1.开启AOF持久化
​
a.修改 appendonly yes 开启持久化
b.修改 appendfilename "appendonly.aof" 指定生成文件名称 

日志追加频率

# 1.always 【谨慎使用】
说明:每个redis写命令都要同步写入磁盘,严重降低redis速度
解释:如果用户使用always选项,那么每个redis写命令都会被写入硬盘,从而将发生系统崩溃时出现的数据减到最少;遗憾的是,因为之中同步策略需要对硬盘进行大量的写入操作,所以redis处理命令的速度会受到硬盘性能的限制;
​
# 2.everysec 【推荐】
说明:每秒执行一次同步显示的将多个写命令同步到磁盘
解释:为了兼顾数据安全和写入性能,用户可以考虑使用everysec选项,让redis以每秒一次的频率对AOF文件进行同步;redis每秒同步一次AOF文件时性能和不使用任何持久化特性时的性能相差无几,而通过每秒同步一次AOF文件,redis可以保证,即使系统崩溃,用户最多丢失一秒之内产生的数据。
​
# 3.no 【不推荐】
说明:由操作系统决定何时同步
解释:最后使用no选项,将完全由操作系统决定什么时候同步AOF日志文件,这个选项不会对redis性能带来影响但是系统崩溃时,会丢失不定数量的数据,另外如果用户硬盘处理写入操作不够快的话,当缓冲区被等待写入硬盘数据填满时,redis会处于阻塞状态,并导致redis的处理命令请求的速度变慢。

修改同步频率

# 1.修改日志同步频率
修改 appendfsync always | everysec | no 指定

AOF文件的重写

AOF带来的问题

AOF的方式也同时带来了另一个问题,就是持久化文件会变得越来越大。例如我们调用incr test命令100次,文件中必须保存全部的100条命令,其实有99条都是多余的。因为要回复数据库的状态其实文件中保存一条set test 100就够了。为了压缩AOF的持久化文件,redis提供了AOF重写机制。

AOF重写

在一定程度上减小AOF文件的体积

触发重写的方式

# 1.客户端方式触发重写
执行 BGREWRITEAOF 命令   不会阻塞redis的服务
​
# 2.服务器配置方式自动触发
配置redis.conf中的 auto-aof-rewrite-percentage选项
如果设置auto-aof-rewrite-percentage值为100和auto-aof-rewrite-min-size 64mb,并且启用AOF持久化时,那么当AOF文件体积大于64mb时会进行一次重写,并且在之后AOF文件每次比上一次重写之后的文件大了至少一倍(100%)时,会自动触发,如果重写过于频繁,用户可以考虑将auto-aof-rewrite-oercentage设置为更大。
​
例:
     重写       增长      重写       增长       重写
64mb----->20mb----->40mb----->18mb----->36mb----->......

重写原理

重写AOF文件的操作,并没有读取旧的AOF文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的AOF文件,然后用新的AOF文件替换原有的文件,与快照有点类似。

# 重写流程
​
1.redis调用fork,现在有父子两个进程,子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令;
2.父进程继续处理client请求,除了把之后收到的写命令写入到原来的AOF文件中,同时也会把这些命令缓存起来。这样就能保证如果子进程重写失败的话并不会出问题。
3.当子进程把快照内容以命令方式写入到临时文件中后,子进程发信号通知父进程。然后父进程把缓存的写命令也写入到临时文件。
4.现在父进程可以使用临时文件替换老的AOF文件,并重命名,后面收到的写命令也开始往新的AOF文件中追加。

持久化总结

两种持久化方案既可以同时使用,又可以单独使用,在某种情况下也可以都不适用,具体使用哪种持久化方案取决于用户的数据和应用;

当两种持久化方式同时启用时,redis会优先以AOF文件中的内容来加载内存数据;

无论使用AOF还是快照机制持久化,将数据持久化到硬盘都是有必要的,除了持久化外,用户还应该对持久化的文件进行备份,最好备份在多个不同的地方;

Redis主从复制

主从复制

主从复制架构仅仅用来解决数据的冗余备份,从节点仅仅用来同步数据

主从复制架构图

主从复制原理

主从复制可分为全量同步和增量同步。

全量同步

Redis全量复制一般发生在Slave初始化阶段,这时Slave需要将Master上的所有数据都复制一份。具体步骤如下:

  1. 从服务器连接主服务器,发送SYNC命令;

  2. 主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的所有写命令;

  3. 主服务器BGSAVE执行完后,向所有从服务器发送快照文件,并在发送期间继续记录被执行的写命令;

  4. 从服务器收到快照文件后丢弃所有旧数据,载入收到的快照;

  5. 主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令;

  6. 从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;

完成上面几个步骤后就完成了从服务器数据初始化的所有操作,从服务器此时可以接收来自用户的读请求。

增量同步

Redis增量复制是指Slave初始化后开始正常工作时主服务器发生的写操作同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。

搭建主从复制

# 1.准备3台机器并修改配置
​
# master
  port 6379
  bind 0.0.0.0
  
# slave1
  port 6380
  bind 0.0.0.0
  replicaof masterip masterport
  
# slave2
  port 6381
  bind 0.0.0.0
  replicaof masterip masterport

# 2.启动3台机器进行测试
​
redis-server /masterpath/redis.conf
redis-server /slave1path/redis.conf
redis-server /slave2path/redis.conf

无法解决的问题

1.master 节点出现故障后,自动故障转移。

主从复制虽然实现了数据的同步,达到了将数据备份的效果,但是当主节点宕机时,从节点并不能顶替主节点,为应用提供服务,为了实现 redis 的高可用,官方提供了一种哨兵机制来解决这个问题,从而实现 redis 的高可用性。

Redis哨兵机制

哨兵Sentinel机制

Sentinel(哨兵)是 Redis 的高可用性解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器。简单的说,哨兵就是带有自动故障转移功能的主从架构

哨兵架构原理

搭建哨兵架构

# 1.在主节点上创建哨兵配置
  新建一个 sentinel.conf 文件,名字不能错!
  
# 2.配置哨兵,在 sentinel.conf 文件中填入内容:
  sentinel monitor 被监控数据库名字(自己起一个名字) ip port count
  # ip:指需要被监控数据库的 ip
  # port:指需要被监控数据库的 port
  # count:指需要启用多少个哨兵来监控该数据库,当启用多个哨兵时,如果被监控的服务器宕机,
  #       需要半数以上的哨兵检测到主服务器宕机时,才会进行主从替换。
  
# 3.启动哨兵模式进行测试
  redis-sentinel /sentinelpath/sentinel.conf

无法解决的问题

1.单节点并发压力问题

2.单节点内存和磁盘的物理上限问题

3.数据冗余问题

当业务不断增大,并发请求不断增多,而单一服务节点的 cpu 的处理能力有限,在高并发下有着巨大压力,会导致 redis 的效率降低;

并且单一节点的内存和硬盘的大小是有上限的,由于 redis 会将数据暂存在内存,并且会将内存数据不断持久化到硬盘,久而久之,内存和硬盘的压力也会不断增大;

单纯的主从架构下,主节点和从节点都保存了全部数据,造成了数据冗余问题;

所以此时需要建立 redis 集群来解决这些问题。

Redis集群

集群

Redis 在3.0之后开始支持 Cluster模式,目前 redis 的集群支持节点的自动发现,支持 slave-master 选举和容错,支持在线分片(sharding shard)等特性。

集群架构图

集群细节

1.所有的redis节点彼此互联(PING-PONG机制),内部使用二进制协议优化传输速度和带宽。
2.节点的fail是通过集群中超过半数的节点检测失效时才生效
3.客户端与redis节点直连,不需要中间proxy层。客户端不需要连接集群所有节点,连接集群中任何一个可用节点即可
4.redis-cluster把所有的物理节点映射到[0-16383]slot上,cluster负责维护node<->slot<->value

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

waywt1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值