redis6
redis6 作用
1、减少IO操作
2、使用内存 ,读取速度快
NoSQL Not Only SQL,不仅仅是 SQL。泛指 非关系型数据库。
- 不遵循SQL标准
- 不支持ACID
- 远超SQL的性能
使用场景
- 对数据高并发的操作
- 海量数据的读写
- 对数据有 高扩展性
不适用的场景
- 需要事务支持
- 基于SQL的结构化查询存储,处理复杂的关系,需要 即席 查询
用不着 SQL 和用了 SQL 也不行的情况,考虑NoSql
| NoSql 名字 | 特点 | |||
|---|---|---|---|---|
| Memcache | 不支持持久化 类型单一 作为 缓存数据库 辅助持久化的数据库 | |||
| Redis | 几乎覆盖 Memcache的绝大功能 支持持久化 支持多种数据结构 作为 缓存数据库 辅助持久化的数据库 | |||
| MongoDB | 高性能、开源、模式自由的 文档型数据库 数据都在内存中,内存不足,把不常用的数据保存到硬盘 支持 二进制数据 及 大型对象 | |||

redis 端口 由来

redis => 单线程 + IO 多路复用
Redis的多路复用
1
2
3
Redis能够在单线程的情况下处理高并发访问,主要得益于其I/O多路复用机制。I/O多路复用允许Redis使用一个主线程来监听多个连接,并在需要时进行处理,从而避免了线程切换的开销。
I/O多路复用的工作原理
I/O多路复用的核心思想是利用一个线程同时监听多个Socket连接,并在某个Socket可读、可写时得到通知,从而避免无效的等待,充分利用CPU资源。具体来说,I/O多路复用的实现方式包括select、poll和epoll。
Select和Poll
select和poll的实现方式是当用户点餐下单,服务员的灯亮了,服务员会去所有的顾客中进行一次询问,依次询问是不是您下的单,这样就会造成每次下单都会将所有的用户都遍历一遍。
Epoll
epoll则是一个提示,当灯泡亮后,服务员就能知道是哪一桌的用户下的单了,而不用一次次的去遍历。epoll在通知用户进程Socket就绪的同时,把已经就绪的Socket写入用户空间,直接处理即可。
Redis的网络模型
Redis的底层使用了I/O多路复用和事件派发机制来应对多个Socket请求。Redis的事件派发是其事件机制的核心部分,主要处理两类事件:文件事件和时间事件。
文件事件
文件事件是对套接字操作的抽象,每当一个套接字准备好执行连接应答、读取、写入、关闭等操作时,就会产生一个文件事件。文件事件处理器使用I/O多路复用程序来同时监听多个套接字,并根据套接字目前执行的任务来为套接字关联不同的事件处理器。
时间事件
时间事件是对定时操作的抽象,例如生成RDB文件和清除过期数据等任务。
Redis的多线程优化
在Redis 6.0之后,引入了多线程来提高I/O读写性能。主要实现思路是将主线程的I/O读写任务拆分给一组独立的线程去执行,这样就可以使多个Socket的读写并行化。
多线程的应用
命令请求处理器:使用多线程是因为多个Socket连接可能会同时发出请求数据,数据转换相对来说比较耗时。
命令回复处理器:使用多线程是为了提高给用户的响应效率,更好地提高性能。
通过这些机制,Redis能够在单线程的情况下,轻松应对高并发的访问。
: : : Redis 常用指令

| 命令 | 意义 |
|---|---|
| select (0-15) | 命令切换数据库 |
| dbsize | 查看当前数据库的key的数量 |
| flushdb | 清空当前库 |
| flushall | 通杀全部库 |
Redis数据类型
String 二进制安全的,最基本的数据类型,一个redis中字符串value最大可以是 512M。
String的数据结构是 简单的动态字符串,字符串 小于 1M,加倍现有空间。字符串 大于 1M ,每次 多扩 1M
| 命令 | 效果 | |||
|---|---|---|---|---|
| set | 重复设置,会覆盖之前的值 | |||
| append key value | 对key的key 追加 值(字符串拼接) | |||
| strlen key | 查看键 的长度 | |||
| setnx key value | 只要key不存在时,才能成功设置key的值 | |||
| incr | 将key中的数值 增加1 只能对数字操作,若为空,新增 为1 | |||
| decr | 将key中数值 减去1 ,若为空,则 为 -1 | |||
| mset key value key value | 同时设置多个key值 | |||
| mget key key | 获取多个key值 | |||
| mstnx key value key value | 同时设置一个或多个 key-value 值 原子性:只要 有一个key是设置之前有的。则本次操作都会失败 | |||
| getrange key 起始位置 结束位置 | 获取值的范围,类似java中 substring 下标从 0 开始 | |||
| setrange key 起始位置 value | 覆写 key 所存储的字符串值 | |||
| setex key 过期时间 value | 设置键值的同时 设置过期时间,单位 秒 | |||
| getset key value | 设置新值的同时,获取 原来的值 |

