Redis初探 | 青训营笔记

64 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 12 天
本文主要介绍在直播课上所学过的redis的内容。包括redis的实现框架,常用的数据类型及相应操作,最后介绍redis数据库一些常见的问题。

实现框架

Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。其基本框架如下图所示:

1.jpeg

如上图所示,redis是一种基于内存运行,性能高效的key-value存储系统。

redis持久化的方式有两种:RDB和AOF,其中RDB是通过保存数据库中的键值对来记录数据库的状态,其缺点是RDB 持久化是一定时间内做一次备份,如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有丢失)。

而另一种持久化方式 AOF 则是通过保存Redis服务器所执行的写命令来记录数据库状态,AOF 持久化的方法提供了多种的同步频率,即使使用默认的同步频率每秒同步一次,Redis 最多也就丢失 1 秒的数据而已。
redis常用的数据结构有string,hash,set,zset以及list,下面对这五种数据类型进行介绍。

常用数据类型

string

3.jpeg

string的基本结构如上图所示,alloc表示sds指针后被分配的内存的大小,len表示sds指针后使用过的内存的大小。因此很容易对string类型数据进行位操作,在redis中key都是以string的形式存储的,当然也有string的value格式,如果value为string类型的话,其基本的操作方法有:
set key value
get key
strlen key # 值的字节长度
append key value # 给该key追加value

incr key # 将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值
incrby key 50 # +50
decr key # -1
decrby key 50 # -50

setbit key offset value # offset表示偏移量
bitcount key [start end] # bitcount表示字节索引,查这个区间里一共有多少1
bitcount key 0 -1 # 表示value所有区间
bitop operation destkey key [key……] # 逻辑语句operation:and or not

man ascii # manual 查看Ascii码字符集(最多可表示127个字符)

应用场景包括:

  • 字符串:键值对类型的数据,eg:session信息

  • 数值:计数器/限流/秒杀

  • 位图:统计用户某时间段内的登录次数/统计某时间段内的活跃用户数量/大数量库存备货

list

在redis中使用的list名称为quicklist,它由一个双向链表和listpack构成,如下图所示:

4.jpeg

常用的操作方法有:

  LINDEX key index
  summary: Get an element from a list by its index
  since: 1.0.0

  LINSERT key BEFORE|AFTER pivot value
  summary: Insert an element before or after another element in a list
  since: 2.2.0

  LLEN key
  summary: Get the length of a list
  since: 1.0.0

  LPOP key
  summary: Remove and get the first element in a list
  since: 1.0.0

  LPUSH key value [value ...]
  summary: Prepend one or multiple values to a list
  since: 1.0.0

  RPOP key
  summary: Remove and get the last element in a list
  since: 1.0.0

  RPUSH key value [value ...]
  summary: Append one or multiple values to a list
  since: 1.0.0

  RPUSHX key value
  summary: Append a value to a list, only if the list exists
  since: 2.2.0 

使用场景:消息通知

例如当文章更新时,将更新后的文章推送到ES,用户就能搜索到最新的文章数据

hash

5.jpeg

hash的数据结构形式如上图所示,适用于一个对象有多个计数要求的情况,对存储对象特定值的更新也比较方便,只需提供更新对象、更新项目以及更新的值便可。其常用的操作方法有:
HDEL key field [field ...]

  HGET key field
  summary: Get the value of a hash field
  since: 2.0.0

  HGETALL key
  summary: Get all the fields and values in a hash
  since: 2.0.0

  HINCRBY key field increment
  summary: Increment the integer value of a hash field by the given number
  since: 2.0.0


  HKEYS key
  summary: Get all the fields in a hash
  since: 2.0.0

  HMGET key field [field ...]
  summary: Get the values of all the given hash fields
  since: 2.0.0

  HMSET key field value [field value ...]
  summary: Set multiple hash fields to multiple values
  since: 2.0.0

  HSET key field value
  summary: Set the string value of a hash field
  since: 2.0.0

  HSETNX key field value
  summary: Set the value of a hash field, only if the field does not exist
  since: 2.0.0

  HVALS key
  summary: Get all the values in a hash
  since: 2.0.0

其扩容有两种方式,分别是rehash和渐进式rehash:

rehash:将ht[0]中的数据全部迁移到ht[1]中。数据量小的场景下,直接将数据从ht[0]拷贝到ht[1]速度是较快的。但是在数据量大的场景下,例如存在上百万的KV时,迁移过程会明显阻塞用户的请求。
渐进式rehash:每次用户访问时都会迁移少量数据,将整个迁移过程平摊到所有的访问过程。

zset

zset使用的数据结构为zskiplist,是由跳表和dict构成的,适用排行榜、评论分页这种情况,其基本的操作方法有:

zadd key [NX|XX] [CN] [INCR] score member [score member……]
zrange # 从小到大排序
zrange key 0 1 # 从小到大取前两名
zrevrange key 0 1 # 从大到小取前两名
zincrby key increment member # 给某个成员的score加increment

Redis使用注意事项

大key

定义

数据类型大key标准
string类型value字节数大于10KB
hash/set/zset/list等复杂数据类型元素个数大于5000个或总value字节数大于10MB

危害

读取成本高

容易导致慢查询(过期、删除)

主从复制异常

无法正常响应请求

消除大key方法

  • 拆分:将大key拆分成小key。例如将一个string拆分成多规格string
  • 压缩:将value压缩后写入redis中,读取时解压后再使用。压缩算法可以实gzip,snappy,lz4等。通常情况下,一个压缩算法压缩率越高,则解压耗时越长。需要对实际数据进行测试,选择一个合适的算法,如果存储的是json字符串,可以考虑使用message pack进行序列化。

压缩不了才想拆分

  • 对于集合类的结构,如hash\list\set\zset,可以使用

    拆分:使用hash取余,位掩码的方式决定放在那个key中

    区分冷热:如榜单场景使用zset,只缓存前十页数据,后续走db

热key

用户访问一个key的QPS特别高,导致server实例出现CPU突增或者不均的情况,热key没有明确的标准,QPS超过500就有可能被识别为热KEY

解决方式

  • 设置localcache:在访问redis前,在业务服务侧设置localcache,降低访问redis的QPS,当localcache的缓存过期或未命中时,从redis中将数据更新到localcache中,Java的Guava,Go的Bigcache就是这类的localcache

  • 拆分:将key:value的一个热key复制写入多份,访问的时候访问多个key,但是value是同一个值,以此将QPS分配到不同的实例上,以此降低负载。代价:更新时需要更新多个key,存在数据短暂不一致的风险。