redis学习完整版

redis概述

nosql概述

数据库主要分为两大类:关系型数据库与 NoSQL 数据库

关系型数据库:建立在关系模型基础上的数据库,其借助于集合代数等数学概念和方法来处理数据库中的数据。主流的 MySQL、Oracle、MS SQL Server 和 DB2 都属于这类传统数据库。

NoSQL 数据库:全称为 Not Only SQL,适用关系型数据库的时候就使用关系型数据库,不适用的时候也没有必要非使用关系型数据库不可,可以考虑使用更加合适的数据存储。

更加合适的数据存储主要分为
               临时性键值存储(memcached、Redis)、
               永久性键值存储(ROMA、Redis)、
               面向文档的数据库(MongoDB、CouchDB)、
               面向列的数据库(Cassandra、HBase),每种NoSQL都有其特有的使用场景及优点。

为什么会有nosql数据库?
互联网发展过快,数据量庞大,性能要求更高,传统数据库具有单击性能缺陷,无法满足,而NoSQL 根本性的优势在于在云计算时代,简单、易于大规模分布式扩展,并且读写性能非常高

redis基础认知

官网

https://redis.io/ 官网
http://www.redis.cn 中文网

1.什么是redis
是完全开源免费的,用C语言编写的,遵守BSD协议,是一个高性能的(Key/Value)分布式内存数据
库,基于内存运行,并支持持久化的NoSQL数据库,是当前最热门的NoSQL数据库之一,也被人们称为
数据结构服务器

2.redis可以做什么
内存存储和持久化:redis支持异步将内存中的数据写到硬盘上,同时不影响继续服务
取最新N个数据的操作,如:可以将最新的10条评论的ID放在Redis的List集合里面
发布、订阅消息系统
地图信息分析
定时器、计数器

3.redis的特性
数据类型、基本操作和配置
持久化和复制,RDB、AOF
事务的控制

4.为什么redis是单线程
我们首先要明白,Redis很快!官方表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis
的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就
顺理成章地采用单线程的方案了!
Redis采用的是基于内存的采用的是单进程单线程模型的 KV 数据库,由C语言编写,官方提供的数据是
可以达到100000+的QPS(每秒内查询次数)。这个数据不比采用单进程多线程的同样基于内存的 KV
数据库 Memcached 差!

5.Redis为什么这么快?
1)以前一直有个误区,以为:高性能服务器 一定是多线程来实现的
原因很简单因为误区二导致的:多线程 一定比 单线程 效率高,其实不然!
在说这个事前希望大家都能对 CPU 、 内存 、 硬盘的速度都有了解了!
2)redis 核心就是 如果我的数据全都在内存里,我单线程的去操作 就是效率最高的,为什么呢,因为
多线程的本质就是 CPU 模拟出来多个线程的情况,这种模拟出来的情况就有一个代价,就是上下文的切
换,对于一个内存的系统来说,它没有上下文的切换就是效率最高的。redis 用 单个CPU 绑定一块内存
的数据,然后针对这块内存的数据进行多次读写的时候,都是在一个CPU上完成的,所以它是单线程处
理这个事。在内存的情况下,这个方案就是最佳方案。
因为一次CPU上下文的切换大概在 1500ns 左右。从内存中读取 1MB 的连续数据,耗时大约为 250us,
假设1MB的数据由多个线程读取了1000次,那么就有1000次时间上下文的切换,那么就有1500ns *
1000 = 1500us ,我单线程的读完1MB数据才250us ,你光时间上下文的切换就用了1500us了,我还不
算你每次读一点数据 的时间。

redis 典型应用场景
Session 共享: 常见于web集群中的Tomcat或者PHP中多web服务器session共享
缓存: 数据查询、电商网站商品信息、新闻内容
计数器: 访问排行榜、商品浏览数等和次数相关的数值统计场景
微博/微信社交场合: 共同好友,粉丝数,关注,点赞评论等
消息队列: ELK的日志缓存、部分业务的订阅发布系统
地理位置: 基于GEO(地理信息定位),实现摇一摇,附近的人,外卖等功能

缓存

企业级缓存

缓存cache 
        是为了调节速度不一致的两个或多个不同的物质的速度,置于中间.
          可以实现速度较快的一方加速访问速度较慢的一方的作用,
            (比如CPU的一级、二级缓存是保存了CPU最近经常访问的数据, 
             内存是保存CPU经常访问硬盘的数据,而且
             硬盘也有大小不一的缓存,甚至是物理服务器的raid卡有也缓存,
             都是为了起到加速CPU 访问硬盘数据的目的,因为CPU的速度太快了,
             CPU需要的数据由于硬盘往往不能在短时间内满足CPU的需求,
             因此CPU缓存、内存、Raid 卡缓存以及硬盘缓存就在一定程度上满足了CPU的数据需求,
             即CPU 从缓存读取数据,从而可以大幅提高CPU的工作效率。)
     总结: 
     缓存cache是调节不同物质速度的,可以促进速度。 
          硬盘有,物理服务器raid,内存都是为了提高cpu效率

缓存区与缓存

buffer:缓冲,也叫写缓冲,一般用于写操作,可以将数据先写入内存在写入磁盘,
        buffer一般用于写缓冲,用于解决不同介质的速度不一致的缓冲,先将数据临时写入到里自己最近的地方,
        以提高写入速度,CPU会把数据先写到内存的磁盘缓冲区,然后应用就认为数据已经写入完成,
        然后由内核在后续的时间再写入磁盘,所以服务器突然断电会丢失内存中的部分数据。

cache:缓存,也叫读缓存,一般用于读操作,CPU读文件从内存读,如果内存没有,就先从硬盘读到内存再读到CPU,
将需要频繁读取的数据放在里自己最近的缓存区域,下次读取的时候即可快速读取。
       如果因为是应用有像   内存泄露、溢出   的问题时,从swap的使用情况是可以比较快速可以判断的,
       但通过执行free反而比较难查看。核心并不会因为内存泄露等问题并没有快速清空buffer或cache(默认值是0),
       生产也不应该随便去改变此值。

