14_redis

1.安装

使用centos7

将下载好的redis放入opt目录

在这里插入图片描述

yum install gcc
gcc --version
cd opt/
tar -zxvf redis-7.0.0.tar.gz
cd redis-7.0.0/
make
make install
cd /usr/local/bin/

在这里插入图片描述

2启动

前台启动:直接运行redis-server

后台启动:

#拷贝redis.conf到其他目录
cp redis.conf /home

#修改拷贝文件redis.conf中deamonize no,将其改为yes
vim redis.conf

#启动
redis-server /home/redis.conf

#连接
redis-cli

在这里插入图片描述

简单库操作

select n(0-15) #切换数据库
dbsize n #查看数据库当前key的数量
flushdb #清空当前库
flushall #通杀全部库

3.Redis数据类型

Redis常用五大数据类型:

  • String 字符串
  • List 列表
  • Set 集合
  • Hash 哈希
  • Zset 有序集合

键(Key)

set key value #设置键值
keys * #查看所有键
exists key #查看某个key是否存在 返回值1(存在)和0(不存在)
type key #查看key类型
del key #删除指定key
unlink key #根据value选择非阻塞删除,仅将keys从keyspace元数据中删除,真正的删除会在后续异步操作
expire key #设置key过期时间
ttl key #查看key过期时间,-1表示永不过期,-2表示已经过期

字符串(String)

​ String类型是二进制安全的,意味着Redis的String可以包含任意数据。比如jpg图片或者序列化的对象。

一个Redis中字符串value最多可以是512M

底层结构为简单动态字符串SDS(Simple Dynamic String)。

在这里插入图片描述

常用命令:

set key value #设置键值,设置相同key会覆盖
keys * #查看所有键
get key #获取对应键的值
append key value #在对应键的值后添加,返回值是追加后的字符串长度
strlen key #获取值长度
setnx key value #只有key不存在时,才设置
incr key #将key中存储的数字+1,只能对数值操作,如果为空则增值为1
decr key #同上,减一。如果为空则值为-1
incrby/decrby key 步长 #同上,自定义增减多少
mset k1 v1 k2 v2 #设置多个键值对
mget k1 k2 k3 #获取多个键的值
msetnx k1 v1 k2 v2 #同时设置多个键值对,且只有给定key不存在
#注:当其中一个失败,都失败
getrange key 起始位置 结束位置 #获得值的范围,前包后包
setrange key 起始位置 value #用value覆盖从起始位置开始的值
setex key 过期时间 value #设置键值同时设置过期时间,单位秒
getset key value #设置新值同时获得旧值

列表(List)

单键多值,是简单的字符串列表

底层是双向链表

在这里插入图片描述

常用命令

lpush/rpush key value1 value2 ... #从左边/右边插入一个或多个值
lpop/rpop key #从左边/右边吐出一个值,当值空时,键删除
rpoplpush key1 key2 #从key1列表右边吐出一个值插入到key2左边
lrange key start stop #按照索引下标获得元素,从左到右 0 -1表示所有
lindex key n #按照索引下标获得元素,从左到右
llen key #获取列表长度
linsert key before value new value #在value后面插入newvalue,左插,所以是在后面。
lrem key n value #从左边删除n个value
lset key index value #将列表key下标为index的值替换为value

集合(Set)

类似列表,但是可以自动排重。

底层是hash表

常用命令:

sadd key value1 value2 ... #在key集合中添加一个或多个元素
smembers key #取出该集合的所有元素
sismember key value #判断集合key是否含有该value值,有1,没0
scard key #返回集合元素个数
srem key value1 value2 ... #删除集合中某元素
spop key #随机从该集合中吐出一个值
srandmember key n #随即从该集合中取出n个值,不会删除
smove source destination value #把集合中一个值移动到另一个集合中
sinter key1 key2 #返回两个集合的交集元素
sunion key1 key2 #返回两个集合的并集元素
sdiff key1 key2 #返回两个集合的差集元素(key1中的,不包含key2中的)

哈希(Hash)

是一个键值对集合,是一个string类型的field和value的映射表。

适合存储对象。

在这里插入图片描述

常用命令:

hset key field value #给key集合中的field赋值value
hget key field #从key集合中field取出value
hmset key1 f1 v1 f2 v2 #批量设置hash
hexists k1 f1 #查看哈希表key中是否存在f1
hkeys k1 #列出k1集合中所有field
hvals k1 #列出k1集合中所有的value
hincrby k1 f1 n #为哈希表k1中的域f1添加n(可以是负数)
hsetnx k1 f1 v1 #将哈希表k1中的域f1的值设为v1,且仅当域f1不存在

有序集合(Zset)

与set非常相似,是一个没有重复元素的字符串集合,不同是每个成员都关联了一个评分,根据评分从低到高方式进行排序集合中的成员。成员唯一,评分不唯一

常用命令:

zadd key score value score2 value2 #讲一个或多个member元素及其socre值添加到有序key中
zrange key start stop [withscores] #返回有序集key中,下标在start和stop之间的元素,带withscores,可以让评分一起返回 
zrangebyscore key min max [withscores] [limit offset] #返回有序集key中,所有score值介于min和max之间(左包右包)的成员,按从小到大排序
zrevrangebyscore key max min [withscores] [limit offset] #同上,从大到小排序
zincrby key increament value #为元素的score加上增量
zrem key value #删除该集合下,指定值的元素
zcount key min max #统计该集合,分数区间内的元素个数
zrank key value #返回该值在集合中的排名,从0开始

在这里插入图片描述

Redis6新数据类型

Bitmaps

实际上还是字符串,不过可以对字符串的位进行操作。

提供了一套单独命令

setbit key offset value #设置bitmaps中某个偏移量的值,偏移量从0开始

在这里插入图片描述

