分布式应用
多个软件应用服务协作,共同对外提供服务。
实际开发中,这些应用一般在内网中,每个应用各司其职。
每个应用服务可能存在多个实例。
应用之间的调用RPC:

协议
1 webservice
基于http,使用soap协议
2 http
直接通过http调用
3 dubbo
直接使用单一tcp长连接
调用feign:
- 通过调用接口中定义的方法,就可以实现RPC远程调用
- 可自定义翻译器、编码器、解码器、http客户端
/**
* @author yawn http://jvm123.com
* 2019/12/1 11:13
*/
public interface SearchClient {
@RequestLine("GET /post/s/{wd}")
List<PostVo> search(@Param("wd") String wd);
@RequestLine("POST /post/s")
@Headers("Content-Type: application/json")
List<PostVo> search(@Param("t") String t, @Param("c") String c);
}
package com.jvm123.blog.config
import com.jvm123.blog.client.FeignRequestInterceptor
import com.jvm123.blog.client.HttpFeignClient
import feign.Feign
import feign.gson.GsonDecoder
import feign.gson.GsonEncoder
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import com.jvm123.blog.client.SearchClient
/**
*
* @author yawn http://jvm123.com
* 2019/10/30 14:46
*/
@Configuration
class FeignConfig {
@Value('${feign.host}')
String host = "https://api.jvm123.com"
@Bean
SearchClient searchClient() {
return Feign.builder()
.encoder(new GsonEncoder())
.decoder(new GsonDecoder())
.requestInterceptor(new FeignRequestInterceptor())
.client(new HttpFeignClient())
.target(SearchClient.class, host)
}
}
/**
* 自己实现的客户端
* @author yawn http://jvm123.com
* 2019/12/3 9:52
*/
public class HttpFeignClient implements Client {
@Override
public Response execute(Request request, Request.Options options) throws IOException {
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpRequestBase httpRequest = new HttpRequestBase() {
@Override
public String getMethod() {
return request.method();
}
};
try {
httpRequest.setURI(new URI(request.url()));
} catch (URISyntaxException ignored) { }
CloseableHttpResponse httpResponse = httpClient.execute(httpRequest);
HttpEntity entity = httpResponse.getEntity();
byte[] body = EntityUtils.toByteArray(entity);
Response response = Response.builder()
.body(body)
.headers(new HashMap<String, Collection<String>>())
.status(httpResponse.getStatusLine().getStatusCode())
.build();
return response;
}
}
应用通信:
消息中间件:jms规范
topic模式:publish/subscribe
queue模式:producer/consumer (send/onMessage)
应用管理及组件:
注册中心:redis、eureka、zookeeper
网关:
配置中心:
监控、日志:zipkin、logstash
负载均衡:客户端负载均衡ribbon(Server,IRule),服务端的nginx配置upstream
常用分布式服务器
数据库:cassandra,mysql,redis,mongodb
搜索服务器:elasticsearch
消息中间件:kafka
目标:高可用、没有单点故障。通常通过以下方法实现
多节点(nodes)
多节点,故障自动切换。
节点越多,可用性越高。
但在节点有限的情况下,可通过分片和副本技术实现。
分片(shards)
存储数据时,将数据按照一定的规则存储在不同的分片上,这就需要在查询时也能很快找到该数据所在的分片。一般可使用以下方法:
shard = hash(routing) % number_of_shards
routing为数据的唯一标识,一般是id。过程类似于根据key寻找hashmap节点所在的bin。

副本
将分片复制,作为副本存储,一般副本不存储在与主分片相同的节点上
特点总结
- 多节点、多副本、分片存储
- 各分片和副本分片均匀分布在各个节点上
- 原数据主分片和副本分片存储在不同的节点上

分布式锁
主流实现方式:redis、zookeeper
redis: setnx key value expr 100ms
zookeeper:
/**
* 1.客户端连接zookeeper,并在/lock下创建临时的且有序的子节点,
* 第一个客户端对应的子节点为/lock/***-lock-0000000000,第二个
* 为/lock/***-lock-0000000001,以此类推。
* 2.客户端获取/lock下的子节点列表,判断自己创建的子节点是否为
* 当前子节点列表中序号最小的子节点,如果是则认为获得锁,否则
* 监听刚好在自己之前一位的子节点删除消息,获得子节点变更通知
* 后重复此步骤直至获得锁;
* 3.执行业务代码;
* 4.完成业务流程后,删除对应的子节点释放锁。
*
* 此锁能保证公平性,非公平锁的实现可参考:
* @see ZkLock2Controller
*/
private void buyWithLock() throws Exception {
//创建分布式锁, 锁空间的根节点路径为/lock
InterProcessMutex lock = new InterProcessMutex(curator, "/lock");
lock.acquire();
//获得了锁, 进行业务流程
buy();
//完成业务流程, 释放锁
lock.release();
}
分布式事务
XA两阶段提交、事务最终一致
最终一致事务使用消息中间件实现:消息中间件 onMessage 确认机制,自动确认、手动确认、批量确认、单条确认
eg:添加用户后,需要初始化用户的积分,因为用户和积分在不同的服务中,所以需要进行分布式的事务。

增加事件(event)表,定时处理事件,如下:
