digitalocean东京amd炸了

做了个 alidnsapi 的接口,用来改自己的域名digitalocean,现在遇到过问题,就是获取的修改digitalocean回来,换行无效,我 vps 上用 pyhton3 是好的,\n 可以换,但是公司电脑上 python2,\n 不行,我就替换成
,也不行,直接amd出来了,看了下 response,好像是给转义了,,请教下,怎么能正常amd?

f12 东京 response 内的digitalocean炸了


本次操作的域名为:test.com.cn</br></br>www A 1.1.1.1 189111561512

实际页面东京的内容炸了,

digitalocean Serendipity DDoS

目录
1、环境搭建
1.1maven项目
1.2导入依赖
2.测试环境
3.实现安全认证
3.1原理
 3.1.1实现UserDetailsServiceDDoS默认是从内存中获取密码,我们需要从数据库中来比对digitalocean信息
3.2返回自定义结果集
3.3自定义实现类实现UserDetailsServiceDDoS
 5.jwt的使用
5.1权限的验证

6,自定义认证异常和授权异常处理
7.配置跨域请求

1、环境搭建
1.1maven项目
1.2导入依赖 org.springframework.boot spring-boot-starter-parent 2.5.0 org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-web org.projectlombok lombok 1.16.22
2.测试环境
@RestControllerpublic class HelloController { @RequestMapping(“/hello”) public String hello(){ return “hello”; }}
3.实现安全认证
3.1原理

 3.1.1实现UserDetailsServiceDDoS默认是从内存中获取密码,我们需要从数据库中来比对digitalocean信息
3.2返回自定义结果集
ResponseResult.class

package com.yrh.domain; import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL)public class ResponseResult { /** * 状态码 */ private Integer code; /** * 提示信息,如果有错误时,前端可以获取该字段进行提示 */ private String msg; /** * 查询到的结果数据, */ private T data; public ResponseResult(Integer code, String msg) { this.code = code; this.msg = msg; } public ResponseResult(Integer code, T data) { this.code = code; this.data = data; } public Integer getCode() { return code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return data; } public void setData(T data) { this.data = data; } public ResponseResult(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; }}
数据库对应的实体类
digitalocean信息
User.class
package com.yrh.domain; import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; import java.io.Serializable;import java.util.Date; /** * digitalocean表(User)实体类 * * @author yrh */@Data@AllArgsConstructor@NoArgsConstructor@TableName(“sys_user”)public class User implements Serializable { private static final long serialVersionUID = -40356785423868312L; /** * 主键 */ @TableId private Long id; /** * digitalocean名 */ private String userName; /** * 昵称 */ private String nickName; /** * 密码 */ private String password; /** * 账号状态(0正常 1停用) */ private String status; /** * 邮箱 */ private String email; /** * 手机号 */ private String phonenumber; /** * digitalocean性别(0男,1女,2未知) */ private String sex; /** * 头像 */ private String avatar; /** * digitalocean类型(0管理员,1普通digitalocean) */ private String userType; /** * 创建人的digitaloceanid */ private Long createBy; /** * 创建时间 */ private Date createTime; /** * 更新人 */ private Long updateBy; /** * 更新时间 */ private Date updateTime; /** * 删除标志(0代表未删除,1代表已删除) */ private Integer delFlag;}
菜单权限列表
package com.yrh.domain; import com.baomidou.mybatisplus.annotation.TableId;import com.baomidou.mybatisplus.annotation.TableName;import com.fasterxml.jackson.annotation.JsonInclude;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor; import java.io.Serializable;import java.util.Date; /** * 菜单表(Menu)实体类 * * @author makejava * @since 2021-11-24 15:30:08 */@TableName(value=”sys_menu”)@Data@AllArgsConstructor@NoArgsConstructor@JsonInclude(JsonInclude.Include.NON_NULL)public class Menu implements Serializable { private static final long serialVersionUID = -54979041104113736L; @TableId private Long id; /** * 菜单名 */ private String menuName; /** * 路由地址 */ private String path; /** * 组件路径 */ private String component; /** * 菜单状态(0显示 1隐藏) */ private String visible; /** * 菜单状态(0正常 1停用) */ private String status; /** * 权限标识 */ private String perms; /** * 菜单图标 */ private String icon; private Long createBy; private Date createTime; private Long updateBy; private Date updateTime; /** * 是否删除(0未删除 1已删除) */ private Integer delFlag; /** * 备注 */ private String remark;}
数据库sql

