Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Examples of the usage of four sharp tools in micro-service architecture

2025-01-20 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/01 Report--

This article mainly introduces the usage examples of the four sharp tools in the micro-service architecture, which has a certain reference value. Interested friends can refer to it. I hope you will gain a lot after reading this article.

Overview

Today, with the development of Internet applications, from single application architecture to SOA and today's micro-services, with the continuous upgrading and evolution of micro-services, the stability between services and services is becoming more and more important. the main reason for the complexity of distributed systems is that distributed systems need to take into account the delay and unreliability of the network. one of the important characteristics of micro-services is the need to ensure service idempotency. The premise of ensuring idempotence is that distributed locks control concurrency. At the same time, caching, degradation and current limitation are the three sharp tools to protect the stability of micro-service systems.

With the continuous development of business, there are more and more subsystems divided by business domain. Each business system needs caching, current limiting, distributed locking, idempotent and other tool components. Distributed-tools components (not yet open source) formally contain the basic functional components needed by the above distributed system. The distributed-tools component provides two springboot starter based on tair and redis, which is very easy to use. Take caching and redis as an example, application.properties adds the following configuration: redis.extend.hostName=127.0.0.1

Redis.extend.port=6379

Redis.extend.password=pwdcode

Redis.extend.timeout=10000

The next part of redis.idempotent.enabled=true will focus on the use of caching, current limiting, distributed locking, idempotency, etc.

Caching

The use of cache can be said to be ubiquitous. From the access path of application requests, users user-> browser cache-> reverse proxy cache-> WEB server cache-> application cache-> database cache, etc., almost every link is full of cache use. The most straightforward explanation of cache is the "space for time" algorithm. Caching is the temporary storage of some data in some places, either in memory or on a hard disk. In short, the goal is to avoid some time-consuming operations. Our common time-consuming operations, such as database queries, some data calculation results, or to reduce the pressure on the server. In fact, to reduce the pressure is also due to query or calculation, although short and time-consuming, but the operation is very frequent, the total is also very long, resulting in serious queuing and other situations, the server can not resist.

The distributed-tools component provides a CacheEngine interface, which has different implementations based on Tair and Redis. The specific CacheEngine definition is as follows: public String get (String key)

/ * *

* get the object corresponding to the specified key, and the exception will also return null

*

* @ param key

* @ param clazz

* @ return

, /

Public T get (String key, Class clz)

/ * *

* store cached data and ignore expiration time

*

* @ param key

* @ param value

* @ return

, /

Public booleanput (String key, T value)

/ * *

* storing cached data

*

* @ param key

* @ param value

* @ param expiredTime

* @ param unit

* @ return

, /

Public booleanput (String key, T value, int expiredTime, TimeUnit unit)

/ * *

* Delete cached data based on key

*

* @ param key

* @ return

, /

Publicbooleaninvalid (String key)

The get method queries against key, put stores cached data, and invalid deletes cached data.

Current limit

In the distributed system, especially in the face of some second-kill and instantaneous high concurrency scenarios, some current-limiting measures are needed to ensure the high availability of the system. Generally speaking, the purpose of current restriction is to protect the system by imposing a speed limit on concurrent visits / requests, or by imposing a speed limit on requests within a time window. Once the speed limit is reached, you can deny service (direct to the error page or tell you that the resource is gone), queue or wait (such as seconds kill, comment, place an order), and downgrade (return bottom data or default data, for example, the product details page is in stock by default).

Some common current limiting algorithms include fixed window, sliding window, leaky bucket and token bucket. At present, distributed-tools component only implements fixed window algorithm based on counter. The specific usage is as follows: / * *

* specify self-increment counter for expiration time. Default is + 1 per time, non-sliding window.

*

* @ param key counter increments key

* @ param expireTime expiration time

* @ param unit time unit

* @ return

, /

PubliclongincrCount (String key, int expireTime, TimeUnit unit)

/ * *