一般情况下,应用在系统上稳定运行了,free值也会保持在一个稳定值的。当发生内存不足、应用获取不到可用内存、
OOM错误等问题时,还是更应该去分析应用方面的原因,否则,清空buffer,强制腾出free的大小,
可能只是把问题给暂时屏蔽了。

排除内存不足的情况外,除非是在软件开发阶段,需要临时清掉buffer,
以判断应用的内存使用情况;或应用已经不再提供支持,即使应用对内存的时候确实有问题,
而且无法避免的情况下,才考虑定时清空buffer。

缓存架构和位置

互联网应用领域,提到缓存为王
用户层: 浏览器DNS缓存,应用程序DNS缓存,操作系统DNS缓存客户端
代理层: CDN,反向代理缓存
Web层: 解释器Opcache,Web服务器缓存

应用层: 页面静态化
数据层: 分布式缓存,数据库
系统层: 操作系统cache
物理层: 磁盘cache, Raid Cache

CDN缓存


什么是CDN
内容分发网络(Content Delivery Network,CDN)是在网上建立并覆盖的,由不同区域的服务器组成的分布式网络。站点资源查找到全国将系统的源服务器,利用全球调度用户能够近获取,降低访问  延迟,降低源站压力,提升服务可用性。


常见的CDN服务商
百度CDN:https://cloud.baidu.com/product/cdn.html
阿 里 CDN:https://www.aliyun.com/product/cdn?spm=5176.8269123.416540.50.728y8n 腾讯CDN:https://www.qcloud.com/product/cdn


CDN主要优势
CDN 有效地解决了目前互联网业务中网络层面的以下问题:
用户与业务服务器地域间物理距离较远,需要进行多次网络转发,传输延时较高且不稳定。 
用户使用运营商与业务服务器所在运营商不同,请求需要运营商之间进行互联转发。
业务服务器网络带宽、处理能力有限,当接收到海量用户请求时,会导致响应速度降低、可用 性降低。
利用CDN防止和抵御DDos等攻击,实现安全保护

redis 数据类型

Redis 常用命令
INFO #显示当前节点redis运行状态信息
SELECT #切换数据库,相当于在MySQL的 USE DBNAME 指
注意: 在 redis cluster 模式下不支持多个数据库,会出现错误
KEYS pattern #查看当前库下的所有key,此命令慎用!
BGSAVE #手动在后台执行RDB持久化操作DBSIZE #返回当前库下的所有key 数量
FLUSHDB #强制清空当前库中的所有key,此命令慎用!
FLUSHALL #强制清空当前redis服务器所有数据库中的所有key,即删除所有数据,此命令慎用! SHUTDOWN #
TYPE #查看一个key的数据类型

string

字符串是所有编程语言中最常见的和最常用的数据类型,是redis最基本的数据类型之一,而
且redis 中所有的 key 的类型都是字符串。常用于保存 Session 信息场景,此数据类型比较常用
字符串 string 语法
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]

SET key value	设置key-value
EX seconds : 将键的过期时间设置为 seconds 秒。
PX milliseconds : 将键的过期时间设置为 milliseconds 毫秒
NX : 只在键不存在时, 才对键进行设置操作
XX : 只在键已经存在时, 才对键进行设置操作
GET key	获取key-value
DEL key	删除key-value
SETNX key value	根据key是否存在设置key-value 
MGET MSET	批量操作key-value
MSET key1 value1 key2 value2 MGET key1 key2

APPEND key value	原有值后追加数据
set key newvalue	设置新值并返回旧值
STRLEN key	返回字符串 key 对应值的字节数
EXISTS key [key ...]	判断 key 是否存在,key的大小写敏感
ttl key	查看key的剩余生存时间,如果key过期后,会自动删除
-1 #返回值表示永不过期,默认创建的key是永不过期,重新对key赋值,也会从有剩余生命周期变成永不过期
-2 #返回值表示没有此key num #key的剩余有效期
EXPIRE key seconds	重新设置key的过期时间
PERSIST key	取消key的过期时间

#利用INCR命令簇(INCR, DECR, INCRBY,DECRBY)来把字符串当作原子计数器使用INCR key	数值递增
DECR key	数值递减
INCRBY key increment	数值增加increment
将key对应的数字加increment(可以是负数)。如果key不存在,操作之前,key就会被置为0。如果  key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的正型  数字。

DECRBY key decrement	数据减少decrement


定义一个键并设置过期时间为60秒
192.168.0.11:6379> set aa abc ex 5
OK
192.168.0.11:6379> get aa
“abc”
192.168.0.11:6379> get aa
(nil)

追加键中的值
192.168.0.11:6379> set aa abc
OK
192.168.0.11:6379> get aa
“abc”
192.168.0.11:6379> append aa efg
(integer) 6
192.168.0.11:6379> get aa
“abcefg”

获取键中值的长度
192.168.0.11:6379> strlen aa
(integer) 6

增加键中的整数值
192.168.0.11:6379> set smlt 1
OK
192.168.0.11:6379> incr smlt
(integer) 2
192.168.0.11:6379> incr smlt
(integer) 3
192.168.0.11:6379> incr smlt
(integer) 4
192.168.0.11:6379> get smlt
“4”
删除键
192.168.0.11:6379> del smlt
(integer) 1
192.168.0.11:6379> get smlt
(nil)


hash

hash 是一个string类型的字段(field)和值(value)的映射表
Redis 中每个 hash 可以存储 2^32 -1 键值对,类似于字典,存放了多个k/v 对,hash特别适合用于
存储对象场景,或者存储mysql的表信息
HSET key field value      生成 hash key,添加字段
例子:HSET 9527 name zhouxingxing age 20
如果给定的哈希表并不存在, 那么一个新的哈希表将被创建并执行 HSET 操作。如果域 field 已经存在于哈希表中, 那么它的旧值将被新值 value 覆盖。
HGET key field	查看指定字段的值
HGETALL key	查看所有字段的值
HDEL key field [field ...]	删除一个hash key 的对应字段
HMSET key field value [field value ...]	批量设置hash key的多个field和value HMGET key field [field ...]	获取hash中指定字段的值
HKEYS key	获取hash中的所有字段名field
HVALS key	获取hash中的所有字段名field

