Redis7(高级完整笔记)

高级篇章

概念和理论

Redis的单线程

在Redis版本4之后R已经变成多线程了,所谓的单线程指定是Redis命令工作线程,命令执行主线程执行,IO读写变成多线程,整体的Redis就是多线程,比如删除命令,执行删除,先删除主存中的值,开启另一个线程进行物理删除,还有其他线程进行RDB数据持久化。

因为Redis是基于内存操作,所以性能高,Redis的数据结构,导致查询很快,不需要多线程,不需要上下文切换;对应Redis系统,性能瓶颈是内存和网络带宽,不是CPU。

所以Redis为什么快

Redis是内存中数据结构存储系统,内存操作都是纳秒级别;Redis的工作线程是单线程执行,没有CPU上下切换;还有IO多路复用技术,可以让单线程处理高效的多个IO请求,跟Nginx一样,有一个单独的线程进行监听每个TCP连接,哪个有数据进行放行处理。就像一场考试,到点大家都交卷,有一个线程考官会跟大家说,谁答完举手,考官会根据举手的人执行放到队列中,到主线程执行任务就是交卷,依次的完成,再走出教室。

当我们Redis的CPU消耗不大,吞吐量不高的情况下,可以开启多线程配置:

# Redis一秒可以处理8万次,当我们查看吞吐量一秒才1万次,就是Redis性能没有充分利用
# 开启多线程配置,在Redis配置文件中
io-threads-do-reads yes  # 默认是no,集群配置不建议开启,单机Redis可配置
io-threads 4   # 注意不要比当前机器核数高,4核的CPU,设置为2或3,如果为8核 CPU设置 6

BigKey

MoreKey案例

模拟生成一百万的redis数据

# 生成100W条redis写入到/tmp目录下的redisTest.txt文件中,在Linux命令行执行
for((i=1;i<=100*10000;i++)); do echo "set k$i v$i" >> /tmp/redisTest.txt ;done;
# 查看是否插入成功
more /tmp/redisTest.txt
# 通过redis管道的–pipe命令插入到Redis中
cat /tmp/redisTest.txt | redis-cli -h 127.0.0.1 -p 6379 -a 123456 --pipe
# 登录Redis,可以查看导入的数据

当key很庞大的时候,进行使用key * 命令

# 通过redis配置进行使用命令,如果哪个命令禁止使用,rename-command 命令语句 "",设置""可以
rename-command keys ""
rename-command flushdb ""
rename-command flushall ""

# 当我们禁止了keys *,我们可以使用scan 查询多个key
scan 0 match k* count 15
# 查询 从0开始(游标,如下一端的游标) match ‘要搜索的key,相当于模糊查询’ count 15 查15个

key很庞大的危害:

  • 内存不均匀,集群迁移问题
  • 超时删除,删除大key会有阻塞
  • 网络IO阻塞

产生大key的场景:

  • 社交类,当某个集合突然暴涨,例:粉丝暴涨
  • 报表类,累积月的积累

防止redis的慢查询

# 设置string类型控制在10kb内,hash、list、set、zset个数不超过5000
# 通过redis-cli --bigkeys查询大key是哪个数据、占比(在Linux命令行执行)
redis-cli -h 127.0.0.1 -p 6379 -a redis密码 --bigkeys
# 每隔 100 条 scan 指令就会休眠 0.1s,ops 就不会剧烈抬升,但是扫描的时间会变长
redis-cli -h 127.0.0.1 -p 6379 -a redis密码 –-bigkeys -i 0.1

# 通过memory usage 查询某个key的字节数,(在Redis命令行执行)
# memory usage key名

删除大key

非字符串,不建议使用del删除,会阻塞删除;
在生产环境,我们使用非阻塞删除
在redis配置文件中更改,
lazyfree-lazy-eviction no
lazyfree-lazy-expire no
lazyfree-lazy-server-del yes
replica-lazy-flush yes

lazyfree-lazy-user-del yes

缓存双写一致性