* specify the auto-increment counter of expiration time. If the maximum value of rateThreshold per unit time is exceeded, true will be returned, otherwise false will be returned.

*

* @ param key current limit key

* @ param rateThreshold current limit threshold

* @ param expireTime fixed window time

* @ param unit time unit

* @ return

, /

PublicbooleanrateLimit (final String key, finalint rateThreshold, int expireTime, TimeUnit unit); rateLimit method based on CacheEngine can realize current limit, and expireTime can only set fixed window time and non-sliding window time. In addition, the distributed-tools component provides the template RateLimitTemplate to simplify the ease of use of current limit, and you can directly call the execute method of RateLimitTemplate to deal with the problem of current limit. / * *

* @ param limitKey current limit KEY

* @ param resultSupplier callback method

* @ param rateThreshold current limit threshold

* @ param limitTime restricted time period

* @ param blockDuration blocking period

* @ param unit time unit

* @ param errCodeEnum specifies current limit error code

* @ return

, /

Public T execute (String limitKey, Supplier resultSupplier, long rateThreshold, long limitTime

Long blockDuration, TimeUnit unit, ErrCodeEnum errCodeEnum) {

Boolean blocked = tryAcquire (limitKey, rateThreshold, limitTime, blockDuration, unit)

If (errCodeEnum! = null) {

AssertUtils.assertTrue (blocked, errCodeEnum)

} else {

AssertUtils.assertTrue (blocked, ExceptionEnumType.ACQUIRE_LOCK_FAIL)

}

Return resultSupplier.get ()

} in addition, the distributed-tools component also provides the use of the annotation @ RateLimit. The annotation RateLimit is defined as follows: @ Retention (RetentionPolicy.RUNTIME)

@ Target (ElementType.METHOD)

@ Documented

Public @ interface RateLimit {

/ * *

* current-limited KEY

, /

String limitKey ()

/ * *

* number of visits allowed. Default is MAX_VALUE.

, /

LonglimitCount () default Long.MAX_VALUE

/ * *

* time period

, /

LongtimeRange ()

/ * *

* blocking period

, /

LongblockDuration ()

/ * *

* Unit of time. Default is seconds.

, /

TimeUnit timeUnit () default TimeUnit.SECONDS

} the code for flow restriction based on annotations is as follows: @ RateLimit (limitKey = "# key", limitCount = 5, timeRange = 2, blockDuration = 3, timeUnit = TimeUnit.MINUTES)

Public String testLimit2 (String key) {

.

Return key

} adding the above annotations to any method has a certain current-limiting capability (the specific method needs to be within the specified interception range of spring aop). For example, the above code indicates that the parameter key is used as the current-limiting key, with no more than 5 requests every 2 minutes, and blocking for 3 minutes after exceeding the limit. Distributed lock

Through synchronized keyword and ReentrantLock reentrant lock in a single process of Java, concurrent access to resources can be controlled in a multithreaded environment. Usually, local locking can not meet our needs. We are more faced with distributed system cross-process locks, referred to as distributed locks. The implementation method of distributed lock is usually to store the lock tag in memory, but the memory is not the memory allocated by a process but the common memory such as Redis and Tair. As for the implementation of locking using database, file and so on, it is the same as that of a stand-alone machine, as long as it ensures that the tag can be mutually exclusive. The main reason why distributed locks are more complex than those of stand-alone processes is that distributed systems need to take into account the delay and unreliability of the network.

Distributed locks provided by distributed-tools components should have the following characteristics:

Mutual exclusion: it is as exclusive as local locks, but distributed locks need to ensure the mutual exclusion of different threads in different node processes.

Reentrancy: if the same thread on the same node acquires the lock, it can also acquire the lock again.

Lock timeout: lock timeout is supported like local locks to prevent deadlock, refresh expiration time through asynchronous heartbeat demon thread, and prevent deadlock in special scenarios (such as FGC deadlock timeout).

High performance and high availability: locking and unlocking requires high performance, as well as high availability to prevent distributed lock failures, which can increase degradation.

