Redis通俗易懂丶

兴趣是最好的老师!

欣赏kuangshen:在外面做自己喜欢的事情,就算吃不饱饭,不愿意打工!

之前的感悟:打工仔只是创业者的衍生物丶

NoSQL概述

架构师有一个套路:什么东西都加一个中间层

数据库解决方案

  • cache(缓存,解决读的压力)
  • 垂直拆分(读写分离、主从复制,读写的时候锁表锁行,影响效率)
  • 水平拆分(分库分表(集群),解决写的压力)

NoSQL特点

  • 方便扩展(数据之间没有关系)
  • 大数据量高性能(Redis一秒写8万次,读11万次;NoSQL的缓存是记录级、细粒度的缓存,性能高)
  • 数据类型是多样型的(不需要事先设计数据库,设计数据库据说需要天赋)

RDBMS(关系型数据库)和NOSQL对比

传统RDBMS

- 结构化组织
- sql
- 数据和关系都存在单独的表中
- 操作数据库定义语言
- 严格的一致性
- 基础的事务
- ……

NOSQL

- 不仅仅是数据
- 没有固定的查询语言
- 键值对存储(Redis)、列存储(大数据Hbase)、文档存储(MongoDB)、图形数据库(Neo4j社交关系)
- 最终一致性
- cap 定理 和 base 理论(异地多活)只要学不死就往死里学
- 高性能,高可用,高可扩
- ……

3V3高

大数据时代的3V

  1. 海量Volume
  2. 多样Variety
  3. 实时Velocity

互联网需求的3高:

  1. 高并发
  2. 高可拓
  3. 高性能

NoSQL + RDBMS 结合使用才是最强大的!

NoSQL的四大分类

KV键值对:

  • sina:redis
  • meituan:redis+tair
  • ali:redis+memcached

文档型数据库(bjson):

  • Mongodb

    • 分布式文件存储数据库,c++,主要用以处理大量文档
    • mongodb是介于关系型数据库和非关系型数据库中间的产品,是非关系型数据库中功能最丰富、最像关系型数据库的
  • conthdb

列存储数据库

  • hbase
  • 分布式文件系统

图关系数据库

  • neo4j,infogrid

所有牛逼的人,都有一段苦逼的岁月丶但是需要去像傻x一样的去坚持!

Redis介绍

https://www.redis.net.cn/

http://www.redis.cn/

概述

- Remote Dictionary Server,远程字典服务
- 是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。redis会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件,并且在此基础上实现了master-slave(主从)同步。
- 免费和开源!是当下最热门的nosql技术之一!也被称之为结构化数据库!

功能

- 持久化的两种机制:RDB、AOF
- 效率高,可用于高速缓存
- 发布订阅(简单的消息队列的活)
- 地图信息分析
- 计时器,计数器(浏览量)

特性

- 多样的数据类型
- 持久化
- 集群
- 事务

Redis单机版安装

docker方式搭建

docker pull redis
docker images
mkdir /usr/local/redis
wget -P /usr/local/redis http://download.redis.io/redis-stable/redis.conf
vi /usr/local/redis/redis.conf
# 修改配置文件:输入/bind 127.0.0.1按N查找下一个,注释掉它,它是限制了只能本地访问
# daemonize改为yes,让其默认后台启动,docker启动可以不用修改
# 通过指定的配置文件启动
docker run -d --name redis -p 6379:6379 -v /usr/local/redis/redis.conf:/etc/redis/redis.conf redis redis-server /etc/redis/redis.conf --appendonly yes --requirepass 'test'
# 进入bash窗口
docker exec -it redis /bin/bash
# client连接server
redis-cli -p 6379
# 输入密码
auth "test"
# 测试
ping
set name xiaoming
get name
keys *
# 关闭redis
shutdown

6379是九宫格输入法MERZ,代表作者信奉的一种理念:but with very technical value, or with an impressive amount of skills and patience and work involved, but still... stupid.

如果要用Java程序连接,关闭保护模式(估计配置文件中也可以事先修改)

[root@10-9-48-229 ~]# docker exec -it redis /bin/bash
root@90702770fe66:/data# redis-cli -p 6379
127.0.0.1:6379> config set protected-mode "no"
OK

redis-benchmark性能测试

# 重新安装redis,这次不带密码
docker exec -it redis /bin/bash
redis-cli -p 6379
exit
redis-benchmark -c 100 -n 100000
# 发现docker启动的redis效率要比正常慢一倍以上

Redis基础命令

[root@10-9-48-229 ~]# docker exec -it redis /bin/bash
root@90702770fe66:/data# redis-cli -p 6379
127.0.0.1:6379> select 3 # 切换数据库
OK
127.0.0.1:6379[3]> dbsize # 查看db大小
(integer) 0
127.0.0.1:6379[3]> set name xiaoming #写入数据
OK
127.0.0.1:6379[3]> dbsize # 发现大小变化了
(integer) 1
127.0.0.1:6379[3]> select 7 # 验证不用的数据放在不同的库内
OK
127.0.0.1:6379[7]> dbsize
(integer) 0
127.0.0.1:6379[7]> get name
(nil)
127.0.0.1:6379[7]> select 3
OK
127.0.0.1:6379[3]> get name
"xiaoming"
127.0.0.1:6379[3]> flushdb # 清空当前db
OK
127.0.0.1:6379[3]> keys *
(empty array)
127.0.0.1:6379[3]> select 0 # 验证清空所有库的数据
OK
127.0.0.1:6379> keys *
1) "myhash:{tag}"
2) "counter:{tag}:__rand_int__"
3) "mylist:{tag}"
4) "key:{tag}:__rand_int__"
127.0.0.1:6379> select 3
OK
127.0.0.1:6379[3]> flushall
OK
127.0.0.1:6379[3]> select 0
OK
127.0.0.1:6379> keys *
(empty array)

redis是单线程的!redis是基于内存操作,cpu不是redis的性能瓶颈,其瓶颈是机器的内存和网络带宽。

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

  1. 高性能的服务器不一定是多线程的
  2. 多线程不一定比单线程效率高(cpu上下文切换消耗资源,redis将所有的数据放在内存,单线程效率更高)

五大数据类型

Redis-Key操作命令,对应Java API

127.0.0.1:6379> exists name # 判断key是否存在
(integer) 1
127.0.0.1:6379> keys * 
1) "age"
2) "name"
127.0.0.1:6379> move name 1 # 移除key
(integer) 1
127.0.0.1:6379> keys *
1) "age"
127.0.0.1:6379> set name xx
OK
127.0.0.1:6379> get name
"xx"
127.0.0.1:6379> expire name 10 # 设置key的过期时间
(integer) 1
127.0.0.1:6379> ttl name # 查看key的剩余时间
(integer) 6
127.0.0.1:6379> ttl name
(integer) -2 # -2表示已经过期,等于是移除了
127.0.0.1:6379> set name xx
OK
127.0.0.1:6379> type name # 查看类型
string

String

API调用工程师!

