GithubHelp home page GithubHelp logo

blog's People

Contributors

vonzhou avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

blog's Issues

null-safe的toString方法

在开发过程中,把对象转换为字符串的场景很常见,但是直接调用对象的toString方法,是可能会抛出NPE的,所以会有很多类似下面的模板代码。

String str = a == null ? "" : a.toString();

其实有很多常用的工具包中都提供了null-safe的工具方法,这里列举下。

JDK自带

其实最简单也最容易被忽略的是 String.valueOf() 方法。

public static String valueOf(Object obj) {
        return (obj == null) ? "null" : obj.toString();
    }

JDK 1.7之后引入的Objects工具中也有:

public static String toString(Object o) {
        return String.valueOf(o);
    }

public static String toString(Object o, String nullDefault) {
        return (o != null) ? o.toString() : nullDefault;
    }

commons-lang中StringUtils

public static String toString(Object obj) {
        return obj == null ? "" : obj.toString();
    }

public static String toString(Object obj, String nullStr) {
        return obj == null ? nullStr : obj.toString();
    }

Arrays.asList踩坑记及源码分析

最近在使用Arrays.asList时又犯了一个低级的错误导致了运行时异常,所以有必要把这个点好好理解下。从踩过的2个坑开始分析。

image

踩过的2个坑

Arrays.asList列表转数组问题

详细可以看这篇文章,toArray 方法返回的依然是 Object[],但是与 java.util.ArrayList 不同的是这里底层存储是泛型类型的数组 privatefinalE[]a,所以保留了实际的类型。

Arrays.asList列表不能新增元素

Arrays.asList构造的是一个固定大小的列表,底层存储是泛型数组,不能新增元素,原因在后面源码分析部分会看到。

Arrays.asList列表源码分析

Arrays.asList返回的是一个Arrays的内部类ArrayList,很小巧。

image

其实这个内部类和java.util.ArrayList的外貌(类层次图)是一样的,只是实现不同而已。
image

接下来我们分析内部类ArrayList源码。

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public Object[] toArray() {
            return a.clone();
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }

        @Override
        public int indexOf(Object o) {
            E[] a = this.a;
            if (o == null) {// !! 支持null
                for (int i = 0; i < a.length; i++)
                    if (a[i] == null)
                        return i;
            } else {
                for (int i = 0; i < a.length; i++)
                    if (o.equals(a[i]))
                        return i;
            }
            return -1;
        }

        @Override
        public boolean contains(Object o) {
            return indexOf(o) != -1;
        }

        @Override
        public Spliterator<E> spliterator() {
            return Spliterators.spliterator(a, Spliterator.ORDERED);
        }

        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            E[] a = this.a;
            for (int i = 0; i < a.length; i++) {
                a[i] = operator.apply(a[i]);
            }
        }

        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }
    }

存储

使用泛型数组,并且原始数组不能为Null。

private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

转换为数组

直接对数组a进行clone,所以保留了原始对象类型。

public Object[] toArray() {
            return a.clone();
        }

java.util.ArrayList一样,如果使用带指定参数类型的toArray就不存在返回类型不匹配的问题。

新增元素

该实现没有覆写add方法,而自AbstractList继承而来的add方法默认会抛出UnsupportedOperationException.

public boolean add(E e) {
        add(size(), e);
        return true;
    }
 public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }

Java8新特性的支持

  • 列表分隔 Spliterator
  • 迭代遍历消费 Consumer
  • 使用一元操作符替换列表 UnaryOperator

总结及想法

  • 遇到问题要善于总结,不放过任何一个学习的机会
  • 直接问Arrays.asList特点可能知道,但是实际中可能不会注意,不思考状态下的编程不可取

todo

一个服务内部依赖外部服务的时候一定要提供注入点(构造器或者setter方法),这样可以方便用例中进行mock

每个接口各种场景都要测试到位,至少本系统内部逻辑是符合预期的

TDD,提测之前要有测试用例,因为事后补的话会发现有些接口设计不合理,或者缺少一些注入点。

安装Kafka

安装ZK

➜  Workspace tar -zxf zookeeper-3.4.6.tar.gz
➜  Workspace mkdir -p data/zookeeper

配置文件:

➜  zookeeper-3.4.6 cat conf/zoo.cfg
tickTime=2000
dataDir=/Users/vonzhou/Workspace/data/zookeeper
clientPort=2181

启动:

➜  zookeeper-3.4.6 bin/zkServer.sh start
JMX enabled by default
Using config: /Users/vonzhou/Workspace/zookeeper-3.4.6/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

验证:

➜  zookeeper-3.4.6 telnet 127.0.0.1 2181
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
srvr
Zookeeper version: 3.4.6-1569965, built on 02/20/2014 09:09 GMT
Latency min/avg/max: 0/0/0
Received: 1
Sent: 0
Connections: 1
Outstanding: 0
Zxid: 0x0
Mode: standalone
Node count: 4
Connection closed by foreign host.

安装Kafka

➜  Workspace tar -zxf kafka_2.11-0.9.0.1.tgz
➜  kafka_2.11-0.9.0.1 bin/kafka-server-start.sh -daemon config/server.properties

创建一个topic:

➜  kafka_2.11-0.9.0.1 bin/kafka-topics.sh --create --zookeeper 127.0.0.1:2181 --replication-factor 1 --partitions 1 --topic test
Created topic "test".

向这个topic发送消息:

➜  kafka_2.11-0.9.0.1 bin/kafka-console-producer.sh --broker-list 127.0.0.1:9092 --topic test
Test msg 0
Test msg 1

从这个topic订阅消息:

➜  kafka_2.11-0.9.0.1 bin/kafka-console-consumer.sh --zookeeper 127.0.0.1:2181 --topic test --from-beginning
Test msg 0
Test msg 1
^CProcessed a total of 2 messages

image

重要配置

  • boker.id :集群中每个broker的唯一标识,默认是0,最好设置成和机器名有相关性的整数。
  • port :broker的监听端口,默认是9092.
  • zookeeper.connect :用于保存broker元数据的ZK地址,冒号分隔的一组 hostname:port/path ,path不指定标识根路径。
  • log.dirs : 存放日志的目录,可以逗号分隔的多个
  • auto.create.topics.enable :是否开启默认创建topic,默认true。
  • num.partitions:主题的分区个数,默认是1.
  • log.retention.ms:决定消息多久以后会被删除,默认是168小时。
  • log.retention.bytes:决定了一个分区可以保留的数据量,如果同时指定了log.retention.ms 和 log.retention.bytes,则任意一个满足都会删除。
  • log.segment.bytes:日志片段的大小,默认是1GB。
  • log.segment.ms:指定了日志片段的关闭时间,而不是非要等到满足log.segment.bytes。
  • message.max.bytes:单个消息的大小(压缩后的),默认是1MB。

浅谈BeanNameAutoProxyCreator

有一个这样的场景,项目中有一个module专门处理同一类事情,比如调用外部的RPC、DB操作等,这个module里面的类命名都遵守统一的规范,比如xxx或者xxx。我们需要在每次RPC前后打印出RPC的耗时以及输入和外部返回的结果,如果在每个类里面都做这个事情显得特别冗余,都知道可以使用AOP,那么针对这种情况,哪种方式更好了, BeanNameAutoProxyCreator,之前没有使用过,所以这里做个梳理。

使用方式

针对这种bean name符合一定特征的bean做拦截的话,就是需要配置特定的BeanNameAutoProxyCreator bean,指定bean name的模式和拦截器的名称。

<bean id="fooLogInterceptor" class="com.vonzhou.interceptor.FooLogInterceptor"/>

<bean id="fooLogProxyCreator"
      class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
    <property name="beanNames">
        <list>
            <value>*RPC</value>
        </list>
    </property>
    <property name="interceptorNames">
        <value>fooLogInterceptor</value>
    </property>
</bean>

拦截器的实现:

public class FooLogInterceptor implements MethodInterceptor {

   
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Method method = invocation.getMethod();

        Object result = null;
        try {
            result = invocation.proceed();
            return result;
        } catch (Exception e) {
            throw e;
        } finally {

            try {
                // do log

            } catch (Exception e) {
                // todo
            }
        }
    }
}

原理分析

本质上,BeanNameAutoProxyCreator也是一个BeanPostProcessor,在Spring bean初始化后进行了代理拦截。

image

荐:微服务中怎么处理分布式事务?

难度系数:☆☆☆

如果应用由单体架构演变成了微服务架构,都会涉及到分布式事务的问题。

  1. 2PC 两阶段提交

引入了事务管理器来管理多个参与者的事务生命周期:

优点:

  • 能保证事务的原子性
  • 读写隔离
  • 同步调用

缺点:

  • 比较重,依赖事务管理器
  • 可能会出现死锁
  1. 最终一致性和事务补偿

基于事件驱动的方式,每个服务基于事件/消息更新本地的数据/状态,然后发布新的事件。

优点:

  • 解耦
  • 每个服务关注自身的事务原子性
  • 异步调用,支持高可扩展

缺点:

  • 无读写隔离,会读到脏数据
  • 服务多的话难以维护

原文

image

Cron表达式详解

Cron语法格式:

Seconds Minutes Hours DayofMonth Month DayofWeek Year

  • Seconds: 可出现", - * /"四个字符,有效范围为0-59的整数
  • Minutes:可出现", - * /"四个字符,有效范围为0-59的整数
  • Hours:可出现", - * /"四个字符,有效范围为0-23的整数
  • DayofMonth:可出现", - * / ? L W C"八个字符,有效范围为0-31的整数
  • Month:可出现", - * /"四个字符,有效范围为1-12的整数或JAN-DEc
  • DayofWeek:可出现", - * / ? L C #"四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
  • Year:可出现", - * /"四个字符,有效范围为1970-2099年

特殊字符含义是:
(1):表示匹配该域的任意值,假如在Minutes域使用, 即表示每分钟都会触发事件。

(2)?:只能用在DayofMonth和DayofWeek两个域。它也匹配域的任意值,但实际不会。因为DayofMonth和DayofWeek会相互影响。例如想在每月的20日触发调度,不管20日到底是星期几,则只能使用如下写法: 13 13 15 20 * ?, 其中最后一位只能用?,而不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。

(3)-:表示范围,例如在Minutes域使用5-20,表示从5分到20分钟每分钟触发一次

(4)/:表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20,则意味着5分钟触发一次,而25,45等分别触发一次.

(5),:表示列出枚举值值。例如:在Minutes域使用5,20,则意味着在5和20分每分钟触发一次。

(6)L:表示最后,只能出现在DayofWeek和DayofMonth域,如果在DayofWeek域使用5L,意味着在最后的一个星期四触发。

(7)W:表示有效工作日(周一到周五),只能出现在DayofMonth域,系统将在离指定日期的最近的有效工作日触发事件。例如:在 DayofMonth使用5W,如果5日是星期六,则将在最近的工作日:星期五,即4日触发。如果5日是星期天,则在6日(周一)触发;如果5日在星期一到星期五中的一天,则就在5日触发。另外一点,W的最近寻找不会跨过月份

(8)LW:这两个字符可以连用,表示在某个月最后一个工作日,即最后一个星期五。

(9)#:用于确定每个月第几个星期几,只能出现在DayofMonth域。例如在4#2,表示某月的第二个星期三。

其实也可以看qurtz中的封装org.quartz.CronExpression

static {
        monthMap.put("JAN", Integer.valueOf(0));
        monthMap.put("FEB", Integer.valueOf(1));
        monthMap.put("MAR", Integer.valueOf(2));
        monthMap.put("APR", Integer.valueOf(3));
        monthMap.put("MAY", Integer.valueOf(4));
        monthMap.put("JUN", Integer.valueOf(5));
        monthMap.put("JUL", Integer.valueOf(6));
        monthMap.put("AUG", Integer.valueOf(7));
        monthMap.put("SEP", Integer.valueOf(8));
        monthMap.put("OCT", Integer.valueOf(9));
        monthMap.put("NOV", Integer.valueOf(10));
        monthMap.put("DEC", Integer.valueOf(11));

        dayMap.put("SUN", Integer.valueOf(1));
        dayMap.put("MON", Integer.valueOf(2));
        dayMap.put("TUE", Integer.valueOf(3));
        dayMap.put("WED", Integer.valueOf(4));
        dayMap.put("THU", Integer.valueOf(5));
        dayMap.put("FRI", Integer.valueOf(6));
        dayMap.put("SAT", Integer.valueOf(7));
    }

    protected transient TreeSet<Integer> seconds;
    protected transient TreeSet<Integer> minutes;
    protected transient TreeSet<Integer> hours;
    protected transient TreeSet<Integer> daysOfMonth;
    protected transient TreeSet<Integer> months;
    protected transient TreeSet<Integer> daysOfWeek;
    protected transient TreeSet<Integer> years;

常用cron表达式举例

每天的11点30分执行 * 30 11 ? * * *

荐:约翰·卡马克参与OpenBSD

读后感

John Carmack这个知名程序员开始阅读并参与OpenBSD代码,通过邮件列表可以看到本次应该是增加注释和改善可读性。
我最开始知道约翰·卡马克是因为2015年阅读的《Doom启示录》一书,两个约翰对技术的着迷让人激动不已。

开源的贡献并不一定是要引入什么复杂的功能或者解决了很大的问题,哪怕是写文档,完善注释都是有价值的。

image

原文

https://news.ycombinator.com/item?id=23224584
点击“阅读原文”,可以找到引用的原文。

Tmux分屏命令

进入分屏

➜  ~ tmux

上下分屏

ctrl + b 再按shift + "

左右分屏

ctrl + b 再按shift + %

切换屏幕

ctrl + b 再按o

关闭当前屏

ctrl + b 再按x

上下分屏与左右分屏切换

ctrl + b 再按空格键

image

《A Philosophy Of Software Design》读书笔记

1. 介绍

一切的一切都是复杂性

2. 复杂性的定义

Complexity: 软件系统的结构导致很难去理解或修改系统。

复杂性的表现:

  • 修改放大 change amplification
  • 认知负载 cognitive load
  • 未知的未知 unknown unknown

造成复杂性的原因:

  • 依赖
  • 晦涩

3. 代码不仅仅要可运行

4. 模块应该深

模块的2方面:接口,实现

模块的使用者(依赖者)只需要关心接口提供的抽象;强大复杂的功能隐藏在后面。

示例:Java中的对象反序列化 vs Unix IO接口。

接下来会讲到实现 deep module的技术:信息隐藏,

5.信息隐藏

使用WatchService监控文件变化

场景

系统实现中经常需要能够感知配置文件的变化,然后及时更新上下文。

实现方案

  • 自己起一个单独线程,定时加载文件,实现较简单,但是无法保证能够实时捕捉文件变化,同时耗CPU
  • 使用commons-io中的 FileAlterationObserver,**和上面类似,对比前后文件列表的变化,触发对应事件
  • JDK 1.7提供的WatchService,利用底层文件系统提供的功能
    image

使用 WatchService

