Redis学习_查看redis那个库没有用-程序员宅基地

技术标签: 后端  数据库  redis  docker  

一、Docker方式安装Redis

  1. 拉取Redis镜像

    docker pull redis
    
  2. 官网下载redis.conf配置文件

  3. 新建Redis本地配置文件和数据存储的目录,并上传官网下载的redis.conf配置文件到新建的配置文件夹下面

    # 切换到根路径
    cd /
    
    # 在根路径下新建docker-images/redis/conf,docker-images/redis/data,文件目录,建立多级目录加 -p
    # 创建好后将下载的redis.conf配置文件上传到/docker-images/redis/conf目录下,作为redis启动的配置文件
    mkdir -p docker-images/redis/{
          conf,data}
    
  4. 启动redis镜像,并挂载之前创建的文件目录

    docker run -d --privileged=true --name redis-server -p 6379:6379 -v /docker-images/redis/conf/redis-6379.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf
    
  5. 进入容器产看redis是否可以连接

    # 以redis-cli的方式进入redis-server容器,可以查看redis是否运行成功
    docker exec -it redis-server redis-cli
    
  6. 配置文件注释过滤操作和复制命令如下

    # 控制台输出配置文件,过滤了注释和空格
    cat redis.conf | grep -v "#" | grep -v "^$" 
    
    # 将过滤后的配置文件的内容输出到redis-6379.conf文件中
    cat redis.conf | grep -v "#" | grep -v "^$"  > redis-6379.conf
    
  7. 注意docker方式安装redis,在redis的配置文件中daemonize不可以配置为yes,否则redis启动不起来

  8. Docker 常用命令如下

    # 强制删除镜像redis-server
    docker rm -f redis-server
    
    # 删除容器镜像
    docker container rm redis-server
    
    # 查看运行中的镜像
    docker ps 
    
    # 查看下载的镜像
    docker images
    

二、基本数据类型

1. key和数据库通用命令

  • help 命令名称:可以查看具体命令的格式和参数
  • help @组名:获取该组的命令, 可以使用Tab键来切换
  • keys *:查看当前库所有key
  • exists key:判断某个key是否存在
  • type key:查看你的key是什么类型
  • del key:删除指定的key数据
  • expire key seconds:为给定的key设置过期时间,单位为秒
  • pexpire key milliseconds:为给定的key设置过期时间,单位为毫秒
  • expireat key timestamp:为给定的key设置过期时间,单位为时间戳
  • pexpireat key milliseconds-timestamp:为给定的key设置过期时间,单位为毫秒时间戳
  • ttl key:查看还有多少秒过期,-1表示永不过期,-2表示已过期,单位为秒
  • pttl key:查看还有多少毫秒过期
  • persist key:切换key从时效性转换为永久性
  • rename key newkey:为key改名,修改成功返回1,不成功返回0
  • renamenx key newkey:修改的newkey不存在时修改成功,修改成功返回1,不成功返回0
  • select index:切换数据库
  • dbsize:查看当前数据库的key的数量
  • flushdb:清空当前库
  • flushall:清空全部库
  • move key db:移动key到其他数据库,前提移动的目的数据库中不存在该key

2. String

2.1 基本命令

  • set key value:添加/修改数据
  • get key: 获取数据
  • del key:删除数据
  • setnx key value:只有在 key 不存在时 ,设置 key 的值
  • getset key value:以新换旧,设置了新值同时获得旧值
  • getrange key start end:获得值的范围,类似java中的substring
  • setrange key start value:用 value覆写key所储存的字符串值,从start位置开始默认索引从0开始
  • mset key1 value1 key2 value2 …:设置过个数据
  • mget key1 key2 …:获取多个数据
  • strlen key:获取数据字符个数(字符串长度)
  • append key value:追加信息到原始信息后部(如果原始信息存在就追加,否则新建)
  • incr key:设置数值数据增加默认为加一,如果为空,新增值为1
  • incrby key increment :设置数值数据增加指定范围的值
  • incrbyfloat key increment:设置浮点数值数据增加指定范围的值
  • decr key:设置数值数据减少默认减一,如果为空,新增值为-1
  • decrby key increment:设置数值数据减少指定范围的值
  • setex key seconds value:默认设置数据具有指定的生命周期,单位秒
  • psetex key milliseconds value:默认设置数据具有指定的生命周期,单位毫秒

注意点

  1. string在redis内部存储默认就是一个字符串,当遇到增减类操作incr,decr时会转成数值型进行计算
  2. redis所有的操作都是原子性的,采用单线程处理所有业务,命令是一个一个执行的,因此无需考虑并发带来的数据影响
  3. 按数值进行操作的数据,如果原始数据不能转成数值,或超越了redis 数值上限范围,将报错。 9223372036854775807(java中long型数据最大值,Long.MAX_VALUE),在做次数控制使用可以使用Long.MAX_VALUE-控制的次数,通过异常来判断,是否超过要控制的次数

应用

  • redis用于控制数据库表主键id,为数据库表主键提供生成策略,保障数据库表的主键唯一性
  • 此方案适用于所有数据库,且支持数据库集群
  • redis 控制数据的生命周期,通过数据是否失效控制业务行为,适用于所有具有时效性限定控制的操作
  • redis应用于各种结构型和非结构型高热度数据访问加速

key的命名规范

  • 表名:主键名:主键值:字段值
  • 实例:user:id:123:name

2.2 数据结构

String的数据结构为简单动态字符串(Simple Dynamic String,缩写SDS)。是可以修改的字符串,内部结构实现上类似于Java的ArrayList,采用预分配冗余空间的方式来减少内存的频繁分配。
在这里插入图片描述
如图中所示,内部为当前字符串实际分配的空间capacity一般要高于实际字符串长度len。当字符串长度小于1M时,扩容都是加倍现有的空间,如果超过1M,扩容时一次只会多扩1M的空间。需要注意的是字符串最大长度为512M。

3. Hash

  • 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息
  • 需要的存储结构:一个存储空间保存多个键值对数据
  • hash类型:底层使用哈希表结构实现数据存储
  • 如果field数量较少,存储结构优化为类数组结构
  • 如果field数量较多,存储结构使用HashMap结构
    在这里插入图片描述

3.1 基本命令

  • hset key field value:添加/修改数据
  • hget key field: 获取该字段的值
  • hgetall key:获取该key下的所有字段和值
  • hdel key field1 [field2]:删除数据
  • hmset key field1 value1 field2 value2 … :添加/修改多个数据
  • hmget key field1 field2 … :获取多个数据
  • hlen key:获取哈希表中字段的数量
  • hexists key field:获取哈希表中字段的数量
  • hkeys key:获取哈希表中所有的字段名
  • hvals key:获取哈希表中所有的字段值
  • hincrby key field increment:设置指定字段的数值数据增加指定范围的值
  • hincrbyfloat key field increment:设置指定字段的浮动数增加指定范围的值
  • hsetnx key field value:将哈希表 key 中的域 field 的值设置为 value ,当且仅当域 field 不存在

注意点

  1. hash类型下的value只能存储字符串,不允许存储其他数据类型,不存在嵌套现象。如果数据未获取到, 对应的值为(nil)
  2. 每个 hash 可以存储 2^32 - 1个键值对
  3. hash类型十分贴近对象的数据存储形式,并且可以灵活添加删除对象属性。但hash设计初衷不是为了存 储大量对象而设计的,切记不可滥用,更不可以将hash作为对象列表使用
  4. hgetall 操作可以获取全部属性,如果内部field过多,遍历整体数据效率就很会低,有可能成为数据访问瓶颈

应用

  • redis 应用于购物车数据存储设计
  • redis 应用于抢购,限购类、限量发放优惠卷、激活码等业务的数据存储设计
  • string存储对象(json)与hash存储对象

3.2 数据结构

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

4. List

  • 数据存储需求:存储多个数据,并对数据进入存储空间的顺序进行区分
  • 需要的存储结构:一个存储空间保存多个数据,且通过数据可以体现进入顺序
  • list类型:保存多个数据,底层使用双向链表存储结构实现
  • 单键多值

3.1 基本命令

  • lpush key value1 [value2] ……:向list左边添加多个数据
  • rpush key value1 [value2] ……:向list右边添加多个数据
  • lrange key start stop: 按照索引下标获得元素(从左到右)
  • lrange key 0 -1: 0左边第一个,-1右边第一个(表示获取所有)
  • lindex key index: 按照索引下标获得元素(从左到右)
  • llen key:获得列表长度
  • lpop key:左边删除一个数据
  • rpop key:右边删除一个数据
  • blpop key1 [key2] timeout:在规定的时间内从左边获取指定key的value,单位为秒,会等待执行
  • brpop key1 [key2] timeout: 在规定的时间内从右边获取指定key的value,单位为秒,会等待执行
  • brpoplpush source destination timeout:原子性地返回并删除存储在source列表的最后一个元素(尾),并将该元素推入并存储为destination列表的第一个元素(头)
  • lrem key count value:从左边删除count个value(从左到右)
  • lset key index value:将列表key下标为index的值替换成value
  • linsert key before value newValue:在value的后面插入newValue