Support for blocking and non-blocking: lock and trylock as well as tryLock (long timeOut) are supported like ReentrantLock.

Fair locks and unfair locks (not supported): fair locks are acquired in the order in which locks are requested, whereas unfair locks are unordered. Currently, distributed locks provided by distributed-tools components do not support this feature.

The distributed lock provided by the distributed-tools component is very easy to use. It provides a distributed lock template: DistributedLockTemplate, which can directly call the static method provided by the template (such as below): / * *

* distributed lock processing template executor

*

* @ param lockKey distributed lock key

* @ param resultSupplier distributed lock processing callback

* @ param waitTime lock waiting time

* @ param unit time unit

* @ param errCodeEnum specifies that a special error code is returned

* @ return

, /

Public static T execute (String lockKey, Supplier resultSupplier, long waitTime, TimeUnit unit

ErrCodeEnum errCodeEnum) {

AssertUtils.assertTrue (StringUtils.isNotBlank (lockKey), ExceptionEnumType.PARAMETER_ILLEGALL)

Boolean locked = false

Lock lock = DistributedReentrantLock.newLock (lockKey)

Try {

Locked = waitTime > 0? Lock.tryLock (waitTime, unit): lock.tryLock ()

} catch (InterruptedException e) {

Throw new RuntimeException (String.format ("lock error,lockResource:%s", lockKey), e)

}

If (errCodeEnum! = null) {

AssertUtils.assertTrue (locked, errCodeEnum)

} else {

AssertUtils.assertTrue (locked, ExceptionEnumType.ACQUIRE_LOCK_FAIL)

}

Try {

Return resultSupplier.get ()

} finally {

Lock.unlock ()

}

}

Idempotent

Idempotent design is very important in distributed system design, especially in complex micro-services, a system contains multiple subsystem services, and one subsystem service often invokes another service, and the service invocation service is nothing more than using RPC communication or restful. Network delay or interruption in distributed systems is inevitable, which usually leads to service call layer triggering retry. Interfaces with this nature are always designed with the idea that when an exception occurs in the calling interface and repeated attempts, it will always cause unbearable losses to the system, so this phenomenon must be prevented.

Idempotents usually have two dimensions:

1. The idempotent in the spatial dimension, that is, the scope of idempotent objects, is an individual or an institution, a transaction or a certain type of transaction.

two。 The idempotent in the dimension of time, that is, the guaranteed time of idempotence, is a few hours, a few days or permanent. In the actual system, there are many operations, no matter how many times, should produce the same effect or return the same result. The following application scenarios are also common:

1. When the frontend repeatedly submits the request and the request data is the same, the backend needs to return the same result corresponding to the request.

two。 When initiating a payment request, the payment center should only deduct money from the user's account once, and only once when the network is interrupted or the system is abnormal.

3. Send a message, and send a text message with the same content to the user only once.

4. To create a business order, you can only create one business request at a time. If you retry to create more than one request, there will be a big problem.

5. Idempotent processing of messages based on msgId. Before formally using the idempotents provided by distributed-tools components, let's take a look at the design of distributed-tools idempotent components.

Idempotent key extraction ability: obtain unique idempotent key

Idempotent key extraction support 2 annotations: IdempotentTxId, IdempotentTxIdGetter, any method to add the above 2 annotations, you can extract the relevant idempotent key, the premise is that you need to add Idempotent annotations to the relevant idempotent methods.

If you only use idempotent templates for business processing, you need to set the relevant idempotent key and ensure its uniqueness.

Distributed locking service capabilities: provide global locking and unlocking capabilities

Distributed-tools idempotent components need to use the distributed lock function provided by themselves to ensure its concurrency uniqueness. The distributed lock provided by distributed-tools can provide its reliable and stable locking and unlocking ability.

High-performance write and query capabilities: query and storage for idempotent results

