Redis总结

关于Redis的基础知识,我就不多说了。我再其他博客里面介绍了一些了。本博文主要汇总,思考总结一些Redis的一些使用场景,解决什么类型的问题。

主要内容有:

1.Redis基础:

  • 数据类型
  • 通用命令
  • Jedis

2.高级

  •  持久化
  •  redis.conf
  • 事务
  • 集群

3.应用

 

 

 

五种数据结构

可以做个类比:

 

 


 

1.关于数据类型,及其应用场景

String类型的数据结构 

1.基本操作:

set

get

delete  成功返回1

 

2.在set get之前加个m (multiple多个)

mset

mget

一次设置多个,一次取多个。

 

 

 

 

 

获取数据字符个数:

 

 

往字符串后面追加:原始数据存在就追加 不存在就新创建

 

 

但数据操作与多数据操作的选择问题:

set key value

mset key1 value1 key2 value2 .....

 

注意:

客户单发送指令消耗时间 到达之后执行消耗时间 结果返回消耗时间

但是: 指令多,返回的数据也会多,需要综合考量。发送时间长和执行时间,数据发送量等等权衡。

 

String类型数据的扩展操作:

场景1

 分表,防止主键重复问题,MySQL不能实现,通过redis实现。

使用:

加:incr key

 

 减:decr key:

一次增减一个自然单位长度(字符串是个纯数字)。

 

加:给定指定整数增加值:

 

 给定指定小数增加值:

增加: decr  减小 decrby

 

 

小结:

加法:

incry key  自然长度

incrby key 10 指定长度

incrbyfloat 小数

 

减法:

decr key

decrby key increment

 

注意:

string 作为数值操作:

  • string在redis内部存储默认是一个字符串,当遇到增加减少操作incr,decr时会抓成数值型进行计算。
  • redis所有的操作都是原子性的,采用单线程处理所有的业务,命令式一个一个执行的,因此无需考虑并发带来的影响。
  • 如果查出了redis上限范围,将报错。long.MAX_VALUE

 

总结应用场景: 主键生成策略,保证唯一性。

 

场景2

超级女声投票,1周之内。

设置数据具有指定声明周期 

setex tel 10 1   #值是10  时间是1s    注意:毫秒

 

 

setex tel 10 1   #值是10  时间是1ms   注意:秒

 

String类型数据操作的注意事项:

  • 数据操作不成功的反馈与数据正常操作之间的差异 :   成功1 失败0
  • 表示运行结果值: 3   3个

  数据查询不到时候: nil 等同于 null

 string最大存储量: 512M

 纯数字时候最大范围是 Long的最大值

 

string应用场景,主页高频信息控制。新浪大V粉丝数

 eg: user:id:234:fans  -> 234234    表名主键值属性名作为key

比如当心粉丝来了时候 直接使用redis的自增处理之

或者value 存放个Json数据,信息量比较大。

 

 

key的设置规范习惯:

 

 

 

 

hash类型;

 string 可以用来存储 JSON,这样就有个困惑。修改数据比较麻烦。

 

所以可以这么设计:

 

 

 

 进而改进为: 属性对应的值: key value的感觉

 

  • 新的存储需求: 对一系列存储的数据进行编组,方便管理,典型应用存储对象信息。(典型的存对象)
  • 需要的存储结构:一个存储空间报错多个键值对数据
  • hash类型: 底层使用哈希表结构实现数据存储

 

 

 注意: hash存储结构优化

  •  如果field数量较少,存储结构优化为类数组结构
  • 如果field数量较多,存储结构使用HashMap结构

 

基本操作(单个):(光有key不行,还得有field)

  •  添加、修改数据: hset  key field  value
  • 获取数 hget key field 、hgetall key
  • 删除数据 hdel key

 

 

 

基本操作(多个)

  •  修改多个数据: hmset field1 value1 field2 value2
  •  获取多个数据  hmget key field1 field2
  •  获取哈希表中字段数量: hlen key
  •  获取哈希表中是否存在指定字段 hexists key field

符合redis的脾气,有就改,没有就创建:

 

 

扩展用法:

  • 获取哈希表中所有的字段名或字段值: hkeys key     hvals key
  • 设置指定字段的数值数据增加指定范围的值

  

 

 

 

还可以自增:

 

 

注意事项:

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

 

·hash业务场景1:购物车

 一个购物车里面,好多商品,对应一个Id。

 

解决方案:

  •  以客户id为key,每位客户创建一个hash存储结构存储对应的购物车信息
  •  将商品编号作为field,购买数量作为value进行存储
  •  添加商品:追加全新的filed与value
  •  浏览:遍历hash
  •  更改数量: 自增/自检,设置value值
  •  删除商品: 删除field
  •  清空: 删除key

 

001用户的 购物车 g01  g02  g03

 

没有加速购物车呈现,只有商品ID和数量。

 

  • 每条购物车中的商品记录保存成两条field
  • field1 专用于保存购买数量:

            命名格式: 商品id:nums

            保存数量:  数值

  • field2专用于保存购物车中显示的信息,包含文字描述,图片地址,所属商家信息等    

    命名格式: 商品id:info

            保存数据:json

   这样数量和信息(json)都有了  图片、地址、描述信息提取出来(但是商品信息也是会重复的!独立起起来)

 

 

 独立的hash: 专门用来保存商品的hash

 

使用: hsetnx key  field value

没有就操作,有就不操作了:

 

 

·hash业务场景2:

  抢购

  不同的商品数量存储和销售数量减少1、20

 

 

尽量不要把redis放在redis,比如超卖,是否有值等等。

注意:redis应用于抢购,限购类,限量发放优惠券,激活码等业务的数据存储设计。

 

区分下用string存对象和hash存对象的问题:

  •  string存储对象(json)与hash存储对象。 一个是全局,一个是局部。根据情况灵活设计

 

list:

list类型

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

 

操作:

  • 添加/修改数据      lpush key value1                rpush key value1
  • 获取数据              lrange key start  stop( 注意start 和 stop为索引,可以为负数 比如 stop为-1  则为倒数第二个)         lindex key index        llen key      
  • 获取并移除数据   lpop key                              rpop key

 

 右进,左查

 

 

反着查(不知道添加了多少个,还是想看全部的情况)

 

 看倒数第二:

 

 

看每个索引上的数据:

 

 

重点看下,怎么进怎么出:

 

 

list类型数据扩展操作:

  •  规定时间内获取并移除数据  blpop key1  timeout       brpop key1 timeout     注释: b代表阻塞的意思  

 

存值然后取值的常规操作:

 

 

可以打开两个客户端:

客户端a

 

客户端b

 

 

 

在b没有放入值时候,a一直等待,等待不了就nil,有结果就直接返回。

 

也可以这么使用: a等待一堆list ,哪个有值了就取出来哪个。即为: blpop list1 list2 list3 timeout

 

业务场景:

 微信朋友圈点赞,要求按照点赞顺序显示点赞好友信息,取消点赞,删除对应好友信息。 lrem key count value  解释:   从左边删除哪里的 第几个(因为有序的哈) 删除什么

 有顺序,又多个,自然redis的list数据结构了

 

 

 

删除的操作: 注意方向 数量 内容

 

 

