Redis

Redis

标签(空格分隔): SQL


1. 什么是Redis

Redis是基于内存的高性能key-value数据库.


2. Redis的数据类型

String, Hash, List, Set, Sorted Set.


3. 内部结构

Redis内部使用一个RedisObject对象来表示所有的keyvalue .

  • type: 表示一个value对象具体是哪一种数据类型.
  • encoding: 不同数据类型在redis内部的存储方式. 如: type = String代表value存储的是一个普通字符串, 那么对应的encoding可以使raw或者是int, 如果是int则代表实际redis内部是按照数值型类存储和表示这个字符串的, 当然前提是这个字符串本身可以用数值表示如"123", "345"这样的字符串.

4. 内存淘汰机制

Redis内存淘汰机制指的是用户存储的一些键可以被Redis主动的从实例中删除, 从而产生读miss的情况, 那么Redis为什么要有这种功能呢? 这就是我们需要探究的设计初衷. Redis最常见的两种应用场景为缓存数据持久存储. 首先需要明确的一个问题是内存淘汰策略更适合于那种场景? 是持久存储还是缓存?

假设我们有一个Redis服务器, 服务器的物理内存大小为1G, 我们需要存在Redis中的数据量很小, 这看起来似乎足够用很长时间了, 但是随着业务量的不断增长, 我们在其中放的数据也会越来越多, 数据量大小似乎超过了1G, 但是应用还可以正常运行, 这是因为操作系统的课件内存并不受物理内存的限制, 而是虚拟内存, 当物理内存不够用的时候, 操作系统会在硬盘上面划分出来一块虚拟内存, 那么这个时候我们的可用内存就是2^32大约为3G, 但是这个时候会在物理内存和虚拟内存之间发生频繁的内存交换(在访问虚拟内存中的数据的时候). 这种交换会严重的降低磁盘性能 和 Redis读写数据的性能. 所以我们需要一定的miss来换取内存的使用效率.

4.1 如何用.

作为Redis用户我们如何使用Redis提供的这个特性呢?

maxmemory <bytes>

我们可以通过配置redis.conf中的maxmemory这个值来开启内存淘汰功能, 至于这个值有什么意义, 我们可以通过了解内存淘汰过程来了解.

  • 客户端发起需要申请内存的命令.
  • Redis检查内存的使用情况, 如果消耗的总内存大于maxmemory则根据用户配置的淘汰策略来淘汰key, 从而换取一定的内存.
  • 如果上面都没问题, 则这个命令执行成功.
  • maxmemory为0的时候表示我们的Redis的内存使用没有限制.

4.2 内存淘汰策略

内存淘汰是Redis提供的的一个功能, 为了更好的使用这个功能必须为不同的应用场景下提供不同的策略, 内存淘汰策略讲的是为实现内存淘汰策略我们应该具体怎么做, 要解决的问题包括键空间怎么选择, 在键空间中淘汰键如何选择?

  • Redis提供了下面几种淘汰策略以供用户选择, 其中默认的策略为noevication策略:
  1. noeviction: 当内存使用达到阈值的时候, 所有引起申请内存的命令都会报错.
  2. allkeys-lru: 在主键空间中, 优先移除最近未使用的key.
  3. volatile-lru: 在设置了过期时间的键空间中, 优先移除最近未使用的key.
  4. allkeys-random: 在主键空间中, 随机移除某个key.
  5. volatile-random: 在设置了过期时间的键空间中, 随机移除某个key.
  6. volatile-ttl: 在设置了过期时间的键空间中, 具有更早过期时间的key优先移除.
  • 在这里补充一下主键空间和设置了过期时间的键空间, 举个栗子, 假设我们有一批键存储在Redis中, 则有那么一个哈希表用于存储这批键和值, 如果这批键中有一部分设置了过期时间, 那么这批键还会被存储到另一个哈希表中, 这个哈希表中的值对应的是键被设置的过期时间, 设置了过期时间的键空间为主键空间的子集.

