主頁 > 作業系統 > 秒殺系統后臺實作詳解

秒殺系統后臺實作詳解

2020-09-10 00:15:30 作業系統

秒殺后臺實作

本文主要講解專案實戰中秒殺如何解決下面問題:

1)實作秒殺異步下單,掌握如何保證生產者&消費者訊息不丟失

2)實作防止惡意刷單

3)實作防止相同商品重復秒殺

4)實作秒殺下單介面隱藏

5)實作下單介面限流

1 秒殺異步下單

用戶在下單的時候,需要基于JWT令牌資訊進行登陸人資訊認證,確定當前訂單是屬于誰的,

針對秒殺的特殊業務場景,僅僅依靠物件快取或者頁面靜態化等技術去解決服務端壓力還是遠遠不夠,

對于資料庫壓力還是很大,所以需要異步下單,異步是最好的解決辦法,但會帶來一些額外的程式上的

復雜性,

1.1 秒殺服務-下單實作
1)將tokenDecode工具類config放入秒殺服務并宣告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)新建下單controller并宣告方法
@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) 新建service介面
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)秒殺下單業務層實作

業務邏輯:

獲取秒殺商品資料與庫存量資料,如果沒有庫存則拋出例外執行redis預扣減庫存,并獲取扣減之后的庫存值如果扣減完的庫存值<=0, 則洗掉redis中對應的商品資訊與庫存資訊基于mq異步方式完成與mysql資料同步(最終一致性)

注意:庫存資料從redis中取出,轉換成String

@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=https://www.cnblogs.com/yychuyu/p/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持久化: 交換機持久化 佇列持久化 訊息持久化

但是如果僅僅只是開啟這兩部分的持久化,也很有可能造成訊息丟失,因為訊息服務器很有可能在持久化的程序中出現宕機,因此需要通過資料保護機制來保證訊息一定會成功進行持久化,否則將一直進行訊息發送,

事務機制 
	事務機制采用類資料庫的事務機制進行資料保護,當訊息到達訊息服務器,首先會開啟一個事務,接著進 行資料磁盤持久化,只有持久化成功才會進行事務提交,向訊息生產者回傳成功通知,訊息生產者一旦接收成 功通知則不會再發送此條訊息,當出現例外,則回傳失敗通知.訊息生產者一旦接收失敗通知,則繼續發送該 條訊息,
	事務機制雖然能夠保證資料安全,但是此機制采用的是同步機制,會產生系統間訊息阻塞,影響整個系統 的訊息吞吐量,從而導致整個系統的性能下降,因此不建議使用, 
	confirm機制 
		confirm模式需要基于channel進行設定, 一旦某條訊息被投遞到佇列之后,訊息佇列就會發送一個確 認資訊給生產者,如果佇列與訊息是可持久化的, 那么確認訊息會等到訊息成功寫入到磁盤之后發出. 			confirm的性能高,主要得益于它是異步的.生產者在將第一條訊息發出之后等待確認訊息的同時也可以 繼續發送后續的訊息.當確認訊息到達之后,就可以通過回呼方法處理這條確認訊息. 如果MQ服務宕機了,則會 回傳nack訊息. 生產者同樣在回呼方法中進行后續處理,
1.2.1 開啟confifirm機制
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 = https://www.cnblogs.com/yychuyu/p/new 
CorrelationData(UUID.randomUUID().toString()); 
	redisTemplate.opsForValue().set(correlationData.getId(),message);
	Map 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 秒殺下單服務更新庫存

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 流量削峰

在秒殺這種高并發的場景下,每秒都有可能產生幾萬甚至十幾萬條訊息,如果沒有對訊息處理量進行任何限制的話,很有可能因為過多的訊息堆積從而導致消費者宕機的情況,因此官網建議對每一個訊息消費者都設定處理訊息總數(訊息抓取總數),

訊息抓取總數的值,設定過大或者過小都不好,過小的話,會導致整個系統訊息吞吐能力下降,造成性能浪費,過大的話,則很有可能導致訊息過多,導致整個系統OOM,因此官網建議每一個消費者將該值設定在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
@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,接著用戶攜帶著這個亂數去訪問秒殺下單,下單介面首先會從redis中獲取該亂數進行匹配,如果匹配成功,則進行后續下單操作,如果匹配不成功,則認定為非法訪問,

4.1 將亂數工具類放入common工程中
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秒殺渲染服務定義亂數介面
/** 
* 介面加密 
* 生成亂數存入redis,10秒有效期 
*/
@GetMapping("/getToken") 
@ResponseBody
public String getToken(){ 
	String randomString = RandomUtil.getRandomString(); 
	String cookieValue = https://www.cnblogs.com/yychuyu/p/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 秒殺渲染服務更改

修改秒殺渲染服務下單介面

/** 
 * 秒殺下單 
 * @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 秒殺下單介面限流

因為秒殺的特殊業務場景,生產場景下,還有可能要對秒殺下單介面進行訪問流量控制,防止過多的請求進入到后端服務器,對于限流的實作方式,我們之前已經接觸過通過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

有識訓?希望老鐵們來個三連擊,給更多的人看到這篇文章

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/169.html

標籤:Linux

上一篇:Ubuntu 18.04 VIM 配置

下一篇:Kubernetes K8S在IPVS代理模式下Service服務的ClusterIP型別訪問失敗處理

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more