总结: redis应用具有操作先后顺序的数据控制

 

list类型数据操作足以事项:

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

 

业务场景:

新浪微博中个人用户的关注猎豹需要按照用户的关注顺序进行展示,粉丝列表需要将最近关注的粉丝列放在最前面。

新闻,资讯放到最前面,按照时间发生展示?

企业运营过程中,系统产生的大量运营数据,如何包装多态服务器操作日志的统一顺序输出?

 

解决方案

  •   依赖lis的数据具有顺序的特征对信息进行管理
  •   使用队列模型解决多路信息汇总合并的问题
  •   使用栈模型解决最新消息的问题

 

日志消息聚集,顺序:

 

 

统一查询: lrange logs 0 -1

 

set类型

  •  新的存储需求: 存储大量的数,在查询方面提供更高的效率。
  •  需要的存储结构:能够保存大量的数据,高效的内部存储机制,便于查询。

 

 

 

hash数据类型,我们用的是value,如果我们用key呢? 何尝不可?

于是,变形,右边全部放空:

 

 

 于是:

 

 

 

set类型数据的基本操作:

  • 添加数据 : sadd key member1  [member2]
  • 获取全部数据: smembers  key
  • 删除数据: srem key member1 [member2]

 

 set部分左右,hash结构,没有索引,操作就比较简单了。

 

 

 

  • 获取集合数据总量: scard key
  • 判断集合中是否包含指定数据: sismember key member

 

 

 

应用场景:

 每位用户首次使用今日头条是会设置3项爱好的内容,但是后期为了增加用户的活跃度,兴趣点,必须让用户对其他信息类别逐渐产生兴趣,增加客户留存度,如何实现?

 

解决方案:

 随机获取集合中指定数量的数据

  srandmember key count   (原集合数据不变)

随机获取集合中的某个数据并将该数据移除集合

 spop key  (原集合数据减少)

 

随机获取:

 

 

 随机剔除:

 

 

 

 

 

比如任务分配: 随机挑选一个去处理。

 

总结: redis应用于随机推荐类信息检索,例如热点歌单推荐,热点新闻推荐等等

 

应用场景:

社交软件为了存进用户间的交流(共同关注),微博增加用户热度(有多少个朋友关注),qq推荐好友, 

 

set类型数据的扩展操作:

 解决方案:

  •  求两个集合的交,并,差集:  

  sinter key1  [key2]   

  sunion key1 [key2]

  sdiff  key1  [key2]

 

  • 求两个集合的并,交、差集并存储到指定集合中

sinterstore destination key1  [key2]

sunterstore destination key1  [key2]

sdiffstore destination key1  [key2]

  • 将指定数据从原始集合汇总移动到目标集合中

smove source destination member

 

 

 

移动w1 从u2移动到u1

 

 

 

 

总结:

  •  redis应用于同类信息的关联搜索,二度关联搜索,深度关联搜索
  •  显示共同关注(一度)
  •  显示共同好友  (一度)
  •  由用户A出发,获取到好友用户B的好友信息列表(一度)
  •  由用户A出发,获取到好友用户B的购物清单列表(二度)
  •  由用户A出发,获取到好友用户B的游戏充值列表(二度)

 

注意: set类型数据操作的注意事项

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

 

场景:

120000员工,内部OA系统中具有800个角色,3000个业务操作,23400多种数据,每个员工具有一个或者多个角色,如何快速进行业务操作的权限校验?

 

 

 解决方案:

  • 依赖set集合数据不重复的特征,依赖set集合hash存储结构特征完成数据过滤与快速查询
  • 依据用户id获取用户所有角色
  • 依据用户所有角色获取用户所有操作权限放入set集合
  • 依据用户所有角色获取用户所有数据全选放入set集合

001 和 002用户分配权限:

 

 

 

合并:

 

 

 

校验方式1:代码看是不是在这个集合里面(推荐这个,业务和数据分开)

 

 

 

校验方式2:sismember指令

 

 

  

注意 redis提供基础数还是提供校验结果,推荐校验结果。

 

set应用场景:

公司对旗下新的网站做推广,统计网站pv(访问量),UV(独立访客),IP(独立IP)

注:

 pv:网站备份昂文次数,可以通过刷新网页提高访问量

 uv:  网站被不同用户访问的次数,可以通过cookie统计访问量,相同用户切换ip地址,uv不变

 ip:   网站被不用IP地址访问的总次数,可以通过ip地址统计访问量,相同ip不同用户访问,ip地址不变

 

解决方案:

  • 利用set结婚的数据去重特性,记录各种访问数据
  • 建立string类型数据,利用incr统计日访问量(pv)
  • 建立set模型,记录不同cookie数量(uv)
  • 建立set模型,记录不同IP数量(IP)

 

重复的进不来了: 快速去重

 

 

 

业务场景: 

黑名单

  爬虫技术,快速获取信息。克隆别人的数据到自己库里。爬虫的访问量不算的。

 

解决方案:

  •  基于经营战略设定问题用户发现,鉴别规则
  •  周期性更新满足规则的用户黑名单,加入set集合
  •  用户行为信息达到后与黑名单进行比对,确认行为去向
  •  黑名单过滤ip地址: 应用于开放游客访问权限的信息源
  •  黑名单过滤设备信息: 应用于限定访问设备的信息源
  •  黑名单过滤用户: 应用于基于访问权限的信息源

总结: 基于redis 可以做黑白名单的控制

 

 sorted_set 类型

  •   新的存储需求: 数据排序有利于数据的有效展示,需要提供一种可以根据自身特征进行排序的方式。
  •   需要的存储结构: 新的存储模型,可以保存可排序的数据
  •   sorted_set类型: 在set的存储结构基础上添加可排序字段

 

 

 对应的基本操作:

  • 添加数据
    • zadd  key score1 member1 [score2 member2]
  • 获取全部数据
    • zrange key start stop 
    • zrevrange key start stop
  • 删除数据
    • zrem key member 

存值和获取(已经排序了的)

 

 

 获取详情的: 数据和排序字段值

 

 

 

 反向:由小到大

 

 

 

删除操作:

 

 

 

  • 按条件获取数据
    • zrangebyscore key min max
    • zrevrangebyscore key max min
  • 条件删除数据  
    • zremrangebyrank key start stop
    • zremrangebyscore key min max

 

 

 

 类似于limit的操作:

 

 

 

 

删除:60 ~ 70

 

 

 

通过索引删除:

删除索引 0 ~1之间的

 

 

 

注意:

  •  min与max用于限定查询的搜索条件
  • start与stop用于限定查询范围,作用于索引,表示开始和结束索引
  •  offset与count用于限定查询范围,作用于查询结果,表示开始位置和数据总量

 

  • 获取集合数据总量
    • zcard key
    • zcount key min max
  • 结合交、并操作
    • zinterstore destination numkeys key [key ...]
    • zunionstore destination numkeys key [key ...]

 

操作:

127.0.0.1:6379> zcard scores
(integer) 0
127.0.0.1:6379> zcount scores 99 200
(integer) 0
127.0.0.1:6379> 

 

存储个集合数据:

127.0.0.1:6379> zcard scores
(integer) 0
127.0.0.1:6379> zcount scores 99 200
(integer) 0
127.0.0.1:6379> 