WatchService用来监控一个目录是否发生改变,但是可以通过 WatchEvent 上下文定位具体文件的变化。具体使用过程中要注意以下两点:

  • 文件改变可能会触发两次事件(我的理解:文件内容的变更,元数据的变更),可以通过文件的时间戳来控制
  • 在文件变化事件发生后,如果立即读取文件,可能所获内容并不完整,建议的做法判断文件的 length > 0
// 监控文件的变化,重新加载
executor.submit(new Runnable() {
    @Override
    public void run() {
        try {
            final Path path = FileSystems.getDefault().getPath(getMonitorDir());
            System.out.println(path);
            final WatchService watchService = FileSystems.getDefault().newWatchService();
            final WatchKey watchKey = path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
            while (true) {
                final WatchKey wk = watchService.take();
                for (WatchEvent<?> event : wk.pollEvents()) {
                    final Path changed = (Path) event.context();
                    Path absolute = path.resolve(changed);
                    File configFile = absolute.toFile();
                    long lastModified = configFile.lastModified();
                    logger.info(lastModified + "----------------");
                    // 利用文件时间戳,防止触发两次
                    if (changed.endsWith(getLicenseName()) && lastModified != LAST_MOD && configFile.length > 0) {
                        logger.info("----------------- reloading -----------------");
                        LAST_MOD = lastModified; // 保存上一次时间戳
                        UPDATED = true; // 设置标志位
                    }
                }

                if (LICENSE_UPDATED) {
                    reloadFile(); // 重新加载
                }
                // reset the key
                boolean valid = wk.reset();
                if (!valid) {
                    logger.error("watch key invalid!");
                }
            }
        } catch (Exception e) {
            logger.error("");
        }
    }
});

参考

Watching a Directory for Changes

add 2017.9.5

金融体系概览

金融市场的功能是将资金从资金盈余的主体引导到资金短缺的主体手中,有助于资本的合理配置,从而对增加生产和提高效果做出贡献。

image

金融市场结构

不同的分类方法可以反映金融市场的特征。

根据筹集资金的方式

  • 发行债券或抵押票据等债务工具,借款人以契约的方式,向债务工具持有人定期支付固定金额(利息与本金支付),直至在一个确定的日期(到期日)支付最后一笔金额。根据债务工具的期限,分为短期债务工具、中期、长期。
  • 股权工具承诺持有者按照份额享有公司的净利益和资产。

根据交易场所

  • 一级市场(primary market)是筹集资金的企业或政府机构将其新发型的股票或债券销售给最初购买者的金融市场,这个过程并不是公开进行的,所以并不为公众锁熟知。投行(investment bank)是协助证券首次出售的重要金融机构,其做法是证券承销(underwriting):确保公司证券能按照某一价格出售出去之后再向公众销售这些证券。
  • 二级市场(secondary market)交易已经发行的证券的金融市场。

根据二级市场的组织形态

  • 交易所(exchange):证券的买卖双方在一个集中的场所进行交易的二级市场。
  • 场外市场(over-the-counter market,OTC):一个分散的无形的市场。

根据期限长短

  • 货币市场(money market):交易短期债务工具(原始期限1年以下)
  • 资本市场(capital market):交易长期债务工具

金融市场工具

货币市场工具

  • 美国国库券(United State Treasury Bills):美国政府发行的短期债务工具,期限分别为1、3、6个月。国库券到期日支付的金额是固定的,不付息,但是国库券是按其面值折价发行的。
  • 可转让存单
  • 商业票据
  • 回购协议(repurchase agreements,repos)的过程是:某大型企业A在银行账户上有一些闲置资金,可以房贷一周,A利用该资金购买国库券,银行同意在一周后按照略高于A购买价格的价格购回这些国库券。这个回购协议,相当于A向银行发放了一笔贷款,在银行归还贷款购回国库券之前,A持有这些国库券。

资本市场工具

  • 股票
  • 商业贷款或消费者贷款

金融中介机构

作用

金融中介机构的功能是间接融资,为何重要?

  • 能大大降低交易成本:具备专业手段;因为中介机构的规模大,可以享受规模经济(economics of scale)的好处
  • 降低风险,对投资者而言,资产被转换成了安全性更好的资产
  • 解决信息不对称的问题

分类

  • 存款机构:商业银行、信用社
  • 契约型储蓄机构:保险公司、养老基金和退休基金
  • 投资中介机构:货币市场共同基金、对冲基金、投资银行

金融体系的监管

我国的监管体系是一行三会:

  • **人民银行负责货币政策
  • 银监会,统一监督管理银行、金融资产管理公司、信托投资公司以及其它存款类金融机构。
  • 证监会,负责对全国证券、期货业进行集中统一监管。
  • 保监会,负责统一监督管理全国保险市场。

TDD

谈谈对TDD的感受。

维基百科定义:

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the software is improved so that the tests pass. This is opposed to software development that allows software to be added that is not proven to meet requirements.

TDD由kent beck于2003年提出,kent出了好几本好书。

工作好几年了,从最开始的不写测试用例,到后来的推荐写用例,再到现在的强制用例覆盖,自己对测试用例的认知也在慢慢改变。

之前的做法是先实现功能,然后本地或者部署到dev环境,用postman接口测试,有问题修改后再部署,如此往复,有一种很混沌的感觉,而且每次部署都比较慢。

TDD可以让自己更有信心, 测试用例能通过,基本逻辑就不会有大的问题,而且各种边界情况都可以覆盖到,代码修改或重构后,如果有测试用例就会实现小步快跑。

现在的做法是从入口处,先把核心的接口定下来,然后写测试用例,外部系统交互采用Mock的方式,然后填充接口的实现直到测试用例通过。为了解决本地测试用例连接dev数据慢的问题,可以集成测试环境使用内存数据库,在支持依赖注入的框架下很容易。

image

使用 redis pipeline优化查询

前言

最近在项目中遇到了一个问题:当[条件A]很大的时候数据查询特别慢,A>1000的时候就需要3秒左右,这是不能忍受的。经过排查发现代码中有一个根据A串行查询redis的操作,如下:

if (CollectionUtils.isNotEmpty(list)) {
    for (String key : list) {
        data = redisService.getDataToday(key)
    }
}

对这种情况的测试效果:

A   时间
100  249.78ms
500   1.17s
1000  2.4s

其实可以看到时间消耗在了每次查询的网络开销上。接下来进行了2中不同方式的优化。

优化1 - 牺牲精确性

上面的场景其实就是把今天的数据放在redis中,如果只是查询历史数据,就可以避免查询redis的开销,如果对精确性没有那么高。

优化后(A=1000)的时间262.93ms。

image

优化2 - 批量操作

既然知道性能的瓶颈在网络开销上,就可以采用批量操作的方式来改进,pipeline,但是要在这里可以使用mget或者pipeline的方式,注意考虑失败的情况。

public List<Object> getValueByKeys(final List<String> keys) {
    if (CollectionUtils.isEmpty(keys))
        return null;
    return redisTemplate.executePipelined(new RedisCallback<Object>() {
        public Object doInRedis(RedisConnection connection) throws DataAccessException {
            StringRedisConnection stringRedisConn = (StringRedisConnection) connection;
            for (String key : keys) {
                stringRedisConn.get(key);
            }
            return null;
        }
    });
}

优化后(A=1000)的时间304.76ms,的确有所改善。

总结

  • 在代码中如果有根据变量循环的情况,一定要注意存在的问题

参考

redis pipeline

Spring Redis support

为什么研究货币、银行与金融市场

《货币金融学》这本书断断续续的看,没有实质性的收获,接下来开始精读,不懂的就去查,同时尽量和现在的业务结合起来。

image

为什么研究金融市场