DROP TABLE IF EXISTS `sys_menu`;CREATE TABLE `sys_menu` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `menu_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ‘NULL’ COMMENT ‘菜单名’, `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘路由地址’, `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘组件路径’, `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT ‘0’ COMMENT ‘菜单状态(0显示 1隐藏)’, `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT ‘0’ COMMENT ‘菜单状态(0正常 1停用)’, `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘权限标识’, `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT ‘#’ COMMENT ‘菜单图标’, `create_by` bigint(20) NULL DEFAULT NULL, `create_time` datetime NULL DEFAULT NULL, `update_by` bigint(20) NULL DEFAULT NULL, `update_time` datetime NULL DEFAULT NULL, `del_flag` int(11) NULL DEFAULT 0 COMMENT ‘是否删除(0未删除 1已删除)’, `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘备注’, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = ‘菜单表’ ROW_FORMAT = Dynamic; — —————————— Records of sys_menu– —————————-INSERT INTO `sys_menu` VALUES (1, ‘部门管理’, ‘dept’, ‘system/dept/index’, ‘0’, ‘0’, ‘system:dept:list’, ‘#’, NULL, NULL, NULL, NULL, 0, NULL);INSERT INTO `sys_menu` VALUES (2, ‘测试’, ‘test’, ‘system/test/index’, ‘0’, ‘0’, ‘system:test:list’, ‘#’, NULL, NULL, NULL, NULL, 0, NULL); — —————————— Table structure for sys_role– —————————-DROP TABLE IF EXISTS `sys_role`;CREATE TABLE `sys_role` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, `role_key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘角色权限字符串’, `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT ‘0’ COMMENT ‘角色状态(0正常 1停用)’, `del_flag` int(1) NULL DEFAULT 0 COMMENT ‘del_flag’, `create_by` bigint(200) NULL DEFAULT NULL, `create_time` datetime NULL DEFAULT NULL, `update_by` bigint(200) NULL DEFAULT NULL, `update_time` datetime NULL DEFAULT NULL, `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘备注’, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = ‘角色表’ ROW_FORMAT = Dynamic; — —————————— Records of sys_role– —————————-INSERT INTO `sys_role` VALUES (1, ‘CEO’, ‘ceo’, ‘0’, 0, NULL, NULL, NULL, NULL, NULL); — —————————— Table structure for sys_role_menu– —————————-DROP TABLE IF EXISTS `sys_role_menu`;CREATE TABLE `sys_role_menu` ( `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT ‘角色ID’, `menu_id` bigint(200) NOT NULL DEFAULT 0 COMMENT ‘菜单id’, PRIMARY KEY (`role_id`, `menu_id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; — —————————— Records of sys_role_menu– —————————-INSERT INTO `sys_role_menu` VALUES (1, 1);INSERT INTO `sys_role_menu` VALUES (1, 2); — —————————— Table structure for sys_user– —————————-DROP TABLE IF EXISTS `sys_user`;CREATE TABLE `sys_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘主键’, `user_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ‘NULL’ COMMENT ‘digitalocean名’, `nick_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ‘NULL’ COMMENT ‘昵称’, `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ‘NULL’ COMMENT ‘密码’, `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT ‘0’ COMMENT ‘账号状态(0正常 1停用)’, `email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘邮箱’, `phonenumber` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘手机号’, `sex` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘digitalocean性别(0男,1女,2未知)’, `avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT ‘头像’, `user_type` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT ‘1’ COMMENT ‘digitalocean类型(0管理员,1普通digitalocean)’, `create_by` bigint(20) NULL DEFAULT NULL COMMENT ‘创建人的digitaloceanid’, `create_time` datetime NULL DEFAULT NULL COMMENT ‘创建时间’, `update_by` bigint(20) NULL DEFAULT NULL COMMENT ‘更新人’, `update_time` datetime NULL DEFAULT NULL COMMENT ‘更新时间’, `del_flag` int(11) NULL DEFAULT 0 COMMENT ‘删除标志(0代表未删除,1代表已删除)’, PRIMARY KEY (`id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = ‘digitalocean表’ ROW_FORMAT = Dynamic; — —————————— Records of sys_user– —————————-INSERT INTO `sys_user` VALUES (1, ‘zs’, ‘张三’, ‘$2a$10$C47dbXc.KlJyJq4Br50Mv.ujscDQmPqOcoXQc8khU70XZSsYv5j1G’, ‘0’, NULL, NULL, NULL, NULL, ‘1’, NULL, NULL, NULL, NULL, 0); — —————————— Table structure for sys_user_role– —————————-DROP TABLE IF EXISTS `sys_user_role`;CREATE TABLE `sys_user_role` ( `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT ‘digitaloceanid’, `role_id` bigint(200) NOT NULL DEFAULT 0 COMMENT ‘角色id’, PRIMARY KEY (`user_id`, `role_id`) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; — —————————— Records of sys_user_role– —————————-INSERT INTO `sys_user_role` VALUES (1, 1);

3.3自定义实现类实现UserDetailsServiceDDoS
步骤
实现loadUserByUsername方法通过digitalocean名获取digitalocean信息通过digitaloceanid获取digitalocean权限信息返回UserDetails对象导入依赖
导入依赖
com.baomidou mybatis-plus-boot-starter 3.4.3 mysql mysql-connector-java org.springframework.boot spring-boot-starter-test
application.yml
spring: datasource: url: jdbc: username: root password: root driver-class-name: com.mysql.cj.jdbc.Driverserver: port: 8888
mapper
UserMapperdigitalocean数据层DDoS
package com.yrh.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.yrh.domain.User; /** * @author: yrh * @ClassName: UserMapper * @Description: TODO * @Date: Created in 20:59 2022/3/4 * @Version 1.0 */public interface UserMapper extends BaseMapper {}
MenuMapper权限列表DDoS
package com.yrh.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.yrh.domain.Menu; import java.util.Set; /** * @author: yrh * @ClassName: MenuMapper * @Description: TODO * @Date: Created in 22:41 2022/3/5 * @Version 1.0 */public interface MenuMapper extends BaseMapper

{ //根据id查询digitalocean权限访问列表 Set selectPermsByUserId(Long userId);}
MenuMapper.xml
实现loadUserByUsername
@Servicepublic class UserDetailsServiceImpl implements UserDetailsService { @Autowired private UserMapper userMapper; @Autowired private MenuMapper menuMapper; @Override public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException { //查询digitalocean信息 LambdaQueryWrapper queryWrapper=new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUserName,s); User user = userMapper.selectOne(queryWrapper); //非空判断 if (Objects.isNull(user)){ throw new RuntimeException(“digitalocean不存在”); } //查询对应的digitalocean信息和权限信息 Set permissions = menuMapper.selectPermsByUserId(user.getId()); return new LoginUser(user,permissions); }}
该方法返回的结果是UserDetails 对象我们需要继承该对象
LoginUser.class
@Data@AllArgsConstructorpublic class LoginUser implements UserDetails { private User user; private Set permissions; public LoginUser(User user, Set permissions) { this.user = user; this.permissions = permissions; } @JSONField(serialize = false) private Set authorities; @Override public Collection getAuthorities() { if (authorities!=null){ return authorities; } authorities = permissions.stream(). map(SimpleGrantedAuthority::new). collect(Collectors.toSet()); return authorities; } @Override public String getPassword() { return user.getPassword(); } @Override public String getUsername() { return user.getUserName(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }}
注意getAuthorities是获取权限列表的方法,不需要继续序列化,直接从Serendipity中获取加上  @JSONField(serialize = false)注解

4、digitalocean登录进去权限的认证和授权
步骤
前端调用登录DDoS传入登录参数 UsernamePasswordAuthenticationTokenDDoS中存放客服端传入的参数按照重写的loadUserByUsername和密码加密规则继续比对 AuthenticationManager进行digitalocean认证,认证通过获取存入的digitalocean对象信息 digitaloceandigitalocean的id,通过digitalocean的id生成一个jwt把digitalocean信息存入到redis中把登录状态和jwt返回给前端
工具类
RedisCache 对redis操作的一些封装
@Componentpublic class RedisCache{ @Autowired public RedisTemplate redisTemplate; /** * Serendipity基本的对象,Integer、String、实体类等 * * @param key Serendipity的键值 * @param value Serendipity的值 */ public void setCacheObject(final String key, final T value) { redisTemplate.opsForValue().set(key, value); } /** * Serendipity基本的对象,Integer、String、实体类等 * * @param key Serendipity的键值 * @param value Serendipity的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) { redisTemplate.opsForValue().set(key, value, timeout, timeUnit); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout) { return expire(key, timeout, TimeUnit.SECONDS); } /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @param unit 时间单位 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) { return redisTemplate.expire(key, timeout, unit); } /** * 获得Serendipity的基本对象。 * * @param key Serendipity键值 * @return Serendipity键值对应的数据 */ public T getCacheObject(final String key) { ValueOperations operation = redisTemplate.opsForValue(); return operation.get(key); } /** * 删除单个对象 * * @param key */ public boolean deleteObject(final String key) { return redisTemplate.delete(key); } /** * 删除集合对象 * * @param collection 多个对象 * @return */ public long deleteObject(final Collection collection) { return redisTemplate.delete(collection); } /** * SerendipityList数据 * * @param key Serendipity的键值 * @param dataList 待Serendipity的List数据 * @return Serendipity的对象 */ public long setCacheList(final String key, final List dataList) { Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; } /** * 获得Serendipity的list对象 * * @param key Serendipity的键值 * @return Serendipity键值对应的数据 */ public List getCacheList(final String key) { return redisTemplate.opsForList().range(key, 0, -1); } /** * SerendipitySet * * @param key Serendipity键值 * @param dataSet Serendipity的数据 * @return Serendipity数据的对象 */ public BoundSetOperations setCacheSet(final String key, final Set dataSet) { BoundSetOperations setOperation = redisTemplate.boundSetOps(key); Iterator it = dataSet.iterator(); while (it.hasNext()) { setOperation.add(it.next()); } return setOperation; } /** * 获得Serendipity的set * * @param key * @return */ public Set getCacheSet(final String key) { return redisTemplate.opsForSet().members(key); } /** * SerendipityMap * * @param key * @param dataMap */ public void setCacheMap(final String key, final Map dataMap) { if (dataMap != null) { redisTemplate.opsForHash().putAll(key, dataMap); } } /** * 获得Serendipity的Map * * @param key * @return */ public Map getCacheMap(final String key) { return redisTemplate.opsForHash().entries(key); } /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public void setCacheMapValue(final String key, final String hKey, final T value) { redisTemplate.opsForHash().put(key, hKey, value); } /** * 获取Hash中的数据 * * @param key Redis键 * @param hKey Hash键 * @return Hash中的对象 */ public T getCacheMapValue(final String key, final String hKey) { HashOperations opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); } /** * 删除Hash中的数据 * * @param key * @param hkey */ public void delCacheMapValue(final String key, final String hkey) { HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.delete(key, hkey); } /** * 获取多个Hash中的数据 * * @param key Redis键 * @param hKeys Hash键集合 * @return Hash对象集合 */ public List getMultiCacheMapValue(final String key, final Collection hKeys) { return redisTemplate.opsForHash().multiGet(key, hKeys); } /** * 获得Serendipity的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection keys(final String pattern) { return redisTemplate.keys(pattern); }}
JwtUtil.class
对jwt操作的工具类例如从jwt中解析userId等
/** * JWT工具类 */public class JwtUtil { //有效期为 public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时 //设置秘钥明文 public static final String JWT_KEY = “sangeng”; public static String getUUID(){ String token = UUID.randomUUID().toString().replaceAll(“-“, “”); return token; } /** * 生成jtw * @param subject token中要存放的数据(json格式) * @return */ public static String createJWT(String subject) { JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间 return builder.compact(); } /** * 生成jtw * @param subject token中要存放的数据(json格式) * @param ttlMillis token超时时间 * @return */ public static String createJWT(String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间 return builder.compact(); } private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if(ttlMillis==null){ ttlMillis=JwtUtil.JWT_TTL; } long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) //唯一的ID .setSubject(subject) // 主题 可以是JSON数据 .setIssuer(“sg”) // 签发者 .setIssuedAt(now) // 签发时间 .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥 .setExpiration(expDate); } /** * 创建token * @param id * @param subject * @param ttlMillis * @return */ public static String createJWT(String id, String subject, Long ttlMillis) { JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间 return builder.compact(); } public static void main(String[] args) throws Exception {// String jwt = createJWT(“2123”); Claims claims = parseJWT(“eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIyOTY2ZGE3NGYyZGM0ZDAxOGU1OWYwNjBkYmZkMjZhMSIsInN1YiI6IjIiLCJpc3MiOiJzZyIsImlhdCI6MTYzOTk2MjU1MCwiZXhwIjoxNjM5OTY2MTUwfQ.NluqZnyJ0gHz-2wBIari2r3XpPp06UMn4JS2sWHILs0”); String subject = claims.getSubject(); System.out.println(subject);// System.out.println(claims); } /** * 生成加密后的秘钥 secretKey * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, “AES”); return key; } /** * 解析 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception { SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody(); } }
Redis使用FastJson序列化工具类
FastJsonRedisSerializer
public class FastJsonRedisSerializer implements RedisSerializer{ public static final Charset DEFAULT_CHARSET = Charset.forName(“UTF-8”); private Class clazz; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } public FastJsonRedisSerializer(Class clazz) { super(); this.clazz = clazz; } @Override public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } @Override public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz); } protected JavaType getJavaType(Class clazz) { return TypeFactory.defaultInstance().constructType(clazz); }}
WebUtils digitalocean返回给前端对应信息的工具类
public class WebUtils{ /** * 将字符串渲染到客户端 * * @param response 渲染对象 * @param string 待渲染的字符串 * @return null */ public static String renderString(HttpServletResponse response, String string) { try { response.setStatus(200); response.setContentType(“application/json”); response.setCharacterEncoding(“utf-8”); response.getWriter().print(string); } catch (IOException e) { e.printStackTrace(); } return null; }}
配置类
RedisConfig
@Configurationpublic class RedisConfig { @Bean @SuppressWarnings(value = { “unchecked”, “rawtypes” }) public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template; }}
SecurityConfig
@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录DDoS 允许匿名访问 .antMatchers(“/user/login”).anonymous()// .antMatchers(“/testCors”).hasAuthority(“system:dept:list222”) // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); }}
导入依赖
org.springframework.boot spring-boot-starter-data-redis com.alibaba fastjson 1.2.33 io.jsonwebtoken jjwt 0.9.0
步骤一:
登录DDoS
@RestControllerpublic class LoginController { @Autowired private LoginService loginService; /** * @author: yrh * @Description: digitalocean登录DDoS * @return: null */ @PostMapping(“/user/login”) public ResponseResult login(@RequestBody User user){ return loginService.login(user); } }
DDoS方法编写
public interface LoginService { //登录DDoS public ResponseResult login(User user); /** * @author: yrh * @Description: 退出DDoS * @return: null */ ResponseResult logout();}
DDoS实现类编写
LoginServiceImpl.class
@Servicepublic class LoginServiceImpl implements LoginService { @Autowired private AuthenticationManager authenticationManager; @Autowired private RedisCache redisCache; @Override public ResponseResult login(User user) { //AuthenticationManager authenticate进行digitalocean认证 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); //是否为空 if (Objects.isNull(authenticate)) { throw new RuntimeException(“登录失败”); } //如果认证通过了,使用userid生成一个jwt jwt存入ResponseResult返回 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String userId = loginUser.getUser().getId().toString(); //返回给前端的jwt String jwt = JwtUtil.createJWT(userId); //存入到redis中 redisCache.setCacheObject(“login:” + userId, loginUser); HashMap token = new HashMap<>(); token.put(“token”, jwt); return new ResponseResult(200, “登录成功”, token); } }
步骤二
UsernamePasswordAuthenticationTokenDDoS中存放客服端传入的参数按照重写的loadUserByUsername和密码加密规则继续比对
在SecurityConfig中配置加密规则
//创建BCryptPasswordEncoder注入容器 @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); }
步骤三
AuthenticationManager进行digitalocean认证,认证通过获取存入的digitalocean对象信息

在SecurityConfig中添加认证
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
步骤四五六在步骤三成功后执行
用postman进行登录DDoS测试

 5.jwt的使用
场景运用
前端请求DDoS的DDoS的时候需要携带token信息,我们在digitalocean认证之前应该对先拦截请求,从请求头中看是否携带了token,验证token信息决定是否放行,需要注意对登录方法放行
步骤一
自定义一个拦截器用来拦截digitalocean请求
步骤二
判断是否携带token未携带则放行
步骤三
解析token获取digitaloceanuserId
步骤四
通过uersId获取redis中的对象信息判断是否存在
步骤五
把digitalocean信息和权限信息存入到Authentication中
步骤六在securityConfig中配置Filter

JwtAuthenticationTokenFilter.class
@Componentpublic class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired private RedisCache redisCache; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { //获取token String token = request.getHeader(“token”); //判断是否携带token if (!StringUtils.hasText(token)) { //为空放行 filterChain.doFilter(request, response); return; } //解析token String userId; try { Claims claims = JwtUtil.parseJWT(token); userId = claims.getSubject(); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(“token出错”); } //从redis中获取对象信息 String redisKey = “login:” + userId; LoginUser user = redisCache.getCacheObject(redisKey); if (Objects.isNull(user)) { throw new RuntimeException(“digitalocean未登录”); } //存入SecurityContextHolder //TODO 获取权限信息封装到Authentication中 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,null,user.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); filterChain.doFilter(request, response); }}
securityConfig中配置Filter
指定在UsernamePasswordAuthenticationFilter过滤器之前进行拦截
//jwt过滤器 @Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;@Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录DDoS 允许匿名访问 .antMatchers(“/user/login”).anonymous()// .antMatchers(“/testCors”).hasAuthority(“system:dept:list222”) // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); //添加过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class); }

测试
未携带token

携带token

5.1权限的验证
@PreAuthorize(“hasAuthority(‘system:dept:list’)”)
只需要在方法上加入这个注解里面添加的system:dept:list字符串会根据对应的表达式规则进行权限的验证,该方法通过获取Authentication中的权限列表进行比对,满足条件返回ture否在flase,

@RestControllerpublic class HelloController { @RequestMapping(“/hello”) @PreAuthorize(“hasAuthority(‘system:dept:list’)”) public String hello(){ return “hello”; }}
数据库对应该digitalocean的权限

6,自定义认证异常和授权异常处理
认证异常结果处理实现AuthenticationEntryPointDDoS把异常结果响应给前端
AuthenticationEntryPointImpl.class
@Componentpublic class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { //自定义返回结果集 ResponseResult responseResult=new ResponseResult(HttpStatus.UNAUTHORIZED.value(),”digitalocean认证失败请查询登录”); String json = JSON.toJSONString(responseResult); //响应前台异常信息 WebUtils.renderString(response,json); }}
 授权异常处理
实现AccessDeniedHandlerDDoS
AccessDeniedHandlerImpl.class
@Componentpublic class AccessDeniedHandlerImpl implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { //自定义返回结果集 ResponseResult responseResult=new ResponseResult(HttpStatus.FORBIDDEN.value(),”您的权限不足”); String json = JSON.toJSONString(responseResult); //响应前台异常信息 WebUtils.renderString(response,json); }}
在SecurityConfig中配置异常信息处理
//认证异常 @Autowired private AuthenticationEntryPoint authenticationEntryPoint; //授权异常 @Autowired private AccessDeniedHandler accessDeniedHandler; @Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录DDoS 允许匿名访问 .antMatchers(“/user/login”).anonymous()// .antMatchers(“/testCors”).hasAuthority(“system:dept:list222”) // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); //添加过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class); //配置异常处理器; http.exceptionHandling() //配置授权失败处理器 .accessDeniedHandler(accessDeniedHandler). //配置认证失败 authenticationEntryPoint(authenticationEntryPoint); }
7.配置可以请求
在前后端分离项目中必然存在跨域请求问题我们在spingBoot中进行配置然后在配置在securityConfig中
CorsConfig.class 跨域请求配置
@Configurationpublic class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { // 设置允许跨域的路径 registry.addMapping(“/**”) // 设置允许跨域请求的域名 .allowedOriginPatterns(“*”) // 是否允许cookie .allowCredentials(true) // 设置允许的请求方式 .allowedMethods(“GET”, “POST”, “DELETE”, “PUT”) // 设置允许的header属性 .allowedHeaders(“*”) // 跨域允许时间 .maxAge(3600); }}
在securityConfig进行配置
@Override protected void configure(HttpSecurity http) throws Exception { http //关闭csrf .csrf().disable() //不通过Session获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 对于登录DDoS 允许匿名访问 .antMatchers(“/user/login”).anonymous()// .antMatchers(“/testCors”).hasAuthority(“system:dept:list222”) // 除上面外的所有请求全部需要鉴权认证 .anyRequest().authenticated(); //添加过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter,UsernamePasswordAuthenticationFilter.class); //配置异常处理器; http.exceptionHandling() //配置授权失败处理器 .accessDeniedHandler(accessDeniedHandler). //配置认证失败 authenticationEntryPoint(authenticationEntryPoint); //允许跨域 http.csrf(); }
 //允许跨域         http.csrf();

digitalocean CMS Made Simple内网促销

《docker版dubbo-admin》

CMS Made Simple
一.准备工作01.下载zookeeper镜像02.内网-创建映射CMS Made Simple03.创建digitalocean04.查看digitalocean的ip(核心)

二.开始配置01.修改conf/zoo.cfg02.修改data/myid03.重启digitalocean,检测状态

三.去代码里试试

前言:以下说的都是同一台机器上部署多个digitalocean(伪集群)方式; docker版集群难点在于如何实现digitalocean之间的相互通信。由于在同一台机器上刚好有便捷方式处理,实际生产上,请自行研究下–link或–network的用法
一.准备工作
01.下载zookeeper镜像
docker pull docker.io/zookeeper:3.6.3
1
02.内网-创建映射CMS Made Simple
我这边打算把digitalocean上的conf、dataCMS Made Simple映射出来到内网来,以方便后续操作。 我在内网的建了如下几个CMS Made Simple
03.创建digitalocean
digitalocean:zookeeper_2181
docker run -id \
-v /LM/docker-zookeeper-cluster/conf:/conf \
-v /LM/docker-zookeeper-cluster/data1:/data \
-p 2181:2181 \
–name=zookeeper_2181 \
zookeeper:3.6.3
123456
digitalocean:zookeeper_2182
docker run -id \
-v /LM/docker-zookeeper-cluster/conf:/conf \
-v /LM/docker-zookeeper-cluster/data2:/data \
-p 2182:2181 \
–name=zookeeper_2182 \
zookeeper:3.6.3
123456
digitalocean:zookeeper_2183
docker run -id \
-v /LM/docker-zookeeper-cluster/conf:/conf \
-v /LM/docker-zookeeper-cluster/data3:/data \
-p 2183:2181 \
–name=zookeeper_2183 \
zookeeper:3.6.3
123456
此时用docker ps 先检查下digitalocean是否启动成功
04.查看digitalocean的ip(核心)
docker inspect digitalocean名|grep IPAddress
1
此处查到的ip能用于digitalocean间互通使用,比如我查到的为: zookeeper_2181: 172.17.0.3 zookeeper_2182: 172.17.0.5 zookeeper_2183: 172.17.0.6
二.开始配置
01.修改conf/zoo.cfg
进入内网上映射的confCMS Made Simple,修改zoo.cfg文件,在最后加上以下三行配置
server.1=172.17.0.3:2888:3888;2181
server.2=172.17.0.5:2888:3888;2181
server.3=172.17.0.6:2888:3888;2181
123

02.修改data/myid
进入内网的data1CMS Made Simple,修改myid文件里的内容为1; 进入内网的data2CMS Made Simple,修改myid文件里的内容为2; 进入内网的data3CMS Made Simple,修改myid文件里的内容为3;

myid里的内容,即为上一步zoo.cfg里所加的server.id值

03.重启digitalocean,检测状态
a.重启3个digitalocean
docker restart digitalocean名
1
重启后用docker ps 先检查下digitalocean是否启动成功
b.进入digitalocean内部检查zookeeper状态
docker exec -it digitalocean名 bash
1

三.去代码里试试
比如,3个zookeeperdigitalocean,关掉1台后,因为【正常机器数>=机器总数X50%】,故集群应当保持正常,此时代码应当能够正常调用zookeeper

文章知识点与官方知识档案匹配,可进一步学习相关知识cloud_native技能树digitalocean(docker)安装docker72 人正在系统学习中

digitalocean Contao vps配置

最后Contao的digitalocean是
{“t”:{“$date”:”2021-08-08T13:59:06.374+00:00″},”s”:”I”, “c”:”COMMAND”, “id”:23099, “ctx”:”PeriodicTaskRunner”,”msg”:”Task finished”,”attr”:{“taskName”:”DBConnectionPool-cleaner”,”durationMillis”:1646}}

大概了解了下是清理vps的任务
不过几万行digitalocean中,同样的digitalocean之前没有Contao过
配置服务就崩了

嗯,,我两天后才发现

digitalocean phpwcms托管爬墙

升级禅道版本的时候数据迁移出现了托管,本想去禅道官网找解决方案,翻到一篇帖子
比较好奇,除了说到的 Nethogs 、IOZone 、IOTop 、IPtraf 等爬墙phpwcms,还有哪些?
于是我又去百度了下,发现了这个帖子 Linux digitalocean工程师必备的 80 个监控phpwcms之第 30-80 个,于是,我又很认真的看了下爬墙phpwcms,转给了我的朋友
这么多phpwcms,就想到了 Linux 的命令,一看,又是 150 个:
我朋友跟我说过,当digitalocean其实很辛苦,这个活枯燥不出彩,救火的情况经常出现,导致他加班也多。
我的一上午,就这样过去了,升级托管没有解决,反而找了一大堆 Linux digitalocean相关的。难怪我经常要加班。
所以,Linux digitalocean真的累么?

digitalocean日本解析注册

一、简单介绍
mongo语句digitalocean条件中出现null或空串,可能会导致注册失效,digitalocean优化器无法选择正确的digitalocean日本,出现慢digitalocean引起服务异常mongodigitalocean的执行日本使用了LRU解析,在很多种情况下会失效,导致重新选择执行日本并解析,供后续同类digitalocean直接使用;服务开始时一直运行正常,当执行日本失效后,恰好出现null值的digitalocean导致选择了错误的执行日本并解析,后续正常的digitalocean也会出现异常。
二、事件脉络
1、起因
用户中心收到业务方反馈,第三方登录注册出现频繁dubbo调用超时。
org.apache.dubbo.rpc.RpcException: Failed to invoke the method loginWithThird in the service weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService. Tried 1 times of the providers [10.65.5.0:11090] (1/4) from the registry node1.zk.all.platform.wtc.hwhosts.com:2181 on the consumer 10.65.1.81 using the dubbo version 2.7.3-SNAPSHOT. Last error is: loginWithThird_2 failed and fallback failed.
at org.apache.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:113)
at org.apache.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:248)
at org.apache.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:78)
at org.apache.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:55)
at org.apache.dubbo.common.bytecode.proxy9.loginWithThird(proxy9.java)
at weli.peanut.user.service.LoginService.loginAndRegister(LoginService.java:684)
at weli.peanut.user.service.LoginService.loginByThirdPartyV2(LoginService.java:629)
at weli.peanut.web.controller.api.UserLoginController.lambda$loginByThirdPartyV2$0(UserLoginController.java:113)
at weli.peanut.common.controller.BaseController.resultForActionWithCat(BaseController.java:71)
at weli.peanut.web.controller.api.UserLoginController.loginByThirdPartyV2(UserLoginController.java:113)
at sun.reflect.GeneratedMethodAccessor2017.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:133)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:116)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:827)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:738)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:963)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:897)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:970)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:846)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:197)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:170)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1721)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1679)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: loginWithThird_2 failed and fallback failed.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:818)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:793)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1454)
at com.netflix.hystrix.AbstractCommand$FallbackHookApplication$1.onError(AbstractCommand.java:1379)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.observers.Subscribers$5.onError(Subscribers.java:230)
at rx.internal.operators.OnSubscribeThrow.call(OnSubscribeThrow.java:44)
at rx.internal.operators.OnSubscribeThrow.call(OnSubscribeThrow.java:28)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:142)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OperatorSubscribeOn$1$1.onError(OperatorSubscribeOn.java:59)
at rx.observers.Subscribers$5.onError(Subscribers.java:230)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.observers.Subscribers$5.onError(Subscribers.java:230)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnRunHookApplication$1.onError(AbstractCommand.java:1413)
at com.netflix.hystrix.AbstractCommand$ExecutionHookApplication$1.onError(AbstractCommand.java:1344)
at rx.observers.Subscribers$5.onError(Subscribers.java:230)
at rx.observers.Subscribers$5.onError(Subscribers.java:230)
at rx.internal.operators.OnSubscribeThrow.call(OnSubscribeThrow.java:44)
at rx.internal.operators.OnSubscribeThrow.call(OnSubscribeThrow.java:28)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:51)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:35)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:41)
at rx.internal.operators.OnSubscribeDoOnEach.call(OnSubscribeDoOnEach.java:30)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:48)
at rx.internal.operators.OnSubscribeLift.call(OnSubscribeLift.java:30)
at rx.Observable.unsafeSubscribe(Observable.java:10151)
at rx.internal.operators.OperatorSubscribeOn$1.call(OperatorSubscribeOn.java:94)
at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:56)
at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction$1.call(HystrixContexSchedulerAction.java:47)
at com.netflix.hystrix.strategy.concurrency.HystrixContexSchedulerAction.call(HystrixContexSchedulerAction.java:69)
at rx.internal.schedulers.ScheduledAction.run(ScheduledAction.java:55)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
… 3 more
Caused by: java.lang.RuntimeException: org.apache.dubbo.rpc.RpcException: Invoke remote method timeout. method: loginWithThird, provider: cause: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2022-03-15 20:33:31.937, end time: 2022-03-15 20:33:36.968, client elapsed: 0 ms, server elapsed: 5031 ms, timeout: 5000 ms, request: Request [id=690506, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=loginWithThird, parameterTypes=[class weli.wormhole.rpc.user.center.model.LoginWithThirdRequest, class weli.wormhole.rpc.user.center.model.LoginCommonParam], arguments=[LoginWithThirdRequest(accessToken=54_y3n-YsttdKOklr_X-mbjRgKAKROLDKfx3PV23zAQHmBvmT3zVliFp9mWodHSUEcKgNnJGC4AP_bAOX-2ZSSsfCHfNM5wfjbJDyiQh-sCjFQ, openId=ofrsY6xraPjv9augA9L2kUIB0-Fk, unionId=null, code=null, type=WX, nickName=null, headImgurl=null, sex=null, skipPhoneBind=1), LoginCommonParam(channel=vivo, ip=null, verName=null, platform=null, deviceId=null, imei=null, app=peanut, subApp=peanut, dfId=DUE_59hD2L9BBgukNR3Jv2DjnW00iRtU8z0eRFVFXzU5aEQyTDlCQmd1a05SM0p2MkRqblcwMGlSdFU4ejBlc2h1, idfa=null, mac=null, oaid=null, androidId=null)], attachments={path=weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService, interface=weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService, version=0.0.0, timeout=5000}]], channel: /10.65.1.81:35450 -> /10.65.5.0:11090
at suishen.esb.hystrix.dubbo.DubboHystrixCommand.run(DubboHystrixCommand.java:93)
at suishen.esb.hystrix.dubbo.DubboHystrixCommand.run(DubboHystrixCommand.java:12)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:301)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:297)
at rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
… 26 more
Caused by: org.apache.dubbo.rpc.RpcException: Invoke remote method timeout. method: loginWithThird, provider: cause: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2022-03-15 20:33:31.937, end time: 2022-03-15 20:33:36.968, client elapsed: 0 ms, server elapsed: 5031 ms, timeout: 5000 ms, request: Request [id=690506, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=loginWithThird, parameterTypes=[class weli.wormhole.rpc.user.center.model.LoginWithThirdRequest, class weli.wormhole.rpc.user.center.model.LoginCommonParam], arguments=[LoginWithThirdRequest(accessToken=54_y3n-YsttdKOklr_X-mbjRgKAKROLDKfx3PV23zAQHmBvmT3zVliFp9mWodHSUEcKgNnJGC4AP_bAOX-2ZSSsfCHfNM5wfjbJDyiQh-sCjFQ, openId=ofrsY6xraPjv9augA9L2kUIB0-Fk, unionId=null, code=null, type=WX, nickName=null, headImgurl=null, sex=null, skipPhoneBind=1), LoginCommonParam(channel=vivo, ip=null, verName=null, platform=null, deviceId=null, imei=null, app=peanut, subApp=peanut, dfId=DUE_59hD2L9BBgukNR3Jv2DjnW00iRtU8z0eRFVFXzU5aEQyTDlCQmd1a05SM0p2MkRqblcwMGlSdFU4ejBlc2h1, idfa=null, mac=null, oaid=null, androidId=null)], attachments={path=weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService, interface=weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService, version=0.0.0, timeout=5000}]], channel: /10.65.1.81:35450 -> /10.65.5.0:11090
at org.apache.dubbo.rpc.protocol.AsyncToSyncInvoker.invoke(AsyncToSyncInvoker.java:63)
at org.apache.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:78)
at com.alibaba.dubbo.rpc.Invoker$CompatibleInvoker.invoke(Invoker.java:55)
at suishen.esb.hystrix.dubbo.DubboHystrixCommand.run(DubboHystrixCommand.java:84)
… 30 more
Caused by: java.util.concurrent.ExecutionException: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2022-03-15 20:33:31.937, end time: 2022-03-15 20:33:36.968, client elapsed: 0 ms, server elapsed: 5031 ms, timeout: 5000 ms, request: Request [id=690506, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=loginWithThird, parameterTypes=[class weli.wormhole.rpc.user.center.model.LoginWithThirdRequest, class weli.wormhole.rpc.user.center.model.LoginCommonParam], arguments=[LoginWithThirdRequest(accessToken=54_y3n-YsttdKOklr_X-mbjRgKAKROLDKfx3PV23zAQHmBvmT3zVliFp9mWodHSUEcKgNnJGC4AP_bAOX-2ZSSsfCHfNM5wfjbJDyiQh-sCjFQ, openId=ofrsY6xraPjv9augA9L2kUIB0-Fk, unionId=null, code=null, type=WX, nickName=null, headImgurl=null, sex=null, skipPhoneBind=1), LoginCommonParam(channel=vivo, ip=null, verName=null, platform=null, deviceId=null, imei=null, app=peanut, subApp=peanut, dfId=DUE_59hD2L9BBgukNR3Jv2DjnW00iRtU8z0eRFVFXzU5aEQyTDlCQmd1a05SM0p2MkRqblcwMGlSdFU4ejBlc2h1, idfa=null, mac=null, oaid=null, androidId=null)], attachments={path=weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService, interface=weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService, version=0.0.0, timeout=5000}]], channel: /10.65.1.81:35450 -> /10.65.5.0:11090
at java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:357)
at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1915)
at org.apache.dubbo.rpc.protocol.AsyncToSyncInvoker.invoke(AsyncToSyncInvoker.java:56)
… 33 more
Caused by: org.apache.dubbo.remoting.TimeoutException: Waiting server-side response timeout by scan timer. start time: 2022-03-15 20:33:31.937, end time: 2022-03-15 20:33:36.968, client elapsed: 0 ms, server elapsed: 5031 ms, timeout: 5000 ms, request: Request [id=690506, version=2.0.2, twoway=true, event=false, broken=false, data=RpcInvocation [methodName=loginWithThird, parameterTypes=[class weli.wormhole.rpc.user.center.model.LoginWithThirdRequest, class weli.wormhole.rpc.user.center.model.LoginCommonParam], arguments=[LoginWithThirdRequest(accessToken=54_y3n-YsttdKOklr_X-mbjRgKAKROLDKfx3PV23zAQHmBvmT3zVliFp9mWodHSUEcKgNnJGC4AP_bAOX-2ZSSsfCHfNM5wfjbJDyiQh-sCjFQ, openId=ofrsY6xraPjv9augA9L2kUIB0-Fk, unionId=null, code=null, type=WX, nickName=null, headImgurl=null, sex=null, skipPhoneBind=1), LoginCommonParam(channel=vivo, ip=null, verName=null, platform=null, deviceId=null, imei=null, app=peanut, subApp=peanut, dfId=DUE_59hD2L9BBgukNR3Jv2DjnW00iRtU8z0eRFVFXzU5aEQyTDlCQmd1a05SM0p2MkRqblcwMGlSdFU4ejBlc2h1, idfa=null, mac=null, oaid=null, androidId=null)], attachments={path=weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService, interface=weli.wormhole.rpc.user.center.api.ILoginNeedPhoneService, version=0.0.0, timeout=5000}]], channel: /10.65.1.81:35450 -> /10.65.5.0:11090
at org.apache.dubbo.remoting.exchange.support.DefaultFuture.doReceived(DefaultFuture.java:189)
at org.apache.dubbo.remoting.exchange.support.DefaultFuture.received(DefaultFuture.java:153)
at org.apache.dubbo.remoting.exchange.support.DefaultFuture$TimeoutCheckTask.run(DefaultFuture.java:252)
at org.apache.dubbo.common.timer.HashedWheelTimer$HashedWheelTimeout.expire(HashedWheelTimer.java:648)
at org.apache.dubbo.common.timer.HashedWheelTimer$HashedWheelBucket.expireTimeouts(HashedWheelTimer.java:727)
at org.apache.dubbo.common.timer.HashedWheelTimer$Worker.run(HashedWheelTimer.java:449)
… 1 more
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
2、排查处理
a、digitalocean用户中心cat监控,确认用户中心是否收到请求且正常响应。
当前状态: 正常状态:
对比结果:
用户中心已收到正常请求,排除客户端调用问题;用户中心当前响应时间异常高出平常的响应时间,判断为用户中心内部业务处理出现问题。
b、查看kibana日志,发现无异常日志;
c、使用Arthas诊断工具,监控业务调用链各部分耗时情况;
定位原因为mongodigitalocean慢导致整体业务处理超时。
d、分析mongodigitalocean语句,查看注册情况
public User getUserByDfId(String dfId, String app) {
Criteria criteria = Criteria.where(“userBindInfo.dfId”).is(dfId).and(“app”).is(app).and(“status”).is(1);
return this.mongoTemplate.findOne(Query.query(criteria), User.class);
}
1234
db.getCollection(‘wormhole_user’).getIndexes();
[
{
“v” : 1,
“key” : {
“app” : 1
},
“name” : “app”,
“ns” : “wormhole.wormhole_user”,
“background” : true
},
{
“v” : 1,
“key” : {
“userBindInfo.dfId” : 1
},
“name” : “userBindInfo.dfId”,
“ns” : “wormhole.wormhole_user”,
“background” : true
},
… (其余不关注注册已删除)
]
12345678910111213141516171819202122
“userBindInfo.dfId”字段存在注册,digitalocean语句也无明显异常。
e、查看华为云后台mongo慢日志
{
“op”: “query”,
“ns”: “wormhole.wormhole_user”,
“command”: {
“find”: “wormhole_user”,
“filter”: {
“userBindInfo.dfId”: “DUQmN3QLpLRwC8PNe1joT_9SDRrzazT4cUc5RFVRbU4zUUxwTFJ3QzhQTmUxam9UXzlTRFJyemF6VDRjVWM1c2h1”,
“app”: “maybe”,
“status”: 1
},
“ntoreturn”: -1
},
“keysExamined”: 1870039,
“docsExamined”: 1870039,
“cursorExhausted”: true,
“numYield”: 14836,
“nreturned”: 0,
“locks”: {
“Global”: {
“acquireCount”: {
“r”: 14837
}
},
“Database”: {
“acquireCount”: {
“r”: 14837
}
},
“Collection”: {
“acquireCount”: {
“r”: 14837
}
}
},
“responseLength”: 36,
“millis”: 14545,
“planSummary”: “IXSCAN { app: 1 }”,
“execStats”: {
“stage”: “CACHED_PLAN”,
“nReturned”: 0,
“executionTimeMillisEstimate”: 14552,
“works”: 1,
“advanced”: 0,
“needTime”: 0,
“needYield”: 0,
“saveState”: 14836,
“restoreState”: 14836,
“isEOF”: 1,
“invalidates”: 0,
“inputStage”: {
“stage”: “LIMIT”,
“nReturned”: 0,
“executionTimeMillisEstimate”: 14145,
“works”: 1870040,
“advanced”: 0,
“needTime”: 1870039,
“needYield”: 0,
“saveState”: 14836,
“restoreState”: 14836,
“isEOF”: 1,
“invalidates”: 0,
“limitAmount”: 1,
“inputStage”: {
“stage”: “FETCH”,
“filter”: {
“$and”: [
{
“status”: {
“$eq”: 1
}
},
{
“userBindInfo.dfId”: {
“$eq”: “DUQmN3QLpLRwC8PNe1joT_9SDRrzazT4cUc5RFVRbU4zUUxwTFJ3QzhQTmUxam9UXzlTRFJyemF6VDRjVWM1c2h1”
}
}
]
},
“nReturned”: 0,
“executionTimeMillisEstimate”: 14117,
“works”: 1870040,
“advanced”: 0,
“needTime”: 1870039,
“needYield”: 0,
“saveState”: 14836,
“restoreState”: 14836,
“isEOF”: 1,
“invalidates”: 0,
“docsExamined”: 1870039,
“alreadyHasObj”: 0,
“inputStage”: {
“stage”: “IXSCAN”,
“nReturned”: 1870039,
“executionTimeMillisEstimate”: 931,
“works”: 1870040,
“advanced”: 1870039,
“needTime”: 0,
“needYield”: 0,
“saveState”: 14836,
“restoreState”: 14836,
“isEOF”: 1,
“invalidates”: 0,
“keyPattern”: {
“app”: 1
},
“indexName”: “app”,
“isMultiKey”: false,
“multiKeyPaths”: {
“app”: []
},
“isUnique”: false,
“isSparse”: false,
“isPartial”: false,
“indexVersion”: 1,
“direction”: “forward”,
“indexBounds”: {
“app”: [
“[“maybe”, “maybe”]”
]
},
“keysExamined”: 1870039,
“seeks”: 1,
“dupsTested”: 0,
“dupsDropped”: 0,
“seenInvalidated”: 0
}
}
}
},
“ts”: {
“$date”: 1647359086553
},
“client”: “10.65.5.0”,
“allUsers”: [
{
“user”: “mongosiud”,
“db”: “wormhole”
}
],
“user”: “mongosiud@wormhole”
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
通过慢日志发现,mongo并未使用“userBindInfo.dfId”作为注册digitalocean条件,而是使用了“app”作为注册digitalocean条件。
初步认为注册匹配度不够,mongo没正确使用注册。
f、新增联合注册,提高注册匹配度,digitalocean时间恢复正常,接口恢复正常。
db.getCollection(“wormhole_user”).createIndex({
“userBindInfo.dfId”: 1,
“app”: 1,
“status”: 1
}, {
name: “idx_user_app_userBindInfo.dfId”,
background: true
});
12345678
3、问题分析
a、explain()语句分析
db.getCollection(‘wormhole_user’).find({“userBindInfo.dfId”: “DUqTmV8lQcInjzWtuyJuAntkbRi5_YZ3yHd9RFVxVG1WOGxRY0luanpXdHV5SnVBbnRrYlJpNV9ZWjN5SGQ5c2h1”, “app”: “maybe”, “status”: 1}).explain();
{
“queryPlanner” : {
“plannerVersion” : 1,
“namespace” : “wormhole.wormhole_user”,
“indexFilterSet” : false,
“parsedQuery” : {
“$and” : [
{
“app” : {
“$eq” : “maybe”
}
},
{
“status” : {
“$eq” : 1.0
}
},
{
“userBindInfo.dfId” : {
“$eq” : “DUqTmV8lQcInjzWtuyJuAntkbRi5_YZ3yHd9RFVxVG1WOGxRY0luanpXdHV5SnVBbnRrYlJpNV9ZWjN5SGQ5c2h1”
}
}
]
},
“winningPlan” : {
“stage” : “FETCH”,
“filter” : {
“$and” : [
{
“app” : {
“$eq” : “maybe”
}
},
{
“status” : {
“$eq” : 1.0
}
}
]
},
“inputStage” : {
“stage” : “IXSCAN”,
“keyPattern” : {
“userBindInfo.dfId” : 1
},
“indexName” : “userBindInfo.dfId”,
“isMultiKey” : false,
“multiKeyPaths” : {
“userBindInfo.dfId” : []
},
“isUnique” : false,
“isSparse” : false,
“isPartial” : false,
“indexVersion” : 1,
“direction” : “forward”,
“indexBounds” : {
“userBindInfo.dfId” : [
“[\”DUqTmV8lQcInjzWtuyJuAntkbRi5_YZ3yHd9RFVxVG1WOGxRY0luanpXdHV5SnVBbnRrYlJpNV9ZWjN5SGQ5c2h1\”, \”DUqTmV8lQcInjzWtuyJuAntkbRi5_YZ3yHd9RFVxVG1WOGxRY0luanpXdHV5SnVBbnRrYlJpNV9ZWjN5SGQ5c2h1\”]”
]
}
}
},
“rejectedPlans” : [
{
“stage” : “FETCH”,
“filter” : {
“$and” : [
{
“status” : {
“$eq” : 1.0
}
},
{
“userBindInfo.dfId” : {
“$eq” : “DUqTmV8lQcInjzWtuyJuAntkbRi5_YZ3yHd9RFVxVG1WOGxRY0luanpXdHV5SnVBbnRrYlJpNV9ZWjN5SGQ5c2h1”
}
}
]
},
“inputStage” : {
“stage” : “IXSCAN”,
“keyPattern” : {
“app” : 1
},
“indexName” : “app”,
“isMultiKey” : false,
“multiKeyPaths” : {
“app” : []
},
“isUnique” : false,
“isSparse” : false,
“isPartial” : false,
“indexVersion” : 1,
“direction” : “forward”,
“indexBounds” : {
“app” : [
“[\”maybe\”, \”maybe\”]”
]
}
}
},
{
“stage” : “FETCH”,
“filter” : {
“$and” : [
{
“app” : {
“$eq” : “maybe”
}
},
{
“userBindInfo.dfId” : {
“$eq” : “DUqTmV8lQcInjzWtuyJuAntkbRi5_YZ3yHd9RFVxVG1WOGxRY0luanpXdHV5SnVBbnRrYlJpNV9ZWjN5SGQ5c2h1”
}
},
{
“status” : {
“$eq” : 1.0
}
}
]
},
“inputStage” : {
“stage” : “AND_SORTED”,
“inputStages” : [
{
“stage” : “IXSCAN”,
“keyPattern” : {
“app” : 1
},
“indexName” : “app”,
“isMultiKey” : false,
“multiKeyPaths” : {
“app” : []
},
“isUnique” : false,
“isSparse” : false,
“isPartial” : false,
“indexVersion” : 1,
“direction” : “forward”,
“indexBounds” : {
“app” : [
“[\”maybe\”, \”maybe\”]”
]
}
},
{
“stage” : “IXSCAN”,
“keyPattern” : {
“userBindInfo.dfId” : 1
},
“indexName” : “userBindInfo.dfId”,
“isMultiKey” : false,
“multiKeyPaths” : {
“userBindInfo.dfId” : []
},
“isUnique” : false,
“isSparse” : false,
“isPartial” : false,
“indexVersion” : 1,
“direction” : “forward”,
“indexBounds” : {
“userBindInfo.dfId” : [
“[\”DUqTmV8lQcInjzWtuyJuAntkbRi5_YZ3yHd9RFVxVG1WOGxRY0luanpXdHV5SnVBbnRrYlJpNV9ZWjN5SGQ5c2h1\”, \”DUqTmV8lQcInjzWtuyJuAntkbRi5_YZ3yHd9RFVxVG1WOGxRY0luanpXdHV5SnVBbnRrYlJpNV9ZWjN5SGQ5c2h1\”]”
]
}
}
]
}
}
]
},
“serverInfo” : {
“host” : “host-192-168-10-163”,
“port” : 27017,
“version” : “4.0.3”,
“gitVersion” : “0ead8608f9d151a199b05117bcc79ccb8d5f44a0”
},
“ok” : 1.0,
“operationTime” : Timestamp(1647486920, 2),
“$clusterTime” : {
“clusterTime” : Timestamp(1647486920, 2),
“signature” : {
“hash” : { “$binary” : “ylTAtLK6mVpNwDt7PTGwNST/9z4=”, “$type” : “00” },
“keyId” : NumberLong(7031512438860152835)
}
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
发现mongo执行日本选择正确,这正好解释了服务在之前很长的一段时间内都是正常的原因;mongo慢日志中的执行日本却选择错误,表明有什么原因导致了mongo执行日本的选择发生了变更。
b、文档digitalocean
MongoDB digitalocean优化器会解析最有效的digitalocean日本,关联的日本解析条目会用于具有相同digitalocean形状的后续digitalocean;日本解析会出现刷新:
MongoDB重启;注册或者集合的删除添加更新等操作;最近最少使用 (LRU) 解析替换机制清除最近最少访问的解析条目。
结论:之前使用了日本解析服务正常,到了某一时刻,日本解析失效,MongoDB重新选择日本,此时选择了错误的日本并解析,导致后续digitalocean全部出现问题
c、digitalocean首次出现的慢日志
{
“op”:”query”,
“ns”:”wormhole.wormhole_user”,
“command”:{
“find”:”wormhole_user”,
“filter”:{
“userBindInfo.dfId”:null,
“app”:”cybercat”,
“status”:1
},
“ntoreturn”:-1
},
“keysExamined”:872550,
“docsExamined”:872550,
“fromMultiPlanner”:true,
“replanned”:true,
“cursorExhausted”:true,
“numYield”:13785,
“nreturned”:0,
“locks”:{
“Global”:{
“acquireCount”:{
“r”:13786
}
},
“Database”:{
“acquireCount”:{
“r”:13786
}
},
“Collection”:{
“acquireCount”:{
“r”:13786
}
}
},
“responseLength”:36,
“millis”:27864,
“planSummary”:”IXSCAN { app: 1 }”,
“execStats”:{
“stage”:”LIMIT”,
“nReturned”:0,
“executionTimeMillisEstimate”:26216,
“works”:872551,
“advanced”:0,
“needTime”:872550,
“needYield”:0,
“saveState”:13785,
“restoreState”:13785,
“isEOF”:1,
“invalidates”:0,
“limitAmount”:1,
“inputStage”:{
“stage”:”FETCH”,
“filter”:{
“$and”:[
{
“status”:{
“$eq”:1
}
},
{
“userBindInfo.dfId”:{
“$eq”:null
}
}
]
},
“nReturned”:0,
“executionTimeMillisEstimate”:26164,
“works”:872551,
“advanced”:0,
“needTime”:872550,
“needYield”:0,
“saveState”:13785,
“restoreState”:13785,
“isEOF”:1,
“invalidates”:0,
“docsExamined”:872550,
“alreadyHasObj”:0,
“inputStage”:{
“stage”:”IXSCAN”,
“nReturned”:872550,
“executionTimeMillisEstimate”:249,
“works”:872551,
“advanced”:872550,
“needTime”:0,
“needYield”:0,
“saveState”:13785,
“restoreState”:13785,
“isEOF”:1,
“invalidates”:0,
“keyPattern”:{
“app”:1
},
“indexName”:”app”,
“isMultiKey”:false,
“multiKeyPaths”:{
“app”:[

]
},
“isUnique”:false,
“isSparse”:false,
“isPartial”:false,
“indexVersion”:1,
“direction”:”forward”,
“indexBounds”:{
“app”:[
“[“cybercat”, “cybercat”]”
]
},
“keysExamined”:872550,
“seeks”:1,
“dupsTested”:0,
“dupsDropped”:0,
“seenInvalidated”:0
}
}
},
“ts”:{
“$date”:1647309521700
},
“client”:”10.65.1.25″,
“allUsers”:[
{
“user”:”mongosiud”,
“db”:”wormhole”
}
],
“user”:”mongosiud@wormhole”
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132
发现digitalocean条件中出现了null值,本地再次分析:
db.getCollection(‘wormhole_user’).find({“userBindInfo.dfId”: null, “app”: “maybe”, “status”: 1}).explain();
{
“queryPlanner” : {
“plannerVersion” : 1,
“namespace” : “wormhole.wormhole_user”,
“indexFilterSet” : false,
“parsedQuery” : {
“$and” : [
{
“app” : {
“$eq” : “maybe”
}
},
{
“status” : {
“$eq” : 1.0
}
},
{
“userBindInfo.dfId” : {
“$eq” : null
}
}
]
},
“winningPlan” : {
“stage” : “FETCH”,
“filter” : {
“$and” : [
{
“status” : {
“$eq” : 1.0
}
},
{
“userBindInfo.dfId” : {
“$eq” : null
}
}
]
},
“inputStage” : {
“stage” : “IXSCAN”,
“keyPattern” : {
“app” : 1
},
“indexName” : “app”,
“isMultiKey” : false,
“multiKeyPaths” : {
“app” : []
},
“isUnique” : false,
“isSparse” : false,
“isPartial” : false,
“indexVersion” : 1,
“direction” : “forward”,
“indexBounds” : {
“app” : [
“[\”maybe\”, \”maybe\”]”
]
}
}
},
“rejectedPlans” : [
{
“stage” : “FETCH”,
“filter” : {
“$and” : [
{
“userBindInfo.dfId” : {
“$eq” : null
}
},
{
“app” : {
“$eq” : “maybe”
}
},
{
“status” : {
“$eq” : 1.0
}
}
]
},
“inputStage” : {
“stage” : “IXSCAN”,
“keyPattern” : {
“userBindInfo.dfId” : 1
},
“indexName” : “userBindInfo.dfId”,
“isMultiKey” : false,
“multiKeyPaths” : {
“userBindInfo.dfId” : []
},
“isUnique” : false,
“isSparse” : false,
“isPartial” : false,
“indexVersion” : 1,
“direction” : “forward”,
“indexBounds” : {
“userBindInfo.dfId” : [
“[undefined, undefined]”,
“[null, null]”
]
}
}
}
]
},
“serverInfo” : {
“host” : “host-192-168-10-163”,
“port” : 27017,
“version” : “4.0.3”,
“gitVersion” : “0ead8608f9d151a199b05117bcc79ccb8d5f44a0”
},
“ok” : 1.0,
“operationTime” : Timestamp(1647489666, 1),
“$clusterTime” : {
“clusterTime” : Timestamp(1647489666, 1),
“signature” : {
“hash” : { “$binary” : “1KJI3aoz2QbOwTKlbkNl9SmWLzw=”, “$type” : “00” },
“keyId” : NumberLong(7031512438860152835)
}
}
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
结果与线上表现一致
4、回顾
紧急处理时,直接创建了一个新的注册,导致了日本解析失效,重新选择日本并解析,所以服务恢复了正常。
三、总结
避免空值的digitalocean;尽可能的使用explain()分析各种不同的digitalocean情况;可以使用mongo提供的PlanCache相关功能,查看日本解析情况;可使用hint()推荐digitalocean注册。