下面看看几种策略的具体适用场景:

  • allkeys-lru: 如果我们应对缓存的访问符合幂律分布(也就是存在相对热点), 或者我们不太清除我们应用缓存访问分布状况, 我们可以采取allkeys-lru. 相对于volatile-lru占用的内存空间更低.
  • volatile-lru: 策略适合我们将一个Redis实例即应用于缓存又应用于数据持久化的时候, 然而我们也可以通过使用两个Redis实例, 来达到相同的效果, 值得一提的是将key设置的有过期时间的时候实际上会消耗更多的内存(主键空间/设置了过期时间的主键空间).
  • allkeys-random: 如果我们的应用对于缓存key的访问概率相等的话, 则可以使用这个策略.
  • volatile-random: 策略适合我们将一个Redis实例即应用于缓存又应用于数据持久化的时候, 然而我们也可以通过使用两个Redis实例, 来达到相同的效果, 值得一提的是将key设置的有过期时间的时候实际上会消耗更多的内存(主键空间/设置了过期时间的主键空间).
  • volatile-ttl: 使得我们可以向Redis提示那些key更适合被eviction

5. 非精准的LRU

上面提到的LRU(Least Recently Used)策略, 实际上Redis实现的LRU并不是可靠的LRU, 也就是名义上我们使用LRU算法淘汰键, 但实际上被淘汰的键并不一定是真正的最久没用的, 这里涉及到一个权衡的问题, 如果需要在全部键空间内搜索最优解, 则必然会增加CPU的负载, 但是因为Redis是单线程的, 也就是同一个实例在每一个时刻只能服务于一个客户端, 所以耗时的操作一定要谨慎. 为了在一定成本内实现相对的LRU, 早起的Redis版本是基于采样的LRU, 也就是放弃全部键空间内搜索解改为采样空间内搜索最优解. 自从Redis3.0之后,其作者对LRU算法进行了优化, 目的就是在一定的成本内得到的结果更加接近真实的LRU.

  • 在这种情况下就有可能发生热点数据被删除然后 发生缓存击穿的情况.

6. 缓存穿透, 雪崩, 击穿.

6.1 缓存穿透.

如果有人恶意的向服务器获取并不存在的数据, 这个时候Redis服务器就不产生作用了, 如果发生大量这样的情况的时候, 会给数据库带来巨大的压力.

实现流程如下:

  1. 参数传入对象主键ID
  2. 根据key从缓存中获取对象.
  3. 如果对象不为空, 直接返回.
  4. 如果对象为空, 则从数据库读取.
  5. 如果对象不为空, 则存储到缓存并返回.
  • 如果传入的主键Id为-1, 并且开始大量的查询的时候就产生了上述状况.

实际上在工作过程中, 如果第五步发现对象为空的时候, 会给该对象的值设为空, 存储到缓存中, 并且设置过期时间(60S).

6.2 缓存雪崩.

在某一个时间段, 缓存集中过期失效. 例如电商项目, 在某个时间段 大量的商品被放入缓存, 然后它们又集体过期, 这个时候如果有查询的话会给数据库带来比较大的压力. 造成缓存雪崩.

在存入缓存的时候, 在设置的过期时间上可以加上一个随机数, 防止集体过期这种情况出现. 随机数在热门资源上可以长一点, 冷门资源上可以短一点.

6.3 缓存击穿

某一个非常热门的资源在缓存数据库中, 如果某个时间点该缓存过期会造成大量线程去数据库访问并缓存该资源, 一般情况下是不会产生这样的情况的, 如果出现这种情况可以直接设置永不过期(更新数据的时候另操作), 或者使用mutex key互斥锁.

7. Redis High Available

高可用集群, 是保证业务连续性的有效解决方案, 一般有两个或者两个以上的节点, 且分为活动节点和备用节点. 通常把正在执行业务的成为活动节点, 而作为活动节点的一个或多个备份成为备份节点. 当活动 节点出现问题的时候导致正在运行的业务中断, 备用节点此时就会侦测到, 并理解使用备用节点执行也许, 从而实现业务的连续性.

Redis一般以主/从方式部署(这里讨论的从实例主要用于备份, 主实例提供读写) 实现HA主要有如下几种方案:

  • keepalived: 通过keepalived的虚拟IP, 提供主从的统一访问, 在主节点出现问题的时候, 通过keepalived运行脚本将从节点提升为主节点, 等到主节点回复之后 先将数据同步, 然后将自动变回主节点. 这样做的优点是应用程序不需要知道Redis那边出现的状况(虚拟的IP不变), 坏处是引入keepalived增加部署复杂度, 而且在某些情况下会导致丢失数据.
  • Zookeeper: 通过Zookeeper主从实例, 维护最新的有效的IP, 应用通过Zookeeper去取得IP对Redis进行访问, 该方案需要编写大量的监控代码.
  • sentinel: 通过Sentinel监控主从实例, 自动进行故障恢复, 该方案有个缺陷: 因为主从实例地址(IP&PORT)是不同的, 当故障发生主从切换之后, 应用程序无法知道新地址, 故在Jedis2.2.2中新增了对Sentinel的支持, 应用通过redis.clients.jedis.JedisSentinelPool.getResource()取得的Jedis实例会及时更新到新的主实例地址.