在金融市场(Financial markets)中,资金从拥有闲置货币的人(没有生产用途)手中转移到资金短缺的人(有生产用途)手中,从而提高了经济效率。

证券(security),又称金融工具,是对发行人未来收入与资产的索取权。

债券(bond)是债务证券,它承诺在一个特定的时间段中进行定期支付。

利率(interest rate)是借款的成本,利率不仅影响消费支出与储蓄的意愿,还影响企业的投资决策,因为利率对整个经济的运行有重要的意义。

股票(stock)代表持有者对公司的所有权,股票是对公司收益和资产的索取权。股票市场是最受关注的金融市场。

为什么研究金融机构和银行

银行和其他金融机构将资金从无生产用途的人手中转移到有生产用途的人手中。

为什么研究货币和货币政策

货币和货币政策对于通货膨胀(inflation)、经济周期(business cycles)和利率有重要的影响。

诺贝尔经济学得主米尔顿·佛利里德曼:“无论何时何地,通货膨胀都是货币现象”。

为什么研究国际金融

全球范围内金融市场一体化的程度在不断增强。

外汇市场(foreign exchange market)是货币兑换的场所,是资金跨国转移的中介市场,是汇率(foreign exchange rate)的决定场所。

近3个月技术上的总结 2019.7.20

剖析

由于工作变动的原因,最近3个月说实话在技术上有点落后了,没有之前那样每天都会持续学习新技术,或者至少在关注,看github,medium,看源码。

这3个月更多的是业务上的学习,完全切换了自己的工作背景,context,从0开始学习,会计知识,银行业务,流动性管理,支付清算,路漫漫,很多目前还是停留在表面,未能产出自己的想法。

另一个自己的弱项就是表述能力,或者说是沟通能力,走出去的能力,并不是写好代码就行了,业务的复杂性迫使自己去打开自己,找人学习,自己蒙头琢磨,可能别人一句话就道破自己的疑问。

麻痹

业务飞速发展,都是使用成熟的轮子在造车,作为一个技术人,如果自己不去抽时间学习,久而久之真的就被麻痹了,离开了这些成熟的轮子,就废人一个。

领域建模,始终是一项核心能力。

总结

要有核心能力。

赶上大趋势。

如何实现一个状态机?

如何实现一个状态机?

状态机有很多的应用场景,表征的是一个事物的生命周期或者状态变迁。


image

如何实现一个状态机呢?

乐观锁

第一种方法是使用乐观锁,只有当原始状态符合我们的预期时才更新为下一个状态。

public void stateTransfer1(Long id, String prevStatus, String status) {
    fooDAO.updateStatusTo(id, prevStatus, status);
}

悲观锁

另一种方式是,先对行加锁,然后在应用层的一个事务里判断记录目前的状态是否可以转换为下一个状态,这里一个好的实际是用一个Map维护状态转移之间的映射关系,方便维护。

private static final Map<String, List<String>> PREV_STATUS = new HashMap<>();

static {
    PREV_STATUS.put("SUCCESS", Arrays.asList("AAA", "BBB"));
    PREV_STATUS.put("FAIL", Arrays.asList("CCC"));
}

public void stateTransfer2(Long id, String status) {
    // do in transaction
    Foo foo1 = fooDAO.lockById(id);
    if (PREV_STATUS.get(status).contains(foo1.getStatus())) {
        fooDAO.updateStatus(id, status);
    }
}

VIM之重复执行

我们都害怕重复,重复是繁琐的,重复是低效的。凡是可以简化重复操作的方式,都会成倍节约我们的时间,甚至降低日后的运维成本。

.命令(dot command)的效果是普通模式下重复上一次执行的操作, 这个看似简单的命令,实则会大大提升我们的效率。

重复上次修改(repeat last change)这个修改可能是删除一个字符、插入特定文本、做了格式调整等等。

接下来看几个例子,每次操作完成, 使用u将文本恢复到初始状态。

删除单个字符

x命令删除单个字符,然后不断的.VIM就会删除光标下的字符。

删除整行

dd命令删除整行,然后不断的.VIM就会删除光标所在的行,先往下,让后往上,直到删掉所有。

5.输入可以同时删除5行。

复制整行

yy命令复制整行,然后移动到合适的位置,使用p进行复制到当前行的下一行,或者P复制到当前行的上一行,后续移到新位置,可以使用.进行上述复制。

缩进

>G命令会增加从当前行到文末的缩进层次,如果.就会不断缩进。

一次修改

从进入插入模式,到<Esc>返回到普通模式,期间进行进行的编辑,也可以使用.进行重复。

删除整个单词

可以使用daw命令(delete a word)来删除整个单词,aw是一种文本对象(text object)。

daw之后不断的.就可以删除光标所在的单词,节省了大量输入。

理解利率

当经济学家使用利率一词时,指的就是到期收益率。

现值

现值(present value,或称现期贴现值,present discounted value),就是把未来的收益换算成现在的价值。

PV = CF / (1+i)^n

PV 现值,CF未来的现金流量(cash flow),i利率,n年限

虽然多个金融工具偿付的时间安排不一样,但是可以借助于现值的概念对他们进行比较。

4种类型的信用市场工具

  • 普通贷款:贷款人向借款人提供一定数量的资金,借款人在到期日向贷款人归还本金,并支付额外的利率。
  • 固定支付贷款(fixed-payment loan,或称分期偿还贷款,fully amortized loan):贷款人向借款人提供一定数量的资金,在约定的若干年内,借款人每个期间(如每月)偿还固定的金额,其中包括本金和利息。
  • 息票债券(coupon bond):在到期日之前每年向债券所有人支付固定金额的利息(息票利息),到期时再偿还事先规定的最终金额(债券面值,face value,par value)。美国联邦政府发行的长期国债与中期国债,及企业债券都属于息票债券。
  • 贴现发行债券(discount bond,或称零息债券,zero-coupon bond):这种债券的购买价格低于其面值(贴现发行),到期时按照面值偿付。美国联邦政府国库券属于这种。

到期收益率

到期收益率(yield to maturity):使债务工具所有的未来回报的现值与其今天的价值相等的利率。

上面4种类型的信用市场工具到期收益率计算如下:

  • 普通贷款:单利率就等于到期收益率。
  • 固定支付贷款:每月偿付贴现之和等于这笔贷款今天的价值
    LV = FP/(1+i) + FP/(1+i)^2 + .... + FP/(1+i)^n

LV贷款金额,FP每年固定偿付金额,n年限,i就是要求的到期收益率

  • 息票债券:债券的现值等于所有息票利息的现值加上最后偿还面值的现值。
    P = C/(1+i) + C/(1+i)^2 + .... + C/(1+i)^n + F/(1+i)^n

P息票债券的现价,C每年的息票利息,n年限,F面值,i就是要求的到期收益率

债券价格和到期收益率之间负相关,可以这么理解,利率越高,未来的息票利息与最终偿付的款项所折现的价值就越少,因此,债券价格必然更低。

息票债券有一个特例,统一公债(consol)或永续债券(perpetuity):没有到期日,不必偿付本金,永远只需要支付固定的息票利息。如果息票利率相等,长期息票债券的价值就与永续债券十分相近。

  • 贴现发行债券:类似于普通贷款。

总结

  • 到期收益率是衡量利率最精确的指标
  • 债券现价与利率是负相关的:当利率上升时,债券价格下跌,反之亦然。

Go入门-Hello World

2年前学过一段时间的Go语言,看了几本书,但是后来不用就又生疏了,本次打算从头学习,never late to learn。最主要的原因是当前云原生技术 Docker,k8s都是Go实现的。在接下来的“Go入门”系列中主要跟着《The way to Go》践行。