实例
192.168.0.11:6379> hset city1 name “tianjin” techan “xiangsheng” gdp 2500
(integer) 3
查看所有字段
192.168.0.11:6379> hgetall city1
1 “name”
2 “tianjin”
3 “techan”
4 “xiangsheng”
5 “gdp”
6 “2500”

查看某个字段的值
192.168.0.11:6379> hget city1 name
“tianjin”
192.168.0.11:6379> hget city1 gdp
“2500”

删除某个字段的值
192.168.0.11:6379> hdel city1 gdp
(integer) 1
192.168.0.11:6379> hgetall city1
1 “name”
2 “tianjin”
3 “techan”
4 “xiangsheng”


list

  一个列表最多可以包含2^32-1(4294967295)个元素 下标 0 表示列表的第一个元素
   可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素
 列表特点
    有序
    可重复
    左右都可以操作

LPUSH key value [value …]	将一个或多个值 value 插入到列表 key 的表头
RPUSH key value [value …]	将一个或多个值 value 插入到列表 key 的表尾(最右边) 如果 key 不存在,一个空列表会被创建并执行 RPUSH 操作。
当 key 存在但不是列表类型时,返回一个错误。
LLEN key	获取列表长度(元素个数)
LRANGE key start stop	获取列表指定范围内数据 例如 1 2,2 2,0 -1 LINDEX key index	获取列表指定位置数据
LSET key index value	修改列表指定索引值
LPOP key	移除列表左边第一个元素数据
RPOP key	移除列表右边第一个元素数据
LTRIM key start stop	让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除
DEL key	删除list

列表的基本操作(在list1中插入数据)
192.168.0.11:6379> lpush list1 aa
(integer) 1
192.168.0.11:6379> lpush list1 bb
(integer) 2
192.168.0.11:6379> lpush list1 cc
(integer) 3
192.168.0.11:6379> lrange list1 0 -1
1) "cc"
2) "bb"
3) "aa"

#将字符串dd插入到列表的尾部
192.168.0.11:6379> rpush list1 dd
(integer) 4
192.168.0.11:6379> lrange list1 0 -1
1) "cc"
2) "bb"
3) "aa"
4) "dd"

获取列表list1的长度
192.168.0.11:6379> llen list1
(integer) 4
获取列表下标为0的值
192.168.0.11:6379> lindex list1 0
"cc"
修改列表中下标为0的值
192.168.0.11:6379> lset list1 0 ccc
OK
192.168.0.11:6379> lindex list1 0
"ccc"

移除列表最左边的第一个元素
192.168.0.11:6379> lpop list1 
"ccc"
192.168.0.11:6379> lrange list1 0 -1
1) "bb"
2) "aa"
3) "dd"

set

Set 是 String 类型的无序集合,集合中的成员是唯一的,这就意味着集合中不能出现重复的数据,
可以在两个不同的集合中对数据进行对比并取值,常用于取值判断,统计,交集等场景
集合特点
无序无重复
集合间操作

SADD key member [member ...]     生成集合key,也可以追加数值
SMEMBERS key                     查看集合的所有数据
SREM key member [member ...]     删除集合中的元素

集合间操作
SINTER key [key ...]             获取集合的交集
SUNION key [key ...]             获取集合的并集
SDIFF key [key ...]              获取集合的差集
                             差集:已属于A而不属于B的元素称为A与B的差(集)

设置一个集合,并添加三个元素

192.168.0.11:6379> sadd set1 redis 
(integer) 1
192.168.0.11:6379> sadd set1 mysql
(integer) 1
192.168.0.11:6379> sadd set1 mongodb
(integer) 1

查看集合中的所有元素
192.168.0.11:6379> smembers set1
1) "redis"
2) "mysql"
3) "mongodb"


删除集合中的元素

192.168.0.11:6379> srem set1 mongodb
192.168.0.11:6379> smembers set1
1) "redis"
2) "mysql"


集合取交集

192.168.0.11:6379> sadd xm baoqiang xiaocui benshan daliu
(integer) 4
192.168.0.11:6379> sadd bb baoqiang xiaoma xiaolu 
(integer) 3
192.168.0.11:6379> sinter xm bb 
1) "baoqiang"

集合取并集
192.168.0.11:6379> sunion xm bb 
1) "baoqiang"
2) "daliu"
3) "xiaolu"
4) "xiaoma"
5) "benshan"
6) "xiaocui"
集合取差集
192.168.0.11:6379> sdiff xm bb 
1) "xiaocui"
2) "daliu"
3) "benshan"
192.168.0.11:6379> sdiff bb xm 
1) "xiaolu"
2) "xiaoma"


Zset

有序集合和集合一样也是string类型元素的集合,且不允许重复的成员,不同的是每个元素都会关联一
个double(双精度浮点型)类型的分数,redis正是通过该分数来为集合中的成员进行从小到大的排序
有序集合的成员是唯一的,但分数(score)却可以重复,,集合是通过哈希表实现,经常用于排行榜的场景
有序集合特点有序
无重复元素
每个元素是由score和value组成
score 可以重复
value 不可以重复

ZADD key [NX|XX] [CH] [INCR] score member [score member ...]	生成有序集合
ZRANGE key start stop [WITHSCORES]	正序排序后显示集合内所有的key,score从小到大显示
                               #超出范围不报错
ZREVRANGE key start stop [WITHSCORES]	倒序排序后显示集合内所有的key,score从大到小显示
ZCARD key	                            获取集合的个数
ZRANK key member	                    返回某个数值的索引(排名)
ZSCORE key member	                    获取分数
ZREM key member [member ...]	        删除元素

添加有序集合
192.168.0.11:6379> zadd haha 100 jjs
(integer) 1
192.168.0.11:6379> zadd haha 200 changjinhu
(integer) 1
192.168.0.11:6379> zadd haha 1000 zhanlang
(integer) 1

显示haha有序集合(会自动根据数值排序)
192.168.0.11:6379> zrange haha 0 4
1) "jjs"
2) "changjinhu"
3) "zhanlang"

