使用的技术栈
1. 即时通信 —环信

* 我开发过的项目在涉及到 用户之间的即时通信, 联系客服等功能时, 使用的是环信的 即时通讯云IM技术.应用场景: 小程序社交, 单聊/群聊/聊天室, 私密社交, 直播互动等 功能如上图.使用官方提供的ServerSDK.
2. 分布式日志技术

日志消息是基于http的协议发送到logstash中, http协议会占用微服务的性能,关于分布式日志的采集尽量采用异步的方式收集, ELK的数据采集 也支持基于redis 或 kafka来采集日志数据. 需引入依赖, 其次修改logstash容器的配置, 修改配置文件logback.xml.
3. 数据库列式存储

# 将原本每一行存储一条对象数据, 变为多行共同存储一个对象数据. POJO对象
4. 项目中哪些地方遇到了分布式事务问题
# 存在前提: 1. 存在多服务之间的远程调用, 每个微服务使用独立的数据源. 2. 对每个数据源进行了增删改操作.# 存在位置: 1. shop模块中的 注册品牌功能.
5. 消息队列中间件RabbitMq

# MQ消息队列中间件,提供应用与应用之间的,安全可靠的消息传递方式. - 优点: 异步调用,降低服务于服务之间的耦合,提高的运行效率. 对请求的流量进行控制,保护微服务的安全. 通过消息持久化策略,保证消息的可靠性.# RabbitMQ支持的消息模式 - Basic Queue 简单队列. publisher(生产者) -> queue(队列) -> consumer(消费者) 生产者发消息给队列,消费者从队列中接收消息,队列与消费者是一对一的关系 - Work Queue 工作对列. publ -> queue -> cons1,cons2... 类似简单队列,但是队列和消费者是一对多的关系 - 发布/订阅模型 新增exchange(交换机)的角色,生产者发消息给交换机,交换机路由到queue,再由消费者消费 - Fanout 广播模式. publ -> exchange -> queue1,queue2... -> cons1,cons2.... 交换机接收到消息后,会广播给所有绑定它的队列,每个队列都会收到消息. - Direct 订阅模式 生产者发送消息的同时,需要指定routeingkey, 交换机只会把消息路由绑定指定routingkey的队列. 默认消费方式是轮询. 会浪费性能,可以开启能者多劳的消费方式. spring.rabbitmq.listener.simple.prefetch: 1 - Topic 通配符模式 Topic类型的Exchange可以让队列在绑定routeingkey的时候使用通配符. # 匹配一个或多个单词 * 匹配一个单词 ex.# ex.* #.ex 等等# springAMQP 基于AMQP协议提供的一套API,底层默认实现是RabbitMQ. - 自动声明队列,交换机及绑定关系 - 基于注解的监听器模式,异步接收消息 - 封装了RabbitTemplate工具,用于发送消息 - 默认序列化方式是JDK,可通过配置第三方转换器提高性能.# RabbitMQ 保证高可用 - 通过使用集群来保证高可用. 普通集群 镜像集群# RabbitMQ 保证消息的可靠性 * 可以在消息传递的全阶段,采用不同的方式,来保证消息的可靠性. - 生产者发送消息的可靠性. 消息确认机制 * 发送者确认. publisher-confirm - 消息成功投递到交换机,返回ack - 消息未投递到交换机,返回nack * 确认模式 - simple 同步等待结果,直到超时 - correlated 异步回调,定义ConfirmCallback * 发送者回调. - 消息投递到交换机,但是没有路由到队列。返回ACK,及路由失败原因。 - 定义ReturnCallback - 消息队列保证投递可靠性. 消息持久化 * 交换机持久化 * 队列持久化 * 消息持久化 - 消费者消费消息的可靠性. 消息回执,失败重试 * RabbitMQ默认阅后即焚.即确认消息被消费者消费后会立刻删除,通过消费者回执来确认. * 确认方式 - manual 手动ack,即开发者主动调用API发送ack - auto 自动ack,默认模式 - none 关闭ack,MQ假定消费者获取消息后会成功处理,因此消息投递后立即被删除 * 消费重试机制,如果消息消费失败,应采取的策略 - 自动补偿 在消费者出现异常时利用本地重试,而不是无限制的requeue到mq队列. - 在配置文件中,设置最大重试次数,等待时长等 - 但是在最大重试次数后,消息仍然消费失败,默认就会被丢弃 * 失败策略 - 死信队列,本地持久化.# 保证消息的幂等性 - 使用全局MessageID判断消费方是否消费 - 使用业务ID+逻辑保证唯一# 大量积压的消息如何快速消费 - 紧急扩容. 即临时声明一个分发数据的consumer, 再创建大量的临时queue,临时的consumer,来快速消费积压的消息. 在消息消费完毕后,再将队列和消费者数量恢复正常.
6. 非关系型数据库之 —Redis

