美文网首页
20190404_ARTS_W00

20190404_ARTS_W00

作者: 活出野性的自己 | 来源:发表于2019-04-09 17:33 被阅读0次

    2019第一篇arts, 果然学习都是反人性的

    **Algorithm**
    每周至少做一个leetcode算法题
    
    **Review**
    阅读并点评至少一篇英文技术文章
    (英文论文文献)
    
    **Tip**
    至少学习一个技术技巧
    
    **Share**
    分享一篇有观点和思考的技术文章
    

    Algorithm

    看了下数据结构与算法之美的A*搜索算法:是一种基于Dijkstra算法优化的次优解算法,意在减少搜索时间,不保证是最优解

    自己按照思路简单画了个导图如下:


    49-A星搜索算法实现游戏中的寻路功能.png

    大概实现了下,还好之前有Dijkstra算法的基础,不然一下午根本搞不定:

    1. 基本数据结构
    //边
    public class Edge {
    
        int start;
        int end;
        int weight;
        public Edge(int start, int end,int weight){
            this.start = start;
            this.end = end;
            this.weight = weight;
        }
    }
    
    //节点坐标信息类
    public class Vertx {
    
        int nodeValue; //图中节点值
        int distance;  //节点距离起点的最小值
    
        int x,y; //节点在xy坐标系中的坐标
    
        int f; //f=distance+节点距离终点的曼哈顿距离(|x1-x2|+|y1-y2|)
    
        public Vertx(int nodeValue, int x, int y){
            this.nodeValue = nodeValue;
            this.x = x;
            this.y = y;
        }
    
    }
    
    //相应的图结构,包括邻接表,各个顶点的坐标数组等
    public class AStarGraph {
    
        int v;
        LinkedList<Edge>[] adj; //邻接表
        Vertx[] vertxes;
        public AStarGraph(int v){
            this.v = v;
            adj = new LinkedList[v];
            for(int i=0;i<v;i++){
                adj[i] = new LinkedList<>();
            }
    
            vertxes = new Vertx[v];
        }
    
        //s->t,权重为weight
        public void addEdge(int s, int t, int weight){
            adj[s].add(new Edge(s,t,weight));
        }
    
        //添加顶点的坐标
        public void addVertx(int id, int x, int y){
            vertxes[id] = new Vertx(id,x,y);
        }
    }
    
    1. 优先级队列: 根据Vertex.f构建小顶堆
    package com.zxb.structurealgo.AStarAlgo49;
    
    /**
     * @ClassName AStarPriorityQueue
     * @Description 优先级队列,根据Vertex.f构建小顶堆
     * @Author xuery
     * @Date 2019/3/21 20:43
     * @Version 1.0
     */
    public class AStarPriorityQueue {
    
        Vertx[] nodes; //堆数组
        int count;     //当前队列实际存在多少个值
    
        public AStarPriorityQueue(int v){
            /**
             * 多加1,从下标为1的位置开始存数据,方便计算堆的节点的左右子树
             * 下标为i的左右子树为:2*i,2*i+1,父节点为i/2
             */
            nodes = new Vertx[v+1];
            count = 0; //专栏里面应该是写错了
        }
    
        public Vertx poll(){
            if(count == 0){
                return null;
            }
            /**
             * 下标为1的元素就是要出队列的元素,
             * 利用小顶堆性质:将下标为1的元素与最后一个元素交换,最后一个元素出队列并将count减1,之后从第一个元素开始向下堆化
             * 假设堆化时有n个元素,下标1...n,则只需要堆化到n/2即可,因为后续的都是叶子节点,举个例子就知道了
             */
            Vertx pollVertx = nodes[1];
            swap(nodes, 1, count);
            count--;
            //从1-count/2开始堆化
            int i=1;
            while(count > 1 && i <= count/2){
                //如果i比它的左右叶子节点2*i,2*i+1大则继续调整
                int f = nodes[i].f;
                int swapIndex = -1;
                if(f > nodes[2*i].f){
                    f = nodes[2*i].f;
                    swapIndex = 2*i;
                }
                if(2*i+1 <= count && f > nodes[2*i+1].f){
                    swapIndex = 2*i+1;
                }
                if(swapIndex != -1){
                    swap(nodes,i,swapIndex);
                    i = swapIndex;
                } else {
                    break;
                }
            }
            return pollVertx;
        }
    
        private void swap(Vertx[] nodes, int i, int j){
            Vertx tmp = nodes[i];
            nodes[i] = nodes[j];
            nodes[j] = tmp;
        }
    
        public void add(Vertx vertx){
            /**
             * 小顶堆插入数据,count加1,先将其插入末尾
             * 然后从末尾开始往上按照小顶堆堆化
             */
            count++;
            nodes[count] = vertx;
            int i = count; //i的父节点为i/2
            while(i/2 >=1){
                if(nodes[i].f < nodes[i/2].f){
                    swap(nodes,i,i/2);
                    i = i/2;
                } else {
                    break;
                }
            }
        }
    
        public void update(Vertx vertx){
            /**
             * 先根据vertex.nodeValue找到其在堆数组中的位置并更新它的f
             * 之后从该点开始,在这里之所以更新它是因为找到了更小的f,
             * 所以从该点开始向上按小顶堆堆化即可,画图举例就知道了
             *
             * 这个查找复杂度为O(n),有点高,这里其实可以通过构造map来实现快速查找<nodeValue,在数组中的位置>
             */
            int updateIndex = -1;
            for(int i=1;i<=count;i++){
                if(nodes[i].nodeValue == vertx.nodeValue){
                    updateIndex = i;
                    break;
                }
            }
            if(updateIndex != -1){
                nodes[updateIndex].f = vertx.f;
            }
            int i = updateIndex;//从updateIndex开始向上堆化
            while(i/2 >=1){
                if(nodes[i].f < nodes[i/2].f){
                    swap(nodes,i,i/2);
                    i = i/2;
                } else {
                    break;
                }
            }
    
        }
    
        public boolean isEmpty(){
            return count == 0;
        }
    
        public void clear(){
            count = 0;
        }
    }
    
    
    1. 算法具体实现类
    package com.zxb.structurealgo.AStarAlgo49;
    
    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.List;
    
    /**
     * @ClassName AStarAlgo
     * @Description A*搜索算法,快速找出一条次优路径解,参考49讲
     *
     * 与Dijkstra算法的区别:
     * 1. 节点引入(x,y)属性,出队列顺序要综合顶点离起点的距离+顶点离终点的距离=f
     * 2. 距离等于1中两者之和,新增属性f=g+h;
     * 3. 遍历到终点就结束,所以不保证最优解
     * @Author xuery
     * @Date 2019/4/9 15:27
     * @Version 1.0
     */
    public class AStarAlgo {
    
        public static void main(String[] args) {
    
            AStarGraph graph = AStarGraphGenUtil.aStarGraphGen2();
    
            AStarAlgo aStarAlgo = new AStarAlgo();
            List<Integer> result = aStarAlgo.aStarAlgo(graph,2,5);
            System.out.println(result);
        }
    
        public List<Integer> aStarAlgo(AStarGraph graph, int start, int end){
    
            //优先级队列,用于每次取距离最短的哪个节点,这里java自带的优先级队列没有提供update接口,只能自己实现一个了
            int v = graph.v;
            LinkedList<Edge>[] adj = graph.adj;
            Vertx[] vertxes = graph.vertxes;
            AStarPriorityQueue queue = new AStarPriorityQueue(v);
    
            //preNode数组,这里节点取值是0-v-1, 所以直接用数组就可以了;如果不是的话就要用map了
            int[] preNodeArr = new int[v];
            boolean[] visited = new boolean[v];
            int[] currMinDisArr = new int[v];
    
            //start作为第一个点,然后根据当前节点广度优先遍历其后继节点
            Vertx vertx0 = vertxes[start];
            queue.add(vertx0);
            preNodeArr[start] = -1;
            visited[start] = true;
            while(!queue.isEmpty()){
                Vertx currMinFVertx = queue.poll();
                //看它的后继节点
                LinkedList<Edge> nextEdges = adj[currMinFVertx.nodeValue];
                for(int i=0;i<nextEdges.size();i++){
                    Edge nextEdge = nextEdges.get(i);
                    if(!visited[nextEdge.end]){
                        //该节点之前没有进入过队列,说明是第一次遍历到它
                        Vertx vertx = vertxes[nextEdge.end];
                        vertx.distance = currMinFVertx.distance + nextEdge.weight;
                        vertx.f = vertx.distance + Math.abs(vertx.x-vertxes[end].x)+Math.abs(vertx.y-vertxes[end].y);
                        queue.add(vertx);
    
                        preNodeArr[nextEdge.end] = currMinFVertx.nodeValue;
                        visited[nextEdge.end] = true;
                        currMinDisArr[nextEdge.end] = vertx.distance;
                    } else {
                        //非第一次遍历到它,需要比较下currMinFVertx.distance+nextEdge.weight与nextEdge.end对应的distance
                        //这里不能每次都去队列中查对应的数据比较,太慢了,构建一个辅助数组
                        int currDis = currMinFVertx.distance+nextEdge.weight;
                        if(currMinDisArr[nextEdge.end] > currDis){
                            //需要更新一波
                            Vertx vertx = vertxes[nextEdge.end];
                            vertx.distance = currDis;
                            vertx.f = vertx.distance + Math.abs(vertx.x-vertxes[end].x)+Math.abs(vertx.y-vertxes[end].y);
                            queue.update(vertx);
    
                            preNodeArr[nextEdge.end] = currMinFVertx.nodeValue;
                            currMinDisArr[nextEdge.end] = currDis;
                        }
                    }
    
                    //截止条件:遍历到end节点就结束
                    if(nextEdge.end == end){
                        queue.clear();
                        break;
                    }
    
                }
            }
    
            //根据preNodeArr回溯
            List<Integer> resultList = new ArrayList<>();
            int index = end;
            while(index != -1){
                resultList.add(index);
                index = preNodeArr[index];
            }
            return resultList;
        }
    }
    
    
    1. 简单的图产生测试用例
    package com.zxb.structurealgo.AStarAlgo49;
    
    
    /**
     * @ClassName AStarGraphGenUtil
     * @Description 拓扑图生成类
     * @Author xuery
     * @Date 2019/3/21 10:56
     * @Version 1.0
     */
    public class AStarGraphGenUtil {
    
    
        /**
         *
         * @return
         */
        public static AStarGraph aStarGraphGen1(){
    
            AStarGraph graph = new AStarGraph(6);
    
            graph.addEdge(0,1,1);
            graph.addEdge(0,2,3);
            graph.addEdge(1,2,1);
            graph.addEdge(1,3,2);
            graph.addEdge(2,3,1);
            graph.addEdge(3,4,2);
            graph.addEdge(3,5,5);
            graph.addEdge(4,5,2);
    
            graph.addVertx(0,0,0);
            graph.addVertx(1,0,0);
            graph.addVertx(2,0,0);
            graph.addVertx(3,0,0);
            graph.addVertx(4,0,0);
            graph.addVertx(5,0,0);
    
            return graph;
        }
    
        public static AStarGraph aStarGraphGen2(){
    
            AStarGraph graph = new AStarGraph(6);
    
            graph.addEdge(2,0,4);
            graph.addEdge(2,1,2);
            graph.addEdge(1,0,1);
            graph.addEdge(0,5,2);
            graph.addEdge(2,3,4);
            graph.addEdge(2,4,5);
            graph.addEdge(3,5,6);
            graph.addEdge(4,5,7);
    
            graph.addVertx(0,0,0);
            graph.addVertx(1,1,1);
            graph.addVertx(2,2,2);
            graph.addVertx(3,3,3);
            graph.addVertx(4,4,4);
            graph.addVertx(5,5,5);
    
            return graph;
        }
    }
    
    

    Review

    Raft算法
    今天大概看了下Raft算法,理解了一些基础的东西,做下总结,希望自己之后可以更深入的理解

    1. Raft算法出现的目的

    因为PAXOS算法过于晦涩,常人难以理解,且落地困难。想要找到一种这样的一致性算法:可实现性强,减少开发的工作量;必须安全且大部分情况下是可用的;大部分操作必须是高效的;可理解性强,Raft一致性算法应运而生。

    2. Raft算法的核心
    领导选举

    集群中的每个节点有三种状态:Follower(跟随者),Candidate(参与选举者),Leader(领导者);
    所有节点的初始状态为Follower, 每个节点都有一个随机时间,超过随机时间时,如果相应的Follower没有收到其他节点的选举信息,则会变成Candidate(先会给自己投一票), 再向其余的节点发送参与选举领导者请求并接收其余节点的投票,如果投票数超过一半则节点由Candidate变成Leader, 选举完毕,保证每次最多只会选出一个领导者。

    有两个timeout设置来保证领导选举的顺利进行:一个是election timeout,它就是上面所说的每个Follower有一个随机的election timeout, 超过这个时间则节点由Follower变成Candidate状态,一般设置为150ms-300ms, 这里每个节点的election timeout可能都是不一样的,可以保证尽快的选出领导(可以想想如果都是一个确定的值,每个节点都同时由Follower变成Candidate,那样选出领导的速度肯定会变慢的);一个是heartbeats timeout, 一旦确定领导者,领导者会定时向Follower发送心跳,来保证领导者正常的情况下,其他Follower不会变成Candidate

    当领导者挂了之后,重新开始选举流程:由于heartbeats timeout了,所以会有节点从Follower变成Candidate, 然后跟之前一样选举出领导者即可

    当多个Follower同时变成Candidate,且投票结果Candidate都没有超过半数以上,则等待下一轮超时重新选举,直到选举出leader.(注:每次选举的时候term会加1)

    日志复制

    选好领导者之后开始日志复制,就是领导者将更新通知到Follower, 要保证所有节点的更新数据的一致性,主要流程如下:当客户端发起一个更新请求会先给到leader,leader会将更新记录追加到它的日志末尾,然后leader会将更新发给其他的Follower, 同理Follower也会记录日志到其对应的日志末尾并ack一个信号给leader, 当leader收到超过半数的ack之后,会commit更新并通知Follower也commit这个更新。(有点类似于2PC,先prepare再commit)

    当发生脑裂时,raft算法还可能可以保证一致性,这是由于要半数以上ack所决定的,即使不返回数据,也不会返回错误数据。

    参考:http://thesecretlivesofdata.com/raft/

    Tip

    项目中一个接口列表慢查询优化实践

    背景:项目开发中提供给外部系统一个列表查询接口,在测试环境验证响应时间1s左右(现在看来也挺慢的_),感觉问题不大,上了生产之后调用方反馈接口响应时间太长了,有些时候甚至超过5s,显然这是不能忍受的,已经超过http的超时时间了,通过响应参数发现,这个列表查询每次能返回100多条数据,之前完全没预料到,于是乎开始优化

    代码大概是下面这样的:

    List<RouteRes> result = new ArrayList<>();
    //根据条件查询出符合条件的task列表
    List<Task> tasks = queryTaskService.getTaskByCondition(map);
    //将task列表按照routeId分组
    Map<String> taskMap = tasks.stream.collect(Collectors.groupingBy(event -> event.getRouteId));
    taskMap.forEach((routeId, tasks) -> {
        //根据routeId查询对应的Route信息,这里面会查缓存,带Biz后缀的类都会加事务
       long t1 = System.System.currentTimeMillis();
        Route route = routeServiceBiz.getRouteByRouteId(routeId);
        long t2 = System.System.currentTimeMillis();
        //组装RouteRes
        RouteRes routeRes = routeServiceBiz.buildRouteRes(route);
       result.add(routeRes);
    });
    return result;
    
    1. 首先之前在taskMap.forEach((routeId, tasks)循环里面,调了一个不应该调的rpc接口(复用原来的接口导致的呜呜呜),代码里没有列出来,每次rpc大概20-30ms, 100多次循环就是2-3s,去除掉基本就节约一半时间了
    2. 还是需要2s多,不正常,再来;分析来分析去只有循环里面查缓存比较耗时,按经验缓存查询一次最多5ms,100多次也就500ms,跟2s相差还是很多啊;那就把在routeServiceBiz.getRouteByRouteId里面把查缓存的时间全部打印出来,结果发现一次缓存大概2-3ms,100多次就是200-300ms,那时间究竟花在哪里了呢?
    3. 百思不得奇解,最后加了上面的t1,t2,计算出所有循环t2-t1的累计值发现比查缓存的累积值要大很多;再想想,外层只是通过方法调用了一下而已啊;不对每次方法调用都会开启一次事务,基本可以确定了循环100多次就有100多次事务,主要时间花在了事务上
    4. 解决方法:去除掉事务,查询根本不需要事务,之后重新查询发现200ms左右,优化完毕

    总结:循环中最好不要循环加事务,当循环次数多的时候,事务占用的时间非常多;造成这样的最终原因还是因为采用的框架,Biz结尾的service所有方法都会默认加上事务,所以对于项目中的查询方法不要写在Biz结尾的service层,应该重新建一个不带Biz结尾的service,将相关的查询方法写到里面去

    Share

    其实买极客时间的专栏,如果你能认真的去看完并做笔记和思考,收获还是蛮多的,当然专栏只是把主要的知识点罗列出来,如果真的想要深入理解的话,可以借助专栏的思路去看相关的书籍和英文官方文档,一步一步拓展,那你习得的知识将会比大部分人都要深。

    相关文章

      网友评论

          本文标题:20190404_ARTS_W00

          本文链接:https://www.haomeiwen.com/subject/uboxiqtx.html