127.0.0.1:6379> set key1 v1
OK
127.0.0.1:6379> exists key1
(integer) 1
127.0.0.1:6379> append key1 "hello" # 字符串追加,如果不存在,就set一个
(integer) 7
127.0.0.1:6379> get key1
"v1hello"
127.0.0.1:6379> strlen key1 # 获取String长度
(integer) 7
127.0.0.1:6379> append key1 ",xiaoming"
(integer) 16
127.0.0.1:6379> get key1
"v1hello,xiaoming"

操作数值类型

127.0.0.1:6379> set views 0
OK
127.0.0.1:6379> get views
"0"
127.0.0.1:6379> incr views # 自增1
(integer) 1
127.0.0.1:6379> incr views
(integer) 2
127.0.0.1:6379> decr views # 自减1
(integer) 1
127.0.0.1:6379> incrby views 10 # 自增,步长10
(integer) 11
127.0.0.1:6379> decrby views 6 # 自减,步长6
(integer) 5

字符串获取范围

127.0.0.1:6379> clear
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> keys *
(empty array)
127.0.0.1:6379> set key1 "hello,xiaoming"
OK
127.0.0.1:6379> getrange key1 0 3 # 截取字符串 [0,3]
"hell"
127.0.0.1:6379> getrange key1 0 -1 # 同get key1
"hello,xiaoming"

替换字符串中的某一段

127.0.0.1:6379> clear
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"

设置过期时间

127.0.0.1:6379> setex key3 30 "hello"
OK
127.0.0.1:6379> ttl key3
(integer) 24
127.0.0.1:6379> get key3
"hello"

不存在再设置,存在就创建失败。

分布式锁中常使用

127.0.0.1:6379> setnx mykey "redis"
(integer) 1
127.0.0.1:6379> get mykey
"redis"
127.0.0.1:6379> setnx mykey "MongoDB"
(integer) 0
127.0.0.1:6379> get mykey
"redis"

批量设置

127.0.0.1:6379> flushdb
OK
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 v11 k4 v4 # 原子性操作,要么一起成功、要么一起失败
(integer) 0 # 未设置成功,因为k1已经存在了(nx表示not exists)
127.0.0.1:6379> mget k1 k2 k3 k4
1) "v1"
2) "v2"
3) "v3"
4) (nil)

实用操作:保存对象

# set user:1 {name:zhangsan,age:3} # 设置user1对象,json保存对象
# 或使用下面的方式
127.0.0.1:6379> mset user:1:name zhangsan user:1:age 12
OK
127.0.0.1:6379> mget user:1:name user:1:age
1) "zhangsan"
2) "12"

组合命令:getset更新操作

127.0.0.1:6379> getset db redis
(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

在Redis里,可以把List搞成队列、栈、阻塞队列。

[root@10-9-48-229 ~]# docker exec -it redis /bin/bash
root@90702770fe66:/data# redis-cli -p 6379
127.0.0.1:6379> lpush list one # 将一个或多个值插入列表的头部(左) 这里list表示名称
(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> lrange list 0 -1
1) "three"
2) "two"
3) "one"
127.0.0.1:6379> lrange list 0 1
1) "three"
2) "two"
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 # 移除第一个元素
"three"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
3) "right"
127.0.0.1:6379> rpop list # 移除最后一个元素
"right"
127.0.0.1:6379> lrange list 0 -1
1) "two"
2) "one"
127.0.0.1:6379> lindex list 0 # 根据下标获取元素
"two"
127.0.0.1:6379> lindex list 1
"one"
127.0.0.1:6379> llen list # 返回列表的长度
(integer) 2
127.0.0.1:6379> lrem list 1 one # 移除一个one元素
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 1 three
(integer) 1
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "two"
127.0.0.1:6379> lpush list three
(integer) 3
127.0.0.1:6379> lrange list 0 -1
1) "three"
2) "three"
3) "two"
127.0.0.1:6379> lrem list 2 three # 移除两个three元素
(integer) 2
127.0.0.1:6379> lrange list 0 -1
1) "two"
127.0.0.1:6379> flushdb
OK
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"
127.0.0.1:6379> rpoplpush mylist mylist2 # 移除队尾元素并将其添加到另一个队列的头部
"hello2"
127.0.0.1:6379> lrange mylist 0 -1
1) "hello1"
127.0.0.1:6379> lrange mylist2 0 -1
1) "hello2"
1) "hello2"
127.0.0.1:6379> clear
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> exists list
(integer) 0
127.0.0.1:6379> lpush lista value1
(integer) 1
127.0.0.1:6379> lrange lista 0 0
1) "value1"
127.0.0.1:6379> lset lista 0 item1 # 重写/覆盖第0个元素
OK
127.0.0.1:6379> lrange lista 0 0
1) "item1"
127.0.0.1:6379> lset lista 1 item # 如果不存在,则会报错
(error) ERR index out of range
127.0.0.1:6379> flushdb
OK
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存在,新增元素;
- 如果移除了所有值,也代表List不存在了;
- 在两边插入和改动值,效率高一些,操作中间元素,效率低一些。

Set

无序不重复集合

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> sadd myset "hello" # 往set集合中加元素
(integer) 1
127.0.0.1:6379> sadd myset "xiaoming"
(integer) 1
127.0.0.1:6379> sadd myset "hahahahahah"
(integer) 1
127.0.0.1:6379> smembers myset # 查看集合中的所有成员
1) "hahahahahah"
2) "xiaoming"
3) "hello"
127.0.0.1:6379> sismember myset hello # 判断某成员是否在集合中
(integer) 1
127.0.0.1:6379> sismember myset world
(integer) 0
127.0.0.1:6379> sadd myset "hahahahahah" # 添加重复的值会失败
(integer) 0
127.0.0.1:6379> scard myset # 查看集合中成员个数
(integer) 3
127.0.0.1:6379> srem myset hello # 移除集合中某个元素
(integer) 1
127.0.0.1:6379> smembers myset
1) "hahahahahah"
2) "xiaoming"
127.0.0.1:6379> smembers myset
1) "hahahahahah"
2) "test1"
3) "test2"
4) "xiaoming"
5) "test3"
127.0.0.1:6379> srandmember myset # 随机抽出一个元素
"test3"
127.0.0.1:6379> srandmember myset
"hahahahahah"
127.0.0.1:6379> srandmember myset
"xiaoming"
127.0.0.1:6379> srandmember myset 2 # 随机抽出两个元素
1) "hahahahahah"
2) "xiaoming"
127.0.0.1:6379> smembers myset
1) "test1"
2) "test2"
3) "hahahahahah"
4) "xiaoming"
5) "test3"
127.0.0.1:6379> spop myset # 随机删除set集合中的元素
"test2"
127.0.0.1:6379> smembers myset
1) "test1"
2) "hahahahahah"
3) "xiaoming"
4) "test3"
127.0.0.1:6379> flushdb
OK
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 "love"
(integer) 1
127.0.0.1:6379> sadd myset2 "set2"
(integer) 1
127.0.0.1:6379> smove myset myset2 "love" # 将集合中指定的值移动到另一个集合中
(integer) 1
127.0.0.1:6379> smembers myset
1) "world"
2) "hello"
127.0.0.1:6379> smembers myset2
1) "love"
2) "set2"
127.0.0.1:6379> flushdb
OK
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 c
(integer) 1
127.0.0.1:6379> sadd key2 d
(integer) 1
127.0.0.1:6379> sdiff key1 key2 # 两个集合的差集
1) "a"
2) "b"
127.0.0.1:6379> sinter key1 key2 # 交集
1) "c"
127.0.0.1:6379> sunion key1 key2 # 并集
1) "d"
2) "a"
3) "c"
4) "b"