操作:三个注意和后面的集合数量对应

 

 

 

合并后:有相同属性的值都累加到一起了。除了交还有求和

 

 

 

 

合并完毕之后,可以进行求最大最小值等操作

127.0.0.1:6379> zinterstore sss 3 s1 s2 s3 aggregate max
(integer) 2

 

业务场景: 榜单类的。除了数量,还要有排序。 sorted_set刚好有排序功能

票选“快女”前十。

各类资源网站top10

聊天室活跃度

游戏好友亲密度

 

解决方案:

  • 获取数据对应的索引(排名)
    • zrank key member
    • zrevrank key member
  • score值获取与修改
    • zscore key member 
    • zincrby key increment menber

 

存放电影排行榜,并且获取排名(索引)

127.0.0.1:6379> zinterstore sss 3 s1 s2 s3 aggregate max
(integer) 2
127.0.0.1:6379> zadd moives 143 aa 97 bb 201 cc
(integer) 3
127.0.0.1:6379> zrank moives bb
(integer) 0
127.0.0.1:6379> zrevrank moives bb
(integer) 2

 

获取值:

127.0.0.1:6379> zscore moives aa
"143"

又有3人来投票了:

127.0.0.1:6379> zincrby movies 1 aa
"4"
127.0.0.1:6379> zscore movies aa
"4"

 

各种投票结束后,可以合并下看看~

 

注意:scored_set

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

 

业务场景:

有期会员,网盘使用加速

 

解决方案:

  •  对于基于时间线氙灯的任务处理,将处理时间记录为score值,利用排序功能邠处理的先后顺序。
  • 记录下一个要处理的时间,当到期后处理对应任务,移除redis总的记录,并记录下一个要处理的时间。
  •  当新任务加入时候,判定并更新当前下一个要处理的任务时间。
  •  为提升sorted_set的性能,通常将任务根据特征存储成若干个sorted_sorted. 例如:1小时内,1天内,周内,月内,季内,年度等,将即将操作的若干个任务纳入到1小时内处理的队列中。

 

腾讯视频的会员模拟:

127.0.0.1:6379> zadd ts 1609785 uid:001
(integer) 1
127.0.0.1:6379> zadd ts 1609235 uid:002
(integer) 1
127.0.0.1:6379> zadd ts 1609135 uid:003
(integer) 1

查看:

127.0.0.1:6379> zrange ts 0 -1 withscores
1) "uid:003"
2) "1609135"
3) "uid:002"
4) "1609235"
5) "uid:001"
6) "1609785"

到期期间一目了然,到期就移出去。

记录的是下一个被提醒的用户,对应的提醒时间。

 

redis中获取当前系统时间:

127.0.0.1:6379> time
1) "1575736433"
2) "275262"

 

总结: redis应用于定时任务顺序管理或任务过期管理

 

 应用场景:

任务/消息权重设定应用

  当任务或者消息等待处理,形成了任务队列或消息队列时,对于高优先级的任务要保障对其优先处理,如何实现任务权重管理。

 

127.0.0.1:6379> zadd tasks 4 order:id:005
(integer) 1
127.0.0.1:6379> zadd tasks 1 order:id:023
(integer) 1
127.0.0.1:6379> zadd tasks 9 order:id:012
(integer) 1
127.0.0.1:6379> zrevrange tasks 0 -1 withscores
1) "order:id:012"
2) "9"
3) "order:id:005"
4) "4"
5) "order:id:023"
6) "1"

移除掉:

127.0.0.1:6379> zrevrange tasks 0 0
1) "order:id:012"
127.0.0.1:6379> zrem tasks order:id:023
(integer) 1
127.0.0.1:6379> zrevrange tasks 0 -1 withscores
1) "order:id:012"
2) "9"
3) "order:id:005"
4) "4"

 

  • 对于带有权重的任务,优先处理权重高的任务,采用score记录权重即可多条件任务权重设定
  •  如果权重条件过多时候,需要对排序score值进行处理,保障score值能够兼容2条件或者多条件。例如外贸订单优先于国内订单,总裁订单优先于员工订单,经理订单优先于员工订单。
  •  因score长度受限,需要对数据进行阶段处理,尤其是时间设置为小时或者分钟级即可(折算后)
  •  先设定订单级别,后设定订单发起角色类别,整体score长度必须是统一的,不足位补0.第一排序规则首位不得是0。
127.0.0.1:6379> zadd tt 102004 order:id:1
(integer) 1
127.0.0.1:6379> zadd tt 102008 order:id:2
(integer) 1
127.0.0.1:6379> zrevrange tt 0 -1 withscores
1) "order:id:2"
2) "102008"
3) "order:id:1"
4) "102004" 

 正序看就行:

127.0.0.1:6379> zrange tt 0 -1 withscores
1) "order:id:1"
2) "102004"
3) "order:id:2"
4) "102008"

 

 

综合经验总结:

限定每个用户每分钟最多发起10次调用。

 

解决方案:

  •  设计计数器,记录调用次数,用于,通知业务执行次数。以用户id作为key,使用次数作为value
  •  在调用前获取次数,判断是否超过限定次数。不超过,每次调用+1, 业务调用失败,计数-1
  •  为计数器设置生命周期指定周期,例如1秒/分钟,自动清除周期内使用次数。

 

 

 

 指令模拟:

127.0.0.1:6379> get 415
(nil)
127.0.0.1:6379> setex 415 60 1
OK
127.0.0.1:6379> get 415
"1"
127.0.0.1:6379> incr 415
(integer) 2
127.0.0.1:6379> incr 415
(integer) 3
127.0.0.1:6379> 

 

解决方案改良:

  •  取消最大值的判定,利用incr操作超过最大值抛出异常的形式替代每次判断是否大于最大值
  •  判断是否为nil:是,设置max次数。 不是,计数+1, 业务调用失败,计数-1
  •  遇到异常即+操作超过上限,视为使用达到上限。用上限异常来搞定判断。

 

 

 

总结: rdis应用于限时按次结算的服务控制

 

应用场景:

微信接受消息后,会默认将最近接受的消息置顶,当多个好友及关注的订阅号同时发送消息时,该排序会不停的进行交替。

 

 解决方案:

  •  依赖list的数据就有顺序的特征对消息进行管理,将list结构作为栈使用。
  •  对置顶与普通会话分别创建独立的list分别管理
  •  当某个list中接收到用户消息后,将消息发送放的id从list的一侧加入list(设定为左侧)
  •  多个相同id发出的消息反复入栈会出现问题,在入栈之前无论是否具有当前id对应的消息,先删除对应id
  •  推送消息时先推送指定会话list,再推送普通会话list,推送完成的list清除所有数据
  •  消息的数量,也就是微信用户对话数量采用计数器的思想另行记录,伴随list操作同步更新。

模拟下:

127.0.0.1:6379> lrem 100 1 200
(integer) 0
127.0.0.1:6379> lpush 100 200
(integer) 1
127.0.0.1:6379> lrem 100 1 300
(integer) 0
127.0.0.1:6379> lpush 100 300
(integer) 2
127.0.0.1:6379> lrem 100 1 400
(integer) 0
127.0.0.1:6379> lpush 100 400
(integer) 3
127.0.0.1:6379> lrem 100 1 200
(integer) 1
127.0.0.1:6379> lpush 100 200
(integer) 3
127.0.0.1:6379> lrem 100 1 300
(integer) 1
127.0.0.1:6379> lpush 100 300
(integer) 3
127.0.0.1:6379> lrange 100 0 -1
1) "300"
2) "200"
3) "400"

 

