Optimization of jixinda short message channel service

Keywords: Java

Why optimize

When we need to connect with a new SMS service provider, what do we need to do?
1. Add a toolkit for a new third-party platform under sms
2. Add a new judgment in our case, judge the platfrom field and call the new toolkit. We also need to manually encapsulate the smsConfig class required by each channel
3. In the process of our query, we have to query the contents of the channel table every time, the contents of the channel signature table every time, and the contents of the channel template table every time. The performance is not high
We can package the query part into our toolkit and pass in the template code and signature code of jixinda

How to optimize

Space for time: can we read some information into memory after the project starts, so that we don't have to query the database every time
The lost space is memory, and time is the efficiency of code execution. Space for time refers to reading some information into memory first, and then each reading is not an IO operation on the disk, but an operation on memory

What information do we need to initialize

How to optimize

Space for time: can we read some information into memory after the project starts, so that we don't have to query the database every time
The lost space is memory, and time is the efficiency of code execution. Space for time refers to reading some information into memory first, and then each reading is not an IO operation on the disk, but an operation on memory

1. Information in channel table

Scheme 1: we read the information in config into our memory and create a List configs,
Then, when we call, we traverse the ids passed by the api service to determine which channel to use to construct the tool class corresponding to the third party

No, we also need to create our tool class when sending, and use this tool class to call the specific implementation logic. We should instantiate our tool class when building and save it in memory

Scheme 2: we first read the contents of config in mysql and instantiate the tool class of the channel into our memory according to the contents of congfig
How to implement: create objects and encapsulate attributes through reflection

Look at the Demo
Two classes, man person, are provided

@Data
public class Person {

    private String name;

    private Integer age;
}

@Data
public class Man extends Person{

    private String sex;

    private String play;

    public String toString(){
        return "name:"+super.getName()+",age:"+super.getAge()+",sex:"+this.sex+",play:"+this.play;
    }
}
public static void main(String[] args) {
        try {
            String classPath = "cn.itheima.reflex.pojo.Man";
            Class classes = ReflexDemo1.class.getClassLoader().loadClass(classPath);
            Object o = classes.newInstance();
            Method[] declaredMethods = classes.getDeclaredMethods();
            //Traverse all methods
            for (Method indexMethod : declaredMethods) {
                if (indexMethod.getName().equals("getName")) {
                    System.out.println("prove getName Method exists");
                }
                if (indexMethod.getName().equals("getAge")) {
                    System.out.println("prove getAge Method exists");
                }
                if (indexMethod.getName().equals("getSex")) {
                    System.out.println("prove getSex Method exists");
                }
                if (indexMethod.getName().equals("getPlay")) {
                    System.out.println("prove getPlay Method exists");
                }
            }
            Method getName = classes.getMethod("getName", null);
            Method getAge = classes.getMethod("getAge", null);
            Method getSex = classes.getMethod("getSex", null);
            Method getPlay = classes.getMethod("getPlay", null);
            if (getName == null) {
                System.out.println("getName non-existent");
            }
            if (getAge == null) {
                System.out.println("getAge non-existent");
            }
            if (getSex == null) {
                System.out.println("getSex non-existent");
            }
            if (getPlay == null) {
                System.out.println("getPlay non-existent");
            }
            //Encapsulation properties
            Method setName = classes.getMethod("setName", String.class);
            setName.invoke(o, "Zhang San");
            Method setAge = classes.getMethod("setAge", Integer.class);
            setAge.invoke(o, 20);
            Method setSex = classes.getMethod("setSex", String.class);
            setSex.invoke(o, "male");
            Method setPlay = classes.getMethod("setPlay", String.class);
            setPlay.invoke(o, "Play basketball");
            System.out.println(o);
        } catch (Exception e) {
            System.out.println("System exception");
        }
    }

When we do not provide the get set method
How do we encapsulate parameters
The entity classes provided are as follows:

public class Man extends Person {

    private String sex;

    private String play;

    public String toString(){
        return "name:"+super.name+",age:"+super.age+",sex:"+this.sex+",play:"+this.play;
    }

}

public class Person {

    public String name;

    public Integer age;

}
public static void main(String[] args) {
        try {
            String classPath = "cn.itheima.reflex.demo2.Man";
            Class classes = ReflexDemo2.class.getClassLoader().loadClass(classPath);
            Object o = classes.newInstance();
            //Gets the properties of the object's parent class
            //Gets the property object declared in the class based on reflection
            Field nameField = classes.getSuperclass().getDeclaredField("name");
            Field ageField = classes.getSuperclass().getDeclaredField("age");

            //Encapsulate attribute content
            nameField.set(o,"Zhang San");
            ageField.set(o,20);

            Field sex = classes.getDeclaredField("sex");
            Field play = classes.getDeclaredField("play");
            //Sets the current property value that can be manipulated
            sex.setAccessible(true);
            play.setAccessible(true);
            sex.set(o,"male");
            play.set(o,"Play games");
            System.out.println(o);
        } catch (Exception e) {
            System.out.println("System exception");
        }
    }