getbit key offset #获取bitmaps中某个偏移量的值,偏移量从0开始
bitcount key [start end] #统计字符串从start字节到end字节比特值为1的数量,注意是字节
bittop and/or/not/xor destkey [key...] #复合操作,可以做多个bitmaps的并交非,异或操作并将结果保存到destkey中
BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL] #BITFIELD 命令可以将一个 Redis 字符串看作是一个由二进制位组成的数组, 并对这个数组中任意偏移进行访问 
BITFIELD mykey INCRBY i5 100 1 GET u4 0 #,对位于 5 位有符号整数的偏移量 100 执行自增操作,并获取位于偏移量 0 上的 4 位长无符号整数
1) (integer) 1
2) (integer) 0

在这里插入图片描述

在这里插入图片描述

HyperLogLog

用来做基数统计的算法

在这里插入图片描述

pfadd key element [elemenet...] #添加指定元素到hyperLogLog中,如果基数发生变化返回1,否则返回0
pfcount key [key...] #计算HLL的近似基数,可以计算多个HLL。
pfmerge destkey sourcekey [sourcekey...] #将一个或多个HLL合并后的结果存储在另一个HLL中

Geospatial

地图信息的缩写,就是二维坐标

两极无法直接添加,一般下载城市数据,直接通过java进行一次性导入

有效经度从-180~180,有效纬度从-85.05112878到85.05112878,超出范围返回错误

geoadd key longitude latitude member [longitude latitude member ...] #添加地理位置:经度,纬度,名称。
geopos key member [member...]  #获取指定地区的坐标值
geodist key member1 member2 [m|km|ft|mi] #获取两个位置之间的直线距离 m表示米[默认值] km表示千米 mi表示英里 ft表示英尺
georadius key longitude latitude radius m|km|ft|mi #以给定的经纬度为中心,找出某一半径内的元素 经度 纬度 半径 单位

4.配置文件需要了解部分

UNITS单位

在这里插入图片描述

配置大小单位,只支持bytes,不支持bit,大小写不敏感

INCLUDES

在这里插入图片描述

包含其他文件,比如公共部分等

NETWORK

bind

在这里插入图片描述

默认情况bind只能接收本机的访问,

不写的情况下,无限制接受任何ip地址的访问,

生产环境需要写应用服务器的地址,服务器需要远程访问,所以要将其注释

protected-mode

在这里插入图片描述

如果开启了protected-mode,那么在没有设定bind ip且没有设置密码的情况下,Redis只允许接收本机的响应。

tcp-backlog

在这里插入图片描述

设置tcp的backlog,是一个连接队列,backlog队列总和=未完成三次握手队列+已经完成三次握手队列。

在高并发环境下需要一个高backlog值避免慢客户端连接问题。

timeout

在这里插入图片描述

当客户端连接不操作,经过x秒断开连接,0表示永不过期。以秒为单位

tcp-keepalive

在这里插入图片描述

每隔300s检测一次客户端连接是否在进行操作。如果没有则由timeout进行断开连接

其他须知

loglevel

在这里插入图片描述

日志级别

logfile

在这里插入图片描述

设置日志文件输出路径

maxclients

在这里插入图片描述

设置同时连接客户端数

maxmemory

在这里插入图片描述

设置redis可使用内存量,到达上限会试图移出内部数据。移出规则通过maxmemory-policy指定

maxmemory-policy

在这里插入图片描述

在这里插入图片描述

5.发布订阅

基本原理

在这里插入图片描述

在这里插入图片描述

命令实现

#打开订阅一个频道
subscribe channel

#发送消息给频道,返回值是订阅者数量
publish channel message

6.Jedis

所需jar包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>4.2.3</version>
</dependency>

注意事项:

关闭linux服务器的防火墙

 systemctl stop firewalld

在这里插入图片描述

在这里插入图片描述

测试:

package jedis;

import redis.clients.jedis.Jedis;

/**
 * @Author: rokned
 */
public class JedisDemo1 {	
    public static void main(String[] args) {
        //创建Jedis对象
        Jedis jedis = new Jedis("192.168.111.128",6379);
        //测试
        System.out.println(jedis.ping());

    }
}

在这里插入图片描述

具体操作见API

案例:

要求

在这里插入图片描述

分析:

在这里插入图片描述

实现:

public class PhoneCode {
    public static void main(String[] args) {
        setCode("18795969870");
        verifyCode("18795969870", "781343");

    }

    //获取redis连接
    public static Jedis getJedis() {
        Jedis jedis = new Jedis("192.168.111.128", 6379);
        return jedis;
    }

    //1.生成六位数字验证码
    public static String getCode() {
        Random random = new Random();
        String code = "";
        for (int i = 0; i < 6; i++) {
            code += random.nextInt(10);
        }
        return code;
    }

    //2.验证码放入redis,设置过期时间
    public static void setCode(String phone) {
        Jedis jedis = getJedis();
        //手机发送次数key
        String countKey = phone + ":count";
        //验证码key
        String codeKey = phone + ":code";

        //验证发送次数
        String count = jedis.get(countKey);
        if (count == null) {
            //第一次发送
            jedis.setex(countKey, 24 * 60 * 60, "1");
        } else if (Integer.parseInt(count) <= 2) {
            //发送次数+1
            jedis.incr(countKey);
        } else if (Integer.parseInt(count) > 2) {
            //超出次数
            System.out.println("发送次数超出次数");
            jedis.close();
            return;
        }
        //放入验证码
        String vcode = getCode();
        jedis.setex(codeKey, 120, vcode);
        jedis.close();
    }

    //3.验证码校验
    public static void verifyCode(String phone, String code) {
        Jedis jedis = getJedis();
        //获取
        String codeKey = phone + ":code";
        String redisCode = jedis.get(codeKey);
        //判断
        if (redisCode.equals(code)) {
            System.out.println("success");
        } else {
            System.out.println("fail");
        }
        jedis.close();
    }
}