Hash

Key-Map集合

[root@10-9-48-229 ~]# docker exec -it redis /bin/bash
root@90702770fe66:/data# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> hset myhash field1 xiaoming # set一个域和值
(integer) 1
127.0.0.1:6379> hget myhash field1 # get一个域
"xiaoming"
127.0.0.1:6379> hmset myhash field1 hello field2 world # m代表multiple指多个;set的时候field存在会覆盖
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"
127.0.0.1:6379> hdel myhash field1 # 删除指定域
(integer) 1
127.0.0.1:6379> hgetall myhash
1) "field2"
2) "world"
127.0.0.1:6379> hlen myhash #得到域的个数
(integer) 1
127.0.0.1:6379> hexists myhash field1 # 判断hash中的某个域是否存在
(integer) 1
127.0.0.1:6379> hexists myhash field3
(integer) 0
127.0.0.1:6379> hkeys myhash # 得到该hash所有的域
1) "field2"
2) "field1"
127.0.0.1:6379> hvals myhash # 得到hash所有的值
1) "world"
2) "hello"
127.0.0.1:6379> hset myhash field3 5
(integer) 1
127.0.0.1:6379> hincrby myhash field5 1
(integer) 1
127.0.0.1:6379> hdel myhash field5
(integer) 1
127.0.0.1:6379> hincrby myhash field3 1 # 自增1
(integer) 6
127.0.0.1:6379> hincrby myhash field3 -1 # 自增-1
(integer) 5
127.0.0.1:6379> hsetnx myhash field4 hello # 不存在,则搞进去
(integer) 1
127.0.0.1:6379> hsetnx myhash field4 hello
(integer) 0

经常变动的信息可以用hash保存,适合存储对象

Zset

有序集合

127.0.0.1:6379> flushdb
OK
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 3 three 4 four # 添加多个
(integer) 2
127.0.0.1:6379> zrange myset 0 -1 # 获取所有元素
1) "one"
2) "two"
3) "three"
4) "four"
127.0.0.1:6379> zadd salary 2500 xiaohong # 薪水 集合
(integer) 1
127.0.0.1:6379> zadd salary 5000 xiaoming
(integer) 1
127.0.0.1:6379> zadd salary 500  hahah
(integer) 1
127.0.0.1:6379> zrangebyscore salary -inf +inf # 排序 负无穷到正无穷 infinite无限的无穷尽的
1) "hahah"
2) "xiaohong"
3) "xiaoming"
127.0.0.1:6379> zrevrange salary 0 -1 # 排序,倒序排列
1) "xiaoming"
2) "hahah"
127.0.0.1:6379> zrangebyscore salary -inf +inf withscores # 排序输出 带上大小
1) "hahah"
2) "500"
3) "xiaohong"
4) "2500"
5) "xiaoming"
6) "5000"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores
1) "hahah"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> zrangebyscore salary -inf 2500 withscores
1) "hahah"
2) "500"
3) "xiaohong"
4) "2500"
127.0.0.1:6379> zrange salary 0 -1
1) "hahah"
2) "xiaohong"
3) "xiaoming"
127.0.0.1:6379> zrem salary xiaohong # 干掉小红
(integer) 1
127.0.0.1:6379> zcard salary # 统计有序集合中的个数
(integer) 2
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> zadd myset 1 hello 2 world 3 xiaoming
(integer) 3
127.0.0.1:6379> zcount myset 1 3 # 获取指定区间的成员数量
(integer) 3
127.0.0.1:6379> zcount myset 1 2
(integer) 2

SortedSets最佳实践

  • 班级成绩表、工资排序表
  • 普通消息、重要消息
  • 排行榜top100

三种特殊数据类型

Geospatial diz 地理位置

朋友的定位、附近的人、打车距离计算

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> geoadd china:city 116.41 39.90 beijing
(integer) 1
127.0.0.1:6379> geoadd china:city 121.47 31.23 shanghai 114.09 22.55 shenzhen # 添加城市
(integer) 2
127.0.0.1:6379> geoadd china:city 106.50 29.53 chongqing 120.15 30.28 hangzhou 108.96 34.27 xian
(integer) 3
127.0.0.1:6379> geopos china:city beijing # 获取指定城市的经度和纬度
1) 1) "116.40999823808670044"
   2) "39.90000009167092543"
127.0.0.1:6379> geopos china:city beijing chongqing
1) 1) "116.40999823808670044"
   2) "39.90000009167092543"
2) 1) "106.49999767541885376"
   2) "29.52999957900659211"
127.0.0.1:6379> geodist china:city beijing shanghai
"1066992.3081"
127.0.0.1:6379> geodist china:city beijing shanghai km # 北京和上海之间的直线距离
"1066.9923"
127.0.0.1:6379> georadius china:city 110 30 1000 km # 以110 30为中心,搜索半径1000km内的城市
1) "chongqing"
2) "xian"
3) "shenzhen"
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) "484.9221"
   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.26999911916288966"
127.0.0.1:6379> georadius china:city 110 30 500 km withcoord withdist count 1
1) 1) "chongqing"
   2) "341.9374"
   3) 1) "106.49999767541885376"
      2) "29.52999957900659211"
127.0.0.1:6379> georadiusbymember china:city beijing 1000 km # 找城市附近的城市
1) "beijing"
2) "xian"
127.0.0.1:6379> georadiusbymember china:city shanghai 400 km
1) "hangzhou"
2) "shanghai"
127.0.0.1:6379> geohash china:city beijing chongqing # 将二维的经纬度位置转换为11位的hash字符串
1) "wx4fbzx4me0"
2) "wm5xzrybty0"
127.0.0.1:6379> zrange china:city 0 -1 # 本质还是集合,
1) "chongqing"
2) "xian"
3) "shenzhen"
4) "hangzhou"
5) "shanghai"
6) "beijing"
127.0.0.1:6379> zrem china:city beijing # 移除某个元素
(integer) 1

Hyperloglog

应用场景:统计网站访问量(可能存在0.81%的误差,优势是位运算,占用空间仅为固定12K)

# 集合形式 基数统计
127.0.0.1:6379> pfadd mykey a b c d e f g h i j # 创建第一组元素
(integer) 1
127.0.0.1:6379> pfcount mykey # 这个目测可以用来统计访问量
(integer) 10
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
(integer) 9
127.0.0.1:6379> pfmerge mykey3 mykey mykey2 # 合并两组元素
OK
127.0.0.1:6379> pfcount mykey3
(integer) 15

Bitmap

位存储:用户是否活跃、员工是否打卡、登录或未登录(只要有两个状态的)

# 记录周一到周五打卡或未打卡
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 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> setbit sign 5 0
(integer) 0
127.0.0.1:6379> setbit sign 6 0
(integer) 0
127.0.0.1:6379> getbit sign 3 # 查询周四是否打卡
(integer) 1
127.0.0.1:6379> bitcount sign # 查询打卡的总数
(integer) 3

