Skip to main content

moregeek program

开发的时候,人与人之间还是要少点儿信任之--注解方式防止重复请求-多极客编程

自定义注解方式防止前端同一时间多次重复提交


一、 前情提要

有这样一个业务,上课的时候老师给表现好的学生送小花花,


每节课都能统计出某个学生收到的花的总数。


按照产品需求,前端点击送花按钮后30秒内是不能再次送花的(信任的基础)


(上课老师送花行为都进行统计了,可见互联网是多么可怕)


二、技术设计

2.1 库表设计


CREATE TABLE `t_student_flower` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键(自增)',
`classroom_id` bigint(20) NOT NULL COMMENT '每堂课的唯一标识',
`student_id` bigint(20) NOT NULL COMMENT '学生唯一标识',
`flower_num` bigint(20) NOT NULL DEFAULT '0' COMMENT '学生收到的花数量',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

2.2 业务逻辑


开发的时候,人与人之间还是要少点儿信任之--注解方式防止重复请求_程序员

业务逻辑很简单,针对某一堂课的某一个学生,老师第一次送花就新增一条记录,之后老师给这个学生送花就在原有的记录基础上增加送花数量即可。


如果前端能保证一堂课,一个学生,30秒内只能送一次花,这样设计能99.9999%的保证业务没问题


2.3 代码编写


至于创建SpringBoot项目,连接Mybatis 准备在Mybatis篇章写,这里主要点不是这些。


重要是业务逻辑


pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>student_flower</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>student_flower</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombok 一款还不错的副主编程工具-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
<scope>provided</scope>
</dependency>
<!--测试使用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

application.yml


server:
# 服务端口配置
port: 8888
spring:
# 数据源配置
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf-8&useSSL=false
username: root
password: 123456

mybatis:
# mapper扫描路径
mapper-locations: classpath:mapper/*.xml
# 实体类别名映射包路径
type-aliases-package: com.example.student_flower.entity
configuration:
# 开启驼峰命名
map-underscore-to-camel-case: true

StudentFlowerController


package com.example.student_flower.controller;

import com.example.student_flower.service.StudentFlowerService;
import com.sun.istack.internal.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
* @create 2021-09-11 10:35
*/
@RestController
public class StudentFlowerController {

@Autowired
StudentFlowerService studentFlowerService;

/**
*
* @param classroomId 教师ID
* @param studentId 学生ID
*/
@GetMapping(value = "/test/sendflower/{classroomId}/{studentId}")
public void sendFlower(@NotNull @PathVariable("classroomId") Long classroomId , @NotNull @PathVariable("studentId") Long studentId){
studentFlowerService.SendFlower(classroomId,studentId);
}
}

StudentFlowerService


package com.example.student_flower.service;

import com.example.student_flower.dao.TStudentFlowerMapper;
import com.example.student_flower.entity.TStudentFlower;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
* @create 2021-09-11 10:38
*/
@Service
public class StudentFlowerService {
@Autowired
TStudentFlowerMapper mapper;

public void SendFlower(Long classroomId, Long studentId){
TStudentFlower tStudentFlower = mapper.selectByClassroomIdAndStudentId(classroomId, studentId);
// 第一次送花 没有记录 新增
if (tStudentFlower == null) {
TStudentFlower tsf = new TStudentFlower();
tsf.setClassroomId(classroomId);
tsf.setStudentId(studentId);
tsf.setFlowerNum(1);
mapper.insert(tsf);
} else {
// 已经送过花了 原来数量上+1
tStudentFlower.setFlowerNum(tStudentFlower.getFlowerNum() + 1);
mapper.update(tStudentFlower);
}
}
}

TStudentFlowerMapper


package com.example.student_flower.dao;

import com.example.student_flower.entity.TStudentFlower;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
* @create 2021-09-11 10:14
*/
@Mapper
public interface TStudentFlowerMapper {
// 插入
void insert(TStudentFlower tStudentFlower);
// 更新
void update(TStudentFlower tStudentFlower);

// 查询
TStudentFlower selectByClassroomIdAndStudentId(
@Param("classroomId") Long classroomId,
@Param("studentId") Long studentId);
}


