スパイクシステムのバックグラウンド実装の詳細な説明

スパイクバックグラウンドの実装

この記事では、主に、実際のプロジェクトスパイクにおける次の問題の解決方法について説明します。

1)seckillの非同期の順序付けを実現し、プロデューサーとコンシューマーのメッセージが失われないようにする方法を習得する

2)悪意のあるブラッシングの防止を実現する

3)同じ製品の繰り返しのスパイクを防ぐことを実現する

4)隠れた注文インターフェースを実現する

5)注文インターフェースの現在の制限を実現する

1 秒のキル非同期順序

ユーザーが注文すると、JWTトークン情報に基づいてログイン情報認証を実行し、現在の注文が誰のものであるかを判別する必要があります。

スパイクの特別なビジネスシナリオでは、サーバープレッシャーを解決するためにオブジェクトキャッシングやページスタティックなどのテクノロジーに依存するだけでは十分ではありません。

データベースへの圧力は依然として非常に大きいため、非同期で注文する必要があります。非同期が最善の解決策ですが、追加の手順が必要になります。

複雑。

1.1 seckillサービスオーダーの実現
1)tokenDecodeツールクラスの設定をスパイクサービスに入れ、Beanを宣言します

public static void main(String[] args){
	SpringApplication.run(SeckillApplication,class,args);
}
@Bean
public TokenDecode tokenDecode(){
	return new TokenDecode();
}
2)スパイクサービスのスタートアップクラスを更新し、redis構成を追加する
/**
	* 设置 redisTemplate 的序列化设置 
	* @param redisConnectionFactory 
	* @return 
	*/ 
@Bean 
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { 
	// 1.创建 redisTemplate 模版 
	RedisTemplate<Object, Object> template = new RedisTemplate<>(); 
	// 2.关联 redisConnectionFactory
     template.setConnectionFactory(redisConnectionFactory);
	// 3.创建 序列化类 
	GenericToStringSerializer genericToStringSerializer = new 	GenericToStringSerializer(Object.class); 
	// 4.序列化类,对象映射设置 
	// 5.设置 value 的转化格式和 key 的转化格式
    template.setValueSerializer(genericToStringSerializer);
    template.setKeySerializer(new StringRedisSerializer()); 
    template.afterPropertiesSet(); 
	return template; 
}
2)新しい注文コントローラーを作成し、メソッドを宣言します
@RestController 
@CrossOrigin 
@RequestMapping("/seckillorder") 
public class SecKillOrderController { 
	@Autowired 
	private TokenDecode tokenDecode; 
	@Autowired 
	private SecKillOrderService secKillOrderService; 
	/**
	* 秒杀下单 
	* @param time 当前时间段 
	* @param id 秒杀商品id 
	* @return 
	*/ 
	@RequestMapping("/add")
	//获取当前登陆人 
	String username = tokenDecode.getUserInfo().get("username"); 
	boolean result = secKillOrderService.add(id,time,username); 
	if (result){ 
		return new Result(true, StatusCode.OK,"下单成功"); 
	}else{
		return new Result(false,StatusCode.ERROR,"下单失败"); 
		} 
	} 
}
3)新しいサービスインターフェイス
public interface SecKillOrderService { 
/**
* 秒杀下单 
* @param id 商品id 
* @param time 时间段 
* @param username 登陆人姓名 
* @return 
*/ 
boolean add(Long id, String time, String username); 
} 
4)プリロードされたスパイク製品を変更する

スパイク製品をプリロードする場合、各製品の在庫情報が事前にロードされ、その後の在庫削減操作では、最初にキャッシュ内の在庫が差し引かれ、次にmysqlデータが非同期に差し引かれます。

在庫の事前控除は、redisアトミック操作に基づいて実現されます。

for (SeckillGoods seckillGoods : seckillGoodsList) { 
	redisTemplate.boundHashOps(SECKILL_GOODS_KEY + 	redisExtName).put(seckillGoods.getId(),seckillGoods); 
		//预加载库存信息 
redisTemplate.OpsForValue(SECKILL_GOODS_STOCK_COUNT_KEY+seckillGoods.getId(),se 
ckillGoods.getStockCount()); 
} 
6)Seckill Orderビジネスレイヤーの実現

