接口和抽象类
抽象类的例子1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950// 抽象类public abstract class Logger { private String name; private boolean enabled; private Level minPermittedLevel; public Logger(String name, boolean enabled, Level minPermittedLevel) { this.name = name; this.enabled = enabled; this.minPermittedLevel = minPermittedLevel; } public void log(Level level, String message) { boolean lo ...
ThreadLocal
ThreadLocal 的使用方法下面这个静态类 ThreadId 会为每个线程分配一个唯一的线程 Id,如果一个线程前后两次调用 ThreadId 的 get() 方法,两次 get() 方法的返回值是相同的。但如果是两个线程分别调用 ThreadId 的 get() 方法,那么两个线程看到的 get() 方法的返回值是不同的。
123456789static class ThreadId { static final AtomicLong nextId = new AtomicLong(0); // 定义ThreadLocal变量 static final ThreadLocal<Long> tl = ThreadLocal.withInitial(() -> nextId.getAndIncrement()); //此方法会为每个线程分配一个唯一的Id static long get() { return tl.get(); }}
一个使用场景:
SimpleDateFormat 不是线程安全的,那如果 ...
Copy-on-Write模式
Copy-on-Write顾名思义就是写时复制。比如String的replace()方法就采用了写时复制,即不修改原字符串,而是创建了一个新的,对于不可变对象的写操作往往都是Copy-on-Write方法解决的。
Copy-on-Write 模式的应用领域类 Unix 的操作系统中创建进程的 API 是 fork(),传统的 fork() 函数会创建父进程的一个完整副本,例如父进程的地址空间现在用到了 1G 的内存,那么 fork() 子进程的时候要复制父进程整个进程的地址空间(占有 1G 内存)给子进程,这是比较耗时的。
而 Linux 中 fork() 子进程的时候,并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间;只用在父进程或者子进程需要写入的时候才会复制地址空间,从而使父子进程拥有各自的地址空间。
本质上来讲,父子进程的地址空间以及数据都是要隔离的,使用 Copy-on-Write 更多地体现的是一种延时策略,只有在真正需要复制的时候才复制,而不是提前复制好,同时 Copy-on-Write 还支持按需复制。
设计模式实战
demo1代码片段
12345678910111213141516171819202122232425// 奖励服务class RewardService { // 外部服务 private WaimaiService waimaiService; private HotelService hotelService; private FoodService foodService; // 使用对入参的条件判断进行发奖 public void issueReward(String rewardType, Object ... params) { if (“Waimai”.equals(rewardType)) { WaimaiRequest request = new WaimaiRequest(); // 构建入参 request.setWaimaiReq(params); waimaiService.issueWaim ...
分布式下如何排查慢请求
在引入了注册中心后,微服务项目的架构会变成下图:
但是,如果此时有一个请求的响应速度比较慢,而这个请求调用了多个RPC服务,该如何去排查呢?
一体化架构中的慢请求排查如何做最简单的办法,就是像下面这样:
123456789101112long start = System.currentTimeMillis();processA();// 打印A步骤的耗时Logs.info("process A cost " + (System.currentTimeMillis() - start));start = System.currentTimeMillis();processB();// 打印B步骤的耗时Logs.info("process B cost " + (System.currentTimeMillis() - start));start = System.currentTimeMillis();processC();// 打印C步骤的耗时Logs.info("process C cost " + (System.cu ...
java并发容器
ListList 里面只有一个实现类就是 CopyOnWriteArrayList。
CopyOnWrite,顾名思义就是写的时候会将共享变量新复制一份出来,这样做的好处是读操作完全无锁。
CopyOnWriteArrayList 内部维护了一个数组,成员变量 array 就指向这个内部数组,所有的读操作都是基于 array 进行的,迭代器 Iterator 遍历的就是 array 数组。
如果在遍历时,还有写入时,CopyOnWriteArrayList 会将 array 复制一份,然后在新复制处理的数组上执行增加元素的操作,执行完之后再将 array 指向这个新的数组。遍历操作一直都是基于原 array 执行,而写操作则是基于新 array 进行。
注意:CopyOnWriteArrayList 仅适用于写操作非常少的场景,而且能够容忍读写的短暂不一致。因为遍历时,写入的新元素并不能第一之间被遍历到。
CopyOnWriteArrayList 迭代器是只读的,不支持增删改。因为迭代器遍历的仅仅是一个快照,而对快照进行增删改是没有意义的。
MapMap 接口的两个实现是 Con ...
Api网关
API 网关起到的作用API 网关可以分为两类:一类叫做入口网关,一类叫做出口网关。
入口网关入口网关部署在负载均衡服务器和应用服务器之间,主要有几方面的作用。
1、它提供客户端一个统一的接入地址,API 网关可以将用户的请求动态路由到不同的业务服务上,并且做一些必要的协议转换工作。这里有一点需要注意,你部署的微服务对外暴露的协议可能不同,有些可能是RPC,有些可能是Http,这些细节都可以在网关中处理。
2、另一方面,在 API 网关中,我们可以植入一些服务治理的策略,比如服务的熔断、降级、流量控制和分流等等。
3、客户端的认证和授权的实现,也可以放在 API 网关中。
4、另外,API 网关还可以做一些与黑白名单相关的事情,比如针对设备 ID、用户 IP、用户 ID 等维度的黑白名单。
5、最后,在 API 网关中也可以做一些日志记录的事情。
出口网关我们在系统开发中,会依赖很多外部的第三方系统,典型的例子:第三方账户登录、使用第三方工具支付等等。我们可以在应用服务器和第三方系统之间,部署出口网关,在出口网关中,对调用外部的 API 做统一的认证、授权、审计以及访问控制。
...
负载均衡
负载均衡服务器的种类负载均衡的含义是:将负载(访问的请求)“均衡”地分配到多个处理节点上。这样可以减少单个处理节点的请求量,提升整体系统的性能。
在项目的架构中,我们一般会同时部署 LVS 和 Nginx 来做 HTTP 应用服务的负载均衡。也就是说,在入口处部署 LVS 将流量分发到多个 Nginx 服务器上,再由 Nginx 服务器分发到应用服务器上。
不过这两个负载均衡服务适用于普通的 Web 服务,对于微服务架构来说,它们是不合适的。
因为微服务架构中的服务节点存储在注册中心里,使用 LVS 就很难和注册中心交互获取全量的服务节点列表。另外,一般微服务架构中,使用的是 RPC 协议而不是 HTTP 协议,所以 Nginx 也不能满足要求。
所以,我们会使用另一类的负载均衡服务,客户端负载均衡服务,也就是把负载均衡的服务内嵌在 RPC 客户端中。
这类服务一般会结合注册中心来使用,注册中心提供服务节点的完整列表,客户端拿到列表之后使用负载均衡服务的策略选取一个合适的节点,然后将请求发到这个节点上。
常见的负载均衡策略有哪些
一类是静态策略,也就是说负载均衡服务器在选择服务节 ...
CountDownLatch和CyclicBarrier:如何让多线程步调一致
假如我们要实现一下功能:
如果单线程处理,那么执行图如下:
如果想要优化,则可以将获取order的操作并行执行,如下图:
大致的代码如下:
12345678910111213141516171819while (存在未对账订单) { // 查询未对账订单 Thread T1 = new Thread(()->{ pos = getPOrders(); }); T1.start(); // 查询派送单 Thread T2 = new Thread(()->{ dos = getDOrders(); }); T2.start(); // 等待T1、T2结束 T1.join(); T2.join(); // 执行对账操作 diff = check(pos, dos); // 差异写入差异库 save(diff);}
但是这样写有一个缺点,就是while循环每次都要创建线程,而创建线程开销 ...
注册中心
当一个项目拆分成微服务之后,接口之间的调用就需要跨网络来完成,这时候就需要知道另外一个接口的ip地址以及端口号,如果把这些内容全部在代码里面写死,那么如果ip发生变动,就需要重启服务,还需要修改代码。注册中心的诞生就解决了这个问题。
注册中心的基本功能有两点:
一是提供了服务地址的存储;
二是当存储内容发生变化时,可以将变更的内容推送给客户端。
使用了注册中心组件之后,RPC 的通信过程就变成了下面这个样子:
客户端会与注册中心建立连接,并且告诉注册中心,它对哪一组服务感兴趣;
服务端向注册中心注册服务后,注册中心会将最新的服务注册信息通知给客户端;
客户端拿到服务端的地址之后就可以向服务端发起调用请求了。
服务状态管理如何来做服务的上线和下线是由服务端主动向注册中心注册和取消注册来实现的,这在正常的流程中是没有问题的。可是,如果某一个服务端意外故障,比如说机器掉电,网络不通等情况,服务端就没有办法向注册中心通信,将自己从服务列表中删除,就会导致客户端请求一个无法响应的服务。
主动探测你的 RPC 服务要打开一个端口,然后由注册中心每隔一段时间(比如 30 秒)探测 ...