注意点

  1. list中保存的数据都是string类型的,数据总容量是有限的,最多2^32 - 1 个元素 (4294967295)
  2. list具有索引的概念,但是操作数据时通常以队列的形式进行入队出队操作,或以栈的形式进行入栈出栈操作
  3. 获取全部数据操作结束索引设置为-1
  4. list可以对数据进行分页操作,通常第一页的信息来自于list,第2页及更多的信息通过数据库的形式加载

应用场景

  • redis 应用于具有操作先后顺序的数据控制
  • redis 应用于最新消息展示

3.2 数据结构

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

首先在列表元素较少的情况下会使用一块连续的内存存储,这个结构是ziplist,也即是压缩列表,它将所有的元素紧挨着一起存储,分配的是一块连续的内存,当数据量比较多的时候才会改成quicklist,因为普通的链表需要的附加指针空间太大,会比较浪费空间。比如这个列表里存的只是int类型的数据,结构上还需要两个额外的指针prev和next。

5. Set

  • 新的存储需求:存储大量的数据,在查询方面提供更高的效率
  • 需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询
  • set类型:与hash存储结构完全相同,仅存储键,不存储值(nil),并且值是不允许重复的
  • 添加,删除,查找的复杂度都是O(1)

3.1 基本命令

  • sadd key member1 [member2]:添加数据
  • smembers key: 获取全部数据
  • srem key member1 [member2]:删除多个数据
  • scard key:获取集合数据总量
  • sismember key member:判断集合中是否包含指定数据
  • srandmember key [count]:随机获取集合中指定数量的数据,不会删除其中的数据
  • spop key [count]:随机获取集合中的某个数据并将该数据移出集合,默认为一个
  • sinter key1 [key2]:求两个集合的交集
  • sunion key1 [key2]:求两个集合的并集
  • sdiff key1 [key2]:求两个集合的差集
  • sinterstore destination key1 [key2]:求两个集合的交集,并保存到destination中
  • sunionstore destination key1 [key2]:求两个集合的并集,并保存到destination中
  • sdiffstore destination key1 [key2]:求两个集合的差集,并保存到destination中
  • smove source destination member:将指定数据从原始集合中移动到目标集合中

注意点

  1. set 类型不允许数据重复,如果添加的数据在 set 中已经存在,将只保留一份 set 类型数据操作的注意事项
  2. set 虽然与hash的存储结构相同,但是无法启用hash中存储值的空间

应用

  • redis 应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐,热卖旅游线路,应用APP推荐, 大V推荐等
  • redis 应用于同类信息的关联搜索,二度关联搜索,深度关联搜索(共同好友、好友的购物信息)
  • redis 应用于基于黑名单与白名单设定的服务控制
  • redis 应用于同类型数据的快速去重
  • 用户权限控制时多个角色有相同的权限,可以使用该结构去重
  • 做访问次数统计(根据cookie,IP地址统计去重)

3.2 数据结构

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

Java中HashSet的内部实现使用的是HashMap,只不过所有的value都指向同一个对象,Redis的set结构也是一样,它的内部也使用hash结构,所有的value都指向同一个内部值。

6. Zset

  • 新的存储需求:数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式
  • 需要的存储结构:新的存储模型,可以保存可排序的数据
  • sorted_set类型:在set的存储结构基础上添加可排序字段
  • 集合的成员是唯一的,但是评分可以是重复了

3.1 基本命令

  • zadd key score1 member1 [score2 member2]:添加对应的分数和元素
  • zrange key start stop [WITHSCORES]: 返回有序集 key 中,下标在start,stop之间的元素,带WITHSCORES,可以让分数一起和值返回到结果集,根据索引来获取
  • zrevrange key start stop [WITHSCORES]:反序返回有序集合
  • zrem key member [member …]:删除数据
  • zrangebyscore key min max [WITHSCORES] [LIMIT]:根据分数来获取数据,并降序排序
  • zrevrangebyscore key max min [WITHSCORES]:根据分数来获取数据,并升序排序
  • zremrangebyrank key start stop:根据排序后的索引删除数据
  • zremrangebyscore key min max:根据分数删除数据
  • zcard key: 获取集合数据总量
  • zcount key min max:计算分数在min-max之间的value的数量
  • zinterstore destination numkeys key [key …]:交集,将两个集合的交集存入destination
  • zunionstore destination numkeys key [key …]:并集,将两个集合的并集存入destination
  • zrank key member:获取对应升序排序后的索引,0开始
  • zrevrank key member:获取对应降序排序后的索引
  • zscore key member:获取该value的score
  • zincrby key increment member:为元素的score加上增量
  • time:获取当前系统时间

注意点

  1. score保存的数据存储空间是64位,如果是整数范围是-9007199254740992~9007199254740992
  2. score保存的数据也可以是一个双精度的double值,基于双精度浮点数的特征,可能会丢失精度,使用时候要慎重
  3. sorted_set 底层存储还是基于set结构的,因此数据不能重复,如果重复添加相同的数据,score值将被反 复覆盖,保留最后一次修改的结果

应用

  • redis 应用于计数器组合排序功能对应的排名
  • redis 应用于定时任务执行顺序管理或任务过期管理,使用time命令
  • redis 应用于即时任务/消息队列执行管理,带权重的任务执行,权重高的优先执行

3.2 数据结构

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

zset底层使用了两个数据结构:

  1. hash,hash的作用就是关联元素value和权重score,保障元素value的唯一性,可以通过元素value找到相应的score值
  2. 跳跃表,跳跃表的目的在于给元素value排序,根据score的范围获取元素列表

三、Jedis

  1. 导入依赖

    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
        <version>3.2.0</version>
    </dependency>
    
  2. 配置连接信息

    redis.host=127.0.0.1
    redis.port=6379
    # 最大连接数
    redis.maxTotal=30
    # 可用连接数
    redis.maxIdle=10
    
  3. 创建封装连接

    public class JedisUtil {
          
    	private static Jedis jedis = null;
    	private static String host = null;
    	private static int port;
    	private static int maxTotal;
    	private static int maxIdle;
    
    	//使用静态代码块,只加载一次
    	static {
          
    		//读取配置文件
    		ResourceBundle resourceBundle = ResourceBundle.getBundle("redis");
    		//获取配置文件中的数据
    		host = resourceBundle.getString("redis.host");
    		port = Integer.parseInt(resourceBundle.getString("redis.port"));
    		//读取最大连接数
    		maxTotal = Integer.parseInt(resourceBundle.getString("redis.maxTotal"));
    		//读取最大活跃数
    		maxIdle = Integer.parseInt(resourceBundle.getString("redis.maxIdle"));
    		JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    		jedisPoolConfig.setMaxTotal(maxTotal);
    		jedisPoolConfig.setMaxIdle(maxIdle);
    		//获取连接池
    		JedisPool jedisPool = new JedisPool(jedisPoolConfig, host, port);
    		jedis = jedisPool.getResource();
    	}
    
    	public Jedis getJedis() {
          
    		return jedis;
    	}
    }
    
  4. Jedis的API和redis命令相同

四、Redis持久化

Redis 提供了2个不同形式的持久化方式:

  • RDB(Redis DataBase): 将当前数据状态进行保存,快照形式,存储数据结果,存储格式简单,关注点在数据
  • AOF(Append Of File):将数据的操作过程进行保存,日志形式,存储操作过程,存储格式复杂,关注点在数据的操作过程

4.1 RDB

概述:在指定的时间间隔内将内存中的数据集快照写入磁盘, 也就是行话讲的Snapshot快照,它恢复时是将快照文件直接读到内存里

执行过程

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

1. 开启RDB持久化的方式

  1. 方式1

    手动命令方式,redis客户端连接后输入以下命令,就可手动保存,但是其只管保存,其它不管,全部阻塞,不建议。

    save
    
  2. 方式2

    手动命令方式,redis客户端连接后输入以下命令,Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求,可以通过lastsave 命令获取最后一次成功执行快照的时间

    bgsave
    
    # 两种特殊启动方式
    # 1.服务器运行过程中重启
    debug reload
    
    # 2.关闭服务器时指定保存数据
    shutdown save
    
  3. 方式3

    修改redis.conf配置文件配置save的参数,具体查看下面

