分类 编码技巧 下的文章

不要自己造轮子

Don't repeat yourself

DRY这句话大家可能耳朵都听出茧子了。不过,这个确实是非常重要的一个填坑要诀。

拿正常的开发(非填坑)来说,使用别人造好的轮子可以大大提高效率和开发进度。
我上次用的别人的大轮子是开发微信公众号和小程序的时候,用的wechat这个开源lib。使得集成微信的工作量大大减少,不必看微信那么多的文档和接口。

专门拿填坑来说,使用别人造好的轮子比正常开发要显得还重要一些。当然这里的轮子主要是原来工程中使用的轮子了。尽量使用工程中原来使用的轮子,别自己造,更别使用其他人的轮子。

我见过最常最轮子的方法就是类似于StringUtils和CollectionUtils这种,用来判断empty,用来split等。


- 阅读剩余部分 -

最近有这么一个需求,要给每个用户生成一个唯一的邀请码,用户可以将这个邀请码分享出去,当新用户使用这个邀请码注册登录的时候,就会给邀请者和被邀请者双方发放奖励。

常见的方法有如下几种:

  1. 直接使用用户uid做邀请码,简单直接,就好像其他app中填入邀请者的手机号一样
  2. 对uid做hash生成一串字符串做邀请码,这个主要是为了避免用户的uid泄露,但是要保存用户uid和邀请码的对应关系
  3. 采用对称加密算法加密uid,这样直接根据邀请码就可以解出用户uid,不需要保存uid和邀请码的对应关系了

我们这边的用户uid是纯数字的,共有13位,给这13位uid生成hash或加密的话,结果会较长,不方便用户分享和填写,产品经理限制邀请码长度为6位,同时生成的邀请码中不能要oO1ILl等容易混淆的字符。

- 阅读剩余部分 -

Java8中推出了Optional这个class,用来做null check,我在实际工作中也经常使用,确实方便很多,不过也遇到了不少坑,现在写出来供大家参考。