总计: redis应用于基于时间顺序的数据操作,而不关注具体时间。

 

 


 

key特征:

 key是一个字符串,通过key获取redis中保存的数据。

 

key应该设计的操作:

 

1.对key自身状态的操作: 例如: 删除,判定存在,获取类型等

127.0.0.1:6379> set str str
OK
127.0.0.1:6379> hset hash1 hash1 hash1 
(integer) 0
127.0.0.1:6379> lpush list1 list1
(integer) 5
127.0.0.1:6379> sadd set1 set1
(integer) 0
127.0.0.1:6379> zadd zset1 1 zset1
(integer) 0
127.0.0.1:6379> type zset1 
zset
127.0.0.1:6379> type str
string
127.0.0.1:6379> type hash1
hash
127.0.0.1:6379> exists str
(integer) 1
127.0.0.1:6379> del zset1
(integer) 1
127.0.0.1:6379> del zset1
(integer) 0
127.0.0.1:6379> exists zset1
(integer) 0

2.对key有效性控制的操作: 例如: 有效期设定,判定是否有效,有效状态的切换

  • 为指定key设置有效期
    • pexpire key milliseconds
    • expireat key timestamp
    • pexpireat key milliseconds-timesamp
    • expire key seconds

   
      获取key的有效时间

    • ttl key
    • pptl key

     

   切换key从时效性转换为永久性

    • persist key

 

127.0.0.1:6379> set str str
OK
127.0.0.1:6379> lpush list1 list1
(integer) 6
127.0.0.1:6379> lpush list1 list2
(integer) 7
127.0.0.1:6379> expire str 3
(integer) 1
127.0.0.1:6379> get str
"str"
127.0.0.1:6379> get str
(nil)

查看剩余时间: -2 代表key不存在。 key存在但是没有剩余时间则返回-1

127.0.0.1:6379> ttl list1
(integer) 25
127.0.0.1:6379> ttl list1
(integer) -2
127.0.0.1:6379> expire list2 60
(integer) 1
127.0.0.1:6379> persist list2
(integer) 1
127.0.0.1:6379> ttl list2
(integer) -1

 

 

3.对key快速查询操作,例如:按指定策略查询key

 查询key 

   key pattern         * 匹配任意数量的任意字符      ?配合一个任意符号     [] 匹配一个指定符号

127.0.0.1:6379> keys *
 1) "p01"
 2) "ss"
 3) "b"
 4) "name"
 5) "s3"
 6) "tt"

 

  为key改名

   rename  key newkey

   renamenx key newkey

 

 对所有key排序

     sort

 

 其他key通用操作: help @generic

 

 

Jedis相关


 

pom:

  <!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.0.0</version>
        </dependency>

test api:

    @Test
    public void test1(){
        Jedis jedis = new Jedis("192.168.91.1", 6379);
//        jedis.set("name", "toov5");
        String name = jedis.get("name");
        System.out.println(name);
        jedis.close();
    }

    //操作list
    @Test
    public void test2(){
        Jedis jedis = new Jedis("192.168.91.1", 6379);

        jedis.lpush("list1", "a", "b", "c");
        jedis.rpush("list1","x");

        List<String> list1 = jedis.lrange("list1", 0, -1);
        for (String s : list1){
            System.out.println(s);
        }
        //查长度
        System.out.println(jedis.llen("list1"));

        //存储哈希
        jedis.hset("hash1", "a1", "a1");
        jedis.hset("hash1", "a2", "a2");
        jedis.hset("hash1", "a3", "a2");

        Map<String, String> hash1 = jedis.hgetAll("hash1");
        System.out.println(hash1);
        //查看长度
        System.out.println(jedis.hlen("hash1"));

        jedis.close();
    }

 

案例: 每分钟调用次数的限制。

public class Service {

    //控制单元
    public void service(String userName){
        Jedis jedis = new Jedis("192.168.91.1", 6379);
        String user = jedis.get("com" + userName);
        try {
        if (null == user){
            //不存在
            jedis.setex("com" + userName, 20, Long.MAX_VALUE - 10+ "");
        }else {
                // 存在
            Long incr = jedis.incr("com" + userName);
            //调用业务
                business(userName,10 - (Long.MAX_VALUE - incr));
            }
        }catch (JedisDataException e){
            System.out.println("次数到达限制,请充值");
            return;
        }finally {
            jedis.close();
        }
    }

    public void business(String user, Long incr){
        System.out.println(user + "业务操作执行"+ incr + "次");
    }
}

class MyThread extends Thread{
    Service sc = new Service();
    @Override
    public void run() {
        while (true){
            sc.service("会员一级");
            try {
                Thread.sleep(1000L);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

        }
    }
}

class Main{
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
    }

 

 

 

 到时了之后,会从0开始计数了又。

 

public class Service {

    private String userName;
    private Integer times;

    public Service(String name, Integer times){
        this.userName = name;
        this.times = times;
    }

    //控制单元
    public void service(){
        Jedis jedis = new Jedis("192.168.91.1", 6379);
        String user = jedis.get("com" + userName);
        try {
        if (null == user){
            //不存在
            jedis.setex("com" + userName, 20, Long.MAX_VALUE - times+ "");
        }else {
                // 存在
            Long incr = jedis.incr("com" + userName);
            //调用业务
            business(userName,times - (Long.MAX_VALUE - incr));
            }
        }catch (JedisDataException e){
            System.out.println(userName + "次数到达限制,请充值");
            return;
        }finally {
            jedis.close();
        }
    }

    public void business(String user, Long incr){
        System.out.println(user + "业务操作执行"+ incr + "次");
    }
}

class MyThread extends Thread{

    Service sc;
    public MyThread(String name, int times){
     sc = new Service(name, times);
    }
    @Override
    public void run() {
        while (true){
            sc.service();
            try {
                Thread.sleep(1000L);
            }catch (InterruptedException e){
                e.printStackTrace();
            }

        }
    }
}

class Main{
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("普通用户", 3);
        mt1.start();

        MyThread mt2 = new MyThread("vip用户", 6);
        mt2.start();
    }
}

 

 

 线程模拟多用户。

 

 

Jedis工具类:


 

 

注JedisPoll:  Jedis连接池。

 

public class JedisUtils {

    private static JedisPool jedisPool;
    private static String host;
    private static int port;
    private static int maxTotal;
    private static int maxIdle;

    static {
        //加载配置文件
        ResourceBundle resourceBundle = ResourceBundle.getBundle("redis");
        host = resourceBundle.getString("redis.host");
        port = Integer.parseInt(resourceBundle.getString("redis.port"));
        maxTotal = Integer.parseInt(resourceBundle.getString("redis.maxTotal"));
        maxIdle = Integer.parseInt(resourceBundle.getString("redis.maxIdle"));


        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        //最大连接数
        jedisPoolConfig.setMaxTotal(30);
        //活动连接数
        jedisPoolConfig.setMaxIdle(10);
        String host = "192.168.91.1";
        int port = 6379;
        jedisPool = new JedisPool(jedisPoolConfig, host, port);
    }

