The background,

In the process of system development, we sometimes encounter the situation of invoking third-party interfaces. Due to the uncertainty of the third-party interface, a third-party company may upgrade the invoked interface after it is online for a period of time, and the original interface is no longer maintained. In this case, you need to modify the code internally to call the new interface of the third party. There are the following problems when calling the new interface:

1, the new interface is stable, how to test 2, the new interface returned data structure does not agree with the old interface 3, how to deal with the original interface flow, switch to the new interface directly out of the question how to do 4, verification of the differences between the old and new interface, namely under the condition of the same request parameters, return value of old and new interface public fields are consistentCopy the code

Using abTest for grayscale publishing solves these four problems.

2. Basic concepts of ABTest

A/B test is A comparative analysis method, which determines the feasibility and effectiveness of the strategy represented by the experiment by subdividing the flow and conducting random experiments, and monitoring and tracking the experimental results. A/B testing addresses the problem of policy optimization, that is, finding the optimal policy from multiple alternative policies. Common application scenarios include:

  • Grayscale publishing: Technique & Algorithm iteration
  • Function optimization: interface module, style, interaction mode, etc
  • Content optimization: promotion posters, landing pages, content modules, copywriting, etc
  • Operation optimization: operation strategy, communication skills, etc

Best practices

3.1 Service Scenarios

Back to the four questions in the background, assume that a third party has an API1 interface for returning person information. The api2 interface was then redeveloped by a third-party company to implement the functions of the api1, returning additional information based on the api1 and indicating that the api1 will not be maintained.

Graph of TD internal system - > | | the original call api1 internal system - > | | new call api2

The internal system then needs to modify the original code to replace all parts of API1 with API2. The easiest way to do this is to modify the code directly, as shown below.

Person person = api1(); / / the old interface -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- PersonNew PersonNew = api2 (); Person person = convert(personNew);Copy the code

And go straight online. But there are several problems with this.

1. The new interface provided by a third party may be unstable. As it is a third party interface, there is no internal way to ensure that the interface is not faulty. In particular, if the traffic calling this third-party interface is heavy, switching to API2 may directly cause online accidents. This situation is unacceptable in Internet companies. 2. When calling api2, there is no way to know whether the result returned by the new interface is exactly the same as that returned by the old interface. Although the third-party enterprise promises to be fully compatible with API1, from the perspective of stability, internal api2 needs to be verified online.Copy the code

Therefore, considering these two problems, we can use AbTest to perform grayscale, already flow playback.

3.2 Solution

Using ABtest, we can solve the above two problems perfectly. The core idea of ABtest is to randomly extract part of the large flow for test. Therefore, according to this idea, we can extract a small amount of traffic from the total traffic to call BOTH API1 and API2, and monitor the success rate of API2, corresponding time, 95 lines, 99 lines and other indicators during the call process to verify the stability of API2. At the same time, because the parameters are the same, we can compare the returns of API1 and API2 and record the difference of the return results of the two interfaces. To verify that the new interface is fully compatible with the old interface.

If api2 interface is found to be unstable or the result is inconsistent with API1 during the verification process, the third-party company can be notified to repair the interface, and the traffic of API2 can be gradually increased under the condition that api2 stability and return results meet the expectations. Finally, the volume of API2 reached 100%, and the switching work of API1 was smoothly completed.

3.3 Code Simulation

During the experiment, a OuterApi class is created to represent a third-party interface with two methods, API1 and API2. Each method passes in an idCard that returns specific person information based on the id number. In API1 and API2, a random number is used to set the age of the person information returned each time, thus simulating the different returns of each interface. In addition to returning all attributes of API1 China, the gender of the person has been added in API2 to represent the added attributes section of the new interface.

public class OuterApi { private Random random = new Random(); public Person api1(String idCard) { Person person = new Person(); Person. Elegantly-named setName (" zhang "); If (random.nextdouble () < 0.5) {person.setage ("13"); } else { person.setAge("15"); } person.setIdCard(idCard); return person; } public PersonNew api2(String idCard) { PersonNew personNew = new PersonNew(); PersonNew. Elegantly-named setName (" zhang "); If (random.nextDouble() < 0.5) {personnew.setage ("13"); } else { personNew.setAge("15"); } personNew.setSex("1"); personNew.setIdCard(idCard); return personNew; }}Copy the code

An AbTestHelper class was created to implement grayscale and flow playback capabilities. When grayscale is implemented, a configuration item is required to determine the percentage of grayscale flow and whether to enable the flow playback function. The following describes the configuration items. The meanings are described in the comments. In real production environments, this configuration is usually stored in the configuration center or in the cache such as Redis. During simulation experiments, this configuration is directly displayed in a file named abtest.properties.

GrayConfig = 100; grayConfig = 100; grayConfig = 100; 0-49Copy the code

The code for the AbTestHelper class is shown below.

package abTest; import java.io.InputStream; import java.util.Comparator; import java.util.Properties; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; Public class AbTestHelper<T, R> {private Comparator<T> comparable; Private BiConsumer<T, T> diffCallBack; Private Function<R, T> convert; public AbTestHelper(Comparator<T> comparable, BiConsumer<T, T> diffCallBack, Function<R, T> convert) { this.comparable = comparable; this.diffCallBack = diffCallBack; this.convert = convert; } /** * abest comparison ** @param aSupplier * @param bSupplier new interface * @param grayKey grayscale key * @return */ public T doABTest(Supplier<T> aSupplier, Supplier<R> bSupplier, int grayKey) { GrayConfig grayConfig = new GrayConfig(getConfig("grayConfig")); T result = null; R tmpResult = null; boolean hitGrayKey = false; if (grayConfig.hitGray(grayKey)) { hitGrayKey = true; tmpResult = bSupplier.get(); System.out.println(" hit grayscale "); } else { result = aSupplier.get(); System.out.println(" missing grayscale "); If (hitGrayKey) {result = convert.apply(tmpResult); If (Boolean. ParseBoolean (getConfig("trafficPlaybackSwitch"))) {flowPlayBack(hitGrayKey, aSupplier, bSupplier, result); } return result; } private static String getConfig(String key) { String value; try { Properties properties = new Properties(); InputStream in = AbTestHelper.class.getClassLoader().getResourceAsStream("abTest.properties"); properties.load(in); value = properties.getProperty(key); } catch (Exception e) { value = null; // ignore } return value; } /** * Generally, traffic playback is placed in a thread pool for asynchronous execution, Avoid affecting the main process response * * @param hitGrayKey * @param aSupplier * @param bSupplier * @param result */ private void flowPlayBack(boolean hitGrayKey, Supplier<T> aSupplier, Supplier<R> bSupplier, T result) { if (hitGrayKey) { T aResult = aSupplier.get(); if (comparable.compare(aResult, result) ! = 0) { diffCallBack.accept(aResult, result); } } else { R bResult = bSupplier.get(); T tmpResult = convert.apply(bResult); if (comparable.compare(result, tmpResult) ! = 0) { diffCallBack.accept(result, tmpResult); }}}}Copy the code

A helper class is created each time a third-party API is called. When you create this class, you pass in comparators, feedback functions, converters, and so on. The new and old interfaces and grayscale keys need to be passed in during the final call. Grayscale strategy uses grayKey % totalPercent in the code to determine which ids call the new interface.

And finally, a total simulation entry,

package abTest; import java.util.Random; import org.apache.commons.lang3.StringUtils; public class MainTest { public static void main(String[] args) throws InterruptedException { OuterApi api = new OuterApi(); AbTestHelper<Person, PersonNew> helper = new AbTestHelper<>((person, person1) -> { if (person == null || person1 == null) { return -1; } if (StringUtils.equals(person.getAge(), person1.getAge()) && StringUtils.equals(person.getIdCard(), person1.getIdCard()) && StringUtils.equals(person.getName(), person1.getName())) { return 0; } return -1; }, (aResult, bResult) -> {// When the two results are not the same, it is usually reported by burying the point. Println (" the old and new interfaces return different values, the age of the original interface: "+ aresult.getage () +", the age of the new interface: "+ bresult.getage ()); }, personNew -> { Person person = new Person(); person.setIdCard(personNew.getIdCard()); person.setAge(personNew.getAge()); person.setName(personNew.getName()); return person; }); Random random = new Random(); while (true) { String idCard = random.nextInt(100) + ""; Person result = helper.doABTest(() -> api.api1(idCard), () -> api.api2(idCard), Integer.parseInt(idCard)); System.out.println(result.toString()); System.out.println("=========="); Thread.sleep(1000); }}}Copy the code

Pass in a random id number and return the specified object based on the id number. The simulation results are shown below.

Full code reference: github.com/Tyoukai/tec…