[redis] 集群
redis集群
redis集群
先理解执行一条sql语句,在mysql内部会发生什么? 以执行一条update 语句为例: 客户端会先通过连接器建立连接,连接器会判断用户身份 这里是一条update语句,所以不需要经过查询缓存(注意,当表上有更新语句,会把整个查询缓存清空,所以在Mysql8.0这个功能就被移除了) 解析器会通过词法分析识别出关键字,构建出语法树,接着做语法分析,判断输入的语句是否符合MySQL语法 预处理器会判断表和字段是否存在 优化器确定执行计划(使用索引或者全表查询) 执行器负责具体执行,找到这一行然后更新 不过,更新语句的流程会涉及到undo log**,redo log,binlog**三种日志: undo log(回滚日志):是InnoDB存储引擎生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC redo log(重做日志):是InnoDB存储引擎生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复 bing log(归档日志):是Server层生成的日志,主要用于数据备份和主从复制 1.为什么需要undo log? 在执行一条“增删改”语句的时候,MySQL会隐式开启事务,执行完后自动提交事务 MySQL中执行一条语句后是否自动提交事务,是由autocommit 参数来决定的,默认是开启的 当事务执行过程中,都记录下回滚时需要的信息到一个日志中,那么在事务执行过程中发生MySQL崩溃后,可以通过这个日志回滚到事务之前的数据 实现这一机制就是 undo log**(回滚日志),它保证了事务的ACID特性中的原子性** 每当InnoDB引擎对每种操作进行回滚时,进行相反操作就行: 插入 - 删除 删除 - 插入 更新 - 更新为旧值 一条记录每次进行操作产生的undo log格式都有一个roll_pointer和一个trx_id事务id: trx_id:记录该记录是被哪些事务修改的 roll_pointer:指针可以将这些undo log串成一个链表,这个链表被称为版本链 另外,undo log可以跟Read View一起实现MVCC(多版本并发控制): 对于 读提交 和 可重复读 隔离级别的事务来说,它们的快照读(普通select语句)是通过Read View + undo log来实现的,区别在于创建Read View的时机不同 读提交:是在每一个select都会生成一个新的Read View,也意味着事务期间的多次读取同一数据,前后两次读的数据可能会出现不一致(不可重复读) 可重复读:是在启动事务时生成一个Read View,然后整个事务期间都在用这个Read View,这样保证了事务期间读到的数据都是事务启动时的记录 这两个隔离级别实现是通过事务的Read View里的字段和记录两个隐藏列trx_id和roll_pointer的对比 事务隔离级别是怎么实现的? 因此,undo log两大作用: 实现事务回滚,保障事务的原子性 实现MVCC(多版本并发控制)关键因素之一 Undo log是如何刷盘? ...
六、场景 1.缓存 Redis由于性能高效,通常可以做数据库存储的缓存,比如给Mysql做缓存 通常业务都满足二八原则,80%的流量在20%的热点数据上,所以缓存可以很大程度提高系统的吞吐量 1.1缓存基础 一般而言,缓存分为服务器缓存,客户端缓存 缓存一般有以下几种模式: 旁路缓存模式: 读穿透模式: 写穿透模式: 异步缓存写入模式: 旁路缓存模式(适用于读多写少) Cache Aside,旁路缓存模式,是最常见的模式,应用服务把缓存当作数据库的旁路,直接和缓存交互 读操作:服务端收到查询请求,先查询数据是否在缓存上,如果在,就用缓存数据直接打包返回,如果不存在,就去数据库查询,并放到缓存 写操作:cache aside模式一般先更新数据库,再直接删除缓存(更新相比删除更容易造成时序性问题) 适用于读多写少的场景,缺点是可能会出现缓存和数据库不一致的情况 这里的写操作,更新相比删除更容易造成时序性问题,具体举例:线程1更新mysql -> 线程2更新mysql -> 线程2更新缓存 -> 线程1更新mysql,这样就出现了时许性问题 该模型的缺点: 可能出现缓存和数据库不一致的情况,具体见:数据库和缓存如何保证一致性? 读穿透模式 与cache aside模式的区别主要在应用服务不再与缓存直接交互,而是直接去访问数据服务。 这里的数据服务理解为一个**代理服务,**用它来访问缓存和数据库 相比于旁路缓存模式,读穿透模式的优势是缓存对业务是透明的;缺点是缓存命中的性能不如旁路缓存模式,会多一层服务调用 写穿透模式 WriteThrough做了一层封装:有缓存服务先写入Mysql,再同步写入Redis,这样及时加载或更新了缓存数据(理解为,应用程序由一个单独的访问源,而存储服务自己维护访问逻辑) 在使用WriteThrough时,一般都配合使用ReadThrough来使用 适用情况: 对缓存及时性要求更高 不能忍受数据丢失和数据不一致 异步缓存写入模式(Write-Behind) write-Behind和Write-Through相同点是都是写入时会更新数据库、也会更新缓存 不同点是:Write-Behind是先写缓存,后异步把数据一起写入数据库 数据库写操作可以用不同的方式完成: 收集写操作并在某个时间点慢慢写入 合并几个写操作成为一个批量操作,一起批量写入 异步写操作极大降低了请求延迟,并减轻了数据库的负担,但是代价是安全性不够,如果缓存中的数据还没写入数据库,存储服务发生了崩溃,那么数据就丢失了 1.2缓存异常 缓存穿透 问题背景 缓存穿透是指**缓存和数据库都没有的数据,**而用户不断发起请求。 在流量大的时候,DB可能就挂掉了,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞 解决方案 接口层增加校验,如用户鉴权校验,id做击穿校验,id<=0的直接拦截 从缓存取不到的数据,在数据库中也没有取到,这时可以将key-value对写成key-null,缓存有效时间写短点,例如30s 布隆过滤器:bloomfilter类似于一个hash set,用于快速判断某个元素是否存在于集合中,关键在于hash算法和容器大小 布隆过滤器: 原理:布隆过滤器底层是一个64位的整型,将字符串用多个Hash函数映射不同的二进制位置,将整型中对应位置设置为1 优点:空间、时间消耗都很小 缺点:结果不完全准 缓存击穿 问题背景 缓存击穿是指缓存中没有但数据库中有的数据(一般缓存时间到期),这时由于并发的用户过多,同时读缓存没有数据又同时查询数据库,引起数据库压力瞬时增大 解决方案 热点数据支付续期,持续访问的数据不断续期,避免因为过期失效而被击穿 发现缓存失效,重建缓存加互斥锁,当线程查询缓存发现缓存不存在就会尝试加锁,线程抢锁,拿到锁的线程进行查询数据库,然后重建缓存 缓存雪崩 问题背景 指大量的应用请求因为异常无法在Redis缓存中处理,直接打到数据库。这里的异常就是:缓存中数据大批量到过期时间,而查询数据量巨大,引起数据库压力过大甚者宕机 ...
锁的类型 Mysql的锁,根据加锁的范围可以分为全局锁、表级锁和行锁三类 全局锁 要使用全局锁,执行下面这条命令: flush tables with read lock 执行之后,整个数据库就处于只读状态,这时其他线程执行以下操作,就会被阻塞 对数据的增删改,比如insert、delete、update等 对表结构的更改操作,比如alter table、drop table等 要释放全局锁,执行下面的命令: unlock tables 全局锁的应用场景: 全局锁主要用于做全库逻辑备份,这样在备份数据库期间,不会因为数据或者结构的更新,而出现备份文件的数据与预期的不一样 加全局锁带来的缺点:会导致业务停滞,因为加全局锁之后,整个数据库都只是只读状态,不能更新数据 可以通过开启事务,在可重复读的隔离级别下,即使其他事务更新了表的数据,也不会影响备份数据库时的Read View, 备份数据库的工具是mysqldump ,在使用mysqldump时加上-single-transaction 参数的时候,就会在备份数据库之前开启事务 表级锁 MySQL里面表级锁有以下几种: 表锁 元数据锁(MDL) 意向锁 AUTO-INC锁 表锁 使用下面的命令对表加锁和释放锁 // 加读锁 lock tables <table_name> read; // 写锁 lock tables <table_name> write; // 释放锁 unlock tables; 表锁会影响别的线程和本线程的读写操作 元数据锁(MDL) 对于MDL,我们不需要显示使用,因为当我们在对数据库进行操作时,会自动给这个表上加MDL: 对一张表进行CURD操作时,加的是MDL读锁 对一张表做结构变更操作的时候,加的是MDL写锁 MDL是为了保证当前用户对表执行CRUD操作时,防止其他线程对这个表结构做了变更 MDL是在事务提交之后才会释放,这意味着事务执行期间,MDL是一直持有 需要注意的是,在事务启用之后,如果事务A没有提交,此时如果有表结构的修改请求发起,就会发生阻塞,这个阻塞也会导致其他CURD的请求被阻塞住 这是因为申请MDL锁的操作会形成一个队列,队列中写锁获取优先级大于读锁,一旦出现MDL写锁等待,会阻塞该表后续的CRUD操作 意向锁 在使用InnoDB引擎的表里对某些记录加上共享锁之前,需要先在表级别加上一个意向共享锁 在使用InnoDB引擎的表里对某些记录加上独占锁之前,需要先在表级别加上一个意向独占锁 在执行insert、update、delete操作时,需要先对表上加 意向独占锁,然后对该记录加独占锁 而普通的select是不会加行级锁,普通的select语句是利用MVCC实现一致性读,是无锁的 // select也是可以对记录加共享锁和独占锁, // 先在表上加上意向共享锁,然后对读取的记录加共享锁 select ... lock in share mode; // 先表上加上意向锁,然后再读取记录加独占锁 select ... for update 意向锁的目的是为了快速判断表里是否有记录被加锁 AUTO-INC锁 表里面的主键通常设置成自增的,在插入数据时,可以不指定主键的值,数据库会自动给主键赋值递增的值,这主要是通过AUTO-INC锁实现的 Auto-Inc锁是特殊的表锁机制,不是在一个事务提交后才释放,而是再执行完插入语句后就会立即释放 行级锁 InnoDB引擎是支持行级锁的,而MyISAM引擎并不支持行级锁 行级锁的类型主要有三类: ...
五、持久化 0.持久化介绍 redis是跑在内存里的,当程序重启或者服务崩溃,数据就会丢失,所以需要持久化,即把数据保存到可永久保存的存储设备中 持久化方法 redis提供两种方式来持久化: 1.RDB(Redis Database) 记录Redis某个时刻的全部数据,这个方法的本质就是数据快照,直接保存二进制数据到磁盘,后续通过加载RDB文件恢复数据 2.AOF(Append Only File) 记录执行的每条命令,重启之后通过重放命令来恢复数据,AOF是记录操作日志,后续通过日志重放恢复数据 两种持久化方法的对比(*) 上面两种持久化方法对比:RDB(快照恢复)和AOF(日志恢复) 体积方面:相同数据量下,RDB体积小,因为RDB记录的是二进制紧凑型数据 恢复速度方面:RDB是数据快照,可以直接加载,而AOF文件恢复,相当于重放情况,RDB显然更快 数据完整性:AOF记录了每条日志,RDB是间隔一段时间记录一次,用AOF恢复数据通常会更加完整 RDB好还是AOF好 业务本身需要的是缓存数据并且不是一个海量访问,可以不用开持久化 对数据本身十分重视,可以同时开启RDB和AOF,注意,在同时开启的情况下,只会用AOF来加载,如果只有RDB文件而没有AOF文件,不会用RDB文件去恢复数据,如果逻辑是你自主开启选择AOF,表明要强一点的一致性,但是AOF文件缺失,此时不会去使用RDB,业务RDB会少很多数据,此时启动是一个空库 1.RDB RDB文件的内容是**二进制数据,**记录的是某一瞬间的内存数据,是实际的数据,也叫做快照 在Redis恢复数据时,RDB恢复数据的效率会比AOF高,因为会直接读取RDB文件到内存即可,不需要像AOF那样还需要额外执行操作命令的步骤才能恢复数据 1.1 RDB怎么使用 Redis提供两个命令来使用RDB文件,分别是save 和bgsave save :执行了save命令,会在主线程生成RDB文件,由于和执行操作命令在同一个线程,所以写入RDB文件的时间太长,会阻塞主线程 bgsave :执行了bgsave命令,会创建一个子进程来生成RDB文件,这样可以避免主线程的阻塞 RDB文件的加载工作是在服务器启动时自动执行,Redis并没有提供专门加载RDB文件的命令 在Redis的配置文件中,有以下的选项来实现每隔一段时间自动执行一次bgsave命令 # 这里写的是save,实际上是bgsave save 900 1 # 900s之内,对数据库进行至少1次修改 save 300 10 # 300s之内,对数据库进行至少10次修改 save 60 10000 # 60s之内,对数据库进行至少10000次修改 Redis的快照是全量快照,也就是说每次执行快照,都是把内存中的所有数据记录到磁盘中 RDB快照的缺点:当服务器发送故障时,丢失的数据会比AOF更多 1.2 执行快照时(bgsave),修改数据会发生什么? 在执行bgsave时,主线程是可以继续执行操作命令,由子线程来构建RDB文件,所以内存里的数据是可以修改的 实现这一功能靠的是写时复制****技术 流程如下: 执行bgsave命令时,会fork()创建子进程,此时子进程和父进程是共享同一片内存数据 主进程创建子进程时,子进程会复制父进程的页表,但是页表指向的物理内存跟主进程是同一个物理内存 此时如果主线程由写命令的执行,就会发生写时复制,物理内存才会被复制一次 发生写时复制后,RDB快照保存的是原本的内存数据 这是因为创建bgsave子进程后,由于父子进程共享所有内存数据,所以可以直接将数据写入到RDB文件 当主进程对共享的内存数据是只读****操作,那么主进程和bgsave子进程是相互不影响 当主进程对共享的内存数据进行写操作时,就会发生写时复制,这块数据的物理内存就会被复制一份,然后主线程在这个数据副本进行写操作,此时bgsave子进程继续把原来的数据(原物理内存)写入到RDB文件 执行bgsave时,在极端情况下,如果所有的共享内存都被修改,则此时的内存占用是原先的两倍 1.3 什么时候执行RDB持久化 主动执行命令save 主动执行命令bgsave ...