华为云网站定制,优化外包顾问,公司邮箱号,搭建 wiki wordpress目录 一、概念1、特征2、关系型数据库和非关系型数据库的区别3、键的结构4、Redis的Java客户端5、缓存更新策略5.1、概念5.2、代码 6、缓存穿透6.1、含义6.2、解决办法6.3、缓存空值代码举例6.4、布隆过滤器代码举例 7、缓存击穿7.1、概念7.2、解决办法7.3、互斥锁代码举例7.4、… 目录 一、概念1、特征2、关系型数据库和非关系型数据库的区别3、键的结构4、Redis的Java客户端5、缓存更新策略5.1、概念5.2、代码 6、缓存穿透6.1、含义6.2、解决办法6.3、缓存空值代码举例6.4、布隆过滤器代码举例 7、缓存击穿7.1、概念7.2、解决办法7.3、互斥锁代码举例7.4、逻辑过期代码举例 8、缓存雪崩8.1、含义8.2、解决办法 9、Lua脚本9.1、Lua教程9.2、Lua介绍9.2.1、概念9.2.2、Redis为Lua语言内置的lua函数9.2.3、在Redis-cli中执行Lua脚本函数 9.3、代码9.3.1、前置准备application.yaml和依赖9.3.2、unlock.lua放在resources下的lua脚本等待被调用9.3.3、ILock接口9.3.4、SimpleRedisLock实现类 9.4、更复杂的lua脚本代码9.4.1、前置准备application.yaml和依赖9.4.2、seckill.lua放在resources下的lua脚本等待被调用9.4.3、IVoucherOrderService 接口9.4.4、VoucherOrderServiceImpl 实现类 10、Redission10.1、概念10.2、官方地址10.3、为什么不使用Redis的setnx命令来实现分布式锁10.3.1、缺点10.3.1、使用Redis的setnx命令来实现分布式锁的代码10.3.1.1、前置准备application.yaml和依赖10.3.1.2、unlock.lua放在resources下的lua脚本等待被调用10.3.1.3、ILock接口10.3.1.4、SimpleRedisLock实现类 10.4、Redisson分布式锁原理10.4.1、解决不可重入问题10.4.1.1、方案10.4.1.2、画图介绍10.4.1.3、代码分析 10.4.2、解决不可重试问题10.4.2.1、方案10.4.2.2、代码分析 10.4.3、解决超时释放问题10.4.3.1、方案10.4.3.2、代码解读 10.4.4、解决主从一致性问题10.4.4.1、方案10.4.4.2、代码 10.5、Redisson用途10.5.1、用途概述10.5.2、waitTime和leaseTime参数区别10.5.3、lock()和tryLock()方法区别 11、RDB和AOF11.1、RDB11.1.1、概念11.1.2、RDB触发机制配置文件手动输入命令操作11.1.2.1、Redis内部触发机制配置文件11.1.2.2、Redis命令行手动触发方式手动操作不建议使用 11.1.3、RDB备份原理11.1.4、总结 11.2、AOF11.2.1、概念11.2.2、如何修改Redis配置文件11.2.2.1、普通配置11.2.2.2、AOF文件瘦身配置 11.3、RDB和AOF对比 12、主从集群、哨兵集群、分片集群相关原理12.1、主从集群12.1.1、数据同步原理12.1.2、从节点第一次加入主节点进行全量同步流程12.1.3、从节点重启之后尝试再次加入主节点进行数据同步12.1.4、总结 12.2、哨兵集群12.2.1、哨兵作用12.2.2、监控服务状态12.2.3、选举新的master12.2.4、实现故障转移12.2.5、总结12.2.6、RedisTemplate的哨兵模式 12.3、分片集群12.3.1、分片集群结构12.3.2、散列插槽12.3.3、故障转移 13、小知识点13.1、RedisTemplate的默认JDK序列化方式、RedisTemplate的自定义Jackson序列化方式、StringRedisTemplate字符串序列化方式到底用哪个 二、操作命令1、Redis命令官网2、数据结构列表3、Redis通用命令使用介绍4、String使用介绍使用redis-cli命令行操作4.1、简单介绍4.2、set ……、set …… ex ……简写setex、set …… px ……、set …… nx简写setnx、set …… xx4.3、mset4.4、getset4.5、get4.6、mget4.7、del所有类型可用4.8、incr、incrby、decr、decrby仅限Integer类型String类型可以转换成Integer类型4.9、incrbyfloat仅限浮点类型String类型可以转换成浮点类型4.10、exists所有类型可用4.11、type所有类型可用4.12、expire、pexpire所有类型可用4.13、persist所有类型可用4.14、ttl、pttl所有类型可用 5、List使用介绍使用redis-cli命令行操作5.1、简单介绍5.2、lpushlleft、rpush5.3、lrangellist5.4、rpop、lpoplleft5.5、ltrim说明1、llist2、获取限定数量的最新数据5.6、llenllist5.7、brpop说明和lpush结合用作队列、blpop说明1、lleft2、不常用5.8、小拓展5.9、思考 6、Hash使用介绍使用redis-cli命令行操作6.1、简单介绍6.2、hset、hmset6.3、hget、hmget、hgetall6.4、hincrby6.5、hkeys6.6、hvals6.7、hsetnx 7、Set使用介绍使用redis-cli命令行操作7.1、简单介绍7.2、sadd7.3、spop7.4、smembers7.5、sismember7.6、scard7.7、sunionstore7.8、sinter7.9、sdiff7.10、sunion7.11、srandmember7.12、srem 8、Sorted Set使用介绍使用redis-cli命令行操作8.1、简单介绍8.2、zadd8.3、zrange、zrevrange8.4、zrangebyscore、zrevrangebyscore8.5、zremrangebyscore8.6、zrank8.7、zrem8.8、zscore8.9、zcard8.10、zcount8.11、zincrby8.12、zdiff8.13、zinter8.14、zunion 三、环境搭建1、windows1单机版1下载2安装3启动 2、linux1单机版1下载2安装gcc编译器3安装Redis4修改Redis配置文件5启动Redis6关闭Redis7拓展启动、停止方式18拓展停止方式29拓展Redis自带客户端使用方式 2哨兵版1下载2安装gcc编译器3安装Redis4搭建Redis主从副本集群5搭建Redis哨兵集群 3分片版1下载2安装gcc编译器3安装Redis4准备Redis节点5创建Redis集群 3、docker1单机版 4、k8s1单机版 5、Redis连接工具1RedisDesktopManager 四、代码五、文档 一、概念
1、特征
Redis诞生于2009年全称是Remote Dictionary Server远程词典服务器是一个基于内存的键值型NoSQL数据特征如下
键值key-value型value支持多种不同数据结构功能丰富单线程每个命令具备原子性低延迟速度快基于内存、IO多路复用、良好的编码。支持数据持久化支持主从集群、分片集群支持多语言客户端
2、关系型数据库和非关系型数据库的区别 3、键的结构
Redis的key允许由多个单词形成层级结构多个单词之间用:隔开格式如下
项目名:业务名:类型:id这个格式并非固定也可以根据自己的需求来删除或添加词条。 例如我们的项目名称叫 heima有user和product两种不同类型的数据我们可以这样定义key user相关的keyheima:user:1 product相关的keyheima:product:1 4、Redis的Java客户端
Spring data redis底层默认使用lettuce但是lettuce存在并发问题所以一般将底层替换成Jedis。
5、缓存更新策略
5.1、概念
说明 缓存更新策略将会影响代码编写过程中的逻辑。
首先用一张图说明缓存作用 可以很清晰的看到Redis缓存是做前锋的避免对关系型数据库造成影响
然后罗列一下几种缓存更新策略如下 通过比对实现难度和最终效果我们采用主动更新策略
然后罗列一下几种主动更新策略如下 通过比对实现难度和最终效果我们采用01
但是01的实现方式也有两种可以分为删除缓存策略还是更新缓存策略其中删除缓存等待用户操作的时候才会把最新数据放到缓存中而更新缓存是在用户往数据库新增或者更新数据的时候就把最新数据放到缓存中
既然提到这2种主动缓存更新策略那就得说一下缓存数据有效性以及查询数据时可能出现的缓存穿透、缓存击穿、缓存雪崩问题的解决方案
对于删除缓存策略可以在删除缓存后通过用户主动获取数据添加最新缓存数据来保证数据有效性当查询缓存时可以通过缓存空值方式来解决缓存穿透问题可以通过分布式锁方式解决缓存击穿问题可以通过设置不同缓存过期时间方式解决缓存雪崩问题对于更新缓存策略有两种实现方案 方案1完全符合在添加 / 更新数据库的同时更新Redis缓存可以保证缓存数据有效性由于缓存数据不会过期并且我们查询数据的时候不会查询数据库那其实不会产生缓存穿透、缓存击穿、缓存雪崩问题但是这会造成很多无效写操作并且还会占据很多缓存空间方案2不太符合在添加数据到数据库的时候添加Redis缓存并且为缓存数据添加逻辑过期时间但是在更新数据库数据的时候不更新Redis缓存而是等待过期逻辑过期时间到期才更新Redis缓存数据无法解决数据有效性问题更新数据库不更新Redis缓存不给Redis造成太大压力当查询缓存时如果从Redis查询不到值的时候直接返回null所以不会产生缓存穿透问题可以通过缓存逻辑过期时间方式来解决缓存击穿问题不会产生缓存雪崩问题方案3不太符合在添加数据到数据库的时候添加Redis缓存并且为缓存数据添加真实过期时间并且为布隆过滤器添加数据标识但是更新的时候不会在更新Redis缓存无法解决数据有效性问题当查询缓存时可以通过布隆过滤器解决缓存穿透问题可以通过分布式锁方式解决缓存击穿问题可以通过设置不同缓存过期时间方式解决缓存雪崩问题
基于实现难度、资源消耗、数据时效性考虑我们采用删除缓存策略下面进行详细介绍在读写操作时的作用
读操作 缓存命中则直接返回缓存未命中则查询数据库并写入缓存也不一定要设置超时时间看具体情况吧 写操作 先写数据库然后再删除缓存原因如果顺序反过来将有可能造成缓存中有旧数据要确保数据库与缓存操作的原子性 单体系统在同一个程序中操作数据库和Redis将缓存与数据库操作放在一个事务分布式系统在不同程序中操作数据库和Redis利用分布式事务方案
5.2、代码
概述 先写数据库然后再删除缓存确保数据库与缓存操作的原子性这就是代码。
解释 如果用户修改数据库数据的操作会影响Redis中缓存值的准确性那就需要在更新数据库值之后就删除Redis缓存值当用户需要获取结果时会自动更新Redis缓存值这样也能减轻缓存资源占用
代码
// 更新店铺信息
Override
Transactional
public Result update(Shop shop) {Long id shop.getId();if (id null) {return Result.fail(店铺id不能为空);}// 1.更新数据库updateById(shop);// 2.删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY id);return Result.ok();
}6、缓存穿透
6.1、含义
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在这样缓存永远不会生效这些请求都会打到数据库。
6.2、解决办法
缓存空对象最常使用简单好用但是一定要设置缓存超时时间并且要比普通值的缓存时间短布隆过滤不常使用原因是在查询缓存之前需要提前将缓存放入Redis并且在布隆过滤器添加值将字符串指定字节位设置为true这样未来在查询的时候布隆过滤器才能起到作用我感觉布隆过滤器的用途是提前将数据放入Redis并且设置过期时间然后在查询缓存的时候查不到说明已经过期了那就从数据库取值即可 举一个缓存空值的例子 6.3、缓存空值代码举例
application.yaml
spring:redis:# ————————————————————单机配置————————————————————host: 127.0.0.1port: 6379依赖
!--redis依赖--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
!--common-pool--
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency
!--hutool--
dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.7.17/version
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional
/dependency代码
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;import static com.hmdp.utils.RedisConstants.CACHE_NULL_TTL;Slf4j
Component
public class CacheClient {Autowiredprivate StringRedisTemplate stringRedisTemplate;public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 设置逻辑过期RedisData redisData new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}// 通过缓存空值方式来解决缓存穿透public R,ID R queryWithPassThrough(String keyPrefix, ID id, ClassR type, FunctionID, R dbFallback, Long time, TimeUnit unit){String key keyPrefix id;// 1.从redis查询商铺缓存String json stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(json)) {// 3.存在直接返回return JSONUtil.toBean(json, type);}// 判断命中的是否是空值不等于空那就是空字符串也就是我们缓存的空值所以直接返回即可if (json ! null) {// 返回一个错误信息return null;}// 4.不存在根据id查询数据库R r dbFallback.apply(id);// 5.不存在返回错误if (r null) {// 将空值写入redisstringRedisTemplate.opsForValue().set(key, , CACHE_NULL_TTL, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在写入redisthis.set(key, r, time, unit);return r;}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}
}6.4、布隆过滤器代码举例
application.yaml
spring:redis:# ————————————————————单机配置————————————————————host: 127.0.0.1port: 6379依赖
!--redis依赖--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
!--common-pool--
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency
!--增加布隆过滤器--
!-- https://mvnrepository.com/artifact/com.google.guava/guava --
dependencygroupIdcom.google.guava/groupIdartifactIdguava/artifactIdversion31.0.1-jre/version
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional
/dependencyRedis工具类代码
import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;Component
Slf4j
public class RedisUtil {Autowiredprivate StringRedisTemplate redisTemplate;/*** 根据给定的布隆过滤器添加值*/public T void addByBloomFilter(BloomFilterHelperT bloomFilterHelper, String key, T value) {Preconditions.checkArgument(bloomFilterHelper ! null, bloomFilterHelper不能为空);int[] offset bloomFilterHelper.murmurHashOffset(value);for (int i : offset) {log.info(key : key value : i);redisTemplate.opsForValue().setBit(key, i, true);}}/*** 根据给定的布隆过滤器判断值是否存在*/public T boolean includeByBloomFilter(BloomFilterHelperT bloomFilterHelper, String key, T value) {Preconditions.checkArgument(bloomFilterHelper ! null, bloomFilterHelper不能为空);int[] offset bloomFilterHelper.murmurHashOffset(value);for (int i : offset) {log.info(key : key value : i);if (!redisTemplate.opsForValue().getBit(key, i)) {return false;}}return true;}
}布隆过滤器工具类代码
import com.google.common.base.Preconditions;
import com.google.common.hash.Funnel;
import com.google.common.hash.Hashing;public class BloomFilterHelperT {private int numHashFunctions;private int bitSize;private FunnelT funnel;public BloomFilterHelper(FunnelT funnel, int expectedInsertions, double fpp) {Preconditions.checkArgument(funnel ! null, funnel不能为空);this.funnel funnel;// 计算bit数组长度bitSize optimalNumOfBits(expectedInsertions, fpp);// 计算hash方法执行次数numHashFunctions optimalNumOfHashFunctions(expectedInsertions, bitSize);}public int[] murmurHashOffset(T value) {int[] offset new int[numHashFunctions];long hash64 Hashing.murmur3_128().hashObject(value, funnel).asLong();int hash1 (int) hash64;int hash2 (int) (hash64 32);for (int i 1; i numHashFunctions; i) {int nextHash hash1 i * hash2;if (nextHash 0) {nextHash ~nextHash;}offset[i - 1] nextHash % bitSize;}return offset;}/*** 计算bit数组长度*/private int optimalNumOfBits(long n, double p) {if (p 0) {// 设定最小期望长度p Double.MIN_VALUE;}int sizeOfBitArray (int) (-n * Math.log(p) / (Math.log(2) * Math.log(2)));return sizeOfBitArray;}/*** 计算hash方法执行次数*/private int optimalNumOfHashFunctions(long n, long m) {int countOfHash Math.max(1, (int) Math.round((double) m / n * Math.log(2)));return countOfHash;}}7、缓存击穿
7.1、概念
缓存击穿问题也叫热点Key问题就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了无数的请求访问会在瞬间给数据库带来巨大的冲击。
7.2、解决办法
互斥锁 最常使用简单好用但是一定要设置锁超时时间逻辑过期很少使用也能考虑首先很难保证数据准确性毕竟可能返回旧数据另外在数据库中数据新增或者更新的时候都需要更新缓存数据这一点比较麻烦
时序图
优缺点 基于互斥锁方式解决缓存击穿问题举例 基于逻辑过期方式解决缓存击穿问题举例 7.3、互斥锁代码举例
application.yaml
spring:redis:# ————————————————————单机配置————————————————————host: 127.0.0.1port: 6379依赖
!--redis依赖--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
!--common-pool--
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency
!--hutool--
dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.7.17/version
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional
/dependency代码
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;
import java.util.function.Function;// 不仅解决缓存击穿问题也通过缓存空值方式来解决缓存穿透问题
Slf4j
Component
public class CacheClient {Autowiredprivate StringRedisTemplate stringRedisTemplate;// 通过缓存空值来解决缓存穿透问题public R, ID R queryWithMutex(String keyPrefix, ID id, ClassR type, FunctionID, R dbFallback, Long time, TimeUnit unit) {String key keyPrefix id;// 1.从redis查询商铺缓存String shopJson stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isNotBlank(shopJson)) {// 3.存在直接返回return JSONUtil.toBean(shopJson, type);}// 判断命中的是否是空值不等于空那就是空字符串也就是我们缓存的空值所以直接返回即可if (shopJson ! null) {// 返回一个错误信息return null;}// 4.实现缓存重建// 4.1.获取互斥锁String lockKey lock:shop: id;R r null;try {boolean isLock tryLock(lockKey);// 4.2.判断是否获取成功if (!isLock) {// 4.3.获取锁失败休眠并重试Thread.sleep(50);return queryWithMutex(keyPrefix, id, type, dbFallback, time, unit);}// 4.4.获取锁成功根据id查询数据库r dbFallback.apply(id);// 5.不存在返回错误if (r null) {// 将空值空串写入redis一定要加上时间stringRedisTemplate.opsForValue().set(key, , 2, TimeUnit.MINUTES);// 返回错误信息return null;}// 6.存在写入redisthis.set(key, r, time, unit);} catch (InterruptedException e) {throw new RuntimeException(e);}finally {// 7.释放锁unlock(lockKey);}// 8.返回return r;}// 添加互斥锁private boolean tryLock(String key) {// 做法类似于Redisson作用带等待时间的分布式锁// 大部分情况下10分钟完全可以完成一个业务功能如果还不能完成那肯定就是业务功能出现问题了当然也可以使用Redisson分布式锁毕竟它有看门口功能进行过期时间的无限续期Boolean flag stringRedisTemplate.opsForValue().setIfAbsent(key, 1, 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}// 解除互斥锁private void unlock(String key) {stringRedisTemplate.delete(key);}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}
}7.4、逻辑过期代码举例
application.yaml
spring:redis:# ————————————————————单机配置————————————————————host: 127.0.0.1port: 6379依赖
!--redis依赖--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
!--common-pool--
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency
!--hutool--
dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.7.17/version
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional
/dependency代码
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;// 不仅解决缓存击穿问题甚至都不会面临缓存穿透问题毕竟不存在直接就返回null了
Slf4j
Component
public class CacheClient {Autowiredprivate StringRedisTemplate stringRedisTemplate;private static final ExecutorService CACHE_REBUILD_EXECUTOR Executors.newFixedThreadPool(10);// 存储缓存数据的同时设置过期时间这件事情在创建数据肯定要做但是更新数据的时候一般不执行该方法public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit) {// 设置逻辑过期RedisData redisData new RedisData();redisData.setData(value);redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));// 写入RedisstringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));}// 查询缓存数据不会面临缓存穿透问题原因是没有就直接返回null了否则才进行缓存击穿处理public R, ID R queryWithLogicalExpire(String keyPrefix, ID id, ClassR type, FunctionID, R dbFallback, Long time, TimeUnit unit) {String key keyPrefix id;// 1.从redis查询商铺缓存String json stringRedisTemplate.opsForValue().get(key);// 2.判断是否存在if (StrUtil.isBlank(json)) {// 3.不存在直接返回说明数据根本不存在否则缓存中绝对有了return null;}// 4.命中需要先把json反序列化为对象RedisData redisData JSONUtil.toBean(json, RedisData.class);R r JSONUtil.toBean((JSONObject) redisData.getData(), type);LocalDateTime expireTime redisData.getExpireTime();// 5.判断是否过期if(expireTime.isAfter(LocalDateTime.now())) {// 5.1.未过期直接返回店铺信息return r;}// 5.2.已过期需要缓存重建// 6.缓存重建// 6.1.获取互斥锁String lockKey lock:shop: id;boolean isLock tryLock(lockKey);// 6.2.判断是否获取锁成功if (isLock){// 6.3.成功开启独立线程实现缓存重建CACHE_REBUILD_EXECUTOR.submit(() - {try {// 查询数据库R newR dbFallback.apply(id);// 重建缓存this.setWithLogicalExpire(key, newR, time, unit);} catch (Exception e) {throw new RuntimeException(e);}finally {// 释放锁unlock(lockKey);}});}// 6.4.返回过期的商铺信息return r;}private boolean tryLock(String key) {Boolean flag stringRedisTemplate.opsForValue().setIfAbsent(key, 1, 10, TimeUnit.SECONDS);return BooleanUtil.isTrue(flag);}private void unlock(String key) {stringRedisTemplate.delete(key);}public void set(String key, Object value, Long time, TimeUnit unit) {stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);}
}8、缓存雪崩
8.1、含义
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机导致大量请求到达数据库带来巨大压力。
8.2、解决办法
给不同的Key的TTL添加随机值解决同一时段大量的缓存key同时失效问题利用Redis集群提高服务的可用性尽量避免Redis服务宕机给缓存业务添加降级限流策略为Redis服务宕机问题兜底给业务添加多级缓存避免完全依赖Redis鸡蛋放到一个篮子里容易出问题
9、Lua脚本
9.1、Lua教程
访问链接https://www.runoob.com/lua/lua-tutorial.html
9.2、Lua介绍
9.2.1、概念
Redis提供了Lua脚本功能在一个脚本中编写多条Redis命令确保多条命令执行时的原子性。
9.2.2、Redis为Lua语言内置的lua函数
redis.call(命令名称, key, 其它参数, ...)举例
// 总结在redis中怎么写命令这里就怎么写命令只是之前命令参数之间用空格分隔现在用逗号分隔而已
// 1、获取键值
redis.call(get, stockKey))// 2、将键name设置为值jack
redis.call(set, name, jack)// 3、设置多个键值
redis.call(xadd, stream.orders, *, userId, userId, voucherId, voucherId, id, orderId)// 4、判断键值中是否已经存在该值
redis.call(sismember, orderKey, userId)// 5、键值自增
redis.call(incrby, stockKey, -1)9.2.3、在Redis-cli中执行Lua脚本函数
语法 举例
情况1假设我们要执行 redis.call(set, name, jack) 这个脚本函数语法如下 情况2如果脚本中的key、value不想写死也可以作为参数传递。key类型参数会放入KEYS数组其它参数会放入ARGV数组在脚本中可以从KEYS和ARGV数组获取这些参数如下 总结情况1和情况2都是我们经常使用的做法只是使用场景不同对于情况2来说我们在java代码中也是经常用的不过代码调用的时候只是传递了键集合和值可变参数而Redis依赖底层会将这种调用形式转换成上述的EVAL脚本形式比如将键参数个数加上等
9.3、代码
9.3.1、前置准备application.yaml和依赖
application.yaml
spring:redis:# ————————————————————单机配置————————————————————host: 127.0.0.1port: 6379依赖
!--redis依赖--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
!--common-pool--
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency
!--hutool--
dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.7.17/version
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional
/dependency9.3.2、unlock.lua放在resources下的lua脚本等待被调用
-- 比较线程标示与锁中的标示是否一致
if(redis.call(get, KEYS[1]) ARGV[1]) then-- 释放锁 del keyreturn redis.call(del, KEYS[1])
end
return 09.3.3、ILock接口
public interface ILock {/*** 尝试获取锁* param timeoutSec 锁持有的超时时间过期后自动释放* return true代表获取锁成功; false代表获取锁失败*/boolean tryLock(long timeoutSec);/*** 释放锁*/void unlock();
}9.3.4、SimpleRedisLock实现类
import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Collections;
import java.util.concurrent.TimeUnit;// 主要看静态代码块和unlock解锁方法这两块在用lua脚本
public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}private static final String KEY_PREFIX lock:;private static final String ID_PREFIX UUID.randomUUID().toString(true) -;private static final DefaultRedisScriptLong UNLOCK_SCRIPT;// 调用lua脚本前的准备工作static {UNLOCK_SCRIPT new DefaultRedisScript();// 读取lua脚本也就是上面resources下的lua脚本文件UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua));// 设置返回值类型UNLOCK_SCRIPT.setResultType(Long.class);}Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}Overridepublic void unlock() {// 调用lua脚本// UNLOCK_SCRIPTlua脚本// Collections.singletonList(KEY_PREFIX name)键集合// ID_PREFIX Thread.currentThread().getId()值可变参数stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX name),ID_PREFIX Thread.currentThread().getId());}
}9.4、更复杂的lua脚本代码
9.4.1、前置准备application.yaml和依赖
application.yaml
spring:redis:# ————————————————————单机配置————————————————————host: 127.0.0.1port: 6379依赖
!--redis依赖--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
!--common-pool--
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency
!--hutool--
dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.7.17/version
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional
/dependency9.4.2、seckill.lua放在resources下的lua脚本等待被调用
-- 1.参数列表
-- 1.1.优惠券id
local voucherId ARGV[1]
-- 1.2.用户id
local userId ARGV[2]
-- 1.3.订单id
local orderId ARGV[3]-- 2.数据key
-- 2.1.库存key
-- ..是字符串连接符
local stockKey seckill:stock: .. voucherId
-- 2.2.订单key
local orderKey seckill:order: .. voucherId-- 3.脚本业务
-- 3.1.判断库存是否充足 get stockKey
if(tonumber(redis.call(get, stockKey)) 0) then-- 3.2.库存不足返回1return 1
end
-- 3.2.判断用户是否下单 SISMEMBER orderKey userId
if(redis.call(sismember, orderKey, userId) 1) then-- 3.3.存在说明是重复下单返回2return 2
end
-- 3.4.扣库存 incrby stockKey -1
redis.call(incrby, stockKey, -1)
-- 3.5.下单保存用户sadd orderKey userId
redis.call(sadd, orderKey, userId)
-- 3.6.发送消息到队列中 XADD stream.orders * k1 v1 k2 v2 ...
redis.call(xadd, stream.orders, *, userId, userId, voucherId, voucherId, id, orderId)
return 09.4.3、IVoucherOrderService 接口
import com.hmdp.dto.Result;
import com.hmdp.entity.VoucherOrder;
import com.baomidou.mybatisplus.extension.service.IService;public interface IVoucherOrderService extends IServiceVoucherOrder {Result seckillVoucher(Long voucherId);
}9.4.4、VoucherOrderServiceImpl 实现类
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.Result;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.Collections;Slf4j
Service
public class VoucherOrderServiceImpl extends ServiceImplVoucherOrderMapper, VoucherOrder implements IVoucherOrderService {Resourceprivate ISeckillVoucherService seckillVoucherService;Resourceprivate RedisIdWorker redisIdWorker;Resourceprivate StringRedisTemplate stringRedisTemplate;private static final DefaultRedisScriptLong SECKILL_SCRIPT;static {SECKILL_SCRIPT new DefaultRedisScript();SECKILL_SCRIPT.setLocation(new ClassPathResource(seckill.lua));SECKILL_SCRIPT.setResultType(Long.class);}// 下订单接口很多功能全部都使用Redis的lua脚本文件完成可以保证原子性Overridepublic Result seckillVoucher(Long voucherId) {Long userId UserHolder.getUser().getId();long orderId redisIdWorker.nextId(order);// 1.执行lua脚本Long result stringRedisTemplate.execute(SECKILL_SCRIPT,Collections.emptyList(),voucherId.toString(), userId.toString(), String.valueOf(orderId));int r result.intValue();// 2.判断结果是否为0if (r ! 0) {// 2.1.不为0 代表没有购买资格return Result.fail(r 1 ? 库存不足 : 不能重复下单);}// 3.返回订单idreturn Result.ok(orderId);}
}10、Redission
10.1、概念
Redisson是一个在Redis的基础上实现的Java驻内存数据网格In-Memory Data Grid。它不仅提供了一系列的分布式的Java常用对象还提供了许多分布式服务其中就包含了各种分布式锁的实现。
10.2、官方地址
官网地址 https://redisson.orgGitHub地址 Redisson文档
10.3、为什么不使用Redis的setnx命令来实现分布式锁
10.3.1、缺点
当然可以使用Redis的setnx命令来实现分布式锁但是它是有缺点的缺点如下 10.3.1、使用Redis的setnx命令来实现分布式锁的代码
10.3.1.1、前置准备application.yaml和依赖
application.yaml
spring:redis:# ————————————————————单机配置————————————————————host: 127.0.0.1port: 6379依赖
!--redis依赖--
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId
/dependency
!--common-pool--
dependencygroupIdorg.apache.commons/groupIdartifactIdcommons-pool2/artifactId
/dependency
!--hutool--
dependencygroupIdcn.hutool/groupIdartifactIdhutool-all/artifactIdversion5.7.17/version
/dependency
dependencygroupIdorg.projectlombok/groupIdartifactIdlombok/artifactIdoptionaltrue/optional
/dependency10.3.1.2、unlock.lua放在resources下的lua脚本等待被调用
-- 比较线程标示与锁中的标示是否一致
if(redis.call(get, KEYS[1]) ARGV[1]) then-- 释放锁 del keyreturn redis.call(del, KEYS[1])
end
return 010.3.1.3、ILock接口
public interface ILock {/*** 尝试获取锁* param timeoutSec 锁持有的超时时间过期后自动释放* return true代表获取锁成功; false代表获取锁失败*/boolean tryLock(long timeoutSec);/*** 释放锁*/void unlock();
}10.3.1.4、SimpleRedisLock实现类
import cn.hutool.core.lang.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;import java.util.Collections;
import java.util.concurrent.TimeUnit;// 主要看静态代码块和unlock解锁方法这两块在用lua脚本
public class SimpleRedisLock implements ILock {private String name;private StringRedisTemplate stringRedisTemplate;public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name name;this.stringRedisTemplate stringRedisTemplate;}private static final String KEY_PREFIX lock:;private static final String ID_PREFIX UUID.randomUUID().toString(true) -;private static final DefaultRedisScriptLong UNLOCK_SCRIPT;// 调用lua脚本前的准备工作static {UNLOCK_SCRIPT new DefaultRedisScript();// 读取lua脚本也就是上面resources下的lua脚本文件UNLOCK_SCRIPT.setLocation(new ClassPathResource(unlock.lua));// 设置返回值类型UNLOCK_SCRIPT.setResultType(Long.class);}Overridepublic boolean tryLock(long timeoutSec) {// 获取线程标示String threadId ID_PREFIX Thread.currentThread().getId();// 获取锁Boolean success stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFIX name, threadId, timeoutSec, TimeUnit.SECONDS);return Boolean.TRUE.equals(success);}Overridepublic void unlock() {// 调用lua脚本// UNLOCK_SCRIPTlua脚本// Collections.singletonList(KEY_PREFIX name)键集合// ID_PREFIX Thread.currentThread().getId()值可变参数stringRedisTemplate.execute(UNLOCK_SCRIPT,Collections.singletonList(KEY_PREFIX name),ID_PREFIX Thread.currentThread().getId());}
}10.4、Redisson分布式锁原理
10.4.1、解决不可重入问题
10.4.1.1、方案
在redis中以hash结构来存储线程id和重入次数类似于synchronized的做法从而实现可重入锁
10.4.1.2、画图介绍 10.4.1.3、代码分析
首先找到tryLock方法然后进入RedissonLock实现类如下 进入tryLock方法之后可以看到leaseTime的值是-1这个将会影响是否启用看门狗机制后续在介绍解决锁超时释放问题的时候详细介绍如下 然后在进入tryLock方法内部可以看到先获取了threadId并且执行了tryAcquire方法如下 然后我们进入tryAcquire方法内部看到调用了tryAcquireAsync方法如下 然后我们进入tryAcquireAsync方法内部可以看到无论leaseTime是否等于-1都会调用tryLockInnerAsync方法如下 我们进入RedissonLock类的tryLockInnerAsync方法如下 上面这段lua脚本的作用就是判断是否加锁了如果没有加锁那就直接加锁如果已经加锁那就判断加锁的是当前线程吗如果不是当前线程那就直接返回锁过期时间。如果加锁的是当前线程那就让重入次数加1从而完成锁重入如果加锁/锁重入成功那就返回nil空值否则返回锁剩余有效时间
上述代码使用Fature方式所以在调用get()方法的时候会阻塞因此我们往上追溯到tryAcquire()方法处如果获取锁新锁 / 重入锁成功那ttl就返回null然后tryLock方法就返回true然后就能执行业务代码了真正实现了可重入锁代码如下 10.4.2、解决不可重试问题
10.4.2.1、方案
如果当前锁是其他线程的那就可以等待其他线程释放锁等待过程不是while循环重试而是使用Redis的pubsub事件通知方式进行重试降低CPU使用率其他线程释放锁之后重新尝试获取锁获取不到那就等待其他线程释放锁其他线程释放锁之后重新尝试获取锁以此循环……
10.4.2.2、代码分析
大家可以先看上面关于解决可重入锁问题的解释我们在上面提到如果没有获取锁那tryAcquire方法返回锁在Redis中的过期时间那么ttl就不是null如下 我们接着上面截图代码继续往下看可以看到等待锁释放的过程当然Redisson没有使用死循环而是使用Redis的pubsub监听方式如下 如果监听到其他线程释放锁了那么代码会继续尝试获取锁如下 假设又获取锁失败了那代码会继续往下执行如下 如果本次获取锁又失败了那就会执行while循环从而解决锁不可重试问题如下 10.4.3、解决超时释放问题
10.4.3.1、方案
我们自己实现的redis分布式锁中的过期时间是指定的由于业务执行时间不是完全确定的甚至执行10分钟也不是不可能但是我们不能把这个值设置太大了否则会影响锁的自动过期释放所以我们需要让锁可以自动续期目前采用看门狗机制通过锁自动续期来解决锁超时释放问题
10.4.3.2、代码解读
首先找到tryLock方法然后进入RedissonLock实现类如下 进入tryLock方法之后可以看到leaseTime的值是-1该值代表启用看门狗机制所以特别注意要想启用看门口机制就不用设置过期释放时间leaseTime如下 然后在进入tryLock方法内部可以看到先获取了threadId并且执行了tryAcquire方法如下 然后我们进入tryAcquire方法内部看到调用了tryAcquireAsync方法如下 然后我们进入tryAcquireAsync方法内部本次聊的是看门口机制所以leaseTime是-1代码如下 我们进入scheduleExpirationRenewal方法如下 我们直接看renewExpiration方法内部代码吧如下
private void renewExpiration() {ExpirationEntry ee EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ee null) {return;}// 创建任务Timeout task commandExecutor.getConnectionManager().newTimeout(new TimerTask() {Overridepublic void run(Timeout timeout) throws Exception {ExpirationEntry ent EXPIRATION_RENEWAL_MAP.get(getEntryName());if (ent null) {return;}Long threadId ent.getFirstThreadId();if (threadId null) {return;}// 2、为锁过期时间进行续期RFutureBoolean future renewExpirationAsync(threadId);future.onComplete((res, e) - {if (e ! null) {log.error(Cant update lock getName() expiration, e);return;}if (res) {// 3、重新调用自身用于下次为锁过期时间进行续期renewExpiration();}});}// 1、看门狗超时时间默认30s也就是10s之后会执行当前任务}, internalLockLeaseTime / 3, TimeUnit.MILLISECONDS);// 4、将任务放入ExpirationEntry对象中用于后续取消任务毕竟任务取消之后看门狗不能一直在吧ee.setTimeout(task);
}上面已经把看门狗机制进行了解释也就是过一段时间会有看门狗进行过期时间刷新但是当锁被取消之后看门狗也要消失的我们来看Redisson的unlock方法如下 进入unlock方法我们进入unlockAsync方法如下 我们进入unlockAsync方法然后在进入cancelExpirationRenewal方法如下 然后进入cancelExpirationRenewal方法如下 这样就完成了锁释放时对看门狗任务的移除工作
10.4.4、解决主从一致性问题
10.4.4.1、方案
假设现在把锁告诉了redis主节点这个时候主节点挂机了那redis从节点有没有redis锁数据这个时候就出现问题了所以最完美的解决办法就是让所有的redis节点都加上锁这样才能真正解决主从一致性问题
10.4.4.2、代码
配置类 使用方式 10.5、Redisson用途
10.5.1、用途概述
可用于以下几种用途
可重入锁Reentrant Lock公平锁Fair Lock联锁MultiLock红锁RedLock类似联锁读写锁ReadWriteLock只有读读不阻塞其他组合都阻塞信号量Semaphore可过期性信号量PermitExpirableSemaphore闭锁CountDownLatch
点击我查看详细代码 分布式锁和同步器
10.5.2、waitTime和leaseTime参数区别
waitTime等待获取锁的最大超时时间一般默认值是-1也就是不超时一直等待leaseTimeRedis锁的过期时间一般默认值是-1也就是不过期
10.5.3、lock()和tryLock()方法区别
lock()没有返回值在获取到分布式锁之前都是阻塞的会使用看门狗机制进行锁自动续期lock(long leaseTime, TimeUnit unit)没有返回值在获取到分布式锁之前都是阻塞的锁过期时间是leaseTime单位是unit不会使用看门狗机制进行锁自动续期tryLock()返回值是布尔类型代表是否获取锁代码会直接返回不会阻塞tryLock(long time, TimeUnit unit)返回值是布尔类型代表是否获取锁获取锁超时时间是time单位是unit锁过期时间leaseTime是-1代表会使用看门狗机制进行锁自动续期tryLock(long waitTime, long leaseTime, TimeUnit unit)返回值是布尔类型代表是否获取锁获取锁超时时间是time单位是unit如果leaseTime是-1那代表会使用看门狗机制进行锁自动续期否则不会使用看门狗机制进行锁自动续期并且锁过期时间是leaseTime单位是unit
11、RDB和AOF
11.1、RDB
11.1.1、概念
RDB全称Redis Database Backup fileRedis数据备份文件也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后从磁盘读取快照文件恢复数据。
注意 Redis停机时会执行一次RDB。
11.1.2、RDB触发机制配置文件手动输入命令操作
11.1.2.1、Redis内部触发机制配置文件 11.1.2.2、Redis命令行手动触发方式手动操作不建议使用 11.1.3、RDB备份原理
总结 写时复制技术详细做法如下 11.1.4、总结
1、RDB方式bgsave的基本流程
fork主进程得到一个子进程共享内存空间子进程读取内存数据并写入新的RDB文件用新RDB文件替换旧的RDB文件。
2、RDB会在什么时候执行
默认是服务停止时。
3、save 60 1000代表什么含义
代表60秒内至少执行1000次修改则触发RDB
4、 RDB的缺点
RDB执行间隔时间长两次RDB之间写入数据有丢失的风险fork子进程、压缩、写出RDB文件都比较耗时
11.2、AOF
11.2.1、概念
AOF全称为Append Only File追加文件。Redis处理的每一个写命令都会记录在AOF文件中可以看做是命令日志文件。
11.2.2、如何修改Redis配置文件
11.2.2.1、普通配置 11.2.2.2、AOF文件瘦身配置
AOF文件用来记录用户输入的命令所以AOF文件会比RDB文件大的多。而且AOF会记录对同一个key的多次写操作但只有最后一次写操作才有意义。通过手动执行bgrewriteaof命令可以让AOF文件执行重写功能用最少的命令达到相同效果。但是手动执行命令不太靠谱然后Redis也会在触发阈值时自动去重写AOF文件其中阈值可以在redis.conf中配置下面是默认值一般不用修改
# AOF文件比上次文件 增长超过多少百分比则触发重写
auto-aof-rewrite-percentage 100
# AOF文件体积最小多大以上才触发重写
auto-aof-rewrite-min-size 64mb11.3、RDB和AOF对比 注意 在搭建Redis集群的时候哨兵模式主从集群之间的数据同步使用RDB文件所以RDB方式一定不能少
12、主从集群、哨兵集群、分片集群相关原理
12.1、主从集群
12.1.1、数据同步原理
首先介绍几个概念
Replication Id简称replid这是数据集的标记。每一个master都有唯一的replidslave则会继承master节点的replid只有replid一致才说明是同一数据集repl_baklog存储尚未备份到RDB文件的命令offset偏移量offset随着记录在repl_baklog中的数据增多而逐渐增大slave完成同步时也会记录当前同步的offset如果slave的offset小于master的offset说明slave数据落后于master需要更新repl_baklog中的命令总结当slave向master做数据同步必须向master声明自己的replid和offset然后master才可以判断到底需要同步哪些数据 如果replid不一致那就需要进行全量同步 如果replid一致但是从节点的offset小于主节点的offset那就进行增量同步 如果replid一致但是由于repl_baklog大小有上限当repl_baklog命令环写满后会覆盖最早的数据如果slave断开时间过久导致尚未备份的数据被覆盖则无法基于log做增量同步只能再次全量同步
12.1.2、从节点第一次加入主节点进行全量同步流程
全量同步的流程slave节点请求增量同步master节点判断replid发现不一致拒绝增量同步master将完整内存数据生成RDB发送RDB到slaveslave清空本地数据加载master的RDBmaster将RDB期间的命令记录在repl_baklog并持续将log中的命令发送给slaveslave执行接收到的命令保持与master之间的同步 第一阶段用命令来解释
12.1.3、从节点重启之后尝试再次加入主节点进行数据同步
注意 可以适当提高repl_baklog的大小发现slave宕机时尽快实现故障恢复尽可能避免全量同步 12.1.4、总结 12.2、哨兵集群
12.2.1、哨兵作用
对于主从集群来说如果主节点宕机那么整个集群的写功能直接瘫痪而引入哨兵之后哨兵可以发现宕机的主从节点当主节点宕机之后哨兵可以将从节点提升为主节点如果从节点宕机之后哨兵不会把读请求发送到该从节点 12.2.2、监控服务状态 12.2.3、选举新的master
一旦发现master故障sentinel需要在salve中选择一个作为新的master选择依据是这样的
首先会判断slave节点与master节点断开时间长短如果超过指定值down-after-milliseconds * 10则会排除该slave节点然后判断slave节点的slave-priority值越小优先级越高如果是0则永不参与选举如果slave-prority一样则判断slave节点的offset值越大说明数据越新优先级越高最后是判断slave节点的运行id大小越小优先级越高。
12.2.4、实现故障转移 12.2.5、总结 12.2.6、RedisTemplate的哨兵模式
在Sentinel集群监管下的Redis主从集群其节点会因为自动故障转移而发生变化Redis的客户端必须感知这种变化及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。
1、在pom文件中引入redis的starter依赖 2、然后在配置文件application.yml中指定sentinel相关信息 3、配置主从读写分离 这里的ReadFrom是配置Redis的读取策略是一个枚举包括下面选择
MASTER从主节点读取MASTER_PREFERRED优先从master节点读取master不可用才读取replicaREPLICA从slavereplica节点读取REPLICA _PREFERRED优先从slavereplica节点读取所有的slave都不可用才读取master
12.3、分片集群
12.3.1、分片集群结构 12.3.2、散列插槽 总结
1、Redis如何判断某个key应该在哪个实例
将16384个插槽分配到不同的实例根据key的有效部分计算哈希值对16384取余余数作为插槽寻找插槽所在实例即可
2、如何将同一类数据固定的保存在同一个Redis实例
这一类数据使用相同的有效部分例如key都以{typeId}为前缀
12.3.3、故障转移 13、小知识点
13.1、RedisTemplate的默认JDK序列化方式、RedisTemplate的自定义Jackson序列化方式、StringRedisTemplate字符串序列化方式到底用哪个
注意用StringRedisTemplate字符串序列化方式不用其他的
先解释下用StringRedisTemplate字符串序列化方式的优缺点 优点不用添加任何多余依赖不用添加任何配置类序列化之后效果也很棒直接看到的就是字符串即使是对象也不会存储全类名统统都是字符串 缺点存储到Redis时的序列化和从Redis读出结果的反序列化都需要自己来操作
再解释下其他序列化方式被弃用的原因如下 不用RedisTemplate的默认JDK序列化方式原因默认情况下RedisTemplate使用JDK序列化方式但是在将数据写入Redis之前会把Object序列化为字节形式然后存储在Redis中的键值就变成了下图模样缺点是可读性差通过Redis客户端连接之后根本看不出来存储的是啥、内存占用较大本来就存储中国这两个字直接能给我序列化出一大坨东西 不用RedisTemplate的自定义Jackson序列化方式原因虽然数据被很好的序列化既不占用太多内存也方便阅读并且可以接收Object类型数据不用我们操太多心但是“成也萧何败也萧何”呀当接收Object类型数据之后在存储到Redis里面的时候不仅会存储数据信息还会存储对象信息比如我将一个User对象交给这种方式的Redis进行存储那Redis中存储的数据就像这种下面第1张图假设未来我把User的全路径位置改变了那在Redis反序列的时候就会报错这一点是我不能接受的总不能被一个好处影响了我代码不能改动吧。当然这种方式还需要添加Jackson依赖下面依赖也需要对RestTemplate进行特殊配置下面配置类 Redis中对User对象的序列化结果 Jackson依赖 dependencygroupIdcom.fasterxml.jackson.core/groupIdartifactIdjackson-databind/artifactId
/dependencyRedisTemplate配置类 import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;Configuration
public class RedisConfig {Beanpublic RedisTemplateString, Object redisTemplate(RedisConnectionFactory connectionFactory){// 创建RedisTemplate对象RedisTemplateString, Object template new RedisTemplate();// 设置连接工厂template.setConnectionFactory(connectionFactory);// 创建JSON序列化工具GenericJackson2JsonRedisSerializer jsonRedisSerializer new GenericJackson2JsonRedisSerializer();// 设置Key的序列化template.setKeySerializer(RedisSerializer.string());template.setHashKeySerializer(RedisSerializer.string());// 设置Value的序列化template.setValueSerializer(jsonRedisSerializer);template.setHashValueSerializer(jsonRedisSerializer);// 返回return template;}
}二、操作命令
1、Redis命令官网
https://redis.io/commands
2、数据结构列表
String安全的二进制字符串List有序可重复字符串集合内部数据结构是链表其中有序的含义是按照插入顺序进行排序Set无序不重复字符串集合Sorted set无序不重复字符串集合但是每一个字符串值都和一个浮点数相关联这个浮点数叫做score分数元素总是按照分数进行排序所以我们可以按照分数来获取字符串元素值HashMap类型其中键和值都是字符串Bit arrays可以使用特殊命令来处理字符串值如位数组您可以设置和清除单个位将所有设置为 1 的位计数找到第一个设置或未设置的位等等。常用来统计打卡情况。HyperLogLogs这是一种概率数据结构用于估计集合的基数。不要害怕它比看起来更简单…。常用来统计日活、月活等。Streams提供抽象日志数据类型的类似地图条目的仅附加集合。
3、Redis通用命令使用介绍
KEYS查看符合模板的所有keyDEL删除一个指定的keyEXISTS判断key是否存在EXPIRE给一个key设置有效期有效期到期时该key会被自动删除TTL查看一个KEY的剩余有效期
通过help [command] 可以查看一个命令的具体用法例如 4、String使用介绍使用redis-cli命令行操作
4.1、简单介绍
String类型也就是字符串类型是Redis中最简单的存储类型。 其value是字符串不过根据字符串的格式不同又可以分为3类
string普通字符串int整数类型可以做自增、自减操作float浮点类型可以做自增、自减操作
不管是哪种格式底层都是字节数组形式存储只不过是编码方式不同。字符串类型的最大空间不能超过512m.
常用命令汇总
SET添加或者修改已经存在的一个String类型的键值对GET根据key获取String类型的valueMSET批量添加多个String类型的键值对MGET根据多个key获取多个String类型的valueINCR让一个整型的key自增1INCRBY:让一个整型的key自增并指定步长例如incrby num 2 让num值自增2INCRBYFLOAT让一个浮点类型的数字自增并指定步长SETNX添加一个String类型的键值对前提是这个key不存在否则不执行SETEX添加一个String类型的键值对并且指定有效期
4.2、set ……、set …… ex ……简写setex、set …… px ……、set …… nx简写setnx、set …… xx
概念 设置键和值
返回值
设置成功将返回提示信息OK
模式 set key value [EX seconds] [PX milliseconds] [NX|XX]
脚本
// 过期时间为-1即不过期
set key1 value1
// 过期时间单位是10秒
set key1 value1 ex 10
// 过期单位是10000毫秒
set key1 value1 px 10000
// key不存在才能设置成功否则失败返回(nil)
set key1 value1 nx
// key存在才能设置成功否则失败返回(nil)
set key1 value1 xx说明
value替换如果不设置nx或者xx无论key存在与否都不会失败如果key不存在那么就添加value如果key存在那就就替换掉原来的value值
4.3、mset
概念 批量设置键和值可以设置一个或者多个
返回值
设置成功将返回提示信息OK
模式 mset key value [key value ...]
脚本
// 其中键a、b、c的值分别是1、2、3mset a 1 b 2 c 3说明
键值写法mset后面可以写多个键值对单个键值对之间用空格隔离多个键值对之间也用空格隔离
4.4、getset
概念 获取原有键的值并为键设置新值
返回值
如果原有键存在那么将返回原有键中的值如果原有键不存在那么将返回(nil)
模式 getset key value
脚本
// 设置key1的值为新值如果key1原来存在那么将返回key1的原来值如果key1不存在将返回(nil)
getset key1 value14.5、get
概念 获取单个键的值
返回值
如果键存在将返回键中的值如果键不存在将返回(nil)
模式 get key
脚本
get key14.6、mget
概念 批量获取键的值可以获取一个或者多个
返回值
如果所有键中的值都不存在将返回
1) (nil)如果部分键中的值不存在不存在的键值返回(nil)存在的键值将返回具体的值如下:
1) (nil)
2) (nil)
3) 1
4) (nil)
5) 2
6) (nil)模式 mget key [key ...]
脚本
// 返回值是一个值数组
mget a b c4.7、del所有类型可用
概念 删除键可以删除一个或者多个
返回值
返回删除成功的键的数量即使键不存在也不会报错
模式 del key [key ...]
脚本
del a b4.8、incr、incrby、decr、decrby仅限Integer类型String类型可以转换成Integer类型
概念 只能对值能强转成Integer类型的键操作含义如下incr增加1、incrby增加指定数量、decr减小1、decrby减小指定数量另外incrby也可以增加负数那相当代替了decr和decrby的作用
返回值
返回增加/减小之后的数值
模式
// 增加1
incr key
// 增加指定数量
incrby key increment
// 减小1
decr key
// 减小指定数量
decrby key decrement脚本
// 增加1
incr a
// 增加50
incrby a 50
// 减小1
decr a
// 减小50
decrby key 504.9、incrbyfloat仅限浮点类型String类型可以转换成浮点类型
概念 只能对值能强转成float类型的键操作含义如下incrbyfloat增加或者减少指定数量如果是正数就是增加否则负数就是减少
返回值
返回增加/减小之后的数值
模式
// 增加指定数量
INCRBYFLOAT key num// 减小指定数量
INCRBYFLOAT key num脚本
// 增加指定数量
INCRBYFLOAT mykey 0.1// 减小指定数量
INCRBYFLOAT mykey -54.10、exists所有类型可用
概念 判断键是否存在可以判断一个或者多个返回存在的键的个数
返回值
返回存在的键的数量即使键不存在也不会报错
模式 exists key [key ...]
脚本
exists a b4.11、type所有类型可用
概念 判断值类型
返回值
如果键存在将返回键值比如string、list等如果键不存在将返回none
模式 type key
脚本
// 键a中存储的是string类型
type a4.12、expire、pexpire所有类型可用
概念 设置键的过期时间其中expire设置的过期时间单位是秒而pexpire设置的过期时间单位是毫秒
返回值
如果键存在将返回1如果键不存在将返回0
模式
// 过期时间是秒
expire key seconds
// 过期时间是毫秒
pexpire key milliseconds脚本
// 设置键a的过期时间是10s
expire a 10
// 设置键a的过期时间是10000毫秒也就是10s
pexpire a 100004.13、persist所有类型可用
概念 设置键不过期
返回值
如果键存在将返回1如果键不存在将返回0
模式 persist key
脚本
// 设置键a不过期
persist a4.14、ttl、pttl所有类型可用
概念 ttl获取键的存活时间以秒为单位、pttl获取键的存活时间以毫秒为单位如果键不过期那么将返回-1
返回值
如果键存在将按照单位返回键的存活时间如果键不过期将返回-1如果键不存在将返回-2
模式 ttl key、pttl key
脚本
// 获取键a的存活时间以秒为单位
ttl a
// 获取键a的存活时间以毫秒为单位
pttl a5、List使用介绍使用redis-cli命令行操作
5.1、简单介绍
Redis中的List类型与Java中的LinkedList类似可以看做是一个双向链表结构。既可以支持正向检索和也可以支持反向检索。 特征也与LinkedList类似
有序元素可以重复插入和删除快查询速度一般
常用来存储一个有序数据例如朋友圈点赞列表评论列表等。
常用命令汇总
LPUSH key element … 向列表左侧插入一个或多个元素LPOP key移除并返回列表左侧的第一个元素没有则返回nil RPUSH key element … 向列表右侧插入一个或多个元素RPOP key移除并返回列表右侧的第一个元素 LRANGE key star end返回一段角标范围内的所有元素BLPOP和BRPOP与LPOP和RPOP类似只不过在没有元素时等待指定时间而不是直接返回nil
5.2、lpushlleft、rpush
概念 lpush代表从左侧往list集合中添加元素rpush代表从右侧往list集合中添加元素
返回值
返回添加成功的元素数量
模式 lpush key value [value ...]、rpush key value [value ...]
脚本
// 从左侧往mylist集合中添加元素
lpush mylist 1 2 3
// 从右侧往mylist集合中添加元素
rpush mylist hello world 4 55.3、lrangellist
概念 查看范围内的集合元素我们可以从左往右数第一个元素下标从0开始往后下标依次递增也可以从右往左数最后一个元素下标是-1往前下标依次递减那么从左往右看集合中倒数第二个元素的下标是-2并且这两种下标计算方式可以混用比如lrange 集合名称 0 -1代表查看集合中的所有元素
返回值
如果集合中存在元素将返回集合元素列表例如
1) 3
2) 2
3) 1
4) hello world
5) 4
6) 5如果集合中不存在元素将返回提示信息如下
(empty list or set)模式 lrange key start stop
脚本
// 查看列表中的所有元素
lrange mylist 0 -15.4、rpop、lpoplleft
概念 rpop代表从右侧弹出一个元素lpop代表从左侧弹出一个元素
返回值
如果集合中存在元素返回值是弹出的元素值元素弹出之后集合中的该元素将会被删除如果集合不存在元素那么返回(nil)
模式 rpop key、lpop key
脚本
// mylist是集合名称从集合右侧弹出元素
rpop mylist
// mylist是集合名称从集合左侧弹出元素
lpop mylist5.5、ltrim说明1、llist2、获取限定数量的最新数据
概念 删除范围之外的元素其中范围代表集合的范围我们可以从左往右数第一个元素下标从0开始往后下标依次递增也可以从右往左数最后一个元素下标是-1往前下标依次递减那么从左往右看集合中倒数第二个元素的下标是-2并且这两种下标计算方式可以混用确实和lrange的用法相似
返回值
无论什么情况都是返回OK
模式 ltrim key start stop
脚本
// 只保留集合中下标从0到2包括边界的元素其他的集合元素都将被删除
ltrim mylist 0 2作用
比如我们只想在集合中保留最新的10个元素我们可以这样执行命令
// 添加元素到集合头部
lpush mylist some element
// 只保留集合中的前10个最新的元素
ltrim mylist 0 95.6、llenllist
概念 获取集合长度
返回值
如果集合存在将返回集合中的元素数量如果集合不存在将返回0
模式 llen key
脚本
// 获取集合名称为mylist的元素个数
llen mylist5.7、brpop说明和lpush结合用作队列、blpop说明1、lleft2、不常用
概念 列表上的阻塞操作可以阻塞式弹出一个元素可以把集合当队列来用我们使用lpush从左边插入元素和brpop弹出右边的元素组合就可以把集合当做队列来使用满足队列的先进先出原则并且我们可以设置阻塞时间单位是秒如果阻塞时间是0那就可以无限期阻塞直到队列中可以弹出元素才会结束如果阻塞时间是正整数在时间结束之前没有弹出元素将返回(nil)在时间内可以弹出元素就返回弹出的元素如果集合中原有就有元素那是可以立即弹出元素的另外多个集合中只要有一个集合弹出元素阻塞就会停止并且会返回集合名称和弹出的元素值 返回值
如果可以弹出元素那就返回弹出的元素如下
1) mylist1
2) 1
(9.44s)如果不能弹出元素在时间结束之间将会一直阻塞在时间结束的时候将会返回(nil)如下
(nil)
(1.02s)模式 brpop key [key ...] timeout、blpop key [key ...] timeout
脚本
// 不限期阻塞从mylist或者mylist1的右侧弹出元素
brpop mylist mylist1 0// 最多阻塞1s从mylist左侧弹出元素
blpop mylist 15.8、小拓展
对于聚合类型比如List、Streams、Sets、Sorted Sets 和 Hashes有以下几点需要说明
当我们将元素添加到聚合数据类型时如果目标键不存在则在添加元素之前创建一个空的聚合数据类型。当我们从聚合数据类型中删除元素时如果最终值为空则键会自动销毁。 Stream 数据类型是此规则的唯一例外。调用只读命令例如 LLEN返回列表的长度或使用del命令操作不存在的聚合类型总是返回0就好像命令找到了空聚合类型。
综上所述 如果操作过后聚合类型为空那么将删除该聚合类型当向聚合类型中添加元素的时候如果聚合类型不存在那么将创建一个空的聚合类型
5.9、思考 6、Hash使用介绍使用redis-cli命令行操作
6.1、简单介绍
Hash类型也叫散列其value是一个无序字典类似于Java中的HashMap结构。 String结构是将对象序列化为JSON字符串后存储当需要修改对象某个字段时很不方便 Hash结构可以将对象中的每个字段独立存储可以针对单个字段做CRUD 常用命令汇总
HSET key field value添加或者修改hash类型key的field的值HGET key field获取一个hash类型key的field的值HMSET批量添加多个hash类型key的field的值HMGET批量获取多个hash类型key的field的值HGETALL获取一个hash类型的key中的所有的field和valueHKEYS获取一个hash类型的key中的所有的field HVALS获取一个hash类型的key中的所有的valueHINCRBY让一个hash类型key的字段值自增并指定步长HSETNX添加一个hash类型的key的field值前提是这个field不存在否则不执行
6.2、hset、hmset
概念 设置hash中的键和值
返回值 hset如果hash中的key之前已经存在设置成功将返回0如果hash中的key之前不存在设置成功将返回1、hmset设置成功返回OK
模式 hset key field value、hmset key field value [field value ...]
脚本
// 键是user:1设置键中的username的值是xiaoming
hset user:1 username xiaoming
// 键是user:1设置键中的username是xiaomingage是10
hmset user:1 username xiaoming age 106.3、hget、hmget、hgetall
概念 获取hash中键对应的值
返回值
hget如果键存在则返回值如果键不存在则返回(nil)
hmget如果键存在则返回值如果键不存在则返回(nil)
1) xiaoming
2) (nil)hgetall返回hash中的所有键和值其中前面的是键后面的是值
1) username
2) xiaoming
3) age
4) 10
5) address
6) china
7) high
8) 2m模式 hget key field、hmget key field [field ...]、hgetall key
脚本
// 键是user:1获取username的值
hget user:1 username
// 键是user:1获取username和age的值
hmget user:1 username age
// 键是user:1获取所有键和值
hgetall user:16.4、hincrby
概念 增加hash中键对应的值的数量
返回值 设置之后的值
模式 hincrby key field increment
脚本
// 年龄增加2岁
hincrby user:1 age 26.5、hkeys
概念 获取hash中值键集合
返回值 hash中值键集合
1) field1
2) field2模式 hkeys key
脚本
// 获取用户hash的键集合
hkeys user6.6、hvals
概念 获取hash中所有值集合
返回值 hash中所有值集合
1) Hello
2) World模式 hvals key
脚本
// 获取用户hash的值集合
hvals user6.7、hsetnx
概念 hash中键不存在才能设置成功否则设置失败
返回值 成功返回1失败返回0
模式 hsetnx key field value
脚本
// 如果用户hash中不存在键为name的情况那么将设置name的值为“明快de玄米61”然后返回1如果用户hash中已经存在键为name的情况那就就不设置返回0
hsetnx user name 明快de玄米617、Set使用介绍使用redis-cli命令行操作
7.1、简单介绍
Redis的Set结构与Java中的HashSet类似可以看做是一个value为null的HashMap。因为也是一个hash表因此具备与HashSet类似的特征
无序元素不可重复查找快支持交集、并集、差集等功能
常用命令汇总
SADD key member … 向set中添加一个或多个元素SREM key member … : 移除set中的指定元素SCARD key 返回set中元素的个数SISMEMBER key member判断一个元素是否存在于set中SMEMBERS获取set中的所有元素SINTER key1 key2 … 求key1与key2的交集SDIFF key1 key2 … 求key1与key2的差集SUNION key1 key2 …求key1和key2的并集
7.2、sadd
概念 添加元素到Set集合中
返回值 添加成功的元素个数
模式 sadd key member [member ...]
脚本
// 添加1和2到myset集合中
sadd myset 1 27.3、spop
概念 从set结合中随机弹出特定数量的元素弹出元素将被删除
返回值
被弹出的元素列表如下
1) 1模式 spop key [count]
脚本
// 从myset集合中随机弹出1个元素
spop myset 17.4、smembers
概念 输出set集合中的所有元素
返回值 集合中的所有元素列表如下
1) 1
2) 2模式 smembers key
脚本
// 输出myset集合中的所有元素
smembers myset7.5、sismember
概念 判断元素是否在set集合中
返回值 如果元素在集合中就返回1如果元素不在集合中就返回0
模式 sismember key member
脚本
// 判断元素1是否在myset集合中
sismember myset 17.6、scard
概念 查看set集合中的元素数量
返回值 set集合中的元素总数量
模式 scard key
脚本
// 获取set集合中的元素总数量
scard myset7.7、sunionstore
概念 将一个或者一个以上集合的并集赋值给一个新的集合
返回值 新集合中的元素总量
模式 sunionstore destination key [key ...]
脚本
// 创建myset1
sadd myset1 1 2
// 创建myset2
sadd myset2 2 3
// 将myset1和myset2中的元素合并的myset3集合中
sunionstore myset3 myset1 myset27.8、sinter
概念 获取一个或者多个集合的交集 返回值 集合交集列表如果是单个集合将返回该集合中的全部元素例如sinter myset1 myset2的结果如下
1) 2模式 sinter key [key ...]
脚本
// 获取myset1和myset2的交集
sinter myset1 myset27.9、sdiff
概念 获取一个或者多个集合对“第一个集合”的差集 返回值 集合差集列表如果是单个集合将返回空例如sdiff myset1 myset2的结果如下
1) 1模式 sdiff key [key ...]
脚本
// 获取myset1和myset2的差集
sinter myset1 myset27.10、sunion
概念 获取一个或者多个集合的并集
返回值 集合并集列表如果是单个集合将返回集合总的全部元素例如sunion myset1 myset2的结果如下
1) 1
2) 2
3) 3模式 sunion key [key ...]
脚本
// 获取myset1和myset2的并集
sunion myset1 myset27.11、srandmember
概念 随机弹出一个或者多个集合中的元素并且不会删除集合中的这些元素根据count的大小不同将返回不同的结果具体规则如下
返回值 如果不写count值就返回一个元素例如1如果count大于1将返回元素列表如下
1) 3
2) 2
3) 1
4) 3
5) 3
6) 2模式 srandmember key [count]
脚本
// 随机返回一个元素
srandmember myset3
// 随机返回6个元素不会重复由于myset3中只有三个元素所以会返回全部3个元素
srandmember myset3 6
// 随机返回6个元素允许重复将会返回6个元素
srandmember myset3 -67.12、srem
概念 删除Set集合中的元素
返回值 删除成功的元素个数
模式 srem key member [member ...]
脚本
// 删除myset集合中的1和2
srem myset 1 28、Sorted Set使用介绍使用redis-cli命令行操作
8.1、简单介绍
Redis的SortedSet是一个可排序的set集合与Java中的TreeSet有些类似但底层数据结构却差别很大。SortedSet中的每一个元素都带有一个score属性可以基于score属性对元素排序底层的实现是一个跳表SkipList加 hash表。 SortedSet具备下列特性
可排序元素不重复查询速度快
因为SortedSet的可排序特性经常被用来实现排行榜这样的功能。
常用命令汇总
ZADD key score member添加一个或多个元素到sorted set 如果已经存在则更新其score值ZREM key member删除sorted set中的一个指定元素ZSCORE key member : 获取sorted set中的指定元素的score值ZRANK key member获取sorted set 中的指定元素的排名ZCARD key获取sorted set中的元素个数ZCOUNT key min max统计score值在给定范围内的所有元素的个数ZINCRBY key increment member让sorted set中的指定元素自增步长为指定的increment值ZRANGE key min max按照score排序后获取指定排名范围内的元素ZRANGEBYSCORE key min max按照score排序后获取指定score范围内的元素ZDIFF、ZINTER、ZUNION求差集、交集、并集
8.2、zadd
概念 插入元素到排序集合中
返回值 插入成功的元素数量
模式 zadd key [NX|XX] [CH] [INCR] score member [score member ...]
脚本
// 其中0、1、2分别是a、b、c的分数
zadd hackers 0 a 1 b 2 c说明
对于NX和XX的含义可以看https://redis.io/commands/zadd/
8.3、zrange、zrevrange
概念 zrange按照分数从大到小排序而zrevrange按照分数从大到小排序并且根据开始下标和结束下标来控制范围其中的开始下标和结束下标解释如下我们可以从左往右数第一个元素下标从0开始往后下标依次递增也可以从右往左数最后一个元素下标是-1往前下标依次递减那么从左往右看集合中倒数第二个元素的下标是-2并且这两种下标计算方式可以混用
返回值 如果不加WITHSCORES将按照相关排序返回范围内的集合中的元素如下
1) a
2) b
3) c如果加WITHSCORES将按照相关排序返回范围内的集合中的元素并且返回对应的分数如下
1) a
2) 0
3) b
4) 1
5) c
6) 2模式 zrange key start stop [WITHSCORES]、zrevrange key start stop [WITHSCORES]
脚本
// 按照分数从小到大排序目前获取的是集合中的全部元素
zrange hackers 0 -1
// 按照分数从小到大排序并且添加withscores之后还会输出分数目前获取的是集合中的全部元素
zrange hackers 0 -1 withscores8.4、zrangebyscore、zrevrangebyscore
概念 zrangebyscore按照分数范围筛选小分数边界在前面大分数边界在后面范围包括边界处的分数然后按照分数从大到小排序而zrevrangebyscore按照分数范围筛选大分数边界在前面小分数边界在后面范围包括边界处的分数然后按照分数从大到小排序它们是根据分数来限制范围而不是下标
返回值 如果不加WITHSCORES将按照相关分数排序后返回分数范围内的集合中的元素如下
1) a
2) b
3) c如果加WITHSCORES将按照相关分数排序后返回分数范围内的集合中的元素并且返回对应的分数如下
1) a
2) 0
3) b
4) 1
5) c
6) 2模式 zrangebyscore key min max [WITHSCORES] [LIMIT offset count]、zrevrangebyscore key max min [WITHSCORES] [LIMIT offset count]
脚本
// 按照分数范围进行筛选其中小分数边界在前面大分数边界在后面范围包括边界处的分数然后按照分数进行从小到大排序并且输出分数
zrangebyscore hackers 1 2 withscores
// 按照分数范围进行筛选其中大分数边界在前面小分数边界在后面范围包括边界处的分数然后按照分数进行从大到小排序并且输出分数
zrevrangebyscore hackers 2 1 withscores8.5、zremrangebyscore
概念 根据分数范围进行筛选之后在删除
返回值 删除成功的元素个数
模式 zremrangebyscore key min max
脚本
// 根据分数删除分数范围内的集合元素前面是小分数后面是大分数包括分数边界
zremrangebyscore hackers 0 18.6、zrank
概念 获取元素在集合中的排行按照分数从大到小排序排行从0开始
返回值 元素在集合中的排行按照分数从大到小排序排行从0开始
模式 zrank key member
脚本
// 获取元素c元素在集合hackers中的排行按照分数从大到小排序排行从0开始
zrank hackers c8.7、zrem
概念 删除排序集合中的元素
返回值 删除成功的元素数量
模式 zrem key member [member ...]
脚本
// 其中one、two都是元素不是分数
ZREM myzset one two8.8、zscore
概念 查询排序集合中的元素得分
返回值 返回集合中的元素得分如果该元素不存在集合中那么返回null
模式 zscore key member
脚本
// 其中three都是元素
zscore myzset three8.9、zcard
概念 查询排序集合中的元素个数
返回值 返回集合中的元素个数如果键不存在那么返回0
模式 zcard key
脚本
// 其中myzset是键名
zscore myzset8.10、zcount
概念 在查询排序集合中返回在分数范围包含边界值内的元素个数
返回值 返回分数范围内的元素个数
模式 zcount key min max
脚本
// 其中myzset是键名用来查找集合中分数大于等于1并且小于等于2的元素数量
zcount myzset 1 28.11、zincrby
概念 让集合中的指定元素自增步长为指定的increment值
返回值 集合中元素修改之后的得分
模式 zincrby key increment member
脚本
// 其中myzset是键名让集合中为one的元素分数值增加2分
zincrby myzset 2 one8.12、zdiff
概念 求差集
返回值 关于第一个集合的差集元素集合
模式 zdiff numkeys key [key ...]
脚本
// 其中myzset1和myzset2都是键名
zincrby myzset1 myzset28.13、zinter
概念 求交集
返回值 多个集合的交集
模式 zinter numkeys key [key ...]
脚本
// 其中myzset1和myzset2都是键名
zinter myzset1 myzset28.14、zunion
概念 求并集
返回值 多个集合的并集
模式 zunion numkeys key [key ...]
脚本
// 其中myzset1和myzset2都是键名
zunion myzset1 myzset2三、环境搭建
1、windows
1单机版
1下载
目前官方不提供windows版本的Redis不过我们可以点击Windows版Redis来选择合适版本然后点击Downloads按钮如下注意别点下图中的zip下载那是源码zip 然后在里面选择zip版本进行下载如下 这里我给大家提供Redis-x64-3.2.100.zip安装包如下
链接https://pan.baidu.com/s/1GzbARFP1cq4LjKUeXlYa_Q
提取码zuw8
2安装
解压即安装成功
3启动
双击redis-service.exe即可启动 2、linux
1单机版
1下载
首先打开Redis官网下载页面然后选择合适的版本进行下载 下面为大家提供redis-6.2.13.tar.gz版本的安装包如下
链接https://pan.baidu.com/s/1k910snMEfzbJP07uQqhOwA?pwd1xuo
提取码1xuo
2安装gcc编译器
yum -y install gcc说明 Redis是使用C语言编写的我们使用源文件安装方式需要编译C语言源文件所以需要安装gcc编译器
3安装Redis
首先将redis-6.2.13.tar.gz上传到虚拟机的/usr/local/src目录下如下 在xshell中执行cd /usr/local/src命令进入压缩包所在目录
在上述src目录下执行tar -zxvf redis*.tar.gz命令解压安装包到当前目录写*目的是都可以执行这些命令
在上述src目录下执行cd redis*进入解压Redis目录
在上述解压Redis目录下执行make make install命令编译Redis源文件如下 等待执行成功即可
4修改Redis配置文件
在redis解压目录中找到redis.conf文件然后根据需要进行修改下面我说两种情况大家根据情况选择
情况1推荐安全 设置Redis密码 将bind 127.0.0.1 -::1改为bind 0.0.0.0 作用不限制连接Redis的ip支持Redis连接工具连接Redis 将daemonize no改为daemonize yes 作用开启Redis守护模式允许redis后台运行所以在启动redis的时候就不用加 了 将dir ./改为dir /usr/local/src/redis-6.2.13请将/usr/local/src/redis-6.2.13替换成你自己的redis安装地址哦 作用首先dir的值是存储aof数据文件、rdb数据文件、日志文件的根目录而dir ./代表redis-server命令启动的目录就是dir目录由于redis-server的位置是/usr/local/bin/redis-server所以/usr/local/bin就是根目录这样放置数据文件不好所以把dir的值修改成特定位置 将logfile 改为logfile redis.log 作用设置Redis日志文件名称用来存储日志信息然后该日志文件将会存储在redis.conf配置文件中dir目录下 将# requirepass foobared改为requirepass admin123456 作用设置Redis连接密码其中admin123456就是我的连接密码大家可以替换成自己的 将appendonly no改成appendonly yes 作用Redis数据持久化
情况2不推荐不安全 不设置Redis密码 将bind 127.0.0.1 -::1改为bind 0.0.0.0 作用不限制连接Redis的ip支持Redis连接工具连接Redis 将protected-mode yes改为protected-mode no 作用取消Redis保护模式可以不设置密码登录记得一定把配置中对requirepass的密码设置那一行注释不然密码还是会起效的 将daemonize no改为daemonize yes 作用开启Redis守护模式允许redis后台运行所以在启动redis的时候就不用加 了 将dir ./改为dir /usr/local/src/redis-6.2.13请将/usr/local/src/redis-6.2.13替换成你自己的redis安装地址哦 作用首先dir的值是存储aof数据文件、rdb数据文件、日志文件的根目录而dir ./代表redis-server命令启动的目录就是dir目录由于redis-server的位置是/usr/local/bin/redis-server所以/usr/local/bin就是Redis数据文件存储的dir根目录这样放置数据文件不好所以把dir的值修改成特定位置 将logfile 改为logfile redis.log 作用设置Redis日志文件名称用来存储日志信息然后该日志文件将会存储在redis.conf配置文件中dir目录下 将appendonly no改成appendonly yes 作用Redis数据持久化
5启动Redis
# 通过cd命令进入redis解压目录下然后执行以下命令主要是用到修改之后的redis.conf配置文件
redis-server redis.conf6关闭Redis
情况1 Redis有密码
我们在虚拟机中执行redis-cli -a 密码命令比如redis-cli -a admin123456就可以登录redis客户端控制台然后输入shutdown回车即可关闭Redis之后CtrlC退出Redis客户端就可以了
情况2 Redis没有密码
我们在虚拟机中执行redis-cli命令就可以登录redis客户端控制台然后输入shutdown回车即可关闭Redis之后CtrlC退出Redis客户端就可以了
7拓展启动、停止方式1
执行命令打开并新建配置文件文件名称是应用名称可以用作快捷调用后缀是固定的service
vim /lib/systemd/system/redis.service然后粘贴以下内容到上述文件中并保存注意修改你的启动命令也就是ExecStart后面的值
[Unit]
Descriptionredis-server
Afternetwork.target[Service]
Typeforking
ExecStart/usr/local/bin/redis-server /usr/local/src/redis-6.2.13/redis.conf
PrivateTmptrue[Install]
WantedBymulti-user.targetDescription描述信息没啥作用根据文件作用做修改ExecStart启动命令对于Reids来说编译Redis源码之后就会在/usr/local/bin/目录下生成对应启动脚本文件所以启动脚本文件用这个也是ok的
执行以下命令重启系统服务不会重启虚拟机
systemctl daemon-reload可以通过如下命令对Redis执行操作
# 说明虽然文件名称是redis.service但是执行命令时可以省略.service
# 运行Redis
systemctl start redis# 停止Redis
systemctl stop redis# 重启Redis
systemctl restart redis# 查看Redis运行状态
systemctl status redis# Redis开机自启
systemctl enable redis# 关闭Redis开机自启
systemctl disable redis8拓展停止方式2
执行
ps -ef | grep redis找到redis进程然后执行
kill -9 Redis进程号来关闭Redis
9拓展Redis自带客户端使用方式
# 连接方式
# 1、有密码
redis-cli -a 密码# 2、需要指定redis端口并且有密码
redis-cli -p 端口 -a 密码# 3、连接Redis主从分片集群
# 说明如果不添加-c那就不是以集群方式连接到客户的按照这种情况来说由于Redis主节点只存储一部分数据当执行添加键值操作的时候而有些数据的hash值不在当前节点可接受范围内那么就需要存储到其他节点对于这种无法存储的情况会报错的。如果添加-c那就是以集群方式连接到客户的这样即使出现了上面的情况集群会把添加键值的请求转发到其他节点。
redis-cli -c -p 端口 -a 密码# 客户端命令
# 1、查看集群副本信息
info replication2哨兵版
1下载
2安装gcc编译器
3安装Redis
请参考上述安装Linux单机版本中的前3步由于我在自己笔记本上搭建集群所以就使用一台虚拟机但是Redis端口不同呢利用这种方式来模拟Redis哨兵集群搭建的过程
4搭建Redis主从副本集群
根据上述安装步骤Redis依然安装在/usr/local/src/redis-6.2.13目录下面
首先我们创建三个目录来存储Redis主从集群配置文件redis.conf
mkdir -p /usr/local/src/redisMasterSlaveCluster/6001
mkdir -p /usr/local/src/redisMasterSlaveCluster/6002
mkdir -p /usr/local/src/redisMasterSlaveCluster/6003创建完成如下图 然后把/usr/local/src/redis-6.2.13目录下的原始redis.conf文件分别复制到上述6001、6002、6003目录下我们来规划主节点和从节点其中端口6001为主节点而6002和6003为从节点然后按照下面要求进行配置文件的修改 说明下面没有做特殊说明的那就是主、从节点都需要做修改 将port 6379改为port 节点端口号 作用修改redis端口号由于6001、6002、6003目录下都存在redis.conf那就把目录名称当做端口号来执行修改操作 将bind 127.0.0.1 -::1改为bind 0.0.0.0 作用不限制连接Redis的ip支持Redis连接工具连接Redis 将daemonize no改为daemonize yes 作用开启Redis守护模式允许redis后台运行所以在启动redis的时候就不用加 了 将dir ./改为dir 6001、6002、6003目录全路径比如6001目录下的redis.conf中就需要将dir ./改为dir /usr/local/src/redisMasterSlaveCluster/6001 作用首先dir的值是存储aof数据文件、rdb数据文件、日志文件的根目录而dir ./代表redis-server命令启动的目录就是dir目录由于redis-server的位置是/usr/local/bin/redis-server所以/usr/local/bin就是根目录这样放置数据文件不好所以把dir的值修改成特定位置 将logfile 改为logfile redis.log 作用设置Redis日志文件名称用来存储日志信息然后该日志文件将会存储在redis.conf配置文件中dir目录下 将# requirepass foobared改为requirepass admin123456 作用设置Redis连接密码其中admin123456就是我的连接密码大家可以替换成自己的 将# masterauth master-password替换成masterauth admin123456 作用设置连接主节点密码我会把Redis节点密码都设置成admin123456所以在哨兵模式下即使出现故障的时候无论哪个节点成为了主节点其他从节点都是可以连接上主节点的 将appendonly no改成appendonly yes 作用Redis数据持久化 只要求从节点执行目前是6002和6003节点将# replicaof masterip masterport替换成replicaof 192.168.56.10 6001 作用从节点要连接上主节点那就需要知道主节点的ip和port信息这就是用来指定主节点的连接信息的其中192.168.56.10是主节点所在虚拟机ip而6001主节点Redis端口
现在就把主从集群准备好了我们可以通过执行命令redis-server redis.conf全路径来依次启动主节点端口为6001的节点和从节点端口分别为6002和6003的节点例如具体命令如下
# 1、启动主节点
redis-server /usr/local/src/redisMasterSlaveCluster/6001/redis.conf# 2、启动6002从节点
redis-server /usr/local/src/redisMasterSlaveCluster/6002/redis.conf# 3、启动6003从节点
redis-server /usr/local/src/redisMasterSlaveCluster/6003/redis.conf现在我们就把一主二从的Redis主从副本集群搭建成功了。
对于搭建成功的主从集群我们介绍一下它的特点
主节点支持读写但是从节点只支持读。大家可以通过redis-cli -p 端口 -a 密码连接Redis本地客户端去尝试也可以通过Redis远程客户端去尝试无法自动完成故障转移。如果主节点挂掉从节点只会无限尝试连接主节点这个过程可以从6002和6003从节点的日志中看到
正是由于Redis主从副本集群无法完成故障转移所以我们需要Redis哨兵集群来帮助完成自动故障转移
5搭建Redis哨兵集群
首先我们创建三个目录来存储Redis哨兵集群配置文件sentinel.conf
mkdir -p /usr/local/src/redisSentinel/7001
mkdir -p /usr/local/src/redisSentinel/7002
mkdir -p /usr/local/src/redisSentinel/7003创建完成如下图 然后把/usr/local/src/redis-6.2.13目录下的原始sentinel.conf文件分别复制到上述7001、7002、7003目录下之后按照下面要求进行配置文件的修改 将port 26379改为port 哨兵节点端口号 作用修改redis哨兵端口号由于7001、7002、7003目录下都存在sentinel.conf那就把目录名称当做端口号来执行修改操作 将daemonize no改为daemonize yes 作用开启守护模式允许redis哨兵后台运行所以在启动redis哨兵的时候就不用加 了 将dir ./改为dir 7001、7002、7003目录全路径比如7001目录下的redis.conf中就需要将dir ./改为dir /usr/local/src/redisSentinel/7001 作用首先dir的值是存储日志文件的根目录而dir ./代表redis-sentinel命令启动的目录就是dir目录由于redis-sentinel的位置是/usr/local/bin/redis-sentinel所以/usr/local/bin就是根目录这样放置数据文件不好所以把dir的值修改成特定位置 将logfile 改为logfile sentinel.log 作用设置Redis哨兵日志文件名称用来存储日志信息然后该日志文件将会存储在sentinel.conf配置文件中dir目录下 将# sentinel monitor master-name ip redis-port quorum替换成sentinel monitor mymaster 192.168.56.10 6001 2 作用Redis哨兵存在的意义就是监听我们上面搭建的Redis主从副本集群由于所有信息都可以通过主节点获取到所以在哨兵配置文件sentinel.conf中需要配置redis主节点连接信息其中mymaster是主节点名称这是我们自己定义的192.168.56.10是Redis主节点ip6001是Redis主节点端口2表示当有两个哨兵节点认为节点无法连接那就需要让节点下线 将# sentinel auth-pass master-name password改成sentinel auth-pass mymaster admin123456 作用由于Redis主节点设置了连接密码所以我们需要指定Redis连接密码才能让Redis哨兵连接上Redis主节点其中admin123456就是我的Redis主节点密码 将sentinel down-after-milliseconds mymaster 30000改成sentinel down-after-milliseconds mymaster 5000 作用设置Redis哨兵节点和Redis节点连接的最大中断时间现在的设置是最多5s连接不上某节点那就说明该节点下线了
现在就把Redis哨兵集群准备好了我们可以通过执行命令redis-sentinel sentinel.conf全路径来依次启动端口为7001、7002、7003的三个Redis哨兵节点例如具体命令如下
# 1、启动7001哨兵节点
redis-sentinel /usr/local/src/redisSentinel/7001/sentinel.conf# 2、启动7002哨兵节点
redis-sentinel /usr/local/src/redisSentinel/7002/sentinel.conf# 3、启动7003哨兵节点
redis-sentinel /usr/local/src/redisSentinel/7003/sentinel.conf现在我们就把一个主节点端口6001、两个从节点端口6002、6003、三个哨兵节点端口7001、7002、7003的Redis哨兵集群搭建成功了。
上面Redis主从副本集群无法实现自动故障转移但是Redis哨兵集群可以假设此时我们把Redis6001主节点关闭那么Redis哨兵集群会发现该变化然后将剩余的Redis从节点提升为主节点并且完成其他从节点对主节点的监听功能。如果此时Redis6001节点完成了手动故障恢复它此时会以从节点的身份加入Redis集群这就实现了自动故障转移。
我们聊一下在Redis主从副本集群的基础上引入Redis哨兵集群的目的引入哨兵就是为了解决Redis主节点宕机导致集群整体无法使用的问题所以即使我们在代码中连接的是Redis哨兵集群但是集群本质没有改变依然是Redis主节点支持读写但是从节点只支持读的状态
3分片版
上面介绍了Redis哨兵集群但是哨兵集群有一个很大的问题是写能力受限毕竟只有一个节点支持写操作其他节点都只支持读操作。另外一个问题是Redis哨兵集群所有节点都存储全量数据但是再好的机器也顶不住纵向扩容呀所以很有可能出现空间不够用的情况。而我们Redis分片集群就能解决这些问题并且依然支持数据副本备份、自动故障转移的能力
这种分片类似于Elasticsearch分片方式我们下面来搭建一个三主三从的Redis分片集群拓扑图如下 1下载
2安装gcc编译器
3安装Redis
请参考上述安装Linux单机版本中的前3步由于我在自己笔记本上搭建集群所以就使用一台虚拟机但是Redis端口不同呢利用这种方式来模拟Redis哨兵集群搭建的过程
4准备Redis节点
根据上述安装步骤Redis依然安装在/usr/local/src/redis-6.2.13目录下面
首先我们创建6个目录来存储Redis分片集群配置文件redis.conf
mkdir -p /usr/local/src/redisShardedCluster/8001
mkdir -p /usr/local/src/redisShardedCluster/8002
mkdir -p /usr/local/src/redisShardedCluster/8003
mkdir -p /usr/local/src/redisShardedCluster/8004
mkdir -p /usr/local/src/redisShardedCluster/8005
mkdir -p /usr/local/src/redisShardedCluster/8006创建完成如下图 然后把/usr/local/src/redis-6.2.13目录下的原始redis.conf文件分别复制到上述8001、8002、8003、8004、8005、8006目录下然后按照下面要求进行配置文件的修改 将port 6379改为port 节点端口号 作用修改redis端口号由于8001、8002、8003、8004、8005、8006目录下都存在redis.conf那就把目录名称当做端口号来执行修改操作 将bind 127.0.0.1 -::1改为bind 0.0.0.0 作用不限制连接Redis的ip支持Redis连接工具连接Redis 将daemonize no改为daemonize yes 作用开启Redis守护模式允许redis后台运行所以在启动redis的时候就不用加 了 将dir ./改为dir 8001、8002、8003、8004、8005、8006目录全路径比如8001目录下的redis.conf中就需要将dir ./改为dir /usr/local/src/redisShardedCluster/8001 作用首先dir的值是存储aof数据文件、rdb数据文件、日志文件的根目录而dir ./代表redis-server命令启动的目录就是dir目录由于redis-server的位置是/usr/local/bin/redis-server所以/usr/local/bin就是根目录这样放置数据文件不好所以把dir的值修改成特定位置 将logfile 改为logfile redis.log 作用设置Redis日志文件名称用来存储日志信息然后该日志文件将会存储在redis.conf配置文件中dir目录下 将# requirepass foobared改为requirepass admin123456 作用设置Redis连接密码其中admin123456就是我的连接密码大家可以替换成自己的 将# masterauth master-password替换成masterauth admin123456 作用设置连接主节点密码我会把Redis节点密码都设置成admin123456所以在分片集群模式下即使出现故障的时候无论哪个节点成为了主节点其他从节点都是可以连接上主节点的 将appendonly no改成appendonly yes 作用Redis数据持久化 将pidfile /var/run/redis_6379.pid改成pidfile /var/run/redis_cluster.pid 作用这个文件的作用我还没有探究清晰但是都用这个名称不太好还是换一个吧 将# cluster-enabled yes改成cluster-enabled yes 作用开启Redis分片集群模式 将# cluster-config-file nodes-6379.conf改成cluster-config-file node-cluster.conf 作用Redis分片集群使用该conf文件存储集群自身所需的数据我们只需要设置名称即可集群会自动创建不需要我们管它 将# cluster-node-timeout 15000改成cluster-node-timeout 5000 作用如果集群主节点之间超过5s没有通信那么从节点将会变成主节点通过主从切换来完成自动故障转移
现在就把主从集群准备好了我们可以通过执行命令redis-server redis.conf全路径来依次启动8001、8002、8003、8004、8005、8006节点具体命令如下
redis-server /usr/local/src/redisShardedCluster/8001/redis.conf
redis-server /usr/local/src/redisShardedCluster/8002/redis.conf
redis-server /usr/local/src/redisShardedCluster/8003/redis.conf
redis-server /usr/local/src/redisShardedCluster/8004/redis.conf
redis-server /usr/local/src/redisShardedCluster/8005/redis.conf
redis-server /usr/local/src/redisShardedCluster/8006/redis.conf5创建Redis集群
虽然上面已经把Redis节点准备好了但是节点之间是无法互通的所以我们需要创建集群执行如下命令即可
redis-cli --cluster create --cluster-replicas 1 192.168.56.10:8001 192.168.56.10:8002 192.168.56.10:8003 192.168.56.10:8004 192.168.56.10:8005 192.168.56.10:8006 -a admin123456解释一下
--cluster-replicas 1指定集群中每个master的副本个数为1此时节点总数 ÷ (replicas 1) 得到的就是master的数量。因此节点列表中的前n个就是master其它节点都是slave节点随机分配到不同master6个ip:port这是上述创建的6个Redis节点的ip和port-a admin123456由于Redis节点有密码所以需要填写密码
上述命令执行完成之后还需要我们输入yes并回车我们照做就是然后集群就创建完成了
上面我们提到Redis分片集群解决了节点数据写入能力不够的问题现在搭建的是一个三主三从节点所以有三个主节点可以用了数据写入所以节点数据写入能力得到了大大提升。
上面我们还提到Redis分片集群可以解决纵向扩容的问题现在三个主节点中存储的部分数据而不是全量数据相当于把全部数据分成了三个地方存储并且从节点可以为对应主节点提供副本能力这样横向扩容和数据安全都保障了
上面我们还提到Redis分片集群依然支持自动故障转移的功能由于每一个主节点目前都有一个从节点所以即使主节点挂了那么从节点会被提升为主节点从而实现自动故障转移
3、docker
1单机版
// 拉取镜像
docker pull redis:6.0.8// 创建redis.conf所属的目录
mkdir /docker/reids/conf// 将以下链接中的redis.conf文件复制到conf目录下
链接https://pan.baidu.com/s/1OLPyYh0NcwmXlHIKhFr3dQ?pwdoq8y
更改参数说明1、开启密码验证搜索“requirepass”可见2、注释“bind 127.0.0.1”允许外部连接3、设置“daemonize no”避免和docker run中-d参数冲突导致容器启动失败4、设置“appendonly yes ”开启容器持久化// 创建容器
docker run -d -p 6379:6379 --privilegedtrue --nameredis6.0.8 -v /docker/redis/conf/redis.conf:/etc/redis/redis.conf -v /docker/redis/data:/data redis:6.0.8 redis-server /etc/redis/redis.conf
说明redis-server /etc/redis/redis.conf代表在redis-server启动的时候使用/etc/redis/redis.conf// 进入容器
docker exec -it redis6.0.8 /bin/bash4、k8s
1单机版
apiVersion: v1
kind: ConfigMap
metadata:name: redis.confnamespace: redis
data:redis.conf: |-protected-mode noport 6379tcp-backlog 511timeout 0tcp-keepalive 300daemonize nosupervised nopidfile /var/run/redis_6379.pidloglevel noticelogfile databases 16always-show-logo yessave 900 1save 300 10save 60 10000stop-writes-on-bgsave-error yesrdbcompression yesrdbchecksum yesdbfilename dump.rdbdir ./replica-serve-stale-data yesreplica-read-only yesrepl-diskless-sync norepl-diskless-sync-delay 5repl-disable-tcp-nodelay noreplica-priority 100requirepass admin123456lazyfree-lazy-eviction nolazyfree-lazy-expire nolazyfree-lazy-server-del noreplica-lazy-flush noappendonly yesappendfilename appendonly.aofappendfsync everysecno-appendfsync-on-rewrite noauto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mbaof-load-truncated yesaof-use-rdb-preamble yeslua-time-limit 5000slowlog-log-slower-than 10000slowlog-max-len 128latency-monitor-threshold 0notify-keyspace-events Exhash-max-ziplist-entries 512hash-max-ziplist-value 64list-max-ziplist-size -2list-compress-depth 0set-max-intset-entries 512zset-max-ziplist-entries 128zset-max-ziplist-value 64hll-sparse-max-bytes 3000stream-node-max-bytes 4096stream-node-max-entries 100activerehashing yesclient-output-buffer-limit normal 0 0 0client-output-buffer-limit replica 256mb 64mb 60client-output-buffer-limit pubsub 32mb 8mb 60hz 10dynamic-hz yesaof-rewrite-incremental-fsync yesrdb-save-incremental-fsync yes---apiVersion: apps/v1
kind: StatefulSet
metadata:labels:app: redisname: redisnamespace: redis
spec:replicas: 1selector:matchLabels:app: redisserviceName: redistemplate:metadata:labels:app: redisspec:containers:- command:- redis-server- /etc/redis/redis.confimage: redis:6.2.6imagePullPolicy: IfNotPresentname: redisports:- containerPort: 6379name: clientprotocol: TCP- containerPort: 16379name: gossipprotocol: TCPvolumeMounts:- mountPath: /dataname: redis-data- mountPath: /etc/redis/name: redis-confvolumes:- configMap:name: redis.confname: redis-confvolumeClaimTemplates:- metadata:name: redis-dataspec:accessModes:- ReadWriteManystorageClassName: managed-nfs-storageresources:requests:storage: 100Mi---apiVersion: v1
kind: Service
metadata:labels:app: redisname: redisnamespace: redis
spec:ports:- name: clientport: 6379protocol: TCPtargetPort: 6379- name: gossipport: 16379protocol: TCPtargetPort: 16379selector:app: redistype: NodePort5、Redis连接工具
1RedisDesktopManager
点击RedisDesktopManager-Windows然后找到合适版本点击Downloads按钮如下 然后下载resp-XXX.zip即可如下 我给大家提供resp-2022.5.0.0.exe安装包如下
链接https://pan.baidu.com/s/1MpAVdolmV4z5WPWuDxtn0A?pwd3eui
提取码3eui
四、代码
链接https://pan.baidu.com/s/1manMY7Rm3-ueELqdQR9S9g?pwdqjyd
提取码qjyd
五、文档
链接https://pan.baidu.com/s/1Bt9x4QdmNJJATEPyZDUQ4Q?pwd82se
提取码82se