    public static Jedis getJedis(){
        return jedisPool.getResource();
    }

}

 

 

关于Redis持久化。

数据持久化的套路: 1.持久化数据(数据快照)。 2.持久化指令(操作的过程,类似于日志)

 

RDB启动方式  ---save指令

手动执行一次保存操作,会生成rdb文件。保存当前快照信息。

创建一个data目录:

 /home/redis/data 

 

修改启动配置文件:

日志生成的目录:

 

 

 

 

 

 

 日志文件名字:便于查阅

 

 

 

关于save指令相关配置:

  • dbfilename :设置快照的文件名,默认是 dump.rdb         经验: 通常设置为dump-端口号.rdb
  • dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。默认是和当前配置文件保存在同一目录。    经验:通常设置成存储空间较大的目录中,目录名称为data
  • rdbcompression ;默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。如果是的话,redis会采用LZF算法进行压缩。如果你不想消耗CPU来进行压缩的话,可以设置为关闭此功能,但是存储在磁盘上的快照会比较大。     
  • rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。

 

 

 

 

 

 

 

 

 此时断电,数据会保存下来。启动时候会加载。

 

注意Redis的单线程执行:

 

 

 

Redis的任务序列:

 

 

 save指令的执行会阻塞当前Redis服务器,知道当前RDB过程完成为止,有可能会造成长时间阻塞,线上环境不建议使用。

总结:数据量过大时候,单线程执行方式造成效率低的问题。

解决方案: 后台执行。

Redis客户端发起指令,redis服务器控制指令执行。合理时间去执行。保存数据。 此时RDB启动方式指令bgsave。租用:说动启动后台保存操作,但是不立即执行。

 

 

 

指令执行原理:

 

 

 

其实是通过系统fork的子进程去实现完成的。

查看下日志:

redis_6379.log

 

 

 

注意: bgsave命令式针对save阻塞问题做的优化。Redis内部所有涉及到RDB擦欧洲哦都是采用bgsave的方式,save命令可以放弃使用。

 

关于配置:

save 和 bgsave都是RDB形式,所以可以共用。

bgsave: stop-writes-on-bgsave-error yes   # 后台存储过程中如果出现错误现象,是否停止保存操作。经验:通常默认开启。

 

问题: 反复执行保存指令,忘记了,或者不知道数据产生了多少变化,什么时候保存问题。

解决方案:

自动执行配置:  save second  changes

参数:(单位时间内,达标了就去执行)

    second: 监控范围

    changes: 监控key的变化量

位置: 

    在cof文件进行配置

 

Redis基于某条件,满足时候,保存数据。 满足限定时间范围内key的变化数量达到指定数量即进行持久化。

每10s,2个发生变化就执行保存

 

 

 

关闭Redis: ps aux|grep redis    然后 kill

 

连续set 两个key值:

 

 

 查看日志:指令: ll

 

 

 会发生变化

 

save配置原理:

 

 

 注意:

  • save配置要根据实际业务情况进行设置,拼读过高或过低都会出现性能问题,结果可能是灾难性的  (上述案例2个key同步一次)
  • save配置中对于second与changes设置通常具有互补对应关系,尽量不要设置成包含性关系。  比如前面second小的,后面大
  • save配置启动后执行的是bgsave操作

 

RDB三种启动方式对比:

 

 

 

 

 

 

其他的RDB启动方式,(不太常用)的:

  • 全量复制
  • 服务器运行过程汇总启动:  debug reload
  • 关闭服务器时指定保存数据   shutdown save

 

 

RDB优点:

  • RDB是一个紧凑压缩的二进制文件,存储效率高。
  • RDB内部存储的是redis在某个时间点的数据快照,非常适用于数据备份,全量复制等场景。
  • RDB回复数的速度要比AOF块很多
  • 应用: 服务器中每X小时执行bgsave备份,并将RDB文件拷贝到远程机器中,用于灾难恢复。

RDB缺点:

  • RDB方式无论是执行指令还是利用配置,无法做到实时持久化,具有较大的可能性丢失数据。
  • bgsave指令每次运行要执行fork操作创建子进程,要消耗掉一些性能。
  • Redis的众多版本中未进行RDB文件格式的版本统一,有可能出现各种版本服务器之间数格式无法兼容情况。

 


 

对于AOF存储的弊端:

  • 存储数据量大,效率较低。  

           基于快照思想,每次读写都是全部数据,当数数据量巨大时,效率非常低。

  • 大量数据下的IO性能较低    
  • 基于Fork创建子进程,内存产生额外消耗。
  • 宕机带来数据丢失风险

 

解决思路(AOF的工作思想):

  • 不写全数据,仅记录部分数据
  • 该记录数据为记录操作过程
  • 对所有操作均进行记录,排除丢失数据的风险。

 

AOF概念:

AOF(append only file)持久化:以独立日志的方式记录每次写命令,重启时再重新执行AOF文件中的命令达到恢复数据的目的。与RDB相比可以简单描述为改记录数据位记录数据产生的过程。

AOF的主要作用是解决了数据持久化的实时性,目前已经是Redis持久化的主流方式。

 

AOF写数据过程:

 

 

 AOF写数据三种策略

  • always(每次): 首次写入操作均同步到AOF文件中,数据零误差,性能较低。
  • everysec(每秒): 控制了IO的速度,如果丢数据,丢一秒的数据。数据准确性高,性能高。
  • no(系统控制): 由操作系统控制每次同步到AOF文件的周期,整体过程不可控

 

AOF功能开启:

  • 配置开关:  appendonly yes
  • 配置策略: appendfsync always | everysec | no
  • 作用: AOF写数据策略

对conf开启支持,配置策略

 

 

启动后:

执行: set name toov5

 

总结: always 每执行一次修数据的指令,都记录下来。 (get不会记录的)

 

其他的配置:

1. appendfilename filename  :  配置文件名

2.dir: 配置文件地址

 

 

对于连续重复的命令:

 

 

 AOF重写(整理的一个过程):

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

 

AOF重写作用

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

 

AOF重写规则:

1、进程内已经超时的数据不在写入文件

2、旧的AOF文件含有无效命令。重写使用进程内数据直接和生成,这样新的的AOF文件爱只保留最终数据的写入命令。 

      如: del key1、 hedl key2、 srem key3、 set key4 111、 set key4 222等

3、多条写入命令可以合并成一个

     如:lpush list1 a、 lpush list1 b、 lpush list1 c 可以转换为: lpush list1 a b c. 

    为了防止多条命令过大造成客户端缓冲溢出,对于list、set、hash、zset等类型操作,以64各元素为界限

 

AOF重写方式:

  • 手动重写:  bgrewriteaof
  • 自动重写: auto-aof-rewrite-min-size  size            auto-aof-rewrite-percentage percentage

 

先set 一组数据,进行一轮操作。

然后开启重写AOF:

127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started

然后看到日志:

 

 

 合并成了一条set

 

下面的操作:

127.0.0.1:6379> lpush list1 a
(integer) 1
127.0.0.1:6379> lpush list1 b
(integer) 2
127.0.0.1:6379> lpush list1 c
(integer) 3
127.0.0.1:6379> del name
(integer) 1
127.0.0.1:6379> bgrewriteaof
Background append only file rewriting started

查看:

[root@joe data]# cat appendonly-6379.aof 
*2
$6
SELECT
$1
0
*5
$5
RPUSH
$5
list1
$1
c
$1
b
$1
a

  

del的那个key没有记录了。list操作也合并成了一条。重写后文件变小了

 

AOF手动重写-bgrewriteaof指令工作原理

和save很像:

 

 

 回复的指令不一样

 

 

 

AOF自动重写方式(自动的一般都是基于条件的)

  • 自动重写触发条件设置: 

             auto-aof-rewrite-min-size size

             auto-aof-rewrite-percentage percent

  • 自动重写触发比对参数(运行指令 info Persistence获取具体信息)                     

           aof_current_size

           aof_base_size

  • 自动重写触发条件

 

通过 info 指令可以查看相关信息。进行比对使用。

 

 

AOF工作流程:不同的配置

 

 

关于基于everysec开启的重写: 可以通过配置去激活执行

 

 

RDB和AOF的比较:

 

 

 选择建议:

 对数据非常敏感,建议使用默认的AOF持久化方案

     AOF持久化策略使用everysecond,每秒钟fsync一次。该策略redis仍可以保持很好的处理性能,当出 现问题时,最多丢失0-1秒内的数据。

     注意:由于AOF文件存储体积较大,且恢复速度较慢

 数据呈现阶段有效性,建议使用RDB持久化方案

    数据可以良好的做到阶段内无丢失(该阶段是开发者或运维人员手工维护的),且恢复速度较快,阶段 点数据恢复通常采用RDB方案

    注意:利用RDB实现紧凑的数据持久化会使Redis降的很低,慎重总结:

 综合比对

    RDB与AOF的选择实际上是在做一种权衡,每种都有利有弊

    如不能承受数分钟以内的数据丢失,对业务数据非常敏感,选用AOF

   如能承受数分钟以内的数据丢失,且追求大数据集的恢复速度,选用RDB

   灾难恢复选用RDB  双保险策略,同时开启 RDB 和 AOF,重启后,Redis优先使用 AOF 来恢复数据,降低丢失数据的量

 

 


 

Redis事务:

Redis执行指令过程中,多条连续执行的指令被干扰,打断,插队。

 

客户端1 :set  name zhangsan

客户端2: set name  lisi

 

有可能被覆盖,读错了。

 

对于一个队列中,一次性、顺序性、排他性的执行一些列命令。

 

关于事务的基本操作:

 

  •   开启事务:    multi

            作用: 设定事务的开启位置,这条指令执行后,后续的所有指令均加入到事务中。

  •   执行事务: exec  

           作用: 设定事务的结束位置,同时执行事务。与multi成对出现,成对使用。

 

两个客户端分别set 同一个key

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 12
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> set age 22
QUEUED
127.0.0.1:6379> get age
QUEUED
127.0.0.1:6379> exec
1) OK
2) "12"
3) OK
4) "22"

返回了每个命令的执行结果。每个命令入队列。

 

注意: 加入事务的明星暂时进入到任务队列中,并没有立即执行,只有执行exec命令才开始执行。

 

如果在事务定义过程中出现了问题,

  • 取消事务 : discard

          作用: 终止当前事务的定义,发生在multi之后,exec之前

 

127.0.0.1:6379> multi
OK
127.0.0.1:6379> set age 23
QUEUED
127.0.0.1:6379> get age 
QUEUED
127.0.0.1:6379> set age 22
QUEUED
127.0.0.1:6379> discard
OK
127.0.0.1:6379> exec
(error) ERR EXEC without MULTI

报错: 没有对应的 multi

 

定义事务的过程中,命令格式输入错误了怎么办?

1.如果定义的事务中所包含的命令存在语法错误,整体事务中所有命令均不执行。(指令没有)

2.能够正确运行的命令会执行,运行错误的命令不执行。 (用错了指令)

 

已经执行完毕的命令对应的数据不会自动回滚,需要开发人员在代码中实现回滚。

 

手动进行事务回滚:

 记录凑在哦过程助攻被影响的数据之前的状态:

  •  单数: string
  •  多数据:  hash, list, set,zset  

 

设置指令恢复所有的被修改的项:

  • 单数据: 直接set(注意周边属性,例如时效)
  • 多数据: 修改对应值或整体克隆复制

 

关于Redis锁:

1.多个客户端有可能同时操作同一组数据,并且该数据一旦被修改后,将不适用于继续操作。

2. 在操作之前锁定要操作的数据,一旦发生变化,终止当前操作。

 

如何监控数据:

1.对key添加监视锁,在执行exec之前如果key发生了编号,终止事务执行。

指令: watch key1 【key2, key3....】

2. 取消对所有key的监视

指令: unwatch

 

127.0.0.1:6379> set name 123
OK
127.0.0.1:6379> set age 23
OK
127.0.0.1:6379> watch name
OK
127.0.0.1:6379> get name
"123"
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set aa bb
QUEUED
127.0.0.1:6379> get aa
QUEUED
127.0.0.1:6379> EXEC
1) OK
2) "bb"

继续监控
127.0.0.1:6379> watch name age
OK
127.0.0.1:6379> MULTI
OK
127.0.0.1:6379> set aa cc
QUEUED
127.0.0.1:6379> get aa
QUEUED
127.0.0.1:6379> EXEC

在exec执行之前,有一个客户端去修改name的值
127.0.0.1:6379> set name 8888
OK

此时客户端去执行:可以看到返回值是空
127.0.0.1:6379> EXEC
(nil)

总结: watch监控的key一旦发生改变,下面定义的事务就不在执行了。

注意: 必须在开启事务之前去 watch 否则报错的。 即: 在multi之前执行。

取消:加锁与取消锁

127.0.0.1:6379> watch name
OK
127.0.0.1:6379> get name
"8888"
127.0.0.1:6379> UNWATCH
OK

总结:

控制想操作某个数据时候,条件是别人不能动他。一旦有人动了,所有操作都取消。

业务场景: Redis状态控制的批量任务处理。

 

关于Redis分布式锁:

分析: 

 1. 使用watch监控一个key有没有改变已经不能解决问题,此处需要监控的是具体数据

 2.虽然Redis是单线程的,但是多个客户端对同一数据同时进行操作时,如何避免不被同时修改?

 

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

使用setnx设置一个公共锁

 setnx lock_key value

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

操作完毕通过del删除掉key

127.0.0.1:6379> SETNX lock-num 1
(integer) 1
127.0.0.1:6379> incrby num -1
(integer) -1
127.0.0.1:6379> del locl-num

业务场景: 基于Redis设计分布式锁

 

 

分布式死锁问题解决:

 业务场景: 依赖分布式锁的机制,某个用户操作时对应客户端宕机,且此时已经获取到锁。解决之。

 分析:

  • 由于锁操作由用户控制加锁解锁,必定会存在加锁后未解锁的风险。
  • 需要解锁操作不仅依赖用户控制,系统级别要给出对应的保底处理方案。

 