ビジネスの論理:

seckill製品データと在庫データを取得し、在庫がない場合は、例外をスローしてredisの控除前在庫を実行し、差し引かれた在庫値を取得します。差し引かれた在庫値<= 0の場合、redisで対応する製品情報を削除しますインベントリ情報を使用してmq非同期メソッドに基づいてmysqlデータと同期する(最終整合性)

:インベントリデータはredisから取得され、文字列に変換されます

@Service 
public class SecKillOrderServiceImpl implements SecKillOrderService { 
	@Autowired 
	private RedisTemplate redisTemplate; 
	@Autowired 
	private IdWorker idWorker; 
	@Autowired 
	private CustomMessageSender customMessageSender; 
	/**
	* 秒杀下单 
	* @param id 商品id 
	* @param time 时间段 
	* @param username 登陆人姓名 
	* @return 
	*/ 
	@Override 
	public boolean add(Long id, String time, String username) { 
	//获取商品数据 
	SeckillGoods goods = (SeckillGoods) 
redisTemplate.boundHashOps("SeckillGoods_" + time).get(id); 
	String redisStock = (String) redisTemplate.boundValueOps("StockCount_" + 
goods.getId()).get(); 
	if(StringUtils.isEmpty(redisStock)){ 
		return false; 
}
	int value=Integer.parseInt(redisStock); 
	//如果没有库存,则直接抛出异常 
	if(goods==null || value<=0){ 
		return false; 
}
	//redis预扣库存 
	Long stockCount = redisTemplate.boundValueOps("StockCount_" + 
id).decrement(); 
	if (stockCount<=0){ 
	//库存没了 
	//删除商品信息 
	redisTemplate.boundHashOps("SeckillGoods_" + time).delete(id); 
	//删除对应的库存信息 
	redisTemplate.delete("StockCount_" + goods.getId()); 
}
	//有库存 
	//如果有库存,则创建秒杀商品订单 
	SeckillOrder seckillOrder = new SeckillOrder(); 
	seckillOrder.setId(idWorker.nextId());
	seckillOrder.setUserId(username); 	
	seckillOrder.setSellerId(goods.getSellerId());
    seckillOrder.setCreateTime(new Date()); 
    seckillOrder.setStatus("0"); 
    //发送消息 
    return true; 
    } 
 }

1.2 プロデューサーはメッセージが失われないことを保証します

既存のrabbitMQ関連の知識によれば、プロデューサーはメッセージサーバーにメッセージを送信します。ただし、実際の運用環境では、ダウンタイムなどのメッセージサーバーに到達した後のメッセージサーバーの問題により、メッセージプロデューサーによって送信されたメッセージが失われる可能性があります。メッセージサーバーはデフォルトでメッセージをメモリに保存するためです。メッセージサーバーがダウンすると、メッセージは失われます。したがって、プロデューサーのメッセージが失われないようにするには、永続化戦略を開始する必要があります。

rabbitMQ持久化: 交换机持久化 队列持久化 消息持久化

ただし、これらの2つの部分の永続化のみがオンになっている場合、メッセージが失われる可能性もあります。メッセージサーバーは、永続化プロセス中にダウンしている可能性が高いためです。したがって、メッセージが正常に永続化されるようにするには、データ保護メカニズムが必要です。そうでない場合、メッセージは常に送信されます。

