JAVA面试中间件

- dubbo

参考大神的dubbo面试的18问https://blog.csdn.net/weixin_44337261/article/details/88149072

  1. dubbo是什么
    Dubbo是阿里巴巴SOA服务化治理方案的核心框架,Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案。
  2. dubbo架构
    在这里插入图片描述
    1.当服务的提供者启动时,会将服务的名称:IP:端口会写入注册中心.
    2.注册中心内部会维护服务列表,某个服务提供者关机了,服务还能正常进行
    3.当消费者需要访问服务时,需要先访问注册中心获取服务列表信息.之后将服务列表保存到本地缓存中.方便后续的访问.在客户端内部有负载均衡的算法,筛选出一台服务器,之后进行访问.
    4.如果后台服务器出现宕机现象.这时注册中心通过心跳检测的方式判断服务器是否宕机.如果服务器宕机则会将该服务器信息从服务列表中删除.之后将新的服务列表发送消费者(客户端)进行更新.
  3. dubbo底层原理
    总结:RPC调用的规则可以传输java对象.底层实现时将数据转化流,并且该流经过加密处理.并且rpc内部使用UTF-8编码格式
    要求:传输的java对象必须序列化
    设计:
    在这里插入图片描述
    实现一个RPC远程调用框架

提供者:

public class ProviderMain {

	public static void main(String[] args) {
		try {
			System.out.println("start server 20881");
			ServerSocket serverSocket = new ServerSocket(20881);
			while (true) {
				ObjectInputStream objectInputStream = null;
				ObjectOutputStream objectOutputStream = null;
				try {
					Socket socket = serverSocket.accept();
					// 1,接收数据
					InputStream inputStream = socket.getInputStream();
					objectInputStream = new ObjectInputStream(inputStream);
					String interfaceName = objectInputStream.readUTF();
					String methodName = objectInputStream.readUTF();
					Class[] parameterTypes = (Class[]) objectInputStream.readObject();
					Object[] argments = (Object[]) objectInputStream.readObject();
					// 2,执行方法
					// 根据interfaceName找到实现类
					// map(interfaceName,impl);
					Class implClass = CartServiceImpl.class;
					Object implObject = implClass.newInstance();
					Method method = implClass.getMethod(methodName, parameterTypes);
					Object result = method.invoke(implObject, argments);

					// 3,返回数据
					OutputStream outputStream = socket.getOutputStream();
					objectOutputStream = new ObjectOutputStream(outputStream);
					objectOutputStream.writeObject(result);

				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					objectInputStream.close();
					objectOutputStream.close();
				}

			}
		} catch (Exception e) {
			// TODO: handle exception
		}

	}

}

消费者:

public class CartController {

	public static void main(String[] args) {
		try {
			Object object = getObject(CartService.class);
			CartService cartService = (CartService) object;
			cartService.findCartByUserId(91L);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	static class MyInvocationHandler implements InvocationHandler {

		String interfaceName;

		public MyInvocationHandler(String interfaceName) {
			super();
			this.interfaceName = interfaceName;
		}

		public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
			ObjectOutputStream objectOutputStream = null;
			ObjectInputStream objectInputStream = null;
			try {
				// 1,得方法名,参数数据
				String methodName = method.getName();
				Class[] parameterTypes = method.getParameterTypes();

				// 2,发送数据
				Socket socket = new Socket("127.0.0.1", 20881);
				OutputStream outputStream = socket.getOutputStream();
				objectOutputStream = new ObjectOutputStream(outputStream);
				objectOutputStream.writeUTF(interfaceName);
				objectOutputStream.writeUTF(methodName);
				objectOutputStream.writeObject(parameterTypes);
				objectOutputStream.writeObject(args);
				// 3,接收数据
				InputStream inputStream = socket.getInputStream();
				objectInputStream = new ObjectInputStream(inputStream);
				Object result = objectInputStream.readObject();
				System.out.println("收到远程方法执行结果" + result);

				return result;
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				objectOutputStream.close();
				objectInputStream.close();
			}
			return null;
		}

	}

	public static Object getObject(final Class interfaceInfo) {
		// 1.将本地的接口调用转换成JDK的动态代理,在动态代理中实现接口的远程调用
		String interfaceName = interfaceInfo.getName();
		ClassLoader classLoader = interfaceInfo.getClassLoader();
		Class[] interfaces = { interfaceInfo };
		MyInvocationHandler myInvocationHandler = new MyInvocationHandler(interfaceName);
		Object object = Proxy.newProxyInstance(classLoader, interfaces, myInvocationHandler);
		return object;
	}

}
  1. 运用

提供者:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
	http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
	http://code.alibabatech.com/schema/dubbo 
	http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
	<!-- 1,设置应用名称 -->
	<dubbo:application name="provider-of-cart"/>
	<!-- 2,设置服务注册中心zookeeper地址 -->
	<dubbo:registry  address="zookeeper://192.168.216.202:2181">
	</dubbo:registry>
	<!-- 3,设置dubbo端口号 -->
	<dubbo:protocol name="dubbo" port="20883"></dubbo:protocol>
	<!-- 4,注册服务实现类对象-->
	<bean class="com.jt.service.CartServiceImpl" id="cartService">
	</bean>
	<!-- 5,设置客户端能访问接口,像servlet注册-->
	<dubbo:service interface="com.jt.service.CartService" ref="cartService">
	</dubbo:service>
</beans>

注册中心、dubbo端口、注册服务实现类、客户端服务访问接口

服务消费者:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
	xsi:schemaLocation="http://www.springframework.org/schema/beans 
			http://www.springframework.org/schema/beans/spring-beans.xsd 
			http://code.alibabatech.com/schema/dubbo 
			http://code.alibabatech.com/schema/dubbo/dubbo.xsd ">
			
