文章目录
- Redis基础
- 简要介绍redis:
- redis支持的数据类型(五种)
- redis底层结构——全局Hash表及五种数据类型的底层结构
- 一个字符串类型的值能存储最大容量是多少?
- redis的两种持久化方式:RDB、AOF
- save命令与bgsave命令的区别
- redis与memcache的区别
- 与DB如何做一致性与同步
- Redis 常见的性能问题都有哪些?如何解决?
- Redis内存淘汰机制
- redis内部缓存淘汰算法实现方案
- redis为什么快,为什么性能如此之高
- 为什么 Redis 需要把所有数据放到内存中?
- redis如何查询容量:包含多少key,多少字节,多少内存使用
- Redis是单线程的吗?
- 为什么Redis 单线程模型效率也能那么高
- redis是如何使用epoll模型的
- Redis6为何引入多线程?
- Redis 支持的 Java 客户端Jedis 与 Redisson 对比有什么优缺点?
- 假如 Redis 里面有1 亿个 key,其中有10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?
- 一个REDIS实例最多能存放多少KEYS
- 说说你对PIPELINE的理解
- 说说你对缓存双写不一致的理解
- Redis是如何解决Hash冲突的?
- 讲解一下Redis的16个数据库
- redis事务
- redis集群
- Redis应用场景
Redis基础
简要介绍redis:
Redis(Remote Dictionary Server),即远程字典服务,是一个C语音编写、基于内存、可持久化的key-value数据库。
- 其读写速度非常快,每秒可以处理超过10万次读写操作。这得益于其使用了epoll I/O多路复用模型+单线程模型避免上下文切换,并且基于内存操作,外加合理的数据结构提高数据访问效率。高吞吐量使redis常用做大型互联网分布式缓存服务器,用于实现高并发、高性能、高可用。
- 其支持五种数据结构,极大丰富了其对业务支持的一个多样性,如list可以用于存储内容列表,ZSet可以用于存储有序内容集合等。
- 支持持久化数据,可用于故障恢复和主从复制备份,提高系统可用性。
- 支持主从复制、哨兵机制和集群方式,实现高可用性和水平扩展,避免单机存储瓶颈、吞吐量瓶颈、单机故障。
- 使用redis需要注意避免7大经典问题:缓存雪崩、缓存穿透、缓存击穿、数据不一致、数据并发竞争、热点key问题、bigkey问题
- 针对redis中的内存回收,有过期数据清除策略和内存淘汰策略,过期数据使用定期随机扫描清除+惰性删除策略,如果写入数据时发现内存还不够,则使用内存淘汰策略,一般有不淘汰、随机淘汰、按过期时间早的淘汰、按最近最少使用淘汰、按最近最不频繁使用的淘汰等策略。
从高性能高可用方面介绍redis
redis支持的数据类型(五种)
String、set、zset、list、Hash
String类型:最基础的数据类型,使用场景:单值存储:SET key value
对象存储:SET key 对象转的json字符串
分布式锁:SETNX key value,同一个key只有第一次set才能成功。
计数器:INCR key ,每次命令执行都会自增1,可用做计数、分库分表的自增ID生成。
Hash使用场景:购物车,可以直接加减商品数量
List使用场景:用户订阅的微博&微信公众号文章list
Set使用场景:抽奖小程序,可以直接通过srandmember命令随机抽取。spop命令可以将抽过奖的用户移除。微信微博点赞、收藏、标签功能。通过交集并集找共同好友、共同关注列表等,可实现社交软件的关注模型,可用于推荐系统。
zset使用场景:微信朋友圈点赞按时间顺序展示,微博热点新闻按热度顺序展示。
redis底层结构——全局Hash表及五种数据类型的底层结构
Key的存储结构:全局hash表,读写检索到对应key的位置的时间复杂度都是O(1)。再通过rehash避免key的hash冲突,保证不会存在链表过长的情况导致检索性能下降。
Value存储结构(6种):
SDS(simple dynamic string):redis中默认的字符串表示,key和String类型的value都是用这个数据结构。
字典dict:字典dict就是类似hashmap key-value的方式,也就是数组+链表的方式。
压缩列表ziplist:压缩列表ziplist是一块连续的内存空间,元素之间紧挨着存储,没有任何冗余空隙。支持双向遍历,元素的检索定位是通过偏移量来完成的。
快速列表quicklist:由压缩列表作为node组成的双向链表。
整数数组inset:set类型值是int且数据量较小时使用。
跳表skiplist:跳跃表的逻辑类似于“树”型结构,是将一个链表其中的元素间隔几个就向上抽取一层,这样实现检索的过程时间复杂度达到O(logN)。
参考《redis底层数据结构》
一个字符串类型的值能存储最大容量是多少?
512M
redis的两种持久化方式:RDB、AOF
RDB:Redis Data Base。在指定的时间间隔内,将内存中的数据集快照写入磁盘,实际操作是fork一个子进程,先将数据集写入临时文件,写入成功后,再替换之前的文件,用二进制压缩存储。可以设置RDB快照生成时机,如900秒内100条keys数据被改变时生成。适用于数据要求性不高的应用。
AOF:append only file,以日志的形式记录服务器所处理的每一个写、删除操作,查询操作不会记录,以文本的方式记录。与binlog不同,AOF是可识别的纯文本,会导致数据发生修改的命令都会记录到AOF,打开文件可看到详细的操作记录。因为AOF文件会越来越大,所以Redis提供rewrite机制(bgrewriteaof命令),重新生成一份新aof文件,每条记录只会记录一个命令。通过appendfsync设置数据写入磁盘的时间间隔。
所有的写命令追加到AOF缓冲区,AOF缓冲区根据对应的策略向硬盘进行同步操作,AOF提供三种同步策略:
- 每秒同步(everysec):异步完成,效率高。可能会丢1s内的数据。
- 每修改同步(always):同步完成,每次发生的数据变化都会立刻同步到磁盘中,最多丢一条数据。
- 不同步(no):由操作系统控制,可能丢失较多数据。
两种持久化方式那种好:
RDB消耗cpu小,恢复数据时也较快。
AOF更安全可靠,可能同步太频繁会导致性能下降。
可以同时配置RDB和AOF,恢复时会优先用AOF。
save命令与bgsave命令的区别
save和bgsave都是redis提供实现RDB持久化的命令,都会调用rdbSave函数,但他们调用方式不同。
- save:主进程直接调用rdbsave,阻塞Redis主进程,直到保存完成为止。在主进程阻塞期间,服务器不能处理客户端任何请求。
- bgsave:先fork出一个子进程,子进程调用rdbSave,并在保存完之后向主进程发送信号,通知保存已完成。rdb被调用期间主进程依然可以继续处理客户端的请求。
但主进程同时写数据和子进程持久化冲突怎么处理呢?这里使用的是Copy-On-Write(COW写时复制)的机制实现,父进程收到修改命令时,会copy出一份副本,然后才修改原来的数据,子进程会读取副本写入快照。
redis与memcache的区别
redis支持5种数据类型,支持持久化,支持m/s数据备份等。
memcache内容放在内存中,只支持简单k/v数据。
使用场景:
- 如果有持久方面的需求或对数据类型和处理有要求的应该选择redis。
- 如果简单的key/value 存储应该选择memcached。
与DB如何做一致性与同步
方法一:延迟双删
- 读:先从cache读,如果没有,则从db读,并更新到cache中。
- 写:先删除Redis缓存数据,再更新Mysql,延迟几百毫秒再删除Redis缓存数据,这样就算在更新Mysql时,有其他线程读了Mysql,把老数据读到了Redis中,那么也会被删除掉,从而把数据保持一致。所有修改只负责删除cache,读时如果空,则读DB并同步cache。
方案二:基于binlog异步更新缓存
- MySQL中产生了新的写入、更新、删除等操作,就可以把binlog相关的消息推送至Redis,Redis再根据binlog中的记录,对Redis进行更新。
方案三:消息队列+重试机制
- 先更新db在删缓存,如果缓存删除失败,则发生消息到MQ,异步监听后再次删除缓存,直到成功为止。
Redis 常见的性能问题都有哪些?如何解决?
1)Master 最好不要写内存快照,如果 Master 写内存快照,save 命令调度 rdbSave 函数,会阻塞主线程的工作,当快照比较大时对性能影响是非常大的,会间断性暂停服务
2)如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一
3)为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网
4)尽量避免在压力很大的主库上增加从
5)主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1<- Slave2 <- Slave3…这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做Master,其他不变。
Redis内存淘汰机制
概述:LRU-淘汰最近最少使用的、LFU-淘汰最近最不频繁使用的、random-随机淘汰、TTL-淘汰更早过期时间的。
参考:Redis的数据过期清除策略与内存淘汰策略
数据过期清除策略:定期删除+惰性删除
- 定时扫描清除:定时100ms随机20个设置了过期时间的key,检查其是否过期,如果有过期就删除。若存在25%以上则继续循环删除。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载。
- 惰性删除:定期删除可能导致很多过期的key 到了时间并没有被删除掉。这时就要使用到惰性删除。在你获取某个key的时候,redis会检查一下,这个key如果设置了过期时间并且过期了,是的话就删除。
定期删除+惰性删除存在的问题:
如果某个key过期后,定期删除没删除成功,然后也没再次去请求key,也就是说惰性删除也没生效。这时,如果大量过期的key堆积在内存中,redis的内存会越来越高,导致redis的内存块耗尽。那么如果有新的写请求进来时,就应该采用内存淘汰机制。
Redis提供了下面几种内存淘汰机制供用户选择,其中默认的策略为noeviction策略:1、noeviction:不进行淘汰数据。一旦缓存被写满,再有写请求进来,Redis就不再提供服务,而是直接返回错误。Redis 用作缓存时,实际的数据集通常都是大于缓存容量的,总会有新的数据要写入缓存,这个策略本身不淘汰数据,也就不会腾出新的缓存空间,我们不把它用在 Redis 缓存中。
2、volatile-ttl:在设置了过期时间的键值对中,移除即将过期的键值对。
3、volatile-random:在设置了过期时间的键值对中,随机移除某个键值对。
4、volatile-lru:在设置了过期时间的键值对中,移除最近最少使用的键值对。
5、volatile-lfu:在设置了过期时间的键值对中,移除最近最不频繁使用的键值对
6、allkeys-random:在所有键值对中,随机移除某个key。
7、allkeys-lru:在所有的键值对中,移除最近最少使用的键值对。
8、allkeys-lfu:在所有的键值对中,移除最近最不频繁使用的键值对LFU (Least Frequently Used) :最近最不频繁使用,跟使用的次数有关,淘汰使用次数最少的。
LRU (Least Recently Used):最近最少使用,跟使用的最后一次时间有关,淘汰最近使用时间离现在最久的。
什么时候执行内存淘汰机制
- 写操作时内存超过限制:当 Redis 内存使用量超过了设置的 maxmemory 参数所定义的限制时,写操作(例如 set、hset 等)会触发内存淘汰策略。Redis 会根据设置的淘汰策略来删除一些键值对,以释放内存空间来容纳新的数据。
- 启用 AOF 重写:如果 Redis 使用了 AOF(Append Only File)持久化方式,并且开启了 AOF 重写功能,那么在执行 AOF 重写过程中也会触发内存淘汰策略。AOF 重写是将 AOF 文件重写为一种更紧凑、更快速加载的格式,这个过程中会使用更少的内存来存储数据。
- 通过命令主动释放内存:Redis 也可以通过主动执行 MEMORY PURGE 命令来释放内存。
redis内部缓存淘汰算法实现方案
最近最少使用(Least Recently Used,LRU):通常LRU的算法是这样的,LRU会使用一个链表维护缓存中每个数据的访问情况,并根据数据的实时访问,调整数据在链表中的位置,然后通过数据在链表中的位置,表示数据是最近刚访问的,还是已有段时间未访问。比如链表长度为5,从链表头部到尾部保存的数据分别是5,33,9,10,8。假设数据9被访问一次,则9就会被移动到链表头部,同时,数据5和33都要向链表尾部移动一位。还可以使用hashmap存储每个对象的地址,这样能用O(1)的时间复杂度访问到链表上的每一个数据地址,再去调整他们的位置。
如果是这样的算法,那么每次访问数据都会导致链表的一个更新,这样便会降低缓存的使用性能,所以redis使用了一个近似LRU的算法:
- redis启动会维护一个全局LRU时钟,并且每隔100ms更新一次。如果redis配置的是allkeys-lru或volatile-lru,就会维护一个定长的数组EvictionPoolLRU(默认长度16),用于保存待删除的key。
- 创建KV对象时,会获取当前全局LRU时钟,并保存到KV对象中,相当于这个对象的访问时间戳,每次对象被访问时都会去更新这个时间戳。
- 当Redis每处理一个命令,都调用performEvictions判断是否需释放内存,也就是使用内存是否超过配置的最大内存,如果是就会随机获取一定数量的KV对,然后从这些key中找到空闲时间最长的16个对象放入EvictionPoolLRU,最后删除这些key。
- 如果还是内存不够,继续执行上面的步骤找到对应的KV对象删除。
最不频繁访问(Least Frequently Used,LFU):如果一个数据在近期被高频率地访问,那么在将来它被再访问的概率也会很高,而访问频率较低的数据将来很大概率不会再使用。
在这段时间片内数据A被访问了5次,数据B与C各被访问了4次,如果按照访问次数判断数据热度值,必然是A>B=C;如果考虑到时效性,距离当前时间越近的访问越有价值,那么数据热度值就应该是C>B>A。因此,LFU算法一般都会有一个时间衰减函数参与热度值的计算,兼顾了访问时间的影响。
LFU算法实现的数据结构与LRU一样,也采用Hash表 + 双向链表的结构,数据在双向链表内按照热度值排序。如果某个数据被访问,更新热度值之重新插入到链表合适的位置,这个比LRU算法处理的流程复杂一些。
Redis中,LFU算法的实现没有使用额外的数据结构,复用了redisObject数据结构的lru字段,把这24bit空间拆分成两部分去使用。
由于记录时间戳在空间被压缩到16bit,所以LFU改成以分钟为单位,大概45.5天会出现数值折返,比LRU时钟周期还短。
低位的8bit用来记录热度值(counter),8bit空间最大值为255,无法记录数据在访问总次数。
Redis一直都采用随机的方式筛选数据,且筛选的个数极其有限,所以,LFU算法无法展现出较大的优势,也可能会淘汰掉比较热的数据。
redis为什么快,为什么性能如此之高
- 纯内存操作:redis是基于内存进行读写的,避免了磁盘读写IO的性能消耗。
- 单线程模型:避免了多线程不必要的上下文切换和竞争条件,不存在加锁释放锁操作,减少了因为锁竞争导致的性能消耗;
- 多路 I/O 复用——应对大量的请求,Redis 中使用 I/O 多路复用程序同时监听多个socket,并将这些事件推送到一个队列里,然后逐个被执行,最终将结果返回给客户端。这样保证了只有读写事件发生时才会产生IO,大大减少了资源占用。
- 底层高效的数据存储结构:使用全局hash表作为key的读写的存储结构,时间复杂度是O(1)。value的五种数据类型也都有高效的结构便于读写。
为什么 Redis 需要把所有数据放到内存中?
主要原因是为了提供高性能的读写操作。
以下是几个主要的原因:
- 高速读写:内存访问速度快,相比于磁盘和数据库,内存操作速度更快,能够更迅速地响应读写请求。将数据存储在内存中可以大大缩短读写的延迟,提高系统的响应速度和吞吐量。
- 简单数据结构:Redis使用简单的数据结构来存储数据,如字符串、列表、哈希、集合和有序集合等。这些数据结构直接映射到内存,不需要进行复杂的数据转换和序列化操作,提高了读写效率。
- 数据持久化:尽管Redis将数据存储在内存中,但它也支持数据的持久化。通过使用RDB快照和AOF日志两种方式,Redis可以将内存中的数据定期或实时写入磁盘,以保证数据的持久性和安全性。
需要注意的是,由于内存容量有限,Redis的内存管理也是需要考虑的。通过设置合适的数据过期策略、内存淘汰策略和最大内存限制等措施,可以在保证高性能的同时,有效地管理内存使用。同时,Redis也可以通过集群和分片等方式来扩展内存容量和提高系统的可用性和性能。
redis如何查询容量:包含多少key,多少字节,多少内存使用
要查看redis的内存占用情况,可以使用redis-cli命令行工具,具体命令如下:
1.查看所有key的占用内存大小:redis-cli --bigkeys
2.查看单个key的占用内存大小:redis-cli MEMORY USAGE key
3.查看redis内存使用情况:redis-cli INFO memory
Redis是单线程的吗?
不完全是
Redis 采用的是单线程模型。通常说得单线程,主要指的是 Redis 对外提供的键值存储服务的主要流程是单线程的,即网络 I/O 和数据读写是由单个线程来完成的。这样设计可以避免多线程之间的竞争条件和锁开销,提高了访问共享数据的效率。
然而,除了对外提供的键值存储服务,Redis 在某些功能上会使用额外的线程来执行,比如持久化、异步删除和集群数据同步等。这些功能需要在后台执行,不参与主要的网络 I/O 和数据处理。因此,严格来说,Redis 并不是完全单线程。
为什么Redis 单线程模型效率也能那么高
尽管Redis采用了单线程模型,但其效率仍然非常高。以下是一些原因:
- 非阻塞IO:Redis使用了事件驱动的非阻塞IO机制。它通过事件循环处理来自客户端的请求,在等待数据IO时并不会阻塞主线程,而是继续处理其他请求。这种机制允许Redis以高效地方式处理大量的并发连接。
- 内存操作:Redis主要将数据存储在内存中,并且由于单线程模型的存在,在内存操作的情况下,Redis可以通过简单的指针操作来实现快速读写,而不需要考虑复杂的数据同步和竞争条件。
- 单线程避免的开销:与多线程模型相比,单线程模型避免了线程间的上下文切换、锁竞争和资源管理开销。这使得Redis可以更高效地使用CPU资源,并减少了大量与线程相关的开销。
需要注意的是,Redis单线程模型适合于处理大量的短期操作和快速响应的场景,但在处理长时间运行的计算密集型任务时可能会有性能上的不足。为了提高处理能力和并发性,可以使用Redis的集群模式、多实例部署或将计算密集型任务委托给其他更适合的工具或语言来处理。
redis是如何使用epoll模型的
epoll I/O多路复用模型
Redis利用epoll来实现IO多路复用,将连接信息和事件放到队列中,依次放到文件事件分派器,事件分派器将事件分发给事件处理器。
redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回,这会导致某一文件的 I/O 阻塞导致整个进程无法对其它客户提供服务,而 I/O 多路复用就是为了解决这个问题而出现。
所谓 I/O 多路复用机制,就是说通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或写就绪》,能够通知程序进行相应的读写操作。这种机制的使用需要select、poll、epoll 来配合。多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象上等待,无需阻塞等待所有连接。当某条连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。
redis 服务采用 Reactor 的方式来实现文件事件处理器(每一个网络连接其实都对应一个文件描述符)
redis基于Reactor模式开发了网络事件处理器,这个处理器被称为文件事件处理器。它的组成结构为4部分:
- 多个套接字
- IO多路复用程序
- 文件事件分派器
- 事件处理器
因为文件事件分派器队列的消费是单线程的,所以Redis才叫单线程模型
Redis6为何引入多线程?
redis6中引入的多线程是正对于网络IO模块进行了多线程改造,因为多路复用的IO模型本质上来说还是同步阻塞型IO模型,在调用epoll的过程是阻塞的,并发量极高的场景就成为了性能瓶颈,那么在碰到这类问题上,就可以通过多线程来解决。Redis 6.0 的多线程并非 “多线程执行命令”,而是将网络数据的读写操作(I/O 密集型)交由多个线程处理,命令的解析、执行、内存操作等仍由主线程单线程完成。这种设计的核心目的是:
- 利用多线程并行处理网络 I/O,提高吞吐量;
- 保持 “命令执行单线程”,避免多线程对共享数据的竞争(无需加锁,简化设计)。
Redis 支持的 Java 客户端Jedis 与 Redisson 对比有什么优缺点?
Jedis 是 Redis 的 Java 实现的客户端,其 API 提供了比较全面的 Redis 命令的支持;Redisson 实现了分布式和可扩展的 Java 数据结构,和 Jedis 相比,功能较为简单,不支持字符串操作,不支持排序、事务、管道、分区等Redis 特性。
Redisson 的宗旨是促进使用者对 Redis 的关注分离,从而让使用者能够将精力更集中地放在处理业务逻辑上。
假如 Redis 里面有1 亿个 key,其中有10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?
使用 keys 指令可以扫出指定模式的 key 列表。
对方接着追问:如果这个 redis 正在给线上的业务提供服务,那使用 keys 指令会有什么问题?
这个时候你要回答 redis 关键的一个特性:redis 的单线程的。keys 指令会导致线程阻塞一段时间,线上服务会停顿,直到指令执行完毕,服务才能恢复。这个时候可以使用 scan 指令,scan 指令可以无阻塞的提取出指定模式的key 列表,但是会有一定的重复概率,在客户端做一次去重就可以了,但是整体所花费的时间会比直接用 keys 指令⻓。
一个REDIS实例最多能存放多少KEYS
Redis 的每个实例最多可以存放约 2^32 - 1 个keys,即大约 42 亿个keys。这是由 Redis 内部使用的哈希表实现决定的,它使用 32 位有符号整数作为索引。Redis 使用的哈希函数和负载因子等因素也会影响实际可存放键的数量。
需要注意的是,尽管 Redis 允许存储数量庞大的键,但在实践中,存储过多的键可能会导致性能下降和内存消耗增加。因此,在设计应用程序时,需要根据实际需求和硬件资源来合理规划键的数量,避免过度使用 Redis 实例造成负担。如果需要存储更多的键值对,可以考虑使用 Redis 集群或分片技术,以扩展整体存储容量。
说说你对PIPELINE的理解
PIPELINE 是 Redis 提供的优化命令执行的机制,通过减少网络往返次数和批量处理命令来提高性能。
它将多个命令打包发送给服务器,减少了网络延迟,提升了吞吐量。同时,命令仍然保持原子性,要么全部执行成功,要么全部失败。
使用 PIPELINE 还可以降低服务器资源消耗,提高整体效率。适用于需要连续执行多个命令或批量操作的场景,特别适合延迟敏感或大量请求的应用程序。
总之,PIPELINE 是一种高效的方式来优化 Redis 命令执行,提升性能和效果。
说说你对缓存双写不一致的理解
缓存双写不一致是指在使用缓存的架构中,当数据更新时,由于缓存和数据库的写操作没有同步进行,导致数据在缓存和数据库之间出现不一致的情况。
以下是对缓存双写不一致的一般理解:
- 更新顺序问题:当应用程序更新了数据库中的数据,但在更新缓存之前发生了错误或异常,导致缓存中的数据仍然是旧值。这种情况下,数据库中的数据已经被修改,但缓存中的数据仍然是旧的,导致缓存和数据库之间存在不一致。
- 缓存失效问题:当数据库中的数据发生变化,并成功更新后,缓存中的数据却没有及时更新或失效了。这可能是由于缓存的过期策略、缓存维护、网络延迟等原因导致的。此时,从缓存读取的数据将是旧值,与数据库中的新值不一致。
- 并发更新问题:当多个应用程序同时更新相同的数据时,缓存和数据库的更新操作可能不是原子性的。如果两个更新操作同时进行,可能会导致缓存和数据库在更新时发生冲突,导致不一致的结果。
为了解决缓存双写不一致的问题,可以考虑以下方法:
- 缓存更新策略:使用延迟双删的策略更新数据库缓存。
- 数据库与缓存的事务性操作:通过数据库事务和缓存的原子性操作来保证更新的一致性。
- 更新通知机制:通过发布-订阅(Pub/Sub)模式,或使用消息队列等机制来通知缓存节点更新数据,确保缓存的实时性。
- 使用强一致性缓存:如 Redis 的事务和 pipeline 特性,可以确保对缓存的多个操作按顺序执行,减少不一致的概率。
- 定期刷新缓存:通过定期刷新缓存,保证缓存中的数据不会过期太久,降低不一致性发生的概率。
综上所述,缓存双写不一致是在使用缓存时常见的问题,通过合理的缓存更新策略、事务性操作、更新通知机制等措施,可以有效地减少不一致的发生。
Redis是如何解决Hash冲突的?
redis是通过我们的链式hash来解决我们的hash冲突问题,哈希算法产生的哈希值的长度是固定并且是有限的,比如说我们通过MD5算法生成32位的散列值,那么它能生成出来的长度则是有限的,我们的数据如果大于32位是不是就可能存在不同数据生成同一个散列值,那么redis通过链式hash,以不扩容的前提下把有相同值的数据链接起来,但是如果链表变得很长就会导致性能下降,那么redis就采用了rehash的机制来解决,类似于hashmap里面的扩容机制,但是redis中的rehash并不是一次把hash表中的数据映射到另外一张表,而是通过了一种渐进式的方式来处理,将rehash分散到多次请求过程中,避免阻塞耗时。
讲解一下Redis的16个数据库
redis有16个数据库,以[0~15]编号命名,默认会使用0号数据库。16个数据库,其实就是底层创建了16个全局hash表。
为什么默认16个数据库?
- 可扩展性:Redis的数据库是可配置的,允许用户根据需要增减。然而,将数据库数量设置为16可以提供一个适当的平衡:既能让大多数应用场景使用到足够的数据库空间,又不会浪费太多资源。
- 性能考虑:在大多数系统中,16个数据库可以在内存中以有效的方式进行管理,同时保持较低的资源消耗。每个数据库都是一个独立的命名空间,这意味着即使在面临某些数据库故障或需要维护时,Redis的整体可用性也不会受到影响。
- 满足大多数应用场景的需求:对于许多常见的用例,例如缓存和会话管理,16个数据库已经足够满足大多数应用场景的需求。通过将数据分散到多个数据库中,可以更容易地实现数据分区和数据淘汰策略。
redis事务
redis事务
- 事务是一个单独的隔离操作:事务中的所有命令都会序列化、按顺序地执行。
- 事务在执行的过程中,不会被其他客户端发送来的命令请求所打断。
- 事务是一个原子操作:事务中的命令要么全部被执行,要么全部都不执行。
- Redis 事务采用了乐观锁的方式实现
- Redis 事务相关的命令有哪几个:MULTI、 EXEC、 DISCARD、 WATCH
事务的四个命令解释:
- MULTI命令用于开启一个事务,它总是返回OK。MULTI执行之后,客户端可以继续向服务器发送任意多条命令,除了上面四条命令,其他读写命令不会立即被执行,而是被放到一个队列中,当EXEC命令被调用时,所有队列中的命令才会被执行。
- EXEC:执行所有事务块内的命令。返回事务块内所有命令的返回值,按命令执行的先后顺序排列。当操作被打断时,返回空值 nil 。
- 通过调用DISCARD,客户端可以清空事务队列,放弃执行事务, 并且客户端会从事务状态中退出。
- WATCH 命令可以为 Redis 事务提供 check-and-set (CAS)行为。可以监控一个或多个键,一旦其中有一个键被修改(或删除),事务执行时会检查watch的结果,如果发生改变则回滚事务,监控一直持续到EXEC命令。
Redis事务实现原理
redis通过四个命令来实现事务:
说说你对Redis操作原子性的理解
Redis 的操作是原子性的,这是因为 Redis 的每个命令都是以单线程的方式执行的,整个命令的执行过程是不可中断的,要么全部执行成功,要么全部执行失败。
在 Redis 中,每个命令都会被转换成一个或多个底层操作,这些操作会基于数据结构的特定实现来执行。比如,对于字符串类型,获取一个键值对、设置一个键值对等操作都是原子性的。在执行这些底层操作时,Redis 会使用一些技术来保证原子性,主要包括以下两点:
- Redis 使用单线程模型,避免了多线程之间的竞争条件和锁开销,从而保证了操作的原子性。
- Redis 在执行一些复杂的操作时,比如事务、Lua 脚本等,会将多个底层操作打包成一个原子性操作,这些底层操作要么全部执行成功,要么全部执行失败。在事务和 Lua 脚本中,Redis 同时支持回滚操作,即当一些命令执行成功,后面的命令出错时,Redis 可以自动撤销已经执行的命令。
因此,Redis 的操作是原子性的,这得益于 Redis 单线程模型和底层操作的实现方式。这种原子性操作保证了 Redis 能够提供高效和可靠的服务。
Redis 事务支持 ACID 么?
基本支持,但存在一些异常场景不能绝对保证ACID
(1)原子性:首先来看原子性。MULTI和EXEC配合使用,确实可以保证多个操作都完成。但是我们要考虑到事务执行如果出错的情况。 分情况讨论:
- 执行EXEC命令前的错误:客户端发来的命令本身就有错误,比如说语法错误。该情况在EXEC提交后,Redis会拒绝所有的操作。这种情况
能保证原子性
。- 命令入队列的时候,命令和操作的数据类型不符:这种情况Redis因为还没执行命令,检查不到错误。当执行EXEC后才会报错。这种情况不会拒绝其他的操作,
不能保证原子性
。- 执行EXEC命令时,Redis实例故障:这种情况也会导致事务失败。如果开启了AOF日志,当AOF恢复Redis实例后,事务不会被执行,
能保证原子性
;但若没有开启AOF,Redis重启后数据丢失,也就不能保证原子性
。(2)一致性:再来看一致性。也要分情况来讨论:
- 命令入队列的时候报错: 事务本身都不会执行,可以保证一致性。
- 命令入队列未报错,实际执行时报错:错误的命令不会执行,但正确的会执行,可能会影响一致性。
- EXEC命令执行时实例故障:
①如果没有开启RDB或AOF:数据都丢失了,没有一致性问题。
②开启了RDB快照:因为 RDB 快照不会在事务执行时执行,所以,事务命令操作的结果不会被保存到 RDB 快照中,使用 RDB 快照进行恢复时,数据库里的数据也是一致的。
③开启了AOF日志:如果事务操作还没记录到AOF日志,实例就故障了,那么AOF日志恢复的数据是一致的;如果部分操作被记录到AOF日志,我们可以用redis-check-aof来清除事务中已经完成的操作,数据恢复后也是一致的。(3)隔离性:事务的隔离性保证,要考虑到同时进行的事务操作。
- 其他事务操作在EXEC命令前执行:此时隔离性的保证要使用WATCH机制来实现,否则隔离性无法保证;
- 其他事务操作在EXEC命令后执行:此时隔离性可以保证。因为Redis是单线程执行命令,EXEC命令执行后,Redis会保证先把命令队列的所有命令执行完。因此其他事务操作不会破坏事务的隔离性。
(4)持久性:最后说说持久性,持久性取决于Redis的持久化配置模式。
- 如果说都没开启RDB或者AOF,数据都丢了,事务的持久性肯定得不到保证。
- 如果你开启了RDB,一个事务执行后,RDB快照还未执行前,此时Redis发生宕机,事务修改的数据丢失了,也不能保证持久化;
- 如果你开启了AOF,因为 AOF 模式的三种配置选项 no、everysec 和 always 都会存在数据丢失的情况,也不能保证持久化。
redis集群
怎么实现Redis的高可用?
主从模式:一主多从,特点:可做读写分离,运维成本低,适合小规模业务使用。
哨兵模式:多主多从+sentinel集群,可在主服务不可用时自动选举主服务器。特点:适合读请求远多于写请求的业务场景,比如在秒杀系统中用来缓存活动信息。 如果写请求较多,当集群 Slave 节点数量多了后,Master 节点同步数据的压力会非常大。
集群模式:为了避免单一节点负载过高导致不稳定,集群模式采用一致性哈希算法或者哈希槽的方法将 Key 做数据分片分布到各个节点上。其中,每个 Master 节点后跟若干个 Slave 节点,也可以每个节点互为主从,用于出现故障时做主备切换。特点:运维复杂、适用于大型高并发数据业务。
Redis Sharding:跟上面的集群模式不一样的是,这个是客户端分片,集群模式是服务端分片,客户端是不感知具体的分片规则和数据存储在哪个服务器上的。是在集群模式出来之前普遍使用的一种集群方式。
是否使用过Redis集群,集群的原理是什么?
Redis 集群通过数据分片和主从复制实现了横向扩展和高可用性。
- Redis Sentinal 着眼于高可用,在 master 宕机时会自动将 slave 提升为 master,继续提供服务。
- Redis Cluster 着眼于扩展性,在单个 redis 内存不足时,使用 Cluster 进行分片存储。
它将数据划分为 16384 个哈希槽,并将这些槽均匀地分配到多个节点上。每个节点负责处理一部分槽的数据,实现了数据的分散存储和负载均衡。
在集群中,每个哈希槽有一个主节点和多个从节点。主节点负责处理读写请求,而从节点则通过主从复制机制复制主节点的数据,提供数据的冗余备份和故障恢复功能。
当主节点发生故障时,集群会自动进行故障转移。它会选举一个从节点升级为新的主节点,保证服务的持续可用性。同时,集群管理节点负责监控节点的状态,并协调故障转移过程。
客户端在与 Redis 集群交互时,根据键的哈希值将请求发送到相应的节点。客户端还可以通过集群管理节点获取整个集群的拓扑信息,了解哪些键存储在哪个节点上。
通过数据分片和主从复制,Redis 集群实现了数据水平切分、负载均衡和高可用性。它允许数据规模和吞吐量的线性扩展,并能自动处理节点故障。集群管理节点协调集群状态,客户端通过哈希槽映射与集群交互,从而实现了一个稳定和可靠的 Redis 分布式系统。
说说 Redis 哈希槽的概念?
Redis 集群没有使用一致性 hash,而是引入了哈希槽的概念,Redis 集群有16384 个哈希槽,每个 key 通过 CRC16校验后对16384 取模来决定放置哪个槽,集群的每个节点负责一部分 hash 槽。
主从复制采用图状结构还是单向链表好
链表结构好,这样如果master挂了可以立即启用slave1做master,其他不变。
redis主从复制
Redis是通过RDB文件实现主从同步数据传输的。
(1)slave服务器连接到master服务器,便开始进行数据同步,发送psync命令(Redis2.8之前是sync命令)
(2)master服务器收到psync命令之后,开始执行bgsave命令生成RDB快照文件并使用缓存区记录此后执行的所有写命令
(3)master服务器bgsave执行完之后,就会向所有Slava服务器发送快照文件,并在发送期间继续在缓冲区内记录被执行的写命令
(4)slave服务器收到RDB快照文件后,会将接收到的数据写入磁盘,然后清空所有旧数据,在从本地磁盘载入收到的快照到内存中,同时基于旧的数据版本对外提供服务。
(5)master服务器发送完RDB快照文件之后,便开始向slave服务器发送缓冲区中的写命令
(6)slave服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
(7)如果slave node开启了AOF,那么会立即执行BGREWRITEAOF,重写AOF
主从复制分为全量同步和增量同步,如下图:
Redis 集群的主从复制模型是怎样的?
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有 N-1 个复制品。
什么是Redis哨兵机制
Redis 哨兵是一种用于高可用性的解决方案,它可以监控 Redis 主从复制模式下的主节点和从节点,发现节点故障,并自动进行故障转移,保证 Redis 系统的稳定性和可靠性。
Redis 哨兵机制由多个相互独立的进程组成,这些进程使用 TCP/IP 协议相互通信,实现 Redis 节点的监控和故障转移。哨兵机制的关键进程包括:
- sentinel:主进程,用于监控 Redis 节点的状态,并执行故障转移操作。
- monitor:哨兵进程,用于监控 Redis 的主节点和从节点是否正常工作,并在需要时通知其他哨兵进程和客户端。
- judge:哨兵进程,用于对节点的健康状况进行评估,并根据预定义的阈值决定是否要将一个不健康的节点标记为“主观下线”。
- failover:哨兵进程,负责执行故障转移操作,将主节点故障时选举出来的从节点晋升为新的主节点,并通知其他 Redis 节点更新配置信息。
通过上述哨兵进程的协同工作,Redis 哨兵机制可以实现自动化的故障转移,使得 Redis 的高可用性得到有效保障。在实际应用中,可以通过配置多个哨兵进程,使其互相监控和备份,从而进一步提高 Redis 系统的可靠性和稳定性。
Redis 集群会有写操作丢失吗?为什么?
Redis 在Redis集群中,由于采用了主从复制模型的异步复制机制,并不能保证数据的强一致性,这意味这在实际中集群在特定的条件下可能会丢失写操作。
当客户端向主节点发送写操作时,主节点会立即返回成功响应,而不等待所有从节点执行复制。如果主节点在执行完写操作后出现故障或网络问题,导致从节点无法及时接收到复制操作,那么这些未复制的写操作将会丢失。
为了减少写操作丢失的可能性,可以采取以下措施:
- 定期监测集群状态,确保主从节点之间的复制正常进行;
- 设置合理的持久化策略,将数据写入磁盘或使用AOF模式以便数据恢复;
- 在应用程序层实施数据确认机制,检查写操作是否成功。
需要注意的是,Redis集群的主从复制模型无法完全消除写操作丢失的风险,但通过配置和监控的合理手段,可以最大限度地降低写操作丢失的可能性,保障数据的安全性和可靠性。
Redis 集群方案什么情况下会导致整个集群不可用?
Redis 集群在以下情况下可能导致整个集群不可用:
- 多个主节点同时故障:如果多个主节点同时发生故障,而且它们的从节点无法正常升级为新的主节点,那么整个集群将无法提供读写服务。
- 集群管理节点故障:集群管理节点负责监控集群状态和协调故障转移操作。如果集群管理节点发生故障,并且无法及时恢复或替换,那么集群将失去管理和协调能力,可能导致集群不可用。
- 网络分区:如果集群中的节点之间发生网络分区,即无法互相通信,那么可能会引起脑裂(split-brain)问题。在这种情况下,每个分区内的节点可能会认为自己是合法的 Redis 集群,导致数据冲突和不一致性,最终导致整个集群无法正常工作。
- 配置错误:如果 Redis 集群的配置出现错误或者某些节点的配置不一致,可能导致集群无法正常运行。
- 内存不足:如果集群中的某个节点的内存不足以容纳当前处理的数据量,可能会导致该节点性能下降甚至崩溃,从而影响整个集群的可用性。
为避免整个集群不可用,建议采取以下措施:
- 配置正确的主从复制和故障转移机制,确保每个主节点都有足够的从节点,并定期进行故障转移测试。
- 部署多个独立的集群管理节点,以确保高可用性和决策一致性。
- 定期检查和监控集群配置,确保各个节点之间的配置一致性。
- 实施网络分区容忍策略,例如使用网络拓扑结构和分布式一致性协议,以减少脑裂问题的发生。
- 监控集群节点的内存使用情况,及时扩容或优化内存管理,避免内存不足问题。
综上所述,要确保 Redis 集群的高可用性和稳定性,需要合理设计和配置集群架构,并采取适当的监控和容错措施来应对潜在的故障情况。
Redis 集群最大节点个数是多少?
16384 个。
Redis应用场景
redis7大经典问题:缓存雪崩、缓存穿透、缓存击穿、数据不一致、数据并发竞争、热点key问题、bigkey问题
缓存雪崩:指缓存同一时间大面积的失效,所以,后面的请求都会落到数据库上,造成数据库短时间内承受大量请求而崩掉。解决办法:①数据的过期时间设置随机,防止同一时间大量数据过期现象发生。②后台服务更新缓存过期时间。
缓存穿透:缓存穿透是指缓存和数据库中都没有的数据,导致所有的请求都落到数据库上,造成数据库短时间内承受大量请求而崩掉。一般存在于外部攻击场景。解决方案:①数据库中没查到的数据在缓存中存一个null值,避免穿透。②前置校验拦截异常请求数据。③布隆过滤器。
缓存击穿:这时由于并发用户特别多,同时读缓存没读到数据,又同时去数据库去取数据,引起数据库压力瞬间增大,造成过大压力。和缓存雪崩不同的是,缓存击穿指并发查同一条数据(热点数据),缓存雪崩是不同数据都过期了,很多数据都查不到从而查数据库。解决方案:读数据库时通过setnx加锁其他线程自旋等待、缓存预热、异步更新、热点数据设置不过期等。
数据不一致:两种不一致,一种是与数据库不一致。一种是多个redis节点间不一致。解决方案:与数据库不一致的每一次更新数据时都先删除缓存,再修改数据库,修改完之后再删除一次缓存,这样保证读取到的数据是最新数据。多个节点不一致可以减少缓存失效时间,让数据早过期早更新。
数据并发竞争:数据并发竞争在大流量系统也比较常见,比如车票系统,如果某个火车车次缓存信息过期,但仍然有大量用户在查询该车次信息。又比如微博系统中,如果某条微博正好被缓存淘汰,但这条微博仍然有大量的转发、评论、赞。上述情况都会造成并发竞争读取的问题。解决方案:加写回操作加互斥锁,查询失败默认值快速返回;对缓存数据保持多个备份,减少并发竞争的概率。
热点key问题:明星结婚、离婚、出轨这种特殊突发事件,比如奥运、春节这些重大活动或节日,还比如秒杀、双12、618 等线上促销活动,都很容易出现 Hot key 的情况。解决方案:多级缓存、缓存预热、缓存集群分片并进行自主扩容、异步主动更新缓存、bigkey缓存默认值如null避免穿透。
Big Key问题:存储的值(Value)非常大,常见场景:热门话题下的讨论、大V的粉丝列表、序列化后的图片、没有及时处理的垃圾数据。
大key的影响:大key会大量占用内存,在集群中无法均衡;Redis的性能下降,主从复制异常;在主动删除或过期删除时会操作时间过长而引起服务阻塞。
大key的处理:
优化big key的原则就是string减少字符串长度,list、hash、set、zset等减少成员数。
1、string类型的big key,尽量不要存入Redis中,可以使用文档型数据库MongoDB或缓存到CDN上。如果必须用Redis存储,最好单独存储,不要和其他的key一起存储。采用一主一从或多从。
2、单个简单的key存储的value很大,可以尝试将对象分拆成几个key-value, 使用mget获取值,这样分拆的意义在于分拆单次操作的压力,将操作压力平摊到多次操作中,降低对redis的IO影响。hash, set,zset,list 中存储过多的元素,可以将这些元素分拆。(常见)
3、删除大key时不要使用del,因为del是阻塞命令,删除时会影响性能。使用 lazy delete (unlink命令),删除指定的key(s),若key不存在则该key被跳过。但是,相比DEL会产生阻塞,该命令会在另一个线程中回收内存,因此它是非阻塞的。 这也是该命令名字的由来:仅将keys从key空间中删除,真正的数据删除会在后续异步操作。
你是如何解决热Key问题的
热 Key 问题是指在缓存系统中,某些特定的缓存key受到高频访问,导致对这些热门数据的读取/写入操作集中在少数几个缓存节点上,使得这些节点的负载过高,而其他节点负载较轻甚至空闲。这会造成系统性能不均衡,可能导致部分请求响应变慢或服务不可用。
解决热 Key 问题有这些方案:
- 缓存预热:在系统启动或业务低峰期,通过批量加载或预先访问热门数据,将这些热门数据提前加载到缓存中。这样可以避免大量请求同时涌入导致的热点问题,提高系统的稳定性和性能。
- 动态散列:将缓存节点组织成一个哈希环,根据缓存键的哈希值将数据分散存储在多个节点上。通过增加缓存节点的数量,让请求更均匀地分布在各个节点上,减轻热 Key 对单个节点的压力。当节点数量发生变化时,可以通过一致性哈希算法进行平滑迁移,避免数据大规模迁移带来的负载过高。
- 多级缓存:在发现热key以后,把热key加载到系统的JVM中,针对这种热key请求,会直接从本地缓存中取,而不会直接请求redis;
- 数据分片:将数据按特定规则(如数据范围、业务维度等)分成多个片段,分别存储在不同的缓存节点上。这样可以使热 Key 所在的数据尽量均匀地分布在多个节点上,减轻单个节点的压力。
- 读写分离:读写分离可以将读操作与写操作分开处理,降低单个节点的负载。在主从复制模式下,可以将读操作分发到从节点上,从而分担主节点的压力。此外,可以使用代理层如Redis Sentinel或Twemproxy实现自动故障转移和读写分离。
- 限流:限流是通过控制请求的速率来防止系统过载。在应用层实现限流,可以有效减轻热点Key对Redis的压力。常见的限流算法有漏桶算法和令牌桶算法。
- 熔断降级:熔断降级是在系统出现问题时,自动降低系统功能的一种策略。在应用层实现熔断降级,可以在Redis出现热点Key问题时,快速降低对Redis的访问压力。熔断降级可以通过开源工具如Hystrix实现。
通过综合使用以上解决方案,有效地应对热 Key 问题,提高缓存系统的性能和稳定性。
redis使用场景
- 缓存(ehcache/memcached):redis的所有数据是放在内存中的(内存数据库)
- 某些特定应用场景下替代传统数据库:比如社交类的应用
- 时效性信息控制:如session共享、购物车、验证码缓存、登录session缓存、股票控制等。
- 为热点数据加速查询(主要场景):如热点商品,热点新闻、热点资讯、推广类等高访问量信息。
- 任务队列:如秒杀、抢购、购票排队等。
- 即时信息查询:如各类排行榜、各类网站访问统计、公交到站、即时点赞评论数量、在线人数信息(聊天室、网站)设备信号等。
- 分布式数据共享:如分布式集群架构的session共享。
- 消息队列:用作中间件实现异步通信和任务队列。
- 分布式锁
redis分布式锁介绍
使用setns来争抢锁(基于CAS乐观锁思想),使用expire设置过期时间防止忘记释放锁。Redis的setnx命令可以在key不存在时设置key的值,因此可以基于此实现分布式锁。一个进程在执行加锁操作时,先尝试使用setnx占据锁的key,如果返回值为1,表示获得了锁;否则表示锁已被其他进程占据,等待一段时间后再次尝试。
与zookeeper锁区别:
• redis 分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。
• zk 分布式锁,注册个监听器即可,不需要不断主动尝试获取锁,ZK获取锁会按照加锁的顺序,所以是公平锁,性能和mysql差不多,和redis差别大
如何用redis实现热点新闻列表
使用redis hash散列 和zset有序集合实现文章的热度排行和点赞排行.
①使用hash,首先为文章建立散列,存入基本信息,包括title、文章简介、id等。
②在zset中以浏览数和点赞数为权重,分别建立浏览数排行和点赞数排行,初始化所有文章的浏览数和点赞数。浏览和点赞时,更新文章的浏览数和点赞数。
③从zset中获取topN文章的id,然后从hash中获取文章内容(如果没有从数据库中读取并更新到缓存中)
如何用redis实现秒杀系统
①如何解决超卖:mysql乐观锁+redis预减库存+redis缓存卖完标记
②热key问题如何解决:redis集群+本地缓存+限流+预热+定时刷新+key加随机值分布在多个实例中
③如何应对高并发读请求:多级缓存+限流
④如何保证缓存数据一致性:延迟双删,写请求先删缓存再更新数据库,然后延迟再次删除缓存。读请求更新缓存。
参考:秒杀系统架构优化思路
如何用redis实现排行榜
Redis提供有序集合(SortedSet)结构,支持按照某个字段为权重进行排序,因此可以用来实现排行榜系统。比如统计音乐的播放次数、新闻的阅读量、用户的积分排名等。
如何用redis实现在线人数统计
显示网站的用户在线数的解决思路
①mysql中维护在线用户表,记录用户的登录时间和登出时间
②使用Redis统计、显示网站并发用户数:
a. 每当用户访问服务时,把该用户的 ID 写入ZSORT队列,权重为当前时间;
b. 根据权重(即时间)计算一分钟内该机构的用户数(Zrange命令);
c. 删掉一分钟以上过期的用户(Zrem命令)。
如何用redis实现消息队列
Redis提供了消息队列功能,可以作为轻量级的消息中间件使用,支持多个生产者和消费者。生产者可以使用lpush或rpush向一个列表插入消息,而消费者则使用blpop或brpop从列表中弹出消息并进行处理。(l&r是左和右的意思,代表队列头和队列尾)
如何用redis实现计数器
Redis 的incr/decr命令可以对一个key进行原子性自增/自减操作,因此经常被用作计数器。比如统计网站访问量、商品的销量、评论的点赞数等。也可用于数据库分库分表生成全局自增ID。