2. redis.conf基本配置属性

  • dbfilename dump.rdb
    • 说明:设置本地数据库文件名,默认值为 dump.rdb
    • 经验:通常设置为dump-端口号.rdb
  • dir
    • 说明:设置存储.rdb文件的路径
    • 经验:通常设置成存储空间较大的目录中,目录名称data
  • rdbcompression yes
    • 说明:设置存储至本地数据库时是否压缩数据,默认为 yes,采用 LZF 压缩
    • 经验:通常默认为开启状态,如果设置为no,可以节省 CPU 运行时间,但会使存储的文件变大(巨大)
  • rdbchecksum yes
    • 说明:设置是否进行RDB文件格式校验,该校验过程在写文件和读文件过程均进行
    • 经验:通常默认为开启状态,如果设置为no,可以节约读写性过程约10%时间消耗,但是存储一定的数据损坏风险
  • stop-writes-on-bgsave-error yes
    • 说明:后台存储过程中如果出现错误现象,是否停止保存操作
    • 经验:通常默认为开启状态
  • save second changes
    • 说明:满足限定时间范围内key的变化数量达到指定数量即进行持久化,second秒内changes个key发生变化就持久化,可多行配置
    • 经验:要么前面的值大,要么后面的值大

配置文件示例

# 绑定的主机地址
bind 127.0.0.1
# 保护模式启动
protected-mode yes
#启动的端口
port 6379
# 是否后台启动,docker环境下设为no,否则redis启动不了
daemonize no
supervised no
#pid文件存放位置
pidfile /var/run/redis_6379.pid
# 设置服务器以指定日志记录级别,debug|verbose|notice|warning
loglevel notice
#日志文件名
logfile "6379.log"
#默认数据库的数量
databases 16
#是否显示日志
always-show-logo yes
#配置RDB持久化策略 每20秒2个key变化就持久化
save 20 2
#bgsave持久化错误就不保存当前数据
stop-writes-on-bgsave-error yes
#开启RDB文件的压缩
rdbcompression yes
#校验RDB文件
rdbchecksum yes
#配置RDB持久化的文件名
dbfilename dump-6379.rdb
rdb-del-sync-files no
#配置数据保存的目录
dir ./
#设置同一时间最大客户端连接数,默认无限制。当客户端连接到达上限,Redis会关闭新的连接
maxclients 0
#客户端闲置等待最大时长,达到最大值后关闭连接。如需关闭该功能,设置为 0
timeout 30
#导入并加载指定配置文件信息,用于快速创建redis公共配置较多的redis实例配置文件,便于维护
include /path/server-6379.conf

注意

  1. save命令的执行会阻塞当前Redis服务器,直到当前RDB过程完成为止,有可能会造成长时间阻塞,线上环境不建议使用
  2. bgsave命令是针对save阻塞问题做的优化。Redis内部所有涉及到RDB操作都采用bgsave的方式,save命令可以放弃使用
  3. save配置要根据实际业务情况进行设置,频度过高或过低都会出现性能问题,结果可能是灾难性的
  4. save配置中对于second与changes设置通常具有互补对应关系,尽量不要设置成包含性关系
  5. save配置启动后执行的是bgsave操作

启动方式对比
在这里插入图片描述

3. RDB持久化优势

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高更适合使用
  • RDB是一个紧凑压缩的二进制文件,节省磁盘空间存储效率较高
  • RDB内部存储的是redis在某个时间点的数据快照,非常适合用于数据备份,全量复制等场景
  • 服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复
  • RDB恢复数据的速度要比AOF快很多

4. RDB持久化劣势

  • 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
  • 无法做到实时持久化,具有较大的可能性丢失数据,如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改
  • Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑

4.2 AOF

概述:以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作,目前已经是Redis持久化的主流方式

执行过程

  1. 客户端的请求写命令会被append追加到AOF缓冲区内
  2. AOF缓冲区根据AOF持久化策略always、everysec、no将操作sync同步到磁盘的AOF文件中
  3. AOF文件大小超过重写策略或手动重写时,会对AOF文件rewrite重写,压缩AOF文件容量
  4. Redis服务重启时,会重新load加载AOF文件中的写操作达到数据恢复的目的
    在这里插入图片描述
    注意点
  • AOF默认不开启
  • AOF和RDB同时开启,系统默认取AOF的数据(数据不会存在丢失)
  • 如遇到AOF文件损坏,通过/usr/local/bin/redis-check-aof–fix appendonly.aof进行恢复

1. AOF重写

概述

随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重 写是将Redis进程内的数据转化为写命令同步到新AOF文件的过程。简单说就是将对同一个数据的若干个条命令执行结 果转化成最终结果数据对应的指令进行记录。

作用

  • 降低磁盘占用量,提高磁盘利用率
  • 提高持久化效率,降低持久化写时间,提高IO性能
  • 降低数据恢复用时,提高数据恢复效率

原理

AOF文件持续增长而过大时,会fork出一条新进程来将文件重写(也是先写临时文件最后再rename),redis4.0版本后的重写,是指把rdb 的快照,以二进制的形式附在新的aof头部,作为已有的历史数据,替换掉原来的流水账操作。

1.1 开启方式
  • 方式1

    手动以命令方式开启,命令如下

    bgrewriteaof
    
  • 方式2

    配置文件配置AOF重写条件

    # 触发AOF重写的最小文件大小
    auto-aof-rewrite-min-size size
    # 触发AOF重写的百分比
    auto-aof-rewrite-percentage percent
    

    运行指令redis-cli 连接Redis服务后使用info Persistence命令获取aof_current_size与aof_base_size参数,这两个参数为系统载入时或者上次重写完毕时,Redis会记录的AOF大小

    方式2根据如下添加是否成立来进行自动的AOF重写

在这里插入图片描述

1.2 重写流程
  1. bgrewriteaof触发重写,判断是否当前有bgsave或bgrewriteaof在运行,如果有,则等待该命令结束后再继续执行
  2. 主进程fork出子进程执行重写操作,保证主进程不会阻塞
  3. 子进程遍历redis内存中数据到临时文件,客户端的写请求同时写入aof_buf缓冲区和aof_rewrite_buf重写缓冲区保证原AOF文件完整以及新AOF文件生成期间的新的数据修改动作不会丢失
  4. 子进程写完新的AOF文件后,向主进程发信号,父进程更新统计信息,主进程把aof_rewrite_buf中的数据写入到新的AOF文件
  5. 使用新的AOF文件覆盖旧的AOF文件,完成AOF重写
    在这里插入图片描述

2. 开启AOF持久化方式

AOF写数据三种策略(appendfsync)

  1. always(每次):每次写入操作均同步到AOF文件中,数据零误差,性能较低
  2. everysec(每秒):每秒将缓冲区中的指令同步到AOF文件中,数据准确性较高,性能较高,在系统突然宕机的情况下丢失1秒内的数据
  3. no(系统控制):由操作系统控制每次同步到AOF文件的周期,整体过程不可控

配置文件AOF持久化相关配置属性

  • appendonly yes|no
    • 是否开启AOF持久化功能,默认为不开启状态
  • appendfsync always|everysec|no
    • AOF写数据策略
  • appendfilename filename
    • AOF持久化文件名,默认文件名未appendonly.aof,建议配置为appendonly-端口号.aof
  • auto-aof-rewrite-min-size size
    • AOF文件重写触发条件文件的大小
  • auto-aof-rewrite-percentage percentage
    • AOF文件重写触发条件百分比的设置

配置示例

#基本配置
bind 127.0.0.1
protected-mode yes
port 6379
daemonize no
supervised no
pidfile /var/run/redis_6379.pid
loglevel notice
logfile "6379.log"
databases 16
always-show-logo yes
save 20 2
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump-6379.rdb
rdb-del-sync-files no
dir ./
#开启密码
requirepass 666
#开启AOF持久化
appendonly yes
#AOF持久化的文件名
appendfilename "appendonly-6379.aof"
#AOF持久化策略
appendfsync everysec
no-appendfsync-on-rewrite no
#AOF重写条件百分比设置
auto-aof-rewrite-percentage 100
#AOF重写条件文件大小设置
auto-aof-rewrite-min-size 64mb
aof-load-truncated yes
aof-use-rdb-preamble yes

3. AOF持久化优势

  • 备份机制更稳健,丢失数据概率更低
  • 可读的日志文本,通过操作AOF稳健,可以处理误操作

4. AOF持久化劣势

  • 比起RDB占用更多的磁盘空间
  • 恢复备份速度要慢
  • 每次读写都同步的话,有一定的性能压力
  • 存在个别Bug,造成恢复不能

