分布式-Zookeeper解决分布式Session一致性问题、实现哨兵机制(Master选举)

一、聊一聊Session

1.1、什么是Session

Session是客户端与服务器通讯会话技术,比如浏览器登录、记录整个浏览会话信息

1.2、Session实现原理

客户端向服务器发送请求后,Session创建在服务器端,返回SessionId给客户端浏览器保存在本地,当下次再发送请求时,在请求头中传递sessionid获取对应的从服务器上获取对用的session。

1.3、Session常见问题

(1)Session 保证在那里?——存放在服务器上

(2)关闭浏览器Session会失效吗?——不会消失

1.4、涉及Session的相关代码了解

@SpringBootApplication
@RestController
public class TestSessionController {

	// 创建session 会话
	@RequestMapping("/createSession")
	public String createSession(HttpServletRequest request, String nameValue) {
		HttpSession session = request.getSession();
		System.out.println("存入Session  sessionid:信息" + session.getId() + ",nameValue:" + nameValue);
		session.setAttribute("name", nameValue);
		return "success";
	}

	// 获取session 会话
	@RequestMapping("/getSession")
	public Object getSession(HttpServletRequest request) {
		HttpSession session = request.getSession();
		System.out.println("获取Session sessionid:信息" + session.getId());
		Object value = session.getAttribute("name");
		return value;
	}

	public static void main(String[] args) {
		SpringApplication.run(TestSessionController.class, args);
	}
}

1.5、服务器集群Session会产生哪些问题

会导致Session不一致问题。服务器产生集群后,因为session是存放在服务器上,客户端会使用同一个sessionid在多个不同的服务器上获取不到对应的Session。

 

二、分布式Session一致性问题解决方案

2.1、几种解决方案了解

(1)nginx或者haproxy实现IP绑定:

用Nginx 做的负载均衡可以添加ip_hash这个配置,

用haproxy做的负载均衡可以用 balance source这个配置。

从而使同一个ip的请求发到同一台服务器。

(2)利用数据库同步session

(3)使用Session集群存放Redis

使用spring-session框架,底层实现原理是重写httpsession。

(4)基于令牌(Token)方式实现

因为Session本身就是分布式共享连接,所以靠谱

2.2、Redis实现,废话不说上代码

(1)maven配置

(2).yml配置文件

启动redis /usr/local/redis/bin/redis-server /usr/local/redis/etc/redis.conf

server:
  port: 8080
redis:
 hostname: 192.168.212.151
 port: 6379
 password: 123456

(3)创建SessionConfig

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

//这个类用配置redis服务器的连接
//maxInactiveIntervalInSeconds为SpringSession的过期时间(单位:秒)
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
public class SessionConfig {

	// 冒号后的值为没有配置文件时,制动装载的默认值
	@Value("${redis.hostname:localhost}")
	String HostName;
	@Value("${redis.port:6379}")
	int Port;

	@Bean
	public JedisConnectionFactory connectionFactory() {
		JedisConnectionFactory connection = new JedisConnectionFactory();
		connection.setPort(Port);
		connection.setHostName(HostName);
		return connection;
	}
}

(4)初始化Session

//初始化Session配置
public class SessionInitializer extends AbstractHttpSessionApplicationInitializer{
    public SessionInitializer() {
        super(SessionConfig.class);
    }
}

2.3、基于令牌(Token)方式实现,上代码

(1)TokenService 

@Service
public class TokenService {
	@Autowired
	private RedisService redisService;

	// 新增 返回token
	public String put(Object object) {
		String token = getToken();
		redisService.setString(token, object);
		return token;
	}

	// 获取信息
	public String get(String token) {
		String reuslt = redisService.getString(token);
		return reuslt;
	}

	public String getToken() {
		return UUID.randomUUID().toString();
	}

}

(2)TokenController

@RestController
public class TokenController {
	@Autowired
	private TokenService tokenService;
	@Value("${server.port}")
	private String serverPort;