Go 语言起源 2007 年,于 2009 年正式对外发布。三位作者都是大牛:Robert Griesemer参与开发 Java HotSpot 虚拟机;Rob Pike,Go 语言项目总负责人,贝尔实验室 Unix 团队成员;Ken Thompson,贝尔实验室 Unix 团队成员,C 语言、Unix 的创始人之一。

环境准备

➜  the-way-to-go git:(master) ✗ go version
go version go1.10.3 darwin/amd64

Hello World

➜  the-way-to-go git:(master) ✗ cat hello_world.go
package main

func main() {
	println("Hello", "world")
}
➜  the-way-to-go git:(master) ✗ go run hello_world.go
Hello world

思考

如果学了不用的话,可能仍然会再次遗忘,还是要有目的的学习,和现有的知识网络产生交集。平时哪些地方可以把Go用起来?

读《领域驱动设计》

好的模型也是不断迭代出来的
Valuable models do not emerge immediately; they require a deep understanding of the domain. That understanding comes from diving in, implementing an initial design based on a probably naive model, and then transforming it again and again. Each time the team gains insight, the model is transformed to reveal that richer knowledge, and the code is refactored to reflect the deeper model and make its potential available to the application. Then, once in a while, this onion peeling leads to an opportunity to break through to a much deeper model, attended by a rush of profound design changes.

什么是货币?

学习目标:

  1. 货币的定义
  2. 货币的计量,即M0、M1

定义

货币(经济学家称货币供给,money supply)指的是在产品和服务支付及债务偿还中被普遍接受的东西。当大部分人谈到货币时,往往指的是通货(currency,纸币和硬币)。

相关的两个词:

  • 财富(wealth):用于价值储藏的各项资产总和,包括货币、债券、股票、艺术品、土地、汽车、房屋。
  • 收入(income):单位时间内收益的流量,而货币时全量。

功能

交易媒介

物物交换的时候,必须实现需求的双重吻合,交易成本很高。运用货币作为交易媒介,节省了产品和服务的交易时间,促进了经济效率。

另一方面,货币会鼓励专业化和劳动分工。

记账单位

统一了经济社会中价值衡量的手段,不需要多种价格(以电影表示经济学课程的价格、以套子表示电影的价格)。

价值储藏

跨越时间购买力的储藏,货币的价值储藏功能并非独有的,其他资产都有,可能更具有优势,但是货币的流动性(liquidity,资产转换为交易媒介的程度和速度)是最好的。

计量

货币计量对于政策制定者和经济学家十分重要。

M0 流通中货币(Currency in Circulation)

M0 = 流通中现金
= 货币发行量 - 商业银行库存现金

M1 货币

M1 = M0 + 单位活期存款

M2 货币和准货币

M2 = M1 + 个人存款 + 单位定期存款 + 单位其他存款

image

MySQL常用函数

字符串

日期

NOW()

返回当前的日期和时间

CURDATE()

返回当前的日期

CURTIME()

返回当前的时间

DATE_ADD()

给日期添加指定的时间间隔

DATE_SUB()

从日期减去指定的时间间隔

语法:

DATE_SUB(date,INTERVAL expr type)

date 参数是合法的日期表达式。expr 参数是您希望添加的时间间隔。

Type 值:

MICROSECOND
SECOND
MINUTE
HOUR
DAY
WEEK
MONTH
QUARTER
YEAR
SECOND_MICROSECOND
MINUTE_MICROSECOND
MINUTE_SECOND
HOUR_MICROSECOND
HOUR_SECOND
HOUR_MINUTE
DAY_MICROSECOND
DAY_SECOND
DAY_MINUTE
DAY_HOUR
YEAR_MONTH

DATEDIFF()

返回两个日期之间的天数

DATE_FORMAT()

用不同的格式显示日期/时间

格式 描述
%a 缩写星期名
%b 缩写月名
%c 月,数值
%D 带有英文前缀的月中的天
%d 月的天,数值(00-31)
%e 月的天,数值(0-31)
%f 微秒
%H 小时 (00-23)
%h 小时 (01-12)
%I 小时 (01-12)
%i 分钟,数值(00-59)
%j 年的天 (001-366)
%k 小时 (0-23)
%l 小时 (1-12)
%M 月名
%m 月,数值(00-12)
%p AM 或 PM
%r 时间,12-小时(hh:mm:ss AM 或 PM)
%S 秒(00-59)
%s 秒(00-59)
%T 时间, 24-小时 (hh:mm:ss)
%U 周 (00-53) 星期日是一周的第一天
%u 周 (00-53) 星期一是一周的第一天
%V 周 (01-53) 星期日是一周的第一天,与 %X 使用
%v 周 (01-53) 星期一是一周的第一天,与 %x 使用
%W 星期名
%w 周的天 (0=星期日, 6=星期六)
%X 年,其中的星期日是周的第一天,4 位,与 %V 使用
%x 年,其中的星期一是周的第一天,4 位,与 %v 使用
%Y 年,4 位
%y 年,2 位

2019阅读书单

年末将至,这里总结下2019阅读的书籍。

李光耀观天下

国父对世界格局的洞察。

A Philosophy of Software Design

软件设计哲学,如何解决软件复杂性的问题。

小岛经济学 : 鱼、美元和经济的故事

入门读物。

原则

生活和工作的原则, 做到极度透明和开放,从失败中学习反思,从历史经验中学习总结原则,要有机器的系统思维,出问题后追究背后的原理,控制自己的非理性部分。

北方的空地

一个人穿越羌塘的游记。

逃不开的经济周期 : 历史,理论与投资现实

精髓就是驱动经济周期的五大要素,及央行的调控手段和面临的十大挑战。MV=PQ

夹边沟记事

那个黑色的年代,读来令人唏嘘不已。

薛兆丰经济学讲义

经济学入门。

半小时漫画**史

世界上最简单的会计书

很好的会计入门书,财务三大报表:资产负债表、利润表、现金流量表。

Spark快速大数据分析

看了2,3,4章,基本能理解Spark的架构,单机模式运行了Scala的几个例子。后续技术选型的时候再深入。

沉默的大多数

**历代政治得失

经典的小书。

RocketMQ实战与原理解析

对RocketMQ的脉络做了一个大概的说明吧,深入细节的东西还是需要自己看代码

Clean Architecture : A Craftsman's Guide to Software Structure and Design

整本书读下来,总的来说,除了老生常谈的几大经典设计模式外,重点就是划分好系统的边界,组织好依赖关系,区分哪些属于高层的业务层,哪些是外层的细节层。

紫禁城的黄昏

末代皇帝溥仪老师庄士敦对清末自己经历的事情的讲述。

快学Scala

Scala将OOP和函数式编程有机结合,动静兼备。前面的基本招式通读后,基本上可以理解。但是后面的类型系统,协变型变,定界延续目前还搞不懂,暂时略过。

一次线上故障的反思

过去好几天了,但是觉得还是有必要分享下这个**的错误。

时间

五一之后的第一天。

经过

系统遗留的一个后门接口威力无比,可以执行任何sql,包括删除所有记录,更新所有记录。

有一天,我掉进了这个坑,一切发生的毫无征兆,意识到的那一刻头脑一片空白。

为了头方便省事,为了修复一个问题,想直接走后门修改掉,so easy。

image

复制-粘贴-执行,操作行云流水,不假思索。然后点击相关页面,一片空白,才意识到刚才执行的sql超出了预期,紧接着是用户的反馈。。。。OMG

**!执行了一个没有加where条件的update语句。。。

第一反应,联系DBA,看能不能全表恢复,才知道数据恢复并没有想象中的那么快速,这种方式恢复可能需要XXX天!!