事务机制 
	事务机制采用类数据库的事务机制进行数据保护,当消息到达消息服务器,首先会开启一个事务,接着进 行数据磁盘持久化,只有持久化成功才会进行事务提交,向消息生产者返回成功通知,消息生产者一旦接收成 功通知则不会再发送此条消息。当出现异常,则返回失败通知.消息生产者一旦接收失败通知,则继续发送该 条消息。
	事务机制虽然能够保证数据安全,但是此机制采用的是同步机制,会产生系统间消息阻塞,影响整个系统 的消息吞吐量。从而导致整个系统的性能下降,因此不建议使用。 
	confirm机制 
		confirm模式需要基于channel进行设置, 一旦某条消息被投递到队列之后,消息队列就会发送一个确 认信息给生产者,如果队列与消息是可持久化的, 那么确认消息会等到消息成功写入到磁盘之后发出. 			confirm的性能高,主要得益于它是异步的.生产者在将第一条消息发出之后等待确认消息的同时也可以 继续发送后续的消息.当确认消息到达之后,就可以通过回调方法处理这条确认消息. 如果MQ服务宕机了,则会 返回nack消息. 生产者同样在回调方法中进行后续处理。
1.2.1確認メカニズムを有効にする
1)スパイクサービス構成ファイルを変更する
rabbitmq: 
	host: 192.168.200.128 
	publisher-confirms: true #开启confirm机制
2)キューの永続化をオンにする
@Configuration 
public class RabbitMQConfig { 
	//秒杀商品订单消息 
	public static final String SECKILL_ORDER_KEY="seckill_order"; 
	@Bean 
	public Queue queue(){ 
		//开启队列持久化 
		return new Queue(SECKILL_ORDER_KEY,true); 
		} 
	}
3)メッセージ永続性のソースコードを表示する

4)rabbitTemplateを拡張する
@Component 
public class CustomMessageSender implements RabbitTemplate.ConfirmCallback { 
	static final Logger log = LoggerFactory.getLogger(CustomMessageSender.class); 
	private static final String MESSAGE_CONFIRM="message_confirm"; 
	@Autowired 
	private RabbitTemplate rabbitTemplate; 
	@Autowired 
	private RedisTemplate redisTemplate; 
	public CustomMessageSender(RabbitTemplate rabbitTemplate) { 
	this.rabbitTemplate = rabbitTemplate; 
	rabbitTemplate.setConfirmCallback(this); 
}
	@Override 
	public void confirm(CorrelationData correlationData, boolean ack, String 
cause) {
	if (ack){ 
	//返回成功通知 
	//删除redis中的相关数据 
	redisTemplate.delete(correlationData.getId()); 
	redisTemplate.delete(MESSAGE_CONFIRM_+correlationData.getId()); 
	}else{
	//返回失败通知 
	Map<String,String> map = 
(Map<String,String>)redisTemplate.opsForHash().entries(MESSAGE_CONFIRM_+correlationData.getId()); 
	String exchange = map.get("exchange"); 
	String routingKey = map.get("routingKey"); 
	String sendMessage = map.get("sendMessage"); 
	//重新发送 
	rabbitTemplate.convertAndSend(exchange,routingKey, 
JSON.toJSONString(sendMessage)); 
	} 
}
	//自定义发送方法 
	public void sendMessage(String exchange,String routingKey,String message){ 
	//设置消息唯一标识并存入缓存 
	CorrelationData correlationData = new 
CorrelationData(UUID.randomUUID().toString()); 
	redisTemplate.opsForValue().set(correlationData.getId(),message);
	Map<String, String> map = new HashMap<>(); 
	map.put("exchange", exchange); 
	map.put("routingKey", routingKey); 
	map.put("sendMessage", message); 
redisTemplate.opsForHash().putAll(MESSAGE_CONFIRM_+correlationData.getId(),map) 
; 
//携带唯一标识发送消息 
rabbitTemplate.convertAndSend(exchange,routingKey,message,correlationData); 
	} 
}
5)メッセージを送信する

注文ビジネス層の実装を変更する

@Autowired 
private CustomMessageSender customMessageSender;

1.3 seckill注文サービスの在庫の更新