redis 操作是原子操作(不会被线程调度机制打断的操作)
List 列表 单键多值
简单的字符串列表按照插入的顺序排序。 底层是 双向链表。对两端的操作性能高,通过下标索引的操作中间的节点性能会较差
List 底层是 快速链表 :元素较少时 ,使用zipList(压缩列表) 一块连续的空间来存储。有很多 zipList时,使用链表进行连接(快速链表)。具有 快速的插入、删除性能,又不会有太大的空间冗余
| lpush/rpush key value value value | 从左边/右边 插入 一个 或 多值 |
| lpop/rpop key | 从左边/右边吐出一个值。 值在键在,值光键无 |
| lpoplpush key1 key2 | 从key1列表中吐出一个值,插到key2的列表左边 |
| lrange key 起始位置 结束位置 | 按照下标索引获得元素 (从左到右) 0 -1 表示 取出 所有 |
| lindex key index | 按照下标索引获得元素(从左到右) |
| linsert key before value newvalue | 在value后面插入newvalue插入值,相当于 新增一个元素 |
| lrem key n value | 从左边删除n个value(从左到右), 相当于 重复值 删除了其中几个 |
| lset key index value | 将列表key下标为index的值替换为value |
Zset string 类型的无序集合。底层是一个value为null的hash表。添加、删除、查找的 复杂度 都是 O(1)
| sadd key value value | 将一个或多个 member元素加入到集合key中,已经存在的将被忽略 |
| smembers key | 取出该集合的所有值 |
| sismember key value | 判断集合 key 是否含有 value 值, 1 有 0 没有 |
| scard key | 返回 该集合的元素个数 |
| srem key value value | 删除集合中的元素 |
| spop | 随机从集合中取出一个值 值 取出完 时,集合为 null |
| srandmember key n | 随机从集合中取出几个值 ,不会从集合中删除 |
| smove key1 key2 value | 把集合中的一个值从一个集合移动到另一个集合 |
| sinter key1 key2 | 返回两个集合的交集 |
| suniion key1 key2 | 返回两个集合的并集 |
| sdiff key1 key2 | 两个集合的差集,(key1 中有的,key2中没有的) |
Hash 是一个string类型的field和value的映射表。
| hset key field value | 给key集合中的field键 赋值 value |
| hget key1 field | 从key1集合field取出value |
| hmset key1 field1 value1 field2 value2 | 批量设置hash的值 |
| hexists key1 field | 查看哈希表key中 给定field是否存在 |
| hkeys key | 列出该集合中所有的fields |
| hvals key | 列出该集合所有的value |
| hincrby key field increment | 为哈希表 key中的field的值 加上增量 |
| hsetnx key | 将哈希表key中的field的值设置为 value, 且field不存在时 |
zset 底层使用了两个数据结构
hash :关联元素value和权重score,保障value的唯一性,可通过value找到相应的score值
跳跃表:给value排序,根据score的范围获取元素列表
| zadd key score1 value1 score2 value2 | 将一个或多个member元素及其score值加入到有序集key中 |
| zrange key start stiop | 返回有序集key中 下标在 start 和stop之间的元素 0 -1返回所有元素 zrange top 0 -1 withscores 返回值和分数到结果集 |
| zrangebyscore key min max 【 withscores】 | 返回有序集key中所有score值介于min和max之间的成员, 有序集按 score值 递增,从小到大排列 |
| zrevrangebyscore key max min | 同上,只是是从大到小排序 |
| zincrby key increment value | 为元素的score加上增量 |
| zrem key value | 删除该集合下指定的元素 |
| zcount key min max | 统计该集合,分数区间内的元素个数 |
| zrank key value | 返回改值在集合中排名 |
发布订阅
订阅消息
subscribe channel1 发布消息
publish channel1 nice 订阅消息端
127.0.0.1:6379> subscribe channel1
Reading messages... (press Ctrl-C to quit)
1) "subscribe"
2) "channel1"
3) (integer) 1
1) "message"
2) "channel1"
3) "nice" redis 配置文件
bind 127.0.0.1 表示 只允许 本机连接。 protected=mode no 支持远程访问 tcp-backlog 511 是一个队列,总和=未完成三次握手+已经完成3次握手 timeout 0 永不超时 tcp-keepalive 300 检查心跳时间 daemonize yes 后台启动 pidfile /var/run/redis_6379.pid 保存进程号文件 loglevel notice 日志级别 logfile "" 日志保存文件 database 16 默认0号库 
redis的淘汰策略

