十、a,b,c三张表,做关联查询,如何优化,可做外键,只在c表加a表外键即可。
1. 对于要求全面的结果时,我们需要使用连接操作(LEFT JOIN / RIGHT JOIN / FULL JOIN);2. 不要以为使用MySQL的一些连接操作对查询有多么大的改善,核心是索引;3. 对被驱动表的join字段添加索引。
十一、.CourrentHashMap JDK1.7和JDK1.8有什么区别?
1. Java 7为实现并行访问,引⼊了Segment这一结构,实现了分段锁,理论上最大并发度与Segment个数相等。2. Java 8为进一步提⾼并发性,摒弃了分段锁的方案,⽽是直接使⽤一个大的数组。同时为了提⾼哈希碰撞下的寻址性能,Java 8在链表长度超过⼀定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红⿊树(寻址时间复杂度为O(long(N)))。其数据结构如下图所示
十二、线程a,b,c,d运行任务,怎么保证当a,b,c线程执行完再执行d线程?
1、CountDownLatch类一个同步辅助类,常用于某个条件发生后才能执⾏后续进程。给定计数初始化CountDownLatch,调用countDown()方法,在计数到达零之前,await⽅法⼀直受阻塞。重要方法为countdown()与await();2、join⽅方法将线程B加⼊到线程A的尾部,当A执行完后B才执行。
public static void main(String[] args) throws Exception {Th t = new Th("t1");Th t2 = new Th("t2");t.start();t.join();t2.start(); }
3、notify、wait⽅法,Java中的唤醒与等待⽅法,关键为synchronized代码块,参数线程间应相同,也常⽤Object作为参数。
十三、拆分微服务应该注意哪些地方,如何拆分?
1、业务方⾯面拆分:所有技术⽅⾯的考虑,包括架构设计和解耦拆分都要考虑业务的需要。在服务拆分时,先从业务角度确定拆分的方案。拆分的边界要充分考虑业务的独立性和专业性,⽐如搜索类服务、支付类服务、购物⻋车类服务,按服务的业务功能合理地划出拆分边界。
2、减少维护成本:拆分前的维护成本 - 拆分后的维护成本 ≧ 0
3、服务独立:确保拆分后的服务由相对独立的团队负责维护,尽量不要出现在不同服务之间的交叉调用。
4、系统扩展:拆分的⼀个重要理由也是最有价值的结果是提高了系统的扩展性。⽤户对不同的服务有不同的并发和性能方面的要求,因此服务具有不同的扩展性。把具有不同扩展性要求的服务拆分出来分别进行部署,可以降低成本,提高效率。
十四、SpringCloud全家桶包含哪些组件?
1. Ribbon,客户端负载均衡,特性有区域亲和、重试机制。
2. Hystrix,客户端容错保护,特性有服务降级、服务熔断、请求缓存、请求合并、依赖隔离。
3. Feign,声明式服务调用,本质上就是Ribbon+Hystrix
4. Stream,消息驱动,有Sink、Source、Processor三种通道,特性有订阅发布、消费组、消息分区。
5. Bus,消息总线,配合Config仓库修改的一种Stream实现。
6. Sleuth,分布式服务追踪,需要搞清楚TraceID和SpanID以及抽样,如何与ELK整合。
7. Eureka,服务注册中心,特性有失效剔除、服务保护。
8. Dashboard,Hystrix仪表盘,监控集群模式和单点模式,其中集群模式需要收集器Turbine配合。
9. Zuul,API服务⽹关,功能有路由分发和过滤。
10. Config,分布式配置中心,⽀持本地仓库、SVN、Git、Jar包内配置等模式。
十五、有没了解Docker,Docker和虚拟机有什么区别?
1、虚拟机:我们传统的虚拟机需要模拟整台机器包括硬件,每台虚拟机都需要有⾃己的操作系统,虚拟机一旦被开启,预分配给他的资源将全部被占⽤。,每⼀个虚拟机包括应用,必要的二进制和库,以及⼀个完整的用户操作系统。
2、Docker:容器技术是和我们的宿主机共享硬件资源及操作系统可以实现资源的动态分配。
容器包含应用和其所有的依赖包,但是与其他容器共享内核。容器在宿主机操作系统中,在用户空间以分离的进程运行。
3、对比:
1. docker启动快速属于秒级别。虚拟机通常需要几分钟去启动。
2. docker需要的资源更少,docker在操作系统级别进行虚拟化,docker容器和内核交互,几乎没有性能损耗,性能优于通过Hypervisor层与内核层的虚拟化。
3. docker更轻量,docker的架构可以共用⼀个内核与共享应用程序库,所占内存极小。同样的硬件环境,Docker运行的镜像数远多于虚拟机数量。对系统的利⽤率非常高
4. 与虚拟机相比,docker隔离性更弱,docker属于进程之间的隔离,虚拟机可实现系统级别隔离;
5. 安全性:docker的安全性也更弱。Docker的租户root和宿主机root等同,⼀旦容器内的用户从普通用户权限提升为root权限,它就直接具备了宿主机的root权限,进⽽可进⾏⽆限制的操作。虚拟机租户root权限和宿主机的root虚拟机权限是分离的,并且虚拟机利用如Intel的VT-d和VT-x的ring-1硬件隔离技术,这种隔离技术可以防⽌虚拟机突破和彼此交互,而容器至今还没有任何形式的硬件隔离,这使得容器容易受到攻击。
6. 可管理性:docker的集中化管理工具还不算成熟。各种虚拟化技术都有成熟的管理工具,例如VMware vCenter提供完备的虚拟机管理能力。
7. ⾼可用和可恢复性:docker对业务的高可用⽀持是通过快速重新部署实现的。虚拟化具备负载均衡,高可⽤,容错,迁移和数据保护等经过生产实践检验的成熟保障机制,VMware可承诺虚拟机99.999%高可用,保证业务连续性。
8. 快速创建、删除:虚拟化创建是分钟级别的,Docker容器创建是秒级别的,Docker的快速迭代性,决定了无论是开发、测试、部署都可以节约⼤量时间。
9. 交付、部署:虚拟机可以通过镜像实现环境交付的一致性,但镜像分发无法体系化;Docker在Dockerfile中记录了容器构建过程,可在集群中实现快速分发和快速部署;
十六、同一个宿主机中多个Docker容器之间如何通信?多个宿主机中Docker容器之间如何通信?
1、这里同主机不同容器之间通信主要使用Docker桥接(Bridge)模式。
2、不同主机的容器之间的通信可以借助于 pipework 这个工具。
十七、高并发系统如何做性能优化?如何防止库存超卖?
1、高并发系统性能优化:
优化程序,优化服务配置,优化系统配置
1).尽量使用缓存,包括用户缓存,信息缓存等,多花点内存来做缓存,可以⼤量减少与数据库的交互,提高性能。
2).用jprofiler等工具找出性能瓶颈,减少额外的开销。
3).优化数据库查询语句,减少直接使用hibernate等工具的直接生成语句句(仅耗时较长的查询做优化)。
4).优化数据库结构,多做索引,提高查询效率。
5).统计的功能尽量做缓存,或按每天一统计或定时统计相关报表,避免需要时进行统计的功能。
6).能使用静态页面的地⽅尽量使用,减少容器的解析(尽量将动态内容⽣生成静态html来显示)。
7).解决以上问题后,使⽤服务器集群来解决单台的瓶颈问题。
2、防⽌库存超卖:
1)、悲观锁:在更新库存期间加锁,不不允许其它线程修改;
a、数据库锁:select xxx for update;
b、分布式锁;
2)、乐观锁:使用带版本号的更新。每个线程都可以并发修改,但在并发时,只有一个线程会修改成功,其它会返回失败。
a、redis watch:监视键值对,作⽤时如果事务提交exec时发现监视的监视对发⽣变化,事务将被取消。
3)、消息队列:通过 FIFO 队列,使修改库存的操作串行化。
4)、总结:总的来说,不能把压力放在数据库上,所以使⽤ "select xxx for update" 的⽅式在高并发的场景下是不可行的。FIFO同步队列的⽅式,可以结合库存限制队列⻓,但是在库存较多的场景下,又不太适用。所以相对来说,我会倾向于选择:乐观锁 / 缓存锁/ 分布式锁的方式。
十八、如何保证服务幂等性?
1、概念:接口的幂等性实际上就是接口可重复用,在调用方多次调⽤的情况下,接口最终得到的结果是⼀致的。有些接⼝可以天然的实现幂等性,比如查询接口,对于查询来说,你查询⼀次和两次,对于系统来说,没有任何影响,查出的结果也是一样。
2、GET幂等:值得注意,幂等性指的是作用于结果而非资源本身。怎么理解呢?例如,这个HTTP GET方法可能会每次得到不同的返回内容,但并不影响资源。
3、POST非幂等:因为它会对资源本身⽣影响,每次调用都会有新的资源产生,因此不满足幂等性。
4、如何保证幂等性:
1)、全局唯一id:如果使用全局唯一ID,就是根据业务的操作和内容⽣成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。如果不存在则把全局ID,存储到存储系统中,比如数据库、redis等。如果存在则表示该方法已经执行。
从工程的角度来说,使用全局ID做幂等可以作为一个业务的基础的微服务存在,在很多的微服务中都会用到这样的服务,在每个微服务中都完成这样的功能,会存在工作量重复。另外打造一个高可靠的幂等服务还需要考虑很多问题,比如⼀台机器虽然把全局ID先写⼊了存储,但是在写入之后挂了,这就需要引入全局ID的超时机制。使用全局唯一ID是一个通用方案,可以支持插入、更新、删除业务操作。但是这个⽅案看起来很美但是实现起来比较麻烦,下面的⽅案适⽤于特定的场景,但是实现起来比较简单。
2)、去重表:这种方法适用于在业务中有唯一标的插入场景中,比如在以上的⽀付场景中,如果一个订单只会支付一次,所以订单ID可以作为唯一标识。这时,我们就可以建一张去重表,并且把唯一标识作为唯一索引,在我们实现时,把创建支付单据和写入去重表,放在一个事务中,如果重复创建,数据库会抛出唯一约束异常,操作就会回滚。
3)、插入或更新:这种方法插⼊并且有唯一索引的情况,⽐如我们要关联商品类,其中商品的ID和品类的ID可以构成唯一索引,并且在数据表中也增加了唯一索引。这时就可以使用InsertOrUpdate操作。在mysql数据库中如下:
insert into goods_category (goods_id,category_id,create_time,update_time)values(#{goodsId},#{categoryId},now(),now()) on DUPLICATE KEY UPDATE update_time=now()
4)、多版本控制:这种方法适合在更新的场景中,比如我们要更新商品的名字,这时我们就可以在更新的接口中增加一个版本号,来做幂等。
boolean updateGoodsName(int id,String newName,int version);
在实现时可以如下
update goods set name=#{newName},version=#{version} where id=#{id} and version<${version}
5、状态机控制:这种方法适合在有状态机流转的情况下,比如就会订单的创建和付款,订单的付款肯定是在之前,这时我们可以通过在设计状态字段时,使⽤int类型,并且通过值类型的大小来做幂等,⽐如订单的创建为0,付款成功为100。付款失败为99
在做状态机更新时,我们就这可以这样控制
update `order` set status=#{status} where id=#{id} and status<#{status}