TStudentFlowerMapper.xml


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.student_flower.dao.TStudentFlowerMapper">
<!--新增-->
<insert id="insert" parameterType="TStudentFlower">
INSERT INTO t_student_flower (classroom_id,student_id,flower_num)
VALUES (#{classroomId},#{studentId},#{flowerNum})
</insert>

<!--更新-->
<update id="update" parameterType="TStudentFlower">
UPDATE t_student_flower
SET flower_num = #{flowerNum}
WHERE id=#{id};
</update>

<select id="selectByClassroomIdAndStudentId"
resultType="TStudentFlower">
select * from t_student_flower
where classroom_id = #{classroomId} and student_id = #{studentId}
</select>
</mapper>

2.4 测试


浏览器直接访问:


http://127.0.0.1:8888/test/sendflower/1/1


就会给classroomId = 1 ,studentId = 1 的学生送一朵花


2.5 问题所在


一切看似没有问题,因为请求频率还没有达到可以出错的速度。


我们写一个测试用了来模拟前端不可信任的时候(由于某种原因他们送花事件绑定了多次没有解绑,也就是同一时间发送多次送花请求)


package com.example.student_flower;

import com.example.student_flower.service.StudentFlowerService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.TimeUnit;

@SpringBootTest
class StudentFlowerApplicationTests {

@Autowired
StudentFlowerService service;

@Test
void sendFlower() throws InterruptedException {
final Long classroomId = 2L;
final Long studengId = 102L;

Thread thread1 = new Thread(() -> {
service.SendFlower(classroomId, studengId);
System.out.println("thread1执行完了");
});
Thread thread2 = new Thread(() -> {
service.SendFlower(classroomId, studengId);
System.out.println("thread2执行完了");
});
Thread thread3 = new Thread(() -> {
service.SendFlower(classroomId, studengId);
System.out.println("thread3执行完了");
});
thread1.start();
thread2.start();
thread3.start();
// 睡会儿 等三个线程跑完 很low? 做测试凑活用吧
Thread.sleep(TimeUnit.SECONDS.toMillis(20));
}

}


执行完看一下数据库结果:


 


开发的时候,人与人之间还是要少点儿信任之--注解方式防止重复请求_技术宅_02

这肯定是有问题的 多三条要出问题的,要扣钱绩效的


三、解决方案


解决方案有很多,我今天介绍一种自定义注解的方式(其实就是用了分布redis锁)


方案看似很简单:


 


开发的时候,人与人之间还是要少点儿信任之--注解方式防止重复请求_技术宅_03

自定义注解MyAnotation


package com.example.student_flower.common.anotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程 分享一个生活在互联网底层做着增删改查的码农的感悟与学习
*
* 关于自定义注解 后边有机会专门写一写 先会用
* @create 2021-09-11 15:26
*/
@Target({ElementType.METHOD}) // 方法上使用的注解
@Retention(RetentionPolicy.RUNTIME) // 运行时通过反射访问
public @interface MyAnotation {

/**
* 获取锁时默认等待多久
*/
int waitTime() default 3;

/**
* 锁过期时间
*/
int expireTime() default 20;

/**
* 锁key值
*/
String redisKey() default "";

/**
* 锁key后拼接的动态参数的值
*/
String[] params() default {};
}

自定义切面处理逻辑,进行放重复提交校验MyAspect


package com.example.student_flower.common.aspect;

import com.example.student_flower.common.anotation.MyAnotation;
import com.example.student_flower.util.HttpContextUtils;
import com.example.student_flower.util.SpelUtil;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
*
* 关于spring面向切面的知识 等以后文章有机会我写一写(自己也不太熟 暂时会用)
*
* @create 2021-09-11 15:29
*/
@Slf4j
@Aspect
@Component
public class MyAspect {

@Autowired
RedissonClient redissonClient;

// 这个是那些方法需要被切 -- 被标记注解MyAnotation的方法要被切
@Pointcut("@annotation(com.example.student_flower.common.anotation.MyAnotation)")
public void whichMethodAspect() {
}

/**
* 切面 执行业务逻辑 在实际业务方法执行前 后 都可以进行一些额外的操作
* 切面的好处就是对你不知不觉
*/
@Around("whichMethodAspect()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 获取注解
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
MyAnotation myAnotation = method.getAnnotation(MyAnotation.class);

// 2. 锁等待时间
int waitTime = myAnotation.waitTime();
// 2. 锁超时时间 怕万一finally没有被执行到的时候 多长时间自动释放锁(基本不会不执行finnaly 除非那个点机器down了)
final int lockSeconds = myAnotation.expireTime();
// 3. 特殊业务自定义key
String key = myAnotation.redisKey();
// 自定义redisKey是否使用参数
String[] params = myAnotation.params();
// 4.获取HttpServletRequest
HttpServletRequest request = HttpContextUtils.getRequest();
if (request == null) {
throw new Exception("错误的请求 request为null");
}
assert request != null;

// 5. 组合redis锁key
// 5.1 如果没有自定义 用默认的 url+token
if (StringUtils.isBlank(key) && (params == null || params.length == 0)) {
// 这里怎么获取token 主要看自己项目用的什么框架 token在哪个位置存储着
String token = request.getHeader("Authorization");
String requestURI = request.getRequestURI();
key = requestURI+token;
} else {
// 5.2 自定义key
key = SpelUtil.generateKeyBySpEL(key, params, joinPoint);
}
// 6. 获取key
// 获取锁 获取不到最多等waitTime秒 lockSeconds秒后自动释放锁
// 每个项目组应该会有自己的redisUtil的封装 我这里就用最简单的方式
// 怎么使用锁不是重点 重点是这个思想
RLock lock = redissonClient.getLock(key);
log.info("tryLock key = {}", key);
boolean b = lock.tryLock(waitTime, lockSeconds, TimeUnit.SECONDS);
// 获取锁成功
if (b) {
try {
log.info("tryLock success, key = {}", key);
// 7. 执行业务代码 返回结果
return joinPoint.proceed();
} finally {
lock.unlock();
}
} else {
// 获取锁失败
log.info("tryLock fail, key = {}", key);
throw new Exception("请求频繁,请稍后重试");
}
}

}

Redisson配置RedissonConfig


package com.example.student_flower;

import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
* @create 2021-09-11 16:31
*/
public class RedissonConfig {
// 这里就简单设置 真实项目中会做到配置文件或配置中心
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
return Redisson.create(config);
}
}


