该笔记是本身在一个月时间内作出的总结,知识点排序不是很整齐,可能有错漏(欢迎指正),知识体系覆盖了javacore、jvm、gc优化、多线程开发,redis、kafka、zookeeper,mysql,hystrix,spring等核心知识点,javacore是本身基于jdk1.六、1.七、1.8作出的一些总结,redis重点是在内存管理这块(基于c++源码理解作出的总结)html
1,幂等 推荐分布式锁(乐观锁)。 场景 <1>,一个订单不容许被屡次支付(包括并发状态下不容许被多我的同时支付) 下单前对订单状态(status字段)校验,对订单加上乐观锁(加上一个字段lock),只有加锁成功的人才能进行支付。 或者针对每一个订单生成惟一支付日志,保证一个未支付的订单只容许被一个线程支付。 <2>,库存扣减,不容许超卖。 须要考虑场景,在c端展现层,读取缓存的方式,若是库存扣减了,消息异步更新缓存。 在对库存更改的时候,使用分布式锁,锁住某个产品id的库存,只容许一个线程去更改。前端
2,redis集群搭建。 基于redis 3.0集群模式,多个master节点根据hash分布在16384槽上,每一个master节点挂靠多个slave节点。 集群是好多个redis一块儿工做的,若是为了保证集群不是那么容易挂掉,因此呢,理论上就应该给集群中的每一个节点至少一个slave redis节点。java
3,redis集群模式: <1>standalone类型架构,单节点结构(非集群模式)。 缺点:单节点,存储空间和并发访问能力有颇有限,很容易发生缓存穿透,流量直接打入db。node
<2>redis主从,一个master挂着多个slave, 优势:master通常只接受写入流量,slave负责读取,提升了负载能力(主从复制是乐观复制,当客户端发送写执行给主,主执行完当即将结果返回客户端,并异步的把命令发送给从,从而不影响性能)。 缺点: A,Redis不具有自动容错和恢复功能,主机从机的宕机都会致使前端部分读写请求失败,须要等待机器重启或者手动切换前端的IP才能恢复。 B,主机宕机,宕机前有部分数据未能及时同步到从机,切换IP后还会引入数据不一致的问题,下降了系统的可用性。 C,主从机器都是全量备份的数据(浪费内存),单机须要更大内存,存储空间受限,不易扩容。mysql
<3>哨兵模式:Redis 2.8中提供了哨兵工具来实现自动化的系统监控和故障恢复功能,哨兵的做用就是监控redis主、从节点是否正常运行,主出现故障自动将从节点转换为主节点。 哨兵的做用就是监控Redis系统的运行情况。它的功能包括如下两个 (1)监控主节点和从节点是否正常运行。 (2)主节点出现故障时自动将从节点转换为主节点。 备注:哨兵也是集群部署,集群初始化时配置。nginx
哨兵工做原理: 哨兵(sentinel) 是一个分布式系统,你能够在一个架构中运行多个哨兵(sentinel) 进程,这些进程使用流言协议(gossipprotocols)来接收关于Master是否下线的信息,并使用投票协议(agreement protocols)来决定是否执行自动故障迁移,以及选择哪一个Slave做为新的Master。 每一个哨兵(sentinel) 会向其它哨兵(sentinel)、master、slave定时发送消息,以确认对方是否”活”着,若是发现对方在指定时间(可配置)内未回应,则暂时认为对方已挂(所谓的”主观认为宕机” Subjective Down,简称sdown)。若“哨兵群”中的多数sentinel,都报告某一master没响应,系统才认为该master"完全死亡"(即:客观上的真正down机,Objective Down,简称odown),经过必定的vote算法,从剩下的slave节点中,选一台提高为master,而后自动修改相关配置. 虽然哨兵(sentinel) 释出为一个单独的可执行文件 redis-sentinel ,但实际上它只是一个运行在特殊模式下的 Redis 服务器,你能够在启动一个普通 Redis 服务器时经过给定 --sentinel 选项来启动哨兵(sentinel).哨兵(sentinel) 的一些设计思路和zookeeper很是相似c++
优势:哨兵集群模式是基于主从模式的,全部主从的优势,哨兵模式一样具备,主从能够切换,故障能够转移,系统可靠性更高。 缺点: 主从机器都是全量备份的数据(浪费内存),单机须要更大内存,存储空间受限,不易扩容,在集群容量达到上限时在线扩容会变得很复杂。为避免这一问题,运维人员在系统上线时必须确保有足够的空间,这对资源形成了很大的浪费。 哨兵模式还存在脑裂问题 Redis 哨兵模式脑裂:master所在机器忽然脱离了正常的网络,跟其余slave机器不能链接,可是实际上master还运行着 此时哨兵可能就会认为master宕机了,而后开启选举,将其余slave切换成了master,这个时候,集群里就会有两个master,也就是所谓的脑裂redis
解决方案: min-slaves-to-write 1 min-slaves-max-lag 10 要求至少有1个slave,数据复制和同步的延迟不能超过10秒,若是说一旦全部的slave,数据复制和同步的延迟都超过了10秒钟 那么这个时候,master就不会再接收任何请求了上面两个配置能够减小异步复制和脑裂致使的数据丢失 上面的配置就确保了,若是跟任何一个slave丢了链接,在10秒后发现没有slave给本身ack,那么就拒绝新的写请求,所以在脑裂场景下,最多就丢失10秒的数据。 连接:www.jianshu.com/p/f6ceae73c…算法
<4>redis 3.0 cluster:分布式存储。即每台redis存储不一样的内容,共有16384个slot。每一个redis分得一些slot,hash_slot = crc16(key) mod 16384 找到对应slot,键是可用键,集群至少须要3主3从,且每一个实例使用不一样的配置文件,主从不用配置,集群会本身选。 备注:它们之间经过互相的ping-pong判断是否节点能够链接上。若是有一半以上的节点去ping一个节点的时候没有回应,集群就认为这个节点宕机了,而后去链接它的备用节点。若是某个节点和全部从节点所有挂掉,咱们集群就进入faill状态。还有就是若是有一半以上的主节点宕机,那么咱们集群一样进入发力了状态。这就是咱们的redis的投票机制。spring
优势:具有哨兵模式的优势,数据分散存储,内存利用率更加高,支持在线扩容。 缺点:若是某个slot上面的master和slave都挂掉,就会出现集群不可用。
备注:在Redis Cluster3.0动态扩容时,新增的Master节点是没有数据的,主节点若是没有slots的话,存取数据就都不会被选中,须要手动把slot及其中数据迁移到新增的Master中(参考Redis集群官方中文教程),操做指令支持节点从新洗牌(调增slot对应的数据)。
<5>Jedis sharding集群 Redis Sharding能够说是在Redis cluster出来以前业界广泛的采用方式,其主要思想是采用hash算法将存储数据的key进行hash散列,这样特定的key会被定为到特定的节点上(采用一致性哈希算法,将key和节点name同时hashing,而后进行映射匹配) <6>中间件代理 常见中间件: Twemproxy Codis nginx
3.0 cluster那么这个集群是如何判断是否有某个节点挂掉了呢? 首先要说的是,每个节点都存有这个集群全部主节点以及从节点的信息。 它们之间经过互相的ping-pong判断是否节点能够链接上。若是有一半以上的节点去ping一个节点的时候没有回应,集群就认为这个节点宕机了,而后去链接它的备用节点。若是某个节点和全部从节点所有挂掉,咱们集群就进入faill状态。还有就是若是有一半以上的主节点宕机,那么咱们集群一样进入发力了状态。这就是咱们的redis的投票机制, (1)投票过程是集群中全部master参与,若是半数以上master节点与master节点通讯超时(cluster-node-timeout),认为当前master节点挂掉. (2)何时整个集群不可用(cluster_state:fail)? a:若是集群任意master挂掉,且当前master没有slave.集群进入fail状态,也能够理解成集群的slot映射[0-16383]不完整时进入fail状态. b:若是集群超过半数以上master挂掉,不管是否有slave,集群进入fail状态.
Redis cluster的slave选举流程:
当slave发现本身的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。因为挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程, 其过程以下: 1.slave发现本身的master变为FAIL 2.将本身记录的集群currentEpoch加1,并广播FAILOVER_AUTH_REQUEST信息 3.其余节点收到该信息,只有master响应,判断请求者的合法性,并发送FAILOVER_AUTH_ACK,对每个epoch只发送一次ack 4.尝试failover的slave收集FAILOVER_AUTH_ACK 5.超过半数后变成新Master 6.广播Pong通知其余集群节点。
SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小表明已复制的数据越新,持有最新数据的slave将会首先发起选举(理论上)
参考:www.cnblogs.com/liyasong/p/…
3,redis持久化(rdb和aof) RDB:在指定的时间间隔能对数据进行快照存储。 优势:使用单独子进程来进行持久化,主进程不会进行任何IO操做,保证了redis的高性能 缺点:RDB是间隔一段时间进行持久化,若是持久化之间redis发生故障,会发生数据丢失,数据的准确性不高。 AOF:AOF持久化方式记录每次对服务器写的操做,当服务器重启的时候会从新执行这些命令来恢复原始的数据,AOF命令以redis协议追加保存每次写的操做到文件末尾.Redis还能对AOF文件进行后台重写,使得AOF文件的体积不至于过大。 优势:能够保持更高的数据完整性,所以已成为主流的持久化方案 缺点:AOF文件比RDB文件大,且恢复速度慢。
4,redis高可用原理分析:blog.csdn.net/qq_41849945… 备注:实际仍是一主多从的结构
5,redis如何实现主从复制?以及数据同步机制? Redis主从复制通常都是异步化完成(复制功能不会阻塞主服务器),Redis主从复制能够根据是不是全量分为全量同步和增量同步,
<1>Redis全量复制通常发生在Slave初始化阶段,这时Slave须要将Master上的全部数据都复制一份(master生成一份全量的rdb快照文件,发送给slave)。具体步骤以下: a,从服务器链接主服务器,发送SYNC命令; b,主服务器接收到SYNC命名后,开始执行BGSAVE命令生成RDB文件并使用缓冲区记录此后执行的全部写命令; c,主服务器BGSAVE执行完后,向全部从服务器发送快照文件,并在发送期间继续记录被执行的写命令; d,从服务器收到快照文件后丢弃全部旧数据,载入收到的快照; e,主服务器快照发送完毕后开始向从服务器发送缓冲区中的写命令; f,从服务器完成对快照的载入,开始接收命令请求,并执行来自主服务器缓冲区的写命令;
<2>Redis增量复制是指Slave初始化后开始正常工做时主服务器发生的写操做同步到从服务器的过程。 增量复制的过程主要是主服务器每执行一个写命令就会向从服务器发送相同的写命令,从服务器接收并执行收到的写命令。
4,redis如何压缩AOF文件,具体过程以下: redis调用fork ,如今有父子两个进程 <1>,子进程根据内存中的数据库快照,往临时文件中写入重建数据库状态的命令 <2>,父进程继续处理client请求,除了把写命令写入到原来的aof文件中。同时把收到的写命令缓存起来。这样就能保证若是子进程重写失败的话并不会出问题。 <3>,当子进程把快照内容写入已命令方式写到临时文件中后,子进程发信号通知父进程。而后父进程把缓存的写命令也写入到临时文件。 <4>,如今父进程可使用临时文件替换老的aof文件,并重命名,后面收到的写命令也开始往新的aof文件中追加。 <5>,须要注意到是重写aof文件的操做,并无读取旧的aof文件,而是将整个内存中的数据库内容用命令的方式重写了一个新的aof文件,这点和快照有点相似。
简单总结:如何缩小AOF文件大小:文件重写是指按期重写AOF文件(产生新的AOF文件),减少AOF文件的体积。须要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件(为了压缩aof的持久化文件。redis提供了bgrewriteaof命令。收到此命令redis将使用与快照相似的方式将内存中的数据 以命令的方式保存到临时文件中,最后替换原来的文件)
参考:www.cnblogs.com/xingzc/p/59…
5,AOF缩减自身文件大小的时候,来了新的写请求怎么办? 子进程同步完内存中数据以后,会发出指令,通知父进程把最近的写请求操做刷入新的aof文件。
6,redis multi,pipeline的区别 redis交互流程:业务应用服务器(如hotel-goods-service)--->redis client--->redis server
备注:redis属于典型的c/s架构
multi特色: <1>实际上当咱们使用multi操做时,redis client是一条条发送数据到 redis server,这些请求是积压在服务端的queue里面,而后依次一次执行完毕,redis服务端一次性返回全部命令返回结果,服务端执行这段操做是开启事务机制的。 <2>因为每发送一条指令,都须要单独发给服务器,服务器再单独返回“该条指令已加入队列”这个消息。这是比Pipeline慢的缘由之一。 <3>Multi执行的时候会先暂停其余命令的执行(事务机制),相似于加了个锁,直到整个Multi结束完成再继续其余客户端的请求。这是Multi能保证一致性的缘由,也是比Pipeline慢的缘由之二 <4>因为服务端开启了事务机制,所以multi是原子性的。 <5>因为redis client逐条发送请求到redis server中的queue,所以multi属于服务端缓冲。
pipeline特色: <1>redis client将全部命令打包一次性发送。发送成功后,服务端不用返回相似“命令已收到”这样的消息,而是一次性批量执行全部命令,成功后再一次性返回全部处理结果。 <2>服务端处理命令的时候,不须要加锁,而是与其余客户端的命令混合在一块儿处理,因此没法保证一致性。 <3>因为是redis client一次打包发送出去的请求,所以pipeline是客户端缓冲 <4>因为pipeline把请求打包发送给redis server,少了与redis server的屡次交互,所以性能更加好。
参考:blog.walkerx.cn/2018/07/08/…
8,redis动态扩容 过程描述: HASH_SLOT = CRC16(key) mod 16384 经过key与slot的映射算法,计算出当前key应该存储在哪一个slot中,从公式中能够看出,当前key与slot的映射是固定不变的。因为每一个Master负责一部分slot,可知在Master节点数量调整时,slot与Master映射的关系也会调整,也就是说slot和master之间有个映射表的。
在动态扩容过程当中slot的特色: 举例:MasterA节点(原集群中的旧机器)迁移部分slot到MasterB节点 MIGRATING状态是发生在MasterA节点中的一种槽的状态,预备迁移槽的时候槽的状态首先会变为MIGRATING状态,这种状态的槽会实际产生什么影响呢?当客户端请求的某个Key所属的槽处于MIGRATING状态的时候,影响有下面几条
<1>若是Key存在则成功处理 <2>若是Key不存在,则返回客户端ASK,仅当此次请求会转向另外一个节点,并不会刷新客户端(redis-client)中node的映射关系,也就是说下次该客户端请求该Key的时候,还会选择MasterA节点 <3>若是Key包含多个命令,若是都存在则成功处理,若是都不存在,则返回客户端ASK,若是一部分存在,则返回客户端TRYAGAIN,通知客户端稍后重试,这样当全部的Key都迁移完毕的时候客户端重试请求的时候回获得ASK,而后通过一次重定向就能够获取这批键。
IMPORTING状态是发生在MasterB节点中的一种槽的状态,预备将槽从MasterA节点迁移到MasterB节点的时候,槽的状态会首先变为IMPORTING。IMPORTING状态的槽对客户端的行为有下面一些影响:
<1>正常命令会被MOVED重定向,若是是ASKING命令则命令会被执行,从而Key没有在老的节点已经被迁移到新的节点的状况能够被顺利处理; <2>若是Key不存在则新建; <3>没有ASKING的请求和正常请求同样被MOVED,这保证客户端node映射关系出错的状况下不会发生写错;
简述:redis在动态扩容时,须要集群里面的旧机器的部分slot迁移到新机器,因为只涉及部分slot迁移,所以这些待迁移的slot会变成迁移状态(MIGRATING),迁移过程当中旧机器仍然接受读取和写入流量,若是key在旧机器不存在,请求将转发到新扩容的节点(如MasterB),等数据迁移完成再更新slot映射关系表便可。
参考:www.cnblogs.com/wxd0108/p/5…
9,单机redis如何提升并发 <1>redis性能瓶颈在io,所以单key不该该存储大值(大key分段存储)。 <2>pipeline代替multiGet操做。 <3>写入redis的数据作压缩。 <4>当业务场景不须要数据持久化时,关闭全部的持久化方式能够得到最佳的性能(数据持久化时须要在持久化和延迟/性能之间作相应的权衡,其实是在持久化的时候,数据占用了内核的页缓存,致使可用页缓存空间紧张) <5>存储对象使用hash,避免修改其中一项信息时,须要把整个对象取回,而且修改操做须要对并发进行保护。
参考:www.cnblogs.com/moonandstar…
数据类型(type) |
---|
编码方式(encoding) |
数据指针(ptr) |
---|
虚拟内存(vm) |
LRU计时时钟 |
---|
数据类型:string,list,hash,set,sorted set, 编码方式:raw,int,ht,zipmap,linkedlist,ziplist,intset lru计时时间:记录对象最后一次被访问的时间,当配置了 maxmemory和maxmemory-policy=volatile-lru | allkeys-lru 时, 用于辅助LRU算法删除键数据。可使用object idletime {key}命令在不更新lru字段状况下查看当前键的空闲时间
备注:首先最重要的一点是不要开启 Redis 的 VM 选项,即虚拟内存功能,这个原本是做为 Redis 存储超出物理内存数据的一种数据在内存与磁盘换入换出的一个持久化策略,可是其内存管理成本也很是的高,而且咱们后续会分析此种持久化策略并不成熟,因此要关闭 VM 功能,请检查你的 redis.conf 文件中 vm-enabled 为 no。 其次最好设置下redis.conf中的 maxmemory 选项,该选项是告诉 Redis 当使用了多少物理内存后就开始拒绝后续的写入请求,该参数能很好的保护好你的 Redis 不会由于使用了过多的物理内存而致使 swap,最终严重影响性能甚至崩溃
Redis内部使用一个redisObject对象来表示全部的key和value,redisObject最主要的信息如上图所示:type表明一个value对象具体是何种数据类型,encoding是不一样数据类型在redis内部的存储方式,好比:type=string表明value存储的是一个普通字符串,那么对应的encoding能够是raw或者是int,若是是int则表明实际redis内部是按数值型类存储和表示这个字符串的,固然前提是这个字符串自己能够用数值表示,好比:"123" "456"这样的字符串,当使用int存储时,比使用raw存储原生的字符串更加节省内存。
Redis内存优化点: <1>存储字符串时,若是值是整数,内部转成int存储,节省空间。 <2>Redis Hash是value内部为一个 HashMap,若是该Map的成员数比较少,则会采用相似一维线性的紧凑格式来存储该Map即省去了大量指针的内存开销,具体配置参数以下: hash-max-zipmap-entries 64 hash-max-zipmap-value 512 含义是当 value 这个 Map 内部不超过多少个成员时会采用线性紧凑格式存储,默认是64,即 value 内部有64个如下的成员就是使用线性紧凑存储,超过该值自动转成真正的 HashMap, hash-max-zipmap-value 含义是当 value 这个 Map 内部的每一个成员值长度不超过多少字节就会采用线性紧凑存储来节省空间。以上2个条件任意一个条件超过设置值都会转换成真正的 HashMap,也就不会再节省内存了,那么这个值是否是设置的越大越好呢,答案固然是否认的,HashMap 的优点就是查找和操做的时间复杂度都是 O(1) 的,而放弃 Hash 采用一维存储则是 O(n) 的时间复杂度,若是成员数量不多,则影响不大,不然会严重影响性能,因此要权衡好这个值的设置,整体上仍是最根本的时间成本和空间成本上的权衡。 <3>共享对象池,对象共享池指Redis内部维护[0-9999]的整数对象池。建立大量的整数类型redisObject存在内存开销,每一个redisObject内部结构至少占16字节,甚至超过了整数自身空间消耗。因此Redis内存维护一个[0-9999]的整数对象池,用于节约内存。 除了整数值对象,其余类型如list,hash,set,zset内部元素也可使用整数对象池。所以开发中在知足需求的前提下,尽可能使用整数对象以节省内存。
参考:www.cnblogs.com/jandison/p/…
11,页缓存技术 Page cache是经过将磁盘中的数据缓存到内存中,从而减小磁盘I/O操做,从而提升性能。此外,还要确保在page cache中的数据更改时可以被同步到磁盘上,后者被称为page回写(page writeback)。一个inode对应一个page cache对象,一个page cache对象包含多个物理page。 对磁盘的数据进行缓存从而提升性能主要是基于两个因素:第一,磁盘访问的速度比内存慢好几个数量级(毫秒和纳秒的差距)。第二是被访问过的数据,有很大几率会被再次访问。 参考:blog.csdn.net/damontive/a…
12,redis的过时数据删除策略和内存淘汰策略
1、过时数据删除策略(redis是两种策略配合一块使用) <1>惰性删除,无论过时的键,在这种策略下,当键在键空间中被取出时,首先检查取出的键是否过时,若过时删除该键,不然,返回该键。很明显,惰性删除依赖过时键的被动访问,对于内存不友好,若是一些键长期没有被访问,会形成内存泄露(垃圾数据占用内存),可是它属于cpu友好型,不须要占用太多cpu时间片。 <2>按期删除,redis建立一个定时任务随机扫描数据是否过时(CPU空闲时在按期serverCron任务中),逐出部分过时Key,具体删除过程以下
A,Redis配置项hz定义了serverCron任务的执行周期,默认为10,即CPU空闲时每秒执行10次; B,每次过时key清理的时间不超过CPU时间的25%,即若hz=1,则一次清理时间最大为250ms,若hz=10,则一次清理时间最大为25ms; C,清理时依次遍历全部的db; D,从db中随机取20个key,判断是否过时,若过时,则逐出; E,如有5个以上key过时,则重复步骤4,不然遍历下一个db; F,在清理过程当中,若达到了25%CPU时间,退出清理过程;
备注:每次删除限定在25%cpu时间片范围内,而且还判断过时的key的比例,好比超过25%过时key才继续下一此删除。这是一个基于几率的简单算法,基本的假设是抽出的样本可以表明整个key空间,redis持续清理过时的数据直至将要过时的key的百分比降到了25%如下。这也意味着在长期来看任何给定的时刻已通过期但仍占据着内存空间的key的量最多为每秒的写操做量除以4。
2、内存淘汰策略 Redis内存淘汰策略被激发,是内存使用达到必定的阈值才开始运行的(redis.conf里面配置),所以内存淘汰策略和key过时删除策略是两码事。
noeviction:当内存不足以容纳新写入数据时,新写入操做会报错。 allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key。 allkeys-random:当内存不足以容纳新写入数据时,在键空间中,随机移除某个key。 volatile-lru:当内存不足以容纳新写入数据时,在设置了过时时间的键空间中,移除最近最少使用的key。 volatile-random:当内存不足以容纳新写入数据时,在设置了过时时间的键空间中,随机移除某个key。 volatile-ttl:当内存不足以容纳新写入数据时,在设置了过时时间的键空间中,有更早过时时间的key优先移除。 以上策略是:(非)过时、随机、lru的组合
redis过时字典: redisDb结构的expires字典保存了数据库中全部键的过时时间,为过时字典,键就是数据库键,值是long long类型,毫秒经度的unix时间戳(过时时间)。
举例简述redis 3.0对allkeys-lru的实现流程 Redis服务器每执行一个命令,都会检测内存,判断是否须要进行数据淘汰 <1>判断目前已经使用的内存大小是否比设置的maxmemory要小,若是小于maxmemory,那么无须执行进一步操做。 <2>判断淘汰策略是否为noeviction,若是是,直接return回去,不进行任何内存淘汰。 <3>根据传入的对象大小,计算须要释放多少字节的内存 <4>开始随机采样,每次随机获取10个key,选出lru时间最小的key放入一个长度为16的pool里面,后续再不断随机取样10个key,若是lru时间比pool最小lru时间还小,就加入pool,直至pool填满,而后开始淘汰pool里面lru时间最小的,直至淘汰的空间足够存储须要的值。
备注:Redis的内存淘汰策略的选取并不会影响过时的key的处理。内存淘汰策略用于处理内存不足时的须要申请额外空间的数据;过时策略用于处理过时的缓存数据。 redis的 lru算法实际上不是很是准确的,是基于快速抽样比较的实现(若是使用双向链表的指针标记,占用的空间更加大) 参考:yq.aliyun.com/articles/25…
13,redis集群访问流量倾斜 <1>hot key出现形成集群访问量倾斜 场景:Hot key,即热点 key,指的是在一段时间内,该 key 的访问量远远高于其余的 redis key, 致使大部分的访问流量在通过 proxy 分片以后,都集中访问到某一个 redis 实例上。hot key 一般在不一样业务中,存储着不一样的热点信息。好比:新闻应用中的热点新闻内容,活动系统中某个用户疯狂参与的活动的活动配置,商城秒杀系统中,最吸引用户眼球,性价比最高的商品信息。 解决方案: 一,对hot key数据进行本地缓存。 二,利用分片算法的特性,对key进行打散处理,好比对key加上0~9的后缀,redis key通过分片分布到不一样的实例上,将访问量均摊到全部实例。 <2>big key在redis存储优化 若是big value 是个大json 经过 mset 的方式,将这个key的内容打散到各个实例中(分段存储),减少big key对数据量倾斜形成的影响。
7,mybatis延迟加载。 resultMap能够实现高级映射(使用association、collection实现一对一及一对多映射),association、collection具有延迟加载功能。 延迟加载:先从主表查询,须要时再从关联表去关联查询,大大提升数据库性能,由于查询单表要比关联查询多张表速度要快。 场景举例:查询出符合要求的订单数据,再去查出这些订单的用户信息,整个过程要执行多个sql,可是在一个resultMap返回结果。
实现原理:在createResultObject的时候,会判断当前返回值是否含有延迟加载的数据,若是有,就建立动态代理对象(Javasisst或者Cglib代理),执行被代理的方法,获取数据,并封装到resultMap。 延迟加载的好处:先在单表查询、须要时再从关联表去关联查询,大大提升 数据库性能,由于查询单表要比关联查询多张表速度要快。 参考:my.oschina.net/wenjinglian…
9,mybatis中#{}和将传入的数据直接显示生成在sql中(没法防止sql注入)。
10,tcp三次握手(两次不行吗?),四次挥手,为何这么作。 三次握手是为了创建tcp的双工通讯,四次挥手是为了可以保证tcp的半闭合状态。
11,网络丢包如何解决,分不一样业务场景。(ack,滑动窗口)
9,读写锁源码(todo) 参考:blog.csdn.net/yanyan19880… 10,各类线程实现。
//线程池大小固定为1
Executors.newSingleThreadExecutor();
//固定大小线程池由本身设定,即本身控制资源的固定分配
Executors.newFixedThreadPool(10);
//动态调整线程池的大小,最小为0,最大为int最大值,,newCachedThreadPool会大幅度提升大量短暂异步任务的性能,
//若是执行业务逻辑比较慢,会致使持续建立线程,致使cpu资源消耗殆尽
//为何使用SynchronousQueue?最多只能持有一个任务数据,当任务数据插入队列失败,会驱动建立新线程,SynchronousQueue做为主线程池的工做队列,它是一个没有容量的阻塞队列。每一个插入操做必须等待另外一个线程的对应移除操做。这意味着,若是主线程提交任务的速度高于线程池中处理任务的速度时,CachedThreadPool会不断建立新线程。极端状况下,CachedThreadPool会由于建立过多线程而耗尽CPU资源
Executors.newCachedThreadPool();//newCachedThreadPool不适合io密集型的网络请求,只适合计算密集型(每次请求耗时很短)
//基于延迟队列实现的延时任务线程池,周期性的执行所提交的任务
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " run");
}
}, 1000,2000, TimeUnit.MILLISECONDS);
复制代码
11,各类队列实现。 <1>PriorityBlockingQueue(无界队列) 内部使用reentrantlock(基于数组实现的堆排序,数组会动态扩容),每次入队和出队都须要加锁,保证线程安全。 PriorityBlockingQueue存储的对象必须是实现Comparable接口的 由于PriorityBlockingQueue队列会根据内部存储的每个元素的compareTo方法比较每一个元素的大小 须要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。所以使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,不然时间一长,会最终耗尽全部的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。PriorityBlockingQueue在take出来的时候会根据优先级 将优先级最小的最早取出
备注:优先队列扩容阶段为何释放锁,由于只有一把锁,扩容期间不影响数据读取(提升并发效率),扩容完以后再拷贝之前的数据(拷贝阶段加锁就能够了)。 因为PriorityBlockingQueue在空间不够的时候,会自增扩容数组进行堆排序,不须要对数据的put操做进行阻塞,只对数据获取进行阻塞,所以只须要一个condition(唤醒数据查询的线程)
<2>ArrayBlockingQueue,基于数组实现,只有1个锁(数据的写入不须要构造node节点,直接存储外部传入的引用,效率已经足够高,LinkedBlockingQueue构造node节点,耗时相对高一些,所以读写锁分离为了提升并发效率),添加数据和删除数据的时候只能有1个被执行,不容许并行执行。使用Condition notEmpty,Condition notFull来实现生产者-消费者模式(通知模式)。
<3>LinkedBlockingQueue,基于链表实现,只有2个锁(因为是无界,所以不用担忧队列写满,读写能够分离),放锁和读锁,两把锁分别管理head节点和last节点的操做,经过原子变量count控制队列长度状态,添加数据和删除数据是能够并行进行的,固然添加数据和删除数据的时候只能有1个线程各自执行。LinkedBlockingQueue将读和写操做分离,可让读写操做在不干扰对方的状况下,完成各自的功能,提升并发吞吐量。使用Condition notEmpty,Condition notFull来实现生产者-消费者模式(通知模式)
备注:ArrayBlockingQueue和LinkedBlockingQueue这两个阻塞队列,队列满了,放不进去会被阻塞,队列为空,取不出结果会被阻塞,所以须要两个condition。 压测报告:一千万条的数据进行多线程插入和读取,明显看出ArrayBlockingQueue比LinkedBlockingQueue性能强30%。
<4> SynchronousQueue经过将入队出队的线程绑定到队列的节点上,并借助LockSupport的park()和unpark()实现等待和唤醒,先到达的线程A需调用LockSupport的park()方法将当前线程进入阻塞状态,知道另外一个与之匹配的线程B调用LockSupport.unpark(Thread)来唤醒在该节点上等待的线程A。其内部没有任何容量,任何的入队操做都须要等待其余线程的出队操做,反之亦然。若是将SynchronousQueue用于生产者/消费者模式,那么至关于生产者和消费者手递手交易,即生产者生产出一个货物,则必须等到消费者过来取货,方可完成交易。
备注:SynchronousQueue没有使用condition(本质上基于LockSupport实现),直接使用了LockSupport的park()和unpark()实现等待和唤醒 参考:blog.csdn.net/vickyway/ar…
<5> DelayQueue的泛型参数须要实现Delayed接口,Delayed接口继承了Comparable接口,DelayQueue内部使用非线程安全的优先队列(PriorityQueue),并使用Leader/Followers模式,最小化没必要要的等待时间。DelayQueue不容许包含null元素。(借助LockSupport.parkNanos和unpark实现延时,reentrantlock实现安全操做),available.awaitNanos(delay)实现延时,available.awaitNanos内部基于LockSupport.parkNanos(this, nanosTimeout)实现挂起,指定时间范围内自动唤醒(由操做系统本身去调度);
特色:元素进入队列后,先进行排序(调用compareTo方法排序),而后,只有getDelay也就是剩余时间为0的时候, 该元素才有资格被消费者从队列中取出来,实际上只有队列头元素出队,其它才能出队,会受到头结点元素延时时间的影响。 备注:DelayQueue使用PriorityQueue(自动扩容的堆数组),所以数据的存入不须要阻塞,读取的时候才进行堵塞,所以只须要一个condition。
13,线程池核心线程如何设置。 若是计算密集型一般是cpu核数+1,io密集型是99线*qps/1000 14,Spring如何处理循环引用的 <1>,循环依赖的对象都经过构造器注入,会注入失败。(不管bean是singleton,仍是prototype,或者是混合了singleton、prototype),由于生成对象必须依赖构造方法,而构造方法里面须要对方的实例对象,所以造成了死循环。 <2>,循环依赖的bean都是经过属性注入,若是注入都是singleton对象,都能建立成功。若是注入都是prototype,就会失败。若是是混合singleton、prototype,只有先建立singleton才能保证成功,不然就会失败。 Spring对于构造器注入的对象会标记为正在建立中,若是在循环依赖建立过程当中发生依赖的对象正在建立中,会抛出异常。 归根结底是spring容器内部保留了singleton对象,prototype对象被丢失。 备注:singleton对象有三级缓存的概念,prototype对象没有,singleton在建立过程当中会检查三级缓存,依次从里面取出数据(前提这些对象都是调用构造方法建立成功了),若是成功取出就完成注入。prototype对象在建立过程当中会被标记为“正在建立中”,若是循环建立中,发现依赖的bean处于“正在建立中”,就会抛出异常。
15,spring初始化对象
单例对象(基于三级缓存实现) (1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象 (2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充 (3)initializeBean:调用spring xml中的init 方法。 单例来讲,在Spring容器整个生命周期内,有且只有一个对象,因此很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。
prototype对象(完成初始化以前存在一个set里面) 初始化流程,在初始化属性的时候,isPrototypeCurrentlyInCreation,会校验这个属性的bean是否在建立中,若是在建立中会抛出异常。
16,spring ioc ioc,依赖注入,在之前的软件工程编码过程当中,类的属性须要硬编码生成对象数据,耦合性较高,若是使用ioc,是在容器启动过程当中,在bean对象实例化过程当中须要检查其依赖数据,而且进行数据注入(setter,构造器注入),完成一个对象的实例化并实现了解耦合,而且可以对这些对象进行复用。
aop,主要分为两大类:一是采用jdk动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;二是采用动态织入的方式,引入特定的语法建立“方面”,是在类加载时期织入有关“方面”的代码。它利用一种称为"横切"的技术,并将那些影响了多个类的公共行为封装到一个可重用模块,简单理解是抽象出与业务逻辑无关的公共行为逻辑。
17,java的future编程 FutureTask实现了Runnable, Future接口,并实例化Callable对象,在线程开启运行时,执行线程任务的实现类的run方法,run执行完毕将结果引用赋值给outcome属性(若是任务线程没执行完,当前主线程会进入阻塞状态,任务线程执行完毕,会设置outcome,并解除主线程阻塞)。
18,hystrix
<1> 使用场景:在soa架构中,资源隔离(线程池、或者信号量隔离),熔断(防止雪崩效应)降级,依赖的服务使用不一样的commandKey(最小隔离单元,可能多个commandKey在一个线程池内)标注,实现隔离,线程池是HystrixCommandGroupKey标识。 <2> hystrix是如何经过线程池实现线程隔离的 Hystrix经过命令模式,将每一个类型的业务请求封装成对应的命令请求,好比查询订单->订单Command,查询商品->商品Command,查询用户->用户Command。每一个类型的Command对应一个线程池。建立好的线程池是被放入到ConcurrentHashMap中,好比查询订单。 <3> hystrix如何实现熔断的 用户请求某一服务以后,Hystrix会先通过熔断器,此时若是熔断器的状态是打开,则说明已经熔断,这时将直接进行降级处理,不会继续将请求发到线程池。若是熔断器是关闭状态,会检测最近10秒的请求错误率,当错误率超过预设的值(默认是50%)且10秒内超过20个请求,则开启熔断。熔断器默认是在5s后开始从新嗅探,会尝试放过去一部分流量进行试探,肯定依赖服务是否恢复。
<4> hystrix如何统计失败率 每一个熔断器默认维护10个bucket 每秒建立一个bucket 每一个blucket记录成功,失败,超时,拒绝的次数 当有新的bucket被建立时,最旧的bucket会被抛弃
<5> 核心参数 HystrixCommandGroupKey,线程池分组 HystrixCommandKey,线程池标识。
Circuit Breaker(熔断器)一共包括以下6个参数。 一、circuitBreaker.enabled 是否启用熔断器,默认是TURE。 二、circuitBreaker.forceOpen 熔断器强制打开,始终保持打开状态。默认值FLASE。 三、circuitBreaker.forceClosed 熔断器强制关闭,始终保持关闭状态。默认值FLASE。 四、circuitBreaker.errorThresholdPercentage 设定错误百分比,默认值50%,例如一段时间(10s)内有100个请求,其中有55个超时或者异常返回了,那么这段时间内的错误百分比是55%,大于了默认值50%,这种状况下触发熔断器-打开。 五、circuitBreaker.requestVolumeThreshold 默认值20.意思是至少有20个请求才进行errorThresholdPercentage错误百分比计算。好比一段时间(10s)内有19个请求所有失败了。错误百分比是100%,但熔断器不会打开,由于requestVolumeThreshold的值是20. 这个参数很是重要,熔断器是否打开首先要知足这个条件。 六、circuitBreaker.sleepWindowInMilliseconds 半开试探休眠时间,默认值5000ms。当熔断器开启一段时间以后好比5000ms,会尝试放过去一部分流量进行试探,肯定依赖服务是否恢复
欢迎打赏