| setbit key offset value | 设置Bitmaps中某个偏移量的值(0 或1) |
| getbit key offset | 获取Bitmaps中某个偏移量的值 |
| bitcount key start end | 统计字符串从start字节到end字节比特值为1的数量 |
| bitop and (or/not/xor) destkey key | 复合操作,and 交集,or 并集,not 非,xor 异或 |
hyperloglog 做基数统计。花12kb内存,计算2^64个不同元素的基数
| pfadd key value | 加入数据 |
| pfcount key | 统计keys集合的基数个数 |
| pfmerge destkey sourcekey soucekey | 将一个或多个HLL合并后的结果存储在另一个HLL中 pfmerge pl(需建立的新的key) kc kc1 |
geospatial 地理位置、经纬度
geoadd key longitude latitude member geoadd chaina:city 121.47 31.23 shanghai | t添加 geo数据 |
| geopos key member | 获取经纬度 |
| geoist key member1 member2 [m/km/ft/mi] | 获取两个位置之间的直线距离 |
| georadius key longitude latitude radius [m/km/ft/mi] | 以给定的经纬度为中心,找出某一半径内的元素 |


事务执行
> multi
"OK"
> set p1 o1
"QUEUED"
> set p2 o2
"QUEUED"
> exec
1) "OK"
2) "OK"
> 放弃事务
> multi
"OK"
> set u1 i1
"QUEUED"
> set u2 i2
"QUEUED"
> discard
"OK" - 在加入队列中 ,出现错误时,exec 语句都会回滚。
- 如果是在 执行阶段某个命令出现错误,则只有报错的命令不会执行,其他命令都执行,不会回滚


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


数据备份
RDB:在指定的时间间隔内将内存中的数据集快照写入磁盘 ,文件名 dump.rdb

RDB缺点:最后一次持久化后的数据可能丢失

redis.conf 中
# 900秒(15分钟)内至少 1 个 key 值改变(则进行数据库保存--持久化)
save 900 1
# 300秒(5分钟)内至少 10 个 key 值改变(则进行数据库保存--持久化)
save 300 10
# 60秒(1分钟)内至少 10000 个 key 值改变(则进行数据库保存--持久化)
save 60 10000
# 持久化出现错误后,是否依然进行继续进行工作
stop-writes-on-bgsave-error yes
# 使用压缩 rdb 文件 yes:压缩,但是需要一些 cpu 的消耗;no:不压缩,需要更多的磁盘空间
rdbcompression yes
# 是否校验 rdb 文件,更有利于文件的容错性,但是在保存 rdb 文件的时候,会有大概 10% 的性能损耗
rdbchecksum yes
# dbfilenamerdb 文件名称
dbfilename dump.rdb
# dir 数据目录,数据库的写入会在这个目录。rdb、aof 文件也会写在这个目录
dir ./ AOF 默认不开启

# 是否以 append only 模式作为持久化方式,默认不开启aof模式,默认使用的是 rdb 方式持久化,这种方式在许多应用中已经足够用了
appendonly no
# appendfilename AOF 持久化的文件的名称
appendfilename "appendonly.aof"
# appendfsync aof 持久化策略的配置:
# no:不执行sync,由操作系统保证数据同步到磁盘(这时候操作系统自己同步数据),速度最快。
# always:每次修改都执行sync,以保证数据同步到磁盘,消耗性能。
# everysec:每秒执行一次sync,可能会导致丢失这1s数据。
appendfsync everysec AOF和RDB都开启,默认读取 AOF的数据
异常恢复;
AOF 文件损坏,通过 redis-check-aof--fix appendonly.aof进行恢复




若只做缓存使用,2种持久化 都可以 不配置。
redis 主从配置
查看主从状态
info replication 从机不能执行写操作,只能进行读取
从服务器宕机后,重新启动,角色是独立的主服务器。当重新加入主从时,会直接 全量复制 主服务器的数据。
主服务器 宕机后,从服务器角色不变,依旧是从服务器,当主服务器重启后,自然而然还是主服务器。

多级复制模式
一台从服务器A复制主服务器的数据,其他的从服务器就复制从服务器A的数据
优点:主服务器只同步到一台,减少性能损耗
缺点:若从服务器A宕机,则其他的服务器将同步不了主服务器的数据
反客为主
当主服务器宕机时,从服务器使用命令,可以升级为主服务器(需要手动操作)
slaveof no one 哨兵模式

新建 sentinel.conf
sentinel monitor mymasster 127.0.0.1 6379 1 启动 sentinel
redis-setinel setinel.conf 情景再现:当1主2从+哨兵模式,主服务器宕机后,从服务器中会选举一个从服务器成为新的主服务器,宕机的服务器角色转为从服务器,当宕机的服务器重启后,角色就是从服务器了。
该模式存在的问题 是复制延时


java代码

Redis 无中心化集群:任何一台服务器都可以作为集群的入口。
- 集群实现了对redis的水平扩容
- 集群中有部分节点失效或者无法进行通讯,集群也可以继续处理命令请求
redis集群时 使用的redis配置