一般我们查询,先查询Redis数据是否有数据,有的话直接返回,没有的话再查询Mysql数据库,再更新到缓存中。

这种对于多线程情况下,会造成多个线程请求到mysql数据库,会造成压力,这时我们采用双检加锁。

更新策略:

可以停机的话,服务降级、温馨提示维护、凌晨升级

不停机处理

  • 先更新数据库,再更新缓存(多线程有问题)

    # 问题 - 数据不一致
    A线程更新数据库值为100   B线程更新数据库值为80
    B线程更新缓存为80       A线程更新缓存为100 
    
  • 先更新缓存,再更新数据库

    # 问题
    mysql一般作为最总解释权,缓存更新成功,数据库更新失败,缓存的值不准确
    
  • 先删除缓存,再更新数据库(多线程有问题)

    # 问题 - 数据不一致
    A线程删除缓存         B线程获取缓存失败,查询数据库,并重新更新缓存
    B线程把旧值写回缓存中  A线程更新数据库,此时缓存中的数据有旧值的存在
    
  • 解决方案 - 延迟双删

    # 先删除缓存,再更新完数据库后,加上一定的睡眠时间(多个100ms即可),再来一次删除缓存;
    # 保证:更新完数据库的时间 + sleep的时间 大于 读取数据并写入换的时间
    # 线程A 睡眠的时间,大于线程B读取数据再写入缓存的时间,就可以解决缓存双写一致性问题,
    # 但一般睡眠时间长,会影响吞吐量问题,我们可以起异步进行删除第二次的缓存
    
    1. 我们可以将要删除缓存和更新数据库的值,存放到消息队列中,
    2. 当我们成功删除缓存,并且更新数据库,我们可以将消息队列中此数据剔除
    3. 如果没有成功,从消息队列中再读该数据,再一次操作
    4. 如果失败,需要业务逻辑发送报错日志

canal

它可以监听mysql的变动且通知给Redis,它模拟Mysql主从的交互协议,将自己作为Mysql的从机 ,向Mysql主机发送dump协议,Mysql主机收到请求,开始推送 binary log 给 从机(即cancal)

官方地址:https://github.com/alibaba/canal

前提:

安装Mysql做为主机、配置Mysql

# 1 mysql的配置文件
[client]
default_character_set=utf8
[mysqld]
[mysqld]
# 主服务器唯一ID
server-id = 1  
log-bin=自己本地的路径/data/mysqlbin  # 启用二进制日志,日志的存放地址(前提把文件创建好)
log-err=自己本地的路径/data/mysqlerr  # 启用二进制日志,错误日志存放地址(前提把文件创建好)
binlog_format=ROW  # 选择 ROW 模式(默认也是ROW)
collation_server = utf8_general_ci
character_set_server = utf8

# 2 配置完,重启mysql
# 查看是否开启binlog的写入功能,值为on代表成功(在mysql环境下执行)
SHOW VARIABLES LIKE 'log_bin';

# 3.创建新用户,进行连接canal
use mysql;
select * from user; # 可查询有哪些用户
# 进行创建用户canal
DROP USER IF EXISTS 'canal'@'%';
create user 'canal用户'@'%' identified by 'canal密码';
GRANT REPLICATION SLAVE ON *.* TO 'canal用户'@'%';
ALTER USER 'canal用户'@'%' IDENTIFIED WITH mysql_native_password BY 'canal密码';
FLUSH PRIVILEGES;

# 4.配置完成
SELECT * FROM mysql.user; # 可查询有哪些用户

安装canal

下载地址:https://github.com/alibaba/canal/releases

在Assets有各种安装包,上传到对应的服务器上,找一个对应的文件目录,进行解压

# 1 解压 
tar -zxvf canal.deployer-1.1.6.tar.gz

# 2 配置
# 修改 /xxx/conf/example 路径下的 instance.properties 文件
canal.instance.master.address=Mysql主键ip地址:端口号
canal.instance.dbUsername=canal用户
canal.instance.dbPassword=canal用户密码
:wq!