事务

Redis事务:一组命令的集合。具有:一次性、顺序性、排他性。

Redis单条命令是保证原子性的,但是事务不保证原子性;没有隔离级别的概念

Redis的事务

  • 开启事务(multi)
  • 命令入队(……)
  • 执行事务(exec)
127.0.0.1:6379> multi # 开启事务
OK
# 命令入队
127.0.0.1:6379> set key1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> get k2
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec # 执行事务
1) OK
2) OK
3) "v2"
4) OK
# 测试 放弃事务
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> discard # 放弃事务
OK
127.0.0.1:6379> keys *
1) "key1"
2) "k2"
3) "k3"

编译型异常:事务中的所有命令都不会被执行

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k1 v1
QUEUED
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> getset k3 # 命令语法错误,会导致一系列命令都不被执行
(error) ERR wrong number of arguments for 'getset' command
127.0.0.1:6379> set k4 v4
QUEUED
127.0.0.1:6379> exec
(error) EXECABORT Transaction discarded because of previous errors.

运行时异常(类似Java 1/0、下标越界):错误命令抛出异常,其他命令正常执行

127.0.0.1:6379> clear
127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set k1 "v1"
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set k2 v2
QUEUED
127.0.0.1:6379> incr k1 # 这里出错,不影响前后命令
QUEUED
127.0.0.1:6379> set k3 v3
QUEUED
127.0.0.1:6379> exec
1) OK
2) (error) ERR value is not an integer or out of range
3) OK
127.0.0.1:6379> keys *
1) "k1"
2) "k2"
3) "k3"

用watch监控实现乐观锁丶

127.0.0.1:6379> flushdb
OK
127.0.0.1:6379> set money 100
OK
127.0.0.1:6379> set out 20
OK
127.0.0.1:6379> watch money # 监视 money 对象
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 20
QUEUED
127.0.0.1:6379> incrby out 20
QUEUED
127.0.0.1:6379> exec # 事务正常结束,监控会取消掉 否则需要执行unwatch
1) (integer) 80
2) (integer) 40
# 多线程测试:watch实现乐观锁
127.0.0.1:6379> watch money
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> decrby money 10
QUEUED
127.0.0.1:6379> incrby out 10
QUEUED
127.0.0.1:6379> exec
(nil) # 下次需要先unwatch
# 执行 exec 命令之前 新开一个会话窗口,充当另一个线程 在上面的事务过程中修改了数据 被watch到了
[root@10-9-48-229 ~]# docker exec -it  redis /bin/bash
root@90702770fe66:/data# redis-cli -p 6379
127.0.0.1:6379> get money
"80"
127.0.0.1:6379> set money 1000
OK

Jedis

先建空项目,再建子项目,选好Java&maven环境及idea设置,api与上文的命令对应,源码见

SpringBoot整合

spring boot操作数据相关的内容都在spring-data里:jpa、jdbc、mongodb、redis等,spring boot和spring data齐名

jedis:直连,多线程不安全,避免的方法是连接池,但是造成server会特别大,更像BIO(阻塞)模式

lettuce:采用netty,实例可以在多个线程中进行共享,不存在线程不安全的问题,可以减少线程数量,更像NIO(非阻塞)模式

导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>

配置连接

# spring-boot-autoconfigure-2.3.4.RELEASE.jar ->
# ->spring.factories
# ->RedisAutoConfiguration
# ->RedisProperties
spring.redis.host=106.75.32.166
spring.redis.port=6379

测试

@SpringBootTest
class Redis02SpringbootApplicationTests {
    @Autowired
    @Qualifier("redisTemplate")
    //private RedisTemplate<String,String> redisTemplate;
    private RedisTemplate redisTemplate;

    @Autowired
    private RedisUtil redisUtil;

    @Test
    void contextLoads() {
        //redisTemplate 操作不同的类型 api和我们的指令是一致的
        //opsForValue 操作字符串
        //opsForList  操作List
        //opsForSet
        //opsForHash
        //连接级别的操作通过:RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();进行
        redisTemplate.opsForValue().set("myKey","小明dd");
        System.out.println(redisTemplate.opsForValue().get("myKey"));
    }

    @Test
    public void test() throws JsonProcessingException {
        User user = new User("小明", 3);
        String jsonUser = new ObjectMapper().writeValueAsString(user);
        redisTemplate.opsForValue().set("user",jsonUser);
        System.out.println(redisTemplate.opsForValue().get("user"));
    }

    @Test
    public void test2() {
        redisUtil.set("kk", "nam");
        System.out.println(redisUtil.get("kk"));
    }
}

配置类、工具类见源码:解决中文、对象存储

Redis.conf详解

/usr/local/redis/redis.conf,能看懂,就比新手厉害了丶行家有没有,出手就知道,一些小小的配置,就可以显著的优化性能,让你脱颖而出!

# 单位配置不区分大小写:1GB 1Gb 1gB
################################## INCLUDES ###################################
# 表示引入其他配置文件
################################## MODULES #####################################
# 加载.so文件,不重要
################################## NETWORK #####################################
# 网络
bind 127.0.0.1
# 表示只能在本地访问
protected-mode yes
# 受保护的模式,java连接的话需要将其设置为no
port 6379
# 端口设置
################################# TLS/SSL #####################################
# 不管
################################# GENERAL #####################################
# 通用设置
daemonize no
# 通常设置为yes,表示后台运行(守护进程方式)
pidfile /var/run/redis_6379.pid
# 配置文件的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 yes
# 是否总是显示logo
################################ SNAPSHOTTING  ################################
# 快照
# 持久化,在规定的时间内,执行了多少次操作,会持久化到文件.rdb .aof
save 900 1
# 900秒内,如果有1个key进行了修改,系统会进行持久化操作。下同
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
# 持久化出错后是否继续工作
rdbcompression yes
# 是否压缩rdb文件,需要消耗cpu资源
rdbchecksum yes
# 保存rdb文件时,进行错误的检查校验
dir ./
# rdb保存的目录,默认当前目录
################################# REPLICATION #################################
# 主从复制用
################################## SECURITY ###################################
# requirepass foobared
# 默认是没有密码,requirepass 123456,通常用命令进行设置密码:config set/get requirepass 123456
################################### CLIENTS ####################################
# maxclients 10000 最大客户端数量
############################## MEMORY MANAGEMENT ################################
# maxmemory <bytes> 最大内存容量
# maxmemory-policy noeviction 内存到达上限之后的处理策略(移除过期的key、报错等)
# noeviction: 不删除策略, 达到最大内存限制时, 如果需要更多内存, 直接返回错误信息。(默认值)
# allkeys-lru: 所有key通用; 优先删除最近最少使用(less recently used ,LRU) 的 key。
# volatile-lru: 只限于设置了 expire 的部分; 优先删除最近最少使用(less recently used ,LRU) 的 key。
# allkeys-random: 所有key通用; 随机删除一部分 key。
# volatile-random: 只限于设置了 expire 的部分; 随机删除一部分 key。
# volatile-ttl: 只限于设置了 expire 的部分; 优先删除剩余时间(time to live,TTL) 短的key。
############################## APPEND ONLY MODE ###############################
# aof相关配置
appendonly no
# 默认是不开启aof模式,默认使用rdb方式持久化,大部分情况rdb够用了
appendfilename "appendonly.aof"
# 持久化文件的名字
# appendfsync always
# 每次修改都会同步(消耗性能)
appendfsync everysec
# 每秒执行一次sync,可能会丢失这1秒的数据
# appendfsync no
# 不同步,这个时候操作系统自己同步数据,速度最快