7.SpringBoot整合Redis

依赖

<!--Redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- spring2.x集成redis所需common-pool2 (连接池)-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.0</version>
</dependency>

配置

yaml

spring:
	redis:
		host: 192.168.111.128
		prot: 6379
		#数据库索引(默认0)
		database: 0
		timeout: 1800000
		lettuce:
			pool:
				#连接池最大连接数(使用负值表示没有限制)
				max-active: 20
				#最大阻塞等待时间(负数表示没有限制)
				max-wait: -1
				#连接池中的最大空闲连接
				max-idle: 5
				#连接池中的最小空闲连接
				min-idle: 0

config配置类

@EnableCaching开启缓存类
@Configuration配置类

@EnableCaching
@Configuration
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
//        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 已被弃用
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        template.setConnectionFactory(factory);
        //key序列化方式
        template.setKeySerializer(redisSerializer);
        //value序列化
        template.setValueSerializer(jackson2JsonRedisSerializer);
        //value hashmap序列化
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        //解决查询缓存转换异常的问题
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        //        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); 已被弃用
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance ,
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置序列化(解决乱码的问题),过期时间600秒
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(600))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
        return cacheManager;
    }
}

测试

@RestController
@RequestMapping("/redisTest")
public class RedisTestController {
    @Autowired
    private RedisTemplate redisTemplate;

    @GetMapping
    public String testRedis() {
        //设置值到redis
        redisTemplate.opsForValue().set("name","test");
        //从redis获取值
        String name = (String)redisTemplate.opsForValue().get("name");
        return name;
    }
}

SpringDataRedis

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

哨兵

在这里插入图片描述

在这里插入图片描述

集群

在这里插入图片描述

8.事务

在这里插入图片描述

主要的三个命令:multi、exec和discard

在这里插入图片描述

基本使用

输入multi的时候下一个会出现tx,代表是事务

在这里插入图片描述

放弃队列

在这里插入图片描述

事务失败

组队失败

在这里插入图片描述

执行失败

只有出现错误的命令失败,其他命令都成功

在这里插入图片描述

watch

乐观锁

在执行multi之前,先执行watch key [key2],可以监视一个或多个key,如果在事务执行之前这个或这些key被其他命令所改动,事务将被打断

在这里插入图片描述

当第一个终端修改过balance后,第二个终端再进行修改时,会报错。两个终端都需要开启watch

在这里插入图片描述

秒杀案例测试

public class SecKill_redis {

	public static void main(String[] args) {
		Jedis jedis =new Jedis("192.168.111.128",6379);
		System.out.println(jedis.ping());
		jedis.close();
	}

	//秒杀过程
	public static boolean doSecKill(String uid,String prodid) throws IOException {
		//1 uid和prodid非空判断
		if(uid == null || prodid == null) {
			return false;
		}

		//2 连接redis
		Jedis jedis = new Jedis("172.22.109.205",6379);

		//3 拼接key
		// 3.1 库存key
		String kcKey = "sk:"+prodid+":qt";
		// 3.2 秒杀成功用户key
		String userKey = "sk:"+prodid+":user";

		//4 获取库存,如果库存null,秒杀还没有开始
		String kc = jedis.get(kcKey);
		if(kc == null) {
			System.out.println("秒杀还没有开始,请等待");
			jedis.close();
			return false;
		}

		// 5 判断用户是否重复秒杀操作
		if(jedis.sismember(userKey, uid)) {
			System.out.println("已经秒杀成功了,不能重复秒杀");
			jedis.close();
			return false;
		}
		
		
	    //因为kc为字符串,所以先转换城integer类型的
		//6 判断如果商品数量,库存数量小于1,秒杀结束
		if(Integer.parseInt(kc)<=0) {
			System.out.println("秒杀已经结束了");
			jedis.close();
			return false;
		}


		//7.1 库存-1
		jedis.decr(kcKey);
		//7.2 把秒杀成功用户添加清单里面
		jedis.sadd(userKey,uid);

		System.out.println("秒杀成功了..");
		jedis.close();
		return true;
	}
}

并发模拟(ab)

yum install httpd-tools
ab --help
ab -n 1000 -c 100 http://192.168.111.128:8080/seckill #通过浏览器请求,一千个请求中有一百个并发操作

在这里插入图片描述

解决超卖超时库存遗留问题

超时问题

通过连接池

在这里插入图片描述

超卖问题

通过乐观锁,使用watch和事务

库存遗留

乐观锁造成的库存遗留问题

lua脚本

在这里插入图片描述

9.持久化

在这里插入图片描述

RDB

在指定的时间间隔内将内存中的数据集快照写入磁盘

在这里插入图片描述

配置文件中

在这里插入图片描述

  • stop-writes-on-bgsave-error yes 关闭写入磁盘操作。比如当Redis无法写入磁盘的话,直接关掉Redis的写操作
  • rdbcompression yes 对于存储到磁盘中的快照,可以设置是否进行压缩存储,如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能。推荐yes.
  • rdbchecksum yes 增加数据校验,增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能
  • dbfilename dump.rdb 在redis.conf中配置文件名称,默认为dump.rdb
  • dir ./ 默认为Redis启动时命令行所在的目录下

save :save时只管保存,其它不管,全部阻塞。手动保存。不建议。

bgsave:Redis会在后台异步进行快照操作, 快照同时还可以响应客户端请求。

具体rdb的备份