0x01 这些情况不要使用Optional

  1. 集合类不要包装为Optional。集合类永远必须返回集合,不允许返回null值,即使如果没有数据,也要返回空集合。返回空集合避免了NPE,特别是使用stream接口处理起来非常方便。
  2. POJO的属性不要使用Optional。Optional不要作为class的属性,如果要对空属性进行处理,总是应该在对应属性的getter方法上做。同时由于Optional没有实现Serializable接口,所以这些类在序列化的时候也非常麻烦。
  3. RPC服务的方法返回值和参数不要使用Optional。正如第二点所说,Optional不能正常序列化,所以使用RPC服务时,如果Optional作为参数或者返回值进行调用的时候,序列化失败就会报错。(但是如果方法返回值不能返回Optional对象,而返回null的话又失去了Optional存在的意义。SO上也有Optional为啥不实现Serializable接口的讨论,地址在此stackoverflow

- 阅读剩余部分 -

RPC客户端负载均衡

0x00 负载均衡的插入点

一般是客户端或调用方进行负载均衡的处理。

0x01 常见的负载均衡策略

LocalFirst-本地优先

获取所有的本地服务提供者,按照一定顺序,取得被调用次数最小的服务提供者。

public class LocalFirstLoadBalance {


    public static void main(String[] args) {
        LocalFirstLoadBalance balance = new LocalFirstLoadBalance();
        for (int i = 0; i < 10; i++) {
            Refer refer = balance.getRefer();
            System.out.println(refer);
            refer.incrActiveCount("");
        }
    }

    public Refer getRefer() {
        List<Refer> refers = getLocalRefers();
        Refer refer = null;
        for (int i = 0; i < refers.size(); i++) {
            int index = i % refers.size();
            Refer temp = refers.get(index);
            if (!ReferUtils.isReferAvailable(temp)) {
                continue;
            }

            if (refer == null) {
                refer = temp;
            } else {
                if (compare(refer, temp) > 0) {
                    refer = temp;
                }
            }

        }
        return refer;
    }

    private int compare(Refer referer1, Refer referer2) {
        return referer1.activeRefererCount() - referer2.activeRefererCount();
    }

    private List<Refer> getLocalRefers() {
        List<Refer> allRefers = ReferUtils.getAllRefers();
        List<Refer> collect = allRefers.stream().filter(ReferUtils::isLocalRefer).collect(Collectors.toList());
        return collect;
    }

}

- 阅读剩余部分 -

0x00 使用java的Future

java的java.util.concurrent.Future接口提供了简单的等待获取结果的功能,可以使用该类完成异步转同步的功能。

public class FutureTest {

    public static void main(String[] args) {
        FutureTest test = new FutureTest();
        try {
            System.out.println(test.get("1"));
            System.out.println(test.get("2"));
            System.out.println(test.get("3"));
            System.out.println(test.get("4"));
            System.out.println(test.get("5"));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    
    // 传入参数,获取结果 
    public String get(String para) throws ExecutionException, InterruptedException { 
        // 新建FutureTask       
        FutureTask<String> futureTask = new FutureTask<>(new MyCallable<>(para));
        // 启动FutureTask
        futureTask.run();
        // 同步获取结果
        return futureTask.get();
    }
    
    // 异步处理
    class MyCallable<T> implements Callable<T> {

        T para;

        MyCallable(T para) {
            this.para = para;
        }

        @Override
        public T call() throws Exception {
            // 将耗时的网络操作放在这里
            Thread.sleep(1000);
            return this.para;
        }
    }
}

这样做的优点如下:
1. 工作量小,使用java自带的功能即可完成,无需编写大量代码
2. 依赖于java自己的concurrent框架,可靠稳定

可以使用ExecutorService.submit来执行Callable,提高效率

没·

0x01 自己进行管理

流行的RPC框架目前没有直接使用Future的,都是自己写了一套类似于Future的,比如dubbo和motan。自己进行管理的优势就是可定制化,可以追踪每条请求的生命周期,可以对所有请求进行同一管理等。

更重要的原因是,RPC常使用TCP进行通信,而不是HTTP,所以发送请求后无法等待特定结果。同时同一TCP连接可能要处理好多请求,所以一般会为每个请求分配一个ID,当收到响应时再根据ID去处理对应的future。

public class RPCTest {

    // 维护请求id
    protected ConcurrentMap<Integer, ResultFuture> callbackMap = new ConcurrentHashMap<Integer, ResultFuture>();

    public static void main(String[] args) {
        RPCTest test = new RPCTest();

        ResultFuture<String> future = test.new ResultFuture<String>();
        // 注册Future
        test.registerFuture(1, future);
        // 模拟网络请求,网络处理可以同一处理
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 网络操作
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 将请求结果更新
                test.updateFuture(1, "1");
            }
        }).start();
        
        // 获取返回结果
        try {
            String s = future.get();
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 更新网络操作执行后的结果
    public void updateFuture(int sn, String value) {
        ResultFuture future = this.callbackMap.get(sn);
        if (future != null) {
            future.setValue(value);
        }
    }

    // 注册请求
    public void registerFuture(int sn, ResultFuture future) {
        this.callbackMap.put(sn, future);
    }

    // 请求类,类似于java的Future
    class ResultFuture<T> {
        final Lock lock = new ReentrantLock();
        final Condition done = lock.newCondition();
        T value;

        public T getValue() {
            return value;
        }

        public void setValue(T value) {
            lock.lock();
            try {
                this.value = value;

                if (done != null) {
                    done.signal();
                }
            } finally {
                lock.unlock();
            }
        }

        public T get() throws Exception {
            return get(3000);
        }

        public T get(int timeout) throws Exception {
            if (timeout <= 0) {
                timeout = 3000;
            }
            if (!isDone()) {
                long start = System.currentTimeMillis();
                lock.lock();
                try {
                    while (!isDone()) {
                        done.await(500, TimeUnit.MILLISECONDS);
                        if (isDone() || System.currentTimeMillis() - start > timeout) {
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }
                if (!isDone()) {
                    throw new Exception("调用超时");
                }
            }
            return value;
        }

        public boolean isDone() {
            return value != null;
        }
    }
}