常见需求开发解决方案与思路
Posted GeorgeLin98
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了常见需求开发解决方案与思路相关的知识,希望对你有一定的参考价值。
常见需求开发解决方案与思路
Dao层
如何查询千万数据量:
- 查询大数据量用途:
①迁移数据
②导出数据
③批量处理数据 - 查询大数据量方式:
①常规查询,一次性读取 500w 数据到 JVM 内存中,或者分页读取
②流式查询,建立长连接,利用服务端游标,每次读取一条加载到 JVM 内存
③游标查询,和流式一样,通过 fetchSize 参数,控制一次读取多少条数据 - 常规查询:
①假设单表 500w 数据量,没有人会一次性加载到内存中,一般会采用分页的方式。因为将全部数据一次性查询到内存中的话数据量太大会爆掉内存。
②上述方式比较简单,但是在不考虑LIMIT 深分页优化
情况下,线上数据库服务器就凉了,亦或者你能等个几天时间检索数据。
@SneakyThrows
@Override
public void pageQuery()
@Cleanup Connection conn = dataSource.getConnection();
@Cleanup Statement stmt = conn.createStatement();
long start = System.currentTimeMillis();
long offset = 0;
int size = 100;
while (true)
String sql = String.format("SELECT COLUMN_A, COLUMN_B, COLUMN_C FROM YOU_TABLE LIMIT %s, %s", offset, size);
@Cleanup ResultSet rs = stmt.executeQuery(sql);
long count = loopResultSet(rs);
if (count == 0) break;
offset += size;
log.info(" 分页查询耗时 :: ", System.currentTimeMillis() - start);
Controller层
自定义SpringMVC的数据校验注解以及处理过程:
- 自定义注解
- 创建一个类实现ConstraintValidator接口,实现接口方法
①initialize方法:会在校验实例化后被调用,一般用于做些初始化工作。
②isValid方法:实际执行验证的方法,第一个参数获取的是字段或对象实际对应的值,取决于类的枚举限定类型。第二个参数是ConstraintValidatorContext,上下文可以做些默认的设置。 - 最后再自定义注解的@Constraint注解中加上创建的类名。
@Constraint(validatedBy = XXX.class)
自定参数解析器:
- 自定义解析器需要实现HandlerMethodArgumentResolver接口
- 接口说明:
①supportsParameter:用于判定是否需要处理该参数分解,返回true为需要,并会去调用下面的方法resolveArgument。
②resolveArgument:真正用于处理参数分解的方法,返回的Object就是controller方法上的形参对象。
/**
- 有@LoginUser注解的方法参数,注入当前登录用户
*/
@Component
public class LoginUserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver
@Autowired
private UserService userService;
@Override
public boolean supportsParameter(MethodParameter parameter)
return parameter.getParameterType().isAssignableFrom(UserEntity.class) && parameter.hasParameterAnnotation(LoginUser.class);
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer container,
NativeWebRequest request, WebDataBinderFactory factory) throws Exception
//获取用户ID
Object object = request.getAttribute(AuthorizationInterceptor.USER_KEY, RequestAttributes.SCOPE_REQUEST);
if(object == null)
return null;
//获取用户信息
UserEntity user = userService.selectById((Long)object);
return user;
Service层
Cache层
如何保证缓存和数据库一致性:
- 链接:如何保持mysql和redis中数据的一致性?
- 想要提高应用的性能,可以引入「缓存」来解决
- 引入缓存后,需要考虑缓存和数据库一致性问题,可选的方案有:「更新数据库 + 更新缓存」、「更新数据库 + 删除缓存」
- 更新数据库 + 更新缓存方案,在「并发」场景下无法保证缓存和数据一致性,解决方案是加「分布锁」,但这种方案存在「缓存资源浪费」和「机器性能浪费」的情况
- 采用「先删除缓存,再更新数据库」方案,在「并发」场景下依旧有不一致问题,解决方案是「延迟双删」,但这个延迟时间很难评估
- 采用「先更新数据库,再删除缓存」方案,为了保证两步都成功执行,需配合「消息队列」或「订阅变更日志」的方案来做,本质是通过「重试」的方式保证数据最终一致
- 采用「先更新数据库,再删除缓存」方案,「读写分离 + 主从库延迟」也会导致缓存和数据库不一致,缓解此问题的方案是「延迟双删」,凭借经验发送「延迟消息」到队列中,延迟删除缓存,同时也要控制主从库延迟,尽可能降低不一致发生的概率
- 总结一下:
没有完美的强一致性解决方案。除非你在更新缓存成功之前,停止所有的读缓存操作,但是这也就失去了缓存的意义。我们所能做的就是在缓存的不一致程度与缓存有效期之间做一个权衡,尽可能的减少不一致的情况发生
其他
初始化操作:
- Spring中的InitializingBean接口的使用:
①InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。
②在配置bean的时候使用init-method配置也可以为bean配置初始化方法,在Spring初始化bean的时候,如果该bean实现了InitializingBean接口,并且同时在配置文件中指定了init-method,系统则是先调用afterPropertieSet()方法,然后再调用init-method中指定的方法。
③链接:Spring中的InitializingBean接口的使用 - Java 中的 static 使用之静态初始化块:
①在类的声明中,可以包含多个初始化块,当创建类的实例时,就会依次执行这些代码块。如果使用 static 修饰初始化块,就称为静态初始化块。
②需要特别注意:静态初始化块只在类加载时执行,且只会执行一次,同时静态初始化块只能给静态变量赋值,不能初始化普通的成员变量。
以上是关于常见需求开发解决方案与思路的主要内容,如果未能解决你的问题,请参考以下文章