	<!--1,设置应用名-->
	<dubbo:application name="consumer-of-cart" />
	
	<!--2,设置注册中心地址 --> 
	<dubbo:registry address="zookeeper://192.168.216.202:2181" />
	
	
	<!--3,得到远程服务代理对象,可以像使用本地bean一样使用cartService -->
	<dubbo:reference timeout="50000"  id="cartService" interface="com.jt.cart.service.CartService" />

注册中心地址,访问接口名

  1. 面试问题
1、一般在项目中需要先启动服务提供者,后启动服务消费者,否则会报错,解决办法:
如:a,b 消费b ,  b也需要消费a中间的service.  
启动时检查提供者是否存在,true报错,false忽略 不检查

@Configuration
public class DubboConfig {

/**
 * 消费者配置不主动监督zookeeper服务
 *
 * @return
 */
@Bean
public ConsumerConfig consumerConfig() {
   ConsumerConfig consumerConfig = new ConsumerConfig();
   consumerConfig.setCheck(false);
   consumerConfig.setTimeout(40000);
   return consumerConfig;
}

}

   2、说明:当zk如果宕机后,消费者能否正确消息?????关zk,provider1,provider2
       答案:可以
	  因为zk会动态的向客户端更新服务列表信息.当zk宕机后,由于之前已经同步了zk的服务列表信息,所以客户端可以 按照自己已经缓存的清单进行访问.如果在这个期间服务端程序发现宕机现象,那么则访问故障机时由于不能通信,则等待超时时间,则访问下一台服务器.
如果这时,所有的服务端程序都宕机,则整个服务陷入瘫痪.

- Zookeeper

  • zookeeper是什么
    ZooKeeper是一个分布式应用程序协调服务。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
    当客户端发起请求时,zookeeper返回正确的服务器地址.

  • 集群原理
    在这里插入图片描述
    总结:Zookeeper --原子广播—Server之间的同步—。Zab协议:两种模式–》1.恢复模式(选主)2.广播模式(同步)。当服务启动或者在领导者崩溃后,Zab就进入了恢复(选主)模式,当领导者被选举出来,且大多数Server完成了和 leader的状态同步以后,恢复模式就结束了。状态同步保证了leader和Server具有相同的系统状态。
    补充:心跳检测能够监控服务的健康状况,同springcloud的Eureka服务器差不多功能

  • 作为dubbo的注册中心Zookeeper对dubbo存储数据的示例:
    在这里插入图片描述
    节点类型
    有4种类型的znode

1、PERSISTENT–持久化目录节点

客户端与zookeeper断开连接后,该节点依旧存在

2、PERSISTENT_SEQUENTIAL-持久化顺序编号目录节点

客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号

3、EPHEMERAL-临时目录节点

客户端与zookeeper断开连接后,该节点被删除

4、EPHEMERAL_SEQUENTIAL-临时顺序编号目录节点

客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号

- RabbitMq、ActiveMq、RocketMq、kafaka