从大到小排序
192.168.0.11:6379> zrevrange haha 0 4 
1) "zhanlang"
2) "changjinhu"
3) "jjs"
查看集合的个数
192.168.0.11:6379> zcard haha
(integer) 4
查看集合的成员的排名(索引)
192.168.0.11:6379> zrank haha zhanlang
(integer) 3

查看集合中某个元素的值
192.168.0.11:6379> zscore haha zhanlang
"1000"
删除集合中的一个元素
192.168.0.11:6379> zrem haha zhanlang 
(integer) 1

redis持久化

目前redis支持两种不同方式的数据持久化保存机制,分别是RDB(快照)和AOF

RDB(快照)模式
RDB(Redis DataBase):基于时间的快照,其默认只保留当前最新的一次快照,
                     特点是  执行速度比较快,
                     缺点是  可能会丢失从上次快照到当前时间点之间未做快照的数据
       
通常而言RDB的快照基于时间有以下几项参数较为重要
save 900 1 #在900秒内有1个key内容发生更改,就执行快照机制
save 300 10 #在300秒内有10个key内容发生更改,就执行快照机制
save 60 10000 #60秒内如果有10000个key以上的变化,就自动快照备份

实现RDB方式手动
save: 同步,会阻赛其它命令,不推荐使用
bgsave: 异步后台执行,不影响其它命令的执行
自动: 制定规则,自动执行

RDB 模式的优缺点

RDB 模式优点
RDB快照保存了某个时间点的数据,可以通过脚本执行redis指令bgsave(非阻塞,后台执行)或 者save(会阻塞写操作,不推荐)命令自定义时间点备份
RDB可以最大化Redis的性能,
RDB在大量数据,比如几个G的数据,恢复的速度比AOF的快
RDB 模式缺点
不能实时保存数据,可能会丢失自上一次执行RDB备份到当前的内存数据,因此你可能会至少5分钟才保存一次RDB文件。
AOF 模式
AOF:AppendOnylFile,按照操作顺序依次将操作追加到指定的日志文件末尾

AOF 和 RDB 一样使用了写时复制机制,AOF默认为每秒钟 fsync一次,即将执行的命令保存到AOF 文件当中,
这样即使redis服务器发生故障的话最多只丢失1秒钟之内的数据,也可以设置不同的fsync策略always,
即设置每次执行命令的时候执行fsync,fsync会在后台执行线程,所以主线程可以继续处理用户的正常请求而
不受到写入AOF文件的I/O影响

同时启用RDB和AOF,进行恢复时,默认AOF文件优先级高于RDB文件,即会使用AOF文件进行恢复

注意: AOF 模式默认是关闭的,第一次开启AOF后,并重启服务生效后,会因为AOF的优先级高于RDB, 
而AOF默认没有文件存在,从而导致所有数据丢失

AOF 相关配置
appendonly no #是否开启AOF日志记录,默认redis使用的是rdb方式持久化,这种方式在许多应用中已经足够用了,
但是redis如果中途宕机,会导致可能有几分钟的数据丢 失(取决于dump数据的间隔时间),根据save来策略进行持久化,

Append Only File是另一种持久化方式,可以提供更好的持久化特性,Redis会把每次写入的数据在接收后都写入 
appendonly.aof 文件,每次启动时Redis都会先把这个文件的数据读入内存里,先忽略RDB文件。默认不启用此功能

appendfilename "appendonly.aof" #文本文件AOF的文件名,存放在dir指令指定的目录中
appendfsync everysec            #aof持久化策略的配置
                 
                 #no表示由操作系统保证数据同步到磁盘,Linux的默认fsync策略是30秒,最多会丢失30s的数据
                 #always表示每次写入都执行fsync,以保证数据同步到磁盘,安全性高,性能较差
                 #everysec表示每秒执行一次fsync,可能会导致丢失这1s数据,此为默认值,也生产建议值
       

RDB和AOF 的选择
如果主要充当缓存功能,或者可以承受数分钟数据的丢失, 通常生产环境一般只需启用RDB即可,此也是默认值
如果数据需要持久保存,一点不能丢失,可以选择同时开启RDB和AOF,一般不建议只开启AOF

redis 消息队列

消息队列: 把要传输的数据放在队列中
     功能: 可以实现多个系统之间的解耦,异步,削峰/限流等
     常用的消息队列应用: kafka,rabbitMQ,redis
消息队列主要分为两种,这两种模式Redis都支持
     生产者/消费者模式
     发布者/订阅者模式
     
在生产者/消费者(Producer/Consumer)模式下,上层应用接收到外部请求后开始处理其当前步骤的操作,在执行完成后将已经完成的操作发送至指定的频道(channel,逻辑队列)当中,并由其下层的应用监听该频道并继续下一步的操作,如果其处理完成后没有下一步的操作就直接返回数据给外部请求,如   果还有下一步的操作就再将任务发布到另外一个频道,由另外一个消费者继续监听和处理。此模式应用广泛。

模式介绍
生产者消费者模式下,多个消费者同时监听一个队列,但是一个消息只能被最先抢到消息的消费者  消费,即消息任务是一次性读取和处理,此模式在分布式业务架构中很常用,比较常用的消息队列软件  还有RabbitMQ、Kafka、RocketMQ、ActiveMQ等。

队列介绍
队列当中的消息由不同的生产者写入,也会有不同的消费者取出进行消费处理,但是一个消息一定  是只能被取出一次也就是被消费一次。

发布者订阅模式	
模式简介
在发布者订阅者模式下,发布者将消息发布到指定的channel里面,凡是监听该channel的消费者都 会收到同样的一份消息,这种模式类似于是收音机的广播模式,即凡是收听某个频道的听众都会收到主持人发布的相同的消息内容。此模式常用语群聊天、群通知、群公告等场景
Publisher:发布者
Subscriber:订阅者
Channel:频道     

模式演示
[root@redis ~]# redis-cli 127.0.0.1:6379> SUBSCRIBE channel1
#订阅者事先订阅指定的频道,之后发布的消息才能收到
Reading messages... (press Ctrl-C to quit)
1)"subscribe"
2)"channel1"
3)(integer) 1

发布者发布消息
127.0.0.1:6379> PUBLISH channel1 test1 #发布者发布消息(integer) 2 #订阅者个数
127.0.0.1:6379> PUBLISH channel1 test2 (integer) 2

redis 主从架构

主	从复制	
主从模式下,Redis 分为主库(master)和从库(slaver)。主库负责读写,从库只负责读。当主库发生写事件后会将数据同步至从库。主库挂了不会重新选举主库,需等主库重启之后才能继续提供写服务,此间从库仍可提供读服务。

初始化阶段:从库启动后,向主库发送 Sync 命令,主库收到 Sync 命令后,生成 RDB 快照,并缓存生成快照期间的写命令,快照生成完后,发送至从库。从库收到后根据快照和缓存的写命令初始化数据库。

同步阶段:此阶段主库每次收到写命令都会将写命令发送至从库,从库执行写命令,保证数据一致性。

主从复制特点
一个master可以有多个slave 一个slave只能有一个master
数据流向是单向的,master到slave

主从复制实现原理
a 从库通过replicaof 192.168.0.11 6379命令连接主库,并发送sync给主库
b 主库收到sync,立刻触发bgsave,后台保存RDB,发送给从库
c 从库接受后会应用rdb快照
d 主库会陆续将中间产生的新的操作,保存并发送给从库
e 到此,我们主从复制就正常工作了
f 之后,主库所有新的操作,都会以命令传播的方式自动发送给从库
g 如果发生主从断开,从库数据没有任何破坏,重新连接后,从库发送psync给主库
h 主库会将从库缺失的部分数据同步给从库应用,达到快速恢复主从的目的

主从复制实现操作(单机多实例实现)

环境准备:准备两个或两个以上的redis实例
主redis需要配置密码,才可以远程主从	

两个从机
[root@hd1 data]# mkdir /data/6380
[root@hd1 data]# mkdir /data/6381
设置配置文件
[root@hd1 data]# cat  /data/6380/redis.conf 
port 6380
daemonize yes
pidfile /data/6380/redis.pid
loglevel notice
logfile /data/6380/redis.log
dir /data/6380
requirepass 123456
masterauth 123456


[root@hd1 data]# cat  /data/6381/redis.conf 
port 6381
daemonize yes
pidfile /data/6381/redis.pid
loglevel notice
logfile /data/6381/redis.log
dir /data/6381
requirepass 123456
masterauth 123456

启动redis
[root@hd1 data]# redis-server  /data/6380/redis.conf 
[root@hd1 data]# redis-server  /data/6381/redis.conf 
查看端口是否是监听
[root@hd1 data]# ss -naput|grep *:63
主节点6379 从节点为6380 6381

开启主从
root@hd1 data]# redis-cli -p 6380 -a 123456 slaveof 127.0.0.1 6379
[root@hd1 data]# redis-cli -p 6381 -a 123456 slaveof 127.0.0.1 6379
或者使用replicaof命令
root@hd1 data]# redis-cli -p 6380 -a 123456 replicaof 127.0.0.1 6379
[root@hd1 data]# redis-cli -p 6381 -a 123456 replicaof 127.0.0.1 6379
查看主从状态
[root@hd1 data]# redis-cli -h 127.0.0.1
127.0.0.1:6379> auth 123456
OK
127.0.0.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=392,lag=0
slave1:ip=127.0.0.1,port=6381,state=online,offset=392,lag=1
master_failover_state:no-failover

测试主从同步(在主库上设置一个字符串)
127.0.0.1:6379> set aa 1000
OK
127.0.0.1:6379> exit
[root@hd1 data]# redis-cli -p 6380 -a 123456
127.0.0.1:6380> get aa
"1000"

解除主从身份(6381)
root@hd1 data]# redis-cli -p 6381 
127.0.0.1:6381> auth 123456
OK
127.0.0.1:6381> replicaof no one 
OK
重新加入主从
[root@hd1 data]# redis-cli -p 6381 -a 123456 slaveof 127.0.0.1 6379
redis两台物理机实现主从同步
主-192.168.1.11(源码包) -  从192.168.1.12(yum)
1.主库修改配置redis.conf
打开网卡bind地址,必须要改
bind 0.0.0.0
requirepass "123456"
masterauth "123456"

从库需要设置 指向主库的ip和端口以及登录密码
replicaof 192.168.1.11 6379 #
masterauth 123456
======================================

总结:缺点:如果主库挂了,集群将无法提供写服务

redis 哨兵机制

Redis哨兵	
redis 哨兵介绍	
Sentinel 模式是建立的主从模式之下的,前面说到如果主库挂了,集群将无法对外提供写服务,不满足高可用。Sentinel 的解决方案是引入一个 Sentinel 系统,监视多个Redis 主库以及主库下的从库,当主库挂了,Sentinel 在从库里选举一个作为新的主库。为了避免 Sentinel 的单点故障,Sentinel 也会以集群方式部署。多个 Sentinel 之间也会互相监视,多个 Sentinel 集群中存在 Leader ,由 Leader 来完成主从库的切换工作。

这里的哨兵有两个作用

1.通过发送命令,让Redis服务器返回监控其运行状态,包括主服务器和从服务器。
2.当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

然而一个哨兵进程对Redis服务器进行监控,可能会出现问题,为此,我们可以使用多个哨兵进行监控。各个哨兵之间还会进行监控,这样就形成了多哨兵模式。

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

Sentinel节点个数应该为大于等于3且最好为奇数,为什么呢? 可能会发生脑裂

注意:使用 Sentinel 的时候,客户端不要直接连接Redis,而是连接 Sentinel 的地址,由 Sentinel 来提供具体的可提供服务的 Redis 节点,这样当master 节点挂掉以后,Sentinel 就会感知并将新的 master 节点提供给客户端。


实现哨兵	
哨兵的准备需要基于实现一个一主两从基于哨兵的高可用redis架构
搭建sentinel过程如下:
[root@hd1 ~]# mkdir /data/26380
[root@hd1 ~]# cd /data/26380
设置配置文件
[root@hd1 26380]# cat sentinel.conf 
port 26380
dir /data/26380
sentinel monitor mymaster 127.0.0.1 6379 1
sentinel down-after-milliseconds mymaster 5000
sentinel auth-pass mymaster 123456