Distributed-tools idempotent components provide storage implementation based on tair and redis, and support custom primary and secondary storage to IdempotentService through spring dependency injection. It is recommended that distributed-tools idempotent storage results first-level storage tair mdb, second-level storage ldb or tablestore, first-level storage to ensure its high performance, secondary storage to ensure its reliability.

Secondary storage parallel queries return the fastest idempotent results of the query.

Secondary storage writes asynchronously in parallel to further improve performance. Highly available idempotent writing and query capabilities: idempotent storage exception, does not affect the normal business process, and increases fault tolerance

Distributed-tools idempotent components support secondary storage. In order to ensure its high availability, after all, the failure probability of secondary storage is too low to cause business unavailability. If secondary storage fails at the same time, some fault tolerance will be done in business. Retry strategy is adopted for uncertain exceptions, and specific idempotent methods will be implemented. The first-level storage is isolated from the write and query processing of the second-level storage, and the exception of any one-level storage will not affect the overall business execution. After understanding the idempotency of distributed-tools components, let's take a look at how to use idempotent components. Let's first learn about the idempotent annotations provided by common-api. The specific idempotent annotations are used as follows:

The idempotent interceptor gets the priority of idempotent ID: it first determines whether the spelKey attribute of Idempotent is empty, and if it is not null, it generates idempotent ID according to the spring expression defined by spelKey. Secondly, it determines whether the parameter contains IdempotentTxId annotations. If there is an IdempotentTxId, the parameter value will be directly obtained to generate idempotent ID. Again, use reflection to get whether the parameter object attribute contains IdempotentTxId annotations. If the object attribute contains IdempotentTxId annotations, it will get the parameter object attribute to generate idempotent ID. Finally, the idempotent ID has not been obtained in the above three cases, and it will further obtain whether the Method of the parameter object defines the IdempotentTxIdGetter annotation through reflection, and if the annotation is included, idempotent ID will be generated through reflection. Code usage example: @ Idempotent (spelKey = "# request.requestId", firstLevelExpireDate = 7 secondLevelExpireDate = 30)

Publicvoidexecute (BizFlowRequest request) {

.

} as shown in the above code, requestId is obtained from request as idempotent key. The first-level storage is valid for 7 days and the second-level storage is valid for 30 days. In addition to using idempotent annotations for distributed-tools, idempotent components also provide a general idempotent template IdempotentTemplate. If you use idempotent templates, you must set tair.idempotent.enabled=true or redis.idempotent.enabled=true, which defaults to false. At the same time, you need to specify first-level storage of idempotent results, and idempotent results are stored as optional configurations. The specific method of using idempotent template IdempotentTemplate is as follows: / * *

* idempotent template processor

*

* @ param request idempotent Request information

* @ param executeSupplier idempotent processing callback function

* @ param resultPreprocessConsumer idempotent result callback function can do some preprocessing to the result

* @ param ifResultNeedIdempotence can provide this parameter not only according to the exception but also to determine whether idempotence is needed according to the result.

* @ return

, /

Public R execute (IdempotentRequest

Request, Supplier executeSupplier

Consumer resultPreprocessConsumer, Predicate ifResultNeedIdempotence) {

.

} request:

Idempotent parameter IdempotentRequest assembly, you can set idempotent parameters and idempotent unique ID. ExecuteSupplier:

The specific idempotent method logic, such as the interface for payment and placing an order, can be handled through the JDK8 functional interface Supplier Callback. ResultBiConsumer:

Idempotent returns the result of the processing, the parameter can be empty, if empty to take the default processing, according to the idempotent result, if successful, non-retry exception error code, directly return the result, if failed, you can retry the exception error code, will retry processing. If the parameter value is not empty, you can set ResultStatus for returning idempotent results (ResultStatus contains three states, including success, failure retry, and failure non-retry). Thank you for reading this article carefully. I hope the article "examples of the usage of the four sharp weapons in the micro-service architecture" shared by the editor will be helpful to everyone. At the same time, I also hope that you will support and pay attention to the industry information channel. More related knowledge is waiting for you to learn!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report