幸运的是,这张DB表对应的有离线表,才迅速通过其他方式把数据恢复了,业务不可用时间1-2小时。

总结

吃一堑长一智,我觉得在这个过程中暴露了很多的问题。

  • 使用后门,而且还没有找人review,意识就有问题
  • 生产变更不走标准流程,找死
  • 执行sql,至少先在测试环境跑一下吧,直接干到线上了,咋想的?

HandlerInterceptor应用及执行过程分析 2016.7.23

HandlerInterceptor 实践

这两天花了很多时间在折腾使用AOP对Spring MVC Controller进行拦截,但是没有效果。然后尝试了下Spring的HandlerInterceptor,使用起来比较简单,**也容易理解。下面是Spring Doc对HandlerInterceptor接口及相关方法的说明。

HandlerInterceptor 接口:

Workflow interface that allows for customized handler execution chains. Applications can register any number of existing or custom interceptors for certain groups of handlers, to add common pre-processing behavior without needing to modify each handler implementation.
A HandlerInterceptor gets called before the appropriate HandlerAdapter triggers the execution of the handler itself. This mechanism can be used for a large field of preprocessing aspects, e.g. for authorization checks, or common handler behavior like locale or theme changes. Its main purpose is to permit the factoring out of otherwise repetitive handler code.

Typically an interceptor chain is defined per HandlerMapping bean, sharing its granularity. To be able to apply a certain interceptor chain to a group of handlers, one needs to map the desired handlers via one HandlerMapping bean. The interceptors themselves are defined as beans in the application context, referenced by the mapping bean definition via its “interceptors” property (in XML: a of elements).

A HandlerInterceptor is basically similar to a Servlet Filter, but in contrast to the latter it allows custom pre-processing with the option to prohibit the execution of the handler itself, and custom post-processing. Filters are more powerful; for example they allow for exchanging the request and response objects that are handed down the chain. Note that a filter gets configured in web.xml, a HandlerInterceptor in the application context.

As a basic guideline, fine-grained handler-related pre-processing tasks are candidates for HandlerInterceptor implementations, especially factored-out common handler code and authorization checks. On the other hand, a Filter is well-suited for request content and view content handling, like multipart forms and GZIP compression. This typically shows when one needs to map the filter to certain content types (e.g. images), or to all requests.

preHandle 方法:

Intercept the execution of a handler. Called after HandlerMapping determined an appropriate handler object, but before HandlerAdapter invokes the handler.
DispatcherServlet processes a handler in an execution chain, consisting of any number of interceptors, with the handler itself at the end. With this method, each interceptor can decide to abort the execution chain, typically sending a HTTP error or writing a custom response.

postHandle 方法:

Intercept the execution of a handler. Called after HandlerAdapter actually invoked the handler, but before the DispatcherServlet renders the view. Can expose additional model objects to the view via the given ModelAndView.
DispatcherServlet processes a handler in an execution chain, consisting of any number of interceptors, with the handler itself at the end. With this method, each interceptor can post-process an execution, getting applied in inverse order of the execution chain.

afterCompletion 方法:

Callback after completion of request processing, that is, after rendering the view. Will be called on any outcome of handler execution, thus allows for proper resource cleanup.
Note: Will only be called if this interceptor’s preHandle method has successfully completed and returned true!
As with the postHandle method, the method will be invoked on each interceptor in the chain in reverse order, so the first interceptor will be the last to be invoked.

接下来是一个具体实例,完整代码

public class LogInterceptor extends HandlerInterceptorAdapter {

    private static Logger logger = Logger.getLogger(LogInterceptor.class);

    //  Intercept the execution of a handler.
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {

        if(!isValidUser()){
            logger.info("Invalid user from " + request.getRemoteAddr());
            response.sendRedirect("http://localhost:8888/user/error");
            return false;
        }

        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        logger.info("Will call " + handler.toString());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        logger.info("before return view  page, i can get model = " + modelAndView.getModelMap().get("hello"));
    }

    // Callback after completion of request processing, that is, after rendering the view.
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // Our Log Func
        long curTime = System.currentTimeMillis();
        long startTime = (Long)request.getAttribute("startTime");
        long cost = curTime - startTime;
        logger.info("Call cost time " + cost + " mills!");
    }

    // FAKE
    private boolean isValidUser(){
        return true;
    }
}

application context中对拦截器的配置:

<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/admin/**"/>
            <bean class="com.vonzhou.learning.interceptor.LogInterceptor"/>
        </mvc:interceptor>
    </mvc:interceptors>

所以当我们访问对应的URL时就会触发拦截器的执行,如下:

~ curl http://localhost:8888/user/service


You are our user, Welcome!

server端的日志输出:

2016-07-23 10:25:37,374  INFO LogInterceptor:32 - Will call public java.lang.String com.vonzhou.learning.controller.UserController.serveUser(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse,java.util.Locale,org.springframework.ui.ModelMap)
2016-07-23 10:25:38,379  INFO LogInterceptor:38 - before return view  page, i can get model = Some Data
2016-07-23 10:25:38,381  INFO LogInterceptor:50 - Call cost time 1007 mills!

执行过程分析

我们只看该逻辑相关的代码,其他的可以忽略掉,做到有目的的阅读源码。

HttpServlet

Service层处理HTTP请求的入口是service()方法(更深层次的需要看Tomcat的原理),分别处理HTTP不同的方法,这里我们看GET方法的逻辑。

public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
    HttpServletRequest request;
    HttpServletResponse response;
    try {
        request = (HttpServletRequest) req;
        response = (HttpServletResponse) res;
    } catch (ClassCastException e) {
        throw new ServletException("non-HTTP request or response");
    }
    service(request, response);
}

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {
    String method = req.getMethod();

    if (method.equals(METHOD_GET)) {
        long lastModified = getLastModified(req);
        if (lastModified == -1) {
            // servlet doesn't support if-modified-since, no reason
            // to go through further expensive logic
            doGet(req, resp);
        } else {
            long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
            if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                // If the servlet mod time is later, call doGet()
                // Round down to the nearest second for a proper compare
                // A ifModifiedSince of -1 will always be less
                maybeSetLastModified(resp, lastModified);
                doGet(req, resp);
            } else {
                resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
            }
        }

    } else if (method.equals(METHOD_HEAD)) {
        long lastModified = getLastModified(req);
        maybeSetLastModified(resp, lastModified);
        doHead(req, resp);

    } else if (method.equals(METHOD_POST)) {
        doPost(req, resp);

    } else if (method.equals(METHOD_PUT)) {
        doPut(req, resp);

    } else if (method.equals(METHOD_DELETE)) {
        doDelete(req, resp);

    } else if (method.equals(METHOD_OPTIONS)) {
        doOptions(req, resp);

    } else if (method.equals(METHOD_TRACE)) {
        doTrace(req, resp);

    } else {
        //
        // Note that this means NO servlet supports whatever
        // method was requested, anywhere on this server.
        //

        String errMsg = lStrings.getString("http.method_not_implemented");
        Object[] errArgs = new Object[1];
        errArgs[0] = method;
        errMsg = MessageFormat.format(errMsg, errArgs);

        resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
    }
}

FrameworkServlet

进入到Spring的框架逻辑中,调用doService,然后发布一个事件,这里重点看处理请求的doService方法,FrameworkServlet中该方法为abstract,具体在DispatcherServlet中实现。

@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    processRequest(request, response);
}

protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    Throwable failureCause = null;

    // Expose current LocaleResolver and request as LocaleContext.
    LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
    LocaleContextHolder.setLocaleContext(buildLocaleContext(request), this.threadContextInheritable);

    // Expose current RequestAttributes to current thread.
    RequestAttributes previousRequestAttributes = RequestContextHolder.getRequestAttributes();
    ServletRequestAttributes requestAttributes = null;
    if (previousRequestAttributes == null || previousRequestAttributes.getClass().equals(ServletRequestAttributes.class)) {
        requestAttributes = new ServletRequestAttributes(request);
        RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
    }

    if (logger.isTraceEnabled()) {
        logger.trace("Bound request context to thread: " + request);
    }

    try {
        doService(request, response);
    } catch (ServletException ex) {
        failureCause = ex;
        throw ex;
    } catch (IOException ex) {
        failureCause = ex;
        throw ex;
    } catch (Throwable ex) {
        failureCause = ex;
        throw new NestedServletException("Request processing failed", ex);
    } finally {
        // Clear request attributes and reset thread-bound context.
        LocaleContextHolder.setLocaleContext(previousLocaleContext, this.threadContextInheritable);
        if (requestAttributes != null) {
            RequestContextHolder.setRequestAttributes(previousRequestAttributes, this.threadContextInheritable);
            requestAttributes.requestCompleted();
        }
        if (logger.isTraceEnabled()) {
            logger.trace("Cleared thread-bound request context: " + request);
        }

        if (failureCause != null) {
            this.logger.debug("Could not complete request", failureCause);
        } else {
            this.logger.debug("Successfully completed request");
        }
        if (this.publishEvents) {
            // Whether or not we succeeded, publish an event.
            long processingTime = System.currentTimeMillis() - startTime;
            this.webApplicationContext.publishEvent(
                    new ServletRequestHandledEvent(this,
                            request.getRequestURI(), request.getRemoteAddr(),
                            request.getMethod(), getServletConfig().getServletName(),
                            WebUtils.getSessionId(request), getUsernameForRequest(request),
                            processingTime, failureCause));
        }
    }
}

DispatcherServlet

doDispatch中具体处理每个请求,包括Interceptor的拦截逻辑和Controller handler的业务逻辑。

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    if (logger.isDebugEnabled()) {
        String requestUri = urlPathHelper.getRequestUri(request);
        logger.debug("DispatcherServlet with name '" + getServletName() + "' processing " + request.getMethod() +
                " request for [" + requestUri + "]");
    }

    /**
     * Make framework objects available to handlers and view objects.
     * 为了处理器对象,视图对象可以访问某些某些全局的对象,设置到HTTP request的属性中
     * */
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    try {
        doDispatch(request, response);
    } finally {
    }
}