# 3 启动
# 切换到 /xxx/bin 目录下,执行 ./startup.sh 进行启动 (注意:前提需要有java8的环境配置)

# 4 看是否成功启动
# 切换到 /xxx/logs/canal 目录下 执行 cat canal.log
# 可以看到 the canal server is running now ...  # 成功
# 切换到 /xxx/logs/example 目录下 执行 cat example.log
# 可以看到 start successful  # 也代表启动成功

编写Canal客户端

在Canal项目中Redis有完整的代码。

hyperloglog

hyperloglog只统计一个集合不重复的元素的个数

pfadd  hel  1 3 5 7      # 添加元素   ,然后统计个数是:4
pfadd  he2  1 3 3 4 6 7  # 添加元素   ,然后统计个数是:5
pfcount hel              # 打印he1个数  ,结果是 4
pfmerge he新 he1 he1     # 添加元素,多个元素
pfcount he新             # 打印he新个数  ,结果是 6
# 适合做统计个数,并不保留结果,添加元素,添加客户端iP

系统常见的统计

  • 聚合统计(多个集合的交集、并集、差集)
  • 排序统计(展示最新的列表、排行榜,如频繁更新的,使用ZSET)
  • 二值统计(打开记录,只有0,1)
  • 基数统计(统计集合不重复元素个数)

名词解释:

  • UA:独立访客,IP地址,需要去重
  • PA:页面浏览量
  • DAU:日活,使用某个产品的用户数
  • MAU:月活

项目中的使用

@Resource
private RedisTemplate redisTemplate;

// 插入key
Long k = redisTemplate.opsForHyperLogLog().add("key名",);
// 获取key,基数统计
Long 个数 = redisTemplate.opsForHyperLogLog().size("key名");

GEO

用二维的经纬度表示,经度范围 (-180, 180],纬度范围 (-90, 90],只要我们确定一个点的经纬度就可以名取得他在地球的位置

geoadd key1 经度1 纬度1 “天安门” 经度2 纬度2 “天安门2”  # 添加经纬度坐标
ZRANGE key1 0 -1                                   # 获取全部的value值
# 如果返回值,有中文乱码,解决:redis -cli -a 123456 – raw (在外部,Linux命令行输入)
geopos key1  天安门 天安门2  # 返回经纬度
GEOHASH key1 天安门 天安门2  # 返回经纬度坐标的(base32编码)
GEODIST key1 天安门 故宫 km  # 返回两个位置之间的距离,km是单位:千米 或 m:米
GEORADIUS 以给定的经纬度为中心, 返回键包含的位置元素当中,与中心的距离不超过给定最大距离的所有位置元素
# 例子:GEORADIUS key1 114.12 39.2 10 km withcoord withhash count 10 desc
# key (现在的经纬度) 10 (km或m) withcoord(会打印hash值)withhash(返回经纬度坐标)count 10(返回10条)
GEORADIUSBYMEMBER 它这个输入范围元素 例:GEORADIUS key1 天安门 10 km ...

项目中的使用

@Autowired
private RedisTemplate redisTemplate;
// 添加坐标
redisTemplate.opsForGeo().add(Key, map);
// 获取坐标
List<Point> position = redisTemplate.opsForGeo().position(Key, "map里的某个key");
// 获取两个位置之间的距离, 单位:KILOMETERS 千米
Distance dist = redisTemplate.opsForGeo().distance(Key, "map里的某个key1", "map里的某个key2",
                RedisGeoCommands.DistanceUnit.KILOMETERS);
// 查找 根据某个坐标位置,显示附近有哪些,单位:KILOMETERS 千米 
Circle circle = new Circle(坐标x,坐标y, Metrics.KILOMETERS.getMultiplier());
// 查询范围条件
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeCoordinates().sortDescending().limit(10);
GeoResults<RedisGeoCommands.GeoLocation<String>> r = redisTemplate.opsForGeo().radius(Key, circle, args);
// 查找 根据某个坐标位置,显示附近有哪些
String member = "map里的某个key1";
RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().sortAscending().limit(10);
// 半径 1 公里内
Distance distance = new Distance(1, Metrics.KILOMETERS);
GeoResults<RedisGeoCommands.GeoLocation<String>> r = redisTemplate.opsForGeo().radius(Key, member, distance, args);