  • 应用解耦
    在这里插入图片描述
    订单系统写入消息队列,然后返回用户下单成功,
    库存系统去订阅消息,进行入库

  • 流量销峰
    在这里插入图片描述
    应用场景:秒杀活动,一般会因为流量过大,导致流量暴增,应用挂掉。为解决这个问题,一般需要在应用前端加入消息队列。

    用户的请求,服务器接收后,首先写入消息队列。假如消息队列长度超过最大数量,则直接抛弃用户请求或跳转到 错误页面
    秒杀业务根据消息队列中的请求信息,再做后续处理
    通过消息队列缓解服务器压力.

  • 异步处理
    在这里插入图片描述

    在这里插入图片描述在这里插入图片描述
    RabbitMQ:
    1.rabbitMQ监控系统的端口:15672
    2.rabbitMQ服务调用端口:5672
    1.0 如何保证消息队列的顺序执行
    场景:
    在这里插入图片描述
    由于拿到每个数据后,消费每个数据的速度不一样,导致了入数据库的数据顺序有问题。

解决方案:(RabbitmQ)
1、多个队列将消息往一个队列发
在这里插入图片描述
2、消费者内部采用内存队列来消费
在这里插入图片描述
解决方案(ActiveMq的):
有消息组
https://blog.csdn.net/YAOQINGGG/article/details/82563304

2.0如何保证消息不丢失?

三处消息丢失:
生产者:confirm机制。
rabbitmq:开启持久化 2处 元数据 和 队列数据。
消费者:手动ack告诉rabbitmq 手动确认机制。

一:rabbitmq的事务,阻塞的,回滚,然后重试;confirm机制,消息队列回传你ack确认(针对提供者断线了导致的数据丢失),如果没能收到会回调你nack接口告诉你失败了
二:持久化数据开启(针对ActiveMq挂了)
三:消费者采取手动确认机制 消费结束的时候手动ack 到消息队列。

参考:https://www.cnblogs.com/756623607-zhang/p/10507267.html

3.0如何处理消费者消费失败?

加入一个死信队列,后台启动线程重新消费

消息队列模式:
1.简单模式
在这里插入图片描述
工作原理:
当客户端(生产者)将消息写入消息队列中时,消息队列中信息的数量加1.
消费者实时监听消息队列,当消息队列中有消息时,则获取消息,之后执行业务逻辑.同时消息队列的数量减一
实现:

<!-- 消息队列 -->
		<dependency>
			<groupId>com.rabbitmq</groupId>
			<artifactId>amqp-client</artifactId>
			<version>3.5.1</version>
		</dependency>

消息生产者

public class Test_1_simple_provider {
	
	@Test
	public void provider() throws Exception
	{
	   //1,建立连接
		ConnectionFactory factory=new ConnectionFactory();		
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection=factory.newConnection();
		//2,建立通道
		Channel channel=connection.createChannel();
		//3,定义队列
		//durable true 持久化,重启服务器后,数据还有
	    //exclusive true,只能通过当前连接消费 false
		//autoDelete true 队列中消息处理完后,自动删除队列
		//arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		//4,发送消息,routingKey必须与queue一致
		String msg="msg1";
		channel.basicPublish("", "orderQueue", null, msg.getBytes());
		//5,关闭
		channel.close();
		connection.close();
		System.out.println("发送数据成功");
	   
	}

}

消息消费者:

public class Test_1_simple_consumer {
	
	
	@Test
	public void consumer() throws Exception
	{
	   //1,建立连接
		ConnectionFactory factory=new ConnectionFactory();		
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection=factory.newConnection();
		//2,建立通道
		Channel channel=connection.createChannel();
		//3,定义队列
		//durable true 持久化,重启服务器后,数据还有
	    //exclusive true,只能通过当前连接消费 false
		//autoDelete true 队列中消息处理完后,自动删除队列
		//arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		
		//4,创建消费者
		QueueingConsumer consumer=new QueueingConsumer(channel);
		//autoAck:自动回复消息
		channel.basicConsume("orderQueue", true, consumer);
		//5,取消息
		while(true)
		{
			Delivery delivery=consumer.nextDelivery();
			byte[] data=delivery.getBody();
			String mString=new String(data);
			System.out.println("消费者取到:"+mString);
		}
	   
	}

}

2.工作模式
在这里插入图片描述
说明:
由一个生产者负责消息写入队列,但是如果有一个消费者负责消费,可能会造成消息的积压.所以准备多个消费者共同消费一个队列中的消息.
实现:
生产者:

public class Test_2_work_provider {

	@Test
	public void provider() throws Exception {
		// 1,建立连接
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection = factory.newConnection();
		// 2,建立通道
		Channel channel = connection.createChannel();
		// 3,定义队列
		// durable true 持久化,重启服务器后,数据还有
		// exclusive true,只能通过当前连接消费 false
		// autoDelete true 队列中消息处理完后,自动删除队列
		// arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		// 4,发送消息
		for (int i = 3; i < 10; i++) {
			String msg = "msg" + i;
			channel.basicPublish("", "orderQueue", null, msg.getBytes());
		}
		// 5,关闭
		channel.close();
		connection.close();
		System.out.println("发送数据成功");

	}

}

消费者1

public class Test_2_work_consumer1 {
	
	
	@Test
	public void consumer() throws Exception
	{
	   //1,建立连接
		ConnectionFactory factory=new ConnectionFactory();		
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection=factory.newConnection();
		//2,建立通道
		Channel channel=connection.createChannel();
		//3,定义队列
		//durable true 持久化,重启服务器后,数据还有
	    //exclusive true,只能通过当前连接消费 false
		//autoDelete true 队列中消息处理完后,自动删除队列
		//arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		
		//4,创建消费者
		QueueingConsumer consumer=new QueueingConsumer(channel);
		//autoAck:自动回复消息
		channel.basicConsume("orderQueue", true, consumer);
		//5,取消息
		System.out.println("消费者1启动");

		while(true)
		{
			Delivery delivery=consumer.nextDelivery();
			byte[] data=delivery.getBody();
			String mString=new String(data);
			System.out.println("消费者1取到:"+mString);
		}
	   
	}

}

消费者2

public class Test_2_work_consumer2 {
	
	
	@Test
	public void consumer() throws Exception
	{
	   //1,建立连接
		ConnectionFactory factory=new ConnectionFactory();		
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection=factory.newConnection();
		//2,建立通道
		Channel channel=connection.createChannel();
		//3,定义队列
		//durable true 持久化,重启服务器后,数据还有
	    //exclusive true,只能通过当前连接消费 false
		//autoDelete true 队列中消息处理完后,自动删除队列
		//arguments 参数
		channel.queueDeclare("orderQueue", true, false, false, null);
		
		//4,创建消费者
		QueueingConsumer consumer=new QueueingConsumer(channel);
		//autoAck:自动回复消息
		channel.basicConsume("orderQueue", true, consumer);
		//5,取消息
		System.out.println("消费者2启动");

		while(true)
		{
			Delivery delivery=consumer.nextDelivery();
			byte[] data=delivery.getBody();
			String mString=new String(data);
			System.out.println("消费者2取到:"+mString);
		}
	   
	}

}

说明:
消息被两个消费者消费

3.发布订阅模式
在这里插入图片描述
特点:如果生产者发送消息,那么订阅的全部消费者都会执行消息.
后期添加的消费者得不到以前的消息。需要先启动消费者

生产者:

public class Test_3_publish_p {
	// 定义生产者
	@Test
	public void provider() throws IOException {
		System.out.println("开始发布消息");
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection connection = factory.newConnection();
		// 定义通道
		Channel channel = connection.createChannel();
		// 定义交换机名称
		String exchange_name = "E1";
		// fanout是定义发布订阅模式  direct是 路由模式 topic是主题模式
		channel.exchangeDeclare(exchange_name, "fanout");

		String msg = "order1" ;
		channel.basicPublish(exchange_name, "", null, msg.getBytes());

		channel.close();
		connection.close();
	}

	
}

消费者1:
public class Test_3_publish_c1 {

@Test
public void consumer1() throws Exception{
	ConnectionFactory factory = new ConnectionFactory();
	factory.setHost("192.168.216.202");
	factory.setPort(5672);
	factory.setUsername("jtadmin");
	factory.setPassword("jtadmin");
	factory.setVirtualHost("/jt");
	Connection	connection = factory.newConnection();
	
	Channel channel = connection.createChannel();
	String exchange_name = "E1";
	//定义交换机模式
	channel.exchangeDeclare(exchange_name, "fanout");
	
	String queue_name = UUID.randomUUID().toString();
	System.out.println("队列名称"+queue_name);
	//定义队列
	channel.queueDeclare(queue_name, true, false, false, null);
	//将队列和交换机绑定   key:表示接收数据标识
	channel.queueBind(queue_name, exchange_name, "");
	//定义消费数量 
	channel.basicQos(1);
	//定义消费者
	QueueingConsumer consumer = new QueueingConsumer(channel);
	
	//将消费者和队列绑定,并且需要手动返回
	channel.basicConsume(queue_name, false, consumer);
	System.out.println("消费者1");

	while(true){
		QueueingConsumer.Delivery delivery = consumer.nextDelivery();
		String msg = new String(delivery.getBody());
		System.out.println(msg+"入库");
		//false表示一个一个返回
		channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
	}
}

}
消费者2:

@Test
	public void consumer1() throws Exception{
		ConnectionFactory factory = new ConnectionFactory();
		factory.setHost("192.168.216.202");
		factory.setPort(5672);
		factory.setUsername("jtadmin");
		factory.setPassword("jtadmin");
		factory.setVirtualHost("/jt");
		Connection	connection = factory.newConnection();
		
		Channel channel = connection.createChannel();
		String exchange_name = "E1";
		//定义交换机模式
		channel.exchangeDeclare(exchange_name, "fanout");
		
		String queue_name = UUID.randomUUID().toString();
		System.out.println("队列名称"+queue_name);
		//定义队列
		channel.queueDeclare(queue_name, true, false, false, null);
		//将队列和交换机绑定   key:表示接收数据标识
		channel.queueBind(queue_name, exchange_name, "");
		//定义消费数量 
		channel.basicQos(1);
		//定义消费者
		QueueingConsumer consumer = new QueueingConsumer(channel);
		
		//将消费者和队列绑定,并且需要手动返回
		channel.basicConsume(queue_name, false, consumer);
		System.out.println("消费者2");

		while(true){
			QueueingConsumer.Delivery delivery = consumer.nextDelivery();
			String msg = new String(delivery.getBody());
			System.out.println(msg+"给用户发短信");
			//false表示一个一个返回
			channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
		}
	}

}

