轻量级RPC框架(0 - 项目概述)
项目概述My-RPC-Framework 是一款基于 Nacos 实现的 RPC 框架。网络传输实现了基于 Java 原生 Socket 与 Netty 版本,并且实现了多种序列化与负载均衡算法。从一个最简单的BIO + Java序列化开始,逐步完善成Netty + 多序列化方式的比较完整的框架,并且配置了Nacos服务发现。rpc框架的原理理解:客户端和服务端都可以访问到通用的接口,但是只有服务端有这个接口的实现类,客户端调用这个接口的方式:通知服务端我要调用这个接口,服务端收到之后找到这个接口的实现类并执行,将执行的结果返回给客户端,作为客户端调用该接口方法的返回值。原理很简单,但是实现值得商榷,例如客户端怎么知道服务端的地址?客户端怎么告诉服务端我要调用的接口?客户端怎么传递参数?只有接口客户端怎么生成实现类……等等等等。1.系统架构消费者调用提供者的方式取决于消费者的客户端选择,如选用原生 Socket 则该步调用使用 BIO,如选用 Netty 方式则该步调用使用 NIO。如该调用有返回值,则提供者向消费者发送返回值的方式同理。2.项目特性网络传输:实现了基于 Java 原生 Socket 传输与 Netty 传输两种网络传输方式消费端如采用 Netty 方式,会复用 Channel 避免多次连接如消费端和提供者都采用 Netty 方式,会采用 Netty 的心跳机制,保证连接序列化算法:实现了四种序列化算法,Json 方式、Kryo 算法、Hessian 算法与 Google Protobuf 方式(默认采用 Kryo方式序列化)负载均衡算法:实现了两种负载均衡算法:随机算法与轮转算法Nacos注册中心:使用 Nacos 作为注册中心,管理服务提供者信息服务提供侧自动注册服务通信协议:实现自定义的通信协议(MRF协议)接口抽象良好,模块耦合度低,网络传输、序列化器、负载均衡算法可配置3.项目模块rpc-api —— 服务端与客户端公共调用接口(通用接口)rpc-common —— 实体对象、工具类、枚举类等公共类rpc-core —— 框架的核心实现test-client —— 客户端测试代码test-server —— 服务端测试代码4.自定义传输协议调用参数与返回值的传输采用了如下 MRF 协议( My-RPC-Framework 首字母)以防止粘包:+---------------+---------------+-----------------+-------------+
| Magic Number | Package Type | Serializer Type | Data Length |
| 4 bytes | 4 bytes | 4 bytes | 4 bytes |
+---------------+---------------+-----------------+-------------+
| Data Bytes |
| Length: ${Data Length} |
+---------------------------------------------------------------+字段解释Magic Number魔数,表识一个 MRF 协议包,0xCAFEBABEPackage Type包类型,标明这是一个调用请求还是调用响应Serializer Type序列化器类型,标明这个包的数据的序列化方式Data Length数据字节的长度Data Bytes传输的对象,通常是一个RpcRequest或RpcClient对象,取决于Package Type字段,对象的序列化方式取决于Serializer Type字段。
RPC框架(6 - 实现自动注销和负载均衡策略)
5.6实现自动注销和负载均衡策略5.6.1服务自动注销上一节我们实现了服务的自动注册和发现,但是有些细心的同学就可能会发现,如果你启动完成服务端后把服务端给关闭了,并不会自动地注销 Nacos 中对应的服务信息,这样就导致了当客户端再次向 Nacos 请求服务时,会获取到已经关闭的服务端信息,最终就有可能因为连接不到服务器而调用失败。那么我们就需要一种办法,在服务端关闭之前自动向 Nacos 注销服务。但是有一个问题,我们不知道什么时候服务器会关闭,也就不知道这个方法调用的时机,就没有办法手工去调用。这时,我们就需要钩子。钩子是什么呢?是在某些事件发生后自动去调用的方法。那么我们只需要把注销服务的方法写到关闭系统的钩子方法里就行了。首先先写向 Nacos 注销所有服务的方法,这部分被放在了 NacosUtils 中作为一个静态方法,NacosUtils 是一个 Nacos 相关的工具类:public static void clearRegistry() {
if(!serviceNames.isEmpty() && address != null) {
String host = address.getHostName();
int port = address.getPort();
Iterator<String> iterator = serviceNames.iterator();
while(iterator.hasNext()) {
String serviceName = iterator.next();
try {
namingService.deregisterInstance(serviceName, host, port);
} catch (NacosException e) {
logger.error("注销服务 {} 失败", serviceName, e);
}
}
}
}所有的服务名称都被存储在 NacosUtils 类中的 serviceNames 中,在注销时只需要用迭代器迭代所有服务名,调用 deregisterInstance 注销服务。接着就是钩子了,新建一个类,ShutdownHook:public class ShutdownHook {
private static final Logger logger = LoggerFactory.getLogger(ShutdownHook.class);
private final ExecutorService threadPool = ThreadPoolFactory.createDefaultThreadPool("shutdown-hook");
private static final ShutdownHook shutdownHook = new ShutdownHook();
public static ShutdownHook getShutdownHook() {
return shutdownHook;
}
public void addClearAllHook() {
logger.info("关闭后将自动注销所有服务");
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
NacosUtil.clearRegistry();
threadPool.shutdown();
}));
}
}使用了单例模式创建其对象,在 addClearAllHook 中,Runtime 对象是 JVM 虚拟机的运行时环境,调用其 addShutdownHook 方法增加一个钩子函数,创建一个新线程调用 clearRegistry 方法完成注销工作。这个钩子函数会在 JVM 关闭之前被调用。这样在 RpcServer 启动之前,只需要调用 addClearAllHook,就可以注册这个钩子了。例如在 NettyServer 中:ChannelFuture future = serverBootstrap.bind(host, port).sync();
+ ShutdownHook.getShutdownHook().addClearAllHook();
future.channel().closeFuture().sync();启动服务端后再关闭,就会发现 Nacos 中的注册信息都被注销了。5.6.2负载均衡策略负载均衡大家应该都熟悉,在上一节中客户端在 lookupService 方法中,从 Nacos 获取到的是所有提供这个服务的服务端信息列表,我们就需要从中选择一个,这便涉及到客户端侧的负载均衡策略。我们新建一个接口:LoadBalancerpublic interface LoadBalancer {
Instance select(List<Instance> instances);
}接口中的 select 方法用于从一系列 Instance 中选择一个。这里我就实现两个比较经典的算法:随机和转轮。随机算法顾名思义,就是随机选一个,毫无技术含量:public class RandomLoadBalancer implements LoadBalancer {
@Override
public Instance select(List<Instance> instances) {
// nextInt():生成一个介于[0, instances.size())的int型随机值
return instances.get(new Random().nextInt(instances.size()));
}
}而转轮算法大家也应该了解,按照顺序依次选择第一个、第二个、第三个……这里就需要一个变量来表示当前选到了第几个:public class RoundRobinLoadBalancer implements LoadBalancer {
private int index = 0;
@Override
public Instance select(List<Instance> instances) {
if(index >= instances.size()) {
index %= instances.size();
}
return instances.get(index++);
}
}index 就表示当前选到了第几个服务器,并且每次选择后都会自增一。最后在 NacosServiceRegistry 中集成就可以了,这里选择外部传入的方式传入 LoadBalancer:public class NacosServiceDiscovery implements ServiceDiscovery {
private final LoadBalancer loadBalancer;
public NacosServiceDiscovery(LoadBalancer loadBalancer) {
if(loadBalancer == null) this.loadBalancer = new RandomLoadBalancer();
else this.loadBalancer = loadBalancer;
}
public InetSocketAddress lookupService(String serviceName) {
try {
List<Instance> instances = NacosUtil.getAllInstance(serviceName);
Instance instance = loadBalancer.select(instances);
return new InetSocketAddress(instance.getIp(), instance.getPort());
} catch (NacosException e) {
logger.error("获取服务时有错误发生:", e);
}
return null;
}
}而这个负载均衡策略,也可以在创建客户端时指定,例如无参构造 NettyClient 时就用默认的策略,也可以有参构造传入策略,具体的实现留给大家。
RPC框架(5 - 实现基于 Nacos 的服务器注册与发现)
5.5实现基于 Nacos 的服务器注册与发现我们目前实现的框架看起来工作的还不错,但是有一个问题:我们的服务端地址是固化在代码中的,也就是说,对于一个客户端,它只会去寻找那么一个服务提供者,如果这个提供者挂了或者换了地址,那就没有办法了。在分布式架构中,有一个重要的组件,就是服务注册中心,它用于保存多个服务提供者的信息,每个服务提供者在启动时都需要向注册中心注册自己所拥有的服务。这样客户端在发起 RPC 时,就可以直接去向注册中心请求服务提供者的信息,如果拿来的这个挂了,还可以重新请求,并且在这种情况下可以很方便地实现负载均衡。常见的注册中心有 Eureka、Zookeeper 和 Nacos。5.5.1获取NacosNacos 是阿里开发的一款服务注册中心,在 SpringCloud Alibaba 逐步替代原始的 SpringCloud 的过程中,Nacos 逐步走红,所以我们就是用 Nacos 作为我们的注册中心。下载解压的过程略过。注意 Nacos 是依赖数据库的,所以我们需要在配置文件中配置 Mysql 的信息。为了简单,我们先以单机模式运行:sh startup.sh -m standalone或者直接运行startup.cmd启动后可以访问 Nacos 的web UI,地址 http://127.0.0.1:8848/nacos/index.html。默认的用户名和密码都是 nacos5.5.2项目中使用Nacos引入 nacos-client 依赖:<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.3.0</version>
</dependency>这里我们修正之前的概念,第二节把本地保存服务的类称为 ServiceRegistry,现在更改为 ServiceProvider,而 ServiceRegistry 作为远程注册表(Nacos)使用,对应的类名也有修改。这里我们实现一个接口 ServiceRegistry:public interface ServiceRegistry {
void register(String serviceName, InetSocketAddress inetSocketAddress);
InetSocketAddress lookupService(String serviceName);
}register 方法将服务的名称和地址注册进服务注册中心lookupService 方法则是根据服务名称从注册中心获取到一个服务提供者的地址。InetSocketAddress:服务的地址(ip+端口号)ps:本机的域名是"localhost",IPv4地址是127.0.0.1.该类中包含了一个InetAddress对象,代表了IP地址和端口号,专门用于socket网络通信,用于需要IP地址和端口号的场景构造方法//根据域名(主机名)获得ip地址加端口对象
InetSocketAddress localhost = new InetSocketAddress("localhost", 8080);
//通过IP地址获取ip地址加端口对象
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8080);主要方法InetAddress getAddress():返回此端口的IP地址。
String getHostName():返回此端口的主机名。
int getPort():返回此端口的端口号。接口有了,我们就可以写实现类了,我们实现一个 Nacos 作为注册中心的实现类:NacosServiceRegistry,我们也可以使用 ZooKeeper 作为注册中心,实现接口就可以public class NacosServiceRegistry implements ServiceRegistry {
private static final Logger logger = LoggerFactory.getLogger(NacosServiceRegistry.class);
private static final String SERVER_ADDR = "127.0.0.1:8848";
private static final NamingService namingService;
static {
try {
namingService = NamingFactory.createNamingService(SERVER_ADDR);
} catch (NacosException e) {
logger.error("连接到Nacos时有错误发生: ", e);
throw new RpcException(RpcError.FAILED_TO_CONNECT_TO_SERVICE_REGISTRY);
}
}
@Override
public void register(String serviceName, InetSocketAddress inetSocketAddress) {
try {
namingService.registerInstance(serviceName, inetSocketAddress.getHostName(), inetSocketAddress.getPort());
} catch (NacosException e) {
logger.error("注册服务时有错误发生:", e);
throw new RpcException(RpcError.REGISTER_SERVICE_FAILED);
}
}
@Override
public InetSocketAddress lookupService(String serviceName) {
try {
List<Instance> instances = namingService.getAllInstances(serviceName);
Instance instance = instances.get(0);
return new InetSocketAddress(instance.getIp(), instance.getPort());
} catch (NacosException e) {
logger.error("获取服务时有错误发生:", e);
}
return null;
}
}Nacos 的使用很简单,通过 NamingFactory 创建 NamingService 连接 Nacos(连接的时候没有找到修改用户名密码的方式……是不需要吗),连接的过程写在了静态代码块中,在类加载时自动连接。namingService 提供了两个很方便的接口,registerInstance 和 getAllInstances 方法:registerInstance :直接向 Nacos 注册服务getAllInstances:可以获得提供某个服务的所有提供者的列表。在 lookupService 方法中,通过 getAllInstance 获取到某个服务的所有提供者列表后,需要选择一个,这里就涉及了负载均衡策略,这里我们先选择第 0 个,后面某节会详细讲解负载均衡。5.5.3注册服务我们修改 RpcServer 接口,新增一个方法 publishService,用于向 Nacos 注册服务:<T> void publishService(Object service, Class<T> serviceClass);接着只需要实现这个方法即可,以 NettyServer 的实现为例,NettyServer 在创建时需要创建一个 ServiceRegistry 了:public NettyServer(String host, int port) {
this.host = host;
this.port = port;
serviceRegistry = new NacosServiceRegistry();
serviceProvider = new ServiceProviderImpl();
}接着实现 publishService 方法即可:public <T> void publishService(Object service, Class<T> serviceClass) {
if(serializer == null) {
logger.error("未设置序列化器");
throw new RpcException(RpcError.SERIALIZER_NOT_FOUND);
}
serviceProvider.addServiceProvider(service);
serviceRegistry.register(serviceClass.getCanonicalName(), new InetSocketAddress(host, port));
start();
}publishService 需要将服务保存在本地的注册表,同时注册到 Nacos 上。我这里的实现是注册完一个服务后直接调用 start() 方法,这是个不太好的实现……导致一个服务端只能注册一个服务,之后可以多注册几个然后再手动调用 start() 方法。5.5.4服务发现客户端的修改就更简单了,以 NettyClient 为例,在过去创建 NettyClient 时,需要传入 host 和 port,现在这个 host 和 port 是通过 Nacos 获取的,sendRequest 修改如下:public Object sendRequest(RpcRequest rpcRequest) {
if(serializer == null) {
logger.error("未设置序列化器");
throw new RpcException(RpcError.SERIALIZER_NOT_FOUND);
}
AtomicReference<Object> result = new AtomicReference<>(null);
try {
InetSocketAddress inetSocketAddress = serviceRegistry.lookupService(rpcRequest.getInterfaceName());
Channel channel = ChannelProvider.get(inetSocketAddress, serializer);
...重点是最后两句,过去是直接使用传入的 host 和 port 直接构造 channel,现在是首先从 ServiceRegistry 中获取到服务的地址和端口,再构造。5.5.5测试NettyTestClient 如下:public class NettyTestClient {
public static void main(String[] args) {
RpcClient client = new NettyClient();
client.setSerializer(new ProtobufSerializer());
RpcClientProxy rpcClientProxy = new RpcClientProxy(client);
HelloService helloService = rpcClientProxy.getProxy(HelloService.class);
HelloObject object = new HelloObject(12, "This is a message");
String res = helloService.hello(object);
System.out.println(res);
}
}构造 RpcClient 时不再需要传入地址和端口(服务地址),直接去向注册中心请求服务提供者的信息。NettyTestServer 如下:public class NettyTestServer {
public static void main(String[] args) {
HelloService helloService = new HelloServiceImpl();
NettyServer server = new NettyServer("127.0.0.1", 9999);
server.setSerializer(new ProtobufSerializer());
server.publishService(helloService, HelloService.class);
}
}我这里是把 start 写在了 publishService 中,实际应当分离,否则只能注册一个服务。分别启动,可以看到和之前相同的结果。这里如果通过修改不同的端口,启动两个服务的话,会看到即使客户端多次调用,也只是由同一个服务端提供服务,这是因为在 NacosServiceRegistry 中,我们直接选择了服务列表的第 0 个,这个会在之后讲解负载均衡时作出修改。
计算机网络基础知识
基本术语结点 (node) :网络中的结点可以是计算机,集线器,交换机或路由器等。链路(link ) : 从一个结点到另一个结点的一段物理线路。中间没有任何其他交点。主机(host) :连接在因特网上的计算机。ISP(Internet Service Provider) :因特网服务提供者(提供商)。IXP(Internet eXchange Point) : 互联网交换点 IXP 的主要作用就是允许两个网络直接相连并交换分组,而不需要再通过第三个网络来转发分组。RFC(Request For Comments) :意思是“请求评议”,包含了关于 Internet 几乎所有的重要的文字资料。广域网 WAN(Wide Area Network) :任务是通过长距离运送主机发送的数据。城域网 MAN(Metropolitan Area Network):用来将多个局域网进行互连。局域网 LAN(Local Area Network) : 学校或企业大多拥有多个互连的局域网。个人区域网 PAN(Personal Area Network) :在个人工作的地方把属于个人使用的电子设备用无线技术连接起来的网络 。分组/包(packet ) :因特网中传送的数据单元。由首部 header 和数据段组成。分组又称为包,首部可称为包头。存储转发(store and forward ) :路由器收到一个分组,先检查分组是否正确,并过滤掉冲突包错误。确定包正确后,取出目的地址,通过查找表找到想要发送的输出端口地址,然后将该包发送出去。带宽(bandwidth) :在计算机网络中,表示在单位时间内从网络中的某一点到另一点所能通过的“最高数据率”。常用来表示网络的通信线路所能传送数据的能力。单位是“比特每秒”,记为 b/s。吞吐量(throughput ) :表示在单位时间内通过某个网络(或信道、接口)的数据量。吞吐量更经常地用于对现实世界中的网络的一种测量,以便知道实际上到底有多少数据量能够通过网络。吞吐量受网络的带宽或网络的额定速率的限制。重要知识点计算机网络(简称网络)把许多计算机连接在一起,而互联网把许多网络连接在一起,是网络的网络。路由器是实现分组交换的关键构件,其任务是转发收到的分组,这是网络核心部分最重要的功能。分组交换采用存储转发技术,表示把一个报文(要发送的整块数据)分为几个分组后再进行传送。在发送报文之前,先把较长的报文划分成为一个个更小的等长数据段。在每个数据端的前面加上一些由必要的控制信息组成的首部后,就构成了一个分组。分组又称为包。分组是在互联网中传送的数据单元,正是由于分组的头部包含了诸如目的地址和源地址等重要控制信息,每一个分组才能在互联网中独立的选择传输路径,并正确地交付到分组传输的终点。互联网按工作方式可划分为边缘部分和核心部分。主机在网络的边缘部分,其作用是进行信息处理。由大量网络和连接这些网络的路由器组成核心部分,其作用是提供连通性和交换。计算机通信是计算机中进程(即运行着的程序)之间的通信。计算机网络采用的通信方式是客户-服务器方式(C/S 方式)和对等连接方式(P2P 方式)。客户和服务器都是指通信中所涉及的应用进程。客户是服务请求方,服务器是服务提供方。按照作用范围的不同,计算机网络分为广域网 WAN,城域网 MAN,局域网 LAN,个人区域网 PAN。计算机网络最常用的性能指标是:速率,带宽,吞吐量,时延(发送时延,处理时延,排队时延),时延带宽积,往返时间和信道利用率。网络协议即协议,是为进行网络中的数据交换而建立的规则。计算机网络的各层以及其协议集合,称为网络的体系结构。五层体系结构由应用层,运输层,网络层(网际层),数据链路层,物理层组成。运输层最主要的协议是 TCP 和 UDP 协议,网络层最重要的协议是 IP 协议。ping和ICMP是如何探测网络情况的?ping和traceroute都是基于ICMP协议进行的。ping查询报文的操作,通过直接ping ip或者ping 域名的方式,计算机会一直发送ICMP请求,目标收到了信息会回复ICMP信息,头部类型8和0分别表示了发送和接收信息。查询报文是属于按照正常思路使用ICMP协议的操作。于此相对的,就是差错报文,差错报文是利用tracerouter发送UDP包到目标计算机的不常用端口(30000以上)的方式来可以让目标计算机返回错误信息。如果成功访问到了目标的UDP层,也会因为端口问题返回ICMP请求,这反而说明了当前计算机和目标计算机是相通的!另外,也可以通过设置TTL(time to live)的方式来进行中间路由的校验。网关和路由的工作原理网关和路由器一般是黏在一起的,网关往往分为两种,转发网关和NAT(network address translate)网关。两者区别:转发网关在转发请求的时候只会改变mac头部,但是NAT网关在转发请求的时候会同时改变ip地址。转发网关仅仅是起到一个过路财神的作用,就是为了进行数据信息的传递工作。但是NAT网关的作用就相对有趣了,是为了进行内外网的衔接而存在的。外网ip地址很稀有,因此就相对很贵,基本不可能对于内网中的每台设备都专门配备一个外网的ip,所以通过路由器时都会被伪装为同一个外网的ip,不同的是端口,NAPT(network address-port protocol)就是为此而存在的,该协议维护了一张表,用以表达两个网络间ip-端口对的互相映射关系。比如192.168.0.1就映射为外网的10086端口,192.168.0.2就映射为外网的10087端口,这样从内网发送出去的数据就可以互相区分彼此了,发送出去的数据也可以找回返回的路了。路由协议:路由之间需要进行沟通从而知道你到底帮助我些什么?路由器会维护一张路由表来记录如下问题:(1)目的网络:你的目的地是哪里?(2)出口设备:将包从哪个口扔出去?(3)下一跳网关:下一个路由器的地址?路由之间通过两种算法来构建路由器之间的网状关系,以及相互的最短路径。第一种:距离矢量路由这种方式通过TCP协议来每隔几秒向邻近的路由器发送自己的路由信息,从而整个网络每个路由器都会通过这种方式逐渐丰富自己的路由信息,最后所有路由器都会知道自己与其他路由器的连接情况。这种方式的优先是新装入路由快,但是路由一旦不行了,刷新路由就很慢,必须要不断试探直到距离超过阈值,才会判定路由之间不通。另外一个缺点就是需要发送整个全部路由表。网络太大就吃不消了。基于该算法的协议为BGP(border gateway protocol),为了避免该算法的在网络过大下产生的问题,就分为iBGP和eBGP用以分别对付内网和外网。第二种:链路状态路由算法这种方式通过DUP协议来每隔一段时间来广播传递信息,常常用于数据中心。他发送的是路由状态的改变信息,因此发送的数据量会明显小于第一种。每当一台新的路由器启动时,会向邻居发送信息以确定自己和邻居之间的距离,然后将该信息广播出去,以让整个网络中的所有路由器都能知道他已经横空出世了!同理,当邻居发送他怎么停机了时也会发送改动信息,以告诉整个网络,他好像暂时不行了。基于该算法的协议为IGP(Interior Gateway Protocol),主要用于数据中心,因为他可能快速地对路由变化进行响应所以更加便于进行负载均衡的操作。路由器和交换机的区别工作层次不同:交换机比路由器更简单,路由器比交换机能获取更多信息,交换机工作在数据链路层,而路由器工作在网络层数据转发所依据的对象不同。交换机的数据转发依据是利用物理地址或者说MAC地址来确定转发数据的目的地址,而路由器是依据ip地址进行工作的传统的交换机只能分割冲突域,不能分割广播域;而路由器可以分割广播域拓展:域名、IP、MAC 与 ARP域名是我们取代记忆复杂的 IP 的一种解决方案IP 地址才是目标在网络中所被分配的节点。MAC 地址是对应目标网卡所在的固定地址(物理地址)。ARP 英文全名为:Address resolution protocol ,地址解析协议,ARP为IP与MAC提供动态映射,过程自动完成。当PC发出通信请求时,根据协议规定,它的目的地址必然是48bit的MAC地址的。MAC并不能和IP直接去通信。那么就需我们的ARP协议来做相应的转换工作。
十三、kubernetes 核心技术Service
service存在意义Service是Kubernetes里最?核心?的资源对象之一,Service定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实力。 Service与其后端Pod副本集群之间则是通过??Label Selector??来实现"?无缝对接?"。而RC的作用实际上是保证Service 的服务能力和服务质量处于预期的标准。防止Pod失联(服务发现)定义一组Pod访问策略(负载均衡)Pod和Service关系(虚拟IP)根据lable和selector标签建立关联的service.yamlselector: app: nginxPod.yamllables: app: nginx3. 常用Service类型

3.1 ClusterIP(集群内部使用)apiVersion: v1kind: Servicemetadata: labels:app: web01name: web01spec: type: ClusterIP ports:name: http port: 80 protocol: TCP targetPort: 80name: https port: 8443 protocol: TCP #仅支持TCP和UDP,不写默认TCP targetPort: 8443selector:app: web01status: loadBalancer: {}
3.2 NodePort(对外访问应用使用)apiVersion: v1kind: Servicemetadata: creationTimestamp: null labels:app: web01name: web01-nodeportspec: type: NodePort ports:name: aaaa port: 80 nodePort: 23232 # 指定对外提供端口, 不加则随机分配 targetPort: 80name: bbb port: 9090 targetPort: 9090selector:app: web01status: loadBalancer: {}
3.3 LoadBalancer(对外访问应用使用,公有云)
LadBlancer Service 是Kubernetes深度结合云平台的一个组件;当使用LoadBlancer Service 暴露服务时,实际上是通过?向底层云平台申请创建一个负载均衡器?来向外暴露服务;目前LoadBlancer Service支持的云平台已经相对完善,比如国外的GCE、DigitalOcean,国内的阿里云,私有云(Openstack)等等,由于LoadBlancer Service深度结合了云平台,所以只能在一些云平台上使用
ECS使用体验
??大家好,我是一名研二的学生,来自于东北大学,就读于控制工程专业,同过师兄的介绍了解到阿里云的“飞天加速计划·高校学生在家实践”活动,非常有幸参与了这次活动。??大家可以同过阿里云的服务器写博客,既可以分享技术,又可以巩固自己得知识。我们可以不光是从开发者的角度去使用ESC,也可以去考虑如何让你的应用部署变得弹性可扩展、高可用、安全、应用如何和阿里云上的其它服务集成。比如说:可以用阿里云的负载均衡实现企业级应用部署,可以思考如何部署一个弹性架构,如果在应用层做扩展,如何实现两层架构,如何将结构化数据和非结构化数据分别处理。可以通过云盾+ECS,学习云上的安全知识。通过云监控,学习如何管理阿里云服务器ECS。可以通过SLS简单日志服务,学习如何分析你的应用程序(比如博客)产生的日志,进而了解访客的各种行为。你可以实现最简单的All-IN-ONE的架构,把你的博客应用(也可以是你觉得有价值的其它WEB应用、Java应用等),数据库,内容都部署在一个ECS实例上。??使用阿里云服务器建网站,推荐一个网站,宝塔面板(WordPress以及其它建站程序)对于爱折腾,有大量闲暇时间的,可以不用宝塔面板,而自己手工安装各种网站程序需要的运行环境。使用阿里云服务器还有一个注意事项,小伙伴要切记在ESC服务器后台管理系统中打开对应端口号的安全组。??通过阿里云的“高校学生在家实践”,让我体验到了如何使用linux操作系统的服务器,曾经看似多么遥不可及的东西,可以变成现实,希望自己将来可以玩转服务器,做一名优秀的程序员,也经常分享自己得技术心得,在这个过程中收获到了无比的开了,另外也祝愿阿里云越来越好!
【云原生 | Kubernetes篇】Kubernetes简介
Kubernetes简介一、背景1、部署方式的变迁传统部署时代:在物理服务器上运行应用程序无法为应用程序定义资源边界导致资源分配问题例如,如果在物理服务器上运行多个应用程序,则可能会出现一个应用程序占用大部分资源的情况, 结果可能导致其他应用程序的性能下降。 一种解决方案是在不同的物理服务器上运行每个应用程序,但是由于资源利用不足而无法扩展, 并且维护许多物理服务器的成本很高。虚拟化部署时代:作为解决方案,引入了虚拟化虚拟化技术允许你在单个物理服务器的 CPU 上运行多个虚拟机(VM)虚拟化允许应用程序在 VM 之间隔离,并提供一定程度的安全一个应用程序的信息 不能被另一应用程序随意访问。虚拟化技术能够更好地利用物理服务器上的资源因为可轻松地添加或更新应用程序 ,所以可以实现更好的可伸缩性,降低硬件成本等等。每个 VM 是一台完整的计算机,在虚拟化硬件之上运行所有组件,包括其自己的操作系统。缺点:虚拟层冗余导致的资源浪费与性能下降容器部署时代:容器类似于 VM,但可以在应用程序之间共享操作系统(OS)。容器被认为是轻量级的。容器与 VM 类似,具有自己的文件系统、CPU、内存、进程空间等。由于它们与基础架构分离,因此可以跨云和 OS 发行版本进行移植。容器优势:敏捷性:敏捷应用程序的创建和部署:与使用 VM 镜像相比,提高了容器镜像创建的简便性和效率。及时性:持续开发、集成和部署:通过快速简单的回滚(由于镜像不可变性),支持可靠且频繁的 容器镜像构建和部署。解耦性:关注开发与运维的分离:在构建/发布时创建应用程序容器镜像,而不是在部署时。 从而将应用程序与基础架构分离。可观测性:可观察性不仅可以显示操作系统级别的信息和指标,还可以显示应用程序的运行状况和其他指标信号。跨平台:跨开发、测试和生产的环境一致性:在便携式计算机上与在云中相同地运行。可移植:跨云和操作系统发行版本的可移植性:可在 Ubuntu、RHEL、CoreOS、本地、 Google Kubernetes Engine 和其他任何地方运行。简易性:以应用程序为中心的管理:提高抽象级别,从在虚拟硬件上运行 OS 到使用逻辑资源在 OS 上运行应用程序。大分布式:松散耦合、分布式、弹性、解放的微服务:应用程序被分解成较小的独立部分, 并且可以动态部署和管理 - 而不是在一台大型单机上整体运行。隔离性:资源隔离:可预测的应用程序性能。高效性:资源利用:高效率和高密度2、容器化问题弹性的容器化应用管理强大的故障转移能力高性能的负载均衡访问机制便捷的扩展自动化的资源监测3、为什么用 Kubernetes容器是打包和运行应用程序的好方式。在生产环境中,你需要管理运行应用程序的容器,并确保不会停机。 例如,如果一个容器发生故障,则需要启动另一个容器。如果系统处理此行为,会不会更容易?这就是 Kubernetes 来解决这些问题的方法! Kubernetes 为你提供了一个可弹性运行分布式系统的框架。linux之上的一个服务编排框架;Kubernetes 会满足你的扩展要求、故障转移、部署模式等。 例如,Kubernetes 可以轻松管理系统的 Canary 部署。Kubernetes 为你提供:服务发现和负载均衡Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器,如果进入容器的流量很大, Kubernetes 可以负载均衡并分配网络流量,从而使部署稳定。存储编排Kubernetes 允许你自动挂载你选择的存储系统,例如本地存储、公共云提供商等。自动部署和回滚你可以使用 Kubernetes 描述已部署容器的所需状态,它可以以受控的速率将实际状态 更改为期望状态。例如,你可以自动化 Kubernetes 来为你的部署创建新容器, 删除现有容器并将它们的所有资源用于新容器。自动完成装箱计算Kubernetes 允许你指定每个容器所需 CPU 和内存(RAM)。 当容器指定了资源请求时,Kubernetes 可以做出更好的决策来管理容器的资源。自我修复Kubernetes 重新启动失败的容器、替换容器、杀死不响应用户定义的 运行状况检查的容器,并且在准备好服务之前不将其通告给客户端。密钥与配置管理Kubernetes 允许你存储和管理敏感信息,例如密码、OAuth 令牌和 ssh 密钥。 你可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需在堆栈配置中暴露密钥为了生产环境的容器化大规模应用编排,必须有一个自动化的框架。4、市场份额4.1、容器化docker swarm?4.2、服务编排?google --- kubernetes --- 发起cncf --- 众多的项目辅佐 kubernetes ---- kubernetes +cncf其他软件 = 整个大型云平台二、简介Kubernetes 是一个可移植的、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态系统。Kubernetes 的服务、支持和工具广泛可用。名称 Kubernetes 源于希腊语,意为“舵手”或“飞行员”。Google 在 2014 年开源了 Kubernetes 项目。 Kubernetes 建立在Google 在大规模运行生产工作负载方面拥有十几年的经验 的基础上,结合了社区中最好的想法和实践。1、Kubernetes不是什么Kubernetes 不是传统的、包罗万象的 PaaS(平台即服务)系统。Kubernetes 在容器级别而不是在硬件级别运行它提供了 PaaS 产品共有的一些普遍适用的功能, 例如部署、扩展、负载均衡、日志记录和监视。但是,Kubernetes 不是单体系统,默认解决方案都是可选和可插拔的。 Kubernetes 提供了构建开发人员平台的基础,但是在重要的地方保留了用户的选择和灵活性。Kubernetes:不限制支持的应用程序类型。 Kubernetes 旨在支持极其多种多样的工作负载,包括无状态、有状态和数据处理工作负载。 如果应用程序可以在容器中运行,那么它应该可以在 Kubernetes 上很好地运行。不部署源代码,也不构建你的应用程序。 持续集成(CI)、交付和部署(CI/CD)工作流取决于组织的文化和偏好以及技术要求。不提供应用程序级别的服务作为内置服务,例如中间件(例如,消息中间件)、 数据处理框架(例如,Spark)、数据库(例如,mysql)、缓存、集群存储系统 (例如,Ceph)。这样的组件可以在 Kubernetes 上运行,并且/或者可以由运行在 Kubernetes 上的应用程序通过可移植机制(例如, 开放服务代理)来访问。不要求日志记录、监视或警报解决方案。 它提供了一些集成作为概念证明,并提供了收集和导出指标的机制。不提供或不要求配置语言/系统(例如 jsonnet),它提供了声明性 API, 该声明性 API 可以由任意形式的声明性规范所构成。RESTful;写yaml文件不提供也不采用任何全面的机器配置、维护、管理或自我修复系统。此外,Kubernetes 不仅仅是一个编排系统,实际上它消除了编排的需要。 编排的技术定义是执行已定义的工作流程:首先执行 A,然后执行 B,再执行 C。 相比之下,Kubernetes 包含一组独立的、可组合的控制过程, 这些过程连续地将当前状态驱动到所提供的所需状态。 如何从 A 到 C 的方式无关紧要,也不需要集中控制,这使得系统更易于使用 且功能更强大、系统更健壮、更为弹性和可扩展。
初学者都能学会的ElasticSearch入门实战《玩转ElasticSearch 2》-1
大家好,我是咔咔 不期速成,日拱一卒项目中准备使用ElasticSearch,之前只是对ElasticSearch有过简单的了解没有系统的学习,本系列文章将从基础的学习再到深入的使用。咔咔之前写了一份死磕MySQL文章,如今再入一个系列玩转ElasticSearch。本期文章会带给大家学习ElasticSearch的基础入门,先把基础学会再深入学习更多的知识点。一、基本概念文档(Document)ElasticSearch是面向文档的,文档是所有可搜索数据的最小单位,例如MySQL的一条数据记录文档会被序列化成为json格式,保存在ElasticSearch中每个文档都有一个唯一ID,例如MySQL中的主键IDJSON文档一篇文档包括了一系列的字段,例如数据中的一条记录json文档,格式灵活,不需要预先定义格式在上期文章中把csv文件格式文件通过Logstash转化为json存储到ElasticSearch中文档的元数据index :文档所属的索引名type:文档所属类型名id:文档唯一IDsource:文档的原始JSON数据version:文档的版本信息score:相关性分数索引索引是文档的容器,是一类文档的结合,每个索引都有自己的mapping定义,用于定义包含的文档的字段和类型每个索引都可以定义mapping,setting,mapping是定义字段类型,setting定义不同的数据分布{
"movies" : {
"aliases" : { },
"mappings" : {
"properties" : {
"@version" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"genre" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"id" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"year" : {
"type" : "long"
}
}
},
"settings" : {
"index" : {
"creation_date" : "1641637408626",
"number_of_shards" : "1",
"number_of_replicas" : "1",
"uuid" : "gf0M2BgnStGZZHsIJD6otQ",
"version" : {
"created" : "7010099"
},
"provided_name" : "movies"
}
}
}
}
节点节点是一个ElasticSearch的实例,本质上就是java的一个进程,一台机器可以运行多个ElasticSearch进程,但生产环境下还是建议一台服务器运行一个ElasticSearch实例每个节点都有名字,通过配置文件配置,或者启动时 -E node.name=node1每个节点在启动后,会分配一个UID,保存在data目录下主节点:master默认情况下任何一个集群中的节点都有可能被选为主节点,职责是创建索引、删除索引、跟踪集群中的节点、决定分片分配给相应的节点。索引数据和搜索查询操作会占用大量的内存、cpu、io资源。因此,为了保证一个集群的稳定性,应该主动分离主节点跟数据节点。数据节点:data看名字就知道是存储索引数据的节点,主要用来增删改查、聚合操作等。数据节点对内存、cpu、io要求比较高,在优化的时候需要注意监控数据节点的状态,当资源不够的时候,需要在集群中添加新的节点。负载均衡节点:client该节点只能处理路由请求,处理搜索,分发索引等操作,该节点类似于Nginx的负载均衡处理,独立的客户端节点在一个比较大的集群中是非常有用的,它会协调主节点、数据节点、客户端节点加入集群的状态,根据集群的状态可以直接路由请求。预处理节点:ingest在索引数据之前可以先对数据做预处理操作,所有节点其实默认都是支持ingest操作的,也可以专门将某个节点配置为ingest节点。分片分片分为主分片,副本分片主分片:用以解决数据水平扩展的问题,将数据分布到集群内的所有节点上,一个分片是一个运行的Lucene(搜索引擎)实例,主分片数在创建时指定,后续不允许修改,除非Reindex副本:用以解决数据高可用的问题,可以理解为主分片的拷贝,增加副本数,还可以在一定程度上提高服务的可用性。在生产环境中分片的设置有何影响分片数设置过小会导致无法增加节点实现水平扩展,单个分片数据量太大,导致数据重新分配耗时。假设你给索引设置了三个主分片 ,这时你给集群加了几个实例,索引也只能在三台服务器上分片数设置过大导致搜索结果相关性打分,影响统计结果的准确性,单个节点上过多的分片,会导致资源浪费,同时也会影响性能从ElasticSearch7.0开始,默认的主分片设置为1,解决了over-sharding的问题查看集群健康状态执行接口get _cluster/healthgreen:主分片与副本都正常分配yellow:主分片全部正常分配,有副本分片未能正常分配red:有主分片未能分配,当服务器的磁盘容量超过85%时创建了一个索引二、Result Api
面试题22解析-CDN分析
本文阅读大概需要15分钟。这个题目主要考查你对CDN工作原理的理解。一CDN是什么?CDN(Content Delivery Network),翻译过来就是内容分发网络,是构建在现有网络之上的一种内容分发网络;它将网站的内容通过中心平台分发到部署在各地的边缘服务器进行缓存,再通过负载均衡技术将用户的请求转发到就近的服务器上去获取所需内容,降低网络堵塞,提供访问网站的响应速度和命中率。它不同于简单的镜像,体现在它的内容的存储和分发、负载均衡上面。二CDN的服务类型?目前,CDN服务的类型根据其服务的内容主要分为网页加速、流媒体服务、文件传输加速和应用协议加速4类。网页加速:主要用于缓存网站的静态数据,比如JS、CSS、图片和静态页面等。用户一般从主站获取动态内容后,再从cdn下载相应的静态数据,从而加快网页的下载速度。流媒体服务:主要服务于视频网站,通过将流媒体内容推送到离用户最近的节点,使用户可以从网络边缘获取内容,从而缩短响应时间,提高视频传输质量,减小中心服务器的压力。文件传输加速:通过使用CDN节点提供下载服务,来缓解文件下载带来的性能压力和带宽压力,提供用户下载速度。应用协议加速:通过对TCP等传输协议的优化,改善和加速和改善用户在广域网上的内容传输速度。或者对一些特定协议,如SSL协议进行加速,解决安全传输时的性能和响应速度问题。三CDN的工作原理?CDN是主要是通过接管DNS的方式来把请求引流到离用户最近的缓存服务器上面,如图所示:详细过程:用户向浏览器提供要访问的域名。首先向Local DNS服务器发送请求,经过迭代解析后回到这个域名的注册服务器去解析,DNS域名解析服务器通常会把它重新解析到另一个域名,这个域名最终会指向CDN全局中的负载均衡服务器,再由GTM分配那个地方的用户访问那个地方的CDN服务器。用户直接去这个DNS节点获得相应的静态资源,如果这个节点的文件不存在,就会再去源站获取这个文件,然后再返回给用户。四负载均衡是什么?由于CDN同一节点内往往包括多台服务器,为取得服务器性能的最优,需要应用负载均衡技术。我们常说的负载均衡,是指处理节点的负载信息通过某代理软件传递给均衡器,由均衡器做出决策并对负载进行动态分配,从而使集群中各处理节点的负载相对趋于平衡。负载均衡主要解决以下几个方面的问题:为用户提供更好的访问质量。提高服务器响应速度。提高服务器及其他资源的利用效率。避免了网络关键部位出现单点失效。解决网络拥塞问题,服务就近提供,实现地理位置无关性。负载均衡的常用算法:轮转调度加权轮转调度随机均衡调度加权随机均衡调度最小连接调度加权最小连接调度目标地址散列调度源地址散列调度基于局部性的最少链接调度带复制的基于局部性最少链接调度响应速度均衡调度处理能力均衡调度DNS均衡调度作者在此只是列举这些常见的负载均衡算法,如果感兴趣的读者,可以去查阅相关的资料了解一下。这些负载均衡算法,我们也会在分布式集群中用到,比如nginx、dubbo、spring cloud微服务等。五CDN怎么动态加速?CDN的动态加速是在CDN的DNS解析过程中,通过动态的链路探寻出一条最优的路径,然后通过DNS的调度将所有请求调度到选定的这条路径上回源,从而加速用户访问速率。如图:怎么来选择这么一条最优的路径呢?每个cdn节点从源站上下载一个特定大小的文件,看看那个链路总耗时最短或者网络成本最小等,形成一个最优的链路列表,将其绑定到DNS解析上,更新到CDN的Local DNS。六CDN的衡量指标可扩展:主要体现在性能可扩展和成本可扩展。安全性:所提供的设备、网络、软件、数据和服务过程的安全性,能抵御DDOS等其它恶意攻击。高可用:面对用户性能下降和故障问题时,能提供及时的容错机制。如果读者希望了解更多关于CDN相关的知识,建议去读一下《内容分发网络(CDN)关键技术、架构与应用》。
Nginx 安装
web服务器, 轻量级, 能处理大并发量反向代理服务器(负载均衡)你可以轻松的在服务器上通过 Nginx 部署 HTTP 静态服务。windows 下 Nginx 环境的安装:下载-解压-双击nginx.exe 即可运行linux 下 Nginx 环境的安装:CentOS 下 yum 安装使用 yum 来安装 Nginxyum install nginx -y安装完成后,使用 nginx 命令启动 Nginx: ./nginxubuntu 下 apt-get 安装apt-get install nginx使用 docker 安装 nginx请参考教程: Docker 安装 Nginx | 菜鸟教程https://www.runoob.com/docker/docker-install-nginx.htmllinux 环境手动安装 nginx从 nginx 官网下载稳定版 Stable version 安装包http://nginx.org/en/download.html安装Nginx依赖,pcre、openssl、gcc、zlib(推荐使?yum源?动安装)yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel解压 Nginx软件包tar -xvf nginx-1.17.8.tar进?解压之后的?录 nginx-1.17.8cd nginx-1.17.8命令?执?./configure命令?执? make命令?执? make install,完毕之后在/usr/local/下会产??个nginx?录-bash: make: command not found - 解决办法一般出现这个-bash: make: command not found提示,是因为安装系统的时候使用的是最小化mini安装,系统没有安装 make 等常用命令,直接 yum 安装即可。yum -y install gcc make进入/usr/local/nginx/sbin, 键入 ./nginx 即可启动默认80端口的nginx.mac下 nginx 的使用分别执行下面这两行命令,就会自动安装nginx,等待安装完成即可brew install nginx其他命令brew search nginxbrew remove nginxmac 下一些重要文件的路径核心安装目录 /usr/local/Cellar/nginx/x.y.z启动文件在该目录的bin下面欢迎页面在html下面Docroot (服务器默认路径): /usr/local/var/wwwThe default port has been set in `/usr/local/etc/nginx/nginx.conf`to 8080 so that
nginx can run without sudo.
nginx will load all files in `/usr/local/etc/nginx/servers/`
To have launchd start nginx now and restart at login:
`brew services start nginx`
Or, if you don't want/need a background service you can just run:
`nginx`其他命令:brew services restart nginx 重启 nginx 服务brew services stop nginx 停止nginx服务.mac 安装过程中遇到的问题$ brew install pcre发现Error: The `brew link` step did not complete successfully
The formula built, but is not symlinked into /usr/local
Could not symlink .
/usr/local/opt is not writable.然后试试brew link pcre 也不行Error: Could not symlink .
/usr/local/opt is not writable.使用$ sudo chown -R $(whoami):admin /usr/local发现还是没用, 最终手动创建/user/local/opt 文件夹解决了(Mac High Sierra 中不能改变/usr/local的拥有者的问题)常用命令Nginx 让新的配置生效 nginx -s reload关闭命令: ./nginx -s stop运行http://localhost/教程如何在 linux 下 安装多个 nginx./configure --prefix=/home/work/nginx2 ..... // 第二个nginx的安装配置, 用于指定安装目录make && make install./configure --prefix=/home/work/nginx3 ..... // 第三个nginx的安装配置make && make install报错总结使用windows版本的nginx启动时遇到(1113: No mapping for the Unicode character exists in the target multi-byte code page)这个错误解决:路径里面包含有中文的缘故