mymaster 监视器是mymaster
down掉5000毫秒后(5s)登进监控,切换


#上述配置中设置名字为mymaster去监控一套redis集群,等待5000ms联系不到主库
就切换,sentinel monitor mymaster 127.0.0.1 6379 1 中的1表示至少有1台sentinel认为
主库down了,才算真正的down机,这里表示1台sentinel认为主库down机了,就切换

启动sentinel
[root@hd1 26380]# redis-sentinel /data/26380/sentinel.conf &>/tmp/sentinel.log &

测试一下 停止主库
[root@hd1 ~]# systemctl stop redis

查看日志是否切换成功

[root@hd1 ~]# cat /tmp/sentinel.log |grep switch
42262:X 09 Feb 2022 18:00:35.562 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381
#上述看到6381成为了主库

#这里可以查看/data/redis/6381/redis.conf(和6380),里面的配置会被更改
1.replicaof 127.0.0.1 6379 会被删除
2.下面会多出几行内容
3.从库会更新新的主库信息


登陆到6381查看是否为主库
[root@hd1 ~]#  redis-cli -p 6381 -a 123456 
127.0.0.1:6381> info replication
# Replication
role:master
connected_slaves:1
slave0:ip=127.0.0.1,port=6380,state=online,offset=31873,lag=1

重新启动6379,正常来说会自动加入到集群中,但是我们这里并没有加入进去
我们进入6379查看一下信息
#上图表示连接不到新的主库,原因很简单就是老主库这需要配置master认证密码

修改主配置文件如下:
[root@hd1 ~]# cat  /data/redis/conf/redis.conf 
requirepass "123456"
masterauth "123456"

重新启动redis
[root@hd1 ~]# systemctl restart  redis 

再次查看,发现ok了

连接到sentinel
查看集群是否正常
[root@hd1 ~]# redis-cli -p 26380
127.0.0.1:26380> ping
PONG
#返回pong表示集群正常
查看主库的信息
127.0.0.1:26380> sentinel masters 
查看从库的信息
127.0.0.1:26380> sentinel slaves mymaster
强制切换(这里首先需要同步一下,在主库set aa aa一下就可以)
127.0.0.1:26380> sentinel failover mymaster
切换之后的主库是?
127.0.0.1:26380> sentinel masters 
1)  1) "name"
    2) "mymaster"
    3) "ip"
    4) "127.0.0.1"
    5) "port"
    6) "6379"
缺陷:写并不是高可用的(哨兵sentinel机制中,可以解决redis高可用问题,即当master故障后可以自动将slave提升为master,从而可以保证redis服务的正常使用,但是无法解决redis单机写入的瓶颈问题,即单机redis写入性能受限于单机的内存大小、并发数量、网卡速率等因素。)

redis cluster 集群部署(三主三从)

Redis Cluster 工作原理
哨兵sentinel机制,可以解决redis高可用问题,当master故障后可以自动将slave提升为master,从而可以保证redis服务的正常使用,
    但是无法解决redis单机写入的瓶颈问题,即单机redis写入性能受限于单机的内存大小、并发数量、网卡速率等因素。
redis 3.0版本之后推出了无中心架构的redis cluster机制,在无中心的redis集群当中,其每个节点保存当前节点数据和整个集群状态,每个节点都和其他所有节点连接

Redis Cluster特点如下:
1.所有Redis节点使用(PING机制)互联
2.集群中某个节点的是否失效,是由整个集群中超过半数的节点监测都失效,才能算真正的失效
3.客户端不需要proxy即可直接连接redis,应用程序中需要配置有全部的redis服务器IP
4.redis cluster把所有的redis node 平均映射到 0-16383个槽位(slot)上,读写需要到指定的redisnode上进行操作,因此有多少个redis node相当于redis 并发扩展了多少倍,每个redis node 承担16384/N个槽位
5.Redis cluster预先分配16384个(slot)槽位,当需要在redis集群中写入一个key-value的时候, 会使用CRC16(key) mod 16384之后的值,决定将key写入值哪一个槽位从而决定写入哪一个Redis节点上,从而有效解决单机瓶颈。

分配槽位
现在我们是三个主节点分别是:A, B, C 三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽 (hash slot)的方式来分配16384个slot 的话,它们三个节点分别承担的slot 区间是:

节点A覆盖0-5460;
节点B覆盖5461-10922;
节点C覆盖10923-16383.

获取数据:
如果存入一个值,按照redis cluster哈希槽的算法: CRC16('key')384 = 6782。 那么就会把这个key 的存储分配到 B 上了。同样,当我连接(A,B,C)任何一个节点想获取'key'这个key时,也会这样的算法,然后内部跳转到B节点上获取数据


新增一个主节点:
新增一个节点D,redis cluster的这种做法是从各个节点的前面各拿取一部分slot到D上,我会在接下来的实践中实验。大致就会变成这样:

节点A覆盖1365-5460
节点B覆盖6827-10922
节点C覆盖12288-16383
节点D覆盖0-1364,5461-6826,10923-12287
同样删除一个节点也是类似,移动完成后就可以删除这个节点了。

Redis Cluster主从模式
redis cluster 为了保证数据的高可用性,加入了主从模式,
    一个主节点对应一个或多个从节点,
    主节点提供数据存取(主提供写),从节点则是从主节点拉取数据备份(从节点读),
    当这个主节点挂掉后,就会有这个从节点选取一个来充当主节点,从而保证集群不会挂掉
    
(上面那个例子里, 集群有ABC三个主节点, 如果这3个节点都没有加入从节点,如果B挂掉了,我们就无法访问整个集群了。A和C的slot也无法访问。
所以我们在集群建立的时候,一定要为每个主节点都添加了从节点, 比如像这样, 集群包含主节点A、B、C, 以及从节点A1、B1、C1, 那么即使B挂掉系统也可以继续正确工作。
B1节点替代了B节点,所以Redis集群将会选择B1节点作为新的主节点,集群将会继续正确地提供服务。 当B重新开启后,它就会变成B1的从节点。)

缺点:不过需要注意,如果节点B和B1同时挂了,Redis集群就无法继续正确地提供服务了。