bitmap

由0和1状态表现得二进制位的bit数组,用于判断用户是否登录过,如钉钉打卡

SETBIT key offset value    # 将第offset的值设为value  value只能是0或1  offset 从0开始
GETBIT key offset          # 获得第offset位的值
STRLEN key                 # 得出占多少字节 超过8位后自己按照8位一组一byte再扩容
BITCOUNT key               # 得出该key里面含有几个1
BITOP and destKey key1 key2 # 对一个或多个 key 求逻辑并,并将结果保存到 destkey 
BITOP or destKey key1 key2  # 对一个或多个 key 求逻辑或,并将结果保存到 destkey 
BITOP XOR destKey key1 key2 # 对一个或多个 key 求逻辑异或,并将结果保存到 destkey 
BITOP NOT destKey key1 key2 # 对一个或多个 key 求逻辑非,并将结果保存到 destkey 

布隆过滤器

它是一种专门用来解决去重问题的高级数据结构,就是一个大型bit数组和多个哈希函数组成,用来快速判断集合中是否存在元素。

常用

getbit key名 值        # 得出结果是 0或者1 来判断是否存在

优点:高效查询,占用内存空间少

缺点:不能删除元素,不够精准,如果删除对象,需要重构过滤器

布隆过滤器不能解决不能删除问题,有了第三方插件布谷鸟过滤器

缓存预热

缓存预热就是系统上线,将一些数据加载到Redis中,避免高并发的时候,用户访问到数据库中,将一些数据进行初始化到Redis中。

缓存雪崩

同一时间,请求量大,访问Redis失败,大量访问到Mysql造成服务压力。

机器故障,Redis挂掉了,解决该问题,部署Redis集群;或者Redis中大量key 同时过期。解决该问题:

  • 将Key设置永不过期、或者不同的过期时间
  • 多缓存结合预防雪崩,本地缓存(ehcache )+Redis缓存
  • 服务降级,使用Hystrix 或者 sentinel 限流、降级
  • 人民币 - 云数据库Redis,由阿里云托管、数据丢失

缓存穿透

缓存穿透就是请求去查询一条数据,先查redis,redis没有,再查mysql,mysql里也没有,如果这样的大量的这样情况,会造成服务器压力。

解决:就是如果mysql也没有,把缺省值更新到redis中,下次查询走redis,不再查mysql;但这也也有缺点,遇到恶性攻击,需要布隆过滤器,Google布隆过滤器Guava可以解决。

// 1.引包
<dependency>
     <groupId>com.google.guava</groupId>
     <artifactId>guava</artifactId>
     <version>23.0</version>
 </dependency>
// 2.使用    
BloomFilter<Integer> bloom = BloomFilter.create(Funnels.integerFunnel(), 1000, 0.03);  
// 添加值
bloom.put();
// 获取值 存不存在
bloomFilter.mightContain();

缓存击穿

缓存击穿就是大量请求同时查询一个key时,然后这个key正好失效了,就会导致大量的请求去mysql查询,也是热点key突然都失效了,MySQL承受高并发量。

解决:对访问频繁的热点key,不设置过期时间;互斥更新,双检加锁;也可以使用差异失效时间解决,新建Redis,先更新缓存B再更新缓存A,查询列表,先查询缓存A,再查询缓存B,都没有再查询mysql。

分布式锁

Synchronized和Lock锁是本地单机锁,不适用分布式项目。使用单机锁会产生超卖问题。

我们一般使用Redis做分布式锁,加锁和解锁通过Lua脚本实现,并且支持可重入锁的特性,为了更加健全还需要进行监听实现自动续期。