4.3 RDB与AOF的选择

  • 数据非常敏感,建议使用默认的AOF持久化方案
    • AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出 现问题时,最多丢失0-1秒内的数据。
    • 注意:由于AOF文件存储体积较大,且恢复速度较慢
  • 数据呈现阶段有效性,建议使用RDB持久化方案
    • 数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段 点数据恢复通常采用RDB方案
    • 注意:利用RDB实现紧凑的数据持久化会使Redis降的很低
  • 综合比对
    • RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊
    • 如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF
    • 如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB
    • 灾难恢复选用RDB
    • 双保险策略,同时开启 RDB 和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据的量,官网建议

五、Redis事务

概述

redis事务就是一个命令执行的队列,将一系列预定义命令包装成一个整体(一个队列)。当执行时,一次性 按照添加顺序依次执行,中间不会被打断或者干扰,一个队列中,一次性、顺序性、排他性的执行一系列命令

5.1 基本操作

基本命令

  • multi:设定事务的开启位置,此指令执行后,后续的所有指令均加入到事务中
  • exec:设定事务的结束位置,同时执行事务。与multi成对出现,成对使用
  • discard:终止当前事务的定义,发生在multi之后,exec之前,用于命令定义错误
    在这里插入图片描述
    注意点
  1. 加入事务的命令暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行

5.2 工作流程

在这里插入图片描述

5.3 事务的错误处理

  • 语法错误,指命令书写格式有误

    • 组队中某个命令出现了报告错误,执行时整个的所有队列都会被取消
      在这里插入图片描述
  • 运行错误 指命令格式正确,但是无法正确的执行,例如对list进行incr操作

    • 如果执行阶段某个命令出现了错误,并没有报告错误,则只有出现错误的命令不会被执行,而其他的命令都会执行,不会回滚
      在这里插入图片描述

5.4 redis事务的特性

  • 单独的隔离操作
    • 事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断
  • 没有隔离级别的概念
    • 队列中的命令没有提交之前都不会实际被执行,因为事务提交前任何指令都不会被实际执行
  • 不保证原子性
    • 事务中如果有一条命令执行失败,其后的命令仍然会被执行,没有回滚

5.4 事务冲突问题

一个请求想给金额减8000

一个请求想给金额减5000

一个请求想给金额减1000
在这里插入图片描述

5.5 悲观锁

在这里插入图片描述

悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁表锁等,读锁写锁等,都是在做操作之前先上锁。

5.6 乐观锁

在这里插入图片描述
乐观锁(Optimistic Lock):顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。Redis就是利用这种check-and-set机制实现事务的。

5.7 基于特定条件的事务执行—锁

基本命令

  • watch key1 [key2……]:在执行multi之前,对 key 添加监视锁,在执行exec前如果key发生了变化,终止事务执行,这为乐观锁机制
  • unwatch:取消对所有 key 的监视

应用

  • redis 应用基于状态控制的批量任务执行

5.8 基于特定条件的事务执行—分布式锁

基本命令

  • setnx lock-key value:使用 setnx 设置一个公共锁
  • expire lock-key second:设置锁的过期时间
  • pexpire lock-key millisecond:设置锁的过期时间

利用setnx命令的返回值特征,有值则返回设置失败,无值则返回设置成功

  • 对于返回设置成功的,拥有控制权,进行下一步的具体业务操作,操作完毕通过del操作释放锁
  • 对于返回设置失败的,不具有控制权,排队或等待操作完毕通过del操作释放锁
  • 如获得锁的应用没有del释放锁,此时redis宕机,其他用户无法获取锁,此时可以为lock-key设置过期时间,自动干预来释放锁

锁过期时间的设定

由于操作通常都是微秒或毫秒级,因此该锁定时间不宜设置过大,具体时间需要业务测试后确认。

  • 例如:持有锁的操作最长执行时间127ms,最短执行时间7ms。
  • 测试百万次最长执行时间对应命令的最大耗时,测试百万次网络延迟平均耗时
  • 锁时间设定推荐:最大耗时*120%+平均网络延迟*110%
  • 如果业务最大耗时<<网络平均延迟,通常为2个数量级,取其中单个耗时较长即可

应用

  • redis 应用基于分布式锁对应的场景控制

六、redis删除策略

Redis是一种内存级数据库,所有数据均存放在内存中,内存中的数据可以通过TTL指令获取其状态

  • XX :具有时效性的数据
  • -1 :永久有效的数据
  • -2 :已经过期的数据 或 被删除的数据 或 未定义的数据
6.1 时效性数据的存储结构
  • Redis中的数据,在expire中以哈希的方式保存在其中。其filed是数据在内存中的地址,value对应的生命周期

在这里插入图片描述

数据删除策略的目标

在内存占用与CPU占用之间寻找一种平衡,顾此失彼都会造成整体redis性能的下降,甚至引发服务器宕机或 内存泄露

6.2 删除策略
  • 定时删除

    • 创建一个定时器,当key设置有过期时间,且过期时间到达时,由定时器任务立即执行对键的删除操作
    • 优点:节约内存,到时就删除,快速释放掉不必要的内存占用
    • 缺点:CPU压力很大,无论CPU此时负载量多高,均占用CPU,会影响redis服务器响应时间和指令吞吐量
    • 总结:用处理器性能换取存储空间(拿时间换空间)
  • 惰性删除

    • 数据到达过期时间,不做处理。等下次访问该数据时,如果未过期,返回数据,发现已过期,删除,返回不存在
    • 优点:节约CPU性能,发现必须删除的时候才删除
    • 缺点:内存压力很大,出现长期占用内存的数据
    • 总结:用存储空间换取处理器性能拿时间换空间)
  • 定期删除

    • 周期性轮询redis库中的时效性数据,采用随机抽取的策略,利用过期数据占比的方式控制删除频度
    • 特点1:CPU性能占用设置有峰值,检测频度可自定义设置
    • 特点2:内存压力不是很大,长期占用内存的冷数据会被持续清理
    • 总结:周期性抽查存储空间 expireIfNeeded() (随机抽查,重点抽查)
      在这里插入图片描述
6.3 逐出策略

当新数据进入redis时,如果内存不足怎么办?

  • Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryIfNeeded()检测内存是否充足。如 果内存不满足新加入数据的最低存储要求,redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称为逐出算法。
  • 注意:逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所 有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。

相关配置

配置文件属性

  • maxmemory:最大可使用内存,占用物理内存的比例,默认值为0,表示不限制。生产环境中根据需求设定,通常设置在50%以上。
  • maxmemory-samples:每次选取待删除数据的个数,选取数据时并不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为待检测删除数据
  • maxmemory-policy: 达到最大内存后的,对被挑选出来的数据进行删除的策略

相关删除策略如下

检测易失数据(可能会过期的数据集server.db[i].expires )

  • volatile-lru:挑选最近最少使用的数据淘汰
  • volatile-lfu:挑选最近使用次数最少的数据淘汰
  • volatile-ttl:挑选将要过期的数据淘汰
  • volatile-random:任意选择数据淘汰

检测全库数据(所有数据集server.db[i].dict )

  • allkeys-lru:挑选最近最少使用的数据淘汰
  • allkeys-lfu:挑选最近使用次数最少的数据淘汰
  • allkeys-random:任意选择数据淘汰

放弃数据驱逐

  • no-enviction(驱逐):禁止驱逐数据(redis4.0中默认策略),会引发错误OOM(Out Of Memory)

调优依据

  • 使用INFO命令输出监控信息,查询缓存 hit 和 miss 的次数,根据业务需求调优Redis配置

七、高级数据类型

7.1 Bitmaps

概述

现代计算机用二进制(位) 作为信息的基础单位, 1个字节等于8位, 例如“abc”字符串是由3个字节组成, 但实际在计算机存储时将其用二进制表示, “abc”分别对应的ASCII码分别是97、 98、 99, 对应的二进制分别是01100001、 01100010和01100011,如下图
在这里插入图片描述
合理地使用操作位能够有效地提高内存使用率和开发效率,Redis提供了Bitmaps这个数据类型可以实现对位的操作:

  1. Bitmaps本身不是一种数据类型, 实际上它就是字符串(key-value) , 但是它可以对字符串的位进行操作
  2. Bitmaps单独提供了一套命令, 所以在Redis中使用Bitmaps和使用字符串的方法不太相同。 可以把Bitmaps想象成一个以位为单位的数组, 数组的每个单元只能存储0和1, 数组的下标在Bitmaps中叫做偏移量。

基本命令

  • setbit key offset value:设置指定key对应偏移量上的bit值,value只能是1或0,offset偏移量从0开始
  • getbit key offset:获取指定key对应偏移量上的bit值,offset偏移量从0开始
  • bitcount key [start end]:统计指定key中1的数量,start、end以byte为单位8个字节为一个位, -1 表示最后一个位,而 -2 表示倒数第二个位
  • bitop op destKey key1 [key2…]:对指定key按位进行交、并、非、异或操作,并将结果保存到destKey中
    • and:交
    • or:并
    • not:非
    • xor:异或