说明:
两个消费者都能消费消息

4.路由模式
在这里插入图片描述
说明:路由模式是发布订阅模式的升级,通过定义不同的路由key使得程序将消息发送到不同的队列中.

5.主题模式
在这里插入图片描述
说明:
可以通过路由key将消息发送到一类相同的key中 使用通配符实现
符号说明:
#号:表示任意字符(任意个.)
*号:任意单个字符或者词组(单个.)

ActiveMq:

1、点对点模式:
只有一个消费者可以接收到消息
不能重复消费
生产者:
$queneName = "/queue/userReg";
消费者:
$stomp->subscribe('/queue/userReg');

2、发布/订阅模型特点:
多个消费者都可以收到消息
能重复消费
生产者:
$queneName = "/topic/userReg";
消费者:
$stomp->subscribe('/topic/userReg');

几种消息队列的比较:

这篇文章对比的不错可以看下

例如:
面试:公司是怎么处理消息被消费者消费失败的问题的
在这里插入图片描述
引入了死信队列,将失败的消息放到死信队列中,重新消费。
在这里插入图片描述

- Redis

  1. 缓存机制

说明:缓存中的数据其实是数据库数据的备份.缓存机制其实就是有效的降低了用户访问真实物理设备的访问频次.
前提:缓存一般保存到内存中
问题:
1.缓存的内存空间如何维护?
2.如何保证数据一致性?
3.缓存中的数据如何保证不丢失?
4.如何实现缓存高可用

  1. Redis介绍

网址: https://redis.io/
Redis 是一个开源(BSD许可)的,key-value存储的.内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
结果:读的速度是110000次/s,写的速度是81000次/s

  1. Redis分片介绍

说明:如果单台redis出现宕机的现象.会影响整个服务.单台的redis内存受限.
解决方法:实现redis的分片.
优点:
1.可以实现redis的内存动态扩容
2.Redis的保存的数据,分别保存到多台redis中,如果其中有一台redis出现问题,那么其中的数据只损失1/n的数据.

哈希一致性算法