	@RequestMapping("/put")
	public String put(String nameValue) {
		String token = tokenService.put(nameValue);
		return token + "-" + serverPort;
	}

	@RequestMapping("/get")
	public String get(String token) {
		String value = tokenService.get(token);
		return value + "-" + serverPort;
	}

}

三、Zookeeper实现哨兵机制

3.1、master选举使用场景及结构

现在很多时候我们的服务需要7*24小时工作,假如一台机器挂了,我们希望能有其它机器顶替它继续工作。此类问题现在多采用master-salve模式,也就是常说的主从模式,正常情况下主机提供服务,备机负责监听主机状态,当主机异常时,可以自动切换到备机继续提供服务,这个切换过程中选出下一个主机的过程就是master选举

3.2、ZK实现Master选举(哨兵机制)原理

 多个服务器在启动的时候,会在ZK上创建相同的临时节点,谁如果能够创建成功,谁就为主(因为节点唯一性),如果主服务器宕机之后,会话连接也会失效,其他服务器又开始选举。(谁能创建节点成功谁就为主

3.3、ZK实现Master选举代码实现

(1)Maven依赖信息

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.0.RELEASE</version>
	</parent>
	<dependencies>
		<dependency>
			<groupId>com.101tec</groupId>
			<artifactId>zkclient</artifactId>
			<version>0.10</version>
			<exclusions>
				<exclusion>
					<artifactId>slf4j-api</artifactId>
					<groupId>org.slf4j</groupId>
				</exclusion>
				<exclusion>
					<artifactId>log4j</artifactId>
					<groupId>log4j</groupId>
				</exclusion>
				<exclusion>
					<artifactId>slf4j-log4j12</artifactId>
					<groupId>org.slf4j</groupId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
	</dependencies>

(2)IndexController

@RestController
public class IndexController {
	// 获取服务信息
	@RequestMapping("/getServerInfo")
	public String getServerInfo() {
		return ElectionMaster.isSurvival ? "当前服务器为主节点" : "当前服务器为从节点";
	}
//main启动类 略
}

(3)MyApplicationRunner

//启动完成后实现的方法

  • 1.项目启动的时候会在ZK上创建一个相同的临时节点
  • 2.谁能够创建成功谁就为主服务器
  • 3.使用服务监听节点是否被删除,如果接收到节点被删除,就重新开始选举(重新开始创建节点)
@Component
public class MyApplicationRunner implements ApplicationRunner {

	// 创建zk连接
	ZkClient zkClient = new ZkClient("127.0.0.1:2181");
//1.项目启动的时候会在ZK上创建一个相同的临时节点
	private String path = "/election";
	@Value("${server.port}")
	private String serverPort;
//启动后执行的方法
	public void run(ApplicationArguments args) throws Exception {
		System.out.println("项目启动完成...");
//2.谁能够创建成功谁就为主服务器
		createEphemeral();
		// 创建事件监听
//3.使用服务监听节点是否被删除,如果接收到节点被删除,就重新开始选举
		zkClient.subscribeDataChanges(path, new IZkDataListener() {

			// 节点被删除,返回通知
			public void handleDataDeleted(String dataPath) throws Exception {
				// 主节点已经挂了,重新选举
				System.out.println("主节点已经挂了,重新开始选举");
				createEphemeral();
			}

			public void handleDataChange(String dataPath, Object data) throws Exception {

			}
		});

	}

	private void createEphemeral() {
		try {
			zkClient.createEphemeral(path, serverPort);
            //选举成功
			ElectionMaster.isSurvival = true;
			System.out.println("serverPort:" + serverPort + ",选举成功....");
		} catch (Exception e) {
            //选举失败
			ElectionMaster.isSurvival = false;
		}
	}

}

(4)ElectionMaster

@Component
public class ElectionMaster {

	// 服务器info信息 是否存活
	public static boolean isSurvival;

}
发布了52 篇原创文章 · 获赞 116 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/RuiKe1400360107/article/details/103827559