Java >> Java opplæring >  >> Java

Send funksjonen din

Nå en dag fungerer som tjeneste (FaaS) trender i serverløst område, og det muliggjør ny mulighet som gjør det mulig å sende funksjoner på fly til server, og den vil begynne å kjøre umiddelbart.

Dette hjelper til med å bygge applikasjoner som tilpasser seg skiftende brukerbehov veldig raskt.

Function_as_a_service er et populært tilbud fra skyleverandører som Amazon , Microsoft, Google osv.

FaaS har mye likhet med Actor-modellen som snakker om å sende meldinger til skuespillere og de utfører lokal handling, hvis kode også kan behandles som data, kan kode også sendes til ekstern prosess og den kan utføre funksjon lokalt.

Jeg husker Joe Armstrong snakket om hvordan han under tiden han bygde Erlang pleide å sende funksjon til server for å bli HTTP-server eller smtp-server osv. Han gjorde dette i 1986!

La oss se på hvordan vi kan lagre kjørbar funksjon og utføre den senere.

Jeg vil bruke java som et eksempel, men det kan gjøres på alle språk som tillater dynamisk kobling. Javascript vil definitivt være vinneren i dynamisk kobling.

Rask revisjon

La oss ta en rask titt på funksjoner/atferd i java

@Test
    public void square_number() {

        Function<Integer, Integer> sqr = x -> x * x;

        assertEquals(4, sqr.apply(2));
        assertEquals(9, sqr.apply(3));
        assertEquals(16, sqr.apply(4));
    }

    @Test
    public void to_upper() {

        Function<String, String> upper = x -> x.toUpperCase();
        assertEquals("FAAS", upper.apply("FaaS"));
    }

Ikke mye å forklare koden ovenfor, det er veldig grunnleggende transformasjon.

Lagre funksjon

La oss prøve å lagre en av disse funksjonene og se hva som skjer.

 @Test
    public void save_function() throws Exception {

        Function<String, String> upper = x -> x.toUpperCase();
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream os = new ObjectOutputStream(bos)) {
            os.writeObject(upper);
        }
    }

Koden ovenfor ser perfekt ut, men den mislykkes under kjøretid med feilen nedenfor


java.io.NotSerializableException:faas.FunctionTest$$Lambda$266/1859039536 at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) på ​​java.io.ObjectStream.Object.Object.Stream .FunctionTest.save_function(FunctionTest.java:39) ved sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

Lambda-funksjoner kan ikke serialiseres som standard.

Java har et fint triks for å bruke cast-uttrykk for å legge til ekstra binding. Flere detaljer er tilgjengelige på Cast Expressions.

I nøtteskall vil det se omtrent ut som nedenfor

@Test()
    public void save_function_works() throws Exception {

        // Addtional casting allow to mark as serilized
        Function<String, String> upper = (Function<String, String> & Serializable) x -> x.toUpperCase(); 
        
        try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
             ObjectOutputStream os = new ObjectOutputStream(bos)) {

            os.writeObject(upper);

            try (ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                 ObjectInputStream in = new ObjectInputStream(bis)) {

                Function<String, String> restoredUpper = (Function<String, String>) in.readObject();

                Assertions.assertEquals("FAAS", restoredUpper.apply("FaaS"));
            }

        }
    }

Denne teknikken gjør det mulig å konvertere ethvert funksjonelt grensesnitt til byte og gjenbruke det senere. Den brukes i JDK på forskjellige steder som TreeMap/TreeSet, da disse datastrukturene har komparator som funksjon og også støtter serialisering.

Med grunnleggende ting som fungerer, kan vi prøve å bygge noe mer nyttig.

Vi må skjule &Serialized magi for å gjøre koden mer lesbar, og dette kan oppnås med funksjonelt grensesnitt som strekker seg fra basisgrensesnittet og bare legger til Serializable, det vil se omtrent slik ut som nedenfor

@FunctionalInterface
public interface SerFunction<T, R> extends Function<T, R>, Serializable {
}

@FunctionalInterface
public interface SerPredicate<T> extends Predicate<T>, Serializable {
}

....

Når vi har tatt vare på boilerplate, blir det veldig enkelt å skrive funksjonene som er klare for serialisering.

List functions = asList(
                SerCode.f((Integer x) -> x * x),
                SerCode.f((String x) -> x.toUpperCase()),
                SerCode.p((String x) -> x.length() > 5)
        );

        byte[] code = saveFunction(functions);
        ObjectInputStream fStream = codeStream(code);

        List rFunctions = (List) fStream.readObject();

        int fIndex = 0;
        Function<Integer, Integer> rSquare = (Function<Integer, Integer>) rFunctions.get(fIndex++);
        System.out.println(rSquare.apply(10)); // Shows 100

        Function<String, String> rUpper = (Function<String, String>) rFunctions.get(fIndex++);
        System.out.println(rUpper.apply("FaaS")); // Shows "FAAS

        Predicate<String> rGt5Length = (Predicate<String>) rFunctions.get(fIndex++);
        System.out.println(rGt5Length.test("I am greater than 5")); // Shows true

Med byggeblokken ovenfor kan vi lagre full transformasjon (kart/filtrer/reduser/samle osv.) og sendes til server for behandling. Dette gjør det også mulig å bygge beregninger som kan beregnes på nytt om nødvendig.

Spark er en distribuert prosesseringsmotor som bruker en slik type mønster der den vedvarer transformasjonsfunksjon og bruker den for å gjøre beregninger på flere noder.

Så neste gang du vil bygge et distribuert prosesseringsrammeverk, se nærmere på dette mønsteret eller ønsker å ta det til det ekstreme, og send deretter lappet funksjon til live server i produksjon for å fikse problemet.

Koden brukt i posten er tilgjengelig @ faas

Java Tag