但Redis如果集群,很难保证ACP特性,只能支持AP,其中一台RedisA机进行加锁,然后突然挂掉,从机B数据复制是异步的,从机B被升级为主,这个时候很难保证数据一致性,无法实现互斥的属性。

ZOOKEEPER集群,可以实现数据一致性,它可以保持CP,它底层必须主机和从机全部健康,并复制完成,才进行返回。不像Redis是异步的复制。

解决Redis分布式锁的单机故障:

需要部署多台Redis机器,保证机器数据为奇数,数量公司: N = 2X + 1 (N是最终部署机器数,X是容错机器数),并保证这些Redis都是独立的主机。

使用Redisson,底层通过Redlock算法实现的。

# 1.需要引入包:
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson</artifactId>
    <version>3.13.4</version>
</dependency>    
# 2.使用
@Autowired  private Redisson redisson;  
RLock redissonLock = redisson.getLock("xxRedisLock");
// 加锁
redissonLock.lock();
// 解锁
if (redissonLock.isLocked() && redissonLock.isHeldByCurrentThread()){
    redissonLock.unlock();
}

缓存淘汰

Redis内存慢了如何?

Redis默认的内存大小,在64位操作系统下,默认是0,表示不限制Redis内容使用;但在一般生产环境,我们设置物理内存的4分之3,如果设置内存大小,超过内存会爆OOM。

修改默认内存大小

// 方式1: 在redis配置文件里进行配置
maxmemory 104857600  (单位是byte)
// 方式2: 在redis执行命令行操作
config set maxmemory 104857600
// 查看
config get maxmemory

Redis过期key的删除策略

  • 立即删除,对CPU不好,用时间换空间

  • 惰性删除,对空间不好,用空间换时间

    // 在redis配置文件进行
    // 开启惰性删除, lazyfree-lazy-eviction=yes
    
  • 定期删除,每隔一段时间进行删除过期的key,定期的key是抽查的,会有漏网之鱼的出现。

缓存淘汰策略:

// 一共8种策略
noevication : 不会删除任何key,如果内存满了会爆异常error
allkeys-lru:  对所有key使用LRU算法进行删除,优先删除掉最近不经常使用的key (推荐)
volatie-lru :  对所有设置了过期时间的key使用LRU算法删除
allkeys-random :对所有key随机删除
volatie-random :对所有设置了过期时间的key随机删除
volatie-ttl :   删除马上过期的key
allkeys-lfu:    对所有key使用LFU算法进行删除
volatile-lfu:   对所有设置了过期时间的key使用LFU算法进行删除
// 在redis配置文件里进行
maxmemory-policy noevication

IO多路复用

用一个进程来处理大量的用户连接,同时处理多个客户端连接。

Redis处理多并发客户端连接:

Redis使用epoll实现IO多路复用,将连续信息事件放到队列中,谁有任务处理,比如举手表示,就交给管理者进行分配,到任务执行返回给客户端。类似监考答卷现场。

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
黑马Redis高级笔记提供了关于Redis的一些最佳实践和高级功能的知识点。其中包括了以下几个主题: 1. Redis键值设计:介绍了如何设计优雅的key结构,避免BigKey的出现,并选择恰当的数据类型来提高性能。 2. 批处理优化:介绍了在Redis集群中如何进行批处理优化,包括使用Pipeline来提高性能,并分享了在集群环境下的批处理技巧。 3. 服务端优化:介绍了一些Redis服务器的优化方法,包括持久化配置、慢查询优化、命令及安全配置以及内存配置。 4. 集群最佳实践:提供了关于Redis集群的最佳实践,包括如何查询集群状态以及散列插槽的相关知识。 这些笔记提供了关于如何优化Redis性能及使用Redis集群的一些实用技巧和建议。 <span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [黑马Redis笔记高级篇 | 分布式缓存](https://blog.csdn.net/2301_77450803/article/details/130547696)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [黑马Redis笔记高级篇 | Redis最佳实践](https://blog.csdn.net/2301_77450803/article/details/130659537)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值