● redis是键值(Key-Value)存储数据库。● 性能极高 – Redis读的速度是110000次/s,写的速度是81000次/s 。● 丰富的数据类型 – Redis支持 Strings, Lists, Hashes, Sets 及 Ordered Sets 数据类型操作。● Redis运行在内存中但是可以持久化到磁盘。# 持久化机制 ○ RDB持久化机制. 默认 ○ AOF持久化机制.# Redis的高可用架构 ○ 主从架构,实现读写分离. ○ 哨兵(Sentinel)机制来实现主从集群的自动故障恢复。 ○ 分片集群, 实现海量数据存储和高并发写入数据.
7. 非关系型数据库之 —MongoDb

#* 是一个非关系型数据库. 但是它的数据结构很类似关系型数据库mysql.* MongoDB中的记录是一个文档,它是一个由字段和值对(field:value)组成的数据结构即BSON, 类似于JSON, 或者说, 就是参数均是二进制表示的 JSON格式.* 文档的ID是自动生成的UUID, 因此, 映射的pojo类中, 主键类型必须是String.* 如果绑定的文档不存在, 那么在连接数据库时,会自动创建文档.1. 数据量大的场景. 比如社交应用中用户的发言,点赞等行为, 游戏中玩家的装备信息, 商品的物流数据等.2. 读写操作频繁的场景. MongoDB支持2000+ 的QPS. (query pre second 每秒搜索)3. 存储价值较低, 而且对事务的要求不高的数据. MongoDB的事务能力很低.
8. 分布式事务 —Seata
# - 分布式架构的项目中,经常会有些功能,需要调用别的微服务的接口,修改多个数据库中的多张表.- 也有些项目,由于数据量过大,单表会极大的影响效率,会将表中的数据进行分库分表操作,以提高服务的响应速 度,优化用户体验.- 在这些情况下,一旦任务执行过程中某个地方出错,即使开启了事务,@Transactional 数据依然无法回滚到错 误发生之前的状态. 进而数据错乱,导致更严重的后果.- 这种时候,就需要采用分布式事务的技术.* 基础理论CAP定理 - Consistency 强一致性 - Availability 高可用性 - Partition tolerance 分区容错 - 这三个指标不能同时做到,只能实现两个,而P是必须保证实现的.# seata* seata是阿里巴巴和蚂蚁金服共同开源的一种分布式事务解决方案. - @GlobalTransactional 在要开启分布式事务的入口方法上添加该注解# 三个角色 * TC 事务协调者 维护全局事务,负责协调全局事务的提交或回滚. * TM 事务管理者 开始全局事务、提交或回滚全局事务. * RM 分支事务 管理单个分支事务的状态.# 四种模式 * XA模式 - 所有分支事务执行完毕后不提交,而是将执行状态发送给TC,由TC确认事务执行成功与否, - 决定是提交事务还是回滚事务. - 强一致性,无业务侵入,会阻塞别的事务 * AT模式 (seata默认模式) - 分支事务RM记录sql执行前后的数据快照undo-log,执行完毕后立即提交或回滚,并将状态报告给TC - 二阶段有TC决定是提交还是回滚. - 提交,各个RM删除undo-log , 回滚,读取undo-log,恢复数据 - 最终一致,无业务侵入. - 有可能出现脏读,可通过开启全局锁解决,但会影响效率. * TCC模式 - 为每个分支事务RM提供了三个方法,try confirm cancel - 一阶段完成后,直接提交事务,释放锁 - 二段端,TC根据事务状态决定执行那个方法. 提交: canfirm 回滚: cancel - try 对要操作的资源进行检查和预留 - confirm 业务执行和提交 - cancel 释放预留的资源 - 最终一致,有业务侵入. * SAGA模式 -主要面向长事务,使用较少.
9. 延时任务方案 —四种常见方案
# ...延时任务方案...1. 定期轮询数据库. - 优点: 实现简单,且任务所在的服务可以设立集群,保证高可用. - 缺点: 定期扫描数据库压力大. 扫描频率难以均衡. 多线程可能出现安全问题等...2. 定时任务 ..xxljob - 优点: 实现简单 - 缺点: 每个定时业务都要开启一次任务...3. Redis键过期通知 - 优点: 高可用 实时性 支持消息的删除 - 缺点: 负载大 没有确认机制,可能会出现消息丢失4. RabbitMQ 死信队列 - 优点: 实现简单 高可用 可持久化 保证消息的可靠性 - 缺点: 无法删除消息 不适合时间跨度大的定时任务
10. 页面静态化之模板引擎 —Freemarker
# 常用的java模板引擎有 Jsp, Freemarker, Thymeleaf, Velocity.● jsp绑定了servlet,不实用. velocity久不更新,过时了.● 天下英雄,唯freemarker和 thymeleaf尔.● Freemarker简单方便,效率高.● Thymeleaf功能强大,效率较低.● 我们使用Freemarker.# Freemarker的使用● 需要一个模板文件, 确定静态网页的样式布局.● 需要开发者提供真实数据, 填充模板中差值表达式的数据位置.● 模板 + 数据 = 静态文件.● 基础语法 : ○ 兼容html的语法格式. ○ 可以在标签中加上 #, 来使用FTL语言特有的标签功能. <#...>___</#...> ○ 差值表达式语法类似jsp. ${ } ○ 内建函数. 在 标签\变量后加 ? [函数名] 来使用内置的函数. ○ 更具体的语法, 在使用时面向百度编程.
11. 基于RPC的远程调用 —Dubbo