Redis持久化

RDB(Redis DataBase)

优:一个dump.rdb方便、恢复数据效率高、fork子进程性能好

劣:最后一次保存的快照信息可能会丢失、因为fork子进程导致服务短暂停滞

img

################################ SNAPSHOTTING  ################################
# 快照
# 持久化,在规定的时间内,执行了多少次操作,会持久化到文件.rdb .aof
save 900 1
# 900秒内,如果有1个key进行了修改,系统会进行持久化操作。下同
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
# 持久化出错后是否继续工作
rdbcompression yes
# 是否压缩rdb文件,需要消耗cpu资源
rdbchecksum yes
# 保存rdb文件时,进行错误的检查校验
dir ./
# rdb保存的目录,默认当前目录

Docker启动找不到目录可以输入docker inspect 容器名进行查看"Mounts":[{"Source": "/var/lib/docker/volumes/fc214b8d4ef0fbcf56d7cf799cc79e1d38ebfe9035a6cf6d2c60798db1bd1eb2/_data"

触发保存rdb的条件
- save设置的条件满足时
- 执行了flushall命令
- 退出redis(shutdown)时
用rdb恢复数据?只需要将文件放到redis的启动目录即可
通常只在从机上持久化RDB文件,并且只保留15分钟的方案

AOF(Append Only File)

优:每秒每修改和不同步带来更高的数据安全性、append这个动作就很温柔不影响以前记录且可以check、rewrite机制写到磁盘、格式清晰易理解

劣:文件较大、恢复慢

img

############################## APPEND ONLY MODE ###############################
# aof相关配置
appendonly no
# 默认是不开启aof模式,默认使用rdb方式持久化,大部分情况rdb够用了
appendfilename "appendonly.aof"
# 持久化文件的名字
# appendfsync always
# 每次修改都会同步(消耗性能)
appendfsync everysec
# 每秒执行一次sync,可能会丢失这1秒的数据
# appendfsync no
# 不同步,这个时候操作系统自己同步数据,速度最快
修改配置文件开启aof,重启redis即可产生.aof文件,接下来每秒钟都会往里边进行append
挖槽,这个aof破坏之后,在docker环境下咋个模拟用check修复呢……

Redis发布订阅

SUBSCRIBE runoobChat
PUBLISH runoobChat "Redis PUBLISH test"

搭建3主3从(太监)

三主三从,无中心节点

先保证云主机端口是开启的:7001-7006,17001-17006

拉取镜像

docker pull redis

新建文件夹redis-cluster2

mkdir /usr/local/redis-cluster2

新建redis-cluster2.tmpl文件(配置文件的模板,在creatRedisCluster2.sh中会使用)

vim redis-cluster2.tmpl

内容如下

##设置外部网络连接redis服务,默认是yes,即开启。开启protected-mode保护模式,需配置bind ip或者设置访问密码,关闭protected-mode模式,此时外部网络可以直接访问
protected-mode no
##节点端口
port ${PORT}
##指定redis是否要用守护线程的方式启动,默认no
daemonize no
##持久化模式
appendonly yes
##cluster集群模式
cluster-enabled yes
##集群配置名
cluster-config-file nodes.conf
##超时时间
cluster-node-timeout 15000
##实际为各节点网卡分配ip
cluster-announce-ip 106.75.32.166
##节点映射端口
cluster-announce-port ${PORT}
##节点总线端
cluster-announce-bus-port 1${PORT}
##连接密码(可选)
requirepass 123456

新建脚本creatRedisCluster2.sh创建目录及配置文件

vim creatRedisCluster2.sh

内容如下

for port in `seq 7001 7006`; do 
  base=6999 
  && ip=$[port-base] 
  && mkdir -p ${port}/conf 
  && PORT=${port} TEMP=${ip} envsubst < redis-cluster2.tmpl > ${port}/conf/redis.conf 
  && mkdir -p ${port}/data;
done

赋予执行权限

chmod 755 creatRedisCluster2.sh

创建自定义网桥

docker network create redis-net

创建启动docker模板(启动docker,试想这些可能事不同机器上的docker及容器)

vi creatDocker.sh

内容如下

for port in `seq 7001 7006`; do
  docker run -p ${port}:${port} -p 1${port}:1${port} --name redis-${port} 
  -v /usr/local/redis-cluster2/${port}/conf/redis.conf:/usr/local/etc/redis/redis.conf 
  -v /usr/local/redis-cluster2/${port}/data:/data --name redis-${port} --net redis-net -d  redis  redis-server /usr/local/etc/redis/redis.conf;
done

说明

--name redis-${port}:容器的名字
-d redis:拉取的镜像名字 redis构建启用容器
redis-server /usr/local/etc/redis/redis.conf::用/usr/local/etc/redis/redis.conf文件作为启动redis的配置文件

赋予执行权限

chmod 755 creatDocker.sh

执行脚本

./creatRedisCluster2.sh

执行结果

[root@10-9-48-229 redis-cluster2]# ./creatRedisCluster2.sh
[root@10-9-48-229 redis-cluster2]# ls
7001  7002  7003  7004  7005  7006  creatDocker.sh  creatRedisCluster2.sh  redis-cluster2.tmpl
[root@10-9-48-229 redis-cluster2]# tree
.
├── 7001
│   ├── conf
│   │   └── redis.conf
│   └── data
├── 7002
│   ├── conf
│   │   └── redis.conf
│   └── data
├── 7003
│   ├── conf
│   │   └── redis.conf
│   └── data
├── 7004
│   ├── conf
│   │   └── redis.conf
│   └── data
├── 7005
│   ├── conf
│   │   └── redis.conf
│   └── data
├── 7006
│   ├── conf
│   │   └── redis.conf
│   └── data
├── creatDocker.sh
├── creatRedisCluster2.sh
└── redis-cluster2.tmpl

18 directories, 9 files

执行脚本

./creatDocker.sh

执行结果

[root@10-9-48-229 redis-cluster2]# ./creatDocker.sh
f0018b73a7f994bd50fdca15f137ff2f955b9dbb9104797bccb1c0a593c7de7d
cfecb00ba432a2152439c7c51f680fe11e126f4c3ce08039bf033a32d160d610
6431a01bc762596e300b97b4bf340a89e661ae939b30b7adc22bd948ca2efa0d
ba7326b91058c24ef3028cc5ed6fc7696c07d045c7ec7b8f277f6aa6cd3286e7
7cbc9f1ece227f059869f1d331e3a30002a4cd80612a7a2d994859afcb5c88bd
19cca5cd14fb2e4529d60ae8ba7263daed00da98ca5285396977ee36ccf14627
[root@10-9-48-229 redis-cluster2]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                                        NAMES
19cca5cd14fb        redis               "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:7006->7006/tcp, 6379/tcp, 0.0.0.0:17006->17006/tcp   redis-7006
7cbc9f1ece22        redis               "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:7005->7005/tcp, 6379/tcp, 0.0.0.0:17005->17005/tcp   redis-7005
ba7326b91058        redis               "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:7004->7004/tcp, 6379/tcp, 0.0.0.0:17004->17004/tcp   redis-7004
6431a01bc762        redis               "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:7003->7003/tcp, 6379/tcp, 0.0.0.0:17003->17003/tcp   redis-7003
cfecb00ba432        redis               "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:7002->7002/tcp, 6379/tcp, 0.0.0.0:17002->17002/tcp   redis-7002
f0018b73a7f9        redis               "docker-entrypoint.s…"   2 minutes ago       Up 2 minutes        0.0.0.0:7001->7001/tcp, 6379/tcp, 0.0.0.0:17001->17001/tcp   redis-7001

随便进入一个容器

docker exec -it redis-7001 /bin/bash

执行集群命令,执行命令之后要输入yes(将不同机器上的容器规划成一个集群)

redis-cli  --cluster create 106.75.32.166:7001 106.75.32.166:7002 106.75.32.166:7003 106.75.32.166:7004 106.75.32.166:7005 106.75.32.166:7006 --cluster-replicas 1 -a 123456 # --cluster-replicas 1 一主一从

执行结果(主机123从机564)

root@f0018b73a7f9:/data# redis-cli  --cluster create 106.75.32.166:7001 106.75.32.166:7002 106.75.32.166:7003 106.75.32.166:7004 106.75.32.166:7005 106.75.32.166:7006 --cluster-replicas 1 -a 123456
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica 106.75.32.166:7005 to 106.75.32.166:7001
Adding replica 106.75.32.166:7006 to 106.75.32.166:7002
Adding replica 106.75.32.166:7004 to 106.75.32.166:7003
>>> Trying to optimize slaves allocation for anti-affinity
[WARNING] Some slaves are in the same host as their master
M: 5ba40559ac1a136158e4eb66475792bba651fff4 106.75.32.166:7001
   slots:[0-5460] (5461 slots) master
M: 0164dc219485c3d598cb6e8ad72b2cd5cada19be 106.75.32.166:7002
   slots:[5461-10922] (5462 slots) master
M: 04d1fa0907a37e6d7d0f1fe321d1fac3b5c1036c 106.75.32.166:7003
   slots:[10923-16383] (5461 slots) master
S: d0bf21473ab351f53f56e9e3af63924aeda7b30a 106.75.32.166:7004
   replicates 04d1fa0907a37e6d7d0f1fe321d1fac3b5c1036c
S: 55f03a29141a8dad7bb8fb6e7dd7638806447718 106.75.32.166:7005
   replicates 5ba40559ac1a136158e4eb66475792bba651fff4
S: 289c038d4e54c0c172ef133ad9828b52bda9c021 106.75.32.166:7006
   replicates 0164dc219485c3d598cb6e8ad72b2cd5cada19be
Can I set the above configuration? (type 'yes' to accept): yes

随便进入一个容器

docker exec -it redis-7001 /bin/bash
redis-cli -c -h 106.75.32.166 -p 7001
auth 123456
ping
cluster nodes
set name xiaoming

客户端连接

image-20201022120730768

主机主要写、从机主要读,主机单向向从机进行复制,读写分离,解决读的问题;单台redis最大使用内存不应超过20G;非docker搭建的集群命令:info replication # 查看当前库的信息、slaveof 127.0.0.1 6379 # 认老大

手动实验上面的3主3从发现

  • 向集群写入内容,集群会自动分配/切换主机进行写,写完之后,查看从机key*发现还是空的,--cluster-replicas没卵用
  • 客户端连接的话,每个机器都能看到全量的数据

主从复制没有得到验证,宣布太监了

搭建1主2从3哨兵(太监)

仅仅几个redis实例,用docker启动确实意义不大,用docker就应该搞服务编排与升级降级…先不管了

拓扑结构

img

先开端口

6379:master节点
6380:slave节点
6381:slave节点
26379:sentinel节点
26380:sentinel节点
26381:sentinel节点
16379:总线端口
16380:总线端口
16381:总线端口

新建文件夹

mkdir /usr/local/redis123
cd /usr/local/redis123
mkdir /usr/local/redis123/master
mkdir /usr/local/redis123/slave_6380
mkdir /usr/local/redis123/slave_6381
mkdir /usr/local/redis123/sentinel_26379
mkdir /usr/local/redis123/sentinel_26380
mkdir /usr/local/redis123/sentinel_26381

拉取镜像

docker pull redis

下载配置文件

wget http://download.redis.io/redis-stable/redis.conf

cp一份命名为redis_master_6379.conf修改如下内容

cp redis.conf redis_master_6379.conf
port 6379
dir "/data" # 默认文件路径,dbfilename及logfile都会在这个路径下创建
dbfilename "dump_6379.rdb"
logfile "6379.log"
# 注释这一行,表示Redis可以接受任意ip的连接
# bind 127.0.0.1 
# 关闭保护模式
protected-mode no 
# 后台运行会报错,只能设为no
daemonize no 
# 设定密码(可选,如果这里开启了密码要求,slave的配置里就要加这个密码. 只是练习配置,就不使用密码认证了)
# requirepass masterpassword 

启动主节点

docker run -d -p 6379:6379 --name redis_master -v /usr/local/redis123/redis_master_6379.conf:/redis/redis.conf -v /usr/local/redis123/master/:/data redis:latest redis-server /redis/redis.conf
# 解释
-p 6379:6379  # 端口映射
--name redis_master  # 容器名称
-v /usr/local/redis123/redis_master_6379.conf:/redis/redis.conf  # 配置文件挂载
-v /usr/local/redis123/master/:/data  # 数据卷挂载
redis:latest  # 镜像
redis-server /redis/redis.conf   # docker启动后执行的命令,配置文件在redis下

开始搞第一个从节点

cp redis.conf redis_slave_6380.conf
# 修改如下配置
port 6379 # 由于是docker部署,所以内部端口都是6379
daemonize no # 不设置为守护模式(后台运行)
dir "/data" # 工作目录
dbfilename "dump_6380.rdb" # 持久化文件名称
logfile "6380.log" # 日志文件名称
# 注释这一行,表示Redis可以接受任意ip的连接
# bind 127.0.0.1 
# 关闭保护模式
protected-mode no 
# 
daemonize no 
# 设定密码(可选,如果这里开启了密码要求,slave的配置里就要加这个密码)
requirepass masterpassword 
# 设定主库的密码,用于认证,如果主库开启了requirepass选项这里就必须填相应的密码
masterauth <master-password>
# 设定master的IP和端口号,redis配置文件中的默认端口号是6379
# 低版本的redis这里会是slaveof,意思是一样的,因为slave是比较敏感的词汇,所以在redis后面的版本中不在使用slave的概念,取而代之的是replica
# 将35.236.172.131做为主,其余两台机器做从。ip和端口号按照机器和配置做相应修改。
replicaof 106.75.32.166 6379

启动第一个从节点

docker run -d -p 6380:6379 --name redis_slave_1 -v /usr/local/redis123/redis_slave_6380.conf:/redis/redis.conf -v /usr/local/redis123/slave_6380/:/data redis:latest redis-server /redis/redis.conf
# 解释
-p 6380:6379  # 端口映射
--name redis_slave_1  # 容器名称
-v /usr/local/redis123/redis_slave_6380.conf:/redis/redis.conf  # 配置文件挂载
-v /usr/local/redis123/slave_6380/:/data  # 数据卷挂载
redis:latest  # 镜像
redis-server /redis/redis.conf   # docker启动后执行的命令,配置文件在redis下

检查第一个从节点

[root@10-9-48-229 ~]# docker exec -it redis_slave_1 /bin/bash
root@2af58390bde6:/data# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:106.75.32.166
master_port:6379
master_link_status:up
master_last_io_seconds_ago:8
master_sync_in_progress:0
slave_repl_offset:56
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:4d3b177ec1321267b2789594441b7aca35479bca
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:56
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:56

配置第二个从节点

cp redis.conf redis_slave_6381.conf
# 修改如下配置
port 6379 # 由于是docker部署,所以内部端口都是6379
daemonize no # 不设置为守护模式(后台运行)
dir "/data" # 工作目录
dbfilename "dump_6381.rdb" # 持久化文件名称
logfile "6381.log" # 日志文件名称
# 注释这一行,表示Redis可以接受任意ip的连接
# bind 127.0.0.1 
# 关闭保护模式
protected-mode no 
# 
daemonize no 
# 设定密码(可选,如果这里开启了密码要求,slave的配置里就要加这个密码)
requirepass masterpassword 
# 设定主库的密码,用于认证,如果主库开启了requirepass选项这里就必须填相应的密码
masterauth <master-password>
# 设定master的IP和端口号,redis配置文件中的默认端口号是6379
# 低版本的redis这里会是slaveof,意思是一样的,因为slave是比较敏感的词汇,所以在redis后面的版本中不在使用slave的概念,取而代之的是replica
# 将35.236.172.131做为主,其余两台机器做从。ip和端口号按照机器和配置做相应修改。
replicaof 106.75.32.166 6379

启动第二个从节点

docker run -d -p 6381:6379 --name redis_slave_2 -v /usr/local/redis123/redis_slave_6381.conf:/redis/redis.conf -v /usr/local/redis123/slave_6381/:/data redis:latest redis-server /redis/redis.conf

查看第二个从节点

[root@10-9-48-229 ~]# docker exec -it redis_slave_2 /bin/bash
root@e4318bd3e641:/data# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
root@5fc83f0afe53:/data# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> info replication
# Replication
role:slave
master_host:106.75.32.166
master_port:6379
master_link_status:up
master_last_io_seconds_ago:9
master_sync_in_progress:0
slave_repl_offset:868
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:8dd4c5bcdec908f1a888e4e2f982b090db376180
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:868
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:855
repl_backlog_histlen:14

查看主节点(胎货)

[root@10-9-48-229 redis123]# docker exec -it redis_master /bin/bash
root@1e14a516bce9:/data# redis-cli -p 6379
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=106.75.32.166,port=6379,state=online,offset=1008,lag=0
slave1:ip=106.75.32.166,port=6379,state=online,offset=1008,lag=0
master_replid:8dd4c5bcdec908f1a888e4e2f982b090db376180
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:1008
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:855
repl_backlog_histlen:154

Redis主从复制

# 主机
127.0.0.1:6379> set name xiaoming
OK
127.0.0.1:6379> get name
"xiaoming"
# 从机1
127.0.0.1:6379> get name
"xiaoming"
127.0.0.1:6379> set k2 v2
(error) READONLY You can't write against a read only replica.
# 从机2
127.0.0.1:6379> get name
"xiaoming"

客户端查看,主从复制也没有问题

image-20201022213949647

配置哨兵sentinel节点(下面这端内容未成功)

wget http://download.redis.io/redis-stable/sentinel.conf
cp sentinel.conf redis_sentinel_26379.conf
cp sentinel.conf redis_sentinel_26380.conf
cp sentinel.conf redis_sentinel_26381.conf

修改redis_sentinel_26379.conf

port 26379  # 由于要进行端口映射,内部端口为6379
daemonize no
logfile "26379.log"
dir "/data"
#bind 106.75.32.166 127.0.0.1
protected-mode no
sentinel monitor mymaster 106.75.32.166 6379 1 #2 主节点的ip及端口
#30秒ping不通主节点的信息,主观认为master宕机(30秒以后扶正)
sentinel down-after-milliseconds mymaster 30000
#故障转移后重新主从复制,1表示串行,>1并行
sentinel parallel-syncs mymaster 2
#故障转移开始,三分钟内没有完成,则认为转移失败
sentinel failover-timeout mymaster 180000

修改redis_sentinel_26380.conf

port 26379  # 由于要进行端口映射,内部端口为6379
daemonize no
logfile "26380.log"
dir "/data"
#bind 106.75.32.166 127.0.0.1
# 172.17.0.2 106.75.32.166
protected-mode no
sentinel monitor mymaster 106.75.32.166 6379 1 #2 主节点的ip及端口docker inspect redis_master

修改redis_sentinel_26381.conf

port 26379  # 由于要进行端口映射,内部端口为6379
daemonize no
logfile "26381.log"
dir "/data"
#bind 106.75.32.166 127.0.0.1
protected-mode no
sentinel monitor mymaster 106.75.32.166 6379 1 #2 主节点的ip及端口

启动哨兵

# 第一个哨兵
docker run -d -p 26379:26379 --name redis_sentinel_1 -v /usr/local/redis123/redis_sentinel_26379.conf:/redis/sentinel.conf -v /usr/local/redis123/sentinel_26379/:/data redis:latest redis-sentinel /redis/sentinel.conf
# 第二个哨兵
docker run -d -p 26380:26379 --name redis_sentinel_2 -v /usr/local/redis123/redis_sentinel_26380.conf:/redis/sentinel.conf -v /usr/local/redis123/sentinel_26380/:/data redis:latest redis-sentinel /redis/sentinel.conf
# 第三个哨兵
docker run -d -p 26381:26379 --name redis_sentinel_3 -v /usr/local/redis123/redis_sentinel_26381.conf:/redis/sentinel.conf -v /usr/local/redis123/sentinel_26381/:/data redis:latest redis-sentinel /redis/sentinel.conf

启动第一个哨兵后,直接就把master斗成了slave了!

反复测试拷贝到记事本

docker rm -f $(docker ps -aq)

docker run -d -p 6379:6379 --name redis_master -v /usr/local/redis123/redis_master_6379.conf:/redis/redis.conf -v /usr/local/redis123/master/:/data redis:latest redis-server /redis/redis.conf
docker run -d -p 6380:6379 --name redis_slave_1 -v /usr/local/redis123/redis_slave_6380.conf:/redis/redis.conf -v /usr/local/redis123/slave_6380/:/data redis:latest redis-server /redis/redis.conf
docker run -d -p 6381:6379 --name redis_slave_2 -v /usr/local/redis123/redis_slave_6381.conf:/redis/redis.conf -v /usr/local/redis123/slave_6381/:/data redis:latest redis-server /redis/redis.conf

docker run -d -p 26379:26379 --name redis_sentinel_1 -v /usr/local/redis123/redis_sentinel_26379.conf:/redis/sentinel.conf -v /usr/local/redis123/sentinel_26379/:/data redis:latest redis-sentinel /redis/sentinel.conf

docker exec -it redis_master /bin/bash
redis-cli -p 6379
info replication

启动第一个哨兵master就被干掉了

[root@10-9-48-229 ~]# docker exec -it redis_sentinel_1 /bin/bash
root@2fcc6ae3ab48:/data# tail 26379.log 
1:X 23 Oct 2020 03:02:39.271 * +convert-to-slave slave 106.75.32.166:6379 106.75.32.166 6379 @ mymaster 106.75.32.166 6379
1:X 23 Oct 2020 03:03:39.285 # +sdown master mymaster 106.75.32.166 6379
1:X 23 Oct 2020 03:03:39.285 # +odown master mymaster 106.75.32.166 6379 #quorum 1/1
1:X 23 Oct 2020 03:03:39.285 # +new-epoch 1
1:X 23 Oct 2020 03:03:39.285 # +try-failover master mymaster 106.75.32.166 6379
1:X 23 Oct 2020 03:03:39.287 # +vote-for-leader df0641c0a7e82d8cf86377272d42fd6c002845ff 1
1:X 23 Oct 2020 03:03:39.287 # +elected-leader master mymaster 106.75.32.166 6379
1:X 23 Oct 2020 03:03:39.287 # +failover-state-select-slave master mymaster 106.75.32.166 6379
1:X 23 Oct 2020 03:03:39.346 # -failover-abort-no-good-slave master mymaster 106.75.32.166 6379
1:X 23 Oct 2020 03:03:39.398 # Next failover delay: I will not start a failover before Fri Oct 23 03:09:39 2020

宣布以上这种方式搭建哨兵太监了(哨兵启动是成功了,但是可能配置问题直接把主redis干掉了)

搭建1主2从3哨兵(compose)

命令

docker pull redis
mkdir /usr/local/redis666
mkdir /usr/local/redis666/redis
cd /usr/local/redis666/redis
vi docker-compose.yml

内容如下

version: '3.7'
services:
  master:
    image: redis
    container_name: redis-master
    restart: always
    command: redis-server --port 6379 --requirepass 123456  --appendonly yes
    ports:
      - 6379:6379
    volumes:
      - ./data:/data
 
  slave1:
    image: redis
    container_name: redis-slave-1
    restart: always
    command: redis-server --slaveof 106.75.32.166 6379 --port 6380  --requirepass 123456 --masterauth 123456  --appendonly yes
    ports:
      - 6380:6380
    volumes:
      - ./data:/data
 
 
  slave2:
    image: redis
    container_name: redis-slave-2
    restart: always
    command: redis-server --slaveof 106.75.32.166 6379 --port 6381  --requirepass 123456 --masterauth 123456  --appendonly yes
    ports:
      - 6381:6381
    volumes:
      - ./data:/data

启动

docker-compose up -d
docker ps
docker logs -f redis-slave-2
docker logs -f redis-master
docker exec -it redis-master bash
redis-cli
ping
auth 123456
info replication

docker exec -it redis-slave-1 bash
redis-cli -p 6380
ping
auth 123456
info replication

docker exec -it redis-slave-2 bash
redis-cli -p 6381
ping
auth 123456
info replication

docker-compose down 
docker-compose up -d

docker stop $(docker ps -a -q)
docker rm $(docker ps -a -q)
exit

主从复制得到验证,客户端连接也没有问题,数据持久化成功!

哨兵模式

一主两从三哨兵:哨兵之间相互监控,哨兵整体监控主机与从机,这种模式感觉够用了,自己做网站小创业也实用;

要做的事情:配置哨兵规则、启动哨兵、主机宕机后哨兵选举从机为新主机、机器宕机配置发送邮件。

部署哨兵

mkdir /usr/local/redis666/sentinel
cd /usr/local/redis666/sentinel
vi docker-compose.yml

内容如下

version: '3.7'
services:
  sentinel1:
    image: redis
    container_name: redis-sentinel-1
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    restart: always
    ports:
      - 26379:26379
    volumes:
      - ./sentinel1.conf:/usr/local/etc/redis/sentinel.conf
 
  sentinel2:
    image: redis
    container_name: redis-sentinel-2
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    restart: always
    ports:
      - 26380:26379
    volumes:
      - ./sentinel2.conf:/usr/local/etc/redis/sentinel.conf
 
  sentinel3:
    image: redis
    container_name: redis-sentinel-3
    command: redis-sentinel /usr/local/etc/redis/sentinel.conf
    restart: always
    ports:
      - 26381:26379
    volumes:
      - ./sentinel3.conf:/usr/local/etc/redis/sentinel.conf

编写sentinel.conf

vi sentinel.conf

内容如下

port 26379
dir /tmp
# 自定义集群名,其中 106.75.32.166 为 redis-master 的 ip,6379 为 redis-master 的端口,2 为最小投票数(因为有 3 台 Sentinel 所以可以设置成 2)
sentinel monitor mymaster 106.75.32.166 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 180000
sentinel deny-scripts-reconfig yes

复制3份

cp sentinel.conf sentinel1.conf
cp sentinel.conf sentinel2.conf
cp sentinel.conf sentinel3.conf

启动

docker-compose up -d
docker exec -it redis-sentinel-1 bash
redis-cli -p 26379
sentinel master mymaster
sentinel slaves mymaster

image-20201023125138544

image-20201023125000465

验证哨兵模式,在上面的基础上,停掉主redis

docker logs -f redis-sentinel-1

docker stop redis-master

docker exec -it redis-slave-1 bash
redis-cli -p 6380
ping
auth 123456
info replication

docker exec -it redis-slave-2 bash
redis-cli -p 6381
ping
auth 123456
info replication

一小会之后,发现6380被扶正了!

image-20201023130111271

新皇操作权限已经改变(虽然名字还没变,但是实权变了)

image-20201023130552997

老皇帝老儿回来乖乖当勾践

image-20201023131142842

Redis缓存穿透和雪崩

缓存穿透

缓存里边查不到(缓存没有被命中),数据库里也查不到,因此疯狂的查数据库,即为缓存穿透。

缓存穿透解决方案一:布隆过滤器

过滤掉一些请求

缓存穿透解决方案二:缓存空对象

数据库中没有的时候,在缓存中存一个空对象(一致性会有问题)

缓存击穿

一个热点key,一直抗着巨大并发,当该key失效的一瞬间(重新加载也许只需要0.1s),所有的请求全部砸在数据库上,导致宕机。

缓存击穿解决方案一:设置热点key不过期

缓存击穿解决方案二:分布式锁,只允许一个线程访问数据库

缓存雪崩

缓存集群全部宕机,所有请求全部砸在数据库上,数据库随即崩溃

缓存雪崩解决方案一:redis高可用

缓存雪崩解决方案二:限流降级

缓存雪崩解决方案一:数据预热

击石乃有火,不击元无烟!!
原文地址:https://www.cnblogs.com/rain2020/p/13896616.html