/**
 * 具体分发请求到handler
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 *
 * @param request  current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    int interceptorIndex = -1;

    try {
        ModelAndView mv;
        boolean errorView = false;

        try {
            processedRequest = checkMultipart(request);

            /**
             * 获取该请求对应的 handler和interceptor(定位到了某个类)
             * Determine handler for the current request.
             * */
            mappedHandler = getHandler(processedRequest, false);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            /**
             * 获得该请求对应的更具体的handler(适配器模式,包含更多的策略和配置),如AnnotationMethodHandlerAdapter
             * Determine handler adapter for the current request.
             * */
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            /**
             * 依次执行我们的Interceptor
             * Apply preHandle methods of registered interceptors.
             * */
            HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
            if (interceptors != null) {
                for (int i = 0; i < interceptors.length; i++) {
                    HandlerInterceptor interceptor = interceptors[i];
                    if (!interceptor.preHandle(processedRequest, response, mappedHandler.getHandler())) {
                        /**
                         * 如果该preHandle方法返回了false,那么就倒序依次执行前面的Interceptor的 afterCompletion 方法,然后返回
                         * 所以不会执行后面的 Controller的handler方法,以及Interceptor的postHandle方法
                         * */
                        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
                        return;
                    }
                    interceptorIndex = i;
                }
            }

            /**
             * 真正进入 Controller handler 进行请求处理
             * Actually invoke the handler.
             * */
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

            // Do we need view name translation?
            if (mv != null && !mv.hasView()) {
                mv.setViewName(getDefaultViewName(request));
            }

            //
            /**
             * 然后逆序依次执行我们的Interceptor的 postHandle 方法
             * Apply postHandle methods of registered interceptors.
             * */
            if (interceptors != null) {
                for (int i = interceptors.length - 1; i >= 0; i--) {
                    HandlerInterceptor interceptor = interceptors[i];
                    interceptor.postHandle(processedRequest, response, mappedHandler.getHandler(), mv);
                }
            }
        } catch (ModelAndViewDefiningException ex) {
            logger.debug("ModelAndViewDefiningException encountered", ex);
            mv = ex.getModelAndView();
        } catch (Exception ex) {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(processedRequest, response, handler, ex);
            errorView = (mv != null);
        }

        // Did the handler return a view to render?
        if (mv != null && !mv.wasCleared()) {
            render(mv, processedRequest, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        } else {
            if (logger.isDebugEnabled()) {
                logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
                        "': assuming HandlerAdapter completed request handling");
            }
        }

        /** 此时所有的Interceptor都顺利完成了(preHandle -> handler -> postHandle),
         * 此时倒序依次执行所有的Interceptor的 afterCompletion方法
         * Trigger after-completion for successful outcome.
         * */
        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, null);
    } catch (Exception ex) {
        // Trigger after-completion for thrown exception.
        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
        throw ex;
    } catch (Error err) {
        ServletException ex = new NestedServletException("Handler processing failed", err);
        // Trigger after-completion for thrown exception.
        triggerAfterCompletion(mappedHandler, interceptorIndex, processedRequest, response, ex);
        throw ex;
    } finally {
        // Clean up any resources used by a multipart request.
        if (processedRequest != request) {
            cleanupMultipart(processedRequest);
        }
    }
}

private void triggerAfterCompletion(HandlerExecutionChain mappedHandler,
                                    int interceptorIndex,
                                    HttpServletRequest request,
                                    HttpServletResponse response,
                                    Exception ex) throws Exception {

    // Apply afterCompletion methods of registered interceptors.
    if (mappedHandler != null) {
        HandlerInterceptor[] interceptors = mappedHandler.getInterceptors();
        if (interceptors != null) {
            for (int i = interceptorIndex; i >= 0; i--) {
                HandlerInterceptor interceptor = interceptors[i];
                try {
                    interceptor.afterCompletion(request, response, mappedHandler.getHandler(), ex);
                } catch (Throwable ex2) {
                    logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
                }
            }
        }
    }
}

总结

image

《逃不开的经济周期》读书笔记

一 发现经济周期-繁荣与崩溃

反映货币供应量的指标

  • 货币(M0)=流通中的现金,即流通于银行体系之外的现金
  • 狭义货币(M1)=(M0)+活期存款
  • 广义货币(M2)=M1+准货币(定期存款+居民储蓄存款+其他存款+证券公司客户保证金+住房公积金中心存款+非存款类金融机构在存款类金融机构的存款)

萨伊定律:让·巴蒂斯特·萨伊1803书《政治经济学概论》中提出的,在商业活动中最容易的就是生产商品,而比较困难的是销售产品,为什么我们不能创造一个总能卖出所有东西的社会呢?当太多的生产手段被用来生产某一种产品,而其他产品的生产却不足,此时就会发生供给过剩的问题。

1862年克莱门特·朱格拉发表《论德、英、美三国经济危机及其发展周期》,他写道:“萧条的唯一原因就是繁荣。”

边际分析法:把追加的支出和追加的收入相比较,二者相等时为临界点,也就是投入的资金所得到的利益与输出损失相等时的点。如果组织的目标是取得最大利润,那么当追加的收入和追加的支出相等时,这一目标就能达到。