1.3.1非同期注文サービスservice_consume
1)依存関係を追加する
<dependencies> 
<dependency> 
<groupId>com.changgou</groupId> 
<artifactId>changgou_common_db</artifactId> 
<version>1.0-SNAPSHOT</version> 
</dependency> 
<dependency> 
<groupId>org.springframework.cloud</groupId> 
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> 
</dependency> 
<dependency> 
<groupId>com.changgou</groupId> 
<artifactId>changgou_service_order_api</artifactId> 
<version>1.0-SNAPSHOT</version> 
</dependency> 
<dependency> 
<groupId>com.changgou</groupId> 
<artifactId>changgou_service_seckill_api</artifactId> 
<version>1.0-SNAPSHOT</version> 
</dependency> 
<dependency> 
<groupId>com.changgou</groupId> 
<artifactId>changgou_service_goods_api</artifactId> 
<version>1.0-SNAPSHOT</version>
</dependency> 
<dependency> 
<groupId>org.springframework.amqp</groupId> 
<artifactId>spring-rabbit</artifactId> 
</dependency> 
</dependencies> 
2)新しいapplication.yml
server: 
	port: 9022 
spring: 
	jackson: 
		time-zone: GMT+8 
	application: 
		name: sec-consume 
	datasource: 
		driver-class-name: com.mysql.jdbc.Driver 
		url: jdbc:mysql://192.168.200.128:3306/changgou_seckill? 
useUnicode=true&characterEncoding=utf- 
8&useSSL=false&allowMultiQueries=true&serverTimezone=GMT%2b8 
		username: root 
		password: root 
	main: 
		allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册 
	redis: 
		host: 192.168.200.128 
	rabbitmq: 
		host: 192.168.200.128 
	eureka: 
		client: 
			service-url: 
				defaultZone: http://127.0.0.1:6868/eureka 
		instance: 
			prefer-ip-address: true 
feign: 
	hystrix: 
		enabled: true 
	client: 
		config: 
			default: #配置全局的feign的调用超时时间 如果 有指定的服务配置 默认的配置不会生效 
			connectTimeout: 60000 # 指定的是 消费者 连接服务提供者的连接超时时间 是否能连接 
单位是毫秒
			readTimeout: 20000 # 指定的是调用服务提供者的 服务 的超时时间() 单位是毫秒 
#hystrix 配置 
hystrix: 
	command: 
		default: 
			execution: 
				timeout: 
				#如果enabled设置为false,则请求超时交给ribbon控制 
				enabled: true 
				isolation: 
					strategy: SEMAPHORE 
					thread: 
					# 熔断器超时时间,默认:1000/毫秒 
						timeoutInMilliseconds: 20000
3)新しいスタートアップクラスを作成する
@SpringBootApplication 
@EnableDiscoveryClient 
@MapperScan(basePackages = {"com.changgou.consume.dao"}) 
public class OrderConsumerApplication { 
	public static void main(String[] args) { 
		SpringApplication.run(OrderConsumerApplication.class,args); 
	} 
}
1.3.2消費者による手動ACK注文の実装

既存のRabbitMQの知識によれば、メッセージコンシューマーがメッセージを正常に受信すると、それを消費し、メッセージサーバーにメッセージを削除するよう自動的に通知することがわかります。この方法の実現には、消費者の自動応答メカニズムが使用されます。しかし、この方法は非常に安全ではありません。実稼働環境では、メッセージコンシューマーがメッセージを受信すると、自動応答メカニズムを使用するのが非常に安全でないため、メッセージの処理プロセスで予期しない状況が発生し、メッセージが失われる可能性が高くなります。コンシューマーがメッセージを正常に処理した後、メッセージサーバーがメッセージを確実に削除するようにする必要があります。現時点でこの効果を達成するには、自動応答を手動応答に変換する必要があります。メッセージコンシューマがメッセージを処理した後にのみ、メッセージサーバーにメッセージの削除が通知されます。

1)構成ファイルを変更する
rabbitmq: 
	host: 192.168.200.128 
	listener: 
		simple: 
			acknowledge-mode: manual #手动