be careful:

sex.setAccessible(true);
play.setAccessible(true);

setAccessible means to allow access to private methods. If it is not set, we can't access some fields with access permission set

Then let's look at our business logic

We hope that after the service is started, the information in config will be read out, and the query will be sorted according to the level. According to the information in config, the util class will be automatically encapsulated into the system memory

How to set after service startup

  1. Declare spring Component @ Component
  2. Implement the CommandLineRunner interface and override the run method
    In the run method, we need to read the contents of the config table, read it, and construct the tool class by reflection according to the name of the access platform
public void initConnect() {
        //TODO initializes the bean object of each channel according to the channel configuration

        //1. Query the database to get the channel list
        List<ConfigEntity> configs = configService.listForConnect();
        log.info("Available channels queried:{}",configs);

        List beanList = new ArrayList();
        //2. Traverse the channel list and create Bean objects for each channel through reflection (such as AliyunSmsService, MengWangSmsService, etc.)
        configs.forEach(config -> {
            try {
                //Encapsulates the SmsConfig configuration object required for the Bean object
                SmsConfig smsConfig = new SmsConfig();
                smsConfig.setId(config.getId());
                smsConfig.setDomain(config.getDomain().trim());
                smsConfig.setName(config.getName().trim());
                smsConfig.setPlatform(config.getPlatform().trim());
                smsConfig.setAccessKeyId(config.getAccessKeyId().trim());
                smsConfig.setAccessKeySecret(config.getAccessKeySecret().trim());
                if (StringUtils.isNotBlank(config.getOther())) {
                    LinkedHashMap linkedHashMap = JSON.parseObject(config.getOther(), LinkedHashMap.class);
                    smsConfig.setOtherConfig(linkedHashMap);
                }

                //Dynamically splice the full class name of the bean instance to be created
                String className = "com.itheima.sms.sms." + config.getPlatform().trim() + "SmsService";
                log.info("Prepare to create dynamically by reflection:{}",className);

                Class<?> aClass = Class.forName(className);
                //Gets the constructor object of the class
                Constructor<?> constructor = aClass.getConstructor(SmsConfig.class);
                //Create bean object
                Object beanService = constructor.newInstance(smsConfig);

                //The signatureService and templateService attributes in the bean object need to be assigned
                SignatureServiceImpl signatureService = SpringUtils.getBean(SignatureServiceImpl.class);
                TemplateServiceImpl templateService = SpringUtils.getBean(TemplateServiceImpl.class);

                //Gets the property object declared in the class based on reflection
                Field signatureServiceField = aClass.getSuperclass().getDeclaredField("signatureService");
                Field templateServiceField = aClass.getSuperclass().getDeclaredField("templateService");
                //Sets the current property value that can be manipulated
                signatureServiceField.setAccessible(true);
                templateServiceField.setAccessible(true);

                //Setting property values for bean objects
                signatureServiceField.set(beanService,signatureService);
                templateServiceField.set(beanService,templateService);

                beanList.add(beanService);
            }catch (Exception e){
                e.printStackTrace();
            }
        });

        //3. Save the Bean object of each channel to connect_ In the list set
        if(!CONNECT_LIST.isEmpty()){
            CONNECT_LIST.clear();
        }
        CONNECT_LIST.addAll(beanList);
        log.info("Load initialized channels into the collection:{}",CONNECT_LIST);

    }

Load our tool class into our memory. When we call, we only need to find the tool class of the corresponding channel through level

When we call, how do we call it? The way we call it now is to new out the specific third party tool class and then call the third party's send method.

Here we introduce the strategy pattern

Learn about cats and dogs

Strategy mode - mainly master the Demo of Comparable and Comparator

What problems does the policy model mainly solve:
It solves the problem of multiple implementations of the same method, such as sorting. I want to sort by age, I want to sort by height, and I want to sort by weight
I can't create a comparator every time and rewrite the corresponding method every time, so there will be a lot of code for my comparison function,
I want to have the sorting code of age, height, weight, etc
This is method rewriting. We only have one comparison method, and the implementation of specific comparison is left to each class. Our subclasses only need to rewrite the corresponding methods
Then our problem arises again. Join me as a cat. I want to rank the first cat according to weight and the second cat according to height
At this time, we can't meet the requirements through method rewriting. When rewriting methods, we can't rewrite them again. We can only rewrite them once, so we can't meet the needs of multiple functions
The strategy model is to solve this problem. Of course, this problem can also be solved by anonymous inner classes, but anonymous inner classes are written dead and cannot be called for other classes
Therefore, the strategy mode is recommended

The policy pattern is to write the corresponding implementation into a specific class and implement a unified interface. When we use the corresponding method, we need to write the specific class (specific comparator class)
Tell us how

This ensures that the behavior of a class or its algorithm can be changed at run time

Advantages and disadvantages of strategic mode

Advantages: 1. The algorithm can be switched freely. 2. 2. Avoid using multiple conditional judgments. 3. Good scalability.
Disadvantages: 1. The number of policy classes will increase and the amount of code will increase. 2. All policy classes need to be exposed.

Usage scenario:
1. If there are many classes in a system, the only difference between them is their behavior,
Then, using the policy pattern, an object can dynamically choose one behavior among many behaviors.
2. A system needs to dynamically choose one of several algorithms.
3. If an object has a lot of behaviors, without appropriate patterns, these behaviors have to be implemented by multiple conditional selection statements.

Where is our policy pattern used for our system
First, all our third-party tool classes inherit from our abstract class AbstractSmsService
And there is a send method, which is the method that really calls our tool class
Whether we call abstractSmsService.send () directly when we execute the call, we just need to determine whether our abstractSmsService implementation class is called in a polymorphic way in advance

Let's look at the business logic of our server service. Refer to the flow chart
Explain with code

Is there a problem with our code now
What should we do if the manager service modifies the content of the channel during execution? Because we read our data into memory and instantiate the tool class according to the content in config
Is this information not synchronized
As a result, the data modified by our manager service cannot be synchronized to our server service in time
We need to do a method in the manager service to notify the server service to update memory messages
The modification method requires:
1) Get all useful redis services through server_ id_ Get all channel IDS in hash
2) Judge whether the time is greater than 5 minutes. If it is greater than 5 minutes, it means that the service has been disconnected from redis and needs to be deleted
3) If the service is normal, go to this topic_ HIGH_ The server key stores a message initializing the channel
The publish subscribe mode of redis is used here, and messages are sent in the form of broadcast

	public void sendUpdateMessage() {
        // TODO sends a message to inform the SMS sending service to update the channel priority in memory

        Map map = redisTemplate.opsForHash().entries("SERVER_ID_HASH");
        log.info("All SMS sending service instances:" + map);
        long currentTimeMillis = System.currentTimeMillis();

        for (Object key : map.keySet()) {
            Object value = map.get(key);
            long parseLong = Long.parseLong(value.toString());
            if(currentTimeMillis - parseLong > (1000 * 60 * 5)) {
                //Delete the available channels cached in redis. Because the channel priority changes, the available channels cached in redis need to be reloaded
                redisTemplate.delete("listForConnect");
            }else{
                //Indicates that the current instance status is normal
                ServerTopic serverTopic = ServerTopic.builder().option(ServerTopic.INIT_CONNECT).value(key.toString()).build();
//                ServerTopic simpleEntityTopic = new ServerTopic(ServerTopic.INIT_CONNECT, key.toString());
                //send message
                redisTemplate.convertAndSend("TOPIC_HIGH_SERVER",serverTopic.toString());
//                redisTemplate.convertAndSend("TOPIC_HIGH_SERVER",simpleEntityTopic.toString());
                return;
            }
        }
    }
stay server The service side needs to receive manager Service messages
 First step statement spring assembly@Component realization MessageListener Interface
 rewrite onMessage Method will receive a message when there is a method call
 Take the message out to the object ServerTopic
 In the judgment object option What if it is init_connect We need to reinitialize the object
 We need to re execute Init method

	@Override
    public void onMessage(Message message, byte[] pattern) {
        //TODO message listening: call smsConnectLoader for channel initialization or channel update according to the message content

        //Deserialize the message body to get the json string
        String jsonMsg = redisTemplate.getDefaultSerializer().deserialize(message.getBody()).toString();
        //Encapsulate the json string into a ServerTopic object
        ServerTopic serverTopic = JSON.parseObject(jsonMsg, ServerTopic.class);

        switch (serverTopic.getOption()){
            case ServerTopic.INIT_CONNECT://Initialize channel
                smsConnectLoader.initConnect();
                break;
//            case ServerTopic.USE_NEW_CONNECT: / / update channel
//                smsConnectLoader.changeNewConnect();
            default:
                break;
        }

    }

Posted by l9pt5 on Fri, 05 Nov 2021 17:24:58 -0700