1885年,西蒙·纽科姆出版了一本《政治经济学原理》,提出了交易方程,该方程后来被欧文·费雪作为货币数量论重新介绍。MV=PQ,其中

M=货币供给
V=货币流通速度
P=商品与服务的价格
Q=商品与服务的数量

四 经济周期的精髓

理论与现实的背离

经济周期4个分类维度:

  • 周期:内生性模型假设不稳定性是由经济系统的非线性规律造成的。
  • 涟漪:外生性模型假设不稳定性来自于外部的冲击。
  • 可预测:决定论模型假设经济行为相对可预测,而且是有序的。
  • 混沌:随机模型假设行为是相对复杂的,而且是不可预测的。

术语解释

出清:出清意味着供求均衡。如果市场没有出清,那么一定存在着短缺或者过剩。经济学术语:当价格确实能使需求等于供给,以至于任何人可以在那个价格上买到他所要买的东西,或者卖掉他所要卖的东西。这时的市场就是出清的。

一般均衡理论:

  一般均衡理论(General Equilibrium Theory)是理论性的微观经济学的一个分支,寻求在整体经济的框架内解释生产、消费和价格。一般均衡是指经济中存在着这样一套价格系统,它能够使

每个消费者都能在给定价格下提供自己所拥有的生产要素,并在各自的预算限制下购买产品来达到自己的消费效用极大化
每个企业都会在给定的价格下决定其产量和对生产要素的需求,来达到其利润的极大化
每个市场(产品市场和要素市场)都会在这套价格体系下达到总供给与总需求的相等(均衡)
  当经济具备上述这样的条件时,就是达到一般均衡,这时的价格就是一般均衡价格。

亚当斯密的3个问题

  1. 经济周期难道不是一个难以理清的毛线团吗?

如何描述,类比共振问题 :+1

  • 除掉不稳定的根源 (除掉自动工资指数化)
  • 制造反向波,(消费不足理论)
  • 吸收振动,(转移支付)
  1. 为什么经济周期中的经济现象都表现明显的波动?
  • 锁模现象?许多无关联的过程自发的锁定彼此的节奏并创造出一个很强的加强运动
  • 集群现象?几个周期同时发生
  1. 经济周期在实践中最重要的表现是什么?

亚当·斯密:我认为人类在探究经济周期方面已经走得很远了,但是我们还要认识到,即使最优秀的专家对处于周期性背景下的经济运行提出合理建议时,他们也从来不会有什么绝对的把握。因此,研究经济周期这个现象总会具有挑战性,无论你是一位商业经理、金融投资者、企业家或者是财政大臣。

术语解释

工资指数化:是指工人的货币工资随物价指数浮动,按照价格指数自动调节收入的一种工资制度。

消费不足论:认为经济危机的根源在于人们对消费品的需求跟不上对消费品的生产。与可以投资的数量相比,富人或节俭的人得到过多的收入,因而造成整个社会消费不足,不能结清市场上的商品。

转移支付:是指政府或企业的一种不以购买本年的商品和劳务而作的支付,即政府或企业无偿地支付给个人或下级政府,以增加其收入和购买力的费用,它是一种收入再分配的形式。在萧条时期,就业减少,个人收入减少,符合接受福利支出的人增加了,从而作为转移支付之一的福利支出增加,抑制了个人消费的减少。在膨胀时期,就业增加,个人收入增加,符合接受福利支出的人减少了,从而作为转移支付之一的福利支出减少,抑制了个人消费的增加。

周期的主要驱动力

把全球经济看作是一台巨大的运作的机器,其中5个巨大的活塞上下运动的速度不通,5个活塞分别是 货币,资产,房地产建设,资本性支出,存货。

货币方面的周期驱动力 经济方面的周期驱动力
利息支付 资产价格,房地产价格,资本性支出,存货

术语解释

“财富效应”理论:股价上升会刺激消费,过度的消费反过来又会提高公司利润和就业,然后造成通货膨胀。利润的提高使股价继续上升,收入的增加又刺激着对股票的需求。然后股价的上升继续刺激消费,于是在理论的永动机上,整个循环将重新开始。

**银行的挑战

10个挑战:

  • 选择战略性方法,(利率,准备金要求)
  • 定义货币,Mx
  • 处理资产泡沫,资产价格是经济的晴雨表,能把未来的事件贴现
  • 测量货币流通速度V=PQ/M(GDP/货币总量),如果流通速度下降了,就会看到广义货币的表现超过了狭义货币
  • 阻止货币流通速度严重下降
  • 预测增长Q
  • 避免过度刺激,MV->Q->P
  • 预测通货膨胀
  • 处理外汇汇率
  • 维持信誉

1992索罗斯投机的过程:英镑在1990.10加入了ERM,英国经济处于严重的衰退期,通货膨胀率高,因为汇率水平限制,买进法国德国的债券,自然就赚了。

术语解释

空头:投机者认为某种商品、股票、债券等的价格将要下跌,于是卖出期货,希望在跌价后再买回或补进,获取差额利益。投机者在卖出后至未买回或补进前,手中并无实物,故称“空头”。

欧洲汇率机制:The European exchange rate mechanism,1979年创立,旨在限制欧盟成员国货币汇率波动的制度。 ERM为成员国货币的汇率设定固定的中心汇率,允许汇率在中心汇率上下一定的幅度内波动。

五 经济周期与资产价格

VIM之操纵缓冲区列表

引子

昨天遇到了一个奇怪的问题,一条在Sublime里面看着十分正常的SQL语句,执行了也是正常的,导出的Insert语句也是看着没毛病的,但是在查询出来,传递到下游的时候,说多了一个乱码。WTF,然后就开始了排查, 既然SQL语句没有问题,工程里面用到了mybatis mapper,然后就在查询出来的地方打印日志,发现的确查询出来有空格,此时甚至怀疑DB的问题(多么naive)。然后我就想为啥偏偏这个column有问题? 把这个column缓存其他的数字,是OK的,那么原本的数字有何问题呢?因为是集成测试环境无法执行SQL,每次都是工单执行的,这一次为了复现直接登mysql客户端,贴入SQL语句,在terminal上看的很清晰就是在这个数字后面多了个不可见字符。在sublime里面试了很多方法看不出来是啥,最后用vim打开,一目了然。

这个不可见字符就是 <202c>。

经验总结:

  • 这个数字是我从一个富文本(Excel)地方拷贝到文本编辑器的,后面这里要注意。
  • 准备推到线上的配置,dev一定要验证一遍,像这种问题谁能预料到?
  • 还是VIM大法好!

正文

VIM可以同时打开多个文件,缓冲区(Buffer)列表记录了本次编辑会话中打开的所有文件,”缓冲区“意味着我们编辑的是文件在内存中的映像。

同时打开多个文件

➜  ~ vim a.txt b.txt c.txt d.txt

此时VIM会打个多个文件(缓冲区),窗口显示的是第一个文件。

展示缓冲区列表

: ls 命令会列出此次打开的缓冲区列表。

  1 #    "a.txt"                        line 1
  2 %a   "b.txt"                        line 1
  3      "c.txt"                        line 0
  4      "d.txt"                        line 0

%符号表名当前窗口展示的文件, #表示上次从这个文件轮换过来。

切换文件(缓冲区)

: bnext 会切换到下一个缓冲区。

Ctrl + ^ 可以在当前文件和轮换文件(#指示的)快速切换。

:bprev bnext 在列表中正向、反向移动一项。

:first :blast 分别跳到列表的第一个、最后一个文件。

:buffer N 直接根据编号跳到对应的文件。

删除缓冲区

一般使用的比较少。

:bdelete N 删除对应编号的缓冲区。

image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.