2)モニタークラスを定義する
@Component 
public class ConsumeListener { 
	@Autowired 
	private SecKillOrderService secKillOrderService; 
	@RabbitListener(queues = RabbitMQConfig.SECKILL_ORDER_KEY) 
	public void receiveSecKillOrderMessage(Channel channel, Message message){ 
	//转换消息 
	SeckillOrder seckillOrder = JSON.parseObject(message.getBody(), 
SeckillOrder.class); 
	//同步mysql订单 
	int rows = secKillOrderService.createOrder(seckillOrder); 
	if (rows>0){ 
		//返回成功通知 
		try { 
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false); 
		} catch (IOException e) { 
			e.printStackTrace();
	} 
	}else{
	//返回失败通知 
	try {
    //第一个boolean true所有消费者都会拒绝这个消息,false代表只有当前消费者拒 
绝 
	//第二个boolean true当前消息会进入到死信队列,false重新回到原有队列中,默
认回到头部 
channel.basicNack(message.getMessageProperties().getDeliveryTag(),false,false); 
	} catch (IOException e) { 
		e.printStackTrace(); 
			} 
		} 
	} 
} 

3)ビジネスレイヤーインターフェイスと実装クラスを定義する

public interface ConsumeService { 
	int handleCreateOrder(SeckillOrder order); 
}
@Service
public class SecKillOrderServiceImpl implements SecKillOrderService { 		 	@Autowired 
	private SeckillGoodsMapper seckillGoodsMapper; 
	@Autowired 
	private SeckillOrderMapper seckillOrderMapper; 
	/**
	* 添加订单 
	* @param seckillOrder 
	* @return 
	*/ 
	@Override 
	@Transactional 
	public int createOrder(SeckillOrder seckillOrder) { 
	int result =seckillGoodsMapper.updateStockCount(seckillOrder.getSeckillId()); 
	if (result<=0){ 
	return result; 
	}
	result =seckillOrderMapper.insertSelective(seckillOrder);
    if (result<=0){ 
    return result;
    }return 1;
数据库字段unsigned介绍 
unsigned-----无符号,修饰int 、char 
ALTER TABLE tb_seckill_goods MODIFY COLUMN stock_count int(11) UNSIGNED DEFAULT NULL COMMENT '剩余库存数';

1.5トラフィックのピーククリッピング

スパイクの同時実行性の高いシナリオでは、1秒あたり数万または数十万のメッセージが生成される可能性があります。メッセージ処理の量に制限がない場合、過剰なメッセージの蓄積によりコンシューマのダウンタイムが発生する可能性があります。機械の状況。したがって、公式Webサイトでは、メッセージコンシューマごとに処理されるメッセージの総数(メッセージグラブの総数を設定することをお勧めします

キャプチャされたメッセージの合計数の値は、設定が大きすぎたり小さすぎたりすると良くありません。小さすぎると、システム全体のメッセージスループット容量が減少し、パフォーマンスが低下します。大きすぎると、メッセージが多すぎてシステム全体のOOMが発生する可能性があります。したがって、公式Webサイトでは、各消費者がこの値を100〜300に設定することを推奨しています。

1)消費者を更新します。

//设置预抓取总数 
channel.basicQos(300);

1.6スパイクレンダリングサービスの注文の実装

1)feignインターフェースを定義する
@FeignClient(name="seckill") 
public interface SecKillOrderFeign { 
	/**
	 * 秒杀下单 
	 * @param time 当前时间段 
	 * @param id 秒杀商品id 
	 * @return 
	 */ 
	 @RequestMapping("/seckillorder/add") 
	 public Result add(@RequestParam("time") String time, @RequestParam("id") Long id); 
	}
2)コントローラを定義する
@Controller 
@CrossOrigin 
@RequestMapping("/wseckillorder")
public class SecKillOrderController { 
	@Autowired 
	private SecKillOrderFeign secKillOrderFeign; 
	/**
		* 秒杀下单 
		* @param time 当前时间段 
		* @param id 秒杀商品id 
		* @return 
		*/ 
		@RequestMapping("/add") 
		@ResponseBody 
		public Result add(String time,Long id){ 
		Result result = secKillOrderFeign.add(time, id); 
		return result; 
		}
	}

2悪意のあるブラシチケットの解決を防ぐ