127.0.0.1:6379> set name 123
OK
127.0.0.1:6379> setnx lock-name 1
(integer) 1
127.0.0.1:6379> expire lock-name 20
(integer) 1
127.0.0.1:6379> get name
"123"
127.0.0.1:6379> del lock-name
(integer) 1

 

分布式锁改良:

使用expire为锁key添加时间限定,到时不释放,放弃锁。

expire lock-key second

pexpire lock-key milliseconds

 

关于Redis删除策略:

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

  •  XX: 具有时效性的数据
  •   -1: 永久有效的数据
  •  -2: 已经过期的数据或被删除的数据或未定义的数据

 

注意:

在Redis中过期id数据不会真正删除。过期数据慢慢删。还是在内存放着的。至于怎么删除,Redis是有删除策略的。

数据删除策略:

  • 定时删除:  创建一个定时器,当key设置有过期的时间,且过期的时间到达时,由定时器任务立即执行对键的操作删除。
    • 节约内存,到点就删除。 但是CPU压力很大,如果此时并发量也大的话,会影响Redis服务器响应时间和指令吞吐量。    
    • 特点:用处理器性能换区存储空间(时间换空间)
  • 惰性删除:数据到达过期时间,不做处理。等下次访问该数据时。   内部通过expirelNeeded()函数实现的。
    • 节约CPU新能,发现必须删除时候才删除。但是内存压力很大,出现长期占用内存的数据,
    • 特点: 用存储空间换区处理器性能
  • 定期删除

         当Redis启动服务器初始化时候,会读取配置server.hz的值,默认为10

查看下:

127.0.0.1:6379> info server
# Server
redis_version:3.2.9
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:565f1f7311e13cb3
redis_mode:standalone
os:Linux 2.6.32-642.el6.x86_64 x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.4.7
process_id:1446
run_id:b928b720a0b5544ec12dd84e8298ba1508b23e72
tcp_port:6379
uptime_in_seconds:68
uptime_in_days:0
hz:10     这里的值!
lru_clock:16415084
executable:/usr/local/redis/bin/redis-server
config_file:/usr/local/redis/etc/redis.conf

每秒钟执行server.hz次 serverCoen() ---> databasesCorn() 读取每个库的信息  ---> activeExpireCycle() 挨个检查里面的信息。检查的时间是定义好的

 

activeExpireCycle() 对每个expires[*] 逐一进行检测,每次执行 250ms/server.hz

对某个expires[*]检测时,随机挑选W个key检测

     如果key超时,删除key 

     如果一轮中删除的key的数量 > W * 25%, 循环该过程

     如果一轮中删除的key的数量 <=W*25%,检查下一个expires[*], 0~15循环

    W取值=ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP属性值

 

 

 

指令:

 

 

 

 

Redis有两个空间:

比如:set   name    123

存储: name 对应 123   123有个地址  这个地址对应着时间,比如:

 

 

 

数据删除策略的目标:

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

 

关于逐出算法:

当新数据进入Redis时候,如果内存不足的情况:

Redis使用内存存储数据,在执行每一个命令前,会调用freeMemoryifNeeded() 检查内存是否充足。 如果内存不满足新加入数据的最低存储要求,Redis要临时删除一些数据为当前指令清理存储空间。清理数据的策略称之为逐出算法。

注意: 逐出数据的过程不是100%能够清理出足够的可使用的内存空间,如果不成功则反复执行。当对所有数据尝试完毕后,如果不能达到内存清理的要求,将出现错误信息。

 

 

影响数据逐出的相关配置:

1.最大可用内存

  maxmemory :  占用物理内存的比例,默认是0,表示不限制。生产环境中根据需求设定,通常设置在50%以上。

2. 每次选取待删除数据的个数

 maxmemory-samples: 选取数据时不会全库扫描,导致严重的性能消耗,降低读写性能。因此采用随机获取数据的方式作为检测删除数据

3.删除策略

 maxmemory-policy

达到最大内存后的,对被挑选出来的数据进行删除的策略

 

影响数据逐出的相关配置:

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

   (1)volatile-lru: 挑选最近最少使用的数据淘汰

 

     (2)  volatile-lfu: 挑选最近使用次数最少使用的数据淘汰

     (3)  volatile-ttl: 挑选将要过期的数据淘汰

     (4)  volatile-lfu: 任意选择数据

 

 

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

  (5) allkeys-lru: 挑选最近最少使用的数据淘汰

  (6) allkeys-lfu: 挑选最近最少使用次数的数据淘汰

  (7) allkeys-random: 任意选择数据淘汰

3. 放弃数据驱逐

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

 

推荐的设置: maxmemory-policy  volatile-lru

 

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

 

总结:Redis删除策略

1.数据删除策略:

      定时删除

      惰性删除

      定期删除

2.数据逐出策略

     8种

 

 

Redis配置的总结:

设置服务器以指定日志记录级别:

loglevel debug | verbose | notice | warning

日志记录文件名

logfile 端口号.log

 

注意: 日志级别开发日期设置为verbose即可,生产环境中配置为notice, 简化日志输出量,降低写日志IO的频度。

 

设置服务器最大连接数:  maxclients 0

设置客户端限制最大等待时间,自动断开连接: timeout 300

导入并加载指定配置文件信息,用于快速创建Redis公共配置(类似于父类配置,子类继承一样)较多的redis实例配置文件,便于维护: include / path/server-端口号.conf   

 

如果存储的需求是个状态,可以使用最小的空间。byte,存储八个位可以存储八个值。 11111111  代表8个人都是好人, 10010001 编号xxx的三个人为好人。

bit的使用:

127.0.0.1:6379> setbit bits 0 1
(integer) 0
127.0.0.1:6379> getbit bits 0
(integer) 1
127.0.0.1:6379> setbit bits 100000000 1
(integer) 0
127.0.0.1:6379> setbit bits 1000000000000 1
(error) ERR bit offset is not an integer or out of range
127.0.0.1:6379> setbit bits 1000000000 1
(integer) 0
(0.57s)

注意:如果偏移量比较大,建议先减1000000

比如:  某一部电影,获取到ID 然后操作他的偏移量

 

每周歌曲被点播的情况:

 

 

 

 Bitmaps类型的扩展操作:

对指定key按位进行交,并,非,异或操作,并将结果保存到destkey中:

 bitop op destKey key1 [key2 ...]

and: 交

or: 并

not: 非

xor: 异或

统计指定key中1的数量

bitcount key [start end]

127.0.0.1:6379> setbit 20880808 0 1
(integer) 0
127.0.0.1:6379> setbit 20880808 4 1
(integer) 0
127.0.0.1:6379> setbit 20880808 8 1
(integer) 0
127.0.0.1:6379> setbit 20880808 0 1
(integer) 1
127.0.0.1:6379> setbit 20880808 5 1
(integer) 0
127.0.0.1:6379> setbit 20880808 8 1
(integer) 1
127.0.0.1:6379> bitcount 20880808
(integer) 4

 

合并之:

127.0.0.1:6379> bitop or 08-09 20880808 20880809 
(integer) 2

 

以上适合的业务场景就是 Redis用户信息状态的统计

 

HyperLogLog:

1,基数是数据集去重后元素个数

2,HyperLogLog是用来做基数统计的,运用了LogLog的算法

 

 