version: '3'

services:
  redis:
    image: redis
    container_name: redis
    restart: always
    command: redis-server /etc/redis.conf
    ports:
      - 6379:6379
    volumes:
      - /data:/data
      - ./redis.conf:/etc/redis.conf
# Redis配置文件样例

# Note on units: when memory size is needed, it is possible to specifiy
# it in the usual form of 1k 5GB 4M and so forth:
#
# 1k => 1000 bytes
# 1kb => 1024 bytes
# 1m => 1000000 bytes
# 1mb => 1024*1024 bytes
# 1g => 1000000000 bytes
# 1gb => 1024*1024*1024 bytes
#
# units are case insensitive so 1GB 1Gb 1gB are all the same.

# Redis默认不是以守护进程的方式运行,可以通过该配置项修改,使用yes启用守护进程
# 启用守护进程后,Redis会把pid写到一个pidfile中,在/var/run/redis.pid
daemonize no

# 当Redis以守护进程方式运行时,Redis默认会把pid写入/var/run/redis.pid文件,可以通过pidfile指定
pidfile /var/run/redis.pid

# 指定Redis监听端口,默认端口为6379
# 如果指定0端口,表示Redis不监听TCP连接
port 6379

# 绑定的主机地址
# 你可以绑定单一接口,如果没有绑定,所有接口都会监听到来的连接
# bind 127.0.0.1

# Specify the path for the unix socket that will be used to listen for
# incoming connections. There is no default, so Redis will not listen
# on a unix socket when not specified.
#
# unixsocket /tmp/redis.sock
# unixsocketperm 755

# 当客户端闲置多长时间后关闭连接,如果指定为0,表示关闭该功能
timeout 0

# 指定日志记录级别,Redis总共支持四个级别:debug、verbose、notice、warning,默认为verbose
# debug (很多信息, 对开发/测试比较有用)
# verbose (many rarely useful info, but not a mess like the debug level)
# notice (moderately verbose, what you want in production probably)
# warning (only very important / critical messages are logged)
loglevel verbose

# 日志记录方式,默认为标准输出,如果配置为redis为守护进程方式运行,而这里又配置为标准输出,则日志将会发送给/dev/null
logfile stdout

# To enable logging to the system logger, just set 'syslog-enabled' to yes,
# and optionally update the other syslog parameters to suit your needs.
# syslog-enabled no

# Specify the syslog identity.
# syslog-ident redis

# Specify the syslog facility.  Must be USER or between LOCAL0-LOCAL7.
# syslog-facility local0

# 设置数据库的数量,默认数据库为0,可以使用select <dbid>命令在连接上指定数据库id
# dbid是从0到‘databases’-1的数目
databases 5

################################ SNAPSHOTTING  #################################
# 指定在多长时间内,有多少次更新操作,就将数据同步到数据文件,可以多个条件配合
# Save the DB on disk:
#
#   save <seconds> <changes>
#
#   Will save the DB if both the given number of seconds and the given
#   number of write operations against the DB occurred.
#
#   满足以下条件将会同步数据:
#   900秒(15分钟)内有1个更改
#   300秒(5分钟)内有10个更改
#   60秒内有10000个更改
#   Note: 可以把所有“save”行注释掉,这样就取消同步操作了

save 900 1
save 300 10
save 60 10000

# 指定存储至本地数据库时是否压缩数据,默认为yes,Redis采用LZF压缩,如果为了节省CPU时间,可以关闭该选项,但会导致数据库文件变的巨大
rdbcompression yes

# 指定本地数据库文件名,默认值为dump.rdb
dbfilename dump.rdb

# 工作目录.
# 指定本地数据库存放目录,文件名由上一个dbfilename配置项指定
# 
# Also the Append Only File will be created inside this directory.
# 
# 注意,这里只能指定一个目录,不能指定文件名
dir ./

################################# REPLICATION #################################

# 主从复制。使用slaveof从 Redis服务器复制一个Redis实例。注意,该配置仅限于当前slave有效
# so for example it is possible to configure the slave to save the DB with a
# different interval, or to listen to another port, and so on.
# 设置当本机为slav服务时,设置master服务的ip地址及端口,在Redis启动时,它会自动从master进行数据同步
# slaveof <masterip> <masterport>


