1、 对于查询频率高的字段创建索引;
2、 对排序、分组、联合查询频率高的字段创建索引;
3、 索引的数目不宜太多 原因:
a、每创建一个索引都会占用相应的物理控件; b、过多的索引会导致 insert、update、delete 语句的执行效率降低;
4、若在实际中,需要将多个列设置索引时,可以采用多列索引 如:某个表(假设表名为 Student),存在多个字段(StudentNo, StudentName, Sex, Address, Phone,BirthDate),其中需要对> StudentNo,StudentName 字段进行查 询,对 Sex 字段进行分组,对 BirthDate> 字段进行排序,此时可以创建多列索引 index index_name (StudentNo, StudentName, Sex,> BirthDate);#index_name 为索 引名 在上面的语句中只创建了一个索引,但是对 4 个字段都赋予了索引的功能。 创建多列索引,需要遵循 BTree 类型,即第一列使用时,才启用索引。 在上面的创建语句中,只有 mysql 语句在使用到> StudentNo 字段时,索引才会被 启用。 如: select * from Student where StudentNo = 1000; #使用到了 StudentNo 字段,索引被启用。 以使用 explain 检测索引是否被启用如:explain select * from Student where StudentNo = 1000;
5、选择唯一性索引 唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。例如,学 生表中学号是具有唯一性的字段。为该字段建立唯一性索引可以很快的确定某个学生的信息。如果使用姓名的话,可能存 在同名现象,从而降低查询速 度。
6、尽量使用数据量少的索引 如果索引的值很长,那么查询的速度会受到影响。例如,对一个 CHAR(100)类型 的字段进行全文检索
需要的时间肯定要比对 CHAR(10)类型的字 段需要的时间要多。 7、尽量使用前缀来索引如果索引字段的值很长,最好使用值的前缀来索引。例如,TEXT 和 BLOG 类型的 字段,进行全文检索会很浪费时间。如果只检索字段的前面的若干个字符,这样可以 提高检索速度。 8、删除不再使用或者很少使用的索引表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响 B+ tree 树索引, B tree,散列
InnoDB: 事务型存储引擎, 并且有较高的并发读取频率
MEMORY: 存储引擎,存放在内存中,数据量小, 速度快 Merge:
ARCHIVE: 归档, 有很好的压缩机制
- 分库分表: 同样量的数据平均存储在不同数据库相同表(或不同表)中,减轻单 表压力,如果还是很大,就可以每个库在分多张表,根据 hash 取值或者其他逻辑判断 将数据存储在哪张表中
- 读写分离: 数据库原本就有主从数据库之分,查询在从服务器,增删改在主服务 器,
- 归档和操作表区分: 建一张归档表,将历史数据放入,需要操作的表数据单独存 储
- 索引啊之类的创建,对于数据量很大,百万级别以上的单表,如果增删改操作不频 繁的话, 可以创建 bitMap 索引,速度要快得多
- 共享锁:要等第一个人操作完,释放锁,才能操作
- 更新锁:解决死锁,别人可以读,但不能操作
- 排他锁:读写都被禁用
- 意向锁(xlock): 对表中部分数据加锁,查询时,可以跳过
- 计划锁: 操作时,别的表连接不了这张表,
原子性: 所有操作要么全部成功,要么全部失败
一致性: 例如转账,一个事务执行前和执行后必须一致
隔离性: 防止脏读, 重复读问题 持久性:
永久性:提交数据库
Concat: 字符串拼接, 或者 || MConcat: 字符串拼接, 或者 || Instr: 指定字符串位置 Length: 长度
Trim: 去空格 Lower: 小写 Upper:大写 Nvl: 判断空 Replace: 替换 Substr: 截取 Floor:
向下取整 To_number: To_char: To_date: Decode: 判断函数等等
- 查询谓词没有使用索引的主要边界,换句话说就是 select *,可能会导致不走索 引
- 单键值的 b 树索引列上存在 null 值,导致 COUNT(*)不能走索引。索引列存在空 值
- 索引列上有函数运算,导致不走索引
- 隐式类型转换导致不走索引。
- 表的数据库小或者需要选择大部分数据,不走索引
- !=或者<>(不等于),可能导致不走索引
- 表字段的属性导致不走索引,字符型的索引列会导致优化器认为需要扫描索引大 部分数据且聚簇因子很大,最终导致弃用索引扫描而改用全表扫描方式,
- 使用 like, in 等, 可能导致不走索引
确定 ID 存储用 64 位,1 个 64 位二进制 1 是这样的 00000000…1100…0101, 切割 64> 位,某段二进制表示成 1 个约束条件,前 41 位为毫秒时间,后紧接 9 位为 IP,IP之后为自增的二进制,记录当前面位数相同情况下是第几个 id,如现在有 10 台机器,这个 id 生成器生成 id 极限是同台机器 1ms内生成 2 的 14 次方个 ID。 分布式唯一 ID = 时间戳 << 41 位, int 类型服务器编号 << 10,序列自增sequence。每个时间戳内只能生成固定数量如(10 万)个自增号,达到最大值则同步 等待下个时间戳,自增从 0开始。将毫秒数放在最高位,保证生成的 ID 是趋势递增 的,每个业务线、每个机房、每个机器生成的 ID 都是不同的。如 39bit毫秒数|4bit 业务线|2bit 机房|预留|7bit 序列号。高位取 2016 年 1 月 1 日 1 到现在的毫秒数,系 统运行 10年,至少需要 10 年 x365 天 x24 小时 x3600 秒 x1000 毫秒=320x10~9,差不 多 39bit给毫秒数,每秒单机高峰并发小于 100,差不多 7bit 给每毫秒的自增号,5 年内机房小于 100 台机器,预留 2bit给机房,每个机房小于 100 台机器,预留 7bit 给每个机房,业务线小于 10 个,预留 4bit 给业务线标识。 64bit 分布式ID(42bit 毫秒+5bit 机器 ID+12 位自增)等 生成分布式 ID 的方式:A,2 个自增表,步长相互隔开B,时间的毫秒或者纳秒 C, UUID D,64 位约束条件(如上)
第一点,NIO 少了 1 次从内核空间到用户空间的拷贝。ByteBuffer.allocateDirect()分配的内存使用的是本机内存而不是 Java 堆上的内存,和网络或者磁盘交互都在操作系统的内核空间中发生。allocateDirect()的区别 在于这块内存不由 java 堆管理,但仍然在同一用户进程内。
第二点,NIO 以块处理数据,IO 以流处理数据
第三点,非阻塞,NIO1 个线程可以管理多个输入输出通道
LRU:从已设置过期时间的数据集合中挑选最近最少使用的数据淘汰
random:从已设置过期时间的数据中挑选任意数据淘汰
ttl:从已设置过期时间的数据集合中挑选将要过期的数据淘汰。
notenvision:禁止驱逐数据 如 mysql 中有 2千万数据,redis 只存储 20 万的热门数据。LRU 或者 TTL 都满足热点 数据读取较多,不太可能超时特点。
redis特点:速度块,O(1),丰富的数据类型,支持事物原子性,可用于缓存,比 memecache 速度块,可以持久化数据。
常见问题和解决:Master 最好不做持久化如 RDB 快照和 AOF 日志文件;如果数据比较 重要,某分 slave 开启 AOF备份数据,策略为每秒 1 次,为了主从复制速度及稳定, MS 主从在同一局域网内;主从复制不要用图状结构,用单向链表更为稳定M-S-S-SS。。。。;redis 过期采用懒汉+定期,懒汉即 get/set 时候检查 key 是否过期,过期则删除key,定期遍历每个 DB,检查制定个数个 key;结合服务器性能调节并发情 况。 过期淘汰,数据写入 redis 会附带 1个有效时间,这个有效时间内该数据被认为是正 确的并不关心真实情况,例如对支付等业务采用版本号实现,redis 中每一份数据都 维持 1个版本号,DB 中也维持 1 份,只有当 redis 的与 DB 中的版本一致时,才会认 为 redis 为有效的,不过仍然每次都要访问DB,只需要查询 version 版本字段即可。
MyISM 采用表级锁,对 Myism 表读不会阻塞读,会阻塞同表写,对 Myism 写则会阻塞 读和写,即一个线程获得 1个表的写锁后,只有持有锁的线程可以对表更新操作,其 他线程的读和写都会等待。
InnoDB采用行级锁,支持事务,例如只对 a列加索引,如果 update …where a=1 and b=2 其实也会锁整个表, select 使用共享锁,update insert delete 采用排它 锁,commit 会把锁取消,当然 select by id for update 也可以制定排它锁。
实时队列采用双队列模式,生产者将行为记录写入 Queue1,worker 服务从 Queue1 消 费新鲜数据,如果异常则写入Queue2(主要保存异常数据),RetryWorker 会监听Queue2,消费异常数据,如果还未处理成功按照一定的策略等待或者将异常数据再写 入 Queue2,如果数据发生积压可以调整 worker的消费游标,从最新数据重新开始消 费,保证了最新data 得到处理,中间未处理的一段则可以启动 backupWorker 指定起止游标在消费完指定区间的数据后,backupWorker 会自动停止。
DB 降级开关后,可直接写入redis(storm),同时将数据写入一份到 Retry 队列,在 开启 DB 降级开关后消费 Retry 队列中的数据,从而把数据写入到mysql 中,达到最终 一致性。MYSQL 切分为分片为 2 的 N 次方,例如原来分为两个库 d0 和d1 均放在 s0 服务器上,s0 同时有备机 s1,扩容只要几步骤:确保 s0 到 s1 服务器同步顺利,没有明 显延迟;s0暂时关闭读写权限;确保 s1已经完全同步到 s0 更新;s1 开放读写权限; d1 的 dns 由 s0 切换到 s1;s0 开放读写权限。
4 大特性:原子性,一致性,分离性,持久性
隔离级别:
读提交:写事务禁止读 读未提交:写事务允许读 可重复读:写事务禁止读事务,读禁止写
序列化:全部禁止
详细说明:读提交 1 个事务开始写则全部禁止其他事务访问该行。读未提交 1 个事务开始写则不允许其他事务同时写,但可以读。可重复读 读事务会禁止写事务,写事物 则禁止其他任何事务。序列化性能最低,全部禁止,串行执行。MYSQL 默认的是可重复读。
Internet 控制报文协议,处于网络层(IP 层)
NIO Reactor 反应器模式,例如汽车是乘客访问的实体 reactor,乘客上车后到售票员 处 Acceptor 登记,之后乘客便可休息睡觉了,到达乘客目的地后,售票员 Aceptor 将其 唤醒即可。持久 TCP 长链接每个 client 和 server之间有存在一 个持久连接,当 CCU(用 户并发数量)上升,阻塞 server 无法为每个连接运行 1 个线程,自己开发 1 个二进制协议,将 message 压缩至 3-6 倍,传输双向且消息频率高,假设 server 链接了 2000 个 client,每个 client平均每分钟传输 1-10 个 message,1 个messaged 的大小为几百字节 /几千字节,而 server 也要向 client广播其他玩家的当前信息,需要高速处 理消息的能 力。Buffer,网络字节存放传输的地方,从 channel 中读写,从 buffer作为中间存储格 式,channel 是网络连 接与 buffer 间数据通道,像之前的 socket 的 stream。
未对作废数据内存单元置为 null,尽早释放无用对象的引用,使用临时变量时,让引 用变量在推出活动域后自动设置为null,暗示垃圾收集器收集;程序避免用 String 拼接,用 StringBuffer,因为每个 String 会占用内存一块区域;尽量少用静态变量 (全局不会回收);不要集中创建对象尤其大对象,可以使用流操作;尽量使用对象池,不再循环中创建对象,优化配置;创建对象到单例 getInstance 中,对象无法回收被单例引用;服务器 session时间设置过长也会引起内存泄漏。
平衡二叉树,左右高度之差不超过 1,Add/delete 可能造成高度>1,此时要旋转,维 持平衡状态,避免二叉树退化为链表,让Add/Delete 时间复杂度但控制在 O(log2N),旋转算法 2 个方法,1 是求树的高度,2 是求 2 个高度最大值,1 个空树高> 度为-1,只有 1 个根节点的树的高度为 0,以后每一层+1,平衡树任意节点最多有 2 个儿子,因此高度不平衡时,此节点的 2棵子树高度差为 2。例如单旋转,双旋转, 插入等。
红黑树放弃完全平衡,追求大致平衡,保证每次插入最多要 3 次旋转就能平衡。
是否递归的调用;大量循环;全局变量是否过多;数组,List,Map 数据是否过大; 用 DDMS 工具检查地方。 内存溢出的原因 过多使用了 static;static 最好只用 int 和 string 等基本类型;大量的递归或者死循环;大数据项的查询,如返回表的所有记录,应该采用分页查询。检查是否有数 组、List、map中存放的是对象的引用而不是对象,这些引用会让对应对象不能被释 放。 栈过大会导致内存占用过多,频繁页交换阻碍效率。
懒汉 2 种,枚举,饿汉 2 种,静态内部类,双重校验锁(推荐)。
三个文件:字典文件,频率文件,位置文件。词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键字的频率信息和位置信 息。
field的概念,用于表达信息所在位置(如标题中,文章中,url 中),在建索引中, 该 field 信息也记录在词典文件中,每个关键词都有一个field 信息(因为每个关键字一定 属于一个或多个 field)。
关键字是按字符顺序排列的(lucene 没有使用 B 树结构),因此lucene 可以用二元 搜索算法快速定位关键词。
假设要查询单词 “live”,lucene 先对词典二元查找、找到该词,通过指向频率文件的指针读出所有文章号,然后返回结果。词典通常非常小,因而,整个过程的时间是毫秒级的。
对词典文件中的关键词进行了压缩,关键词压缩为<前缀长度,后缀>,例如:当前词为“阿拉伯语”,上一个词为“阿拉伯”,那么“阿拉伯语”压缩为<3,语>。对数字的压 缩,数字只保存与上一个值的差值。
ZooKeeper 运行期间,集群中至少有过半的机器保存了最新数据。集群超过半数的机 器能够正常工作,集群就能够对外提供服务。
zookeeper 可以选出 N 台机器作主机,它可以实现 M:N 的备份;keepalive 只能选出 1 台机器作主机,所以keepalive 只能实现 M:1 的备份。
通常有以下两种部署方案:双机房部署(一个稳定性更好、设备更可靠的机房,这个机房就是主要机房,而另外一个机房则更加廉价一些,例如,对于一个由 7 台机器组成的 ZooKeeper 集群,通常在主要机房中部署 4台机器,剩下的 3 台机器部署到另外一个机 房中);三机房部署(无论哪个机房发生了故障,剩下两个机房的机器数量都超过半数。在三个机房中都部署若干个机器来组成一个 ZooKeeper 集群。假设机器总数为 N,各机房 机器数:N1 = (N-1)/2 ,N2=1~(N-N1)/2 ,N3 = N - N1 - N2 )。
水平扩容就是向集群中添加更多机器,Zookeeper2 种方式(不完美),一种是集群整 体重启,另外一种是逐台进行服务器的重启。
redis 本身支持 16 个数据库,通过 数据库 id 设置,默认为 0。 例如 jedis 客户端设置。一:JedisPool(org.apache.commons.pool.impl.GenericObjectPool.Config poolConfig, String host, int port, int timeout, String password, int database);
第一种通过指定构造函数 database 字段选择库,不设置则默认 0 库。
二:jedis.select(index);调用 jedis 的 select 方法指定。
A,Broker NIO 异步消息处理,实现了 IO 线程与业务线程分离;
B,磁盘顺序写;
C, 零拷贝(跳过用户缓冲区的拷贝,建立一个磁盘空间和内存的直接映射,数据不 再复制到用户态缓冲区);
D,分区/分段(每次文件操作都是对一个小文件的操作,非常轻便,同时也增加了并 行处理能力);
E,数据压缩
F,批量发送 (可以指定缓存的消息达到某个量的时候就发出去,或者缓存了固定的时 间后就发送出去,大大减少服务端的 I/O 次数)
一、查询与删除操作是天然幂等
二、唯一索引,防止新增脏数据
三、token 机制,防止页面重复提交
四、悲观锁 for update
五、乐观锁(通过版本号/时间戳实现, 通过条件限制 where avai_amount- #subAmount# >= 0)
六、分布式锁
七、状态机幂等(如果状态机已经处于下一个状态,这时候来了一个上一个状态的变 更,理论上是不能够变更的,这样的话,保证了有限状态机的幂等。)
八、select + insert(并发不高的后台系统,或者一些任务JOB,为了支持幂等,支 持重复执行)
a、客户端发送自己支持的加密规则给服务器,代表告诉服务器要进行连接了
b、服务器从中选出一套加密算法和 hash算法以及自己的身份信息(地址等)以证书的 形式发送给浏览器,证书中包含服务器信息,加密公钥,证书的办法机构
c、客户端收到网站的证书之后要做下面的事情:
c1、验证证书的合法性
c2、如果验证通过证书,浏览器会生成一串随机数作为密钥 K,并用证书中的公钥进 行加密
c3、用约定好的 hash 算法计算握手消息,然后用生成的密钥 K 进行加密,然后一起发 送给服务器
d、服务器接收到客户端传送来的信息,要求下面的事情:
d1、用私钥解析出密码,用密码解析握手消息,验证 hash 值是否和浏览器发来的一致
d2、使用密钥加密消息,回送 如果计算法 hash 值一致,握手成功
增加消费者的处理能力(例如优化代码),或减少发布频率
单纯升级硬件不是办法,只能起到一时的作用
考虑使用队列最大长度限制,RabbitMQ3.1 支持
给消息设置年龄,超时就丢弃
默认情况下,rabbitmq 消费者为单线程串行消费,设置并发消费两个关键属性 concurrentConsumers 和 prefetchCount,concurrentConsumers 设置的是对每个> listener 在初始化的时候设置的并发消费者的个数,prefetchCount 是每次一次性从 broker 里面取的待消费的消息的个数
建立新的 queue,消费者同时订阅新旧 queue
生产者端缓存数据,在 mq 被消费完后再发送到 mq
打破发送循环条件,设置合适的 qos 值,当 qos 值被用光,而新的 ack 没有被 mq 接收> 时,就可以跳出发送循环,去接收新的消息;消费者主动 block 接收进程,消费者感受到 接收消息过快时主动 block,利用 block 和 unblock 方法调节接收速率,当接收线程被 block 时,跳出发送循环。
新建一个 topic,partition 是原来的 10 倍;然后写一个临时的分发数据的 consumer 程序,这个程序部署上去消费积压的数据,消费之后不做耗时的处理,直接均匀轮询写入 临时建立好的 10 倍数量的 queue;接着临时征用 10 倍的机器来部署 consumer,每一批 consumer 消费一个临时 queue 的数据;等快速消费完积压数据之后,得恢复原先部署架 构,重新用原先的 consumer 机器来消费消息;
消息持久化:Exchange 设置持久化:durable:true;Queue 设置持久化;Message 持 久化发送。
ACK确认机制:消息发送确认;消息接收确认。
常见 6 种负载均衡算法:轮询,随机,源地址哈希,加权轮询,加权随机,最小连接 数。 nginx5
种负载均衡算法:轮询,weight,ip_hash,fair(响应时间),url_hash dubbo
负载均衡算法:随机,轮询,最少活跃调用数,一致性 Hash
队列实现持久化储存,下次启动自动载入。 但是实际需要看情况,大体思路是这样。 添加标志位,未处理 0,处理中 1,已处理 2。每次启动的时候,把所有状态为 1 的, 置为 0。或者定时器处理 关键性的应用就给电脑配个UPS。
DoS 是 Denial of Service 的简写就是拒绝服务。 DDoS 就是 Distributed Denial of Service 的简写就是分布式拒绝服务。 DRDoS 就是 Distributed Reflection Denial of Service
的简写,分布反射式拒绝服 务。 DoS、DDos 以及 DRDoS 攻击手段和防范措施
漏桶:水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入 速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求。
令牌桶算法:系统会按恒定 1/QPS 时间间隔(如果QPS=100,则间隔是 10ms)往桶里加入 Token,如果桶已经满了就不再加了.新请求来临时,会各自拿走一个 Token,如果没有Token 就拒绝服务。
基于 redis 实现的限流:假设每分钟访问次数不能超过 10 次,在 Redis 中创建一个 键, 过期 60 秒,对此服务接口的访问就把键值加 1,在 60 秒内增加到 10 的时候,禁止访 问服务接口。
计数器,滑动窗口
A、scheduler 是一个计划调度器容器(总部),容器里面可以盛放众多的 JobDetail 和trigger,当容器启动后,里面的每个 JobDetail 都会根据 trigger 按部就班自动 去 执行。
B、JobDetail是一个可执行的工作,它本身可能是有状态的。
C、Trigger 代表一个调度参数的配置,什么时候去调。
D、当 JobDetail 和Trigger 在 scheduler 容器上注册后,形成了装配好的作业 (JobDetail 和 Trigger所组成的一对儿),就可以伴随容器启动而调度执行了。
E、scheduler 是个容器,容器中有一个线程池,用来并行调度执行每个作业,这样可以提高容器效率。
行锁(共享锁和排他锁),表锁,页级锁,页级锁,意向锁,读锁,写锁,悲观锁, 乐观锁等
聚集索引:
索引中键值的逻辑顺序决定了表中相应行的物理顺序(索引中的数据物理存放地址和索引的顺序是一致的),可以这么理解:只要是索引是连续的,那么数据在存储介质上的 存储位置也是连续的。比方说:想要到字典上查找一个字,我们可以根据字典前面的拼音找到该字,注意拼 音的排列时有顺序的。 聚集索引就像我们根据拼音的顺序查字典一样,可以大大的提高效率。在经常搜索一 定范围的值时,通过索引找到第一条数据,根据物理地址连续存储的特点,然后检索相邻 的数据,直到到达条件截至项。
非聚集索引
索引的逻辑顺序与磁盘上的物理存储顺序不同。非聚集索引的键值在逻辑上也是连续 的,但是表中的数据在存储介质上的物理顺序是不一致的,即记录的逻辑顺序和实际存储的物理顺序没有任何联系。索引的记录节点有一个数据指针指向真正的数据存储位置。
总结如下:
如果一个主键被定义了,那么这个主键就是作为聚集索引 如果没有主键被定义,那么该表的第一个唯一非空索引被作为聚集索引 如果没有主键也没有合适的唯一索引,那么 innodb 内部会生成一个隐藏的主键作为聚 集索引,这个隐藏的主键是一个 6 个字节的列,改列的值会随着数据的插入自增。 InnoDB 引擎会为每张表都加一个聚集索引,而聚集索引指向的的数据又是以物理磁盘 顺序来存储的,自增的主键会把数据自动向后插入,避免了插入过程中的聚集索引排序问 题。如果对聚集索引进行排序,这会带来磁盘 IO 性能损耗是非常大的。
查询锁表信息
当前运行的所有事务
select * from information_schema.innodb_trx
当前出现的锁
select * from information_schema.innodb_locks
锁等待的对应关系
select * from information_schema.innodb_lock_waits
通过 select * from> information_schema.innodb_trx 查询trx_mysql_thread_id 然 后执行 kill 线程 ID KILL 8807;//后面的数字即时进程的 ID
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL
或 NULL) 的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。[这里指 到 叶子节点的路径]
Kafka 消息发送分同步(sync)、异步(async)两种方式。默认是使用同步方式,可通过 producer.type> 属性进行配置;Kafka 保证消息被安全生产,有三个选项分别是 0,1,- 1。
通过 request.required.acks 属性进行配置:
0 代表:不进行消息接收是否成功的确认(默认值);
1 代表:当 Leader 副本接收成功后,返回接收成功确认信息;
-1 代表:当 Leader 和 Follower 副本都接收成功后,返回接收成功确认信息;
网络异常 acks 设置为 0 时,不和 Kafka 集群进行消息接受确认,当网络发生异常等情况时,存 在消息丢失的可能; 客户端异常 异步发送时,消息并没有直接发送至 Kafka集群,而是在 Client 端按一定规则缓存并 批量发送。在这期间,如果客户端发生死机等情况,都会导致消息的丢失; 缓冲区满了异步发送时,Client 端缓存的消息超出了缓冲池的大小,也存在消息丢失的可能; Leader 副本异常 acks 设置为 1时,Leader 副本接收成功,Kafka 集群就返回成功确认信息,而 Follower 副本可能还在同步。这时 Leader 副本突然出现异常,新 Leader 副本(原 Follower 副本) 未能和其保持一致,就会出现消息丢失的情况;
以上就是消息丢失的几种情况,在日常应用中,我们需要结合自身的应用场景来选择 不 同的配置。想要更高的吞吐量就设置:异步、ack=0;想要不丢失消息数据就选:同步、ack=-1 策略
如果某个分区 patition 的 Leader 挂了,那么其它跟随者将会进行选举产生一个新的 leader,之后所有的读写就会转移到这个新的 Leader 上,在 kafka 中,其不是采用常见 的 多数选举的方式进行副本的 Leader 选举,而是会在 Zookeeper 上针对每个 Topic 维护 一 个称为 ISR(in-sync replica,已同步的副本)的集合,显然还有一些副本没有来得 及 同步。只有这个 ISR 列表里面的才有资格成为 leader(先使用 ISR 里面的第一个,如 果 不行依次类推,因为 ISR 里面的是同步副本,消息是最完整且各个节点都是一样的)。 通过 ISR,kafka 需要的冗余度较低,可以容忍的失败数比较高。假设某个 topic 有 f+1 个副本,kafka 可以容忍 f 个不可用,当然,如果全部 ISR 里面的副本都不可用,也可以 选择其他可用的副本,只是存在数据的不一致。
其实很简单主要是用二分查找算法,比如我们要查找一条 offest=10000 的文件,kafka 首先会在对应分区下的 log 文件里采用二分查看定位到某个记录该 offest =10000 这条消息的 log,然后从相应的 index 文件定位其偏移量,然后拿着偏移量到 log 里面直接获取。这样就完成了一个消息的检索过程。
1)普通集群: 以两个节点(rabbit01、rabbit02)为例来进行说明。 rabbit01 和 rabbit02 两个节点仅有相同的元数据,即队列的结构,但消息实体只存 在 于其中一个节点 rabbit01(或者 rabbit02)中。 当消息进入 rabbit01 节点的 Queue 后,consumer 从 rabbit02 节点消费时,RabbitMQ 会临时在 rabbit01、rabbit02 间进行消息传输,把 A 中的消息实体取出并经过 B 发送 给 consumer。所以 consumer 应尽量连接每一个节点,从中取消息。即对于同一个逻 辑 队列,要在多个节点建立物理 Queue。否则无论 consumer 连 rabbit01 或 rabbit02, 出 口总在 rabbit01,会产生瓶颈。当 rabbit01 节点故障后,rabbit02 节点无法取到 rabbit01 节点中还未消费的消息实体。如果做了消息持久化,那么得等 rabbit01 节点恢复,然后才可被消费;如果没有持久化的话,就会产生消息丢失的现象。
2)镜像集群: 在普通集群的基础上,把需要的队列做成镜像队列,消息实体会主动在镜像节点间同 步, 而不是在客户端取数据时临时拉取,也就是说多少节点消息就会备份多少份。该 模式带 来的副作用也很明显,除了降低系统性能外,如果镜像队列数量过多,加之大量 的消息 进入,集群内部的网络带宽将会被这种同步通讯大大消耗掉。所以在对可靠性要 求较高 的场合中适用 由于镜像队列之间消息自动同步,且内部有选举 master 机制,即使 master 节点宕机 也 不会影响整个集群的使用,达到去中心化的目的,从而有效的防止消息丢失及服务不可用等问题
使用 scroll(有状态)和 search after(无状态)的游标方式。
相比于单系统登录,sso 需要一个独立的认证中心,只有认证中心能接受用户的用户> 名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通 过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以 借此创建局部会话,局部会话登录方式与单系统的登录方式相同。这个过程,也就是单点 登录的原理.单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁.
MQ 做数据同步也会造成不一致,又需要引入监控,实时计算 2 个集群的数据同步,做 一致性同步。大部分来说,同步 es 和 solr不要在代码中去同步,同步失败无法保证事 务,而且业务耦合。可以使用 Databug 和 cancel 等工具去做代码解耦,MQ 支持重试,存储失败后抛出异常下次再处理。数据做异构,对外服务时任意拼装,MYSQL 在半同步复制 上做了一些优化,保证了一致性,引入了诸如 paxos 等主流算法保证强一致性问题。
当 DB(监听从库),binlog 有变化,cancel 监听到时候解析过滤发送 MQ(表名字,主键等)到变化的实时从库中查询数据同步到 ES 聚合表,MQ 可以重试,系统解耦。事务 log 挖掘县城会对 DB 的事务 log监听,并把这些事件发布到消息代理。
分布式服务调用可以实现跟踪系统,可以在业务日志中添加调用链 ID,各个环节 RPC 均添加调用时延,QPS 等。非业务组件应该少加入业务代码,服务调用采用买点,也会采用配置采样率方式,买 点 即当前节点的上下文信息,包含TraceId,RPCId,开始结束时间,类型,协议,调用 方 IP,端口,服务名等,以及其他异常信息,报文等扩展,日志采用离线+实时的如 flume 结合 kafka 等,应按照 TraceId 汇总日志后按 RPCId 顺序整理。
(1)每个 Sentinel 以每秒钟一次的频率向它所知的 Master,Slave 以及其他Sentinel 实例发送一个 PING 命令;
(2)如果一个实例(instance)距离最后一次有效回复 PING 命令的时间超过down-after-milliseconds 选项所指定的值, 则这个实例会被 Sentinel 标记为主观下线;
(3)如果一个 Master 被标记为主观下线,则正在监视这个 Master 的所有Sentinel 要以每秒一次的频率确认 Master的确进入了主观下线状态;
(4)当有足够数量的 Sentinel(大于等于配置文件指定的值)在指定的时间范围内确 认 Master 的确进入了主观下线状态,则 Master 会被标记为客观下线;
(5)在一般情况下, 每个 Sentinel 会以每 10 秒一次的频率向它已知的所有Master,Slave 发送 INFO 命令;当 Master 被 Sentinel 标记为客观下线时,Sentinel 向下线的 Master 的所有Slave 发送 INFO 命令的频率会从 10 秒一次改为每秒一次;
(6)若没有足够数量的 Sentinel 同意 Master 已经下线, Master
的客观下线状态就会被移除;
(7)若 Master 重新向 Sentinel 的 PING 命令返回有效回复, Master 的主观下线状态就会被移除。监控( Monitoring ): Redis Sentinel 实时监控主服务器和从服务器运行状态;自动故障转移:如果一个 master 不正常运行了,哨兵可以启动一个故障转移进程,将 一个 slave 升级成为 master,其他的 slave 被重新配置使用新的 master,并且应用 程序使用 Redis 服务端通知的新地址;
(1)使用 redis 的 set 集合
(2)使用 redis 的 bitmap(注意内存消耗)
(1)拆分集群
对于存在明显分界线的业务,可以按照业务、地域使用不同集群,这种拆分集群的思 路是非常靠谱的。对于我们的场景,已经按照地域拆分了集群,且同一地域的子业务间 分 界线不明显,拆分过多的集群维护成本较高。
(2)调整滚动周期
根据保留时长调整 index 滚动周期是最简单有效的思路。例如保留 3 天的数据按天滚 动, 保留 31天的数据按周滚动,保留一年的数据按月滚动。合理的滚动周期,可以在 存储 成本增加不大的情况下,大幅降低分片数量。对于我们的场景,大部分数据保留 31 天,在按周滚动的情况下,集群的总分片数可以 下降到 6.5w~个。
(3)合理设置分片数和副本数
除个别子业务压力较高外,大部分业务压力较小,合理设置单 Index 的分片数效果也 不 错。我们的经验是单个分片的大小在 10GB~30GB之间比较合适,对于压力非常小的业 务 可以直接分配 1 个分片。其他用户可结合具体场景考虑,同时注意单分片的记录条数 不 要超过上限2,147,483,519。 在平衡我们的业务场景对数据可靠性的要求 及 不同副本数对存储成本的开销 两个因素之后,我们选择使用一主一从的副本策略。 目前我们集群单 Index 的平均分配数为 3,集群的总分片数下降到 3w~个。
(4)分片分配流程优化
默认情况下,ES 在分配分片时会考虑分片 relocation 对磁盘空间的影响。在分片数较少时,这个优化处理的副作用不明显。但随着单机分片数量的上升,这个优化处理涉 及 的多层循环嵌套过程耗时愈发明显。可通过 cluster.routing.allocation.disk.include_relocations: false 关闭此功能,这对磁盘均衡程度影响不明显。
(5)预创建 Index
对于单集群 3w 分片的场景,集中在每周某天 0 点创建 Index,对集群的压力还是较大, 且存储空间存在波动。考虑到集群的持续扩展能力和可靠性,我们采用预创建方 式提前 创建分片,并把按 Index的创建时间均匀打散到每周的每一天。
(6)持续调整分片数
对于集群分片的调整,通常不是一蹴而就的。随着业务的发展,不断新增的子业务 或 原有子业务规模发生突变,都需要持续调整分片数量。 默认情况下,新增的子业务会有默认的分片数量,如果不足,会在测试阶段及上线初 期及时发现。随着业务发展,系统会考虑 Index 近期的数据量、写入速度、集群规模等 因 素,动态调整分片数量。
首先明确一个概念,就是指向数组的指针,和存放指针的数组。 指向数组的指针: char (*array)[5];含义是一个指向存放 5个字符的数组的指针。 存放指针的数组: char *array[5];含义是一个数组中存放了 5 个指向字符型数据的指针。 按照题意, 我理解为初始化一个存放指针的数组,char *array[2]={“China”,”Beijing”}; 其含义是初始化了一个有两个指向字符型数据的指针的数组,这两个指针分别指向字 符串”China”和”Beijing”。
我只要一听到被面试者说:“const 意味着常数”,我就知道我正在和一个业余者打 交道。去年 Dan Saks 已经在他的文章里完全概括了const 的所有用法,因此 ESP(译 者:Embedded Systems Programming)的每一位读者应该非常熟悉 const 能做什么和不 能做什么.如果你从没有读到那篇文章,只要能说出 const 意味着“只读”就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知 道更详细的答案,仔细读一下 Saks的文章吧。)如果应试者能正确回答这 个问题, 我将问他一个附加的问题:下面的声明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前两个的作用是一样,a是一个常整型数。第三个意味着 a 是一个指向常整型数的指 针(也就是,整型数是不可修改的,但指针可以)。第四个意思 a 是一个指向整 型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着 a是一个指向常整型数的常指针(也就是说,指针指向的整型 数是不可 修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。
顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字 const 呢?我也 如下的几下理由:
1). 关键字 const 的作用是为给读你代码的人传达非常有用的信 息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾 花很多时间清理 其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然, 懂得用 const 的程序员很少会留下的垃圾让别人来清理的。)
2).通过给优化器一些 附加的信息,使用关键字 const 也许能产生更紧凑的代码。
3). 合理地使用关键字 const 可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修 改。简而言之,这样可以减少 bug 的出现。
const关键字至少有下列 n 个作用:
(1)欲阻止一个变量被改变,可以使用 const 关键字。在定义该 const
变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为const,或二者同时指定为 const;
(3)在一个函数声明中,const 可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为 const 类型,则表明其是一个常函数,不能修改 类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为 const 类型,以使得其返回值不 为“左值”。例如: const classA operator*(const classA& a1,const classA& a2); operator的返回结果必须是一个const 对象。如果不是,这样的变态代码也不会 编译出错: classA a, b, c; (a * b) = c; // 对 ab 的结果赋值 操作(a * b) = c 显然不符合编程者的初衷,也没有任何意义。
在绝大多数情况下, 程序的功能是在编译的时候就确定下来的, 我们称之为静态特 性。 反之, 如果程序的功能是在运行时刻才能确定下来的,则称之为动态特性。 C++中, 虚函数,抽象基类, 动态绑定和多态构成了出色的动态特性。
不申明没有关系的。 不过,我总是喜欢显式申明,使得代码更加清晰。
函数和变量被 C++编译后在符号库中的名字与 C 语言的不同,被extern “C”修饰的 变量和函数是按照 C语言方式编译和连接的。由于编译后的名字不同,C++程序不能直 接调用 C 函数。C++提供了一个 C 连接交换指定符号 extern“C”来解决这个问题。
不知道这个题有什么陷阱,写到现在神经已经大了,一般来说先要把 TURE 和 FALSE 给 定义了,使用#define 就可以: #define TURE 1 #define FALSE 0 如果有一个变量 需要定义成 bool 型的,举个例子:bool a=TURE;就可以了。
false/true 是标准 C++语言里新增的关键字,而 FALSE/TRUE 是通过#define,这样的用途是解决程序在 C 与 C++中环境的差异,以下是 FALSE/TRUE 在 windef.h 的定义:
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
也就是说 FALSE/TRUE 是 int 类型,而 false/true 是 bool 类型;所以两者不一样的, 只不过我们在使用中没有这种感觉,因为 C++会帮你做隐式转换。
内联函数是在编译的时候已经做好将对应的函数代码替换嵌入到对应的位置,适用于 代码较少的函数。宏定义是简单的替换变量,如果定义的是有参数的函数形式,参数 不做类型校验。
char *strcpy(char *strDest, const char *strSrc)
{
if ( strDest == NULL || strSrc == NULL)
return NULL ;
if ( strDest == strSrc)
return strDest ;
char *tempptr = strDest ;
while( (*strDest++ = *strSrc++) != ‘’);
return tempptr ;
}
#include
void sort(int array[] );
int main()
{
int array[]={
45,56,76,234,1,34,23,2,3}; //数字任意给出
sort( array );
return 0;
}
void sort( int array[] )
{
int i,j,k;
for (i=1;i<=7;i++) {
if (array[i]>array[i-1]) {
k=array[i];
j=i-1;
do
{
array[j+1]=array[j];
j– ;
}
while(k>array[j]&&j>=0);
array[j+1]=k;
}
}
}
(1)隐藏。 当我们同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具 有全局可见性,故使用 static在不同的文件中定义同名函数和同名变量,而不必担心命名 冲突。
(2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序 刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和 static 变量。 (3)static 的第三个作用是默认初始化为 0.其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是 0×00,某些 时候这一特点可以减少程序员的工作量。
void GetMemory(char **p, int num){
*p = (char *)malloc(num);
}
void Test(void){
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行 Test 函数会有什么样的结果? 可以运行
delete []arry 释放的是多个同一类型的地址空间
delete arry 释放的是一个某种类型的地址空间
for (i=0; i<n; i++) {
if (condition)
DoSomething();
else
DoOtherthing();
}
if (condition) {
for (i=0; i<n; i++)
DoSomething();
}
else
{
for (i=0; i<n; i++)
DoSomething();
}
1)优点:程序简洁。 缺点:多执行了 n-1 次逻辑判断,并且打断了循环“流水线” 作业,使得编译器不能对循环进行优化处理,降低了效率。
2)优点:循环的效率高。缺点:程序不简洁。
- extern C 的作用是当程序被 C++编译器编译时,让后续的链接器以 C 方式来寻找函 数,方便 C++程序调用 C 程序。
- C++风格注释形如:// …,推荐使用这样的注释。但是,头文件说明和函数默认参数的注释,还是用 C 风格(/**/)的较好。 3) 不要写与编译器依赖紧密的代码 例如:printf(“a %d %d”, p(),q()),p 和 q 函数的执行前后顺序与编译器实现相 关,应当避免此类代码。类似的还有 c = p() * q() * r()。
4)尽量用 const,enum,inline 代替#define inline关键字用在函数调用展开,在类声明中定义并且实现的函数自动为内联函数。如果需要将其他函数定义为内联函数,则需要在函数实现头声明此关键字,才让编译
器尝试去内联,至于具体是否内联,还要求函数体满足一定条件才行,总体原则是短 小精悍。 在使用 define的场合,注意用()来保护宏函数。例如:#define MAX(a, b) ((a) > (b) ? (a) : (b))- struct在 C 和 C++下的异同,C 语言的 struct 不允许定义函数程序,而 C++语言下 的 struct 可以。
- 所有数据成员一律为 private 类型。如果派生类需要用到,那么在用到的时候再将 其改为 protect 类型,否则,一律声明为private 类型,对外隐藏。在具体声明时, 可以按类型来多段声明,比如私有控件,来一个 private,私有数据来另外一个private。
- 当类中至少包含一个虚函数时,才需要将其析构函数设置为虚函数。不要在构造/ 析构函数中调用虚函数。
8)以行为为中心的类设计,对外的 public 函数放在前面,需要继承的 protect 虚函 数紧随其后,再后面是 private的虚函数、普通函数以及成员变量。- 语法的背后含义是语义,接口设计要有明确的语义,不可模棱两可、职责不清。
- 如底层发生异常,则需要逐级上报,直到有能力处理此异常的层级来处理。如果 程序都没处理,则会被C++系统捕获并终止程序运行。异常可以将发生错误和处理错 误分离。
- 一般以传值来抛出异常,以 const引用来捕获异常,不涉及到异常对象的清理工 作,无对象切割问题,如本层级处理后还需要继续抛出异常,可调用 throw 来。
- 优先使用shared_ptr,它内部工作原理是引用计数,线程安全,支持扩展,推荐 使用。
10家大厂面试题精选 —头条篇
10家大厂面试题精选 —百度篇
10家大厂面试题精选 —小米篇
10家大厂面试题精选 —华为篇
10家大厂面试题精选 —中兴篇
10家大厂面试题精选 —美团篇
10家大厂面试题精选 —阿里篇
10家大厂面试题精选 —腾讯篇
10家大厂面试题精选 —京东篇
10家大厂面试题精选 —滴滴篇