最小集群需要包含至少三个主节点。 强烈建议 启动一个具有三个主节点和三个从节点的六节点集群

配置
基于Redis 6.2 的 redis cluster 部署

创建6个目录
[root@hd1 ~]# cd /usr/local/
[root@hd1 local]# mkdir cluster-test
[root@hd1 local]# cd cluster-test
[root@hd1 cluster-test]# mkdir 7000 7001 7002 7003 7004 7005

配置文件介绍

cluster-enabled yes #取消此行注释,必须开启集群,开启后redis 进程会有cluster显示

cluster-config-file nodes-6379.conf #取消此行注释,此为集群状态文件,记录主从关系及slot范围信息,由redis cluster 集群自动创建和维护

cluster-require-full-coverage no #默认值为yes,设为no可以防止一个节点不可用导致整cluster不可用


创建6个主配置文件
[root@hd1 cluster-test]# cat  7000/redis.conf 
#7000端口
port 7000
cluster-enabled yes
cluster-config-file nodes-7000.conf
cluster-node-timeout 5000
appendonly yes
requirepass "123456"
masterauth 123456
loglevel notice
logfile "/usr/local/cluster-test/7000.log"
daemonize yes
pidfile "/usr/local/cluster-test/7000/redis.pid"
dir "/usr/local/cluster-test/7000/data"

[root@hd1 cluster-test]# cat  7001/redis.conf 
#7001端口
port 7001
cluster-enabled yes
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000
appendonly yes
requirepass "123456"
masterauth 123456
loglevel notice
logfile "/usr/local/cluster-test/7001.log"
daemonize yes
pidfile "/usr/local/cluster-test/7001/redis.pid"
dir "/usr/local/cluster-test/7001/data"

#7002,7003 7004 7005依次类推配置

[root@hd1 cluster-test]# cp   7000/redis.conf  7003/redis.conf
[root@hd1 cluster-test]# cp   7000/redis.conf  7004/redis.conf
[root@hd1 cluster-test]# cp   7000/redis.conf  7005/redis.conf
[root@hd1 cluster-test]# sed -i 's/7000/7003/g' 7003/redis.conf 
[root@hd1 cluster-test]# sed -i 's/7000/7004/g' 7004/redis.conf 
[root@hd1 cluster-test]# sed -i 's/7000/7005/g' 7005/redis.conf 
启动每一个服务
[root@hd1 cluster-test]# mkdir 7000/data
[root@hd1 cluster-test]# mkdir 7001/data
[root@hd1 cluster-test]# mkdir 7002/data
[root@hd1 cluster-test]# mkdir 7003/data
[root@hd1 cluster-test]# mkdir 7004/data
[root@hd1 cluster-test]# mkdir 7005/data

[root@hd1 cluster-test]# redis-server 7000/redis.conf 
[root@hd1 cluster-test]# redis-server 7001/redis.conf 
[root@hd1 cluster-test]# redis-server 7002/redis.conf 
[root@hd1 cluster-test]# redis-server 7003/redis.conf 
[root@hd1 cluster-test]# redis-server 7004/redis.conf 
[root@hd1 cluster-test]# redis-server 7005/redis.conf 

查看端口
[root@hd1 ~]# ss -naput |grep 700
tcp    LISTEN     0      128       *:7004          
tcp    LISTEN     0      128       *:7005    
查看启动情况
[root@hd1 ~]# ps aux |grep redis
root      34496  0.1  0.1 162404  3060 ?        Ssl  15:22   0:00 redis-server *:7000 [cluster]
root      34502  0.0  0.1 162404  3028 ?        Ssl  15:22   0:00 redis-server *:7001 [cluster]
root      34508  0.1  0.1 162404  3032 ?        Ssl  15:22   0:00 redis-server *:7002 [cluster]
root      34514  0.1  0.1 162404  3036 ?        Ssl  15:22   0:00 redis-server *:7003 [cluster]
root      34520  0.1  0.1 162404  3032 ?        Ssl  15:22   0:00 redis-server *:7004 [cluster]
root      34526  0.1  0.1 162404  3036 ?        Ssl  15:22   0:00 redis-server *:7005 [cluster]


构建cluster

[root@hd1 ~]# redis-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-replicas 1 -a 123456 
(这里写2就是一主两从,但是这里是三主三从)
#执行命令之后需要输入一个:yes

之后cluster会自动将7000 7001 7002 设置为主库将 7003 7004 7005 设置为从库

查看集群信息
[root@hd1 ~]# redis-cli -p 7000 -a 123456
127.0.0.1:7000> cluster info 
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6   六个节点,查看信息,cluster nodes
cluster_size:3
cluster_current_epoch:6
查看集群节点
127.0.0.1:7000> cluster nodes

测试

127.0.0.1:7000> set a a 
(error) MOVED 15495 127.0.0.1:7002
127.0.0.1:7000> set b b 
OK
127.0.0.1:7000> set cc cc 
OK

#我上面的例子我们可以看出,设置的key会被分配到不同的实例去

#高可用redis集群的从库不支持读操作,计算hash的时候,redis实例已经固定,只能从主库里面读

测试故障转移
将7000这个主库down掉(这里启动后会变成从库)
[root@hd1 ~]# redis-cli -p 7000 -a 123456 shutdown

查看之前的master和slave 发现7000和7004构成了主从关系
查看7004的日志,发现成为了主库
[root@hd1 cluster-test]# tail 7004.log 
34520:S 10 Feb 2022 16:19:38.669 # Cluster state changed: fail
34520:S 10 Feb 2022 16:19:38.769 * Connecting to MASTER 127.0.0.1:7000
34520:S 10 Feb 2022 16:19:38.769 * MASTER <-> REPLICA sync started
34520:S 10 Feb 2022 16:19:39.480 # Failover election won: I'm the new master.

查看集群节点信息,7000节点是失败的
127.0.0.1:7001> cluster nodes
ed71d7be3749a5219dc4697c6f20a9c566691321 127.0.0.1:7000@17000 master,fail - 
51856ddd9aca7d8e4d988317cbdcfa55848e0fd0 127.0.0.1:7004@17004 master - 0 1644481620090 