1. 简介 - 面向接口代理的高性能RPC调用 - 智能容错和负载均衡 - 服务自动注册和发现 - 高度可扩展能力 - 运行期流量调度 - 可视化的服务治理与运维2. dubbo与feign的区别 * dubbo: 基于RPC的远程调用(默认基于TCP协议), 面向接口, 智能负载均衡. - 支持多种连接协议. - 利用Netty,TCP传输,单一、异步、长连接,适合数据量小、高并发的场景. - 负载均衡的算法可以精准到某个服务接口的某个方法 提供方 @DubboService 消费端 @DubboReference * feign: 基于http协议的远程调用, 也是面向接口. - 通过短连接的方式进行通信,不适合高并发的访问. - Feign默认使用Ribbon作为负载均衡的组件,Hystrix作为服务熔断的组件. - 负载均衡是Client级别的. 客户端 @FeignCilent 消费端 @Autowired 自动注入 二者都是同步调用.3. dubbo的配置原则 * dubbo推荐在Provider上尽量多配置Consumer端属性. 提供方拥有具体的业务实现的逻辑代码,可以提供基础的, 参考配置. 优先级: 方法级 > 接口级 > 全局配置, 其中 消费方consumer的配置优先于服务提供方Provider的配置. 在@DubboService 和 @DubboReference注解中, 有一个methods属性, 可以针对指定的方法进行配置.4. 常用配置 - timeout 超时时间, 默认1s - retries 重试次数, 默认为2次 - version 灰度发布, 即在版本更新时 分时分段 的更新服务者和消费者的版本.5. 隐式传参 * org.apache.dubbo.rpc.RpcContext ;线程共享数据的处理类 RpcContext.getContext().setAttachment(key, value) RpcContext.getContext().getAttachment(key, value) 多个微服务之间的上下文信息不同, 隐式传递的参数不能跨过多个微服务.6. 负载均衡配置 * Random LoadBalance 加权随机调用(默认策略) - 按权重设置随机概率. 调用次数越多, 调用越接近权重的策略 * RoundRobin LoadBalance 加权轮询策略 - 按公约后的权重设置轮循比率. - 在首轮分配后, 继续按照权重的次数来分配路由, 直到所有的权重分配结束, 算是一次完整的轮询, 之后开启下次轮询. * LeastActive LoadBalance 最少活跃调用数 - 类似能者多劳模式, 每个服务会计算执行前后的时间差, 执行越慢的服务分配到的任务越少, 越快的服务会接到更多的请求. * ConsistentHash LoadBalance 一致性 Hash - 把请求的hash值, 对服务总数取模,把相同余数的请求路由到同一个服务. - 默认只计算第一个请求参数的hash, 默认使用160分虚拟节点.7. 服务降级和集群容错策略 * 服务降级 - 自定义降级后的返回策略. * 集群容错 - Failover Cluster 失败自动切换(默认容错模式) 失败自动切换,当出现失败,重试其它服务器。通常用于读操作,但重试会带来更长延迟。可通过 retries=“2” 来设置重试次数. - Failfast Cluster 快速失败 快速失败,只发起一次调用,失败立即报错。通常用于非幂等性的写操作,比如新增记录。 - Failsafe Cluster 失败安全 失败安全,出现异常时,直接忽略。通常用于写入审计日志等操作。 - Failback Cluster 失败自动恢复,后台记录失败请求,定时重发。通常用于消息通知操作。 - Forking Cluster 并行调用多个服务器,只要一个成功即返回。通常用于实时性要求较高的读操作,但需要浪费更多服务资源。可通过 forks=“2” 来设置最大并行数。 - Broadcast Cluster 广播调用所有提供者,逐个调用,任意一台报错则报错 [2]。通常用于通知所有提供者更新缓存或日志等本地资源信息。8. 整合hystrix的微服务保护 * 添加依赖 在Application类上增加@EnableHystrix来启用hystrix starter. <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> <version>1.5.9.RELEASE</version> </dependency> * 配置Provider端 在Dubbo的Provider上增加 @HystrixCommand 配置.注: 1. 注册中心宕机会有什么影响? - 监控中心宕掉不影响使用,只是丢失部分采样数据 - 数据库宕掉后,注册中心仍能通过缓存提供服务列表查询,但不能注册新服务 - 注册中心对等集群,任意一台宕掉后,将自动切换到另一台 - 注册中心全部宕掉后,服务提供者和服务消费者仍能通过本地缓存通讯 - 服务提供者无状态,任意一台宕掉后,不影响使用 - 服务提供者全部宕掉后,服务消费者应用将无法使用,并无限次重连等待服务提供者恢复 2. dubbo可以直连调用吗? 可以, 添加服务提供方的地址就可以, 但是不推荐,失去了高可用性. @Reference(url=“127.0.0.1:20880”) 3. 可以使用别的注册中心吗? 可以, zookeeper, redis等. * redis 存储的结构为 HASH, 通过订阅/发布实现注册功能, HASH KEY是当前服务的全限定名+类型(提供方/消费方), 存储的MAP对象的key 是服务的ID, value是过期时间 引入依赖 <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> 修改配置 dubbo: registry: address: redis://${host}:6379
12. 消息队列中间件 —Kafka
# Kafka 是一款分布式流处理框架,用于实时构建流处理应用。高吞吐量、低延迟:kafka每秒可以处理几十万条消息,它的延迟最低只有几毫秒可扩展性:kafka集群支持热扩展持久性、可靠性:消息被持久化到本地磁盘,并且支持数据备份防止数据丢失容错性:允许集群中节点失败(若副本数量为n,则允许n-1个节点失败)高并发:支持数千个客户端同时读写消费者组: 是Kafka独有的概念,是Kafka提供的可扩展且具有容错性的消费者机制. 在Kafka 中,消费者组是一个由多个消费者实例 构成的组。多个实例共同订阅若干个主题,实现共同消费。同一个组下的每个实例都配置有 相同的组 ID,被分配不同的订阅分区。当某个实例挂掉的时候,其他实例会自动地承担起 它负责消费的分区。
13. 分布式文件存储 —fastDFS
# FastDFS 是一个开源的轻量级分布式文件系统,它可以对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等- 搭建太过复杂, 不如minIO简单. - 自从阿里云oss出现后,两个都不用了.
14. alibaba旧类注册中心 —zookeeper
# Zookeeper 是一个分布式协调服务的开源框架, 主要用来解决分布式集群中应用系统的一致性问题. ZooKeeper 本质上是一个分布式的小文件存储系统,提供基于类似于文件系统的目录树方式的数据存储 在ZooKeeper中还支持一种watch(监听)机制, 它允许对ZooKeeper注册监听, 当监听的对象发生指定的事件的时候, ZooKeeper就会返回一个通知. Watcher 分为以下三个过程:客户端向ZK服务端注册 Watcher、服务端事件发生触发 Watcher、客户端回调 Watcher 得到触发事件情况.触发事件种类很多,如:节点创建,节点删除,节点改变,子节点改变等。 Watcher是一次性的. 一旦被触发将会失效. 如果需要反复进行监听就需要反复进行注册. 简单来说zookeeper=文件系统+监听通知机制. 现在我们用nacos替代.
15. 连接数据库封装JDBC —hibrnate
# 连接数据库的一个实现方案, 封装了JDBC, 是 ORM(对象关系映射) 关系的一种实现.我们使用的是Mybatis.
16. alibaba连接数据库封装JDBC —Netty
# Netty 是基于Java NIO的异步事件驱动的 网络应用框架 它提供了对TCP、UDP 和文件传输的支持,作为一个异步 NIO 框架,Netty 的所有 IO 操作都是异步非阻塞的.我们项目使用的都是springboot, 内置了tomcat, 所以没有使用netty
17. 分库分表 —Sharding-JDBC
18. Websocket
# websocket是html5规范中的一个部分,它借鉴了socket这种思想,为web应用程序客户端和服务端之间(注意是客户端服务端)提供了一种全双工通信机制。 是一种通信协议说白话, 就是 HTTP 协议有一个缺陷:通信只能由客户端发起. websocket协议, 服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种. <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 使用入门:https://zhengkai.blog.csdn.net/article/details/80275084?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-3.no_search_link
19. Kubernetes —即k8s
* Kubernetes是一个开源的,用于管理云平台中多个主机上的容器化的应用,Kubernetes的目标是让部署容器化的应用简单并且高效(powerful),Kubernetes提供了应用部署,规划,更新,维护的一种机制。\ 作用是部署环境和容器, 正在逐渐的取代docker. 有时间可以学习下入门文档 https://www.kubernetes.org.cn/k8s
开发bug:
1. 热点新闻更新错误
1. rabbitmq自定义配置, 采用jackson2json序列化消息, 体积更小,效率更高2. 在文章微服务监听消息, 收到的消息多了一对双引号, 存入redis中, 再取出序列化失败.3. 解决方法: 1. 发消息的微服务采用默认序列化方式 2. 监听器接收到消息后, 先对内容进行处理, 去除两端的双引号,在存入redis
2. Long类型数据精度丢失
* 如果是对象, 主键ID修改序列化方式. * 如果是Long对象, 可选一: 转为String返回 可选二: 前段js修改, 异步请求接收结果时不做转换,直接返回
3. redis分布式锁失效