集群的构建命令

集群构建成功字样

集群连接方式
redis-cli -c -p 6379 查看集群节点
cluster nodes 在集群中 同时 加 多个 set ,可能出错

解决方式 多个值,使用组的方式。集群会根据 组 的名字计算插槽,便不会报错

计算插槽位
cluster keyslot k1 查看插槽内的键值数量(注意的是 只有在当前连接客户端内的插槽可以查询,不能查询 其他集群节点的插槽键值数量)
cluster countkeysinslot 12706 返回count个slot中的键 cluster getkeysinslot 插槽位置 取出key的数量
cluster getkeysinslot 110 3 集群故障恢复

jedis操作集群

集群优点:
- 实现扩容
- 分摊压力
- 无中心配置相对简单
缺点:
- 多键操作不被较好(可以转换方式添加)支持
- 多键的Redis事务是不被支持的,lua脚本不支持
- 集群方案较晚,很多公司已经使用其他集群方案,代理或客户端分片的方案想要迁移redis-cluster,需要整体迁移,没有过渡,复杂度较大
缓存穿透
- 应用服务器压力大
- redis命中率低
- 一直查询数据库
原因
- redis查不到数据
- 出现很多非正常url访问
解决方案:
- 对空值缓存(简单应急方案):将查询为空的数据进行缓存,设置较短的过期时间,不超过5分钟
- 设置可访问的名单(白名单):使用bitmaps类型定义一个可访问的名单,名单id作为bitmaps的偏移量,每次访问和bitmaps里面的id进行比较,如果访问不在bitmaps中,,进行拦截,不许访问
- 采用布隆过滤器:
- 进行实时监控:当发现redis的命中率开始急剧下降,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务
缓存击穿
- 数据库访问压力瞬间增加
- redis里面没有出现大量的key过期
- redis正常运行
原因:
- redis某个key过期了,大量访问使用这个key
解决方案:
- 预先设置热门数据:在redis访问高峰之前,把一些热门数据提前存入redis中,加大这些热门数据key的过期时间
- 实时调整:现场监控哪些热门数据,实时调整key的过期时长
- 使用锁:
- 在缓存失效的时候,判断拿出来的值为空,不是立即去加载数据库
- 先使用缓存工具的某些带成功操作返回值的操作(如redis的setnx)
- 当操作返回成功时,在进行数据库加载,并回设缓存,最后删除mutex key
- 当操作返回失败,证明有线程在load db,当前线程睡眠一段时间在-再重试整个get缓存的方法
雪崩
- 数据库压力变大 服务器崩溃
原因:
- 在极少时间段内,查询大量key的集中过期情况
方案:
- 构建多级缓存架构:nginx缓存+redis缓存+其他缓存(ehcache等)
- 使用锁或队列:用加锁或者队列的方式来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求会落到底层存储系统上。不适用高并发
- 设置过期标志更新缓存:记录缓存数据是否过期(设置提前量),如果过期触发通知另外的线程在后台去更新实际key的缓存
- 将缓存失效时间分散开:我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会很低,很难引发集体失效时间
分布式锁
- 基于数据库实现分布式锁
- 基于缓存实现
- 基于zookeeper
性能:redis最高
可靠性:zookeeper最高
1、redis 分布式锁 使用 setnx实现,del key 删除锁。
2、锁一致没有释放,设置key过期,自动释放。
Q:上锁之后异常,无法设置过期时间
3、同时设置过期时间,同时上锁
set user 10 nx 13 java代码 分布式锁

设置过期时间
Q:高并发下 可能误删其他处理环节的锁
添加 uuid 防止误删

加锁使用uuid,释放前 先比对,是自己加锁的,则可以释放

Q:比较uuid通过,准备删除时异常,另一个程序已加锁,缺乏原子性 导致误删
使用lua脚本,进行原子操作

分布式锁特性:
- 互斥性
- 不会发生死锁
- 加锁、解锁是同一客户端
- 加锁、解锁必须具有原子性
ACL 权限控制

查看添加权限指令类别
acl cat 或
acl cat string 查看当前用户
acl whoami 添加 acl 用户
acl setuser lucy 添加 赋予权限的用户
acl setuser marry on >123 ~cached:* +get > auth marry 123
"OK"
> acl whoami
"NOPERM this user has no permissions to run the 'acl' command or its subcommand"
> get ji
"NOPERM this user has no permissions to access one of the keys used as arguments"
> get cached:33
(nil)
> redis6 IO多线程
redis的多线程部分是处理网络数据的读写和协议解析,执行命令仍是单线程
默认不开启,需要手动开启

6版本 rubby 集成到 redis-cli中。
- 本文标签: Java
- 本文链接: http://119.91.109.247:8443//article/161
- 版权声明: 本文由张亚东原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权