注意点

  1. Bitmaps的setbit设置或清除的是bit位置,而bitcount计算的是byte位置8个字节为一个单位

应用

  • 统计用户的月活跃度
  • 统计用户是否访问过网站

7.2 HyperLogLog

概述

在工作当中,我们经常会遇到与统计相关的功能需求,比如统计网站PV(PageView页面访问量),可以使用Redis的incr、incrby轻松实现,但像UV(UniqueVisitor,独立访客)、独立IP数、搜索记录数等需要去重和计数的问题如何解决?这种求集合中不重复元素个数的问题称为基数问题。

解决基数问题有很多种方案:

  1. 数据存储在MySQL表中,使用distinct count计算不重复个数
  2. 使用Redis提供的hash、set、bitmaps等数据结构来处理

以上的方案结果精确,但随着数据不断增加,导致占用空间越来越大,对于非常大的数据集是不切实际的。

能否能够降低一定的精度来平衡存储空间?Redis推出了HyperLogLog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比,但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog 不能像集合那样,返回输入的各个元素。

什么是基数?

比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。

基本命令

  • pfadd key element [element …]:添加数据
  • pfcount key [key …]:统计数据
  • pfmerge destkey sourcekey [sourcekey…]:合并数据

注意点

  1. 用于进行基数统计,不是集合,不保存数据,只记录数量而不是具体数据
  2. 核心是基数估算算法,最终数值存在一定误差
  3. 误差范围:基数估计的结果是一个带有 0.81% 标准错误的近似值
  4. 耗空间极小,每个hyperloglog key占用了12K的内存用于标记基数
  5. pfadd命令不是一次性分配12K内存使用,会随着基数的增加内存逐渐增大
  6. Pfmerge命令合并后占用的存储空间为12K,无论合并之前数据量多少

应用

  • 统计网站PV

7.3 GEO

概述

Redis 3.2 中增加了对GEO类型的支持。GEO,Geographic,地理信息的缩写。该类型,就是元素的2维坐标,在地图上就是经纬度。redis基于该类型,提供了经纬度设置,查询,范围查询,距离查询,经纬度Hash等常见操作。

基本命令

  • geoadd key longitude latitude member [longitude latitude member …]:添加坐标点位置
    • geoadd china:city 106.50 29.53 chongqing 114.05 22.52 shenzhen 116.38 39.90 beijing
  • geopos key member [member …]:获取坐标点位置
    • geopos china:city chongqing
  • geodist key member1 member2 [unit]:取两个位置之间的直线距离,单位:m(米),km(千米),mi(英里),ft(英寸),默认为米
    • geodist china:city beijing shenzhen km
  • georadius key longitude latitude radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]:以给定的经纬度为中心,找出某一半径内的元素,withcoord返回经纬度,withdist返回距离,withhash返回geohash值,可以指定asc或desc根据距离来排序,指定count返回的数量
    • georadius china:city 110 30 1000 km
  • georadiusbymember key member radius m|km|ft|mi [withcoord] [withdist] [withhash] [count count]:统计指定半径范围内的元素个数,withcoord返回经纬度,withdist返回距离,withhash返回geohash值,可以指定asc或desc根据距离来排序,指定count返回的数量
    • georadiusbymember china:city beijing 1000 km
  • geohash key member [member …]:获取位置的hash值

应用

  • redis 应用于地理位置计算

八、Redis的发布订阅

概述

Redis 发布订阅 (pub/sub) 是一种消息通信模式:发送者 (pub) 发送消息,订阅者 (sub) 接收消息,Redis 客户端可以订阅任意数量的频道。

流程图

  1. 客户端可以订阅频道
    在这里插入图片描述
  2. 当给这个频道发布消息后,消息就会发送给订阅的客户端
    在这里插入图片描述
    基本命令
  • subscribe channel1:客户端订阅一个频道,只要该频道接收到其他客户端发送的消息后就显示
  • publish channel1 msg:客户端给指定的端口发送一个消息,返回的是该频道的订阅人数

注意

  1. 发布的消息没有持久化,如果在订阅的客户端收不到消息,只能收到订阅后发布的消息

九、主从复制

概述

为了避免单点Redis服务器故障,准备多台服务器,互相连通。将数据复制多个副本保存在不同的服 务器上,连接在一起,并保证数据是同步的。即使有其中一台服务器宕机,其他服务器依然可以继续 提供服务,实现Redis的高可用,同时实现数据冗余备份。

多服务器连接方案

  • 提供数据方:master
    • 主服务器,主节点,主库 主客户端
  • 接收数据方:slave
    • 从服务器,从节点,从库
    • 从客户端
  • 需要解决的问题: 数据同步
  • 核心工作: master的数据复制到slave中
    在这里插入图片描述
    主从复制

主从复制即将master中的数据即时、有效的复制到slave中

特征:一个master可以拥有多个slave,一个slave只对应一个master

职责:

  • master
    • 写数据
    • 执行写操作时,将出现变化的数据自动同步到slave
    • 读数据(可忽略)
  • slave
    • 读数据
    • 写数据(禁止)
9.1 主从复制的作用
  • 读写分离:master写、slave读,提高服务器的读写负载能力
  • 负载均衡:基于主从结构,配合读写分离,由slave分担master负载,并根据需求的变化,改变slave的数 量,通过多个从节点分担数据读取负载,大大提高Redis服务器并发量与数据吞吐
  • 故障恢复:当master出现问题时,由slave提供服务,实现快速的故障恢复
  • 数据冗余:实现数据热备份,是持久化之外的一种数据冗余方式
  • 高可用基石:基于主从复制,构建哨兵模式与集群,实现Redis的高可用方案
9.2 主从复制流程

概述

大致可以分为三个阶段

  1. 建立连接阶段(即准备阶段)
  2. 数据同步阶段
  3. 命令传播阶段
    在这里插入图片描述
1. 建立连接阶段

建立slave到master的连接,使master能够识别slave,并保存slave端口号

  • 步骤

    1. 设置master的地址和端口,保存master信息
    2. 建立socket连接
    3. 发送ping命令(定时器任务)
    4. 身份验证
    5. 发送slave端口信息 至此,主从连接成功!
  • 状态

    • slave:保存master的地址与端口
    • master: 保存slave的端口
    • 总体: 之间创建了连接的socket
      在这里插入图片描述
2. 数据同步阶段

在slave初次连接master后,复制master中的所有数据到slave

将slave的数据库状态更新成master当前的数据库状态

  • 步骤
    1. 请求同步数据
    2. 创建RDB同步数据
    3. 恢复RDB同步数据
    4. 请求部分同步数据
    5. 恢复部分同步数据
  • 状态
    • slave:具有master端全部数据,包含RDB过程接收的数据
    • master: 保存slave当前数据同步的位置
    • 总体: 之间完成了数据克隆
      在这里插入图片描述
  • 全量复制:在master执行bgsave之后,master中所有的数据同步到slave中
  • 部分复制(增量复制):将master执行bgsave操作中时,将新加入的数据(复制缓冲区中的数据) 传给slave,slave通过bgrewriteaof指令来恢复数据

数据同步阶段master说明

  1. 如果master数据量巨大,数据同步阶段应避开流量高峰期,避免造成master阻塞,影响业务正常执行
  2. 复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失的情况,必须进行第二次全量复制,致使slave陷入死循环状态。
  3. master单机内存占用主机内存的比例不应过大,建议使用50%-70%的内存,留下30%-50%的内存用于执 行bgsave命令和创建复制缓冲区

解决方法

配置文件中配置一下属性,具体配置数据大小参考如下说明

#配置复杂缓冲区的内存大小
repl-backlog-size 1mb
  1. 测算从master到slave的重连平均时长second
  2. 获取master平均每秒产生写命令数据总量write_size_per_second
  3. 最优复制缓冲区空间 = 2 * second * write_size_per_second

数据同步阶段slave说明

  1. 为避免slave进行全量复制、部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务
  2. 数据同步阶段,master发送给slave信息可以理解master是slave的一个客户端,主动向slave发送命令
  3. 多个slave同时对master请求数据同步,master发送的RDB文件增多,会对带宽造成巨大冲击,如果 master带宽不足,因此数据同步需要根据业务需求,适量错峰
  4. slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构,中间的节点既是master,也是 slave。注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟 较大,数据一致性变差,应谨慎选择

配置命令

# 配置从机进行全量复制和部分复制时该机是否对外服务
slave-serve-stale-data yes|no
3. 命令传播阶段

当master数据库状态被修改后,导致主从服务器数据库状态不一致,此时需要让主从数据同步到一致的 状态,同步的动作称为命令传播,master将接收到的数据变更命令发送给slave,slave接收命令后执行命令