获取request对象HttpContextUtils


package com.example.student_flower.util;

import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
* @create 2021-09-11 16:17
*
* 获取springboot环境中的request/response对象
*/
public class HttpContextUtils {
// 获取request
public static HttpServletRequest getRequest(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = servletRequestAttributes.getRequest();
return request;
}

// 获取response
public static HttpServletResponse getResponse(){
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletResponse response = servletRequestAttributes.getResponse();
return response;
}
}


El表达式解析 SpelUtil


package com.example.student_flower.util;

import java.lang.reflect.Method;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
* @create 2021-09-11 15:35
*/

/**
* EL表达式解析
*/
public class SpelUtil {

/**
* 用于SpEL表达式解析.
*/
private static SpelExpressionParser parser = new SpelExpressionParser();
/**
* 用于获取方法参数定义名字.
*/
private static DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

/**
* 解析表达式
*/
public static String generateKeyBySpEL(String key, String[] params, ProceedingJoinPoint joinPoint) {
StringBuilder spELString = new StringBuilder();
if (params != null && params.length > 0) {
spELString.append("'" + key + "'");
for (int i = 0; i < params.length; i++) {
spELString.append("+#" + params[i]);
}
} else {
return key;
}
// 通过joinPoint获取被注解方法
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
// 使用spring的DefaultParameterNameDiscoverer获取方法形参名数组
String[] paramNames = nameDiscoverer.getParameterNames(method);
// 解析过后的Spring表达式对象
Expression expression = parser.parseExpression(spELString.toString());
// spring的表达式上下文对象
EvaluationContext context = new StandardEvaluationContext();
// 通过joinPoint获取被注解方法的形参
Object[] args = joinPoint.getArgs();
// 给上下文赋值
for (int i = 0; i < args.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
return expression.getValue(context).toString();
}
}


controller使用注解:


package com.example.student_flower.controller;

import com.example.student_flower.common.anotation.MyAnotation;
import com.example.student_flower.service.StudentFlowerService;
import com.sun.istack.internal.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
* @author 发现更多精彩 关注公众号:木子的昼夜编程
* 一个生活在互联网底层,做着增删改查的码农,不谙世事的造作
* @create 2021-09-11 10:35
*/
@RestController
public class StudentFlowerController {

@Autowired
StudentFlowerService studentFlowerService;

/**
*
* @param classroomId 教师ID
* @param studentId 学生ID
*/
@MyAnotation(redisKey = "/test/sendflower", params = {"classroomId", "studentId"})
@GetMapping(value = "/test/sendflower/{classroomId}/{studentId}")
public void sendFlower(@NotNull @PathVariable("classroomId") Long classroomId , @NotNull @PathVariable("studentId") Long studentId){
studentFlowerService.SendFlower(classroomId,studentId);
}
}


测试类(这里用了MockMvc直接测试controller)


package com.example.student_flower;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import java.util.concurrent.TimeUnit;


@SpringBootTest
@AutoConfigureMockMvc
class StudentFlowerTests {

@Autowired
protected MockMvc mockMvc;

@Test
void sendFlower() throws Exception {
final Long classroomId = 7L;
final Long studengId = 102L;

Thread thread1 = new Thread(() -> {
try {
mockMvc.perform(MockMvcRequestBuilders
.get("/test/sendflower/" + classroomId + "/"
+ studengId).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
} catch (Exception e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
mockMvc.perform(MockMvcRequestBuilders
.get("/test/sendflower/" + classroomId + "/"
+ studengId).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
} catch (Exception e) {
e.printStackTrace();
}
});
Thread thread3 = new Thread(() -> {
try {
mockMvc.perform(MockMvcRequestBuilders
.get("/test/sendflower/" + classroomId + "/"
+ studengId).accept(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print())
.andReturn();
} catch (Exception e) {
e.printStackTrace();
}
});

thread1.start();
thread2.start();
thread3.start();

// 睡会儿 等三个线程跑完 很low? 做测试凑活用吧
Thread.sleep(TimeUnit.SECONDS.toMillis(20));
}
}


去掉controller注解测试 会插入多条,加上MyAnotation注解只会生成一条


四 、唠唠


4.1 项目
主要用到了自定义注解、RedissonClient的redis锁、AOP等知识


可能么有写过这种场景代码的人会觉得比较乱:木有关系全部代码已经提交到github上了,


地址:https://github.com/githubforliming/student_flower


开发的时候,人与人之间还是要少点儿信任之--注解方式防止重复请求_程序员_04

 


4.2 redis服务
贴心的我把redis的windows免安装包都放到项目里了


test/java/soft 解压 双击redis-server.exe 即可运行


默认没密码


 


开发的时候,人与人之间还是要少点儿信任之--注解方式防止重复请求_Java_05

4.3 其他问题
支持参数是对象的自定义key


    @MyAnotation(redisKey = "/test/sendflower", params = {"p.id"})
@PostMapping(value = "/test/sendflower02")
public void sendFlower(@RequestBody Person p){
// xxx
}

©著作权归作者所有:来自51CTO博客作者跟着小苏不加班的原创作品,如需转载,请注明出处,否则将追究法律责任
开发的时候,人与人之间还是要少点儿信任之--注解方式防止重复请求
https://blog.51cto.com/u_15306230/3939573

求你了,别再说数据库锁的只是索引了!!!-多极客编程

在MySQL数据库中,为了解决并发问题,引入了很多的锁机制,很多时候,数据库的锁是在有数据库操作的过程中自动添加的。 所以,这就导致很多程序员经常会忽略数据库的锁机制的真正的原理。比如,我经常在面试中会问候选人,你知道MySQL Innodb的锁,到底锁的是什么吗? 关于这个问题的回答,我听到过很多种,但是很少有人可以把他回答的很完美。因为想要回答好这个问题,需要对数据库的隔离级别、索引等都有一定

为什么MySQL数据库索引选择使用B+树?-多极客编程

在进一步分析为什么MySQL数据库索引选择使用B+树之前,我相信很多小伙伴对数据结构中的树还是有些许模糊的,因此我们由浅入深一步步探讨树的演进过程,在一步步引出B树以及为什么MySQL数据库索引选择使用B+树! 学过数据结构的一般对最基础的树都有所认识,因此我们就从与我们主题更为相近的二叉查找树开始。 一、二叉查找树 (1)二叉树简介: 二叉查找树也称为有序二叉查找树,满足二叉查找树的一般性质,是

史上最全jdk新特性总结,涵盖jdk8到jdk15!-多极客编程

前言在本文中,我将描述自第8版以来Java最重要且对开发人员友好的功能。为什么会有这样的主意?在Web上,您可以找到许多文章,其中包含每种Java版本的新功能列表。但是,由于缺少文章,因此无法简要概述自第8版以来最重要的更改。好的,但是为什么是第8版?令人惊讶的是,它仍然是最常用的Java版本。即使我们已经到了Java 16发行版的前夕果。如您所见,超过46%的响应者仍在生产中使用Java 8。相

【Java虚拟机规范】JVM类加载机制-多极客编程

作者:threedayman 来源:恒生LIGHT云社区 理论知识 一个类型从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段,其中验证、准备、解析三个部分统称为

netty系列之:在netty中处理CORS-多极客编程

简介 CORS的全称是跨域资源共享,他是一个基于HTTP-header检测的机制,通过对HTTP-header进行控制,可以实现对跨域资源的权限管理功能。在之前的CORS详解文章中,我们已经对CORS有了基本的解释。 本文将会从netty的实现角度,讲解如何在netty中实现CORS。 服务端的CORS配置 熟悉CORS的朋友应该知道,CORS所有的操作都是在HTTP协议之上通过控制HTTP头来实

【算法与数据结构系列】「限流算法专项」带你认识常用的限流算法的技术指南(分析篇)-多极客编程

限流 限流的目的是通过对并发访问/请求进行限速,或者对一个时间窗口内的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务、排队或等待、降级等处理 限流一词常用于计算机网络之中,定义如下: In computer networks, rate limiting is used to control the rate of traffic sent or received by a netw

求你了,别再说数据库锁的只是索引了!!!-多极客编程

在MySQL数据库中,为了解决并发问题,引入了很多的锁机制,很多时候,数据库的锁是在有数据库操作的过程中自动添加的。 所以,这就导致很多程序员经常会忽略数据库的锁机制的真正的原理。比如,我经常在面试中会问候选人,你知道MySQL Innodb的锁,到底锁的是什么吗? 关于这个问题的回答,我听到过很多种,但是很少有人可以把他回答的很完美。因为想要回答好这个问题,需要对数据库的隔离级别、索引等都有一定

为什么MySQL数据库索引选择使用B+树?-多极客编程

在进一步分析为什么MySQL数据库索引选择使用B+树之前,我相信很多小伙伴对数据结构中的树还是有些许模糊的,因此我们由浅入深一步步探讨树的演进过程,在一步步引出B树以及为什么MySQL数据库索引选择使用B+树! 学过数据结构的一般对最基础的树都有所认识,因此我们就从与我们主题更为相近的二叉查找树开始。 一、二叉查找树 (1)二叉树简介: 二叉查找树也称为有序二叉查找树,满足二叉查找树的一般性质,是

Java线上故障排查不会怎么办,p8大佬总结的套路清单带你轻松玩转!-多极客编程

前言 线上故障主要会包括 CPU、磁盘、内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍。同时例如 jstack、jmap 等工具也是不囿于一个方面的问题的,基本上出问题就是 df、free、top 三连,然后依次 jstack、jmap 伺候,具体问题具体分析即可。 CPU 一般来讲我们首先会排查 CPU 方面的问题。CPU 异常往往还是比较好

❤超级详细万文零基础也能学的面向对象—没对象?new一个!-多极客编程

​ 面向对象(OOP)基本概念面向对象编程 —— Object Oriented Programming 简写 OOP 目标了解 面向对象 基本概念 面向对象基本概念 我们之前学习的编程方式就是面向过程 的 面相过程 和 面相对象,是两种不同的 编程方式对比 面向过程 的特点,可以更好地了解什么是面向对象 1.1 过程和函数(科普) 过程 是早期的一个编程概念过程 类似于函数,只能执行

10个Python绘画表白代码【内附源码,再不收藏你只能单身了】-多极客编程

发现一些很好玩的画图小项目,今天分享给大家,教你怎样用Python画一朵玫瑰花、时钟、爱心、太阳花、月饼、进阶自定义爱心、小猪佩奇、星空、超梦幻的蓝色背景樱花等大家快来学习吧。运行工具:pycharm 、python玫瑰花效果图: 源码:from turtle import *import timesetup(1000,800,0,0)speed(0)penup()seth(90)fd(340)s

Java编程之伪共享与缓存行填充-多极客编程

最近在回顾Disruptor的相关知识,觉得Disruptor在计算机底层的领域确实比一般人厉害不少,以前在写程序的时候,基本是从应用逻辑的角度考虑,觉得设计模式+少量算法+ 优美的代码=理想的结果,但看完Disruptor的设计后,觉得只考虑应用本身是有一定的局限性,还需要懂底层,硬件层面的东西,就像Disruptor一样,通过底层优化,让程序有质的飞跃。下面就Disruptor提到的CPU缓存