# 当master服务设置了密码保护时,slav服务连接master的密码
# 下文的“requirepass”配置项可以指定密码
# masterauth <master-password>

# When a slave lost the connection with the master, or when the replication
# is still in progress, the slave can act in two different ways:
#
# 1) if slave-serve-stale-data is set to 'yes' (the default) the slave will
#    still reply to client requests, possibly with out of data data, or the
#    data set may just be empty if this is the first synchronization.
#
# 2) if slave-serve-stale data is set to 'no' the slave will reply with
#    an error "SYNC with master in progress" to all the kind of commands
#    but to INFO and SLAVEOF.
#
slave-serve-stale-data yes

# Slaves send PINGs to server in a predefined interval. It's possible to change
# this interval with the repl_ping_slave_period option. The default value is 10
# seconds.
#
# repl-ping-slave-period 10

# The following option sets a timeout for both Bulk transfer I/O timeout and
# master data or ping response timeout. The default value is 60 seconds.
#
# It is important to make sure that this value is greater than the value
# specified for repl-ping-slave-period otherwise a timeout will be detected
# every time there is low traffic between the master and the slave.
#
# repl-timeout 60

################################## SECURITY ###################################

# Warning: since Redis is pretty fast an outside user can try up to
# 150k passwords per second against a good box. This means that you should
# use a very strong password otherwise it will be very easy to break.
# 设置Redis连接密码,如果配置了连接密码,客户端在连接Redis时需要通过auth <password>命令提供密码,默认关闭
requirepass mimamimamima

# Command renaming.
#
# It is possilbe to change the name of dangerous commands in a shared
# environment. For instance the CONFIG command may be renamed into something
# of hard to guess so that it will be still available for internal-use
# tools but not available for general clients.
#
# Example:
#
# rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
#
# It is also possilbe to completely kill a command renaming it into
# an empty string:
#
# rename-command CONFIG ""

################################### LIMITS ####################################

# 设置同一时间最大客户端连接数,默认无限制,Redis可以同时打开的客户端连接数为Redis进程可以打开的最大文件描述符数,
# 如果设置maxclients 0,表示不作限制。当客户端连接数到达限制时,Redis会关闭新的连接并向客户端返回max Number of clients reached错误信息
# maxclients 128

# Don't use more memory than the specified amount of bytes.
# When the memory limit is reached Redis will try to remove keys with an
# EXPIRE set. It will try to start freeing keys that are going to expire
# in little time and preserve keys with a longer time to live.
# Redis will also try to remove objects from free lists if possible.
#
# If all this fails, Redis will start to reply with errors to commands
# that will use more memory, like SET, LPUSH, and so on, and will continue
# to reply to most read-only commands like GET.
#
# WARNING: maxmemory can be a good idea mainly if you want to use Redis as a
# 'state' server or cache, not as a real DB. When Redis is used as a real
# database the memory usage will grow over the weeks, it will be obvious if
# it is going to use too much memory in the long run, and you'll have the time
# to upgrade. With maxmemory after the limit is reached you'll start to get
# errors for write operations, and this may even lead to DB inconsistency.
# 指定Redis最大内存限制,Redis在启动时会把数据加载到内存中,达到最大内存后,Redis会先尝试清除已到期或即将到期的Key,
# 当此方法处理后,仍然到达最大内存设置,将无法再进行写入操作,但仍然可以进行读取操作。
# Redis新的vm机制,会把Key存放内存,Value会存放在swap区
maxmemory 100000000

# MAXMEMORY POLICY: how Redis will select what to remove when maxmemory
# is reached? You can select among five behavior:
# 
# volatile-lru -> remove the key with an expire set using an LRU algorithm
# allkeys-lru -> remove any key accordingly to the LRU algorithm
# volatile-random -> remove a random key with an expire set
# allkeys->random -> remove a random key, any key
# volatile-ttl -> remove the key with the nearest expire time (minor TTL)
# noeviction -> don't expire at all, just return an error on write operations
# 
# Note: with all the kind of policies, Redis will return an error on write
#       operations, when there are not suitable keys for eviction.
#
#       At the date of writing this commands are: set setnx setex append
#       incr decr rpush lpush rpushx lpushx linsert lset rpoplpush sadd
#       sinter sinterstore sunion sunionstore sdiff sdiffstore zadd zincrby
#       zunionstore zinterstore hset hsetnx hmset hincrby incrby decrby
#       getset mset msetnx exec sort
#
# The default is:
#
maxmemory-policy volatile-lru