命令传播阶段的部分复制

命令传播阶段出现了断网现象

  • 网络闪断闪连:忽略
  • 短时间网络中断:部分复制
  • 长时间网络中断:全量复制

部分复制的三个核心要素

  • 服务器的运行 id(run id)
  • 主服务器的复制积压缓冲区
  • 主从服务器的复制偏移量

服务器运行ID(runid)

  • 概念:服务器运行ID是每一台服务器每次运行的身份识别码,一台服务器多次运行可以生成多个运行id
  • 组成:运行id由40位字符组成,是一个随机的十六进制字符 例如:fdc9ff13b9bbaab28db42b3d50f852bb5e3fcdce
  • 作用:运行id被用于在服务器间进行传输,识别身份 如果想两次操作均对同一台服务器进行,必须每次操作携带对应的运行id,用于对方识别
  • 实现方式:运行id在每台服务器启动时自动生成的,master在首次连接slave时,会将自己的运行ID发 送给slave,slave保存此ID,通过info Server命令,可以查看节点的run id

复制缓冲区

  • 概念:复制缓冲区,又名复制积压缓冲区,是一个先进先出(FIFO)的队列,用于存储服务器执行过的命 令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区
    • 复制缓冲区默认数据存储空间大小是1M,由于存储空间大小是固定的,当入队元素的数量大于队 列长度时,最先入队的元素会被弹出,而新元素会被放入队列
  • 由来:每台服务器启动时,如果开启有AOF或被连接成为master节点,即创建复制缓冲区
  • 组成:偏移量 + 字节值
  • 工作原理
    • 通过offset区分不同的slave当前数据传播的差异
    • master记录已发送的信息对应的offset
    • slave记录已接收的信息对应的offset
  • 作用:用于保存master收到的所有指令(仅影响数据变更的指令,例如set,select)
  • 数据来源:当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中

主从服务器复制偏移量(offset)

  • 概念:一个数字,描述复制缓冲区中的指令字节位置
  • 分类
    • master复制偏移量:记录发送给所有slave的指令字节对应的位置(多个)
    • slave复制偏移量:记录slave接收master发送过来的指令字节对应的位置(一个)
  • 数据来源
    • master端:发送一次记录一次
    • slave端:接收一次记录一次
  • 作用:同步信息,比对master与slave的差异,当slave断线后,恢复数据使用
    在这里插入图片描述
    在这里插入图片描述

心跳机制

  • 进入命令传播阶段候,master与slave间需要进行信息交换,使用心跳机制进行维护,实现双方连接保持在线
  • master心跳
    • 指令:ping
    • 周期:由repl-ping-slave-period决定,默认10秒
    • 作用:判断slave是否在线
    • 查询:info replication命令 获取slave最后一次连接时间间隔,lag项维持在0或1视为正常
  • slave心跳任务
    • 指令:REPLCONF ACK {offset}
    • 周期:1秒
    • 作用1:汇报slave自己的复制偏移量,获取最新的数据变更指令
    • 作用2:判断master是否在

心跳阶段注意事项

  1. 当slave多数掉线,或延迟过高时,master为保障数据稳定性,将拒绝所有信息同步操作

    #slave数量少于2个,或者所有slave的延迟都大于等于10秒时,强制关闭master写功能,停止数据同步
    min-slaves-to-write 2
    min-slaves-max-lag 10
    
  2. slave数量由slave发送REPLCONF ACK命令做确认

  3. slave延迟由slave发送REPLCONF ACK命令做确认

9.4 Docker配置Redis主从复制
  1. 创建一台主机,两台从机的3个配置文件,使用include引入默认的配置文件

    注意:以docker方式配置主从复制,配置文件中要注释一下两个配置属性bind 127.0.0.1protected-mode yes,否则配置后主从机无法连接通信,protected-mode yes 可以改为no

    
    #主机配置文件-6379
    port 6379
    tcp-backlog 511
    timeout 0
    tcp-keepalive 300
    daemonize no
    supervised no
    pidfile /var/run/redis_6379.pid
    loglevel notice
    logfile "6379.log"
    rdb-del-sync-files no
    databases 16
    always-show-logo yes
    save 20 2
    dbfilename dump6379.rdb
    stop-writes-on-bgsave-error yes
    rdbcompression yes
    rdbchecksum yes
    dir ./
    
    #从机1配置文件-6380
    port 6380
    tcp-backlog 511
    timeout 0
    tcp-keepalive 300
    daemonize no
    supervised no
    pidfile /var/run/redis_6380.pid
    loglevel notice
    logfile "6380.log"
    rdb-del-sync-files no
    databases 16
    always-show-logo yes
    save 20 2
    dbfilename dump6380.rdb
    stop-writes-on-bgsave-error yes
    rdbcompression yes
    rdbchecksum yes
    dir ./
    
    #从机1配置文件-6381
    port 6381
    tcp-backlog 511
    timeout 0
    tcp-keepalive 300
    daemonize no
    supervised no
    pidfile /var/run/redis_6381.pid
    loglevel notice
    logfile "6381.log"
    rdb-del-sync-files no
    databases 16
    always-show-logo yes
    save 20 2
    dbfilename dump6381.rdb
    stop-writes-on-bgsave-error yes
    rdbcompression yes
    rdbchecksum yes
    dir ./
    

    技巧快速创建并修改文件中的端口

    # 将redis-6379.conf文中所有的6379端口变为6380之后保存到redis-6380.conf中
    sed 's/6379/6380/g' redis-6379.conf > redis-6380.conf
    

在这里插入图片描述
2. docker启动三台redis服务器

#6379主机
docker run -d --name redis-79 -p 6379:6379 -v /docker-images/redis/conf/redis-6379.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf

#6380从机1
docker run -d --name redis-80 -p 6380:6380 -v /docker-images/redis/conf/redis-6380.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf

#6381从机2
docker run -d --name redis-81 -p 6381:6381 -v /docker-images/redis/conf/redis-6381.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf

在这里插入图片描述
3. 使用redis客户端连接查看redis的信息,此时全部为主机

#使用客户端工具连接主机
docker exec -it redis-79 redis-cli 

#使用客户端工具连接从机1
docker exec -it redis-80 redis-cli -p 6380

#使用客户端工具连接从机2
docker exec -it redis-81 redis-cli -p 6381

在这里插入图片描述
4. 进入6379、6380、6381容器查看各个容器的ip地址,主要记住6379主机的ip地址,这里为172.17.0.2

root@veo:/# docker inspect redis-79 redis-80 redis-81 | grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "",
                    "IPAddress": "172.17.0.2",
            "SecondaryIPAddresses": null,
            "IPAddress": "",
                    "IPAddress": "172.17.0.3",
            "SecondaryIPAddresses": null,
            "IPAddress": "",
                    "IPAddress": "172.17.0.4",

  1. 配置6380和6381从机的主机

    #6380 配置如下
    127.0.0.1:6380> slaveof 172.17.0.2 6379
    OK
    
    
    #6381配置如下
    127.0.0.1:6381> slaveof 172.17.0.2 6379
    OK
    
    

在这里插入图片描述

  1. 以上主从复制搭建成功

  2. 以配置文件配置主从复制,按如上流程下来,只需在6380和6381机子的配置文件添加如下

    #从机1配置文件-6380中添加主机的ip和端口,ip之前以查出容器中的ip
    slaveof 172.17.0.2 6379
    
    #从机1配置文件-6381中添加
    slaveof 172.17.0.2 6379
    

十、哨兵模式

概述

哨兵(sentinel) 是一个分布式系统,用于对主从结构中的每台服务器进行监控,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。

作用

  • 监控:不断的检查master和slave是否正常运行, master存活检测、master与slave运行情况检测
  • 通知(提醒):当被监控的服务器出现问题时,向其他(哨兵间,客户端)发送通知
  • 自动故障转移:断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服 务器地址

注意点

  1. 哨兵也是一台redis服务器,只是不提供数据服务
  2. 通常哨兵配置数量为单数
10.1 工作原理

哨兵在进行主从切换过程中经历三个阶段

  1. 监控
  2. 通知
  3. 故障转移

阶段一:监控阶段

用于同步各个节点的状态信息

  • 获取各个sentinel的状态(是否在线)
  • 获取master的状态
    • master属性:runid、role:master
    • 各个slave的详细信息
  • 获取所有slave的状态(根据master中的slave信息)
    • slave属性:runid、role:slave、master_host、master_port、offset

阶段二:通知阶段

阶段三:故障转移阶段

  1. 服务器列表中挑选备选master
  • 在线的
  • 响应慢的
  • 与原master断开时间久的
  • 优先原则
    • 优先级:slave-priority
    • offset
    • runid
  1. 发送指令( sentinel )
    • 向新的master发送slaveof no one
    • 向其他slave发送slaveof 新masterIP端口