本番環境のシナリオでは、一部のユーザーが悪意を持って注文をスワイプする可能性があります。システムの場合、このような操作はビジネスエラー、ダーティデータ、およびバックエンドアクセスのプレッシャーを引き起こす可能性があります。

一般に、この問題を解決するには、フロントエンドを制御する必要があり、バックエンドも制御する必要があります。バックエンドの実装は、Redisincrdeアトミック増分によって解決できます。

2.1 スパイクサービスの順序を更新する

2.2アンチウェイト法の実施
//防止重复提交 
private String preventRepeatCommit(String username,Long id) { 
	String redisKey = "seckill_user_" + username+"_id_"+id; 
	long count = redisTemplate.opsForValue().increment(redisKey, 1); 
	if (count == 1){ 
		//设置有效期五分钟 
		redisTemplate.expire(redisKey, 5, TimeUnit.MINUTES); 
		return "success"; 
		}
		if (count>1){
        	return "fail"; 
        	}
        	return "fail";
            }

3 同じ製品の繰り返しのスパイクを防ぐ

3.1 注文ビジネス層の実装を変更する

3.2 daoレイヤーの新しいクエリメソッド
public interface SeckillOrderMapper extends Mapper<SeckillOrder> { 
	/**
	 * 查询秒杀订单信息 
	 * @param username 
	 * @param id 
	 * @return 
	 */ 
	 @Select("select * from tb_seckill_order where user_id=#{username} and seckill_id=#{id}") 
	 SeckillOrder getSecKillOrderByUserNameAndGoodsId(String username, Long id); }

4 秒のキルオーダーインターフェイスが非表示

現時点では、ユーザーがログインしたときにのみ注文できることは保証されていますが、一部の悪意のあるユーザーがログイン後に注文のインターフェースアドレスを推測して、悪意を持って注文をスワイプする方法はありません。したがって、スパイクインターフェイスアドレスを非表示にする必要があります。

ユーザーがクリックして購入するたびに、最初に乱数を生成してredisに保存し、次にユーザーは乱数を携帯してseckillにアクセスして注文を出します。注文インターフェースは、最初にredisから乱数を取得して照合します。マッチが成功した場合、次の注文操作が実行され、マッチが失敗した場合、不正アクセスと見なされます。

4.1乱数ツールクラスを共通プロジェクトに配置する
public class RandomUtil { 
	public static String getRandomString() { 
	int length = 15; 
	String base = "abcdefghijklmnopqrstuvwxyz0123456789"; 
	Random random = new Random(); 
	StringBuffer sb = new StringBuffer(); 
	for (int i = 0; i < length; i++) { 
		int number = random.nextInt(base.length()); 
		sb.append(base.charAt(number)); 
}
	return sb.toString(); 
}
public static void main(String[] args) { 
	String randomString = RandomUtil.getRandomString();
}
4.2 seckillレンダリングサービスは乱数インターフェイスを定義します
/** 
* 接口加密 
* 生成随机数存入redis,10秒有效期 
*/
@GetMapping("/getToken") 
@ResponseBody
public String getToken(){ 
	String randomString = RandomUtil.getRandomString(); 
	String cookieValue = this.readCookie(); 
    redisTemplate.boundValueOps("randomcode_"+cookieValue).set(randomString,10, TimeUnit.SECONDS);
	return randomString; 
	}
//读取cookie private String readCookie(){
	HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 
	String cookieValue = CookieUtil.readCookie(request, "uid").get("uid"); 	 
	return cookieValue; 
}
4.3 jsの変更

jsオーダーメソッドを変更する

//秒杀下单 
add:function(id){
	app.msg ='正在下单'; 
	//获取随机数 
	axios.get("/api/wseckillorder/getToken").then(function (response) { 
	var random=response.data; 
	axios.get("/api/wseckillorder/add? time="+moment(app.dateMenus[0]).format("YYYYMMDDHH")+"&id="+id+"&random="+random ).then(function (response) { 
	if (response.data.flag){ 
	app.msg='抢单成功,即将进入支付!'; 
	}else{app.msg='抢单失败'; 
		}
    })
 }) 
}