HyperLogLog类型的基本操作:

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

 

127.0.0.1:6379> pfadd h11 001
(integer) 1
127.0.0.1:6379> pfadd h11 001
(integer) 0
127.0.0.1:6379> pfadd h11 001
(integer) 0
127.0.0.1:6379> pfadd h11 001
(integer) 0
127.0.0.1:6379> pfadd h11 001
(integer) 0
127.0.0.1:6379> pfadd h11 001
(integer) 0
127.0.0.1:6379> pfadd h11 002
(integer) 1
127.0.0.1:6379> pfadd h11 002

基数:

127.0.0.1:6379> pfcount h11
(integer) 2

 

应用场景: Redis应用于独立信息统计

 

相关说明:

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

 

关于GEO:

微信附近的人,美团附近的美食。位置类的。

 

  • 添加坐标点: geoadd key longitude latitude member  [longitude latitude member ...]
  • 获取坐标点: key member [member ...]
  • 计算坐标点距离: geodist key member member2 [unit]

 

127.0.0.1:6379> geoadd geos 1 1 a
(integer) 1
127.0.0.1:6379> geoadd geos 2 2 b
(integer) 1
127.0.0.1:6379> geopos geos a
1) 1) "0.99999994039535522"
   2) "0.99999945914297683"
127.0.0.1:6379> geodist geos a b
"157270.0561"
127.0.0.1:6379> geodist geos a b km
"157.2701"

 

 

127.0.0.1:6379> geoadd geos 1 2 1,2
(integer) 1
127.0.0.1:6379> geoadd geos 1 3 1,3
(integer) 1
127.0.0.1:6379> geoadd geos 2 1 2,1
(integer) 1
127.0.0.1:6379> geoadd geos 2 2 2,2
(integer) 1
127.0.0.1:6379> geoadd geos 2 3 2,3
(integer) 1
127.0.0.1:6379> geoadd geos 3 1 3,1
(integer) 1
127.0.0.1:6379> geoadd geos 3 2 3,2
(integer) 1
127.0.0.1:6379> geoadd geos 3 3 3,3
(integer) 1
127.0.0.1:6379> geoadd geos 5 5 5,5
(integer) 1
127.0.0.1:6379> georadiusbymember geos 2,2 180 km
 1) "1,1"
 2) "a"
 3) "2,1"
 4) "1,2"
 5) "2,2"
 6) "b"
 7) "3,1"
 8) "3,2"
 9) "1,3"
10) "2,3"
11) "3,3"
127.0.0.1:6379> georadiusbymember geos 2,2 120 km
1) "1,2"
2) "2,2"
3) "b"
4) "2,3"
5) "2,1"
6) "3,2"

 

127.0.0.1:6379> georadius geos 1.5 1.5 90 km
1) "1,2"
2) "2,2"
3) "b"
4) "1,1"
5) "a"
6) "2,1"

 

内部计算你的公式:

127.0.0.1:6379> geohash geos 2,2
1) "s037ms06g70"

 

应用场景: Redis应用于地理位置的计算

 

 


 

关于Redis主从复制:

 

 核心: master的数据复制到slave中

 

主从复制作用:

 

 主从复制过程分三个阶段:

1. 建立连接阶段(准备阶段)

2. 数据同步阶段

3. 命令传播阶段

 

 

客户端的不同:

 

 

步骤1:设置master的地址和端口,保存master信息

步骤2:建立socket连接

步骤3:发送ping命令(定时器任务)

 

 

 

 

 

注意: 设置时候如果master设置了密码。从配置时候要挂上密码才能授权。

 

数据同步,Master说明:

1. 如果master数据量巨大,数据同步阶段应该避开流浪高峰期,避免造成master阻塞,影响业务正常执行

2. 复制缓冲区大小设定不合理,会导致数据溢出。如进行全量复制周期太长,进行部分复制时发现数据已经存在丢失情况,必须进行第二次全量复制,致使salve陷入死循环状态。

    repl-backlog-size 1mb

3. master单机内存占用主机内存的比例不应过大,建议使用50%  - 70% 的内存,留下30% - 50% 的内存用于执行bgsave 命令和创建复制缓冲区。

 

数据同步,Slave说明:

  1.为避免salve进行全量复制,部分复制时服务器响应阻塞或数据不同步,建议关闭此期间的对外服务。

  salve-server-stale-data  yes | no

 2.数据同步阶段,master发送给slave信息可以理解成master是slave的一个客户端,主动向slave发送命令。

 3.多个salve同时对master请求数据同步,master发送的RDB文件增多,会对宽带造成巨大冲击,如果master宽带不足,因此数据同步需要根据业务需求,适量错峰。

 4. slave过多时,建议调整拓扑结构,由一主多从结构变为树状结构,中间的节点既是master,也是slave.注意使用树状结构时,由于层级深度,导致深度越高的slave与最顶层master间数据同步延迟较大,数据一致性变差,应谨慎选择。

 

命令传播阶段的部分复制:

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

         闪断闪连               (忽略) 

        短时间网络中断      部分复制

        长时间网络中断      全量复制

2.部分复制的三个核心要素

      服务器的运行id (run id)            运行id在每台服务器启动时自动生成的,master在首次连接slave时候,会将自己的运行id发送给slave,slave保存id。通过info server命令,可以查看节点的runid

      主服务器的复制积压缓冲区           

      主从服务器的复制偏移量

 

关于 复制缓冲区:复制缓冲区,是一个先进先出的队列,用于存储服务器执行过的命令,每次传播命令,master都会将传播的命令记录下来,并存储在复制缓冲区。

 

   复制缓冲区默认数据存储空间大小是1M,由于存储空间的大小是固定的,当入队元素的数量大于队列长度时,最先入队的元素会被弹出,而新元素会放入队列。

由来: 每台服务器启动时,如果开启有AOF或被连接成master节点,即创建复制缓冲区。

 

作用: 用于保存master收到的所有指令(仅影响数据变更的指令,例如set, select换数据库)

数据来源: 当master接收到主客户端的指令时,除了将指令执行,会将该指令存储到缓冲区中

 

关于偏移量,一个数字,描述复制缓冲区中的指令字节位置。

 

psync2 runid  offset 翻译: 找机器要数据

 

 

 

 

关于心跳机制:

1. master心跳: PING

   判断slave是否在线。

   查询: INFO replication   获取slave最后一次连接时间间隔,lag项维持在0和1视为正常

2.slave心跳任务:REPLCONF ACK {offset}

  作用1: 汇报slave自己的复制偏移量,获取最新的数据变更指令

  作用2: 判断master是否在线

 

注意事项:

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

  min-salves-to-write 2

  min-slaves-max-lag 8

  意味着: slave数量少于2个,或者所有slave的延迟都大于10秒时,强制关闭master写工功能,停止数据同步。

2.slave数量由slave发送REPLCONF ACK命令做确认    

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

 

 


 

 


 

关于哨兵模式:

  

 

 监控,通知,故障转移。

 

注意: 

  哨兵也是一台Redis服务器,只是不提供数据服务

 通常哨兵配置数量为单数

 

posted @ 2019-11-28 00:01  toov5  阅读(409)  评论(0编辑  收藏  举报