小结

  • 监控:同步信息
  • 通知:保持联通
  • 故障转移:发现问题、竞选负责人、优选新master、新master上任,其他slave切换master,原master作为slave故障回复后连接
10.2 Docker配置哨兵模式
  1. 根据上面的主从复制,接下来为其添加三个哨兵,创建三个哨兵的配置文件,主要配置哨兵要监听的主机的ip地址和端口号

    快速复制 sed "s/26379/26381/g" sentinel-6379.conf > sentinel-6381.conf

    #哨兵1的端口
    port 26379
    #取消后台启动否则docker中启动不了
    daemonize no
    #pid存储位置
    pidfile /var/run/redis-sentinel.pid
    #日志文件名
    logfile "sentinel-26379.log"
    dir /tmp
    #其中172.17.0.2 为 redis-79主机 的 ip,6379 为 redis-79主机 的端口,2 为最小投票数(因为有 3 台 Sentinel 所以可以设置成 2)
    sentinel monitor mymaster 172.17.0.2 6379 2
    #指定哨兵在监控Redis服务时,判定服务器挂掉的时间周期,默认30秒(30000),也是主从切换的启动条件之一
    sentinel down-after-milliseconds mymaster 10000
    #指定同时进行主从的slave数量,数值越大,要求网络资源越高,要求约小,同步时间约长
    sentinel parallel-syncs mymaster 1
    #指定出现故障后,故障切换的最大超时时间,超过该值,认定切换失败,默认3分钟
    sentinel failover-timeout mymaster 180000
    
    
    #哨兵2
    port 26380
    daemonize no
    pidfile /var/run/redis-sentinel.pid
    logfile "sentinel-26380.log"
    dir /tmp
    sentinel monitor mymaster 172.17.0.2 6379 2
    sentinel down-after-milliseconds mymaster 10000
    sentinel parallel-syncs mymaster 1
    sentinel failover-timeout mymaster 180000
    sentinel deny-scripts-reconfig yes
    
    #哨兵3
    port 26381
    daemonize no
    pidfile /var/run/redis-sentinel.pid
    logfile "sentinel-26381.log"
    dir /tmp
    sentinel monitor mymaster 172.17.0.2 6379 2
    sentinel down-after-milliseconds mymaster 10000
    sentinel parallel-syncs mymaster 1
    sentinel failover-timeout mymaster 180000
    sentinel deny-scripts-reconfig yes
    
  2. 启动三个哨兵

    #第一个哨兵
    docker run -d --name sentinel-1 -p 26379:26379 -v /docker-images/redis/conf/sentinel-26379.conf:/etc/redis/sentinel.conf redis redis-sentinel /etc/redis/sentinel.conf
    
    #第二个哨兵
    docker run -d --name sentinel-2 -p 26380:26380 -v /docker-images/redis/conf/sentinel-26380.conf:/etc/redis/sentinel.conf redis redis-sentinel /etc/redis/sentinel.conf
    
    #第三个哨兵
    docker run -d --name sentinel-3 -p 26381:26381 -v /docker-images/redis/conf/sentinel-26381.conf:/etc/redis/sentinel.conf redis redis-sentinel /etc/redis/sentinel.conf
    

在这里插入图片描述
3. 进入哨兵容器内部,使用info sentinel命令查看内部配置信息

docker exec -it sentinel-1 redis-cli -p 26379

docker exec -it sentinel-2 redis-cli -p 26380

docker exec -it sentinel-3 redis-cli -p 26381

在这里插入图片描述
4. 此时使用docker stop redis-79停掉主机,在进入容器内部查看信息,此时经过三个哨兵投票选择了slave2成为新的主机,此时重启master后,master会成为slave2的从机
在这里插入图片描述
在这里插入图片描述

十一、集群

概述

当容量不够,redis如何进行扩容,并发写操作, redis如何分摊?

另外,主从模式,主机宕机,导致ip地址发生变化,应用程序中配置需要修改对应的主机地址、端口等信息,之前通过代理主机来解决,但是redis3.0中提供了解决方案,就是无中心化集群配置。

Redis 集群实现了对Redis的水平扩容,即启动N个redis节点,将整个数据库分布存储在这N个节点中,每个节点存储总数据的1/N

Redis 集群通过分区(partition)来提供一定程度的可用性(availability): 即使集群中有一部分节点失效或者无法进行通讯, 集群也可以继续处理命令请求。

作用

  • 分散单台服务器的访问压力,实现负载均衡
  • 分散单台服务器的存储压力,实现可扩展性
  • 降低单台服务器宕机带来的业务灾难
11.1 Redis集群数据存储设计-slots

一个 Redis 集群包含 16384 个插槽(hash slot), 数据库中的每个键都属于这 16384 个插槽的其中一个,

集群使用公式 CRC16(key) % 16384 来计算键 key 属于哪个槽, 其中 CRC16(key) 语句用于计算键 key 的 CRC16 校验和 。

集群中的每个节点负责处理一部分插槽。 举个例子, 如果一个集群可以有主节点, 其中:

  • 节点 A 负责处理 0 号至 5460 号插槽
  • 节点 B 负责处理 5461 号至 10922 号插槽
  • 节点 C 负责处理 10923 号至 16383 号插槽

注意点

  1. 在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口
  2. redis-cli客户端提供了 –c 参数实现自动重定向
11.2 Docker配置Redis集群
  1. 搭建一个三主三从的主从复制结构,可参考上方配置,配置6个配置文件6379、6380、6381、6382、6383、6384内容如下

    #6379端口配置文件,其他配置文件就该一下端口和配置文件名即可
    port 6379
    tcp-backlog 511
    timeout 0
    tcp-keepalive 300
    daemonize no
    supervised no
    pidfile /var/run/redis_6379.pid
    loglevel notice
    logfile "6379.log"
    rdb-del-sync-files no
    databases 16
    always-show-logo yes
    save 20 2
    dbfilename dump6379.rdb
    stop-writes-on-bgsave-error yes
    rdbcompression yes
    rdbchecksum yes
    dir ./
    #开启集群模式
    cluster-enabled yes
    #集群模式的配置文件名
    cluster-config-file nodes-6379.conf
    #节点服务响应超时时间,用于判定该节点是否下线或切换为从节点
    cluster-node-timeout 15000
    # master连接的slave最小数量
    cluster-migration-barrier 1
    
  2. 启动六个redis容器实例

    docker run -d --name redis-79 -p 6379:6379 -v /docker-images/redis/conf/redis-6379.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf
    
    docker run -d --name redis-80 -p 6380:6380 -v /docker-images/redis/conf/redis-6380.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf
    
    docker run -d --name redis-81 -p 6381:6381 -v /docker-images/redis/conf/redis-6381.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf
    
    docker run -d --name redis-82 -p 6382:6382 -v /docker-images/redis/conf/redis-6382.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf
    
    docker run -d --name redis-83 -p 6383:6383 -v /docker-images/redis/conf/redis-6383.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf
    
    docker run -d --name redis-84 -p 6384:6384 -v /docker-images/redis/conf/redis-6384.conf:/etc/redis/redis.conf -v  /docker-images/redis/data:/data redis redis-server /etc/redis/redis.conf
    

在这里插入图片描述
3. 查看每个redis的实例记录其ip,这里使用docker网络模式的桥接模式,内部通过docker-0来转换网络的连接
在这里插入图片描述
4. 随机进入一个redis容器,使用ip地址配置6个redis集群,记住--cluster-replicas后面要加一个配置的数字,数字表示一个master连接几个slaves

docker exec -it redis-79 redis-cli --cluster create --cluster-replicas 1 172.17.0.2:6379 172.17.0.3:6380 172.17.0.4:6381 172.17.0.5:6382 172.17.0.6:6383 172.17.0.7:6384

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 直接以普通方式登录主机,存储数据时,会出现MOVED重定向操作,所以,应该以集群方式登录

    docker exec -it redis-79 redis-cli -c
    
    docker exec -it redis-80 redis-cli -c -p 6380
    
  2. 当其中一台主机下线后后,其对应的从机就会替代主机的位置成为主机,当下线的主机重启后会变成其之前从机的从机

十二、应用问题解决

12.1 缓存预热

问题

服务器启动后迅速宕机

原因

  1. 请求数量较高
  2. 主从之间数据吞吐量较大,数据同步操作频度较高

解决方案

  • 日常例行统计数据访问记录,统计访问频度较高的热点数据
  • 利用LRU数据删除策略,构建数据留存队列
  • 将统计结果中的数据分类,根据级别,redis优先加载级别较高的热点数据
  • 利用分布式多服务器同时进行数据读取,提速数据加载过程
  • 热点数据主从同时预热