# LRU and minimal TTL algorithms are not precise algorithms but approximated
# algorithms (in order to save memory), so you can select as well the sample
# size to check. For instance for default Redis will check three keys and
# pick the one that was used less recently, you can change the sample size
# using the following configuration directive.
#
# maxmemory-samples 3

############################## APPEND ONLY MODE ###############################

# 
# Note that you can have both the async dumps and the append only file if you
# like (you have to comment the "save" statements above to disable the dumps).
# Still if append only mode is enabled Redis will load the data from the
# log file at startup ignoring the dump.rdb file.
# 指定是否在每次更新操作后进行日志记录,Redis在默认情况下是异步的把数据写入磁盘,如果不开启,可能会在断电时导致一段时间内的数据丢失。
# 因为redis本身同步数据文件是按上面save条件来同步的,所以有的数据会在一段时间内只存在于内存中。默认为no
# IMPORTANT: Check the BGREWRITEAOF to check how to rewrite the append
# log file in background when it gets too big.

appendonly no

# 指定更新日志文件名,默认为appendonly.aof
# appendfilename appendonly.aof

# The fsync() call tells the Operating System to actually write data on disk
# instead to wait for more data in the output buffer. Some OS will really flush 
# data on disk, some other OS will just try to do it ASAP.

# 指定更新日志条件,共有3个可选值:
# no:表示等操作系统进行数据缓存同步到磁盘(快)
# always:表示每次更新操作后手动调用fsync()将数据写到磁盘(慢,安全)
# everysec:表示每秒同步一次(折衷,默认值)

appendfsync everysec
# appendfsync no

# When the AOF fsync policy is set to always or everysec, and a background
# saving process (a background save or AOF log background rewriting) is
# performing a lot of I/O against the disk, in some Linux configurations
# Redis may block too long on the fsync() call. Note that there is no fix for
# this currently, as even performing fsync in a different thread will block
# our synchronous write(2) call.
#
# In order to mitigate this problem it's possible to use the following option
# that will prevent fsync() from being called in the main process while a
# BGSAVE or BGREWRITEAOF is in progress.
#
# This means that while another child is saving the durability of Redis is
# the same as "appendfsync none", that in pratical terms means that it is
# possible to lost up to 30 seconds of log in the worst scenario (with the
# default Linux settings).
# 
# If you have latency problems turn this to "yes". Otherwise leave it as
# "no" that is the safest pick from the point of view of durability.
no-appendfsync-on-rewrite no

# Automatic rewrite of the append only file.
# Redis is able to automatically rewrite the log file implicitly calling
# BGREWRITEAOF when the AOF log size will growth by the specified percentage.
# 
# This is how it works: Redis remembers the size of the AOF file after the
# latest rewrite (or if no rewrite happened since the restart, the size of
# the AOF at startup is used).
#
# This base size is compared to the current size. If the current size is
# bigger than the specified percentage, the rewrite is triggered. Also
# you need to specify a minimal size for the AOF file to be rewritten, this
# is useful to avoid rewriting the AOF file even if the percentage increase
# is reached but it is still pretty small.
#
# Specify a precentage of zero in order to disable the automatic AOF
# rewrite feature.

auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

################################## SLOW LOG ###################################

# The Redis Slow Log is a system to log queries that exceeded a specified
# execution time. The execution time does not include the I/O operations
# like talking with the client, sending the reply and so forth,
# but just the time needed to actually execute the command (this is the only
# stage of command execution where the thread is blocked and can not serve
# other requests in the meantime).
# 
# You can configure the slow log with two parameters: one tells Redis
# what is the execution time, in microseconds, to exceed in order for the
# command to get logged, and the other parameter is the length of the
# slow log. When a new command is logged the oldest one is removed from the
# queue of logged commands.

# The following time is expressed in microseconds, so 1000000 is equivalent
# to one second. Note that a negative number disables the slow log, while
# a value of zero forces the logging of every command.
slowlog-log-slower-than 10000

# There is no limit to this length. Just be aware that it will consume memory.
# You can reclaim memory used by the slow log with SLOWLOG RESET.
slowlog-max-len 1024


posted @ 2020-04-10 15:43  X-POWER  阅读(271)  评论(0编辑  收藏  举报