因为是临时文件,如果redis关闭之后,rdb的东西就会不见
所以通过cp 复制之后cp dump.rdb d.rdb
在启动之前,删除原来的dump.rdb,再将复制的更改回来名字即可,mv d.rdb dump.rdb`(启动Redis, 备份数据会直接加载)

总结

优点:

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高更适合使用
  • 节省磁盘空间
  • 恢复速度快

缺点:

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

AOF

以日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件

  • redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作
  • AOF的备份机制和性能虽然和RDB不同, 但是备份和恢复的操作同RDB一样,都是拷贝备份文件,需要恢复时再拷贝到Redis工作目录下,启动系统即加载
  • 默认是不开启AOF,开启RDB
  • 可以在redis.conf中配置文件名称,默认为 appendonly.aof
  • AOF文件的保存路径,同RDB的路径一致

开启AOF

在这里插入图片描述

当同时开启RDB和AOF,以AOF为准

日志修复

如果直接在日志添加一些无法识别的数据,启动redis会启动不了
可以通通过/usr/local/bin/redis-check-aof--fix appendonly.aof进行恢复

AOF同步频率

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

Rewrite压缩

AOF采用文件追加方式,文件会越来越大为避免出现此种情况,新增了重写机制, 当AOF文件的大小超过所设定的阈值时,Redis就会启动AOF文件的内容压缩, 只保留可以恢复数据的最小指令集.可以使用命令bgrewriteaof

什么时候重写:

Redis会记录上次重写时的AOF大小,默认配置是当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发

重写虽然可以节约大量磁盘空间,减少恢复时间。但是每次重写还是有一定的负担的,因此设定Redis要满足一定条件才会进行重写。

在这里插入图片描述

  • auto-aof-rewrite-percentage:设置重写的基准值,文件达到100%时开始重写(文件是原来重写后文件的2倍时触发)
  • auto-aof-rewrite-min-size:设置重写的基准值,最小文件64MB。达到这个值开始重写。

总结

优点:

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

缺点:

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

10.主从复制

一主两从

在这里插入图片描述

创建配置文件

在这里插入图片描述

配置从库

在这里插入图片描述

info replication
#slaveof <ip><port>
slaveof 127.0.0.1 6379
  • 当从服务器挂掉后重启,需要重新进行配置,会把数据从主服务器重头复制过来
  • 当主服务器挂掉后重启,重启即可,不需要进行其他配置

从服务器还可以挂载从服务器

反客为主

当主机挂掉后,从机升级为主机(手动)

slaveof no one

哨兵模式

自动检测主机是否宕机,升级从机为主机

  1. 新建sentinel.conf文件

  2. 配置内容

    sentinel monitor mymaster 127.0.0.1 6379 1
    #mymaster是为监控对象起的名称,1表示至少有1个哨兵同意才准迁移
    
  3. 启动哨兵

    redis-sentinel /sentinel.conf
    

当主机挂掉,哨兵在从机中进行选举,选择一台提升为主机,将原来挂掉的主机作为从机,当原来的主机重启后,将自动作为从机

判定规则主要为
(顺序依次往下,优先级》偏移量》runid)

  • 优先级在redis.conf中默认:slave-priority 100,值越小优先级越高
  • 偏移量是指获得原主机数据最全的,也就是数据越多,变主机的机会越大
  • 每个redis实例启动后都会随机生成一个40位的runid

缺点就是复制会有延时
由于所有的写操作都是先在Master上操作,然后同步更新到Slave上,所以从Master同步到Slave机器有一定的延迟,当系统很繁忙的时候,延迟问题会更加严重,Slave机器数量的增加也会使这个问题更加严重。

java代码结合

private static JedisSentinelPool jedisSentinelPool=null;

public static  Jedis getJedisFromSentinel(){
    if(jedisSentinelPool==null){
        Set<String> sentinelSet=new HashSet<>();
        sentinelSet.add("192.168.11.103:26379");

        JedisPoolConfig jedisPoolConfig =new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(10); //最大可用连接数
        jedisPoolConfig.setMaxIdle(5); //最大闲置连接数
        jedisPoolConfig.setMinIdle(5); //最小闲置连接数
        jedisPoolConfig.setBlockWhenExhausted(true); //连接耗尽是否等待
        jedisPoolConfig.setMaxWaitMillis(2000); //等待时间
        jedisPoolConfig.setTestOnBorrow(true); //取连接的时候进行一下测试 ping pong

        jedisSentinelPool=new JedisSentinelPool("mymaster",sentinelSet,jedisPoolConfig);
        return jedisSentinelPool.getResource();
    }else{
        return jedisSentinelPool.getResource();
    }
}

11.集群

配置修改

include /myredis/redis.conf #公共配置
pidfile "/var/run/redis_6391.pid"	#pid文件
port 6391 #端口
dbfilename "dump6391.rdb" #RDB文件
#以下是集群配置
cluster-enabled yes	#开启集群
cluster-config-file nodes-6391.conf #设置节点配置文件名
cluster-node-timeout 15000 #节点失联时间,超过该时间(毫秒),集群自动进行主从切换

开启集群

此处使用真实ip

-replicas 1 采用最简单的方式进行集群配置,一台主机一台从机,正好三组

redis-cli --cluster create --cluster-replicas 1 192.168.242.110:6379 192.168.242.110:6380 192.168.242.110:6381 192.168.242.110:6389 192.168.242.110:6390 192.168.242.110:6391

集群的启动要多加一个- c的参数,而且哪一个启动都可以,redis-cli -c -p 6379

可以通过cluster nodes查看集群信息

一个集群中至少要有三个主节点,replicas 1表示为每个主节点创建一个从节点

分配原则尽量保证每个主数据库运行在不同的IP地址,每个从库和主库不在一个IP地址上。

故障

如果某个主机宕机了,从机上位变主机,之前那个主机上线之后,就会变成从机

那如果主从都宕机了,也就是负责该服务的主从都宕机了
就看具体的配置

cluster-require-full-coverage
  • 为yes ,那么 ,整个集群都挂掉
  • 为no ,那么,该插槽数据全都不能使用,也无法存储。

slots

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

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

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

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

在redis-cli每次录入、查询键值,redis都会计算出该key应该送往的插槽,如果不是该客户端对应服务器的插槽,redis会报错,并告知应前往的redis实例地址和端口

CLUSTER KEYSLOT k1 #查询集群中的值
CLUSTER COUNTKEYSINSLOT 5474 #查询卡槽中key的数量 注:只能看自己插槽的值,不能看别的
CLUSTER GETKEYSINSLOT 5474 2 #查询指定卡槽返回key的数量

如果在集群中录入值,录一个值,会根据计算进入到某个主从的卡槽值

传输多个值(分组)

如果传输多个值,要使用到分组的技术,在用mset 同时设置多个值的时候,需要把这些key放到同一个组中,不然会报错。可以通过{}来定义组的概念,从而使key中{}内相同内容的键值对放到一个slot中去在这里插入图片描述

伸缩节点

在这里插入图片描述

在这里插入图片描述

重新分配插槽

在这里插入图片描述

在这里插入图片描述

java代码结合

即使连接的不是主机,集群会自动切换主机存储。主机写,从机读。
无中心化主从集群。无论从哪台主机写的数据,其他主机上都能读到数据

public class JedisClusterTest {
    public static void main(String[] args) {
        HostAndPort hostAndPort = new HostAndPort("192.168.242.110", 6381);
        JedisCluster jedisCluster = new JedisCluster(hostAndPort);
        jedisCluster.set("k5","v5");
        String k5 = jedisCluster.get("k5");
        System.out.println(k5);
    }
}

Springboot

在这里插入图片描述

优势劣势

优势:

  • 实现扩容
  • 分摊压力
  • 无中心配置相对简单

劣势:

  • 多键操作是不被支持的
  • 多键的Redis事务是不被支持的。lua脚本不被支持
  • 由于集群方案出现较晚,很多公司已经采用了其他的集群方案,而代理或者客户端分片的方案想要迁移至redis cluster,需要整体迁移而不是逐步过渡,复杂度较大。

12.应用问题

缓存穿透

在这里插入图片描述

redis正常运行,但一直查询redis中不存在的数据,从redis一直转发查询给数据库,造成数据库压力过大

解决方案

  1. 对空值缓存:如果一个查询返回的数据为空(不管是数据是否不存在),我们仍然把这个空结果(null)进行缓存,设置空结果的过期时间会很短,最长不超过五分钟
  2. 设置可访问的名单(白名单):
    使用bitmaps类型定义一个可以访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmap里面的id进行比较,如果访问id不在bitmaps里面,进行拦截,不允许访问。
  3. 采用布隆过滤器:(布隆过滤器(Bloom Filter)是1970年由布隆提出的。它实际上是一个很长的二进制向量(位图)和一系列随机映射函数(哈希函数)。布隆过滤器可以用于检索一个元素是否在一个集合中。它的优点是空间效率和查询时间都远远超过一般的算法,缺点是有一定的误识别率和删除困难。)
    将所有可能存在的数据哈希到一个足够大的bitmaps中,一个一定不存在的数据会被 这个bitmaps拦截掉,从而避免了对底层存储系统的查询压力。
  4. 进行实时监控:当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

缓存击穿

在这里插入图片描述

redis正常运行,redis中某个key过期了,正好这个key正在被大量访问,因此会造成这个问题

解决方案

  1. 预先设置热门数据:在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长
  2. 实时调整:现场监控哪些数据热门,实时调整key的过期时长
  3. 使用锁

缓存雪崩

在这里插入图片描述

key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。

解决方案

  1. 构建多级缓存架构:nginx缓存 + redis缓存 +其他缓存(ehcache等)
  2. 使用锁或队列:
    用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
  3. 设置过期标志更新缓存:
    记录缓存数据是否过期(设置提前量),如果过期会触发通知另外的线程在后台去更新实际key的缓存。
  4. 将缓存失效时间分散开:
    比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。

分布式锁

由于分布式系统多线程、多进程并且分布在不同机器上,这将使原单机部署情况下的并发控制锁策略失效,单纯的Java API并不能提供分布式锁的能力。为了解决这个问题就需要一种跨JVM的互斥机制来控制共享资源的访问

也就是在这个机器上了锁,另外一个机器也要可以识别到这个锁,也就是共享锁,都是同一把锁

  1. 基于数据库实现分布式锁
  2. 基于缓存(Redis等)
  3. 基于Zookeeper

每一种分布式锁解决方案都有各自的优缺点:

  1. 性能:redis最高
  2. 可靠性:zookeeper最高

设置锁

就基于redis实现分布式锁

  • setnx 上锁,通过del 解释在这里插入图片描述

  • 锁一直没有释放,可以通过设置过期时间来自动释放在这里插入图片描述

但是如果上锁之后就断电了

**原子操作:**可以边上锁边设置过期时间,通过命令set users 10 nx ex 12,nx为上锁,ex为过期时间

设置锁结合java

@GetMapping("testLock")
public void testLock(){
    //1获取锁,setne ,顺便设置过期时间
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", "111",3,TimeUnit.SECONDS);
    //2获取锁成功、查询num的值
    if(lock){
        Object value = redisTemplate.opsForValue().get("num");
        //2.1判断num为空return
        if(StringUtils.isEmpty(value)){
            return;
        }
        //2.2有值就转成成int
        int num = Integer.parseInt(value+"");
        //2.3把redis的num加1
        redisTemplate.opsForValue().set("num", ++num);
        //2.4释放锁,del
        redisTemplate.delete("lock");

    }else{
        //3获取锁失败、每隔0.1秒再获取
        try {
            Thread.sleep(100);
            testLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

UUID防止误删

释放别人的锁

在这里插入图片描述

为此应该多一个判断是否是你的锁,虽然是共享锁,都是一样的,但是可以上锁之后在设置时间,还要给每个用户的这把锁都来一个uuid

@GetMapping("testLock")
public void testLock(){
	String uuid = UUID.randomUUID().toString();
    //1获取锁,setne ,顺便设置过期时间
    Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock", uuid,3,TimeUnit.SECONDS);
    //2获取锁成功、查询num的值
    if(lock){
       ...
        String lockUuid = (String)redisTemplate.opsForValue().get("lock");
        if(uuid.equals(lockUuid)){
             //2.4释放锁,del
        	redisTemplate.delete("lock");
        }
    }else{
       ...
    }
}

lua脚本保证原删除子性

在这里插入图片描述

@GetMapping("testLockLua")
public void testLockLua() {
    //1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中
    String uuid = UUID.randomUUID().toString();
    //2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!
    String skuId = "25"; // 访问skuId 为25号的商品 100008348542
    String locKey = "lock:" + skuId; // 锁住的是每个商品的数据

    // 3 获取锁
    Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 3, TimeUnit.SECONDS);

    // 第一种: lock 与过期时间中间不写任何的代码。
    // redisTemplate.expire("lock",10, TimeUnit.SECONDS);//设置过期时间
    // 如果true
    if (lock) {
        // 执行的业务逻辑开始
        // 获取缓存中的num 数据
        Object value = redisTemplate.opsForValue().get("num");
        // 如果是空直接返回
        if (StringUtils.isEmpty(value)) {
            return;
        }
        // 不是空 如果说在这出现了异常! 那么delete 就删除失败! 也就是说锁永远存在!
        int num = Integer.parseInt(value + "");
        // 使num 每次+1 放入缓存
        redisTemplate.opsForValue().set("num", String.valueOf(++num));
        /*使用lua脚本来锁*/
        // 定义lua 脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        // 使用redis执行lua执行
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(script);
        // 设置一下返回值类型 为Long
        // 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,
        // 那么返回字符串与0 会有发生错误。
        redisScript.setResultType(Long.class);
        // 第一个要是script 脚本 ,第二个需要判断的key,第三个就是key所对应的值。
        redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);
    } else {
        // 其他线程等待
        try {
            // 睡眠
            Thread.sleep(1000);
            // 睡醒了之后,调用方法。
            testLockLua();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

13.新功能

ACL

ACL是Access Control List(访问控制列表)的缩写,该功能允许根据可以执行的命令和可以访问的键来限制某些连接。

(1)接入权限:用户名和密码

(2)可以执行的命令

(3)可以操作的 KEY

  • acl list命令展现用户权限列表
  • acl cat,查看添加权限指令类别
  • acl whoami命令查看当前用户
  • acl set user命令创建和编辑用户ACL

IO多线程

IO多线程其实指客户端交互部分的网络IO交互处理模块多线程,而非执行命令多线程。Redis6执行命令依然是单线程

另外,多线程IO默认也是不开启的,需要再配置文件中配置

io-threads-do-reads yes
io-threads 4

14.使用

redis做session缓存

在这里插入图片描述

15.Redisson

在这里插入图片描述

在这里插入图片描述

简单使用

在这里插入图片描述

在这里插入图片描述

重入锁

在这里插入图片描述

在这里插入图片描述

锁重试与超时释放

在这里插入图片描述

在这里插入图片描述

16.消息队列

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

list结构

在这里插入图片描述

pubsub结构

在这里插入图片描述

stream结构

是5.0引入的一种新数据类型,可以数据持久化

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

消费者组

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

对比

在这里插入图片描述

17.Feed流

在这里插入图片描述

在这里插入图片描述

实现方案

拉模式

收件箱不进行保存,每次清空。需要获取时进行拉取。

在这里插入图片描述

推模式

没有发件箱,占用内存,耗时少

在这里插入图片描述

推拉结合

18.滚动分页思路

使用zset

不根据角标进行排序

在这里插入图片描述

19.多级缓存

在这里插入图片描述

JVM进程缓存

在这里插入图片描述

在这里插入图片描述

简单示例

在这里插入图片描述

在这里插入图片描述

OpenResty

在这里插入图片描述

安装开发库

yum install pcre-devel openssl-devel gcc curl
#或
apt-get install libpcre3-dev libssl-dev perl make build-essential curl

安装openresty仓库

# add the yum repo:
wget https://openresty.org/package/centos/openresty.repo
sudo mv openresty.repo /etc/yum.repos.d/

# update the yum index:
sudo yum check-update
#如有问题或者其他系统可见官方文档

安装openresty和opm管理工具

sudo yum install -y openresty
sudo yum install -y openresty-opm

配置nginx环境变量

vi /etc/profile
#加入命令
#NGINX_HOME后面是OpenResty安装目录下的nginx的目录
export NGINX_HOME=/usr/local/openresty/nginx
export PATH=${NGINX_HOME}/sbin:$PATH
#配置生效
source /etc/profile

#启动nginx
nginx

#重载配置
nginx -s reload

#停止
nginx -s stop

需要在openresty中配置nginx.conf文件

#user  nobody;
worker_processes  1;
error_log  logs/error.log;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;

    server {
        listen       8081;
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
}

配置返回假数据

在这里插入图片描述

创建lua/item.lua文件进行编写

在这里插入图片描述

获取请求参数

在这里插入图片描述

nginx内部发送http请求

在这里插入图片描述

封装http请求函数

在这里插入图片描述

处理json

在这里插入图片描述

查询

在这里插入图片描述

tomcat集群负载

在这里插入图片描述

此时负载均衡会导致每次访问路径会访问不同的tomcat服务器,导致缓存失效,所以需要hash运算指定tomcat服务器进行访问

在这里插入图片描述

Redis缓存预热

在这里插入图片描述

在这里插入图片描述

查询Redis

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

查询nginx本地缓存

在这里插入图片描述

缓存同步

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

canal

在这里插入图片描述

在这里插入图片描述

安装

https://github.com/alibaba/cana

开启mysql主从

修改文件:

vi /tmp/mysql/conf/my.cnf

添加内容:

log-bin=/var/lib/mysql/mysql-bin
binlog-do-db=heima

配置解读:

  • log-bin=/var/lib/mysql/mysql-bin:设置binary log文件的存放地址和文件名,叫做mysql-bin
  • binlog-do-db=test:指定对哪个database记录binary log events,这里记录test这个库

最终效果:

[mysqld]
skip-name-resolve
character_set_server=utf8
datadir=/var/lib/mysql
#配置 MySQL replaction 需要定义,不要和 canal 的 slaveId 重复
server-id=1000  
log-bin=/var/lib/mysql/mysql-bin
binlog-do-db=test
设置用户权限

接下来添加一个仅用于数据同步的账户

create user canal@'%' IDENTIFIED by 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT,SUPER ON *.* TO 'canal'@'%' identified by 'canal';
FLUSH PRIVILEGES;

测试设置是否成功:在mysql控制台,输入命令:

show master status;
启动canal
wget https://github.com/alibaba/canal/releases/download/canal-1.0.17/canal.deployer-1.0.17.tar.gz

mkdir /tmp/canal
tar zxvf canal.deployer-$version.tar.gz  -C /tmp/canal
#修改配置
vi conf/example/instance.properties
## mysql serverId
canal.instance.mysql.slaveId = 1234
#position info,需要改成自己的数据库信息
canal.instance.master.address = 127.0.0.1:3306 
canal.instance.master.journal.name = 
canal.instance.master.position = 
canal.instance.master.timestamp = 
#canal.instance.standby.address = 
#canal.instance.standby.journal.name =
#canal.instance.standby.position = 
#canal.instance.standby.timestamp = 
#username/password,需要改成自己的数据库信息
canal.instance.dbUsername = canal  
canal.instance.dbPassword = canal
canal.instance.defaultDatabaseName =
canal.instance.connectionCharset = UTF-8
#table regex
canal.instance.filter.regex = .\*\\\\..\*
sh bin/startup.sh
通知客户端更新

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

20.大数据量导入

MSET

在这里插入图片描述

Pipeline

在这里插入图片描述

集群批处理

在这里插入图片描述

hash_tag就是集群的分组

  • 一般使用并行或者串行slot
  • jedis需要手动实现插槽计算,springboot的template自动实现

21.服务端优化

部署建议

在这里插入图片描述

慢查询

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

命令安全

redis未授权访问配合ssh key文件可以免密登陆

  1. 简单流程就是ssh免密登陆需要在本地生成公私密钥,将公钥上传至服务器,并命名为authoried_keys即可免密登陆
  2. 而通过redis可以先连接redis然后通过cat foo.txt | redis-cil -h 192.168.1.11 -x set crackit(foo.txt就是公钥,通过管道符保存进redis)。此时redis中有一个键是crackit,值是公钥
  3. 再连接redis在redis中执行config set dir /root/.ssh/(此命令用于修改redis持久化文件目录),config get dir进行目录查看
  4. config set dbfilename "authorized_keys"(此命令用于修改持久化文件名称)
  5. save执行持久化,就会将公钥保存,从而免密登陆服务器。

在这里插入图片描述

在这里插入图片描述

内存配置

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

普通客户端默认没有上限!

可以通过client list命令查看客户端信息

集群问题

在这里插入图片描述

工具类

package com.hmdp.utils;

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.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;

import static com.hmdp.utils.RedisConstants.CACHE_NULL_TTL;
import static com.hmdp.utils.RedisConstants.LOCK_SHOP_KEY;

@Slf4j
@Component
public class CacheClient {

    private final StringRedisTemplate stringRedisTemplate;

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public CacheClient(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit) {
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value), time, unit);
    }

    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)));
        // 写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }

    public <R,ID> R queryWithPassThrough(
            String keyPrefix, ID id, Class<R> type, Function<ID, 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) {
            // 将空值写入redis
            stringRedisTemplate.opsForValue().set(key, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6.存在,写入redis
        this.set(key, r, time, unit);
        return r;
    }

    public <R, ID> R queryWithLogicalExpire(
            String keyPrefix, ID id, Class<R> type, Function<ID, 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_KEY + 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;
    }

    public <R, ID> R queryWithMutex(
            String keyPrefix, ID id, Class<R> type, Function<ID, 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_KEY + 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, "", CACHE_NULL_TTL, TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6.存在,写入redis
            this.set(key, r, time, unit);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            // 7.释放锁
            unlock(lockKey);
        }
        // 8.返回
        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);
    }
}

Lua简单使用

调用.lua文件直接使用lua xxx.lua即可

数据类型

在这里插入图片描述

声明变量

在这里插入图片描述

循环

在这里插入图片描述

函数

在这里插入图片描述

条件控制

在这里插入图片描述

java_lua脚本使用

在这里插入图片描述

在这里插入图片描述

lua执行类

在这里插入图片描述

lua文件

在这里插入图片描述

加载lua脚本

在这里插入图片描述

调用

在这里插入图片描述

Yum被锁定

[root@belial ~]# yum install gcc
已加载插件:fastestmirror, langpacks
/var/run/yum.pid 已被锁定,PID 为 2946 的另一个程序正在运行。
Another app is currently holding the yum lock; waiting for it to exit...
  另一个应用程序是:PackageKit
    内存: 75 M RSS (565 MB VSZ)
    已启动: Sat May 21 13:55:03 2022 - 00:05之前
    状态  :睡眠中,进程ID:2946

解决方法:删除yum.pid

[root@belial ~]# rm -rf /var/run/yum.pid

redis底层原理

底层是C写的

数据结构

动态字符串SDS

在这里插入图片描述

在这里插入图片描述

IntSet

在这里插入图片描述

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

在这里插入图片描述

Dict

在这里插入图片描述

在这里插入图片描述

dict扩容以及收缩

触发扩容

在这里插入图片描述

在这里插入图片描述

rehash

在这里插入图片描述

在这里插入图片描述

ZipList

在这里插入图片描述

ziplistentry

在这里插入图片描述

在这里插入图片描述

encoding

字符串

在这里插入图片描述

在这里插入图片描述

整数

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

连锁更新问题

在这里插入图片描述

在这里插入图片描述

QuickList

在这里插入图片描述

限制大小

在这里插入图片描述

节点压缩

在这里插入图片描述

结构

在这里插入图片描述

SkipList

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

RedisObject

在这里插入图片描述

在这里插入图片描述

编码方式

在这里插入图片描述

在这里插入图片描述

String

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

LIST

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

SET

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

ZSet

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Hash

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

网络模型

用户空间与内核空间

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

阻塞IO

在这里插入图片描述

非阻塞IO

在这里插入图片描述

IO多路复用

在这里插入图片描述

在这里插入图片描述

select

拷贝监听列表

在这里插入图片描述

遍历监听列表查看是否就绪,未就绪置0

在这里插入图片描述

拷贝回用户空间再次遍历

在这里插入图片描述

在这里插入图片描述

poll

在这里插入图片描述

epoll

在这里插入图片描述

在这里插入图片描述

事件通知

ET模式中,就绪列表拷贝后直接断开链表,就为空了,如果第一次没有读完数据,还需要剩下数据,需要手动添加就绪列表(调用epoll_ctl函数,用于增删改查eventpoll上的fd)在这里插入图片描述

LT模式中,还需要重新添加链表指针,会有损耗

在这里插入图片描述

基于epoll模式的web服务模式基本流程

初始化

在这里插入图片描述

创建ssfd,进行监听

在这里插入图片描述

fd是否就绪并判断类型

在这里插入图片描述

注册客户端socket-fd

在这里插入图片描述

读取客户端请求

在这里插入图片描述

总结

在这里插入图片描述

信号驱动IO

在这里插入图片描述

异步IO

在这里插入图片描述

在这里插入图片描述

Redis网络模型

网络多线程,核心单线程

在这里插入图片描述

API大纲

在这里插入图片描述

初始化并监听

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

客户端读取请求数据并写出

在这里插入图片描述

在这里插入图片描述

beforesleep

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

总结

在这里插入图片描述

在这里插入图片描述

RESP协议

在这里插入图片描述

在这里插入图片描述

模拟客户端

package com.company;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

public class Main {

    static Socket s;
    static PrintWriter writer;
    static BufferedReader reader;

    public static void main(String[] args) {
        try {
            // 1.建立连接
            String host = "192.168.150.101";
            int port = 6379;
            s = new Socket(host, port);
            // 2.获取输出流、输入流
            writer = new PrintWriter(new OutputStreamWriter(s.getOutputStream(), StandardCharsets.UTF_8));
            reader = new BufferedReader(new InputStreamReader(s.getInputStream(), StandardCharsets.UTF_8));

            // 3.发出请求
            // 3.1.获取授权 auth 123321
            sendRequest("auth", "123321");
            Object obj = handleResponse();
            System.out.println("obj = " + obj);

            // 3.2.set name 虎哥
            sendRequest("set", "name", "虎哥");
            // 4.解析响应
            obj = handleResponse();
            System.out.println("obj = " + obj);

            // 3.2.set name 虎哥
            sendRequest("get", "name");
            // 4.解析响应
            obj = handleResponse();
            System.out.println("obj = " + obj);

            // 3.2.set name 虎哥
            sendRequest("mget", "name", "num", "msg");
            // 4.解析响应
            obj = handleResponse();
            System.out.println("obj = " + obj);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 5.释放连接
            try {
                if (reader != null) reader.close();
                if (writer != null) writer.close();
                if (s != null) s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private static Object handleResponse() throws IOException {
        // 读取首字节
        int prefix = reader.read();
        // 判断数据类型标示
        switch (prefix) {
            case '+': // 单行字符串,直接读一行
                return reader.readLine();
            case '-': // 异常,也读一行
                throw new RuntimeException(reader.readLine());
            case ':': // 数字
                return Long.parseLong(reader.readLine());
            case '$': // 多行字符串
                // 先读长度
                int len = Integer.parseInt(reader.readLine());
                if (len == -1) {
                    return null;
                }
                if (len == 0) {
                    return "";
                }
                // 再读数据,读len个字节。我们假设没有特殊字符,所以读一行(简化)
                return reader.readLine();
            case '*':
                return readBulkString();
            default:
                throw new RuntimeException("错误的数据格式!");
        }
    }

    private static Object readBulkString() throws IOException {
        // 获取数组大小
        int len = Integer.parseInt(reader.readLine());
        if (len <= 0) {
            return null;
        }
        // 定义集合,接收多个元素
        List<Object> list = new ArrayList<>(len);
        // 遍历,依次读取每个元素
        for (int i = 0; i < len; i++) {
            list.add(handleResponse());
        }
        return list;
    }

    // set name 虎哥
    private static void sendRequest(String ... args) {
        writer.println("*" + args.length);
        for (String arg : args) {
            writer.println("$" + arg.getBytes(StandardCharsets.UTF_8).length);
            writer.println(arg);
        }
        writer.flush();
    }
}

内存回收

在这里插入图片描述

在这里插入图片描述

过期策略

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

淘汰策略

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值