小结

缓存预热就是系统启动前,提前将相关的缓存数据直接加载到缓存系统,避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

12.2 缓存雪崩

问题

  1. 在一个较短的时间内,缓存中较多的key集中过期
  2. 此周期内请求访问过期的数据,redis未命中,redis向数据库获取数据
  3. 数据库同时接收到大量的请求无法及时处理
  4. Redis大量请求被积压,开始出现超时现象
  5. 数据库流量激增,数据库崩溃

原因

  1. 短时间范围内,大量key集中过期,导致大量请求查询数据库

解决方案

  • 更多的页面静态化处理
  • 构建多级缓存架构
  • 检测Mysql严重耗时业务进行优化,对数据库的瓶颈排查:例如超时查询、耗时较高事务等
  • 灾难预警机制
  • 限流、降级
  • LRU与LFU切换
  • 数据有效期策略调整
  • 超热数据使用永久key
  • 加锁
12.3 缓存击穿

问题

  1. Redis中某个key过期,该key访问量巨大
  2. 多个数据请求从服务器直接压到Redis后,均未命中
  3. Redis在短时间内发起了大量对数据库中同一数据的访问

原因

  1. 单个key高热数据 ,key过期后,大量请求查询数据库,导致数据库崩溃

解决方案

  • 预先设定,加大此类信息key的过期时长
  • 后台刷新数据 启动定时任务,高峰期来临之前,刷新数据有效期,确保不丢失
  • 二级缓存 设置不同的失效时间,保障不会被同时淘汰就行
  • 加锁

小结

缓存击穿就是单个高热数据过期的瞬间,数据访问量较大,未命中redis后,发起了大量对同一数据的数据库访问,导致对数据库服 务器造成压力。应对策略应该在业务数据分析与预防方面进行,配合运行监控测试与即时调整策略,毕竟单个key的过期监控难度 较高,配合雪崩处理策略即可。

12.4 缓存穿透

问题

  1. Redis中大面积出现未命中
  2. 出现非正常URL访问,redis与数据库中都没有该数据

解决方案

  • 缓存null,设定短时限,例如30-60秒,最高5分钟
  • 白名单策略
    • 提前预热各种分类数据id对应的bitmaps,id作为bitmaps的offset,相当于设置了数据白名单。当加载正常数据时,放 行,加载异常数据时直接拦截(效率偏低)
    • 使用布隆过滤器
  • key加密
  • 进行实时监控
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_44292366/article/details/122671990

智能推荐

js-选项卡原理_选项卡js原理-程序员宅基地

文章浏览阅读90次。【代码】js-选项卡原理。_选项卡js原理

设计模式-原型模式(Prototype)-程序员宅基地

文章浏览阅读67次。原型模式是一种对象创建型模式,它采用复制原型对象的方法来创建对象的实例。它创建的实例,具有与原型一样的数据结构和值分为深度克隆和浅度克隆。浅度克隆:克隆对象的值类型(基本数据类型),克隆引用类型的地址;深度克隆:克隆对象的值类型,引用类型的对象也复制一份副本。UML图:具体代码:浅度复制:import java.util.List;/*..._prototype 设计模式

个性化政府云的探索-程序员宅基地

文章浏览阅读59次。入选国内首批云计算服务创新发展试点城市的北京、上海、深圳、杭州和无锡起到了很好的示范作用,不仅促进了当地产业的升级换代,而且为国内其他城市发展云计算产业提供了很好的借鉴。据了解,目前国内至少有20个城市确定将云计算作为重点发展的产业。这势必会形成新一轮的云计算基础设施建设的**。由于云计算基础设施建设具有投资规模大,运维成本高,投资回收周期长,地域辐射性强等诸多特点,各地在建...

STM32问题集之BOOT0和BOOT1的作用_stm32boot0和boot1作用-程序员宅基地

文章浏览阅读9.4k次,点赞2次,收藏20次。一、功能及目的 在每个STM32的芯片上都有两个管脚BOOT0和BOOT1,这两个管脚在芯片复位时的电平状态决定了芯片复位后从哪个区域开始执行程序。BOOT1=x BOOT0=0 // 从用户闪存启动,这是正常的工作模式。BOOT1=0 BOOT0=1 // 从系统存储器启动,这种模式启动的程序_stm32boot0和boot1作用

C语言函数递归调用-程序员宅基地

文章浏览阅读3.4k次,点赞2次,收藏22次。C语言函数递归调用_c语言函数递归调用

明日方舟抽卡模拟器wiki_明日方舟bilibili服-明日方舟bilibili服下载-程序员宅基地

文章浏览阅读410次。明日方舟bilibili服是一款天灾驾到战斗热血的创新二次元废土风塔防手游,精妙的二次元纸片人设计,为宅友们源源不断更新超多的纸片人老婆老公们,玩家将扮演废土正义一方“罗德岛”中的指挥官,与你身边的感染者们并肩作战。与同类塔防手游与众不同的几点,首先你可以在这抽卡轻松获得稀有,同时也可以在战斗体系和敌军走位机制看到不同。明日方舟bilibili服设定:1、起因不明并四处肆虐的天灾,席卷过的土地上出..._明日方舟抽卡模拟器

随便推点

Maven上传Jar到私服报错:ReasonPhrase: Repository version policy: SNAPSHOT does not allow version: xxx_repository version policy snapshot does not all-程序员宅基地

文章浏览阅读437次。Maven上传Jar到私服报错:ReasonPhrase: Repository version policy: SNAPSHOT does not allow version: xxx_repository version policy snapshot does not all

斐波那契数列、素数、质数和猴子吃桃问题_斐波那契日-程序员宅基地

文章浏览阅读1.2k次。斐波那契数列(Fibonacci Sequence)是由如下形式的一系列数字组成的:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …上述数字序列中反映出来的规律,就是下一个数字是该数字前面两个紧邻数字的和,具体如下所示:示例:比如上述斐波那契数列中的最后两个数,可以推导出34后面的数为21+34=55下面是一个更长一些的斐波那契数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584,_斐波那契日

PHP必会面试题_//该层循环用来控制每轮 冒出一个数 需要比较的次数-程序员宅基地

文章浏览阅读363次。PHP必会面试题1. 基础篇1. 用 PHP 打印出前一天的时间格式是 2017-12-28 22:21:21? //&gt;&gt;1.当前时间减去一天的时间,然后再格式化echo date('Y-m-d H:i:s',time()-3600*24);//&gt;&gt;2.使用strtotime,可以将任何字符串时间转换成时间戳,仅针对英文echo date('Y-m-d H:i:s',str..._//该层循环用来控制每轮 冒出一个数 需要比较的次数

windows用mingw(g++)编译opencv,opencv_contrib,并install安装_opencv mingw contrib-程序员宅基地

文章浏览阅读1.3k次,点赞26次,收藏26次。windows下用mingw编译opencv貌似不支持cuda,选cuda会报错,我无法解决,所以没选cuda,下面两种编译方式支持。打开cmake gui程序,在下面两个框中分别输入opencv的源文件和编译目录,build-mingw为你创建的目录,可自定义命名。1、如果已经安装Qt,则Qt自带mingw编译器,从Qt安装目录找到编译器所在目录即可。1、如果已经安装Qt,则Qt自带cmake,从Qt安装目录找到cmake所在目录即可。2、若未安装Qt,则安装Mingw即可,参考我的另外一篇文章。_opencv mingw contrib

5个高质量简历模板网站,免费、免费、免费_hoso模板官网-程序员宅基地

文章浏览阅读10w+次,点赞42次,收藏309次。今天给大家推荐5个好用且免费的简历模板网站,简洁美观,非常值得收藏!1、菜鸟图库https://www.sucai999.com/search/word/0_242_0.html?v=NTYxMjky网站主要以设计类素材为主,办公类素材也很多,简历模板大部个偏简约风,各种版式都有,而且经常会更新。最重要的是全部都能免费下载。2、个人简历网https://www.gerenjianli.com/moban/这是一个专门提供简历模板的网站,里面有超多模板个类,找起来非常方便,风格也很多样,无须注册就能免费下载,_hoso模板官网

通过 TikTok 联盟提高销售额的 6 个步骤_tiktok联盟-程序员宅基地

文章浏览阅读142次。你听说过吗?该计划可让您以推广您的产品并在成功销售时支付佣金。它提供了新的营销渠道,使您的产品呈现在更广泛的受众面前并提高品牌知名度。此外,TikTok Shop联盟可以是一种经济高效的产品或服务营销方式。您只需在有人购买时付费,因此不存在在无效广告上浪费金钱的风险。这些诱人的好处是否足以让您想要开始您的TikTok Shop联盟活动?如果是这样,本指南适合您。_tiktok联盟