4.4 seckillレンダリングサービスの変更

スパイクレンダリングサービスの注文インターフェイスを変更する

/** 
 * 秒杀下单 
 * @param time 当前时间段 
 * @param id 秒杀商品id 
 * @return 
 */ 
 @RequestMapping("/add") 
 @ResponseBody 
 public Result add(String time,Long id,String random){ 
 //校验密文有效 
 String randomcode = (String) redisTemplate.boundValueOps("randomcode").get(); 	if (StringUtils.isEmpty(randomcode) || !random.equals(randomcode)){ 
 	return new Result(false, StatusCode.ERROR,"无效访问"); 
 }
 	Result result = secKillOrderFeign.add(time, id);
    return result; 
   }

単一インターフェースの現在の制限を終了するための5

seckillの特別なビジネスシナリオのため、本番シナリオでは、過剰な要求がバックエンドサーバーに入らないように、seckill注文インターフェイスのアクセスフローを制御する必要がある場合があります。電流制限の実装については、nginxとゲートウェイの電流制限による電流制限についてはすでに触れました。しかし、それらはすべて大きなサービスへのアクセスを制限しているので、特定のサービスのインターフェースメソッドのみを制限したい場合はどうでしょうか。実装には、Googleが提供するguavaツールキットのRateLimiterを使用することをお勧めします。その内部は、現在の制限計算のためのトークンバケットアルゴリズムに基づいています。

1)依存関係を追加する
<dependency> 
	<groupId>com.google.guava</groupId> 
	<artifactId>guava</artifactId> 
	<version>28.0-jre</version> 
</dependency>
2)カスタム電流制限アノテーション
@Documented
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME)
public @interface AccessLimit {}
3)カスタムアスペクト
@Component 
@Scope 
@Aspect 
public class AccessLimitAop { 
	@Autowired 
	private HttpServletResponse httpServletResponse; 
	private RateLimiter rateLimiter = RateLimiter.create(20.0); 		  		@Pointcut("@annotation(com.changgou.webSecKill.aspect.AccessLimit)") 
	public void limit(){} @Around("limit()") 
	public Object around(ProceedingJoinPoint proceedingJoinPoint){ 
	boolean flag = rateLimiter.tryAcquire(); 
	Object obj = null; 
	try{
		if (flag){ 
		obj=proceedingJoinPoint.proceed(); 
		}else{
		String errorMessage = JSON.toJSONString(new Result(false,StatusCode.ERROR,"fail")); 	  		
		outMessage(httpServletResponse,errorMessage); 
		} 
		}catch (Throwable throwable) { throwable.printStackTrace(); 
		}return obj; 
		}
private void outMessage(HttpServletResponse response, String errorMessage) { 	ServletOutputStream outputStream = null; 
	try {
	response.setContentType("application/json;charset=UTF-8");
    outputStream = response.getOutputStream();
    outputStream.write(errorMessage.getBytes("UTF-8")); 
    } catch (IOException e) { 
    e.printStackTrace(); 
    }finally { 
    try {outputStream.close(); 
    } catch (IOException e) { 
    e.printStackTrace();
    }
  }
4)カスタムの電流制限アノテーションを使用する

自分の意見を見て、書いてください。一緒に議論してください!

最後に、最近、多くの友人からLinuxラーニングロードマップの提供を求められたため、私の経験に基づいて、1か月間余暇を過ごし、電子書籍を作成しました。面接であろうと自己改善であろうと、私はそれがあなたを助けると信じています!ディレクトリは次のとおりです。

みんなに無料でプレゼントして、いいね!

電子ブック| Linux開発ラーニングロードマップ

この電子書籍をより完璧にするために、何人かの友人が私に加わってくれることを願っています。

利得?古いアイアンに3ストライクのコンボを付けて、より多くの人がこの記事を読めるようにしてほしい

推奨読書:

おすすめ

転載: blog.csdn.net/yychuyu/article/details/108477629