Que faire si la session distribuée est incohérente ? L'article suivant vous présentera la solution à l'incohérence des sessions distribuées dans Redis. J'espère qu'il vous sera utile !
1. Quel est le rôle de Session ?
Session est une technologie de suivi de session de communication entre le client et le serveur. Le serveur et le client conservent les informations de session de base de l'ensemble de la communication. [Recommandations associées : Tutoriel vidéo Redis]
Lorsque le client accède au serveur pour la première fois, le serveur répondra avec un identifiant de session et le stockera dans un cookie local. Les visites ultérieures stockeront l'identifiant de session dans le cookie. Le sessionId est placé dans l'en-tête de la requête pour accéder au serveur
Si les données correspondantes ne sont pas trouvées via ce sessionId, le serveur créera un nouveau sessionId et répondra au client.
2. Quels sont les problèmes avec la session distribuée ?
Dans une application Web à serveur unique, les informations de session doivent uniquement être stockées sur le serveur. C'est la manière la plus courante avec laquelle nous sommes entrés en contact ces dernières années
Mais avec la popularité des systèmes distribués dans. Ces dernières années, un seul système ne peut plus répondre aux besoins croissants. Avec la demande croissante de millions d'utilisateurs, les serveurs de déploiement de cluster ont été utilisés par de nombreuses entreprises
Lorsque les requêtes à haute concurrence arrivent sur le serveur, elles sont distribuées à un serveur dans le cluster via l'équilibrage de charge, ce qui peut entraîner la distribution de plusieurs requêtes du même utilisateur sur différents serveurs du cluster, les données de session ne seront pas obtenues, le partage de session devient donc un problème.
3. Comment se fait généralement le clustering de services ?
4. La différence entre l'équilibrage de charge nginx et l'équilibrage de charge du ruban
5. Solution de cohérence de session
1. Réplication de session (synchronisation)
Idée : plusieurs serveurs synchronisent les sessions entre eux, de sorte que chaque serveur Toutes les sessions soient incluses dans chacun session
Avantages : Les fonctions prises en charge par le serveur, l'application n'a pas besoin de modifier le code
Inconvénients :
2. Méthode de stockage du client
Idée : le serveur stocke les sessions de tous les utilisateurs et l'utilisation de la mémoire est importante, donc les sessions peuvent être stockées Dans le cookie du navigateur, chaque extrémité n'a besoin de stocker que les données d'un utilisateur
Avantages : Le serveur n'a pas besoin de stocker
Inconvénients :
Idée : afin de garantir une haute disponibilité, le serveur dispose de plusieurs serveurs redondants. La couche proxy inverse peut-elle faire quelque chose pour garantir que la demande du même utilisateur tombe sur le serveur ? à propos d'un serveur ? Option 1 : hachage proxy à quatre couches
Option 2 : agent à sept couches hachage
Le proxy inverse utilise certains attributs commerciaux du protocole http pour le hachage, tels que sid, city_id, user_id, etc., ce qui peut mettre en œuvre la stratégie de hachage de manière plus flexible pour garantir que les requêtes du même utilisateur du navigateur tombent sur le même serveur
Avantages :
Inconvénients :
La session a généralement une période de validité. Les deux défauts peuvent être considérés comme équivalents à un échec partiel de session, ce qui n'est généralement pas un gros problème.
Concernant le hachage à quatre couches ou à sept couches, je recommande personnellement le premier : laissez les logiciels professionnels faire des choses professionnelles, et le proxy inverse est responsable du transfert. Essayez de ne pas introduire d'attributs commerciaux de la couche d'application, sauf si vous y êtes obligé (par exemple). , il y a parfois de nombreuses activités multi-activités dans la salle informatique qui doivent être acheminées vers des serveurs situés dans différentes salles informatiques en fonction des attributs métier).
La différence entre l'équilibrage de charge à quatre et sept couches
4. Stockage centralisé unifié back-end
Avantages :
Inconvénient : un appel réseau est ajouté et le code de l'application doit être modifié
Pour la base de données stockage ou cache, je recommande personnellement ce dernier : fréquence de lecture de session Elle sera très élevée et la pression sur la base de données sera relativement élevée. Si une haute disponibilité de session est nécessaire, le cache peut être rendu hautement disponible, mais dans la plupart des cas, la session peut être perdue et il n'est généralement pas nécessaire d'envisager une haute disponibilité.
Résumé
Méthodes courantes de conception architecturale pour assurer la cohérence des sessions :
6. Cas pratique : SpringSession+redis résout le problème d'incohérence des sessions distribuées
Étape 1 : Ajouter le package de dépendances de SpringSession et redis
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.4.7.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
Étape 2 : Fichier de configuration
# 为某个包目录下 设置日志 logging.level.com.ljw=debug # 设置session的存储方式,采用redis存储 spring.session.store-type=redis # session有效时长为10分钟 server.servlet.session.timeout=PT10M ## Redis 配置 ## Redis数据库索引(默认为0) spring.redis.database=0 ## Redis服务器地址 spring.redis.host=127.0.0.1 ## Redis服务器连接端口 spring.redis.port=6379 ## Redis服务器连接密码(默认为空) spring.redis.password=
Étape 3 : Configurer l'intercepteur
@Configuration public class SessionConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new SecurityInterceptor()) //排除拦截的2个路径 .excludePathPatterns("/user/login") .excludePathPatterns("/user/logout") //拦截所有URL路径 .addPathPatterns("/**"); } }
@Configuration public class SecurityInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { HttpSession session = request.getSession(); //验证当前session是否存在,存在返回true true代表能正常处理业务逻辑 if (session.getAttribute(session.getId()) != null){ log.info("session拦截器,session={},验证通过",session.getId()); return true; } //session不存在,返回false,并提示请重新登录。 response.setCharacterEncoding("UTF-8"); response.setContentType("application/json; charset=utf-8"); response.getWriter().write("请登录!!!!!"); log.info("session拦截器,session={},验证失败",session.getId()); return false; } }
Étape 4 : Contrôleur
@RestController @RequestMapping(value = "/user") public class UserController { Map<String, User> userMap = new HashMap<>(); public UserController() { //初始化2个用户,用于模拟登录 User u1=new User(1,"user1","user1"); userMap.put("user1",u1); User u2=new User(2,"user2","user2"); userMap.put("user2",u2); } @GetMapping(value = "/login") public String login(String username, String password, HttpSession session) { //模拟数据库的查找 User user = this.userMap.get(username); if (user != null) { if (!password.equals(user.getPassword())) { return "用户名或密码错误!!!"; } else { session.setAttribute(session.getId(), user); log.info("登录成功{}",user); } } else { return "用户名或密码错误!!!"; } return "登录成功!!!"; } /** * 通过用户名查找用户 */ @GetMapping(value = "/find/{username}") public User find(@PathVariable String username) { User user=this.userMap.get(username); log.info("通过用户名={},查找出用户{}",username,user); return user; } /** *拿当前用户的session */ @GetMapping(value = "/session") public String session(HttpSession session) { log.info("当前用户的session={}",session.getId()); return session.getId(); } /** * 退出登录 */ @GetMapping(value = "/logout") public String logout(HttpSession session) { log.info("退出登录session={}",session.getId()); session.removeAttribute(session.getId()); return "成功退出!!"; } }
Étape 5 : Classe d'entité
@Data public class User implements Serializable{ private int id; private String username; private String password; public User(int id, String username, String password) { this.id = id; this.username = username; this.password = password; } }
Étape 6 : Test d'accès
Journal En premier:http://127.0.0.1:8080/user/login?username=user1&password=user1
Requêtehttp://127.0.0.1:8080/user/find/user1
7 . Analyse Le principe redis de SpringSession
Étape 1 : Analyser la structure des données redis de SpringSession
127.0.0.1:6379> keys * 1) "spring:session:sessions:9889ccfd-f4c9-41e5-b9ab-a77649a7bb6a" 2) "spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b" 3) "spring:session:expirations:1635413520000" 4) "spring:session:sessions:expires:9889ccfd-f4c9-41e5-b9ab-a77649a7bb6a" 5) "spring:session:expirations:1635412980000" 6) "spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"
Le point commun : les trois clés commencent toutes par spring:session:, qui représente les données redis de SpringSession. .
Type de requête
127.0.0.1:6379> type spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b hash
127.0.0.1:6379> hgetall spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b // session的创建时间 1) "creationTime" 2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01|\xc5\xdb\xecu" // sesson的属性,存储了user对象 3) "sessionAttr:d3434f61-4d0a-4687-9070-610bd7790f3b" 4) "\xac\xed\x00\x05sr\x00\x1ecom.ljw.redis.controller.User\x16\"_m\x1b\xa0W\x7f\x02\x00\x03I\x00\x02idL\x00\bpasswordt\x00\x12Ljava/lang/String;L\x00\busernameq\x00~\x00\x01xp\x00\x00\x00\x01t\x00\x05user1q\x00~\x00\x03" //最后的访问时间 5) "lastAccessedTime" 6) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01|\xc5\xe1\xc7\xed" //失效时间 100分钟 7) "maxInactiveInterval" 8) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x17p"
Étape 2 : Analyser la stratégie d'expiration redis de SpringSession
Pour les données expirées, il existe généralement trois stratégies de suppression :
Suppression programmée, c'est-à-dire lors de la définition du délai d'expiration de la clé, créez une minuterie qui supprime la clé immédiatement lorsque son heure d'expiration arrive.
Suppression paresseuse, c'est-à-dire lors de l'accès à une clé, déterminez si la clé a expiré, supprimez-la si elle expire, sinon renvoyez la valeur de la clé.
定期删除,即每隔一段时间,程序就对数据库进行一次检查,删除里面的过期键。至于要删除多少过期键,以及要检查多少个数据库,则由算法决定。
redis删除过期数据采用的是懒性删除+定期删除组合策略,也就是数据过期了并不会及时被删除。
但由于redis是单线程,并且redis对删除过期的key优先级很低;如果有大量的过期key,就会出现key已经过期但是未删除。
为了实现 session 过期的及时性,spring session 采用了定时删除+惰性删除的策略。
定时删除
127.0.0.1:6379> type spring:session:expirations:1635413520000 set 127.0.0.1:6379> smembers spring:session:expirations:1635413520000 1) "\xac\xed\x00\x05t\x00,expires:d3434f61-4d0a-4687-9070-610bd7790f3b"
2) "spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b" 3) "spring:session:expirations:1635413520000" 6) "spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"
惰性删除
127.0.0.1:6379> type spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b string 127.0.0.1:6379> get spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b "" 127.0.0.1:6379> ttl spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b (integer) 3143 127.0.0.1:6379>
2) "spring:session:sessions:expires:d3434f61-4d0a-4687-9070-610bd7790f3b" 3) "spring:session:expirations:1635413520000" 6) "spring:session:sessions:d3434f61-4d0a-4687-9070-610bd7790f3b"
更多编程相关知识,请访问:编程视频!!
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!