重新启动7000实例,发现7000成为了一个从库
[root@hd1 ~]# redis-server /usr/local/cluster-test/7000/redis.conf 
[root@hd1 ~]# redis-cli -p 7000 -a 123456
127.0.0.1:7000> cluster nodes
ed71d7be3749a5219dc4697c6f20a9c566691321 127.0.0.1:7000@17000 myself,slave 


动态扩容缩容 (扩展实验)

为了防止脑裂 我们增加两个节点,7006 7007
[root@hd1 ~]# cd /usr/local/cluster-test/
[root@hd1 cluster-test]# mkdir 7006
[root@hd1 cluster-test]# mkdir 7006/data
[root@hd1 cluster-test]# cp 7000/redis.conf  7006/
[root@hd1 cluster-test]# sed -i 's/7000/7006/' 7006/redis.conf 
[root@hd1 cluster-test]# redis-server  7006/redis.conf 
[root@hd1 cluster-test]# ps aux |grep 7006
root      34662  0.4  0.1 162404  3040 ?        Ssl  16:37   0:00 redis-server *:7006 [cluster]

[root@hd1 cluster-test]# mkdir 7007
[root@hd1 cluster-test]# mkdir 7007/data
[root@hd1 cluster-test]# cp 7000/redis.conf 7007/
[root@hd1 cluster-test]# sed -i 's/7000/7007/' 7007/redis.conf 
[root@hd1 cluster-test]# redis-server  7007/redis.conf 

添加新节点  


[root@hd1 cluster-test]# redis-cli --cluster add-node 127.0.0.1:7006  127.0.0.1:7001 -a 123456
这里注意7006指向主节点,7000已经是从节点,需要改成7001
#上图可以看到没有槽位
查看集群节点
127.0.0.1:7002> cluster nodes
668eed1bda96da690297f32eb3b6923b463d515c 127.0.0.1:7006@17006 master - 0 1644483256209 

添加7006的一个从节点7007
[root@hd1 cluster-test]# redis-cli -a 123456 --cluster add-node 127.0.0.1:7007 127.0.0.1:7001 --cluster-slave --cluster-master-id 668eed1bda96da690297f32eb3b6923b463d515c

#注意master-id要和上一条命令的id保持一致

重新查看集群节点情况
127.0.0.1:7002> cluster nodes
668eed1bda96da690297f32eb3b6923b463d515c 127.0.0.1:7006@17006 master  
51ae9338f33a6d9044a90c643ddfc84f436f5071 127.0.0.1:7007@17007 slave 


分配槽位

重新分配Redis槽位扩容----槽点中的数据会一起移动

[root@hd1 cluster-test]# redis-cli -a 123456 --cluster reshard 127.0.0.1:7001
1000
668eed1bda96da690297f32eb3b6923b463d515c
all

#上述命令执行后会,进入交互模式 1000(正常应该是4096)表示要分配的槽位,66xxxxx5c表示新的master的id,all表示要在其他的所有master主机上把槽位移动到新的master库

查看集群节点的槽位数
127.0.0.1:7002> cluster nodes
668eed1bda96da690297f32eb3b6923b463d515c 127.0.0.1:7006@17006 master - 0 1644485324341 9 connected 0-1364 5461-6826 10923-12287


缩容

将7006的slot给了7002(都必须是master)
[root@hd1 cluster-test]# redis-cli -a 123456 --cluster reshard 127.0.0.1:7000 --cluster-from 668eed1bda96da690297f32eb3b6923b463d515c --cluster-to d04ce3eb0ffe51cc476018f2bb0fe27e8eca6925

再次查看
127.0.0.1:7002> cluster nodes
668eed1bda96da690297f32eb3b6923b463d515c 127.0.0.1:7006@17006 master - 0 1644485751557 9 connected
d04ce3eb0ffe51cc476018f2bb0fe27e8eca6925 127.0.0.1:7002@17002 myself,master - 0 1644485750000 10 connected 0-1364 5461-6826 10923-16383

删除节点7006和7007
root@hd1 cluster-test]# redis-cli -a 123456 --cluster del-node 127.0.0.1:7000 668eed1bda96da690297f32eb3b6923b463d515c

root@hd1 cluster-test]# redis-cli -a 123456 --cluster del-node 127.0.0.1:7000 51ae9338f33a6d9044a90c643ddfc84f436f5071

#66xxxxx5c为7006实例的id ,51xxxx71为7007实例的id


mysql+redis配置

1.安装 gcc*
2.安装所需要的包
3.配置网站 nginx 并启动 nginx
4.启动 php 和数据库
5.授权,使登录数据库时使用‘123456’密码
6.测试网站和 php 的连通性
7.安装 redis
8.安装提供 php 和 redis 联系的软件
9.进入 mysql 插数据
10.开启 redis,并编写脚本
11.验证 php 访问 redis 和 mysql

redis 扩展

1.对比memcached

①Redis不仅仅支持简单的k/v类型的数据,同时还提供list,set,zset,hash等数据结构的存储。
②Redis支持master-slave(主—从)模式应用。
③Redis支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用。
④Redis单个value存储string的最大限制是512MB, memcached只能保存1MB的数据
⑤redis是单核,memcached是多核

由于redis只能使用单核,而memcached可以使用多核,所以在比较上,平均每一个核上redis在储存小数据时比memcached性能更高。而却100K以上数据中,memcached性能要高于redis,虽然redis最近也在储存大数据的性能上进行优化,但是比起memcached还是有点逊色。结论是无论你使用那个,每秒处理请求的次数都不会成为瓶颈。

需要关注内存使用率。对于key-vlaue这样简单的数据储存,memcached的内存使用率更高,如果采用hash结构,redis的内存使用率会更高,当然这都依赖于具体的应用场景。

2.安全,限制频繁访问

Nginx+lua+redis 实现访问攻击黑名单 WAF
①安装openresty

②配置文件

③lua脚本文件

④测试效果

192.168.1.10/index.html
超过访问计数之后,就禁止访问

持续补充

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

带上耳机世界与我无关

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

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

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

打赏作者

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

抵扣说明:

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

余额充值