说明:redis分片时采用哈希一致性算法,实现数据动态的绑定.
术语:
1.节点(node) 代表真实的redis服务器
2.key 用户保存/读取关键字
在这里插入图片描述

均衡性

问题:
由于node节点是经过hash一致性算法计算的结果.那么可能会出现计算的节点所分配的内存不均.如果内存不均,会导致某些节点内存负载过高.
特点说明:
要求全部的数据,尽可能的均匀分配到不同的节点中,每个节点中保存的数据尽可能接近1/n
实现步骤:
如果遇到节点负载不均时,会自动的启动虚拟节点,进行数据的平衡
在这里插入图片描述

单调性

说明:如果节点的个数增加,原有的节点的挂载会自动的发生变化.将满足条件的数据自动的挂载到新的节点中.
原则:尽可能保证原有的数据不变
总结:节点增加,数据动态发生迁移

  1. Redis哨兵实现

比较

分片特点:端口不一样
优点:实现内存动态扩容
缺点:
1.如果分片的节点宕机,则数据丢失
2.如果分片的节点宕机,那么整个服务器都不能正常使用.

哨兵特点:两台redis
实现主从挂载

  1. Redis中持久化策略

说明:因为redis中保存的数据都在内存中,当断电/宕机.缓存中的数据都会被清空.如果redis中没有配置持久化策略,安全性不够完善.
策略说明:
1.RDB方式(定期)
该方式是redis默认选择的持久化策略
特点:持久化的效率更高,定期持久化可能会丢失数据
2.AOF方式(/s)
该方式需要通过配置文件手动开启
特点:持久化效率低,每秒持久化/每次操作持久化,保证数据尽可能不丢失
持久化步骤:
1.当用户set操作时,redis中的数据会新增/更新
2.这时根据用户选择的持久化的策略.自动的进行数据持久化操作.以下以RDB模式为例.
3.定期会将redis中全部的数据通过xxx.RDB文件的方式保存.
4.如果redis服务器宕机重启时,首先会加载持久化文件xxx.RDB.恢复内存中的数据.
5.当用户使用redis时,这时redis内存中已经恢复了数据,为用户继续提供服务.

  1. Redis中内存策略

因为redis中的数据全部都写入内存中.当redis中的数据不能再次写入时这时redis服务会有问题.所以应该保证redis服务永远可写.如何实现redis数据内存优化呢??

1.定义redis中最大内存

在这里插入图片描述
在这里插入图片描述

2.LRU算法:

内存管理的一种页面置换算法,对于在内存中但又不用的数据块(内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据。

3.内存优化手段:

volatile-lru -> 将设定超时时间的数据并且其中使用较少的数据进行删除
allkeys-lru -> 将redis中全部key进行LRU筛选,之后进行删除
volatile-random -> 设定了超时间的数据,随机删除
allkeys-random -> 全部的key随机删除
volatile-ttl -> 将已经设定了超时时间的数据,按照存活时间排序,将马上要过期的数据进行删除.
noeviction -> 不做任何操作,将报错信息返回给用户.

4.修改优化策略

在这里插入图片描述

  1. Redis集群搭建

比较:
分片和哨兵说明

优点:
1.分片可以实现内存的动态的扩容
2.使用分片能够将数据分开保存,如果宕机/数据损坏只丢失1/n的数据
3.哨兵可以实现redis的高可用.
缺点:
1.如果分片中的一个节点宕机,那么整个分片将不能正常运行.
2.分片中的数据因为hash一致性的原因.可能会导致数据分布不是特别的平均.
3.如果哨兵宕机,则整个服务陷入瘫痪.

说明:
redis集群实质上将redis分片和redis哨兵的机制进行整合.redis集群中每个节点多可以与其他节点进行通讯.同时集群内部有心跳检测.如果节点发生宕机的现象.由所在集群的全部服务器负责推选.保证服务的正常运行.如果全部的从节点宕机,并且这时主节点宕机那么整个集群才会奔溃.

  1. 面试问题总结

redis中能存储哪些数据类型?
字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets)
redis中的数据持久化策略?
rdb,将用户操作记录在二进制文件中,如果瘫痪或者停电了,将从文件中读取,这是redis的默认持久化策略。
Redis计数器:是能够解决并发请求的,让其串行去执行,

发布了33 篇原创文章 · 获赞 1 · 访问量 1002

猜你喜欢

转载自blog.csdn.net/weixin_44734394/article/details/102692301