起因: 测试开桌的时候, 模拟前段多个请求, 而服务器内部网络堵塞(debug模拟), 恢复后会出现重复开桌的安全问题.原理: 在释放分布式锁后, 该任务仍有很多后续代码要执行, 而由于方法上加了事务, 在执行完毕之前, order表并不会写入数据. 此时下一个线程获取了分布式锁, 并通过了order状态判断, 导致重复开桌.解决方案: 见图. 选择三: 该段代码结束后直接return 当前结果参数, 从web层的方法里调用后续步骤并传参. (推荐)
4. mybatis的分页插件bug
# 问题描述 一个本来没有做分页的查询,通过输出日志可以看到,在执行查询的时候却用了分页。导致查询不到数据,或是查询到错误的数据。# 问题原因 mybaits分页插件底层使用了ThreadLocal封装了Page对象, 这个变量内封装了分页查询要用的参数,以及查询的结果。 它会在执行sql时自动插入分页语句. 底层源码在finally里执行了clear(), 似乎是不会出现脏数据的问题. 但是, 如果还没有进入try就抛异常了,就会导致没能让finally中的内容执行到。 因为它只有在执行过mapper查询语句后, 才会执行finally清空缓存的逻辑!!! 在我们的业务代码里获取到Page对象后,没有立即调用mapper做查询。而是执行了一些其他的内容。正是这些其他的内容抛出了异常,导致后面没有调用mapper做查询,也就无法进入到上面贴的插件中的代码,更没有可能清空ThreadLocal变量了。 在这个线程下次被使用时,如果正好是一个不需要分页查询的功能,也就没法把ThreadLocal中已有的变量覆盖掉,导致把一个不需要分页查询的功能做了分页查询。# 解决方案 要使用MyBatis分页插件PageHelper做分页查询时 ,一定要记得,在获得了Page对象后要立即调用mapper做查询,不要有其他的逻辑插入其中。
面试题:
1. 选择排序优化
//选择排序的优化 for(int i = 0; i < arr.length - 1; i++) {// 做第i趟排序 int k = i; for(int j = k + 1; j < arr.length; j++){// 选最小的记录 if(arr[j] < arr[k]){ k = j; //记下目前找到的最小值所在的位置 } } //在内层循环结束,也就是找到本轮循环的最小的数以后,再进行交换 if(i != k){ //交换a[i]和a[k] int temp = arr[i]; arr[i] = arr[k]; arr[k] = temp; } }
2. 冒泡排序优化
public static void bubbleSort3(int[] values) { System.out.println("排序前:" + Arrays.toString(values)); int tmp;// 循环外创建变量,减少垃圾产生,提高性能 int count = 0;// 比较了多少趟 int num = 0; // 比较次数 // 外层循环,n 个元素,最多需要 n -1 趟循环 for(int i = 0; i < values.length -1 ; i++) { // 定义一个boolean 值,标记数组是否达到有序状态 boolean flag = true; count ++; // 内层循环, 每一次都从开始两个元素开始两两比较到最后 /* 每一趟循环都可以找你最大的数,内循环可以比上一趟循环减少一次比较 */ for (int j = 0; j < values.length - 1 - i; j++) { if(values[j] > values[j + 1]) { tmp = values[j]; values[j] = values[j + 1]; values[j + 1] = tmp; // 如果本趟发生了交换,表明数组还是无序,需要进入下一轮循环 flag = false; } num ++; } // 根据标记的布尔值判断数组是否达到有序状态,如有序,则退出循环 if( flag) { break; } } System.out.println("排序后:" + Arrays.toString(values)); System.out.println("比较趟数:"+count + ",比较次数:" + num); }